// 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_types';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';
/// 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, 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]);
}
// To support an easier transition, we allow users to use `@staticInterop`
// classes - with or without the `@anonymous` annotation.
class _StaticInterop {
const _StaticInterop();
}
/// [staticInterop] enables the [JS] annotated class to be treated as a "static"
/// interop class.
///
/// These classes allow interop with native types, like the ones in `dart:html`.
/// These classes implicitly all erase to the internal interceptor
/// `JavaScriptObject`, so they can be freely casted to and from other
/// [staticInterop] types, `dart:html` types, and `JSObject` from
/// `dart:js_interop`. Non-[staticInterop] `package:js` types can be casted to
/// [staticInterop] types, but the reverse can fail if the underlying value is a
/// `@Native`-reserved type (like `dart:html` types).
///
/// [staticInterop] classes have the following restrictions:
/// - They must contain a [JS] annotation, either from this library or from
/// `dart:js_interop`.
/// - They should not contain any instance members, inherited or otherwise, and
/// should instead use static extension members, which can be external or
/// non-external.
/// - They can only contain factories and `static` members. They can be
/// combined with [anonymous] to make external factories create new
/// JavaScript object literals instead.
/// - They should not implement, extend, or mixin non-[staticInterop] classes
/// and vice-versa.
/// - The annotation should only be applied to non-mixin classes and no other
/// declarations.
const _StaticInterop staticInterop = _StaticInterop();
class _Anonymous {
const _Anonymous();
}
/// An annotation that indicates a [JS] annotated class is structural and does
/// not have a known JavaScript prototype.
///
/// A class marked with [anonymous] allows external factories with named
/// parameters. Invoking these factories creates JavaScript object literals with
/// name-value pairs corresponding to any named parameters and their values. If
/// there are no named parameters, an empty JavaScript object is created.
///
/// [anonymous] classes have the following restrictions:
/// - They must contain a [JS] annotation, either from this library or from
/// `dart:js_interop`. If the latter, the class must also contain
/// [staticInterop].
/// - They cannot contain any non-external members unless it's a
/// [staticInterop] class, in which case it can also contain non-external
/// factories and static methods.
/// - They cannot contain any external generative constructors.
/// - Any external factory must not contain any positional parameters.
/// - They cannot extend or be extended by a non-[JS] annotated class.
/// - The annotation should only be applied to non-mixin classes and no other
/// declarations.
const _Anonymous anonymous = _Anonymous();
/// Annotation to allow Dart classes to be wrapped with a JS object using
/// `dart:js_interop`'s `createJSInteropWrapper`.
///
/// When an instance of a class annotated with this annotation is passed to
/// `createJSInteropWrapper`, the method returns a JS object that contains
/// a property for each of the class' instance members. When called, these
/// properties forward to the instance's corresponding members.
///
/// You can either annotate specific instance members to only wrap those members
/// or you can annotate the entire class, which will include all of its instance
/// members.
///
/// By default, the property will have the same name as the corresponding
/// instance member. You can change the property name of a member in the JS
/// object by providing a [name] in the @[JSExport] annotation on the member,
/// like so:
/// ```
/// class Export {
/// @JSExport('printHelloWorld')
/// void printMessage() => print('Hello World!');
/// }
/// ```
/// which will then set the property 'printHelloWorld' in the JS object to
/// forward to `printMessage`.
///
/// Classes and mixins in the hierarchy of the annotated class are included only
/// if they are annotated as well or specific members in them are annotated. If
/// a superclass does not have an annotation anywhere, its members are not
/// included. If members are overridden, only the overriding member will
/// be wrapped as long as it or its class has this annotation.
///
/// Only concrete instance members can and will be wrapped, and it's an error to
/// annotate other members with this annotation.
class JSExport {
final String name;
const JSExport([this.name = '']);
}
@JS('Reflect.get')
external JSAny? _getPropertyForJSAny(JSAny value, JSAny property);
/// 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, [JSAny]? is the top type of
/// the type hierarchy as it includes nullish JavaScript values as well.
extension type JSAny._(JSAnyType _jsAny) implements Object, JSAnyType {
/// Like `JSObjectUnsafeExtension.getProperty`, but works for any JS type.
R _getProperty(JSAny property) =>
_getPropertyForJSAny(this, property) as R;
/// Like `JSObjectUnsafeExtension.callMethod`, but works for any JS type.
R _callMethod(JSAny method) =>
_getProperty(method).callAsFunction(this) as R;
}
/// 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._(JSObjectType _jsObject)
implements JSAny, JSObjectType {
/// 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 JSObjectType;
/// 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 JSObjectType _createObjectLiteral();
/// A JavaScript [`Function`](https://tc39.es/ecma262/#sec-function-objects)
/// value.
@JS('Function')
extension type JSFunction._(JSFunctionType _jsFunction)
implements JSObject, JSFunctionType {}
/// 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._(
JSExportedDartFunctionType _jsExportedDartFunction
)
implements JSFunction, JSExportedDartFunctionType {}
/// The synchronous [JS iterable protocol].
///
/// [JS iterable protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol
///
/// This interface is the minimal protocol necessary to interact with JS
/// features like `for`/`of`. All JS standard library types that implement the
/// JS iterable protocol implement [JSIterable] as well, whose [iterator] is a
/// [JSIterator] with more utility methods than [JSIteratorProtocol].
@Since('3.12')
extension type JSIterableProtocol._(JSAnyType _)
implements JSAny {
/// See [`[Symbol.iterator]()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#symbol.iterator).
JSIteratorProtocol get iterator => _callMethod(JSSymbol.iterator);
}
/// A [JSIterableProtocol] whose [iterator] is a [JSIterator].
///
/// On its own, the synchronous [JS iterable protocol][] provides a
/// [JSIteratorProtocol] that can only do simple iteration. Many JS iterables
/// provide a JS [Iterator][] ([JSIterator]) with more utility methods,
/// including all iterables provided by the JS standard library.
///
/// [JS iterable protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol
/// [Iterator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator
///
/// All types that implement [JSIterableProtocol] in the JS core library are
/// [JSIterable]s, but user-defined [JSIterableProtocol] implementations may not
/// be.
@Since('3.12')
extension type JSIterable._(JSAnyType _)
implements JSIterableProtocol {
/// See [`[Symbol.iterator]()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#symbol.iterator).
JSIterator get iterator => _callMethod>(JSSymbol.iterator);
}
/// The synchronous [JS iterator protocol].
///
/// [JS iterator protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
///
/// This is the minimal interface that JS iterators are allowed to implement in
/// order to work with core library APIs and language features like `for`/`of`.
///
/// Iterators are strongly encouraged to also extend the [JS `Iterator`
/// class][], which adds various utility methods. If they do, they're referred
/// to as "proper iterators" (see [JSIterator]). All iterators provided by the
/// core library are proper iterators.
///
/// [JS `Iterator` class]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#proper_iterators
@Since('3.12')
extension type JSIteratorProtocol._(JSAny _)
implements JSAny {
@JS('return')
external JSFunction? get _nullableReturnValue;
@JS('throw')
external JSFunction? get _nullableThrowError;
/// See [`next()`].
///
/// [`next()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#next
external JSIteratorResult next([JSAny? yieldValue]);
@JS('return')
external JSIteratorResult _returnValue([JSAny? value]);
/// See [`return()`].
///
/// [`return()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#returnvalue
///
/// This is a nullable getter because not all iterators support this method.
///
/// If the `value` is null or omitted, the callback is called with no value.
/// It's not possible to call the callback with a `null` value.
JSIteratorResult Function([T? value])? get returnValue =>
// Make sure to pass along whether an argument was passed or not, because
// that's observable from JavaScript.
_nullableReturnValue == null
? null
: ([value]) => value == null ? _returnValue() : _returnValue(value);
@JS('throw')
external JSIteratorResult _throwError([JSAny? error]);
/// See [`throw()`].
///
/// [`throw()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#throwexception
///
/// This is a nullable getter because not all iterators support this method.
///
/// If the `error` is null or omitted, the callback is called with no value.
/// It's not possible to call the callback with a `null` error.
JSIteratorResult Function([JSAny? error])? get throwError =>
// Make sure to pass along whether an argument was passed or not, because
// that's observable from JavaScript.
_nullableThrowError == null
? null
: ([error]) => error == null ? _throwError() : _throwError(error);
}
/// A proper JS iterator.
///
/// In JS, a ["proper iterator"][] implements the [JS iterator protocol] and is an
/// instance of the [JS `Iterator` class][].
///
/// ["proper iterator"]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#proper_iterators
/// [JS iterator protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
/// [JS `Iterator` class]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator#proper_iterators
///
/// This library provides no type for the JS `Iterator` type without the JS
/// iterator protocol members. Such an object is not useful.
@JS('Iterator')
@Since('3.12')
extension type JSIterator._(JSObject _)
implements JSIteratorProtocol, JSIterable {
/// Converts an object that just implements the [Iterator protocol] into a
/// proper iterator.
///
/// [Iterator protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
external static JSIterator from(
JSIteratorProtocol object,
);
/// Creates a proper iterator from an object that just implements the
/// [Iterable protocol].
///
/// [Iterable protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols
///
/// This is equivalent to `JSIterator.from(object.iterator)`.
@JS('from')
external static JSIterator fromIterable(
JSIterableProtocol object,
);
/// Creates a proper [JSIterator] from the methods defined by the [Iterator
/// protocol].
///
/// [Iterator protocol]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterator_protocol
///
/// This is the best way to create a custom JS iterator from Dart code. To
/// convert an existing Dart iterable, use
/// [IterableToJSIterable.toJSIterable], and to convert an existing Dart
/// iterator use [IteratorToJSIterator.toJSIterator]
static JSIterator fromFunctions(
JSIteratorResult Function() next, {
JSIteratorResult Function()? returnValue,
}) {
final iterator = _CustomIteratorProtocol(next: next.toJS);
if (returnValue != null) iterator._returnValue = returnValue.toJS;
return from(iterator);
}
/// See [`Iterator.drop()`].
///
/// [`Iterator.drop()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/drop
external JSIterator drop(int limit);
/// See [`Iterator.take()`].
///
/// [`Iterator.take()`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Iterator/take
external JSIterator take(int limit);
}
/// A [JSIteratorProtocol] with a settable `return` property.
///
/// This is used by [JSIterator.fromFunctions] to construct a proper iterator.
extension type _CustomIteratorProtocol._(JSObject _)
implements JSIteratorProtocol {
@JS('return')
external set _returnValue(JSFunction? function);
external _CustomIteratorProtocol({required JSFunction next});
}
/// A synchronous [JS `IteratorResult`].
///
/// [JS `IteratorResult`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#done
@Since('3.12')
extension type JSIteratorResult._(JSObject _)
implements JSObject {
/// Creates a result that indicates the end of iteration.
///
/// The value is the "return value" of the iterator. For example, [generator
/// functions] use this to represent the return value of the generator
/// function.
///
/// [generator functions]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function*
///
/// If the `value` is null or omitted, the result has no value. It's not
/// possible to create a result with a `null` value.
factory JSIteratorResult.done([T? returnValue]) {
final result = JSIteratorResult._(JSObject());
result._done = true;
if (returnValue != null) result._value = returnValue;
return result;
}
/// Creates a result that indicates the iterator is emitting a value and is
/// not yet finished.
factory JSIteratorResult.value(T value) {
final result = JSIteratorResult._(JSObject());
result._value = value;
return result;
}
// Wrap this to hide the distinction between undefined and false from users.
@JS('done')
external bool? _done;
// Wrap this so that the setter is private.
@JS('value')
external T? _value;
/// See [`done`].
///
/// [`done`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#done
bool get isDone => _done == true;
/// See [`value`].
///
/// [`value`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#value
external T? get value;
}
/// 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 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], casts may be introduced to ensure that it is indeed
/// a [List].
@JS('Array')
extension type JSArray._(JSArrayType _jsArray)
implements JSObject, JSArrayType, JSIterable {
/// 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 from(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], there is a cast to ensure that
/// the [Future] actually resolves to a [T] to ensure soundness.
@JS('Promise')
extension type JSPromise._(JSPromiseType _jsPromise)
implements JSObject, JSPromiseType {
external JSPromise(JSFunction executor);
}
/// Exception for when a [JSPromise] that is converted via
/// [JSPromiseToFuture.toDart] is rejected with a `null` or `undefined` value.
///
/// This is public to allow users to catch when the promise is rejected with
/// `null` or `undefined` versus some other value.
class NullRejectionException implements Exception {
// Indicates whether the value is `undefined` or `null`.
final bool isUndefined;
NullRejectionException(this.isUndefined);
@override
String toString() {
var value = this.isUndefined ? 'undefined' : 'null';
return 'Promise was rejected with a value of `$value`.';
}
}
/// 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._(JSBoxedDartObjectType _jsBoxedDartObject)
implements JSObject, JSBoxedDartObjectType {}
/// A JavaScript `ArrayBuffer`.
@JS('ArrayBuffer')
extension type JSArrayBuffer._(JSArrayBufferType _jsArrayBuffer)
implements JSObject, JSArrayBufferType {
/// 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._(JSDataViewType _jsDataView)
implements JSObject, JSDataViewType {
/// 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._(JSTypedArrayType _jsTypedArray)
implements JSObject, JSTypedArrayType {}
/// A JavaScript `Int8Array`.
@JS('Int8Array')
extension type JSInt8Array._(JSInt8ArrayType _jsInt8Array)
implements JSTypedArray, JSInt8ArrayType {
/// 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._(JSUint8ArrayType _jsUint8Array)
implements JSTypedArray, JSUint8ArrayType {
/// 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._(
JSUint8ClampedArrayType _jsUint8ClampedArray
)
implements JSTypedArray, JSUint8ClampedArrayType {
/// 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._(JSInt16ArrayType _jsInt16Array)
implements JSTypedArray, JSInt16ArrayType {
/// 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._(JSUint16ArrayType _jsUint16Array)
implements JSTypedArray, JSUint16ArrayType {
/// 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._(JSInt32ArrayType _jsInt32Array)
implements JSTypedArray, JSInt32ArrayType {
/// 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._(JSUint32ArrayType _jsUint32Array)
implements JSTypedArray, JSUint32ArrayType {
/// 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._(JSFloat32ArrayType _jsFloat32Array)
implements JSTypedArray, JSFloat32ArrayType {
/// 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._(JSFloat64ArrayType _jsFloat64Array)
implements JSTypedArray, JSFloat64ArrayType {
/// 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._(JSNumberType _jsNumber)
implements JSAny, JSNumberType {}
/// A JavaScript boolean.
extension type JSBoolean._(JSBooleanType _jsBoolean)
implements JSAny, JSBooleanType {}
/// A JavaScript string.
extension type JSString._(JSStringType _jsString)
implements JSAny, JSStringType, JSIterable {}
@JS('Symbol')
external JSSymbol _constructSymbol([String? description]);
/// A JavaScript `Symbol`.
@JS('Symbol')
extension type JSSymbol._(JSSymbolType _jsSymbol)
implements JSAny, JSSymbolType {
// TODO(srujzs): See if this can be made `const` so it can be used in similar
// situations to a Dart symbol literal.
/// Creates a new, unique JavaScript `Symbol`.
///
/// If [description] is provided, it's used for debugging but not to access
/// the symbol itself.
@Since('3.11')
JSSymbol([String? description])
: _jsSymbol =
(description == null
? _constructSymbol()
: _constructSymbol(description))
._jsSymbol;
/// Searches for an existing symbol in a runtime-wide symbol registry with the
/// given key and returns it if found.
///
/// Otherwise, creates a new symbol with this key, adds it to the global
/// registry, and returns it.
@Since('3.11')
@JS('for')
external static JSSymbol forKey(String key);
/// See [`Symbol.asyncIterator`].
///
/// [`Symbol.asyncIterator`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/asyncIterator
@Since('3.11')
external static JSSymbol get asyncIterator;
/// See [`Symbol.hasInstance`].
///
/// [`Symbol.hasInstance`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/hasInstance
@Since('3.11')
external static JSSymbol get hasInstance;
/// See [`Symbol.isConcatSpreadable`].
///
/// [`Symbol.isConcatSpreadable`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/isConcatSpreadable
@Since('3.11')
external static JSSymbol get isConcatSpreadable;
/// See [`Symbol.iterator`].
///
/// [`Symbol.iterator`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator
@Since('3.11')
external static JSSymbol get iterator;
/// See [`Symbol.match`].
///
/// [`Symbol.match`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/match
@Since('3.11')
external static JSSymbol get match;
/// See [`Symbol.matchAll`].
///
/// [`Symbol.matchAll`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/matchAll
@Since('3.11')
external static JSSymbol get matchAll;
/// See [`Symbol.replace`].
///
/// [`Symbol.replace`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace
@Since('3.11')
external static JSSymbol get replace;
/// See [`Symbol.search`].
///
/// [`Symbol.search`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/search
@Since('3.11')
external static JSSymbol get search;
/// See [`Symbol.species`].
///
/// [`Symbol.species`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/species
@Since('3.11')
external static JSSymbol get species;
/// See [`Symbol.split`].
///
/// [`Symbol.split`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/split
@Since('3.11')
external static JSSymbol get split;
/// See [`Symbol.toPrimitive`].
///
/// [`Symbol.toPrimitive`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toPrimitive
@Since('3.11')
external static JSSymbol get toPrimitive;
/// See [`Symbol.toStringTag`].
///
/// [`Symbol.toStringTag`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/toStringTag
@Since('3.11')
external static JSSymbol get toStringTag;
/// See [`Symbol.unscopables`].
///
/// [`Symbol.unscopables`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/unscopables
@Since('3.11')
external static JSSymbol get unscopables;
@Since('3.11')
@JS('keyFor')
external static String? _keyFor(JSSymbol symbol);
/// Returns the shared symbol key from the global symbol registry for this
/// symbol (as registered with [forKey]), if this symbol was created with
/// [JSSymbol.forKey].
@Since('3.11')
String? get key => _keyFor(this);
/// A string containing the description of the symbol, as passed to
/// [JSSymbol.new].
@Since('3.11')
external String get description;
}
/// A JavaScript `BigInt`.
extension type JSBigInt._(JSBigIntType _jsBigInt)
implements JSAny, JSBigIntType {}
/// 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._(
ExternalDartReferenceType _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 = JSVoidType;
/// Helper members 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`. 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 [JSAny]? is
/// [typeString].
external bool typeofEquals(String typeString);
/// Whether this [JSAny]? is an `instanceof` [constructor].
external bool instanceof(JSFunction constructor);
/// Whether this [JSAny]? 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);
}
/// 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]