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
49 lines
1.9 KiB
HTML
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>
|