Files
sousa-gecko/testing/web-platform/tests/preload/modulepreload-consumed-by-script.html
Yoshi Cheng-Hao Huang 6649967a43 Bug 2052949 - Fire load/error event for a modulepreload whose module is fetching, fetched or cached. r=jonco,dom-core-reviewers,edgar,arai
A <link rel=modulepreload> whose module is already in the module map, or comes
from the in-memory navigation cache, is satisfied without opening a channel.
Registration with the PreloadService happens in StartLoadInternal, which isn't
reached in those cases, so PreloadService::PreloadOrCoalesce finds no preloader
for the element and a spurious error event is fired.

Register such a request in ScriptLoader::PreloadURI instead, identifying it by
having no channel, and report the top-level module's result from
ScriptLoadContext::NotifyPreloadCoalescingResult: load once the module has been
fetched, even with a parse error, error once the fetch has failed or been
canceled, and nothing while it is still fetching - ModuleLoaderBase notifies
again when the fetch it coalesced onto finishes or is dropped. As the
modulepreload spec requires, this happens before the module's dependencies.

The coalesced state is flagged on the ScriptLoadContext instead of being derived
from the eLinkPreload script mode, which a <script> element stealing the preload
clears while the element still needs its event.

Add WPTs for the module map states, for firing before dependencies and for a
preload consumed by a <script> element, and a mochitest for the memory cache.

Differential Revision: https://phabricator.services.mozilla.com/D311782
2026-07-30 19:38:06 +00:00

49 lines
1.9 KiB
HTML

<!DOCTYPE html>
<meta charset=utf-8>
<title>link rel=modulepreload fires its event when a script element consumes the same module</title>
<meta name="timeout" content="long">
<link rel="help" href="https://html.spec.whatwg.org/multipage/links.html#link-type-modulepreload">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/common/utils.js"></script>
<script src="./resources/preload-module-helper.js"></script>
<body>
<script>
// A <link rel=modulepreload> can be satisfied by a fetch of its URL that is
// already in flight, and a <script type=module> for that URL added while the
// fetch is still outstanding can then consume that same preload as its own load.
// The link element must get its event either way: it is the element that asked
// for the preload, and what the module ends up being used for afterwards is none
// of its business.
promise_test(async t => {
const uuid = token();
const url = moduleUrl({ block: 1, uuid });
// Puts the URL in the module map as fetching, and the server holds the
// response until the release below, so it stays that way.
const importPromise = import(url).catch(() => {});
// Satisfied by the fetch above rather than by a fetch of its own.
const link = makeModulePreload(url);
const linkEvent = eventFor(link);
document.head.appendChild(link);
// Consumes the preload while that fetch is still outstanding.
const script = document.createElement("script");
script.type = "module";
script.crossOrigin = "";
script.src = url;
const scriptEvent = eventFor(script);
document.body.appendChild(script);
await release(uuid);
assert_equals(await linkEvent, "load", "the modulepreload fired load");
assert_equals(await scriptEvent, "load", "the script element loaded as well");
await importPromise;
}, "modulepreload satisfied by an in-flight fetch fires load when a script " +
"element consumes it");
</script>
</body>