blob: 058668465c17f264d4ba6389cf14cbb4b5105162 [file] [log] [blame] [edit]
// Copyright (c) 2023, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
/// Interoperability, "interop" for short, with JavaScript and browser APIs.
///
/// JavaScript interop allows a Dart program to interact with a JavaScript
/// runtime. This can, for example, be to access JavaScript declarations and
/// interact with JavaScript values, or to adapt Dart values so that they can be
/// passed to and used by JavaScript code.
///
/// This JavaScript interop library works by introducing an abstraction over
/// JavaScript values, a Dart type hierarchy ("JS types") which mirrors known
/// JavaScript types, and a framework for introducing new Dart types that bind
/// Dart type declarations to JavaScript values and external member declarations
/// to JavaScript APIs.
///
/// This abstraction allows the same interop API to be used both when the Dart
/// code is compiled to JavaScript and when compiled to Wasm.
///
/// See https://dart.dev/interop/js-interop for more details on usage, types,
/// and previous JavaScript interop.
///
/// > [!NOTE]
/// > The types defined in this library only provide static guarantees. The
/// > runtime types differ based on the backend, so it is important to rely on
/// > static functionality like the conversion functions. Similarly, don't rely
/// > on `is` checks that involve JS types or JS-typed values. Furthermore,
/// > `identical` may also return different results for the same JS value
/// > depending on the compiler. Use `==` to check for equality of two JS-typed
/// > values instead, but do not check for equality between a Dart value and a
/// > JS-typed value.
///
/// {@category Web}
library;
import 'dart:_internal' show Since;
import 'dart:_js_annotations' show JSExport;
import 'dart:_js_types';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';
// To support an easier transition, we allow users to use `@staticInterop`
// classes - with or without the `@anonymous` annotation.
export 'dart:_js_annotations' show staticInterop, anonymous, JSExport;
export 'dart:js_util' show NullRejectionException;
/// An annotation on a JavaScript interop declaration.
///
/// This annotation defines a given library, top-level external declaration, or
/// extension type as a JavaScript interop declaration.
///
/// Specifying [name] customizes the JavaScript name to use, which can be used
/// in the following scenarios:
///
/// - Adding a JavaScript prefix to all the external top-level declarations,
/// static members, and constructors of a library by parameterizing the
/// annotation on the library with [name].
/// - Specifying the JavaScript class to use for external static members and
/// constructors of an interop extension type by parameterizing the annotation
/// on the interop extension type with [name].
/// - Renaming external declarations by parameterizing the annotation on the
/// member with [name].
///
/// In the case where [name] is not specified, is `null`, or is empty, the Dart
/// name of the extension type or external declaration is used as the default.
///
/// See https://dart.dev/interop/js-interop/usage#js for more details on how to
/// use this annotation.
///
/// > [!NOTE]
/// > `package:js` exports an `@JS` annotation as well. Unlike that annotation,
/// > this annotation applies to extension types, and will result in more
/// > type-checking for external top-level declarations.
class JS {
final String? name;
const JS([this.name]);
}
/// A non-nullish JavaScript value.
///
/// A [JSAny] can be any JavaScript value except JavaScript `null` and
/// `undefined`. JavaScript `null` and `undefined` are instead converted to Dart
/// `null` by the compiler. Therefore, <code>[JSAny]?</code> is the top type of
/// the type hierarchy as it includes nullish JavaScript values as well.
extension type JSAny._(JSAnyRepType _jsAny) implements Object {}
/// A JavaScript `Object`.
///
/// [JSObject] is the supertype of all JavaScript objects, but not other JS
/// types, like primitives. See https://dart.dev/interop/js-interop for more
/// details on how to use JavaScript interop.
///
/// When declaring interop extension types, [JSObject] is usually the type you
/// will use as the representation type.
@JS('Object')
extension type JSObject._(JSObjectRepType _jsObject) implements JSAny {
/// Creates a [JSObject] from an object provided by an earlier interop
/// library.
///
/// Accepts, for example, the types created using `package:js` or `dart:html`.
///
/// This constructor is intended to allow users to avoid having to cast to and
/// from [JSObject].
JSObject.fromInteropObject(Object interopObject)
: _jsObject = interopObject as JSObjectRepType;
/// Creates a new empty JavaScript object.
///
/// The object is created using the JavaScript object initializer syntax
/// (`{}`), and this constructor is more efficient than `{}.jsify()`.
JSObject() : _jsObject = _createObjectLiteral();
}
// TODO(srujzs): Move this member to `JSObject` once we can patch extension type
// members.
external JSObjectRepType _createObjectLiteral();
/// A JavaScript [`Function`](https://tc39.es/ecma262/#sec-function-objects)
/// value.
@JS('Function')
extension type JSFunction._(JSFunctionRepType _jsFunction)
implements JSObject {}
/// A JavaScript callable function created from 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
)
implements JSFunction {}
/// A JavaScript [`Array`](https://tc39.es/ecma262/#sec-array-objects).
///
/// Because [JSArray] is an extension type, [T] is only a static guarantee and
/// the array does not necessarily only contain [T] elements. For example:
///
/// ```dart
/// @JS()
/// external JSArray<JSNumber> get array;
/// ```
///
/// `array` is not actually checked to ensure it contains instances of
/// [JSNumber] when called.
///
/// [T] may introduce additional checking elsewhere, however. When accessing
/// elements of [JSArray] with type [T], there is a check to ensure the element
/// is a [T] to ensure soundness. Similarly, when converting to a [List<T>],
/// casts may be introduced to ensure that it is indeed a [List<T>].
@JS('Array')
extension type JSArray<T extends JSAny?>._(JSArrayRepType _jsArray)
implements JSObject {
/// Creates an empty JavaScript `Array`.
///
/// Equivalent to `new Array()` and more efficient than `[].jsify()`.
external JSArray();
/// Creates a JavaScript `Array` of size [length] with no elements.
external JSArray.withLength(int length);
/// Creates a new, shallow-copied JavaScript `Array` instance from a
/// JavaScript iterable or array-like object.
@Since('3.6')
external static JSArray<T> from<T extends JSAny>(JSObject arrayLike);
/// The length in elements of this `Array`.
@Since('3.6')
external int get length;
/// Sets the length in elements of this `Array`.
///
/// Setting it smaller than the current length truncates this `Array`, and
/// setting it larger adds empty slots, which requires [T] to be nullable.
@Since('3.6')
external set length(int newLength);
/// The value at [position] in this `Array`.
@Since('3.6')
external T operator [](int position);
/// Sets the [value] at [position] in this `Array`.
@Since('3.6')
external void operator []=(int position, T value);
/// Adds [value] to the end of this `Array`, extending the length by one.
// This maps to `List.add` to avoid accidental usage of
// `JSAnyOperatorExtension.add` when migrating `List`s to `JSArray`s. See
// https://github.com/dart-lang/sdk/issues/59830.
@Since('3.10')
@JS('push')
external void add(T value);
}
/// A JavaScript `Promise` or a promise-like object.
///
/// Because [JSPromise] is an extension type, [T] is only a static guarantee and
/// the [JSPromise] may not actually resolve to a [T].
///
/// Also like with [JSArray], [T] may introduce additional checking elsewhere.
/// When converted to a [Future<T>], there is a cast to ensure that the [Future]
/// actually resolves to a [T] to ensure soundness.
@JS('Promise')
extension type JSPromise<T extends JSAny?>._(JSPromiseRepType _jsPromise)
implements JSObject {
external JSPromise(JSFunction executor);
}
/// A Dart object that is wrapped with a JavaScript object so that it can be
/// passed to JavaScript safely.
///
/// Unlike [ExternalDartReference], this can be used as a JS type and is a
/// subtype of [JSAny]. Users can also declare interop types using this as the
/// representation type or declare interop members on this type.
///
/// Use this interface when you want to pass Dart objects within the same
/// runtime through JavaScript. There are no usable members in the resulting
/// [JSBoxedDartObject].
///
/// See [ObjectToJSBoxedDartObject.toJSBox] to wrap an arbitrary [Object].
@JS('Object')
extension type JSBoxedDartObject._(JSBoxedDartObjectRepType _jsBoxedDartObject)
implements JSObject {}
/// A JavaScript `ArrayBuffer`.
@JS('ArrayBuffer')
extension type JSArrayBuffer._(JSArrayBufferRepType _jsArrayBuffer)
implements JSObject {
/// Creates a JavaScript `ArrayBuffer` of size [length] using an optional
/// [options] JavaScript object that sets the `maxByteLength`.
@Since('3.6')
external JSArrayBuffer(int length, [JSObject options]);
}
/// A JavaScript `DataView`.
@JS('DataView')
extension type JSDataView._(JSDataViewRepType _jsDataView) implements JSObject {
/// Creates a JavaScript `DataView` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [byteLength].
@Since('3.6')
external JSDataView(JSArrayBuffer buffer, [int byteOffset, int byteLength]);
}
/// Abstract supertype of all JavaScript typed arrays.
extension type JSTypedArray._(JSTypedArrayRepType _jsTypedArray)
implements JSObject {}
/// A JavaScript `Int8Array`.
@JS('Int8Array')
extension type JSInt8Array._(JSInt8ArrayRepType _jsInt8Array)
implements JSTypedArray {
/// Creates a JavaScript `Int8Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Int8Array`.
@Since('3.6')
external JSInt8Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Int8Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSInt8Array.withLength(int length);
}
/// A JavaScript `Uint8Array`.
@JS('Uint8Array')
extension type JSUint8Array._(JSUint8ArrayRepType _jsUint8Array)
implements JSTypedArray {
/// Creates a JavaScript `Uint8Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Uint8Array`.
@Since('3.6')
external JSUint8Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Uint8Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSUint8Array.withLength(int length);
}
/// A JavaScript `Uint8ClampedArray`.
@JS('Uint8ClampedArray')
extension type JSUint8ClampedArray._(
JSUint8ClampedArrayRepType _jsUint8ClampedArray
)
implements JSTypedArray {
/// Creates a JavaScript `Uint8ClampedArray` with [buffer] as its backing
/// storage, offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Uint8ClampedArray`.
@Since('3.6')
external JSUint8ClampedArray([
JSArrayBuffer buffer,
int byteOffset,
int length,
]);
/// Creates a JavaScript `Uint8ClampedArray` of size [length] whose elements
/// are initialized to 0.
@Since('3.6')
external JSUint8ClampedArray.withLength(int length);
}
/// A JavaScript `Int16Array`.
@JS('Int16Array')
extension type JSInt16Array._(JSInt16ArrayRepType _jsInt16Array)
implements JSTypedArray {
/// Creates a JavaScript `Int16Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Int16Array`.
@Since('3.6')
external JSInt16Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Int16Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSInt16Array.withLength(int length);
}
/// A JavaScript `Uint16Array`.
@JS('Uint16Array')
extension type JSUint16Array._(JSUint16ArrayRepType _jsUint16Array)
implements JSTypedArray {
/// Creates a JavaScript `Uint16Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Uint16Array`.
@Since('3.6')
external JSUint16Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Uint16Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSUint16Array.withLength(int length);
}
/// A JavaScript `Int32Array`.
@JS('Int32Array')
extension type JSInt32Array._(JSInt32ArrayRepType _jsInt32Array)
implements JSTypedArray {
/// Creates a JavaScript `Int32Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Int32Array`.
@Since('3.6')
external JSInt32Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Int32Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSInt32Array.withLength(int length);
}
/// A JavaScript `Uint32Array`.
@JS('Uint32Array')
extension type JSUint32Array._(JSUint32ArrayRepType _jsUint32Array)
implements JSTypedArray {
/// Creates a JavaScript `Uint32Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Uint32Array`.
@Since('3.6')
external JSUint32Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Uint32Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSUint32Array.withLength(int length);
}
/// A JavaScript `Float32Array`.
@JS('Float32Array')
extension type JSFloat32Array._(JSFloat32ArrayRepType _jsFloat32Array)
implements JSTypedArray {
/// Creates a JavaScript `Float32Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Float32Array`.
@Since('3.6')
external JSFloat32Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Float32Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSFloat32Array.withLength(int length);
}
/// A JavaScript `Float64Array`.
@JS('Float64Array')
extension type JSFloat64Array._(JSFloat64ArrayRepType _jsFloat64Array)
implements JSTypedArray {
/// Creates a JavaScript `Float64Array` with [buffer] as its backing storage,
/// offset by [byteOffset] bytes, of size [length].
///
/// If no [buffer] is provided, creates an empty `Float64Array`.
@Since('3.6')
external JSFloat64Array([JSArrayBuffer buffer, int byteOffset, int length]);
/// Creates a JavaScript `Float64Array` of size [length] whose elements are
/// initialized to 0.
@Since('3.6')
external JSFloat64Array.withLength(int length);
}
// The various JavaScript primitive types. Crucially, unlike the Dart type
// hierarchy, none of these types are subtypes of [JSObject]. They are just
// subtypes of [JSAny].
/// A JavaScript number.
extension type JSNumber._(JSNumberRepType _jsNumber) implements JSAny {}
/// A JavaScript boolean.
extension type JSBoolean._(JSBooleanRepType _jsBoolean) implements JSAny {}
/// A JavaScript string.
extension type JSString._(JSStringRepType _jsString) implements JSAny {}
/// A JavaScript `Symbol`.
extension type JSSymbol._(JSSymbolRepType _jsSymbol) implements JSAny {}
/// A JavaScript `BigInt`.
extension type JSBigInt._(JSBigIntRepType _jsBigInt) implements JSAny {}
/// An opaque reference to a Dart object that can be passed to JavaScript.
///
/// The reference representation depends on the underlying platform. When
/// compiling to JavaScript, a Dart object is a JavaScript object, and can be
/// used directly without any conversions. When compiling to Wasm, an internal
/// Wasm function is used to convert the Dart object to an opaque JavaScript
/// value, which can later be converted back using another internal function.
/// The underlying representation type is nullable, meaning a non-nullable
/// [ExternalDartReference] may be `null`.
///
/// This interface is a faster alternative to [JSBoxedDartObject] by not
/// wrapping the Dart object with a JavaScript object. However, unlike
/// [JSBoxedDartObject], this value belongs to the Dart runtime, and therefore
/// can not be used as a JS type. This means users cannot declare interop types
/// using this as the representation type or declare interop members on this
/// type. This type is also not a subtype of [JSAny]. This type can only be used
/// as parameter and return types of external JavaScript interop members or
/// callbacks. Use [JSBoxedDartObject] to avoid those limitations.
///
/// Besides these differences, [ExternalDartReference] operates functionally the
/// same as [JSBoxedDartObject]. Use it to pass Dart objects within the same
/// runtime through JavaScript. There are no usable members in the resulting
/// [ExternalDartReference].
///
/// See [ObjectToExternalDartReference.toExternalReference] to allow an
/// arbitrary value of type [T] to be passed to JavaScript.
extension type ExternalDartReference<T extends Object?>._(
ExternalDartReferenceRepType<T> _externalDartReference
) {}
/// JS type equivalent for `undefined` for interop member return types.
///
/// Prefer using `void` instead of this.
// TODO(srujzs): Mark this as deprecated. There are no performance costs from
// using `void`, and we'll likely provide a different way to box `undefined`.
typedef JSVoid = JSVoidRepType;
/// Helper methods to determine if a value is JavaScript `undefined` or `null`.
///
/// > [!NOTE]
/// > The members within these extensions may throw depending on the platform.
/// > Do not rely on them to be platform-consistent.
///
/// JavaScript `undefined` and JavaScript `null` are internalized differently
/// based on the backend. When compiling to JavaScript, Dart `null` can actually
/// be JavaScript `undefined` or JavaScript `null`. When compiling to Wasm,
/// that's not the case: there's only one Wasm value `null` can be. Therefore,
/// when an interop API returns JavaScript `null` or JavaScript `undefined`,
/// they are both converted to Dart `null` when compiling to Wasm, and when you
/// pass a Dart `null` to an interop API, it is called with JavaScript `null`.
/// When compiling to JavaScript, Dart `null` retains its original JavaScript
/// value. Avoid writing code where this distinction between `null` and
/// `undefined` matters.
// TODO(srujzs): Investigate what it takes to allow users to distinguish between
// the two "nullish" values. An annotation-based model where users annotate
// interop APIs to internalize `undefined` differently seems promising, but does
// not handle some cases like converting a `JSArray` with `undefined`s in it to
// `List<JSAny?>`. In this case, the implementation of the list wrapper needs to
// make the decision, not the user.
extension NullableUndefineableJSAnyExtension on JSAny? {
/// Whether this value corresponds to JavaScript `undefined`.
///
/// > [!NOTE]
/// > Currently, there is no way to distinguish between JavaScript `undefined`
/// > and JavaScript `null` when compiling to Wasm. Therefore, this getter
/// > should only be used for code that compiles to JavaScript and will throw
/// > when compiling to Wasm.
external bool get isUndefined;
/// Whether this value corresponds to JavaScript `null`.
///
/// > [!NOTE]
/// > Currently, there is no way to distinguish between JavaScript `undefined`
/// > and JavaScript `null` when compiling to Wasm. Therefore, this getter
/// > should only be used for code that compiles to JavaScript and will throw
/// > when compiling to Wasm.
external bool get isNull;
bool get isUndefinedOrNull => this == null;
bool get isDefinedAndNotNull => !isUndefinedOrNull;
}
/// Common utility functions that are useful for any JavaScript value.
extension JSAnyUtilityExtension on JSAny? {
/// Whether the result of `typeof` on this <code>[JSAny]?</code> is
/// [typeString].
external bool typeofEquals(String typeString);
/// Whether this <code>[JSAny]?</code> is an `instanceof` [constructor].
external bool instanceof(JSFunction constructor);
/// Whether this <code>[JSAny]?</code> is an `instanceof` the constructor that
/// is defined by [constructorName], which is looked up in the
/// [globalContext].
///
/// If [constructorName] contains '.'s, the name is split into several parts
/// in order to get the constructor. For example, `library1.JSClass` would
/// involve fetching `library1` off of the [globalContext], and then fetching
/// `JSClass` off of `library1` to get the constructor.
///
/// If [constructorName] is empty or any of the parts or the constructor don't
/// exist, returns false.
bool instanceOfString(String constructorName) {
if (constructorName.isEmpty) return false;
final parts = constructorName.split('.');
JSObject? constructor = globalContext;
for (final part in parts) {
constructor = constructor?[part] as JSObject?;
if (constructor == null) return false;
}
return instanceof(constructor as JSFunction);
}
/// Whether this <code>[JSAny]?</code> is an instance of the JavaScript type
/// that is declared by [T].
///
/// Since the type-check this function emits is determined at compile-time,
/// [T] needs to be an interop extension type that can also be determined at
/// compile-time. In particular, `isA` can't be provided a generic type
/// variable as a type argument.
///
/// This method uses a combination of `null`, `typeof`, and `instanceof`
/// checks in order to do this check. Use this instead of `is` checks.
///
/// If [T] is a primitive JS type like [JSString], this uses a `typeof` check
/// that corresponds to that primitive type like `typeofEquals('string')`.
///
/// If [T] is a non-primitive JS type like [JSArray] or an interop extension
/// type on one, this uses an `instanceof` check using the name or the
/// <code>@[JS]</code> rename of the given type like
/// `instanceOfString('Array')`. Note that if you rename the library using the
/// <code>@[JS]</code> annotation, this uses the rename in the `instanceof`
/// check like `instanceOfString('library1.JSClass')`.
///
/// To determine the JavaScript constructor to use as the second operand in
/// the `instanceof` check, this function uses the JavaScript name associated
/// with the extension type, which is either the argument given to the
/// <code>@[JS]</code> annotation or the Dart declaration name. So, if you had
/// an interop extension type `JSClass` that wraps `JSArray` without a rename,
/// this does an `instanceOfString('JSClass')` check and not an
/// `instanceOfString('Array')` check.
///
/// There are two exceptions to this rule. The first exception is
/// `JSTypedArray`. As `TypedArray` does not exist as a property in
/// JavaScript, this does some prototype checking to make `isA<JSTypedArray>`
/// do the right thing. The other exception is `JSAny`. If you do a
/// `isA<JSAny>` check, it will only do a `null` check.
///
/// Using this method with a [T] that has an object literal constructor will
/// result in an error as you likely want to use [JSObject] instead.
///
/// Using this method with a [T] that wraps a primitive JS type will result in
/// an error telling you to use the primitive JS type instead.
@Since('3.4')
external bool isA<T extends JSAny?>();
/// Converts a JavaScript JSON-like value to the Dart equivalent if possible.
///
/// Effectively the inverse of [NullableObjectUtilExtension.jsify], [dartify]
/// takes a JavaScript JSON-like value and recursively converts it to a Dart
/// object, doing the following:
///
/// - If the value is a string, number, boolean, `null`, `undefined`,
/// `DataView` or a typed array, does the equivalent `toDart` operation if
/// it exists and returns the result.
/// - If the value is a simple JS object (the protoype is either `null` or JS
/// `Object`), creates and returns a `[Map]<Object?, Object?>` whose keys
/// are the recursively converted keys obtained from `Object.keys` and its
/// values are the associated values of the keys in the JS object.
/// - If the value is a JS `Array`, each item in it is recursively converted
/// and added to a new `[List]<Object?>`, which is then returned.
/// - Otherwise, the conversion is undefined.
///
/// If the value contains a cycle, the behavior is undefined.
///
/// > [!NOTE]
/// > Prefer using the specific conversion method like `toDart` if you know
/// > the JavaScript type as this method may perform many type-checks. You
/// > should generally call this method with values that only contain
/// > JSON-like values as the conversion may be platform- and
/// > compiler-specific otherwise.
// TODO(srujzs): We likely need stronger tests for this method to ensure
// consistency. We should also limit the accepted types in this API to avoid
// confusion. Once the conversion for unrelated types is consistent across all
// backends, we can update the documentation to say that the value is
// internalized instead of the conversion being undefined.
external Object? dartify();
}
/// Common utility functions for <code>[Object]?</code>s.
extension NullableObjectUtilExtension on Object? {
/// Converts a Dart JSON-like object to the JavaScript equivalent if possible.
///
/// Effectively the inverse of [JSAnyUtilityExtension.dartify], [jsify] takes
/// a Dart JSON-like object and recursively converts it to a JavaScript value,
/// doing the following:
///
/// - If the object is a JS value, returns the object.
/// - If the object is a Dart primitive type, `null`, or a `dart:typed_data`
/// type, does the equivalent `toJS` operation if it exists and returns the
/// result.
/// - If the object is a [Map], creates and returns a new JS object whose
/// properties and associated values are the recursively converted keys and
/// values of the [Map].
/// - If the object is an [Iterable], each item in it is recursively converted
/// and pushed into a new JS `Array` which is then returned.
/// - Otherwise, the conversion is undefined.
///
/// If the object contains a cycle, the behavior is undefined.
///
/// > [!NOTE]
/// > Prefer using the specific conversion method like `toJS` if you know the
/// > Dart type as this method may perform many type-checks. You should
/// > generally call this method with objects that only contain JSON-like
/// > values as the conversion may be platform- and compiler-specific
/// > otherwise.
// TODO(srujzs): We likely need stronger tests for this method to ensure
// consistency. We should also limit the accepted types in this API to avoid
// confusion. Once the conversion for unrelated types is consistent across all
// backends, we can update the documentation to say that the object is
// externalized instead of the conversion being undefined.
external JSAny? jsify();
}
/// Utility extensions for [JSFunction].
// TODO(srujzs): We may want to provide a syntax for users to avoid `.call` and
// directly call the function in JavaScript using `(...)`.
extension JSFunctionUtilExtension on JSFunction {
/// Call this [JSFunction] using the JavaScript `.call` syntax and returns the
/// result.
///
/// Takes at most 4 args for consistency with other APIs and relative brevity.
/// If more are needed, you can declare your own external member with the same
/// syntax.
// We rename this function since declaring a `call` member makes a class
// callable in Dart. This is convenient, but unlike Dart functions, JavaScript
// functions explicitly take a `this` argument (which users can provide `null`
// for in the case where the function doesn't need it), which may lead to
// confusion.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call
@JS('call')
external JSAny? callAsFunction([
JSAny? thisArg,
JSAny? arg1,
JSAny? arg2,
JSAny? arg3,
JSAny? arg4,
]);
}
// Extension members to support conversions between Dart types and JS types.
// Not all Dart types can be converted to JS types and vice versa.
// TODO(srujzs): Move some of these to the associated extension type.
/// Conversions from [JSExportedDartFunction] to [Function].
extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
/// The Dart [Function] that this [JSExportedDartFunction] wrapped.
///
/// Must be a function that was wrapped with
/// [FunctionToJSExportedDartFunction.toJS] or
/// [FunctionToJSExportedDartFunction.toJSCaptureThis].
external Function get toDart;
}
/// Conversions from [Function] to [JSExportedDartFunction].
extension FunctionToJSExportedDartFunction on Function {
/// A callable JavaScript function that wraps this [Function].
///
/// If the static type of the [Function] could not be determined or if
/// the static type uses types that are disallowed, the call will fail to
/// compile. See
/// https://dart.dev/interop/js-interop/js-types#requirements-on-external-declarations-and-function-tojs
/// for more details on what types are allowed.
///
/// The max number of arguments that are passed to this [Function] from the
/// wrapper JavaScript function is determined by this [Function]'s static
/// type. Any extra arguments passed to the JavaScript function after the max
/// number of arguments are discarded like they are with regular JavaScript
/// functions.
///
/// 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.
@Since('3.6')
external JSExportedDartFunction get toJSCaptureThis;
}
/// Conversions from [JSBoxedDartObject] to [Object].
extension JSBoxedDartObjectToObject on JSBoxedDartObject {
/// The Dart [Object] that this [JSBoxedDartObject] wrapped.
///
/// Throws an [Exception] if the Dart runtime was not the same as the one in
/// which the [Object] was wrapped or if this was not a wrapped Dart [Object].
external Object get toDart;
}
/// Conversions from [Object] to [JSBoxedDartObject].
extension ObjectToJSBoxedDartObject on Object {
/// A JavaScript object that wraps this [Object].
///
/// There are no usable members in the resulting [JSBoxedDartObject] and you
/// may get a new [JSBoxedDartObject] when calling [toJSBox] on the same Dart
/// [Object].
///
/// Throws an [Exception] if this [Object] is a JavaScript value.
///
/// Unlike [ObjectToExternalDartReference.toExternalReference], this returns a
/// JavaScript value. Therefore, the representation is guaranteed to be
/// consistent across all platforms and interop members can be declared on
/// [JSBoxedDartObject]s.
external JSBoxedDartObject get toJSBox;
}
/// Conversions from [ExternalDartReference] to the value of type [T].
extension ExternalDartReferenceToObject<T extends Object?>
on ExternalDartReference<T> {
/// The Dart value of type [T] that this [ExternalDartReference] refers to.
///
/// When compiling to JavaScript, a Dart object is a JavaScript object, and
/// therefore this directly returns the Dart object. When compiling to Wasm,
/// an internal Wasm function is used to convert the opaque JavaScript value
/// to the original Dart object.
external T get toDartObject;
}
/// Conversions from a value of type [T] to [ExternalDartReference].
extension ObjectToExternalDartReference<T extends Object?> on T {
/// An opaque reference to this value of type [T] which can be passed to
/// JavaScript.
///
/// When compiling to JavaScript, a Dart object is a JavaScript object, and
/// therefore this directly returns the Dart object. When compiling to Wasm,
/// an internal Wasm function is used to convert the Dart object to an opaque
/// JavaScript value. If this value is `null`, returns `null`.
///
/// A value of type [ExternalDartReference] should be treated as completely
/// opaque. It can only be passed around as-is or converted back using
/// [ExternalDartReferenceToObject.toDartObject].
///
/// When this getter is called multiple times on the same Dart object, the
/// underlying references in the resulting [ExternalDartReference]s are
/// guaranteed to be equal. Therefore, `==` will always return true between
/// such [ExternalDartReference]s. However, like JS types, `identical` between
/// such values may return different results depending on the compiler.
external ExternalDartReference<T> get toExternalReference;
}
/// Conversions from [JSPromise] to [Future].
extension JSPromiseToFuture<T extends JSAny?> on JSPromise<T> {
/// A [Future] that either completes with the result of the resolved
/// [JSPromise] or propagates the error that the [JSPromise] rejected with.
external Future<T> get toDart;
}
/// Conversions from [Future] to [JSPromise] where the [Future] returns a value.
extension FutureOfJSAnyToJSPromise<T extends JSAny?> on Future<T> {
/// A [JSPromise] that either resolves with the result of the completed
/// [Future] or rejects with an object that contains its error.
///
/// The rejected object contains the original error as a [JSBoxedDartObject]
/// in the property `error` and the original stack trace as a [String] in the
/// property `stack`.
JSPromise<T> get toJS {
return JSPromise<T>(
(JSFunction resolve, JSFunction reject) {
this.then(
(JSAny? value) {
resolve.callAsFunction(resolve, value);
return value;
},
onError: (Object error, StackTrace stackTrace) {
// TODO(srujzs): Can we do something better here? This is pretty much
// useless to the user unless they call a Dart callback that consumes
// this value and unboxes.
final errorConstructor = globalContext['Error'] as JSFunction;
final wrapper = errorConstructor.callAsConstructor<JSObject>(
"Dart exception thrown from converted Future. Use the properties "
"'error' to fetch the boxed error and 'stack' to recover "
"the stack trace."
.toJS,
);
wrapper['error'] = error.toJSBox;
wrapper['stack'] = stackTrace.toString().toJS;
reject.callAsFunction(reject, wrapper);
return wrapper;
},
);
}.toJS,
);
}
}
/// Conversions from [Future] to [JSPromise] where the [Future] does not return
/// a value.
extension FutureOfVoidToJSPromise on Future<void> {
/// A [JSPromise] that either resolves once this [Future] completes or rejects
/// with an object that contains its error.
///
/// The rejected object contains the original error as a [JSBoxedDartObject]
/// in the property `error` and the original stack trace as a [String] in the
/// property `stack`.
JSPromise get toJS {
return JSPromise(
(JSFunction resolve, JSFunction reject) {
this.then(
(_) => resolve.callAsFunction(resolve),
onError: (Object error, StackTrace stackTrace) {
// TODO(srujzs): Can we do something better here? This is pretty much
// useless to the user unless they call a Dart callback that consumes
// this value and unboxes.
final errorConstructor = globalContext['Error'] as JSFunction;
final wrapper = errorConstructor.callAsConstructor<JSObject>(
"Dart exception thrown from converted Future. Use the properties "
"'error' to fetch the boxed error and 'stack' to recover "
"the stack trace."
.toJS,
);
wrapper['error'] = error.toJSBox;
wrapper['stack'] = stackTrace.toString().toJS;
reject.callAsFunction(reject, wrapper);
},
);
}.toJS,
);
}
}
/// Conversions from [JSArrayBuffer] to [ByteBuffer].
extension JSArrayBufferToByteBuffer on JSArrayBuffer {
/// Converts this [JSArrayBuffer] to a [ByteBuffer] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [ByteBuffer]s are [JSArrayBuffer]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSArrayBuffer] will affect the [ByteBuffer] and
/// vice versa.
external ByteBuffer get toDart;
}
/// Conversions from [ByteBuffer] to [JSArrayBuffer].
extension ByteBufferToJSArrayBuffer on ByteBuffer {
/// Converts this [ByteBuffer] to a [JSArrayBuffer] by either casting,
/// unwrapping, or cloning the [ByteBuffer].
///
/// Throws if the [ByteBuffer] wraps a JS `SharedArrayBuffer`.
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, [ByteBuffer]s are either `ArrayBuffer`s or
/// > `SharedArrayBuffer`s so this will just check the type and cast.
/// > When compiling to Wasm, this [ByteBuffer] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it and either returns the
/// > `ArrayBuffer` or throws if the unwrapped buffer was a
/// > `SharedArrayBuffer`. If it's instantiated in Dart, this method clones
/// > this [ByteBuffer]'s values into a new [JSArrayBuffer].
/// > Avoid assuming that modifications to this [ByteBuffer] will affect the
/// > [JSArrayBuffer] and vice versa unless it was instantiated in JavaScript.
external JSArrayBuffer get toJS;
}
/// Conversions from [JSDataView] to [ByteData].
extension JSDataViewToByteData on JSDataView {
/// Converts this [JSDataView] to a [ByteData] by either casting or wrapping
/// it.
///
/// When compiling to JavaScript, [ByteData]s are [JSDataView]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSDataView] will affect the [ByteData] and vice
/// versa.
external ByteData get toDart;
}
/// Conversions from [ByteData] to [JSDataView].
extension ByteDataToJSDataView on ByteData {
/// Converts this [ByteData] to a [JSDataView] by either casting, unwrapping,
/// or cloning the [ByteData].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [ByteData] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [ByteData]'s values into a new
/// > [JSDataView].
/// > Avoid assuming that modifications to this [ByteData] will affect the
/// > [JSDataView] and vice versa unless it was instantiated in JavaScript.
external JSDataView get toJS;
}
/// Conversions from [JSInt8Array] to [Int8List].
extension JSInt8ArrayToInt8List on JSInt8Array {
/// Converts this [JSInt8Array] to a [Int8List] by either casting or wrapping
/// it.
///
/// When compiling to JavaScript, [Int8List]s are [JSInt8Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSInt8Array] will affect the [Int8List] and vice
/// versa.
external Int8List get toDart;
}
/// Conversions from [Int8List] to [JSInt8Array].
extension Int8ListToJSInt8Array on Int8List {
/// Converts this [Int8List] to a [JSInt8Array] by either casting,
/// unwrapping, or cloning the [Int8List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Int8List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Int8List]'s values into a new
/// > [JSInt8Array].
/// > Avoid assuming that modifications to this [Int8List] will affect the
/// > [JSInt8Array] and vice versa unless it was instantiated in JavaScript.
external JSInt8Array get toJS;
}
/// Conversions from [JSUint8Array] to [Uint8List].
extension JSUint8ArrayToUint8List on JSUint8Array {
/// Converts this [JSUint8Array] to a [Uint8List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Uint8List]s are [JSUint8Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSUint8Array] will affect the [Uint8List] and vice
/// versa.
external Uint8List get toDart;
}
/// Conversions from [Uint8List] to [JSUint8Array].
extension Uint8ListToJSUint8Array on Uint8List {
/// Converts this [Uint8List] to a [JSUint8Array] by either casting,
/// unwrapping, or cloning the [Uint8List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Uint8List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Uint8List]'s values into a new
/// > [JSUint8Array].
/// > Avoid assuming that modifications to this [Uint8List] will affect the
/// > [JSUint8Array] and vice versa unless it was instantiated in JavaScript.
external JSUint8Array get toJS;
}
/// Conversions from [JSUint8ClampedArray] to [Uint8ClampedList].
extension JSUint8ClampedArrayToUint8ClampedList on JSUint8ClampedArray {
/// Converts this [JSUint8ClampedArray] to a [Uint8ClampedList] by either
/// casting or wrapping it.
///
/// When compiling to JavaScript, [Uint8ClampedList]s are
/// [JSUint8ClampedArray]s and this operation will be a cast. When compiling
/// to Wasm, a wrapper is introduced. Modifications to this
/// [JSUint8ClampedArray] will affect the [Uint8ClampedList] and vice versa.
external Uint8ClampedList get toDart;
}
/// Conversions from [Uint8ClampedList] to [JSUint8ClampedArray].
extension Uint8ClampedListToJSUint8ClampedArray on Uint8ClampedList {
/// Converts this [Uint8ClampedList] to a [JSUint8ClampedArray] by either
/// casting, unwrapping, or cloning the [Uint8ClampedList].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Uint8ClampedList] may or may not be a
/// > wrapper depending on if it was converted from JavaScript or instantiated
/// > in Dart. If it's a wrapper, this method unwraps it. If it's instantiated
/// > in Dart, this method clones this [Uint8ClampedList]'s values into a new
/// > [JSUint8ClampedArray].
/// > Avoid assuming that modifications to this [Uint8ClampedList] will affect
/// > the [JSUint8ClampedArray] and vice versa unless it was instantiated in
/// > JavaScript.
external JSUint8ClampedArray get toJS;
}
/// Conversions from [JSInt16Array] to [Int16List].
extension JSInt16ArrayToInt16List on JSInt16Array {
/// Converts this [JSInt16Array] to a [Int16List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Int16List]s are [JSInt16Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSInt16Array] will affect the [Int16List] and vice
/// versa.
external Int16List get toDart;
}
/// Conversions from [Int16List] to [JSInt16Array].
extension Int16ListToJSInt16Array on Int16List {
/// Converts this [Int16List] to a [JSInt16Array] by either casting,
/// unwrapping, or cloning the [Int16List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Int16List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Int16List]'s values into a new
/// > [JSInt16Array].
/// > Avoid assuming that modifications to this [Int16List] will affect the
/// > [JSInt16Array] and vice versa unless it was instantiated in JavaScript.
external JSInt16Array get toJS;
}
/// Conversions from [JSUint16Array] to [Uint16List].
extension JSUint16ArrayToInt16List on JSUint16Array {
/// Converts this [JSUint16Array] to a [Uint16List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Uint16List]s are [JSUint16Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSUint16Array] will affect the [Uint16List] and
/// vice versa.
external Uint16List get toDart;
}
/// Conversions from [Uint16List] to [JSUint16Array].
extension Uint16ListToJSInt16Array on Uint16List {
/// Converts this [Uint16List] to a [JSUint16Array] by either casting,
/// unwrapping, or cloning the [Uint16List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Uint16List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Uint16List]'s values into a new
/// > [JSUint16Array].
/// > Avoid assuming that modifications to this [Uint16List] will affect the
/// > [JSUint16Array] and vice versa unless it was instantiated in JavaScript.
external JSUint16Array get toJS;
}
/// Conversions from [JSInt32Array] to [Int32List].
extension JSInt32ArrayToInt32List on JSInt32Array {
/// Converts this [JSInt32Array] to a [Int32List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Int32List]s are [JSInt32Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSInt32Array] will affect the [Int32List] and vice
/// versa.
external Int32List get toDart;
}
/// Conversions from [Int32List] to [JSInt32Array].
extension Int32ListToJSInt32Array on Int32List {
/// Converts this [Int32List] to a [JSInt32Array] by either casting,
/// unwrapping, or cloning the [Int32List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Int32List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Int32List]'s values into a new
/// > [JSInt32Array].
/// > Avoid assuming that modifications to this [Int32List] will affect the
/// > [JSInt32Array] and vice versa unless it was instantiated in JavaScript.
external JSInt32Array get toJS;
}
/// Conversions from [JSUint32Array] to [Uint32List].
extension JSUint32ArrayToUint32List on JSUint32Array {
/// Converts this [JSUint32Array] to a [Uint32List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Uint32List]s are [JSUint32Array]s and this
/// operation will be a cast. When compiling to Wasm, a wrapper is introduced.
/// Modifications to this [JSUint32Array] will affect the [Uint32List] and
/// vice versa.
external Uint32List get toDart;
}
/// Conversions from [Uint32List] to [JSUint32Array].
extension Uint32ListToJSUint32Array on Uint32List {
/// Converts this [Uint32List] to a [JSUint32Array] by either casting,
/// unwrapping, or cloning the [Uint32List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Uint32List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Uint32List]'s values into a new
/// > [JSUint32Array].
/// > Avoid assuming that modifications to this [Uint32List] will affect the
/// > [JSUint32Array] and vice versa unless it was instantiated in JavaScript.
external JSUint32Array get toJS;
}
/// Conversions from [JSFloat32Array] to [Float32List].
extension JSFloat32ArrayToFloat32List on JSFloat32Array {
/// Converts this [JSFloat32Array] to a [Float32List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Float32List]s are [JSFloat32Array]s and
/// this operation will be a cast. When compiling to Wasm, a wrapper is
/// introduced. Modifications to this [JSFloat32Array] will affect the
/// [Float32List] and vice versa.
external Float32List get toDart;
}
/// Conversions from [Float32List] to [JSFloat32Array].
extension Float32ListToJSFloat32Array on Float32List {
/// Converts this [Float32List] to a [JSFloat32Array] by either casting,
/// unwrapping, or cloning the [Float32List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Float32List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Float32List]'s values into a new
/// > [JSFloat32Array].
/// > Avoid assuming that modifications to this [Float32List] will affect the
/// > [JSFloat32Array] and vice versa unless it was instantiated in
/// > JavaScript.
external JSFloat32Array get toJS;
}
/// Conversions from [JSFloat64Array] to [Float64List].
extension JSFloat64ArrayToFloat64List on JSFloat64Array {
/// Converts this [JSFloat64Array] to a [Float64List] by either casting or
/// wrapping it.
///
/// When compiling to JavaScript, [Float64List]s are [JSFloat64Array]s and
/// this operation will be a cast. When compiling to Wasm, a wrapper is
/// introduced. Modifications to this [JSFloat64Array] will affect the
/// [Float64List] and vice versa.
external Float64List get toDart;
}
/// Conversions from [Float64List] to [JSFloat64Array].
extension Float64ListToJSFloat64Array on Float64List {
/// Converts this [Float64List] to a [JSFloat64Array] by either casting,
/// unwrapping, or cloning the [Float64List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, all typed lists are the equivalent
/// > JavaScript typed arrays, and therefore this method simply casts.
/// > When compiling to Wasm, this [Float64List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [Float64List]'s values into a new
/// > [JSFloat64Array].
/// > Avoid assuming that modifications to this [Float64List] will affect the
/// > [JSFloat64Array] and vice versa unless it was instantiated in
/// > JavaScript.
external JSFloat64Array get toJS;
}
/// Conversions from [JSArray] to [List].
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
/// Converts this [JSArray] to a [List] by either casting or wrapping it.
///
/// When compiling to JavaScript, [List]s are [JSArray]s and this will be a
/// cast. When compiling to Wasm, a wrapper is introduced. Modifications to
/// this [JSArray] will affect the [List] and vice versa. In order to ensure
/// type soundness, this method may introduce casts when accessing elements in
/// order to ensure they are of type [T].
external List<T> get toDart;
}
/// Conversions from [List] to [JSArray].
extension ListToJSArray<T extends JSAny?> on List<T> {
/// Converts this [List] to a [JSArray] by either casting, unwrapping, or
/// cloning the [List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, the core [List] is a JavaScript `Array`,
/// > and therefore this method simply casts. User-defined [List]s are
/// > currently unsupported when compiling to JavaScript.
/// > When compiling to Wasm, this [List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method clones this [List]'s values into a new [JSArray].
/// > Avoid assuming that modifications to this [List] will affect the
/// > [JSArray] and vice versa unless it was instantiated in JavaScript.
external JSArray<T> get toJS;
/// Converts this [List] to a [JSArray] by either casting, unwrapping, or
/// proxying the [List].
///
/// > [!NOTE]
/// > Depending on whether code is compiled to JavaScript or Wasm, this
/// > conversion will have different semantics.
/// > When compiling to JavaScript, the core [List] is a JavaScript `Array`,
/// > and therefore this method simply casts. User-defined [List]s are
/// > currently unsupported when compiling to JavaScript.
/// > When compiling to Wasm, this [List] may or may not be a wrapper
/// > depending on if it was converted from JavaScript or instantiated in
/// > Dart. If it's a wrapper, this method unwraps it. If it's instantiated in
/// > Dart, this method proxies the [List] using a heavyweight `Array`
/// > wrapper. Access to the original [List]'s elements may be very
/// > unperformant.
/// > Modifications to this [List] will affect the [JSArray] and vice versa.
external JSArray<T> get toJSProxyOrRef;
}
/// Conversions from [JSNumber] to [double] or [int].
extension JSNumberToNumber on JSNumber {
/// Converts this [JSNumber] to a [double].
external double get toDartDouble;
/// Converts this [JSNumber] to an [int].
///
/// If this [JSNumber] is not an integer value, throws.
external int get toDartInt;
}
/// Conversions from [double] to [JSNumber].
extension DoubleToJSNumber on double {
/// Converts this [double] to a [JSNumber].
external JSNumber get toJS;
}
/// Conversions from [num] to [JSNumber].
extension NumToJSExtension on num {
/// Converts this [num] to a [JSNumber].
JSNumber get toJS => DoubleToJSNumber(toDouble()).toJS;
}
/// Conversions from [JSBoolean] to [bool].
extension JSBooleanToBool on JSBoolean {
/// Converts this [JSBoolean] to a [bool].
external bool get toDart;
}
/// Conversions from [bool] to [JSBoolean].
extension BoolToJSBoolean on bool {
/// Converts this [bool] to a [JSBoolean].
external JSBoolean get toJS;
}
/// Conversions from [JSString] to [String].
extension JSStringToString on JSString {
/// Converts this [JSString] to a [String].
external String get toDart;
}
/// Conversions from [String] to [JSString].
extension StringToJSString on String {
/// Converts this [String] to a [JSString].
external JSString get toJS;
}
/// General-purpose JavaScript operators.
///
/// Indexing operators (`[]`, `[]=`) should be declared through operator
/// overloading instead like:
/// ```
/// external operator int [](int key);
/// ```
///
/// All operators in this extension shall accept and return only JS types.
// TODO(srujzs): Add more as needed. For now, we just expose the ones needed to
// migrate from `dart:js_util`.
extension JSAnyOperatorExtension on JSAny? {
// Arithmetic operators.
/// The result of <code>`this` + [any]</code> in JavaScript.
external JSAny add(JSAny? any);
/// The result of <code>`this` - [any]</code> in JavaScript.
external JSAny subtract(JSAny? any);
/// The result of <code>`this` * [any]</code> in JavaScript.
external JSAny multiply(JSAny? any);
/// The result of <code>`this` / [any]</code> in JavaScript.
external JSAny divide(JSAny? any);
/// The result of <code>`this` % [any]</code> in JavaScript.
external JSAny modulo(JSAny? any);
/// The result of <code>`this` ** [any]</code> in JavaScript.
external JSAny exponentiate(JSAny? any);
// Comparison operators.
/// The result of <code>`this` > [any]</code> in JavaScript.
external JSBoolean greaterThan(JSAny? any);
/// The result of <code>`this` >= [any]</code> in JavaScript.
external JSBoolean greaterThanOrEqualTo(JSAny? any);
/// The result of <code>`this` < [any]</code> in JavaScript.
external JSBoolean lessThan(JSAny? any);
/// The result of <code>`this` <= [any]</code> in JavaScript.
external JSBoolean lessThanOrEqualTo(JSAny? any);
/// The result of <code>`this` == [any]</code> in JavaScript.
external JSBoolean equals(JSAny? any);
/// The result of <code>`this` != [any]</code> in JavaScript.
external JSBoolean notEquals(JSAny? any);
/// The result of <code>`this` === [any]</code> in JavaScript.
external JSBoolean strictEquals(JSAny? any);
/// The result of <code>`this` !== [any]</code> in JavaScript.
external JSBoolean strictNotEquals(JSAny? any);
// Bitwise operators.
/// The result of <code>`this` >>> [any]</code> in JavaScript.
// TODO(srujzs): This should return `num` or `double` instead.
external JSNumber unsignedRightShift(JSAny? any);
// Logical operators.
/// The result of <code>`this` && [any]</code> in JavaScript.
external JSAny? and(JSAny? any);
/// The result of <code>`this` || [any]</code> in JavaScript.
external JSAny? or(JSAny? any);
/// The result of <code>!`this`</code> in JavaScript.
external JSBoolean get not;
/// The result of <code>!!`this`</code> in JavaScript.
external JSBoolean get isTruthy;
}
/// The global scope that is used to find user-declared interop members.
///
/// For example:
///
/// ```
/// library;
///
/// @JS()
/// external String get name;
/// ```
///
/// Reading the top-level member `name` will execute JavaScript code like
/// `<globalContext>.name`.
///
/// There are subtle differences depending on the compiler, but in general,
/// [globalContext] can be treated like JavaScript's `globalThis`.
external JSObject get globalContext;
/// Given a instance of a Dart class that contains an <code>@[JSExport]</code>
/// annotation, creates a JavaScript object that wraps the given Dart object.
///
/// The object literal will be a map of properties, which are either the written
/// instance member names or their renames, to callbacks that call the
/// corresponding Dart instance members.
///
/// See https://dart.dev/interop/js-interop/mock for more details on how to
/// declare classes that can be used in this method.
external JSObject createJSInteropWrapper<T extends Object>(T dartObject);
// TODO(srujzs): Expose this method when we handle conformance checking for
// interop extension types. We don't expose this method today due to the bound
// on `T`. `@staticInterop` types can't implement `JSObject`, so this method
// simply wouldn't work. We could make it extend `Object` to support the
// `@staticInterop` case, but if we ever refactor to `extends JSObject`, this
// would be a breaking change. For now, due to the low usage of
// `createStaticInteropMock`, we avoid introducing this method until later.
// external T createJSInteropMock<T extends JSObject, U extends Object>(
// U dartMock, [JSObject? proto = null]);
/// Dynamically imports a JavaScript module with the given [moduleName] using
/// the JavaScript `import()` syntax.
///
/// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/import
/// for more details.
///
/// Returns a [JSPromise] that resolves to a [JSObject] that's the module
/// namespace object.
external JSPromise<JSObject> importModule(JSAny moduleName);