From c04b463f1f49136d176078ecc388ca46379c56b6 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Mon, 27 Feb 2023 12:56:32 -0600 Subject: [PATCH] avm2: Implement XML.children, XMLList.children, and related methods --- core/src/avm2/globals/XML.as | 10 ++++- core/src/avm2/globals/XMLList.as | 1 + core/src/avm2/globals/xml.rs | 24 +++++++++++- core/src/avm2/globals/xml_list.rs | 18 ++++++++- core/src/avm2/object/xml_list_object.rs | 31 ++++++++++++++++ core/src/avm2/object/xml_object.rs | 35 ++++++++++++++---- tests/tests/swfs/avm2/xml_children/Test.as | 26 +++++++++++++ tests/tests/swfs/avm2/xml_children/output.txt | 10 +++++ tests/tests/swfs/avm2/xml_children/test.fla | Bin 0 -> 3815 bytes tests/tests/swfs/avm2/xml_children/test.swf | Bin 0 -> 949 bytes tests/tests/swfs/avm2/xml_children/test.toml | 1 + 11 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 tests/tests/swfs/avm2/xml_children/Test.as create mode 100644 tests/tests/swfs/avm2/xml_children/output.txt create mode 100644 tests/tests/swfs/avm2/xml_children/test.fla create mode 100644 tests/tests/swfs/avm2/xml_children/test.swf create mode 100644 tests/tests/swfs/avm2/xml_children/test.toml diff --git a/core/src/avm2/globals/XML.as b/core/src/avm2/globals/XML.as index 2460966b5..296246505 100644 --- a/core/src/avm2/globals/XML.as +++ b/core/src/avm2/globals/XML.as @@ -10,6 +10,8 @@ package { AS3 native function name():Object; AS3 native function localName():Object; AS3 native function toXMLString():String; + AS3 native function children():XMLList; + AS3 native function toString():String; @@ -32,6 +34,11 @@ package { return self.AS3::toXMLString(); }; + prototype.children = function():String { + var self:XML = this; + return self.AS3::children(); + }; + prototype.toString = function():String { if (this === prototype) { return ""; @@ -39,6 +46,5 @@ package { var self:XML = this; return self.AS3::toString(); }; - } -} \ No newline at end of file +} diff --git a/core/src/avm2/globals/XMLList.as b/core/src/avm2/globals/XMLList.as index 82f253fd0..e05ad86c4 100644 --- a/core/src/avm2/globals/XMLList.as +++ b/core/src/avm2/globals/XMLList.as @@ -10,6 +10,7 @@ package { AS3 native function hasSimpleContent():Boolean; AS3 native function length():int + AS3 native function children():XMLList; public native function toString():String; } diff --git a/core/src/avm2/globals/xml.rs b/core/src/avm2/globals/xml.rs index 6a623b4c1..338000ea9 100644 --- a/core/src/avm2/globals/xml.rs +++ b/core/src/avm2/globals/xml.rs @@ -1,8 +1,8 @@ //! XML builtin and prototype -use crate::avm2::e4x::E4XNode; +use crate::avm2::e4x::{E4XNode, E4XNodeKind}; pub use crate::avm2::object::xml_allocator; -use crate::avm2::object::{QNameObject, TObject}; +use crate::avm2::object::{E4XOrXml, QNameObject, TObject, XmlListObject}; use crate::avm2::{Activation, Error, Object, QName, Value}; use crate::avm2_stub_method; @@ -81,3 +81,23 @@ pub fn to_xml_string<'gc>( let node = xml.node(); Ok(Value::String(node.xml_to_xml_string(activation)?)) } + +pub fn children<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let xml = this.unwrap().as_xml_object().unwrap(); + let children = if let E4XNodeKind::Element { children, .. } = &*xml.node().kind() { + // FIXME - avoid clone + children.clone() + } else { + Vec::new() + }; + + Ok(XmlListObject::new( + activation, + children.iter().map(|node| E4XOrXml::E4X(*node)).collect(), + ) + .into()) +} diff --git a/core/src/avm2/globals/xml_list.rs b/core/src/avm2/globals/xml_list.rs index eaba99f98..29a628664 100644 --- a/core/src/avm2/globals/xml_list.rs +++ b/core/src/avm2/globals/xml_list.rs @@ -4,7 +4,7 @@ pub use crate::avm2::object::xml_list_allocator; use crate::{ avm2::{ e4x::{simple_content_to_string, E4XNode, E4XNodeKind}, - object::E4XOrXml, + object::{E4XOrXml, XmlListObject}, Activation, Error, Object, TObject, Value, }, avm2_stub_method, @@ -79,3 +79,19 @@ pub fn length<'gc>( let children = list.children(); Ok(children.len().into()) } + +pub fn children<'gc>( + activation: &mut Activation<'_, 'gc>, + this: Option>, + _args: &[Value<'gc>], +) -> Result, Error<'gc>> { + let list = this.unwrap().as_xml_list_object().unwrap(); + let children = list.children(); + let mut sub_children = Vec::new(); + for child in &*children { + if let E4XNodeKind::Element { ref children, .. } = &*child.node().kind() { + sub_children.extend(children.iter().map(|node| E4XOrXml::E4X(*node))); + } + } + Ok(XmlListObject::new(activation, sub_children).into()) +} diff --git a/core/src/avm2/object/xml_list_object.rs b/core/src/avm2/object/xml_list_object.rs index f371e33d2..51bebf6db 100644 --- a/core/src/avm2/object/xml_list_object.rs +++ b/core/src/avm2/object/xml_list_object.rs @@ -195,4 +195,35 @@ impl<'gc> TObject<'gc> for XmlListObject<'gc> { ) -> Result<(), Error<'gc>> { Err("Modifying an XMLList object is not yet implemented".into()) } + + fn get_next_enumerant( + self, + last_index: u32, + _activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + let read = self.0.read(); + if (last_index as usize) < read.children.len() { + return Ok(Some(last_index + 1)); + } + Ok(None) + } + + fn get_enumerant_name( + self, + index: u32, + _activation: &mut Activation<'_, 'gc>, + ) -> Result, Error<'gc>> { + let children_len = self.0.read().children.len() as u32; + if children_len >= index { + Ok(index + .checked_sub(1) + .map(|index| index.into()) + .unwrap_or(Value::Undefined)) + } else { + Ok(self + .base() + .get_enumerant_name(index - children_len) + .unwrap_or(Value::Undefined)) + } + } } diff --git a/core/src/avm2/object/xml_object.rs b/core/src/avm2/object/xml_object.rs index d244eb036..9a04dbe88 100644 --- a/core/src/avm2/object/xml_object.rs +++ b/core/src/avm2/object/xml_object.rs @@ -149,12 +149,33 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> { read.base.get_property_local(name, activation) } - fn set_property_local( - self, - _name: &Multiname<'gc>, - _value: Value<'gc>, - _activation: &mut Activation<'_, 'gc>, - ) -> Result<(), Error<'gc>> { - Err("Modifying an XML object is not yet implemented".into()) + fn has_own_property(self, name: &Multiname<'gc>) -> bool { + let read = self.0.read(); + + // FIXME - see if we can deduplicate this with get_property_local in + // an efficient way + if name.contains_public_namespace() { + if let Some(local_name) = name.local_name() { + // The only supported numerical index is 0 + if let Ok(index) = local_name.parse::() { + return index == 0; + } + + if let E4XNodeKind::Element { + children, + attributes, + } = &*read.node.kind() + { + let search_children = if name.is_attribute() { + attributes + } else { + children + }; + + return search_children.iter().any(|child| child.matches_name(name)); + } + } + } + read.base.has_own_dynamic_property(name) } } diff --git a/tests/tests/swfs/avm2/xml_children/Test.as b/tests/tests/swfs/avm2/xml_children/Test.as new file mode 100644 index 000000000..1c0397f9b --- /dev/null +++ b/tests/tests/swfs/avm2/xml_children/Test.as @@ -0,0 +1,26 @@ +package { + public class Test { + public static function test() { + var outer = + First Child + Second Child + Third Child:

Inner element

+
; + + trace("Children length: " + outer.children().length()); + + trace("'child' in outer: " + ('child' in outer)); + + for each (var child in outer.children()) { + trace("Child kind= " + child.@kind); + } + + for each (var innerChild in outer.children().children()) { + trace("Inner child localName " + innerChild.localName()); + } + + var empty = ; + trace("Empty children: " + empty.children().length()); + } + } +} \ No newline at end of file diff --git a/tests/tests/swfs/avm2/xml_children/output.txt b/tests/tests/swfs/avm2/xml_children/output.txt new file mode 100644 index 000000000..8e2611982 --- /dev/null +++ b/tests/tests/swfs/avm2/xml_children/output.txt @@ -0,0 +1,10 @@ +Children length: 3 +'child' in outer: true +Child kind= A +Child kind= B +Child kind= A +Inner child localName null +Inner child localName null +Inner child localName null +Inner child localName p +Empty children: 0 diff --git a/tests/tests/swfs/avm2/xml_children/test.fla b/tests/tests/swfs/avm2/xml_children/test.fla new file mode 100644 index 0000000000000000000000000000000000000000..f160a5490ab995292cf9c302a8d4099ff56df15e GIT binary patch literal 3815 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 XBaruPc4_vGw{|%Cj(Gk7Tk56w(jeEx literal 0 HcmV?d00001 diff --git a/tests/tests/swfs/avm2/xml_children/test.toml b/tests/tests/swfs/avm2/xml_children/test.toml new file mode 100644 index 000000000..dbee897f5 --- /dev/null +++ b/tests/tests/swfs/avm2/xml_children/test.toml @@ -0,0 +1 @@ +num_frames = 1