blob: c32af9b3d026bce92b8b73d3c7e72535494512be [file] [log] [blame]
// Copyright (c) 2013, 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.
library dart._js_helper;
import 'dart:collection';
import 'dart:_foreign_helper' show JS, JSExportName;
import 'dart:_interceptors';
import 'dart:_internal'
show
EfficientLengthIterable,
MappedIterable,
IterableElementError,
SubListIterable;
import 'dart:_native_typed_data';
import 'dart:_runtime' as dart;
part 'annotations.dart';
part 'linked_hash_map.dart';
part 'identity_hash_map.dart';
part 'custom_hash_map.dart';
part 'native_helper.dart';
part 'regexp_helper.dart';
part 'string_helper.dart';
part 'js_rti.dart';
class _Patch {
const _Patch();
}
const _Patch patch = _Patch();
/// Adapts a JS `[Symbol.iterator]` to a Dart `get iterator`.
///
/// This is the inverse of `JsIterator`, for classes where we can more
/// efficiently obtain a JS iterator instead of a Dart one.
///
// TODO(jmesserly): this adapter is to work around
// https://github.com/dart-lang/sdk/issues/28320
class DartIterator<E> implements Iterator<E> {
final _jsIterator;
E? _current;
DartIterator(this._jsIterator);
E get current => _current as E;
bool moveNext() {
final ret = JS('', '#.next()', _jsIterator);
_current = JS('', '#.value', ret);
return JS<bool>('!', '!#.done', ret);
}
}
/// Used to compile `sync*`.
class SyncIterable<E> extends IterableBase<E> {
final Function() _initGenerator;
SyncIterable(this._initGenerator);
@JSExportName('Symbol.iterator')
_jsIterator() => _initGenerator();
get iterator => DartIterator(_initGenerator());
}
class Primitives {
static int? parseInt(@nullCheck String source, int? _radix) {
var re = JS('', r'/^\s*[+-]?((0x[a-f0-9]+)|(\d+)|([a-z0-9]+))\s*$/i');
// This isn't reified List<String?>?, but it's safe to use as long as we use
// it locally and don't expose it to user code.
var match = JS<List<String?>?>('', '#.exec(#)', re, source);
int digitsIndex = 1;
int hexIndex = 2;
int decimalIndex = 3;
if (match == null) {
// TODO(sra): It might be that the match failed due to unrecognized U+0085
// spaces. We could replace them with U+0020 spaces and try matching
// again.
return null;
}
var decimalMatch = match[decimalIndex];
if (_radix == null) {
if (decimalMatch != null) {
// Cannot fail because we know that the digits are all decimal.
return JS<int>('!', r'parseInt(#, 10)', source);
}
if (match[hexIndex] != null) {
// Cannot fail because we know that the digits are all hex.
return JS<int>('!', r'parseInt(#, 16)', source);
}
return null;
}
@notNull
var radix = _radix;
if (radix < 2 || radix > 36) {
throw RangeError.range(radix, 2, 36, 'radix');
}
if (radix == 10 && decimalMatch != null) {
// Cannot fail because we know that the digits are all decimal.
return JS<int>('!', r'parseInt(#, 10)', source);
}
// If radix >= 10 and we have only decimal digits the string is safe.
// Otherwise we need to check the digits.
if (radix < 10 || decimalMatch == null) {
// We know that the characters must be ASCII as otherwise the
// regexp wouldn't have matched. Lowercasing by doing `| 0x20` is thus
// guaranteed to be a safe operation, since it preserves digits
// and lower-cases ASCII letters.
int maxCharCode;
if (radix <= 10) {
// Allow all digits less than the radix. For example 0, 1, 2 for
// radix 3.
// "0".codeUnitAt(0) + radix - 1;
maxCharCode = (0x30 - 1) + radix;
} else {
// Letters are located after the digits in ASCII. Therefore we
// only check for the character code. The regexp above made already
// sure that the string does not contain anything but digits or
// letters.
// "a".codeUnitAt(0) + (radix - 10) - 1;
maxCharCode = (0x61 - 10 - 1) + radix;
}
assert(match[digitsIndex] is String);
String digitsPart = JS<String>('!', '#[#]', match, digitsIndex);
for (int i = 0; i < digitsPart.length; i++) {
int characterCode = digitsPart.codeUnitAt(i) | 0x20;
if (characterCode > maxCharCode) {
return null;
}
}
}
// The above matching and checks ensures the source has at least one digits
// and all digits are suitable for the radix, so parseInt cannot return NaN.
return JS<int>('!', r'parseInt(#, #)', source, radix);
}
static double? parseDouble(@nullCheck String source) {
// Notice that JS parseFloat accepts garbage at the end of the string.
// Accept only:
// - [+/-]NaN
// - [+/-]Infinity
// - a Dart double literal
// We do allow leading or trailing whitespace.
if (!JS(
'bool',
r'/^\s*[+-]?(?:Infinity|NaN|'
r'(?:\.\d+|\d+(?:\.\d*)?)(?:[eE][+-]?\d+)?)\s*$/.test(#)',
source)) {
return null;
}
var result = JS<double>('!', r'parseFloat(#)', source);
if (result.isNaN) {
var trimmed = source.trim();
if (trimmed == 'NaN' || trimmed == '+NaN' || trimmed == '-NaN') {
return result;
}
return null;
}
return result;
}
/** `r"$".codeUnitAt(0)` */
static const int DOLLAR_CHAR_VALUE = 36;
static int dateNow() => JS<int>('!', r'Date.now()');
static void initTicker() {
if (timerFrequency != 0) return;
// Start with low-resolution. We overwrite the fields if we find better.
timerFrequency = 1000;
if (JS<bool>('!', 'typeof window == "undefined"')) return;
var jsWindow = JS('var', 'window');
if (jsWindow == null) return;
var performance = JS('var', '#.performance', jsWindow);
if (performance == null) return;
if (JS<bool>('!', 'typeof #.now != "function"', performance)) return;
timerFrequency = 1000000;
timerTicks = () => (1000 * JS<num>('!', '#.now()', performance)).floor();
}
/// 0 frequency indicates the default uninitialized state.
static int timerFrequency = 0;
static int Function() timerTicks = dateNow; // Low-resolution version.
static bool get isD8 {
return JS(
'bool',
'typeof version == "function"'
' && typeof os == "object" && "system" in os');
}
static bool get isJsshell {
return JS(
'bool', 'typeof version == "function" && typeof system == "function"');
}
static String currentUri() {
// In a browser return self.location.href.
if (JS<bool>('!', '!!#.location', dart.global_)) {
return JS<String>('!', '#.location.href', dart.global_);
}
// TODO(vsm): Consider supporting properly in non-browser settings.
return '';
}
// This is to avoid stack overflows due to very large argument arrays in
// apply(). It fixes http://dartbug.com/6919
@notNull
static String _fromCharCodeApply(List<int> array) {
const kMaxApply = 500;
@nullCheck
int end = array.length;
if (end <= kMaxApply) {
return JS<String>('!', r'String.fromCharCode.apply(null, #)', array);
}
String result = '';
for (int i = 0; i < end; i += kMaxApply) {
int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
result = JS(
'String',
r'# + String.fromCharCode.apply(null, #.slice(#, #))',
result,
array,
i,
chunkEnd);
}
return result;
}
@notNull
static String stringFromCodePoints(JSArray<int> codePoints) {
List<int> a = <int>[];
for (@nullCheck var i in codePoints) {
if (i <= 0xffff) {
a.add(i);
} else if (i <= 0x10ffff) {
a.add(0xd800 + ((((i - 0x10000) >> 10) & 0x3ff)));
a.add(0xdc00 + (i & 0x3ff));
} else {
throw argumentErrorValue(i);
}
}
return _fromCharCodeApply(a);
}
@notNull
static String stringFromCharCodes(JSArray<int> charCodes) {
for (@nullCheck var i in charCodes) {
if (i < 0) throw argumentErrorValue(i);
if (i > 0xffff) return stringFromCodePoints(charCodes);
}
return _fromCharCodeApply(charCodes);
}
// [start] and [end] are validated.
@notNull
static String stringFromNativeUint8List(
NativeUint8List charCodes, @nullCheck int start, @nullCheck int end) {
const kMaxApply = 500;
if (end <= kMaxApply && start == 0 && end == charCodes.length) {
return JS<String>('!', r'String.fromCharCode.apply(null, #)', charCodes);
}
String result = '';
for (int i = start; i < end; i += kMaxApply) {
int chunkEnd = (i + kMaxApply < end) ? i + kMaxApply : end;
result = JS(
'String',
r'# + String.fromCharCode.apply(null, #.subarray(#, #))',
result,
charCodes,
i,
chunkEnd);
}
return result;
}
@notNull
static String stringFromCharCode(@nullCheck int charCode) {
if (0 <= charCode) {
if (charCode <= 0xffff) {
return JS<String>('!', 'String.fromCharCode(#)', charCode);
}
if (charCode <= 0x10ffff) {
var bits = charCode - 0x10000;
var low = 0xDC00 | (bits & 0x3ff);
var high = 0xD800 | (bits >> 10);
return JS<String>('!', 'String.fromCharCode(#, #)', high, low);
}
}
throw RangeError.range(charCode, 0, 0x10ffff);
}
static String flattenString(String str) {
return JS<String>('!', "#.charCodeAt(0) == 0 ? # : #", str, str, str);
}
static String getTimeZoneName(DateTime receiver) {
// Firefox and Chrome emit the timezone in parenthesis.
// Example: "Wed May 16 2012 21:13:00 GMT+0200 (CEST)".
// We extract this name using a regexp.
var d = lazyAsJsDate(receiver);
// In this method all calls to `exec()` include a single capture group and
// it is only read if there is a match so a value will be present. To avoid
// extra null checks or casts from dynamic we type the return type of
// `exec()` to always contain non-nullable Strings.
var match = JS<List<String>?>('', r'/\((.*)\)/.exec(#.toString())', d);
if (match != null) return match[1];
// Internet Explorer 10+ emits the zone name without parenthesis:
// Example: Thu Oct 31 14:07:44 PDT 2013
match = JS<List<String>?>(
'',
// Thu followed by a space.
r'/^[A-Z,a-z]{3}\s'
// Oct 31 followed by space.
r'[A-Z,a-z]{3}\s\d+\s'
// Time followed by a space.
r'\d{2}:\d{2}:\d{2}\s'
// The time zone name followed by a space.
r'([A-Z]{3,5})\s'
// The year.
r'\d{4}$/'
'.exec(#.toString())',
d);
if (match != null) return match[1];
// IE 9 and Opera don't provide the zone name. We fall back to emitting the
// UTC/GMT offset.
// Example (IE9): Wed Nov 20 09:51:00 UTC+0100 2013
// (Opera): Wed Nov 20 2013 11:03:38 GMT+0100
match =
JS<List<String>?>('', r'/(?:GMT|UTC)[+-]\d{4}/.exec(#.toString())', d);
if (match != null) return match[0];
return "";
}
static int getTimeZoneOffsetInMinutes(DateTime receiver) {
// Note that JS and Dart disagree on the sign of the offset.
return -JS<int>('!', r'#.getTimezoneOffset()', lazyAsJsDate(receiver));
}
static int? valueFromDecomposedDate(
@nullCheck int years,
@nullCheck int month,
@nullCheck int day,
@nullCheck int hours,
@nullCheck int minutes,
@nullCheck int seconds,
@nullCheck int milliseconds,
@nullCheck bool isUtc) {
final int MAX_MILLISECONDS_SINCE_EPOCH = 8640000000000000;
var jsMonth = month - 1;
// The JavaScript Date constructor 'corrects' year NN to 19NN. Sidestep that
// correction by adjusting years out of that range and compensating with an
// adjustment of months. This hack should not be sensitive to leap years but
// use 400 just in case.
if (0 <= years && years < 100) {
years += 400;
jsMonth -= 400 * 12;
}
int value;
if (isUtc) {
value = JS<int>('!', r'Date.UTC(#, #, #, #, #, #, #)', years, jsMonth,
day, hours, minutes, seconds, milliseconds);
} else {
value = JS<int>('!', r'new Date(#, #, #, #, #, #, #).valueOf()', years,
jsMonth, day, hours, minutes, seconds, milliseconds);
}
if (value.isNaN ||
value < -MAX_MILLISECONDS_SINCE_EPOCH ||
value > MAX_MILLISECONDS_SINCE_EPOCH) {
return null;
}
if (years <= 0 || years < 100) return patchUpY2K(value, years, isUtc);
return value;
}
static int patchUpY2K(value, years, isUtc) {
var date = JS<int>('!', r'new Date(#)', value);
if (isUtc) {
JS<int>('!', r'#.setUTCFullYear(#)', date, years);
} else {
JS<int>('!', r'#.setFullYear(#)', date, years);
}
return JS<int>('!', r'#.valueOf()', date);
}
// Lazily keep a JS Date stored in the JS object.
static lazyAsJsDate(DateTime receiver) {
if (JS<bool>('!', r'#.date === (void 0)', receiver)) {
JS('void', r'#.date = new Date(#)', receiver,
receiver.millisecondsSinceEpoch);
}
return JS('var', r'#.date', receiver);
}
// The getters for date and time parts below add a positive integer to ensure
// that the result is really an integer, because the JavaScript implementation
// may return -0.0 instead of 0.
static int getYear(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCFullYear() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getFullYear() + 0)', lazyAsJsDate(receiver));
}
static int getMonth(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'#.getUTCMonth() + 1', lazyAsJsDate(receiver))
: JS<int>('!', r'#.getMonth() + 1', lazyAsJsDate(receiver));
}
static int getDay(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCDate() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getDate() + 0)', lazyAsJsDate(receiver));
}
static int getHours(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCHours() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getHours() + 0)', lazyAsJsDate(receiver));
}
static int getMinutes(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCMinutes() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getMinutes() + 0)', lazyAsJsDate(receiver));
}
static int getSeconds(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCSeconds() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getSeconds() + 0)', lazyAsJsDate(receiver));
}
static int getMilliseconds(DateTime receiver) {
return (receiver.isUtc)
? JS<int>('!', r'(#.getUTCMilliseconds() + 0)', lazyAsJsDate(receiver))
: JS<int>('!', r'(#.getMilliseconds() + 0)', lazyAsJsDate(receiver));
}
static int getWeekday(DateTime receiver) {
int weekday = (receiver.isUtc)
? JS<int>('!', r'#.getUTCDay() + 0', lazyAsJsDate(receiver))
: JS<int>('!', r'#.getDay() + 0', lazyAsJsDate(receiver));
// Adjust by one because JS weeks start on Sunday.
return (weekday + 6) % 7 + 1;
}
static num valueFromDateString(str) {
if (str is! String) throw argumentErrorValue(str);
num value = JS('!', r'Date.parse(#)', str);
if (value.isNaN) throw argumentErrorValue(str);
return value;
}
static Object? getProperty(Object? object, Object key) {
if (object == null || object is bool || object is num || object is String) {
throw argumentErrorValue(object);
}
return JS('var', '#[#]', object, key);
}
static void setProperty(object, key, value) {
if (object == null || object is bool || object is num || object is String) {
throw argumentErrorValue(object);
}
JS('void', '#[#] = #', object, key, value);
}
}
/**
* Diagnoses an indexing error. Returns the ArgumentError or RangeError that
* describes the problem.
*/
Error diagnoseIndexError(indexable, int index) {
int length = indexable.length;
// The following returns the same error that would be thrown by calling
// [RangeError.checkValidIndex] with no optional parameters provided.
if (index < 0 || index >= length) {
return RangeError.index(index, indexable, 'index', null, length);
}
// The above should always match, but if it does not, use the following.
return RangeError.value(index, 'index');
}
/**
* Diagnoses a range error. Returns the ArgumentError or RangeError that
* describes the problem.
*/
Error diagnoseRangeError(int? start, int? end, int length) {
if (start == null) {
return ArgumentError.value(start, 'start');
}
if (start < 0 || start > length) {
return RangeError.range(start, 0, length, 'start');
}
if (end != null) {
if (end < start || end > length) {
return RangeError.range(end, start, length, 'end');
}
}
// The above should always match, but if it does not, use the following.
return ArgumentError.value(end, "end");
}
@notNull
int stringLastIndexOfUnchecked(receiver, element, start) =>
JS<int>('!', r'#.lastIndexOf(#, #)', receiver, element, start);
/// 'factory' for constructing ArgumentError.value to keep the call sites small.
ArgumentError argumentErrorValue(object) {
return ArgumentError.value(object);
}
void throwArgumentErrorValue(value) {
throw argumentErrorValue(value);
}
checkInt(value) {
if (value is! int) throw argumentErrorValue(value);
return value;
}
throwRuntimeError(message) {
throw RuntimeError(message);
}
throwAbstractClassInstantiationError(className) {
throw AbstractClassInstantiationError(className);
}
throwConcurrentModificationError(collection) {
throw ConcurrentModificationError(collection);
}
class JsNoSuchMethodError extends Error implements NoSuchMethodError {
final String? _message;
final String? _method;
final String? _receiver;
JsNoSuchMethodError(this._message, match)
: _method = match == null ? null : JS('String|Null', '#.method', match),
_receiver =
match == null ? null : JS('String|Null', '#.receiver', match);
String toString() {
if (_method == null) return 'NoSuchMethodError: $_message';
if (_receiver == null) {
return "NoSuchMethodError: method not found: '$_method' ($_message)";
}
return "NoSuchMethodError: "
"method not found: '$_method' on '$_receiver' ($_message)";
}
}
class UnknownJsTypeError extends Error {
final String _message;
UnknownJsTypeError(this._message);
String toString() => _message.isEmpty ? 'Error' : 'Error: $_message';
}
/**
* Called by generated code to build a map literal. [keyValuePairs] is
* a list of key, value, key, value, ..., etc.
*/
fillLiteralMap(keyValuePairs, Map result) {
// TODO(johnniwinther): Use JSArray to optimize this code instead of calling
// [getLength] and [getIndex].
int index = 0;
int length = getLength(keyValuePairs);
while (index < length) {
var key = getIndex(keyValuePairs, index++);
var value = getIndex(keyValuePairs, index++);
result[key] = value;
}
return result;
}
bool jsHasOwnProperty(jsObject, String property) {
return JS<bool>('!', r'#.hasOwnProperty(#)', jsObject, property);
}
jsPropertyAccess(jsObject, String property) {
return JS('var', r'#[#]', jsObject, property);
}
/**
* Called at the end of unaborted switch cases to get the singleton
* FallThroughError exception that will be thrown.
*/
getFallThroughError() => FallThroughErrorImplementation();
/**
* A metadata annotation describing the types instantiated by a native element.
*
* The annotation is valid on a native method and a field of a native class.
*
* By default, a field of a native class is seen as an instantiation point for
* all native classes that are a subtype of the field's type, and a native
* method is seen as an instantiation point fo all native classes that are a
* subtype of the method's return type, or the argument types of the declared
* type of the method's callback parameter.
*
* An @[Creates] annotation overrides the default set of instantiated types. If
* one or more @[Creates] annotations are present, the type of the native
* element is ignored, and the union of @[Creates] annotations is used instead.
* The names in the strings are resolved and the program will fail to compile
* with dart2js if they do not name types.
*
* The argument to [Creates] is a string. The string is parsed as the names of
* one or more types, separated by vertical bars `|`. There are some special
* names:
*
* * `=Object`. This means 'exactly Object', which is a plain JavaScript object
* with properties and none of the subtypes of Object.
*
* Example: we may know that a method always returns a specific implementation:
*
* @Creates('_NodeList')
* List<Node> getElementsByTagName(String tag) native;
*
* Useful trick: A method can be marked as not instantiating any native classes
* with the annotation `@Creates('Null')`. This is useful for fields on native
* classes that are used only in Dart code.
*
* @Creates('Null')
* var _cachedFoo;
*/
class Creates {
final String types;
const Creates(this.types);
}
/**
* A metadata annotation describing the types returned or yielded by a native
* element.
*
* The annotation is valid on a native method and a field of a native class.
*
* By default, a native method or field is seen as returning or yielding all
* subtypes if the method return type or field type. This annotation allows a
* more precise set of types to be specified.
*
* See [Creates] for the syntax of the argument.
*
* Example: IndexedDB keys are numbers, strings and JavaScript Arrays of keys.
*
* @Returns('String|num|JSExtendableArray')
* dynamic key;
*
* // Equivalent:
* @Returns('String') @Returns('num') @Returns('JSExtendableArray')
* dynamic key;
*/
class Returns {
final String types;
const Returns(this.types);
}
/**
* A metadata annotation placed on native methods and fields of native classes
* to specify the JavaScript name.
*
* This example declares a Dart field + getter + setter called `$dom_title` that
* corresponds to the JavaScript property `title`.
*
* class Document native "*Foo" {
* @JSName('title')
* String $dom_title;
* }
*/
class JSName {
final String name;
const JSName(this.name);
}
/**
* Special interface recognized by the compiler and implemented by DOM
* objects that support integer indexing. This interface is not
* visible to anyone, and is only injected into special libraries.
*/
abstract class JavaScriptIndexingBehavior<E> extends JSMutableIndexable<E> {}
/// Thrown by type assertions that fail.
class TypeErrorImpl extends Error implements TypeError, CastError {
final String _message;
TypeErrorImpl(this._message);
String toString() => _message;
}
/// Thrown by the 'as' operator if the cast isn't valid.
class CastErrorImpl extends Error implements CastError, TypeError {
final String _message;
CastErrorImpl(this._message);
String toString() => _message;
}
class FallThroughErrorImplementation extends FallThroughError {
String toString() => "Switch case fall-through.";
}
/**
* Error thrown when a runtime error occurs.
*/
class RuntimeError extends Error {
final message;
RuntimeError(this.message);
String toString() => "RuntimeError: $message";
}
class DeferredNotLoadedError extends Error implements NoSuchMethodError {
String enclosingLibrary;
String importPrefix;
DeferredNotLoadedError(this.enclosingLibrary, this.importPrefix);
String toString() {
return 'Deferred import $importPrefix (from $enclosingLibrary) was not loaded.';
}
}
/// Error thrown by DDC when an `assert()` fails (with or without a message).
class AssertionErrorImpl extends AssertionError {
final String? _fileUri;
final int? _line;
final int? _column;
final String? _conditionSource;
AssertionErrorImpl(Object? message,
[this._fileUri, this._line, this._column, this._conditionSource])
: super(message);
String toString() {
var failureMessage = "";
if (_fileUri != null &&
_line != null &&
_column != null &&
_conditionSource != null) {
failureMessage += "$_fileUri:${_line}:${_column}\n$_conditionSource\n";
}
failureMessage +=
message != null ? Error.safeToString(message) : "is not true";
return "Assertion failed: $failureMessage";
}
}
/**
* Creates a random number with 64 bits of randomness.
*
* This will be truncated to the 53 bits available in a double.
*/
int random64() {
// TODO(lrn): Use a secure random source.
int int32a = JS("int", "(Math.random() * 0x100000000) >>> 0");
int int32b = JS("int", "(Math.random() * 0x100000000) >>> 0");
return int32a + int32b * 0x100000000;
}
class BooleanConversionAssertionError extends AssertionError {
toString() => 'Failed assertion: boolean expression must not be null';
}
// Hook to register new global object. This is invoked from dart:html
// whenever a new window is accessed for the first time.
void registerGlobalObject(object) {
try {
if (dart.polyfill(object)) {
dart.applyAllExtensions(object);
}
} catch (e) {
// This may fail due to cross-origin errors. In that case, we shouldn't
// need to polyfill as we can't get objects from that frame.
// TODO(vsm): Detect this more robustly - ideally before we try to polyfill.
}
}
/// Expose browser JS classes.
void applyExtension(name, nativeObject) {
dart.applyExtension(name, nativeObject);
}
/// Hook to apply extensions on native JS classes defined in a native unit test.
void applyTestExtensions(List<String> names) {
names.forEach(dart.applyExtensionForTesting);
}
/// Used internally by DDC to map ES6 symbols to Dart.
class PrivateSymbol implements Symbol {
// TODO(jmesserly): could also get this off the native symbol instead of
// storing it. Mirrors already does this conversion.
final String _name;
final Object _nativeSymbol;
const PrivateSymbol(this._name, this._nativeSymbol);
static String getName(Symbol symbol) => (symbol as PrivateSymbol)._name;
static Object? getNativeSymbol(Symbol symbol) {
if (symbol is PrivateSymbol) return symbol._nativeSymbol;
return null;
}
bool operator ==(other) =>
other is PrivateSymbol &&
_name == other._name &&
identical(_nativeSymbol, other._nativeSymbol);
get hashCode => _name.hashCode;
// TODO(jmesserly): is this equivalent to _nativeSymbol toString?
toString() => 'Symbol("$_name")';
}
/// Asserts that if [value] is a function, it is a JavaScript function or has
/// been wrapped by [allowInterop].
///
/// This function does not recurse if [value] is a collection.
void assertInterop(Object? value) {
if (value is Function) dart.assertInterop(value);
}
/// Like [assertInterop], except iterates over a list of arguments
/// non-recursively.
void assertInteropArgs(List<Object?> args) => args.forEach(assertInterop);