Closed Bug 2052949 Opened 1 month ago Closed 13 days ago

Firefox fires an "error" event for a <link rel="modulepreload" crossorigin> whose target URL is already present in the module map

Categories

(Core :: DOM: Core & HTML, defect)

defect

Tracking

()

RESOLVED FIXED
155 Branch
Tracking Status
firefox155 --- fixed

People

(Reporter: jrmuizel, Assigned: allstars.chh)

References

Details

(Keywords: webcompat:platform-bug)

User Story

user-impact-score:1600

Attachments

(1 file)

Blocks: 2052837

Jon, do you have a sense for how easy this would be to fix?

Flags: needinfo?(jcoppeard)
User Story: (updated)

FWIW, I ran this through my / dom-core's bug triage skill:


AI-Generated Bug Triage

Note: This triage was generated automatically by an AI assistant.
Findings should be verified by an engineer before acting on them.

TL;DR

Valid spec-violation bug. When a <link rel="modulepreload"> targets a URL already present in the module map, Firefox fires an error event instead of load. Root cause: the module preload takes a fast path (WaitForModuleFetch) that skips network channel creation, so the preload is never registered with the PreloadService, causing PreloadLinkElement to interpret the missing preloader as failure. This breaks bahn.de login (S2/P1 webcompat). Fix is medium-sized — the preload service needs a way to know the module was already complete.


Triage: Bug 2052949 - Firefox fires an "error" event for a <link rel="modulepreload" crossorigin> whose target URL is already present in the module map

Classification

  • Type: webcompat / feature-behavior
  • Component: Core :: DOM: Core & HTML

Validity

Real bug. Spec is unambiguous. The fetch a single module script algorithm step 6 says: "If moduleMap[(url, moduleType)] exists, run onComplete given moduleMap[(url, moduleType)], and return." — the existing module script (non-null) should flow back through onComplete, which per the modulepreload processing model step 14.2 fires a load event. Chrome fires load; Firefox fires error. Test case attached to the bug reproduces reliably.

Severity Recommendation: S2

Blocks bahn.de login page (bug 2052837, S2/P1 webcompat). Vite/rolldown-built SPAs using __vitePreload helper will hit this pattern — a <script type=module> imports a module, then Vite appends a redundant <link rel=modulepreload crossorigin> for the same URL. Any site with a capture-phase error handler on resource elements will break.

Spec Situation

Spec is clear. fetch a single module script step 6: if the module map entry exists, run onComplete with the module script. The modulepreload link fires load when onComplete receives a non-null result (step 14.2). Firefox deviates by firing error.

Implementation Overview

The preload flow for <link rel=modulepreload>:

  1. PreloadService::PreloadLinkElement calls PreloadOrCoalesce, then checks if a preloader was registered. If not, calls NotifyNodeEvent(node, alreadyComplete).
  2. PreloadService::PreloadOrCoalesce calls PreloadScriptScriptLoader::PreloadURIStartLoadStartModuleLoad.
  3. ModuleLoaderBase::StartOrRestartModuleLoad at line 589: if module is already in the module map, takes the WaitForModuleFetch fast path. This skips StartFetchStartLoadInternal, so NotifyOpen is never called and the preload is never registered with the PreloadService.
  4. Back in PreloadOrCoalesce line 266: LookupPreload returns null.
  5. Returns {nullptr, false}NotifyNodeEvent fires error (because false).

Contrast with a fresh modulepreload (module NOT in map): StartFetchStartLoadInternal → channel opened → NotifyOpen registers the preload → LookupPreload finds it → preloader gets AddLinkPreloadNode → eventual load event. Works correctly.

Compare with stylesheets: PreloadOrCoalesce line 248 handles SheetPreloadStatus::AlreadyComplete by returning {nullptr, true} — which fires load. Module preloads have no equivalent path.

Size & Difficulty: Medium

Touches the boundary between the preload service and module loader. Several possible fix approaches, each touching 2-3 files. Needs a way to signal "module already complete" back from the module loader to the preload service. Regression risk is moderate — the preload/module interaction is complex and undertested for this edge case.

Confidence

Dimension Rating Evidence
Reproducibility Confirmed Test case in bug reproduces, root cause traced through code
Fix direction Probable Code area identified, similar pattern exists for stylesheets (AlreadyComplete), but exact fix needs design consideration at the preload-service/module-loader boundary

Where to Start

  1. The cleanest fix is likely modeled after the stylesheet AlreadyComplete pattern. ScriptLoader::PreloadURI (or a new return value from it) needs to signal back to PreloadOrCoalesce that the module was already in the module map. Then PreloadOrCoalesce returns {nullptr, true}NotifyNodeEvent fires load.
  2. Alternatively, StartOrRestartModuleLoad's WaitForModuleFetch path could register the preload via NotifyOpen (without a channel) when the request is a preload, so the normal completion flow fires load via the preloader. This is more invasive but might be more correct long-term.
  3. Add a WPT test: import a module via import(), then dynamically create a <link rel=modulepreload crossorigin> for the same URL, assert it fires load not error.

Suggested Contacts

Who Role Last Active Why
Jon Coppeard (:jonco) blame: WaitForModuleFetch author active (bug 721236, 2026-07) Wrote the module map waiting logic; reviewed modulepreload implementation
Yoshi Cheng-Hao Huang blame: modulepreload pref/import-map code active (bug 1916277, 2026-03) Recent modulepreload-related changes
Olli Pettay (:smaug) reviewer on modulepreload impl active Reviewed original modulepreload landing, DOM: Core & HTML peer

Related Bugs

Bug Summary Status Relevance
2052837 bahn.de login page is blank ASSIGNED (S2/P1) Direct dependent — this bug is the platform root cause
2042169 bahn.de claims bot detected (Linux) NEW (S4/P3) Possibly same site, different issue
1425310 Implement modulepreload for link rel FIXED Original modulepreload implementation — the code that's now buggy
1646776 Fire load (not error) for preload links hitting CSS cache FIXED Exact same pattern for stylesheets — AlreadyComplete return value was the fix. Module preloads need equivalent handling.

★ Insight ─────────────────────────────────────
Two preload tracking systems diverge. Firefox has two separate mechanisms tracking module loads: the module loader's own module map (mFetchingModules/mFetchedModules in ModuleLoaderBase) and the preload service's preload hash (PreloadService::RegisterPreload). The script preload registers with both only when it goes through the full network path (StartLoadInternalNotifyOpen). The WaitForModuleFetch shortcut for already-fetched modules bypasses the network path entirely, so the preload service never learns the module exists — it sees a missing preloader and defaults to "error."

Bug 1646776 is the prior art. Stylesheets hit this exact same class of bug in 2020: a stylesheet already in cache wasn't registered as a preloader, so the <link rel=preload> fired error. The fix added a SheetPreloadStatus::AlreadyComplete return path. Module preloads need an analogous mechanism. Studying that fix (by Emilio) would be the fastest way to design this one.
─────────────────────────────────────────────────

Assignee: nobody → allstars.chh
Flags: needinfo?(jcoppeard)
Severity: -- → S3

When the preloading request finds an existing entry in module maps
(fetching/fetched), it returns silently and causes an error to be reported.

Adding two callbacks: OnNotifyPreloadOpen/OnNotifyPreloadStop to notify
the preload result.

test_bug_2052949.html to reproduce the reporting problem,
also add tests covering the fetching/fetched success, failure, and
syntax-error states in test_bug_2052949_states.html.

Attachment #9607063 - Attachment description: Bug 2052949 - Add OnNotifyPreloadOpen/Stop to notify PreloadService. r?jonco!,#dom-core-reviewers! → Bug 2052949 - Add NotifyPreloadOpen/Stop to notify PreloadService. r?jonco!,#dom-core-reviewers!
Attachment #9607063 - Attachment description: Bug 2052949 - Add NotifyPreloadOpen/Stop to notify PreloadService. r?jonco!,#dom-core-reviewers! → Bug 2052949 - Add NotifyPreloadOpen to notify PreloadService. r?jonco!,#dom-core-reviewers!
Attachment #9607063 - Attachment description: Bug 2052949 - Add NotifyPreloadOpen to notify PreloadService. r?jonco!,#dom-core-reviewers! → Bug 2052949 - Add ScriptLoader::NotifyPreloadOpen to notify PreloadService if the preload request is fetching/fetched/cached. r?jonco!,arai!,#dom-core-reviewers!
Attachment #9607063 - Attachment description: Bug 2052949 - Add ScriptLoader::NotifyPreloadOpen to notify PreloadService if the preload request is fetching/fetched/cached. r?jonco!,arai!,#dom-core-reviewers! → Bug 2052949 - Fire load/error event for a modulepreload module is fetching/fetched/cached. r?jonco!,arai!,#dom-core-reviewers!
Attachment #9607063 - Attachment description: Bug 2052949 - Fire load/error event for a modulepreload module is fetching/fetched/cached. r?jonco!,arai!,#dom-core-reviewers! → Bug 2052949 - Fire load/error event for a modulepreload whose module is fetching, fetched or cached. r?jonco!,arai!,#dom-core-reviewers!
Pushed by allstars.chh@gmail.com: https://github.com/mozilla-firefox/firefox/commit/6649967a438e https://hg.mozilla.org/integration/autoland/rev/6b42ae86be3e Fire load/error event for a modulepreload whose module is fetching, fetched or cached. r=jonco,dom-core-reviewers,edgar,arai

Created web-platform-tests PR https://github.com/web-platform-tests/wpt/pull/61647 for changes under testing/web-platform/tests

Status: NEW → RESOLVED
Closed: 13 days ago
Resolution: --- → FIXED
Target Milestone: --- → 155 Branch

Upstream PR merged by moz-wptsync-bot

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: