diff --git a/NEWS b/NEWS index b617503dbb5..a331bf6c3a1 100644 --- a/NEWS +++ b/NEWS @@ -23,6 +23,7 @@ PHP NEWS . Fix manually calling __construct() on DOM classes. (nielsdos) . Fixed bug GH-11830 (ParentNode methods should perform their checks upfront). (nielsdos) + . Fix viable next sibling search for replaceWith. (nielsdos) - FFI: . Fix leaking definitions when using FFI::cdef()->new(...). (ilutov) diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index df14f194bcb..4f3187db969 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -550,25 +550,45 @@ void dom_child_node_remove(dom_object *context) void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) { + /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-replacewith */ + xmlNodePtr child = dom_object_get_node(context); + + /* Spec step 1 */ xmlNodePtr parentNode = child->parent; + /* Spec step 2 */ + if (!parentNode) { + int stricterror = dom_get_strict_error(context->document); + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + return; + } int stricterror = dom_get_strict_error(context->document); if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) { return; } - xmlNodePtr insertion_point = child->next; + /* Spec step 3: find first following child not in nodes; otherwise null */ + xmlNodePtr viable_next_sibling = child->next; + while (viable_next_sibling) { + if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) { + break; + } + viable_next_sibling = viable_next_sibling->next; + } if (UNEXPECTED(dom_sanity_check_node_list_for_insertion(context->document, parentNode, nodes, nodesc) != SUCCESS)) { return; } + /* Spec step 4: convert nodes into fragment */ xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (UNEXPECTED(fragment == NULL)) { return; } + /* Spec step 5: perform the replacement */ + xmlNodePtr newchild = fragment->children; xmlDocPtr doc = parentNode->doc; @@ -580,7 +600,7 @@ void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) if (newchild) { xmlNodePtr last = fragment->last; - dom_pre_insert(insertion_point, parentNode, newchild, fragment); + dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); dom_reconcile_ns_list(doc, newchild, last); diff --git a/ext/dom/tests/replaceWith_non_viable_next_sibling.phpt b/ext/dom/tests/replaceWith_non_viable_next_sibling.phpt new file mode 100644 index 00000000000..f0ee54c78d8 --- /dev/null +++ b/ext/dom/tests/replaceWith_non_viable_next_sibling.phpt @@ -0,0 +1,36 @@ +--TEST-- +replaceWith() with a non-viable next sibling +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + + + + +XML); + +$container = $doc->documentElement; +$child = $container->firstElementChild; +$alone = $child->firstElementChild; + +$child->after($alone); +echo $doc->saveXML(); +$child->replaceWith($alone); +echo $doc->saveXML(); +?> +--EXPECT-- + + + + + + + + + +