[dartdevc] simplify microtask scheduling using JS Promise

All of DDC's supported platforms have Promises, so we can use them
instead of MutationObservers (web) and timers (node.js).

See issue #20055 (same issue, but for dart2js).

Change-Id: Id635a4a9fa104a2ab19dd20824d209f682f831f9
Reviewed-on: https://dart-review.googlesource.com/c/91765
Reviewed-by: Mark Zhou <markzipan@google.com>
Commit-Queue: Jenny Messerly <jmesserly@google.com>
diff --git a/pkg/dev_compiler/test/sourcemap/ddc_common.dart b/pkg/dev_compiler/test/sourcemap/ddc_common.dart
index 3d187dd..437b008 100644
--- a/pkg/dev_compiler/test/sourcemap/ddc_common.dart
+++ b/pkg/dev_compiler/test/sourcemap/ddc_common.dart
@@ -134,15 +134,24 @@
     let global = new Function('return this;')();
     $d8Preambles
 
+    // d8 does not seem to print the `.stack` property like
+    // node.js and browsers do, so include that.
+    Error.prototype.toString = function() {
+      // Note: on d8, the stack property includes the error message too.
+      return this.stack;
+    };
+
+    global.scheduleImmediate = function(callback) {
+      // Ensure unhandled promise rejections get printed.
+      Promise.resolve(null).then(callback).catch(e => console.error(e));
+    };
+
     let main = $inputFileNameNoExt.main;
     dart.ignoreWhitelistedErrors(false);
     try {
       dartMainRunner(main, []);
     } catch(e) {
       console.error(e);
-      // d8 does not seem to print the `.stack` property like
-      // node.js and browsers do.
-      console.error(e.stack);
     }
     """;
 }
diff --git a/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart b/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart
index fe36fea..ca4ef7d 100644
--- a/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart
+++ b/pkg/dev_compiler/tool/input_sdk/patch/async_patch.dart
@@ -9,9 +9,6 @@
 import 'dart:_foreign_helper' show JS, JSExportName;
 import 'dart:_runtime' as dart;
 
-typedef void _Callback();
-typedef void _TakeCallback(_Callback callback);
-
 /// This function adapts ES6 generators to implement Dart's async/await.
 ///
 /// It's designed to interact with Dart's Future and follow Dart async/await
@@ -135,77 +132,39 @@
 @patch
 class _AsyncRun {
   @patch
-  static void _scheduleImmediate(void callback()) {
+  static void _scheduleImmediate(void Function() callback) {
     _scheduleImmediateClosure(callback);
   }
 
   // Lazily initialized.
-  static final _TakeCallback _scheduleImmediateClosure =
-      _initializeScheduleImmediate();
+  static final _scheduleImmediateClosure = _initializeScheduleImmediate();
 
-  static _TakeCallback _initializeScheduleImmediate() {
-    // TODO(rnystrom): Not needed by dev_compiler.
-    // requiresPreamble();
+  static void Function(void Function()) _initializeScheduleImmediate() {
+    // d8 support, see preambles/d8.js for the definiton of `scheduleImmediate`.
+    //
+    // TODO(jmesserly): do we need this? It's only for our d8 stack trace test.
     if (JS('', '#.scheduleImmediate', dart.global_) != null) {
-      return _scheduleImmediateJsOverride;
+      return _scheduleImmediateJSOverride;
     }
-    if (JS('', '#.MutationObserver', dart.global_) != null &&
-        JS('', '#.document', dart.global_) != null) {
-      // Use mutationObservers.
-      var div = JS('', '#.document.createElement("div")', dart.global_);
-      var span = JS('', '#.document.createElement("span")', dart.global_);
-      _Callback storedCallback;
-
-      internalCallback(_) {
-        var f = storedCallback;
-        storedCallback = null;
-        dart.removeAsyncCallback();
-        f();
-      }
-
-      var observer =
-          JS('', 'new #.MutationObserver(#)', dart.global_, internalCallback);
-      JS('', '#.observe(#, { childList: true })', observer, div);
-
-      return (void callback()) {
-        assert(storedCallback == null);
-        dart.addAsyncCallback();
-        storedCallback = callback;
-        // Because of a broken shadow-dom polyfill we have to change the
-        // children instead a cheap property.
-        // See https://github.com/Polymer/ShadowDOM/issues/468
-        JS('', '#.firstChild ? #.removeChild(#): #.appendChild(#)', div, div,
-            span, div, span);
-      };
-    } else if (JS('', '#.setImmediate', dart.global_) != null) {
-      return _scheduleImmediateWithSetImmediate;
-    }
-    // TODO(20055): We should use DOM promises when available.
-    return _scheduleImmediateWithTimer;
+    return _scheduleImmediateWithPromise;
   }
 
-  static void _scheduleImmediateJsOverride(void callback()) {
-    internalCallback() {
+  @ReifyFunctionTypes(false)
+  static void _scheduleImmediateJSOverride(void Function() callback) {
+    dart.addAsyncCallback();
+    JS('void', '#.scheduleImmediate(#)', dart.global_, () {
       dart.removeAsyncCallback();
       callback();
-    }
-
-    dart.addAsyncCallback();
-    JS('void', '#.scheduleImmediate(#)', dart.global_, internalCallback);
+    });
   }
 
-  static void _scheduleImmediateWithSetImmediate(void callback()) {
-    internalCallback() {
+  @ReifyFunctionTypes(false)
+  static Object _scheduleImmediateWithPromise(void Function() callback) {
+    dart.addAsyncCallback();
+    JS('', '#.Promise.resolve(null).then(#)', dart.global_, () {
       dart.removeAsyncCallback();
       callback();
-    }
-
-    dart.addAsyncCallback();
-    JS('void', '#.setImmediate(#)', dart.global_, internalCallback);
-  }
-
-  static void _scheduleImmediateWithTimer(void callback()) {
-    Timer._createTimer(Duration.zero, callback);
+    });
   }
 }
 
diff --git a/pkg/expect/lib/async_minitest.dart b/pkg/expect/lib/async_minitest.dart
index 6d135bc..40c69da 100644
--- a/pkg/expect/lib/async_minitest.dart
+++ b/pkg/expect/lib/async_minitest.dart
@@ -56,7 +56,7 @@
   _popName(oldName);
 }
 
-void expect(Object value, Object matcher) {
+void expect(Object value, Object matcher, {String reason}) {
   Matcher m;
   if (matcher is _Matcher) {
     m = matcher.call;
@@ -260,6 +260,10 @@
   Expect.type<List>(o);
 }
 
+void isNotNull(Object o) {
+  Expect.isNotNull(o);
+}
+
 abstract class _Matcher {
   void call(Object o);
 }
diff --git a/tests/lib_2/html/websql_test.dart b/tests/lib_2/html/websql_test.dart
index 1d52d5d..07f9ff2 100644
--- a/tests/lib_2/html/websql_test.dart
+++ b/tests/lib_2/html/websql_test.dart
@@ -4,9 +4,7 @@
 import 'dart:html';
 import 'dart:web_sql';
 
-import 'package:unittest/unittest.dart';
-import 'package:unittest/html_config.dart';
-import 'package:async_helper/async_helper.dart';
+import 'package:expect/async_minitest.dart';
 
 Future<SqlResultSet> createTable(
     SqlTransaction transaction, String tableName, String columnName) async {
@@ -52,8 +50,6 @@
 }
 
 main() async {
-  useHtmlConfiguration();
-
   await setup();
 
   group('Database', () {
diff --git a/tools/testing/dart/test_controller.js b/tools/testing/dart/test_controller.js
index 8a82fce..8515ddf 100644
--- a/tools/testing/dart/test_controller.js
+++ b/tools/testing/dart/test_controller.js
@@ -103,6 +103,17 @@
   notifyDone('FAIL');
 };
 
+window.onunhandledrejection = function (e) {
+  var reason = e.reason != null ? e.reason.stack : null;
+  var message = ('window.onunhandledrejection called: \n\n' + reason + '\n\n');
+  if (testExpectsGlobalError) {
+    testSuppressedGlobalErrors.push({message: message});
+    return;
+  }
+  recordEvent('window_onerror', message);
+  notifyDone('FAIL');
+};
+
 var waitForDone = false;
 
 var driverWindowCached = false;