diff --git a/CHANGELOG.md b/CHANGELOG.md index 0369f54ac8c..669a1531fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ [pub-security-advisories]: https://dart.dev/go/pub-security-advisories +### Libraries + +#### `dart:js_interop` + +- On dart2wasm, `JSBoxedDartObject` now is an actual JS object that wraps the + opaque Dart value instead of only externalizing the value. Like the JS + backends, you'll now get a more useful error when trying to use it in another + Dart runtime. + ## 3.3.0 ### Language diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart index aef57c95003..8f9c2926d8a 100644 --- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart +++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart @@ -94,11 +94,27 @@ extension FunctionToJSExportedDartFunction on Function { 'by the interop transformer.'); } +/// Embedded global property for wrapped Dart objects passed via JS interop. +/// +/// This is a Symbol so that different Dart applications don't share Dart +/// objects from different Dart runtimes. We expect all [JSBoxedDartObject]s to +/// have this Symbol. +final JSSymbol _jsBoxedDartObjectProperty = _boxNonNullable( + js_helper.JS('() => Symbol("jsBoxedDartObjectProperty")')); + /// [JSBoxedDartObject] <-> [Object] @patch extension JSBoxedDartObjectToObject on JSBoxedDartObject { @patch - Object get toDart => jsObjectToDartObject(toExternRef); + Object get toDart { + final val = js_helper.JS('(o,s) => o[s]', this.toExternRef, + _jsBoxedDartObjectProperty.toExternRef); + if (isDartNull(val)) { + throw 'Expected a wrapped Dart object, but got a JS object or a wrapped ' + 'Dart object from a separate runtime instead.'; + } + return jsObjectToDartObject(val); + } } @patch @@ -108,7 +124,10 @@ extension ObjectToJSBoxedDartObject on Object { if (this is JSValue) { throw 'Attempting to box non-Dart object.'; } - return _boxNonNullable(jsObjectFromDartObject(this)); + final box = JSObject(); + js_helper.JS('(o,s,v) => o[s] = v', box.toExternRef, + _jsBoxedDartObjectProperty.toExternRef, jsObjectFromDartObject(this)); + return box as JSBoxedDartObject; } } diff --git a/tests/lib/js/static_interop_test/js_types_test.dart b/tests/lib/js/static_interop_test/js_types_test.dart index e6da910af27..666d3855bcd 100644 --- a/tests/lib/js/static_interop_test/js_types_test.dart +++ b/tests/lib/js/static_interop_test/js_types_test.dart @@ -173,6 +173,7 @@ void syncTests() { expect(edo is JSBoxedDartObject, true); expect(confuse(edo) is JSBoxedDartObject, true); expect(((edo as JSBoxedDartObject).toDart as DartObject).foo, 'bar'); + expect(edo.instanceOfString('Object'), true); // Functions should be boxed without assertInterop. final concat = (String a, String b) => a + b; edo = concat.toJSBox;