323 lines
9.0 KiB
JavaScript
323 lines
9.0 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/. */
|
|
|
|
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 = {};
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Synthesize a mouse event in `win` at a point.
|
|
*
|
|
* If the type is specified in `event`, a mouse event of that type is
|
|
* fired. Otherwise, a mousedown followed by a mouseup is performed.
|
|
*
|
|
* @param {number} left - Value for the X offset in CSS pixels.
|
|
* @param {number} top - Value for the Y offset in CSS pixels.
|
|
* @param {module:EventUtils~MouseEventData} event - Details of the mouse event
|
|
* to dispatch.
|
|
* @param {DOMWindow} win - DOM window used to dispatch the event.
|
|
*
|
|
* @returns {Promise<boolean>} Promise that resolves to a boolean,
|
|
* indicating whether the event had preventDefault() called on it.
|
|
*/
|
|
event.synthesizeMouseAtPoint = function (left, top, event, win) {
|
|
if (!event.asyncEnabled) {
|
|
return Promise.resolve(
|
|
_getEventUtils(win).synthesizeMouseAtPoint(left, top, event, win)
|
|
);
|
|
}
|
|
|
|
// A callback must be used when handling events with the `asyncEnabled`
|
|
// flag set to `true`, as these events are synthesized in the parent process.
|
|
// We need to wait for them to be fully dispatched to the content process
|
|
// before continuing.
|
|
const { promise, resolve } = Promise.withResolvers();
|
|
const preventDefaultFlag = _getEventUtils(win).synthesizeMouseAtPoint(
|
|
left,
|
|
top,
|
|
event,
|
|
win,
|
|
() => resolve()
|
|
);
|
|
|
|
return promise.then(() => preventDefaultFlag);
|
|
};
|
|
|
|
/**
|
|
* Synthesize one or more touch events in `win` at the points.
|
|
*
|
|
* If the type is specified in `event`, a touch event of that type is
|
|
* fired. Otherwise, a touchstart followed by a touchend is performed.
|
|
*
|
|
* @param {module:EventUtils~TouchEventData} event - Details of the touch event(s)
|
|
* to dispatch.
|
|
* @param {DOMWindow} win - DOM window used to dispatch the event.
|
|
*
|
|
* @returns {Promise<boolean>} Promise that resolves to a boolean,
|
|
* indicating whether the event had preventDefault() called on it.
|
|
*/
|
|
event.synthesizeTouchAtPoint = function (event, win) {
|
|
const touchData = {
|
|
type: event.type,
|
|
asyncEnabled: event.asyncEnabled,
|
|
id: event.id,
|
|
rx: event.rx,
|
|
ry: event.ry,
|
|
angle: event.angle,
|
|
force: event.force,
|
|
tiltX: event.tiltx,
|
|
tiltY: event.tilty,
|
|
twist: event.twist,
|
|
altitudeAngle: event.altitudeAngle,
|
|
azimuthAngle: event.azimuthAngle,
|
|
modifiers: _getEventUtils(win)._parseModifiers(event),
|
|
};
|
|
|
|
if (!event.asyncEnabled) {
|
|
return Promise.resolve(
|
|
_getEventUtils(win).synthesizeTouchAtPoint(
|
|
event.x,
|
|
event.y,
|
|
touchData,
|
|
win
|
|
)
|
|
);
|
|
}
|
|
|
|
// A callback must be used when handling events with the `asyncEnabled`
|
|
// flag set to `true`, as these events are synthesized in the parent process.
|
|
// We need to wait for them to be fully dispatched to the content process
|
|
// before continuing.
|
|
const { promise, resolve } = Promise.withResolvers();
|
|
const preventDefaultFlag = _getEventUtils(win).synthesizeTouchAtPoint(
|
|
event.x,
|
|
event.y,
|
|
touchData,
|
|
win,
|
|
() => resolve()
|
|
);
|
|
|
|
return promise.then(() => preventDefaultFlag);
|
|
};
|
|
|
|
/**
|
|
* Synthesize a wheel event in `win` at a point, without flushing layout.
|
|
*
|
|
* @param {number} left - Floating-point value for the X offset in CSS pixels.
|
|
* @param {number} top - Floating-point value for the Y offset in CSS pixels.
|
|
* @param {module:EventUtils~WheelEventData} event - Details of the wheel event
|
|
* to dispatch.
|
|
* @param {DOMWindow} win - DOM window used to dispatch the event.
|
|
*
|
|
* @returns {Promise} - Promise that resolves once the event has been dispatched.
|
|
*/
|
|
event.synthesizeWheelAtPoint = function (left, top, event, win) {
|
|
const dpr = win.devicePixelRatio;
|
|
|
|
// All delta properties expect the value in device pixels while the
|
|
// WebDriver specification uses CSS pixels.
|
|
// TODO: check if the conversion needs to be done at the platform level
|
|
if (typeof event.deltaX !== "undefined") {
|
|
event.deltaX *= dpr;
|
|
}
|
|
if (typeof event.deltaY !== "undefined") {
|
|
event.deltaY *= dpr;
|
|
}
|
|
if (typeof event.deltaZ !== "undefined") {
|
|
event.deltaZ *= dpr;
|
|
}
|
|
|
|
return new Promise(resolve =>
|
|
_getEventUtils(win).synthesizeWheelAtPoint(left, top, event, win, resolve)
|
|
);
|
|
};
|
|
|
|
/**
|
|
* 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 keyValue of keyString) {
|
|
// keyValue will contain enough to represent the UTF-16 encoding of a single abstract character
|
|
// i.e. either a single scalar value, or a surrogate pair
|
|
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.cancel = function (el, modifiers = {}, opts = {}) {
|
|
return event.sendEvent("cancel", 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);
|
|
};
|