From a5fdffa8c81dd9446917ee2b78a53586c8559d39 Mon Sep 17 00:00:00 2001 From: htoor3 <110993981+htoor3@users.noreply.github.com> Date: Tue, 11 Apr 2023 14:41:14 -0500 Subject: [PATCH] [web] - Move text editing nodes outside of shadowDOM, reland (flutter/engine#40968) [web] - Move text editing nodes outside of shadowDOM, reland --- .../lib/web_ui/lib/src/engine/embedder.dart | 71 ++++-- .../lib/web_ui/lib/src/engine/host_node.dart | 8 +- .../web_ui/lib/src/engine/mouse_cursor.dart | 2 +- .../platform_views/content_manager.dart | 3 +- .../lib/src/engine/pointer_binding.dart | 60 ++--- .../event_position_helper.dart | 16 +- .../lib/src/engine/semantics/text_field.dart | 5 +- .../src/engine/text_editing/text_editing.dart | 3 +- .../test/engine/pointer_binding_test.dart | 222 +++++++++--------- .../test/engine/semantics/semantics_test.dart | 95 ++++---- .../engine/semantics/semantics_tester.dart | 10 +- .../engine/semantics/text_field_test.dart | 105 ++++----- .../web_ui/test/engine/text_editing_test.dart | 43 ++-- 13 files changed, 335 insertions(+), 308 deletions(-) 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 d16821e3d9e..245fd26e00b 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 @@ -24,7 +24,8 @@ import 'view_embedder/embedding_strategy/embedding_strategy.dart'; /// Manages several top-level elements that host Flutter-generated content, /// including: /// -/// - [glassPaneElement], the root element of a Flutter view. +/// - [flutterViewElement], the root element of a Flutter view. +/// - [glassPaneElement], the glass pane element that hosts the shadowDOM. /// - [glassPaneShadow], the shadow root used to isolate Flutter-rendered /// content from the surrounding page content, including from the platform /// views. @@ -62,7 +63,10 @@ class FlutterViewEmbedder { /// Abstracts all the DOM manipulations required to embed a Flutter app in an user-supplied `hostElement`. final EmbeddingStrategy _embeddingStrategy; - // The tag name for the root view of the flutter app (glass-pane) + // The tag name for the Flutter View, which hosts the app. + static const String flutterViewTagName = 'flutter-view'; + + // The tag name for the glass-pane. static const String glassPaneTagName = 'flt-glass-pane'; /// The element that contains the [sceneElement]. @@ -117,6 +121,9 @@ 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; + late DomElement _flutterViewElement; + DomElement get glassPaneElement => _glassPaneElement; late DomElement _glassPaneElement; @@ -124,6 +131,9 @@ class FlutterViewEmbedder { HostNode get glassPaneShadow => _glassPaneShadow; late HostNode _glassPaneShadow; + DomElement get textEditingHostNode => _textEditingHostNode; + late DomElement _textEditingHostNode; + static const String defaultFontStyle = 'normal'; static const String defaultFontWeight = 'normal'; static const double defaultFontSize = 14; @@ -149,14 +159,17 @@ class FlutterViewEmbedder { ); // Create and inject the [_glassPaneElement]. + _flutterViewElement = domDocument.createElement(flutterViewTagName); _glassPaneElement = domDocument.createElement(glassPaneTagName); + // This must be attached to the DOM now, so the engine can create a host // node (ShadowDOM or a fallback) next. // // The embeddingStrategy will take care of cleaning up the glassPane on hot // restart. - _embeddingStrategy.attachGlassPane(glassPaneElement); + _embeddingStrategy.attachGlassPane(flutterViewElement); + flutterViewElement.appendChild(glassPaneElement); // Create a [HostNode] under the glass pane element, and attach everything // there, instead of directly underneath the glass panel. @@ -168,6 +181,9 @@ class FlutterViewEmbedder { ); _glassPaneShadow = glassPaneElementHostNode; + _textEditingHostNode = + createTextEditingHostNode(flutterViewElement, defaultCssFont); + // Don't allow the scene to receive pointer events. _sceneHostElement = domDocument.createElement('flt-scene-host') ..style.pointerEvents = 'none'; @@ -189,20 +205,20 @@ class FlutterViewEmbedder { glassPaneElementHostNode.appendAll([ accessibilityPlaceholder, _sceneHostElement!, - - // The semantic host goes last because hit-test order-wise it must be - // first. If semantics goes under the scene host, platform views will - // obscure semantic elements. - // - // You may be wondering: wouldn't semantics obscure platform views and - // make then not accessible? At least with some careful planning, that - // should not be the case. The semantics tree makes all of its non-leaf - // 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. - semanticsHostElement, ]); + // The semantic host goes last because hit-test order-wise it must be + // first. If semantics goes under the scene host, platform views will + // obscure semantic elements. + // + // You may be wondering: wouldn't semantics obscure platform views and + // make then not accessible? At least with some careful planning, that + // should not be the case. The semantics tree makes all of its non-leaf + // 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); + // When debugging semantics, make the scene semi-transparent so that the // semantics tree is more prominent. if (configuration.debugShowSemanticsNodes) { @@ -211,7 +227,7 @@ class FlutterViewEmbedder { KeyboardBinding.initInstance(); PointerBinding.initInstance( - glassPaneElement, + flutterViewElement, KeyboardBinding.instance!.converter, ); @@ -336,7 +352,7 @@ class FlutterViewEmbedder { if (isWebKit) { // The resourcesHost *must* be a sibling of the glassPaneElement. _embeddingStrategy.attachResourcesHost(resourcesHost, - nextTo: glassPaneElement); + nextTo: flutterViewElement); } else { glassPaneShadow.node .insertBefore(resourcesHost, glassPaneShadow.node.firstChild); @@ -393,3 +409,24 @@ FlutterViewEmbedder? _flutterViewEmbedder; FlutterViewEmbedder ensureFlutterViewEmbedderInitialized() => _flutterViewEmbedder ??= FlutterViewEmbedder(hostElement: configuration.hostElement); + +/// Creates a node to host text editing elements and applies a stylesheet +/// to Flutter nodes that exist outside of the shadowDOM. +DomElement createTextEditingHostNode(DomElement root, String defaultFont) { + final DomElement domElement = + domDocument.createElement('flt-text-editing-host'); + final DomHTMLStyleElement styleElement = createDomHTMLStyleElement(); + + styleElement.id = 'flt-text-editing-stylesheet'; + root.appendChild(styleElement); + applyGlobalCssRulesToSheet( + styleElement.sheet! as DomCSSStyleSheet, + hasAutofillOverlay: browserHasAutofillOverlay(), + cssSelectorPrefix: FlutterViewEmbedder.flutterViewTagName, + defaultCssFont: defaultFont, + ); + + root.appendChild(domElement); + + return domElement; +} diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart index 4b0ca8e13d9..dea385d0a4d 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/host_node.dart @@ -110,10 +110,8 @@ class ShadowDomHostNode implements HostNode { /// This also calls [applyGlobalCssRulesToSheet], with the [defaultFont] /// to be used as the default font definition. ShadowDomHostNode(DomElement root, String defaultFont) - : assert( - root.isConnected ?? true, - 'The `root` of a ShadowDomHostNode must be connected to the Document object or a ShadowRoot.' - ) { + : assert(root.isConnected ?? true, + 'The `root` of a ShadowDomHostNode must be connected to the Document object or a ShadowRoot.') { _shadow = root.attachShadow({ 'mode': 'open', // This needs to stay false to prevent issues like this: @@ -181,7 +179,7 @@ class ElementHostNode implements HostNode { applyGlobalCssRulesToSheet( styleElement.sheet! as DomCSSStyleSheet, hasAutofillOverlay: browserHasAutofillOverlay(), - cssSelectorPrefix: FlutterViewEmbedder.glassPaneTagName, + cssSelectorPrefix: FlutterViewEmbedder.flutterViewTagName, defaultCssFont: defaultFont, ); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart index d6eee129464..0ccfbed03b1 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/mouse_cursor.dart @@ -67,7 +67,7 @@ class MouseCursor { void activateSystemCursor(String? kind) { setElementStyle( - flutterViewEmbedder.glassPaneElement, + flutterViewEmbedder.flutterViewElement, 'cursor', _mapKindToCssValue(kind), ); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart index f50d3af3ee6..28aeb8da085 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/platform_views/content_manager.dart @@ -128,8 +128,9 @@ class PlatformViewManager { } _ensureContentCorrectlySized(content, viewType); + wrapper.append(content); - return wrapper..append(content); + return wrapper; }); } diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart index 23dafa64e66..e2305c50b5e 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -80,7 +80,7 @@ class SafariPointerEventWorkaround { } class PointerBinding { - PointerBinding(this.glassPaneElement, this._keyboardConverter) + PointerBinding(this.flutterViewElement, this._keyboardConverter) : _pointerDataConverter = PointerDataConverter(), _detector = const PointerSupportDetector() { if (isIosSafari) { @@ -93,9 +93,9 @@ class PointerBinding { static PointerBinding? get instance => _instance; static PointerBinding? _instance; - static void initInstance(DomElement glassPaneElement, KeyboardConverter keyboardConverter) { + static void initInstance(DomElement flutterViewElement, KeyboardConverter keyboardConverter) { if (_instance == null) { - _instance = PointerBinding(glassPaneElement, keyboardConverter); + _instance = PointerBinding(flutterViewElement, keyboardConverter); assert(() { registerHotRestartListener(_instance!.dispose); return true; @@ -110,7 +110,7 @@ class PointerBinding { _pointerDataConverter.clearPointerState(); } - final DomElement glassPaneElement; + final DomElement flutterViewElement; PointerSupportDetector _detector; final PointerDataConverter _pointerDataConverter; @@ -156,15 +156,15 @@ class PointerBinding { // TODO(dit): remove old API fallbacks, https://github.com/flutter/flutter/issues/116141 _BaseAdapter _createAdapter() { if (_detector.hasPointerEvents) { - return _PointerAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); + return _PointerAdapter(_onPointerData, flutterViewElement, _pointerDataConverter, _keyboardConverter); } // Fallback for Safari Mobile < 13. To be removed. if (_detector.hasTouchEvents) { - return _TouchAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); + return _TouchAdapter(_onPointerData, flutterViewElement, _pointerDataConverter, _keyboardConverter); } // Fallback for Safari Desktop < 13. To be removed. if (_detector.hasMouseEvents) { - return _MouseAdapter(_onPointerData, glassPaneElement, _pointerDataConverter, _keyboardConverter); + return _MouseAdapter(_onPointerData, flutterViewElement, _pointerDataConverter, _keyboardConverter); } throw UnsupportedError('This browser does not support pointer, touch, or mouse events.'); } @@ -264,7 +264,7 @@ class _Listener { abstract class _BaseAdapter { _BaseAdapter( this._callback, - this.glassPaneElement, + this.flutterViewElement, this._pointerDataConverter, this._keyboardConverter, ) { @@ -272,7 +272,7 @@ abstract class _BaseAdapter { } final List<_Listener> _listeners = <_Listener>[]; - final DomElement glassPaneElement; + final DomElement flutterViewElement; final _PointerDataCallback _callback; final PointerDataConverter _pointerDataConverter; final KeyboardConverter _keyboardConverter; @@ -293,7 +293,7 @@ abstract class _BaseAdapter { /// Adds a listener for the given [eventName] to [target]. /// - /// Generally speaking, down and leave events should use [glassPaneElement] + /// Generally speaking, down and leave events should use [flutterViewElement] /// as the [target], while move and up events should use [domWindow] /// instead, because the browser doesn't fire the latter two for DOM elements /// when the pointer is outside the window. @@ -313,7 +313,7 @@ abstract class _BaseAdapter { if (_debugLogPointerEvents) { if (domInstanceOfString(event, 'PointerEvent')) { final DomPointerEvent pointerEvent = event as DomPointerEvent; - final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement); + final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement); print('${pointerEvent.type} ' '${offset.dx.toStringAsFixed(1)},' '${offset.dy.toStringAsFixed(1)}'); @@ -452,7 +452,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter { } final List data = []; - final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement); + final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement); bool ignoreCtrlKey = false; if (operatingSystem == OperatingSystem.macOs) { ignoreCtrlKey = (KeyboardBinding.instance?.converter.keyIsPressed(kPhysicalControlLeft) ?? false) || @@ -498,7 +498,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter { void _addWheelEventListener(DartDomEventListener handler) { _listeners.add(_Listener.registerNative( event: 'wheel', - target: glassPaneElement, + target: flutterViewElement, jsHandler: createDomEventListener(handler), )); } @@ -687,7 +687,7 @@ typedef _PointerEventListener = dynamic Function(DomPointerEvent event); class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _PointerAdapter( super.callback, - super.glassPaneElement, + super.flutterViewElement, super.pointerDataConverter, super.keyboardConverter, ); @@ -744,7 +744,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { @override void setup() { - _addPointerEventListener(glassPaneElement, 'pointerdown', (DomPointerEvent event) { + _addPointerEventListener(flutterViewElement, 'pointerdown', (DomPointerEvent event) { final int device = _getPointerId(event); final List pointerData = []; final _ButtonSanitizer sanitizer = _ensureSanitizer(device); @@ -779,7 +779,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _callback(pointerData); }); - _addPointerEventListener(glassPaneElement, 'pointerleave', (DomPointerEvent event) { + _addPointerEventListener(flutterViewElement, 'pointerleave', (DomPointerEvent event) { final int device = _getPointerId(event); final _ButtonSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; @@ -790,7 +790,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } }, useCapture: false, checkModifiers: false); - // TODO(dit): This must happen in the glassPane, https://github.com/flutter/flutter/issues/116561 + // TODO(dit): This must happen in the flutterViewElement, https://github.com/flutter/flutter/issues/116561 _addPointerEventListener(domWindow, 'pointerup', (DomPointerEvent event) { final int device = _getPointerId(event); if (_hasSanitizer(device)) { @@ -804,11 +804,11 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } }); - // TODO(dit): Synthesize a "cancel" event when 'pointerup' happens outside of the glassPane, https://github.com/flutter/flutter/issues/116561 + // TODO(dit): Synthesize a "cancel" event when 'pointerup' happens outside of the flutterViewElement, https://github.com/flutter/flutter/issues/116561 // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) - _addPointerEventListener(glassPaneElement, 'pointercancel', (DomPointerEvent event) { + _addPointerEventListener(flutterViewElement, 'pointercancel', (DomPointerEvent event) { final int device = _getPointerId(event); if (_hasSanitizer(device)) { final List pointerData = []; @@ -835,7 +835,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { final double tilt = _computeHighestTilt(event); final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); final num? pressure = event.pressure; - final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement); + final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement); _pointerDataConverter.convert( data, change: details.change, @@ -906,7 +906,7 @@ typedef _TouchEventListener = dynamic Function(DomTouchEvent event); class _TouchAdapter extends _BaseAdapter { _TouchAdapter( super.callback, - super.glassPaneElement, + super.flutterViewElement, super.pointerDataConverter, super.keyboardConverter, ); @@ -938,7 +938,7 @@ class _TouchAdapter extends _BaseAdapter { @override void setup() { - _addTouchEventListener(glassPaneElement, 'touchstart', (DomTouchEvent event) { + _addTouchEventListener(flutterViewElement, 'touchstart', (DomTouchEvent event) { final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); final List pointerData = []; for (final DomTouch touch in event.changedTouches.cast()) { @@ -957,7 +957,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(pointerData); }); - _addTouchEventListener(glassPaneElement, 'touchmove', (DomTouchEvent event) { + _addTouchEventListener(flutterViewElement, 'touchmove', (DomTouchEvent event) { event.preventDefault(); // Prevents standard overscroll on iOS/Webkit. final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); final List pointerData = []; @@ -976,7 +976,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(pointerData); }); - _addTouchEventListener(glassPaneElement, 'touchend', (DomTouchEvent event) { + _addTouchEventListener(flutterViewElement, 'touchend', (DomTouchEvent event) { // On Safari Mobile, the keyboard does not show unless this line is // added. event.preventDefault(); @@ -998,7 +998,7 @@ class _TouchAdapter extends _BaseAdapter { _callback(pointerData); }); - _addTouchEventListener(glassPaneElement, 'touchcancel', (DomTouchEvent event) { + _addTouchEventListener(flutterViewElement, 'touchcancel', (DomTouchEvent event) { final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!); final List pointerData = []; for (final DomTouch touch in event.changedTouches.cast()) { @@ -1064,7 +1064,7 @@ typedef _MouseEventListener = dynamic Function(DomMouseEvent event); class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _MouseAdapter( super.callback, - super.glassPaneElement, + super.flutterViewElement, super.pointerDataConverter, super.keyboardConverter, ); @@ -1099,7 +1099,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { @override void setup() { - _addMouseEventListener(glassPaneElement, 'mousedown', (DomMouseEvent event) { + _addMouseEventListener(flutterViewElement, 'mousedown', (DomMouseEvent event) { final List pointerData = []; final _SanitizedDetails? up = _sanitizer.sanitizeMissingRightClickUp(buttons: event.buttons!.toInt()); @@ -1127,7 +1127,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _callback(pointerData); }); - _addMouseEventListener(glassPaneElement, 'mouseleave', (DomMouseEvent event) { + _addMouseEventListener(flutterViewElement, 'mouseleave', (DomMouseEvent event) { final List pointerData = []; final _SanitizedDetails? details = _sanitizer.sanitizeLeaveEvent(buttons: event.buttons!.toInt()); if (details != null) { @@ -1136,7 +1136,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { } }, useCapture: false); - // TODO(dit): This must happen in the glassPane, https://github.com/flutter/flutter/issues/116561 + // TODO(dit): This must happen in the flutterViewElement, https://github.com/flutter/flutter/issues/116561 _addMouseEventListener(domWindow, 'mouseup', (DomMouseEvent event) { final List pointerData = []; final _SanitizedDetails? sanitizedDetails = _sanitizer.sanitizeUpEvent(buttons: event.buttons?.toInt()); @@ -1158,7 +1158,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { required DomMouseEvent event, required _SanitizedDetails details, }) { - final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement); + final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement); _pointerDataConverter.convert( data, change: details.change, diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart index 6be2b9ccc2b..f2bdbd9a2f6 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart @@ -19,23 +19,25 @@ import '../semantics.dart' show EngineSemanticsOwner; /// It also takes into account semantics being enabled to fix the case where /// offsetX, offsetY == 0 (TalkBack events). ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) { - // On top of a platform view - if (event.target != actualTarget) { - return _computeOffsetOnPlatformView(event, actualTarget); - } // On a TalkBack event if (EngineSemanticsOwner.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) { return _computeOffsetForTalkbackEvent(event, actualTarget); } + + final bool isTargetOutsideOfShadowDOM = event.target != actualTarget; + if (isTargetOutsideOfShadowDOM) { + return _computeOffsetRelativeToActualTarget(event, actualTarget); + } // Return the offsetX/Y in the normal case. // (This works with 3D translations of the parent element.) return ui.Offset(event.offsetX, event.offsetY); } -/// Computes the event offset when hovering over a platformView. +/// Computes the event offset when hovering over any nodes that don't exist in +/// the shadowDOM such as platform views or text editing nodes. /// /// This still uses offsetX/Y, but adds the offset from the top/left corner of the -/// platform view to the glass pane (`actualTarget`). +/// platform view to the Flutter View (`actualTarget`). /// /// ×--FlutterView(actualTarget)--------------+ /// |\ | @@ -57,7 +59,7 @@ ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarge /// /// Event offset relative to FlutterView = (offsetX + xP, offsetY + yP) // TODO(dit): Make this understand 3D transforms, https://github.com/flutter/flutter/issues/117091 -ui.Offset _computeOffsetOnPlatformView(DomMouseEvent event, DomElement actualTarget) { +ui.Offset _computeOffsetRelativeToActualTarget(DomMouseEvent event, DomElement actualTarget) { final DomElement target = event.target! as DomElement; final DomRect targetRect = target.getBoundingClientRect(); final DomRect actualTargetRect = actualTarget.getBoundingClientRect(); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart index ae2cec9394c..c2244378a73 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/semantics/text_field.dart @@ -7,7 +7,6 @@ import 'package:ui/ui.dart' as ui; import '../browser_detection.dart'; import '../dom.dart'; -import '../embedder.dart'; import '../platform_dispatcher.dart'; import '../text_editing/text_editing.dart'; import 'semantics.dart'; @@ -421,14 +420,14 @@ class TextField extends RoleManager { ..height = '${semanticsObject.rect!.height}px'; if (semanticsObject.hasFocus) { - if (flutterViewEmbedder.glassPaneShadow.activeElement != + if (domDocument.activeElement != activeEditableElement) { semanticsObject.owner.addOneTimePostUpdateCallback(() { activeEditableElement.focus(); }); } SemanticsTextEditingStrategy._instance?.activate(this); - } else if (flutterViewEmbedder.glassPaneShadow.activeElement == + } else if (domDocument.activeElement == activeEditableElement) { if (!isIosSafari) { SemanticsTextEditingStrategy._instance?.deactivate(this); diff --git a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart index b451d8b0b0b..3e22f3dd10a 100644 --- a/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart +++ b/engine/src/flutter/lib/web_ui/lib/src/engine/text_editing/text_editing.dart @@ -51,7 +51,8 @@ void _emptyCallback(dynamic _) {} /// The default [HostNode] that hosts all DOM required for text editing when a11y is not enabled. @visibleForTesting -HostNode get defaultTextEditingRoot => flutterViewEmbedder.glassPaneShadow; +DomElement get defaultTextEditingRoot => + flutterViewEmbedder.textEditingHostNode; /// These style attributes are constant throughout the life time of an input /// element. diff --git a/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart b/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart index 33b4e029419..3171e3213b1 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/pointer_binding_test.dart @@ -42,7 +42,7 @@ void main() { void testMain() { ensureFlutterViewEmbedderInitialized(); - final DomElement glassPane = flutterViewEmbedder.glassPaneElement; + final DomElement flutterViewElement = flutterViewEmbedder.flutterViewElement; late double dpi; setUp(() { @@ -483,7 +483,7 @@ void testMain() { _MouseEventContext(), _TouchEventContext(), ], - 'can receive pointer events on the glass pane', + 'can receive pointer events on the app root', (_BasicEventContext context) { PointerBinding.instance!.debugOverrideDetector(context); ui.PointerDataPacket? receivedPacket; @@ -491,7 +491,7 @@ void testMain() { receivedPacket = packet; }; - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(receivedPacket, isNotNull); expect(receivedPacket!.data[0].buttons, equals(1)); @@ -512,7 +512,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -546,7 +546,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalLeft), false); expect(keyboardConverter.keyIsPressed(physicalRight), false); - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 1); expectKeyData(keyDataList.last, type: ui.KeyEventType.down, @@ -598,7 +598,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalRight), false); keyDataList.clear(); // Remove key data generated by handleEvent - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 0); } @@ -618,7 +618,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalRight), true); keyDataList.clear(); // Remove key data generated by handleEvent - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 0); } @@ -668,7 +668,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalRight), false); keyDataList.clear(); // Remove key data generated by handleEvent - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 1); expectKeyData(keyDataList.last, type: ui.KeyEventType.up, @@ -697,7 +697,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalRight), true); keyDataList.clear(); // Remove key data generated by handleEvent - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 1); expectKeyData(keyDataList.last, type: ui.KeyEventType.up, @@ -749,7 +749,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalRight), false); keyDataList.clear(); // Remove key data generated by handleEvent - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 0); } @@ -786,7 +786,7 @@ void testMain() { expect(keyboardConverter.keyIsPressed(physicalAltRight), true); keyDataList.clear(); // Remove key data generated by handleEvent. - glassPane.dispatchEvent(context.primaryDown()); + flutterViewElement.dispatchEvent(context.primaryDown()); expect(keyDataList.length, 1); expectKeyData(keyDataList.last, type: ui.KeyEventType.up, @@ -814,7 +814,7 @@ void testMain() { final DomElement semanticsPlaceholder = createDomElement('flt-semantics-placeholder'); - glassPane.append(semanticsPlaceholder); + flutterViewElement.append(semanticsPlaceholder); // Press on the semantics placeholder. semanticsPlaceholder.dispatchEvent(context.primaryDown( @@ -853,7 +853,7 @@ void testMain() { packets.clear(); // Release the pointer on the semantics placeholder. - glassPane.dispatchEvent(context.primaryUp( + flutterViewElement.dispatchEvent(context.primaryUp( clientX: 100.0, clientY: 200.0, )); @@ -887,7 +887,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.hover()); + flutterViewElement.dispatchEvent(context.hover()); expect(packets, hasLength(1)); expect(packets.single.data, hasLength(2)); @@ -911,7 +911,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.primaryDown( + flutterViewElement.dispatchEvent(context.primaryDown( clientX: 10.0, clientY: 10.0, )); @@ -923,7 +923,7 @@ void testMain() { expect(packets[0].data[1].change, equals(ui.PointerChange.down)); packets.clear(); - glassPane.dispatchEvent(context.primaryDown( + flutterViewElement.dispatchEvent(context.primaryDown( clientX: 20.0, clientY: 20.0, )); @@ -948,7 +948,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -956,7 +956,7 @@ void testMain() { deltaY: 10, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 20, clientY: 50, @@ -964,14 +964,14 @@ void testMain() { deltaY: 10, )); - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 0, buttons: 1, clientX: 20.0, clientY: 50.0, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 1, clientX: 30, clientY: 60, @@ -1073,7 +1073,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1122,7 +1122,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1170,7 +1170,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1181,7 +1181,7 @@ void testMain() { timeStamp: 0, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1192,7 +1192,7 @@ void testMain() { timeStamp: 10, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1203,7 +1203,7 @@ void testMain() { timeStamp: 20, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1214,7 +1214,7 @@ void testMain() { timeStamp: 1000, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1225,7 +1225,7 @@ void testMain() { timeStamp: 1010, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1236,7 +1236,7 @@ void testMain() { timeStamp: 2000, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1411,7 +1411,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1419,7 +1419,7 @@ void testMain() { deltaY: 120, )); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1431,7 +1431,7 @@ void testMain() { debugOperatingSystemOverride = OperatingSystem.macOs; KeyboardBinding.instance?.converter.handleEvent(keyDownEvent('ControlLeft', 'Control', kCtrl)); - glassPane.dispatchEvent(context.wheel( + flutterViewElement.dispatchEvent(context.wheel( buttons: 0, clientX: 10, clientY: 10, @@ -1516,7 +1516,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.hover( + flutterViewElement.dispatchEvent(context.hover( clientX: 10.0, clientY: 10.0, )); @@ -1539,7 +1539,7 @@ void testMain() { expect(packets[0].data[1].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.hover( + flutterViewElement.dispatchEvent(context.hover( clientX: 20.0, clientY: 20.0, )); @@ -1554,7 +1554,7 @@ void testMain() { expect(packets[0].data[0].physicalDeltaY, equals(10.0 * dpi)); packets.clear(); - glassPane.dispatchEvent(context.primaryDown( + flutterViewElement.dispatchEvent(context.primaryDown( clientX: 20.0, clientY: 20.0, )); @@ -1569,7 +1569,7 @@ void testMain() { expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.primaryMove( + flutterViewElement.dispatchEvent(context.primaryMove( clientX: 40.0, clientY: 30.0, )); @@ -1584,7 +1584,7 @@ void testMain() { expect(packets[0].data[0].physicalDeltaY, equals(10.0 * dpi)); packets.clear(); - glassPane.dispatchEvent(context.primaryUp( + flutterViewElement.dispatchEvent(context.primaryUp( clientX: 40.0, clientY: 30.0, )); @@ -1599,7 +1599,7 @@ void testMain() { expect(packets[0].data[0].physicalDeltaY, equals(0.0)); packets.clear(); - glassPane.dispatchEvent(context.hover( + flutterViewElement.dispatchEvent(context.hover( clientX: 20.0, clientY: 10.0, )); @@ -1614,7 +1614,7 @@ void testMain() { expect(packets[0].data[0].physicalDeltaY, equals(-20.0 * dpi)); packets.clear(); - glassPane.dispatchEvent(context.primaryDown( + flutterViewElement.dispatchEvent(context.primaryDown( clientX: 20.0, clientY: 10.0, )); @@ -1646,7 +1646,7 @@ void testMain() { // Add and hover - glassPane.dispatchEvent(context.hover( + flutterViewElement.dispatchEvent(context.hover( clientX: 10, clientY: 11, )); @@ -1665,7 +1665,7 @@ void testMain() { expect(packets[0].data[1].buttons, equals(0)); packets.clear(); - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 0, buttons: 1, clientX: 10.0, @@ -1680,7 +1680,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 1, clientX: 20.0, @@ -1695,7 +1695,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(1)); packets.clear(); - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 0, clientX: 20.0, clientY: 21.0, @@ -1710,7 +1710,7 @@ void testMain() { packets.clear(); // Drag with secondary button - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 20.0, @@ -1725,7 +1725,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 2, clientX: 30.0, @@ -1740,7 +1740,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 30.0, clientY: 31.0, @@ -1755,7 +1755,7 @@ void testMain() { packets.clear(); // Drag with middle button - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 1, buttons: 4, clientX: 30.0, @@ -1770,7 +1770,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 4, clientX: 40.0, @@ -1785,7 +1785,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(4)); packets.clear(); - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 1, clientX: 40.0, clientY: 41.0, @@ -1801,7 +1801,7 @@ void testMain() { // Leave - glassPane.dispatchEvent(context.mouseLeave( + flutterViewElement.dispatchEvent(context.mouseLeave( buttons: 1, clientX: 1000.0, clientY: 2000.0, @@ -1809,7 +1809,7 @@ void testMain() { expect(packets, isEmpty); packets.clear(); - glassPane.dispatchEvent(context.mouseLeave( + flutterViewElement.dispatchEvent(context.mouseLeave( buttons: 0, clientX: 1000.0, clientY: 2000.0, @@ -1839,7 +1839,7 @@ void testMain() { }; // Press LMB. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 0, buttons: 1, )); @@ -1854,7 +1854,7 @@ void testMain() { packets.clear(); // Press MMB. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: 1, buttons: 5, )); @@ -1866,7 +1866,7 @@ void testMain() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: 0, buttons: 4, )); @@ -1878,7 +1878,7 @@ void testMain() { packets.clear(); // Release MMB. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 1, )); expect(packets, hasLength(1)); @@ -1906,7 +1906,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10, @@ -1927,7 +1927,7 @@ void testMain() { expect(packets[0].data[1].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 2, clientX: 20.0, @@ -1942,7 +1942,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 2, clientX: 20.0, @@ -1957,7 +1957,7 @@ void testMain() { expect(packets[0].data[0].buttons, equals(2)); packets.clear(); - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 20.0, clientY: 21.0, @@ -1993,7 +1993,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, )); @@ -2010,7 +2010,7 @@ void testMain() { // Press LMB. The event will have "button: -1" here, despite the change // in "buttons", probably because the "press" gesture was absorbed by // dismissing the context menu. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 3, )); @@ -2022,7 +2022,7 @@ void testMain() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: 0, buttons: 2, )); @@ -2034,7 +2034,7 @@ void testMain() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, )); expect(packets, hasLength(1)); @@ -2066,7 +2066,7 @@ void testMain() { // Press RMB popping up the context menu, then release by LMB down and up. // Browser won't send up event in that case. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, )); @@ -2081,7 +2081,7 @@ void testMain() { packets.clear(); // User now hovers. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: _kNoButtonChange, buttons: 0, )); @@ -2115,7 +2115,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseMove( + flutterViewElement.dispatchEvent(context.mouseMove( button: -1, buttons: 2, clientX: 10.0, @@ -2152,7 +2152,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10.0, @@ -2171,7 +2171,7 @@ void testMain() { // Move the mouse. The event will have "buttons: 0" because RMB was // released but the browser didn't send a pointerup/mouseup event. // The hover is also triggered at a different position. - glassPane.dispatchEvent(context.hover( + flutterViewElement.dispatchEvent(context.hover( clientX: 20.0, clientY: 20.0, )); @@ -2215,7 +2215,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10.0, @@ -2232,7 +2232,7 @@ void testMain() { packets.clear(); // Press LMB. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 0, buttons: 3, clientX: 20.0, @@ -2246,7 +2246,7 @@ void testMain() { packets.clear(); // Release LMB. - glassPane.dispatchEvent(context.primaryUp( + flutterViewElement.dispatchEvent(context.primaryUp( clientX: 20.0, clientY: 20.0, )); @@ -2278,7 +2278,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10.0, @@ -2296,7 +2296,7 @@ void testMain() { // Press RMB again. In Chrome, when RMB is clicked again while the // context menu is still active, it sends a pointerdown/mousedown event // with "buttons:0". We convert this to pointer up, pointer down. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 0, clientX: 20.0, @@ -2318,7 +2318,7 @@ void testMain() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 20.0, clientY: 20.0, @@ -2353,7 +2353,7 @@ void testMain() { }; // Press RMB, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10.0, @@ -2369,7 +2369,7 @@ void testMain() { packets.clear(); // RMB up. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 10.0, clientY: 10.0, @@ -2384,7 +2384,7 @@ void testMain() { // Press RMB again. In Chrome, when RMB is clicked again while the // context menu is still active, it sends a pointerdown/mousedown event // with "buttons:0". - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 0, clientX: 20.0, @@ -2401,7 +2401,7 @@ void testMain() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 20.0, clientY: 20.0, @@ -2439,7 +2439,7 @@ void testMain() { }; // Press RMB and hold, popping up the context menu. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 10.0, @@ -2455,7 +2455,7 @@ void testMain() { packets.clear(); // Release RMB. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, clientX: 10.0, clientY: 10.0, @@ -2467,7 +2467,7 @@ void testMain() { packets.clear(); // Press RMB again, in a different location. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 2, clientX: 20.0, @@ -2505,7 +2505,7 @@ void testMain() { }; // Press and hold LMB. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 0, buttons: 1, clientX: 5.0, @@ -2524,7 +2524,7 @@ void testMain() { // Press and hold RMB. The pointer is already down, so we only send a move // to update the position of the pointer. - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( button: 2, buttons: 3, clientX: 20.0, @@ -2540,7 +2540,7 @@ void testMain() { // Release LMB. The pointer is still down (RMB), so we only send a move to // update the position of the pointer. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 0, buttons: 2, clientX: 30.0, @@ -2555,7 +2555,7 @@ void testMain() { packets.clear(); // Release RMB. There's no more buttons down, so we send an up event. - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( button: 2, buttons: 0, clientX: 30.0, @@ -2574,7 +2574,7 @@ void testMain() { if (!isIosSafari) _PointerEventContext(), if (!isIosSafari) _MouseEventContext(), ], - 'correctly detects up event outside of glasspane', + 'correctly detects up event outside of flutterViewElement', (_ButtonedEventMixin context) { PointerBinding.instance!.debugOverrideDetector(context); // This can happen when the up event occurs while the mouse is outside the @@ -2586,26 +2586,26 @@ void testMain() { }; // Press and drag around. - glassPane.dispatchEvent(context.primaryDown( + flutterViewElement.dispatchEvent(context.primaryDown( clientX: 10.0, clientY: 10.0, )); - glassPane.dispatchEvent(context.primaryMove( + flutterViewElement.dispatchEvent(context.primaryMove( clientX: 12.0, clientY: 10.0, )); - glassPane.dispatchEvent(context.primaryMove( + flutterViewElement.dispatchEvent(context.primaryMove( clientX: 15.0, clientY: 10.0, )); - glassPane.dispatchEvent(context.primaryMove( + flutterViewElement.dispatchEvent(context.primaryMove( clientX: 20.0, clientY: 10.0, )); packets.clear(); - // Move outside the glasspane. - glassPane.dispatchEvent(context.primaryMove( + // Move outside the flutterViewElement. + flutterViewElement.dispatchEvent(context.primaryMove( clientX: 900.0, clientY: 1900.0, )); @@ -2616,8 +2616,8 @@ void testMain() { expect(packets[0].data[0].physicalY, equals(1900.0 * dpi)); packets.clear(); - // Release outside the glasspane. - glassPane.dispatchEvent(context.primaryUp( + // Release outside the flutterViewElement. + flutterViewElement.dispatchEvent(context.primaryUp( clientX: 1000.0, clientY: 2000.0, )); @@ -2653,7 +2653,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 100, clientY: 101), _TouchDetails(pointer: 3, clientX: 200, clientY: 201), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); if (context.runtimeType == _PointerEventContext) { expect(packets.length, 2); expect(packets[0].data.length, 2); @@ -2700,7 +2700,7 @@ void testMain() { context.multiTouchMove(const <_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 300, clientY: 302), _TouchDetails(pointer: 2, clientX: 400, clientY: 402), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); if (context.runtimeType == _PointerEventContext) { expect(packets.length, 2); expect(packets[0].data.length, 1); @@ -2734,7 +2734,7 @@ void testMain() { // One pointer up context.multiTouchUp(const <_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 300, clientY: 302), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -2757,7 +2757,7 @@ void testMain() { // Another pointer up context.multiTouchUp(const <_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 400, clientY: 402), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -2781,7 +2781,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 500, clientY: 501), _TouchDetails(pointer: 2, clientX: 600, clientY: 601), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); if (context.runtimeType == _PointerEventContext) { expect(packets.length, 2); expect(packets[0].data.length, 2); @@ -2843,13 +2843,13 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 100, clientY: 101), _TouchDetails(pointer: 3, clientX: 200, clientY: 201), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); packets.clear(); // Down event is tested in other tests. // One pointer cancel context.multiTouchCancel(const <_TouchDetails>[ _TouchDetails(pointer: 3, clientX: 300, clientY: 302), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets.length, 1); expect(packets[0].data.length, 2); expect(packets[0].data[0].change, equals(ui.PointerChange.cancel)); @@ -2887,7 +2887,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 1, clientX: 100, clientY: 101), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); // An add will be synthesized. expect(packets[0].data, hasLength(2)); @@ -2900,7 +2900,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 200, clientY: 202), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); // An add will be synthesized. expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); @@ -2927,12 +2927,12 @@ void testMain() { context.multiTouchUp(const <_TouchDetails>[ _TouchDetails(pointer: 23, clientX: 200, clientY: 202), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(0)); context.multiTouchCancel(const <_TouchDetails>[ _TouchDetails(pointer: 24, clientX: 200, clientY: 202), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(0)); }, ); @@ -2955,7 +2955,7 @@ void testMain() { packets.add(packet); }; - glassPane.dispatchEvent(context.mouseDown( + flutterViewElement.dispatchEvent(context.mouseDown( pointerId: 12, button: 0, buttons: 1, @@ -2973,7 +2973,7 @@ void testMain() { expect( () { - glassPane.dispatchEvent(context.mouseUp( + flutterViewElement.dispatchEvent(context.mouseUp( pointerId: 41, button: 0, buttons: 0, @@ -3008,7 +3008,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 1, clientX: 20, clientY: 20), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); @@ -3030,7 +3030,7 @@ void testMain() { context.multiTouchMove(const <_TouchDetails>[ _TouchDetails(pointer: 1, clientX: 40, clientY: 30), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(1)); expect(packets[0].data[0].change, equals(ui.PointerChange.move)); @@ -3044,7 +3044,7 @@ void testMain() { context.multiTouchUp(const <_TouchDetails>[ _TouchDetails(pointer: 1, clientX: 40, clientY: 30), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.up)); @@ -3066,7 +3066,7 @@ void testMain() { context.multiTouchDown(const <_TouchDetails>[ _TouchDetails(pointer: 2, clientX: 20, clientY: 10), - ]).forEach(glassPane.dispatchEvent); + ]).forEach(flutterViewElement.dispatchEvent); expect(packets, hasLength(1)); expect(packets[0].data, hasLength(2)); expect(packets[0].data[0].change, equals(ui.PointerChange.add)); diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart index e773cb33dce..e4da65ad22d 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_test.dart @@ -157,18 +157,16 @@ void _testEngineSemanticsOwner() { expect(semantics().semanticsEnabled, isFalse); // Synthesize a click on the placeholder. - final DomElement placeholder = - appHostNode.querySelector('flt-semantics-placeholder')!; + final DomElement placeholder = flutterViewEmbedder.glassPaneShadow + .querySelector('flt-semantics-placeholder')!; expect(placeholder.isConnected, isTrue); final DomRect rect = placeholder.getBoundingClientRect(); - placeholder.dispatchEvent(createDomMouseEvent( - 'click', { - 'clientX': (rect.left + (rect.right - rect.left) / 2).floor(), - 'clientY': (rect.top + (rect.bottom - rect.top) / 2).floor(), - } - )); + placeholder.dispatchEvent(createDomMouseEvent('click', { + 'clientX': (rect.left + (rect.right - rect.left) / 2).floor(), + 'clientY': (rect.top + (rect.bottom - rect.top) / 2).floor(), + })); // On mobile semantics is enabled asynchronously. if (isMobile) { @@ -182,7 +180,8 @@ void _testEngineSemanticsOwner() { test('accessibilityFeatures copyWith function works', () { const EngineAccessibilityFeatures original = EngineAccessibilityFeatures(0); - EngineAccessibilityFeatures copy = original.copyWith(accessibleNavigation: true); + EngineAccessibilityFeatures copy = + original.copyWith(accessibleNavigation: true); expect(copy.accessibleNavigation, true); expect(copy.boldText, false); expect(copy.disableAnimations, false); @@ -254,8 +253,8 @@ void _testEngineSemanticsOwner() { .instance.accessibilityFeatures.accessibleNavigation, isFalse); - final DomElement placeholder = - appHostNode.querySelector('flt-semantics-placeholder')!; + final DomElement placeholder = flutterViewEmbedder.glassPaneShadow + .querySelector('flt-semantics-placeholder')!; expect(placeholder.isConnected, isTrue); @@ -428,7 +427,8 @@ void _testEngineSemanticsOwner() { ); }); - test('forwards events to framework if shouldEnableSemantics returns true', () { + test('forwards events to framework if shouldEnableSemantics returns true', + () { final MockSemanticsEnabler mockSemanticsEnabler = MockSemanticsEnabler(); semantics().semanticsHelper.semanticsEnabler = mockSemanticsEnabler; final DomEvent pointerEvent = createDomEvent('Event', 'pointermove'); @@ -439,8 +439,7 @@ void _testEngineSemanticsOwner() { class MockSemanticsEnabler implements SemanticsEnabler { @override - void dispose() { - } + void dispose() {} @override bool get isWaitingToEnableSemantics => throw UnimplementedError(); @@ -716,7 +715,8 @@ void _testContainer() { semantics().semanticsEnabled = false; }); - test('renders in traversal order, hit-tests in reverse z-index order', () async { + test('renders in traversal order, hit-tests in reverse z-index order', + () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -809,7 +809,9 @@ void _testContainer() { semantics().semanticsEnabled = false; }); - test('container nodes are transparent and leaf children are opaque hit-test wise', () async { + test( + 'container nodes are transparent and leaf children are opaque hit-test wise', + () async { semantics() ..debugOverrideTimestampFunction(() => _testTime) ..semanticsEnabled = true; @@ -835,10 +837,12 @@ void _testContainer() { final DomElement root = appHostNode.querySelector('#flt-semantic-node-0')!; expect(root.style.pointerEvents, 'none'); - final DomElement child1 = appHostNode.querySelector('#flt-semantic-node-1')!; + final DomElement child1 = + appHostNode.querySelector('#flt-semantic-node-1')!; expect(child1.style.pointerEvents, 'all'); - final DomElement child2 = appHostNode.querySelector('#flt-semantic-node-2')!; + final DomElement child2 = + appHostNode.querySelector('#flt-semantic-node-2')!; expect(child2.style.pointerEvents, 'all'); semantics().semanticsEnabled = false; @@ -1179,8 +1183,8 @@ void _testIncrementables() { '''); - final DomHTMLInputElement input = appHostNode.querySelector('input')! as - DomHTMLInputElement; + final DomHTMLInputElement input = + appHostNode.querySelector('input')! as DomHTMLInputElement; input.value = '2'; input.dispatchEvent(createDomEvent('Event', 'change')); @@ -1212,8 +1216,8 @@ void _testIncrementables() { '''); - final DomHTMLInputElement input = appHostNode.querySelector('input')! as - DomHTMLInputElement; + final DomHTMLInputElement input = + appHostNode.querySelector('input')! as DomHTMLInputElement; input.value = '0'; input.dispatchEvent(createDomEvent('Event', 'change')); @@ -1299,11 +1303,11 @@ void _testTextField() { final DomElement textField = appHostNode.querySelector('input[data-semantics-role="text-field"]')!; - expect(appHostNode.activeElement, isNot(textField)); + expect(appHostNode.ownerDocument?.activeElement, isNot(textField)); textField.focus(); - expect(appHostNode.activeElement, textField); + expect(appHostNode.ownerDocument?.activeElement, textField); expect(await logger.idLog.first, 0); expect(await logger.actionLog.first, ui.SemanticsAction.tap); @@ -1616,13 +1620,15 @@ void _testTappable() { } updateTappable(enabled: false); - expectSemanticsTree(''); + expectSemanticsTree( + ''); updateTappable(enabled: true); expectSemanticsTree(''); updateTappable(enabled: false); - expectSemanticsTree(''); + expectSemanticsTree( + ''); updateTappable(enabled: true); expectSemanticsTree(''); @@ -1647,7 +1653,7 @@ void _testTappable() { ); tester.apply(); - expect(flutterViewEmbedder.glassPaneShadow.activeElement, tester.getSemanticsObject(0).element); + expect(domDocument.activeElement, tester.getSemanticsObject(0).element); semantics().semanticsEnabled = false; }); } @@ -1942,13 +1948,13 @@ void _testPlatformView() { ui.window.render(sceneBuilder.build()); final ui.SemanticsUpdateBuilder builder = ui.SemanticsUpdateBuilder(); - updateNode( - builder, - rect: const ui.Rect.fromLTRB(0, 0, 20, 60), - childrenInTraversalOrder: Int32List.fromList([1, 2, 3]), - childrenInHitTestOrder: Int32List.fromList([1, 2, 3]), - transform: Float64List.fromList(Matrix4.diagonal3Values(ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1).storage) - ); + updateNode(builder, + rect: const ui.Rect.fromLTRB(0, 0, 20, 60), + childrenInTraversalOrder: Int32List.fromList([1, 2, 3]), + childrenInHitTestOrder: Int32List.fromList([1, 2, 3]), + transform: Float64List.fromList(Matrix4.diagonal3Values( + ui.window.devicePixelRatio, ui.window.devicePixelRatio, 1) + .storage)); updateNode( builder, id: 1, @@ -2009,22 +2015,19 @@ void _testPlatformView() { final DomElement platformViewElement = flutterViewEmbedder.glassPaneElement.querySelector('#view-0')!; - final DomRect platformViewRect = platformViewElement.getBoundingClientRect(); + final DomRect platformViewRect = + platformViewElement.getBoundingClientRect(); expect(platformViewRect.left, 0); expect(platformViewRect.top, 15); expect(platformViewRect.right, 20); expect(platformViewRect.bottom, 45); - // This test is only relevant for shadow DOM because we only really support - // proper platform view embedding in browsers that support shadow DOM. - final DomShadowRoot shadowRoot = appHostNode.node as DomShadowRoot; - // Hit test child 1 - expect(shadowRoot.elementFromPoint(10, 10), child1); + expect(domDocument.elementFromPoint(10, 10), child1); // Hit test overlap between child 1 and 2 // TODO(yjbanov): this is a known limitation, see https://github.com/flutter/flutter/issues/101439 - expect(shadowRoot.elementFromPoint(10, 20), child1); + expect(domDocument.elementFromPoint(10, 20), child1); // Hit test child 2 // Clicking at the location of the middle semantics node should allow the @@ -2043,10 +2046,10 @@ void _testPlatformView() { expect(domDocument.elementFromPoint(10, 30), platformViewElement); // Hit test overlap between child 2 and 3 - expect(shadowRoot.elementFromPoint(10, 40), child3); + expect(domDocument.elementFromPoint(10, 40), child3); // Hit test child 3 - expect(shadowRoot.elementFromPoint(10, 50), child3); + expect(domDocument.elementFromPoint(10, 50), child3); semantics().semanticsEnabled = false; }); @@ -2111,9 +2114,11 @@ void updateNode( String value = '', List valueAttributes = const [], String increasedValue = '', - List increasedValueAttributes = const [], + List increasedValueAttributes = + const [], String decreasedValue = '', - List decreasedValueAttributes = const [], + List decreasedValueAttributes = + const [], String tooltip = '', ui.TextDirection textDirection = ui.TextDirection.ltr, Float64List? transform, diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart index 67b5c4a3a44..bd2ebed3f52 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/semantics_tester.dart @@ -8,7 +8,6 @@ import 'dart:typed_data'; import 'package:test/test.dart'; import 'package:ui/src/engine/dom.dart'; import 'package:ui/src/engine/embedder.dart'; -import 'package:ui/src/engine/host_node.dart'; import 'package:ui/src/engine/semantics.dart'; import 'package:ui/src/engine/util.dart'; import 'package:ui/src/engine/vector_math.dart'; @@ -19,10 +18,11 @@ import '../../common/matchers.dart'; /// Gets the DOM host where the Flutter app is being rendered. /// /// This function returns the correct host for the flutter app under testing, -/// so we don't have to hardcode domDocument across the test. (The host of a -/// normal flutter app used to be domDocument, but now that the app is wrapped -/// in a Shadow DOM, that's not the case anymore.) -HostNode get appHostNode => flutterViewEmbedder.glassPaneShadow; +/// so we don't have to hardcode domDocument across the test. The semantics +/// tree has moved outside of the shadowDOM as a workaround for a password +/// autofill bug on Chrome. +/// Ref: https://github.com/flutter/flutter/issues/87735 +DomElement get appHostNode => flutterViewEmbedder.flutterViewElement; /// CSS style applied to the root of the semantics tree. // TODO(yjbanov): this should be handled internally by [expectSemanticsTree]. diff --git a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart index b9ffec7f640..59ccd3ca56b 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/semantics/text_field_test.dart @@ -102,11 +102,11 @@ void testMain() { final DomElement textField = appHostNode .querySelector('input[data-semantics-role="text-field"]')!; - expect(appHostNode.activeElement, isNot(textField)); + expect(appHostNode.ownerDocument?.activeElement, isNot(textField)); textField.focus(); - expect(appHostNode.activeElement, textField); + expect(appHostNode.ownerDocument?.activeElement, textField); expect(await logger.idLog.first, 0); expect(await logger.actionLog.first, ui.SemanticsAction.tap); }, // TODO(yjbanov): https://github.com/flutter/flutter/issues/46638 @@ -115,8 +115,7 @@ void testMain() { skip: browserEngine != BrowserEngine.blink); test('Syncs semantic state from framework', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); int changeCount = 0; int actionCount = 0; @@ -140,8 +139,7 @@ void testMain() { final TextField textField = textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); expect(textField.editableElement, strategy.domElement); expect(textField.activeEditableElement.getAttribute('aria-label'), 'greeting'); expect(textField.activeEditableElement.style.width, '10px'); @@ -154,8 +152,7 @@ void testMain() { rect: const ui.Rect.fromLTWH(0, 0, 12, 17), ); - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); expect(strategy.domElement, null); expect(textField.activeEditableElement.getAttribute('aria-label'), 'farewell'); expect(textField.activeEditableElement.style.width, '12px'); @@ -201,8 +198,7 @@ void testMain() { test( 'Updates editing state when receiving framework messages from the text input channel', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.enable( singlelineConfig, @@ -246,8 +242,7 @@ void testMain() { }); test('Gives up focus after DOM blur', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.enable( singlelineConfig, @@ -262,13 +257,11 @@ void testMain() { final TextField textField = textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField; expect(textField.editableElement, strategy.domElement); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); // The input should not refocus after blur. textField.activeEditableElement.blur(); - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.disable(); }); @@ -288,8 +281,7 @@ void testMain() { isFocused: true, ); expect(strategy.domElement, isNotNull); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.disable(); expect(strategy.domElement, isNull); @@ -300,8 +292,7 @@ void testMain() { expect(appHostNode.contains(textField.editableElement), isTrue); // Editing element is not enabled. expect(strategy.isEnabled, isFalse); - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); }); test('Refocuses when setting editing state', () { @@ -316,13 +307,11 @@ void testMain() { isFocused: true, ); expect(strategy.domElement, isNotNull); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); // Blur the element without telling the framework. strategy.activeDomElement.blur(); - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); // The input will have focus after editing state is set and semantics updated. strategy.setEditingState(EditingState(text: 'foo')); @@ -340,8 +329,7 @@ void testMain() { value: 'hello', isFocused: true, ); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.disable(); }); @@ -361,8 +349,7 @@ void testMain() { final DomHTMLTextAreaElement textArea = strategy.domElement! as DomHTMLTextAreaElement; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.enable( singlelineConfig, @@ -371,8 +358,7 @@ void testMain() { ); textArea.blur(); - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.disable(); // It doesn't remove the textarea from the DOM. @@ -456,13 +442,14 @@ void testMain() { createTwoFieldSemantics(tester, focusFieldId: 1); expect(tester.apply().length, 3); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, tester.getTextField(1).editableElement); + expect(appHostNode.ownerDocument?.activeElement, + tester.getTextField(1).editableElement); expect(strategy.domElement, tester.getTextField(1).editableElement); createTwoFieldSemantics(tester, focusFieldId: 2); expect(tester.apply().length, 3); - expect(appHostNode.activeElement, tester.getTextField(2).editableElement); + expect(appHostNode.ownerDocument?.activeElement, + tester.getTextField(2).editableElement); expect(strategy.domElement, tester.getTextField(2).editableElement); } }); @@ -510,8 +497,7 @@ void testMain() { }); test('Syncs semantic state from framework', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); int changeCount = 0; int actionCount = 0; @@ -535,8 +521,7 @@ void testMain() { final TextField textField = textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); expect(textField.editableElement, strategy.domElement); expect(textField.activeEditableElement.getAttribute('aria-label'), 'greeting'); expect(textField.activeEditableElement.style.width, '10px'); @@ -552,8 +537,7 @@ void testMain() { appHostNode.querySelector('flt-semantics[role="textbox"]')!; expect(strategy.domElement, null); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, textBox); + expect(appHostNode.ownerDocument?.activeElement, textBox); expect(textBox.getAttribute('aria-label'), 'farewell'); strategy.disable(); @@ -596,8 +580,7 @@ void testMain() { test( 'Updates editing state when receiving framework messages from the text input channel', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.enable( singlelineConfig, @@ -641,8 +624,7 @@ void testMain() { }); test('Gives up focus after DOM blur', () { - expect(domDocument.activeElement, domDocument.body); - expect(appHostNode.activeElement, null); + expect(appHostNode.ownerDocument?.activeElement, domDocument.body); strategy.enable( singlelineConfig, @@ -657,15 +639,13 @@ void testMain() { textFieldSemantics.debugRoleManagerFor(Role.textField)! as TextField; expect(textField.editableElement, strategy.domElement); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); // The input should not refocus after blur. textField.activeEditableElement.blur(); final DomElement textBox = appHostNode.querySelector('flt-semantics[role="textbox"]')!; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, textBox); + expect(appHostNode.ownerDocument?.activeElement, textBox); strategy.disable(); }); @@ -686,8 +666,7 @@ void testMain() { isFocused: true, ); expect(strategy.domElement, isNotNull); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.disable(); expect(strategy.domElement, isNull); @@ -700,8 +679,7 @@ void testMain() { // Focus is on the semantic object final DomElement textBox = appHostNode.querySelector('flt-semantics[role="textbox"]')!; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, textBox); + expect(appHostNode.ownerDocument?.activeElement, textBox); }); test('Refocuses when setting editing state', () { @@ -716,15 +694,13 @@ void testMain() { isFocused: true, ); expect(strategy.domElement, isNotNull); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); // Blur the element without telling the framework. strategy.activeDomElement.blur(); final DomElement textBox = appHostNode.querySelector('flt-semantics[role="textbox"]')!; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, textBox); + expect(appHostNode.ownerDocument?.activeElement, textBox); // The input will have focus after editing state is set and semantics updated. strategy.setEditingState(EditingState(text: 'foo')); @@ -742,8 +718,7 @@ void testMain() { value: 'hello', isFocused: true, ); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.disable(); }); @@ -760,9 +735,9 @@ void testMain() { isMultiline: true, ); - final DomHTMLTextAreaElement textArea = strategy.domElement! as DomHTMLTextAreaElement; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, strategy.domElement); + final DomHTMLTextAreaElement textArea = + strategy.domElement! as DomHTMLTextAreaElement; + expect(appHostNode.ownerDocument?.activeElement, strategy.domElement); strategy.enable( singlelineConfig, @@ -776,8 +751,7 @@ void testMain() { final DomElement textBox = appHostNode.querySelector('flt-semantics[role="textbox"]')!; - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, textBox); + expect(appHostNode.ownerDocument?.activeElement, textBox); strategy.disable(); // It removes the textarea from the DOM. @@ -840,13 +814,14 @@ void testMain() { createTwoFieldSemanticsForIos(tester, focusFieldId: 1); expect(tester.apply().length, 3); - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(appHostNode.activeElement, tester.getTextField(1).editableElement); + expect(appHostNode.ownerDocument?.activeElement, + tester.getTextField(1).editableElement); expect(strategy.domElement, tester.getTextField(1).editableElement); createTwoFieldSemanticsForIos(tester, focusFieldId: 2); expect(tester.apply().length, 3); - expect(appHostNode.activeElement, tester.getTextField(2).editableElement); + expect(appHostNode.ownerDocument?.activeElement, + tester.getTextField(2).editableElement); expect(strategy.domElement, tester.getTextField(2).editableElement); } }); diff --git a/engine/src/flutter/lib/web_ui/test/engine/text_editing_test.dart b/engine/src/flutter/lib/web_ui/test/engine/text_editing_test.dart index 8577a9487f5..830be7b916b 100644 --- a/engine/src/flutter/lib/web_ui/test/engine/text_editing_test.dart +++ b/engine/src/flutter/lib/web_ui/test/engine/text_editing_test.dart @@ -90,7 +90,8 @@ Future testMain() async { ); // The focus initially is on the body. expect(domDocument.activeElement, domDocument.body); - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); editingStrategy!.enable( singlelineConfig, @@ -105,8 +106,8 @@ Future testMain() async { final DomElement input = defaultTextEditingRoot.querySelector('input')!; // Now the editing element should have focus. - expect(domDocument.activeElement, flutterViewEmbedder.glassPaneElement); - expect(defaultTextEditingRoot.activeElement, input); + expect(domDocument.activeElement, input); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, input); expect(editingStrategy!.domElement, input); expect(input.getAttribute('type'), null); @@ -121,7 +122,8 @@ Future testMain() async { ); // The focus is back to the body. expect(domDocument.activeElement, domDocument.body); - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); }); test('Respects read-only config', () { @@ -280,7 +282,7 @@ Future testMain() async { final DomHTMLTextAreaElement textarea = defaultTextEditingRoot.querySelector('textarea')! as DomHTMLTextAreaElement; // Now the textarea should have focus. - expect(defaultTextEditingRoot.activeElement, textarea); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, textarea); expect(editingStrategy!.domElement, textarea); textarea.value = 'foo\nbar'; @@ -302,7 +304,8 @@ Future testMain() async { // The textarea should be cleaned up. expect(defaultTextEditingRoot.querySelectorAll('textarea'), hasLength(0)); // The focus is back to the body. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); // There should be no input action. expect(lastInputAction, isNull); @@ -619,7 +622,7 @@ Future testMain() async { const MethodCall show = MethodCall('TextInput.show'); sendFrameworkMessage(codec.encodeMethodCall(show)); - expect(defaultTextEditingRoot.activeElement, + expect(defaultTextEditingRoot.ownerDocument?.activeElement, textEditing!.strategy.domElement); }); @@ -679,7 +682,8 @@ Future testMain() async { sendFrameworkMessage(codec.encodeMethodCall(setEditingState)); // Editing shouldn't have started yet. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); const MethodCall show = MethodCall('TextInput.show'); sendFrameworkMessage(codec.encodeMethodCall(show)); @@ -704,7 +708,7 @@ Future testMain() async { expect(spy.messages, hasLength(0)); await Future.delayed(Duration.zero); // DOM element still keeps the focus. - expect(defaultTextEditingRoot.activeElement, + expect(defaultTextEditingRoot.ownerDocument?.activeElement, textEditing!.strategy.domElement); }); @@ -722,7 +726,8 @@ Future testMain() async { sendFrameworkMessage(codec.encodeMethodCall(setEditingState)); // Editing shouldn't have started yet. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); const MethodCall show = MethodCall('TextInput.show'); sendFrameworkMessage(codec.encodeMethodCall(show)); @@ -751,7 +756,8 @@ Future testMain() async { spy.messages[0].methodName, 'TextInputClient.onConnectionClosed'); await Future.delayed(Duration.zero); // DOM element loses the focus. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); }, // Test on ios-safari only. skip: browserEngine != BrowserEngine.webkit || @@ -772,7 +778,8 @@ Future testMain() async { sendFrameworkMessage(codec.encodeMethodCall(setEditingState)); // Editing shouldn't have started yet. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); const MethodCall show = MethodCall('TextInput.show'); sendFrameworkMessage(codec.encodeMethodCall(show)); @@ -1151,7 +1158,8 @@ Future testMain() async { // In Safari Desktop Autofill menu appears as soon as an element is // focused, therefore the input element is only focused after the // location is received. - expect(defaultTextEditingRoot.activeElement, inputElement); + expect( + defaultTextEditingRoot.ownerDocument?.activeElement, inputElement); expect(inputElement.selectionStart, 2); expect(inputElement.selectionEnd, 3); } @@ -1164,7 +1172,7 @@ Future testMain() async { sendFrameworkMessage(codec.encodeMethodCall(updateSizeAndTransform)); // Check the element still has focus. User can keep editing. - expect(defaultTextEditingRoot.activeElement, + expect(defaultTextEditingRoot.ownerDocument?.activeElement, textEditing!.strategy.domElement); // Check the cursor location is the same. @@ -1764,7 +1772,8 @@ Future testMain() async { sendFrameworkMessage(codec.encodeMethodCall(setClient)); // Editing shouldn't have started yet. - expect(defaultTextEditingRoot.activeElement, null); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, + domDocument.body); const MethodCall show = MethodCall('TextInput.show'); sendFrameworkMessage(codec.encodeMethodCall(show)); @@ -2646,7 +2655,7 @@ void checkInputEditingState( expect(element, isNotNull); expect(domInstanceOfString(element, 'HTMLInputElement'), true); final DomHTMLInputElement input = element! as DomHTMLInputElement; - expect(defaultTextEditingRoot.activeElement, input); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, input); expect(input.value, text); expect(input.selectionStart, start); expect(input.selectionEnd, end); @@ -2672,7 +2681,7 @@ void checkTextAreaEditingState( int start, int end, ) { - expect(defaultTextEditingRoot.activeElement, textarea); + expect(defaultTextEditingRoot.ownerDocument?.activeElement, textarea); expect(textarea.value, text); expect(textarea.selectionStart, start); expect(textarea.selectionEnd, end);