dart-lang / web

Lightweight browser API bindings built around JS static interop.
https://pub.dev/packages/web
BSD 3-Clause "New" or "Revised" License
107 stars 18 forks source link

HTMLIFrameElement contentWindow property in CORS case - cannot use postMessage() #247

Open slavap opened 1 month ago

slavap commented 1 month ago

I am trying to migrate from html package, and figured out that it is impossible to call postMessage() for iframe with content from another domain.

myIFrame.contentWindow?.postMessage('aaa'.toJS, '*'.toJS);

The same code with html package works without any security exceptions.

I'm getting the following error when accessing myIFrame.contentWindow: SecurityError: Failed to read a named property from 'Window': Blocked a frame with origin "http://localhost:51005" from accessing a cross-origin frame.

There is a quite ugly workaround for this issue.

Add to index.html:

<script>
    window.jsMyx = {
      postMessage(iframeElt, msg) {
        if (iframeElt) {
          iframeElt.contentWindow.postMessage(msg, '*');
        }
      }
    };
  </script>

Then in Dart:

import 'dart:js_interop';

import 'package:web/web.dart' as web;

extension type JsMyx._(JSObject _) implements JSObject {
  external void postMessage(web.HTMLIFrameElement iframe, JSAny? msg);
}

@JS()
external JsMyx get jsMyx;

And the following call works without any errors again:

void _sendMessage(Map<String, String> msg) {
    if (kIsWeb) {
      HTMLIFrameElement? fr = getMyIframe();
      if (fr != null) {
        jsMyx.postMessage(fr, msg.jsify());
      }
  }
}
srujzs commented 1 month ago

I suspect this is the same issue as https://github.com/dart-lang/sdk/issues/54443.

The general complication is that null-checks violate cross-origin policy as it's a toString call in JS. This is true for the JS compilers, whereas I believe dart2wasm likely avoids this issue due to how it does its null-checks and then immediately boxes. Using a conditional import could probably also work as a workaround if so.

Fixing this will likely involve a runtime type that dart2js/ddc knows not to call any members on, or providing some other interface that allows users to make cross-origin calls while avoiding null and type-checks in the JS compilers. dart:html does the latter.

slavap commented 1 month ago

@srujzs Looks like the same problem in devtools was "solved" by disabling dart2js optimizations :-( Also trick with safelyPostMessage() extension is not working.