From b7281b9ec18c1f90360f0c357945513866681cf3 Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Fri, 3 Jan 2020 10:38:31 -0800 Subject: [PATCH 1/2] [web] Reduce the usage of unnecessary lists in pointer binding --- .../lib/src/engine/pointer_binding.dart | 190 ++++++++---------- .../lib/src/engine/pointer_converter.dart | 33 +++ .../test/engine/pointer_binding_test.dart | 2 +- 3 files changed, 116 insertions(+), 109 deletions(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 5f7eed8e9dbf7..606efe90eb7dd 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -308,7 +308,7 @@ class _ButtonSanitizer { return _htmlButtonsToFlutterButtons(buttons); } - List<_SanitizedDetails> sanitizeDownEvent({ + _SanitizedDetails sanitizeDownEvent({ @required int button, @required int buttons, }) { @@ -319,15 +319,13 @@ class _ButtonSanitizer { } _pressedButtons = _inferDownFlutterButtons(button, buttons); - return <_SanitizedDetails>[ - _SanitizedDetails( - change: ui.PointerChange.down, - buttons: _pressedButtons, - ) - ]; + return _SanitizedDetails( + change: ui.PointerChange.down, + buttons: _pressedButtons, + ); } - List<_SanitizedDetails> sanitizeMoveEvent({@required int buttons}) { + _SanitizedDetails sanitizeMoveEvent({@required int buttons}) { final int newPressedButtons = _htmlButtonsToFlutterButtons(buttons); // This could happen when the context menu is active and the user clicks // RMB somewhere else. The browser sends a down event with `buttons:0`. @@ -335,56 +333,50 @@ class _ButtonSanitizer { // In this case, we keep the old `buttons` value so we don't confuse the // framework. if (_pressedButtons != 0 && newPressedButtons == 0) { - return <_SanitizedDetails>[ - _SanitizedDetails( - change: ui.PointerChange.move, - buttons: _pressedButtons, - ) - ]; + return _SanitizedDetails( + change: ui.PointerChange.move, + buttons: _pressedButtons, + ); } // This could happen when the user clicks RMB then moves the mouse quickly. // The brower sends a move event with `buttons:2` even though there's no // buttons down yet. if (_pressedButtons == 0 && newPressedButtons != 0) { - return <_SanitizedDetails>[ - _SanitizedDetails( - change: ui.PointerChange.hover, - buttons: _pressedButtons, - ) - ]; + return _SanitizedDetails( + change: ui.PointerChange.hover, + buttons: _pressedButtons, + ); } _pressedButtons = newPressedButtons; - return <_SanitizedDetails>[ - _SanitizedDetails( - change: _pressedButtons == 0 - ? ui.PointerChange.hover - : ui.PointerChange.move, - buttons: _pressedButtons, - ) - ]; + return _SanitizedDetails( + change: _pressedButtons == 0 + ? ui.PointerChange.hover + : ui.PointerChange.move, + buttons: _pressedButtons, + ); } - List<_SanitizedDetails> sanitizeUpEvent() { + _SanitizedDetails sanitizeUpEvent() { // The pointer could have been released by a `pointerout` event, in which // case `pointerup` should have no effect. if (_pressedButtons == 0) { - return <_SanitizedDetails>[]; + return null; } _pressedButtons = 0; - return <_SanitizedDetails>[_SanitizedDetails( + return _SanitizedDetails( change: ui.PointerChange.up, buttons: _pressedButtons, - )]; + ); } - List<_SanitizedDetails> sanitizeCancelEvent() { + _SanitizedDetails sanitizeCancelEvent() { _pressedButtons = 0; - return <_SanitizedDetails>[_SanitizedDetails( + return _SanitizedDetails( change: ui.PointerChange.cancel, buttons: _pressedButtons, - )]; + ); } } @@ -415,13 +407,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { return sanitizer; } - void _removePointerIfUnhoverable(List<_SanitizedDetails> details, html.PointerEvent event) { + void _removePointerIfUnhoverable(html.PointerEvent event) { if (event.pointerType == 'touch') { _sanitizers.remove(event.pointerId); - details.add(_SanitizedDetails( - buttons: 0, - change: ui.PointerChange.remove, - )); } } @@ -437,12 +425,12 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointerdown', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final List<_SanitizedDetails> detailsList = + final _SanitizedDetails details = _ensureSanitizer(device).sanitizeDownEvent( button: event.button, buttons: event.buttons, ); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + _convertEventsToPointerData(data: pointerData, event: event, details: details); _callback(pointerData); }); @@ -450,19 +438,23 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { final int device = event.pointerId; final _ButtonSanitizer sanitizer = _ensureSanitizer(device); final List pointerData = []; - final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).expand( + final Iterable<_SanitizedDetails> detailsList = _expandEvents(event).map( (html.PointerEvent expandedEvent) => sanitizer.sanitizeMoveEvent(buttons: expandedEvent.buttons), ); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + for (_SanitizedDetails details in detailsList) { + _convertEventsToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); _addPointerEventListener('pointerup', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeUpEvent(); - _removePointerIfUnhoverable(detailsList, event); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + final _SanitizedDetails details = _getSanitizer(device).sanitizeUpEvent(); + _removePointerIfUnhoverable(event); + if (details != null) { + _convertEventsToPointerData(data: pointerData, event: event, details: details); + } _callback(pointerData); }); @@ -471,9 +463,9 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { _addPointerEventListener('pointercancel', (html.PointerEvent event) { final int device = event.pointerId; final List pointerData = []; - final List<_SanitizedDetails> detailsList = _getSanitizer(device).sanitizeCancelEvent(); - _removePointerIfUnhoverable(detailsList, event); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: detailsList); + final _SanitizedDetails details = _getSanitizer(device).sanitizeCancelEvent(); + _removePointerIfUnhoverable(event); + _convertEventsToPointerData(data: pointerData, event: event, details: details); _callback(pointerData); }); @@ -490,15 +482,15 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { } // For each event that is de-coalesced from `event` and described in - // `detailsList`, convert it to pointer data and store in `data`. + // `details`, convert it to pointer data and store in `data`. void _convertEventsToPointerData({ @required List data, @required html.PointerEvent event, - @required Iterable<_SanitizedDetails> detailsList, + @required _SanitizedDetails details, }) { assert(data != null); assert(event != null); - assert(detailsList != null); + assert(details != null); final ui.PointerDeviceKind kind = _pointerTypeToDeviceKind(event.pointerType); // We force `device: _mouseDeviceId` on mouse pointers because Wheel events // might come before any PointerEvents, and since wheel events don't contain @@ -506,23 +498,21 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { final int device = kind == ui.PointerDeviceKind.mouse ? _mouseDeviceId : event.pointerId; final double tilt = _computeHighestTilt(event); final Duration timeStamp = _BaseAdapter._eventTimeStampToDuration(event.timeStamp); - for (_SanitizedDetails details in detailsList) { - _pointerDataConverter.convert( - data, - change: details.change, - timeStamp: timeStamp, - kind: kind, - signalKind: ui.PointerSignalKind.none, - device: device, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: details.buttons, - pressure: event.pressure, - pressureMin: 0.0, - pressureMax: 1.0, - tilt: tilt, - ); - } + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: timeStamp, + kind: kind, + signalKind: ui.PointerSignalKind.none, + device: device, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: details.buttons, + pressure: event.pressure, + pressureMin: 0.0, + pressureMax: 1.0, + tilt: tilt, + ); } List _expandEvents(html.PointerEvent event) { @@ -639,13 +629,6 @@ class _TouchAdapter extends _BaseAdapter { pressed: false, timeStamp: timeStamp, ); - _convertEventToPointerData( - data: pointerData, - change: ui.PointerChange.remove, - touch: touch, - pressed: false, - timeStamp: timeStamp, - ); } } _callback(pointerData); @@ -665,13 +648,6 @@ class _TouchAdapter extends _BaseAdapter { pressed: false, timeStamp: timeStamp, ); - _convertEventToPointerData( - data: pointerData, - change: ui.PointerChange.remove, - touch: touch, - pressed: false, - timeStamp: timeStamp, - ); } } _callback(pointerData); @@ -742,29 +718,29 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { void setup() { _addMouseEventListener('mousedown', (html.MouseEvent event) { final List pointerData = []; - final List<_SanitizedDetails> sanitizedDetails = + final _SanitizedDetails sanitizedDetails = _sanitizer.sanitizeDownEvent( button: event.button, buttons: event.buttons, ); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + _convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails); _callback(pointerData); }); _addMouseEventListener('mousemove', (html.MouseEvent event) { final List pointerData = []; - final List<_SanitizedDetails> sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + final _SanitizedDetails sanitizedDetails = _sanitizer.sanitizeMoveEvent(buttons: event.buttons); + _convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails); _callback(pointerData); }); _addMouseEventListener('mouseup', (html.MouseEvent event) { final List pointerData = []; final bool isEndOfDrag = event.buttons == 0; - final List<_SanitizedDetails> sanitizedDetails = isEndOfDrag ? + final _SanitizedDetails sanitizedDetails = isEndOfDrag ? _sanitizer.sanitizeUpEvent() : _sanitizer.sanitizeMoveEvent(buttons: event.buttons); - _convertEventsToPointerData(data: pointerData, event: event, detailsList: sanitizedDetails); + _convertEventsToPointerData(data: pointerData, event: event, details: sanitizedDetails); _callback(pointerData); }); @@ -785,26 +761,24 @@ class _MouseAdapter extends _BaseAdapter with _WheelEventListenerMixin { void _convertEventsToPointerData({ @required List data, @required html.MouseEvent event, - @required Iterable<_SanitizedDetails> detailsList, + @required _SanitizedDetails details, }) { assert(data != null); assert(event != null); - assert(detailsList != null); - for (_SanitizedDetails details in detailsList) { - _pointerDataConverter.convert( - data, - change: details.change, - timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), - kind: ui.PointerDeviceKind.mouse, - signalKind: ui.PointerSignalKind.none, - device: _mouseDeviceId, - physicalX: event.client.x * ui.window.devicePixelRatio, - physicalY: event.client.y * ui.window.devicePixelRatio, - buttons: details.buttons, - pressure: 1.0, - pressureMin: 0.0, - pressureMax: 1.0, - ); - } + assert(details != null); + _pointerDataConverter.convert( + data, + change: details.change, + timeStamp: _BaseAdapter._eventTimeStampToDuration(event.timeStamp), + kind: ui.PointerDeviceKind.mouse, + signalKind: ui.PointerSignalKind.none, + device: _mouseDeviceId, + physicalX: event.client.x * ui.window.devicePixelRatio, + physicalY: event.client.y * ui.window.devicePixelRatio, + buttons: details.buttons, + pressure: 1.0, + pressureMin: 0.0, + pressureMax: 1.0, + ); } } diff --git a/lib/web_ui/lib/src/engine/pointer_converter.dart b/lib/web_ui/lib/src/engine/pointer_converter.dart index cda223d092b4a..f08ef76c0c937 100644 --- a/lib/web_ui/lib/src/engine/pointer_converter.dart +++ b/lib/web_ui/lib/src/engine/pointer_converter.dart @@ -534,6 +534,39 @@ class PointerDataConverter { scrollDeltaY: scrollDeltaY, ) ); + if (kind == ui.PointerDeviceKind.touch) { + // The browser sends a new device ID for each touch gesture. To + // avoid memory leaks, we send a "remove" event when the gesture is + // over (i.e. when "up" or "cancel" is received). + result.add( + _synthesizePointerData( + timeStamp: timeStamp, + change: ui.PointerChange.remove, + kind: kind, + device: device, + physicalX: physicalX, + physicalY: physicalY, + buttons: 0, + obscured: obscured, + pressure: 0.0, + pressureMin: pressureMin, + pressureMax: pressureMax, + distance: distance, + distanceMax: distanceMax, + size: size, + radiusMajor: radiusMajor, + radiusMinor: radiusMinor, + radiusMin: radiusMin, + radiusMax: radiusMax, + orientation: orientation, + tilt: tilt, + platformData: platformData, + scrollDeltaX: scrollDeltaX, + scrollDeltaY: scrollDeltaY, + ) + ); + _pointers.remove(device); + } break; case ui.PointerChange.remove: assert(_pointers.containsKey(device)); diff --git a/lib/web_ui/test/engine/pointer_binding_test.dart b/lib/web_ui/test/engine/pointer_binding_test.dart index f6d4e8af0d76e..a23d559bb54b4 100644 --- a/lib/web_ui/test/engine/pointer_binding_test.dart +++ b/lib/web_ui/test/engine/pointer_binding_test.dart @@ -1782,7 +1782,7 @@ void main() { expect(packets[0].data[1].change, equals(ui.PointerChange.remove)); expect(packets[0].data[1].pointerIdentifier, equals(1)); - expect(packets[0].data[1].synthesized, equals(false)); + expect(packets[0].data[1].synthesized, equals(true)); expect(packets[0].data[1].physicalX, equals(40.0)); expect(packets[0].data[1].physicalY, equals(30.0)); expect(packets[0].data[1].physicalDeltaX, equals(0.0)); From 776518de72d68438e57455cd2dd74e1644dc0f9b Mon Sep 17 00:00:00 2001 From: Mouad Debbar Date: Fri, 21 Feb 2020 14:12:41 -0800 Subject: [PATCH 2/2] Cast coalesced pointer events to avoid typecheck errors --- lib/web_ui/lib/src/engine/pointer_binding.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/pointer_binding.dart b/lib/web_ui/lib/src/engine/pointer_binding.dart index 606efe90eb7dd..6b6726f3b6ce7 100644 --- a/lib/web_ui/lib/src/engine/pointer_binding.dart +++ b/lib/web_ui/lib/src/engine/pointer_binding.dart @@ -520,7 +520,7 @@ class _PointerAdapter extends _BaseAdapter with _WheelEventListenerMixin { // using the original event. if (js_util.hasProperty(event, 'getCoalescedEvents')) { final List coalescedEvents = - event.getCoalescedEvents(); + event.getCoalescedEvents().cast(); // Some events don't perform coalescing, so they return an empty list. In // that case, we also fallback to using the original event. if (coalescedEvents.isNotEmpty) {