fix(script): keep currentScript in its execution Document

A running script can be adopted into another Document without changing the
source Document's currentScript. Query the main execution stack for the
main Document rather than filtering by the node's new ownerDocument;
retain the existing per-child-Document stack and shadow visibility checks.

Dynamic inline error reporting exposed this pre-existing bug as a WPT
failure. A direct old-release probe also returns the wrong currentScript,
so the old WPT pass was masking the exception rather than proving correct
behavior. Cover adoption, the destination's null value and stack restoration.
This commit is contained in:
ldm0
2026-11-13 02:39:13 +08:00
parent b14d66c094
commit 1344d8d26d
2 changed files with 43 additions and 28 deletions
+10 -28
View File
@@ -1052,36 +1052,18 @@ fn current_script_handle_for_document(
// its node document or tree while its currentScript value stays set.
return runtime.child_current_script_handle_for_document(document_handle);
}
if let Some(script) = runtime.current_inline_script_handle()
&& current_script_belongs_to_document(runtime, script, document_handle)
{
return current_script_is_visible_for_document(runtime, script, document_handle)
.then_some(script);
if document_handle != runtime.dom_host().document_handle() {
return None;
}
// The executing Document owns currentScript, even if the script is adopted
// into another Document before its execution finishes.
let dom = runtime.dom_host();
runtime
.current_script_handle()
.filter(|script| current_script_is_visible_for_document(runtime, *script, document_handle))
}
fn current_script_is_visible_for_document(
runtime: &PageScriptHost,
script: DomHandle,
document_handle: DomHandle,
) -> bool {
let dom = runtime.dom_host();
current_script_belongs_to_document(runtime, script, document_handle)
&& (!dom.is_connected(script) || dom.containing_shadow_root(script).is_none())
}
fn current_script_belongs_to_document(
runtime: &PageScriptHost,
script: DomHandle,
document_handle: DomHandle,
) -> bool {
let dom = runtime.dom_host();
dom.node(script)
.and_then(Node::owner_document)
.is_some_and(|owner_document| owner_document == document_handle)
.current_inline_script_handle()
.or_else(|| runtime.current_script_handle())
.filter(|script| {
!dom.is_connected(*script) || dom.containing_shadow_root(*script).is_none()
})
}
fn document_hidden_getter_function<'s>(
@@ -5,6 +5,39 @@ const DYNAMIC_INLINE_ERRORS: &str = include_str!(concat!(
"/tests/fixtures/dynamic-inline-errors.js"
));
#[test]
fn current_script_stays_with_its_execution_document_after_adoption() {
let mut vm = new_parsed_test_vm(
"https://inline-adoption.test/page.html",
"<!doctype html><body></body>",
);
let result = vm
.eval(
r#"
globalThis.adoptionResults = [];
globalThis.outer = document.createElement('script');
outer.textContent = `
globalThis.inner = document.createElement('script');
inner.textContent = \`
adoptionResults.push(document.currentScript === inner);
globalThis.destination = document.implementation.createHTMLDocument();
destination.body.appendChild(inner);
adoptionResults.push(inner.ownerDocument === destination);
adoptionResults.push(document.currentScript === inner);
adoptionResults.push(destination.currentScript === null);
\`;
document.body.appendChild(inner);
adoptionResults.push(document.currentScript === outer);
`;
document.body.appendChild(outer);
adoptionResults.push(document.currentScript === null);
JSON.stringify(adoptionResults)
"#,
)
.unwrap();
assert_eq!(result, "[true,true,true,true,true,true]");
}
#[test]
fn main_dynamic_inline_script_errors_are_synchronous_and_restore_current_script() {
for borrow_child_insertion in [false, true] {