As per https://github.com/tc39/ecma262/issues/1435 and https://github.com/WebAssembly/threads/issues/124 the plan is that SharedArrayBuffer is enabled by default, as defined by JavaScript, but its high-resolution timer capabilities are restricted via restricting postMessage(). (Other APIs that can enable high-resolution timer capabilities with SharedArrayBuffer independently of postMessage() would have to be similarly restricted. No such APIs as of yet, hopefully [AllowShared] will allow us to track these.)
To make postMessage() do the right thing, both Cross-Origin-Opener-Policy (COOP; #3740) and Cross-Origin-Embedder-Policy (COEP; #4175) have to be set for the document (or shared/service worker).
I realized we did not have a tracking issue for that specific change, so here it is.
(For clarity, without postMessage() "Shared" in SharedArrayBuffer is meaningless and it's effectively equivalent to ArrayBuffer.)
cc @whatwg/security @rniwa @csreis
Folding #4872 into this, we'll expose this COOP+COEP state in JavaScript as self.crossOriginIsolated.
Also, you can now play with this in Firefox Nightly: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer/Planned_changes.
It looks COOP+COEP does not inherited to blob: uri script?
It seems now every dynamic created script can't use sharedArrayBuffer to share data on firefox nightly(74.0a1 (2020-01-06)).
The most worst thing, it break feature detection of SharedArrayBuffer.
Because the SharedArrayBuffer do exist, just you can't use it to pass data between worker.
The script detected it may try to use it and result in a crash.
If blocking this is actually desired, maybe we should hide the whole constructor instead so it won't break backward compatibility?
Some script break by current implementation i found:
https://github.com/qiaozi-tech/WXInlinePlayer/blob/1671bf6a50f88bcd935a87b98830319a3608b2b2/src/loader/stream.js#L52-L57
It looks COOP+COEP does not inherited to blob: uri script?
That's something we should fix (and specify).
The most worst thing, it break feature detection of SharedArrayBuffer.
This is by design to not "fragment" JavaScript. self.crossOriginIsolated is to be consulted instead. If this ends up breaking too many sites we'll have to reconsider of course.
@mmis1000 do you have an example of blob: URLs not working in Firefox? I created https://github.com/web-platform-tests/wpt/pull/21146 (and various other tests) and thus far I cannot reproduce, except for blob: URL popups. I was expecting blob: URL dedicated workers to not work, but they do as far as I can tell. (It'd be even better if you could file a bug at https://bugzilla.mozilla.org/enter_bug.cgi?product=Core.)
@mmis1000 I'd really appreciate it if you could look into this one more time.
This change to SharedArrayBuffer is a pretty major issue for us at Esri as we are currently using feature detection to determine whether or not use use SharedArrayBuffer or a workaround that involves copying over ArrayBuffers. This breaks our JS mapping API on FF nightly (features do not display), and we will have to backport a fix to all previous versions of our API with this issue, as well as to our enterprise deployments.
It seems like this change would essentially break every app that currently uses SharedArrayBuffer, hiding the constructor would have been a bit nicer here as at least it wouldn't be a breaking change.
Do we know for sure that this proposal is final? We ideally want to patch our API before this is live in FF (seems like this is currently in the next version?)
This is still the plan of action, yes. (And yes, Firefox 75 might have SharedArrayBuffer available, but no postMessage().) The reason for not hiding the constructor is that "we" don't want to put conditionals around "language features". Therefore the conditional is in serialization/deserialization.
And yes, Firefox 75 might have
SharedArrayBufferavailable, but nopostMessage().
This seems to be rolling out with Firefox 74 since it's already broken at: https://developers.arcgis.com/javascript/latest/sample-code/layers-featurelayer/live/index.html
There's no mention of this in either of these:
https://www.fxsitecompat.dev/en-CA/versions/74/
https://www.fxsitecompat.dev/en-CA/versions/75/
At the moment, the SharedArrayBuffer constructor is only enabled in Firefox in "early beta or earlier". See https://searchfox.org/mozilla-central/rev/5a10be606f2d76ef22f1f44565749490de991d35/modules/libpref/init/all.js#1206-1210
74 is still in early beta, so SharedArrayBuffer is enabled there at the moment, to allow testing. As 74 moves to late beta and release, SharedArrayBuffer will be disabled.
The reason for not hiding the constructor is that "we" don't want to put conditionals around "language features". Therefore the conditional is in serialization/deserialization.
Hmm I guess I'm just kind of confused on this point. Without the serialization/deserialization, there's no use-case for SharedArrayBuffer. Whatever logic you have to workaround not having access to SharedArrayBuffer has to happen at construction time, so you still do need to wrap creating SharedArrayBuffers in a conditional. Effectively this replaces the typical feature detection with a different flag that needs to be checked instead.
This is further complicated by the fact that browsers already do support SharedArrayBuffer. This means that to check for whether or not you can use shared memory, we have to do both feature detection and check this new conditional. Basically:
function hasSharedMemory() {
const hasSharedArrayBuffer = "SharedArrayBuffer" in global;
const notCrossOriginIsolated = global.crossOriginIsolated === false;
return hasSharedArrayBuffer && !notCrossOriginIsolated;
}
Breaking feature detection in this way is also kind of scary for using new browser features going forward. I suppose one could argue that new JS standards should be treated skeptically until they are fully implemented by all browsers.
SharedArrayBuffer cannot be detached, which is a useful property. And it would allow some code patterns that do not require threaded usage, but can, not have to use different buffers. And yeah, this is a somewhat unusual case that doesn't generally apply to features.
Apologies for not engaging here earlier.
It's also V8's preference to gate the existence of the SharedArrayBuffer constructor on COOP+COEP, precisely for the reasons of not breaking existing feature detection in the wild.
@annevk I take the "don't make language features conditional" point, but pragmatism here trumps that IMO. I find the argument from @mmgeorge compelling: browsers that don't implement SharedArrayBuffer in practice can't be meaningfully distinguished from sites that don't opt into COOP+COEP, so I see no reason to make web devs write two kinds of feature detection code for SABs.
Could we change this?
Edit: To preempt Atomics not being gated on as well, I have plans to enable Atomics to be usable on TypedArrays backed by regular ArrayBuffers to align with wasm.
We haven't seen any breakage in the Firefox Nightly/Beta landscape thus far from having SharedArrayBuffer's constructor exposed and also have postMessage() throw outside of cross-origin isolated environments. (Emscripten doesn't do any meaningful feature detection either.)
And the reason to have SharedArrayBuffer available is because it can also be used without threads as a non-detachable ArrayBuffer and there are numerous web platform APIs that will work with it regardless of the environment being cross-origin isolated.
For a plan communicated well over a year ago and reiterated at various points this is also an extremely late request for change.
cc @lukewagner
The SharedArrayBuffer don't even work with TextEncoder/TextDecoder.
Lots of api don't work with it either.
I am curious about who actually use SharedArrayBuffer for non cross worker reason and for what reason?
The SharedArrayBuffer don't even work with TextEncoder/TextDecoder.
It does per spec. That part is not implemented in Gecko yet, looks like...
Another potential benefit of having SAB always be available, while keeping only postMessage() gated on COOP+COEP, is that it would enable the creation of ungated Web APIs where the host could write into a SAB from a background host thread. (This assumes the host's racy writes could not be used to construct a high-res timer.) A concrete example would be avoiding the copying inherent in BYOB streams. If SAB's existence was gated, so necessarily would be these Web APIs.
For a plan communicated well over a year ago and reiterated at various points this is also an extremely late request for change.
For not engaging here until so late, that's on us. You're right that this request for change is very late. This slipped through the cracks in V8 and Chrome for too long. This request isn't lightly made: we don't want to break a non-trivial population of real-world users.
We haven't seen any breakage in the Firefox Nightly/Beta landscape thus far from having SharedArrayBuffer's constructor exposed and also have postMessage() throw outside of cross-origin isolated environments. (Emscripten doesn't do any meaningful feature detection either.)
The Chrome team has gathered data that shows there will be real-world breakage.
To wit, use counters and real-world feature detection code.
SAB use is at just under 1.25% among all pageloads in Chrome and growing. Until we ship COOP+COEP, SharedArrayBuffer is available where sharing it across agents was allowed. E.g., on desktop, where we had site isolation. That's quite a lot of users.
Edit: I said something previously that suggests Chrome turned off the SAB constructor. It has been and remains still available on platforms with site isolation until COOP+COEP ships.
We also collected the top scripts that use SABs. I went through the top 4 to see how they're used. Relevant pretty-printed snippets below:
https://static.adsafeprotected.com/sca.17.4.114.js
function() {
if (
"function" == typeof SharedArrayBuffer &&
util.fnc(SharedArrayBuffer)
)
try {
var a = new SharedArrayBuffer(1024);
if (
"[object SharedArrayBuffer]" == a.toString() &&
a.byteLength &&
1024 === a.byteLength
)
try {
SharedArrayBuffer(1024);
} catch (a) {
return !0;
}
} catch (a) {}
return !1;
}
That function is then used to check if SABs can be used.
https://meetfam.com/static/fam/fam.ad180408bcd0b1f105e5.js
https://meetfam.com/static/fam/fam-discount.7e41fe517671d8b5bfad.js
https://unpkg.com/[email protected]/umd/react.development.js
var sharedProfilingBuffer = enableProfiling ? // $FlowFixMe Flow doesn't know about SharedArrayBuffer
typeof SharedArrayBuffer === 'function' ? new SharedArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT) : // $FlowFixMe Flow doesn't know about ArrayBuffer
typeof ArrayBuffer === 'function' ? new ArrayBuffer(profilingStateSize * Int32Array.BYTES_PER_ELEMENT) : null // Don't crash the init path on IE9
: null;
These ultimately all come from React or inlined parts of React.
React's profiling feature uses a SharedArrayBuffer to read profiling info of the Scheduler from a Worker, and this is gated on whether or not the SharedArrayBuffer constructor exists.
V8's engagement is indeed late. At the same time, not breaking our existing users is the higher order concern.
Is the data compelling enough to consider the change?
And the reason to have SharedArrayBuffer available is because it can also be used without threads as a non-detachable ArrayBuffer and there are numerous web platform APIs that will work with it regardless of the environment being cross-origin isolated.
For the non-detachable use case, that seems like a useful feature to add to ArrayBuffers themselves. I'd be happy to champion such a thing.
Another potential benefit of having SAB always be available, while keeping only postMessage() gated on COOP+COEP, is that it would enable the creation of ungated Web APIs where the host could write into a SAB from a background host thread. (This assumes the host's racy writes could not be used to construct a high-res timer.) A concrete example would be avoiding the copying inherent in BYOB streams. If SAB's existence was gated, so necessarily would be these Web APIs.
I don't think that's necessarily true. Hosts can provide under-the-hood shared buffers without giving users the ability to create new ones via an exposed SharedArrayBuffer constructor. The .constructor property can either be nulled out. Or even if it were available, that's not the worst thing. I agree technically there's not much harm in letting users create SABs for intra-thread use. Again, the high-order bit here is not breaking existing feature detection code, which only relies on the existence of the SharedArrayBuffer global constructor.
Edit: Put another way, I see no reason to gate future Web APIs that want to vend SABs on the decision of the visibility of the constructor.
Hosts can provide under-the-hood shared buffers without giving users the ability to create new ones via an exposed SharedArrayBuffer constructor.
I'm talking about future Web APIs that, motivated by the general performance theme of "APIs shouldn't perform allocations; they should write into views of buffers/memories allocated by client code", accept a view to a SAB so that they can write into it racily from another thread. So client JS code needs to be able to create the SAB to use the Web API. (Yes, a workaround could be defining some new function for constructing SABs.)
I'm talking about future Web APIs that, motivated by the general performance theme of "APIs shouldn't perform allocations; they should write into views of buffers/memories allocated by client code", accept a view to a SAB so that they can write into it racily from another thread. So client JS code needs to be able to create the SAB to use the Web API. (Yes, a workaround could be defining some new function for constructing SABs.)
Ah, interesting. What's a possible future API you had in mind that uses this pattern?
@lukewagner Also to get on the same page for wasm, is Mozilla's position here that WebAssembly.Memory({shared: true}) create an actually shared memory, instead of ignoring the flag? Chrome currently ignores shared if threading isn't available.
Ah, interesting. What's a possible future API you had in mind that uses this pattern?
For starters, the better BYOB stream API I mentioned above.
Also to get on the same page for wasm, is Mozilla's position here that WebAssembly.Memory({shared: true}) create an actually shared memory, instead of ignoring the flag? Chrome currently ignores shared if threading isn't available.
Well, at the moment we just throw if shared:true and COOP/COEP are not enabled, but I think that's just where we landed a year ago, not necessarily our goal state. In general, I think it'd be good to be symmetric with SAB... although it's less clear what that means in this case.
For starters, the better BYOB stream API I mentioned above.
There's no clear consensus in that thread. Like @domenic, I'm also wondering if it can't be done with regular ArrayBuffers that are in fact racily written to behind the scenes. SABs is all about the user opting in to astonishing behaviors. For a web API that wants to racily write into a buffer on a site without COOP+COEP, I see no reason to require users to opt into astonishment. The BYOB API would need to signal when data in the buffer is ready for consumption. One reason to require users to opt into astonishment is that that signal is performed in a very low-level way (i.e. via futex). That seems like the wrong abstraction level to me? If instead the signal is high-level, I think we can spec BYOB APIs in such a way that they take ArrayBuffers, can be racily written to by another thread by engines, and do not incur any weak memory reasoning on the user. E.g., conceptually detach the view until data is ready.
In general, I think it'd be good to be symmetric with SAB... although it's less clear what that means in this case.
Definitely agreed.
Anyway the compat issue stands and I don't see a good way to work around it without gating the constructor. I'd love to get alignment here. Should we do a call or something to try to hash it out?
I chatted with the V8 wasm folks, we agree technically that it's probably good to make shared memory available everywhere and check for concurrent access instead. Here's our proposal to mitigate compat risk:
SharedArrayBuffer constructor in HTML on COOP+COEP ("the price we pay for Spectre")WebAssembly.Memory({shared:true}) always create a shared memory regardless of COOP+COEP. This needs to be implemented by everyone! Chrome and Safari currently ignore shared if threading is not available. Firefox throws a LinkError. This change is probably safe from compat risk due to low usage in Chrome. @lukewagner What do FF numbers look like here?SharedArrayBuffer if we can get data that enough users have moved to detecting via self.crossOriginIsolated, or@kmiller68 What's Safari's take here?
This sounds like a reasonable compromise to me.
To be extra clear for posterity, the plan for Chrome is:
SharedArrayBuffer will remain regardless of COOP+COEP in the near term when COOP+COEP ships. It will be gated on it in the future. New APIs will that need concurrent access will be gated on COOP+COEP.SharedArrayBuffer will be gated on COOP+COEP when COOP+COEP ships.This means the feature detection code that checks for SABs today keep working for both desktop and mobile both when COOP+COEP initially ship and in the transition period, before SAB use is fully migrated to be gated on COOP+COEP on desktop.
It seems one could do const sab = new WebAsssembly.Memory({ shared: true, initial: 1, maximum: 1 }).buffer to create a SharedArrayBuffer instance when cross-origin isolated is false, right?
@annevk Why is that valuable if only one thread can look at the SAB? I could see an issue with speculative execution and Atomics even in one thread. It seems like that could be solved though by the JS engine only doing the actual atomic if the site is cross-origin isolated.
It seems one could do const sab = new WebAsssembly.Memory({ shared: true, initial: 64 }).buffer to create a SharedArrayBuffer instance when cross-origin isolated is false, right?
Right. SharedArrayBuffer is special because we don't want to break or degrade existing sites. I'm not against the symmetry argument that shared memory should be available regardless of cross-origin isolation and whether concurrent access is available. The V8 wasm team felt likewise. (Though I'm also not that much of a champion, just that absent of other concerns I see no reason to disallow it.)
Why is that valuable if only one thread can look at the SAB?
@kmiller68 The arguments put forth so far are:
I don't find any or the sum of them too compelling, mostly lukewarm, thus the compromise on having the wasm shared memory available while gating SharedArrayBuffer for breakage concerns.
@syg Huh? I was speaking to the fact that Anne was showing there's a way to get an ArrayBuffer with shared enabled in JS. Although, perhaps I misunderstood the purpose of the comment.
Maybe what Anne was saying is: Why disable the SAB constructor if you can just get a SAB via Wasm? Which is a question I'd also like to know the answer to.
@kmiller68 The reasoning for how we arrived at this point is as follows.
The fact that you can get a SAB out of a shared wasm memory is intentional even when the SharedArrayBuffer is not present due to not having cross-origin isolation. The original plan which was to make SAB _and_ shared wasm memory always available (for the reasons listed in https://github.com/whatwg/html/issues/4732#issuecomment-600727901), regardless of COOP+COEP, and to have COOP+COEP gate the postMessage-ability of SABs.
I requested on behalf of Chrome that we also gate the SharedArrayBuffer constructor on COOP+COEP to not break and degrade existing sites, because Chrome on desktop has had SharedArrayBuffer available since it ships with site isolation. Since the motivation for gating the constructor is back compat and not objection to the technical arguments, the compromise is that wasm shared memory remains available everywhere.
I started updating existing tests at https://github.com/web-platform-tests/wpt/issues/22358 to account for not exposing the constructor all the time. I'll specify it as part of #4734 I think.
(@syg and I discussed the possibility of exposing the constructor on some globals, but V8 were not in favor and I don't feel particularly strongly about it.)
All tests have outstanding PRs now. Deleting the constructor is specified in the aforementioned PR. https://bugzilla.mozilla.org/show_bug.cgi?id=1624266 tracks aligning Firefox with this change. Review of all of this appreciated.
Most helpful comment
Apologies for not engaging here earlier.
It's also V8's preference to gate the existence of the
SharedArrayBufferconstructor on COOP+COEP, precisely for the reasons of not breaking existing feature detection in the wild.@annevk I take the "don't make language features conditional" point, but pragmatism here trumps that IMO. I find the argument from @mmgeorge compelling: browsers that don't implement
SharedArrayBufferin practice can't be meaningfully distinguished from sites that don't opt into COOP+COEP, so I see no reason to make web devs write two kinds of feature detection code for SABs.Could we change this?
Edit: To preempt
Atomicsnot being gated on as well, I have plans to enable Atomics to be usable on TypedArrays backed by regular ArrayBuffers to align with wasm.