[dart:_js_interop] Expose a interop type for JS objects
Moves JSObject to dart:_js_helper, so that JavaScript objects
can be used with that type in the JS backends. The type then
gets reexported in dart:_js_interop with a typedef.
This is purely for experimentation. This class needs to be sealed
before we can publish this library.
Change-Id: I16093165deaa5bc5d7940eb0cb98da32c36e485a
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/278894
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Joshua Litt <joshualitt@google.com>
diff --git a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
index 50dee73..894af55 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/interceptors.dart
@@ -84,9 +84,8 @@
/// Superclass of all interop objects and native types defined in the web
/// libraries.
///
-/// This is the class static interop classes erase to and the class interop
-/// extension types should use as the on-type.
-class JavaScriptObject extends Interceptor {
+/// This is the class @staticInterop classes erase to.
+class JavaScriptObject extends Interceptor implements JSObject {
const JavaScriptObject();
}
@@ -94,7 +93,7 @@
/// specific native type.
///
/// Note that this used to be `JavaScriptObject`.
-class LegacyJavaScriptObject extends JavaScriptObject implements JSObject {
+class LegacyJavaScriptObject extends JavaScriptObject {
const LegacyJavaScriptObject();
}
diff --git a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
index 0a7a517..b62ff02 100644
--- a/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
+++ b/sdk/lib/_internal/js_dev_runtime/private/js_helper.dart
@@ -21,6 +21,10 @@
import 'dart:_native_typed_data';
import 'dart:_runtime' as dart;
+// Export `JSObject` so `dart:js_interop` can import a representation type
+// consistently across all backends.
+export 'dart:_interceptors' show JSObject;
+
part 'annotations.dart';
part 'linked_hash_map.dart';
part 'identity_hash_map.dart';
diff --git a/sdk/lib/_internal/js_runtime/lib/interceptors.dart b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
index df77cc4..becd8b6 100644
--- a/sdk/lib/_internal/js_runtime/lib/interceptors.dart
+++ b/sdk/lib/_internal/js_runtime/lib/interceptors.dart
@@ -408,9 +408,8 @@
/// Superclass of all interop objects and native types defined in the web
/// libraries.
///
-/// This is the class static interop classes erase to and the class interop
-/// extension types should use as the on-type.
-class JavaScriptObject extends Interceptor {
+/// This is the class @staticInterop classes erase to.
+class JavaScriptObject extends Interceptor implements JSObject {
const JavaScriptObject();
}
@@ -418,7 +417,7 @@
/// specific native type.
///
/// Note that this used to be `JavaScriptObject`.
-class LegacyJavaScriptObject extends JavaScriptObject implements JSObject {
+class LegacyJavaScriptObject extends JavaScriptObject {
const LegacyJavaScriptObject();
// It would be impolite to stash a property on the object.
diff --git a/sdk/lib/_internal/js_runtime/lib/js_helper.dart b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
index 781d60b..c4e65a0 100644
--- a/sdk/lib/_internal/js_runtime/lib/js_helper.dart
+++ b/sdk/lib/_internal/js_runtime/lib/js_helper.dart
@@ -72,6 +72,10 @@
import 'dart:_load_library_priority';
+// Export `JSObject` so `dart:js_interop` can import a representation type
+// consistently across all backends.
+export 'dart:_interceptors' show JSObject;
+
part 'annotations.dart';
part 'constant_map.dart';
part 'instantiation.dart';
diff --git a/sdk/lib/_internal/wasm/lib/js_helper.dart b/sdk/lib/_internal/wasm/lib/js_helper.dart
index 3d1b22d..037f59d 100644
--- a/sdk/lib/_internal/wasm/lib/js_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/js_helper.dart
@@ -13,8 +13,16 @@
part 'regexp_helper.dart';
+/// The interface implemented by JavaScript objects and used as the
+/// representation type for inline classes.
+///
+/// Note that the semantics of this on dart2wasm is slightly different from the
+/// JS backends as [JSValue] implements this. This means we can interop with any
+/// JS extern ref. TODO(srujzs): Change this when we add JS types.
+abstract class JSObject {}
+
/// [JSValue] is the root of the JS interop object hierarchy.
-class JSValue {
+class JSValue implements JSObject {
final WasmExternRef _ref;
JSValue(this._ref);
@@ -86,12 +94,15 @@
external int get length;
}
-/// A [JSObject] is a wrapper for any JS object literal.
+/// A [JSObjectInterface] is a wrapper for any JS object literal.
+///
+/// TODO(srujzs): This is a temporary placeholder type. We should remove this
+/// once we expose the extension elsewhere.
@js.JS()
@js.staticInterop
-class JSObject {}
+class JSObjectInterface {}
-extension JSObjectExtension on JSObject {
+extension JSObjectInterfaceExtension on JSObjectInterface {
external Object? operator [](String key);
external void operator []=(String key, Object? value);
}
@@ -511,10 +522,9 @@
getPropertyRaw(globalThisRaw(), name.toExternRef());
/// Equivalent to `Object.keys(object)`.
-JSArray objectKeys(JSObject object) => JSValue.box(callMethodVarArgsRaw(
- getConstructorRaw('Object'),
- 'keys'.toExternRef(),
- [object].toExternRef())!) as JSArray;
+JSArray objectKeys(JSObjectInterface object) =>
+ JSValue.box(callMethodVarArgsRaw(getConstructorRaw('Object'),
+ 'keys'.toExternRef(), [object].toExternRef())!) as JSArray;
/// Takes a [codeTemplate] string which must represent a valid JS function, and
/// a list of optional arguments. The [codeTemplate] will be inserted into the
diff --git a/sdk/lib/_internal/wasm/lib/regexp_helper.dart b/sdk/lib/_internal/wasm/lib/regexp_helper.dart
index d90dca4..763bbbc 100644
--- a/sdk/lib/_internal/wasm/lib/regexp_helper.dart
+++ b/sdk/lib/_internal/wasm/lib/regexp_helper.dart
@@ -28,7 +28,7 @@
extension JSNativeMatchExtension on JSNativeMatch {
external String get input;
external int get index;
- external JSObject? get groups;
+ external JSObjectInterface? get groups;
}
@js.JS()
@@ -196,7 +196,7 @@
}
String? namedGroup(String name) {
- JSObject? groups = _match.groups;
+ JSObjectInterface? groups = _match.groups;
if (groups != null) {
Object? result = groups[name];
if (result != null ||
@@ -209,7 +209,7 @@
}
Iterable<String> get groupNames {
- JSObject? groups = _match.groups;
+ JSObjectInterface? groups = _match.groups;
if (groups != null) {
return JSArrayIterableAdapter<String>(objectKeys(groups));
}
diff --git a/sdk/lib/_js_interop/js_interop.dart b/sdk/lib/_js_interop/js_interop.dart
index c475e11..e05b105 100644
--- a/sdk/lib/_js_interop/js_interop.dart
+++ b/sdk/lib/_js_interop/js_interop.dart
@@ -10,3 +10,15 @@
// for sound top-level external members and inline classes instead of the
// `package:js` classes.
export 'dart:_js_annotations' show JS;
+
+import 'dart:_js_helper' as _js_helper;
+
+/// The representation type of all JavaScript objects for inline classes.
+///
+/// This is the supertype of all JS objects, but not other JS types, like
+/// primitives.
+///
+/// TODO(srujzs): This class _must_ be sealed before we can make this library
+/// public. Either use the CFE mechanisms that exist today, or use the Dart 3
+/// sealed classes feature.
+typedef JSObject = _js_helper.JSObject;
diff --git a/tests/lib/js/static_interop_test/jsobject_type_test.dart b/tests/lib/js/static_interop_test/jsobject_type_test.dart
new file mode 100644
index 0000000..e8adc4e
--- /dev/null
+++ b/tests/lib/js/static_interop_test/jsobject_type_test.dart
@@ -0,0 +1,27 @@
+// 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.
+
+// Check that `JSObject` can be used to type JS objects.
+
+@JS()
+library jsobject_type_test;
+
+// ignore: IMPORT_INTERNAL_LIBRARY
+import 'dart:_js_interop';
+
+import 'package:expect/minitest.dart';
+
+@JS()
+external dynamic eval(String code);
+
+@JS()
+external Object get obj;
+
+void main() {
+ eval('''
+ globalThis.obj = {};
+ ''');
+ expect(obj is JSObject, true);
+ obj as JSObject;
+}
diff --git a/tests/lib/lib.status b/tests/lib/lib.status
index c436bf2..0cba784 100644
--- a/tests/lib/lib.status
+++ b/tests/lib/lib.status
@@ -72,6 +72,7 @@
js/static_interop_test/external_static_member_lowerings_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/static_interop_test/external_static_member_lowerings_trusttypes_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/static_interop_test/external_static_member_lowerings_with_namespaces_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
+js/static_interop_test/jsobject_type_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
js/trust_types_test: SkipByDesign # Issue 42085. CSP policy disallows injected JS code
[ $compiler != dart2js && $compiler != dartdevc && $compiler != dartdevk ]