blob: 4888baf604b0540f72555095a4010d4db91c6364 [file] [log] [blame]
// Copyright (c) 2012, 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.
// Patch file for the dart:async library.
import 'dart:_js_helper'
show
patch,
ExceptionAndStackTrace,
convertDartClosureToJS,
getTraceFromException,
requiresPreamble,
wrapException,
unwrapException;
import 'dart:_foreign_helper' show JS, JS_GET_FLAG;
import 'dart:_async_await_error_codes' as async_error_codes;
import "dart:collection" show IterableBase;
@patch
class _AsyncRun {
@patch
static void _scheduleImmediate(void callback()) {
_scheduleImmediateClosure(callback);
}
// Lazily initialized.
static final Function _scheduleImmediateClosure =
_initializeScheduleImmediate();
static Function _initializeScheduleImmediate() {
requiresPreamble();
if (JS('', 'self.scheduleImmediate') != null) {
return _scheduleImmediateJsOverride;
}
if (JS('', 'self.MutationObserver') != null &&
JS('', 'self.document') != null) {
// Use mutationObservers.
var div = JS('', 'self.document.createElement("div")');
var span = JS('', 'self.document.createElement("span")');
void Function()? storedCallback;
internalCallback(_) {
var f = storedCallback;
storedCallback = null;
f!();
}
var observer = JS('', 'new self.MutationObserver(#)',
convertDartClosureToJS(internalCallback, 1));
JS('', '#.observe(#, { childList: true })', observer, div);
return (void callback()) {
assert(storedCallback == null);
storedCallback = callback;
// Because of a broken shadow-dom polyfill we have to change the
// children instead a cheap property.
JS('', '#.firstChild ? #.removeChild(#): #.appendChild(#)', div, div,
span, div, span);
};
} else if (JS('', 'self.setImmediate') != null) {
return _scheduleImmediateWithSetImmediate;
}
// TODO(20055): We should use DOM promises when available.
return _scheduleImmediateWithTimer;
}
static void _scheduleImmediateJsOverride(void callback()) {
internalCallback() {
callback();
}
JS('void', 'self.scheduleImmediate(#)',
convertDartClosureToJS(internalCallback, 0));
}
static void _scheduleImmediateWithSetImmediate(void callback()) {
internalCallback() {
callback();
}
JS('void', 'self.setImmediate(#)',
convertDartClosureToJS(internalCallback, 0));
}
static void _scheduleImmediateWithTimer(void callback()) {
Timer._createTimer(Duration.zero, callback);
}
}
@patch
class DeferredLibrary {
@patch
Future<Null> load() {
throw 'DeferredLibrary not supported. '
'please use the `import "lib.dart" deferred as lib` syntax.';
}
}
@patch
class Timer {
@patch
static Timer _createTimer(Duration duration, void callback()) {
int milliseconds = duration.inMilliseconds;
if (milliseconds < 0) milliseconds = 0;
return new _TimerImpl(milliseconds, callback);
}
@patch
static Timer _createPeriodicTimer(
Duration duration, void callback(Timer timer)) {
int milliseconds = duration.inMilliseconds;
if (milliseconds < 0) milliseconds = 0;
return new _TimerImpl.periodic(milliseconds, callback);
}
}
class _TimerImpl implements Timer {
final bool _once;
int? _handle;
int _tick = 0;
_TimerImpl(int milliseconds, void callback()) : _once = true {
if (_hasTimer()) {
void internalCallback() {
_handle = null;
this._tick = 1;
callback();
}
_handle = JS('int', 'self.setTimeout(#, #)',
convertDartClosureToJS(internalCallback, 0), milliseconds);
} else {
throw new UnsupportedError('`setTimeout()` not found.');
}
}
_TimerImpl.periodic(int milliseconds, void callback(Timer timer))
: _once = false {
if (_hasTimer()) {
int start = JS('int', 'Date.now()');
_handle = JS(
'int',
'self.setInterval(#, #)',
convertDartClosureToJS(() {
int tick = this._tick + 1;
if (milliseconds > 0) {
int end = JS('int', 'Date.now()');
int duration = end - start;
if (duration > (tick + 1) * milliseconds) {
tick = duration ~/ milliseconds;
}
}
this._tick = tick;
callback(this);
}, 0),
milliseconds);
} else {
throw new UnsupportedError('Periodic timer.');
}
}
@override
bool get isActive => _handle != null;
@override
int get tick => _tick;
@override
void cancel() {
if (_hasTimer()) {
if (_handle == null) return;
if (_once) {
JS('void', 'self.clearTimeout(#)', _handle);
} else {
JS('void', 'self.clearInterval(#)', _handle);
}
_handle = null;
} else {
throw new UnsupportedError('Canceling a timer.');
}
}
}
bool _hasTimer() {
requiresPreamble();
return JS('', 'self.setTimeout') != null;
}
class _AsyncAwaitCompleter<T> implements Completer<T> {
final _future = new _Future<T>();
bool isSync;
_AsyncAwaitCompleter() : isSync = false;
void complete([FutureOr<T>? value]) {
// All paths require that if value is null, null as T succeeds.
value = (value == null) ? value as T : value;
if (!isSync) {
_future._asyncComplete(value);
} else if (value is Future<T>) {
assert(!_future._isComplete);
_future._chainFuture(value);
} else {
// TODO(40014): Remove cast when type promotion works.
// This would normally be `as T` but we use `as dynamic` to make the
// unneeded check be implicit to match dart2js unsound optimizations in
// the user code.
_future._completeWithValue(value as dynamic);
}
}
void completeError(Object e, [StackTrace? st]) {
st ??= AsyncError.defaultStackTrace(e);
if (isSync) {
_future._completeError(e, st);
} else {
_future._asyncCompleteError(e, st);
}
}
Future<T> get future => _future;
bool get isCompleted => !_future._mayComplete;
}
/// Creates a Completer for an `async` function.
///
/// Used as part of the runtime support for the async/await transformation.
@pragma('dart2js:assumeDynamic') // Global type inference can't see call site.
Completer<T> _makeAsyncAwaitCompleter<T>() {
return new _AsyncAwaitCompleter<T>();
}
/// Initiates the computation of an `async` function and starts the body
/// synchronously.
///
/// Used as part of the runtime support for the async/await transformation.
///
/// This function sets up the first call into the transformed [bodyFunction].
/// Independently, it takes the [completer] and returns the future of the
/// completer for convenience of the transformed code.
dynamic _asyncStartSync(
_WrappedAsyncBody bodyFunction, _AsyncAwaitCompleter completer) {
bodyFunction(async_error_codes.SUCCESS, null);
completer.isSync = true;
return completer.future;
}
/// Performs the `await` operation of an `async` function.
///
/// Used as part of the runtime support for the async/await transformation.
///
/// Arranges for [bodyFunction] to be called when the future or value [object]
/// is completed with a code [async_error_codes.SUCCESS] or
/// [async_error_codes.ERROR] depending on the success of the future.
dynamic _asyncAwait(dynamic object, _WrappedAsyncBody bodyFunction) {
_awaitOnObject(object, bodyFunction);
}
/// Completes the future of an `async` function.
///
/// Used as part of the runtime support for the async/await transformation.
///
/// This function is used when the `async` function returns (explicitly or
/// implicitly).
dynamic _asyncReturn(dynamic object, Completer completer) {
completer.complete(object);
}
/// Completes the future of an `async` function with an error.
///
/// Used as part of the runtime support for the async/await transformation.
///
/// This function is used when the `async` function re-throws an exception.
dynamic _asyncRethrow(dynamic object, Completer completer) {
// The error is a js-error.
completer.completeError(
unwrapException(object), getTraceFromException(object));
}
/// Awaits on the given [object].
///
/// If the [object] is a Future, registers on it, otherwise wraps it into a
/// future first.
///
/// The [bodyFunction] argument is the continuation that should be invoked
/// when the future completes.
void _awaitOnObject(object, _WrappedAsyncBody bodyFunction) {
FutureOr<dynamic> Function(dynamic) thenCallback =
(result) => bodyFunction(async_error_codes.SUCCESS, result);
Function errorCallback = (dynamic error, StackTrace stackTrace) {
ExceptionAndStackTrace wrappedException =
new ExceptionAndStackTrace(error, stackTrace);
bodyFunction(async_error_codes.ERROR, wrappedException);
};
if (object is _Future) {
// We can skip the zone registration, since the bodyFunction is already
// registered (see [_wrapJsFunctionForAsync]).
object._thenAwait(thenCallback, errorCallback);
} else if (object is Future) {
object.then(thenCallback, onError: errorCallback);
} else {
_Future future = new _Future().._setValue(object);
// We can skip the zone registration, since the bodyFunction is already
// registered (see [_wrapJsFunctionForAsync]).
future._thenAwait(thenCallback, errorCallback);
}
}
typedef void _WrappedAsyncBody(int errorCode, dynamic result);
_WrappedAsyncBody _wrapJsFunctionForAsync(dynamic /* js function */ function) {
var protected = JS(
'',
"""
(function (fn, ERROR) {
// Invokes [function] with [errorCode] and [result].
//
// If (and as long as) the invocation throws, calls [function] again,
// with an error-code.
return function(errorCode, result) {
while (true) {
try {
fn(errorCode, result);
break;
} catch (error) {
result = error;
errorCode = ERROR;
}
}
}
})(#, #)""",
function,
async_error_codes.ERROR);
return Zone.current.registerBinaryCallback((int errorCode, dynamic result) {
JS('', '#(#, #)', protected, errorCode, result);
});
}
/// Implements the runtime support for async* functions.
///
/// Called by the transformed function for each original return, await, yield,
/// yield* and before starting the function.
///
/// When the async* function wants to return it calls this function with
/// [asyncBody] == [async_error_codes.SUCCESS], the asyncStarHelper takes this
/// as signal to close the stream.
///
/// When the async* function wants to signal that an uncaught error was thrown,
/// it calls this function with [asyncBody] == [async_error_codes.ERROR],
/// the streamHelper takes this as signal to addError [object] to the
/// [controller] and close it.
///
/// If the async* function wants to do a yield or yield*, it calls this function
/// with [object] being an [IterationMarker].
///
/// In the case of a yield or yield*, if the stream subscription has been
/// canceled, schedules [asyncBody] to be called with
/// [async_error_codes.STREAM_WAS_CANCELED].
///
/// If [object] is a single-yield [IterationMarker], adds the value of the
/// [IterationMarker] to the stream. If the stream subscription has been
/// paused, return early. Otherwise schedule the helper function to be
/// executed again.
///
/// If [object] is a yield-star [IterationMarker], starts listening to the
/// yielded stream, and adds all events and errors to our own controller (taking
/// care if the subscription has been paused or canceled) - when the sub-stream
/// is done, schedules [asyncBody] again.
///
/// If the async* function wants to do an await it calls this function with
/// [object] not an [IterationMarker].
///
/// If [object] is not a [Future], it is wrapped in a `Future.value`.
/// The [asyncBody] is called on completion of the future (see [asyncHelper].
void _asyncStarHelper(
dynamic object,
dynamic /* int | _WrappedAsyncBody */ bodyFunctionOrErrorCode,
_AsyncStarStreamController controller) {
if (identical(bodyFunctionOrErrorCode, async_error_codes.SUCCESS)) {
// This happens on return from the async* function.
if (controller.isCanceled) {
controller.cancelationFuture!._completeWithValue(null);
} else {
controller.close();
}
return;
} else if (identical(bodyFunctionOrErrorCode, async_error_codes.ERROR)) {
// The error is a js-error.
if (controller.isCanceled) {
controller.cancelationFuture!._completeError(
unwrapException(object), getTraceFromException(object));
} else {
controller.addError(
unwrapException(object), getTraceFromException(object));
controller.close();
}
return;
}
_WrappedAsyncBody bodyFunction = bodyFunctionOrErrorCode;
if (object is _IterationMarker) {
if (controller.isCanceled) {
bodyFunction(async_error_codes.STREAM_WAS_CANCELED, null);
return;
}
if (object.state == _IterationMarker.YIELD_SINGLE) {
controller.add(object.value);
scheduleMicrotask(() {
if (controller.isPaused) {
// We only suspend the thread inside the microtask in order to allow
// listeners on the output stream to pause in response to the just
// output value, and have the stream immediately stop producing.
controller.isSuspended = true;
return;
}
bodyFunction(async_error_codes.SUCCESS, null);
});
return;
} else if (object.state == _IterationMarker.YIELD_STAR) {
Stream stream = object.value;
// Errors of [stream] are passed though to the main stream. (see
// [AsyncStreamController.addStream]).
// TODO(sigurdm): The spec is not very clear here. Clarify with Gilad.
controller.addStream(stream).then((_) {
// No check for isPaused here because the spec 17.16.2 only
// demands checks *before* each element in [stream] not after the last
// one. On the other hand we check for isCanceled, as that check happens
// after insertion of each element.
int errorCode = controller.isCanceled
? async_error_codes.STREAM_WAS_CANCELED
: async_error_codes.SUCCESS;
bodyFunction(errorCode, null);
});
return;
}
}
_awaitOnObject(object, bodyFunction);
}
Stream _streamOfController(_AsyncStarStreamController controller) {
return controller.stream;
}
/// A wrapper around a [StreamController] that keeps track of the state of
/// the execution of an async* function.
/// It can be in 1 of 3 states:
///
/// - running/scheduled
/// - suspended
/// - canceled
///
/// If yielding while the subscription is paused it will become suspended. And
/// only resume after the subscription is resumed or canceled.
class _AsyncStarStreamController<T> {
late StreamController<T> controller;
Stream get stream => controller.stream;
/// True when the async* function has yielded while being paused.
/// When true execution will only resume after a `onResume` or `onCancel`
/// event.
bool isSuspended = false;
bool get isPaused => controller.isPaused;
_Future? cancelationFuture = null;
/// True after the StreamSubscription has been cancelled.
/// When this is true, errors thrown from the async* body should go to the
/// [cancelationFuture] instead of adding them to [controller], and
/// returning from the async function should complete [cancelationFuture].
bool get isCanceled => cancelationFuture != null;
add(event) => controller.add(event);
addStream(Stream<T> stream) {
return controller.addStream(stream, cancelOnError: false);
}
addError(error, stackTrace) => controller.addError(error, stackTrace);
close() => controller.close();
_AsyncStarStreamController(_WrappedAsyncBody body) {
_resumeBody() {
scheduleMicrotask(() {
body(async_error_codes.SUCCESS, null);
});
}
controller = new StreamController<T>(onListen: () {
_resumeBody();
}, onResume: () {
// Only schedule again if the async* function actually is suspended.
// Resume directly instead of scheduling, so that the sequence
// `pause-resume-pause` will result in one extra event produced.
if (isSuspended) {
isSuspended = false;
_resumeBody();
}
}, onCancel: () {
// If the async* is finished we ignore cancel events.
if (!controller.isClosed) {
cancelationFuture = new _Future();
if (isSuspended) {
// Resume the suspended async* function to run finalizers.
isSuspended = false;
scheduleMicrotask(() {
body(async_error_codes.STREAM_WAS_CANCELED, null);
});
}
return cancelationFuture;
}
});
}
}
/// Creates a stream controller for an `async*` function.
///
/// Used as part of the runtime support for the async/await transformation.
@pragma('dart2js:assumeDynamic') // Global type inference can't see call site.
_makeAsyncStarStreamController<T>(_WrappedAsyncBody body) {
return new _AsyncStarStreamController<T>(body);
}
class _IterationMarker {
static const YIELD_SINGLE = 0;
static const YIELD_STAR = 1;
static const ITERATION_ENDED = 2;
static const UNCAUGHT_ERROR = 3;
final value;
final int state;
const _IterationMarker._(this.state, this.value);
static yieldStar(dynamic /* Iterable or Stream */ values) {
return new _IterationMarker._(YIELD_STAR, values);
}
static endOfIteration() {
return const _IterationMarker._(ITERATION_ENDED, null);
}
static yieldSingle(dynamic value) {
return new _IterationMarker._(YIELD_SINGLE, value);
}
static uncaughtError(dynamic error) {
return new _IterationMarker._(UNCAUGHT_ERROR, error);
}
toString() => "IterationMarker($state, $value)";
}
class _SyncStarIterator<T> implements Iterator<T> {
// _SyncStarIterator handles stepping a sync* generator body state machine.
//
// It also handles the stepping over 'nested' iterators to flatten yield*
// statements. For non-sync* iterators, [_nestedIterator] contains the
// iterator. We delegate to [_nestedIterator] when it is not `null`.
//
// For nested sync* iterators, [this] iterator acts on behalf of the innermost
// nested sync* iterator. The current state machine is suspended on a stack
// until the inner state machine ends.
// The state machine for the innermost _SyncStarIterator.
dynamic _body;
// The current value, unless iterating a non-sync* nested iterator.
T? _current = null;
// This is the nested iterator when iterating a yield* of a non-sync iterator.
Iterator<T>? _nestedIterator = null;
// Stack of suspended state machines when iterating a yield* of a sync*
// iterator.
List? _suspendedBodies = null;
_SyncStarIterator(this._body);
T get current {
var nested = _nestedIterator;
if (nested == null) return _current as dynamic; // implicit: as T;
return nested.current;
}
_runBody() {
// TODO(sra): Find a way to hard-wire SUCCESS and ERROR codes.
return JS(
'',
'''
// Invokes [body] with [errorCode] and [result].
//
// If (and as long as) the invocation throws, calls [function] again,
// with an error-code.
(function(body, SUCCESS, ERROR) {
var errorValue, errorCode = SUCCESS;
while (true) {
try {
return body(errorCode, errorValue);
} catch (error) {
errorValue = error;
errorCode = ERROR;
}
}
})(#, #, #)''',
_body,
async_error_codes.SUCCESS,
async_error_codes.ERROR);
}
bool moveNext() {
while (true) {
if (_nestedIterator != null) {
if (_nestedIterator!.moveNext()) {
return true;
} else {
_nestedIterator = null;
}
}
var value = _runBody();
if (value is _IterationMarker) {
int state = value.state;
if (state == _IterationMarker.ITERATION_ENDED) {
var suspendedBodies = _suspendedBodies;
if (suspendedBodies == null || suspendedBodies.isEmpty) {
_current = null;
// Rely on [_body] to repeatedly return `ITERATION_ENDED`.
return false;
}
// Resume the innermost suspended iterator.
_body = suspendedBodies.removeLast();
continue;
} else if (state == _IterationMarker.UNCAUGHT_ERROR) {
// Rely on [_body] to repeatedly return `UNCAUGHT_ERROR`.
// This is a wrapped exception, so we use JavaScript throw to throw
// it.
JS('', 'throw #', value.value);
} else {
assert(state == _IterationMarker.YIELD_STAR);
Iterator<T> inner = value.value.iterator;
if (inner is _SyncStarIterator) {
// The test needs to be 'is _SyncStarIterator<T>' for promotion to
// work. However, that test is much more expensive, so we use an
// unsafe cast.
_SyncStarIterator<T> innerSyncStarIterator = JS('', '#', inner);
// Suspend the current state machine and start acting on behalf of
// the nested state machine.
//
// TODO(sra): Recognize "tail yield*" statements and avoid
// suspending the current body when all it will do is step without
// effect to ITERATION_ENDED.
(_suspendedBodies ??= []).add(_body);
_body = innerSyncStarIterator._body;
continue;
} else {
_nestedIterator = inner;
// TODO(32956): Change to the following when strong-mode is the only
// option:
//
// _nestedIterator = JS<Iterator<T>>('','#', inner);
continue;
}
}
} else {
// TODO(32956): Remove this test.
_current = JS<T>('', '#', value);
return true;
}
}
return false; // TODO(sra): Fix type inference so that this is not needed.
}
}
/// Creates an Iterable for a `sync*` function.
///
/// Used as part of the runtime support for the async/await transformation.
@pragma('dart2js:assumeDynamic') // Global type inference can't see call site.
_SyncStarIterable<T> _makeSyncStarIterable<T>(body) {
return new _SyncStarIterable<T>(body);
}
/// An Iterable corresponding to a sync* method.
///
/// Each invocation of a sync* method will return a new instance of this class.
class _SyncStarIterable<T> extends IterableBase<T> {
// This is a function that will return a helper function that does the
// iteration of the sync*.
//
// Each invocation should give a body with fresh state.
final dynamic /* js function */ _outerHelper;
_SyncStarIterable(this._outerHelper);
Iterator<T> get iterator =>
new _SyncStarIterator<T>(JS('', '#()', _outerHelper));
}