Attention is currently required from: Joshua Litt, Sigmund Cherem.
Joshua Litt uploaded patch set #6 to this change.
[js] Add `js_interop_unsafe` library.
Change-Id: I4d6007e0731ecae81fcc614168baee7d5e662fd8
---
M pkg/compiler/lib/src/kernel/dart2js_target.dart
M pkg/dart2wasm/lib/js/runtime_generator.dart
M pkg/dart2wasm/lib/target.dart
M pkg/dev_compiler/lib/src/kernel/target.dart
M sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
A sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
M sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
M sdk/lib/_internal/wasm/lib/js_interop_patch.dart
A sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
M sdk/lib/js_interop/js_interop.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni
M sdk/lib/libraries.json
M sdk/lib/libraries.yaml
A tests/lib/js_interop_unsafe/basic_test.dart
15 files changed, 638 insertions(+), 8 deletions(-)
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
Patch set 7:Commit-Queue +1
3 comments:
Patchset:
Thanks!
File sdk/lib/js_interop/js_interop.dart:
Patch Set #5, Line 60: // TODO(joshualitt): Add a [JSObject] constructor.
Ah yeah, this will have to wait until we stop using typdefs. […]
Done
Patch Set #5, Line 135: /// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object, and
nit:wrap
Done
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Joshua Litt, Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/bb819aa704316cd5bee70395dfab0f7859fd3b4e
Attention is currently required from: Joshua Litt, Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/52756ee1107aba7528b5592dec04ef1c7baa1651
Attention is currently required from: Joshua Litt.
2 comments:
File sdk/lib/_internal/js_shared/lib/js_interop_patch.dart:
Patch Set #10, Line 8: import 'dart:js_util' as js_util;
curious: is the goal to eventually make js_interop self contained and remove the dependency on js_util?
File sdk/lib/js_interop/js_interop.dart:
Patch Set #10, Line 142: extension NullableObjectUtilExtension on Object? {
Thoughts on limiting this to a set of types instead?
I cannot quite pinpoint my concern here, but seeing an extension on all `Object?` stands out to me as potentially risky.
One concrete concern is that it may give the illusion that this is guaranteed to work on arbitrary code.
One option is to add the extension separately on various types like List, Map, Null, num, and String. That said, if we don't expect calls to be as common, I'm also OK not making the API fluid (via an extension method) and just keeping it as a top-level instead.
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
2 comments:
File sdk/lib/_internal/js_shared/lib/js_interop_patch.dart:
Patch Set #10, Line 8: import 'dart:js_util' as js_util;
curious: is the goal to eventually make js_interop self contained and remove the dependency on js_ut […]
Yes exactly, but that is down the road.
File sdk/lib/js_interop/js_interop.dart:
Patch Set #10, Line 142: extension NullableObjectUtilExtension on Object? {
Thoughts on limiting this to a set of types instead? […]
The reason to make this an extension method is to avoid a name conflict with `js_util`. Also, I really like the API as a getter(see the test, but being able to chain these calls looks and feels good).
How strong are your feelings here? Three factors which might make it reasonable to leave this as is:
1) The name of the API is fairly explicity(`jsify`).
2) The existing API takes an `Object?`.
3) This is just a regular extension, and thus not sticky. Users will have to explicitly import `dart:js_interop`, which on top of the naming of the extension(`jsify`), seems like it should make this quite safe?
That said, I'm open to making the type more specific, but I think it will be hard to do that cleanly because of how many types `jsify` applies to.
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Joshua Litt.
1 comment:
File sdk/lib/js_interop/js_interop.dart:
Patch Set #10, Line 142: extension NullableObjectUtilExtension on Object? {
I'd be OK with the name conflict to be honest (yay prefixes 😊)
How strong are your feelings here?
Not sure 😕 - I think not so strong, and given this is all static dispatch, it can potentially all be evolved via `dart fix`es.
A few more considerations to help us decide:
Re (A): Today we have logic listing each case we accept and convert ([_noJsifyRequired][1] plus Map and Iterable), but we pass `o` (or a box in wasm) if it isn't any of those. If that's the behavior we plan to have going forward, then it is fine to keep this in `Object?`. If we want to make it an error, I rather put the jsify down where it will not throw.
In other words. If we had union types in Dart, would the API still take `Object?` or would we have used a union in the type type signature of jsify? Or would we have had two APIs `jsifyAcceptAny` and `jsfiy`, where the latter throws?
Re (B): One of the hidden features of extension methods is that it gives us a way of providing overloaded methods based on the receiver type. `jsify` in `num` could potentially directly convert numbers and ignore all other cases, making a more efficient implementation. Technically we can still do this if we put it in Object, we just get the most specialized version based on the type. However, removing it from `Object?` would ensure developers don't hit the slow path accidentally.
Re (C): We also have discussed adding a non-recursive lighter jsify api (this has mainly come up in the context of `objectLiteral` but it could be slightly more general). Do we expect to have both APIs in the same extension or in different places?
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
1 comment:
File sdk/lib/js_interop/js_interop.dart:
Patch Set #10, Line 142: extension NullableObjectUtilExtension on Object? {
I'd be OK with the name conflict to be honest (yay prefixes 😊) […]
We spoke offline.
In the short term, I'm going to make these methods because they are a bit more heavyweight than the other getters.
In the longer term, we can think about making fast paths for `jsify`. If we need to later on, we can always get rid of the conversion on `Object?` by introducing helpers in user code that perform the recursive conversion.
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/387aeaf04944225567f375c449571e26ba0047f4
Attention is currently required from: Joshua Litt, Sigmund Cherem.
Joshua Litt uploaded patch set #12 to this change.
[js] Add `js_interop_unsafe` library.
CoreLibraryReviewExempt: Web only library.
Change-Id: I4d6007e0731ecae81fcc614168baee7d5e662fd8
---
M pkg/compiler/lib/src/kernel/dart2js_target.dart
M pkg/dart2wasm/lib/js/runtime_generator.dart
M pkg/dart2wasm/lib/target.dart
M pkg/dev_compiler/lib/src/kernel/target.dart
M sdk/BUILD.gn
M sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
A sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
M sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
M sdk/lib/_internal/wasm/lib/js_interop_patch.dart
A sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
M sdk/lib/js_interop/js_interop.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni
M sdk/lib/libraries.json
M sdk/lib/libraries.yaml
A tests/lib/js_interop_unsafe/basic_test.dart
M tests/lib/lib.status
17 files changed, 646 insertions(+), 8 deletions(-)
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Joshua Litt, Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/bebc1ba4ea5f1d2cb5e4d2826a0815d7cb6f17f4
Attention is currently required from: Joshua Litt, Sigmund Cherem.
Patch set 13:Code-Review +1
2 comments:
File sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart:
Patch Set #13, Line 26: == null
Hmm, it's possible that a call explicitly passes null. We could make this an `isUndefined` check, but that might be too confusing, so this is fine for now.
File sdk/lib/js_interop_unsafe/js_interop_unsafe.dart:
Out of curiosity: did we get rid of `setProperty` at some point?
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
Patch set 14:Commit-Queue +1
1 comment:
File sdk/lib/js_interop_unsafe/js_interop_unsafe.dart:
Out of curiosity: did we get rid of `setProperty` at some point?
We spoke offline, `setProperty` doesn't add anything over `[]=`, so it doesn't make sense to have it. `getProperty` adds a `T` to cast the return value to. TBH, I'd be okay with getting rid of `getProperty` for symmetry, but the tax here is small, so we're just going to leave it in.
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/f0f24367f74c2ae2168bb93aee035b298efe0749
Attention is currently required from: Sigmund Cherem.
Patch set 16:Commit-Queue +1
1 comment:
File sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart:
Patch Set #13, Line 26: == null
Hmm, it's possible that a call explicitly passes null. […]
Yea, it could happen, eventually we need to callsite specialize these functions. A added a TODO so this doesn't slip between the cracks.
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
Attention is currently required from: Joshua Litt, Sigmund Cherem.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/fb4881842c96543316200ab9f4fab46cbf59624a
Attention is currently required from: Joshua Litt, Sigmund Cherem.
Patch set 17:Commit-Queue +2
Commit Queue submitted this change.
13 is the latest approved patch-set.
The change was submitted with unreviewed changes in the following files:
```
The name of the file: sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
Insertions: 4, Deletions: 3.
The diff is too large to show. Please review the diff.
```
```
The name of the file: pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
Insertions: 1, Deletions: 0.
The diff is too large to show. Please review the diff.
```
```
The name of the file: sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
Insertions: 2, Deletions: 1.
The diff is too large to show. Please review the diff.
```
```
The name of the file: sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
Insertions: 8, Deletions: 6.
The diff is too large to show. Please review the diff.
```
```
The name of the file: sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
Insertions: 7, Deletions: 6.
The diff is too large to show. Please review the diff.
```
[js] Add `js_interop_unsafe` library.
CoreLibraryReviewExempt: Web only library.
Change-Id: I4d6007e0731ecae81fcc614168baee7d5e662fd8
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/293743
Commit-Queue: Joshua Litt <joshu...@google.com>
Reviewed-by: Srujan Gaddam <sru...@google.com>
---
M pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
M pkg/compiler/lib/src/kernel/dart2js_target.dart
M pkg/dart2wasm/lib/js/runtime_generator.dart
M pkg/dart2wasm/lib/target.dart
M pkg/dev_compiler/lib/src/kernel/target.dart
M sdk/BUILD.gn
M sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
A sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
M sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
M sdk/lib/_internal/wasm/lib/js_interop_patch.dart
A sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
M sdk/lib/js_interop/js_interop.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
A sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni
M sdk/lib/libraries.json
M sdk/lib/libraries.yaml
A tests/lib/js_interop_unsafe/basic_test.dart
M tests/lib/lib.status
18 files changed, 749 insertions(+), 8 deletions(-)
diff --git a/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart b/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
index 5aa2895..23477ab 100644
--- a/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
+++ b/pkg/_js_interop_checks/lib/src/transformations/static_interop_class_eraser.dart
@@ -126,6 +126,7 @@
// To avoid this, we use an allowlist that contains libraries that we know use
// `@staticInterop`.
late final Set<String> _erasableCoreLibraries = {
+ 'js_interop_unsafe',
'ui',
'_engine',
'_skwasm_impl'
diff --git a/pkg/compiler/lib/src/kernel/dart2js_target.dart b/pkg/compiler/lib/src/kernel/dart2js_target.dart
index e04a298..4504444 100644
--- a/pkg/compiler/lib/src/kernel/dart2js_target.dart
+++ b/pkg/compiler/lib/src/kernel/dart2js_target.dart
@@ -290,6 +290,7 @@
'dart:js',
'dart:js_interop',
'dart:js_util',
+ 'dart:js_interop_unsafe',
'dart:math',
'dart:svg',
'dart:typed_data',
@@ -324,6 +325,7 @@
'dart:isolate',
'dart:js',
'dart:js_interop',
+ 'dart:js_interop_unsafe',
'dart:js_util',
'dart:math',
'dart:typed_data',
diff --git a/pkg/dart2wasm/lib/js/runtime_generator.dart b/pkg/dart2wasm/lib/js/runtime_generator.dart
index 7387982..6014ec8 100644
--- a/pkg/dart2wasm/lib/js/runtime_generator.dart
+++ b/pkg/dart2wasm/lib/js/runtime_generator.dart
@@ -29,7 +29,12 @@
final staticInteropClassEraser = StaticInteropClassEraser(coreTypes, null,
eraseStaticInteropType: (staticInteropType) =>
InterfaceType(jsValueClass, staticInteropType.declaredNullability),
- additionalCoreLibraries: {'_js_helper', '_js_types', 'js_interop'});
+ additionalCoreLibraries: {
+ '_js_helper',
+ '_js_types',
+ 'js_interop',
+ 'js_interop_unsafe'
+ });
for (Library library in interopDependentLibraries) {
staticInteropClassEraser.visitLibrary(library);
}
diff --git a/pkg/dart2wasm/lib/target.dart b/pkg/dart2wasm/lib/target.dart
index a8ea4ef..c36f658 100644
--- a/pkg/dart2wasm/lib/target.dart
+++ b/pkg/dart2wasm/lib/target.dart
@@ -73,6 +73,7 @@
'dart:nativewrappers',
'dart:io',
'dart:js_interop',
+ 'dart:js_interop_unsafe',
'dart:js',
'dart:js_util',
'dart:_wasm',
diff --git a/pkg/dev_compiler/lib/src/kernel/target.dart b/pkg/dev_compiler/lib/src/kernel/target.dart
index 95cbf83..93f90a5 100644
--- a/pkg/dev_compiler/lib/src/kernel/target.dart
+++ b/pkg/dev_compiler/lib/src/kernel/target.dart
@@ -79,6 +79,7 @@
'dart:isolate',
'dart:js',
'dart:js_interop',
+ 'dart:js_interop_unsafe',
'dart:js_util',
'dart:math',
'dart:typed_data',
diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn
index 003176e..52897f8 100644
--- a/sdk/BUILD.gn
+++ b/sdk/BUILD.gn
@@ -90,6 +90,7 @@
# ......isolate/
# ......js/
# ......js_interop/
+# ......js_interop_unsafe/
# ......js_util/
# ......math/
# ......mirrors/
@@ -160,6 +161,7 @@
"isolate",
"js",
"js_interop",
+ "js_interop_unsafe",
"js_util",
"math",
"mirrors",
diff --git a/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart b/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
index dadf384..850e6bb 100644
--- a/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
+++ b/sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
@@ -5,17 +5,41 @@
import 'dart:_foreign_helper' show JS;
import 'dart:_internal' show patch;
import 'dart:_js_types';
-import 'dart:js_util';
+import 'dart:js_util' as js_util;
import 'dart:typed_data';
+@patch
+JSObject get globalJSObject => js_util.globalThis as JSObject;
+
/// Helper for working with the [JSAny?] top type in a backend agnostic way.
/// TODO(joshualitt): Remove conflation of null and undefined after migration.
extension NullableUndefineableJSAnyExtension on JSAny? {
@patch
- bool get isUndefined => this == null || typeofEquals(this, 'undefined');
+ bool get isUndefined =>
+ this == null || js_util.typeofEquals(this, 'undefined');
@patch
bool get isNull => this == null || JS('bool', '# === null', this);
+
+ @patch
+ JSBoolean typeofEquals(JSString typeString) =>
+ JS('bool', 'typeof # === #', this, typeString);
+
+ @patch
+ Object? dartify() => js_util.dartify(this);
+}
+
+/// Utility extensions for [Object?].
+extension NullableObjectUtilExtension on Object? {
+ @patch
+ JSAny? jsify() => js_util.jsify(this) as JSAny?;
+}
+
+/// Utility extensions for [JSObject].
+extension JSObjectUtilExtension on JSObject {
+ @patch
+ JSBoolean instanceof(JSFunction constructor) =>
+ JS('bool', '# instanceof #', this, constructor);
}
/// [JSExportedDartFunction] <-> [Function]
@@ -27,7 +51,7 @@
extension FunctionToJSExportedDartFunction on Function {
@patch
JSExportedDartFunction get toJS =>
- allowInterop(this) as JSExportedDartFunction;
+ js_util.allowInterop(this) as JSExportedDartFunction;
}
/// [JSExportedDartObject] <-> [Object]
@@ -44,7 +68,7 @@
/// [JSPromise] -> [Future<JSAny?>].
extension JSPromiseToFuture on JSPromise {
@patch
- Future<JSAny?> get toDart => promiseToFuture<JSAny?>(this);
+ Future<JSAny?> get toDart => js_util.promiseToFuture<JSAny?>(this);
}
/// [JSArrayBuffer] <-> [ByteBuffer]
diff --git a/sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart b/sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
new file mode 100644
index 0000000..26f1535
--- /dev/null
+++ b/sdk/lib/_internal/js_shared/lib/js_interop_unsafe_patch.dart
@@ -0,0 +1,71 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:_internal' show patch;
+import 'dart:_foreign_helper' show JS;
+import 'dart:js_interop' hide JS;
+import 'dart:js_util' as js_util;
+
+extension JSObjectUtilExtension on JSObject {
+ @patch
+ JSBoolean hasProperty(JSAny property) =>
+ JS<bool>('bool', '# in #', property, this).toJS;
+
+ @patch
+ JSAny? operator [](JSAny property) =>
+ JS<dynamic>('Object|Null', '#[#]', this, property);
+
+ @patch
+ void operator []=(JSAny property, JSAny? value) =>
+ JS<void>('', '#[#] = #', this, property, value);
+
+ // TODO(joshualitt): Specialize at callsites.
+ @patch
+ JSAny? _callMethod(JSAny method,
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) {
+ if (arg1 == null) {
+ return JS<dynamic>('Object|Null', '#[#]()', this, method);
+ } else if (arg2 == null) {
+ return JS<dynamic>('Object|Null', '#[#](#)', this, method, arg1);
+ } else if (arg3 == null) {
+ return JS<dynamic>('Object|Null', '#[#](#, #)', this, method, arg1, arg2);
+ } else if (arg4 == null) {
+ return JS<dynamic>(
+ 'Object|Null', '#[#](#, #, #)', this, method, arg1, arg2, arg3);
+ } else {
+ return JS<dynamic>('Object|Null', '#[#](#, #, #, #)', this, method, arg1,
+ arg2, arg3, arg4);
+ }
+ }
+
+ @patch
+ JSAny? _callMethodVarArgs(JSAny method, [List<JSAny?>? arguments]) =>
+ JS<dynamic>(
+ 'Object|Null', '#[#].apply(#, #)', this, method, this, arguments);
+
+ @patch
+ JSBoolean delete(JSAny property) =>
+ JS<bool>('bool', 'delete #[#]', this, property).toJS;
+}
+
+extension JSFunctionUtilExtension on JSFunction {
+ // TODO(joshualitt): Specialize `callAsConstructor`.
+ @patch
+ JSObject _callAsConstructor(
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
+ js_util.callConstructor<JSObject>(
+ this,
+ arg1 == null
+ ? null
+ : [
+ arg1,
+ if (arg2 != null) arg2,
+ if (arg3 != null) arg3,
+ if (arg4 != null) arg4,
+ ]);
+
+ @patch
+ JSObject _callAsConstructorVarArgs([List<JSAny?>? arguments]) =>
+ js_util.callConstructor<JSObject>(this, arguments);
+}
diff --git a/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart b/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
index 9ec8ff0..0af6f11 100644
--- a/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
+++ b/sdk/lib/_internal/sdk_library_metadata/lib/libraries.dart
@@ -134,6 +134,12 @@
documented: false,
platforms: DART2JS_PLATFORM,
),
+ 'js_interop_unsafe': const LibraryInfo(
+ 'js_interop_unsafe/js_interop_unsafe.dart',
+ categories: 'Client',
+ maturity: Maturity.EXPERIMENTAL,
+ platforms: DART2JS_PLATFORM,
+ ),
'js_util': const LibraryInfo(
'js_util/js_util.dart',
categories: 'Client',
diff --git a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
index 843bc6d..3675762 100644
--- a/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
+++ b/sdk/lib/_internal/wasm/lib/js_interop_patch.dart
@@ -6,8 +6,8 @@
import 'dart:_js_helper';
import 'dart:_wasm';
import 'dart:async' show Completer;
-import 'dart:js_interop';
-import 'dart:js_util' show NullRejectionException;
+import 'dart:js_interop' hide JS;
+import 'dart:js_util' as js_util;
import 'dart:typed_data';
/// Some helpers for working with JS types internally. If we implement the JS
@@ -15,6 +15,11 @@
/// TODO(joshualitt): Find a way to get rid of the explicit casts.
T _box<T>(WasmExternRef? ref) => JSValue(ref) as T;
+// TODO(joshualitt): Eventually delete `dart:js_util` on Dart2Wasm and migrate
+// any used logic to this file.
+@patch
+JSObject get globalJSObject => js_util.globalThis as JSObject;
+
/// Helper for working with the [JSAny?] top type in a backend agnostic way.
extension NullableUndefineableJSAnyExtension on JSAny? {
// TODO(joshualitt): To support incremental migration of existing users to
@@ -25,6 +30,27 @@
@patch
bool get isNull => this == null || this!.toExternRef.isNull;
+
+ @patch
+ JSBoolean typeofEquals(JSString type) => _box<JSBoolean>(JS<WasmExternRef?>(
+ '(o, t) => typeof o === t', this?.toExternRef, type.toExternRef));
+
+ @patch
+ Object? dartify() => js_util.dartify(this);
+}
+
+/// Utility extensions for [Object?].
+extension NullableObjectUtilExtension on Object? {
+ @patch
+ JSAny? jsify() => js_util.jsify(this) as JSAny?;
+}
+
+/// Utility extensions for [JSObject].
+extension JSObjectUtilExtension on JSObject {
+ @patch
+ JSBoolean instanceof(JSFunction constructor) =>
+ _box<JSBoolean>(JS<WasmExternRef?>(
+ '(o, c) => o instanceof c', toExternRef, constructor.toExternRef));
}
/// [JSExportedDartFunction] <-> [Function]
@@ -65,7 +91,7 @@
// TODO(joshualitt): Use helpers to avoid conflating `null` and `JSNull` /
// `JSUndefined`.
if (e == null) {
- return completer.completeError(NullRejectionException(false));
+ return completer.completeError(js_util.NullRejectionException(false));
}
return completer.completeError(e);
}.toJS;
diff --git a/sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart b/sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
new file mode 100644
index 0000000..d1da4e9
--- /dev/null
+++ b/sdk/lib/_internal/wasm/lib/js_interop_unsafe_patch.dart
@@ -0,0 +1,77 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+library dart.js_interop_unsafe;
+
+import 'dart:_internal' show patch;
+import "dart:_js_helper";
+import 'dart:_wasm';
+import 'dart:js_interop' hide JS;
+
+/// TODO(joshualitt): When `JSNull` and `JSUndefined` are boxed we can share
+/// this with `js_interop_patch.dart`.
+T _box<T>(WasmExternRef? ref) => JSValue.box(ref) as T;
+
+extension JSObjectUtilExtension on JSObject {
+ @patch
+ JSBoolean hasProperty(JSAny property) => _box<JSBoolean>(JS<WasmExternRef?>(
+ '(o, p) => p in o', toExternRef, property.toExternRef));
+
+ @patch
+ JSAny? operator [](JSAny property) => _box<JSAny?>(
+ JS<WasmExternRef?>('(o, p) => o[p]', toExternRef, property.toExternRef));
+
+ @patch
+ void operator []=(JSAny property, JSAny? value) => JS<void>(
+ '(o, p, v) => o[p] = v',
+ toExternRef,
+ property.toExternRef,
+ value?.toExternRef);
+
+ // TODO(joshualitt): Consider specializing variadic functions.
+ @patch
+ JSAny? _callMethod(JSAny method,
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
+ _box<JSAny?>(callMethodVarArgsRaw(
+ toExternRef,
+ method.toExternRef,
+ arg1 == null
+ ? null
+ : [
+ arg1,
+ if (arg2 != null) arg2,
+ if (arg3 != null) arg3,
+ if (arg4 != null) arg4,
+ ].toExternRef));
+
+ @patch
+ JSAny? _callMethodVarArgs(JSAny method, [List<JSAny?>? arguments]) =>
+ _box<JSAny?>(callMethodVarArgsRaw(
+ toExternRef, method.toExternRef, arguments?.toExternRef));
+
+ @patch
+ JSBoolean delete(JSAny property) => _box<JSBoolean>(JS<WasmExternRef?>(
+ '(o, p) => delete o[p]', toExternRef, property.toExternRef));
+}
+
+extension JSFunctionUtilExtension on JSFunction {
+ @patch
+ JSObject _callAsConstructor(
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
+ _box<JSObject>(callConstructorVarArgsRaw(
+ toExternRef,
+ arg1 == null
+ ? null
+ : [
+ arg1,
+ if (arg2 != null) arg2,
+ if (arg3 != null) arg3,
+ if (arg4 != null) arg4,
+ ].toExternRef));
+
+ @patch
+ JSObject _callAsConstructorVarArgs([List<JSAny?>? arguments]) =>
+ _box<JSObject>(
+ callConstructorVarArgsRaw(toExternRef, arguments?.toExternRef));
+}
diff --git a/sdk/lib/js_interop/js_interop.dart b/sdk/lib/js_interop/js_interop.dart
index d728b5c..990ebb7 100644
--- a/sdk/lib/js_interop/js_interop.dart
+++ b/sdk/lib/js_interop/js_interop.dart
@@ -71,6 +71,7 @@
// sealed classes feature.
// TODO(joshualitt): Do we need to seal any other JS types on JS backends? We
// probably want to seal all JS types on Wasm backends.
+// TODO(joshualitt): Add a [JSObject] constructor.
typedef JSObject = js_types.JSObject;
/// The type of all JS functions, [JSFunction] <: [JSObject].
@@ -126,6 +127,9 @@
/// The type of JS strings, [JSString] <: [JSAny].
typedef JSString = js_types.JSString;
+/// A getter to retrieve the Global [JSObject].
+external JSObject get globalJSObject;
+
/// `JSUndefined` and `JSNull` are actual reified types on some backends, but
/// not others. Instead, users should use nullable types for any type that could
/// contain `JSUndefined` or `JSNull`. However, instead of trying to determine
@@ -140,6 +144,24 @@
external bool get isNull;
bool get isUndefinedOrNull => isUndefined || isNull;
bool get isDefinedAndNotNull => !isUndefinedOrNull;
+ external JSBoolean typeofEquals(JSString typeString);
+
+ /// Effectively the inverse of [jsify], [dartify] Takes a JavaScript object,
+ /// and converts it to a Dart based object. Only JS primitives, arrays, or
+ /// 'map' like JS objects are supported.
+ external Object? dartify();
+}
+
+/// Utility extensions for [Object?].
+extension NullableObjectUtilExtension on Object? {
+ /// Recursively converts a JSON-like collection, or Dart primitive to a
+ /// JavaScript compatible representation.
+ external JSAny? jsify();
+}
+
+/// Utility extensions for [JSObject].
+extension JSObjectUtilExtension on JSObject {
+ external JSBoolean instanceof(JSFunction constructor);
}
/// The type of `JSUndefined` when returned from functions. Unlike pure JS,
diff --git a/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart b/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
new file mode 100644
index 0000000..9938311
--- /dev/null
+++ b/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+/// Typed utility methods to manipulate JS objects in cases where the name to
+/// call is not known at runtime.
+///
+/// Safe usage of these methods cannot necessarily be verified statically.
+/// Therefore, they should be used cautiously and only when the same effect
+/// cannot be achieved with static interop.
+///
+/// {@category Web}
+library dart.js_util_typed;
+
+import 'dart:js_interop';
+
+extension JSObjectUtilExtension on JSObject {
+ /// Whether or not this [JSObject] has a given property.
+ external JSBoolean hasProperty(JSAny property);
+
+ /// Equivalent to invoking operator `[]` in JS.
+ external JSAny? operator [](JSAny property);
+
+ /// Gets a given property from this [JSObject].
+ T getProperty<T extends JSAny?>(JSAny property) => this[property] as T;
+
+ /// Equivalent to invoking `[]=` in JS.
+ external void operator []=(JSAny property, JSAny? value);
+
+ /// Calls a method on this [JSObject] with up to four arguments and returns
+ /// the result.
+ external JSAny? _callMethod(JSAny method,
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]);
+ T callMethod<T extends JSAny?>(JSAny method,
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
+ _callMethod(method, arg1, arg2, arg3, arg4) as T;
+
+ /// Calls a method on this [JSObject] with a variable number of arguments and
+ /// returns the result.
+ external JSAny? _callMethodVarArgs(JSAny method, [List<JSAny?>? arguments]);
+ T callMethodVarArgs<T extends JSAny?>(JSAny method,
+ [List<JSAny?>? arguments]) =>
+ _callMethodVarArgs(method, arguments) as T;
+
+ /// Deletes the given property from this [JSObject].
+ external JSBoolean delete(JSAny property);
+}
+
+extension JSFunctionUtilExtension on JSFunction {
+ /// Calls this [JSFunction] as a constructor with up to four arguments and
+ /// returns the constructed [JSObject].
+ external JSObject _callAsConstructor(
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]);
+ T callAsConstructor<T>(
+ [JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
+ _callAsConstructor(arg1, arg2, arg3, arg4) as T;
+
+ /// Calls this [JSFunction] as a constructor with a variable number of
+ /// arguments and returns the constructed [JSObject].
+ external JSObject _callAsConstructorVarArgs([List<JSAny?>? arguments]);
+ T callAsConstructorVarArgs<T extends JSObject>([List<JSAny?>? arguments]) =>
+ _callAsConstructorVarArgs(arguments) as T;
+}
diff --git a/sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni b/sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni
new file mode 100644
index 0000000..515fc56
--- /dev/null
+++ b/sdk/lib/js_interop_unsafe/js_interop_unsafe_sources.gni
@@ -0,0 +1,5 @@
+# Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+# for details. All rights reserved. Use of this source code is governed by a
+# BSD-style license that can be found in the LICENSE file.
+
+js_interop_unsafe_sdk_sources = [ "js_interop_unsafe.dart" ]
diff --git a/sdk/lib/libraries.json b/sdk/lib/libraries.json
index 004df82..02f2e98 100644
--- a/sdk/lib/libraries.json
+++ b/sdk/lib/libraries.json
@@ -219,6 +219,10 @@
"uri": "js_interop/js_interop.dart",
"patches": "_internal/wasm/lib/js_interop_patch.dart"
},
+ "js_interop_unsafe": {
+ "uri": "js_interop_unsafe/js_interop_unsafe.dart",
+ "patches": "_internal/wasm/lib/js_interop_unsafe_patch.dart"
+ },
"js_util": {
"uri": "js_util/js_util.dart",
"patches": "_internal/wasm/lib/js_util_patch.dart"
@@ -327,6 +331,10 @@
"uri": "js_interop/js_interop.dart",
"patches": "_internal/js_shared/lib/js_interop_patch.dart"
},
+ "js_interop_unsafe": {
+ "uri": "js_interop_unsafe/js_interop_unsafe.dart",
+ "patches": "_internal/js_shared/lib/js_interop_unsafe_patch.dart"
+ },
"_js_annotations": {
"uri": "js/_js_annotations.dart"
},
@@ -508,6 +516,10 @@
"uri": "js_interop/js_interop.dart",
"patches": "_internal/js_shared/lib/js_interop_patch.dart"
},
+ "js_interop_unsafe": {
+ "uri": "js_interop_unsafe/js_interop_unsafe.dart",
+ "patches": "_internal/js_shared/lib/js_interop_unsafe_patch.dart"
+ },
"js_util": {
"uri": "js_util/js_util.dart",
"patches": [
diff --git a/sdk/lib/libraries.yaml b/sdk/lib/libraries.yaml
index fa72e0a..7a805ec 100644
--- a/sdk/lib/libraries.yaml
+++ b/sdk/lib/libraries.yaml
@@ -187,6 +187,9 @@
js_interop:
uri: js_interop/js_interop.dart
patches: _internal/wasm/lib/js_interop_patch.dart
+ js_interop_unsafe:
+ uri: js_interop_unsafe/js_interop_unsafe.dart
+ patches: _internal/wasm/lib/js_interop_unsafe_patch.dart
js_util:
uri: js_util/js_util.dart
patches: _internal/wasm/lib/js_util_patch.dart
@@ -280,6 +283,10 @@
uri: "js_interop/js_interop.dart"
patches: "_internal/js_shared/lib/js_interop_patch.dart"
+ js_interop_unsafe:
+ uri: "js_interop_unsafe/js_interop_unsafe.dart"
+ patches: "_internal/js_shared/lib/js_interop_unsafe_patch.dart"
+
_js_annotations:
uri: "js/_js_annotations.dart"
@@ -458,6 +465,10 @@
uri: "js_interop/js_interop.dart"
patches: "_internal/js_shared/lib/js_interop_patch.dart"
+ js_interop_unsafe:
+ uri: "js_interop_unsafe/js_interop_unsafe.dart"
+ patches: "_internal/js_shared/lib/js_interop_unsafe_patch.dart"
+
js_util:
uri: "js_util/js_util.dart"
patches:
diff --git a/tests/lib/js_interop_unsafe/basic_test.dart b/tests/lib/js_interop_unsafe/basic_test.dart
new file mode 100644
index 0000000..9f682bc
--- /dev/null
+++ b/tests/lib/js_interop_unsafe/basic_test.dart
@@ -0,0 +1,409 @@
+// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:js_interop';
+import 'dart:js_interop_unsafe';
+// TODO(joshualitt): Once we reify JS types on JS backends, we can add a
+// constructor to [JSObject].
+import 'dart:js_util' show newObject;
+import 'dart:typed_data';
+
+import 'package:expect/expect.dart';
+import 'package:js/js.dart';
+
+@JS()
+external void eval(String code);
+
+void createObjectTest() {
+ JSObject o = newObject<JSObject>();
+ Expect.isFalse(o.hasProperty('foo'.toJS).toDart);
+ o['foo'.toJS] = 'bar'.toJS;
+ Expect.isTrue(o.hasProperty('foo'.toJS).toDart);
+ Expect.equals('bar', (o['foo'.toJS] as JSString).toDart);
+}
+
+void equalTest() {
+ // Different objects aren't equal.
+ {
+ JSObject o1 = newObject<JSObject>();
+ JSObject o2 = newObject<JSObject>();
+ Expect.notEquals(o1, o2);
+ }
+
+ {
+ eval(r'''
+ function JSClass() {}
+
+ globalThis.boolData = true;
+ globalThis.boolData2 = true;
+ globalThis.numData = 4;
+ globalThis.numData2 = 4;
+ globalThis.arrData = [1, 2, 3];
+ globalThis.strData = 'foo';
+ globalThis.strData2 = 'foo';
+ globalThis.funcData = function JSClass() {}
+ globalThis.JSClass = new globalThis.funcData();
+ ''');
+ JSObject gt = globalJSObject;
+ void test(String propertyName, bool testCanonicalization) {
+ Expect.equals(gt[propertyName.toJS], gt[propertyName.toJS]);
+ if (testCanonicalization) {
+ Expect.equals(gt[propertyName.toJS], gt[(propertyName + "2").toJS]);
+ }
+ }
+
+ test("boolData", true);
+ test("numData", true);
+ test("arrData", false);
+ test("strData", true);
+ test("funcData", false);
+ test("JSClass", false);
+ }
+}
+
+void typeofTest() {
+ eval(r'''
+ globalThis.b = true;
+ globalThis.n = 4;
+ globalThis.str = 'foo';
+ globalThis.f = function foo() {}
+ globalThis.o = {};
+ globalThis.u = undefined
+ globalThis.sym = Symbol('sym');
+ ''');
+
+ final types = {
+ 'boolean',
+ 'number',
+ 'string',
+ 'function',
+ 'object',
+ 'undefined',
+ 'symbol'
+ };
+ void test(String property, String expectedType) {
+ Expect.isTrue(
+ globalJSObject[property.toJS]?.typeofEquals(expectedType.toJS).toDart);
+ for (final type in types) {
+ if (type != expectedType) {
+ Expect.isFalse(
+ globalJSObject[property.toJS]?.typeofEquals(type.toJS).toDart);
+ }
+ }
+ }
+
+ test('b', 'boolean');
+ test('n', 'number');
+ test('str', 'string');
+ test('f', 'function');
+ test('o', 'object');
+ // TODO(joshualitt): Test for `undefined` when we it can flow into `JSAny?`.
+ // test('u', 'undefined');
+ test('sym', 'symbol');
+}
+
+void instanceOfTest() {
+ eval(r'''
+ globalThis.JSClass1 = function() {}
+ globalThis.JSClass2 = function() {}
+
+ globalThis.obj = new JSClass1();
+ ''');
+ JSObject gt = globalJSObject;
+ JSObject obj = gt['obj'.toJS] as JSObject;
+ JSFunction jsClass1Constructor = gt['JSClass1'.toJS] as JSFunction;
+ JSFunction jsClass2Constructor = gt['JSClass2'.toJS] as JSFunction;
+ Expect.isTrue(obj.instanceof(jsClass1Constructor).toDart);
+ Expect.isFalse(obj.instanceof(jsClass2Constructor).toDart);
+}
+
+void _expectIterableEquals(Iterable<Object?> l, Iterable<Object?> r) {
+ final lIt = l.iterator;
+ final rIt = r.iterator;
+ while (lIt.moveNext()) {
+ Expect.isTrue(rIt.moveNext());
+ _expectRecEquals(lIt.current, rIt.current);
+ }
+ Expect.isFalse(rIt.moveNext());
+}
+
+void _expectRecEquals(Object? l, Object? r) {
+ if (l is Iterable && r is Iterable) {
+ _expectIterableEquals(l, r);
+ } else if (l is Map && r is Map) {
+ _expectIterableEquals(l.keys, r.keys);
+ for (final key in l.keys) {
+ _expectRecEquals(l[key], r[key]);
+ }
+ } else {
+ Expect.equals(l, r);
+ }
+}
+
+void evalAndConstructTest() {
+ eval(r'''
+ function JSClass(c) {
+ this.c = c;
+ this.sum = (a, b) => {
+ console.log(a + ' ' + b);
+ return a + b + this.c;
+ }
+ }
+ globalThis.JSClass = JSClass;
+ ''');
+ JSObject gt = globalJSObject;
+ JSFunction constructor = gt['JSClass'.toJS] as JSFunction;
+
+ // Var args
+ JSObject jsObj1 =
+ constructor.callAsConstructorVarArgs<JSObject>(<JSAny?>['world!'.toJS]);
+ Expect.equals(
+ 'hello world!',
+ jsObj1.callMethodVarArgs<JSString>(
+ 'sum'.toJS, <JSAny?>['hello'.toJS, ' '.toJS]).toDart);
+
+ // Fixed args
+ JSObject jsObj2 = constructor.callAsConstructor<JSObject>('world!'.toJS);
+ Expect.equals('hello world!',
+ jsObj2.callMethod<JSString>('sum'.toJS, 'hello'.toJS, ' '.toJS).toDart);
+}
+
+void deepConversionsTest() {
+ // Dart to JS.
+ Expect.isNull(null.jsify().dartify());
+ Expect.equals(true, true.jsify().dartify());
+ Expect.equals(2.0, 2.0.jsify().dartify());
+ Expect.equals('foo', 'foo'.jsify().dartify());
+ _expectRecEquals(
+ ['a', 'b', 'c'], ['a', 'b', 'c'].jsify().dartify() as List<Object?>);
+ _expectRecEquals(
+ {
+ 'null': 'foo',
+ 'foo': null,
+ 'a': 1,
+ 'b': true,
+ 'c': [1, 2, 3, null],
+ 'd': 'foo',
+ 'e': {
+ 'f': 2,
+ 'g': [2, 4, 6]
+ },
+ },
+ {
+ 'null': 'foo',
+ 'foo': null,
+ 'a': 1,
+ 'b': true,
+ 'c': [1, 2, 3, null],
+ 'd': 'foo',
+ 'e': {
+ 'f': 2,
+ 'g': [2, 4, 6]
+ },
+ }.jsify().dartify());
+ List<Object?> l = Int8List.fromList(<int>[-128, 0, 127]);
+ _expectIterableEquals(l, l.jsify().dartify() as Int8List);
+ l = Uint8List.fromList([-1, 0, 255, 256]);
+ _expectIterableEquals(l, l.jsify().dartify() as Uint8List);
+ l = Uint8ClampedList.fromList([-1, 0, 255, 256]);
+ _expectIterableEquals(l, l.jsify().dartify() as Uint8ClampedList);
+ l = Int16List.fromList([-32769, -32768, 0, 32767, 32768]);
+ _expectIterableEquals(l, l.jsify().dartify() as Int16List);
+ l = Uint16List.fromList([-1, 0, 65535, 65536]);
+ _expectIterableEquals(l, l.jsify().dartify() as Uint16List);
+ l = Int32List.fromList([-2147483648, 0, 2147483647]);
+ _expectIterableEquals(l, l.jsify().dartify() as Int32List);
+ l = Uint32List.fromList([-1, 0, 4294967295, 4294967296]);
+ _expectIterableEquals(l, l.jsify().dartify() as Uint32List);
+ l = Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
+ _expectIterableEquals(l, l.jsify().dartify() as Float32List);
+ l = Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]);
+ _expectIterableEquals(l, l.jsify().dartify() as Float64List);
+ ByteBuffer buffer = Uint8List.fromList([0, 1, 2, 3]).buffer;
+ _expectIterableEquals(buffer.asUint8List(),
+ (buffer.jsify().dartify() as ByteBuffer).asUint8List());
+ ByteData byteData = ByteData.view(buffer);
+ _expectIterableEquals(byteData.buffer.asUint8List(),
+ (byteData.jsify().dartify() as ByteData).buffer.asUint8List());
+
+ // JS to Dart.
+ eval(r'''
+ globalThis.a = null;
+ globalThis.b = 'foo';
+ globalThis.c = ['a', 'b', 'c'];
+ globalThis.d = 2.5;
+ globalThis.e = true;
+ globalThis.f = function () { return 'hello world'; };
+ globalThis.g = {
+ null: 'foo',
+ 'foo': null,
+ 'a': 1,
+ 'b': true,
+ 'c': [1, 2, 3, null],
+ 'd': 'foo',
+ 'e': {'f': 2, 'g': [2, 4, 6]},
+ };
+ globalThis.invoke = function (f) { return f(); }
+ globalThis.rec = {};
+ globalThis.rec = {'a': rec};
+ globalThis.int8Array = new Int8Array([-128, 0, 127]);
+ globalThis.uint8Array = new Uint8Array([-1, 0, 255, 256]);
+ globalThis.uint8ClampedArray = new Uint8ClampedArray([-1, 0, 255, 256]);
+ globalThis.int16Array = new Int16Array([-32769, -32768, 0, 32767, 32768]);
+ globalThis.uint16Array = new Uint16Array([-1, 0, 65535, 65536]);
+ globalThis.int32Array = new Int32Array([-2147483648, 0, 2147483647]);
+ globalThis.uint32Array = new Uint32Array([-1, 0, 4294967295, 4294967296]);
+ globalThis.float32Array = new Float32Array([-1000.488, -0.00001, 0.0001,
+ 10004.888]);
+ globalThis.float64Array = new Float64Array([-1000.488, -0.00001, 0.0001,
+ 10004.888]);
+ globalThis.arrayBuffer = globalThis.uint8Array.buffer;
+ globalThis.dataView = new DataView(globalThis.arrayBuffer);
+ globalThis.implicitExplicit = [
+ {'foo': 'bar'},
+ [1, 2, 3, {'baz': 'boo'}],
+ ];
+ let keyObject = function () {};
+ globalThis.keyObject1 = keyObject;
+ globalThis.keyObject2 = keyObject;
+ ''');
+ JSObject gt = globalJSObject;
+ Expect.isNull(gt['a'.toJS]);
+ Expect.equals('foo', gt.getProperty<JSString>('b'.toJS).toDart);
+ _expectRecEquals(
+ ['a', 'b', 'c'],
+ gt
+ .getProperty<JSArray>('c'.toJS)
+ .toDart
+ .map((JSAny? o) => (o as JSString).toDart));
+ Expect.equals(2.5, gt.getProperty<JSNumber>('d'.toJS).toDart);
+ Expect.equals(true, gt.getProperty<JSBoolean>('e'.toJS).toDart);
+ _expectRecEquals({
+ 'null': 'foo',
+ 'foo': null,
+ 'a': 1,
+ 'b': true,
+ 'c': [1, 2, 3, null],
+ 'd': 'foo',
+ 'e': {
+ 'f': 2,
+ 'g': [2, 4, 6]
+ },
+ }, gt.getProperty('g'.toJS).dartify());
+ _expectRecEquals({
+ 'a': {},
+ }, gt.getProperty('rec'.toJS).dartify());
+
+ _expectIterableEquals(Int8List.fromList(<int>[-128, 0, 127]),
+ gt.getProperty<JSInt8Array>('int8Array'.toJS).toDart);
+ _expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
+ gt.getProperty<JSUint8Array>('uint8Array'.toJS).toDart);
+ _expectIterableEquals(Uint8ClampedList.fromList([-1, 0, 255, 256]),
+ gt.getProperty<JSUint8ClampedArray>('uint8ClampedArray'.toJS).toDart);
+ _expectIterableEquals(Int16List.fromList([-32769, -32768, 0, 32767, 32768]),
+ gt.getProperty<JSInt16Array>('int16Array'.toJS).toDart);
+ _expectIterableEquals(Uint16List.fromList([-1, 0, 65535, 65536]),
+ gt.getProperty<JSUint16Array>('uint16Array'.toJS).toDart);
+ _expectIterableEquals(Int32List.fromList([-2147483648, 0, 2147483647]),
+ gt.getProperty<JSInt32Array>('int32Array'.toJS).toDart);
+ _expectIterableEquals(Uint32List.fromList([-1, 0, 4294967295, 4294967296]),
+ gt.getProperty<JSUint32Array>('uint32Array'.toJS).toDart);
+ _expectIterableEquals(
+ Float32List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
+ gt.getProperty<JSFloat32Array>('float32Array'.toJS).toDart);
+ _expectIterableEquals(
+ Float64List.fromList([-1000.488, -0.00001, 0.0001, 10004.888]),
+ gt.getProperty<JSFloat64Array>('float64Array'.toJS).toDart);
+ _expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
+ gt.getProperty<JSArrayBuffer>('arrayBuffer'.toJS).toDart.asUint8List());
+ _expectIterableEquals(Uint8List.fromList([-1, 0, 255, 256]),
+ gt.getProperty<JSDataView>('dataView'.toJS).toDart.buffer.asUint8List());
+
+ // Confirm a function that takes a roundtrip remains a function.
+ JSFunction foo = gt['f'.toJS].dartify() as JSFunction;
+ Expect.equals(
+ 'hello world', gt.callMethod<JSString>('invoke'.toJS, foo).toDart);
+
+ // Confirm arrays, which need to be converted implicitly, are still
+ // recursively converted by dartify() when desired.
+ _expectIterableEquals([
+ {'foo': 'bar'},
+ [
+ 1,
+ 2,
+ 3,
+ {'baz': 'boo'}
+ ],
+ ], gt['implicitExplicit'.toJS].dartify() as Iterable);
+
+ // Test that JS objects behave as expected in Map / Set.
+ Set<Object?> set = {};
+ JSAny? key1 = gt['keyObject1'.toJS];
+ JSAny? key2 = gt['keyObject2'.toJS];
+ Expect.isTrue(set.add(key1));
+ Expect.isTrue(set.contains(key1));
+ Expect.isFalse(set.add(key2));
+ Expect.isTrue(set.contains(key2));
+ Expect.equals(1, set.length);
+
+ Map<Object?, Object?> map = {};
+ map[key1] = 'foo';
+ map[key2] = 'bar';
+ Expect.equals(1, map.length);
+ Expect.equals('bar', map[key1]);
+}
+
+@JS('Symbol')
+@staticInterop
+class _JSSymbol {
+ @JS('for')
+ external static JSAny _for(JSString s);
+ external static JSString keyFor(JSAny s);
+}
+
+@JS()
+external JSAny get symbol;
+
+@JS()
+external JSAny get symbol2;
+
+@JS()
+external JSString methodWithSymbol(JSAny s);
+
+void symbolTest() {
+ eval(r'''
+ var s1 = Symbol.for('symbol');
+ globalThis.symbol = s1;
+ globalThis[s1] = 'boo';
+ globalThis.methodWithSymbol = function(s) {
+ return Symbol.keyFor(s);
+ }
+ var symbol2 = Symbol.for('symbolMethod');
+ globalThis[symbol2] = function() {
+ return 'hello world';
+ }
+ globalThis.symbol2 = symbol2;
+ ''');
+ JSObject gt = globalJSObject;
+ Expect.equals(
+ _JSSymbol.keyFor(_JSSymbol._for('symbol'.toJS)).toDart, 'symbol');
+ Expect.equals(
+ gt.getProperty<JSString>(gt.getProperty<JSAny>('symbol'.toJS)).toDart,
+ 'boo');
+ Expect.equals(methodWithSymbol(symbol).toDart, 'symbol');
+ Expect.equals(_JSSymbol.keyFor(symbol).toDart, 'symbol');
+ Expect.equals(
+ _JSSymbol.keyFor(gt.getProperty<JSAny>('symbol'.toJS)).toDart, 'symbol');
+ Expect.equals(gt.callMethod<JSString>(symbol2).toDart, 'hello world');
+}
+
+void main() {
+ createObjectTest();
+ equalTest();
+ typeofTest();
+ instanceOfTest();
+ evalAndConstructTest();
+ deepConversionsTest();
+ symbolTest();
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index 7f0c0db..1447d0b 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -86,6 +86,9 @@
[ $simulator ]
convert/utf85_test: Skip # Pass, Slow Issue 20111.
+[ $compiler != dart2js && $compiler != dart2wasm && $compiler != ddc ]
+js_interop_unsafe/*: SkipByDesign # Only supported on web backends.
+
[ $compiler != dart2js && $compiler != ddc ]
web/*: SkipByDesign
To view, visit change 293743. To unsubscribe, or for help writing mail filters, visit settings.
go/dart-cbuild result: SUCCESS
Details: https://goto.google.com/dart-cbuild/find/47b7c1f31dcae564aac399a5aace158724d10213