Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

avm2: Correctly implement XML.prototype.child #18442

Merged
merged 2 commits into from
Nov 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 68 additions & 68 deletions core/src/avm2/object/xml_object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,47 +77,86 @@ impl<'gc> XmlObject<'gc> {
))
}

fn get_child_list(
self,
activation: &mut Activation<'_, 'gc>,
name: &Multiname<'gc>,
) -> XmlListObject<'gc> {
let matched_children = if let E4XNodeKind::Element {
children,
attributes,
..
} = &*self.0.node.get().kind()
{
let search_children = if name.is_attribute() {
attributes
} else {
children
};

search_children
.iter()
.filter_map(|child| {
if child.matches_name(name) {
Some(E4XOrXml::E4X(*child))
} else {
None
}
})
.collect::<Vec<_>>()
} else {
Vec::new()
};

// NOTE: avmplus does set the target_dirty flag on the list object if there was at least one child
// due to the way avmplus implemented this.
let list = XmlListObject::new_with_children(
activation,
matched_children,
Some(self.into()),
Some(name.clone()),
);

if list.length() > 0 {
list.set_dirty_flag();
}

list
}

// 13.4.4.6 XML.prototype.child ( propertyName )
pub fn child(
&self,
name: &Multiname<'gc>,
activation: &mut Activation<'_, 'gc>,
) -> XmlListObject<'gc> {
let children = if let E4XNodeKind::Element { children, .. } = &*self.node().kind() {
if let Some(local_name) = name.local_name() {
if let Ok(index) = local_name.parse::<usize>() {
let children = if let Some(node) = children.get(index) {
// 1. If ToString(ToUint32(propertyName)) == propertyName
if let Some(local_name) = name.local_name() {
if let Ok(index) = local_name.parse::<usize>() {
let result = if let E4XNodeKind::Element { children, .. } = &*self.node().kind() {
if let Some(node) = children.get(index) {
vec![E4XOrXml::E4X(*node)]
} else {
Vec::new()
};

let list = XmlListObject::new_with_children(activation, children, None, None);

if list.length() > 0 {
// NOTE: Since avmplus uses appendNode here, when the node exists, that implicitly sets the target_dirty flag.
list.set_dirty_flag();
}
} else {
Vec::new()
};

let list = XmlListObject::new_with_children(activation, result, None, None);

return list;
if list.length() > 0 {
// NOTE: Since avmplus uses appendNode here, when the node exists, that implicitly sets the target_dirty flag.
list.set_dirty_flag();
}
}

children
.iter()
.filter(|node| node.matches_name(name))
.map(|node| E4XOrXml::E4X(*node))
.collect()
} else {
Vec::new()
};
return list;
}
}

// FIXME: If name is not a number index, then we should call [[Get]] (get_property_local) with the name.
XmlListObject::new_with_children(
activation,
children,
Some(XmlOrXmlListObject::Xml(*self)),
Some(name.clone()),
)
// 2. Let temporary be the result of calling the [[Get]] method of x with argument propertyName
// 3. Return ToXMLList(temporary)
self.get_child_list(activation, name)
}

pub fn elements(
Expand Down Expand Up @@ -306,46 +345,7 @@ impl<'gc> TObject<'gc> for XmlObject<'gc> {
}

let name = handle_input_multiname(name.clone(), activation);

let matched_children = if let E4XNodeKind::Element {
children,
attributes,
..
} = &*self.0.node.get().kind()
{
let search_children = if name.is_attribute() {
attributes
} else {
children
};

search_children
.iter()
.filter_map(|child| {
if child.matches_name(&name) {
Some(E4XOrXml::E4X(*child))
} else {
None
}
})
.collect::<Vec<_>>()
} else {
Vec::new()
};

// NOTE: avmplus does set the target_dirty flag on the list object if there was at least one child
// due to the way avmplus implemented this.
let list = XmlListObject::new_with_children(
activation,
matched_children,
Some(self.into()),
Some(name.clone()),
);

if list.length() > 0 {
list.set_dirty_flag();
}

let list = self.get_child_list(activation, &name);
Ok(list.into())
}

Expand Down
10 changes: 10 additions & 0 deletions tests/tests/swfs/avm2/xml_child/Test.as
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@ var xml: XML = new XML("<x><foo>foo1</foo><bar>bar1</bar><foo>foo2</foo></x>")
trace('child("foo") length: ' + xml.child("foo").length());
trace('child("bar") length: ' + xml.child("bar").length());
trace('child("XXXXX") length: ' + xml.child("XXX").length());
trace('child("*") length: ' + xml.child("*").length());

for each (var child in xml.child("foo")) {
trace('child("foo") toString: ' + child.toString());
}
for each (var child in xml.child("bar")) {
trace('child("bar") toString: ' + child.toString());
}
for each (var child in xml.child("*")) {
trace('child("*") toString: ' + child.toString());
}

var nested: XML = new XML("<x><a b='c'><b>bbb</b></a></x>")
trace('child("a").length: ' + nested.child("a").length());
Expand Down Expand Up @@ -45,3 +49,9 @@ trace('xml_list.child("unknown").length():', xml_list.child("unknown").length())
trace('xml_list.child("b"):', xml_list.child("b"));
trace('xml_list.child("c"):', xml_list.child("c"));
trace('xml_list.child("unknown"):', xml_list.child("unknown"));

var attrs: XML = <xml hello="world" foo="bar" />;
trace('attrs.child("@unknown"):', attrs.child("@unknown"))
trace('attrs.child("@hello"):', attrs.child("@hello"))
trace('attrs.child("@foo"):', attrs.child("@foo"))
trace('attrs.child("@*"):', attrs.child("@*"))
8 changes: 8 additions & 0 deletions tests/tests/swfs/avm2/xml_child/output.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
child("foo") length: 2
child("bar") length: 1
child("XXXXX") length: 0
child("*") length: 3
child("foo") toString: foo1
child("foo") toString: foo2
child("bar") toString: bar1
child("*") toString: foo1
child("*") toString: bar1
child("*") toString: foo2
child("a").length: 1
child("b").length: 0
child("a").@b: c
Expand All @@ -15,3 +19,7 @@ xml_list.child("b"): <b>a1-b1</b>
<b>a2-b</b>
xml_list.child("c"): a2-c
xml_list.child("unknown"):
attrs.child("@unknown"):
attrs.child("@hello"): world
attrs.child("@foo"): bar
attrs.child("@*"): worldbar
Binary file modified tests/tests/swfs/avm2/xml_child/test.swf
Binary file not shown.