From 97be4385cef91d42b3324641b5db72c500e29f40 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 5 Dec 2024 13:07:20 -0500 Subject: [PATCH] [web] Use `eventTarget` when computing pointer offset (#56949) These changes are mainly things I missed in https://github.com/flutter/engine/pull/56719 Fixes https://github.com/flutter/flutter/issues/159804 --- .../event_position_helper.dart | 23 +++++++++---- .../event_position_helper_test.dart | 32 +++++++++++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart b/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart index 494f940d58eb4..82954325ab85e 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart @@ -18,9 +18,12 @@ import '../window.dart'; /// The offset is *not* multiplied by DPR or anything else, it's the closest /// to what the DOM would return if we had currentTarget readily available. /// -/// This needs an `eventTarget`, because the `event.target` (which is what -/// this would really need to use) gets lost when the `event` comes from a -/// "coalesced" event (see https://github.com/flutter/flutter/issues/155987). +/// This takes an optional `eventTarget`, because the `event.target` may have +/// the wrong value for "coalesced" events. See: +/// +/// - https://github.com/flutter/flutter/issues/155987 +/// - https://github.com/flutter/flutter/issues/159804 +/// - https://g-issues.chromium.org/issues/382473107 /// /// It also takes into account semantics being enabled to fix the case where /// offsetX, offsetY == 0 (TalkBack events). @@ -41,12 +44,12 @@ ui.Offset computeEventOffsetToTarget( if (isInput) { final EditableTextGeometry? inputGeometry = textEditing.strategy.geometry; if (inputGeometry != null) { - return _computeOffsetForInputs(event, inputGeometry); + return _computeOffsetForInputs(event, eventTarget, inputGeometry); } } // On another DOM Element (normally a platform view) - final bool isTargetOutsideOfShadowDOM = event.target != actualTarget; + final bool isTargetOutsideOfShadowDOM = eventTarget != actualTarget; if (isTargetOutsideOfShadowDOM) { final DomRect origin = actualTarget.getBoundingClientRect(); // event.clientX/Y and origin.x/y are relative **to the viewport**. @@ -70,8 +73,14 @@ ui.Offset computeEventOffsetToTarget( /// sent from the framework, which includes information on how to transform the /// underlying input element. We transform the `event.offset` points we receive /// using the values from the input's transform matrix. -ui.Offset _computeOffsetForInputs(DomMouseEvent event, EditableTextGeometry inputGeometry) { - final DomElement targetElement = event.target! as DomHTMLElement; +/// +/// See [computeEventOffsetToTarget] for more information about `eventTarget`. +ui.Offset _computeOffsetForInputs( + DomMouseEvent event, + DomEventTarget eventTarget, + EditableTextGeometry inputGeometry, +) { + final DomElement targetElement = eventTarget as DomElement; final DomHTMLElement domElement = textEditing.strategy.activeDomElement; assert(targetElement == domElement, 'The targeted input element must be the active input element'); final Float32List transformValues = inputGeometry.globalTransform; diff --git a/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart b/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart index eca204cd6e613..14f41dccbbf9a 100644 --- a/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart +++ b/lib/web_ui/test/engine/pointer_binding/event_position_helper_test.dart @@ -32,6 +32,7 @@ void doTests() { group('computeEventOffsetToTarget', () { setUp(() { view = EngineFlutterView(EnginePlatformDispatcher.instance, domDocument.body!); + EnginePlatformDispatcher.instance.viewManager.registerView(view); rootElement = view.dom.rootElement; eventSource = createDomElement('div-event-source'); rootElement.append(eventSource); @@ -58,6 +59,7 @@ void doTests() { }); tearDown(() { + EnginePlatformDispatcher.instance.viewManager.unregisterView(view.viewId); view.dispose(); }); @@ -101,6 +103,36 @@ void doTests() { expect(offset.dy, 110); }); + test('eventTarget takes precedence', () async { + final input = view.dom.textEditingHost.appendChild(createDomElement('input')); + + textEditing.strategy.enable( + InputConfiguration(viewId: view.viewId), + onChange: (_, __) {}, + onAction: (_) {}, + ); + + addTearDown(() { + textEditing.strategy.disable(); + }); + + final moveEvent = createDomPointerEvent('pointermove', { + 'bubbles': true, + 'clientX': 10, + 'clientY': 20, + }); + + expect( + () => computeEventOffsetToTarget(moveEvent, view), + throwsA(anything), + ); + + expect( + () => computeEventOffsetToTarget(moveEvent, view, eventTarget: input), + returnsNormally, + ); + }); + test('Event dispatched by TalkBack gets a computed offset', () async { // Fill this in to test _computeOffsetForTalkbackEvent }, skip: 'To be implemented!');