未验证 提交 3e6d6bc6 编写于 作者: C chunhtai 提交者: GitHub

add pointer data santizing in flutter web engine (#14082)

上级 90e28c02
......@@ -417,6 +417,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/picture.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/platform_views.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/plugins.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_binding.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/pointer_converter.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/recording_canvas.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/render_vertices.dart
FILE: ../../../flutter/lib/web_ui/lib/src/engine/rrect_renderer.dart
......
......@@ -62,6 +62,7 @@ part 'engine/picture.dart';
part 'engine/platform_views.dart';
part 'engine/plugins.dart';
part 'engine/pointer_binding.dart';
part 'engine/pointer_converter.dart';
part 'engine/recording_canvas.dart';
part 'engine/render_vertices.dart';
part 'engine/rrect_renderer.dart';
......
......@@ -15,23 +15,17 @@ class PointerBinding {
static PointerBinding get instance => _instance;
static PointerBinding _instance;
// Set of pointerIds that are added before routing hover and mouse wheel
// events.
//
// The device needs to send a one time PointerChange.add before hover and
// wheel events.
Set<int> _activePointerIds = <int>{};
PointerBinding(this.domRenderer) {
if (_instance == null) {
_instance = this;
_pointerDataConverter = PointerDataConverter();
_detector = const PointerSupportDetector();
_adapter = _createAdapter();
}
assert(() {
registerHotRestartListener(() {
_adapter?.clearListeners();
_activePointerIds.clear();
_pointerDataConverter?.clearPointerState();
});
return true;
}());
......@@ -40,7 +34,7 @@ class PointerBinding {
final DomRenderer domRenderer;
PointerSupportDetector _detector;
BaseAdapter _adapter;
PointerDataConverter _pointerDataConverter;
/// Should be used in tests to define custom detection of pointer support.
///
/// ```dart
......@@ -62,22 +56,22 @@ class PointerBinding {
newDetector ??= const PointerSupportDetector();
// When changing the detector, we need to swap the adapter.
if (newDetector != _detector) {
_activePointerIds.clear();
_detector = newDetector;
_adapter?.clearListeners();
_adapter = _createAdapter();
_pointerDataConverter?.clearPointerState();
}
}
BaseAdapter _createAdapter() {
if (_detector.hasPointerEvents) {
return PointerAdapter(_onPointerData, domRenderer);
return PointerAdapter(_onPointerData, domRenderer, _pointerDataConverter);
}
if (_detector.hasTouchEvents) {
return TouchAdapter(_onPointerData, domRenderer);
return TouchAdapter(_onPointerData, domRenderer, _pointerDataConverter);
}
if (_detector.hasMouseEvents) {
return MouseAdapter(_onPointerData, domRenderer);
return MouseAdapter(_onPointerData, domRenderer, _pointerDataConverter);
}
return null;
}
......@@ -123,11 +117,19 @@ class _PressedButton {
/// Common functionality that's shared among adapters.
abstract class BaseAdapter {
BaseAdapter(this._callback, this.domRenderer, this._pointerDataConverter) {
_setup();
}
/// Listeners that are registered through dart to js api.
static final Map<String, html.EventListener> _listeners =
<String, html.EventListener>{};
/// Listeners that are registered through native javascript api.
static final Map<String, html.EventListener> _nativeListeners =
<String, html.EventListener>{};
final DomRenderer domRenderer;
PointerDataCallback _callback;
PointerDataConverter _pointerDataConverter;
// A set of the buttons that are currently being pressed.
Set<_PressedButton> _pressedButtons = Set<_PressedButton>();
......@@ -144,10 +146,6 @@ abstract class BaseAdapter {
}
}
BaseAdapter(this._callback, this.domRenderer) {
_setup();
}
/// Each subclass is expected to override this method to attach its own event
/// listeners and convert events into pointer events.
void _setup();
......@@ -158,7 +156,19 @@ abstract class BaseAdapter {
_listeners.forEach((String eventName, html.EventListener listener) {
glassPane.removeEventListener(eventName, listener, true);
});
// For native listener, we will need to remove it through native javascript
// api.
_nativeListeners.forEach((String eventName, html.EventListener listener) {
js_util.callMethod(
domRenderer.glassPaneElement,
'removeEventListener', <dynamic>[
'wheel',
listener,
]
);
});
_listeners.clear();
_nativeListeners.clear();
}
void _addEventListener(String eventName, html.EventListener handler) {
......@@ -177,6 +187,75 @@ abstract class BaseAdapter {
domRenderer.glassPaneElement
.addEventListener(eventName, loggedHandler, true);
}
/// Converts a floating number timestamp (in milliseconds) to a [Duration] by
/// splitting it into two integer components: milliseconds + microseconds.
Duration _eventTimeStampToDuration(num milliseconds) {
final int ms = milliseconds.toInt();
final int micro =
((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
return Duration(milliseconds: ms, microseconds: micro);
}
List<ui.PointerData> _convertWheelEventToPointerData(
html.WheelEvent event,
) {
const int domDeltaPixel = 0x00;
const int domDeltaLine = 0x01;
const int domDeltaPage = 0x02;
// Flutter only supports pixel scroll delta. Convert deltaMode values
// to pixels.
double deltaX = event.deltaX;
double deltaY = event.deltaY;
switch (event.deltaMode) {
case domDeltaLine:
deltaX *= 32.0;
deltaY *= 32.0;
break;
case domDeltaPage:
deltaX *= ui.window.physicalSize.width;
deltaY *= ui.window.physicalSize.height;
break;
case domDeltaPixel:
default:
break;
}
final List<ui.PointerData> data = <ui.PointerData>[];
_pointerDataConverter.convert(
data,
change: ui.PointerChange.hover,
timeStamp: _eventTimeStampToDuration(event.timeStamp),
kind: ui.PointerDeviceKind.mouse,
signalKind: ui.PointerSignalKind.scroll,
device: _mouseDeviceId,
physicalX: event.client.x * ui.window.devicePixelRatio,
physicalY: event.client.y * ui.window.devicePixelRatio,
buttons: event.buttons,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
scrollDeltaX: deltaX,
scrollDeltaY: deltaY,
);
return data;
}
void _addWheelEventListener(html.EventListener handler) {
final dynamic eventOptions = js_util.newObject();
final html.EventListener jsHandler = js.allowInterop((html.Event event) => handler(event));
_nativeListeners['wheel'] = jsHandler;
js_util.setProperty(eventOptions, 'passive', false);
js_util.callMethod(
domRenderer.glassPaneElement,
'addEventListener', <dynamic>[
'wheel',
jsHandler,
eventOptions
]
);
}
}
const int _kPrimaryMouseButton = 0x1;
......@@ -207,16 +286,17 @@ int _deviceFromHtmlEvent(event) {
/// Adapter class to be used with browsers that support native pointer events.
class PointerAdapter extends BaseAdapter {
PointerAdapter(PointerDataCallback callback, DomRenderer domRenderer)
: super(callback, domRenderer);
PointerAdapter(
PointerDataCallback callback,
DomRenderer domRenderer,
PointerDataConverter _pointerDataConverter
) : super(callback, domRenderer, _pointerDataConverter);
@override
void _setup() {
_addEventListener('pointerdown', (html.Event event) {
final int pointerButton = _pointerButtonFromHtmlEvent(event);
final int device = _deviceFromHtmlEvent(event);
// The pointerdown event will cause an 'add' event on the framework side.
PointerBinding._instance._activePointerIds.add(device);
if (_isButtonDown(device, pointerButton)) {
// TODO(flutter_web): Remove this temporary fix for right click
// on web platform once context guesture is implemented.
......@@ -239,13 +319,6 @@ class PointerAdapter extends BaseAdapter {
? ui.PointerChange.move
: ui.PointerChange.hover,
pointerEvent);
_ensureMouseDeviceAdded(
data,
pointerEvent.client.x,
pointerEvent.client.y,
pointerEvent.buttons,
pointerEvent.timeStamp,
pointerEvent.pointerId);
_callback(data);
});
......@@ -270,7 +343,8 @@ class PointerAdapter extends BaseAdapter {
_callback(_convertEventToPointerData(ui.PointerChange.cancel, event));
});
_addWheelEventListener((html.WheelEvent event) {
_addWheelEventListener((html.Event event) {
assert(event is html.WheelEvent);
if (_debugLogPointerEvents) {
print(event.type);
}
......@@ -289,7 +363,8 @@ class PointerAdapter extends BaseAdapter {
final List<ui.PointerData> data = <ui.PointerData>[];
for (int i = 0; i < allEvents.length; i++) {
final html.PointerEvent event = allEvents[i];
data.add(ui.PointerData(
_pointerDataConverter.convert(
data,
change: change,
timeStamp: _eventTimeStampToDuration(event.timeStamp),
kind: _pointerTypeToDeviceKind(event.pointerType),
......@@ -301,7 +376,7 @@ class PointerAdapter extends BaseAdapter {
pressureMin: 0.0,
pressureMax: 1.0,
tilt: _computeHighestTilt(event),
));
);
}
return data;
}
......@@ -343,8 +418,11 @@ class PointerAdapter extends BaseAdapter {
/// Adapter to be used with browsers that support touch events.
class TouchAdapter extends BaseAdapter {
TouchAdapter(PointerDataCallback callback, DomRenderer domRenderer)
: super(callback, domRenderer);
TouchAdapter(
PointerDataCallback callback,
DomRenderer domRenderer,
PointerDataConverter _pointerDataConverter
) : super(callback, domRenderer, _pointerDataConverter);
@override
void _setup() {
......@@ -381,11 +459,12 @@ class TouchAdapter extends BaseAdapter {
html.TouchEvent event,
) {
final html.TouchList touches = event.changedTouches;
final List<ui.PointerData> data = List<ui.PointerData>(touches.length);
final List<ui.PointerData> data = List<ui.PointerData>();
final int len = touches.length;
for (int i = 0; i < len; i++) {
final html.Touch touch = touches[i];
data[i] = ui.PointerData(
_pointerDataConverter.convert(
data,
change: change,
timeStamp: _eventTimeStampToDuration(event.timeStamp),
kind: ui.PointerDeviceKind.touch,
......@@ -408,8 +487,11 @@ const int _mouseDeviceId = -1;
/// Adapter to be used with browsers that support mouse events.
class MouseAdapter extends BaseAdapter {
MouseAdapter(PointerDataCallback callback, DomRenderer domRenderer)
: super(callback, domRenderer);
MouseAdapter(
PointerDataCallback callback,
DomRenderer domRenderer,
PointerDataConverter _pointerDataConverter
) : super(callback, domRenderer, _pointerDataConverter);
@override
void _setup() {
......@@ -442,7 +524,8 @@ class MouseAdapter extends BaseAdapter {
_callback(_convertEventToPointerData(ui.PointerChange.up, event));
});
_addWheelEventListener((html.WheelEvent event) {
_addWheelEventListener((html.Event event) {
assert(event is html.WheelEvent);
if (_debugLogPointerEvents) {
print(event.type);
}
......@@ -455,16 +538,9 @@ class MouseAdapter extends BaseAdapter {
ui.PointerChange change,
html.MouseEvent event,
) {
final List<ui.PointerData> data = <ui.PointerData>[];
// The mousedown event will cause an 'add' event on the framework side.
if (event.type == 'mousedown') {
PointerBinding._instance._activePointerIds.add(_mouseDeviceId);
}
if (event.type == 'mousemove') {
_ensureMouseDeviceAdded(data, event.client.x, event.client.y,
event.buttons, event.timeStamp, _mouseDeviceId);
}
data.add(ui.PointerData(
List<ui.PointerData> data = <ui.PointerData>[];
_pointerDataConverter.convert(
data,
change: change,
timeStamp: _eventTimeStampToDuration(event.timeStamp),
kind: ui.PointerDeviceKind.mouse,
......@@ -476,101 +552,7 @@ class MouseAdapter extends BaseAdapter {
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
));
);
return data;
}
}
/// Convert a floating number timestamp (in milliseconds) to a [Duration] by
/// splitting it into two integer components: milliseconds + microseconds.
Duration _eventTimeStampToDuration(num milliseconds) {
final int ms = milliseconds.toInt();
final int micro =
((milliseconds - ms) * Duration.microsecondsPerMillisecond).toInt();
return Duration(milliseconds: ms, microseconds: micro);
}
void _ensureMouseDeviceAdded(List<ui.PointerData> data, double clientX,
double clientY, int buttons, double timeStamp, int deviceId) {
if (PointerBinding.instance._activePointerIds.contains(deviceId)) {
return;
}
PointerBinding.instance._activePointerIds.add(deviceId);
// Only send [PointerChange.add] the first time.
data.insert(
0,
ui.PointerData(
change: ui.PointerChange.add,
timeStamp: _eventTimeStampToDuration(timeStamp),
kind: ui.PointerDeviceKind.mouse,
// In order for Flutter to actually add this pointer, we need to set the
// signal to none.
signalKind: ui.PointerSignalKind.none,
device: deviceId,
physicalX: clientX * ui.window.devicePixelRatio,
physicalY: clientY * ui.window.devicePixelRatio,
buttons: buttons,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
scrollDeltaX: 0,
scrollDeltaY: 0,
));
}
List<ui.PointerData> _convertWheelEventToPointerData(
html.WheelEvent event,
) {
const int domDeltaPixel = 0x00;
const int domDeltaLine = 0x01;
const int domDeltaPage = 0x02;
// Flutter only supports pixel scroll delta. Convert deltaMode values
// to pixels.
double deltaX = event.deltaX;
double deltaY = event.deltaY;
switch (event.deltaMode) {
case domDeltaLine:
deltaX *= 32.0;
deltaY *= 32.0;
break;
case domDeltaPage:
deltaX *= ui.window.physicalSize.width;
deltaY *= ui.window.physicalSize.height;
break;
case domDeltaPixel:
default:
break;
}
final List<ui.PointerData> data = <ui.PointerData>[];
_ensureMouseDeviceAdded(data, event.client.x, event.client.y, event.buttons,
event.timeStamp, _mouseDeviceId);
data.add(ui.PointerData(
change: ui.PointerChange.hover,
timeStamp: _eventTimeStampToDuration(event.timeStamp),
kind: ui.PointerDeviceKind.mouse,
signalKind: ui.PointerSignalKind.scroll,
device: _mouseDeviceId,
physicalX: event.client.x * ui.window.devicePixelRatio,
physicalY: event.client.y * ui.window.devicePixelRatio,
buttons: event.buttons,
pressure: 1.0,
pressureMin: 0.0,
pressureMax: 1.0,
scrollDeltaX: deltaX,
scrollDeltaY: deltaY,
));
return data;
}
void _addWheelEventListener(void listener(html.WheelEvent e)) {
final dynamic eventOptions = js_util.newObject();
js_util.setProperty(eventOptions, 'passive', false);
js_util.callMethod(PointerBinding.instance.domRenderer.glassPaneElement,
'addEventListener', <dynamic>[
'wheel',
js.allowInterop((html.WheelEvent event) => listener(event)),
eventOptions
]);
}
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
part of engine;
class _PointerState {
_PointerState(this.x, this.y);
/// The identifier used in framework hit test.
int get pointer => _pointer;
int _pointer;
static int _pointerCount = 0;
void startNewPointer() {
_pointerCount += 1;
_pointer = _pointerCount;
}
bool down = false;
double x;
double y;
}
/// Converter to convert web pointer data into a form that framework can
/// understand.
///
/// This converter calculates pointer location delta and pointer identifier for
/// each pointer. Both are required by framework to correctly trigger gesture
/// activity. It also attempts to sanitize pointer data input sequence by always
/// synthesizing an add pointer data prior to hover or down if it the pointer is
/// not previously added.
///
/// For example:
/// before:
/// hover -> down -> move -> up
/// after:
/// add(synthesize) -> hover -> down -> move -> up
///
/// before:
/// down -> move -> up
/// after:
/// add(synthesize) -> down -> move -> up
class PointerDataConverter {
PointerDataConverter();
// Map from browser pointer identifiers to PointerEvent pointer identifiers.
final Map<int, _PointerState> _pointers = <int, _PointerState>{};
/// Clears the existing pointer states.
///
/// This method is invoked during hot reload to make sure we have a clean
/// converter after hot reload.
void clearPointerState() {
_pointers.clear();
_PointerState._pointerCount = 0;
}
_PointerState _ensureStateForPointer(int device, double x, double y) {
return _pointers.putIfAbsent(
device,
() => _PointerState(x, y),
);
}
ui.PointerData _generateCompletePointerData({
Duration timeStamp,
ui.PointerChange change,
ui.PointerDeviceKind kind,
ui.PointerSignalKind signalKind,
int device,
double physicalX,
double physicalY,
int buttons,
bool obscured,
double pressure,
double pressureMin,
double pressureMax,
double distance,
double distanceMax,
double size,
double radiusMajor,
double radiusMinor,
double radiusMin,
double radiusMax,
double orientation,
double tilt,
int platformData,
double scrollDeltaX,
double scrollDeltaY,
}) {
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
final double deltaX = physicalX - state.x;
final double deltaY = physicalY - state.y;
state.x = physicalX;
state.y = physicalY;
return ui.PointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
pointerIdentifier: state.pointer ?? 0,
physicalX: physicalX,
physicalY: physicalY,
physicalDeltaX: deltaX,
physicalDeltaY: deltaY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
);
}
bool _locationHasChanged(int device, double physicalX, double physicalY) {
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
return state.x != physicalX || state.y != physicalY;
}
ui.PointerData _synthesizePointerData({
Duration timeStamp,
ui.PointerChange change,
ui.PointerDeviceKind kind,
int device,
double physicalX,
double physicalY,
int buttons,
bool obscured,
double pressure,
double pressureMin,
double pressureMax,
double distance,
double distanceMax,
double size,
double radiusMajor,
double radiusMinor,
double radiusMin,
double radiusMax,
double orientation,
double tilt,
int platformData,
double scrollDeltaX,
double scrollDeltaY,
}) {
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
final double deltaX = physicalX - state.x;
final double deltaY = physicalY - state.y;
state.x = physicalX;
state.y = physicalY;
return ui.PointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
// All the pointer data except scroll should not have a signal kind, and
// there is no use case for synthetic scroll event. We should be
// safe to default it to ui.PointerSignalKind.none.
signalKind: ui.PointerSignalKind.none,
device: device,
pointerIdentifier: state.pointer ?? 0,
physicalX: physicalX,
physicalY: physicalY,
physicalDeltaX: deltaX,
physicalDeltaY: deltaY,
buttons: buttons,
obscured: obscured,
synthesized: true,
pressure: pressure,
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,
);
}
/// Converts the given html pointer event metrics into a sequence of framework-compatible
/// pointer data and stores it into [result]
void convert(
List<ui.PointerData> result, {
Duration timeStamp = Duration.zero,
ui.PointerChange change = ui.PointerChange.cancel,
ui.PointerDeviceKind kind = ui.PointerDeviceKind.touch,
ui.PointerSignalKind signalKind,
int device = 0,
double physicalX = 0.0,
double physicalY = 0.0,
int buttons = 0,
bool obscured = false,
double pressure = 0.0,
double pressureMin = 0.0,
double pressureMax = 0.0,
double distance = 0.0,
double distanceMax = 0.0,
double size = 0.0,
double radiusMajor = 0.0,
double radiusMinor = 0.0,
double radiusMin = 0.0,
double radiusMax = 0.0,
double orientation = 0.0,
double tilt = 0.0,
int platformData = 0,
double scrollDeltaX = 0.0,
double scrollDeltaY = 0.0,
}) {
assert(change != null);
if (signalKind == null ||
signalKind == ui.PointerSignalKind.none) {
switch (change) {
case ui.PointerChange.add:
assert(!_pointers.containsKey(device));
_ensureStateForPointer(device, physicalX, physicalY);
assert(!_locationHasChanged(device, physicalX, physicalY));
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerChange.hover:
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
assert(!state.down);
if (!alreadyAdded) {
// Synthesizes an add pointer data.
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.add,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
}
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerChange.down:
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
assert(!state.down);
if (!alreadyAdded) {
// Synthesizes an add pointer data.
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.add,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
}
assert(!_locationHasChanged(device, physicalX, physicalY));
state.startNewPointer();
state.down = true;
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerChange.move:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
assert(state.down);
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerChange.up:
case ui.PointerChange.cancel:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
assert(state.down);
assert(!_locationHasChanged(device, physicalX, physicalY));
state.down = false;
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerChange.remove:
assert(_pointers.containsKey(device));
final _PointerState state = _pointers[device];
assert(!state.down);
assert(!_locationHasChanged(device, physicalX, physicalY));
_pointers.remove(device);
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
}
} else {
switch (signalKind) {
case ui.PointerSignalKind.scroll:
final bool alreadyAdded = _pointers.containsKey(device);
final _PointerState state = _ensureStateForPointer(
device, physicalX, physicalY);
if (!alreadyAdded) {
// Synthesizes an add pointer data.
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.add,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
}
if (_locationHasChanged(device, physicalX, physicalY)) {
// Synthesize a hover/move of the pointer to the scroll location
// before sending the scroll event, if necessary, so that clients
// don't have to worry about native ordering of hover and scroll
// events.
if (state.down) {
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.move,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
} else {
result.add(
_synthesizePointerData(
timeStamp: timeStamp,
change: ui.PointerChange.hover,
kind: kind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
}
}
result.add(
_generateCompletePointerData(
timeStamp: timeStamp,
change: change,
kind: kind,
signalKind: signalKind,
device: device,
physicalX: physicalX,
physicalY: physicalY,
buttons: buttons,
obscured: obscured,
pressure: pressure,
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,
)
);
break;
case ui.PointerSignalKind.none:
assert(false); // This branch should already have 'none' filtered out.
break;
case ui.PointerSignalKind.unknown:
// Ignore unknown signals.
break;
}
}
}
}
......@@ -56,7 +56,11 @@ void main() {
}));
expect(packets, hasLength(3));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
// An add will be synthesized.
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, equals(true));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[1].data[0].change, equals(ui.PointerChange.up));
expect(packets[2].data[0].change, equals(ui.PointerChange.down));
});
......@@ -78,10 +82,20 @@ void main() {
}));
expect(packets, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
// An add will be synthesized.
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, equals(true));
expect(packets[0].data[0].device, equals(1));
expect(packets[1].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].device, equals(1));
// An add will be synthesized.
expect(packets[1].data, hasLength(2));
expect(packets[1].data[0].change, equals(ui.PointerChange.add));
expect(packets[1].data[0].synthesized, equals(true));
expect(packets[1].data[0].device, equals(2));
expect(packets[1].data[1].change, equals(ui.PointerChange.down));
expect(packets[1].data[1].device, equals(2));
});
test('creates an add event if the first pointer activity is a hover', () {
......@@ -99,10 +113,11 @@ void main() {
expect(packets.single.data, hasLength(2));
expect(packets.single.data[0].change, equals(ui.PointerChange.add));
expect(packets.single.data[0].synthesized, equals(true));
expect(packets.single.data[1].change, equals(ui.PointerChange.hover));
});
test('does not create an add event if got a pointerdown', () {
test('does create an add event if got a pointerdown', () {
List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
......@@ -114,9 +129,247 @@ void main() {
}));
expect(packets, hasLength(1));
expect(packets.single.data, hasLength(1));
expect(packets.single.data, hasLength(2));
expect(packets.single.data[0].change, equals(ui.PointerChange.add));
expect(packets.single.data[1].change, equals(ui.PointerChange.down));
});
test('does calculate delta and pointer identifier correctly', () {
List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(html.PointerEvent('pointermove', {
'pointerId': 1,
'button': 1,
'clientX': 10.0,
'clientY': 10.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointermove', {
'pointerId': 1,
'button': 1,
'clientX': 20.0,
'clientY': 20.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointerdown', {
'pointerId': 1,
'button': 1,
'clientX': 20.0,
'clientY': 20.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointermove', {
'pointerId': 1,
'button': 1,
'clientX': 40.0,
'clientY': 30.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointerup', {
'pointerId': 1,
'button': 1,
'clientX': 40.0,
'clientY': 30.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointermove', {
'pointerId': 1,
'button': 1,
'clientX': 20.0,
'clientY': 10.0,
}));
glassPane.dispatchEvent(html.PointerEvent('pointerdown', {
'pointerId': 1,
'button': 1,
'clientX': 20.0,
'clientY': 10.0,
}));
expect(packets, hasLength(7));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].pointerIdentifier, equals(0));
expect(packets[0].data[0].synthesized, equals(true));
expect(packets[0].data[0].physicalX, equals(10.0));
expect(packets[0].data[0].physicalY, equals(10.0));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].pointerIdentifier, equals(0));
expect(packets[0].data[1].synthesized, equals(false));
expect(packets[0].data[1].physicalX, equals(10.0));
expect(packets[0].data[1].physicalY, equals(10.0));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
expect(packets[1].data, hasLength(1));
expect(packets[1].data[0].change, equals(ui.PointerChange.hover));
expect(packets[1].data[0].pointerIdentifier, equals(0));
expect(packets[1].data[0].synthesized, equals(false));
expect(packets[1].data[0].physicalX, equals(20.0));
expect(packets[1].data[0].physicalY, equals(20.0));
expect(packets[1].data[0].physicalDeltaX, equals(10.0));
expect(packets[1].data[0].physicalDeltaY, equals(10.0));
expect(packets[2].data, hasLength(1));
expect(packets[2].data[0].change, equals(ui.PointerChange.down));
expect(packets[2].data[0].pointerIdentifier, equals(1));
expect(packets[2].data[0].synthesized, equals(false));
expect(packets[2].data[0].physicalX, equals(20.0));
expect(packets[2].data[0].physicalY, equals(20.0));
expect(packets[2].data[0].physicalDeltaX, equals(0.0));
expect(packets[2].data[0].physicalDeltaY, equals(0.0));
expect(packets[3].data, hasLength(1));
expect(packets[3].data[0].change, equals(ui.PointerChange.move));
expect(packets[3].data[0].pointerIdentifier, equals(1));
expect(packets[3].data[0].synthesized, equals(false));
expect(packets[3].data[0].physicalX, equals(40.0));
expect(packets[3].data[0].physicalY, equals(30.0));
expect(packets[3].data[0].physicalDeltaX, equals(20.0));
expect(packets[3].data[0].physicalDeltaY, equals(10.0));
expect(packets[4].data, hasLength(1));
expect(packets[4].data[0].change, equals(ui.PointerChange.up));
expect(packets[4].data[0].pointerIdentifier, equals(1));
expect(packets[4].data[0].synthesized, equals(false));
expect(packets[4].data[0].physicalX, equals(40.0));
expect(packets[4].data[0].physicalY, equals(30.0));
expect(packets[4].data[0].physicalDeltaX, equals(0.0));
expect(packets[4].data[0].physicalDeltaY, equals(0.0));
expect(packets[5].data, hasLength(1));
expect(packets[5].data[0].change, equals(ui.PointerChange.hover));
expect(packets[5].data[0].pointerIdentifier, equals(1));
expect(packets[5].data[0].synthesized, equals(false));
expect(packets[5].data[0].physicalX, equals(20.0));
expect(packets[5].data[0].physicalY, equals(10.0));
expect(packets[5].data[0].physicalDeltaX, equals(-20.0));
expect(packets[5].data[0].physicalDeltaY, equals(-20.0));
expect(packets[6].data, hasLength(1));
expect(packets[6].data[0].change, equals(ui.PointerChange.down));
expect(packets[6].data[0].pointerIdentifier, equals(2));
expect(packets[6].data[0].synthesized, equals(false));
expect(packets[6].data[0].physicalX, equals(20.0));
expect(packets[6].data[0].physicalY, equals(10.0));
expect(packets[6].data[0].physicalDeltaX, equals(0.0));
expect(packets[6].data[0].physicalDeltaY, equals(0.0));
});
test('does synthesize add or hover or more for scroll', () {
List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(html.WheelEvent('wheel',
button: 1,
clientX: 10,
clientY: 10,
deltaX: 10,
deltaY: 10,
));
glassPane.dispatchEvent(html.WheelEvent('wheel',
button: 1,
clientX: 20,
clientY: 50,
deltaX: 10,
deltaY: 10,
));
glassPane.dispatchEvent(html.PointerEvent('pointerdown', {
'pointerId': -1,
'button': 1,
'clientX': 20.0,
'clientY': 50.0,
}));
glassPane.dispatchEvent(html.WheelEvent('wheel',
button: 1,
clientX: 30,
clientY: 60,
deltaX: 10,
deltaY: 10,
));
expect(packets, hasLength(4));
// An add will be synthesized.
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].pointerIdentifier, equals(0));
expect(packets[0].data[0].synthesized, equals(true));
expect(packets[0].data[0].physicalX, equals(10.0));
expect(packets[0].data[0].physicalY, equals(10.0));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
expect(packets[0].data[1].pointerIdentifier, equals(0));
expect(packets[0].data[1].synthesized, equals(false));
expect(packets[0].data[1].physicalX, equals(10.0));
expect(packets[0].data[1].physicalY, equals(10.0));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
// A hover will be synthesized.
expect(packets[1].data, hasLength(2));
expect(packets[1].data[0].change, equals(ui.PointerChange.hover));
expect(packets[1].data[0].pointerIdentifier, equals(0));
expect(packets[1].data[0].synthesized, equals(true));
expect(packets[1].data[0].physicalX, equals(20.0));
expect(packets[1].data[0].physicalY, equals(50.0));
expect(packets[1].data[0].physicalDeltaX, equals(10.0));
expect(packets[1].data[0].physicalDeltaY, equals(40.0));
expect(packets[1].data[1].change, equals(ui.PointerChange.hover));
expect(packets[1].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
expect(packets[1].data[1].pointerIdentifier, equals(0));
expect(packets[1].data[1].synthesized, equals(false));
expect(packets[1].data[1].physicalX, equals(20.0));
expect(packets[1].data[1].physicalY, equals(50.0));
expect(packets[1].data[1].physicalDeltaX, equals(0.0));
expect(packets[1].data[1].physicalDeltaY, equals(0.0));
// No synthetic pointer data for down event.
expect(packets[2].data, hasLength(1));
expect(packets[2].data[0].change, equals(ui.PointerChange.down));
expect(packets[2].data[0].signalKind, equals(null));
expect(packets[2].data[0].pointerIdentifier, equals(1));
expect(packets[2].data[0].synthesized, equals(false));
expect(packets[2].data[0].physicalX, equals(20.0));
expect(packets[2].data[0].physicalY, equals(50.0));
expect(packets[2].data[0].physicalDeltaX, equals(0.0));
expect(packets[2].data[0].physicalDeltaY, equals(0.0));
// A move will be synthesized instead of hover because the button is currently down.
expect(packets[3].data, hasLength(2));
expect(packets[3].data[0].change, equals(ui.PointerChange.move));
expect(packets[3].data[0].pointerIdentifier, equals(1));
expect(packets[3].data[0].synthesized, equals(true));
expect(packets[3].data[0].physicalX, equals(30.0));
expect(packets[3].data[0].physicalY, equals(60.0));
expect(packets[3].data[0].physicalDeltaX, equals(10.0));
expect(packets[3].data[0].physicalDeltaY, equals(10.0));
expect(packets.single.data[0].change, equals(ui.PointerChange.down));
expect(packets[3].data[1].change, equals(ui.PointerChange.hover));
expect(packets[3].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
expect(packets[3].data[1].pointerIdentifier, equals(1));
expect(packets[3].data[1].synthesized, equals(false));
expect(packets[3].data[1].physicalX, equals(30.0));
expect(packets[3].data[1].physicalY, equals(60.0));
expect(packets[3].data[1].physicalDeltaX, equals(0.0));
expect(packets[3].data[1].physicalDeltaY, equals(0.0));
});
});
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册