Immediately run deferred inline scripts after "load" is fired

Currently, any inline deferred script, e.g:

<script type=module>
...
</script>

That happens AFTER load, never executes. Unclear how serious an issue this is,
but it _does_ cause problems for some WPT tests which use document.write to
inject a <script type="module">...</script> block after an iframe is loaded.
This commit is contained in:
Karl Seguin
2026-05-25 18:16:44 +08:00
parent 43102317aa
commit 0d9482ccbf
2 changed files with 45 additions and 2 deletions

View File

@@ -210,7 +210,13 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
};
const is_blocking = mode == .normal;
if (is_blocking == false) {
// Once parsing is done, the deferred-script batch has already drained and
// won't run again, so a non-blocking script inserted afterwards would go
// unprocessed. Run it immediately instead. Remote scripts still need to
// be queued so they execute when their fetch completes.
const run_immediately = is_blocking or (self.base.static_scripts_done and remote_url == null);
if (run_immediately == false) {
self.base.scriptList(script).append(&script.node);
}
@@ -278,7 +284,7 @@ pub fn addFromElement(self: *ScriptManager, comptime from_parser: bool, script_e
handover = true;
}
if (is_blocking == false) {
if (run_immediately == false) {
return;
}

View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<head></head>
<script src="../../../testing.js"></script>
<!--
Scripts inserted *after* parsing is done (static_scripts_done == true). At that
point the deferred-script batch has already drained and won't run again, so a
non-blocking script must either run immediately (inline, nothing to fetch) or
still be queued for its fetch (remote). A module script's continuation after
the first `await` runs in this post-parse window, so we use it to do the
insertions.
-->
<script id=postload type=module>
const state = await testing.async();
// Inline (non-blocking, module) script must execute even though the defer
// batch is already done.
window.postload_inline_ran = false;
const inline_mod = document.createElement('script');
inline_mod.type = 'module';
inline_mod.textContent = 'window.postload_inline_ran = true;';
document.head.appendChild(inline_mod);
// Remote script must still be fetched and executed — NOT run synchronously
// with a not-yet-loaded status (which would drop it and free it mid-fetch).
window.loaded1 = 0;
const remote = document.createElement('script');
remote.src = 'dynamic1.js'; // does: loaded1 += 1
remote.onload = () => state.resolve();
remote.onerror = () => state.resolve(); // don't hang if it (wrongly) errors
document.head.appendChild(remote);
await state.done(() => {
testing.expectTrue(window.postload_inline_ran);
testing.expectEqual(1, window.loaded1);
});
</script>