avm1: Allow XMLNode reparenting (closes #3962)

`XMLNode.{appendChild, insertNode}` can now be used to move
child nodes from a parent to another.

There are two special cases:
  - if the node is already a child of the destination, nothing happens
  - if moving the node would create a cycle, nothing happens
This commit is contained in:
Moulins 2021-04-11 19:30:29 +02:00 committed by Mike Welsh
parent 20d6fcc128
commit ae1a01d181
6 changed files with 61 additions and 2 deletions

View File

@ -82,7 +82,7 @@ pub fn xmlnode_append_child<'gc>(
args.get(0) args.get(0)
.and_then(|n| n.coerce_to_object(activation).as_xml_node()), .and_then(|n| n.coerce_to_object(activation).as_xml_node()),
) { ) {
if let Ok(None) = child_xmlnode.parent() { if !xmlnode.has_child(child_xmlnode) {
let position = xmlnode.children_len(); let position = xmlnode.children_len();
if let Err(e) = if let Err(e) =
xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode) xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode)
@ -111,7 +111,7 @@ pub fn xmlnode_insert_before<'gc>(
args.get(1) args.get(1)
.and_then(|n| n.coerce_to_object(activation).as_xml_node()), .and_then(|n| n.coerce_to_object(activation).as_xml_node()),
) { ) {
if let Ok(None) = child_xmlnode.parent() { if !xmlnode.has_child(child_xmlnode) {
if let Some(position) = xmlnode.child_position(insertpoint_xmlnode) { if let Some(position) = xmlnode.child_position(insertpoint_xmlnode) {
if let Err(e) = if let Err(e) =
xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode) xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode)

View File

@ -665,6 +665,12 @@ impl<'gc> XmlNode<'gc> {
return Err(Error::CannotInsertIntoSelf); return Err(Error::CannotInsertIntoSelf);
} }
if let Some(mut it) = self.ancestors() {
if it.find(|ancestor| GcCell::ptr_eq(ancestor.0, child.0)).is_some() {
return Err(Error::CannotInsertIntoSelf);
}
}
match &mut *self.0.write(mc) { match &mut *self.0.write(mc) {
XmlNodeData::Element { XmlNodeData::Element {
ref mut children, .. ref mut children, ..
@ -779,6 +785,15 @@ impl<'gc> XmlNode<'gc> {
None None
} }
/// Checks if `child` is a direct descendant of `self`.
pub fn has_child(self, child: XmlNode<'gc>) -> bool {
child
.parent()
.unwrap_or(None)
.filter(|p| GcCell::ptr_eq(self.0, p.0))
.is_some()
}
/// Retrieve a given child by index (e.g. position in the document). /// Retrieve a given child by index (e.g. position in the document).
pub fn get_child_by_index(self, index: usize) -> Option<XmlNode<'gc>> { pub fn get_child_by_index(self, index: usize) -> Option<XmlNode<'gc>> {
match &*self.0.read() { match &*self.0.read() {

View File

@ -203,6 +203,7 @@ swf_tests! {
(xml_append_child, "avm1/xml_append_child", 1), (xml_append_child, "avm1/xml_append_child", 1),
(xml_append_child_with_parent, "avm1/xml_append_child_with_parent", 1), (xml_append_child_with_parent, "avm1/xml_append_child_with_parent", 1),
(xml_remove_node, "avm1/xml_remove_node", 1), (xml_remove_node, "avm1/xml_remove_node", 1),
(xml_reparenting, "avm1/xml_reparenting", 1),
(xml_insert_before, "avm1/xml_insert_before", 1), (xml_insert_before, "avm1/xml_insert_before", 1),
(xml_to_string, "avm1/xml_to_string", 1), (xml_to_string, "avm1/xml_to_string", 1),
(xml_to_string_comment, "avm1/xml_to_string_comment", 1), (xml_to_string_comment, "avm1/xml_to_string_comment", 1),

View File

@ -0,0 +1,29 @@
// Compile with:
// mtasc -main -header 200:150:30 Test.as -swf test.swf
class Test {
static function main(current) {
trace("// reparenting in self");
var xml = new XML("<a><b/></a><c/>");
trace(xml);
xml.appendChild(xml.firstChild.firstChild);
trace("// after");
trace(xml);
trace("// reparenting in other");
xml = new XML("<a><b/></a>");
var other = new XML("<c/>");
trace(xml);
trace(other);
other.insertBefore(xml.firstChild.firstChild, other.firstChild);
trace("// after");
trace(xml);
trace(other);
trace("// can't reparent in descendent");
xml = new XML("<a><b/></a>");
trace(xml);
xml.firstChild.firstChild.appendChild(xml.firstChild);
trace("// after");
trace(xml);
}
}

View File

@ -0,0 +1,14 @@
// reparenting in self
<a><b /></a><c />
// after
<a /><c /><b />
// reparenting in other
<a><b /></a>
<c />
// after
<a />
<b /><c />
// can't reparent in descendent
<a><b /></a>
// after
<a><b /></a>

Binary file not shown.