[dart:js_interop/_unsafe] Clean up API docs
- Rephrase docs on JS types
- JS -> JavaScript where applicable
- “This” and “that” expanded to refer to the thing
- Add docs to undocumented members
- Rephrase some docs to be clearer and more explicit
- Move warnings to format that dartdoc can display specially
- Add warnings to every single member that should have it
- e.g. -> like
- Makes similar docs consistent
- Removes library declarations per style guide. Regenerates
expectations to handle the change.
CoreLibraryReviewExempt: Backend-specific library. Docs only.
Change-Id: I246f8c20d594741149766e5a76bb186debf54ded
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352977
Reviewed-by: Sigmund Cherem <sigmund@google.com>
Commit-Queue: Srujan Gaddam <srujzs@google.com>
Reviewed-by: Lasse Nielsen <lrn@google.com>
diff --git a/pkg/front_end/testcases/dart2js/extension_types/external.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/extension_types/external.dart.weak.outline.expect
index 5f6af44..1c181a0 100644
--- a/pkg/front_end/testcases/dart2js/extension_types/external.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dart2js/extension_types/external.dart.weak.outline.expect
@@ -1,4 +1,4 @@
-@dart.js_interop::JS::•()
+@#lib1::JS::•()
library static_interop;
import self as self;
import "dart:js_interop" as js_;
diff --git a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect
index 8b02920..a4efeeb 100644
--- a/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dart2js/js_interop_transforms/isa.dart.weak.outline.expect
@@ -1,4 +1,4 @@
-@dart.js_interop::JS::•("library1")
+@#lib1::JS::•("library1")
library;
import self as self;
import "dart:js_interop" as js_;
diff --git a/pkg/front_end/testcases/dartdevc/extension_types/external.dart.weak.outline.expect b/pkg/front_end/testcases/dartdevc/extension_types/external.dart.weak.outline.expect
index 5f6af44..1c181a0 100644
--- a/pkg/front_end/testcases/dartdevc/extension_types/external.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dartdevc/extension_types/external.dart.weak.outline.expect
@@ -1,4 +1,4 @@
-@dart.js_interop::JS::•()
+@#lib1::JS::•()
library static_interop;
import self as self;
import "dart:js_interop" as js_;
diff --git a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect
index 36024fa..83ada2d 100644
--- a/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect
+++ b/pkg/front_end/testcases/dartdevc/js_interop_transforms/isa.dart.weak.outline.expect
@@ -1,4 +1,4 @@
-@dart.js_interop::JS::•("library1")
+@#lib1::JS::•("library1")
library;
import self as self;
import "dart:js_interop" as js_;
diff --git a/sdk/lib/js_interop/js_interop.dart b/sdk/lib/js_interop/js_interop.dart
index 9d47ea8..c8bf548 100644
--- a/sdk/lib/js_interop/js_interop.dart
+++ b/sdk/lib/js_interop/js_interop.dart
@@ -2,10 +2,24 @@
// 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 with JavaScript and browser APIs.
+/// Interoperability, "interop" for short, with JavaScript and browser APIs.
///
-/// The [JSObject] type hierarchy is modeled after the JavaScript
-/// type hierarchy, and facilitates sound interoperability with JavaScript.
+/// 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.
@@ -14,84 +28,106 @@
/// > and not runtime mechanisms like type checks (`is`) and casts (`as`).
///
/// {@category Web}
-library dart.js_interop;
+library;
-import "dart:_internal" show Since;
+import 'dart:_internal' show Since;
+import 'dart:_js_annotations' show JSExport;
import 'dart:_js_types';
import 'dart:js_interop_unsafe';
import 'dart:typed_data';
-// Allow use of `@staticInterop` classes with JS types as well as export
-// functionality.
+// 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;
-/// The annotation for JS interop members.
+/// An annotation on a JavaScript interop declaration.
///
-/// This is meant to signify that a given library, top-level external member, or
-/// extension type is a JS 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. This can be used in
-/// the following scenarios:
+/// Specifying [name] customizes the JavaScript name to use, which can be used
+/// in the following scenarios:
///
-/// - Namespacing all the external top-level members, static members, and
-/// constructors of a library by annotating the library with a custom name.
-/// - Namespacing all the external static members and constructors of an
-/// extension type by annotating the extension type with a custom name.
-/// - Renaming external members by annotating the member with a custom name.
+/// - 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, we default to the Dart name of
-/// the extension type and external members.
+/// In the case where [name] is not specified, the Dart name of the extension
+/// type or external declaration is used as the default.
///
-/// Note: `package:js` exports an `@JS` annotation as well. Unlike that
-/// annotation, this is meant for extension types, and will result in more
-/// type-checking for external top-level members.
+/// 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]);
}
-/// The overall top type in the JS types hierarchy.
+/// 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 {}
-/// The representation type of all JavaScript objects for extension types.
+/// A JavaScript `Object`.
///
-/// This is the supertype of all JS objects, but not other JS types, like
-/// primitives. See https://dart.dev/interop/js-interop for more details on how
-/// to use JS interop.
+/// [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 {
- /// Constructor to go from an object from previous interop, like the types
- /// from `package:js` or `dart:html`, to [JSObject].
+ /// Creates a [JSObject] from an object provided by an earlier interop
+ /// library.
///
- /// This constructor and the public representation field are intended to allow
- /// users to avoid having to cast to and from [JSObject].
+ /// 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 JavaScript object.
+ /// 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 to `JSObject` once we can patch extension type
+// TODO(srujzs): Move this member to `JSObject` once we can patch extension type
// members.
external JSObjectRepType _createObjectLiteral();
-/// The type of all JS functions.
+/// 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.
///
-/// We only allow a subset of Dart functions to be callable from JS.
-// TODO(joshualitt): Detail exactly what are the requirements.
+/// See [FunctionToJSExportedDartFunction.toJS] for more details on how to
+/// convert a Dart function.
@JS('Function')
extension type JSExportedDartFunction._(
JSExportedDartFunctionRepType _jsExportedDartFunction)
implements JSFunction {}
-/// The type of all JS arrays.
+/// 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:
@@ -102,8 +138,7 @@
/// ```
///
/// `array` is not actually checked to ensure it contains instances of
-/// [JSNumber] when called. The only check is that `array` is an instance of
-/// [JSArray].
+/// [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
@@ -112,15 +147,19 @@
@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);
}
/// 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]. Like with [JSArray], we
-/// only check that this is a [JSPromiseRepType].
+/// 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]
@@ -131,104 +170,119 @@
external JSPromise(JSFunction executor);
}
-/// A boxed Dart object that can be passed to JavaScript safely.
+/// A Dart object that is wrapped with a JavaScript object so that it can be
+/// passed to JavaScript safely.
///
-/// There is no interface specified of this boxed object, and you may get a new
-/// box each time you box the same Dart object.
+/// 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 {}
-/// The JavaScript `ArrayBuffer`.
+/// A JavaScript `ArrayBuffer`.
@JS('ArrayBuffer')
extension type JSArrayBuffer._(JSArrayBufferRepType _jsArrayBuffer)
implements JSObject {}
-/// The JavaScript `DataView`.
+/// A JavaScript `DataView`.
@JS('DataView')
extension type JSDataView._(JSDataViewRepType _jsDataView)
implements JSObject {}
-/// The abstract supertype of all JS typed arrays.
+/// Abstract supertype of all JavaScript typed arrays.
extension type JSTypedArray._(JSTypedArrayRepType _jsTypedArray)
implements JSObject {}
-/// The JavaScript `Int8Array`.
+/// A JavaScript `Int8Array`.
@JS('Int8Array')
extension type JSInt8Array._(JSInt8ArrayRepType _jsInt8Array)
implements JSTypedArray {}
-/// The JavaScript `Uint8Array`.
+/// A JavaScript `Uint8Array`.
@JS('Uint8Array')
extension type JSUint8Array._(JSUint8ArrayRepType _jsUint8Array)
implements JSTypedArray {}
-/// The Javascript `Uint8ClampedArray`.
+/// A JavaScript `Uint8ClampedArray`.
@JS('Uint8ClampedArray')
extension type JSUint8ClampedArray._(
JSUint8ClampedArrayRepType _jsUint8ClampedArray) implements JSTypedArray {}
-/// The JavaScript `Int16Array`.
+/// A JavaScript `Int16Array`.
@JS('Int16Array')
extension type JSInt16Array._(JSInt16ArrayRepType _jsInt16Array)
implements JSTypedArray {}
-/// The Javascript `Uint16Array`.
+/// A JavaScript `Uint16Array`.
@JS('Uint16Array')
extension type JSUint16Array._(JSUint16ArrayRepType _jsUint16Array)
implements JSTypedArray {}
-/// The JavaScript `Int32Array`.
+/// A JavaScript `Int32Array`.
@JS('Int32Array')
extension type JSInt32Array._(JSInt32ArrayRepType _jsInt32Array)
implements JSTypedArray {}
-/// The Javascript `Uint32Array`.
+/// A JavaScript `Uint32Array`.
@JS('Uint32Array')
extension type JSUint32Array._(JSUint32ArrayRepType _jsUint32Array)
implements JSTypedArray {}
-/// The JavaScript `Float32Array`.
+/// A JavaScript `Float32Array`.
@JS('Float32Array')
extension type JSFloat32Array._(JSFloat32ArrayRepType _jsFloat32Array)
implements JSTypedArray {}
-/// The Javascript `Float64Array`.
+/// A JavaScript `Float64Array`.
@JS('Float64Array')
extension type JSFloat64Array._(JSFloat64ArrayRepType _jsFloat64Array)
implements JSTypedArray {}
-// The various JS primitive types. Crucially, unlike the Dart type hierarchy,
-// none of these are subtypes of [JSObject], but rather they are logically
+// 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].
-/// The JavaScript numbers.
+/// A JavaScript number.
extension type JSNumber._(JSNumberRepType _jsNumber) implements JSAny {}
-/// The Javascript booleans.
+/// A JavaScript boolean.
extension type JSBoolean._(JSBooleanRepType _jsBoolean) implements JSAny {}
-/// The JavaScript strings.
+/// A JavaScript string.
extension type JSString._(JSStringRepType _jsString) implements JSAny {}
-/// The JavaScript `Symbol`s.
+/// A JavaScript `Symbol`.
extension type JSSymbol._(JSSymbolRepType _jsSymbol) implements JSAny {}
-/// The JavaScript `BigInt`.
+/// A JavaScript `BigInt`.
extension type JSBigInt._(JSBigIntRepType _jsBigInt) implements JSAny {}
-/// A getter to retrieve the global context that is used in static interop
-/// lowering.
-external JSObject get globalContext;
+/// 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;
-/// JS `undefined` and JS `null` are internalized differently based on the
-/// backends. In the JS backends, Dart `null` can actually be JS `undefined` or
-/// JS `null`. In dart2wasm, that's not the case: there's only one Wasm value
-/// `null` can be. Therefore, when we get back JS `null` or JS `undefined`, we
-/// internalize both as Dart `null` in dart2wasm, and when we pass Dart `null`
-/// to an interop API, we pass JS `null`. In the JS backends, Dart `null`
-/// retains its original value when passed back to an interop API. Be wary of
-/// writing code where this distinction between `null` and `undefined` matters.
+/// 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
@@ -236,36 +290,42 @@
// `List<JSAny?>`. In this case, the implementation of the list wrapper needs to
// make the decision, not the user.
extension NullableUndefineableJSAnyExtension on JSAny? {
- /// Determine if this value corresponds to JS `undefined`.
+ /// Whether this value corresponds to JavaScript `undefined`.
///
- /// **WARNING**: Currently, there isn't a way to distinguish between JS
- /// `undefined` and JS `null` in dart2wasm. As such, this should only be used
- /// for code that compiles to JS and will throw on dart2wasm.
+ /// > [!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;
- /// Determine if this value corresponds to JS `null`.
+ /// Whether this value corresponds to JavaScript `null`.
///
- /// **WARNING**: Currently, there isn't a way to distinguish between JS
- /// `undefined` and JS `null` in dart2wasm. As such, this should only be used
- /// for code that compiles to JS and will throw on dart2wasm.
+ /// > [!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 JS value.
+/// Common utility functions that are useful for any JavaScript value.
extension JSAnyUtilityExtension on JSAny? {
- /// Returns whether the result of `typeof` on this [JSAny]? is [typeString].
+ /// Whether the result of `typeof` on this <code>[JSAny]?</code> is
+ /// [typeString].
external bool typeofEquals(String typeString);
- /// Returns whether this [JSAny]? is an `instanceof` [constructor].
+ /// Whether this <code>[JSAny]?</code> is an `instanceof` [constructor].
external bool instanceof(JSFunction constructor);
- /// Returns whether this [JSAny]? is an `instanceof` the constructor that is
- /// defined by [constructorName], which is looked up in the [globalContext].
+ /// 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, we split the name into several parts
+ /// 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.
@@ -283,31 +343,35 @@
return instanceof(constructor as JSFunction);
}
- /// Whether this [JSAny]? is the actual JS type that is declared by [T].
+ /// Whether this <code>[JSAny]?</code> is an instance of the JavaScript type
+ /// that is declared by [T].
///
/// 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 e.g. [JSString], this uses a `typeof` check
- /// that corresponds to that primitive type e.g. `typeofEquals('string')`.
+ /// 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 e.g. [JSArray] or an interop extension
- /// type on one, this uses an `instanceof` check using the name or @[JS]
- /// rename of the given type e.g. `instanceOfString('Array')`. Note that if
- /// you rename the library using the @[JS] annotation, this uses the rename in
- /// the `instanceof` check e.g. `instanceOfString('library1.JSClass')`.
+ /// 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')`.
///
- /// In order to determine the right value for the `instanceof` check, this
- /// uses the name or the rename of the extension type. So, if you had an
- /// interop extension type `JSClass` that wraps `JSArray`, this does an
- /// `instanceOfString('JSClass')` check and not an `instanceOfString('Array')`
- /// check.
+ /// 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 JS, 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.
+ /// `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.
@@ -317,69 +381,116 @@
@Since('3.4')
external bool isA<T extends JSAny?>();
- /// Effectively the inverse of [jsify], [dartify] takes a JavaScript object,
- /// and converts it to a Dart based object. Only JS primitives, arrays, or
- /// 'map' like JS objects are supported.
+ /// Converts a JavaScript value to the Dart equivalent if possible.
+ ///
+ /// Effectively the inverse of [NullableObjectUtilExtension.jsify], [dartify]
+ /// takes a JavaScript value and recursively converts it to a Dart object.
+ /// Only JavaScript primitives, `Array`s, typed arrays, and map-like objects
+ /// with string property names are supported.
+ ///
+ /// > [!NOTE]
+ /// > Prefer using the specific conversion method like `toDart` if you know
+ /// > the JavaScript type as this method may perform many type-checks.
+ // TODO(srujzs): We likely need stronger tests for this method to ensure
+ // consistency.
external Object? dartify();
}
-/// Utility extensions for [Object?].
+/// Common utility functions for <code>[Object]?</code>s.
extension NullableObjectUtilExtension on Object? {
- /// Recursively converts a JSON-like collection, or Dart primitive to a
- /// JavaScript compatible representation.
+ /// Converts a Dart object to the JavaScript equivalent if possible.
+ ///
+ /// Effectively the inverse of [JSAnyUtilityExtension.dartify], [jsify] takes
+ /// a Dart object and recursively converts it to a JavaScript value. Only Dart
+ /// primitives, [Iterable]s, typed lists, and [Map]s are supported.
+ ///
+ /// > [!NOTE]
+ /// > Prefer using the specific conversion method like `toJS` if you know the
+ /// > Dart type as this method may perform many type-checks.
+ // TODO(srujzs): We likely need stronger tests for this method to ensure
+ // consistency.
external JSAny? jsify();
}
-/// The type of `JSUndefined` when returned from functions. Unlike pure JS,
-/// no actual object will be returned.
-// TODO(srujzs): Should we just remove this? There are no performance costs from
-// using `void`, and we'll likely provide a different way to box `undefined`.
-typedef JSVoid = JSVoidRepType;
-
-// 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(joshualitt): We might want to investigate using extension types instead
-// of extension methods for these methods.
-
-/// Conversion from [JSExportedDartFunction] to [Function].
-extension JSExportedDartFunctionToFunction on JSExportedDartFunction {
- external Function get toDart;
-}
-
-/// Conversion from [Function] to [JSExportedDartFunction].
-extension FunctionToJSExportedDartFunction on Function {
- external JSExportedDartFunction get toJS;
-}
-
/// 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 {
- // Take at most 4 args for consistency with other APIs and relative brevity.
- // If more are needed, you can declare your own external member. We rename
- // this function since declaring a `call` member makes a class callable in
- // Dart. This is convenient, but unlike Dart functions, JS 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.
+ /// 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]);
}
-/// Conversion from [JSBoxedDartObject] to [Object].
+// 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 wrapped Dart [Function].
+ 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.
+ external JSExportedDartFunction get toJS;
+}
+
+/// Conversions from [JSBoxedDartObject] to [Object].
extension JSBoxedDartObjectToObject on JSBoxedDartObject {
+ /// The Dart [Object] that this [JSBoxedDartObjectToObject] 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].
external JSBoxedDartObject get toJSBox;
}
-/// Conversion from [JSPromise] to [Future].
+/// 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) {
@@ -404,8 +515,15 @@
}
}
-/// Conversion from [Future] to [JSPromise].
+/// 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),
@@ -427,305 +545,558 @@
}
}
-// **WARNING**:
-// Currently, the `toJS` getters on `dart:typed_data` types have inconsistent
-// semantics today between dart2wasm and the JS compilers. dart2wasm copies the
-// contents over, while the JS compilers passes the typed arrays by reference as
-// they are JS typed arrays under the hood. Do not rely on modifications to the
-// Dart type to affect the JS type.
-//
-// All the `toDart` getters on the JS typed arrays will introduce a wrapper
-// around the JS typed array, however. So modifying the Dart type will modify
-// the JS type and vice versa in that case.
-
-/// Conversion from [JSArrayBuffer] to [ByteBuffer].
+/// 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;
}
-/// Conversion from [ByteBuffer] to [JSArrayBuffer].
+/// Conversions from [ByteBuffer] to [JSArrayBuffer].
extension ByteBufferToJSArrayBuffer on ByteBuffer {
+ /// Converts this [ByteBuffer] to a [JSArrayBuffer] by either casting,
+ /// unwrapping, or cloning the [ByteBuffer].
+ ///
+ /// > [!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 [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. 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;
}
-/// Conversion from [JSDataView] to [ByteData].
+/// 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;
}
-/// Conversion from [ByteData] to [JSDataView].
+/// 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;
}
-/// Conversion from [JSInt8Array] to [Int8List].
+/// 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;
}
-/// Conversion from [Int8List] to [JSInt8Array].
+/// 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;
}
-/// Conversion from [JSUint8Array] to [Uint8List].
+/// 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;
}
-/// Conversion from [Uint8List] to [JSUint8Array].
+/// 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;
}
-/// Conversion from [JSUint8ClampedArray] to [Uint8ClampedList].
+/// 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;
}
-/// Conversion from [Uint8ClampedList] to [JSUint8ClampedArray].
+/// 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;
}
-/// Conversion from [JSInt16Array] to [Int16List].
+/// 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;
}
-/// Conversion from [Int16List] to [JSInt16Array].
+/// 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;
}
-/// Conversion from [JSUint16Array] to [Uint16List].
+/// 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;
}
-/// Conversion from [Uint16List] to [JSUint16Array].
+/// 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;
}
-/// Conversion from [JSInt32Array] to [Int32List].
+/// 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;
}
-/// Conversion from [Int32List] to [JSInt32Array].
+/// 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;
}
-/// Conversion from [JSUint32Array] to [Uint32List].
+/// 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;
}
-/// Conversion from [Uint32List] to [JSUint32Array].
+/// 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;
}
-/// Conversion from [JSFloat32Array] to [Float32List].
+/// 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;
}
-/// Conversion from [Float32List] to [JSFloat32Array].
+/// 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;
}
-/// Conversion from [JSFloat64Array] to [Float64List].
+/// 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;
}
-/// Conversion from [Float64List] to [JSFloat64Array].
+/// 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;
}
-/// Conversion from [JSArray] to [List].
+/// Conversions from [JSArray] to [List].
extension JSArrayToList<T extends JSAny?> on JSArray<T> {
- /// Returns a list wrapper of the JS array.
+ /// Converts this [JSArray] to a [List] by either casting or wrapping it.
///
- /// Modifying the JS array will modify the returned list and vice versa.
+ /// 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;
}
-/// Conversion from [List] to [JSArray].
+/// Conversions from [List] to [JSArray].
extension ListToJSArray<T extends JSAny?> on List<T> {
- /// Compiler-specific conversion from list to JS array.
+ /// Converts this [List] to a [JSArray] by either casting, unwrapping, or
+ /// cloning the [List].
///
- /// This is either a pass-by-reference, unwrap, or copy depending on the
- /// implementation of the given list, and users shouldn't rely on
- /// modifications to the list to affect the array or vice versa.
+ /// > [!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;
- /// Either passes by reference, unwraps, or creates a heavyweight proxy that
- /// wraps the list.
+ /// Converts this [List] to a [JSArray] by either casting, unwrapping, or
+ /// proxying the [List].
///
- /// Only use this member if you want modifications to the list to also affect
- /// the JS array and vice versa. In practice, dart2js and DDC will pass lists
- /// by reference and dart2wasm will add a proxy or unwrap for most lists.
- ///
- /// **WARNING**: Do not rely on this to be performant.
+ /// > [!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;
}
-/// Conversion from [JSNumber] to [double] or [int].
+/// Conversions from [JSNumber] to [double] or [int].
extension JSNumberToNumber on JSNumber {
- /// Returns a Dart [double] for the given [JSNumber].
+ /// Converts this [JSNumber] to a [double].
external double get toDartDouble;
- /// Returns a Dart [int] for the given [JSNumber].
+ /// Converts this [JSNumber] to an [int].
///
- /// If the [JSNumber] is not an integer value, throws.
+ /// If this [JSNumber] is not an integer value, throws.
external int get toDartInt;
}
-/// Conversion from [double] to [JSNumber].
+/// Conversions from [double] to [JSNumber].
extension DoubleToJSNumber on double {
+ /// Converts this [double] to a [JSNumber].
external JSNumber get toJS;
}
-/// Conversion from [num] to [JSNumber].
+/// Conversions from [num] to [JSNumber].
extension NumToJSExtension on num {
+ /// Converts this [num] to a [JSNumber].
JSNumber get toJS => DoubleToJSNumber(toDouble()).toJS;
}
-/// Conversion from [JSBoolean] to [bool].
+/// Conversions from [JSBoolean] to [bool].
extension JSBooleanToBool on JSBoolean {
+ /// Converts this [JSBoolean] to a [bool].
external bool get toDart;
}
-/// Conversion from [bool] to [JSBoolean].
+/// Conversions from [bool] to [JSBoolean].
extension BoolToJSBoolean on bool {
+ /// Converts this [bool] to a [JSBoolean].
external JSBoolean get toJS;
}
-/// Conversion from [JSString] to [String].
+/// Conversions from [JSString] to [String].
extension JSStringToString on JSString {
+ /// Converts this [JSString] to a [String].
external String get toDart;
}
-/// Conversion from [String] to [JSString].
+/// Conversions from [String] to [JSString].
extension StringToJSString on String {
+ /// Converts this [String] to a [JSString].
external JSString get toJS;
}
-// General-purpose operators.
-//
-// Indexing operators (`[]`, `[]=`) should be declared through operator
-// overloading instead e.g. `external operator int [](int key);`.
+/// General-purpose JavaScript operators.
+///
+/// Indexing operators (`[]`, `[]=`) should be declared through operator
+/// overloading instead like:
+/// ```
+/// external operator int [](int key);
+/// ```
// 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.
- /// Returns the result of '[this] + [any]' in JS.
+ /// The result of <code>[this] + [any]</code> in JavaScript.
external JSAny add(JSAny? any);
- /// Returns the result of '[this] - [any]' in JS.
+ /// The result of <code>[this] - [any]</code> in JavaScript.
external JSAny subtract(JSAny? any);
- /// Returns the result of '[this] * [any]' in JS.
+ /// The result of <code>[this] * [any]</code> in JavaScript.
external JSAny multiply(JSAny? any);
- /// Returns the result of '[this] / [any]' in JS.
+ /// The result of <code>[this] / [any]</code> in JavaScript.
external JSAny divide(JSAny? any);
- /// Returns the result of '[this] % [any]' in JS.
+ /// The result of <code>[this] % [any]</code> in JavaScript.
external JSAny modulo(JSAny? any);
- /// Returns the result of '[this] ** [any]' in JS.
+ /// The result of <code>[this] ** [any]</code> in JavaScript.
external JSAny exponentiate(JSAny? any);
// Comparison operators.
- /// Returns the result of '[this] > [any]' in JS.
+ /// The result of <code>[this] > [any]</code> in JavaScript.
external JSBoolean greaterThan(JSAny? any);
- /// Returns the result of '[this] >= [any]' in JS.
+ /// The result of <code>[this] >= [any]</code> in JavaScript.
external JSBoolean greaterThanOrEqualTo(JSAny? any);
- /// Returns the result of '[this] < [any]' in JS.
+ /// The result of <code>[this] < [any]</code> in JavaScript.
external JSBoolean lessThan(JSAny? any);
- /// Returns the result of '[this] <= [any]' in JS.
+ /// The result of <code>[this] <= [any]</code> in JavaScript.
external JSBoolean lessThanOrEqualTo(JSAny? any);
- /// Returns the result of '[this] == [any]' in JS.
+ /// The result of <code>[this] == [any]</code> in JavaScript.
external JSBoolean equals(JSAny? any);
- /// Returns the result of '[this] != [any]' in JS.
+ /// The result of <code>[this] != [any]</code> in JavaScript.
external JSBoolean notEquals(JSAny? any);
- /// Returns the result of '[this] === [any]' in JS.
+ /// The result of <code>[this] === [any]</code> in JavaScript.
external JSBoolean strictEquals(JSAny? any);
- /// Returns the result of '[this] !== [any]' in JS.
+ /// The result of <code>[this] !== [any]</code> in JavaScript.
external JSBoolean strictNotEquals(JSAny? any);
// Bitwise operators.
- /// Returns the result of '[this] >>> [any]' in JS.
+ /// The result of <code>[this] >>> [any]</code> in JavaScript.
external JSNumber unsignedRightShift(JSAny? any);
// Logical operators.
- /// Returns the result of '[this] && [any]' in JS.
+ /// The result of <code>[this] && [any]</code> in JavaScript.
external JSAny? and(JSAny? any);
- /// Returns the result of '[this] || [any]' in JS.
+ /// The result of <code>[this] || [any]</code> in JavaScript.
external JSAny? or(JSAny? any);
- /// Returns the result of '![this]' in JS.
+ /// The result of <code>![this]</code> in JavaScript.
external bool get not;
- /// Returns the result of '!![this]' in JS.
+ /// The result of <code>!![this]</code> in JavaScript.
external bool get isTruthy;
}
-// Top-levels.
-
-/// Given a Dart object that is marked "exportable", creates a JS object that
-/// wraps the given Dart object. Look at the `@JSExport` annotation to determine
-/// what constitutes "exportable" for a Dart class. The object literal
-/// will be a map of export names (which are either the written instance member
-/// names or their rename) to their respective Dart instance members.
+/// The global scope that is used to find user-declared interop members.
///
/// For example:
///
-/// ```dart
-/// import 'dart:js_interop';
-///
-/// import 'package:expect/expect.dart';
-///
-/// @JSExport()
-/// class ExportCounter {
-/// @JSExport('value')
-/// int counterValue = 0;
-/// String stringify() => counterValue.toString();
-/// }
-///
-/// extension type Counter(JSObject _) {
-/// external int get value;
-/// external set value(int val);
-/// external String stringify();
-/// }
-///
-/// void main() {
-/// var export = ExportCounter();
-/// var counter = Counter(createJSInteropWrapper(export));
-/// export.counterValue = 1;
-/// Expect.equals(counter.value, export.counterValue);
-/// Expect.equals(counter.stringify(), export.stringify());
-/// }
/// ```
+/// @JS()
+/// external String get name;
+/// ```
+///
+/// Reading `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
@@ -738,8 +1109,8 @@
// external T createJSInteropMock<T extends JSObject, U extends Object>(
// U dartMock, [JSObject? proto = null]);
-/// Call to dynamically import a JS module with the given [moduleName] using the
-/// JS `import()` syntax.
+/// 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.
diff --git a/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart b/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
index 3eb4c1f..f5eb186 100644
--- a/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
+++ b/sdk/lib/js_interop_unsafe/js_interop_unsafe.dart
@@ -9,84 +9,100 @@
/// except the methods here are extension methods that use JS types. This
/// allows code using these functions to also be compiled to WebAssembly.
///
-/// In general, prefer to write JS interop interfaces and
-/// external static interop members using `dart:js_interop`. This library is
-/// meant to work around issues and help with migration from older JS interop
-/// libraries like `dart:js`.
+/// In general, prefer to write JS interop interfaces and external static
+/// interop members using `dart:js_interop`. This library is meant to work
+/// around issues and help with migration from older JS interop libraries.
///
-/// As the name suggests, usage of this library *can* be unsafe. This means that
-/// safe usage of these methods cannot necessarily be verified statically.
-/// Prefer using statically analyzable values like constants or literals for
-/// property or method names so that usage can be verified. This library should
-/// be used cautiously and only when the same effect cannot be achieved with
-/// static interop.
+/// > [!NOTE]
+/// > As the name suggests, usage of this library *can* be unsafe. This means
+/// > that safe usage of these methods cannot necessarily be verified
+/// > statically. Prefer using statically analyzable values like constants or
+/// > literals for property or method names so that usage can be verified. This
+/// > library should be used cautiously and only when the same effect cannot be
+/// > achieved with static interop.
///
/// {@category Web}
-library dart.js_interop_unsafe;
+library;
import 'dart:js_interop';
+/// Utility methods to check, get, set, and call properties on a [JSObject].
+///
+/// See the [JavaScript specification](https://tc39.es/ecma262/#sec-object-type)
+/// for more details on using properties.
extension JSObjectUnsafeUtilExtension on JSObject {
- /// Shorthand helper to check for String properties.
+ /// Shorthand helper for [hasProperty] to check whether this [JSObject]
+ /// contains the property key [property], but takes and returns a Dart value.
bool has(String property) => hasProperty(property.toJS).toDart;
- /// Whether or not this [JSObject] has a given property.
+ /// Whether or not this [JSObject] contains the property key [property].
external JSBoolean hasProperty(JSAny property);
- /// Shorthand helper to get String properties.
+ /// Shorthand helper for [getProperty] to get the value of the property key
+ /// [property] of this [JSObject], but takes and returns a Dart value.
JSAny? operator [](String property) => getProperty(property.toJS);
- /// Gets a given [property] from this [JSObject].
- external T getProperty<T extends JSAny?>(JSAny property);
+ /// The value of the property key [property] of this [JSObject].
+ external R getProperty<R extends JSAny?>(JSAny property);
- /// Shorthand helper to set String properties.
+ /// Shorthand helper for [setProperty] to write the [value] of the property
+ /// key [property] of this [JSObject], but takes a Dart value.
void operator []=(String property, JSAny? value) =>
setProperty(property.toJS, value);
- /// Sets a given [property] with [value] on this [JSObject].
+ /// Write the [value] of property key [property] of this [JSObject].
external void setProperty(JSAny property, JSAny? value);
external JSAny? _callMethod(JSAny method,
[JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]);
- /// Calls a method on this [JSObject] with up to four arguments and returns
- /// the result.
+ /// Calls [method] on this [JSObject] with up to four arguments.
///
- /// TODO(srujzs): This helper doesn't allow passing nulls, as we use null as
- /// our sentinel to determine if an argument is passed.
- T callMethod<T extends JSAny?>(JSAny method,
+ /// Returns the result of calling [method], which must be an [R].
+ ///
+ /// This helper doesn't allow passing nulls, as it determines whether an
+ /// argument is passed based on whether it was null or not. Prefer
+ /// [callMethodVarArgs] if you need to pass nulls.
+ R callMethod<R extends JSAny?>(JSAny method,
[JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
- _callMethod(method, arg1, arg2, arg3, arg4) as T;
+ _callMethod(method, arg1, arg2, arg3, arg4) as R;
external JSAny? _callMethodVarArgs(JSAny method, [List<JSAny?>? arguments]);
- /// Calls a method on this [JSObject] with a variable number of arguments and
- /// returns the result.
- T callMethodVarArgs<T extends JSAny?>(JSAny method,
+ /// Calls [method] on this [JSObject] with a variable number of [arguments].
+ ///
+ /// Returns the result of calling [method], which must be an [R].
+ R callMethodVarArgs<R extends JSAny?>(JSAny method,
[List<JSAny?>? arguments]) =>
- _callMethodVarArgs(method, arguments) as T;
+ _callMethodVarArgs(method, arguments) as R;
- /// Deletes the given property from this [JSObject].
+ /// Deletes the property with key [property] from this [JSObject].
external JSBoolean delete(JSAny property);
}
+/// Utility methods to call [JSFunction]s as constructors.
extension JSFunctionUnsafeUtilExtension on JSFunction {
external JSObject _callAsConstructor(
[JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]);
- /// Calls this [JSFunction] as a constructor with up to four arguments and
- /// returns the constructed [JSObject].
+ /// Calls this [JSFunction] as a constructor with up to four arguments.
///
- /// TODO(srujzs): This helper doesn't allow passing nulls, as we use null as
- /// our sentinel to determine if an argument is passed.
- T callAsConstructor<T>(
+ /// Returns the constructed object, which must be an [R].
+ ///
+ /// This helper doesn't allow passing nulls, as it determines whether an
+ /// argument is passed based on whether it was null or not. Prefer
+ /// [callAsConstructorVarArgs] if you need to pass nulls.
+ // TODO(srujzs): The type bound should extend `JSObject`.
+ R callAsConstructor<R>(
[JSAny? arg1, JSAny? arg2, JSAny? arg3, JSAny? arg4]) =>
- _callAsConstructor(arg1, arg2, arg3, arg4) as T;
+ _callAsConstructor(arg1, arg2, arg3, arg4) as R;
external JSObject _callAsConstructorVarArgs([List<JSAny?>? arguments]);
/// Calls this [JSFunction] as a constructor with a variable number of
- /// arguments and returns the constructed [JSObject].
- T callAsConstructorVarArgs<T extends JSObject>([List<JSAny?>? arguments]) =>
- _callAsConstructorVarArgs(arguments) as T;
+ /// arguments.
+ ///
+ /// Returns the constructed [JSObject], which must be an [R].
+ R callAsConstructorVarArgs<R extends JSObject>([List<JSAny?>? arguments]) =>
+ _callAsConstructorVarArgs(arguments) as R;
}