// 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 = '']);
}
/// 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._(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 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._(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 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._(JSPromiseRepType _jsPromise)
implements JSObject {
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._(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 {}
@JS('Symbol')
external JSSymbol _constructSymbol([String? description]);
/// A JavaScript `Symbol`.
@JS('Symbol')
extension type JSSymbol._(JSSymbolRepType _jsSymbol) implements JSAny {
// 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._(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._(
ExternalDartReferenceRepType _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 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);
}
/// Whether this [JSAny]? 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
/// @[JS] rename of the given type like
/// `instanceOfString('Array')`. Note that if you rename the library using the
/// @[JS] 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
/// @[JS] 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 a few values for [T] that are exceptions to this rule:
/// - `JSTypedArray`: As `TypedArray` does not exist as a class in JavaScript,
/// this does some prototype checking to make `isA` do the
/// right thing.
/// - `JSBoxedDartObject`: `isA` will check if the value is
/// a result of a previous [ObjectToJSBoxedDartObject.toJSBox] call.
/// - `JSAny`: If you do an `isA` check, it will only check for `null`.
/// - User interop types whose representation types are JS primitive types:
/// This will result in an error to avoid confusion on whether the user
/// interop type is used in the type-check. Use the primitive JS type as the
/// value for [T] instead.
/// - User interop types that have an object literal constructor: This will
/// result in an error as you likely want to use [JSObject] instead.
@Since('3.4')
external bool isA();
/// 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]