diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvas_pool.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvas_pool.dart index 3bf1202ca2f..2d9a1fa62ec 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvas_pool.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvas_pool.dart @@ -219,7 +219,7 @@ class CanvasPool extends _SaveStackTracking { return tryCreateCanvasElement( (width * _density).ceil(), (height * _density).ceil(), - ) as DomCanvasElement?; + ); } @override diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart index 84ca6a0efb3..da5fe210ac0 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/image_web_codecs.dart @@ -458,7 +458,7 @@ Future encodeVideoFrameAsPng(VideoFrame videoFrame) async { final DomCanvasElement canvas = createDomCanvasElement(width: width, height: height); final DomCanvasRenderingContext2D ctx = canvas.context2D; - ctx.drawImage(videoFrame as DomCanvasImageSource, 0, 0); + ctx.drawImage(videoFrame, 0, 0); final String pngBase64 = canvas.toDataURL().substring('data:image/png;base64,'.length); return base64.decode(pngBase64); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart index 9f98ee22cc0..1870e9c9ff7 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/dom.dart @@ -40,7 +40,12 @@ extension DomWindowExtension on DomWindow { external DomURL get URL; external bool dispatchEvent(DomEvent event); external DomMediaQueryList matchMedia(String? query); - external DomCSSStyleDeclaration getComputedStyle(DomElement elt); + DomCSSStyleDeclaration getComputedStyle(DomElement elt, + [String? pseudoElt]) => + js_util.callMethod(this, 'getComputedStyle', [ + elt, + if (pseudoElt != null) pseudoElt + ]) as DomCSSStyleDeclaration; } @JS() @@ -88,6 +93,7 @@ extension DomDocumentExtension on DomDocument { String namespaceURI, String qualifiedName); external DomText createTextNode(String data); external DomEvent createEvent(String eventType); + external DomElement? get activeElement; } @JS() @@ -196,6 +202,15 @@ extension DomNodeExtension on DomNode { js_util.setProperty(this, 'textContent', value); external DomNode cloneNode(bool? deep); external bool contains(DomNode? other); + external void append(DomNode node); + Iterable get childNodes => createDomListWrapper( + js_util.getProperty<_DomList>(this, 'childNodes')); + external DomDocument? get ownerDocument; + void clearChildren() { + while (firstChild != null) { + removeChild(firstChild!); + } + } } @JS() @@ -255,7 +270,12 @@ extension DomElementExtension on DomElement { this, 'getElementsByClassName', [className]).cast(); external void click(); external bool hasAttribute(String name); - external DomNodeList get childNodes; + Iterable get childNodes => createDomListWrapper( + js_util.getProperty<_DomList>(this, 'childNodes')); + DomShadowRoot attachShadow(Map initDict) => js_util + .callMethod(this, 'attachShadow', [js_util.jsify(initDict)]) + as DomShadowRoot; + external DomShadowRoot? get shadowRoot; void clearChildren() { while (firstChild != null) { removeChild(firstChild!); @@ -506,8 +526,12 @@ class DomHTMLStyleElement extends DomHTMLElement {} extension DomHTMLStyleElementExtension on DomHTMLStyleElement { external set type(String? value); + external DomStyleSheet? get sheet; } +DomHTMLStyleElement createDomHTMLStyleElement() => + domDocument.createElement('style') as DomHTMLStyleElement; + @JS() @staticInterop class DomPerformance extends DomEventTarget {} @@ -549,8 +573,8 @@ extension DomCanvasElementExtension on DomCanvasElement { external int? get height; external set height(int? value); external bool? get isConnected; - String toDataURL([String? type]) => - js_util.callMethod(this, 'toDataURL', [if (type != null) type]); + String toDataURL([String type = 'image/png']) => + js_util.callMethod(this, 'toDataURL', [type]); Object? getContext(String contextType, [Map? attributes]) { return js_util.callMethod(this, 'getContext', [ @@ -635,6 +659,9 @@ extension DomCanvasRenderingContext2DExtension on DomCanvasRenderingContext2D { @staticInterop class DomImageData {} +DomImageData createDomImageData(Object? data, int sw, int sh) => js_util + .callConstructor(domGetConstructor('ImageData')!, [data, sw, sh]); + extension DomImageDataExtension on DomImageData { external Uint8ClampedList get data; } @@ -1126,12 +1153,71 @@ DomHTMLLabelElement createDomHTMLLabelElement() => @JS() @staticInterop -class DomNodeList {} +class DomOffscreenCanvas extends DomEventTarget {} -extension DomNodeListExtension on DomNodeList { - external DomNode? item(int index); +extension DomOffscreenCanvasExtension on DomOffscreenCanvas { + external int? get height; + external int? get width; + external set height(int? value); + external set width(int? value); + Object? getContext(String contextType, [Map? attributes]) { + return js_util.callMethod(this, 'getContext', [ + contextType, + if (attributes != null) js_util.jsify(attributes) + ]); + } + + Future convertToBlob([Map? options]) => + js_util.promiseToFuture(js_util.callMethod(this, 'convertToBlob', + [if (options != null) js_util.jsify(options)])); } +DomOffscreenCanvas createDomOffscreenCanvas(int width, int height) => + js_util.callConstructor( + domGetConstructor('OffscreenCanvas')!, [width, height]); + +@JS() +@staticInterop +class DomFileReader extends DomEventTarget {} + +extension DomFileReaderExtension on DomFileReader { + external void readAsDataURL(DomBlob blob); +} + +DomFileReader createDomFileReader() => + js_util.callConstructor(domGetConstructor('FileReader')!, []) + as DomFileReader; + +@JS() +@staticInterop +class DomDocumentFragment extends DomNode {} + +extension DomDocumentFragmentExtension on DomDocumentFragment { + external DomElement? querySelector(String selectors); + Iterable querySelectorAll(String selectors) => + createDomListWrapper(js_util + .callMethod<_DomList>(this, 'querySelectorAll', [selectors])); +} + +@JS() +@staticInterop +class DomShadowRoot extends DomDocumentFragment {} + +extension DomShadowRootExtension on DomShadowRoot { + external DomElement? get activeElement; + external DomElement? get host; + external String? get mode; + external bool? get delegatesFocus; +} + +@JS() +@staticInterop +class DomStyleSheet {} + +@JS() +@staticInterop +class DomCSSStyleSheet extends DomStyleSheet {} + // A helper class for managing a subscription. On construction it will add an // event listener of the requested type to the target. Calling [cancel] will // remove the listener. Caller is still responsible for calling [allowInterop] diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/embedder.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/embedder.dart index 3f1ed5dc1de..c93c6e14472 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/embedder.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/embedder.dart @@ -313,9 +313,9 @@ class FlutterViewEmbedder { .instance.semanticsHelper .prepareAccessibilityPlaceholder() as html.Element; - glassPaneElementHostNode.nodes.addAll([ - _accessibilityPlaceholder, - _sceneHostElement!, + glassPaneElementHostNode.appendAll([ + _accessibilityPlaceholder as DomNode, + _sceneHostElement! as DomNode, // The semantic host goes last because hit-test order-wise it must be // first. If semantics goes under the scene host, platform views will @@ -327,7 +327,7 @@ class FlutterViewEmbedder { // elements transparent. This way, if a platform view appears among other // interactive Flutter widgets, as long as those widgets do not intersect // with the platform view, the platform view will be reachable. - semanticsHostElement, + semanticsHostElement as DomNode, ]); // When debugging semantics, make the scene semi-transparent so that the @@ -384,10 +384,10 @@ class FlutterViewEmbedder { // Creates a [HostNode] into a `root` [html.Element]. HostNode _createHostNode(html.Element root) { if (getJsProperty(root, 'attachShadow') != null) { - return ShadowDomHostNode(root); + return ShadowDomHostNode(root as DomElement); } else { // attachShadow not available, fall back to ElementHostNode. - return ElementHostNode(root); + return ElementHostNode(root as DomElement); } } @@ -526,7 +526,7 @@ class FlutterViewEmbedder { bodyNode.insertBefore(_resourcesHost!, bodyNode.firstChild); } else { _glassPaneShadow!.node.insertBefore( - _resourcesHost!, _glassPaneShadow!.node.firstChild); + _resourcesHost! as DomNode, _glassPaneShadow!.node.firstChild); } } _resourcesHost!.append(element); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart index 4a7de54d9de..fe48e9dff7a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart @@ -5,21 +5,22 @@ import 'dart:html' as html; import 'browser_detection.dart'; +import 'dom.dart'; import 'embedder.dart'; import 'text_editing/text_editing.dart'; /// The interface required to host a flutter app in the DOM, and its tests. /// -/// Consider this as the intersection in functionality between [html.ShadowRoot] -/// (preferred Flutter rendering method) and [html.Document] (fallback). +/// Consider this as the intersection in functionality between [DomShadowRoot] +/// (preferred Flutter rendering method) and [DomDocument] (fallback). /// -/// Not to be confused with [html.DocumentOrShadowRoot]. +/// Not to be confused with [DomDocumentOrShadowRoot]. abstract class HostNode { - /// Retrieves the [html.Element] that currently has focus. + /// Retrieves the [DomElement] that currently has focus. /// /// See: /// * [Document.activeElement](https://developer.mozilla.org/en-US/docs/Web/API/Document/activeElement) - html.Element? get activeElement; + DomElement? get activeElement; /// Adds a node to the end of the child [nodes] list of this node. /// @@ -31,18 +32,18 @@ abstract class HostNode { /// /// See: /// * [Node.appendChild](https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild) - html.Node append(html.Node node); + DomNode append(DomNode node); + + /// Appends all of an [Iterable] to this [HostNode]. + void appendAll(Iterable nodes); /// Returns true if this node contains the specified node. /// See: /// * [Node.contains](https://developer.mozilla.org/en-US/docs/Web/API/Node.contains) - bool contains(html.Node? other); + bool contains(DomNode? other); - /// Returns the currently wrapped [html.Node]. - html.Node get node; - - /// A modifiable list of this node's children. - List get nodes; + /// Returns the currently wrapped [DomNode]. + DomNode get node; /// Finds the first descendant element of this document that matches the /// specified group of selectors. @@ -59,7 +60,7 @@ abstract class HostNode { /// /// See: /// * [Document.querySelector](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector) - html.Element? querySelector(String selectors); + DomElement? querySelector(String selectors); /// Finds all descendant elements of this document that match the specified /// group of selectors. @@ -75,10 +76,10 @@ abstract class HostNode { /// /// See: /// * [Document.querySelectorAll](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) - List querySelectorAll(String selectors); + Iterable querySelectorAll(String selectors); } -/// A [HostNode] implementation, backed by a [html.ShadowRoot]. +/// A [HostNode] implementation, backed by a [DomShadowRoot]. /// /// This is the preferred flutter implementation, but it might not be supported /// by all browsers yet. @@ -87,12 +88,12 @@ abstract class HostNode { /// supported in the current environment. In this case, a fallback [ElementHostNode] /// should be created instead. class ShadowDomHostNode implements HostNode { - late html.ShadowRoot _shadow; + late DomShadowRoot _shadow; - /// Build a HostNode by attaching a [html.ShadowRoot] to the `root` element. + /// Build a HostNode by attaching a [DomShadowRoot] to the `root` element. /// /// This also calls [applyGlobalCssRulesToSheet], defined in dom_renderer. - ShadowDomHostNode(html.Element root) : + ShadowDomHostNode(DomElement root) : assert( root.isConnected ?? true, 'The `root` of a ShadowDomHostNode must be connected to the Document object or a ShadowRoot.', @@ -104,9 +105,9 @@ class ShadowDomHostNode implements HostNode { 'delegatesFocus': false, }); - final html.StyleElement shadowRootStyleElement = html.StyleElement(); + final DomHTMLStyleElement shadowRootStyleElement = createDomHTMLStyleElement(); // The shadowRootStyleElement must be appended to the DOM, or its `sheet` will be null later. - _shadow.append(shadowRootStyleElement); + _shadow.appendChild(shadowRootStyleElement); // TODO(dit): Apply only rules for the shadow root applyGlobalCssRulesToSheet( @@ -117,74 +118,74 @@ class ShadowDomHostNode implements HostNode { } @override - html.Element? get activeElement => _shadow.activeElement; + DomElement? get activeElement => _shadow.activeElement; @override - html.Element? querySelector(String selectors) { + DomElement? querySelector(String selectors) { return _shadow.querySelector(selectors); } @override - List querySelectorAll(String selectors) { + Iterable querySelectorAll(String selectors) { return _shadow.querySelectorAll(selectors); } @override - html.Node append(html.Node node) { - return _shadow.append(node); + DomNode append(DomNode node) { + return _shadow.appendChild(node); } @override - bool contains(html.Node? other) { + bool contains(DomNode? other) { return _shadow.contains(other); } @override - html.Node get node => _shadow; + DomNode get node => _shadow; @override - List get nodes => _shadow.nodes; + void appendAll(Iterable nodes) => nodes.forEach(append); } -/// A [HostNode] implementation, backed by a [html.Element]. +/// A [HostNode] implementation, backed by a [DomElement]. /// /// This is a fallback implementation, in case [ShadowDomHostNode] fails when /// being constructed. class ElementHostNode implements HostNode { - late html.Element _element; + late DomElement _element; - /// Build a HostNode by attaching a child [html.Element] to the `root` element. - ElementHostNode(html.Element root) { - _element = html.document.createElement('flt-element-host-node'); - root.append(_element); + /// Build a HostNode by attaching a child [DomElement] to the `root` element. + ElementHostNode(DomElement root) { + _element = domDocument.createElement('flt-element-host-node'); + root.appendChild(_element); } @override - html.Element? get activeElement => _element.ownerDocument?.activeElement; + DomElement? get activeElement => _element.ownerDocument?.activeElement; @override - html.Element? querySelector(String selectors) { + DomElement? querySelector(String selectors) { return _element.querySelector(selectors); } @override - List querySelectorAll(String selectors) { + Iterable querySelectorAll(String selectors) { return _element.querySelectorAll(selectors); } @override - html.Node append(html.Node node) { - return _element.append(node); + DomNode append(DomNode node) { + return _element.appendChild(node); } @override - bool contains(html.Node? other) { + bool contains(DomNode? other) { return _element.contains(other); } @override - html.Node get node => _element; + DomNode get node => _element; @override - List get nodes => _element.nodes; + void appendAll(Iterable nodes) => nodes.forEach(append); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart index 53525d046a4..6297789e197 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/html/render_vertices.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html' as html; import 'dart:math' as math; import 'dart:typed_data'; @@ -300,7 +299,7 @@ class _WebGlRenderer implements GlRenderer { context!.save(); context.resetTransform(); - gl.drawImage(context as html.CanvasRenderingContext2D, offsetX, offsetY); + gl.drawImage(context, offsetX, offsetY); context.restore(); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart index b7ae35a9d56..39da68a841a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_dispatcher.dart @@ -1184,7 +1184,6 @@ const double _defaultRootFontSize = 16.0; /// Finds the text scale factor of the browser by looking at the computed style /// of the browser's element. double findBrowserTextScaleFactor() { - final num fontSize = parseFontSize(domDocument.documentElement! as - html.Element) ?? _defaultRootFontSize; + final num fontSize = parseFontSize(domDocument.documentElement!) ?? _defaultRootFontSize; return fontSize / _defaultRootFontSize; } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart index db32dce1e91..baa68a08cf8 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html' as html; - import '../browser_detection.dart'; import '../dom.dart'; import '../embedder.dart'; @@ -163,8 +161,7 @@ class PlatformViewManager { final DomElement slot = domDocument.createElement('slot') ..style.display = 'none' ..setAttribute('name', tombstoneName); - // Remove cast to [html.Node] after migration. - flutterViewEmbedder.glassPaneShadow!.append(slot as html.Node); + flutterViewEmbedder.glassPaneShadow!.append(slot); // Link the element to the new slot element.setAttribute('slot', tombstoneName); // Delete both the element, and the new slot diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/safe_browser_api.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/safe_browser_api.dart index 8c6b682765b..b2b29a4718a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/safe_browser_api.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/safe_browser_api.dart @@ -12,7 +12,6 @@ library browser_api; import 'dart:async'; -import 'dart:html' as html; import 'dart:js' as js; import 'dart:js_util' as js_util; import 'dart:math' as math; @@ -22,6 +21,7 @@ import 'package:js/js.dart'; import 'package:ui/ui.dart' as ui; import 'browser_detection.dart'; +import 'dom.dart'; import 'platform_dispatcher.dart'; import 'vector_math.dart'; @@ -87,7 +87,7 @@ typedef OnBenchmark = void Function(String name, double value); /// /// [eventOptions] supply additional configuration parameters. /// -/// This is different from [html.Element.addEventListener] in that the listener +/// This is different from [DomElement.addEventListener] in that the listener /// is added as a plain JavaScript function, as opposed to a Dart function. /// /// To remove the listener, call [removeJsEventListener]. @@ -139,11 +139,11 @@ num? parseFloat(String source) { } final bool supportsFontLoadingApi = - js_util.hasProperty(html.window, 'FontFace'); + js_util.hasProperty(domWindow, 'FontFace'); final bool supportsFontsClearApi = - js_util.hasProperty(html.document, 'fonts') && - js_util.hasProperty(html.document.fonts!, 'clear'); + js_util.hasProperty(domDocument, 'fonts') && + js_util.hasProperty(domDocument.fonts!, 'clear'); /// Used to decide if the browser tab still has the focus. /// @@ -154,10 +154,10 @@ final bool supportsFontsClearApi = /// See for more details: /// https://developer.mozilla.org/en-US/docs/Web/API/Document/hasFocus bool get windowHasFocus => - js_util.callMethod(html.document, 'hasFocus', []); + js_util.callMethod(domDocument, 'hasFocus', []); /// Parses the font size of [element] and returns the value without a unit. -num? parseFontSize(html.Element element) { +num? parseFontSize(DomElement element) { num? fontSize; if (hasJsProperty(element, 'computedStyleMap')) { @@ -175,7 +175,8 @@ num? parseFontSize(html.Element element) { if (fontSize == null) { // Fallback to `getComputedStyle`. - final String fontSizeString = element.getComputedStyle().fontSize; + final String fontSizeString = + domWindow.getComputedStyle(element).getPropertyValue('font-size'); fontSize = parseFloat(fontSizeString); } @@ -184,7 +185,7 @@ num? parseFontSize(html.Element element) { /// Provides haptic feedback. void vibrate(int durationMs) { - final html.Navigator navigator = html.window.navigator; + final DomNavigator navigator = domWindow.navigator; if (hasJsProperty(navigator, 'vibrate')) { js_util.callMethod(navigator, 'vibrate', [durationMs]); } @@ -192,12 +193,12 @@ void vibrate(int durationMs) { /// Creates a `` but anticipates that the result may be null. /// -/// The [html.CanvasElement] factory assumes that element allocation will +/// The [DomCanvasElement] factory assumes that element allocation will /// succeed and will return a non-null element. This is not always true. For /// example, when Safari on iOS runs out of memory it returns null. -html.CanvasElement? tryCreateCanvasElement(int width, int height) { - final html.CanvasElement? canvas = js_util.callMethod( - html.document, +DomCanvasElement? tryCreateCanvasElement(int width, int height) { + final DomCanvasElement? canvas = js_util.callMethod( + domDocument, 'createElement', ['CANVAS'], ); @@ -331,7 +332,7 @@ class DecodeOptions { /// * https://www.w3.org/TR/webcodecs/#videoframe-interface @JS() @anonymous -class VideoFrame implements html.CanvasImageSource { +class VideoFrame implements DomCanvasImageSource { external int allocationSize(); external JsPromise copyTo(Uint8List destination); external String? get format; @@ -447,14 +448,14 @@ class GlContext { offScreenCanvas.canvasElement!, webGLVersion == WebGLVersion.webgl1); } - GlContext._fromOffscreenCanvas(html.OffscreenCanvas canvas) + GlContext._fromOffscreenCanvas(DomOffscreenCanvas canvas) : glContext = canvas.getContext('webgl2', {'premultipliedAlpha': false})!, isOffscreen = true { _programCache = {}; _canvas = canvas; } - GlContext._fromCanvasElement(html.CanvasElement canvas, bool useWebGl1) + GlContext._fromCanvasElement(DomCanvasElement canvas, bool useWebGl1) : glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2', {'premultipliedAlpha': false})!, isOffscreen = false { @@ -468,7 +469,7 @@ class GlContext { } /// Draws Gl context contents to canvas context. - void drawImage(html.CanvasRenderingContext2D context, + void drawImage(DomCanvasRenderingContext2D context, double left, double top) { // Actual size of canvas may be larger than viewport size. Use // source/destination to draw part of the image data. @@ -808,7 +809,7 @@ class GlContext { /// Reads gl contents as image data. /// /// Warning: data is read bottom up (flipped). - html.ImageData readImageData() { + DomImageData readImageData() { const int kBytesPerPixel = 4; final int bufferWidth = _widthInPixels!; final int bufferHeight = _heightInPixels!; @@ -818,14 +819,14 @@ class GlContext { Uint8List(bufferWidth * bufferHeight * kBytesPerPixel); js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); - return html.ImageData( + return createDomImageData( Uint8ClampedList.fromList(pixels), bufferWidth, bufferHeight); } else { final Uint8ClampedList pixels = Uint8ClampedList(bufferWidth * bufferHeight * kBytesPerPixel); js_util.callMethod(glContext, 'readPixels', [0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]); - return html.ImageData(pixels, bufferWidth, bufferHeight); + return createDomImageData(pixels, bufferWidth, bufferHeight); } } @@ -845,8 +846,8 @@ class GlContext { []); return imageBitmap; } else { - final html.CanvasElement canvas = html.CanvasElement(width: _widthInPixels, height: _heightInPixels); - final html.CanvasRenderingContext2D ctx = canvas.context2D; + final DomCanvasElement canvas = createDomCanvasElement(width: _widthInPixels, height: _heightInPixels); + final DomCanvasRenderingContext2D ctx = canvas.context2D; drawImage(ctx, 0, 0); return canvas; } @@ -854,10 +855,10 @@ class GlContext { /// Returns image data in data url format. String toImageUrl() { - final html.CanvasElement canvas = html.CanvasElement(width: _widthInPixels, height: _heightInPixels); - final html.CanvasRenderingContext2D ctx = canvas.context2D; + final DomCanvasElement canvas = createDomCanvasElement(width: _widthInPixels, height: _heightInPixels); + final DomCanvasRenderingContext2D ctx = canvas.context2D; drawImage(ctx, 0, 0); - final String dataUrl = canvas.toDataUrl(); + final String dataUrl = canvas.toDataURL(); canvas.width = 0; canvas.height = 0; return dataUrl; @@ -950,19 +951,19 @@ dynamic tileModeToGlWrapping(GlContext gl, ui.TileMode tileMode) { } } -/// Polyfill for html.OffscreenCanvas that is not supported on some browsers. +/// Polyfill for DomOffscreenCanvas that is not supported on some browsers. class OffScreenCanvas { - html.OffscreenCanvas? offScreenCanvas; - html.CanvasElement? canvasElement; + DomOffscreenCanvas? offScreenCanvas; + DomCanvasElement? canvasElement; int width; int height; static bool? _supported; OffScreenCanvas(this.width, this.height) { if (OffScreenCanvas.supported) { - offScreenCanvas = html.OffscreenCanvas(width, height); + offScreenCanvas = createDomOffscreenCanvas(width, height); } else { - canvasElement = html.CanvasElement( + canvasElement = createDomCanvasElement( width: width, height: height, ); @@ -971,7 +972,7 @@ class OffScreenCanvas { } } - void _updateCanvasCssSize(html.CanvasElement element) { + void _updateCanvasCssSize(DomCanvasElement element) { final double cssWidth = width / EnginePlatformDispatcher.browserDevicePixelRatio; final double cssHeight = height / EnginePlatformDispatcher.browserDevicePixelRatio; element.style @@ -1034,18 +1035,18 @@ class OffScreenCanvas { Future toDataUrl() { final Completer completer = Completer(); if (offScreenCanvas != null) { - offScreenCanvas!.convertToBlob().then((html.Blob value) { - final html.FileReader fileReader = html.FileReader(); - fileReader.onLoad.listen((html.ProgressEvent event) { + offScreenCanvas!.convertToBlob().then((DomBlob value) { + final DomFileReader fileReader = createDomFileReader(); + fileReader.addEventListener('load', allowInterop((DomEvent event) { completer.complete( js_util.getProperty(js_util.getProperty(event, 'target'), 'result'), ); - }); - fileReader.readAsDataUrl(value); + })); + fileReader.readAsDataURL(value); }); return completer.future; } else { - return Future.value(canvasElement!.toDataUrl()); + return Future.value(canvasElement!.toDataURL()); } } @@ -1057,5 +1058,5 @@ class OffScreenCanvas { /// Feature detects OffscreenCanvas. static bool get supported => _supported ??= - js_util.hasProperty(html.window, 'OffscreenCanvas'); + js_util.hasProperty(domWindow, 'OffscreenCanvas'); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text/measurement.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text/measurement.dart index 07b162ebc99..00fb31aa313 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text/measurement.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text/measurement.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html' as html; - import '../../engine.dart' show registerHotRestartListener; import '../dom.dart'; import '../embedder.dart'; @@ -31,7 +29,7 @@ class RulerHost { ..height = '0'; if (root == null) { - flutterViewEmbedder.glassPaneShadow!.node.append(_rulerHost as html.Node); + flutterViewEmbedder.glassPaneShadow!.node.appendChild(_rulerHost); } else { root.appendChild(_rulerHost); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index 27df6e8353e..658c8649f9c 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -274,7 +274,7 @@ class EngineAutofillForm { void placeForm(DomHTMLElement mainTextEditingElement) { formElement.append(mainTextEditingElement); - defaultTextEditingRoot.append(formElement as html.Node); + defaultTextEditingRoot.append(formElement); } void storeForm() { @@ -1154,7 +1154,7 @@ abstract class DefaultTextEditingStrategy with CompositionAwareMixin implements // DOM later, when the first location information arrived. // Otherwise, on Blink based Desktop browsers, the autofill menu appears // on top left of the screen. - defaultTextEditingRoot.append(activeDomElement as html.Node); + defaultTextEditingRoot.append(activeDomElement); _appendedToForm = false; } @@ -1633,7 +1633,7 @@ class AndroidTextEditingStrategy extends GloballyPositionedTextEditingStrategy { if (hasAutofillGroup) { placeForm(); } else { - defaultTextEditingRoot.append(activeDomElement as html.Node); + defaultTextEditingRoot.append(activeDomElement); } inputConfig.textCapitalization.setAutocapitalizeAttribute( activeDomElement); diff --git a/engine/src/flutter/lib/web_ui/test/embedder_test.dart b/engine/src/flutter/lib/web_ui/test/embedder_test.dart index 6ba119da14b..528b93f953a 100644 --- a/engine/src/flutter/lib/web_ui/test/embedder_test.dart +++ b/engine/src/flutter/lib/web_ui/test/embedder_test.dart @@ -95,7 +95,9 @@ void testMain() { embedder.addResource(regularTextField); regularTextField.focus(); - html.CssStyleDeclaration? style = embedder.glassPaneShadow?.querySelector('input')?.getComputedStyle('::placeholder'); + html.CssStyleDeclaration? style = domWindow.getComputedStyle( + embedder.glassPaneShadow!.querySelector('input')!, + '::placeholder') as html.CssStyleDeclaration?; expect(style, isNotNull); expect(style?.opacity, isNot('0')); @@ -105,7 +107,9 @@ void testMain() { embedder.addResource(textField); textField.focus(); - style = embedder.glassPaneShadow?.querySelector('input.flt-text-editing')?.getComputedStyle('::placeholder'); + style = domWindow.getComputedStyle( + embedder.glassPaneShadow!.querySelector('input.flt-text-editing')!, + '::placeholder') as html.CssStyleDeclaration?; expect(style, isNotNull); expect(style?.opacity, '0'); }, skip: browserEngine != BrowserEngine.firefox); diff --git a/engine/src/flutter/lib/web_ui/test/engine/host_node_test.dart b/engine/src/flutter/lib/web_ui/test/engine/host_node_test.dart index 0d18398dd8a..100b9ce01bc 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/host_node_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/host_node_test.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:html' as html; - import 'package:test/bootstrap/browser.dart'; import 'package:test/test.dart'; import 'package:ui/src/engine.dart'; @@ -13,15 +11,15 @@ void main() { } void testMain() { - final html.Element rootNode = html.document.createElement('div'); - html.document.body!.append(rootNode); + final DomElement rootNode = domDocument.createElement('div'); + domDocument.body!.append(rootNode); group('ShadowDomHostNode', () { final HostNode hostNode = ShadowDomHostNode(rootNode); test('Initializes and attaches a shadow root', () { - expect(hostNode.node, isA()); - expect((hostNode.node as html.ShadowRoot).host, rootNode); + expect(domInstanceOfString(hostNode.node, 'ShadowRoot'), isTrue); + expect((hostNode.node as DomShadowRoot).host, rootNode); expect(hostNode.node, rootNode.shadowRoot); // The shadow root should be initialized with correct parameters. @@ -35,8 +33,8 @@ void testMain() { }); test('Attaches a stylesheet to the shadow root', () { - final html.Element firstChild = - (hostNode.node as html.ShadowRoot).children.first; + final DomElement firstChild = + (hostNode.node as DomShadowRoot).childNodes.toList()[0] as DomElement; expect(firstChild.tagName, equalsIgnoringCase('style')); }); @@ -48,9 +46,9 @@ void testMain() { final HostNode hostNode = ElementHostNode(rootNode); test('Initializes and attaches a child element', () { - expect(hostNode.node, isA()); - expect((hostNode.node as html.Element).shadowRoot, isNull); - expect(hostNode.node.parent, rootNode); + expect(domInstanceOfString(hostNode.node, 'Element'), isTrue); + expect((hostNode.node as DomElement).shadowRoot, isNull); + expect(hostNode.node.parentNode, rootNode); }); _runDomTests(hostNode); @@ -60,29 +58,29 @@ void testMain() { // The common test suite that all types of HostNode implementations need to pass. void _runDomTests(HostNode hostNode) { group('DOM operations', () { - final html.Element target = html.document.createElement('div')..id = 'yep'; + final DomElement target = domDocument.createElement('div')..id = 'yep'; setUp(() { - hostNode.nodes.addAll([ - html.document.createElement('div'), + hostNode.appendAll([ + domDocument.createElement('div'), target, - html.document.createElement('flt-span'), - html.document.createElement('div'), + domDocument.createElement('flt-span'), + domDocument.createElement('div'), ]); }); tearDown(() { - hostNode.nodes.clear(); + hostNode.node.clearChildren(); }); test('querySelector', () { - final html.Element? found = hostNode.querySelector('#yep'); + final DomElement? found = hostNode.querySelector('#yep'); expect(identical(found, target), isTrue); }); test('.contains and .append', () { - final html.Element another = html.document.createElement('div') + final DomElement another = domDocument.createElement('div') ..id = 'another'; expect(hostNode.contains(target), isTrue); @@ -94,7 +92,7 @@ void _runDomTests(HostNode hostNode) { }); test('querySelectorAll', () { - final List found = hostNode.querySelectorAll('div'); + final List found = hostNode.querySelectorAll('div').toList(); expect(found.length, 3); expect(found[1], target); diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart index d05d0e0a91c..701c157b0f9 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -97,7 +97,7 @@ void _testEngineSemanticsOwner() { // Synthesize a click on the placeholder. final html.Element placeholder = - appHostNode.querySelector('flt-semantics-placeholder')!; + appHostNode.querySelector('flt-semantics-placeholder')! as html.Element; expect(placeholder.isConnected, isTrue); @@ -193,7 +193,7 @@ void _testEngineSemanticsOwner() { isFalse); final html.Element placeholder = - appHostNode.querySelector('flt-semantics-placeholder')!; + appHostNode.querySelector('flt-semantics-placeholder')! as html.Element; expect(placeholder.isConnected, isTrue); @@ -522,9 +522,9 @@ void _testContainer() { '''); final html.Element parentElement = - appHostNode.querySelector('flt-semantics')!; + appHostNode.querySelector('flt-semantics')! as html.Element; final html.Element container = - appHostNode.querySelector('flt-semantics-container')!; + appHostNode.querySelector('flt-semantics-container')! as html.Element; if (isMacOrIOS) { expect(parentElement.style.top, '0px'); @@ -576,9 +576,9 @@ void _testContainer() { '''); final html.Element parentElement = - appHostNode.querySelector('flt-semantics')!; + appHostNode.querySelector('flt-semantics')! as html.Element; final html.Element container = - appHostNode.querySelector('flt-semantics-container')!; + appHostNode.querySelector('flt-semantics-container')! as html.Element; expect(parentElement.style.transform, 'matrix(1, 0, 0, 1, 10, 10)'); expect(parentElement.style.transformOrigin, '0px 0px 0px'); @@ -619,9 +619,9 @@ void _testContainer() { '''); final html.Element parentElement = - appHostNode.querySelector('flt-semantics')!; + appHostNode.querySelector('flt-semantics')! as html.Element; final html.Element container = - appHostNode.querySelector('flt-semantics-container')!; + appHostNode.querySelector('flt-semantics-container')! as html.Element; if (isMacOrIOS) { expect(parentElement.style.top, '0px'); @@ -758,13 +758,13 @@ void _testContainer() { '''); - final html.Element root = appHostNode.querySelector('#flt-semantic-node-0')!; + final DomElement root = appHostNode.querySelector('#flt-semantic-node-0')!; expect(root.style.pointerEvents, 'none'); - final html.Element child1 = appHostNode.querySelector('#flt-semantic-node-1')!; + final DomElement child1 = appHostNode.querySelector('#flt-semantic-node-1')!; expect(child1.style.pointerEvents, 'all'); - final html.Element child2 = appHostNode.querySelector('#flt-semantic-node-2')!; + final DomElement child2 = appHostNode.querySelector('#flt-semantic-node-2')!; expect(child2.style.pointerEvents, 'all'); semantics().semanticsEnabled = false; @@ -1239,7 +1239,8 @@ void _testTextField() { semantics().updateSemantics(builder.build()); final html.Element textField = - appHostNode.querySelector('input[data-semantics-role="text-field"]')!; + appHostNode.querySelector('input[data-semantics-role="text-field"]')! as + html.Element; expect(appHostNode.activeElement, isNot(textField)); @@ -1768,7 +1769,7 @@ void _testPlatformView() { semantics().updateSemantics(builder.build()); expectSemanticsTree(''); - final html.Element element = appHostNode.querySelector('flt-semantics')!; + final DomElement element = appHostNode.querySelector('flt-semantics')!; expect(element.style.pointerEvents, 'none'); semantics().semanticsEnabled = false; @@ -1857,10 +1858,11 @@ void _testPlatformView() { '''); - final html.Element root = appHostNode.querySelector('#flt-semantic-node-0')!; + final DomElement root = appHostNode.querySelector('#flt-semantic-node-0')!; expect(root.style.pointerEvents, 'none'); - final html.Element child1 = appHostNode.querySelector('#flt-semantic-node-1')!; + final html.Element child1 = + appHostNode.querySelector('#flt-semantic-node-1')! as html.Element; expect(child1.style.pointerEvents, 'all'); final html.Rectangle child1Rect = child1.getBoundingClientRect(); expect(child1Rect.left, 0); @@ -1868,7 +1870,8 @@ void _testPlatformView() { expect(child1Rect.right, 20); expect(child1Rect.bottom, 25); - final html.Element child2 = appHostNode.querySelector('#flt-semantic-node-2')!; + final html.Element child2 = + appHostNode.querySelector('#flt-semantic-node-2')! as html.Element; expect(child2.style.pointerEvents, 'none'); final html.Rectangle child2Rect = child2.getBoundingClientRect(); expect(child2Rect.left, 0); @@ -1876,7 +1879,8 @@ void _testPlatformView() { expect(child2Rect.right, 20); expect(child2Rect.bottom, 45); - final html.Element child3 = appHostNode.querySelector('#flt-semantic-node-3')!; + final html.Element child3 = + appHostNode.querySelector('#flt-semantic-node-3')! as html.Element; expect(child3.style.pointerEvents, 'all'); final html.Rectangle child3Rect = child3.getBoundingClientRect(); expect(child3Rect.left, 0); diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart index b08a6acdbdc..e441379bf68 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -8,6 +8,7 @@ import 'dart:typed_data'; import 'package:test/test.dart'; import 'package:ui/src/engine/browser_detection.dart'; +import 'package:ui/src/engine/dom.dart'; import 'package:ui/src/engine/embedder.dart'; import 'package:ui/src/engine/host_node.dart'; import 'package:ui/src/engine/semantics.dart'; @@ -362,7 +363,7 @@ class SemanticsTester { void expectSemanticsTree(String semanticsHtml) { const List ignoredAttributes = ['pointer-events']; expect( - canonicalizeHtml(appHostNode.querySelector('flt-semantics')!.outerHtml!, ignoredAttributes: ignoredAttributes), + canonicalizeHtml(appHostNode.querySelector('flt-semantics')!.outerHTML!, ignoredAttributes: ignoredAttributes), canonicalizeHtml(semanticsHtml), ); } @@ -370,12 +371,12 @@ void expectSemanticsTree(String semanticsHtml) { /// Finds the first HTML element in the semantics tree used for scrolling. html.Element? findScrollable() { return appHostNode.querySelectorAll('flt-semantics').firstWhereOrNull( - (html.Element element) { - return element.style.overflow == 'hidden' || + (DomElement? element) { + return element!.style.overflow == 'hidden' || element.style.overflowY == 'scroll' || element.style.overflowX == 'scroll'; }, - ); + ) as html.Element?; } /// Logs semantics actions dispatched to [ui.window]. diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart index e442367d1ba..d902e53ad80 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -78,7 +78,8 @@ void testMain() { createTextFieldSemantics(value: 'hello'); final html.Element textField = appHostNode - .querySelector('input[data-semantics-role="text-field"]')!; + .querySelector('input[data-semantics-role="text-field"]')! as + html.Element; expect(appHostNode.activeElement, isNot(textField)); @@ -215,7 +216,7 @@ void testMain() { // It doesn't remove the DOM element. final TextField textField = textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField; - expect(appHostNode.contains(textField.editableElement as html.Node), isTrue); + expect(appHostNode.contains(textField.editableElement), isTrue); // Editing element is not enabled. expect(strategy.isEnabled, isFalse); expect(html.document.activeElement, html.document.body); @@ -303,7 +304,7 @@ void testMain() { strategy.disable(); // It doesn't remove the textarea from the DOM. - expect(appHostNode.contains(textArea), isTrue); + expect(appHostNode.contains(textArea as DomNode), isTrue); // Editing element is not enabled. expect(strategy.isEnabled, isFalse); semantics().semanticsEnabled = false; diff --git a/engine/src/flutter/lib/web_ui/test/html/bitmap_canvas_golden_test.dart b/engine/src/flutter/lib/web_ui/test/html/bitmap_canvas_golden_test.dart index 17bacffaa89..df748bc2c03 100644 --- a/engine/src/flutter/lib/web_ui/test/html/bitmap_canvas_golden_test.dart +++ b/engine/src/flutter/lib/web_ui/test/html/bitmap_canvas_golden_test.dart @@ -32,7 +32,8 @@ Future testMain() async { testScene.style.transform = 'scale(0.3)'; } testScene.append(canvas.rootElement as html.Node); - flutterViewEmbedder.glassPaneShadow!.querySelector('flt-scene-host')!.append(testScene); + flutterViewEmbedder.glassPaneShadow!.querySelector('flt-scene-host')!.append(testScene + as DomElement); } setUpStableTestFonts(); @@ -260,7 +261,7 @@ Future testMain() async { } sceneElement.querySelector('flt-clip')!.append(canvas.rootElement); - flutterViewEmbedder.glassPaneShadow!.querySelector('flt-scene-host')!.append(sceneElement as html.Node); + flutterViewEmbedder.glassPaneShadow!.querySelector('flt-scene-host')!.append(sceneElement); await matchGoldenFile( 'bitmap_canvas_draws_text_on_top_of_canvas.png', diff --git a/engine/src/flutter/lib/web_ui/test/text_editing_test.dart b/engine/src/flutter/lib/web_ui/test/text_editing_test.dart index 57533ed583f..c5d00564a3d 100644 --- a/engine/src/flutter/lib/web_ui/test/text_editing_test.dart +++ b/engine/src/flutter/lib/web_ui/test/text_editing_test.dart @@ -105,7 +105,8 @@ Future testMain() async { defaultTextEditingRoot.querySelectorAll('input'), hasLength(1), ); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; // Now the editing element should have focus. expect(document.activeElement, flutterViewEmbedder.glassPaneElement); @@ -115,8 +116,7 @@ Future testMain() async { expect(input.getAttribute('type'), null); // Input is appended to the right point of the DOM. - expect(defaultTextEditingRoot.contains(editingStrategy!.domElement as - Node?), isTrue); + expect(defaultTextEditingRoot.contains(editingStrategy!.domElement), isTrue); editingStrategy!.disable(); expect( @@ -138,7 +138,8 @@ Future testMain() async { onAction: trackInputAction, ); expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; expect(editingStrategy!.domElement, input); expect(input.getAttribute('readonly'), 'readonly'); @@ -155,7 +156,8 @@ Future testMain() async { onAction: trackInputAction, ); expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; expect(editingStrategy!.domElement, input); expect(input.getAttribute('type'), 'password'); @@ -172,7 +174,8 @@ Future testMain() async { onAction: trackInputAction, ); expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; expect(editingStrategy!.domElement, input); expect(input.getAttribute('autocorrect'), 'off'); @@ -189,7 +192,8 @@ Future testMain() async { onAction: trackInputAction, ); expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; expect(editingStrategy!.domElement, input); expect(input.getAttribute('autocorrect'), 'on'); @@ -206,7 +210,8 @@ Future testMain() async { onAction: trackInputAction, ); expect(defaultTextEditingRoot.querySelectorAll('input'), hasLength(1)); - final Element input = defaultTextEditingRoot.querySelector('input')!; + final Element input = defaultTextEditingRoot.querySelector('input')! as + Element; expect(editingStrategy!.domElement, input); expect(input.getAttribute('autocomplete'), 'off'); @@ -1970,7 +1975,7 @@ Future testMain() async { // 3 child nodes. expect(form.childNodes, hasLength(3)); - final DomHTMLInputElement firstElement = form.childNodes.item(0)! as + final DomHTMLInputElement firstElement = form.childNodes.toList()[0] as DomHTMLInputElement; // Autofill value is applied to the element. expect(firstElement.name, @@ -2063,7 +2068,7 @@ Future testMain() async { final DomHTMLFormElement form = autofillForm.formElement; // Submit button is added to the form. expect(form.childNodes, isNotEmpty); - final InputElement inputElement = form.childNodes.item(0)! as InputElement; + final InputElement inputElement = form.childNodes.toList()[0] as InputElement; expect(inputElement.type, 'submit'); // The submit button should have class `submitBtn`.