Update dashboard, kb, memory +4 more (+28 ~18 -1)
This commit is contained in:
742
node_modules/puppeteer-core/src/bidi/Input.ts
generated
vendored
Normal file
742
node_modules/puppeteer-core/src/bidi/Input.ts
generated
vendored
Normal file
@@ -0,0 +1,742 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2017 Google Inc.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as Bidi from 'webdriver-bidi-protocol';
|
||||
|
||||
import type {Point} from '../api/ElementHandle.js';
|
||||
import {
|
||||
Keyboard,
|
||||
Mouse,
|
||||
MouseButton,
|
||||
Touchscreen,
|
||||
type TouchHandle,
|
||||
type KeyboardTypeOptions,
|
||||
type KeyDownOptions,
|
||||
type KeyPressOptions,
|
||||
type MouseClickOptions,
|
||||
type MouseMoveOptions,
|
||||
type MouseOptions,
|
||||
type MouseWheelOptions,
|
||||
} from '../api/Input.js';
|
||||
import {UnsupportedOperation} from '../common/Errors.js';
|
||||
import {TouchError} from '../common/Errors.js';
|
||||
import type {KeyInput} from '../common/USKeyboardLayout.js';
|
||||
|
||||
import type {BidiPage} from './Page.js';
|
||||
|
||||
const enum InputId {
|
||||
Mouse = '__puppeteer_mouse',
|
||||
Keyboard = '__puppeteer_keyboard',
|
||||
Wheel = '__puppeteer_wheel',
|
||||
Finger = '__puppeteer_finger',
|
||||
}
|
||||
|
||||
enum SourceActionsType {
|
||||
None = 'none',
|
||||
Key = 'key',
|
||||
Pointer = 'pointer',
|
||||
Wheel = 'wheel',
|
||||
}
|
||||
|
||||
enum ActionType {
|
||||
Pause = 'pause',
|
||||
KeyDown = 'keyDown',
|
||||
KeyUp = 'keyUp',
|
||||
PointerUp = 'pointerUp',
|
||||
PointerDown = 'pointerDown',
|
||||
PointerMove = 'pointerMove',
|
||||
Scroll = 'scroll',
|
||||
}
|
||||
|
||||
const getBidiKeyValue = (key: KeyInput) => {
|
||||
switch (key) {
|
||||
case '\r':
|
||||
case '\n':
|
||||
key = 'Enter';
|
||||
break;
|
||||
}
|
||||
// Measures the number of code points rather than UTF-16 code units.
|
||||
if ([...key].length === 1) {
|
||||
return key;
|
||||
}
|
||||
switch (key) {
|
||||
case 'Cancel':
|
||||
return '\uE001';
|
||||
case 'Help':
|
||||
return '\uE002';
|
||||
case 'Backspace':
|
||||
return '\uE003';
|
||||
case 'Tab':
|
||||
return '\uE004';
|
||||
case 'Clear':
|
||||
return '\uE005';
|
||||
case 'Enter':
|
||||
return '\uE007';
|
||||
case 'Shift':
|
||||
case 'ShiftLeft':
|
||||
return '\uE008';
|
||||
case 'Control':
|
||||
case 'ControlLeft':
|
||||
return '\uE009';
|
||||
case 'Alt':
|
||||
case 'AltLeft':
|
||||
return '\uE00A';
|
||||
case 'Pause':
|
||||
return '\uE00B';
|
||||
case 'Escape':
|
||||
return '\uE00C';
|
||||
case 'PageUp':
|
||||
return '\uE00E';
|
||||
case 'PageDown':
|
||||
return '\uE00F';
|
||||
case 'End':
|
||||
return '\uE010';
|
||||
case 'Home':
|
||||
return '\uE011';
|
||||
case 'ArrowLeft':
|
||||
return '\uE012';
|
||||
case 'ArrowUp':
|
||||
return '\uE013';
|
||||
case 'ArrowRight':
|
||||
return '\uE014';
|
||||
case 'ArrowDown':
|
||||
return '\uE015';
|
||||
case 'Insert':
|
||||
return '\uE016';
|
||||
case 'Delete':
|
||||
return '\uE017';
|
||||
case 'NumpadEqual':
|
||||
return '\uE019';
|
||||
case 'Numpad0':
|
||||
return '\uE01A';
|
||||
case 'Numpad1':
|
||||
return '\uE01B';
|
||||
case 'Numpad2':
|
||||
return '\uE01C';
|
||||
case 'Numpad3':
|
||||
return '\uE01D';
|
||||
case 'Numpad4':
|
||||
return '\uE01E';
|
||||
case 'Numpad5':
|
||||
return '\uE01F';
|
||||
case 'Numpad6':
|
||||
return '\uE020';
|
||||
case 'Numpad7':
|
||||
return '\uE021';
|
||||
case 'Numpad8':
|
||||
return '\uE022';
|
||||
case 'Numpad9':
|
||||
return '\uE023';
|
||||
case 'NumpadMultiply':
|
||||
return '\uE024';
|
||||
case 'NumpadAdd':
|
||||
return '\uE025';
|
||||
case 'NumpadSubtract':
|
||||
return '\uE027';
|
||||
case 'NumpadDecimal':
|
||||
return '\uE028';
|
||||
case 'NumpadDivide':
|
||||
return '\uE029';
|
||||
case 'F1':
|
||||
return '\uE031';
|
||||
case 'F2':
|
||||
return '\uE032';
|
||||
case 'F3':
|
||||
return '\uE033';
|
||||
case 'F4':
|
||||
return '\uE034';
|
||||
case 'F5':
|
||||
return '\uE035';
|
||||
case 'F6':
|
||||
return '\uE036';
|
||||
case 'F7':
|
||||
return '\uE037';
|
||||
case 'F8':
|
||||
return '\uE038';
|
||||
case 'F9':
|
||||
return '\uE039';
|
||||
case 'F10':
|
||||
return '\uE03A';
|
||||
case 'F11':
|
||||
return '\uE03B';
|
||||
case 'F12':
|
||||
return '\uE03C';
|
||||
case 'Meta':
|
||||
case 'MetaLeft':
|
||||
return '\uE03D';
|
||||
case 'ShiftRight':
|
||||
return '\uE050';
|
||||
case 'ControlRight':
|
||||
return '\uE051';
|
||||
case 'AltRight':
|
||||
return '\uE052';
|
||||
case 'MetaRight':
|
||||
return '\uE053';
|
||||
case 'Digit0':
|
||||
return '0';
|
||||
case 'Digit1':
|
||||
return '1';
|
||||
case 'Digit2':
|
||||
return '2';
|
||||
case 'Digit3':
|
||||
return '3';
|
||||
case 'Digit4':
|
||||
return '4';
|
||||
case 'Digit5':
|
||||
return '5';
|
||||
case 'Digit6':
|
||||
return '6';
|
||||
case 'Digit7':
|
||||
return '7';
|
||||
case 'Digit8':
|
||||
return '8';
|
||||
case 'Digit9':
|
||||
return '9';
|
||||
case 'KeyA':
|
||||
return 'a';
|
||||
case 'KeyB':
|
||||
return 'b';
|
||||
case 'KeyC':
|
||||
return 'c';
|
||||
case 'KeyD':
|
||||
return 'd';
|
||||
case 'KeyE':
|
||||
return 'e';
|
||||
case 'KeyF':
|
||||
return 'f';
|
||||
case 'KeyG':
|
||||
return 'g';
|
||||
case 'KeyH':
|
||||
return 'h';
|
||||
case 'KeyI':
|
||||
return 'i';
|
||||
case 'KeyJ':
|
||||
return 'j';
|
||||
case 'KeyK':
|
||||
return 'k';
|
||||
case 'KeyL':
|
||||
return 'l';
|
||||
case 'KeyM':
|
||||
return 'm';
|
||||
case 'KeyN':
|
||||
return 'n';
|
||||
case 'KeyO':
|
||||
return 'o';
|
||||
case 'KeyP':
|
||||
return 'p';
|
||||
case 'KeyQ':
|
||||
return 'q';
|
||||
case 'KeyR':
|
||||
return 'r';
|
||||
case 'KeyS':
|
||||
return 's';
|
||||
case 'KeyT':
|
||||
return 't';
|
||||
case 'KeyU':
|
||||
return 'u';
|
||||
case 'KeyV':
|
||||
return 'v';
|
||||
case 'KeyW':
|
||||
return 'w';
|
||||
case 'KeyX':
|
||||
return 'x';
|
||||
case 'KeyY':
|
||||
return 'y';
|
||||
case 'KeyZ':
|
||||
return 'z';
|
||||
case 'Semicolon':
|
||||
return ';';
|
||||
case 'Equal':
|
||||
return '=';
|
||||
case 'Comma':
|
||||
return ',';
|
||||
case 'Minus':
|
||||
return '-';
|
||||
case 'Period':
|
||||
return '.';
|
||||
case 'Slash':
|
||||
return '/';
|
||||
case 'Backquote':
|
||||
return '`';
|
||||
case 'BracketLeft':
|
||||
return '[';
|
||||
case 'Backslash':
|
||||
return '\\';
|
||||
case 'BracketRight':
|
||||
return ']';
|
||||
case 'Quote':
|
||||
return '"';
|
||||
default:
|
||||
throw new Error(`Unknown key: "${key}"`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class BidiKeyboard extends Keyboard {
|
||||
#page: BidiPage;
|
||||
|
||||
constructor(page: BidiPage) {
|
||||
super();
|
||||
this.#page = page;
|
||||
}
|
||||
|
||||
override async down(
|
||||
key: KeyInput,
|
||||
_options?: Readonly<KeyDownOptions>,
|
||||
): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Key,
|
||||
id: InputId.Keyboard,
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.KeyDown,
|
||||
value: getBidiKeyValue(key),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async up(key: KeyInput): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Key,
|
||||
id: InputId.Keyboard,
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.KeyUp,
|
||||
value: getBidiKeyValue(key),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async press(
|
||||
key: KeyInput,
|
||||
options: Readonly<KeyPressOptions> = {},
|
||||
): Promise<void> {
|
||||
const {delay = 0} = options;
|
||||
const actions: Bidi.Input.KeySourceAction[] = [
|
||||
{
|
||||
type: ActionType.KeyDown,
|
||||
value: getBidiKeyValue(key),
|
||||
},
|
||||
];
|
||||
if (delay > 0) {
|
||||
actions.push({
|
||||
type: ActionType.Pause,
|
||||
duration: delay,
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
type: ActionType.KeyUp,
|
||||
value: getBidiKeyValue(key),
|
||||
});
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Key,
|
||||
id: InputId.Keyboard,
|
||||
actions,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async type(
|
||||
text: string,
|
||||
options: Readonly<KeyboardTypeOptions> = {},
|
||||
): Promise<void> {
|
||||
const {delay = 0} = options;
|
||||
// This spread separates the characters into code points rather than UTF-16
|
||||
// code units.
|
||||
const values = ([...text] as KeyInput[]).map(getBidiKeyValue);
|
||||
const actions: Bidi.Input.KeySourceAction[] = [];
|
||||
if (delay <= 0) {
|
||||
for (const value of values) {
|
||||
actions.push(
|
||||
{
|
||||
type: ActionType.KeyDown,
|
||||
value,
|
||||
},
|
||||
{
|
||||
type: ActionType.KeyUp,
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for (const value of values) {
|
||||
actions.push(
|
||||
{
|
||||
type: ActionType.KeyDown,
|
||||
value,
|
||||
},
|
||||
{
|
||||
type: ActionType.Pause,
|
||||
duration: delay,
|
||||
},
|
||||
{
|
||||
type: ActionType.KeyUp,
|
||||
value,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Key,
|
||||
id: InputId.Keyboard,
|
||||
actions,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async sendCharacter(char: string): Promise<void> {
|
||||
// Measures the number of code points rather than UTF-16 code units.
|
||||
if ([...char].length > 1) {
|
||||
throw new Error('Cannot send more than 1 character.');
|
||||
}
|
||||
const frame = await this.#page.focusedFrame();
|
||||
await frame.isolatedRealm().evaluate(async char => {
|
||||
document.execCommand('insertText', false, char);
|
||||
}, char);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface BidiMouseClickOptions extends MouseClickOptions {
|
||||
origin?: Bidi.Input.Origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface BidiMouseMoveOptions extends MouseMoveOptions {
|
||||
origin?: Bidi.Input.Origin;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface BidiTouchMoveOptions {
|
||||
origin?: Bidi.Input.Origin;
|
||||
}
|
||||
|
||||
const getBidiButton = (button: MouseButton) => {
|
||||
switch (button) {
|
||||
case MouseButton.Left:
|
||||
return 0;
|
||||
case MouseButton.Middle:
|
||||
return 1;
|
||||
case MouseButton.Right:
|
||||
return 2;
|
||||
case MouseButton.Back:
|
||||
return 3;
|
||||
case MouseButton.Forward:
|
||||
return 4;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class BidiMouse extends Mouse {
|
||||
#page: BidiPage;
|
||||
#lastMovePoint: Point = {x: 0, y: 0};
|
||||
|
||||
constructor(page: BidiPage) {
|
||||
super();
|
||||
this.#page = page;
|
||||
}
|
||||
|
||||
override async reset(): Promise<void> {
|
||||
this.#lastMovePoint = {x: 0, y: 0};
|
||||
await this.#page.mainFrame().browsingContext.releaseActions();
|
||||
}
|
||||
|
||||
override async move(
|
||||
x: number,
|
||||
y: number,
|
||||
options: Readonly<BidiMouseMoveOptions> = {},
|
||||
): Promise<void> {
|
||||
const from = this.#lastMovePoint;
|
||||
const to = {
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
};
|
||||
const actions: Bidi.Input.PointerSourceAction[] = [];
|
||||
const steps = options.steps ?? 0;
|
||||
for (let i = 0; i < steps; ++i) {
|
||||
actions.push({
|
||||
type: ActionType.PointerMove,
|
||||
x: from.x + (to.x - from.x) * (i / steps),
|
||||
y: from.y + (to.y - from.y) * (i / steps),
|
||||
origin: options.origin,
|
||||
});
|
||||
}
|
||||
actions.push({
|
||||
type: ActionType.PointerMove,
|
||||
...to,
|
||||
origin: options.origin,
|
||||
});
|
||||
// https://w3c.github.io/webdriver-bidi/#command-input-performActions:~:text=input.PointerMoveAction%20%3D%20%7B%0A%20%20type%3A%20%22pointerMove%22%2C%0A%20%20x%3A%20js%2Dint%2C
|
||||
this.#lastMovePoint = to;
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: InputId.Mouse,
|
||||
actions,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async down(options: Readonly<MouseOptions> = {}): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: InputId.Mouse,
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerDown,
|
||||
button: getBidiButton(options.button ?? MouseButton.Left),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async up(options: Readonly<MouseOptions> = {}): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: InputId.Mouse,
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerUp,
|
||||
button: getBidiButton(options.button ?? MouseButton.Left),
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async click(
|
||||
x: number,
|
||||
y: number,
|
||||
options: Readonly<BidiMouseClickOptions> = {},
|
||||
): Promise<void> {
|
||||
const actions: Bidi.Input.PointerSourceAction[] = [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x: Math.round(x),
|
||||
y: Math.round(y),
|
||||
origin: options.origin,
|
||||
},
|
||||
];
|
||||
const pointerDownAction = {
|
||||
type: ActionType.PointerDown,
|
||||
button: getBidiButton(options.button ?? MouseButton.Left),
|
||||
} as const;
|
||||
const pointerUpAction = {
|
||||
type: ActionType.PointerUp,
|
||||
button: pointerDownAction.button,
|
||||
} as const;
|
||||
for (let i = 1; i < (options.count ?? 1); ++i) {
|
||||
actions.push(pointerDownAction, pointerUpAction);
|
||||
}
|
||||
actions.push(pointerDownAction);
|
||||
if (options.delay) {
|
||||
actions.push({
|
||||
type: ActionType.Pause,
|
||||
duration: options.delay,
|
||||
});
|
||||
}
|
||||
actions.push(pointerUpAction);
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: InputId.Mouse,
|
||||
actions,
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override async wheel(
|
||||
options: Readonly<MouseWheelOptions> = {},
|
||||
): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Wheel,
|
||||
id: InputId.Wheel,
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.Scroll,
|
||||
...(this.#lastMovePoint ?? {
|
||||
x: 0,
|
||||
y: 0,
|
||||
}),
|
||||
deltaX: options.deltaX ?? 0,
|
||||
deltaY: options.deltaY ?? 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
override drag(): never {
|
||||
throw new UnsupportedOperation();
|
||||
}
|
||||
|
||||
override dragOver(): never {
|
||||
throw new UnsupportedOperation();
|
||||
}
|
||||
|
||||
override dragEnter(): never {
|
||||
throw new UnsupportedOperation();
|
||||
}
|
||||
|
||||
override drop(): never {
|
||||
throw new UnsupportedOperation();
|
||||
}
|
||||
|
||||
override dragAndDrop(): never {
|
||||
throw new UnsupportedOperation();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class BidiTouchHandle implements TouchHandle {
|
||||
#started = false;
|
||||
#x: number;
|
||||
#y: number;
|
||||
#bidiId: string;
|
||||
#page: BidiPage;
|
||||
#touchScreen: BidiTouchscreen;
|
||||
#properties: Bidi.Input.PointerCommonProperties;
|
||||
|
||||
constructor(
|
||||
page: BidiPage,
|
||||
touchScreen: BidiTouchscreen,
|
||||
id: number,
|
||||
x: number,
|
||||
y: number,
|
||||
properties: Bidi.Input.PointerCommonProperties,
|
||||
) {
|
||||
this.#page = page;
|
||||
this.#touchScreen = touchScreen;
|
||||
this.#x = Math.round(x);
|
||||
this.#y = Math.round(y);
|
||||
this.#properties = properties;
|
||||
this.#bidiId = `${InputId.Finger}_${id}`;
|
||||
}
|
||||
|
||||
async start(options: BidiTouchMoveOptions = {}): Promise<void> {
|
||||
if (this.#started) {
|
||||
throw new TouchError('Touch has already started');
|
||||
}
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: this.#bidiId,
|
||||
parameters: {
|
||||
pointerType: Bidi.Input.PointerType.Touch,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerMove,
|
||||
x: this.#x,
|
||||
y: this.#y,
|
||||
origin: options.origin,
|
||||
},
|
||||
{
|
||||
...this.#properties,
|
||||
type: ActionType.PointerDown,
|
||||
button: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
this.#started = true;
|
||||
}
|
||||
|
||||
move(x: number, y: number): Promise<void> {
|
||||
const newX = Math.round(x);
|
||||
const newY = Math.round(y);
|
||||
return this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: this.#bidiId,
|
||||
parameters: {
|
||||
pointerType: Bidi.Input.PointerType.Touch,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
...this.#properties,
|
||||
type: ActionType.PointerMove,
|
||||
x: newX,
|
||||
y: newY,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
}
|
||||
|
||||
async end(): Promise<void> {
|
||||
await this.#page.mainFrame().browsingContext.performActions([
|
||||
{
|
||||
type: SourceActionsType.Pointer,
|
||||
id: this.#bidiId,
|
||||
parameters: {
|
||||
pointerType: Bidi.Input.PointerType.Touch,
|
||||
},
|
||||
actions: [
|
||||
{
|
||||
type: ActionType.PointerUp,
|
||||
button: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
this.#touchScreen.removeHandle(this);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class BidiTouchscreen extends Touchscreen {
|
||||
#page: BidiPage;
|
||||
declare touches: BidiTouchHandle[];
|
||||
|
||||
constructor(page: BidiPage) {
|
||||
super();
|
||||
this.#page = page;
|
||||
}
|
||||
|
||||
override async touchStart(
|
||||
x: number,
|
||||
y: number,
|
||||
options: BidiTouchMoveOptions = {},
|
||||
): Promise<TouchHandle> {
|
||||
const id = this.idGenerator();
|
||||
const properties: Bidi.Input.PointerCommonProperties = {
|
||||
width: 0.5 * 2, // 2 times default touch radius.
|
||||
height: 0.5 * 2, // 2 times default touch radius.
|
||||
pressure: 0.5,
|
||||
altitudeAngle: Math.PI / 2,
|
||||
};
|
||||
const touch = new BidiTouchHandle(this.#page, this, id, x, y, properties);
|
||||
await touch.start(options);
|
||||
this.touches.push(touch);
|
||||
return touch;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user