Skip to content

Add crossOriginStorage import attribute (COS integration) #12771

Description

@tomayac

What problem are you trying to solve?

Large resources such as AI model weights, WebAssembly modules, and popular JavaScript libraries are frequently downloaded redundantly, once per origin, even when multiple origins use the identical bytes. The Cross-Origin Storage (COS) API (formal spec draft) addresses this with a shared, hash-keyed cache, but its only surface today is the imperative navigator.crossOriginStorage.requestFileHandle() API, which requires separate setup code before each import(), and cannot be used for static imports at all, since those run before any such setup code has a chance to execute.

This issue proposes a new host-defined crossOriginStorage import attribute, usable in both static import declarations and dynamic import() calls, so a module can opt into COS directly at the point where it is imported, without going through navigator.crossOriginStorage imperatively. crossOriginStorage is a host-defined attribute (like integrity), it requires no TC39 involvement (as per conversations with Google's TC39 folks) and belongs entirely in the HTML Standard. A parallel CSS Working Group proposal (w3c/csswg-drafts#14056) and a companion whatwg/html issue for the crossoriginstorage attribute on <link>/<script> share the same underlying model. See "Anything else?" below.

What solutions exist today?

The only existing (proposed) opt-in is navigator.crossOriginStorage.requestFileHandle(). For a dynamic import(), a page can call it first, obtain a handle, read it into a Blob, create an object URL, and import() that, but this is verbose boilerplate repeated at every call site, and it still leaves the specifier/hash pairing to be tracked by hand. For a static import declaration it does not work at all: static imports are resolved and evaluated before the module graph's top-level code runs, so there is no point at which imperative setup code could run first and hand back a URL for the import statement to use.

How would you solve it?

Add crossOriginStorage as a new supported import attribute key. The attribute is only meaningful alongside integrity; the integrity hash identifies the resource in COS. When both are present, the user agent may serve the module from COS instead of fetching it from the network, and may store a freshly fetched module in COS for future use.

Import attribute values must be string literals, so the attribute value cannot mirror the (DOMString or sequence<DOMString>) shape of the origins option directly. Instead, it mirrors the crossoriginstorage attribute's own space-separated-string convention (see the companion crossoriginstorage attribute issue):

Value Meaning
absent No COS participation (existing behavior)
"" (empty string) Same-site access only
"*" Globally available to all origins
Space-separated origin list Available to the listed origins only

A space-separated origin list is subject to the same implementation-defined maximum length as the origins option of requestFileHandle(), for the same reason: without a cap, this attribute could enumerate enough origins to functionally approximate "*" without going through it (see Cross-site probing).

Examples

Same-site only (static import)
import styles from "same-site-styles.css" with {
  type: "css",
  integrity: "sha256-abc123...",
  crossOriginStorage: "",
};
Same-site only (dynamic import)
const styles = await import("same-site-styles.css", {
  with: {
    type: "css",
    integrity: "sha256-abc123...",
    crossOriginStorage: "",
  },
});
Globally available (static import)
import styles from "popular-css-framework.css" with {
  type: "css",
  integrity: "sha256-abc123...",
  crossOriginStorage: "*",
};
Globally available (dynamic import)
const styles = await import("popular-css-framework.css", {
  with: {
    type: "css",
    integrity: "sha256-abc123...",
    crossOriginStorage: "*",
  },
});
JavaScript module (no type required)

crossOriginStorage is independent of type. JavaScript modules are the default and need no type attribute:

import { something } from "popular-utils.js" with {
  integrity: "sha256-abc123...",
  crossOriginStorage: "*",
};
Restricted to specific origins
import styles from "acme-inc-corporate.css" with {
  type: "css",
  integrity: "sha256-def456...",
  crossOriginStorage: "https://acme-inc.example.com https://acme-cdn.example.com",
};

Processing model

When fetching a module and the crossOriginStorage attribute is present:

  1. COS lookup: Before issuing a network request, the user agent looks up the integrity hash in COS. If a matching resource is found and the requesting origin satisfies the crossOriginStorage value, the resource is served from COS. No network request is made.
  2. Network fetch fallback: If no COS hit, the resource is fetched from the network as usual. If the fetched content matches the integrity hash and the origin satisfies crossOriginStorage, the user agent stores the resource in COS for future use by this or other origins.
  3. Hash mismatch: If the fetched content does not match integrity, the resource is rejected per existing SRI behavior and is not stored in COS.

Step 1's "found" is not just presence-in-COS: even when the requesting origin satisfies crossOriginStorage, the lookup can still miss for privacy reasons — the hash may not be on the Public Hash List, or the user agent may apply GREASE'ing. This is indistinguishable from a genuine cache miss and falls through to step 2 exactly the same way; see Availability gating.

When crossOriginStorage is present but integrity is absent, the user agent should ignore crossOriginStorage (no COS participation). A console warning is recommended.

Progressive enhancement

Unlike the HTML and CSS integrations, this one cannot degrade gracefully as written — and the failure mode differs by import form, verified directly against a current browser:

  • Static import … from "url" with { crossOriginStorage }: an unsupported attribute key (or a non-string value) is a SyntaxError. An early error, thrown while parsing the module and before any of its code runs. This isn't a per-import failure: it invalidates the entire containing module (though not unrelated module graphs elsewhere on the page). There is no hook a page can use to catch this, feature-detect around it, or supply a fallback. The literal presence of crossOriginStorage in a static import's with clause is an unconditional requirement that the host understand it. An author targeting engines that may not support it cannot use this form at all; the only options are shipping separate COS-aware and COS-unaware builds, or routing the resource through a dynamic import instead.

  • Dynamic import(url, { with: { crossOriginStorage } }): the with option is an ordinary object value, not baked-in syntax, so a page can branch at runtime: attempt the enhanced import, and on failure, retry without crossOriginStorage. The obstacle is reliable feature detection: an unsupported key rejects with a plain TypeError, indistinguishable via any standardized mechanism from a TypeError thrown for other reasons (the message text, e.g. "Invalid attribute key", is not a stable API to depend on). A more robust pattern is a positive capability check performed once, before ever constructing the enhanced with clause, e.g., gating on navigator.crossOriginStorage's presence, rather than reacting to the failure itself.

This isn't a crossOriginStorage-specific gap: Import Attributes deliberately make an unsupported key a hard failure rather than a silently-ignored one, for the same reason type: "json" must fail rather than degrade, silently ignoring an unrecognized attribute could let a resource be type-confused (e.g. a JSON response parsed as executable script) instead of rejected outright. crossOriginStorage inherits that safety-over-progressive-enhancement tradeoff by design; this proposal doesn't change it.

Relationship to other specifications

  • import-with-integrity: crossOriginStorage depends on integrity being present. The integrity hash serves double duty: it is verified against the fetched content (SRI) and used as the COS lookup key. The two attributes are designed to compose.
  • import-attributes (landed): crossOriginStorage is a host-defined key within the already-landed attribute syntax. No changes to ECMAScript are required.
  • WebAssembly ESM integration: Proposes that Wasm modules should be importable without a type attribute. If adopted, crossOriginStorage must work on Wasm imports that carry no type — see the open questions below.
  • Cross-Origin Storage (formal spec): Defines the underlying COS mechanism, availability gating, and privacy mitigations that apply equally to this import-attribute path.
  • w3c/csswg-drafts#14056: The parallel CSS integration via cross-origin-storage(). The processing model is intentionally consistent across all three host-language integrations.
  • Companion whatwg/html issue: The crossoriginstorage attribute on <link>/<script>, which shares the same processing model.

Anything else?

Open questions:

  1. Should the host be required to silently ignore crossOriginStorage when integrity is absent, or may it emit a warning? (Authors' hunch: emit a warning.)
  2. The WebAssembly ESM integration proposal argues that no type attribute should be required for Wasm imports. If adopted, crossOriginStorage must be usable without type. This is likely fine since crossOriginStorage is independent of type, but the interaction with the integrity-dependency rule should be explicitly addressed.

Related proposals

Related proposals, sharing the same underlying model:

Metadata

Metadata

Assignees

No one assigned

    Labels

    addition/proposalNew features or enhancementsneeds implementer interestMoving the issue forward requires implementers to express interest

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions