LibWeb: Address edge case on async module load

Issue #6294 describes an edge case where the browser crash if the same
module is loaded three times in a document, but all attempts fail.

Failure scenario:
1. Module load 1 set the state to "Fetching"
2. Module load 2 registers a callback to `on_complete` since the
   current state is "Fetching"
3. Module load 1 finish with a failure, invoking the callback for load
   number 2
4. Module load 3 cause a crash. The state is neither "Fetching" or
   "ModuleScript", so we'll reset the state to "Fetching". This invokes
   the callback for module load 2 again, now with an unexpected state
   which will cause an assert violation.

Proposed fix is to remove the condition that invokes `on_complete`
immediately for successfully loaded modules only, the callback should
be invoked regardless of whether the fetch succeeded or failed.

This reveals a separate bug in HTMLScriptElement, where
`mark_as_ready()` can be invoked before
`m_steps_to_run_when_the_result_is_ready` is assigned.
This appears to be a spec bug, reported as
https://github.com/whatwg/html/issues/12073 and addressed by delaying
the callback by a task, similar to the issue was resolved for inline
scripts.
This commit is contained in:
Christoffer Haglund 2026-01-05 17:11:01 +01:00 committed by Shannon Booth
parent 1106496d1c
commit 14ccc87190
Notes: github-actions[bot] 2026-01-13 17:13:50 +00:00
5 changed files with 32 additions and 3 deletions

View file

@ -478,6 +478,19 @@ void HTMLScriptElement::prepare_script()
if (!has_attribute(HTML::AttributeNames::integrity))
options.integrity_metadata = resolve_a_module_integrity_metadata(*url, settings_object);
// AD-HOC: Queue an element task on the networking task source to run the onComplete steps
// This resolves an edge case where mark_as_ready() can execute before we set
// m_steps_to_run_when_the_result_is_ready
// See https://github.com/whatwg/html/issues/12073
auto on_complete = create_on_fetch_script_complete(heap(), [this](auto result) {
queue_an_element_task(Task::Source::Networking, [this, result = move(result)] {
if (!result)
mark_as_ready(ResultState::Null {});
else
mark_as_ready(Result(*result));
});
});
// Fetch an external module script graph given url, settings object, options, and onComplete.
fetch_external_module_script_graph(realm(), *url, settings_object, options, on_complete);
}