blob: 33b4e0294196cede6d97e2118d0ca8657040a073 [file] [log] [blame]
// 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.
import 'dart:js_util' as js_util;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart';
import 'package:ui/ui.dart' as ui;
import 'keyboard_converter_test.dart';
const int _kNoButtonChange = -1;
const PointerSupportDetector _defaultSupportDetector = PointerSupportDetector();
List<ui.PointerData> _allPointerData(List<ui.PointerDataPacket> packets) {
return packets.expand((ui.PointerDataPacket packet) => packet.data).toList();
}
typedef _ContextTestBody<T> = void Function(T);
void _testEach<T extends _BasicEventContext>(
Iterable<T> contexts,
String description,
_ContextTestBody<T> body, {
Object? skip,
}
) {
for (final T context in contexts) {
if (context.isSupported) {
test('${context.name} $description', () {
body(context);
}, skip: skip);
}
}
}
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
ensureFlutterViewEmbedderInitialized();
final DomElement glassPane = flutterViewEmbedder.glassPaneElement;
late double dpi;
setUp(() {
ui.window.onPointerDataPacket = null;
dpi = window.devicePixelRatio;
});
KeyboardConverter createKeyboardConverter(List<ui.KeyData> keyDataList) {
return KeyboardConverter((ui.KeyData key) {
keyDataList.add(key);
return true;
}, OperatingSystem.linux);
}
test('ios workaround', () {
debugEmulateIosSafari = true;
addTearDown(() {
debugEmulateIosSafari = false;
});
final MockSafariPointerEventWorkaround mockSafariPointer =
MockSafariPointerEventWorkaround();
SafariPointerEventWorkaround.instance = mockSafariPointer;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
final PointerBinding instance = PointerBinding(createDomHTMLDivElement(), keyboardConverter);
expect(mockSafariPointer.workAroundInvoked, isIosSafari);
instance.dispose();
}, skip: !isSafari);
test('_PointerEventContext generates expected events', () {
if (!_PointerEventContext().isSupported) {
return;
}
DomPointerEvent expectCorrectType(DomEvent e) {
expect(domInstanceOfString(e, 'PointerEvent'), isTrue);
return e as DomPointerEvent;
}
List<DomPointerEvent> expectCorrectTypes(List<DomEvent> events) {
return events.map(expectCorrectType).toList();
}
final _PointerEventContext context = _PointerEventContext();
DomPointerEvent event;
List<DomPointerEvent> events;
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.type, equals('pointerdown'));
expect(event.pointerId, equals(1));
expect(event.button, equals(0));
expect(event.buttons, equals(1));
expect(event.client.x, equals(100));
expect(event.client.y, equals(101));
event = expectCorrectType(
context.mouseDown(clientX: 110, clientY: 111, button: 2, buttons: 2));
expect(event.type, equals('pointerdown'));
expect(event.pointerId, equals(1));
expect(event.button, equals(2));
expect(event.buttons, equals(2));
expect(event.client.x, equals(110));
expect(event.client.y, equals(111));
events = expectCorrectTypes(context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 100, clientX: 120, clientY: 121),
_TouchDetails(pointer: 101, clientX: 122, clientY: 123),
]));
expect(events.length, equals(2));
expect(events[0].type, equals('pointerdown'));
expect(events[0].pointerId, equals(100));
expect(events[0].button, equals(0));
expect(events[0].buttons, equals(1));
expect(events[0].client.x, equals(120));
expect(events[0].client.y, equals(121));
expect(events[1].type, equals('pointerdown'));
expect(events[1].pointerId, equals(101));
expect(events[1].button, equals(0));
expect(events[1].buttons, equals(1));
expect(events[1].client.x, equals(122));
expect(events[1].client.y, equals(123));
event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201));
expect(event.type, equals('pointermove'));
expect(event.pointerId, equals(1));
expect(event.button, equals(-1));
expect(event.buttons, equals(1));
expect(event.client.x, equals(200));
expect(event.client.y, equals(201));
event = expectCorrectType(context.mouseMove(
clientX: 210, clientY: 211, button: _kNoButtonChange, buttons: 6));
expect(event.type, equals('pointermove'));
expect(event.pointerId, equals(1));
expect(event.button, equals(-1));
expect(event.buttons, equals(6));
expect(event.client.x, equals(210));
expect(event.client.y, equals(211));
event = expectCorrectType(
context.mouseMove(clientX: 212, clientY: 213, button: 2, buttons: 6));
expect(event.type, equals('pointermove'));
expect(event.pointerId, equals(1));
expect(event.button, equals(2));
expect(event.buttons, equals(6));
expect(event.client.x, equals(212));
expect(event.client.y, equals(213));
event = expectCorrectType(
context.mouseMove(clientX: 214, clientY: 215, button: 2, buttons: 1));
expect(event.type, equals('pointermove'));
expect(event.pointerId, equals(1));
expect(event.button, equals(2));
expect(event.buttons, equals(1));
expect(event.client.x, equals(214));
expect(event.client.y, equals(215));
events = expectCorrectTypes(context.multiTouchMove(const <_TouchDetails>[
_TouchDetails(pointer: 102, clientX: 220, clientY: 221),
_TouchDetails(pointer: 103, clientX: 222, clientY: 223),
]));
expect(events.length, equals(2));
expect(events[0].type, equals('pointermove'));
expect(events[0].pointerId, equals(102));
expect(events[0].button, equals(-1));
expect(events[0].buttons, equals(1));
expect(events[0].client.x, equals(220));
expect(events[0].client.y, equals(221));
expect(events[1].type, equals('pointermove'));
expect(events[1].pointerId, equals(103));
expect(events[1].button, equals(-1));
expect(events[1].buttons, equals(1));
expect(events[1].client.x, equals(222));
expect(events[1].client.y, equals(223));
event = expectCorrectType(context.mouseLeave(clientX: 1000, clientY: 2000, buttons: 6));
expect(event.type, equals('pointerleave'));
expect(event.pointerId, equals(1));
expect(event.button, equals(0));
expect(event.buttons, equals(6));
expect(event.client.x, equals(1000));
expect(event.client.y, equals(2000));
event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301));
expect(event.type, equals('pointerup'));
expect(event.pointerId, equals(1));
expect(event.button, equals(0));
expect(event.buttons, equals(0));
expect(event.client.x, equals(300));
expect(event.client.y, equals(301));
event = expectCorrectType(
context.mouseUp(clientX: 310, clientY: 311, button: 2));
expect(event.type, equals('pointerup'));
expect(event.pointerId, equals(1));
expect(event.button, equals(2));
expect(event.buttons, equals(0));
expect(event.client.x, equals(310));
expect(event.client.y, equals(311));
events = expectCorrectTypes(context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 104, clientX: 320, clientY: 321),
_TouchDetails(pointer: 105, clientX: 322, clientY: 323),
]));
expect(events.length, equals(2));
expect(events[0].type, equals('pointerup'));
expect(events[0].pointerId, equals(104));
expect(events[0].button, equals(0));
expect(events[0].buttons, equals(0));
expect(events[0].client.x, equals(320));
expect(events[0].client.y, equals(321));
expect(events[1].type, equals('pointerup'));
expect(events[1].pointerId, equals(105));
expect(events[1].button, equals(0));
expect(events[1].buttons, equals(0));
expect(events[1].client.x, equals(322));
expect(events[1].client.y, equals(323));
event = expectCorrectType(context.hover(clientX: 400, clientY: 401));
expect(event.type, equals('pointermove'));
expect(event.pointerId, equals(1));
expect(event.button, equals(-1));
expect(event.buttons, equals(0));
expect(event.client.x, equals(400));
expect(event.client.y, equals(401));
events = expectCorrectTypes(context.multiTouchCancel(const <_TouchDetails>[
_TouchDetails(pointer: 106, clientX: 500, clientY: 501),
_TouchDetails(pointer: 107, clientX: 502, clientY: 503),
]));
expect(events.length, equals(2));
expect(events[0].type, equals('pointercancel'));
expect(events[0].pointerId, equals(106));
expect(events[0].button, equals(0));
expect(events[0].buttons, equals(0));
expect(events[0].client.x, equals(0));
expect(events[0].client.y, equals(0));
expect(events[1].type, equals('pointercancel'));
expect(events[1].pointerId, equals(107));
expect(events[1].button, equals(0));
expect(events[1].buttons, equals(0));
expect(events[1].client.x, equals(0));
expect(events[1].client.y, equals(0));
context.pressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.getModifierState('Alt'), true);
expect(event.getModifierState('Control'), true);
expect(event.getModifierState('Meta'), true);
expect(event.getModifierState('Shift'), true);
context.unpressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.getModifierState('Alt'), false);
expect(event.getModifierState('Control'), false);
expect(event.getModifierState('Meta'), false);
expect(event.getModifierState('Shift'), false);
});
test('_TouchEventContext generates expected events', () {
if (!_TouchEventContext().isSupported) {
return;
}
DomTouchEvent expectCorrectType(DomEvent e) {
expect(domInstanceOfString(e, 'TouchEvent'), isTrue);
return e as DomTouchEvent;
}
List<DomTouchEvent> expectCorrectTypes(List<DomEvent> events) {
return events.map(expectCorrectType).toList();
}
final _TouchEventContext context = _TouchEventContext();
DomTouchEvent event;
List<DomTouchEvent> events;
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.type, equals('touchstart'));
expect(event.changedTouches.length, equals(1));
expect(event.changedTouches.first.identifier, equals(1));
expect(event.changedTouches.first.client.x, equals(100));
expect(event.changedTouches.first.client.y, equals(101));
events = expectCorrectTypes(context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 100, clientX: 120, clientY: 121),
_TouchDetails(pointer: 101, clientX: 122, clientY: 123),
]));
expect(events.length, equals(1));
expect(events[0].type, equals('touchstart'));
expect(events[0].changedTouches.length, equals(2));
expect(events[0].changedTouches.first.identifier, equals(100));
expect(events[0].changedTouches.first.client.x, equals(120));
expect(events[0].changedTouches.first.client.y, equals(121));
expect(events[0].changedTouches.elementAt(1).identifier, equals(101));
expect(events[0].changedTouches.elementAt(1).client.x, equals(122));
expect(events[0].changedTouches.elementAt(1).client.y, equals(123));
event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201));
expect(event.type, equals('touchmove'));
expect(event.changedTouches.length, equals(1));
expect(event.changedTouches.first.identifier, equals(1));
expect(event.changedTouches.first.client.x, equals(200));
expect(event.changedTouches.first.client.y, equals(201));
events = expectCorrectTypes(context.multiTouchMove(const <_TouchDetails>[
_TouchDetails(pointer: 102, clientX: 220, clientY: 221),
_TouchDetails(pointer: 103, clientX: 222, clientY: 223),
]));
expect(events.length, equals(1));
expect(events[0].type, equals('touchmove'));
expect(events[0].changedTouches.length, equals(2));
expect(events[0].changedTouches.first.identifier, equals(102));
expect(events[0].changedTouches.first.client.x, equals(220));
expect(events[0].changedTouches.first.client.y, equals(221));
expect(events[0].changedTouches.elementAt(1).identifier, equals(103));
expect(events[0].changedTouches.elementAt(1).client.x, equals(222));
expect(events[0].changedTouches.elementAt(1).client.y, equals(223));
event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301));
expect(event.type, equals('touchend'));
expect(event.changedTouches.length, equals(1));
expect(event.changedTouches.first.identifier, equals(1));
expect(event.changedTouches.first.client.x, equals(300));
expect(event.changedTouches.first.client.y, equals(301));
events = expectCorrectTypes(context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 104, clientX: 320, clientY: 321),
_TouchDetails(pointer: 105, clientX: 322, clientY: 323),
]));
expect(events.length, equals(1));
expect(events[0].type, equals('touchend'));
expect(events[0].changedTouches.length, equals(2));
expect(events[0].changedTouches.first.identifier, equals(104));
expect(events[0].changedTouches.first.client.x, equals(320));
expect(events[0].changedTouches.first.client.y, equals(321));
expect(events[0].changedTouches.elementAt(1).identifier, equals(105));
expect(events[0].changedTouches.elementAt(1).client.x, equals(322));
expect(events[0].changedTouches.elementAt(1).client.y, equals(323));
events = expectCorrectTypes(context.multiTouchCancel(const <_TouchDetails>[
_TouchDetails(pointer: 104, clientX: 320, clientY: 321),
_TouchDetails(pointer: 105, clientX: 322, clientY: 323),
]));
expect(events.length, equals(1));
expect(events[0].type, equals('touchcancel'));
expect(events[0].changedTouches.length, equals(2));
expect(events[0].changedTouches.first.identifier, equals(104));
expect(events[0].changedTouches.first.client.x, equals(320));
expect(events[0].changedTouches.first.client.y, equals(321));
expect(events[0].changedTouches.elementAt(1).identifier, equals(105));
expect(events[0].changedTouches.elementAt(1).client.x, equals(322));
expect(events[0].changedTouches.elementAt(1).client.y, equals(323));
context.pressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.altKey, true);
expect(event.ctrlKey, true);
expect(event.metaKey, true);
expect(event.shiftKey, true);
context.unpressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.altKey, false);
expect(event.ctrlKey, false);
expect(event.metaKey, false);
expect(event.shiftKey, false);
});
test('_MouseEventContext generates expected events', () {
if (!_MouseEventContext().isSupported) {
return;
}
DomMouseEvent expectCorrectType(DomEvent e) {
expect(domInstanceOfString(e, 'MouseEvent'), isTrue);
return e as DomMouseEvent;
}
final _MouseEventContext context = _MouseEventContext();
DomMouseEvent event;
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.type, equals('mousedown'));
expect(event.button, equals(0));
expect(event.buttons, equals(1));
expect(event.client.x, equals(100));
expect(event.client.y, equals(101));
expect(event.offset.x, equals(100));
expect(event.offset.y, equals(101));
event = expectCorrectType(
context.mouseDown(clientX: 110, clientY: 111, button: 2, buttons: 2));
expect(event.type, equals('mousedown'));
expect(event.button, equals(2));
expect(event.buttons, equals(2));
expect(event.client.x, equals(110));
expect(event.client.y, equals(111));
event = expectCorrectType(context.primaryMove(clientX: 200, clientY: 201));
expect(event.type, equals('mousemove'));
expect(event.button, equals(0));
expect(event.buttons, equals(1));
expect(event.client.x, equals(200));
expect(event.client.y, equals(201));
event = expectCorrectType(context.mouseMove(
clientX: 210, clientY: 211, button: _kNoButtonChange, buttons: 6));
expect(event.type, equals('mousemove'));
expect(event.button, equals(0));
expect(event.buttons, equals(6));
expect(event.client.x, equals(210));
expect(event.client.y, equals(211));
event = expectCorrectType(
context.mouseMove(clientX: 212, clientY: 213, button: 2, buttons: 6));
expect(event.type, equals('mousedown'));
expect(event.button, equals(2));
expect(event.buttons, equals(6));
expect(event.client.x, equals(212));
expect(event.client.y, equals(213));
event = expectCorrectType(
context.mouseMove(clientX: 214, clientY: 215, button: 2, buttons: 1));
expect(event.type, equals('mouseup'));
expect(event.button, equals(2));
expect(event.buttons, equals(1));
expect(event.client.x, equals(214));
expect(event.client.y, equals(215));
event = expectCorrectType(context.mouseLeave(clientX: 1000, clientY: 2000, buttons: 6));
expect(event.type, equals('mouseleave'));
expect(event.button, equals(0));
expect(event.buttons, equals(6));
expect(event.client.x, equals(1000));
expect(event.client.y, equals(2000));
event = expectCorrectType(context.primaryUp(clientX: 300, clientY: 301));
expect(event.type, equals('mouseup'));
expect(event.button, equals(0));
expect(event.buttons, equals(0));
expect(event.client.x, equals(300));
expect(event.client.y, equals(301));
event = expectCorrectType(
context.mouseUp(clientX: 310, clientY: 311, button: 2));
expect(event.type, equals('mouseup'));
expect(event.button, equals(2));
expect(event.buttons, equals(0));
expect(event.client.x, equals(310));
expect(event.client.y, equals(311));
event = expectCorrectType(context.hover(clientX: 400, clientY: 401));
expect(event.type, equals('mousemove'));
expect(event.button, equals(0));
expect(event.buttons, equals(0));
expect(event.client.x, equals(400));
expect(event.client.y, equals(401));
context.pressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.getModifierState('Alt'), true);
expect(event.getModifierState('Control'), true);
expect(event.getModifierState('Meta'), true);
expect(event.getModifierState('Shift'), true);
context.unpressAllModifiers();
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.getModifierState('Alt'), false);
expect(event.getModifierState('Control'), false);
expect(event.getModifierState('Meta'), false);
expect(event.getModifierState('Shift'), false);
});
// ALL ADAPTERS
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'can receive pointer events on the glass pane',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
ui.PointerDataPacket? receivedPacket;
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
receivedPacket = packet;
};
glassPane.dispatchEvent(context.primaryDown());
expect(receivedPacket, isNotNull);
expect(receivedPacket!.data[0].buttons, equals(1));
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'does create an add event if got a pointerdown',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.primaryDown());
expect(packets, 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));
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'synthesize modifier keys left down event if left or right are not pressed',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
// Should synthesize a modifier left key down event when DOM event indicates
// that the modifier key is pressed and known pressing state doesn't contain
// the modifier left key nor the modifier right key.
void shouldSynthesizeLeftDownIfNotPressed(String key) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final int logicalLeft = kWebLogicalLocationMap[key]![kLocationLeft]!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
expect(keyboardConverter.keyIsPressed(physicalLeft), false);
expect(keyboardConverter.keyIsPressed(physicalRight), false);
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 1);
expectKeyData(keyDataList.last,
type: ui.KeyEventType.down,
physical: physicalLeft,
logical: logicalLeft,
character: null,
synthesized: true,
);
}
context.altPressed = true;
shouldSynthesizeLeftDownIfNotPressed('Alt');
context.unpressAllModifiers();
context.ctrlPressed = true;
shouldSynthesizeLeftDownIfNotPressed('Control');
context.unpressAllModifiers();
context.metaPressed = true;
shouldSynthesizeLeftDownIfNotPressed('Meta');
context.unpressAllModifiers();
context.shiftPressed = true;
shouldSynthesizeLeftDownIfNotPressed('Shift');
context.unpressAllModifiers();
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'should not synthesize modifier keys down event if left or right are pressed',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
// Should not synthesize a modifier down event when DOM event indicates
// that the modifier key is pressed and known pressing state contains
// the modifier left key.
void shouldNotSynthesizeDownIfLeftPressed(String key, int modifiers) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
keyboardConverter.handleEvent(keyDownEvent('${key}Left', key, modifiers, kLocationLeft));
expect(keyboardConverter.keyIsPressed(physicalLeft), true);
expect(keyboardConverter.keyIsPressed(physicalRight), false);
keyDataList.clear(); // Remove key data generated by handleEvent
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 0);
}
// Should not synthesize a modifier down event when DOM event indicates
// that the modifier key is pressed and known pressing state contains
// the modifier right key.
void shouldNotSynthesizeDownIfRightPressed(String key, int modifiers) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
keyboardConverter.handleEvent(keyDownEvent('${key}Right', key, modifiers, kLocationRight));
expect(keyboardConverter.keyIsPressed(physicalLeft), false);
expect(keyboardConverter.keyIsPressed(physicalRight), true);
keyDataList.clear(); // Remove key data generated by handleEvent
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 0);
}
context.altPressed = true;
shouldNotSynthesizeDownIfLeftPressed('Alt', kAlt);
shouldNotSynthesizeDownIfRightPressed('Alt', kAlt);
context.unpressAllModifiers();
context.ctrlPressed = true;
shouldNotSynthesizeDownIfLeftPressed('Control', kCtrl);
shouldNotSynthesizeDownIfRightPressed('Control', kCtrl);
context.unpressAllModifiers();
context.metaPressed = true;
shouldNotSynthesizeDownIfLeftPressed('Meta', kMeta);
shouldNotSynthesizeDownIfRightPressed('Meta', kMeta);
context.unpressAllModifiers();
context.shiftPressed = true;
shouldNotSynthesizeDownIfLeftPressed('Shift', kShift);
shouldNotSynthesizeDownIfRightPressed('Shift', kShift);
context.unpressAllModifiers();
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'synthesize modifier keys up event if left or right are pressed',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
// Should synthesize a modifier left key up event when DOM event indicates
// that the modifier key is not pressed and known pressing state contains
// the modifier left key.
void shouldSynthesizeLeftUpIfLeftPressed(String key, int modifiers) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final int logicalLeft = kWebLogicalLocationMap[key]![kLocationLeft]!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
keyboardConverter.handleEvent(keyDownEvent('${key}Left', key, modifiers, kLocationLeft));
expect(keyboardConverter.keyIsPressed(physicalLeft), true);
expect(keyboardConverter.keyIsPressed(physicalRight), false);
keyDataList.clear(); // Remove key data generated by handleEvent
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 1);
expectKeyData(keyDataList.last,
type: ui.KeyEventType.up,
physical: physicalLeft,
logical: logicalLeft,
character: null,
synthesized: true,
);
expect(keyboardConverter.keyIsPressed(physicalLeft), false);
}
// Should synthesize a modifier right key up event when DOM event indicates
// that the modifier key is not pressed and known pressing state contains
// the modifier right key.
void shouldSynthesizeRightUpIfRightPressed(String key, int modifiers) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final int logicalRight = kWebLogicalLocationMap[key]![kLocationRight]!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
keyboardConverter.handleEvent(keyDownEvent('${key}Right', key, modifiers, kLocationRight));
expect(keyboardConverter.keyIsPressed(physicalLeft), false);
expect(keyboardConverter.keyIsPressed(physicalRight), true);
keyDataList.clear(); // Remove key data generated by handleEvent
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 1);
expectKeyData(keyDataList.last,
type: ui.KeyEventType.up,
physical: physicalRight,
logical: logicalRight,
character: null,
synthesized: true,
);
expect(keyboardConverter.keyIsPressed(physicalRight), false);
}
context.altPressed = false;
shouldSynthesizeLeftUpIfLeftPressed('Alt', kAlt);
shouldSynthesizeRightUpIfRightPressed('Alt', kAlt);
context.ctrlPressed = false;
shouldSynthesizeLeftUpIfLeftPressed('Control', kCtrl);
shouldSynthesizeRightUpIfRightPressed('Control', kCtrl);
context.metaPressed = false;
shouldSynthesizeLeftUpIfLeftPressed('Meta', kMeta);
shouldSynthesizeRightUpIfRightPressed('Meta', kMeta);
context.shiftPressed = false;
shouldSynthesizeLeftUpIfLeftPressed('Shift', kShift);
shouldSynthesizeRightUpIfRightPressed('Shift', kShift);
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'should not synthesize modifier keys up event if left or right are not pressed',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
// Should not synthesize a modifier up event when DOM event indicates
// that the modifier key is not pressed and known pressing state does
// not contain the modifier left key nor the modifier right key.
void shouldNotSynthesizeUpIfNotPressed(String key) {
final int physicalLeft = kWebToPhysicalKey['${key}Left']!;
final int physicalRight = kWebToPhysicalKey['${key}Right']!;
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
expect(keyboardConverter.keyIsPressed(physicalLeft), false);
expect(keyboardConverter.keyIsPressed(physicalRight), false);
keyDataList.clear(); // Remove key data generated by handleEvent
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 0);
}
context.altPressed = false;
shouldNotSynthesizeUpIfNotPressed('Alt');
context.ctrlPressed = false;
shouldNotSynthesizeUpIfNotPressed('Control');
context.metaPressed = false;
shouldNotSynthesizeUpIfNotPressed('Meta');
context.shiftPressed = false;
shouldNotSynthesizeUpIfNotPressed('Shift');
},
);
_testEach<_BasicEventContext>(
<_BasicEventContext>[
_PointerEventContext(),
_MouseEventContext(),
_TouchEventContext(),
],
'should synthesize modifier keys up event for AltGraph',
(_BasicEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.KeyData> keyDataList = <ui.KeyData>[];
final KeyboardConverter keyboardConverter = createKeyboardConverter(keyDataList);
PointerBinding.instance!.debugOverrideKeyboardConverter(keyboardConverter);
final int physicalAltRight = kWebToPhysicalKey['AltRight']!;
final int logicalAltGraph = kWebLogicalLocationMap['AltGraph']![0]!;
// Simulate pressing `AltGr` key.
keyboardConverter.handleEvent(keyDownEvent('AltRight', 'AltGraph'));
expect(keyboardConverter.keyIsPressed(physicalAltRight), true);
keyDataList.clear(); // Remove key data generated by handleEvent.
glassPane.dispatchEvent(context.primaryDown());
expect(keyDataList.length, 1);
expectKeyData(keyDataList.last,
type: ui.KeyEventType.up,
physical: physicalAltRight,
logical: logicalAltGraph,
character: null,
synthesized: true,
);
expect(keyboardConverter.keyIsPressed(physicalAltRight), false);
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'correctly detects events on the semantics placeholder',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
final DomElement semanticsPlaceholder =
createDomElement('flt-semantics-placeholder');
glassPane.append(semanticsPlaceholder);
// Press on the semantics placeholder.
semanticsPlaceholder.dispatchEvent(context.primaryDown(
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].physicalX, equals(10.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
packets.clear();
// Drag on the semantics placeholder.
semanticsPlaceholder.dispatchEvent(context.primaryMove(
clientX: 12.0,
clientY: 10.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(12.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
packets.clear();
// Keep dragging.
semanticsPlaceholder.dispatchEvent(context.primaryMove(
clientX: 15.0,
clientY: 10.0,
));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.move));
expect(packets[0].data[0].physicalX, equals(15.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
packets.clear();
// Release the pointer on the semantics placeholder.
glassPane.dispatchEvent(context.primaryUp(
clientX: 100.0,
clientY: 200.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(100.0 * dpi));
expect(packets[0].data[0].physicalY, equals(200.0 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.up));
expect(packets[0].data[1].physicalX, equals(100.0 * dpi));
expect(packets[0].data[1].physicalY, equals(200.0 * dpi));
packets.clear();
semanticsPlaceholder.remove();
},
skip: isFirefox, // https://bugzilla.mozilla.org/show_bug.cgi?id=1804190
);
// BUTTONED ADAPTERS
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'creates an add event if the first pointer activity is a hover',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.hover());
expect(packets, hasLength(1));
expect(packets.single.data, hasLength(2));
expect(packets.single.data[0].change, equals(ui.PointerChange.add));
expect(packets.single.data[0].synthesized, isTrue);
expect(packets.single.data[1].change, equals(ui.PointerChange.hover));
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'sends a pointermove event instead of the second pointerdown in a row',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.primaryDown(
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
// 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, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
packets.clear();
glassPane.dispatchEvent(context.primaryDown(
clientX: 20.0,
clientY: 20.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].buttons, equals(1));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'does synthesize add or hover or move for scroll',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 10,
deltaY: 10,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 20,
clientY: 50,
deltaX: 10,
deltaY: 10,
));
glassPane.dispatchEvent(context.mouseDown(
button: 0,
buttons: 1,
clientX: 20.0,
clientY: 50.0,
));
glassPane.dispatchEvent(context.wheel(
buttons: 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, isTrue);
expect(packets[0].data[0].physicalX, equals(10.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
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, isFalse);
expect(packets[0].data[1].physicalX, equals(10.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
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, isTrue);
expect(packets[1].data[0].physicalX, equals(20.0 * dpi));
expect(packets[1].data[0].physicalY, equals(50.0 * dpi));
expect(packets[1].data[0].physicalDeltaX, equals(10.0 * dpi));
expect(packets[1].data[0].physicalDeltaY, equals(40.0 * dpi));
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, isFalse);
expect(packets[1].data[1].physicalX, equals(20.0 * dpi));
expect(packets[1].data[1].physicalY, equals(50.0 * dpi));
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(ui.PointerSignalKind.none));
expect(packets[2].data[0].pointerIdentifier, equals(1));
expect(packets[2].data[0].synthesized, isFalse);
expect(packets[2].data[0].physicalX, equals(20.0 * dpi));
expect(packets[2].data[0].physicalY, equals(50.0 * dpi));
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, isTrue);
expect(packets[3].data[0].physicalX, equals(30.0 * dpi));
expect(packets[3].data[0].physicalY, equals(60.0 * dpi));
expect(packets[3].data[0].physicalDeltaX, equals(10.0 * dpi));
expect(packets[3].data[0].physicalDeltaY, equals(10.0 * dpi));
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, isFalse);
expect(packets[3].data[1].physicalX, equals(30.0 * dpi));
expect(packets[3].data[1].physicalY, equals(60.0 * dpi));
expect(packets[3].data[1].physicalDeltaX, equals(0.0));
expect(packets[3].data[1].physicalDeltaY, equals(0.0));
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'converts scroll delta to physical pixels (Firefox)',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
const double dpi = 2.5;
debugOperatingSystemOverride = OperatingSystem.macOs;
debugBrowserEngineOverride = BrowserEngine.firefox;
window.debugOverrideDevicePixelRatio(dpi);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 10,
deltaY: 10,
));
expect(packets, hasLength(1));
// An add will be synthesized.
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
// Scroll deltas should be multiplied by `dpi`.
expect(packets[0].data[0].scrollDeltaX, equals(10.0 * dpi));
expect(packets[0].data[0].scrollDeltaY, equals(10.0 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
// Scroll deltas should be multiplied by `dpi`.
expect(packets[0].data[0].scrollDeltaX, equals(10.0 * dpi));
expect(packets[0].data[0].scrollDeltaY, equals(10.0 * dpi));
window.debugOverrideDevicePixelRatio(1.0);
debugOperatingSystemOverride = null;
debugBrowserEngineOverride = null;
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'scroll delta are already in physical pixels (Chrome)',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
const double dpi = 2.5;
debugOperatingSystemOverride = OperatingSystem.macOs;
debugBrowserEngineOverride = BrowserEngine.blink;
window.debugOverrideDevicePixelRatio(dpi);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 10,
deltaY: 10,
));
expect(packets, hasLength(1));
// An add will be synthesized.
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
// Scroll deltas should NOT be multiplied by `dpi`.
expect(packets[0].data[0].scrollDeltaX, equals(10.0));
expect(packets[0].data[0].scrollDeltaY, equals(10.0));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
// Scroll deltas should NOT be multiplied by `dpi`.
expect(packets[0].data[0].scrollDeltaX, equals(10.0));
expect(packets[0].data[0].scrollDeltaY, equals(10.0));
window.debugOverrideDevicePixelRatio(1.0);
debugOperatingSystemOverride = null;
debugBrowserEngineOverride = null;
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'does set pointer device kind based on delta precision and wheelDelta',
(_ButtonedEventMixin context) {
if (isFirefox) {
// Firefox does not support trackpad events, as they cannot be
// disambiguated from smoothed mouse wheel events.
return;
}
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 119,
deltaY: 119,
wheelDeltaX: -357,
wheelDeltaY: -357,
timeStamp: 0,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 120,
deltaY: 120,
wheelDeltaX: -360,
wheelDeltaY: -360,
timeStamp: 10,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 120,
deltaY: 120,
wheelDeltaX: -360,
wheelDeltaY: -360,
timeStamp: 20,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 119,
deltaY: 119,
wheelDeltaX: -357,
wheelDeltaY: -357,
timeStamp: 1000,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: -120,
deltaY: -120,
wheelDeltaX: 360,
wheelDeltaY: 360,
timeStamp: 1010,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 0,
deltaY: -120,
wheelDeltaX: 0,
wheelDeltaY: 360,
timeStamp: 2000,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 0,
deltaY: 40,
wheelDeltaX: 0,
wheelDeltaY: -360,
timeStamp: 3000,
));
expect(packets, hasLength(7));
// 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, isTrue);
expect(packets[0].data[0].physicalX, equals(10.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
// Because the delta is not in increments of 120 and has matching wheelDelta,
// it will be a trackpad event.
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].kind, equals(ui.PointerDeviceKind.trackpad));
expect(packets[0].data[1].device, equals(-2));
expect(packets[0].data[1].pointerIdentifier, equals(0));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(10.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
expect(packets[0].data[1].scrollDeltaX, equals(119.0));
expect(packets[0].data[1].scrollDeltaY, equals(119.0));
// Because the delta is in increments of 120, but is similar to the
// previous event, it will be a trackpad event.
expect(packets[1].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[1].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[1].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
expect(packets[1].data[0].device, equals(-2));
expect(packets[1].data[0].pointerIdentifier, equals(0));
expect(packets[1].data[0].synthesized, isFalse);
expect(packets[1].data[0].physicalX, equals(10.0 * dpi));
expect(packets[1].data[0].physicalY, equals(10.0 * dpi));
expect(packets[1].data[0].physicalDeltaX, equals(0.0));
expect(packets[1].data[0].physicalDeltaY, equals(0.0));
expect(packets[1].data[0].scrollDeltaX, equals(120.0));
expect(packets[1].data[0].scrollDeltaY, equals(120.0));
// Because the delta is in increments of 120, but is again similar to the
// previous event, it will be a trackpad event.
expect(packets[2].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[2].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[2].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
expect(packets[2].data[0].device, equals(-2));
expect(packets[2].data[0].pointerIdentifier, equals(0));
expect(packets[2].data[0].synthesized, isFalse);
expect(packets[2].data[0].physicalX, equals(10.0 * dpi));
expect(packets[2].data[0].physicalY, equals(10.0 * dpi));
expect(packets[2].data[0].physicalDeltaX, equals(0.0));
expect(packets[2].data[0].physicalDeltaY, equals(0.0));
expect(packets[2].data[0].scrollDeltaX, equals(120.0));
expect(packets[2].data[0].scrollDeltaY, equals(120.0));
// Because the delta is not in increments of 120 and has matching wheelDelta,
// it will be a trackpad event.
expect(packets[3].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[3].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[3].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
expect(packets[3].data[0].device, equals(-2));
expect(packets[3].data[0].pointerIdentifier, equals(0));
expect(packets[3].data[0].synthesized, isFalse);
expect(packets[3].data[0].physicalX, equals(10.0 * dpi));
expect(packets[3].data[0].physicalY, equals(10.0 * dpi));
expect(packets[3].data[0].physicalDeltaX, equals(0.0));
expect(packets[3].data[0].physicalDeltaY, equals(0.0));
expect(packets[3].data[0].scrollDeltaX, equals(119.0));
expect(packets[3].data[0].scrollDeltaY, equals(119.0));
// Because the delta is in increments of 120, and is not similar to the
// previous event, but occurred soon after the previous event, it will be
// a trackpad event.
expect(packets[4].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[4].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[4].data[0].kind, equals(ui.PointerDeviceKind.trackpad));
expect(packets[4].data[0].device, equals(-2));
expect(packets[4].data[0].pointerIdentifier, equals(0));
expect(packets[4].data[0].synthesized, isFalse);
expect(packets[4].data[0].physicalX, equals(10.0 * dpi));
expect(packets[4].data[0].physicalY, equals(10.0 * dpi));
expect(packets[4].data[0].physicalDeltaX, equals(0.0));
expect(packets[4].data[0].physicalDeltaY, equals(0.0));
expect(packets[4].data[0].scrollDeltaX, equals(-120.0));
expect(packets[4].data[0].scrollDeltaY, equals(-120.0));
// An add will be synthesized.
expect(packets[5].data, hasLength(2));
expect(packets[5].data[0].change, equals(ui.PointerChange.add));
expect(
packets[5].data[0].signalKind, equals(ui.PointerSignalKind.none));
expect(
packets[5].data[0].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[5].data[0].device, equals(-1));
expect(packets[5].data[0].pointerIdentifier, equals(0));
expect(packets[5].data[0].synthesized, isTrue);
expect(packets[5].data[0].physicalX, equals(10.0 * dpi));
expect(packets[5].data[0].physicalY, equals(10.0 * dpi));
expect(packets[5].data[0].physicalDeltaX, equals(0.0));
expect(packets[5].data[0].physicalDeltaY, equals(0.0));
expect(packets[5].data[0].scrollDeltaX, equals(0.0));
expect(packets[5].data[0].scrollDeltaY, equals(-120.0));
// Because the delta is in increments of 120, and is not similar to
// the previous event, and occurred long after the previous event, it will
// be a mouse event.
expect(packets[5].data[1].change, equals(ui.PointerChange.hover));
expect(
packets[5].data[1].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[5].data[1].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[5].data[1].device, equals(-1));
expect(packets[5].data[1].pointerIdentifier, equals(0));
expect(packets[5].data[1].synthesized, isFalse);
expect(packets[5].data[1].physicalX, equals(10.0 * dpi));
expect(packets[5].data[1].physicalY, equals(10.0 * dpi));
expect(packets[5].data[1].physicalDeltaX, equals(0.0));
expect(packets[5].data[1].physicalDeltaY, equals(0.0));
expect(packets[5].data[1].scrollDeltaX, equals(0.0));
expect(packets[5].data[1].scrollDeltaY, equals(-120.0));
// Because the delta is not in increments of 120 and has non-matching
// wheelDelta, it will be a mouse event.
expect(packets[6].data, hasLength(1));
expect(packets[6].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[6].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[6].data[0].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[6].data[0].device, equals(-1));
expect(packets[6].data[0].pointerIdentifier, equals(0));
expect(packets[6].data[0].synthesized, isFalse);
expect(packets[6].data[0].physicalX, equals(10.0 * dpi));
expect(packets[6].data[0].physicalY, equals(10.0 * dpi));
expect(packets[6].data[0].physicalDeltaX, equals(0.0));
expect(packets[6].data[0].physicalDeltaY, equals(0.0));
expect(packets[6].data[0].scrollDeltaX, equals(0.0));
expect(packets[6].data[0].scrollDeltaY, equals(40.0));
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'does choose scroll vs scale based on ctrlKey',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 0,
deltaY: 120,
));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 0,
deltaY: 100,
ctrlKey: true,
));
debugOperatingSystemOverride = OperatingSystem.macOs;
KeyboardBinding.instance?.converter.handleEvent(keyDownEvent('ControlLeft', 'Control', kCtrl));
glassPane.dispatchEvent(context.wheel(
buttons: 0,
clientX: 10,
clientY: 10,
deltaX: 0,
deltaY: 240,
ctrlKey: true,
));
KeyboardBinding.instance?.converter.handleEvent(keyUpEvent('ControlLeft', 'Control', kCtrl));
expect(packets, hasLength(3));
// 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, isTrue);
expect(packets[0].data[0].physicalX, equals(10.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
// Because ctrlKey is not pressed, it will be a scroll.
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].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[0].data[1].pointerIdentifier, equals(0));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(10.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
expect(packets[0].data[1].scrollDeltaX, equals(0.0));
expect(packets[0].data[1].scrollDeltaY, equals(120.0));
// Because ctrlKey is pressed, it will be a scale.
expect(packets[1].data, hasLength(1));
expect(packets[1].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[1].data[0].signalKind, equals(ui.PointerSignalKind.scale));
expect(
packets[1].data[0].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[1].data[0].pointerIdentifier, equals(0));
expect(packets[1].data[0].synthesized, isFalse);
expect(packets[1].data[0].physicalX, equals(10.0 * dpi));
expect(packets[1].data[0].physicalY, equals(10.0 * dpi));
expect(packets[1].data[0].physicalDeltaX, equals(0.0));
expect(packets[1].data[0].physicalDeltaY, equals(0.0));
expect(packets[1].data[0].scale, closeTo(0.60653065971, 1e-10)); // math.exp(-100/200)
// [macOS only]: Because ctrlKey is true, but the key is pressed physically, it will be a scroll.
expect(packets[2].data, hasLength(1));
expect(packets[2].data[0].change, equals(ui.PointerChange.hover));
expect(
packets[2].data[0].signalKind, equals(ui.PointerSignalKind.scroll));
expect(
packets[2].data[0].kind, equals(ui.PointerDeviceKind.mouse));
expect(packets[2].data[0].pointerIdentifier, equals(0));
expect(packets[2].data[0].synthesized, isFalse);
expect(packets[2].data[0].physicalX, equals(10.0 * dpi));
expect(packets[2].data[0].physicalY, equals(10.0 * dpi));
expect(packets[2].data[0].physicalDeltaX, equals(0.0));
expect(packets[2].data[0].physicalDeltaY, equals(0.0));
expect(packets[2].data[0].scrollDeltaX, equals(0.0));
expect(packets[2].data[0].scrollDeltaY, equals(240.0));
debugOperatingSystemOverride = null;
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext()
],
'does calculate delta and pointer identifier correctly',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.hover(
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
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, isTrue);
expect(packets[0].data[0].physicalX, equals(10.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
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, isFalse);
expect(packets[0].data[1].physicalX, equals(10.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
packets.clear();
glassPane.dispatchEvent(context.hover(
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.hover));
expect(packets[0].data[0].pointerIdentifier, equals(0));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(20.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(10.0 * dpi));
expect(packets[0].data[0].physicalDeltaY, equals(10.0 * dpi));
packets.clear();
glassPane.dispatchEvent(context.primaryDown(
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[0].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(20.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
packets.clear();
glassPane.dispatchEvent(context.primaryMove(
clientX: 40.0,
clientY: 30.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].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40.0 * dpi));
expect(packets[0].data[0].physicalY, equals(30.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalDeltaY, equals(10.0 * dpi));
packets.clear();
glassPane.dispatchEvent(context.primaryUp(
clientX: 40.0,
clientY: 30.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40.0 * dpi));
expect(packets[0].data[0].physicalY, equals(30.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
packets.clear();
glassPane.dispatchEvent(context.hover(
clientX: 20.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.hover));
expect(packets[0].data[0].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(-20.0 * dpi));
expect(packets[0].data[0].physicalDeltaY, equals(-20.0 * dpi));
packets.clear();
glassPane.dispatchEvent(context.primaryDown(
clientX: 20.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[0].pointerIdentifier, equals(2));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0.0));
expect(packets[0].data[0].physicalDeltaY, equals(0.0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'correctly converts buttons of down, move, leave, and up events',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Add and hover
glassPane.dispatchEvent(context.hover(
clientX: 10,
clientY: 11,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].physicalX, equals(10 * dpi));
expect(packets[0].data[0].physicalY, equals(11 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(10 * dpi));
expect(packets[0].data[1].physicalY, equals(11 * dpi));
expect(packets[0].data[1].buttons, equals(0));
packets.clear();
glassPane.dispatchEvent(context.mouseDown(
button: 0,
buttons: 1,
clientX: 10.0,
clientY: 11.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(10 * dpi));
expect(packets[0].data[0].physicalY, equals(11 * dpi));
expect(packets[0].data[0].buttons, equals(1));
packets.clear();
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 1,
clientX: 20.0,
clientY: 21.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].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(1));
packets.clear();
glassPane.dispatchEvent(context.mouseUp(
button: 0,
clientX: 20.0,
clientY: 21.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
// Drag with secondary button
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 20.0,
clientY: 21.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(2));
packets.clear();
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 2,
clientX: 30.0,
clientY: 31.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].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(30 * dpi));
expect(packets[0].data[0].physicalY, equals(31 * dpi));
expect(packets[0].data[0].buttons, equals(2));
packets.clear();
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 30.0,
clientY: 31.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(30 * dpi));
expect(packets[0].data[0].physicalY, equals(31 * dpi));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
// Drag with middle button
glassPane.dispatchEvent(context.mouseDown(
button: 1,
buttons: 4,
clientX: 30.0,
clientY: 31.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.down));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(30 * dpi));
expect(packets[0].data[0].physicalY, equals(31 * dpi));
expect(packets[0].data[0].buttons, equals(4));
packets.clear();
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 4,
clientX: 40.0,
clientY: 41.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].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40 * dpi));
expect(packets[0].data[0].physicalY, equals(41 * dpi));
expect(packets[0].data[0].buttons, equals(4));
packets.clear();
glassPane.dispatchEvent(context.mouseUp(
button: 1,
clientX: 40.0,
clientY: 41.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40 * dpi));
expect(packets[0].data[0].physicalY, equals(41 * dpi));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
// Leave
glassPane.dispatchEvent(context.mouseLeave(
buttons: 1,
clientX: 1000.0,
clientY: 2000.0,
));
expect(packets, isEmpty);
packets.clear();
glassPane.dispatchEvent(context.mouseLeave(
buttons: 0,
clientX: 1000.0,
clientY: 2000.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.hover));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(1000 * dpi));
expect(packets[0].data[0].physicalY, equals(2000 * dpi));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles button changes during a down sequence',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press LMB.
glassPane.dispatchEvent(context.mouseDown(
button: 0,
buttons: 1,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(1));
packets.clear();
// Press MMB.
glassPane.dispatchEvent(context.mouseMove(
button: 1,
buttons: 5,
));
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].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(5));
packets.clear();
// Release LMB.
glassPane.dispatchEvent(context.mouseMove(
button: 0,
buttons: 4,
));
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].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(4));
packets.clear();
// Release MMB.
glassPane.dispatchEvent(context.mouseUp(
button: 1,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _MouseEventContext(),
],
'synthesizes a pointerup event when pointermove comes before the up',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen when the user pops up the context menu by right
// clicking, then dismisses it with a left click.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10,
clientY: 11,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].physicalX, equals(10 * dpi));
expect(packets[0].data[0].physicalY, equals(11 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(10 * dpi));
expect(packets[0].data[1].physicalY, equals(11 * dpi));
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 2,
clientX: 20.0,
clientY: 21.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].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(2));
packets.clear();
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 2,
clientX: 20.0,
clientY: 21.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].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(2));
packets.clear();
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 20.0,
clientY: 21.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(20 * dpi));
expect(packets[0].data[0].physicalY, equals(21 * dpi));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles uncontinuous button changes during a down sequence',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking, but holds RMB;
// - Clicks LMB;
// - Releases RMB.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Press LMB. The event will have "button: -1" here, despite the change
// in "buttons", probably because the "press" gesture was absorbed by
// dismissing the context menu.
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 3,
));
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].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(3));
packets.clear();
// Release LMB.
glassPane.dispatchEvent(context.mouseMove(
button: 0,
buttons: 2,
));
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].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(2));
packets.clear();
// Release RMB.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
],
'correctly handles missing right mouse button up when followed by move',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking;
// - Clicks LMB to close context menu.
// - Moves mouse.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB popping up the context menu, then release by LMB down and up.
// Browser won't send up event in that case.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// User now hovers.
glassPane.dispatchEvent(context.mouseMove(
button: _kNoButtonChange,
buttons: 0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'handles RMB click when the browser sends it as a move',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// When the user clicks the RMB and moves the mouse quickly (before the
// context menu shows up), the browser sends a move event before down.
// The move event will have "button:-1, buttons:2".
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseMove(
button: -1,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.hover));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles hover after RMB click',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking, but holds RMB;
// - Move the pointer to hover.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Move the mouse. The event will have "buttons: 0" because RMB was
// released but the browser didn't send a pointerup/mouseup event.
// The hover is also triggered at a different position.
glassPane.dispatchEvent(context.hover(
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(3));
expect(packets[0].data[0].change, equals(ui.PointerChange.move));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].buttons, equals(2));
expect(packets[0].data[1].change, equals(ui.PointerChange.up));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(0));
expect(packets[0].data[2].change, equals(ui.PointerChange.hover));
expect(packets[0].data[2].synthesized, isFalse);
expect(packets[0].data[2].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles LMB click after RMB click',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking, but holds RMB;
// - Clicks LMB in a different location;
// - Release LMB.
//
// The LMB click occurs in a different location because when RMB is
// clicked, and the contextmenu is shown, the browser stops sending
// `pointermove`/`mousemove` events. Then when the LMB click comes in, it
// could be in a different location without any `*move` events in between.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Press LMB.
glassPane.dispatchEvent(context.mouseDown(
button: 0,
buttons: 3,
clientX: 20.0,
clientY: 20.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].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(3));
packets.clear();
// Release LMB.
glassPane.dispatchEvent(context.primaryUp(
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles two consecutive RMB clicks with no up in between',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking, but holds RMB;
// - Clicks RMB again in a different location;
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Press RMB again. In Chrome, when RMB is clicked again while the
// context menu is still active, it sends a pointerdown/mousedown event
// with "buttons:0". We convert this to pointer up, pointer down.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 0,
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(3));
expect(packets[0].data[0].change, equals(ui.PointerChange.move));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].buttons, equals(2));
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(20.0 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.up));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(0));
expect(packets[0].data[2].change, equals(ui.PointerChange.down));
expect(packets[0].data[2].synthesized, isFalse);
expect(packets[0].data[2].buttons, equals(2));
packets.clear();
// Release RMB.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles two consecutive RMB clicks with up in between',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking, but doesn't hold RMB;
// - Clicks RMB again in a different location;
//
// This seems to be happening sometimes when using RMB on the Mac trackpad.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// RMB up.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
// Press RMB again. In Chrome, when RMB is clicked again while the
// context menu is still active, it sends a pointerdown/mousedown event
// with "buttons:0".
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 0,
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.hover));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Release RMB.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'correctly handles two consecutive RMB clicks in two different locations',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// - Pops up the context menu by right clicking;
// - The browser sends RMB up event;
// - Click RMB again in a different location;
//
// This scenario happens occasionally. I'm still not sure why, but in some
// cases, the browser actually sends an `up` event for the RMB click even
// when the context menu is shown.
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press RMB and hold, popping up the context menu.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
// Release RMB.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
// Press RMB again, in a different location.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 2,
clientX: 20.0,
clientY: 20.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.hover));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(2));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
_PointerEventContext(),
_MouseEventContext(),
],
'handles overlapping left/right down and up events',
(_ButtonedEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This can happen with the following gesture sequence:
//
// LMB: down-------------------up
// RMB: down------------------up
// Flutter: down-------move-------move-------up
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Press and hold LMB.
glassPane.dispatchEvent(context.mouseDown(
button: 0,
buttons: 1,
clientX: 5.0,
clientY: 100.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].buttons, equals(1));
expect(packets[0].data[1].physicalX, equals(5.0 * dpi));
expect(packets[0].data[1].physicalY, equals(100.0 * dpi));
packets.clear();
// Press and hold RMB. The pointer is already down, so we only send a move
// to update the position of the pointer.
glassPane.dispatchEvent(context.mouseDown(
button: 2,
buttons: 3,
clientX: 20.0,
clientY: 100.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].buttons, equals(3));
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(100.0 * dpi));
packets.clear();
// Release LMB. The pointer is still down (RMB), so we only send a move to
// update the position of the pointer.
glassPane.dispatchEvent(context.mouseUp(
button: 0,
buttons: 2,
clientX: 30.0,
clientY: 100.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].buttons, equals(2));
expect(packets[0].data[0].physicalX, equals(30.0 * dpi));
expect(packets[0].data[0].physicalY, equals(100.0 * dpi));
packets.clear();
// Release RMB. There's no more buttons down, so we send an up event.
glassPane.dispatchEvent(context.mouseUp(
button: 2,
buttons: 0,
clientX: 30.0,
clientY: 100.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].buttons, equals(0));
packets.clear();
},
);
_testEach<_ButtonedEventMixin>(
<_ButtonedEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _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.
final 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.
glassPane.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 * dpi));
expect(packets[0].data[0].physicalY, equals(1900.0 * dpi));
packets.clear();
// Release outside the glasspane.
glassPane.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 * dpi));
expect(packets[0].data[0].physicalY, equals(2000.0 * dpi));
expect(packets[0].data[1].change, equals(ui.PointerChange.up));
expect(packets[0].data[1].physicalX, equals(1000.0 * dpi));
expect(packets[0].data[1].physicalY, equals(2000.0 * dpi));
packets.clear();
},
);
// MULTIPOINTER ADAPTERS
_testEach<_MultiPointerEventMixin>(
<_MultiPointerEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _TouchEventContext(),
],
'treats each pointer separately',
(_MultiPointerEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
List<ui.PointerData> data;
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Two pointers down
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 2, clientX: 100, clientY: 101),
_TouchDetails(pointer: 3, clientX: 200, clientY: 201),
]).forEach(glassPane.dispatchEvent);
if (context.runtimeType == _PointerEventContext) {
expect(packets.length, 2);
expect(packets[0].data.length, 2);
expect(packets[1].data.length, 2);
} else if (context.runtimeType == _TouchEventContext) {
expect(packets.length, 1);
expect(packets[0].data.length, 4);
} else {
assert(false, 'Unexpected context type ${context.runtimeType}');
}
data = _allPointerData(packets);
expect(data, hasLength(4));
expect(data[0].change, equals(ui.PointerChange.add));
expect(data[0].synthesized, isTrue);
expect(data[0].device, equals(2));
expect(data[0].physicalX, equals(100 * dpi));
expect(data[0].physicalY, equals(101 * dpi));
expect(data[1].change, equals(ui.PointerChange.down));
expect(data[1].device, equals(2));
expect(data[1].buttons, equals(1));
expect(data[1].physicalX, equals(100 * dpi));
expect(data[1].physicalY, equals(101 * dpi));
expect(data[1].physicalDeltaX, equals(0));
expect(data[1].physicalDeltaY, equals(0));
expect(data[2].change, equals(ui.PointerChange.add));
expect(data[2].synthesized, isTrue);
expect(data[2].device, equals(3));
expect(data[2].physicalX, equals(200 * dpi));
expect(data[2].physicalY, equals(201 * dpi));
expect(data[3].change, equals(ui.PointerChange.down));
expect(data[3].device, equals(3));
expect(data[3].buttons, equals(1));
expect(data[3].physicalX, equals(200 * dpi));
expect(data[3].physicalY, equals(201 * dpi));
expect(data[3].physicalDeltaX, equals(0));
expect(data[3].physicalDeltaY, equals(0));
packets.clear();
// Two pointers move
context.multiTouchMove(const <_TouchDetails>[
_TouchDetails(pointer: 3, clientX: 300, clientY: 302),
_TouchDetails(pointer: 2, clientX: 400, clientY: 402),
]).forEach(glassPane.dispatchEvent);
if (context.runtimeType == _PointerEventContext) {
expect(packets.length, 2);
expect(packets[0].data.length, 1);
expect(packets[1].data.length, 1);
} else if (context.runtimeType == _TouchEventContext) {
expect(packets.length, 1);
expect(packets[0].data.length, 2);
} else {
assert(false, 'Unexpected context type ${context.runtimeType}');
}
data = _allPointerData(packets);
expect(data, hasLength(2));
expect(data[0].change, equals(ui.PointerChange.move));
expect(data[0].device, equals(3));
expect(data[0].buttons, equals(1));
expect(data[0].physicalX, equals(300 * dpi));
expect(data[0].physicalY, equals(302 * dpi));
expect(data[0].physicalDeltaX, equals(100 * dpi));
expect(data[0].physicalDeltaY, equals(101 * dpi));
expect(data[1].change, equals(ui.PointerChange.move));
expect(data[1].device, equals(2));
expect(data[1].buttons, equals(1));
expect(data[1].physicalX, equals(400 * dpi));
expect(data[1].physicalY, equals(402 * dpi));
expect(data[1].physicalDeltaX, equals(300 * dpi));
expect(data[1].physicalDeltaY, equals(301 * dpi));
packets.clear();
// One pointer up
context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 3, clientX: 300, clientY: 302),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].device, equals(3));
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[0].physicalX, equals(300 * dpi));
expect(packets[0].data[0].physicalY, equals(302 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0));
expect(packets[0].data[0].physicalDeltaY, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.remove));
expect(packets[0].data[1].device, equals(3));
expect(packets[0].data[1].buttons, equals(0));
expect(packets[0].data[1].physicalX, equals(300 * dpi));
expect(packets[0].data[1].physicalY, equals(302 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0));
expect(packets[0].data[1].physicalDeltaY, equals(0));
packets.clear();
// Another pointer up
context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 2, clientX: 400, clientY: 402),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].device, equals(2));
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[0].physicalX, equals(400 * dpi));
expect(packets[0].data[0].physicalY, equals(402 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0));
expect(packets[0].data[0].physicalDeltaY, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.remove));
expect(packets[0].data[1].device, equals(2));
expect(packets[0].data[1].buttons, equals(0));
expect(packets[0].data[1].physicalX, equals(400 * dpi));
expect(packets[0].data[1].physicalY, equals(402 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0));
expect(packets[0].data[1].physicalDeltaY, equals(0));
packets.clear();
// Again two pointers down (reuse pointer ID)
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 3, clientX: 500, clientY: 501),
_TouchDetails(pointer: 2, clientX: 600, clientY: 601),
]).forEach(glassPane.dispatchEvent);
if (context.runtimeType == _PointerEventContext) {
expect(packets.length, 2);
expect(packets[0].data.length, 2);
expect(packets[1].data.length, 2);
} else if (context.runtimeType == _TouchEventContext) {
expect(packets.length, 1);
expect(packets[0].data.length, 4);
} else {
assert(false, 'Unexpected context type ${context.runtimeType}');
}
data = _allPointerData(packets);
expect(data, hasLength(4));
expect(data[0].change, equals(ui.PointerChange.add));
expect(data[0].synthesized, isTrue);
expect(data[0].device, equals(3));
expect(data[0].physicalX, equals(500 * dpi));
expect(data[0].physicalY, equals(501 * dpi));
expect(data[1].change, equals(ui.PointerChange.down));
expect(data[1].device, equals(3));
expect(data[1].buttons, equals(1));
expect(data[1].physicalX, equals(500 * dpi));
expect(data[1].physicalY, equals(501 * dpi));
expect(data[1].physicalDeltaX, equals(0));
expect(data[1].physicalDeltaY, equals(0));
expect(data[2].change, equals(ui.PointerChange.add));
expect(data[2].synthesized, isTrue);
expect(data[2].device, equals(2));
expect(data[2].physicalX, equals(600 * dpi));
expect(data[2].physicalY, equals(601 * dpi));
expect(data[3].change, equals(ui.PointerChange.down));
expect(data[3].device, equals(2));
expect(data[3].buttons, equals(1));
expect(data[3].physicalX, equals(600 * dpi));
expect(data[3].physicalY, equals(601 * dpi));
expect(data[3].physicalDeltaX, equals(0));
expect(data[3].physicalDeltaY, equals(0));
packets.clear();
},
);
_testEach<_MultiPointerEventMixin>(
<_MultiPointerEventMixin>[
if (!isIosSafari) _PointerEventContext(),
if (!isIosSafari) _TouchEventContext(),
],
'correctly parses cancel event',
(_MultiPointerEventMixin context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
// Two pointers down
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 2, clientX: 100, clientY: 101),
_TouchDetails(pointer: 3, clientX: 200, clientY: 201),
]).forEach(glassPane.dispatchEvent);
packets.clear(); // Down event is tested in other tests.
// One pointer cancel
context.multiTouchCancel(const <_TouchDetails>[
_TouchDetails(pointer: 3, clientX: 300, clientY: 302),
]).forEach(glassPane.dispatchEvent);
expect(packets.length, 1);
expect(packets[0].data.length, 2);
expect(packets[0].data[0].change, equals(ui.PointerChange.cancel));
expect(packets[0].data[0].device, equals(3));
expect(packets[0].data[0].buttons, equals(0));
expect(packets[0].data[0].physicalX, equals(200 * dpi));
expect(packets[0].data[0].physicalY, equals(201 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(0));
expect(packets[0].data[0].physicalDeltaY, equals(0));
expect(packets[0].data[1].change, equals(ui.PointerChange.remove));
expect(packets[0].data[1].device, equals(3));
expect(packets[0].data[1].buttons, equals(0));
expect(packets[0].data[1].physicalX, equals(200 * dpi));
expect(packets[0].data[1].physicalY, equals(201 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0));
expect(packets[0].data[1].physicalDeltaY, equals(0));
packets.clear();
},
);
// POINTER ADAPTER
_testEach<_PointerEventContext>(
<_PointerEventContext>[
if (!isIosSafari) _PointerEventContext(),
],
'does not synthesize pointer up if from different device',
(_PointerEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 1, clientX: 100, clientY: 101),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
// 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, isTrue);
expect(packets[0].data[0].device, equals(1));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].device, equals(1));
packets.clear();
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 2, clientX: 200, clientY: 202),
]).forEach(glassPane.dispatchEvent);
// An add will be synthesized.
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].device, equals(2));
expect(packets[0].data[1].change, equals(ui.PointerChange.down));
expect(packets[0].data[1].device, equals(2));
packets.clear();
},
);
_testEach<_PointerEventContext>(
<_PointerEventContext>[
if (!isIosSafari) _PointerEventContext(),
],
'ignores pointer up or pointer cancel events for unknown device',
(_PointerEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 23, clientX: 200, clientY: 202),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(0));
context.multiTouchCancel(const <_TouchDetails>[
_TouchDetails(pointer: 24, clientX: 200, clientY: 202),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(0));
},
);
_testEach<_PointerEventContext>(
<_PointerEventContext>[
_PointerEventContext(),
],
'handles random pointer id on up events',
(_PointerEventContext context) {
PointerBinding.instance!.debugOverrideDetector(context);
// This happens with pens that are simulated with mouse events
// (e.g. Wacom). It sends events with the pointer type "mouse", and
// assigns a random pointer ID to each event.
//
// For more info, see: https://github.com/flutter/flutter/issues/75559
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
glassPane.dispatchEvent(context.mouseDown(
pointerId: 12,
button: 0,
buttons: 1,
clientX: 10.0,
clientY: 10.0,
));
expect(packets, hasLength(1));
expect(packets.single.data, hasLength(2));
expect(packets.single.data[0].change, equals(ui.PointerChange.add));
expect(packets.single.data[0].synthesized, isTrue);
expect(packets.single.data[1].change, equals(ui.PointerChange.down));
packets.clear();
expect(
() {
glassPane.dispatchEvent(context.mouseUp(
pointerId: 41,
button: 0,
buttons: 0,
clientX: 10.0,
clientY: 10.0,
));
},
returnsNormally,
);
expect(packets, hasLength(1));
expect(packets.single.data, hasLength(1));
expect(packets.single.data[0].change, equals(ui.PointerChange.up));
},
);
// TOUCH ADAPTER
_testEach<_TouchEventContext>(
<_TouchEventContext>[
if (!isIosSafari) _TouchEventContext(),
],
'does calculate delta and pointer identifier correctly',
(_TouchEventContext context) {
// Mouse and Pointer are in another test since these tests can involve hovering
PointerBinding.instance!.debugOverrideDetector(context);
final List<ui.PointerDataPacket> packets = <ui.PointerDataPacket>[];
ui.window.onPointerDataPacket = (ui.PointerDataPacket packet) {
packets.add(packet);
};
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 1, clientX: 20, clientY: 20),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(20.0 * dpi));
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.down));
expect(packets[0].data[1].pointerIdentifier, equals(1));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(20.0 * dpi));
expect(packets[0].data[1].physicalY, equals(20.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
packets.clear();
context.multiTouchMove(const <_TouchDetails>[
_TouchDetails(pointer: 1, clientX: 40, clientY: 30),
]).forEach(glassPane.dispatchEvent);
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].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40.0 * dpi));
expect(packets[0].data[0].physicalY, equals(30.0 * dpi));
expect(packets[0].data[0].physicalDeltaX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalDeltaY, equals(10.0 * dpi));
packets.clear();
context.multiTouchUp(const <_TouchDetails>[
_TouchDetails(pointer: 1, clientX: 40, clientY: 30),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.up));
expect(packets[0].data[0].pointerIdentifier, equals(1));
expect(packets[0].data[0].synthesized, isFalse);
expect(packets[0].data[0].physicalX, equals(40.0 * dpi));
expect(packets[0].data[0].physicalY, equals(30.0 * dpi));
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.remove));
expect(packets[0].data[1].pointerIdentifier, equals(1));
expect(packets[0].data[1].synthesized, isTrue);
expect(packets[0].data[1].physicalX, equals(40.0 * dpi));
expect(packets[0].data[1].physicalY, equals(30.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
packets.clear();
context.multiTouchDown(const <_TouchDetails>[
_TouchDetails(pointer: 2, clientX: 20, clientY: 10),
]).forEach(glassPane.dispatchEvent);
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.add));
expect(packets[0].data[0].pointerIdentifier, equals(2));
expect(packets[0].data[0].synthesized, isTrue);
expect(packets[0].data[0].physicalX, equals(20.0 * dpi));
expect(packets[0].data[0].physicalY, equals(10.0 * dpi));
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.down));
expect(packets[0].data[1].pointerIdentifier, equals(2));
expect(packets[0].data[1].synthesized, isFalse);
expect(packets[0].data[1].physicalX, equals(20.0 * dpi));
expect(packets[0].data[1].physicalY, equals(10.0 * dpi));
expect(packets[0].data[1].physicalDeltaX, equals(0.0));
expect(packets[0].data[1].physicalDeltaY, equals(0.0));
packets.clear();
},
);
}
class MockSafariPointerEventWorkaround implements SafariPointerEventWorkaround {
bool workAroundInvoked = false;
@override
void workAroundMissingPointerEvents() {
workAroundInvoked = true;
}
}
abstract class _BasicEventContext implements PointerSupportDetector {
String get name;
bool get isSupported;
// Accepted modifier keys are 'Alt', 'Control', 'Meta' and 'Shift'.
// https://www.w3.org/TR/uievents-key/#keys-modifier defines more modifiers,
// but only the four main modifiers could be set from MouseEvent, PointerEvent
// and TouchEvent constructors.
bool altPressed = false;
bool ctrlPressed = false;
bool metaPressed = false;
bool shiftPressed = false;
// Generate an event that is:
//
// * For mouse, a left click
// * For touch, a touch down
DomEvent primaryDown({double clientX, double clientY});
// Generate an event that is:
//
// * For mouse, a drag with LMB down
// * For touch, a touch drag
DomEvent primaryMove({double clientX, double clientY});
// Generate an event that is:
//
// * For mouse, release LMB
// * For touch, a touch up
DomEvent primaryUp({double clientX, double clientY});
void pressAllModifiers() {
altPressed = true;
ctrlPressed = true;
metaPressed = true;
shiftPressed = true;
}
void unpressAllModifiers() {
altPressed = false;
ctrlPressed = false;
metaPressed = false;
shiftPressed = false;
}
}
mixin _ButtonedEventMixin on _BasicEventContext {
// Generate an event that is a mouse down with the specific buttons.
DomEvent mouseDown(
{double? clientX, double? clientY, int? button, int? buttons});
// Generate an event that is a mouse drag with the specific buttons, or button
// changes during the drag.
//
// If there is no button change, assign `button` with _kNoButtonChange.
DomEvent mouseMove(
{double? clientX,
double? clientY,
required int button,
required int buttons});
// Generate an event that moves the mouse outside of the tracked area.
DomEvent mouseLeave({double? clientX, double? clientY, required int buttons});
// Generate an event that releases all mouse buttons.
DomEvent mouseUp({double? clientX, double? clientY, int? button, int? buttons});
DomEvent hover({double? clientX, double? clientY}) {
return mouseMove(
buttons: 0,
button: _kNoButtonChange,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent primaryDown({double? clientX, double? clientY}) {
return mouseDown(
buttons: 1,
button: 0,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent primaryMove({double? clientX, double? clientY}) {
return mouseMove(
buttons: 1,
button: _kNoButtonChange,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent primaryUp({double? clientX, double? clientY}) {
return mouseUp(
button: 0,
clientX: clientX,
clientY: clientY,
);
}
DomEvent wheel({
required int? buttons,
required double? clientX,
required double? clientY,
required double? deltaX,
required double? deltaY,
double? wheelDeltaX,
double? wheelDeltaY,
int? timeStamp,
bool ctrlKey = false,
}) {
final DomEvent event = createDomWheelEvent('wheel', <String, Object>{
if (buttons != null) 'buttons': buttons,
if (clientX != null) 'clientX': clientX,
if (clientY != null) 'clientY': clientY,
if (deltaX != null) 'deltaX': deltaX,
if (deltaY != null) 'deltaY': deltaY,
if (wheelDeltaX != null) 'wheelDeltaX': wheelDeltaX,
if (wheelDeltaY != null) 'wheelDeltaY': wheelDeltaY,
'ctrlKey': ctrlKey,
});
// timeStamp can't be set in the constructor, need to override the getter.
if (timeStamp != null) {
js_util.callMethod(
objectConstructor,
'defineProperty',
<dynamic>[
event,
'timeStamp',
js_util.jsify(<String, dynamic>{
'value': timeStamp,
'configurable': true
})
]
);
}
return event;
}
}
class _TouchDetails {
const _TouchDetails({this.pointer, this.clientX, this.clientY});
final int? pointer;
final double? clientX;
final double? clientY;
}
mixin _MultiPointerEventMixin on _BasicEventContext {
List<DomEvent> multiTouchDown(List<_TouchDetails> touches);
List<DomEvent> multiTouchMove(List<_TouchDetails> touches);
List<DomEvent> multiTouchUp(List<_TouchDetails> touches);
List<DomEvent> multiTouchCancel(List<_TouchDetails> touches);
@override
DomEvent primaryDown({double? clientX, double? clientY}) {
return multiTouchDown(<_TouchDetails>[
_TouchDetails(
pointer: 1,
clientX: clientX,
clientY: clientY,
),
])[0];
}
@override
DomEvent primaryMove({double? clientX, double? clientY}) {
return multiTouchMove(<_TouchDetails>[
_TouchDetails(
pointer: 1,
clientX: clientX,
clientY: clientY,
),
])[0];
}
@override
DomEvent primaryUp({double? clientX, double? clientY}) {
return multiTouchUp(<_TouchDetails>[
_TouchDetails(
pointer: 1,
clientX: clientX,
clientY: clientY,
),
])[0];
}
}
// A test context for `_TouchAdapter`, including its name, PointerSupportDetector
// to override, and how to generate events.
class _TouchEventContext extends _BasicEventContext
with _MultiPointerEventMixin
implements PointerSupportDetector {
_TouchEventContext() : _target = domDocument.createElement('div');
@override
String get name => 'TouchAdapter';
@override
bool get isSupported => _defaultSupportDetector.hasTouchEvents;
@override
bool get hasPointerEvents => false;
@override
bool get hasTouchEvents => true;
@override
bool get hasMouseEvents => false;
final DomEventTarget _target;
DomTouch _createTouch({
int? identifier,
double? clientX,
double? clientY,
}) {
return createDomTouch(<String, dynamic>{
'identifier': identifier,
'clientX': clientX,
'clientY': clientY,
'target': _target,
});
}
DomTouchEvent _createTouchEvent(
String eventType, List<_TouchDetails> touches) {
return createDomTouchEvent(
eventType,
<String, dynamic>{
'changedTouches': touches
.map(
(_TouchDetails details) => _createTouch(
identifier: details.pointer,
clientX: details.clientX,
clientY: details.clientY,
),
)
.toList(),
'altKey': altPressed,
'ctrlKey': ctrlPressed,
'metaKey': metaPressed,
'shiftKey': shiftPressed,
},
);
}
@override
List<DomEvent> multiTouchDown(List<_TouchDetails> touches) {
return <DomEvent>[_createTouchEvent('touchstart', touches)];
}
@override
List<DomEvent> multiTouchMove(List<_TouchDetails> touches) {
return <DomEvent>[_createTouchEvent('touchmove', touches)];
}
@override
List<DomEvent> multiTouchUp(List<_TouchDetails> touches) {
return <DomEvent>[_createTouchEvent('touchend', touches)];
}
@override
List<DomEvent> multiTouchCancel(List<_TouchDetails> touches) {
return <DomEvent>[_createTouchEvent('touchcancel', touches)];
}
}
// A test context for `_MouseAdapter`, including its name, PointerSupportDetector
// to override, and how to generate events.
//
// For the difference between MouseEvent and PointerEvent, see _MouseAdapter.
class _MouseEventContext extends _BasicEventContext
with _ButtonedEventMixin
implements PointerSupportDetector {
@override
String get name => 'MouseAdapter';
@override
bool get isSupported => _defaultSupportDetector.hasMouseEvents;
@override
bool get hasPointerEvents => false;
@override
bool get hasTouchEvents => false;
@override
bool get hasMouseEvents => true;
@override
DomEvent mouseDown({
double? clientX,
double? clientY,
int? button,
int? buttons,
}) {
return _createMouseEvent(
'mousedown',
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent mouseMove({
double? clientX,
double? clientY,
required int button,
required int buttons,
}) {
final bool hasButtonChange = button != _kNoButtonChange;
final bool changeIsButtonDown =
hasButtonChange && (buttons & convertButtonToButtons(button)) != 0;
final String adjustedType = !hasButtonChange
? 'mousemove'
: changeIsButtonDown
? 'mousedown'
: 'mouseup';
final int adjustedButton = hasButtonChange ? button : 0;
return _createMouseEvent(
adjustedType,
buttons: buttons,
button: adjustedButton,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent mouseLeave({
double? clientX,
double? clientY,
required int buttons,
}) {
return _createMouseEvent(
'mouseleave',
buttons: buttons,
button: 0,
clientX: clientX,
clientY: clientY,
);
}
@override
DomEvent mouseUp({
double? clientX,
double? clientY,
int? button,
int? buttons,
}) {
return _createMouseEvent(
'mouseup',
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
);
}
DomMouseEvent _createMouseEvent(
String type, {
int? buttons,
int? button,
double? clientX,
double? clientY,
}) {
return createDomMouseEvent(type, <String, Object>{
if (buttons != null) 'buttons': buttons,
if (button != null) 'button': button,
if (clientX != null) 'clientX': clientX,
if (clientY != null) 'clientY': clientY,
'bubbles': true,
'altKey': altPressed,
'ctrlKey': ctrlPressed,
'metaKey': metaPressed,
'shiftKey': shiftPressed,
});
}
}
// A test context for `_PointerAdapter`, including its name, PointerSupportDetector
// to override, and how to generate events.
//
// For the difference between MouseEvent and PointerEvent, see _MouseAdapter.
class _PointerEventContext extends _BasicEventContext
with _ButtonedEventMixin
implements PointerSupportDetector, _MultiPointerEventMixin {
@override
String get name => 'PointerAdapter';
@override
bool get isSupported => _defaultSupportDetector.hasPointerEvents;
@override
bool get hasPointerEvents => true;
@override
bool get hasTouchEvents => false;
@override
bool get hasMouseEvents => false;
@override
List<DomEvent> multiTouchDown(List<_TouchDetails> touches) {
return touches
.map((_TouchDetails details) => _downWithFullDetails(
pointer: details.pointer,
buttons: 1,
button: 0,
clientX: details.clientX,
clientY: details.clientY,
pointerType: 'touch',
))
.toList();
}
@override
DomEvent mouseDown({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointerId = 1,
}) {
return _downWithFullDetails(
pointer: pointerId,
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
pointerType: 'mouse',
);
}
DomEvent _downWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return createDomPointerEvent('pointerdown', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
'altKey': altPressed,
'ctrlKey': ctrlPressed,
'metaKey': metaPressed,
'shiftKey': shiftPressed,
});
}
@override
List<DomEvent> multiTouchMove(List<_TouchDetails> touches) {
return touches
.map((_TouchDetails details) => _moveWithFullDetails(
pointer: details.pointer,
buttons: 1,
button: _kNoButtonChange,
clientX: details.clientX,
clientY: details.clientY,
pointerType: 'touch',
))
.toList();
}
@override
DomEvent mouseMove({
double? clientX,
double? clientY,
required int button,
required int buttons,
int pointerId = 1,
}) {
return _moveWithFullDetails(
pointer: pointerId,
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
pointerType: 'mouse',
);
}
DomEvent _moveWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return createDomPointerEvent('pointermove', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
DomEvent mouseLeave({
double? clientX,
double? clientY,
required int buttons,
int pointerId = 1,
}) {
return _leaveWithFullDetails(
pointer: pointerId,
buttons: buttons,
button: 0,
clientX: clientX,
clientY: clientY,
pointerType: 'mouse',
);
}
DomEvent _leaveWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return createDomPointerEvent('pointerleave', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
List<DomEvent> multiTouchUp(List<_TouchDetails> touches) {
return touches
.map((_TouchDetails details) => _upWithFullDetails(
pointer: details.pointer,
button: 0,
clientX: details.clientX,
clientY: details.clientY,
pointerType: 'touch',
))
.toList();
}
@override
DomEvent mouseUp({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointerId = 1,
}) {
return _upWithFullDetails(
pointer: pointerId,
button: button,
buttons: buttons,
clientX: clientX,
clientY: clientY,
pointerType: 'mouse',
);
}
DomEvent _upWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return createDomPointerEvent('pointerup', <String, dynamic>{
'bubbles': true,
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
List<DomEvent> multiTouchCancel(List<_TouchDetails> touches) {
return touches
.map((_TouchDetails details) =>
createDomPointerEvent('pointercancel', <String, dynamic>{
'pointerId': details.pointer,
'button': 0,
'buttons': 0,
'clientX': 0,
'clientY': 0,
'pointerType': 'touch',
}))
.toList();
}
}