Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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