From d493db8aacc786bc301a0814706c60843676aa71 Mon Sep 17 00:00:00 2001 From: joshualitt Date: Fri, 6 May 2022 09:09:37 -0700 Subject: [PATCH] [web] Migrate Flutter Web DOM usage to JS static interop - 5 (flutter/engine#33020) --- .../src/engine/canvaskit/embedded_views.dart | 73 +++++++++---------- .../src/engine/canvaskit/initialization.dart | 3 +- .../lib/src/engine/canvaskit/surface.dart | 12 ++- .../lib/web_ui/lib/src/engine/dom.dart | 18 +++++ .../lib/web_ui/lib/src/engine/embedder.dart | 5 +- .../web_ui/test/canvaskit/surface_test.dart | 28 +++---- 6 files changed, 77 insertions(+), 62 deletions(-) diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart index 1ee076b17ad..1b57d486f8c 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/embedded_views.dart @@ -10,6 +10,7 @@ import 'package:ui/ui.dart' as ui; import '../../engine.dart' show platformViewManager; import '../configuration.dart'; +import '../dom.dart'; import '../html/path_to_svg_clip.dart'; import '../platform_views/slots.dart'; import '../util.dart'; @@ -212,10 +213,10 @@ class HtmlViewEmbedder { void _compositeWithParams(int viewId, EmbeddedViewParams params) { // If we haven't seen this viewId yet, cache it for clips/transforms. final ViewClipChain clipChain = _viewClipChains.putIfAbsent(viewId, () { - return ViewClipChain(view: createPlatformViewSlot(viewId)); + return ViewClipChain(view: createPlatformViewSlot(viewId) as DomElement); }); - final html.Element slot = clipChain.slot; + final DomElement slot = clipChain.slot; // See `apply()` in the PersistedPlatformView class for the HTML version // of this code. @@ -228,8 +229,8 @@ class HtmlViewEmbedder { final int currentClippingCount = _countClips(params.mutators); final int previousClippingCount = clipChain.clipCount; if (currentClippingCount != previousClippingCount) { - final html.Element oldPlatformViewRoot = clipChain.root; - final html.Element newPlatformViewRoot = _reconstructClipViewsChain( + final DomElement oldPlatformViewRoot = clipChain.root; + final DomElement newPlatformViewRoot = _reconstructClipViewsChain( currentClippingCount, slot, oldPlatformViewRoot, @@ -255,26 +256,26 @@ class HtmlViewEmbedder { return clipCount; } - html.Element _reconstructClipViewsChain( + DomElement _reconstructClipViewsChain( int numClips, - html.Element platformView, - html.Element headClipView, + DomElement platformView, + DomElement headClipView, ) { int indexInFlutterView = -1; - if (headClipView.parent != null) { + if (headClipView.parentElement != null) { indexInFlutterView = skiaSceneHost!.children.indexOf(headClipView); headClipView.remove(); } - html.Element head = platformView; + DomElement head = platformView; int clipIndex = 0; // Re-use as much existing clip views as needed. while (head != headClipView && clipIndex < numClips) { - head = head.parent!; + head = head.parentElement!; clipIndex++; } // If there weren't enough existing clip views, add more. while (clipIndex < numClips) { - final html.Element clippingView = html.Element.tag('flt-clip'); + final DomElement clippingView = createDomElement('flt-clip'); clippingView.append(head); head = clippingView; clipIndex++; @@ -309,9 +310,9 @@ class HtmlViewEmbedder { } void _applyMutators( - EmbeddedViewParams params, html.Element embeddedView, int viewId) { + EmbeddedViewParams params, DomElement embeddedView, int viewId) { final MutatorsStack mutators = params.mutators; - html.Element head = embeddedView; + DomElement head = embeddedView; Matrix4 headTransform = params.offset == ui.Offset.zero ? Matrix4.identity() : Matrix4.translationValues(params.offset.dx, params.offset.dy, 0); @@ -329,7 +330,7 @@ class HtmlViewEmbedder { case MutatorType.clipRect: case MutatorType.clipRRect: case MutatorType.clipPath: - final html.Element clipView = head.parent!; + final DomElement clipView = head.parentElement!; clipView.style.clip = ''; clipView.style.clipPath = ''; headTransform = Matrix4.identity(); @@ -397,14 +398,15 @@ class HtmlViewEmbedder { final Matrix4 scaleMatrix = Matrix4.diagonal3Values(inverseScale, inverseScale, 1); headTransform = scaleMatrix.multiplied(headTransform); - head.style.transform = float64ListToCssTransform(headTransform.storage); + head.style.transform = + float64ListToCssTransform(headTransform.storage); } /// Sets the transform origin to the top-left corner of the element. /// /// By default, the transform origin is the center of the element, but /// Flutter assumes the transform origin is the top-left point. - void _resetAnchor(html.Element element) { + void _resetAnchor(DomElement element) { element.style.transformOrigin = '0 0 0'; element.style.position = 'absolute'; } @@ -424,7 +426,7 @@ class HtmlViewEmbedder { } _svgPathDefs = kSvgResourceHeader.clone(false) as svg.SvgSvgElement; _svgPathDefs!.append(svg.DefsElement()..id = 'sk_path_defs'); - skiaSceneHost!.append(_svgPathDefs!); + skiaSceneHost!.append(_svgPathDefs! as DomElement); } void submitFrame() { @@ -489,7 +491,7 @@ class HtmlViewEmbedder { _activeCompositionOrder.addAll(_compositionOrder); unusedViews.removeAll(_compositionOrder); - html.Element? elementToInsertBefore; + DomElement? elementToInsertBefore; if (diffResult.addToBeginning) { elementToInsertBefore = _viewClipChains[diffResult.viewToInsertBefore!]!.root; @@ -504,27 +506,26 @@ class HtmlViewEmbedder { } } if (diffResult.addToBeginning) { - final html.Element platformViewRoot = _viewClipChains[viewId]!.root; + final DomElement platformViewRoot = _viewClipChains[viewId]!.root; skiaSceneHost!.insertBefore(platformViewRoot, elementToInsertBefore); final Surface? overlay = _overlays[viewId]; if (overlay != null) { skiaSceneHost! - .insertBefore(overlay.htmlElement as html.Element, elementToInsertBefore); + .insertBefore(overlay.htmlElement, elementToInsertBefore); } } else { - final html.Element platformViewRoot = _viewClipChains[viewId]!.root; + final DomElement platformViewRoot = _viewClipChains[viewId]!.root; skiaSceneHost!.append(platformViewRoot); final Surface? overlay = _overlays[viewId]; if (overlay != null) { - skiaSceneHost!.append(overlay.htmlElement as html.Element); + skiaSceneHost!.append(overlay.htmlElement); } } } insertBeforeMap?.forEach((int viewId, int viewIdToInsertBefore) { - final html.Element overlay = _overlays[viewId]!.htmlElement as - html.Element; + final DomElement overlay = _overlays[viewId]!.htmlElement; if (viewIdToInsertBefore != -1) { - final html.Element nextSibling = + final DomElement nextSibling = _viewClipChains[viewIdToInsertBefore]!.root; skiaSceneHost!.insertBefore(overlay, nextSibling); } else { @@ -533,8 +534,7 @@ class HtmlViewEmbedder { }); if (_didPaintBackupSurface) { skiaSceneHost! - .append(SurfaceFactory.instance.backupSurface.htmlElement as - html.Element); + .append(SurfaceFactory.instance.backupSurface.htmlElement); } } else { SurfaceFactory.instance.removeSurfacesFromDom(); @@ -549,19 +549,18 @@ class HtmlViewEmbedder { } } - final html.Element platformViewRoot = _viewClipChains[viewId]!.root; + final DomElement platformViewRoot = _viewClipChains[viewId]!.root; final Surface? overlay = _overlays[viewId]; skiaSceneHost!.append(platformViewRoot); if (overlay != null) { - skiaSceneHost!.append(overlay.htmlElement as html.Element); + skiaSceneHost!.append(overlay.htmlElement); } _activeCompositionOrder.add(viewId); unusedViews.remove(viewId); } if (_didPaintBackupSurface) { skiaSceneHost! - .append(SurfaceFactory.instance.backupSurface.htmlElement as - html.Element); + .append(SurfaceFactory.instance.backupSurface.htmlElement); } } @@ -770,19 +769,19 @@ class HtmlViewEmbedder { /// * The slot view in the stack (the actual contents of the platform view). /// * The number of clipping elements used last time the view was composited. class ViewClipChain { - html.Element _root; - html.Element _slot; + DomElement _root; + DomElement _slot; int _clipCount = -1; - ViewClipChain({required html.Element view}) + ViewClipChain({required DomElement view}) : _root = view, _slot = view; - html.Element get root => _root; - html.Element get slot => _slot; + DomElement get root => _root; + DomElement get slot => _slot; int get clipCount => _clipCount; - void updateClipChain({required html.Element root, required int clipCount}) { + void updateClipChain({required DomElement root, required int clipCount}) { _root = root; _clipCount = clipCount; } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/initialization.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/initialization.dart index 8fdc698de30..6f199280fe9 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/initialization.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/initialization.dart @@ -4,7 +4,6 @@ library canvaskit_initialization; import 'dart:async'; -import 'dart:html' as html; import '../../engine.dart' show kProfileMode; import '../browser_detection.dart'; @@ -119,4 +118,4 @@ void ensureSkiaFontCollectionInitialized() { } /// The scene host, where the root canvas and overlay canvases are added to. -html.Element? skiaSceneHost; +DomElement? skiaSceneHost; diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart index 241fd30af81..241075bb1ce 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/surface.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/canvaskit/surface.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:ui/ui.dart' as ui; import '../browser_detection.dart'; @@ -133,7 +131,7 @@ class Surface { void addToScene() { if (!_addedToScene) { - skiaSceneHost!.children.insert(0, htmlElement as html.Element); + skiaSceneHost!.prepend(htmlElement); } _addedToScene = true; } @@ -210,8 +208,8 @@ class Surface { final double logicalWidth = _pixelWidth / window.devicePixelRatio; final double logicalHeight = _pixelHeight / window.devicePixelRatio; final DomCSSStyleDeclaration style = htmlCanvas!.style; - style.setProperty('width', '${logicalWidth}px'); - style.setProperty('height', '${logicalHeight}px'); + style.width = '${logicalWidth}px'; + style.height = '${logicalHeight}px'; } /// Translate the canvas so the surface covers the visible portion of the @@ -226,7 +224,7 @@ class Surface { final int surfaceHeight = _currentSurfaceSize!.height.ceil(); final double offset = (_pixelHeight - surfaceHeight) / window.devicePixelRatio; - htmlCanvas!.style.setProperty('transform', 'translate(0, -${offset}px)'); + htmlCanvas!.style.transform = 'translate(0, -${offset}px)'; } void _contextRestoredListener(DomEvent event) { @@ -296,7 +294,7 @@ class Surface { // accessible. htmlCanvas.setAttribute('aria-hidden', 'true'); - htmlCanvas.style.setProperty('position', 'absolute'); + htmlCanvas.style.position = 'absolute'; _updateLogicalHtmlCanvasSize(); // When the browser tab using WebGL goes dormant the browser and/or OS may 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 54ce05cba9d..ba9af194964 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 @@ -112,6 +112,7 @@ extension DomElementExtension on DomElement { external /* List */ List get children; external DomCSSStyleDeclaration get style; external void append(DomNode node); + external void prepend(DomNode node); external void setAttribute(String name, Object value); } @@ -120,6 +121,23 @@ extension DomElementExtension on DomElement { class DomCSSStyleDeclaration {} extension DomCSSStyleDeclarationExtension on DomCSSStyleDeclaration { + set width(String value) => setProperty('width', value); + set height(String value) => setProperty('height', value); + set position(String value) => setProperty('position', value); + 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 opacity(String value) => setProperty('opacity', value); + String get width => getPropertyValue('width'); + String get height => getPropertyValue('height'); + String get position => getPropertyValue('position'); + String get clip => getPropertyValue('clip'); + String get clipPath => getPropertyValue('clip-path'); + String get transform => getPropertyValue('transform'); + String get transformOrigin => getPropertyValue('transform-origin'); + String get opacity => getPropertyValue('opacity'); + external String getPropertyValue(String property); external void setProperty(String propertyName, String value, [String priority]); 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 5b228e6fca4..9c30eb6d38d 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 @@ -11,6 +11,7 @@ import '../engine.dart' show buildMode, registerHotRestartListener; import 'browser_detection.dart'; import 'canvaskit/initialization.dart'; import 'configuration.dart'; +import 'dom.dart'; import 'host_node.dart'; import 'keyboard_binding.dart'; import 'platform_dispatcher.dart'; @@ -295,8 +296,8 @@ class FlutterViewEmbedder { /// added eagerly during initialization here and never touched, unless the /// system is reset due to hot restart or in a test. if (useCanvasKit) { - skiaSceneHost = html.Element.tag('flt-scene'); - addSceneToSceneHost(skiaSceneHost); + skiaSceneHost = createDomElement('flt-scene'); + addSceneToSceneHost(skiaSceneHost as html.Element?); } final html.Element semanticsHostElement = diff --git a/engine/src/flutter/lib/web_ui/test/canvaskit/surface_test.dart b/engine/src/flutter/lib/web_ui/test/canvaskit/surface_test.dart index 1bb1dd1caa5..b6dcee9f961 100644 --- a/engine/src/flutter/lib/web_ui/test/canvaskit/surface_test.dart +++ b/engine/src/flutter/lib/web_ui/test/canvaskit/surface_test.dart @@ -29,8 +29,8 @@ void testMain() { // Expect exact requested dimensions. expect(original.width, 9); expect(original.height, 19); - expect(original.style.getPropertyValue('width'), '9px'); - expect(original.style.getPropertyValue('height'), '19px'); + expect(original.style.width, '9px'); + expect(original.style.height, '19px'); expect(originalSurface.width(), 9); expect(originalSurface.height(), 19); @@ -39,8 +39,8 @@ void testMain() { surface.acquireFrame(const ui.Size(5, 15)).skiaSurface; final DomCanvasElement shrunk = surface.htmlCanvas!; expect(shrunk, same(original)); - expect(shrunk.style.getPropertyValue('width'), '9px'); - expect(shrunk.style.getPropertyValue('height'), '19px'); + expect(shrunk.style.width, '9px'); + expect(shrunk.style.height, '19px'); expect(shrunkSurface, isNot(same(original))); expect(shrunkSurface.width(), 5); expect(shrunkSurface.height(), 15); @@ -56,8 +56,8 @@ void testMain() { // Expect overallocated dimensions expect(firstIncrease.width, 14); expect(firstIncrease.height, 28); - expect(firstIncrease.style.getPropertyValue('width'), '14px'); - expect(firstIncrease.style.getPropertyValue('height'), '28px'); + expect(firstIncrease.style.width, '14px'); + expect(firstIncrease.style.height, '28px'); expect(firstIncreaseSurface.width(), 10); expect(firstIncreaseSurface.height(), 20); @@ -79,8 +79,8 @@ void testMain() { // Also over-allocated expect(huge.width, 28); expect(huge.height, 56); - expect(huge.style.getPropertyValue('width'), '28px'); - expect(huge.style.getPropertyValue('height'), '56px'); + expect(huge.style.width, '28px'); + expect(huge.style.height, '56px'); expect(hugeSurface.width(), 20); expect(hugeSurface.height(), 40); @@ -154,8 +154,8 @@ void testMain() { expect(original.width(), 10); expect(original.height(), 16); - expect(surface.htmlCanvas!.style.getPropertyValue('width'), '10px'); - expect(surface.htmlCanvas!.style.getPropertyValue('height'), '16px'); + expect(surface.htmlCanvas!.style.width, '10px'); + expect(surface.htmlCanvas!.style.height, '16px'); // Increase device-pixel ratio: this makes CSS pixels bigger, so we need // fewer of them to cover the browser window. @@ -164,8 +164,8 @@ void testMain() { surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; expect(highDpr.width(), 10); expect(highDpr.height(), 16); - expect(surface.htmlCanvas!.style.getPropertyValue('width'), '5px'); - expect(surface.htmlCanvas!.style.getPropertyValue('height'), '8px'); + expect(surface.htmlCanvas!.style.width, '5px'); + expect(surface.htmlCanvas!.style.height, '8px'); // Decrease device-pixel ratio: this makes CSS pixels smaller, so we need // more of them to cover the browser window. @@ -174,8 +174,8 @@ void testMain() { surface.acquireFrame(const ui.Size(10, 16)).skiaSurface; expect(lowDpr.width(), 10); expect(lowDpr.height(), 16); - expect(surface.htmlCanvas!.style.getPropertyValue('width'), '20px'); - expect(surface.htmlCanvas!.style.getPropertyValue('height'), '32px'); + expect(surface.htmlCanvas!.style.width, '20px'); + expect(surface.htmlCanvas!.style.height, '32px'); }); }, skip: isIosSafari); }