Don't recurse in Node::GetRootNode (#35725)

* Don't recurse in Node::GetRootNode

This causes servo to crash when computing
the root of deeply nested shadow roots.

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

* Add test case

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>

---------

Signed-off-by: Simon Wülker <simon.wuelker@arcor.de>
This commit is contained in:
Simon Wülker 2025-03-01 18:19:27 +01:00 committed by GitHub
parent ce977636f6
commit 25cc675101
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 59 additions and 13 deletions

View file

@ -2887,19 +2887,16 @@ impl NodeMethods<crate::DomTypeHolder> for Node {
/// <https://dom.spec.whatwg.org/#dom-node-getrootnode> /// <https://dom.spec.whatwg.org/#dom-node-getrootnode>
fn GetRootNode(&self, options: &GetRootNodeOptions) -> DomRoot<Node> { fn GetRootNode(&self, options: &GetRootNodeOptions) -> DomRoot<Node> {
if let Some(shadow_root) = self.containing_shadow_root() { if !options.composed {
return if options.composed { if let Some(shadow_root) = self.containing_shadow_root() {
// shadow-including root. return DomRoot::upcast(shadow_root);
shadow_root.Host().upcast::<Node>().GetRootNode(options) }
} else {
DomRoot::from_ref(shadow_root.upcast::<Node>())
};
} }
if self.is_in_a_document_tree() { if self.is_connected() {
DomRoot::from_ref(self.owner_doc().upcast::<Node>()) DomRoot::from_ref(self.owner_doc().upcast::<Node>())
} else { } else {
self.inclusive_ancestors(ShadowIncluding::No) self.inclusive_ancestors(ShadowIncluding::Yes)
.last() .last()
.unwrap() .unwrap()
} }

View file

@ -13496,14 +13496,14 @@
] ]
], ],
"interfaces.https.html": [ "interfaces.https.html": [
"6c48986beea5c8aca4e54c6da0fb8f3b7a4390e0", "81f4d942f94366d8f9ecf22cfc3e1e22fe4ab8f1",
[ [
null, null,
{} {}
] ]
], ],
"interfaces.worker.js": [ "interfaces.worker.js": [
"4ac5c822dcc6fbb021050ac2ab2ba4d04452d945", "06eb8d3ba2334951cb1e0f791527ba118d4f13ec",
[ [
"mozilla/interfaces.worker.html", "mozilla/interfaces.worker.html",
{} {}
@ -14337,6 +14337,15 @@
] ]
] ]
}, },
"shadow-dom": {
"getrootnode-in-deeply-nested-shadow.html": [
"355cc270e6020f40176afac26c49a14a59b67169",
[
null,
{}
]
]
},
"webxr": { "webxr": {
"create_session.https.html": [ "create_session.https.html": [
"5b5d485b372bfffb22204bc162c9e182306395cb", "5b5d485b372bfffb22204bc162c9e182306395cb",

1
tests/wpt/mozilla/meta/__dir__.ini vendored Normal file
View file

@ -0,0 +1 @@
prefs: ["dom_imagebitmap_enabled:true", "dom_offscreen_canvas_enabled:true", "dom_shadowdom_enabled:true", "dom_xpath_enabled:true", "dom_intersection_observer_enabled:true", "dom_resize_observer_enabled:true", "dom_notification_enabled:true", "dom_fontface_enabled:true"]

View file

@ -1,2 +0,0 @@
[partial_shadow_dom_layout_style.html]
expected: FAIL

View file

@ -92,6 +92,7 @@ test_interfaces([
"FileList", "FileList",
"FileReader", "FileReader",
"FocusEvent", "FocusEvent",
"FontFace",
"FontFaceSet", "FontFaceSet",
"FormData", "FormData",
"FormDataEvent", "FormDataEvent",
@ -181,7 +182,10 @@ test_interfaces([
"IIRFilterNode", "IIRFilterNode",
"ImageData", "ImageData",
"Image", "Image",
"ImageBitmap",
"InputEvent", "InputEvent",
"IntersectionObserver",
"IntersectionObserverEntry",
"KeyboardEvent", "KeyboardEvent",
"Location", "Location",
"MediaElementAudioSourceNode", "MediaElementAudioSourceNode",
@ -213,6 +217,8 @@ test_interfaces([
"Notification", "Notification",
"OfflineAudioCompletionEvent", "OfflineAudioCompletionEvent",
"OfflineAudioContext", "OfflineAudioContext",
"OffscreenCanvas",
"OffscreenCanvasRenderingContext2D",
"Option", "Option",
"OscillatorNode", "OscillatorNode",
"PageTransitionEvent", "PageTransitionEvent",
@ -242,6 +248,9 @@ test_interfaces([
"ReadableByteStreamController", "ReadableByteStreamController",
"ReadableStreamBYOBRequest", "ReadableStreamBYOBRequest",
"Request", "Request",
"ResizeObserver",
"ResizeObserverEntry",
"ResizeObserverSize",
"Response", "Response",
"Screen", "Screen",
"SecurityPolicyViolationEvent", "SecurityPolicyViolationEvent",
@ -303,6 +312,9 @@ test_interfaces([
"XMLHttpRequestEventTarget", "XMLHttpRequestEventTarget",
"XMLHttpRequestUpload", "XMLHttpRequestUpload",
"XMLSerializer", "XMLSerializer",
"XPathEvaluator",
"XPathExpression",
"XPathResult",
"XRBoundedReferenceSpace", "XRBoundedReferenceSpace",
"XRFrame", "XRFrame",
"XRHand", "XRHand",

View file

@ -37,11 +37,14 @@ test_interfaces([
"FormData", "FormData",
"Headers", "Headers",
"History", "History",
"ImageBitmap",
"ImageData", "ImageData",
"MessageChannel", "MessageChannel",
"MessageEvent", "MessageEvent",
"MessagePort", "MessagePort",
"Notification", "Notification",
"OffscreenCanvas",
"OffscreenCanvasRenderingContext2D",
"Performance", "Performance",
"PerformanceEntry", "PerformanceEntry",
"PerformanceMark", "PerformanceMark",

View file

@ -0,0 +1,26 @@
<!--https://github.com/servo/servo/issues/35724 -->
<html>
<head>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<script>
'use strict';
test(() => {
let original_host = document.createElement("div");
original_host.id = "original-host";
let current_host = original_host;
for (let i = 0; i < 10000; i++) {
let root = current_host.attachShadow({ mode: "open" });
let new_host = document.createElement("div");
root.appendChild(new_host);
current_host = new_host;
}
let root_of_tree = current_host.getRootNode({ composed: true });
assert_equals(root_of_tree.id, "original-host");
}, "Calling Node.getRootNode in a deeply nested shadow tree must return the correct result without crashing");
</script>
</body>
</html>