[web] Migrate Flutter Web DOM usage to JS static interop - 36. (flutter/engine#33372)

This commit is contained in:
joshualitt 2022-06-21 10:24:40 -07:00 committed by GitHub
parent ffd7eadb73
commit 73a8bd7c5b
18 changed files with 263 additions and 168 deletions

View File

@ -219,7 +219,7 @@ class CanvasPool extends _SaveStackTracking {
return tryCreateCanvasElement(
(width * _density).ceil(),
(height * _density).ceil(),
) as DomCanvasElement?;
);
}
@override

View File

@ -458,7 +458,7 @@ Future<Uint8List> 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);
}

View File

@ -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', <Object>[
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<String?>(this, 'textContent', value);
external DomNode cloneNode(bool? deep);
external bool contains(DomNode? other);
external void append(DomNode node);
Iterable<DomNode> get childNodes => createDomListWrapper<DomElement>(
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', <Object>[className]).cast<DomNode>();
external void click();
external bool hasAttribute(String name);
external DomNodeList get childNodes;
Iterable<DomNode> get childNodes => createDomListWrapper<DomElement>(
js_util.getProperty<_DomList>(this, 'childNodes'));
DomShadowRoot attachShadow(Map<Object?, Object?> initDict) => js_util
.callMethod(this, 'attachShadow', <Object?>[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', <Object>[if (type != null) type]);
String toDataURL([String type = 'image/png']) =>
js_util.callMethod(this, 'toDataURL', <Object>[type]);
Object? getContext(String contextType, [Map<dynamic, dynamic>? attributes]) {
return js_util.callMethod(this, 'getContext', <Object?>[
@ -635,6 +659,9 @@ extension DomCanvasRenderingContext2DExtension on DomCanvasRenderingContext2D {
@staticInterop
class DomImageData {}
DomImageData createDomImageData(Object? data, int sw, int sh) => js_util
.callConstructor(domGetConstructor('ImageData')!, <Object?>[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<dynamic, dynamic>? attributes]) {
return js_util.callMethod(this, 'getContext', <Object?>[
contextType,
if (attributes != null) js_util.jsify(attributes)
]);
}
Future<DomBlob> convertToBlob([Map<Object?, Object?>? options]) =>
js_util.promiseToFuture(js_util.callMethod(this, 'convertToBlob',
<Object>[if (options != null) js_util.jsify(options)]));
}
DomOffscreenCanvas createDomOffscreenCanvas(int width, int height) =>
js_util.callConstructor(
domGetConstructor('OffscreenCanvas')!, <Object>[width, height]);
@JS()
@staticInterop
class DomFileReader extends DomEventTarget {}
extension DomFileReaderExtension on DomFileReader {
external void readAsDataURL(DomBlob blob);
}
DomFileReader createDomFileReader() =>
js_util.callConstructor(domGetConstructor('FileReader')!, <Object>[])
as DomFileReader;
@JS()
@staticInterop
class DomDocumentFragment extends DomNode {}
extension DomDocumentFragmentExtension on DomDocumentFragment {
external DomElement? querySelector(String selectors);
Iterable<DomElement> querySelectorAll(String selectors) =>
createDomListWrapper<DomElement>(js_util
.callMethod<_DomList>(this, 'querySelectorAll', <Object>[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]

View File

@ -313,9 +313,9 @@ class FlutterViewEmbedder {
.instance.semanticsHelper
.prepareAccessibilityPlaceholder() as html.Element;
glassPaneElementHostNode.nodes.addAll(<html.Node>[
_accessibilityPlaceholder,
_sceneHostElement!,
glassPaneElementHostNode.appendAll(<DomNode>[
_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<Object?>(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);

View File

@ -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<DomNode>] to this [HostNode].
void appendAll(Iterable<DomNode> 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<html.Node> 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<html.Element> querySelectorAll(String selectors);
Iterable<DomElement> 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<html.Element> querySelectorAll(String selectors) {
Iterable<DomElement> 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<html.Node> get nodes => _shadow.nodes;
void appendAll(Iterable<DomNode> 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<html.Element> querySelectorAll(String selectors) {
Iterable<DomElement> 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<html.Node> get nodes => _element.nodes;
void appendAll(Iterable<DomNode> nodes) => nodes.forEach(append);
}

View File

@ -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();
}

View File

@ -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 <html> element.
double findBrowserTextScaleFactor() {
final num fontSize = parseFontSize(domDocument.documentElement! as
html.Element) ?? _defaultRootFontSize;
final num fontSize = parseFontSize(domDocument.documentElement!) ?? _defaultRootFontSize;
return fontSize / _defaultRootFontSize;
}

View File

@ -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

View File

@ -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<bool>(html.document, 'hasFocus', <dynamic>[]);
js_util.callMethod<bool>(domDocument, 'hasFocus', <dynamic>[]);
/// 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<void>(navigator, 'vibrate', <num>[durationMs]);
}
@ -192,12 +193,12 @@ void vibrate(int durationMs) {
/// Creates a `<canvas>` 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.CanvasElement?>(
html.document,
DomCanvasElement? tryCreateCanvasElement(int width, int height) {
final DomCanvasElement? canvas = js_util.callMethod<DomCanvasElement?>(
domDocument,
'createElement',
<dynamic>['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', <String, dynamic>{'premultipliedAlpha': false})!,
isOffscreen = true {
_programCache = <String, GlProgram?>{};
_canvas = canvas;
}
GlContext._fromCanvasElement(html.CanvasElement canvas, bool useWebGl1)
GlContext._fromCanvasElement(DomCanvasElement canvas, bool useWebGl1)
: glContext = canvas.getContext(useWebGl1 ? 'webgl' : 'webgl2',
<String, dynamic>{'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<void>(glContext, 'readPixels',
<dynamic>[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<void>(glContext, 'readPixels',
<dynamic>[0, 0, bufferWidth, bufferHeight, kRGBA, kUnsignedByte, pixels]);
return html.ImageData(pixels, bufferWidth, bufferHeight);
return createDomImageData(pixels, bufferWidth, bufferHeight);
}
}
@ -845,8 +846,8 @@ class GlContext {
<dynamic>[]);
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<String> toDataUrl() {
final Completer<String> completer = Completer<String>();
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<String>(js_util.getProperty<Object>(event, 'target'), 'result'),
);
});
fileReader.readAsDataUrl(value);
}));
fileReader.readAsDataURL(value);
});
return completer.future;
} else {
return Future<String>.value(canvasElement!.toDataUrl());
return Future<String>.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');
}

View File

@ -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);
}

View File

@ -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);

View File

@ -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);

View File

@ -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<html.ShadowRoot>());
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<html.Element>());
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.Node>[
html.document.createElement('div'),
hostNode.appendAll(<DomNode>[
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<html.Node> found = hostNode.querySelectorAll('div');
final List<DomNode> found = hostNode.querySelectorAll('div').toList();
expect(found.length, 3);
expect(found[1], target);

View File

@ -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() {
</sem>''');
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() {
</sem>''');
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() {
</sem>''');
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() {
</sem-c>
</sem>''');
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('<sem style="$rootSemanticStyle"></sem>');
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() {
</sem-c>
</sem>''');
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<num> 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<num> 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<num> child3Rect = child3.getBoundingClientRect();
expect(child3Rect.left, 0);

View File

@ -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<String> ignoredAttributes = <String>['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].

View File

@ -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;

View File

@ -32,7 +32,8 @@ Future<void> 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<void> 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',

View File

@ -105,7 +105,8 @@ Future<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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`.