From f6b1e3ceb2a87748c660a17533497c9daca6e6bd Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 2 Apr 2020 17:51:04 -0700 Subject: [PATCH 1/4] [web] Detect when the mouseup occurs outside of window --- .../lib/src/engine/pointer_binding.dart | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index d88e1d4074d84..7307542d63851 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -166,7 +166,7 @@ abstract class _BaseAdapter { /// Remove all active event listeners. void clearListeners() { _listeners.forEach((String eventName, html.EventListener listener) { - glassPaneElement.removeEventListener(eventName, listener, true); + html.window.removeEventListener(eventName, listener, true); }); // For native listener, we will need to remove it through native javascript // api. @@ -183,8 +183,16 @@ abstract class _BaseAdapter { _nativeListeners.clear(); } - void addEventListener(String eventName, html.EventListener handler) { + void addEventListener( + String eventName, + html.EventListener handler, { + bool acceptOutsideGlasspane = false, + }) { final html.EventListener loggedHandler = (html.Event event) { + if (!acceptOutsideGlasspane && event.target != glassPaneElement) { + return; + } + if (_debugLogPointerEvents) { print(event.type); } @@ -196,8 +204,11 @@ abstract class _BaseAdapter { } }; _listeners[eventName] = loggedHandler; - glassPaneElement - .addEventListener(eventName, loggedHandler, true); + // We have to attach the event listener on the window instead of the + // glasspane element. That's because "up" events that occur outside the + // browser are only reported on window, not on DOM elements. + // See: https://github.com/flutter/flutter/issues/52827 + html.window.addEventListener(eventName, loggedHandler, true); } /// Converts a floating number timestamp (in milliseconds) to a [Duration] by @@ -412,11 +423,15 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } } - void _addPointerEventListener(String eventName, _PointerEventListener handler) { + void _addPointerEventListener( + String eventName, + _PointerEventListener handler, { + bool acceptOutsideGlasspane = false, + }) { addEventListener(eventName, (html.Event event) { final html.PointerEvent pointerEvent = event; return handler(pointerEvent); - }); + }, acceptOutsideGlasspane: acceptOutsideGlasspane); } @override @@ -455,7 +470,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _convertEventsToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); - }); + }, acceptOutsideGlasspane: true); // A browser fires cancel event if it concludes the pointer will no longer // be able to generate events (example: device is deactivated) @@ -706,11 +721,15 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final _ButtonSanitizer _sanitizer = _ButtonSanitizer(); - void _addMouseEventListener(String eventName, _MouseEventListener handler) { + void _addMouseEventListener( + String eventName, + _MouseEventListener handler, { + bool acceptOutsideGlasspane = false, + }) { addEventListener(eventName, (html.Event event) { final html.MouseEvent mouseEvent = event; return handler(mouseEvent); - }); + }, acceptOutsideGlasspane: acceptOutsideGlasspane); } @override @@ -741,7 +760,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { _sanitizer.sanitizeMoveEvent(buttons: event.buttons); _convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails); _callback(pointerData); - }); + }, acceptOutsideGlasspane: true); _addWheelEventListener((html.Event event) { assert(event is html.WheelEvent); From 9d01e92d0b9aa5171393603826a4b8ccebf46216 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Thu, 2 Apr 2020 18:27:30 -0700 Subject: [PATCH 2/4] test --- .../test/engine/pointer_binding_test.dart | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index caa345b238534..5b88f2469ba1c 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -1446,6 +1446,55 @@ void main() { }, ); + _testEach<_ButtonedEventMixin>( + [_PointerEventContext(), _MouseEventContext()], + 'correctly detects up event outside of glasspane', + (_ButtonedEventMixin context) { + PointerBinding.instance.debugOverrideDetector(context); + // This can happen when the up event occurs while the mouse is outside the + // browser window. + + List packets = []; + ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) { + packets.add(packet); + }; + + // Press and drag around. + glassPane.dispatchEvent(context.primaryDown( + clientX: 10.0, + clientY: 10.0, + )); + glassPane.dispatchEvent(context.primaryMove( + clientX: 12.0, + clientY: 10.0, + )); + glassPane.dispatchEvent(context.primaryMove( + clientX: 15.0, + clientY: 10.0, + )); + glassPane.dispatchEvent(context.primaryMove( + clientX: 20.0, + clientY: 10.0, + )); + packets.clear(); + + // Release outside the glasspane. + html.window.dispatchEvent(context.primaryUp( + clientX: 1000.0, + clientY: 2000.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(2)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].physicalX, equals(1000.0)); + expect(packets[0].data[0].physicalY, equals(2000.0)); + expect(packets[0].data[1].change, equals(ui.PointerChange.up)); + expect(packets[0].data[1].physicalX, equals(1000.0)); + expect(packets[0].data[1].physicalY, equals(2000.0)); + packets.clear(); + }, + ); + // MULTIPOINTER ADAPTERS _testEach<_MultiPointerEventMixin>( From fb416d828ab70accf5f9ab6f74775fc92d9f93bb Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Fri, 3 Apr 2020 11:05:56 -0700 Subject: [PATCH 3/4] Allow dragging to continue when the mouse leaves the browser window --- lib/web_ui/lib/src/engine/pointer_binding.dart | 4 ++-- lib/web_ui/test/engine/pointer_binding_test.dart | 12 ++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 7307542d63851..9adb7d56efb22 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -459,7 +459,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _convertEventsToPointerData(data: pointerData, event: event, details: details); } _callback(pointerData); - }); + }, acceptOutsideGlasspane: true); _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; @@ -750,7 +750,7 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { final _SanitizedDetails sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons); _convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails); _callback(pointerData); - }); + }, acceptOutsideGlasspane: true); _addMouseEventListener('mouseup', (html.MouseEvent event) { final List pointerData = []; diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index 5b88f2469ba1c..dbe030d4910bf 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -1478,6 +1478,18 @@ void main() { )); packets.clear(); + // Move outside the glasspane. + html.window.dispatchEvent(context.primaryMove( + clientX: 900.0, + clientY: 1900.0, + )); + expect(packets, hasLength(1)); + expect(packets[0].data, hasLength(1)); + expect(packets[0].data[0].change, equals(ui.PointerChange.move)); + expect(packets[0].data[0].physicalX, equals(900.0)); + expect(packets[0].data[0].physicalY, equals(1900.0)); + packets.clear(); + // Release outside the glasspane. html.window.dispatchEvent(context.primaryUp( clientX: 1000.0, From e6eccd273a8dc2fb91e9f899a8289947ba1d71b9 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Fri, 3 Apr 2020 11:28:02 -0700 Subject: [PATCH 4/4] dartdocs --- lib/web_ui/lib/src/engine/pointer_binding.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 9adb7d56efb22..c4ed88afe2793 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -183,6 +183,13 @@ abstract class _BaseAdapter { _nativeListeners.clear(); } + /// Adds a listener to the given [eventName]. + /// + /// The event listener is attached to [html.window] but only events that have + /// [glassPaneElement] as a target will be let through by default. + /// + /// If [acceptOutsideGlasspane] is set to true, events outside of the + /// glasspane will also invoke the [handler]. void addEventListener( String eventName, html.EventListener handler, {