diff --git a/lib/web_ui/lib/src/engine/dom.dart b/lib/web_ui/lib/src/engine/dom.dart index ca6d6d1f13072..041efd03820d7 100644 --- a/lib/web_ui/lib/src/engine/dom.dart +++ b/lib/web_ui/lib/src/engine/dom.dart @@ -66,9 +66,9 @@ class DomDocument {} extension DomDocumentExtension on DomDocument { external DomElement? get documentElement; external DomElement? querySelector(String selectors); - List querySelectorAll(String selectors) => - js_util.callMethod>( - this, 'querySelectorAll', [selectors]).cast(); + Iterable querySelectorAll(String selectors) => + _DomElementListWrapper.create(js_util.callMethod<_DomElementList>( + this, 'querySelectorAll', [selectors])); DomElement createElement(String name, [Object? options]) => js_util.callMethod(this, 'createElement', [name, if (options != null) options]) as DomElement; @@ -172,8 +172,8 @@ class DomElement extends DomNode {} DomElement createDomElement(String tag) => domDocument.createElement(tag); extension DomElementExtension on DomElement { - List get children => - js_util.getProperty>(this, 'children').cast(); + Iterable get children => _DomElementListWrapper.create( + js_util.getProperty<_DomElementList>(this, 'children')); external int get clientHeight; external int get clientWidth; external String get id; @@ -188,9 +188,9 @@ extension DomElementExtension on DomElement { external DomRect getBoundingClientRect(); external void prepend(DomNode node); external DomElement? querySelector(String selectors); - List querySelectorAll(String selectors) => - js_util.callMethod>( - this, 'querySelectorAll', [selectors]).cast(); + Iterable querySelectorAll(String selectors) => + _DomElementListWrapper.create(js_util.callMethod<_DomElementList>( + this, 'querySelectorAll', [selectors])); external void remove(); external void setAttribute(String name, Object value); void appendText(String text) => append(createDomText(text)); @@ -207,16 +207,14 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { set clip(String value) => setProperty('clip', value); set clipPath(String value) => setProperty('clip-path', value); set transform(String value) => setProperty('transform', value); - set transformOrigin(String value) => - setProperty('transform-origin', value); + set transformOrigin(String value) => setProperty('transform-origin', value); set opacity(String value) => setProperty('opacity', value); set color(String value) => setProperty('color', value); set top(String value) => setProperty('top', value); set left(String value) => setProperty('left', value); set right(String value) => setProperty('right', value); set bottom(String value) => setProperty('bottom', value); - set backgroundColor(String value) => - setProperty('background-color', value); + set backgroundColor(String value) => setProperty('background-color', value); set pointerEvents(String value) => setProperty('pointer-events', value); set filter(String value) => setProperty('filter', value); set zIndex(String value) => setProperty('z-index', value); @@ -251,8 +249,7 @@ extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { set borderRadius(String value) => setProperty('border-radius', value); set perspective(String value) => setProperty('perspective', value); set padding(String value) => setProperty('padding', value); - set backgroundImage(String value) => - setProperty('background-image', value); + set backgroundImage(String value) => setProperty('background-image', value); set border(String value) => setProperty('border', value); set mixBlendMode(String value) => setProperty('mix-blend-mode', value); set backgroundSize(String value) => setProperty('background-size', value); @@ -655,11 +652,11 @@ extension DomHTMLTextAreaElementExtension on DomHTMLTextAreaElement { class DomClipboard extends DomEventTarget {} extension DomClipboardExtension on DomClipboard { - Future readText() => - js_util.promiseToFuture(js_util.callMethod(this, 'readText', [])); + Future readText() => js_util.promiseToFuture( + js_util.callMethod(this, 'readText', [])); - Future writeText(String data) => - js_util.promiseToFuture(js_util.callMethod(this, 'readText', [data])); + Future writeText(String data) => js_util + .promiseToFuture(js_util.callMethod(this, 'readText', [data])); } extension DomResponseExtension on DomResponse { @@ -694,6 +691,57 @@ extension DomKeyboardEventExtension on DomKeyboardEvent { external bool getModifierState(String keyArg); } +/// [_DomElementList] is the shared interface for APIs that return either +/// `NodeList` or `HTMLCollection`. Do *not* add any API to this class that +/// isn't support by both JS objects. Furthermore, this is an internal class and +/// should only be returned as a wrapped object to Dart. +@JS() +@staticInterop +class _DomElementList {} + +extension DomElementListExtension on _DomElementList { + external int get length; + DomElement item(int index) => + js_util.callMethod(this, 'item', [index]); +} + +class _DomElementListIterator extends Iterator { + final _DomElementList elementList; + int index = -1; + + _DomElementListIterator(this.elementList); + + @override + bool moveNext() { + index++; + if (index > elementList.length) { + throw 'Iterator out of bounds'; + } + return index < elementList.length; + } + + @override + DomElement get current => elementList.item(index); +} + +class _DomElementListWrapper extends Iterable { + final _DomElementList elementList; + + _DomElementListWrapper._(this.elementList); + + /// This is a work around for a `TypeError` which can be triggered by calling + /// `toList` on the `Iterable`. + static Iterable create(_DomElementList elementList) => + _DomElementListWrapper._(elementList).cast(); + + @override + Iterator get iterator => _DomElementListIterator(elementList); + + /// Override the length to avoid iterating through the whole collection. + @override + int get length => elementList.length; +} + Object? domGetConstructor(String constructorName) => js_util.getProperty(domWindow, constructorName); diff --git a/lib/web_ui/lib/src/engine/html/surface_stats.dart b/lib/web_ui/lib/src/engine/html/surface_stats.dart index b43e9a549f91f..931fa32d61c0b 100644 --- a/lib/web_ui/lib/src/engine/html/surface_stats.dart +++ b/lib/web_ui/lib/src/engine/html/surface_stats.dart @@ -291,7 +291,7 @@ void debugPrintSurfaceStats(PersistedScene scene, int frameNumber) { // A microtask will fire after the DOM is flushed, letting us probe into // actual tags. scheduleMicrotask(() { - final List canvasElements = domDocument.querySelectorAll('canvas'); + final Iterable canvasElements = domDocument.querySelectorAll('canvas'); final StringBuffer canvasInfo = StringBuffer(); final int pixelCount = canvasElements .cast() diff --git a/lib/web_ui/test/engine/surface/scene_builder_test.dart b/lib/web_ui/test/engine/surface/scene_builder_test.dart index 7fa0541b2cadc..754eb779b93ec 100644 --- a/lib/web_ui/test/engine/surface/scene_builder_test.dart +++ b/lib/web_ui/test/engine/surface/scene_builder_test.dart @@ -248,7 +248,7 @@ void testMain() { final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!; final List list = - contentAfterReuse.querySelectorAll('canvas').cast(); + contentAfterReuse.querySelectorAll('canvas').cast().toList(); expect(list[0].style.zIndex, '-1'); expect(list[1].style.zIndex, ''); }); @@ -268,7 +268,7 @@ void testMain() { final DomElement content = builder.build().webOnlyRootElement!; domDocument.body!.append(content); List list = - content.querySelectorAll('img').cast(); + content.querySelectorAll('img').cast().toList(); for (final DomHTMLImageElement image in list) { image.alt = 'marked'; } @@ -284,7 +284,8 @@ void testMain() { builder2.pop(); final DomElement contentAfterReuse = builder2.build().webOnlyRootElement!; - list = contentAfterReuse.querySelectorAll('img').cast(); + list = + contentAfterReuse.querySelectorAll('img').cast().toList(); for (final DomHTMLImageElement image in list) { expect(image.alt, 'marked'); } @@ -515,7 +516,8 @@ void testMain() { renderedLayers[char] = pushChild(builder, char, oldLayer: renderedLayers[char]); } final SurfaceScene scene = builder.build(); - final List pTags = scene.webOnlyRootElement!.querySelectorAll('flt-paragraph'); + final List pTags = + scene.webOnlyRootElement!.querySelectorAll('flt-paragraph').toList(); expect(pTags, hasLength(string.length)); expect( scene.webOnlyRootElement!.querySelectorAll('flt-paragraph').map((DomElement p) => p.innerText).join(''), diff --git a/lib/web_ui/test/text_test.dart b/lib/web_ui/test/text_test.dart index 5af49625de0a6..2ecf480ac683d 100644 --- a/lib/web_ui/test/text_test.dart +++ b/lib/web_ui/test/text_test.dart @@ -225,7 +225,7 @@ Future testMain() async { paragraph.layout(const ParagraphConstraints(width: 800.0)); expect(paragraph.plainText, 'abcdef'); final List spans = - paragraph.toDomElement().querySelectorAll('flt-span').cast(); + paragraph.toDomElement().querySelectorAll('flt-span').cast().toList(); expect(spans[0].style.fontFamily, 'Ahem, $fallback, sans-serif'); // The nested span here should not set it's family to default sans-serif. expect(spans[1].style.fontFamily, 'Ahem, $fallback, sans-serif');