[web] Move scene DOM management to DomManager (flutter/engine#47460)

- Move scene insertion logic to `DomManager`.
- Add TODOs in `Renderer` subclasses (cc @harryterkelsen).

Part of https://github.com/flutter/flutter/issues/134443
This commit is contained in:
Mouad Debbar
2023-11-17 13:35:15 -05:00
committed by GitHub
parent cb70984e12
commit 3e7fea4e4f
7 changed files with 70 additions and 66 deletions

View File

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

View File

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

View File

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

View File

@@ -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<String, Future<ui.FragmentProgram>> _programs = <String, Future<ui.FragmentProgram>>{};

View File

@@ -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 <flt-scene-host> 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) {

View File

@@ -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()]!)

View File

@@ -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, <DomElement>[scene1]);
// Insert the same scene again.
domManager.setScene(scene1);
expect(sceneHost.children, <DomElement>[scene1]);
// Insert a different scene.
final DomElement scene2 = createDomElement('flt-scene');
domManager.setScene(scene2);
expect(sceneHost.children, <DomElement>[scene2]);
expect(scene1.parent, isNull);
});
});
}