Added support for members of type Promise.

Fixes #31046

R=sra@google.com

Change-Id: Id1e8d415b91a842bd8e2f50c6064c24aaaed9d98
Reviewed-on: https://dart-review.googlesource.com/59460
Reviewed-by: Stephen Adams <sra@google.com>
Commit-Queue: Terry Lucas <terry@google.com>
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index 843e633..ee8e653 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -870,7 +870,8 @@
   @DomName('Animation.finished')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future finished;
+  Future<Animation> get finished =>
+      promiseToFuture<Animation>(JS("", "#.finished", this));
 
   @DomName('Animation.id')
   @DocsEditable()
@@ -890,7 +891,8 @@
   @DomName('Animation.ready')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future ready;
+  Future<Animation> get ready =>
+      promiseToFuture<Animation>(JS("", "#.ready", this));
 
   @DomName('Animation.startTime')
   @DocsEditable()
@@ -2118,7 +2120,8 @@
   @DomName('BeforeInstallPromptEvent.userChoice')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future userChoice;
+  Future<Map<String, dynamic>> get userChoice =>
+      promiseToFutureAsMap(JS("", "#.userChoice", this));
 
   @DomName('BeforeInstallPromptEvent.prompt')
   @DocsEditable()
@@ -19387,7 +19390,8 @@
   @DomName('FetchEvent.preloadResponse')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future preloadResponse;
+  Future get preloadResponse =>
+      promiseToFuture(JS("", "#.preloadResponse", this));
 
   @DomName('FetchEvent.request')
   @DocsEditable()
@@ -20108,7 +20112,8 @@
   @DomName('FontFace.loaded')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future loaded;
+  Future<FontFace> get loaded =>
+      promiseToFuture<FontFace>(JS("", "#.loaded", this));
 
   @DomName('FontFace.status')
   @DocsEditable()
@@ -25820,7 +25825,7 @@
   @DomName('MediaKeySession.closed')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future closed;
+  Future<void> get closed => promiseToFuture<void>(JS("", "#.closed", this));
 
   @DomName('MediaKeySession.expiration')
   @DocsEditable()
@@ -33137,7 +33142,9 @@
   @DomName('PresentationReceiver.connectionList')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future connectionList;
+  Future<PresentationConnectionList> get connectionList =>
+      promiseToFuture<PresentationConnectionList>(
+          JS("", "#.connectionList", this));
 }
 // 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
@@ -33330,7 +33337,7 @@
   @DomName('PromiseRejectionEvent.promise')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future promise;
+  Future get promise => promiseToFuture(JS("", "#.promise", this));
 
   @DomName('PromiseRejectionEvent.reason')
   @DocsEditable()
@@ -36013,7 +36020,8 @@
   @DomName('ServiceWorkerContainer.ready')
   @DocsEditable()
   @Experimental() // untriaged
-  final Future ready;
+  Future<ServiceWorkerRegistration> get ready =>
+      promiseToFuture<ServiceWorkerRegistration>(JS("", "#.ready", this));
 
   @DomName('ServiceWorkerContainer.getRegistration')
   @DocsEditable()
diff --git a/tests/html/html.status b/tests/html/html.status
index 9fe79e4..f83e0c7 100644
--- a/tests/html/html.status
+++ b/tests/html/html.status
@@ -401,7 +401,6 @@
 
 [ $compiler == dart2js && $browser ]
 custom/created_callback_test: Fail # Support for created constructor. Issue 14835
-fontface_loaded_test: Fail # Support for promises.
 
 [ $compiler == dart2js && $browser && $csp ]
 custom/element_upgrade_test: Fail # Issue 17298
diff --git a/tests/lib_2/html/fontface_loaded_test.dart b/tests/lib_2/html/fontface_loaded_test.dart
index a9334b6..a4670e7 100644
--- a/tests/lib_2/html/fontface_loaded_test.dart
+++ b/tests/lib_2/html/fontface_loaded_test.dart
@@ -32,22 +32,28 @@
       treeSanitizer: new NullTreeSanitizer());
   document.head.append(style);
 
-  test('document fonts - temporary', () {
+  test('document fonts - temporary', () async {
     var atLeastOneFont = false;
-    var loaded = <Future>[];
-    document.fonts.forEach((FontFace fontFace, _, __) {
+    var loaded = <Future<FontFace>>[];
+    document.fonts.forEach((FontFace fontFace, _, __) async {
       atLeastOneFont = true;
-      Future f1 = fontFace.loaded;
-      Future f2 = fontFace.loaded;
+      var f1 = fontFace.loaded;
+      var f2 = fontFace.loaded;
       loaded.add(fontFace.load());
       loaded.add(f1);
       loaded.add(f2);
     });
     expect(atLeastOneFont, isTrue);
-    return Future.wait(loaded).then(expectAsync((_) {
+    return Future.wait(loaded).then(expectAsync((_) async {
       document.fonts.forEach((fontFace, _, __) {
         expect(fontFace.status, 'loaded');
       });
+      expect(loaded.length, 3);
+      for (var loadedEntry in loaded) {
+        var fontFace = await loadedEntry;
+        expect(fontFace.status, 'loaded');
+        expect(fontFace.family, 'Ahem');
+      }
     }));
   });
 }
diff --git a/tests/lib_2/lib_2_dart2js.status b/tests/lib_2/lib_2_dart2js.status
index e3b55be..695151b 100644
--- a/tests/lib_2/lib_2_dart2js.status
+++ b/tests/lib_2/lib_2_dart2js.status
@@ -186,6 +186,7 @@
 html/fileapi_supported_throws_test: RuntimeError
 html/filereader_test: RuntimeError
 html/filteredelementlist_test: RuntimeError
+html/fontface_loaded_test: RuntimeError
 html/fontface_test: RuntimeError
 html/form_data_test: RuntimeError
 html/form_element_test: RuntimeError
@@ -339,7 +340,6 @@
 collection/list_test: RuntimeError
 
 [ $compiler == dart2js && $runtime != d8 && $runtime != jsshell ]
-html/fontface_loaded_test: RuntimeError
 html/html_mock_test: RuntimeError # Issue 31038
 html/input_element_attributes_test: RuntimeError
 html/js_dart_functions_test: RuntimeError # does not implement Dart 2 implicit `.call` tearoff
@@ -474,7 +474,6 @@
 
 [ $compiler == dart2js && $browser ]
 html/custom/created_callback_test: RuntimeError
-html/fontface_loaded_test: Fail # Support for promises.
 html/js_typed_interop_lazy_test/01: RuntimeError
 html/notification_permission_test: Timeout, Pass # Issue 32002
 html/private_extension_member_test: RuntimeError
@@ -688,7 +687,6 @@
 [ $compiler == dart2js && $fasta && $host_checked ]
 html/custom/mirrors_2_test: Crash # 'file:*/pkg/compiler/lib/src/common_elements.dart': Failed assertion: line 405 pos 12: 'element.name == '=='': is not true.
 html/custom/mirrors_test: Crash # 'file:*/pkg/compiler/lib/src/common_elements.dart': Failed assertion: line 405 pos 12: 'element.name == '=='': is not true.
-html/fontface_loaded_test: RuntimeError # TypeError: Cannot read property 'createFragment$3$treeSanitizer$validator' of undefined
 html/indexeddb_3_test: Crash # 'file:*/pkg/compiler/lib/src/common_elements.dart': Failed assertion: line 405 pos 12: 'element.name == '=='': is not true.
 html/indexeddb_5_test: Crash # 'file:*/pkg/compiler/lib/src/common_elements.dart': Failed assertion: line 405 pos 12: 'element.name == '=='': is not true.
 html/js_array_test: CompileTimeError
@@ -725,7 +723,6 @@
 html/event_customevent_test: RuntimeError
 html/file_sample_test: RuntimeError
 html/fileapi_entry_test: RuntimeError
-html/fontface_loaded_test: RuntimeError
 html/indexeddb_1_test/functional: RuntimeError
 html/indexeddb_2_test: RuntimeError
 html/indexeddb_3_test: RuntimeError
diff --git a/tests/lib_2/lib_2_dartdevc.status b/tests/lib_2/lib_2_dartdevc.status
index bbfa3a4..db43afe 100644
--- a/tests/lib_2/lib_2_dartdevc.status
+++ b/tests/lib_2/lib_2_dartdevc.status
@@ -82,7 +82,6 @@
 html/custom_elements_test: Skip # Issue 29922
 html/deferred_multi_app_htmltest: Skip # Issue 29919
 html/element_classes_test: RuntimeError # Issue 29922
-html/fontface_loaded_test: RuntimeError
 html/isolates_test: RuntimeError # Issue 29922
 html/js_typed_interop_default_arg_test/default_value: MissingCompileTimeError # Issue 29922
 html/js_typed_interop_side_cast_exp_test/01: RuntimeError # Requires --experimental-trust-js-interop-type-annotations flag.
diff --git a/tools/dom/scripts/systemhtml.py b/tools/dom/scripts/systemhtml.py
index 324900c..458d810 100644
--- a/tools/dom/scripts/systemhtml.py
+++ b/tools/dom/scripts/systemhtml.py
@@ -771,12 +771,14 @@
                  parameterized Promise type.
 '''
 promise_attributes = monitored.Dict('systemhtml.promise_attr_type', {
-    "Animation.finished": {"type": "Animation"},
-    "Animation.ready": {"type": "Animation"},
-    "FontFace.loaded": {"type": "FontFace"},
-    "FontFaceSet.ready": {"type": "FontFaceSet"},
-    "PresentationReceiver.connectionList": {"type": "PresentationConnectionList"},
-    "ServiceWorkerContainer.ready": {"type": "ServiceWorkerRegistration"},
+  "Animation.finished": {"type": "Animation"},
+  "Animation.ready": {"type": "Animation"},
+  "BeforeInstallPromptEvent.userChoice": {"type": "dictionary"},
+  "FontFace.loaded": {"type": "FontFace"},
+  "FontFaceSet.ready": {"type": "FontFaceSet"},
+  "MediaKeySession.closed": {"type": "void"},
+  "PresentationReceiver.connectionList": {"type": "PresentationConnectionList"},
+  "ServiceWorkerContainer.ready": {"type": "ServiceWorkerRegistration"},
 })
 
 promise_operations = monitored.Dict('systemhtml.promise_oper_type', {
@@ -1125,6 +1127,8 @@
     metadata = self._Metadata(attribute.type.id, attribute.id, output_type)
     rename = self._RenamingAnnotation(attribute.id, html_name)
     if not read_only:
+      if attribute.type.id == 'Promise':
+        _logger.warn('R/W member is a Promise: %s.%s' % (self._interface.id, html_name))
       self._members_emitter.Emit(
           '\n  $RENAME$METADATA$TYPE $NAME;'
           '\n',
@@ -1134,17 +1138,44 @@
           TYPE=output_type)
     else:
       template = '\n  $RENAME$(ANNOTATIONS)final $TYPE $NAME;\n'
-      # Need to use a getter for list.length properties so we can add a
-      # setter which throws an exception, satisfying List API.
-      if self._interface_type_info.list_item_type() and html_name == 'length':
-        template = ('\n  $RENAME$(ANNOTATIONS)$TYPE get $NAME => ' +
-            'JS("$TYPE", "#.$NAME", this);\n')
-      self._members_emitter.Emit(
-          template,
-          RENAME=rename,
-          ANNOTATIONS=metadata,
-          NAME=html_name,
-          TYPE=input_type if output_type == 'double' else output_type)
+      if attribute.type.id == 'Promise':
+          lookupOp = "%s.%s" % (self._interface.id, html_name)
+          promiseFound = _GetPromiseAttributeType(lookupOp)
+          promiseType = 'Future'
+          promiseCall = 'promiseToFuture'
+          if promiseFound is not (None):
+              if 'maplike' in promiseFound:
+                  promiseCall = 'promiseToFuture<dynamic>'
+                  promiseType = 'Future'
+              elif promiseFound['type'] == 'dictionary':
+                  # It's a dictionary so return as a Map.
+                  promiseCall = 'promiseToFutureAsMap'
+                  promiseType = 'Future<Map<String, dynamic>>'
+              else:
+                  paramType = promiseFound['type']
+                  promiseCall = 'promiseToFuture<%s>' % paramType
+                  promiseType = 'Future<%s>' % paramType
+
+          template = '\n  $RENAME$(ANNOTATIONS)$TYPE get $NAME => $PROMISE_CALL(JS("", "#.$NAME", this));\n'
+
+          self._members_emitter.Emit(template,
+                                     RENAME=rename,
+                                     ANNOTATIONS=metadata,
+                                     TYPE=promiseType,
+                                     PROMISE_CALL=promiseCall,
+                                     NAME=html_name)
+      else:
+        # Need to use a getter for list.length properties so we can add a
+        # setter which throws an exception, satisfying List API.
+        if self._interface_type_info.list_item_type() and html_name == 'length':
+          template = ('\n  $RENAME$(ANNOTATIONS)$TYPE get $NAME => ' +
+              'JS("$TYPE", "#.$NAME", this);\n')
+        self._members_emitter.Emit(
+            template,
+            RENAME=rename,
+            ANNOTATIONS=metadata,
+            NAME=html_name,
+            TYPE=input_type if output_type == 'double' else output_type)
 
   def _AddAttributeUsingProperties(self, attribute, html_name, read_only):
     self._AddRenamingGetter(attribute, html_name)