Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 76cec66

Browse files
committed
Fix compute physicalX/Y for TalkBack events.
Extracted compute function to a helper file.
1 parent b293d36 commit 76cec66

File tree

5 files changed

+124
-24
lines changed

5 files changed

+124
-24
lines changed

ci/licenses_golden/licenses_flutter

+1
Original file line numberDiff line numberDiff line change
@@ -4413,6 +4413,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/message_handler.
44134413
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views/slots.dart
44144414
FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart
44154415
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
4416+
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding/event_position_helper.dart
44164417
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart
44174418
FILE: ../../../flutter/lib/web_ui/lib/src/engine/profiler.dart
44184419
FILE: ../../../flutter/lib/web_ui/lib/src/engine/raw_keyboard.dart

lib/web_ui/lib/src/engine.dart

+1
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ export 'engine/platform_views/message_handler.dart';
122122
export 'engine/platform_views/slots.dart';
123123
export 'engine/plugins.dart';
124124
export 'engine/pointer_binding.dart';
125+
export 'engine/pointer_binding/event_position_helper.dart';
125126
export 'engine/pointer_converter.dart';
126127
export 'engine/profiler.dart';
127128
export 'engine/raw_keyboard.dart';

lib/web_ui/lib/src/engine/dom.dart

+5
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,9 @@ class DomHTMLElement extends DomElement {}
462462

463463
extension DomHTMLElementExtension on DomHTMLElement {
464464
external double get offsetWidth;
465+
external double get offsetLeft;
466+
external double get offsetTop;
467+
external DomHTMLElement? get offsetParent;
465468
}
466469

467470
@JS()
@@ -1090,6 +1093,8 @@ extension DomMouseEventExtension on DomMouseEvent {
10901093
external double get clientY;
10911094
external double get offsetX;
10921095
external double get offsetY;
1096+
external double get pageX;
1097+
external double get pageY;
10931098
DomPoint get client => DomPoint(clientX, clientY);
10941099
DomPoint get offset => DomPoint(offsetX, offsetY);
10951100
external double get button;

lib/web_ui/lib/src/engine/pointer_binding.dart

+4-24
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import '../engine.dart' show registerHotRestartListener;
1212
import 'browser_detection.dart';
1313
import 'dom.dart';
1414
import 'platform_dispatcher.dart';
15+
import 'pointer_binding/event_position_helper.dart';
1516
import 'pointer_converter.dart';
1617
import 'safe_browser_api.dart';
1718
import 'semantics.dart';
@@ -342,27 +343,6 @@ abstract class _BaseAdapter {
342343
((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
343344
return Duration(milliseconds: ms, microseconds: micro);
344345
}
345-
346-
/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
347-
///
348-
/// The offset is *not* multiplied by DPR or anything else, it's the closest
349-
/// to what the DOM would return if we had currentTarget readily available.
350-
///
351-
// TODO(dit): Make this understand 3D transforms in the platform view case, https://github.com/flutter/flutter/issues/117091
352-
static ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
353-
if (event.target != actualTarget) {
354-
// We're on top of a platform view.
355-
final DomElement target = event.target! as DomElement;
356-
// We can't use currentTarget because it gets lost when the PointerEvents
357-
// are coalesced!
358-
final DomRect targetRect = target.getBoundingClientRect();
359-
final DomRect actualTargetRect = actualTarget.getBoundingClientRect();
360-
final double offsetTop = targetRect.y - actualTargetRect.y;
361-
final double offsetLeft = targetRect.x - actualTargetRect.x;
362-
return ui.Offset(event.offsetX + offsetLeft, event.offsetY + offsetTop);
363-
}
364-
return ui.Offset(event.offsetX, event.offsetY);
365-
}
366346
}
367347

368348
mixin _WheelEventListenerMixin on _BaseAdapter {
@@ -472,7 +452,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
472452
}
473453

474454
final List<ui.PointerData> data = <ui.PointerData>[];
475-
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
455+
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
476456
_pointerDataConverter.convert(
477457
data,
478458
change: ui.PointerChange.hover,
@@ -844,7 +824,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
844824
final double tilt = _computeHighestTilt(event);
845825
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!);
846826
final num? pressure = event.pressure;
847-
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
827+
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
848828
_pointerDataConverter.convert(
849829
data,
850830
change: details.change,
@@ -1170,7 +1150,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin {
11701150
assert(data != null);
11711151
assert(event != null);
11721152
assert(details != null);
1173-
final ui.Offset offset = _BaseAdapter.computeEventOffsetToTarget(event, glassPaneElement);
1153+
final ui.Offset offset = computeEventOffsetToTarget(event, glassPaneElement);
11741154
_pointerDataConverter.convert(
11751155
data,
11761156
change: details.change,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:ui/ui.dart' as ui show Offset;
6+
7+
import '../dom.dart';
8+
import '../semantics.dart' show EngineSemanticsOwner;
9+
10+
/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
11+
///
12+
/// The offset is *not* multiplied by DPR or anything else, it's the closest
13+
/// to what the DOM would return if we had currentTarget readily available.
14+
///
15+
/// This needs an `actualTarget`, because the `event.currentTarget` (which is what
16+
/// this would really need to use) gets lost when the `event` comes from a "coalesced"
17+
/// event.
18+
///
19+
/// It also takes into account semantics being enabled to fix the case where
20+
/// offsetX, offsetY == 0 (TalkBack events).
21+
//
22+
// TODO(dit): Make this understand 3D transforms in the platform view case, https://github.com/flutter/flutter/issues/117091
23+
ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
24+
// On top of a platform view
25+
if (event.target != actualTarget) {
26+
return _computeOffsetOnPlatformView(event, actualTarget);
27+
}
28+
// On a TalkBack event
29+
if (EngineSemanticsOwner.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) {
30+
return _computeOffsetForTalkbackEvent(event, actualTarget);
31+
}
32+
// Return the offsetX/Y in the normal case.
33+
return ui.Offset(event.offsetX, event.offsetY);
34+
}
35+
36+
/// Computes the event offset when hovering over a platformView.
37+
///
38+
/// This still uses offsetX/Y, but adds the offset from the top/left corner of the
39+
/// platform view to the glass pane (`actualTarget`).
40+
///
41+
/// ×--FlutterView(actualTarget)--------------+
42+
/// |\ |
43+
/// | x1,y1 |
44+
/// | |
45+
/// | |
46+
/// | ×-PlatformView(target)---------+ |
47+
/// | |\ | |
48+
/// | | x2,y2 | |
49+
/// | | | |
50+
/// | | × (event) | |
51+
/// | | \ | |
52+
/// | | offsetX, offsetY | |
53+
/// | | (Relative to PlatformView) | |
54+
/// | +------------------------------+ |
55+
/// +-----------------------------------------+
56+
///
57+
/// Offset between PlatformView and FlutterView (xP, yP) = (x2 - x1, y2 - y1)
58+
///
59+
/// Event offset relative to FlutterView = (offsetX + xP, offsetY + yP)
60+
ui.Offset _computeOffsetOnPlatformView(DomMouseEvent event, DomElement actualTarget) {
61+
final DomElement target = event.target! as DomElement;
62+
final DomRect targetRect = target.getBoundingClientRect();
63+
final DomRect actualTargetRect = actualTarget.getBoundingClientRect();
64+
final double offsetTop = targetRect.y - actualTargetRect.y;
65+
final double offsetLeft = targetRect.x - actualTargetRect.x;
66+
return ui.Offset(event.offsetX + offsetLeft, event.offsetY + offsetTop);
67+
}
68+
69+
/// Computes the event offset when TalkBack is firing the event.
70+
///
71+
/// In this case, we need to use the clientX/Y position of the event (which are
72+
/// relative to the absolute top-left corner of the page, including scroll), then
73+
/// deduct the offsetLeft/Top from every offsetParent of the `actualTarget`.
74+
///
75+
/// ×-Page----║-------------------------------+
76+
/// | ║ |
77+
/// | ×-------║--------offsetParent(s)-----+ |
78+
/// | |\ | |
79+
/// | | offsetLeft, offsetTop | |
80+
/// | | | |
81+
/// | | | |
82+
/// | | ×-----║-------------actualTarget-+ | |
83+
/// | | | | | |
84+
/// ═════ × ─ (scrollLeft, scrollTop)═ ═ ═
85+
/// | | | | | |
86+
/// | | | × | | |
87+
/// | | | \ | | |
88+
/// | | | clientX, clientY | | |
89+
/// | | | (Relative to Page + Scroll) | | |
90+
/// | | +-----║--------------------------+ | |
91+
/// | +-------║----------------------------+ |
92+
/// +---------║-------------------------------+
93+
///
94+
/// Computing the offset of the event relative to the actualTarget requires to
95+
/// compute the clientX, clientY of the actualTarget. To do that, we iterate
96+
/// up the offsetParent elements of actualTarget adding their offset and scroll
97+
/// positions. Finally, we deduct that from clientX, clientY of the event.
98+
99+
ui.Offset _computeOffsetForTalkbackEvent(DomMouseEvent event, DomElement actualTarget) {
100+
assert(EngineSemanticsOwner.instance.semanticsEnabled);
101+
// Use clientX/clientY as the position of the event (this is relative to
102+
// the top left of the page, including scroll)
103+
double offsetX = event.clientX;
104+
double offsetY = event.clientY;
105+
// Compute the scroll offset of actualTarget
106+
DomHTMLElement parent = actualTarget as DomHTMLElement;
107+
while(parent.offsetParent != null){
108+
offsetX -= parent.offsetLeft - parent.scrollLeft;
109+
offsetY -= parent.offsetTop - parent.scrollTop;
110+
parent = parent.offsetParent!;
111+
}
112+
return ui.Offset(offsetX, offsetY);
113+
}

0 commit comments

Comments
 (0)