Skip to content

Commit

Permalink
[web] Detect when the mouseup occurs outside of window (#17495)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdebbar authored Apr 6, 2020
1 parent ba615d5 commit bd76076
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 12 deletions.
50 changes: 38 additions & 12 deletions lib/web_ui/lib/src/engine/pointer_binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -183,8 +183,23 @@ abstract class _BaseAdapter {
_nativeListeners.clear();
}

void addEventListener(String eventName, html.EventListener handler) {
/// 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, {
bool acceptOutsideGlasspane = false,
}) {
final html.EventListener loggedHandler = (html.Event event) {
if (!acceptOutsideGlasspane && event.target != glassPaneElement) {
return;
}

if (_debugLogPointerEvents) {
print(event.type);
}
Expand All @@ -196,8 +211,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
Expand Down Expand Up @@ -412,11 +430,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
Expand Down Expand Up @@ -444,7 +466,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;
Expand All @@ -455,7 +477,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)
Expand Down Expand Up @@ -706,11 +728,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
Expand All @@ -731,7 +757,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<ui.PointerData> pointerData = <ui.PointerData>[];
Expand All @@ -741,7 +767,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);
Expand Down
61 changes: 61 additions & 0 deletions lib/web_ui/test/engine/pointer_binding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1446,6 +1446,67 @@ 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<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
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();

// 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,
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>(
Expand Down

0 comments on commit bd76076

Please sign in to comment.