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

Commit bc10542

Browse files
committed
[web] PointerBinding per view
1 parent 0a8b615 commit bc10542

File tree

10 files changed

+270
-208
lines changed

10 files changed

+270
-208
lines changed

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

Lines changed: 0 additions & 10 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';
@@ -67,14 +65,6 @@ class FlutterViewEmbedder {
6765

6866
renderer.reset(this);
6967

70-
// TODO(mdebbar): Move these to `engine/initialization.dart`.
71-
72-
KeyboardBinding.initInstance();
73-
PointerBinding.initInstance(
74-
_flutterViewElement,
75-
KeyboardBinding.instance!.converter,
76-
);
77-
7868
window.onResize.listen(_metricsDidChange);
7969
}
8070

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

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

220220
RawKeyboard.initialize(onMacOs: operatingSystem == OperatingSystem.macOs);
221+
KeyboardBinding.initInstance();
222+
221223
if (!configuration.multiViewEnabled) {
222224
ensureImplicitViewInitialized(hostElement: configuration.hostElement);
223225
ensureFlutterViewEmbedderInitialized();

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/platform_dispatcher.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -634,7 +634,7 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
634634
const StandardMessageCodec codec = StandardMessageCodec();
635635
// TODO(yjbanov): Dispatch the announcement to the correct view?
636636
// https://github.com/flutter/flutter/issues/137445
637-
implicitView!.accessibilityAnnouncements.handleMessage(codec, data);
637+
implicitView?.accessibilityAnnouncements.handleMessage(codec, data);
638638
replyToPlatformMessage(callback, codec.encodeMessage(true));
639639
return;
640640

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

Lines changed: 71 additions & 54 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,27 @@ 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();
115-
_pointerDataConverter.clearPointerState();
127+
_adapter.dispose();
128+
_safariWorkaround?.dispose();
116129
clickDebouncer.reset();
117130
}
118131

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

121135
final PointerSupportDetector _detector;
122136
final PointerDataConverter _pointerDataConverter;
123-
KeyboardConverter _keyboardConverter;
137+
final KeyboardConverter _keyboardConverter;
124138
late _BaseAdapter _adapter;
125139

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-
139140
_BaseAdapter _createAdapter() {
140141
if (_detector.hasPointerEvents) {
141-
return _PointerAdapter(clickDebouncer.onPointerData, flutterViewElement, _pointerDataConverter, _keyboardConverter);
142+
return _PointerAdapter(clickDebouncer.onPointerData, view, _pointerDataConverter, _keyboardConverter);
142143
}
143144
throw UnsupportedError(
144145
'This browser does not support pointer events which '
@@ -470,27 +471,29 @@ class _Listener {
470471
abstract class _BaseAdapter {
471472
_BaseAdapter(
472473
this._callback,
473-
this.flutterViewElement,
474+
this._view,
474475
this._pointerDataConverter,
475476
this._keyboardConverter,
476477
) {
477478
setup();
478479
}
479480

480481
final List<_Listener> _listeners = <_Listener>[];
481-
final DomElement flutterViewElement;
482+
final EngineFlutterView _view;
482483
final _PointerDataCallback _callback;
483484
final PointerDataConverter _pointerDataConverter;
484485
final KeyboardConverter _keyboardConverter;
485486
DomWheelEvent? _lastWheelEvent;
486487
bool _lastWheelEventWasTrackpad = false;
487488

489+
DomElement get _rootElement => _view.dom.rootElement;
490+
488491
/// Each subclass is expected to override this method to attach its own event
489492
/// listeners and convert events into pointer events.
490493
void setup();
491494

492-
/// Remove all active event listeners.
493-
void clearListeners() {
495+
/// Cleans up all event listeners attached by this adapter.
496+
void dispose() {
494497
for (final _Listener listener in _listeners) {
495498
listener.unregister();
496499
}
@@ -499,7 +502,7 @@ abstract class _BaseAdapter {
499502

500503
/// Adds a listener for the given [eventName] to [target].
501504
///
502-
/// Generally speaking, down and leave events should use [flutterViewElement]
505+
/// Generally speaking, down and leave events should use [_rootElement]
503506
/// as the [target], while move and up events should use [domWindow]
504507
/// instead, because the browser doesn't fire the latter two for DOM elements
505508
/// when the pointer is outside the window.
@@ -512,7 +515,7 @@ abstract class _BaseAdapter {
512515
if (_debugLogPointerEvents) {
513516
if (domInstanceOfString(event, 'PointerEvent')) {
514517
final DomPointerEvent pointerEvent = event as DomPointerEvent;
515-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
518+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
516519
print('${pointerEvent.type} '
517520
'${offset.dx.toStringAsFixed(1)},'
518521
'${offset.dy.toStringAsFixed(1)}');
@@ -636,36 +639,37 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
636639
deltaX *= _defaultScrollLineHeight!;
637640
deltaY *= _defaultScrollLineHeight!;
638641
case domDeltaPage:
639-
deltaX *= ui.window.physicalSize.width;
640-
deltaY *= ui.window.physicalSize.height;
642+
deltaX *= _view.physicalSize.width;
643+
deltaY *= _view.physicalSize.height;
641644
case domDeltaPixel:
642645
if (operatingSystem == OperatingSystem.macOs && (isSafari || isFirefox)) {
643646
// Safari and Firefox seem to report delta in logical pixels while
644647
// Chrome uses physical pixels.
645-
deltaX *= ui.window.devicePixelRatio;
646-
deltaY *= ui.window.devicePixelRatio;
648+
deltaX *= _view.devicePixelRatio;
649+
deltaY *= _view.devicePixelRatio;
647650
}
648651
default:
649652
break;
650653
}
651654

652655
final List<ui.PointerData> data = <ui.PointerData>[];
653-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
656+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
654657
bool ignoreCtrlKey = false;
655658
if (operatingSystem == OperatingSystem.macOs) {
656-
ignoreCtrlKey = (KeyboardBinding.instance?.converter.keyIsPressed(kPhysicalControlLeft) ?? false) ||
657-
(KeyboardBinding.instance?.converter.keyIsPressed(kPhysicalControlRight) ?? false);
659+
ignoreCtrlKey = (_keyboardConverter.keyIsPressed(kPhysicalControlLeft)) ||
660+
(_keyboardConverter.keyIsPressed(kPhysicalControlRight));
658661
}
659662
if (event.ctrlKey && !ignoreCtrlKey) {
660663
_pointerDataConverter.convert(
661664
data,
665+
viewId: _view.viewId,
662666
change: ui.PointerChange.hover,
663667
timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp!),
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,
@@ -674,13 +678,14 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
674678
} else {
675679
_pointerDataConverter.convert(
676680
data,
681+
viewId: _view.viewId,
677682
change: ui.PointerChange.hover,
678683
timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp!),
679684
kind: kind,
680685
signalKind: ui.PointerSignalKind.scroll,
681686
device: deviceId,
682-
physicalX: offset.dx * ui.window.devicePixelRatio,
683-
physicalY: offset.dy * ui.window.devicePixelRatio,
687+
physicalX: offset.dx * _view.devicePixelRatio,
688+
physicalY: offset.dy * _view.devicePixelRatio,
684689
buttons: event.buttons!.toInt(),
685690
pressure: 1.0,
686691
pressureMax: 1.0,
@@ -696,7 +701,7 @@ mixin _WheelEventListenerMixin on _BaseAdapter {
696701
void _addWheelEventListener(DartDomEventListener handler) {
697702
_listeners.add(_Listener.register(
698703
event: 'wheel',
699-
target: flutterViewElement,
704+
target: _rootElement,
700705
handler: handler,
701706
passive: false,
702707
));
@@ -886,13 +891,24 @@ typedef _PointerEventListener = dynamic Function(DomPointerEvent event);
886891
class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
887892
_PointerAdapter(
888893
super.callback,
889-
super.flutterViewElement,
894+
super.view,
890895
super.pointerDataConverter,
891896
super.keyboardConverter,
892897
);
893898

894899
final Map<int, _ButtonSanitizer> _sanitizers = <int, _ButtonSanitizer>{};
895900

901+
DomEventTarget get _globalTarget {
902+
// When the Flutter app owns the full page, we want to listen on window for
903+
// some events. Otherwise, we just want to listen on the root element of the
904+
// view.
905+
// TODO(mdebbar): Is there a better way of doing this?
906+
if (_view == EnginePlatformDispatcher.instance.implicitView) {
907+
return domWindow;
908+
}
909+
return _view.dom.rootElement;
910+
}
911+
896912
@visibleForTesting
897913
Iterable<int> debugTrackedDevices() => _sanitizers.keys;
898914

@@ -942,7 +958,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
942958

943959
@override
944960
void setup() {
945-
_addPointerEventListener(flutterViewElement, 'pointerdown', (DomPointerEvent event) {
961+
_addPointerEventListener(_rootElement, 'pointerdown', (DomPointerEvent event) {
946962
final int device = _getPointerId(event);
947963
final List<ui.PointerData> pointerData = <ui.PointerData>[];
948964
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
@@ -961,7 +977,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
961977
});
962978

963979
// Why `domWindow` you ask? See this fiddle: https://jsfiddle.net/ditman/7towxaqp
964-
_addPointerEventListener(domWindow, 'pointermove', (DomPointerEvent event) {
980+
_addPointerEventListener(_globalTarget, 'pointermove', (DomPointerEvent event) {
965981
final int device = _getPointerId(event);
966982
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
967983
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -977,7 +993,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
977993
_callback(event, pointerData);
978994
});
979995

980-
_addPointerEventListener(flutterViewElement, 'pointerleave', (DomPointerEvent event) {
996+
_addPointerEventListener(_rootElement, 'pointerleave', (DomPointerEvent event) {
981997
final int device = _getPointerId(event);
982998
final _ButtonSanitizer sanitizer = _ensureSanitizer(device);
983999
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -989,7 +1005,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
9891005
}, checkModifiers: false);
9901006

9911007
// TODO(dit): This must happen in the flutterViewElement, https://github.com/flutter/flutter/issues/116561
992-
_addPointerEventListener(domWindow, 'pointerup', (DomPointerEvent event) {
1008+
_addPointerEventListener(_globalTarget, 'pointerup', (DomPointerEvent event) {
9931009
final int device = _getPointerId(event);
9941010
if (_hasSanitizer(device)) {
9951011
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -1006,7 +1022,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
10061022

10071023
// A browser fires cancel event if it concludes the pointer will no longer
10081024
// be able to generate events (example: device is deactivated)
1009-
_addPointerEventListener(flutterViewElement, 'pointercancel', (DomPointerEvent event) {
1025+
_addPointerEventListener(_rootElement, 'pointercancel', (DomPointerEvent event) {
10101026
final int device = _getPointerId(event);
10111027
if (_hasSanitizer(device)) {
10121028
final List<ui.PointerData> pointerData = <ui.PointerData>[];
@@ -1033,16 +1049,17 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin {
10331049
final double tilt = _computeHighestTilt(event);
10341050
final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp!);
10351051
final num? pressure = event.pressure;
1036-
final ui.Offset offset = computeEventOffsetToTarget(event, flutterViewElement);
1052+
final ui.Offset offset = computeEventOffsetToTarget(event, _view);
10371053
_pointerDataConverter.convert(
10381054
data,
1055+
viewId: _view.viewId,
10391056
change: details.change,
10401057
timeStamp: timeStamp,
10411058
kind: kind,
10421059
signalKind: ui.PointerSignalKind.none,
10431060
device: _getPointerId(event),
1044-
physicalX: offset.dx * ui.window.devicePixelRatio,
1045-
physicalY: offset.dy * ui.window.devicePixelRatio,
1061+
physicalX: offset.dx * _view.devicePixelRatio,
1062+
physicalY: offset.dy * _view.devicePixelRatio,
10461063
buttons: details.buttons,
10471064
pressure: pressure == null ? 0.0 : pressure.toDouble(),
10481065
pressureMax: 1.0,

0 commit comments

Comments
 (0)