[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 ]