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:
- 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.
- 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.
- 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:
- Should the host be required to silently ignore
crossOriginStorage when integrity is absent, or may it emit a warning? (Authors' hunch: emit a warning.)
- 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:
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 eachimport(), 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
crossOriginStorageimport attribute, usable in both staticimportdeclarations and dynamicimport()calls, so a module can opt into COS directly at the point where it is imported, without going throughnavigator.crossOriginStorageimperatively.crossOriginStorageis a host-defined attribute (likeintegrity), 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 thecrossoriginstorageattribute 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 dynamicimport(), a page can call it first, obtain a handle, read it into aBlob, create an object URL, andimport()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 staticimportdeclaration 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 theimportstatement to use.How would you solve it?
Add
crossOriginStorageas a new supported import attribute key. The attribute is only meaningful alongsideintegrity; theintegrityhash 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 theoriginsoption directly. Instead, it mirrors thecrossoriginstorageattribute's own space-separated-string convention (see the companioncrossoriginstorageattribute issue):""(empty string)"*"A space-separated origin list is subject to the same implementation-defined maximum length as the
originsoption ofrequestFileHandle(), 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)
Same-site only (dynamic import)
Globally available (static import)
Globally available (dynamic import)
JavaScript module (no
typerequired)crossOriginStorageis independent oftype. JavaScript modules are the default and need notypeattribute:Restricted to specific origins
Processing model
When fetching a module and the
crossOriginStorageattribute is present:integrityhash in COS. If a matching resource is found and the requesting origin satisfies thecrossOriginStoragevalue, the resource is served from COS. No network request is made.integrityhash and the origin satisfiescrossOriginStorage, the user agent stores the resource in COS for future use by this or other origins.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
crossOriginStorageis present butintegrityis absent, the user agent should ignorecrossOriginStorage(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 aSyntaxError. 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 ofcrossOriginStoragein a static import'swithclause 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 } }): thewithoption is an ordinary object value, not baked-in syntax, so a page can branch at runtime: attempt the enhanced import, and on failure, retry withoutcrossOriginStorage. The obstacle is reliable feature detection: an unsupported key rejects with a plainTypeError, indistinguishable via any standardized mechanism from aTypeErrorthrown 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 onnavigator.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 reasontype: "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.crossOriginStorageinherits that safety-over-progressive-enhancement tradeoff by design; this proposal doesn't change it.Relationship to other specifications
crossOriginStoragedepends onintegritybeing present. Theintegrityhash 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.crossOriginStorageis a host-defined key within the already-landed attribute syntax. No changes to ECMAScript are required.typeattribute. If adopted,crossOriginStoragemust work on Wasm imports that carry notype— see the open questions below.cross-origin-storage(). The processing model is intentionally consistent across all three host-language integrations.crossoriginstorageattribute on<link>/<script>, which shares the same processing model.Anything else?
Open questions:
crossOriginStoragewhenintegrityis absent, or may it emit a warning? (Authors' hunch: emit a warning.)typeattribute should be required for Wasm imports. If adopted,crossOriginStoragemust be usable withouttype. This is likely fine sincecrossOriginStorageis independent oftype, but the interaction with theintegrity-dependency rule should be explicitly addressed.Related proposals
Related proposals, sharing the same underlying model:
cross-origin-storage()<request-url-modifier>.crossoriginstorageattribute to<link>and<script>#12770 for thecrossoriginstorageattribute on<link>/<script>.