LibWeb: Let document.write() reenter parser from parser-blocking scripts

When resuming after an async wait for a pending parser-blocking script,
clear the parser pause flag before executing the script. The spec has
unblocked the tokenizer by this point, and document.write() calls from
the script must be able to synchronously process inserted markup up to
the insertion point.

This fixes ordering for document.write()'d inserted scripts during
external parser-blocking script execution.
This commit is contained in:
Shannon Booth 2026-05-15 16:42:19 +02:00 committed by Andreas Kling
parent f743263871
commit d595369ae4
13 changed files with 131 additions and 4 deletions

View file

@ -3509,6 +3509,11 @@ void HTMLParser::resume_after_parser_blocking_script()
VERIFY(script_nesting_level() == 0);
increment_script_nesting_level();
// Step 8 unblocked the tokenizer above. Our async "spin the event loop" implementation uses the parser pause flag
// to yield while waiting for the pending script, so clear it before executing the script. This allows
// document.write() calls made by the script to synchronously re-enter the parser up to the insertion point.
m_parser_pause_flag = false;
// 1. Let the script be the pending parsing-blocking script.
// 2. Set the pending parsing-blocking script to null.
// 11. Execute the script element the script.
@ -3528,6 +3533,14 @@ void HTMLParser::resume_after_parser_blocking_script()
// 13. Let the insertion point be undefined again.
m_tokenizer.undefine_insertion_point();
// The spec's loop would handle the next pending parsing-blocking script before continuing normal tokenization.
// In this async implementation, pause again and resume when that next script is ready.
if (document().has_pending_parsing_blocking_script()) {
m_parser_pause_flag = true;
schedule_resume_check();
return;
}
// The spec's "While the pending parsing-blocking script is not null" iteration is realized by run() pausing again
// on the next </script> end tag if the executed script set up a new pending blocking script (e.g. via
// document.write).

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass scheduler: document.write external - multiple with doc.write

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass scheduler: document.write external and onload events

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass scheduler: document.write scripts that write scripts

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail document.write external script into iframe write back into parent
1 Pass
Pass document.write external script into iframe write back into parent

View file

@ -2,5 +2,5 @@ Harness status: OK
Found 1 tests
1 Fail
Fail document.write external script that document.writes external script
1 Pass
Pass document.write external script that document.writes external script

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html><head>
<title> scheduler: document.write external - multiple with doc.write</title>
<script src="../../../../../resources/testharness.js"></script>
<script src="../../../../../resources/testharnessreport.js"></script>
<script src="testlib/testlib.js"></script>
</head>
<body>
<div id="log">FAILED (This TC requires JavaScript enabled)</div>
<script>log('inline script #1');
document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
document.write( '<script src="scripts/include-3.js">log(\'ignore this\')<\/script>' );
document.write( '<script src="scripts/include-2.js">log(\'ignore this\')<\/script>' );
log('end script #1');
</script>
<script type="text/javascript">
var t = async_test()
function test() {
assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'external script before doc write', 'document.write external script', 'external script after doc write', 'external script #2' ]);
t.done();
}
onload = t.step_func(test)
</script>
</body></html>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html><head>
<title> scheduler: document.write external and onload events </title>
<script src="../../../../../resources/testharness.js"></script>
<script src="../../../../../resources/testharnessreport.js"></script>
<script src="testlib/testlib.js"></script>
</head>
<body>
<div id="log">FAILED (This TC requires JavaScript enabled)</div>
<script>log('inline script #1');
document.write( '<script src="scripts/include-1.js" onload = "log(\'include-1 load\')">log(\'ignore this\')<\/script>' )
document.write( '<script src="scripts/include-3.js" onload = "log(\'include-3 load\')"><\/script>' )
document.write( '<script src="scripts/include-2.js" onload = "log(\'include-2 load\')">log(\'ignore this\')<\/script>' )
log('end script #1');
</script>
<script type="text/javascript">
log( 'inline script #2' );
var t = async_test()
function test() {
assert_array_equals(eventOrder, ['inline script #1', 'end script #1', 'external script #1', 'include-1 load', 'external script before doc write', 'document.write external script', 'external script after doc write', 'include-3 load', 'external script #2', 'include-2 load', 'inline script #2']);
t.done();
}
onload = t.step_func(test)
</script>
</body></html>

View file

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html><head>
<title> scheduler: document.write scripts that write scripts</title>
<script src="../../../../../resources/testharness.js"></script>
<script src="../../../../../resources/testharnessreport.js"></script>
<script src="testlib/testlib.js"></script>
</head>
<body>
<div id="log">FAILED (This TC requires JavaScript enabled)</div>
<script>log('inline script #1');
document.write( '<script>log(\'inline with doc.write #1\'); document.write(\'<script src="scripts/include-4.js"><\\\/script>\');log(\'end inline with doc.write\');<\/script>' );
document.write( '<script src="scripts/include-1.js">log(\'ignore this\')<\/script>' );
log('end script #1');
</script>
<script type="text/javascript">
log( 'inline script #2' );
var t = async_test()
function test() {
assert_array_equals(eventOrder, ['inline script #1', 'inline with doc.write #1', 'end inline with doc.write', 'end script #1', 'include-4 before doc write', 'include-4 after doc write', 'external script before doc write', 'document.write external script', 'external script after doc write', 'external script #1', 'inline script #2']);
t.done();
}
onload = t.step_func(test)
</script>
</body></html>

View file

@ -0,0 +1,3 @@
log('external script before doc write');
document.write( '<script>log(\'document.write external script\');</script>');
log('external script after doc write');

View file

@ -0,0 +1,3 @@
log('include-4 before doc write');
document.write( '<script src="scripts/include-3.js"></script>');
log('include-4 after doc write');