Skip to content

Add CROSS_ORIGIN_STORAGE link flag#27066

Merged
sbc100 merged 88 commits into
emscripten-core:mainfrom
tomayac:cross-origin-storage
Jun 18, 2026
Merged

Add CROSS_ORIGIN_STORAGE link flag#27066
sbc100 merged 88 commits into
emscripten-core:mainfrom
tomayac:cross-origin-storage

Conversation

@tomayac

@tomayac tomayac commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Integrates the WICG Cross-Origin Storage API into Emscripten's standard Wasm loading path as a progressive enhancement.

When -sCROSS_ORIGIN_STORAGE=1 is set at link time, Emscripten computes the SHA-256 hash of the final .wasm binary and embeds it as a build-time constant. At runtime the generated JavaScript tries to retrieve the module from the cross-origin cache before falling back to the normal fetch() / WebAssembly.instantiateStreaming() path, so pages always load regardless of browser support.

New settings

  • -sCROSS_ORIGIN_STORAGE=1 — enables the feature (Web target only, disabled by default).
  • -sCROSS_ORIGIN_STORAGE_ORIGINS — controls which origins can read the cached file: '*' (default, globally available), an explicit HTTPS origin list, or [] (same-site only).

Because no browser ships the API natively yet, testing requires the COS Chrome extension (source), which polyfills navigator.crossOriginStorage on every page.

Example

Without the COS API

Loading goes through the normal fetch() / WebAssembly.instantiateStreaming() path:

Screenshot 2026-06-08 at 12 26 41

With the COS API

Now with the COS Chrome extension installed, see its popup window for details.

First time load

Loading goes through the normal fetch() / WebAssembly.instantiateStreaming() path, but the resource is stored in COS for the next time:

Screenshot 2026-06-08 at 12 27 41

Repeated load

Loading now goes through the COS path:

Screenshot 2026-06-08 at 12 29 34

Loading on a different origin

Loading now goes through the COS path and the resource is shared across origins:

Screenshot 2026-06-08 at 12 31 02

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tomayac !

Mostly looks good. Very impressive documentation (maybe even too much details, if that is possible :)

Comment thread site/source/docs/compiling/CrossOriginStorage.rst Outdated
Comment thread site/source/docs/compiling/CrossOriginStorage.rst Outdated
Comment thread site/source/docs/compiling/CrossOriginStorage.rst Outdated
Comment thread site/source/docs/compiling/CrossOriginStorage.rst Outdated
Comment thread site/source/docs/compiling/CrossOriginStorage.rst
Comment thread tools/link.py Outdated
Comment thread tools/link.py Outdated
Comment thread tools/link.py Outdated
Comment thread tools/link.py Outdated
Comment thread tools/link.py Outdated

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little concerned about adding more Module properties here.

For the Module['wasmSHA256'], how about we just don't initially support custom instantiateWasm here? Avoid the need to document this or add this new property?

Regarding the 3 new callback.. are they really needed by users? Or is it mostly useful for testing?

@tomayac

tomayac commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks for the great review feedback, will address tomorrow!

Just a quick reaction to the below:

I'm a little concerned about adding more Module properties here.

For the Module['wasmSHA256'], how about we just don't initially support custom instantiateWasm here? Avoid the need to document this or add this new property?

Custom instantiateWasm is literally the first thing I encountered when I tried this with SQLite Wasm, so having the hash as an easily accessible property seemed to make a lot of sense to make custom COS easy.

Regarding the 3 new callback.. are they really needed by users? Or is it mostly useful for testing?

It was mostly for testing, though may be useful in general as well for module owners to easily report on COS stats in their analytics?! Is there a way to have those just for testing?

@sbc100

sbc100 commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

It was mostly for testing, though may be useful in general as well for module owners to easily report on COS stats in their analytics?! Is there a way to have those just for testing?

You could put them behind -sASSERTIONS maybe?

One issue we have historically had with these module properties is they unconditionally took up space in the output file, even for users who didn't use them. So we created INCOMING_MODULE_JS_API along with EXTRA_INCOMING_JS_API. Then you can wrap the usage in expectToReceiveOnModule('xxx') to avoid overhead for folks that don't use it.

In this case (and for all new settings), I think its best not to enable them by default but add them to EXTRA_INCOMING_JS_API instead, so users can opt into them.

@tomayac tomayac left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped =1 from all -sCROSS_ORIGIN_STORAGE references throughout the guide.

@tomayac

tomayac commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator Author

Alright, this was intense, but I hope to have addressed your review comments satisfyingly, @sbc100!

The module properties are all opt-in now, except for the one with the hash, which makes it more straight-forward to roll your own instantiation.

I've also managed to add real browser tests that install the extension for testing, so we can actually be more confident that the flag works.

PTAL! Thanks <3

@tomayac tomayac requested a review from sbc100 June 9, 2026 16:36

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking much better!

The amount of testing in test_other.py is perhaps still a little excessive. I'm not normally in the position of asking folks to add fewer tests but... I guess I am. @kripken WDYT? Perhaps they could be condenses / reduced somehow?

Comment thread .gitignore Outdated
Comment thread src/preamble.js Outdated
Comment thread src/settings.js Outdated
Comment thread src/settings.js Outdated
Comment thread src/preamble.js Outdated
Comment thread test/cross_origin_storage/Makefile Outdated
Comment thread test/test_other.py Outdated
Comment thread test/test_other.py Outdated
Comment thread test/test_other.py Outdated
Comment thread tools/link.py Outdated
@tomayac

tomayac commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Looking much better!

:-D

The amount of testing in test_other.py is perhaps still a little excessive. I'm not normally in the position of asking folks to add fewer tests but... I guess I am. @kripken WDYT? Perhaps they could be condenses / reduced somehow?

Reduced to just 5 tests now in b09b640.

@tomayac tomayac requested a review from sbc100 June 10, 2026 09:00
Comment thread ChangeLog.md Outdated
Comment thread test/test_other.py Outdated
Comment thread test/cross_origin_storage/main.cpp Outdated
Comment thread test/cross_origin_storage/main.cpp Outdated
Comment thread test/cross_origin_storage/index.html Outdated
Comment thread ChangeLog.md Outdated
Comment thread tools/link.py Outdated
Comment thread test/test_browser.py Outdated
Comment thread test/test_browser.py Outdated
Comment thread test/browser_common.py
@tomayac tomayac requested review from kripken and sbc100 June 10, 2026 17:24
Comment thread src/preamble.js Outdated
Comment thread src/preamble.js Outdated
Comment thread src/preamble.js Outdated
Comment thread ChangeLog.md Outdated
@tomayac tomayac requested a review from sbc100 June 10, 2026 17:58

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm % final nits

Comment thread test/test_other.py Outdated
Comment thread test/test_other.py Outdated
Comment thread test/test_other.py Outdated
Comment thread test/test_browser.py
@tomayac

tomayac commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Thanks a lot for the reviews, @sbc100 and @kripken! PTAL.

@sbc100 sbc100 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm!

I do have a qestion around when we might be able to remove the polyfil, and a slight concert about shipping features that are supported in zero browser.

But since these are tagged as experimental it should be easy enough to rib them out if that feature doesn't pan out I guess?

Comment thread test/browser_common.py
@tomayac

tomayac commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

As this is an opt-in and marked as experimental, most developers will likely never touch it. At this stage, this is meant for "white glove" partners we may want to work with directly, like SQLite Wasm (and also see ffmpegwasm/ffmpeg.wasm#940), and of course interested power users.

For availability and a feature bug to track, keep an eye on ChromeStatus for current developer interest. (You're in really good company.) We know that we will have a chicken and egg problem rolling this out, so the sooner we start, the higher the chance of cache hits once it lands, and developers will need lead time.

For early adopters, there's the Cross-Origin Storage extension that accurately implements the proposed API today. (We use a similar approach with WebMCP, where the Model Context Tool Inspector extension mimics an actual agent's interactions.)

Thanks for the reviews again! Really appreciate you taking the time! I was really out of my comfort zone most of the time, so thanks for being my patient guard rails! <3

@sbc100

sbc100 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

To get the codesize tests passing you will want to do emsdk install tot then run ./tools/maint/rebaseline_tests.py (or ./test/runner codesize --rebase)

@tomayac

tomayac commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator Author

Alright, hope this did the trick. CI running.

@sbc100

sbc100 commented Jun 11, 2026

Copy link
Copy Markdown
Collaborator

Ah, it looks like main is actually out-of-date now. I will update that now, then you will need to rebaseline once again I'm afraid. Sorry this part is a little annoying.

@tomayac

tomayac commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

Branch updated, CI running, fingers crossed.

@sbc100

sbc100 commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Sorry, we had some CI issues. See #27110.

If you rebase I think we are good to land.

tomayac added 24 commits June 18, 2026 19:47
SIDE_MODULE is now a hard error (no JS glue emitted — genuinely
incompatible). SPLIT_MODULE and MAIN_MODULE partial-coverage warnings
are dropped; they are not true incompatibilities and add noise for an
experimental feature. Per reviewer feedback (r3383144746).
- Drop -sENVIRONMENT=web (web is included by default)
- Drop inline comment and docstrings
- Drop section banner comment

Per reviewer feedback.
- Use globalThis.navigator?.crossOriginStorage (matches codebase pattern)
- Move var cosHash inside the if block
- Hardcode 'SHA-256' in the template; drop <<< WASM_HASH_ALGORITHM >>> placeholder

Per reviewer feedback.
Collapse the NotFoundError / NotAllowedError / else branches into a
single bare catch that always falls back to a network fetch and
attempts a fire-and-forget store into COS. This makes the code
robust against any future renaming of the error (see
WICG/cross-origin-storage#62).
- Remove duplicate test_deprecated_settings definition in test_other.py
- Fix extra blank line before test_cross_origin_storage_fallback in test_browser.py
- Fix extra blank line after WASM_BINDGEN block in tools/link.py
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (24) test expectation files were updated by
running the tests with `--rebaseline`:

```
codesize/test_codesize_cxx_ctors1.json: 151801 => 151847 [+46 bytes / +0.03%]
codesize/test_codesize_cxx_ctors2.json: 151204 => 151250 [+46 bytes / +0.03%]
codesize/test_codesize_cxx_except.json: 195695 => 195741 [+46 bytes / +0.02%]
codesize/test_codesize_cxx_except_wasm.json: 166925 => 166971 [+46 bytes / +0.03%]
codesize/test_codesize_cxx_except_wasm_legacy.json: 164809 => 164855 [+46 bytes / +0.03%]
codesize/test_codesize_cxx_lto.json: 120628 => 120689 [+61 bytes / +0.05%]
codesize/test_codesize_cxx_mangle.json: 262174 => 262220 [+46 bytes / +0.02%]
codesize/test_codesize_cxx_noexcept.json: 153801 => 153847 [+46 bytes / +0.03%]
codesize/test_codesize_cxx_wasmfs.json: 179676 => 179596 [-80 bytes / -0.04%]
codesize/test_codesize_files_wasmfs.json: 63873 => 63677 [-196 bytes / -0.31%]
codesize/test_codesize_hello_O0.json: 38629 => 38687 [+58 bytes / +0.15%]
codesize/test_codesize_hello_dylink_all.json: 855713 => 855762 [+49 bytes / +0.01%]
codesize/test_codesize_minimal_O0.json: 19682 => 19740 [+58 bytes / +0.29%]
codesize/test_minimal_runtime_code_size_audio_worklet.json: 16401 => 16399 [-2 bytes / -0.01%]
codesize/test_minimal_runtime_code_size_hello_embind.json: 14903 => 14904 [+1 bytes / +0.01%]
codesize/test_minimal_runtime_code_size_hello_embind_val.json: 11642 => 11643 [+1 bytes / +0.01%]
codesize/test_minimal_runtime_code_size_hello_wasm_worker.json: 4116 => 4128 [+12 bytes / +0.29%]
codesize/test_minimal_runtime_code_size_hello_webgl2_wasm.json: 13161 => 13161 [+0 bytes / +0.00%]
codesize/test_minimal_runtime_code_size_hello_webgl2_wasm2js.json: 18531 => 18534 [+3 bytes / +0.02%]
codesize/test_minimal_runtime_code_size_hello_webgl_wasm.json: 12699 => 12699 [+0 bytes / +0.00%]
codesize/test_minimal_runtime_code_size_hello_webgl_wasm2js.json: 18057 => 18060 [+3 bytes / +0.02%]
codesize/test_minimal_runtime_code_size_random_printf_wasm.json: 11052 => 11057 [+5 bytes / +0.05%]
codesize/test_minimal_runtime_code_size_random_printf_wasm2js.json: 17413 => 17417 [+4 bytes / +0.02%]
codesize/test_unoptimized_code_size.json: 176861 => 177085 [+224 bytes / +0.13%]

Average change: +0.04% (-0.31% - +0.29%)
```
…e.js

- ChangeLog.md: remove duplicate RST-format entry and bad-merge artifact;
  keep single clean Markdown entry for -sCROSS_ORIGIN_STORAGE
- src/settings.js: reflow CROSS_ORIGIN_STORAGE comment so first line
  is not shorter than the rest
- src/preamble.js: drop redundant .length === 1 check in origins #if
  (Python link-time validation already prevents '*' from being mixed
  with explicit origins, so [0] === '*' implies length === 1)
If CROSS_ORIGIN_STORAGE is set and we reach this point in the link
but wasm_target does not exist, crashing is the correct behavior
rather than silently skipping the hash substitution.
Default ENVIRONMENT is empty which means ENVIRONMENT_MAY_BE_WEB=True,
so the explicit flag adds no value in these error-path tests.
…cmds

Default ENVIRONMENT already includes web, so the flag is redundant.
Condense the two single-flag run_process calls into one line each.
This is an automatic change generated by tools/maint/rebaseline_tests.py.

The following (2) test expectation files were updated by
running the tests with `--rebaseline`:

```
codesize/test_codesize_cxx_wasmfs.json: 179722 => 179596 [-126 bytes / -0.07%]
codesize/test_codesize_hello_dylink_all.json: 855768 => 855762 [-6 bytes / -0.00%]

Average change: -0.04% (-0.07% - -0.00%)
```
@tomayac tomayac force-pushed the cross-origin-storage branch from 0ee4946 to 57e1ec4 Compare June 18, 2026 17:49
@sbc100 sbc100 merged commit 25e4e8d into emscripten-core:main Jun 18, 2026
39 checks passed
@tomayac tomayac deleted the cross-origin-storage branch June 19, 2026 06:51
@tomayac

tomayac commented Jun 19, 2026

Copy link
Copy Markdown
Collaborator Author

Amazing, it's merged 🥳! Thanks for making it happen, @sbc100, really excited to see what this will unlock in the future! Appreciate a lot all the help in getting this landed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants