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

Commit 252b4df

Browse files
committed
[web] PointerBinding per view
1 parent 6e8e55e commit 252b4df

File tree

6 files changed

+142
-136
lines changed

6 files changed

+142
-136
lines changed

lib/web_ui/lib/src/engine/embedder.dart

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,7 @@ import '../engine.dart' show buildMode, renderer;
88
import 'browser_detection.dart';
99
import 'configuration.dart';
1010
import 'dom.dart';
11-
import 'keyboard_binding.dart';
1211
import 'platform_dispatcher.dart';
13-
import 'pointer_binding.dart';
1412
import 'text_editing/text_editing.dart';
1513
import 'view_embedder/style_manager.dart';
1614
import 'window.dart';
@@ -65,16 +63,6 @@ class FlutterViewEmbedder {
6563
},
6664
);
6765

68-
renderer.reset(this);
69-
70-
// TODO(mdebbar): Move these to `engine/initialization.dart`.
71-
72-
KeyboardBinding.initInstance();
73-
PointerBinding.initInstance(
74-
_flutterViewElement,
75-
KeyboardBinding.instance!.converter,
76-
);
77-
7866
window.onResize.listen(_metricsDidChange);
7967
}
8068

lib/web_ui/lib/src/engine/initialization.dart

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,13 @@ Future<void> initializeEngineUi() async {
218218
_initializationState = DebugEngineInitializationState.initializingUi;
219219

220220
RawKeyboard.initialize(onMacOs: operatingSystem == OperatingSystem.macOs);
221-
ensureImplicitViewInitialized(hostElement: configuration.hostElement);
222-
ensureFlutterViewEmbedderInitialized();
221+
final EngineFlutterWindow implicitView =
222+
ensureImplicitViewInitialized(hostElement: configuration.hostElement);
223+
final FlutterViewEmbedder embedder = ensureFlutterViewEmbedderInitialized();
224+
renderer.reset(embedder);
225+
226+
KeyboardBinding.initInstance();
227+
PointerBinding.initInstance(implicitView, KeyboardBinding.instance!.converter);
223228
_initializationState = DebugEngineInitializationState.initialized;
224229
}
225230

lib/web_ui/lib/src/engine/keyboard_binding.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ class KeyboardConverter {
258258
bool _disposed = false;
259259
void dispose() {
260260
_disposed = true;
261+
clearPressedKeys();
261262
}
262263

263264
// On macOS, CapsLock behaves differently in that, a keydown event occurs when
@@ -699,4 +700,8 @@ class KeyboardConverter {
699700
bool keyIsPressed(int physical) {
700701
return _pressingRecords.containsKey(physical);
701702
}
703+
704+
void clearPressedKeys() {
705+
_pressingRecords.clear();
706+
}
702707
}

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

Lines changed: 55 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import 'pointer_binding/event_position_helper.dart';
1818
import 'pointer_converter.dart';
1919
import 'safe_browser_api.dart';
2020
import 'semantics.dart';
21+
import 'window.dart';
2122

2223
/// Set this flag to true to log all the browser events.
2324
const bool _debugLogPointerEvents = false;
@@ -73,21 +74,31 @@ int convertButtonToButtons(int button) {
7374
/// Wrapping the Safari iOS workaround that adds a dummy event listener
7475
/// More info about the issue and workaround: https://github.com/flutter/flutter/issues/70858
7576
class SafariPointerEventWorkaround {
76-
static SafariPointerEventWorkaround instance = SafariPointerEventWorkaround();
77+
DomEventListener? _listener;
7778

7879
void workAroundMissingPointerEvents() {
79-
domDocument.addEventListener('touchstart', createDomEventListener((DomEvent event) {}));
80+
_listener = createDomEventListener((_) {});
81+
domDocument.addEventListener('touchstart', _listener);
82+
}
83+
84+
void dispose() {
85+
if (_listener != null) {
86+
domDocument.removeEventListener('touchstart', _listener);
87+
}
8088
}
8189
}
8290

8391
class PointerBinding {
8492
PointerBinding(
85-
this.flutterViewElement,
86-
this._keyboardConverter, [
87-
this._detector = const PointerSupportDetector(),
88-
]) : _pointerDataConverter = PointerDataConverter() {
93+
this.view,
94+
this._keyboardConverter, {
95+
PointerSupportDetector detector = const PointerSupportDetector(),
96+
SafariPointerEventWorkaround? safariWorkaround,
97+
}) : _pointerDataConverter = PointerDataConverter(),
98+
_detector = detector {
8999
if (isIosSafari) {
90-
SafariPointerEventWorkaround.instance.workAroundMissingPointerEvents();
100+
_safariWorkaround = safariWorkaround ?? SafariPointerEventWorkaround();
101+
_safariWorkaround!.workAroundMissingPointerEvents();
91102
}
92103
_adapter = _createAdapter();
93104
}
@@ -96,9 +107,9 @@ class PointerBinding {
96107
static PointerBinding? get instance => _instance;
97108
static PointerBinding? _instance;
98109

99-
static void initInstance(DomElement flutterViewElement, KeyboardConverter keyboardConverter) {
110+
static void initInstance(EngineFlutterView view, KeyboardConverter keyboardConverter) {
100111
if (_instance == null) {
101-
_instance = PointerBinding(flutterViewElement, keyboardConverter);
112+
_instance = PointerBinding(view, keyboardConverter);
102113
assert(() {
103114
registerHotRestartListener(_instance!.dispose);
104115
return true;
@@ -108,37 +119,28 @@ class PointerBinding {
108119

109120
final ClickDebouncer clickDebouncer = ClickDebouncer();
110121

122+
SafariPointerEventWorkaround? _safariWorkaround;
123+
111124
/// Performs necessary clean up for PointerBinding including removing event listeners
112125
/// and clearing the existing pointer state
113126
void dispose() {
114-
_adapter.clearListeners();
127+
_adapter.dispose();
115128
_pointerDataConverter.clearPointerState();
129+
_safariWorkaround?.dispose();
116130
clickDebouncer.reset();
117131
}
118132

119-
final DomElement flutterViewElement;
133+
final EngineFlutterView view;
134+
DomElement get rootElement => view.dom.rootElement;
120135

121136
final PointerSupportDetector _detector;
122137
final PointerDataConverter _pointerDataConverter;
123-
KeyboardConverter _keyboardConverter;
138+
final KeyboardConverter _keyboardConverter;
124139
late _BaseAdapter _adapter;
125140

126-
@visibleForTesting
127-
void debugReset() {
128-
_adapter.clearListeners();
129-
_adapter = _createAdapter();
130-
_pointerDataConverter.clearPointerState();
131-
}
132-
133-
@visibleForTesting
134-
void debugOverrideKeyboardConverter(KeyboardConverter keyboardConverter) {
135-
_keyboardConverter = keyboardConverter;
136-
debugReset();
137-
}
138-
139141
_BaseAdapter _createAdapter() {
140142
if (_detector.hasPointerEvents) {
141-
return _PointerAdapter(clickDebouncer.onPointerData, flutterViewElement, _pointerDataConverter, _keyboardConverter);
143+
return _PointerAdapter(clickDebouncer.onPointerData, view, _pointerDataConverter, _keyboardConverter);
142144
}
143145
throw UnsupportedError(
144146
'This browser does not support pointer events which '
@@ -470,27 +472,29 @@ class _Listener {
470472
abstract class _BaseAdapter {
471473
_BaseAdapter(
472474
this._callback,
473-
this.flutterViewElement,
475+
this._view,
474476
this._pointerDataConverter,
475477
this._keyboardConverter,
476478
) {
477479
setup();
478480
}
479481

480482
final List<_Listener> _listeners = <_Listener>[];
481-
final DomElement flutterViewElement;
483+
final EngineFlutterView _view;
482484
final _PointerDataCallback _callback;
483485
final PointerDataConverter _pointerDataConverter;
484486
final KeyboardConverter _keyboardConverter;
485487
DomWheelEvent? _lastWheelEvent;
486488
bool _lastWheelEventWasTrackpad = false;
487489

490+
DomElement get _rootElement => _view.dom.rootElement;
491+
488492
/// Each subclass is expected to override this method to attach its own event
489493
/// listeners and convert events into pointer events.
490494
void setup();
491495

492-
/// Remove all active event listeners.
493-
void clearListeners() {
496+
/// Cleans up all event listeners attached by this adapter.
497+
void dispose() {
494498
for (final _Listener listener in _listeners) {
495499
listener.unregister();
496500
}
@@ -499,7 +503,7 @@ abstract class _BaseAdapter {
499503

500504
/// Adds a listener for the given [eventName] to [target].
501505
///
502-
/// Generally speaking, down and leave events should use [flutterViewElement]
506+
/// Generally speaking, down and leave events should use [_rootElement]
503507
/// as the [target], while move and up events should use [domWindow]
504508
/// instead, because the browser doesn't fire the latter two for DOM elements
505509
/// when the pointer is outside the window.
@@ -512,7 +516,7 @@ abstract class _BaseAdapter {
512516
if (_debugLogPointerEvents) {
513517
if (domInstanceOfString(event, 'PointerEvent')) {
514518
final DomPointerEvent pointerEvent = event as DomPointerEvent;
515-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
519+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
516520
print('${pointerEvent.type} '
517521
'${offset.dx.toStringAsFixed(1)},'
518522
'${offset.dy.toStringAsFixed(1)}');
@@ -636,25 +640,25 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
636640
deltaX *= _defaultScrollLineHeight!;
637641
deltaY *= _defaultScrollLineHeight!;
638642
case domDeltaPage:
639-
deltaX *= ui.window.physicalSize.width;
640-
deltaY *= ui.window.physicalSize.height;
643+
deltaX *= _view.physicalSize.width;
644+
deltaY *= _view.physicalSize.height;
641645
case domDeltaPixel:
642646
if (operatingSystem == OperatingSystem.macOs && (isSafari || isFirefox)) {
643647
// Safari and Firefox seem to report delta in logical pixels while
644648
// Chrome uses physical pixels.
645-
deltaX *= ui.window.devicePixelRatio;
646-
deltaY *= ui.window.devicePixelRatio;
649+
deltaX *= _view.devicePixelRatio;
650+
deltaY *= _view.devicePixelRatio;
647651
}
648652
default:
649653
break;
650654
}
651655

652656
final List<ui.PointerData> data = <ui.PointerData>[];
653-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
657+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
654658
bool ignoreCtrlKey = false;
655659
if (operatingSystem == OperatingSystem.macOs) {
656-
ignoreCtrlKey = (KeyboardBinding.instance?.converter.keyIsPressed(kPhysicalControlLeft) ?? false) ||
657-
(KeyboardBinding.instance?.converter.keyIsPressed(kPhysicalControlRight) ?? false);
660+
ignoreCtrlKey = (_keyboardConverter.keyIsPressed(kPhysicalControlLeft)) ||
661+
(_keyboardConverter.keyIsPressed(kPhysicalControlRight));
658662
}
659663
if (event.ctrlKey && !ignoreCtrlKey) {
660664
_pointerDataConverter.convert(
@@ -664,8 +668,8 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
664668
kind: kind,
665669
signalKind: ui.PointerSignalKind.scale,
666670
device: deviceId,
667-
physicalX: offset.dx * ui.window.devicePixelRatio,
668-
physicalY: offset.dy * ui.window.devicePixelRatio,
671+
physicalX: offset.dx * _view.devicePixelRatio,
672+
physicalY: offset.dy * _view.devicePixelRatio,
669673
buttons: event.buttons!.toInt(),
670674
pressure: 1.0,
671675
pressureMax: 1.0,
@@ -679,8 +683,8 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
679683
kind: kind,
680684
signalKind: ui.PointerSignalKind.scroll,
681685
device: deviceId,
682-
physicalX: offset.dx * ui.window.devicePixelRatio,
683-
physicalY: offset.dy * ui.window.devicePixelRatio,
686+
physicalX: offset.dx * _view.devicePixelRatio,
687+
physicalY: offset.dy * _view.devicePixelRatio,
684688
buttons: event.buttons!.toInt(),
685689
pressure: 1.0,
686690
pressureMax: 1.0,
@@ -696,7 +700,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
696700
void _addWheelEventListener(DartDomEventListener handler) {
697701
_listeners.add(_Listener.register(
698702
event: 'wheel',
699-
target: flutterViewElement,
703+
target: _rootElement,
700704
handler: handler,
701705
passive: false,
702706
));
@@ -886,7 +890,7 @@ typedef _PointerEventListener = dynamic Function(DomPointerEvent event);
886890
class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
887891
_PointerAdapter(
888892
super.callback,
889-
super.flutterViewElement,
893+
super.view,
890894
super.pointerDataConverter,
891895
super.keyboardConverter,
892896
);
@@ -942,7 +946,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
942946

943947
@override
944948
void setup() {
945-
_addPointerEventListener(flutterViewElement, 'pointerdown', (DomPointerEvent event) {
949+
_addPointerEventListener(_rootElement, 'pointerdown', (DomPointerEvent event) {
946950
final int device = _getPointerId(event);
947951
final List<ui.PointerData> pointerData = <ui.PointerData>[];
948952
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
@@ -977,7 +981,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
977981
_callback(event, pointerData);
978982
});
979983

980-
_addPointerEventListener(flutterViewElement, 'pointerleave', (DomPointerEvent event) {
984+
_addPointerEventListener(_rootElement, 'pointerleave', (DomPointerEvent event) {
981985
final int device = _getPointerId(event);
982986
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
983987
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -1006,7 +1010,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
10061010

10071011
// A browser fires cancel event if it concludes the pointer will no longer
10081012
// be able to generate events (example: device is deactivated)
1009-
_addPointerEventListener(flutterViewElement, 'pointercancel', (DomPointerEvent event) {
1013+
_addPointerEventListener(_rootElement, 'pointercancel', (DomPointerEvent event) {
10101014
final int device = _getPointerId(event);
10111015
if (_hasSanitizer(device)) {
10121016
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -1033,16 +1037,16 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
10331037
final double tilt = _computeHighestTilt(event);
10341038
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!);
10351039
final num? pressure = event.pressure;
1036-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
1040+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
10371041
_pointerDataConverter.convert(
10381042
data,
10391043
change: details.change,
10401044
timeStamp: timeStamp,
10411045
kind: kind,
10421046
signalKind: ui.PointerSignalKind.none,
10431047
device: _getPointerId(event),
1044-
physicalX: offset.dx * ui.window.devicePixelRatio,
1045-
physicalY: offset.dy * ui.window.devicePixelRatio,
1048+
physicalX: offset.dx * _view.devicePixelRatio,
1049+
physicalY: offset.dy * _view.devicePixelRatio,
10461050
buttons: details.buttons,
10471051
pressure: pressure == null ? 0.0 : pressure.toDouble(),
10481052
pressureMax: 1.0,

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ import 'dart:typed_data';
77
import 'package:ui/ui.dart' as ui show Offset;
88

99
import '../dom.dart';
10-
import '../platform_dispatcher.dart';
1110
import '../semantics.dart' show EngineSemanticsOwner;
1211
import '../text_editing/text_editing.dart';
1312
import '../vector_math.dart';
13+
import '../window.dart';
1414

1515
/// Returns an [ui.Offset] of the position of [event], relative to the position of [actualTarget].
1616
///
@@ -23,17 +23,15 @@ import '../vector_math.dart';
2323
///
2424
/// It also takes into account semantics being enabled to fix the case where
2525
/// offsetX, offsetY == 0 (TalkBack events).
26-
ui.Offset computeEventOffsetToTarget(DomMouseEvent event, DomElement actualTarget) {
26+
ui.Offset computeEventOffsetToTarget(DomMouseEvent event, EngineFlutterView view) {
27+
final DomElement actualTarget = view.dom.rootElement;
2728
// On a TalkBack event
2829
if (EngineSemanticsOwner.instance.semanticsEnabled && event.offsetX == 0 && event.offsetY == 0) {
2930
return _computeOffsetForTalkbackEvent(event, actualTarget);
3031
}
3132

3233
// On one of our text-editing nodes
33-
// TODO(mdebbar): There could be multiple views with multiple text editing hosts.
34-
// https://github.com/flutter/flutter/issues/137344
35-
final DomElement textEditingHost = EnginePlatformDispatcher.instance.implicitView!.dom.textEditingHost;
36-
final bool isInput = textEditingHost.contains(event.target! as DomNode);
34+
final bool isInput = view.dom.textEditingHost.contains(event.target! as DomNode);
3735
if (isInput) {
3836
final EditableTextGeometry? inputGeometry = textEditing.strategy.geometry;
3937
if (inputGeometry != null) {

0 commit comments

Comments
 (0)