LibWeb/DOM: Throw errors from correct realm in Node::move_node()

This commit is contained in:
Tim Ledbetter 2025-07-09 14:36:08 +01:00 committed by Jelle Raaijmakers
commit 57dd85e4ac
Notes: github-actions[bot] 2025-07-09 14:00:19 +00:00
6 changed files with 93 additions and 9 deletions

View file

@ -1215,33 +1215,35 @@ WebIDL::ExceptionOr<GC::Ref<Node>> Node::clone_node(Document* document, bool sub
// https://dom.spec.whatwg.org/#move
WebIDL::ExceptionOr<void> Node::move_node(Node& new_parent, Node* child)
{
auto& realm = new_parent.realm();
// 1. If newParents shadow-including root is not the same as nodes shadow-including root, then throw a "HierarchyRequestError" DOMException.
if (&new_parent.shadow_including_root() != &shadow_including_root())
return WebIDL::HierarchyRequestError::create(realm(), "New parent is not in the same shadow tree"_string);
return WebIDL::HierarchyRequestError::create(realm, "New parent is not in the same shadow tree"_string);
// NOTE: This has the side effect of ensuring that a move is only performed if newParents connected is nodes connected.
// 2. If node is a host-including inclusive ancestor of newParent, then throw a "HierarchyRequestError" DOMException.
if (is_host_including_inclusive_ancestor_of(new_parent))
return WebIDL::HierarchyRequestError::create(realm(), "New parent is an ancestor of this node"_string);
return WebIDL::HierarchyRequestError::create(realm, "New parent is an ancestor of this node"_string);
// 3. If child is non-null and its parent is not newParent, then throw a "NotFoundError" DOMException.
if (child && child->parent() != &new_parent)
return WebIDL::NotFoundError::create(realm(), "Child does not belong to the new parent"_string);
return WebIDL::NotFoundError::create(realm, "Child does not belong to the new parent"_string);
// 4. If node is not an Element or a CharacterData node, then throw a "HierarchyRequestError" DOMException.
if (!is<Element>(*this) && !is<CharacterData>(*this))
return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
return WebIDL::HierarchyRequestError::create(realm, "Invalid node type for insertion"_string);
// 5. If node is a Text node and newParent is a document, then throw a "HierarchyRequestError" DOMException.
if (is<Text>(*this) && is<Document>(new_parent))
return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
return WebIDL::HierarchyRequestError::create(realm, "Invalid node type for insertion"_string);
// 6. If newParent is a document, node is an Element node, and either newParent has an element child, child is a doctype,
// or child is non-null and a doctype is following child then throw a "HierarchyRequestError" DOMException.
if (is<Document>(new_parent) && is<Element>(*this)) {
if (new_parent.has_child_of_type<Element>() || is<DocumentType>(child) || (child && child->has_following_node_of_type_in_tree_order<DocumentType>()))
return WebIDL::HierarchyRequestError::create(realm(), "Invalid node type for insertion"_string);
return WebIDL::HierarchyRequestError::create(realm, "Invalid node type for insertion"_string);
}
// 7. Let oldParent be nodes parent.

View file

@ -2,8 +2,7 @@ Harness status: OK
Found 32 tests
31 Pass
1 Fail
32 Pass
Pass If node is a host-including inclusive ancestor of parent, then throw a HierarchyRequestError DOMException.
Pass If node is not a DocumentFragment, DocumentType, Element, Text, ProcessingInstruction, or Comment node, then throw a HierarchyRequestError DOMException.
Pass If node is a Text node and parent is a document, then throw a HierarchyRequestError DOMException.
@ -18,7 +17,7 @@ Pass Calling moveBefore with second argument missing, or other than Node, null,
Pass moveBefore() method does not exist on non-ParentNode Nodes
Pass moveBefore() on disconnected parent throws a HierarchyRequestError
Pass moveBefore() with disconnected target node throws a HierarchyRequestError
Fail moveBefore() on a cross-document target node throws a HierarchyRequestError
Pass moveBefore() on a cross-document target node throws a HierarchyRequestError
Pass moveBefore() into a Document throws a HierarchyRequestError
Pass moveBefore() CharacterData into a Document
Pass moveBefore() with node being an inclusive ancestor of parent throws a HierarchyRequestError

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass Moving a transition across documents should reset its state

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass moveBefore() on a cross-document target node

View file

@ -0,0 +1,51 @@
<!DOCTYPE html>
<title>Node.moveBefore should not preserve CSS transition state when crossing document boundaries</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<body>
<iframe id="iframe">
</iframe>
<section id="new-parent">
</section>
<style id="style">
#item {
width: 100px;
height: 100px;
background: green;
transition: left 10s;
position: absolute;
left: 0;
}
section {
position: relative;
}
body {
margin-left: 0;
}
</style>
<script>
promise_test(async t => {
const iframe = document.querySelector("#iframe");
const style = document.querySelector("#style");
iframe.contentDocument.head.append(style.cloneNode(true));
const item = iframe.contentDocument.createElement("div");
item.id = "item";
iframe.contentDocument.body.append(item);
assert_equals(item.getBoundingClientRect().x, 0);
item.style.left = "400px";
await new Promise(resolve => item.addEventListener("transitionstart", resolve));
// Calling `moveBefore()` on a cross-document element undergoing a
// transition does not move the element, nor alter the transition.
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
document.querySelector("#new-parent").moveBefore(item, null);
});
await new Promise(resolve => requestAnimationFrame(() => resolve()));
assert_less_than(item.getBoundingClientRect().x, 20);
}, "Moving a transition across documents should reset its state");
</script>
</body>

View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<title>moveBefore exception conditions</title>
<script src="../../../resources/testharness.js"></script>
<script src="../../../resources/testharnessreport.js"></script>
<body>
<div></div>
<script>
test(t => {
const iframe = document.createElement('iframe');
document.body.append(iframe);
const connectedCrossDocChild = iframe.contentDocument.createElement('div');
const connectedLocalParent = document.querySelector('div');
assert_throws_dom("HIERARCHY_REQUEST_ERR", () => {
connectedLocalParent.moveBefore(connectedCrossDocChild, null);
}, "moveBefore on a cross-document target node throws an exception");
}, "moveBefore() on a cross-document target node");
</script>