Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[web] DomManager for each FlutterView #47388

Merged
merged 2 commits into from
Oct 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -2942,6 +2942,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 @@ -5728,6 +5729,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