[dart:js_interop] Add toJSCaptureThis
Closes https://github.com/dart-lang/sdk/issues/54381 - Adds an API to capture the this value so that users can use it in the callback. - Adds specialized stubs to dart2js similar to what was done for toJS. - Adds generic stub to DDC as these stubs don't get tree-shaken away and toJSCaptureThis is less likely to be used. - Modifies dart2wasm lowerings to add this to the JS function wrapper if calling toJSCaptureThis. Change-Id: Ic0a7fd768de1dd6b491998e029ff5eb406ee7992 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/377160 Reviewed-by: Stephen Adams <sra@google.com> Commit-Queue: Srujan Gaddam <srujzs@google.com> Reviewed-by: Leaf Petersen <leafp@google.com> Reviewed-by: Martin Kustermann <kustermann@google.com>
This commit is contained in:
committed by
Commit Queue
parent
93054a62d5
commit
ae3dbcdfb1
@@ -17,6 +17,8 @@
|
||||
- Added constructors for `JSArrayBuffer`, `JSDataView`, and concrete typed array
|
||||
types e.g. `JSInt8Array`.
|
||||
- Added `length` and `[]`/`[]=` operators to `JSArray`.
|
||||
- Added `toJSCaptureThis` so `this` is passed in from JavaScript to the
|
||||
callback as the first parameter.
|
||||
|
||||
## 3.5.0
|
||||
|
||||
|
||||
@@ -33,26 +33,37 @@ typedef _InvocationBuilder = Expression Function(
|
||||
/// Replaces js_util methods with inline calls to foreign_helper JS which
|
||||
/// emits the code as a JavaScript code fragment.
|
||||
class JsUtilOptimizer extends Transformer {
|
||||
final Procedure _allowInteropTarget;
|
||||
final Iterable<Procedure> _allowedInteropJsUtilTargets;
|
||||
final Procedure _callMethodTarget;
|
||||
final Procedure _callMethodTrustTypeTarget;
|
||||
final List<Procedure> _callMethodUncheckedTargets;
|
||||
final List<Procedure> _callMethodUncheckedTrustTypeTargets;
|
||||
final Procedure _callConstructorTarget;
|
||||
final List<Procedure> _callConstructorUncheckedTargets;
|
||||
final CloneVisitorNotMembers _cloner = CloneVisitorWithMembers();
|
||||
final Map<Member, _InvocationBuilder?> _externalInvocationBuilders = {};
|
||||
final Procedure _functionToJSTarget;
|
||||
final Procedure _functionToJSCaptureThisTarget;
|
||||
final List<Procedure> _functionToJSTargets;
|
||||
final List<Procedure>? _functionToJSCaptureThisTargets;
|
||||
final Procedure _functionToJSNTarget;
|
||||
final Procedure _functionToJSCaptureThisNTarget;
|
||||
final Procedure _getPropertyTarget;
|
||||
final Procedure _getPropertyTrustTypeTarget;
|
||||
final Procedure _globalContextTarget;
|
||||
final Procedure _jsExportedDartFunctionToDartTarget;
|
||||
final Procedure _jsFunctionToDart;
|
||||
final Procedure _jsTarget;
|
||||
final Procedure _listEmptyFactory;
|
||||
final InterfaceType _objectType;
|
||||
final Procedure _setPropertyTarget;
|
||||
final Procedure _setPropertyUncheckedTarget;
|
||||
|
||||
final CoreTypes _coreTypes;
|
||||
final CloneVisitorNotMembers _cloner = CloneVisitorWithMembers();
|
||||
final ExtensionIndex _extensionIndex;
|
||||
final Map<Member, _InvocationBuilder?> _externalInvocationBuilders = {};
|
||||
final StatefulStaticTypeContext _staticTypeContext;
|
||||
|
||||
/// Dynamic members in js_util that interop allowed.
|
||||
static const List<String> _allowedInteropJsUtilMembers = [
|
||||
'callConstructor',
|
||||
@@ -63,18 +74,9 @@ class JsUtilOptimizer extends Transformer {
|
||||
'setProperty'
|
||||
];
|
||||
|
||||
final Procedure _allowInteropTarget;
|
||||
final Iterable<Procedure> _allowedInteropJsUtilTargets;
|
||||
final Procedure _jsTarget;
|
||||
final Procedure _listEmptyFactory;
|
||||
|
||||
final CoreTypes _coreTypes;
|
||||
final StatefulStaticTypeContext _staticTypeContext;
|
||||
|
||||
final ExtensionIndex _extensionIndex;
|
||||
|
||||
JsUtilOptimizer(
|
||||
this._coreTypes, ClassHierarchy hierarchy, this._extensionIndex)
|
||||
this._coreTypes, ClassHierarchy hierarchy, this._extensionIndex,
|
||||
{required bool isDart2JS})
|
||||
: _callMethodTarget =
|
||||
_coreTypes.index.getTopLevelProcedure('dart:js_util', 'callMethod'),
|
||||
_callMethodTrustTypeTarget = _coreTypes.index
|
||||
@@ -95,12 +97,23 @@ class JsUtilOptimizer extends Transformer {
|
||||
'dart:js_util', '_callConstructorUnchecked$i')),
|
||||
_functionToJSTarget = _coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'),
|
||||
_functionToJSCaptureThisTarget = _coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_interop',
|
||||
'FunctionToJSExportedDartFunction|get#toJSCaptureThis'),
|
||||
_functionToJSTargets = List<Procedure>.generate(
|
||||
6,
|
||||
(i) => _coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', '_functionToJS$i')),
|
||||
_functionToJSCaptureThisTargets = isDart2JS
|
||||
? List<Procedure>.generate(
|
||||
5,
|
||||
(i) => _coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_util', '_functionToJSCaptureThis$i'))
|
||||
: null,
|
||||
_functionToJSNTarget = _coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', '_functionToJSN'),
|
||||
_functionToJSCaptureThisNTarget = _coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', '_functionToJSCaptureThisN'),
|
||||
_getPropertyTarget = _coreTypes.index
|
||||
.getTopLevelProcedure('dart:js_util', 'getProperty'),
|
||||
_getPropertyTrustTypeTarget = _coreTypes.index
|
||||
@@ -500,6 +513,8 @@ class JsUtilOptimizer extends Transformer {
|
||||
// https://github.com/dart-lang/sdk/issues/53367 is resolved.
|
||||
} else if (target == _functionToJSTarget) {
|
||||
invocation = _lowerFunctionToJS(node);
|
||||
} else if (target == _functionToJSCaptureThisTarget) {
|
||||
invocation = _lowerFunctionToJS(node, captureThis: true);
|
||||
} else if (target == _jsExportedDartFunctionToDartTarget) {
|
||||
invocation = _lowerJSExportedDartFunctionToDart(node);
|
||||
} else if (target.isExternal && !JsInteropChecks.isPatchedMember(target)) {
|
||||
@@ -684,27 +699,47 @@ class JsUtilOptimizer extends Transformer {
|
||||
..parent = nodeParent;
|
||||
}
|
||||
|
||||
/// For the given `dart:js_interop` `Function.toJS` invocation [node], returns
|
||||
/// an invocation of `_functionToJSX` with the given `Function` argument,
|
||||
/// where X is the number of the positional arguments.
|
||||
/// For the given `dart:js_interop` `Function.toJS` or
|
||||
/// `Function.toJSCaptureThis` invocation [node], returns an invocation of the
|
||||
/// corresponding private stub in `js_util` with the invocation's [Function]
|
||||
/// argument and the number of positional parameters of that [Function].
|
||||
///
|
||||
/// If the number of the positional arguments is larger than 5, returns an
|
||||
/// invocation of `_functionToJSN` instead.
|
||||
StaticInvocation _lowerFunctionToJS(StaticInvocation node) {
|
||||
/// If [captureThis] is false, the node target is assumed to be
|
||||
/// `Function.toJS` and otherwise `Function.toJSCaptureThis`.
|
||||
///
|
||||
/// There are specialized stubs up to a certain positional parameter length,
|
||||
/// and after that, either an invocation of `_functionToJSN` or
|
||||
/// `_functionToJSCaptureThisN` is returned.
|
||||
StaticInvocation _lowerFunctionToJS(StaticInvocation node,
|
||||
{bool captureThis = false}) {
|
||||
// JS interop checks assert that the static type is available, and that
|
||||
// there are no named arguments or type arguments.
|
||||
final function = node.arguments.positional.single;
|
||||
final functionType =
|
||||
function.getStaticType(_staticTypeContext) as FunctionType;
|
||||
final argumentsLength = functionType.positionalParameters.length;
|
||||
final parametersLength = functionType.positionalParameters.length;
|
||||
List<Procedure>? specializedStubs;
|
||||
int stubIndex;
|
||||
Procedure genericStub;
|
||||
if (captureThis) {
|
||||
specializedStubs = _functionToJSCaptureThisTargets;
|
||||
stubIndex = parametersLength - 1; // Account for `this`.
|
||||
genericStub = _functionToJSCaptureThisNTarget;
|
||||
} else {
|
||||
specializedStubs = _functionToJSTargets;
|
||||
stubIndex = parametersLength;
|
||||
genericStub = _functionToJSNTarget;
|
||||
}
|
||||
Procedure target;
|
||||
Arguments arguments;
|
||||
if (argumentsLength < _functionToJSTargets.length) {
|
||||
target = _functionToJSTargets[argumentsLength];
|
||||
if (specializedStubs != null &&
|
||||
stubIndex >= 0 &&
|
||||
stubIndex < specializedStubs.length) {
|
||||
target = specializedStubs[stubIndex];
|
||||
arguments = Arguments([function]);
|
||||
} else {
|
||||
target = _functionToJSNTarget;
|
||||
arguments = Arguments([function, IntLiteral(argumentsLength)]);
|
||||
target = genericStub;
|
||||
arguments = Arguments([function, IntLiteral(parametersLength)]);
|
||||
}
|
||||
return StaticInvocation(
|
||||
target, arguments..fileOffset = node.arguments.fileOffset)
|
||||
|
||||
@@ -156,8 +156,9 @@ class Dart2jsTarget extends Target {
|
||||
jsInteropReporter,
|
||||
jsInteropChecks.exportChecker,
|
||||
jsInteropChecks.extensionIndex);
|
||||
var jsUtilOptimizer =
|
||||
JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex);
|
||||
var jsUtilOptimizer = JsUtilOptimizer(
|
||||
coreTypes, hierarchy, jsInteropChecks.extensionIndex,
|
||||
isDart2JS: true);
|
||||
for (var library in libraries) {
|
||||
// Shared transformer has static checks, so we still visit even if there
|
||||
// are errors.
|
||||
|
||||
@@ -177,11 +177,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` or `Function.toJS`). [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.
|
||||
/// `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.
|
||||
///
|
||||
/// 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
|
||||
@@ -190,20 +191,30 @@ class CallbackSpecializer {
|
||||
///
|
||||
/// Returns the created [Procedure].
|
||||
Procedure _getJSWrapperFunction(Procedure node, FunctionType type,
|
||||
{required bool boxExternRef, required bool needsCastClosure}) {
|
||||
{required bool boxExternRef,
|
||||
required bool needsCastClosure,
|
||||
required bool captureThis}) {
|
||||
final functionTrampolineName =
|
||||
_createFunctionTrampoline(node, type, boxExternRef: boxExternRef);
|
||||
List<String> jsParameters = [];
|
||||
for (int i = 0; i < type.positionalParameters.length; i++) {
|
||||
var jsParametersLength = type.positionalParameters.length;
|
||||
if (captureThis) jsParametersLength--;
|
||||
for (int i = 0; i < jsParametersLength; i++) {
|
||||
jsParameters.add('x$i');
|
||||
}
|
||||
String jsWrapperParams = jsParameters.join(',');
|
||||
String dartArguments = 'f,arguments.length';
|
||||
// We could avoid incrementing the arguments length in the case of
|
||||
// `captureThis` and have the function trampoline account for the extra
|
||||
// argument, but there's no benefit in doing that.
|
||||
String argumentsLength =
|
||||
captureThis ? 'arguments.length + 1' : 'arguments.length';
|
||||
String dartArguments = 'f,$argumentsLength';
|
||||
String jsMethodParams = 'f';
|
||||
if (needsCastClosure) {
|
||||
dartArguments = '$dartArguments,castClosure';
|
||||
jsMethodParams = '($jsMethodParams,castClosure)';
|
||||
}
|
||||
if (captureThis) dartArguments = '$dartArguments,this';
|
||||
if (jsParameters.isNotEmpty) {
|
||||
dartArguments = '$dartArguments,$jsWrapperParams';
|
||||
}
|
||||
@@ -263,7 +274,7 @@ class CallbackSpecializer {
|
||||
final type = argument.getStaticType(_staticTypeContext) as FunctionType;
|
||||
final jsWrapperFunction = _getJSWrapperFunction(
|
||||
staticInvocation.target, type,
|
||||
boxExternRef: false, needsCastClosure: false);
|
||||
boxExternRef: false, needsCastClosure: false, captureThis: false);
|
||||
final v = VariableDeclaration('#var',
|
||||
initializer: argument, type: type, isSynthesized: true);
|
||||
return Let(
|
||||
@@ -332,7 +343,7 @@ class CallbackSpecializer {
|
||||
returnType: VoidType()));
|
||||
}
|
||||
|
||||
/// Given an invocation of `<Function>.toJS`, returns an [Expression]
|
||||
/// Given an invocation of `Function.toJS`, returns an [Expression]
|
||||
/// representing:
|
||||
///
|
||||
/// JSValue(jsWrapperFunction(<Function>))
|
||||
@@ -340,13 +351,19 @@ class CallbackSpecializer {
|
||||
/// or if a cast closure is needed:
|
||||
///
|
||||
/// JSValue(jsWrapperFunction(<Function>, <CastClosure>))
|
||||
Expression functionToJS(StaticInvocation staticInvocation) {
|
||||
///
|
||||
/// If [captureThis] is true, this is assumed to be an invocation of
|
||||
/// `Function.toJSCaptureThis`.
|
||||
Expression functionToJS(StaticInvocation staticInvocation,
|
||||
{bool captureThis = false}) {
|
||||
final argument = staticInvocation.arguments.positional.single;
|
||||
final type = argument.getStaticType(_staticTypeContext) as FunctionType;
|
||||
final castClosure = _createCastClosure(type);
|
||||
final jsWrapperFunction = _getJSWrapperFunction(
|
||||
staticInvocation.target, type,
|
||||
boxExternRef: true, needsCastClosure: castClosure != null);
|
||||
boxExternRef: true,
|
||||
needsCastClosure: castClosure != null,
|
||||
captureThis: captureThis);
|
||||
return _createJSValue(StaticInvocation(
|
||||
jsWrapperFunction,
|
||||
Arguments([
|
||||
|
||||
@@ -79,6 +79,8 @@ class InteropTransformer extends Transformer {
|
||||
return _callbackSpecializer.allowInterop(node);
|
||||
} else if (target == _util.functionToJSTarget) {
|
||||
return _callbackSpecializer.functionToJS(node);
|
||||
} else if (target == _util.functionToJSCaptureThisTarget) {
|
||||
return _callbackSpecializer.functionToJS(node, captureThis: true);
|
||||
} else if (target == _util.inlineJSTarget) {
|
||||
return _inlineExpander.expand(node);
|
||||
} else {
|
||||
|
||||
@@ -16,6 +16,7 @@ class CoreTypesUtil {
|
||||
final Procedure allowInteropTarget;
|
||||
final Procedure dartifyRawTarget;
|
||||
final Procedure functionToJSTarget;
|
||||
final Procedure functionToJSCaptureThisTarget;
|
||||
final Procedure greaterThanOrEqualToTarget;
|
||||
final Procedure inlineJSTarget;
|
||||
final Procedure isDartFunctionWrappedTarget;
|
||||
@@ -35,6 +36,9 @@ class CoreTypesUtil {
|
||||
.getTopLevelProcedure('dart:_js_helper', 'dartifyRaw'),
|
||||
functionToJSTarget = coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_interop', 'FunctionToJSExportedDartFunction|get#toJS'),
|
||||
functionToJSCaptureThisTarget = coreTypes.index.getTopLevelProcedure(
|
||||
'dart:js_interop',
|
||||
'FunctionToJSExportedDartFunction|get#toJSCaptureThis'),
|
||||
greaterThanOrEqualToTarget =
|
||||
coreTypes.index.getProcedure('dart:core', 'num', '>='),
|
||||
inlineJSTarget =
|
||||
|
||||
@@ -209,8 +209,9 @@ class DevCompilerTarget extends Target {
|
||||
jsInteropReporter,
|
||||
jsInteropChecks.exportChecker,
|
||||
jsInteropChecks.extensionIndex);
|
||||
final jsUtilOptimizer =
|
||||
JsUtilOptimizer(coreTypes, hierarchy, jsInteropChecks.extensionIndex);
|
||||
final jsUtilOptimizer = JsUtilOptimizer(
|
||||
coreTypes, hierarchy, jsInteropChecks.extensionIndex,
|
||||
isDart2JS: false);
|
||||
for (var node in nodes) {
|
||||
_CovarianceTransformer(node).transform();
|
||||
// Shared interop transformer has static checks, so we still visit.
|
||||
|
||||
@@ -178,6 +178,30 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
// TODO(srujzs): We could add specific arity stubs for this like we do with
|
||||
// `Function.toJS`, but unlike dart2js, unused ones aren't tree-shaken away.
|
||||
// Considering this isn't a very commonly used API, that seems wasteful.
|
||||
JavaScriptFunction _functionToJSCaptureThisN(Function f, int maxLength) {
|
||||
if (!dart.isDartFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final ret = JS<JavaScriptFunction>(
|
||||
'!',
|
||||
'''
|
||||
function (...arguments) {
|
||||
let args = [this];
|
||||
args.push.apply(args, arguments);
|
||||
return #(#, Array.prototype.slice.call(args, 0,
|
||||
Math.min(args.length, #)));
|
||||
}
|
||||
''',
|
||||
dart.dcall,
|
||||
f,
|
||||
maxLength);
|
||||
JS('', '#[#] = #', ret, _functionToJSProperty, f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
_callDartFunctionFast0(callback) => JS('', '#()', callback);
|
||||
|
||||
_callDartFunctionFast1(callback, arg1, int length) {
|
||||
|
||||
@@ -103,6 +103,25 @@ JavaScriptFunction _functionToJS0(Function f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJSCaptureThis0(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f) {
|
||||
return function() {
|
||||
return _call(f, this, 1);
|
||||
}
|
||||
}(#, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast1),
|
||||
f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJS1(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
@@ -122,6 +141,25 @@ JavaScriptFunction _functionToJS1(Function f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJSCaptureThis1(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f) {
|
||||
return function(arg1) {
|
||||
return _call(f, this, arg1, arguments.length + 1);
|
||||
}
|
||||
}(#, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast2),
|
||||
f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJS2(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
@@ -141,6 +179,25 @@ JavaScriptFunction _functionToJS2(Function f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJSCaptureThis2(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f) {
|
||||
return function(arg1, arg2) {
|
||||
return _call(f, this, arg1, arg2, arguments.length + 1);
|
||||
}
|
||||
}(#, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast3),
|
||||
f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJS3(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
@@ -160,6 +217,25 @@ JavaScriptFunction _functionToJS3(Function f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJSCaptureThis3(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f) {
|
||||
return function(arg1, arg2, arg3) {
|
||||
return _call(f, this, arg1, arg2, arg3, arguments.length + 1);
|
||||
}
|
||||
}(#, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast4),
|
||||
f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJS4(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
@@ -179,6 +255,25 @@ JavaScriptFunction _functionToJS4(Function f) {
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJSCaptureThis4(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f) {
|
||||
return function(arg1, arg2, arg3, arg4) {
|
||||
return _call(f, this, arg1, arg2, arg3, arg4, arguments.length + 1);
|
||||
}
|
||||
}(#, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFast5),
|
||||
f);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
JavaScriptFunction _functionToJS5(Function f) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
@@ -206,7 +301,7 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f, maxLength) {
|
||||
return function () {
|
||||
return function() {
|
||||
return _call(f, Array.prototype.slice.call(arguments, 0,
|
||||
Math.min(arguments.length, maxLength)));
|
||||
}
|
||||
@@ -219,6 +314,33 @@ JavaScriptFunction _functionToJSN(Function f, int maxLength) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// TODO(srujzs): It would be nice if we can generate these and the
|
||||
// `Function.toJS` stubs dynamically as needed in the interop transformer. It
|
||||
// would reduce the potential of accidentally introducing bugs in specific
|
||||
// stubs.
|
||||
JavaScriptFunction _functionToJSCaptureThisN(Function f, int maxLength) {
|
||||
if (isJSFunction(f)) {
|
||||
throw ArgumentError('Attempting to rewrap a JS function.');
|
||||
}
|
||||
final result = JS(
|
||||
'JavaScriptFunction',
|
||||
'''
|
||||
function(_call, f, maxLength) {
|
||||
return function() {
|
||||
var args = [this];
|
||||
args.push.apply(args, arguments);
|
||||
return _call(f, Array.prototype.slice.call(args, 0,
|
||||
Math.min(args.length, maxLength)));
|
||||
}
|
||||
}(#, #, #)
|
||||
''',
|
||||
DART_CLOSURE_TO_JS(_callDartFunctionFastN),
|
||||
f,
|
||||
maxLength);
|
||||
JS('', '#.# = #', result, DART_CLOSURE_PROPERTY_NAME, f);
|
||||
return result;
|
||||
}
|
||||
|
||||
_callDartFunctionFast0(Function callback) => callback();
|
||||
|
||||
_callDartFunctionFast1(Function callback, arg1, int length) {
|
||||
|
||||
@@ -77,6 +77,12 @@ extension FunctionToJSExportedDartFunction on Function {
|
||||
JSExportedDartFunction get toJS => throw UnimplementedError(
|
||||
"'toJS' should never directly be called. Calls to 'toJS' should have "
|
||||
'been transformed by the interop transformer.');
|
||||
|
||||
@patch
|
||||
JSExportedDartFunction get toJSCaptureThis => throw UnimplementedError(
|
||||
"'toJSCaptureThis' should never directly be called. Calls to "
|
||||
"'toJSCaptureThis' should have been transformed by the interop "
|
||||
'transformer.');
|
||||
}
|
||||
|
||||
// Embedded global property for wrapped Dart objects passed via JS interop.
|
||||
|
||||
@@ -92,6 +92,12 @@ extension FunctionToJSExportedDartFunction on Function {
|
||||
JSExportedDartFunction get toJS => throw UnimplementedError(
|
||||
"This should never be called. Calls to 'toJS' should have been "
|
||||
'transformed by the interop transformer.');
|
||||
|
||||
@patch
|
||||
JSExportedDartFunction get toJSCaptureThis => throw UnimplementedError(
|
||||
"'toJSCaptureThis' should never directly be called. Calls to "
|
||||
"'toJSCaptureThis' should have been transformed by the interop "
|
||||
'transformer.');
|
||||
}
|
||||
|
||||
// Embedded global property for wrapped Dart objects passed via JS interop.
|
||||
|
||||
@@ -123,8 +123,9 @@ extension type JSFunction._(JSFunctionRepType _jsFunction)
|
||||
|
||||
/// A JavaScript callable function created from a Dart function.
|
||||
///
|
||||
/// See [FunctionToJSExportedDartFunction.toJS] for more details on how to
|
||||
/// convert a Dart function.
|
||||
/// See [FunctionToJSExportedDartFunction.toJS] or
|
||||
/// [FunctionToJSExportedDartFunction.toJSCaptureThis] for more details on how
|
||||
/// to convert a Dart function.
|
||||
@JS('Function')
|
||||
extension type JSExportedDartFunction._(
|
||||
JSExportedDartFunctionRepType _jsExportedDartFunction)
|
||||
@@ -591,7 +592,8 @@ extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
|
||||
/// The Dart [Function] that this [JSExportedDartFunction] wrapped.
|
||||
///
|
||||
/// Must be a function that was wrapped with
|
||||
/// [FunctionToJSExportedDartFunction.toJS].
|
||||
/// [FunctionToJSExportedDartFunction.toJS] or
|
||||
/// [FunctionToJSExportedDartFunction.toJSCaptureThis].
|
||||
external Function get toDart;
|
||||
}
|
||||
|
||||
@@ -614,6 +616,16 @@ extension FunctionToJSExportedDartFunction on Function {
|
||||
/// Calling this on the same [Function] again will always result in a new
|
||||
/// JavaScript function.
|
||||
external JSExportedDartFunction get toJS;
|
||||
|
||||
/// A callable JavaScript function that wraps this [Function] and captures the
|
||||
/// `this` value when called.
|
||||
///
|
||||
/// Identical to [toJS], except the resulting [JSExportedDartFunction] will
|
||||
/// pass `this` from JavaScript as the first argument to the converted
|
||||
/// [Function]. Any [Function] that is converted with this member should take
|
||||
/// in an extra parameter at the beginning of the parameter list to handle
|
||||
/// this.
|
||||
external JSExportedDartFunction get toJSCaptureThis;
|
||||
}
|
||||
|
||||
/// Conversions from [JSBoxedDartObject] to [Object].
|
||||
|
||||
@@ -7,9 +7,11 @@
|
||||
import 'dart:js_interop';
|
||||
|
||||
import 'package:expect/expect.dart';
|
||||
import 'package:expect/variations.dart';
|
||||
|
||||
const isDDC = const bool.fromEnvironment('dart.library._ddc_only');
|
||||
const isDart2JS = const bool.fromEnvironment('dart.tool.dart2js');
|
||||
const soundNullSafety = !unsoundNullSafety;
|
||||
|
||||
@JS('call')
|
||||
external String _call(JSFunction f, JSArray<JSAny?> args);
|
||||
@@ -22,10 +24,12 @@ external void eval(String code);
|
||||
|
||||
// Zero.
|
||||
String zeroArgs() => '0';
|
||||
String zeroArgsThis([JSObject? this_]) => '0';
|
||||
|
||||
// One.
|
||||
String oneRequired(String arg1) => arg1;
|
||||
String oneOptional([String arg1 = 'default']) => '$arg1';
|
||||
String oneOptionalThis(JSObject? this_, [String arg1 = 'default']) => '$arg1';
|
||||
|
||||
// Two.
|
||||
String twoRequired(String arg1, String? arg2) => '$arg1$arg2';
|
||||
@@ -33,6 +37,9 @@ String oneRequiredOneOptional(String arg1, [String? arg2 = 'default']) =>
|
||||
'$arg1$arg2';
|
||||
String twoOptional([String arg1 = 'default', String? arg2 = 'default']) =>
|
||||
'$arg1$arg2';
|
||||
String oneRequiredOneOptionalThis(JSObject? this_, String arg1,
|
||||
[String? arg2 = 'default']) =>
|
||||
'$arg1$arg2';
|
||||
|
||||
// Three.
|
||||
String threeRequired(String arg1, String? arg2, String arg3) =>
|
||||
@@ -48,6 +55,12 @@ String threeOptional(
|
||||
String? arg2 = 'default',
|
||||
String arg3 = 'default']) =>
|
||||
'$arg1$arg2$arg3';
|
||||
String threeOptionalThis(
|
||||
[JSObject? this_,
|
||||
String arg1 = 'default',
|
||||
String? arg2 = 'default',
|
||||
String arg3 = 'default']) =>
|
||||
'$arg1$arg2$arg3';
|
||||
|
||||
// Four.
|
||||
String fourRequired(String arg1, String? arg2, String arg3, String arg4) =>
|
||||
@@ -69,6 +82,11 @@ String fourOptional(
|
||||
String arg3 = 'default',
|
||||
String arg4 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4';
|
||||
String oneRequiredThreeOptionalThis(JSObject? this_, String arg1,
|
||||
[String? arg2 = 'default',
|
||||
String arg3 = 'default',
|
||||
String arg4 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4';
|
||||
|
||||
// Five.
|
||||
String fiveRequired(
|
||||
@@ -99,6 +117,10 @@ String fiveOptional(
|
||||
String arg4 = 'default',
|
||||
String arg5 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4$arg5';
|
||||
String threeRequiredTwoOptionalThis(
|
||||
JSObject? this_, String arg1, String? arg2, String arg3,
|
||||
[String arg4 = 'default', String arg5 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4$arg5';
|
||||
|
||||
// Six.
|
||||
String sixRequired(String arg1, String? arg2, String arg3, String arg4,
|
||||
@@ -138,29 +160,45 @@ String sixOptional(
|
||||
String arg5 = 'default',
|
||||
String arg6 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4$arg5$arg6';
|
||||
String sixOptionalThis(
|
||||
[JSObject? this_,
|
||||
String arg1 = 'default',
|
||||
String? arg2 = 'default',
|
||||
String arg3 = 'default',
|
||||
String arg4 = 'default',
|
||||
String arg5 = 'default',
|
||||
String arg6 = 'default']) =>
|
||||
'$arg1$arg2$arg3$arg4$arg5$arg6';
|
||||
|
||||
void testZero() {
|
||||
// Arity tests.
|
||||
Expect.equals(call(zeroArgs.toJS, []), '0');
|
||||
Expect.equals(call(zeroArgs.toJS, ['extra']), '0');
|
||||
Expect.equals(call(zeroArgs.toJS, [1.0]), '0');
|
||||
Expect.equals(call(zeroArgsThis.toJSCaptureThis, []), '0');
|
||||
Expect.equals(call(zeroArgs.toJSCaptureThis, []), '0');
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = zeroArgs;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = zeroArgsThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(() => (zeroArgs.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() => (zeroArgs.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(zeroArgsThis.toJSCaptureThis as String Function()).toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
void testOne() {
|
||||
// Type tests.
|
||||
Expect.throws(() => call(oneRequired.toJS, [0]));
|
||||
Expect.throwsWhen(hasSoundNullSafety, () => call(oneOptional.toJS, [null]));
|
||||
Expect.throwsWhen(soundNullSafety, () => call(oneOptional.toJS, [null]));
|
||||
Expect.throwsWhen(
|
||||
hasSoundNullSafety, () => call(oneOptional.toJS, ['undefined']));
|
||||
soundNullSafety, () => call(oneOptional.toJS, ['undefined']));
|
||||
Expect.throws(() => call(oneOptionalThis.toJSCaptureThis, [true]));
|
||||
|
||||
// Arity tests.
|
||||
Expect.throws(() => call(oneRequired.toJS, []));
|
||||
@@ -169,20 +207,32 @@ void testOne() {
|
||||
Expect.equals(call(oneOptional.toJS, []), 'default');
|
||||
Expect.equals(call(oneOptional.toJS, ['a']), 'a');
|
||||
Expect.equals(call(oneOptional.toJS, ['a', 'extra']), 'a');
|
||||
Expect.equals(call(oneOptionalThis.toJSCaptureThis, ['a']), 'a');
|
||||
Expect.throws(() => call(oneRequired.toJSCaptureThis, []));
|
||||
|
||||
// Function subtyping tests.
|
||||
Expect.equals(call((oneOptional as String Function()).toJS, []), 'default');
|
||||
// Throws away the additional args due to the static typing.
|
||||
Expect.equals(
|
||||
call((oneOptional as String Function()).toJS, ['a']), 'default');
|
||||
Expect.equals(
|
||||
call((oneOptionalThis as String Function(JSObject?)).toJSCaptureThis,
|
||||
['a']),
|
||||
'default');
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = oneRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = oneOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(() => (oneOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(
|
||||
() => (oneOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(oneOptionalThis.toJSCaptureThis as String Function(JSObject?))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,11 +240,12 @@ void testTwo() {
|
||||
// Type tests.
|
||||
Expect.throws(() => call(twoOptional.toJS, [false, 'b']));
|
||||
Expect.throws(() => call(twoOptional.toJS, ['a', 1.0]));
|
||||
Expect.throwsWhen(hasSoundNullSafety,
|
||||
Expect.throwsWhen(soundNullSafety,
|
||||
() => call(oneRequiredOneOptional.toJS, ['undefined', 'b']));
|
||||
Expect.throws(() => call(oneRequiredOneOptional.toJS, ['a', true]));
|
||||
Expect.throws(() => call(twoRequired.toJS, [0, 'b']));
|
||||
Expect.throws(() => call(twoRequired.toJS, ['a', 0]));
|
||||
Expect.throws(() => call(oneRequiredOneOptional.toJSCaptureThis, [0]));
|
||||
|
||||
// Arity tests.
|
||||
Expect.throws(() => call(twoRequired.toJS, []));
|
||||
@@ -209,6 +260,11 @@ void testTwo() {
|
||||
Expect.equals(call(twoOptional.toJS, ['a']), 'adefault');
|
||||
Expect.equals(call(twoOptional.toJS, ['a', 'b']), 'ab');
|
||||
Expect.equals(call(twoOptional.toJS, ['a', 'b', 'extra']), 'ab');
|
||||
Expect.equals(
|
||||
call(oneRequiredOneOptionalThis.toJSCaptureThis, ['a', 'b', 'extra']),
|
||||
'ab');
|
||||
Expect.equals(
|
||||
call(oneRequiredOneOptionalThis.toJSCaptureThis, ['a']), 'adefault');
|
||||
|
||||
// Function subtyping tests.
|
||||
// TODO(55881): dart2wasm's type conversions are based on the static type,
|
||||
@@ -242,6 +298,12 @@ void testTwo() {
|
||||
Expect.equals(
|
||||
call((twoOptional as String Function([String])).toJS, ['a', false]),
|
||||
'adefault');
|
||||
Expect.equals(
|
||||
call(
|
||||
(oneRequiredOneOptionalThis as String Function(JSObject?, String))
|
||||
.toJSCaptureThis,
|
||||
['a', 'b']),
|
||||
'adefault');
|
||||
|
||||
// `undefined` tests.
|
||||
Expect.equals(call(twoRequired.toJS, ['a', 'undefined']), 'anull');
|
||||
@@ -255,12 +317,17 @@ void testTwo() {
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = twoRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = oneRequiredOneOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(
|
||||
() => (oneRequiredOneOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(
|
||||
() => (oneRequiredOneOptional.toJS as String Function(String)).toJS);
|
||||
Expect.throwsArgumentError(() => (oneRequiredOneOptionalThis.toJSCaptureThis
|
||||
as String Function(JSObject?, String))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,10 +337,13 @@ void testThree() {
|
||||
// Type tests.
|
||||
Expect.throws(() => call(threeRequired.toJS, [0, 'b', 'c']));
|
||||
Expect.throws(() => call(oneRequiredTwoOptional.toJS, ['a', false]));
|
||||
Expect.throws(() => call(threeOptionalThis.toJSCaptureThis, [true]));
|
||||
|
||||
// Arity tests.
|
||||
Expect.equals(call(twoRequiredOneOptional.toJS, ['a', 'b']), 'abdefault');
|
||||
Expect.throws(() => call(oneRequiredTwoOptional.toJS, []));
|
||||
Expect.equals(
|
||||
call(threeOptionalThis.toJSCaptureThis, ['a', 'b']), 'abdefault');
|
||||
|
||||
// Function subtyping tests.
|
||||
var closure = () => call(
|
||||
@@ -287,6 +357,12 @@ void testThree() {
|
||||
Expect.equals(
|
||||
call((threeOptional as String Function([String])).toJS, ['a', 0, true]),
|
||||
'adefaultdefault');
|
||||
Expect.equals(
|
||||
call(
|
||||
(threeOptionalThis as String Function([JSObject?, String, String?]))
|
||||
.toJSCaptureThis,
|
||||
['a', 'b', false]),
|
||||
'abdefault');
|
||||
|
||||
// `undefined` tests.
|
||||
Expect.equals(call(threeOptional.toJS, ['a', 'undefined']),
|
||||
@@ -294,12 +370,17 @@ void testThree() {
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = threeRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = threeOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(
|
||||
() => (twoRequiredOneOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(twoRequiredOneOptional.toJS as String Function(String, String?)).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(threeOptionalThis.toJSCaptureThis as String Function(JSObject?))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,10 +389,13 @@ void testFour() {
|
||||
Expect.throws(
|
||||
() => call(threeRequiredOneOptional.toJS, ['a', 'b', 'c', true]));
|
||||
Expect.throws(() => call(oneRequiredThreeOptional.toJS, [false]));
|
||||
Expect.throws(() => call(oneRequiredThreeOptional.toJSCaptureThis, ['a']));
|
||||
|
||||
// Arity tests.
|
||||
Expect.equals(call(fourRequired.toJS, ['a', 'b', 'c', 'd', false]), 'abcd');
|
||||
Expect.equals(call(fourOptional.toJS, ['a']), 'adefaultdefaultdefault');
|
||||
Expect.equals(call(oneRequiredThreeOptionalThis.toJSCaptureThis, ['a', 'b']),
|
||||
'abdefaultdefault');
|
||||
|
||||
// Function subtyping tests.
|
||||
final closure = () => call(
|
||||
@@ -329,6 +413,12 @@ void testFour() {
|
||||
.toJS,
|
||||
['a', null]),
|
||||
'anulldefaultdefault');
|
||||
Expect.equals(
|
||||
call(
|
||||
(oneRequiredThreeOptionalThis as String Function(JSObject?, String))
|
||||
.toJSCaptureThis,
|
||||
['a', 'b']),
|
||||
'adefaultdefaultdefault');
|
||||
|
||||
// `undefined` tests.
|
||||
Expect.equals(call(oneRequiredThreeOptional.toJS, ['a', 'undefined', 'c']),
|
||||
@@ -336,12 +426,17 @@ void testFour() {
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = fourRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = oneRequiredThreeOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(
|
||||
() => (oneRequiredThreeOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(
|
||||
() => (oneRequiredThreeOptional.toJS as String Function(String)).toJS);
|
||||
Expect.throwsArgumentError(() => (oneRequiredThreeOptionalThis
|
||||
.toJSCaptureThis as String Function(JSObject?, String))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,11 +444,16 @@ void testFive() {
|
||||
// Type tests.
|
||||
Expect.throws(() => call(twoRequiredThreeOptional.toJS, ['a', 0]));
|
||||
Expect.throws(() => call(fiveOptional.toJS, [false]));
|
||||
Expect.throws(() =>
|
||||
call(threeRequiredTwoOptionalThis.toJSCaptureThis, ['a', 'b', 1.0]));
|
||||
|
||||
// Arity tests.
|
||||
Expect.equals(call(fiveRequired.toJS, ['a', 'b', 'c', 'd', 'e', 0]), 'abcde');
|
||||
Expect.equals(call(fourRequiredOneOptional.toJS, ['a', null, 'c', 'd']),
|
||||
'anullcddefault');
|
||||
Expect.equals(
|
||||
call(threeRequiredTwoOptionalThis.toJSCaptureThis, ['a', 'b', 'c', 'd']),
|
||||
'abcddefault');
|
||||
|
||||
// Function subtyping tests.
|
||||
final closure = () => call(
|
||||
@@ -373,6 +473,13 @@ void testFive() {
|
||||
.toJS,
|
||||
['a', null, 'c']),
|
||||
'anullcdefaultdefault');
|
||||
Expect.equals(
|
||||
call(
|
||||
(threeRequiredTwoOptionalThis as String Function(
|
||||
JSObject?, String, String?, String))
|
||||
.toJSCaptureThis,
|
||||
['a', 'b', 'c', 'd']),
|
||||
'abcdefaultdefault');
|
||||
|
||||
// `undefined` tests.
|
||||
Expect.equals(
|
||||
@@ -381,12 +488,19 @@ void testFive() {
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = fiveRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = threeRequiredTwoOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(
|
||||
() => (twoRequiredThreeOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(twoRequiredThreeOptional.toJS as String Function(String, String?))
|
||||
.toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(threeRequiredTwoOptionalThis.toJSCaptureThis as String Function(
|
||||
JSObject?, String, String?, String))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -395,6 +509,8 @@ void testSix() {
|
||||
// Type tests.
|
||||
Expect.throws(() => call(sixRequired.toJS, ['a', 'b', 0.0, 'd', 'e', 'f']));
|
||||
Expect.throws(() => call(threeRequiredThreeOptional.toJS, ['undefined']));
|
||||
Expect.throwsWhen(
|
||||
soundNullSafety, () => call(sixOptionalThis.toJSCaptureThis, [null]));
|
||||
|
||||
// Arity tests.
|
||||
// Verify that we appropriately truncate arguments even though we don't have
|
||||
@@ -403,6 +519,9 @@ void testSix() {
|
||||
call(fourRequiredTwoOptional.toJS, ['a', 'b', 'c', 'd', 'e', 'f', 0]),
|
||||
'abcdef');
|
||||
Expect.throws(() => call(twoRequiredFourOptional.toJS, []));
|
||||
Expect.equals(
|
||||
call(sixOptionalThis.toJSCaptureThis, ['a', 'b', 'c', 'd', 'e', 'f', 0]),
|
||||
'abcdef');
|
||||
|
||||
// Function subtyping tests.
|
||||
var closure = () => call(
|
||||
@@ -419,6 +538,10 @@ void testSix() {
|
||||
call((oneRequiredFiveOptional as String Function(String, [String?])).toJS,
|
||||
['a', 'b', 0, 0.0, false]),
|
||||
'abdefaultdefaultdefaultdefault');
|
||||
Expect.equals(
|
||||
call((sixOptionalThis as String Function()).toJSCaptureThis,
|
||||
[true, 0, 0.0]),
|
||||
'defaultdefaultdefaultdefaultdefaultdefault');
|
||||
|
||||
// `undefined` tests.
|
||||
Expect.equals(call(sixOptional.toJS, ['a', 'undefined', 'c', 'd', 'e']),
|
||||
@@ -426,11 +549,17 @@ void testSix() {
|
||||
|
||||
// Conversion round-trip test.
|
||||
final tearOff = sixRequired;
|
||||
Expect.equals(tearOff, tearOff.toJS.toDart);
|
||||
Expect.identical(tearOff, tearOff.toJS.toDart);
|
||||
final tearOffThis = sixOptionalThis;
|
||||
Expect.identical(tearOffThis, tearOffThis.toJSCaptureThis.toDart);
|
||||
|
||||
// Avoid rewrapping test.
|
||||
if (isDDC || isDart2JS) {
|
||||
Expect.throws(() => (sixOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(
|
||||
() => (sixOptional.toJS as String Function()).toJS);
|
||||
Expect.throwsArgumentError(() =>
|
||||
(sixOptionalThis.toJSCaptureThis as String Function(JSObject?))
|
||||
.toJSCaptureThis);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -215,8 +215,12 @@ void main() {
|
||||
expect(anyF(zero), zero);
|
||||
|
||||
void setBoundNonNullAnyMultipleParametersFunction<T extends JSAny,
|
||||
U extends JSAny, V extends JSAny>() {
|
||||
jsFunction = ((T t, U u, [V? v]) => t).toJS;
|
||||
U extends JSAny, V extends JSAny>({bool captureThis = false}) {
|
||||
if (captureThis) {
|
||||
jsFunction = ((T this_, U u, [V? v]) => this_).toJSCaptureThis;
|
||||
} else {
|
||||
jsFunction = ((T t, U u, [V? v]) => t).toJS;
|
||||
}
|
||||
}
|
||||
|
||||
setBoundNonNullAnyMultipleParametersFunction();
|
||||
@@ -225,6 +229,17 @@ void main() {
|
||||
anyF(zero, zero, null);
|
||||
anyF(zero, zero, zero);
|
||||
|
||||
setBoundNonNullAnyMultipleParametersFunction(captureThis: true);
|
||||
Expect.throwsWhen(soundNullSafety, () => anyF(null, zero));
|
||||
anyF(zero, null);
|
||||
anyF(zero, zero);
|
||||
// TODO(srujzs): It'd be nice if we can test that passing a null value for
|
||||
// `this` throws. However, unless we're in strict mode, that isn't possible
|
||||
// on all backends. While we can define local functions in strict mode, the
|
||||
// wrapping of the function when converting a Dart function to JS may not be
|
||||
// in a strict mode context, and therefore `this` will still be non-nullable.
|
||||
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call#thisarg
|
||||
|
||||
void setBoundExternalDartReference<T extends ExternalDartReference<U>?, U>() {
|
||||
jsFunction = ((T t) => t?.toDartObject.toExternalReference).toJS;
|
||||
}
|
||||
|
||||
@@ -184,12 +184,23 @@ void syncTests() {
|
||||
'foo'.toJS, 'bar'.toJS)
|
||||
.toDart,
|
||||
'foobar');
|
||||
Expect.equals(edf.toDart, dartFunction);
|
||||
Expect.isTrue(identical(edf.toDart, dartFunction));
|
||||
Expect.identical(edf.toDart, dartFunction);
|
||||
// Two wrappers should not be the same.
|
||||
Expect.notEquals(edf, dartFunction.toJS);
|
||||
// Converting a non-function should throw.
|
||||
Expect.throws(() => ('foo'.toJS as JSExportedDartFunction).toDart);
|
||||
// `this` should be captured correctly in `toJSCaptureThis`.
|
||||
final this_ = JSObject();
|
||||
final dartFunctionThis = (JSObject this__, JSString a, JSString b) {
|
||||
Expect.equals(this_, this__);
|
||||
return (a.toDart + b.toDart).toJS;
|
||||
};
|
||||
edf = dartFunctionThis.toJSCaptureThis;
|
||||
Expect.equals(
|
||||
(edf.callAsFunction(this_, 'foo'.toJS, 'bar'.toJS) as JSString).toDart,
|
||||
'foobar');
|
||||
Expect.identical(edf.toDart, dartFunctionThis);
|
||||
Expect.notEquals(edf, dartFunctionThis.toJSCaptureThis);
|
||||
|
||||
// [JSBoxedDartObject] <-> [Object]
|
||||
edo = DartObject().toJSBox;
|
||||
|
||||
Reference in New Issue
Block a user