blob: a8e27fe9ec6ded0cef797abf94345f8509cda41c [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:html' as html;
import 'dart:js_util' as js_util;
import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine.dart' show domRenderer, window;
import 'package:ui/src/engine/browser_detection.dart';
import 'package:ui/src/engine/dom_renderer.dart';
import 'package:ui/src/engine/pointer_binding.dart';
import 'package:ui/ui.dart' as ui;
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,
) {
for (final T context in contexts) {
if (context.isSupported) {
test('${context.name} $description', () {
body(context);
});
}
}
}
/// Some methods in this class are skipped for iOS-Safari.
/// TODO: https://github.com/flutter/flutter/issues/60033
bool get isIosSafari => browserEngine == BrowserEngine.webkit &&
operatingSystem == OperatingSystem.iOs;
void main() {
internalBootstrapBrowserTest(() => testMain);
}
void testMain() {
final html.Element glassPane = domRenderer.glassPaneElement!;
double dpi = 1.0;
setUp(() {
ensureDomRendererInitialized();
ui.window.onPointerDataPacket = null;
dpi = window.devicePixelRatio;
});
test('_PointerEventContext generates expected events', () {
if (!_PointerEventContext().isSupported) {
return;
}
html.PointerEvent expectCorrectType(html.Event e) {
expect(e.runtimeType, equals(html.PointerEvent));
return e as html.PointerEvent;
}
List<html.PointerEvent> expectCorrectTypes(List<html.Event> events) {
return events.map(expectCorrectType).toList();
}
final _PointerEventContext context = _PointerEventContext();
html.PointerEvent event;
List<html.PointerEvent> 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.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));
});
test('_TouchEventContext generates expected events', () {
if (!_TouchEventContext().isSupported) {
return;
}
html.TouchEvent expectCorrectType(html.Event e) {
expect(e.runtimeType, equals(html.TouchEvent));
return e as html.TouchEvent;
}
List<html.TouchEvent> expectCorrectTypes(List<html.Event> events) {
return events.map(expectCorrectType).toList();
}
final _TouchEventContext context = _TouchEventContext();
html.TouchEvent event;
List<html.TouchEvent> events;
event = expectCorrectType(context.primaryDown(clientX: 100, clientY: 101));
expect(event.type, equals('touchstart'));
expect(event.changedTouches!.length, equals(1));
expect(event.changedTouches![0].identifier, equals(1));
expect(event.changedTouches![0].client.x, equals(100));
expect(event.changedTouches![0].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![0].identifier, equals(100));
expect(events[0].changedTouches![0].client.x, equals(120));
expect(events[0].changedTouches![0].client.y, equals(121));
expect(events[0].changedTouches![1].identifier, equals(101));
expect(events[0].changedTouches![1].client.x, equals(122));
expect(events[0].changedTouches![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![0].identifier, equals(1));
expect(event.changedTouches![0].client.x, equals(200));
expect(event.changedTouches![0].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![0].identifier, equals(102));
expect(events[0].changedTouches![0].client.x, equals(220));
expect(events[0].changedTouches![0].client.y, equals(221));
expect(events[0].changedTouches![1].identifier, equals(103));
expect(events[0].changedTouches![1].client.x, equals(222));
expect(events[0].changedTouches![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![0].identifier, equals(1));
expect(event.changedTouches![0].client.x, equals(300));
expect(event.changedTouches![0].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![0].identifier, equals(104));
expect(events[0].changedTouches![0].client.x, equals(320));
expect(events[0].changedTouches![0].client.y, equals(321));
expect(events[0].changedTouches![1].identifier, equals(105));
expect(events[0].changedTouches![1].client.x, equals(322));
expect(events[0].changedTouches![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![0].identifier, equals(104));
expect(events[0].changedTouches![0].client.x, equals(320));
expect(events[0].changedTouches![0].client.y, equals(321));
expect(events[0].changedTouches![1].identifier, equals(105));
expect(events[0].changedTouches![1].client.x, equals(322));
expect(events[0].changedTouches![1].client.y, equals(323));
});
test('_MouseEventContext generates expected events', () {
if (!_MouseEventContext().isSupported) {
return;
}
html.MouseEvent expectCorrectType(html.Event e) {
expect(e.runtimeType, equals(html.MouseEvent));
return e as html.MouseEvent;
}
final _MouseEventContext context = _MouseEventContext();
html.MouseEvent 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));
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.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));
});
// 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<_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 html.Element semanticsPlaceholder =
html.Element.tag('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.
html.window.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();
},
);
// 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()
],
'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 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();
},
);
_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.
html.window.dispatchEvent(context.primaryMove(
clientX: 900.0,
clientY: 1900.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(1));
expect(packets[0].data[0].change, equals(ui.PointerChange.move));
expect(packets[0].data[0].physicalX,
equals(900.0 * dpi));
expect(packets[0].data[0].physicalY, equals(1900.0 * dpi));
packets.clear();
// Release outside the glasspane.
html.window.dispatchEvent(context.primaryUp(
clientX: 1000.0,
clientY: 2000.0,
));
expect(packets, hasLength(1));
expect(packets[0].data, hasLength(2));
expect(packets[0].data[0].change, equals(ui.PointerChange.move));
expect(packets[0].data[0].physicalX, equals(1000.0 * 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>[
_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();
},
);
}
abstract class _BasicEventContext implements PointerSupportDetector {
String get name;
bool get isSupported;
// Generate an event that is:
//
// * For mouse, a left click
// * For touch, a touch down
html.Event primaryDown({double clientX, double clientY});
// Generate an event that is:
//
// * For mouse, a drag with LMB down
// * For touch, a touch drag
html.Event primaryMove({double clientX, double clientY});
// Generate an event that is:
//
// * For mouse, release LMB
// * For touch, a touch up
html.Event primaryUp({double clientX, double clientY});
}
mixin _ButtonedEventMixin on _BasicEventContext {
// Generate an event that is a mouse down with the specific buttons.
html.Event 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.
html.Event mouseMove(
{double? clientX, double? clientY, required int button, required int buttons});
// Generate an event that releases all mouse buttons.
html.Event mouseUp({double? clientX, double? clientY, int? button, int? buttons});
html.Event hover({double? clientX, double? clientY}) {
return mouseMove(
buttons: 0,
button: _kNoButtonChange,
clientX: clientX,
clientY: clientY,
);
}
@override
html.Event primaryDown({double? clientX, double? clientY}) {
return mouseDown(
buttons: 1,
button: 0,
clientX: clientX,
clientY: clientY,
);
}
@override
html.Event primaryMove({double? clientX, double? clientY}) {
return mouseMove(
buttons: 1,
button: _kNoButtonChange,
clientX: clientX,
clientY: clientY,
);
}
@override
html.Event primaryUp({double? clientX, double? clientY}) {
return mouseUp(
button: 0,
clientX: clientX,
clientY: clientY,
);
}
html.Event wheel({
required int? buttons,
required double? clientX,
required double? clientY,
required double? deltaX,
required double? deltaY,
}) {
final Function jsWheelEvent = js_util.getProperty(html.window, 'WheelEvent');
final List<dynamic> eventArgs = <dynamic>[
'wheel',
<String, dynamic>{
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'deltaX': deltaX,
'deltaY': deltaY,
}
];
return js_util.callConstructor(jsWheelEvent, js_util.jsify(eventArgs));
}
}
class _TouchDetails {
const _TouchDetails({this.pointer, this.clientX, this.clientY});
final int? pointer;
final double? clientX;
final double? clientY;
}
mixin _MultiPointerEventMixin on _BasicEventContext {
List<html.Event> multiTouchDown(List<_TouchDetails> touches);
List<html.Event> multiTouchMove(List<_TouchDetails> touches);
List<html.Event> multiTouchUp(List<_TouchDetails> touches);
List<html.Event> multiTouchCancel(List<_TouchDetails> touches);
@override
html.Event primaryDown({double? clientX, double? clientY}) {
return multiTouchDown(<_TouchDetails>[
_TouchDetails(
pointer: 1,
clientX: clientX,
clientY: clientY,
),
])[0];
}
@override
html.Event primaryMove({double? clientX, double? clientY}) {
return multiTouchMove(<_TouchDetails>[
_TouchDetails(
pointer: 1,
clientX: clientX,
clientY: clientY,
),
])[0];
}
@override
html.Event 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 = html.document.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;
html.EventTarget _target;
html.Touch _createTouch({
int? identifier,
double? clientX,
double? clientY,
}) {
return html.Touch(<String, dynamic>{
'identifier': identifier,
'clientX': clientX,
'clientY': clientY,
'target': _target,
});
}
html.TouchEvent _createTouchEvent(
String eventType, List<_TouchDetails> touches) {
return html.TouchEvent(
eventType,
<String, dynamic>{
'changedTouches': touches
.map(
(_TouchDetails details) => _createTouch(
identifier: details.pointer,
clientX: details.clientX,
clientY: details.clientY,
),
)
.toList(),
},
);
}
@override
List<html.Event> multiTouchDown(List<_TouchDetails> touches) {
return <html.Event>[_createTouchEvent('touchstart', touches)];
}
@override
List<html.Event> multiTouchMove(List<_TouchDetails> touches) {
return <html.Event>[_createTouchEvent('touchmove', touches)];
}
@override
List<html.Event> multiTouchUp(List<_TouchDetails> touches) {
return <html.Event>[_createTouchEvent('touchend', touches)];
}
@override
List<html.Event> multiTouchCancel(List<_TouchDetails> touches) {
return <html.Event>[_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
html.Event mouseDown({
double? clientX,
double? clientY,
int? button,
int? buttons,
}) {
return _createMouseEvent(
'mousedown',
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
);
}
@override
html.Event 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
html.Event mouseUp({
double? clientX,
double? clientY,
int? button,
int? buttons,
}) {
return _createMouseEvent(
'mouseup',
buttons: buttons,
button: button,
clientX: clientX,
clientY: clientY,
);
}
html.MouseEvent _createMouseEvent(
String type, {
int? buttons,
int? button,
double? clientX,
double? clientY,
}) {
final Function jsMouseEvent =
js_util.getProperty(html.window, 'MouseEvent');
final List<dynamic> eventArgs = <dynamic>[
type,
<String, dynamic>{
'buttons': buttons,
'button': button,
'clientX': clientX,
'clientY': clientY,
}
];
return js_util.callConstructor(jsMouseEvent, js_util.jsify(eventArgs));
}
}
// 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<html.Event> 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
html.Event 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',
);
}
html.Event _downWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return html.PointerEvent('pointerdown', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
List<html.Event> 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
html.Event 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',
);
}
html.Event _moveWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return html.PointerEvent('pointermove', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
List<html.Event> 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
html.Event 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',
);
}
html.Event _upWithFullDetails({
double? clientX,
double? clientY,
int? button,
int? buttons,
int? pointer,
String? pointerType,
}) {
return html.PointerEvent('pointerup', <String, dynamic>{
'pointerId': pointer,
'button': button,
'buttons': buttons,
'clientX': clientX,
'clientY': clientY,
'pointerType': pointerType,
});
}
@override
List<html.Event> multiTouchCancel(List<_TouchDetails> touches) {
return touches
.map((_TouchDetails details) =>
html.PointerEvent('pointercancel', <String, dynamic>{
'pointerId': details.pointer,
'button': 0,
'buttons': 0,
'clientX': 0,
'clientY': 0,
'pointerType': 'touch',
}))
.toList();
}
}