Skip to content

Commit

Permalink
[web] DomManager for each FlutterView (#47388)
Browse files Browse the repository at this point in the history
The PR may seem large, but the main changes are simple:

- Introduce a `DomManager` that aims to take over all DOM responsibilities from `FlutterViewEmbedder`.
- Update all references to `flutterViewEmbedder.*domElement*` to `domManager.*domElement*`.
- Describe the general DOM structure of a Flutter View in a doc comment.

Next steps (in future PRs):
- Move all DOM manipulation methods out of `FlutterViewEmbedder` into `DomManager`.
- Move DOM creation logic out of `FlutterViewEmbedder` into `DomManager`.
  • Loading branch information
mdebbar committed Oct 27, 2023
1 parent cad3163 commit 0dc05b1
Show file tree
Hide file tree
Showing 19 changed files with 387 additions and 278 deletions.
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -3765,6 +3765,7 @@ ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart + ../../../f
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart + ../../../flutter/LICENSE
ORIGIN: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart + ../../../flutter/LICENSE
Expand Down Expand Up @@ -6552,6 +6553,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/vector_math.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/embedding_strategy.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart
Expand Down
1 change: 1 addition & 0 deletions lib/web_ui/lib/src/engine.dart
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export 'engine/vector_math.dart';
export 'engine/view_embedder/dimensions_provider/custom_element_dimensions_provider.dart';
export 'engine/view_embedder/dimensions_provider/dimensions_provider.dart';
export 'engine/view_embedder/dimensions_provider/full_page_dimensions_provider.dart';
export 'engine/view_embedder/dom_manager.dart';
export 'engine/view_embedder/embedding_strategy/custom_element_embedding_strategy.dart';
export 'engine/view_embedder/embedding_strategy/embedding_strategy.dart';
export 'engine/view_embedder/embedding_strategy/full_page_embedding_strategy.dart';
Expand Down
48 changes: 23 additions & 25 deletions lib/web_ui/lib/src/engine/embedder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ class FlutterViewEmbedder {
///
/// This element is inserted after the [semanticsHostElement] so that
/// platform views take precedence in DOM event handling.
DomElement? get sceneHostElement => _sceneHostElement;
DomElement? _sceneHostElement;
DomElement get sceneHostElement => _sceneHostElement;
late DomElement _sceneHostElement;

/// A child element of body outside the shadowroot that hosts
/// global resources such svg filters and clip paths when using webkit.
Expand All @@ -95,8 +95,8 @@ class FlutterViewEmbedder {
///
/// This element is inserted before the [semanticsHostElement] so that
/// platform views take precedence in DOM event handling.
DomElement? get semanticsHostElement => _semanticsHostElement;
DomElement? _semanticsHostElement;
DomElement get semanticsHostElementDEPRECATED => _semanticsHostElement;
late DomElement _semanticsHostElement;

/// The last scene element rendered by the [render] method.
DomElement? get sceneElement => _sceneElement;
Expand All @@ -110,7 +110,7 @@ class FlutterViewEmbedder {
if (sceneElement != _sceneElement) {
_sceneElement?.remove();
_sceneElement = sceneElement;
_sceneHostElement!.append(sceneElement!);
_sceneHostElement.append(sceneElement!);
}
}

Expand All @@ -120,17 +120,17 @@ class FlutterViewEmbedder {
/// which captures semantics input events. The semantics DOM tree must be a
/// child of the glass pane element so that events bubble up to the glass pane
/// if they are not handled by semantics.
DomElement get flutterViewElement => _flutterViewElement;
DomElement get flutterViewElementDEPRECATED => _flutterViewElement;
late DomElement _flutterViewElement;

DomElement get glassPaneElement => _glassPaneElement;
DomElement get glassPaneElementDEPRECATED => _glassPaneElement;
late DomElement _glassPaneElement;

/// The shadow root of the [glassPaneElement], which contains the whole Flutter app.
DomShadowRoot get glassPaneShadow => _glassPaneShadow;
DomShadowRoot get glassPaneShadowDEPRECATED => _glassPaneShadow;
late DomShadowRoot _glassPaneShadow;

DomElement get textEditingHostNode => _textEditingHostNode;
DomElement get textEditingHostNodeDEPRECATED => _textEditingHostNode;
late DomElement _textEditingHostNode;

AccessibilityAnnouncements get accessibilityAnnouncements => _accessibilityAnnouncements;
Expand Down Expand Up @@ -169,16 +169,16 @@ class FlutterViewEmbedder {
//
// The embeddingStrategy will take care of cleaning up the glassPane on hot
// restart.
_embeddingStrategy.attachGlassPane(flutterViewElement);
flutterViewElement.appendChild(glassPaneElement);
_embeddingStrategy.attachGlassPane(_flutterViewElement);
_flutterViewElement.appendChild(_glassPaneElement);

if (getJsProperty<Object?>(glassPaneElement, 'attachShadow') == null) {
if (getJsProperty<Object?>(_glassPaneElement, 'attachShadow') == null) {
throw UnsupportedError('ShadowDOM is not supported in this browser.');
}

// Create a [HostNode] under the glass pane element, and attach everything
// there, instead of directly underneath the glass panel.
final DomShadowRoot shadowRoot = glassPaneElement.attachShadow(<String, dynamic>{
final DomShadowRoot shadowRoot = _glassPaneElement.attachShadow(<String, dynamic>{
'mode': 'open',
// This needs to stay false to prevent issues like this:
// - https://github.com/flutter/flutter/issues/85759
Expand All @@ -196,20 +196,18 @@ class FlutterViewEmbedder {
);

_textEditingHostNode =
createTextEditingHostNode(flutterViewElement, defaultCssFont, configuration.nonce);
createTextEditingHostNode(_flutterViewElement, defaultCssFont, configuration.nonce);

// Don't allow the scene to receive pointer events.
_sceneHostElement = domDocument.createElement('flt-scene-host')
..style.pointerEvents = 'none';

renderer.reset(this);

final DomElement semanticsHostElement =
domDocument.createElement('flt-semantics-host');
semanticsHostElement.style
_semanticsHostElement = domDocument.createElement('flt-semantics-host');
_semanticsHostElement.style
..position = 'absolute'
..transformOrigin = '0 0 0';
_semanticsHostElement = semanticsHostElement;
updateSemanticsScreenProperties();

final DomElement accessibilityPlaceholder = EngineSemanticsOwner
Expand All @@ -220,7 +218,7 @@ class FlutterViewEmbedder {
_accessibilityAnnouncements = AccessibilityAnnouncements(hostElement: announcementsElement);

shadowRoot.append(accessibilityPlaceholder);
shadowRoot.append(_sceneHostElement!);
shadowRoot.append(_sceneHostElement);
shadowRoot.append(announcementsElement);

// The semantic host goes last because hit-test order-wise it must be
Expand All @@ -233,17 +231,17 @@ 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.
flutterViewElement.appendChild(semanticsHostElement);
_flutterViewElement.appendChild(_semanticsHostElement);

// When debugging semantics, make the scene semi-transparent so that the
// semantics tree is more prominent.
if (configuration.debugShowSemanticsNodes) {
_sceneHostElement!.style.opacity = '0.3';
_sceneHostElement.style.opacity = '0.3';
}

KeyboardBinding.initInstance();
PointerBinding.initInstance(
flutterViewElement,
_flutterViewElement,
KeyboardBinding.instance!.converter,
);

Expand All @@ -259,7 +257,7 @@ class FlutterViewEmbedder {
/// logical pixels. To compensate, an inverse scale is injected at the root
/// level.
void updateSemanticsScreenProperties() {
_semanticsHostElement!.style
_semanticsHostElement.style
.setProperty('transform', 'scale(${1 / window.devicePixelRatio})');
}

Expand Down Expand Up @@ -301,9 +299,9 @@ class FlutterViewEmbedder {
if (isWebKit) {
// The resourcesHost *must* be a sibling of the glassPaneElement.
_embeddingStrategy.attachResourcesHost(resourcesHost,
nextTo: flutterViewElement);
nextTo: _flutterViewElement);
} else {
glassPaneShadow.insertBefore(resourcesHost, glassPaneShadow.firstChild);
_glassPaneShadow.insertBefore(resourcesHost, _glassPaneShadow.firstChild);
}
_resourcesHost = resourcesHost;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@

import 'dart:typed_data';

import 'package:ui/src/engine/embedder.dart';
import 'package:ui/src/engine/text_editing/text_editing.dart';
import 'package:ui/src/engine/vector_math.dart';
import 'package:ui/ui.dart' as ui show Offset;

import '../dom.dart';
import '../platform_dispatcher.dart';
import '../semantics.dart' show EngineSemanticsOwner;
import '../text_editing/text_editing.dart';
import '../vector_math.dart';

/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
///
Expand All @@ -30,7 +30,10 @@ ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarge
}

// On one of our text-editing nodes
final bool isInput = flutterViewEmbedder.textEditingHostNode.contains(event.target! as DomNode);
// TODO(mdebbar): There could be multiple views with multiple text editing hosts.
// https://github.com/flutter/flutter/issues/137344
final DomElement textEditingHost = EnginePlatformDispatcher.instance.implicitView!.dom.textEditingHost;
final bool isInput = textEditingHost.contains(event.target! as DomNode);
if (isInput) {
final EditableTextGeometry? inputGeometry = textEditing.strategy.geometry;
if (inputGeometry != null) {
Expand Down
6 changes: 4 additions & 2 deletions lib/web_ui/lib/src/engine/semantics/semantics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import '../alarm_clock.dart';
import '../browser_detection.dart';
import '../configuration.dart';
import '../dom.dart';
import '../embedder.dart';
import '../platform_dispatcher.dart';
import '../util.dart';
import '../vector_math.dart';
Expand Down Expand Up @@ -2198,7 +2197,10 @@ class EngineSemanticsOwner {
if (_rootSemanticsElement == null) {
final SemanticsObject root = _semanticsTree[0]!;
_rootSemanticsElement = root.element;
flutterViewEmbedder.semanticsHostElement!.append(root.element);
// TODO(mdebbar): There could be multiple views with multiple semantics hosts.
// https://github.com/flutter/flutter/issues/137344
final DomElement semanticsHost = EnginePlatformDispatcher.instance.implicitView!.dom.semanticsHost;
semanticsHost.append(root.element);
}

_finalizeTree();
Expand Down
18 changes: 8 additions & 10 deletions lib/web_ui/lib/src/engine/text/measurement.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import '../../engine.dart' show registerHotRestartListener;
import '../dom.dart';
import '../embedder.dart';
import '../platform_dispatcher.dart';
import '../view_embedder/dom_manager.dart';

// TODO(yjbanov): this is a hack we use to compute ideographic baseline; this
// number is the ratio ideographic/alphabetic for font Ahem,
Expand All @@ -14,11 +15,9 @@ import '../embedder.dart';
// anything as of this writing.
const double baselineRatioHack = 1.1662499904632568;

/// Hosts ruler DOM elements in a hidden container under a `root` [DomNode].
///
/// The `root` [DomNode] is optional. Defaults to [flutterViewEmbedder.glassPaneShadow].
/// Hosts ruler DOM elements in a hidden container under [DomManager.renderingHost].
class RulerHost {
RulerHost({DomNode? root}) {
RulerHost() {
_rulerHost.style
..position = 'fixed'
..visibility = 'hidden'
Expand All @@ -28,11 +27,10 @@ class RulerHost {
..width = '0'
..height = '0';

if (root == null) {
flutterViewEmbedder.glassPaneShadow.appendChild(_rulerHost);
} else {
root.appendChild(_rulerHost);
}
// TODO(mdebbar): There could be multiple views with multiple rendering hosts.
// https://github.com/flutter/flutter/issues/137344
final DomNode renderingHost = EnginePlatformDispatcher.instance.implicitView!.dom.renderingHost;
renderingHost.appendChild(_rulerHost);
registerHotRestartListener(dispose);
}

Expand Down
6 changes: 3 additions & 3 deletions lib/web_ui/lib/src/engine/text_editing/text_editing.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import 'package:ui/ui.dart' as ui;

import '../browser_detection.dart';
import '../dom.dart';
import '../embedder.dart';
import '../mouse/prevent_default.dart';
import '../platform_dispatcher.dart';
import '../safe_browser_api.dart';
Expand Down Expand Up @@ -51,8 +50,9 @@ void _emptyCallback(dynamic _) {}

/// The default [HostNode] that hosts all DOM required for text editing when a11y is not enabled.
@visibleForTesting
DomElement get defaultTextEditingRoot =>
flutterViewEmbedder.textEditingHostNode;
// TODO(mdebbar): There could be multiple views with multiple text editing hosts.
// https://github.com/flutter/flutter/issues/137344
DomElement get defaultTextEditingRoot => EnginePlatformDispatcher.instance.implicitView!.dom.textEditingHost;

/// These style attributes are constant throughout the life time of an input
/// element.
Expand Down
66 changes: 66 additions & 0 deletions lib/web_ui/lib/src/engine/view_embedder/dom_manager.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:ui/ui.dart' as ui;

import '../dom.dart';
import '../embedder.dart';

/// Manages DOM elements and the DOM structure for a [ui.FlutterView].
///
/// Here's the general DOM structure of a Flutter View:
///
/// [rootElement] <flutter-view>
/// |
/// +- [platformViewsHost] <flt-glass-pane>
/// | |
/// | +- [renderingHost] #shadow-root
/// | | |
/// | | +- <flt-semantics-placeholder>
/// | | |
/// | | +- <flt-scene-host>
/// | | | |
/// | | | +- <flt-scene>
/// | | |
/// | | +- <flt-announcement-host>
/// | |
/// | +- ...platform views
/// |
/// +- [textEditingHost] <text-editing-host>
/// | |
/// | +- ...text fields
/// |
/// +- [semanticsHost] <semantics-host>
/// |
/// +- ...semantics nodes
///
class DomManager {
DomManager.fromFlutterViewEmbedderDEPRECATED(this._embedder);

final FlutterViewEmbedder _embedder;

/// The root DOM element for the entire Flutter View.
///
/// This is where input events are captured, such as pointer events.
///
/// If semantics is enabled, this element also contains the semantics DOM tree,
/// which captures semantics input events.
DomElement get rootElement => _embedder.flutterViewElementDEPRECATED;

/// Hosts all platform view elements.
DomElement get platformViewsHost => _embedder.glassPaneElementDEPRECATED;

/// Hosts all rendering elements and canvases.
DomShadowRoot get renderingHost => _embedder.glassPaneShadowDEPRECATED;

/// Hosts all text editing elements.
DomElement get textEditingHost => _embedder.textEditingHostNodeDEPRECATED;

/// Hosts the semantics tree.
///
/// This element is in front of the [renderingHost] and [platformViewsHost].
/// Otherwise, the phone will disable focusing by touch, only by tabbing
/// around the UI.
DomElement get semanticsHost => _embedder.semanticsHostElementDEPRECATED;
}
12 changes: 7 additions & 5 deletions lib/web_ui/lib/src/engine/window.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import 'platform_dispatcher.dart';
import 'platform_views/message_handler.dart';
import 'services.dart';
import 'util.dart';
import 'view_embedder/dom_manager.dart';

typedef _HandleMessageCallBack = Future<bool> Function();

Expand All @@ -39,9 +40,9 @@ const int kImplicitViewId = 0;
/// a few web-specific properties.
abstract interface class EngineFlutterView extends ui.FlutterView {
ContextMenu get contextMenu;
DomManager get dom;
MouseCursor get mouseCursor;
PlatformViewMessageHandler get platformViewMessageHandler;
DomElement get rootElement;
}

/// The Web implementation of [ui.SingletonFlutterWindow].
Expand Down Expand Up @@ -69,17 +70,18 @@ class EngineFlutterWindow extends ui.SingletonFlutterWindow implements EngineFlu
final EnginePlatformDispatcher platformDispatcher;

@override
late final MouseCursor mouseCursor = MouseCursor(rootElement);
late final MouseCursor mouseCursor = MouseCursor(dom.rootElement);

@override
late final ContextMenu contextMenu = ContextMenu(rootElement);
late final ContextMenu contextMenu = ContextMenu(dom.rootElement);

@override
DomElement get rootElement => flutterViewEmbedder.flutterViewElement;
late final DomManager dom =
DomManager.fromFlutterViewEmbedderDEPRECATED(flutterViewEmbedder);

@override
late final PlatformViewMessageHandler platformViewMessageHandler =
PlatformViewMessageHandler(platformViewsContainer: flutterViewEmbedder.glassPaneElement);
PlatformViewMessageHandler(platformViewsContainer: dom.platformViewsHost);

/// Handles the browser history integration to allow users to use the back
/// button, etc.
Expand Down

0 comments on commit 0dc05b1

Please sign in to comment.