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:
parent
20d6fcc128
commit
ae1a01d181
|
@ -82,7 +82,7 @@ pub fn xmlnode_append_child<'gc>(
|
|||
args.get(0)
|
||||
.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();
|
||||
if let Err(e) =
|
||||
xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode)
|
||||
|
@ -111,7 +111,7 @@ pub fn xmlnode_insert_before<'gc>(
|
|||
args.get(1)
|
||||
.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 Err(e) =
|
||||
xmlnode.insert_child(activation.context.gc_context, position, child_xmlnode)
|
||||
|
|
|
@ -665,6 +665,12 @@ impl<'gc> XmlNode<'gc> {
|
|||
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) {
|
||||
XmlNodeData::Element {
|
||||
ref mut children, ..
|
||||
|
@ -779,6 +785,15 @@ impl<'gc> XmlNode<'gc> {
|
|||
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).
|
||||
pub fn get_child_by_index(self, index: usize) -> Option<XmlNode<'gc>> {
|
||||
match &*self.0.read() {
|
||||
|
|
|
@ -203,6 +203,7 @@ swf_tests! {
|
|||
(xml_append_child, "avm1/xml_append_child", 1),
|
||||
(xml_append_child_with_parent, "avm1/xml_append_child_with_parent", 1),
|
||||
(xml_remove_node, "avm1/xml_remove_node", 1),
|
||||
(xml_reparenting, "avm1/xml_reparenting", 1),
|
||||
(xml_insert_before, "avm1/xml_insert_before", 1),
|
||||
(xml_to_string, "avm1/xml_to_string", 1),
|
||||
(xml_to_string_comment, "avm1/xml_to_string_comment", 1),
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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.
Loading…
Reference in New Issue