//! AVM1 object type to represent the attributes of XML nodes use crate::avm1::error::Error; use crate::avm1::function::Executable; use crate::avm1::object::{ObjectPtr, TObject}; use crate::avm1::property::Attribute; use crate::avm1::return_value::ReturnValue; use crate::avm1::{Avm1, Object, ScriptObject, UpdateContext, Value}; use crate::xml::{XMLName, XMLNode}; use enumset::EnumSet; use gc_arena::{Collect, MutationContext}; use std::borrow::Cow; use std::fmt; /// A ScriptObject that is inherently tied to an XML node's attributes. /// /// Note that this is *not* the same as the XMLNode object itself; for example, /// `XMLNode`s must store both their base object and attributes object /// separately. #[derive(Clone, Copy, Collect)] #[collect(no_drop)] pub struct XMLAttributesObject<'gc>(ScriptObject<'gc>, XMLNode<'gc>); impl<'gc> XMLAttributesObject<'gc> { /// Construct an XMLAttributesObject for an already existing node's /// attributes. pub fn from_xml_node( gc_context: MutationContext<'gc, '_>, xml_node: XMLNode<'gc>, ) -> Object<'gc> { XMLAttributesObject(ScriptObject::object(gc_context, None), xml_node).into() } fn base(&self) -> ScriptObject<'gc> { match self { XMLAttributesObject(base, ..) => *base, } } fn node(&self) -> XMLNode<'gc> { match self { XMLAttributesObject(_, node) => *node, } } } impl fmt::Debug for XMLAttributesObject<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { XMLAttributesObject(base, node) => f .debug_tuple("XMLAttributesObject") .field(base) .field(node) .finish(), } } } impl<'gc> TObject<'gc> for XMLAttributesObject<'gc> { fn get_local( &self, name: &str, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, ) -> Result, Error> { Ok(self .node() .attribute_value(&XMLName::from_str(name)) .map(|s| s.into()) .unwrap_or_else(|| Value::Undefined)) } fn set( &self, name: &str, value: Value<'gc>, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, ) -> Result<(), Error> { self.node().set_attribute_value( context.gc_context, &XMLName::from_str(name), &value.coerce_to_string(avm, context)?, ); self.base().set(name, value, avm, context) } fn call( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, base_proto: Option>, args: &[Value<'gc>], ) -> Result, Error> { self.base().call(avm, context, this, base_proto, args) } fn call_setter( &self, name: &str, value: Value<'gc>, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, this: Object<'gc>, ) -> Result, Error> { self.base().call_setter(name, value, avm, context, this) } #[allow(clippy::new_ret_no_self)] fn new( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, _this: Object<'gc>, _args: &[Value<'gc>], ) -> Result, Error> { //TODO: `new xmlnode.attributes()` returns undefined, not an object log::warn!("Cannot create new XML Attributes object"); Ok(Value::Undefined.coerce_to_object(avm, context)) } fn delete( &self, avm: &mut Avm1<'gc>, gc_context: MutationContext<'gc, '_>, name: &str, ) -> bool { self.node() .delete_attribute(gc_context, &XMLName::from_str(name)); self.base().delete(avm, gc_context, name) } fn add_property( &self, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, set: Option>, attributes: EnumSet, ) { self.base() .add_property(gc_context, name, get, set, attributes) } fn add_property_with_case( &self, avm: &mut Avm1<'gc>, gc_context: MutationContext<'gc, '_>, name: &str, get: Executable<'gc>, set: Option>, attributes: EnumSet, ) { self.base() .add_property_with_case(avm, gc_context, name, get, set, attributes) } fn define_value( &self, gc_context: MutationContext<'gc, '_>, name: &str, value: Value<'gc>, attributes: EnumSet, ) { self.base() .define_value(gc_context, name, value, attributes) } fn set_attributes( &mut self, gc_context: MutationContext<'gc, '_>, name: Option<&str>, set_attributes: EnumSet, clear_attributes: EnumSet, ) { self.base() .set_attributes(gc_context, name, set_attributes, clear_attributes) } fn proto(&self) -> Option> { self.base().proto() } fn set_proto(&self, gc_context: MutationContext<'gc, '_>, prototype: Option>) { self.base().set_proto(gc_context, prototype); } fn has_property( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { self.base().has_property(avm, context, name) } fn has_own_property( &self, _avm: &mut Avm1<'gc>, _context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { self.node() .attribute_value(&XMLName::from_str(name)) .is_some() } fn has_own_virtual( &self, avm: &mut Avm1<'gc>, context: &mut UpdateContext<'_, 'gc, '_>, name: &str, ) -> bool { self.base().has_own_virtual(avm, context, name) } fn is_property_overwritable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { self.base().is_property_overwritable(avm, name) } fn is_property_enumerable(&self, avm: &mut Avm1<'gc>, name: &str) -> bool { self.base().is_property_enumerable(avm, name) } fn get_keys(&self, avm: &mut Avm1<'gc>) -> Vec { self.base().get_keys(avm) } fn as_string(&self) -> Cow { Cow::Owned(self.base().as_string().into_owned()) } fn type_of(&self) -> &'static str { self.base().type_of() } fn interfaces(&self) -> Vec> { self.base().interfaces() } fn set_interfaces(&mut self, context: MutationContext<'gc, '_>, iface_list: Vec>) { self.base().set_interfaces(context, iface_list) } fn as_script_object(&self) -> Option> { Some(self.base()) } fn as_xml_node(&self) -> Option> { Some(self.node()) } fn as_ptr(&self) -> *const ObjectPtr { self.base().as_ptr() as *const ObjectPtr } fn length(&self) -> usize { self.base().length() } fn array(&self) -> Vec> { self.base().array() } fn set_length(&self, gc_context: MutationContext<'gc, '_>, length: usize) { self.base().set_length(gc_context, length) } fn array_element(&self, index: usize) -> Value<'gc> { self.base().array_element(index) } fn set_array_element( &self, index: usize, value: Value<'gc>, gc_context: MutationContext<'gc, '_>, ) -> usize { self.base().set_array_element(index, value, gc_context) } fn delete_array_element(&self, index: usize, gc_context: MutationContext<'gc, '_>) { self.base().delete_array_element(index, gc_context) } }