diff --git a/core/src/avm2/globals/xml.rs b/core/src/avm2/globals/xml.rs index f3f2f16ef..015a8a651 100644 --- a/core/src/avm2/globals/xml.rs +++ b/core/src/avm2/globals/xml.rs @@ -314,22 +314,59 @@ pub fn append_child<'gc>( ) -> Result, Error<'gc>> { let xml = this.as_xml_object().unwrap(); - let child = args.get_object(activation, 0, "child")?; - if let Some(child) = child.as_xml_object() { + let child = args.get_value(0); + if let Some(child) = child.as_object().and_then(|o| o.as_xml_object()) { xml.node() .append_child(activation.context.gc_context, *child.node())?; - } else if let Some(list) = child.as_xml_list_object() { - if list.target().is_some() { - return Err("Cannot append XMLList with target".into()); - } + } else if let Some(list) = child.as_object().and_then(|o| o.as_xml_list_object()) { for child in &*list.children() { xml.node() .append_child(activation.context.gc_context, *child.node())?; } } else { - return Err(format!("Cannot append non-XML value {child:?}").into()); + // Appending a non-XML/XMLList object + let last_child_name = if let E4XNodeKind::Element { children, .. } = &*xml.node().kind() { + let num_children = children.len(); + + match num_children { + 0 => None, + _ => children[num_children - 1].local_name(), + } + } else { + // FIXME - figure out exactly when appending is allowed in FP, + // and throw the proper AVM error. + return Err(Error::RustError( + format!( + "Cannot append child {child:?} to node {:?}", + xml.node().kind() + ) + .into(), + )); + }; + + let text = child.coerce_to_string(activation)?; + if let Some(last_child_name) = last_child_name { + let element_node = + E4XNode::element(activation.context.gc_context, last_child_name, *xml.node()); // Creating an element requires passing a parent node, unlike creating a text node + + let text_node = E4XNode::text(activation.context.gc_context, text, None); + + element_node + .append_child(activation.context.gc_context, text_node) + .expect("Appending to an element node should succeed"); + + xml.node() + .append_child(activation.context.gc_context, element_node)?; + } else { + let node = E4XNode::text(activation.context.gc_context, text, None); + // The text node will be parented in the append_child operation + + xml.node() + .append_child(activation.context.gc_context, node)?; + } }; - Ok(Value::Undefined) + + Ok(this.into()) } pub fn descendants<'gc>( diff --git a/tests/tests/swfs/avm2/xml_appendchild/Test.as b/tests/tests/swfs/avm2/xml_appendchild/Test.as new file mode 100644 index 000000000..09eddd803 --- /dev/null +++ b/tests/tests/swfs/avm2/xml_appendchild/Test.as @@ -0,0 +1,27 @@ +package { + import flash.display.MovieClip; + + public class Test extends MovieClip { + + public function Test() { + XML.prettyPrinting = false; + var xml:XML = ; + trace(xml); + var xmls:XMLList = ().children(); + var appended:XML = xml.appendChild(xmls); + trace(appended); + trace(xml); + trace(xml == appended); + xml.child3 = "4"; + trace(xmls); + xml.appendChild("qwerty"); + trace(xml); + trace(xml.appendChild({"key":"value"})); + var xml2:XML = abcd; + trace(xml2.appendChild("text1").toXMLString()); + xml2 = ; + trace(xml2.appendChild("text2").toXMLString()); + } + } +} + diff --git a/tests/tests/swfs/avm2/xml_appendchild/output.txt b/tests/tests/swfs/avm2/xml_appendchild/output.txt new file mode 100644 index 000000000..e9eb1ebe2 --- /dev/null +++ b/tests/tests/swfs/avm2/xml_appendchild/output.txt @@ -0,0 +1,10 @@ + + + +true +4 + +4qwerty +4qwerty[object Object] +abcdtext1 +text2 diff --git a/tests/tests/swfs/avm2/xml_appendchild/test.swf b/tests/tests/swfs/avm2/xml_appendchild/test.swf new file mode 100644 index 000000000..e82a23fd3 Binary files /dev/null and b/tests/tests/swfs/avm2/xml_appendchild/test.swf differ diff --git a/tests/tests/swfs/avm2/xml_appendchild/test.toml b/tests/tests/swfs/avm2/xml_appendchild/test.toml new file mode 100644 index 000000000..dbee897f5 --- /dev/null +++ b/tests/tests/swfs/avm2/xml_appendchild/test.toml @@ -0,0 +1 @@ +num_frames = 1