gecko-dev/remote/marionette/event.sys.mjs

325 строки
8.4 KiB
JavaScript

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable no-restricted-globals */
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
keyData: "chrome://remote/content/shared/webdriver/KeyData.sys.mjs",
});
/** Provides functionality for creating and sending DOM events. */
export const event = {};
ChromeUtils.defineLazyGetter(lazy, "dblclickTimer", () => {
return Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
});
const _eventUtils = new WeakMap();
function _getEventUtils(win) {
if (!_eventUtils.has(win)) {
const eventUtilsObject = {
window: win,
parent: win,
_EU_Ci: Ci,
_EU_Cc: Cc,
};
Services.scriptloader.loadSubScript(
"chrome://remote/content/external/EventUtils.js",
eventUtilsObject
);
_eventUtils.set(win, eventUtilsObject);
}
return _eventUtils.get(win);
}
// Max interval between two clicks that should result in a dblclick (in ms)
const DBLCLICK_INTERVAL = 640;
event.MouseEvents = {
click: 0,
dblclick: 1,
mousedown: 2,
mouseup: 3,
mouseover: 4,
mouseout: 5,
};
event.Modifiers = {
shiftKey: 0,
ctrlKey: 1,
altKey: 2,
metaKey: 3,
};
event.MouseButton = {
isPrimary(button) {
return button === 0;
},
isAuxiliary(button) {
return button === 1;
},
isSecondary(button) {
return button === 2;
},
};
event.DoubleClickTracker = {
firstClick: false,
isClicked() {
return event.DoubleClickTracker.firstClick;
},
setClick() {
if (!event.DoubleClickTracker.firstClick) {
event.DoubleClickTracker.firstClick = true;
event.DoubleClickTracker.startTimer();
}
},
resetClick() {
event.DoubleClickTracker.firstClick = false;
event.DoubleClickTracker.cancelTimer();
},
startTimer() {
lazy.dblclickTimer.initWithCallback(
event.DoubleClickTracker.resetClick,
DBLCLICK_INTERVAL,
Ci.nsITimer.TYPE_ONE_SHOT
);
},
cancelTimer() {
lazy.dblclickTimer.cancel();
},
};
/**
* Synthesise a mouse event at a point.
*
* If the type is specified in opts, an mouse event of that type is
* fired. Otherwise, a mousedown followed by a mouseup is performed.
*
* @param {number} left
* Offset from viewport left, in CSS pixels
* @param {number} top
* Offset from viewport top, in CSS pixels
* @param {object} opts
* Object which may contain the properties "shiftKey", "ctrlKey",
* "altKey", "metaKey", "accessKey", "clickCount", "button", and
* "type".
* @param {Window} win
* Window object.
*
* @returns {boolean} defaultPrevented
*/
event.synthesizeMouseAtPoint = function (left, top, opts, win) {
return _getEventUtils(win).synthesizeMouseAtPoint(left, top, opts, win);
};
/**
* Synthesise a touch event at a point.
*
* If the type is specified in opts, a touch event of that type is
* fired. Otherwise, a touchstart followed by a touchend is performed.
*
* @param {number} left
* Offset from viewport left, in CSS pixels
* @param {number} top
* Offset from viewport top, in CSS pixels
* @param {object} opts
* Object which may contain the properties "id", "rx", "ry", "angle",
* "force", "shiftKey", "ctrlKey", "altKey", "metaKey", "accessKey",
* "type".
* @param {Window} win
* Window object.
*
* @returns {boolean} defaultPrevented
*/
event.synthesizeTouchAtPoint = function (left, top, opts, win) {
return _getEventUtils(win).synthesizeTouchAtPoint(left, top, opts, win);
};
/**
* Synthesise a wheel scroll event at a point.
*
* @param {number} left
* Offset from viewport left, in CSS pixels
* @param {number} top
* Offset from viewport top, in CSS pixels
* @param {object} opts
* Object which may contain the properties "shiftKey", "ctrlKey",
* "altKey", "metaKey", "accessKey", "deltaX", "deltaY", "deltaZ",
* "deltaMode", "lineOrPageDeltaX", "lineOrPageDeltaY", "isMomentum",
* "isNoLineOrPageDelta", "isCustomizedByPrefs", "expectedOverflowDeltaX",
* "expectedOverflowDeltaY"
* @param {Window} win
* Window object.
*/
event.synthesizeWheelAtPoint = function (left, top, opts, win) {
const dpr = win.devicePixelRatio;
// All delta properties expect the value in device pixels while the
// WebDriver specification uses CSS pixels.
if (typeof opts.deltaX !== "undefined") {
opts.deltaX *= dpr;
}
if (typeof opts.deltaY !== "undefined") {
opts.deltaY *= dpr;
}
if (typeof opts.deltaZ !== "undefined") {
opts.deltaZ *= dpr;
}
return _getEventUtils(win).synthesizeWheelAtPoint(left, top, opts, win);
};
event.synthesizeMultiTouch = function (opts, win) {
const modifiers = _getEventUtils(win)._parseModifiers(opts);
win.windowUtils.sendTouchEvent(
opts.type,
opts.id,
opts.x,
opts.y,
opts.rx,
opts.ry,
opts.angle,
opts.force,
opts.tiltx,
opts.tilty,
opts.twist,
modifiers
);
};
/**
* Synthesize a keydown event for a single key.
*
* @param {object} key
* Key data as returned by keyData.getData
* @param {Window} win
* Window object.
*/
event.sendKeyDown = function (key, win) {
event.sendSingleKey(key, win, "keydown");
};
/**
* Synthesize a keyup event for a single key.
*
* @param {object} key
* Key data as returned by keyData.getData
* @param {Window} win
* Window object.
*/
event.sendKeyUp = function (key, win) {
event.sendSingleKey(key, win, "keyup");
};
/**
* Synthesize a key event for a single key.
*
* @param {object} key
* Key data as returned by keyData.getData
* @param {Window} win
* Window object.
* @param {string=} type
* Event to emit. By default the full keydown/keypressed/keyup event
* sequence is emitted.
*/
event.sendSingleKey = function (key, win, type = null) {
let keyValue = key.key;
if (!key.printable) {
keyValue = `KEY_${keyValue}`;
}
const event = {
code: key.code,
location: key.location,
altKey: key.altKey ?? false,
shiftKey: key.shiftKey ?? false,
ctrlKey: key.ctrlKey ?? false,
metaKey: key.metaKey ?? false,
repeat: key.repeat ?? false,
};
if (type) {
event.type = type;
}
_getEventUtils(win).synthesizeKey(keyValue, event, win);
};
/**
* Send a string as a series of keypresses.
*
* @param {string} keyString
* Sequence of characters to send as key presses
* @param {Window} win
* Window object
*/
event.sendKeys = function (keyString, win) {
const modifiers = {};
for (let modifier in event.Modifiers) {
modifiers[modifier] = false;
}
for (let i = 0; i < keyString.length; i++) {
let keyValue = keyString.charAt(i);
if (modifiers.shiftKey) {
keyValue = lazy.keyData.getShiftedKey(keyValue);
}
const data = lazy.keyData.getData(keyValue);
const key = { ...data, ...modifiers };
if (data.modifier) {
// Negating the state of the modifier here is not spec compliant but
// makes us compatible to Chrome's behavior for now. That's fine unless
// we know the correct behavior.
//
// @see: https://github.com/w3c/webdriver/issues/1734
modifiers[data.modifier] = !modifiers[data.modifier];
}
event.sendSingleKey(key, win);
}
};
event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
opts.canBubble = opts.canBubble || true;
let doc = el.ownerDocument || el.document;
let ev = doc.createEvent("Event");
ev.shiftKey = modifiers.shift;
ev.metaKey = modifiers.meta;
ev.altKey = modifiers.alt;
ev.ctrlKey = modifiers.ctrl;
ev.initEvent(eventType, opts.canBubble, true);
el.dispatchEvent(ev);
};
event.mouseover = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("mouseover", el, modifiers, opts);
};
event.mousemove = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("mousemove", el, modifiers, opts);
};
event.mousedown = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("mousedown", el, modifiers, opts);
};
event.mouseup = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("mouseup", el, modifiers, opts);
};
event.click = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("click", el, modifiers, opts);
};
event.change = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("change", el, modifiers, opts);
};
event.input = function (el, modifiers = {}, opts = {}) {
return event.sendEvent("input", el, modifiers, opts);
};