avm1: Store childNodes on the node itself

This commit is contained in:
Adrian Wielgosik 2023-09-26 00:01:54 +02:00 committed by Nathan Adams
parent b427940431
commit a8ea913bf6
3 changed files with 50 additions and 9 deletions

View File

@ -222,6 +222,8 @@ impl<'gc> Xml<'gc> {
}
}
self.root().refresh_cached_child_nodes(activation).unwrap(); // :(
Ok(())
}

View File

@ -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<Value<'gc>, 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<Value<'gc>, 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)

View File

@ -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<ArrayObject<'gc>>,
/// Child nodes of this element.
children: Vec<XmlNode<'gc>>,
}
@ -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<ArrayObject<'gc>, 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(),
},
));