emscripten-core / emscripten

Emscripten: An LLVM-to-WebAssembly Compiler
Other
25.83k stars 3.31k forks source link

TextDecoder sometimes fails when used through an iframe #15217

Open wffurr opened 3 years ago

wffurr commented 3 years ago

String types via embind sometimes fail with:

Failed to execute 'decode' on 'TextDecoder': The provided ArrayBufferView value must not be shared."

When the embind functions are called through an iframe. The instanceof check in TextDecoderWrapper fails when the object is from an iframe, which is a separate JavaScript realm.

This is a known problem with instanceof checks and iframes:

https://www.oreilly.com/library/view/speaking-javascript/9781449365028/ch17.html#cross-realm_instanceof https://jakearchibald.com/2017/arrays-symbols-realms/#multiple-realms https://esdiscuss.org/topic/cross-global-instanceof

For built-ins, the only real workaround is checking foo.constructor.name -> ArrayBuffer or Object.getPrototypeOf(foo).toString() -> [object ArrayBuffer].

This should work in all WebAssembly-supporting browsers.

wffurr commented 3 years ago

Here's a short example of how this works.

In the iframe's code, e.g. iframe.js:

var emscriptenBinary = require('./my_emscripten_binary.js');
emscriptenBinary().then(instance => {
  window.foo = new (instance.Foo)();  // initialize some embind type
  window.parent.postMessage({type: 'ready'}, '*');
});

In the parent window's code, e.g. app.js:

window.addEventListener('message', messageEvent => {
  if (messageEvent.type === 'ready') {
    const result = window.frames[0].foo.frob();  // Call an embind function that returns a string.
  }
});

Note how the parent window calls into a function in an object on the iframe's window. All instanceof checks in that call will fail because they will be checking against the prototype objects in the parent window's realm, not the iframe's.

The other advice in the O'Reilly book on handling this is to just not make cross-realm calls. I think we can make Emscripten robust against this though.

wffurr commented 3 years ago

Note this can be worked around at a performance hit (especially for large strings) with -s TEXTDECODER=0 compilation option.

jiulongw commented 2 years ago

I hit the same problem in a different context.

When pthread is enabled, and a tab is duplicated (Chrome, right click tab -> Duplicate), somehow the buffer sent from wasm (via embind) is actually a SAB but instanceof SharedArrayBuffer returns false. It causes a shared buffer being sent to TextDecoder and triggers this exact same exception.

kripken commented 2 years ago

@jiulongw Does "duplicate tab" not just open a new tab with the same URL? Or does it involve an iframe somehow?

If it does not involve an iframe then it sounds like a browser bug, and we should file a bug on Chrome. Also we can file a bug if we are not sure either way.

jiulongw commented 2 years ago

I feel duplicating a tab is not simply a new tab with same URL. It carries some sort of state or context from the tab being duplicated.

I will try to repro it without involving Emscripten and open a bug to Chromium.

Either way, this patch might still be useful for people hitting this issue, given Symbol.toStringTag is supported by major browsers, and faster than instanceof.