diff --git a/core/src/avm1/globals/xml.rs b/core/src/avm1/globals/xml.rs index 45eebf34b..033755c50 100644 --- a/core/src/avm1/globals/xml.rs +++ b/core/src/avm1/globals/xml.rs @@ -222,6 +222,8 @@ impl<'gc> Xml<'gc> { } } + self.root().refresh_cached_child_nodes(activation).unwrap(); // :( + Ok(()) } diff --git a/core/src/avm1/globals/xml_node.rs b/core/src/avm1/globals/xml_node.rs index ea7e5db57..c1692c434 100644 --- a/core/src/avm1/globals/xml_node.rs +++ b/core/src/avm1/globals/xml_node.rs @@ -3,7 +3,7 @@ use crate::avm1::activation::Activation; use crate::avm1::error::Error; use crate::avm1::property_decl::{define_properties_on, Declaration}; -use crate::avm1::{ArrayObject, NativeObject, Object, ScriptObject, TObject, Value}; +use crate::avm1::{NativeObject, Object, ScriptObject, TObject, Value}; use crate::context::GcContext; use crate::string::{AvmString, WStr}; use crate::xml::{XmlNode, TEXT_NODE}; @@ -64,6 +64,7 @@ fn append_child<'gc>( if !xmlnode.has_child(child_xmlnode) { let position = xmlnode.children_len(); xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode); + xmlnode.refresh_cached_child_nodes(activation)?; } } @@ -85,6 +86,7 @@ fn insert_before<'gc>( if !xmlnode.has_child(child_xmlnode) { if let Some(position) = xmlnode.child_position(insertpoint_xmlnode) { xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode); + xmlnode.refresh_cached_child_nodes(activation)?; } } } @@ -173,7 +175,11 @@ fn remove_node<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(mut node) = this.as_xml_node() { + let old_parent = node.parent(); node.remove_node(activation.context.gc_context); + if let Some(old_parent) = old_parent { + old_parent.refresh_cached_child_nodes(activation)?; + } } Ok(Value::Undefined) @@ -275,13 +281,7 @@ fn child_nodes<'gc>( _args: &[Value<'gc>], ) -> Result, Error<'gc>> { if let Some(node) = this.as_xml_node() { - return Ok(ArrayObject::new( - activation.context.gc_context, - activation.context.avm1.prototypes().array, - node.children() - .map(|mut child| child.script_object(activation).into()), - ) - .into()); + return Ok(node.get_or_init_cached_child_nodes(activation)?.into()); } Ok(Value::Undefined) diff --git a/core/src/xml/tree.rs b/core/src/xml/tree.rs index 2aadb79b0..8c03295e6 100644 --- a/core/src/xml/tree.rs +++ b/core/src/xml/tree.rs @@ -2,7 +2,7 @@ use crate::avm1::Attribute; use crate::avm1::{Activation, NativeObject}; -use crate::avm1::{Error, Object, ScriptObject, TObject, Value}; +use crate::avm1::{ArrayObject, Error, Object, ScriptObject, TObject, Value}; use crate::string::{AvmString, WStr, WString}; use crate::xml; use gc_arena::{Collect, GcCell, Mutation}; @@ -45,6 +45,9 @@ pub struct XmlNodeData<'gc> { /// Attributes of the element. attributes: ScriptObject<'gc>, + /// The array object used for AS2 `.childNodes` + cached_child_nodes: Option>, + /// Child nodes of this element. children: Vec>, } @@ -62,6 +65,7 @@ impl<'gc> XmlNode<'gc> { node_type, node_value, attributes: ScriptObject::new(mc, None), + cached_child_nodes: None, children: Vec::new(), }, )) @@ -373,6 +377,40 @@ impl<'gc> XmlNode<'gc> { self.0.read().attributes } + /// Gets a lazy-created .childNodes array + pub fn get_or_init_cached_child_nodes( + &self, + activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + let array = self.0.read().cached_child_nodes; + if let Some(array) = array { + Ok(array) + } else { + let array = ArrayObject::empty(activation); + self.0 + .write(activation.context.gc_context) + .cached_child_nodes = Some(array); + self.refresh_cached_child_nodes(activation)?; + Ok(array) + } + } + + /// Refreshes the .childNodes array. Call this after every child list mutation. + pub fn refresh_cached_child_nodes( + &self, + activation: &mut Activation<'_, 'gc>, + ) -> Result<(), Error<'gc>> { + let array = self.0.read().cached_child_nodes; + if let Some(array) = array { + array.set_length(activation, 0)?; + for (i, mut child) in self.children().enumerate() { + let value = child.script_object(activation).into(); + array.set_element(activation, i as i32, value)?; + } + } + Ok(()) + } + /// Create a duplicate copy of this node. /// /// If the `deep` flag is set true, then the entire node tree will be cloned. @@ -392,6 +430,7 @@ impl<'gc> XmlNode<'gc> { node_type: self.0.read().node_type, node_value: self.0.read().node_value, attributes, + cached_child_nodes: None, children: Vec::new(), }, ));