Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1450,9 +1450,11 @@ class ExtensionIndex {
return false;
}

bool isJSType(ExtensionTypeDeclaration decl) =>
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
decl.name.startsWith('JS');
bool isJSType(ExtensionTypeDeclaration decl) {
var url = decl.enclosingLibrary.importUri.toString();
return (url == 'dart:js_interop' || url == 'dart:js_interop_unsafe') &&
decl.name.startsWith('JS');
}

bool isExternalDartReference(ExtensionTypeDeclaration decl) =>
decl.enclosingLibrary.importUri.toString() == 'dart:js_interop' &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ class SharedInteropTransformer extends Transformer {
final Procedure _isJSBoxedDartObject;
final Procedure _isJSExportedDartFunction;
final Procedure _isJSObject;
final Procedure _isJSUnsafeObject;
final Procedure _isNullableJSAny;
final Procedure _isNullableJSBoxedDartObject;
final Procedure _isNullableJSExportedDartFunction;
final Procedure _isNullableJSObject;
final Procedure _isNullableJSUnsafeObject;
final ExtensionTypeDeclaration _jsAny;
final ExtensionTypeDeclaration _jsFunction;
final ExtensionTypeDeclaration _jsObject;
Expand Down Expand Up @@ -111,6 +113,10 @@ class SharedInteropTransformer extends Transformer {
'dart:js_interop',
'_isJSObject',
),
_isJSUnsafeObject = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
'dart:js_interop',
'_isJSUnsafeObject',
),
_isNullableJSAny = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
'dart:js_interop',
'_isNullableJSAny',
Expand All @@ -127,6 +133,8 @@ class SharedInteropTransformer extends Transformer {
),
_isNullableJSObject = _typeEnvironment.coreTypes.index
.getTopLevelProcedure('dart:js_interop', '_isNullableJSObject'),
_isNullableJSUnsafeObject = _typeEnvironment.coreTypes.index
.getTopLevelProcedure('dart:js_interop', '_isNullableJSUnsafeObject'),
_jsAny = _typeEnvironment.coreTypes.index.getExtensionType(
'dart:js_interop',
'JSAny',
Expand Down Expand Up @@ -650,6 +658,16 @@ class SharedInteropTransformer extends Transformer {
Arguments([VariableGet(receiverVar)]),
);
break;
case 'JSUnsafeObject' when interopTypeDecl == jsType:
// Only do this special case when users are referring directly to the
// `dart:js_interop` type and not some wrapper.
isJSAnyCheck = null;
nullChecksNeeded = false;
check = StaticInvocation(
interopTypeNullable ? _isNullableJSUnsafeObject : _isJSUnsafeObject,
Arguments([VariableGet(receiverVar)]),
);
break;
case 'JSExportedDartFunction' when interopTypeDecl == jsType:
// Only do this special case when users are referring directly to the
// `dart:js_interop` type and not some wrapper.
Expand Down
9 changes: 9 additions & 0 deletions sdk/lib/_internal/js_shared/lib/js_interop_patch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,15 @@ bool _isJSObject(Object? any) =>
@pragma('dart2js:prefer-inline')
bool _isNullableJSObject(Object? any) => any == null || _isJSObject(any);

@pragma('dart2js:prefer-inline')
bool _isJSUnsafeObject(Object? any) =>
foreign_helper.JS('bool', 'typeof # === "object"', any) ||
foreign_helper.JS('bool', 'typeof # === "function"', any);

@pragma('dart2js:prefer-inline')
bool _isNullableJSUnsafeObject(Object? any) =>
any == null || _isJSUnsafeObject(any);

@pragma('dart2js:prefer-inline')
bool _isJSExportedDartFunction(Object? any) =>
any is JavaScriptFunction && isJSExportedDartFunction(any);
Expand Down
4 changes: 4 additions & 0 deletions sdk/lib/_internal/wasm/lib/js_interop_patch.dart
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,10 @@ bool _isJSObject(Object? any) =>

bool _isNullableJSObject(Object? any) => any == null || _isJSObject(any);

bool _isJSUnsafeObject(Object? any) => _isJSObject(any);

bool _isNullableJSUnsafeObject(Object? any) => _isNullableJSObject(any);

bool _isJSExportedDartFunction(Object? any) =>
_isJSAny(any) &&
js_helper.isJSWrappedDartFunction(unsafeCast<JSAny>(any).toExternRef);
Expand Down
2 changes: 1 addition & 1 deletion sdk/lib/js_interop/js_interop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ extension type JSAny._(JSAnyType _jsAny) implements Object, JSAnyType {
/// will use as the representation type.
@JS('Object')
extension type JSObject._(JSObjectType _jsObject)
implements JSAny, JSObjectType {
implements JSAny, JSUnsafeObject, JSObjectType {
/// Creates a [JSObject] from an object provided by an earlier interop
/// library.
///
Expand Down
10 changes: 10 additions & 0 deletions sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,18 @@
/// {@category Web}
library;

import 'dart:_js_types';
import 'dart:js_interop';

/// A JavaScript `Object`s *or* a Dart object's JavaScript representation.
///
/// This can always be safely used as a type for real [JSObject]s, and in a WASM
/// context those are its only instances. When compiled to JS, though, this is
/// also the type of Dart objects and their prototype chains. JS considers these
/// to be `instanceof Object`, while Dart does not consider them to be
/// `isA<JSObject>` but does consider them to be `isA<JSUnsafeObject>`.
extension type JSUnsafeObject._(JSAnyType _) implements JSAny {}

/// Utility methods to check, get, set, and call properties on a [JSObject].
///
/// See the [JavaScript specification](https://tc39.es/ecma262/#sec-object-type)
Expand Down
20 changes: 20 additions & 0 deletions tests/lib/js/static_interop_test/isa/isa_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@
// Test `dart:js_interop`'s `isA` method.

import 'dart:js_interop';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';

import 'package:expect/expect.dart';

const isJSBackend = const bool.fromEnvironment('dart.library.html');

const isJSCompiler =
const bool.fromEnvironment('dart.library._ddc_only') ||
const bool.fromEnvironment('dart.tool.dart2js');
Expand Down Expand Up @@ -86,9 +89,20 @@ void testIsJSObject(JSObject any) {
Expect.isTrue(any.isA<JSObject?>());
Expect.isTrue((any as Object).isA<JSObject>());
Expect.isTrue((any as Object?).isA<JSObject?>());
testIsJSUnsafeObject(any);
}

void testIsJSUnsafeObject(JSUnsafeObject any) {
Expect.isTrue(any.isA<JSUnsafeObject>());
Expect.isTrue(any.isA<JSUnsafeObject?>());
testIsJSAny(any);
}

void testIsJSUnsafeObjectOnJSBackend(Object any) {
Expect.equals(isJSBackend, any.isA<JSUnsafeObject>());
Expect.equals(isJSBackend, any.isA<JSUnsafeObject?>());
}

void testIsJSAny(JSAny any) {
Expect.isTrue(any.isA<JSAny>());
Expect.isTrue(any.isA<JSAny?>());
Expand Down Expand Up @@ -327,6 +341,11 @@ void testJSObjects() {
Expect.isFalse(jsExportedDartFunctionObj.isA<JSString>());
}

void testJSUnsafeObjects() {
testIsJSUnsafeObjectOnJSBackend(Object());
testIsJSUnsafeObjectOnJSBackend(testJSUnsafeObjects);
}

void testTypedData() {
// JSArrayBuffer.
final jsArrayBuffer = Uint8List(1).buffer.toJS;
Expand Down Expand Up @@ -658,6 +677,7 @@ void main() {
testNull();
testPrimitives();
testJSObjects();
testJSUnsafeObjects();
testTypedData();
testUserTypes();
testExternalDartReference();
Expand Down
12 changes: 12 additions & 0 deletions tests/lib/js/static_interop_test/js_types_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ void syncTests() {
Expect.isTrue(confuse(obj) is JSObject);
Expect.equals('bar', (obj as SimpleObject).foo.toDart);

// [JSUnsafeObject]
Expect.isTrue(obj is JSUnsafeObject);
Expect.isTrue(confuse(obj) is JSUnsafeObject);

if (isJSBackend) {
Expect.isTrue(Object() is JSUnsafeObject);
Expect.isTrue(syncTests is JSUnsafeObject);
} else {
Expect.isFalse(Object() is JSUnsafeObject);
Expect.isFalse(syncTests is JSUnsafeObject);
}

// [JSFunction]
Expect.isTrue(fun is JSFunction);
Expect.isTrue(confuse(fun) is JSFunction);
Expand Down