diff --git a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart index cc189ab40d795..a9ea464da00cf 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/renderer.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/renderer.dart @@ -68,7 +68,9 @@ class CanvasKitRenderer implements Renderer { // added eagerly during initialization here and never touched, unless the // system is reset due to hot restart or in a test. _sceneHost = createDomElement('flt-scene'); - embedder.addSceneToSceneHost(_sceneHost); + // TODO(harryterkelsen): Do this operation on the appropriate Flutter View. + final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; + implicitView.dom.setScene(_sceneHost!); } @override diff --git a/lib/web_ui/lib/src/engine/embedder.dart b/lib/web_ui/lib/src/engine/embedder.dart index 4cb8828dbccc0..039758ec0c835 100644 --- a/lib/web_ui/lib/src/engine/embedder.dart +++ b/lib/web_ui/lib/src/engine/embedder.dart @@ -39,30 +39,12 @@ class FlutterViewEmbedder { reset(); } - DomElement get _sceneHostElement => window.dom.sceneHost; - /// A child element of body outside the shadowroot that hosts /// global resources such svg filters and clip paths when using webkit. DomElement? _resourcesHost; DomElement get _semanticsHostElement => window.dom.semanticsHost; - /// The last scene element rendered by the [render] method. - DomElement? get sceneElement => _sceneElement; - DomElement? _sceneElement; - - /// Don't unnecessarily move DOM nodes around. If a DOM node is - /// already in the right place, skip DOM mutation. This is both faster and - /// more correct, because moving DOM nodes loses internal state, such as - /// text selection. - void addSceneToSceneHost(DomElement? sceneElement) { - if (sceneElement != _sceneElement) { - _sceneElement?.remove(); - _sceneElement = sceneElement; - _sceneHostElement.append(sceneElement!); - } - } - DomElement get _flutterViewElement => window.dom.rootElement; DomShadowRoot get _glassPaneShadow => window.dom.renderingHost; diff --git a/lib/web_ui/lib/src/engine/html/renderer.dart b/lib/web_ui/lib/src/engine/html/renderer.dart index 9aa45c5e08a1a..5d5598f639dbf 100644 --- a/lib/web_ui/lib/src/engine/html/renderer.dart +++ b/lib/web_ui/lib/src/engine/html/renderer.dart @@ -20,8 +20,6 @@ class HtmlRenderer implements Renderer { late final HtmlFontCollection _fontCollection = HtmlFontCollection(); - late FlutterViewEmbedder _viewEmbedder; - @override HtmlFontCollection get fontCollection => _fontCollection; @@ -38,9 +36,7 @@ class HtmlRenderer implements Renderer { } @override - void reset(FlutterViewEmbedder embedder) { - _viewEmbedder = embedder; - } + void reset(FlutterViewEmbedder embedder) {} @override ui.Paint createPaint() => SurfacePaint(); @@ -328,7 +324,8 @@ class HtmlRenderer implements Renderer { @override void renderScene(ui.Scene scene) { - _viewEmbedder.addSceneToSceneHost((scene as SurfaceScene).webOnlyRootElement); + final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; + implicitView.dom.setScene((scene as SurfaceScene).webOnlyRootElement!); frameTimingsOnRasterFinish(); } diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart index 136caa160d645..edcd0a7e19b1f 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/renderer.dart @@ -406,7 +406,9 @@ class SkwasmRenderer implements Renderer { @override void reset(FlutterViewEmbedder embedder) { - embedder.addSceneToSceneHost(sceneView.sceneElement); + // TODO(harryterkelsen): Do this operation on the appropriate Flutter View. + final EngineFlutterView implicitView = EnginePlatformDispatcher.instance.implicitView!; + implicitView.dom.setScene(sceneView.sceneElement); } static final Map> _programs = >{}; diff --git a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart index 8b703d24b19a5..9b39c34c6c7cd 100644 --- a/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart +++ b/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart @@ -178,6 +178,25 @@ class DomManager { /// This is where accessibility announcements are inserted. final DomElement announcementsHost; + + DomElement? _lastSceneElement; + + /// Inserts the [sceneElement] into the DOM and removes the existing scene (if + /// any). + /// + /// The [sceneElement] is inserted as a child of the element + /// inside the [renderingHost]. + /// + /// If the [sceneElement] has already been inserted, this method does nothing + /// to avoid unnecessary DOM mutations. This is both faster and more correct, + /// because moving DOM nodes loses internal state, such as text selection. + void setScene(DomElement sceneElement) { + if (sceneElement != _lastSceneElement) { + _lastSceneElement?.remove(); + _lastSceneElement = sceneElement; + sceneHost.append(sceneElement); + } + } } DomShadowRoot _attachShadowRoot(DomElement element) { diff --git a/lib/web_ui/test/canvaskit/embedded_views_test.dart b/lib/web_ui/test/canvaskit/embedded_views_test.dart index cda10de9fc96b..bd945fe8b5f4d 100644 --- a/lib/web_ui/test/canvaskit/embedded_views_test.dart +++ b/lib/web_ui/test/canvaskit/embedded_views_test.dart @@ -16,9 +16,8 @@ import 'test_data.dart'; EngineFlutterWindow get implicitView => EnginePlatformDispatcher.instance.implicitView!; -DomElement get platformViewsHost { - return EnginePlatformDispatcher.instance.implicitView!.dom.platformViewsHost; -} +DomElement get platformViewsHost => implicitView.dom.platformViewsHost; +DomElement get sceneElement => implicitView.dom.sceneHost.querySelector('flt-scene')!; void main() { internalBootstrapBrowserTest(() => testMain); @@ -49,8 +48,7 @@ void testMain() { // as a child of the glassPane, and the slot lives in the glassPane // shadow root. The slot is the one that has pointer events auto. final DomElement contents = platformViewsHost.querySelector('#view-0')!; - final DomElement slot = - flutterViewEmbedder.sceneElement!.querySelector('slot')!; + final DomElement slot = sceneElement.querySelector('slot')!; final DomElement contentsHost = contents.parent!; final DomElement slotHost = slot.parent!; @@ -82,13 +80,11 @@ void testMain() { rasterizer.draw(sb.build().layerTree); expect( - flutterViewEmbedder.sceneElement! - .querySelectorAll('#sk_path_defs') - .single, + sceneElement.querySelectorAll('#sk_path_defs').single, isNotNull, ); expect( - flutterViewEmbedder.sceneElement! + sceneElement .querySelectorAll('#sk_path_defs') .single .querySelectorAll('clipPath') @@ -96,27 +92,15 @@ void testMain() { isNotNull, ); expect( - flutterViewEmbedder.sceneElement! - .querySelectorAll('flt-clip') - .single - .style - .clipPath, + sceneElement.querySelectorAll('flt-clip').single.style.clipPath, 'url("#svgClip1")', ); expect( - flutterViewEmbedder.sceneElement! - .querySelectorAll('flt-clip') - .single - .style - .width, + sceneElement.querySelectorAll('flt-clip').single.style.width, '100%', ); expect( - flutterViewEmbedder.sceneElement! - .querySelectorAll('flt-clip') - .single - .style - .height, + sceneElement.querySelectorAll('flt-clip').single.style.height, '100%', ); }); @@ -140,8 +124,8 @@ void testMain() { rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. - final DomElement slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + final DomElement slotHost = + sceneElement.querySelector('flt-platform-view-slot')!; expect( slotHost.style.transform, @@ -163,8 +147,8 @@ void testMain() { sb.addPlatformView(0, offset: const ui.Offset(3, 4), width: 5, height: 6); CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); - final DomElement slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + final DomElement slotHost = + sceneElement.querySelector('flt-platform-view-slot')!; final DomCSSStyleDeclaration style = slotHost.style; expect(style.transform, 'matrix(1, 0, 0, 1, 3, 4)'); @@ -206,8 +190,8 @@ void testMain() { CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. - DomElement slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + DomElement slotHost = + sceneElement.querySelector('flt-platform-view-slot')!; expect( getTransformChain(slotHost), @@ -227,8 +211,7 @@ void testMain() { CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. - slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + slotHost = sceneElement.querySelector('flt-platform-view-slot')!; expect( getTransformChain(slotHost), @@ -256,8 +239,8 @@ void testMain() { CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. - final DomElement slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + final DomElement slotHost = + sceneElement.querySelector('flt-platform-view-slot')!; expect( getTransformChain(slotHost), @@ -283,8 +266,8 @@ void testMain() { CanvasKitRenderer.instance.rasterizer.draw(sb.build().layerTree); // Transformations happen on the slot element. - final DomElement slotHost = flutterViewEmbedder.sceneElement! - .querySelector('flt-platform-view-slot')!; + final DomElement slotHost = + sceneElement.querySelector('flt-platform-view-slot')!; expect( getTransformChain(slotHost), @@ -736,8 +719,7 @@ void testMain() { rasterizer.draw(sb.build().layerTree); } - final DomNode skPathDefs = - flutterViewEmbedder.sceneElement!.querySelector('#sk_path_defs')!; + final DomNode skPathDefs = sceneElement.querySelector('#sk_path_defs')!; expect(skPathDefs.childNodes, hasLength(0)); @@ -1000,8 +982,7 @@ void _expectSceneMatches( String? reason, }) { // Convert the scene elements to its corresponding array of _EmbeddedViewMarker - final List<_EmbeddedViewMarker> sceneElements = flutterViewEmbedder - .sceneElement!.children + final List<_EmbeddedViewMarker> sceneElements = sceneElement.children .where((DomElement element) => element.tagName != 'svg') .map((DomElement element) => _tagToViewMarker[element.tagName.toLowerCase()]!) diff --git a/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart b/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart index f194eae3fa011..15ef608dd2fcf 100644 --- a/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart +++ b/lib/web_ui/test/engine/view_embedder/dom_manager_test.dart @@ -117,6 +117,27 @@ void doTests() { expect(style!.tagName, equalsIgnoringCase('style')); expect(style.parentNode, domManager.renderingHost); }); + + test('setScene', () { + final DomManager domManager = DomManager(devicePixelRatio: 3.0); + + final DomElement sceneHost = + domManager.renderingHost.querySelector('flt-scene-host')!; + + final DomElement scene1 = createDomElement('flt-scene'); + domManager.setScene(scene1); + expect(sceneHost.children, [scene1]); + + // Insert the same scene again. + domManager.setScene(scene1); + expect(sceneHost.children, [scene1]); + + // Insert a different scene. + final DomElement scene2 = createDomElement('flt-scene'); + domManager.setScene(scene2); + expect(sceneHost.children, [scene2]); + expect(scene1.parent, isNull); + }); }); }