[dart:js_interop] Make isA<JSExportedDartFunction>() check that it's a wrapped function
Closes https://github.com/dart-lang/sdk/issues/62573 isA<JSExportedDartFunction>() used to just check if the object was a function, leading to a possible runtime error if `toDart` is called. Fixes that by introducing some helpers and moving around some functions in the JS compilers to other internal libraries (public members can't be added to js_allow_interop_patch.dart). Also fixes a minor issue in dart2js where `allowInterop`ed functions could successfully invoke `JSExportedDartFunction.toDart`. CoreLibraryReviewExempt: Documentation change. Change-Id: I5a9d7c31d3143eb3fb6ebd3273a4bf06ca329479 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/482967 Reviewed-by: Martin Kustermann <kustermann@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Nate Biggs <natebiggs@google.com>
This commit is contained in:
committed by
Commit Queue
parent
c87555403c
commit
0615d3a467
@@ -79,6 +79,14 @@ main() {
|
||||
functions must now also accept an `instantiator` callback to which they
|
||||
should pass the loaded results.
|
||||
|
||||
#### dart2js
|
||||
|
||||
- `JSExportedDartFunction.toDart` sometimes incorrectly returned the original
|
||||
Dart function even if the wrapper JS function was cast from a call to the
|
||||
deprecated `allowInterop`. Instead, to be consistent with DDC and dart2wasm,
|
||||
it now throws if the wrapper JS function wasn't a result of `Function.toJS` or
|
||||
`Function.toJSCaptureThis`.
|
||||
|
||||
### Libraries
|
||||
|
||||
#### `dart:js_interop`
|
||||
@@ -92,6 +100,9 @@ main() {
|
||||
the supertype `Object?`, this change is only breaking if users referred to the
|
||||
extension name directly, either through applying the extension directly or
|
||||
through using `show`/`hide` directives.
|
||||
- `isA<JSExportedDartFunction>()` now checks if the function is actually a JS
|
||||
wrapper function that is returned from `Function.toJS` or
|
||||
`Function.toJSCaptureThis`.
|
||||
|
||||
[#56905]: https://github.com/dart-lang/sdk/issues/56905
|
||||
|
||||
|
||||
@@ -36,9 +36,11 @@ class SharedInteropTransformer extends Transformer {
|
||||
final Procedure _isATearoff;
|
||||
final Procedure _isJSAny;
|
||||
final Procedure _isJSBoxedDartObject;
|
||||
final Procedure _isJSExportedDartFunction;
|
||||
final Procedure _isJSObject;
|
||||
final Procedure _isNullableJSAny;
|
||||
final Procedure _isNullableJSBoxedDartObject;
|
||||
final Procedure _isNullableJSExportedDartFunction;
|
||||
final Procedure _isNullableJSObject;
|
||||
final ExtensionTypeDeclaration _jsAny;
|
||||
final ExtensionTypeDeclaration _jsFunction;
|
||||
@@ -103,6 +105,8 @@ class SharedInteropTransformer extends Transformer {
|
||||
),
|
||||
_isJSBoxedDartObject = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_interop', '_isJSBoxedDartObject'),
|
||||
_isJSExportedDartFunction = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_interop', '_isJSExportedDartFunction'),
|
||||
_isJSObject = _typeEnvironment.coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_interop',
|
||||
'_isJSObject',
|
||||
@@ -116,6 +120,11 @@ class SharedInteropTransformer extends Transformer {
|
||||
'dart:js_interop',
|
||||
'_isNullableJSBoxedDartObject',
|
||||
),
|
||||
_isNullableJSExportedDartFunction = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure(
|
||||
'dart:js_interop',
|
||||
'_isNullableJSExportedDartFunction',
|
||||
),
|
||||
_isNullableJSObject = _typeEnvironment.coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_interop', '_isNullableJSObject'),
|
||||
_jsAny = _typeEnvironment.coreTypes.index.getExtensionType(
|
||||
@@ -601,8 +610,6 @@ class SharedInteropTransformer extends Transformer {
|
||||
Expression? check;
|
||||
String? typeofString;
|
||||
String? instanceOfString;
|
||||
// TODO(srujzs): Add specific check for `JSExportedDartFunction`.
|
||||
// https://github.com/dart-lang/sdk/issues/62573
|
||||
// TODO(srujzs): Maybe use `Array.isArray` for `JSArray`.
|
||||
// https://github.com/dart-lang/sdk/issues/62699
|
||||
switch (jsTypeName) {
|
||||
@@ -643,6 +650,18 @@ class SharedInteropTransformer extends Transformer {
|
||||
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.
|
||||
isJSAnyCheck = null;
|
||||
nullChecksNeeded = false;
|
||||
check = StaticInvocation(
|
||||
interopTypeNullable
|
||||
? _isNullableJSExportedDartFunction
|
||||
: _isJSExportedDartFunction,
|
||||
Arguments([VariableGet(receiverVar)]),
|
||||
);
|
||||
break;
|
||||
case 'JSTypedArray' when interopTypeDecl == jsType:
|
||||
// Only do this special case when users are referring directly to the
|
||||
// `dart:js_interop` type and not some wrapper.
|
||||
|
||||
@@ -200,12 +200,12 @@ class CallbackSpecializer {
|
||||
/// Create a [Procedure] that will wrap a Dart callback in a JS wrapper.
|
||||
///
|
||||
/// [node] is the conversion function that is called by the user (either
|
||||
/// `allowInterop`, `Function.toJS`, or `Function.toJSCaptureThis`). [type] is
|
||||
/// the static type of the callback. [boxExternRef] determines if the
|
||||
/// trampoline should box the arguments and return value or convert every
|
||||
/// value. [needsCastClosure] determines if a cast closure is needed in order
|
||||
/// to validate the types of some arguments. [captureThis] determines if
|
||||
/// `this` needs to be passed into the trampoline from the JS wrapper.
|
||||
/// `Function.toJS` or `Function.toJSCaptureThis`). [type] is the static type
|
||||
/// of the callback. [boxExternRef] determines if the trampoline should box
|
||||
/// the arguments and return value or convert every value. [needsCastClosure]
|
||||
/// determines if a cast closure is needed in order to validate the types of
|
||||
/// some arguments. [captureThis] determines if `this` needs to be passed into
|
||||
/// the trampoline from the JS wrapper.
|
||||
///
|
||||
/// The procedure will call a JS method that will create a wrapper, cache the
|
||||
/// callback, and call the trampoline function with the callback, the JS
|
||||
|
||||
@@ -45,11 +45,13 @@ void test(JSAny any, JSAny? nullableAny, Object obj, Object? nullableObj) {
|
||||
|
||||
any.isA<JSArray>();
|
||||
obj.isA<JSArray?>();
|
||||
// JSTypedArray and JSBoxedDartObject handled differently.
|
||||
// Check that there's a specific lowering for some JS types.
|
||||
any.isA<JSTypedArray>();
|
||||
any.isA<JSBoxedDartObject>();
|
||||
any.isA<JSExportedDartFunction>();
|
||||
obj.isA<JSTypedArray>();
|
||||
obj.isA<JSBoxedDartObject>();
|
||||
obj.isA<JSExportedDartFunction>();
|
||||
|
||||
// User-defined types.
|
||||
any.isA<CustomJSAny>();
|
||||
|
||||
@@ -76,8 +76,10 @@ static method test(js_::JSAny /* erasure=core::Object */ any, js_::JSAny? /* era
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSArray<js_::JSAny? /* erasure=core::Object? */>? /* erasure=_interceptors::JSArray<core::Object?>? */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSBoxedDartObject /* erasure=_interceptors::JSObject */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSExportedDartFunction /* erasure=_interceptors::JavaScriptFunction */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSBoxedDartObject /* erasure=_interceptors::JSObject */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSExportedDartFunction /* erasure=_interceptors::JavaScriptFunction */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomJSAny /* erasure=core::Object */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomJSObject /* erasure=_interceptors::JSObject */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(any);
|
||||
|
||||
@@ -76,8 +76,10 @@ static method test(js_::JSAny /* erasure=core::Object */ any, js_::JSAny? /* era
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSArray<js_::JSAny? /* erasure=core::Object? */>? /* erasure=_interceptors::JSArray<core::Object?>? */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSBoxedDartObject /* erasure=_interceptors::JSObject */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSExportedDartFunction /* erasure=_interceptors::JavaScriptFunction */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSBoxedDartObject /* erasure=_interceptors::JSObject */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<js_::JSExportedDartFunction /* erasure=_interceptors::JavaScriptFunction */>(obj);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomJSAny /* erasure=core::Object */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomJSObject /* erasure=_interceptors::JSObject */>(any);
|
||||
js_::NullableObjectUtilExtension|isA<self::CustomTypedArray /* erasure=dart.typed_data.implementation::NativeTypedData */>(any);
|
||||
|
||||
+2
@@ -77,8 +77,10 @@ static method test(js_::JSAny /* erasure=core::Object */ any, js_::JSAny? /* era
|
||||
obj == null || js_::_isJSAny(obj) && js_::JSAnyUtilityExtension|instanceOfString(obj as js_::JSAny? /* erasure=core::Object? */, "Array");
|
||||
!(any == null) && js_::JSAnyUtilityExtension|instanceof(any, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs<js_::JSFunction /* erasure=_interceptors::JavaScriptFunction */>(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as js_::JSObject /* erasure=_interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), <js_::JSAny? /* erasure=core::Object? */>[js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as js_::JSObject /* erasure=_interceptors::JSObject */]));
|
||||
js_::_isJSBoxedDartObject(any);
|
||||
js_::_isJSExportedDartFunction(any);
|
||||
!(obj == null) && (js_::_isJSAny(obj) && js_::JSAnyUtilityExtension|instanceof(obj as js_::JSAny? /* erasure=core::Object? */, js_2::JSObjectUnsafeUtilExtension|callMethodVarArgs<js_::JSFunction /* erasure=_interceptors::JavaScriptFunction */>(js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Object") as js_::JSObject /* erasure=_interceptors::JSObject */, js_::StringToJSString|get#toJS("getPrototypeOf"), <js_::JSAny? /* erasure=core::Object? */>[js_2::JSObjectUnsafeUtilExtension|[](js_::globalContext, "Int8Array") as js_::JSObject /* erasure=_interceptors::JSObject */])));
|
||||
js_::_isJSBoxedDartObject(obj);
|
||||
js_::_isJSExportedDartFunction(obj);
|
||||
!(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSAny");
|
||||
!(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomJSObject");
|
||||
!(any == null) && js_::JSAnyUtilityExtension|instanceOfString(any, "library1.CustomTypedArray");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Patch file for dart:js_util library.
|
||||
import 'dart:_foreign_helper' show JS;
|
||||
import 'dart:_interceptors' show JavaScriptFunction;
|
||||
import 'dart:_interceptors' show functionToJSProperty, JavaScriptFunction;
|
||||
import 'dart:_internal' show patch;
|
||||
import 'dart:_runtime' as dart;
|
||||
|
||||
@@ -50,12 +50,6 @@ Function allowInteropCaptureThis(Function f) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO(srujzs): In dart2js, this is guaranteed to be unique per isolate. DDC
|
||||
// doesn't have a mechanism to guarantee that, so use a Symbol instead to match
|
||||
// the unique-per-runtime semantics of [allowInterop].
|
||||
final _functionToJSPropertyName = r'_$dart_dartClosure';
|
||||
final _functionToJSProperty = JS('!', "Symbol($_functionToJSPropertyName)");
|
||||
|
||||
JavaScriptFunction _functionToJS0(Function f) {
|
||||
// This can only happen if a user casted a JavaScriptFunction to Function.
|
||||
// Such a cast is an error in dart2wasm, so we should make this behavior an
|
||||
@@ -73,7 +67,7 @@ JavaScriptFunction _functionToJS0(Function f) {
|
||||
_callDartFunctionFast0,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -91,7 +85,7 @@ JavaScriptFunction _functionToJS1(Function f) {
|
||||
_callDartFunctionFast1,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -109,7 +103,7 @@ JavaScriptFunction _functionToJS2(Function f) {
|
||||
_callDartFunctionFast2,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -127,7 +121,7 @@ JavaScriptFunction _functionToJS3(Function f) {
|
||||
_callDartFunctionFast3,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -145,7 +139,7 @@ JavaScriptFunction _functionToJS4(Function f) {
|
||||
_callDartFunctionFast4,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -163,7 +157,7 @@ JavaScriptFunction _functionToJS5(Function f) {
|
||||
_callDartFunctionFast5,
|
||||
f,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -183,7 +177,7 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
|
||||
f,
|
||||
maxLength,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -208,7 +202,7 @@ JavaScriptFunction _functionToJSCaptureThisN(Function f, int maxLength) {
|
||||
f,
|
||||
maxLength,
|
||||
);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
JS('', '#[#] = #', ret, functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -360,5 +354,5 @@ _callDartFunctionFast5(callback, arg1, arg2, arg3, arg4, arg5, int length) {
|
||||
}
|
||||
|
||||
Function _jsFunctionToDart(JavaScriptFunction f) {
|
||||
return JS('Function', '#[#]', f, _functionToJSProperty);
|
||||
return JS('Function', '#[#]', f, functionToJSProperty);
|
||||
}
|
||||
|
||||
@@ -305,6 +305,17 @@ findInterceptorForType(Type? type) {}
|
||||
/// the dart:rti library because stores information used for type checks.
|
||||
class JavaScriptFunction extends LegacyJavaScriptObject implements Function {}
|
||||
|
||||
// TODO(srujzs): In dart2js, this is guaranteed to be unique per isolate. DDC
|
||||
// doesn't have a mechanism to guarantee that, so use a Symbol instead to match
|
||||
// the unique-per-runtime semantics of [allowInterop].
|
||||
final _functionToJSPropertyName = r'_$dart_dartClosure';
|
||||
final functionToJSProperty = JS('!', "Symbol($_functionToJSPropertyName)");
|
||||
|
||||
/// Returns whether [f] is a wrapped Dart function through `dart:js_interop`'s
|
||||
/// conversion methods.
|
||||
bool isJSExportedDartFunction(JavaScriptFunction f) =>
|
||||
JS('', '#.#', f, functionToJSProperty) != null;
|
||||
|
||||
/// Interceptor for JavaScript BigInt primitive values, i.e. values `x` for
|
||||
/// which `typeof x == "bigint"`.
|
||||
@JsPeerInterface(name: 'BigInt')
|
||||
|
||||
@@ -79,6 +79,17 @@ final String DART_CLOSURE_PROPERTY_NAME = getIsolateAffinityTag(
|
||||
r'_$dart_dartClosure',
|
||||
);
|
||||
|
||||
/// Present in JS functions that were converted specifically using
|
||||
/// `dart:js_interop`.
|
||||
final String DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME = getIsolateAffinityTag(
|
||||
r'_$dart_dartClosure_dartJSInterop',
|
||||
);
|
||||
|
||||
/// Returns whether [f] is a wrapped Dart function through `dart:js_interop`'s
|
||||
/// conversion methods.
|
||||
bool isJSExportedDartFunction(JavaScriptFunction f) =>
|
||||
JS('', '#.#', f, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME) != null;
|
||||
|
||||
getDispatchProperty(object) {
|
||||
return JS(
|
||||
'',
|
||||
@@ -484,7 +495,9 @@ final class JavaScriptFunction extends LegacyJavaScriptObject
|
||||
const JavaScriptFunction();
|
||||
|
||||
String toString() {
|
||||
var dartClosure = JS('', '#.#', this, DART_CLOSURE_PROPERTY_NAME);
|
||||
var dartClosure =
|
||||
JS('', '#.#', this, DART_CLOSURE_PROPERTY_NAME) ??
|
||||
JS('', '#.#', this, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME);
|
||||
if (dartClosure == null) return super.toString();
|
||||
return 'JavaScript function for ${dartClosure.toString()}';
|
||||
}
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
// Patch file for dart:js_util library.
|
||||
|
||||
import 'dart:_foreign_helper' show JS, DART_CLOSURE_TO_JS;
|
||||
import 'dart:_interceptors' show DART_CLOSURE_PROPERTY_NAME, JavaScriptFunction;
|
||||
import 'dart:_interceptors'
|
||||
show
|
||||
DART_CLOSURE_PROPERTY_NAME,
|
||||
DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME,
|
||||
JavaScriptFunction;
|
||||
import 'dart:_internal' show patch;
|
||||
import 'dart:_js_helper'
|
||||
show
|
||||
@@ -103,7 +107,7 @@ JavaScriptFunction _functionToJS0(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast0),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -123,7 +127,7 @@ JavaScriptFunction _functionToJSCaptureThis0(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast1),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -143,7 +147,7 @@ JavaScriptFunction _functionToJS1(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast1),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -163,7 +167,7 @@ JavaScriptFunction _functionToJSCaptureThis1(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast2),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -183,7 +187,7 @@ JavaScriptFunction _functionToJS2(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast2),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -203,7 +207,7 @@ JavaScriptFunction _functionToJSCaptureThis2(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast3),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -223,7 +227,7 @@ JavaScriptFunction _functionToJS3(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast3),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -243,7 +247,7 @@ JavaScriptFunction _functionToJSCaptureThis3(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast4),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -263,7 +267,7 @@ JavaScriptFunction _functionToJS4(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast4),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -283,7 +287,7 @@ JavaScriptFunction _functionToJSCaptureThis4(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast5),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -303,7 +307,7 @@ JavaScriptFunction _functionToJS5(Function f) {
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast5),
|
||||
f,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -325,7 +329,7 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
|
||||
f,
|
||||
maxLength,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -353,7 +357,7 @@ JavaScriptFunction _functionToJSCaptureThisN(Function f, int maxLength) {
|
||||
f,
|
||||
maxLength,
|
||||
);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -407,5 +411,5 @@ _callDartFunctionFastN(Function callback, List arguments) {
|
||||
}
|
||||
|
||||
Function _jsFunctionToDart(JavaScriptFunction f) {
|
||||
return JS('Function', '#.#', f, DART_CLOSURE_PROPERTY_NAME);
|
||||
return JS('Function', '#.#', f, DART_CLOSURE_DART_JSINTEROP_PROPERTY_NAME);
|
||||
}
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
// BSD-style license that can be found in the LICENSE file.
|
||||
|
||||
import 'dart:_foreign_helper' as foreign_helper;
|
||||
import 'dart:_interceptors' show Interceptor, JavaScriptObject;
|
||||
import 'dart:_interceptors'
|
||||
show
|
||||
Interceptor,
|
||||
JavaScriptFunction,
|
||||
JavaScriptObject,
|
||||
isJSExportedDartFunction;
|
||||
import 'dart:_internal' show patch;
|
||||
import 'dart:_js_helper' show createObjectLiteral, staticInteropGlobalContext;
|
||||
import 'dart:_js_types';
|
||||
@@ -130,6 +135,14 @@ bool _isJSObject(Object? any) =>
|
||||
@pragma('dart2js:prefer-inline')
|
||||
bool _isNullableJSObject(Object? any) => any == null || _isJSObject(any);
|
||||
|
||||
@pragma('dart2js:prefer-inline')
|
||||
bool _isJSExportedDartFunction(Object? any) =>
|
||||
any is JavaScriptFunction && isJSExportedDartFunction(any);
|
||||
|
||||
@pragma('dart2js:prefer-inline')
|
||||
bool _isNullableJSExportedDartFunction(Object? any) =>
|
||||
any == null || _isJSExportedDartFunction(any);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JSBoxedDartObject <-> Object
|
||||
@patch
|
||||
|
||||
@@ -266,6 +266,13 @@ bool _isJSObject(Object? any) =>
|
||||
|
||||
bool _isNullableJSObject(Object? any) => any == null || _isJSObject(any);
|
||||
|
||||
bool _isJSExportedDartFunction(Object? any) =>
|
||||
_isJSAny(any) &&
|
||||
js_helper.isJSWrappedDartFunction(unsafeCast<JSAny>(any).toExternRef);
|
||||
|
||||
bool _isNullableJSExportedDartFunction(Object? any) =>
|
||||
any == null || _isJSExportedDartFunction(any);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// JSBoxedDartObject <-> Object
|
||||
@patch
|
||||
|
||||
@@ -888,6 +888,10 @@ extension NullableObjectUtilExtension on Object? {
|
||||
/// - `JSObject`: `isA<JSObject>` will call an intrinsic function to check
|
||||
/// that the value is a JS object (`instanceof Object` is insufficient for
|
||||
/// some objects).
|
||||
/// - `JSExportedDartFunction`: `isA<JSExportedDartFunction>` will check if
|
||||
/// the value is a result of a previous
|
||||
/// [FunctionToJSExportedDartFunction.toJS] or
|
||||
/// [FunctionToJSExportedDartFunction.toJSCaptureThis] call.
|
||||
/// - User interop types whose representation types are JS primitive types:
|
||||
/// This will result in an error to avoid confusion on whether the user
|
||||
/// interop type is used in the type-check. Use the primitive JS type as the
|
||||
|
||||
@@ -43,9 +43,15 @@ extension type ArraySubtype._(JSArray _) implements JSObject {
|
||||
extension type WrapJSBoxedDartObject(JSBoxedDartObject _)
|
||||
implements JSBoxedDartObject {}
|
||||
|
||||
extension type WrapJSExportedDartFunction(JSExportedDartFunction _)
|
||||
implements JSExportedDartFunction {}
|
||||
|
||||
@JS('WrapJSBoxedDartObject.prototype')
|
||||
external JSObject get wrapJSBoxedDartObjectPrototype;
|
||||
|
||||
@JS('WrapJSExportedDartFunction.prototype')
|
||||
external JSObject get wrapJSExportedDartFunctionPrototype;
|
||||
|
||||
@JS('Object.setPrototypeOf')
|
||||
external void setPrototypeOf(JSObject obj, JSObject prototype);
|
||||
|
||||
@@ -100,19 +106,23 @@ void testNull() {
|
||||
Expect.isTrue(nil.isA<JSObject?>());
|
||||
Expect.isTrue(nil.isA<JSAny?>());
|
||||
Expect.isTrue(nil.isA<JSBoxedDartObject?>());
|
||||
Expect.isTrue(nil.isA<JSExportedDartFunction?>());
|
||||
Expect.isFalse(nil.isA<JSString>());
|
||||
Expect.isFalse(nil.isA<JSObject>());
|
||||
Expect.isFalse(nil.isA<JSAny>());
|
||||
Expect.isFalse(nil.isA<JSBoxedDartObject>());
|
||||
Expect.isFalse(nil.isA<JSExportedDartFunction>());
|
||||
Object? nilObj = null;
|
||||
Expect.isTrue(nilObj.isA<JSString?>());
|
||||
Expect.isTrue(nilObj.isA<JSObject?>());
|
||||
Expect.isTrue(nilObj.isA<JSAny?>());
|
||||
Expect.isTrue(nilObj.isA<JSBoxedDartObject?>());
|
||||
Expect.isTrue(nilObj.isA<JSExportedDartFunction?>());
|
||||
Expect.isFalse(nilObj.isA<JSString>());
|
||||
Expect.isFalse(nilObj.isA<JSObject>());
|
||||
Expect.isFalse(nilObj.isA<JSAny>());
|
||||
Expect.isFalse(nilObj.isA<JSBoxedDartObject>());
|
||||
Expect.isFalse(nilObj.isA<JSExportedDartFunction>());
|
||||
// JS nullish values should behave no differently.
|
||||
eval('''
|
||||
globalThis.nullable = null;
|
||||
@@ -121,10 +131,12 @@ void testNull() {
|
||||
Expect.isTrue(nullable.isA<JSObject?>());
|
||||
Expect.isTrue(nullable.isA<JSAny?>());
|
||||
Expect.isTrue(nullable.isA<JSBoxedDartObject?>());
|
||||
Expect.isTrue(nullable.isA<JSExportedDartFunction?>());
|
||||
Expect.isFalse(nullable.isA<JSString>());
|
||||
Expect.isFalse(nullable.isA<JSObject>());
|
||||
Expect.isFalse(nullable.isA<JSAny>());
|
||||
Expect.isFalse(nullable.isA<JSBoxedDartObject>());
|
||||
Expect.isFalse(nullable.isA<JSExportedDartFunction>());
|
||||
eval('''
|
||||
globalThis.nullable = undefined;
|
||||
''');
|
||||
@@ -132,10 +144,12 @@ void testNull() {
|
||||
Expect.isTrue(nullable.isA<JSObject?>());
|
||||
Expect.isTrue(nullable.isA<JSAny?>());
|
||||
Expect.isTrue(nullable.isA<JSBoxedDartObject?>());
|
||||
Expect.isTrue(nullable.isA<JSExportedDartFunction?>());
|
||||
Expect.isFalse(nullable.isA<JSString>());
|
||||
Expect.isFalse(nullable.isA<JSObject>());
|
||||
Expect.isFalse(nullable.isA<JSAny>());
|
||||
Expect.isFalse(nullable.isA<JSBoxedDartObject>());
|
||||
Expect.isFalse(nullable.isA<JSExportedDartFunction>());
|
||||
}
|
||||
|
||||
void testPrimitives() {
|
||||
@@ -292,14 +306,12 @@ void testJSObjects() {
|
||||
Expect.isTrue(jsFunction.isA<JSFunction?>());
|
||||
testIsJSObject(jsFunction);
|
||||
Expect.isFalse(jsFunction.isA<JSNumber>());
|
||||
// TODO(srujzs): Currently, we can't distinguish between a JS function and an
|
||||
// exported function. https://github.com/dart-lang/sdk/issues/62573
|
||||
Expect.isTrue(jsFunction.isA<JSExportedDartFunction>());
|
||||
Expect.isFalse(jsFunction.isA<JSExportedDartFunction>());
|
||||
|
||||
Object jsFunctionObj = jsFunction;
|
||||
Expect.isTrue(jsFunctionObj.isA<JSFunction>());
|
||||
Expect.isFalse(jsFunctionObj.isA<JSBoxedDartObject>());
|
||||
Expect.isTrue(jsFunctionObj.isA<JSExportedDartFunction>());
|
||||
Expect.isFalse(jsFunctionObj.isA<JSExportedDartFunction>());
|
||||
|
||||
// JSExportedDartFunction.
|
||||
final jsExportedDartFunction = () {}.toJS;
|
||||
@@ -526,6 +538,22 @@ void testUserTypes() {
|
||||
Expect.isTrue(wrapJsBox.isA<WrapJSBoxedDartObject?>());
|
||||
Expect.isTrue(wrapJsBoxObj.isA<WrapJSBoxedDartObject>());
|
||||
|
||||
// Test that a type wrapping `JSExportedDartFunction` should have a different
|
||||
// type-check.
|
||||
eval('''
|
||||
class WrapJSExportedDartFunction {}
|
||||
globalThis.WrapJSExportedDartFunction = WrapJSExportedDartFunction;
|
||||
''');
|
||||
final wrapJsEdf = WrapJSExportedDartFunction(() {}.toJS);
|
||||
Expect.isFalse(wrapJsEdf.isA<WrapJSExportedDartFunction>());
|
||||
setPrototypeOf(wrapJsEdf, wrapJSExportedDartFunctionPrototype);
|
||||
Expect.isTrue(wrapJsEdf.isA<WrapJSExportedDartFunction>());
|
||||
Expect.isTrue(nil.isA<WrapJSExportedDartFunction?>());
|
||||
Expect.isFalse(nil.isA<WrapJSExportedDartFunction>());
|
||||
|
||||
Object? wrapJsEdfObj = wrapJsEdf;
|
||||
Expect.isTrue(wrapJsEdfObj.isA<WrapJSExportedDartFunction>());
|
||||
|
||||
// Test subtyping a type in the browser.
|
||||
eval('''
|
||||
class SubtypeArray extends Array {}
|
||||
|
||||
Reference in New Issue
Block a user