mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
28c7394319
PR-URL: https://github.com/nodejs/node/pull/55063 Reviewed-By: Filip Skokan <panva.ip@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
600 lines
18 KiB
JavaScript
600 lines
18 KiB
JavaScript
(function() {
|
|
let sourceNameIdx = 0;
|
|
|
|
/**
|
|
* @class
|
|
* Builder for creating a sequence of actions
|
|
*
|
|
*
|
|
* The actions are dispatched once
|
|
* :js:func:`test_driver.Actions.send` is called. This returns a
|
|
* promise which resolves once the actions are complete.
|
|
*
|
|
* The other methods on :js:class:`test_driver.Actions` object are
|
|
* used to build the sequence of actions that will be sent. These
|
|
* return the `Actions` object itself, so the actions sequence can
|
|
* be constructed by chaining method calls.
|
|
*
|
|
* Internally :js:func:`test_driver.Actions.send` invokes
|
|
* :js:func:`test_driver.action_sequence`.
|
|
*
|
|
* @example
|
|
* let text_box = document.getElementById("text");
|
|
*
|
|
* let actions = new test_driver.Actions()
|
|
* .pointerMove(0, 0, {origin: text_box})
|
|
* .pointerDown()
|
|
* .pointerUp()
|
|
* .addTick()
|
|
* .keyDown("p")
|
|
* .keyUp("p");
|
|
*
|
|
* await actions.send();
|
|
*
|
|
* @param {number} [defaultTickDuration] - The default duration of a
|
|
* tick. Be default this is set to 16ms, which is one frame time
|
|
* based on 60Hz display.
|
|
*/
|
|
function Actions(defaultTickDuration=16) {
|
|
this.sourceTypes = new Map([["key", KeySource],
|
|
["pointer", PointerSource],
|
|
["wheel", WheelSource],
|
|
["none", GeneralSource]]);
|
|
this.sources = new Map();
|
|
this.sourceOrder = [];
|
|
for (let sourceType of this.sourceTypes.keys()) {
|
|
this.sources.set(sourceType, new Map());
|
|
}
|
|
this.currentSources = new Map();
|
|
for (let sourceType of this.sourceTypes.keys()) {
|
|
this.currentSources.set(sourceType, null);
|
|
}
|
|
this.createSource("none");
|
|
this.tickIdx = 0;
|
|
this.defaultTickDuration = defaultTickDuration;
|
|
this.context = null;
|
|
}
|
|
|
|
Actions.prototype = {
|
|
ButtonType: {
|
|
LEFT: 0,
|
|
MIDDLE: 1,
|
|
RIGHT: 2,
|
|
BACK: 3,
|
|
FORWARD: 4,
|
|
},
|
|
|
|
/**
|
|
* Generate the action sequence suitable for passing to
|
|
* test_driver.action_sequence
|
|
*
|
|
* @returns {Array} Array of WebDriver-compatible actions sequences
|
|
*/
|
|
serialize: function() {
|
|
let actions = [];
|
|
for (let [sourceType, sourceName] of this.sourceOrder) {
|
|
let source = this.sources.get(sourceType).get(sourceName);
|
|
let serialized = source.serialize(this.tickIdx + 1, this.defaultTickDuration);
|
|
if (serialized) {
|
|
serialized.id = sourceName;
|
|
actions.push(serialized);
|
|
}
|
|
}
|
|
return actions;
|
|
},
|
|
|
|
/**
|
|
* Generate and send the action sequence
|
|
*
|
|
* @returns {Promise} fulfilled after the sequence is executed,
|
|
* rejected if any actions fail.
|
|
*/
|
|
send: function() {
|
|
let actions;
|
|
try {
|
|
actions = this.serialize();
|
|
} catch(e) {
|
|
return Promise.reject(e);
|
|
}
|
|
return test_driver.action_sequence(actions, this.context);
|
|
},
|
|
|
|
/**
|
|
* Set the context for the actions
|
|
*
|
|
* @param {WindowProxy} context - Context in which to run the action sequence
|
|
*/
|
|
setContext: function(context) {
|
|
this.context = context;
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Get the action source with a particular source type and name.
|
|
* If no name is passed, a new source with the given type is
|
|
* created.
|
|
*
|
|
* @param {String} type - Source type ('none', 'key', 'pointer', or 'wheel')
|
|
* @param {String?} name - Name of the source
|
|
* @returns {Source} Source object for that source.
|
|
*/
|
|
getSource: function(type, name) {
|
|
if (!this.sources.has(type)) {
|
|
throw new Error(`${type} is not a valid action type`);
|
|
}
|
|
if (name === null || name === undefined) {
|
|
name = this.currentSources.get(type);
|
|
}
|
|
if (name === null || name === undefined) {
|
|
return this.createSource(type, null);
|
|
}
|
|
return this.sources.get(type).get(name);
|
|
},
|
|
|
|
setSource: function(type, name) {
|
|
if (!this.sources.has(type)) {
|
|
throw new Error(`${type} is not a valid action type`);
|
|
}
|
|
if (!this.sources.get(type).has(name)) {
|
|
throw new Error(`${name} is not a valid source for ${type}`);
|
|
}
|
|
this.currentSources.set(type, name);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a new key input source with the given name
|
|
*
|
|
* @param {String} name - Name of the key source
|
|
* @param {Bool} set - Set source as the default key source
|
|
* @returns {Actions}
|
|
*/
|
|
addKeyboard: function(name, set=true) {
|
|
this.createSource("key", name);
|
|
if (set) {
|
|
this.setKeyboard(name);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the current default key source
|
|
*
|
|
* @param {String} name - Name of the key source
|
|
* @returns {Actions}
|
|
*/
|
|
setKeyboard: function(name) {
|
|
this.setSource("key", name);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a new pointer input source with the given name
|
|
*
|
|
* @param {String} type - Name of the pointer source
|
|
* @param {String} pointerType - Type of pointing device
|
|
* @param {Bool} set - Set source as the default pointer source
|
|
* @returns {Actions}
|
|
*/
|
|
addPointer: function(name, pointerType="mouse", set=true) {
|
|
this.createSource("pointer", name, {pointerType: pointerType});
|
|
if (set) {
|
|
this.setPointer(name);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the current default pointer source
|
|
*
|
|
* @param {String} name - Name of the pointer source
|
|
* @returns {Actions}
|
|
*/
|
|
setPointer: function(name) {
|
|
this.setSource("pointer", name);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a new wheel input source with the given name
|
|
*
|
|
* @param {String} type - Name of the wheel source
|
|
* @param {Bool} set - Set source as the default wheel source
|
|
* @returns {Actions}
|
|
*/
|
|
addWheel: function(name, set=true) {
|
|
this.createSource("wheel", name);
|
|
if (set) {
|
|
this.setWheel(name);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Set the current default wheel source
|
|
*
|
|
* @param {String} name - Name of the wheel source
|
|
* @returns {Actions}
|
|
*/
|
|
setWheel: function(name) {
|
|
this.setSource("wheel", name);
|
|
return this;
|
|
},
|
|
|
|
createSource: function(type, name, parameters={}) {
|
|
if (!this.sources.has(type)) {
|
|
throw new Error(`${type} is not a valid action type`);
|
|
}
|
|
let sourceNames = new Set();
|
|
for (let [_, name] of this.sourceOrder) {
|
|
sourceNames.add(name);
|
|
}
|
|
if (!name) {
|
|
do {
|
|
name = "" + sourceNameIdx++;
|
|
} while (sourceNames.has(name))
|
|
} else {
|
|
if (sourceNames.has(name)) {
|
|
throw new Error(`Alreay have a source of type ${type} named ${name}.`);
|
|
}
|
|
}
|
|
this.sources.get(type).set(name, new (this.sourceTypes.get(type))(parameters));
|
|
this.currentSources.set(type, name);
|
|
this.sourceOrder.push([type, name]);
|
|
return this.sources.get(type).get(name);
|
|
},
|
|
|
|
/**
|
|
* Insert a new actions tick
|
|
*
|
|
* @param {Number?} duration - Minimum length of the tick in ms.
|
|
* @returns {Actions}
|
|
*/
|
|
addTick: function(duration) {
|
|
this.tickIdx += 1;
|
|
if (duration) {
|
|
this.pause(duration);
|
|
}
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Add a pause to the current tick
|
|
*
|
|
* @param {Number?} duration - Minimum length of the tick in ms.
|
|
* @param {String} sourceType - source type
|
|
* @param {String?} sourceName - Named key, pointer or wheel source to use
|
|
* or null for the default key, pointer or
|
|
* wheel source
|
|
* @returns {Actions}
|
|
*/
|
|
pause: function(duration=0, sourceType="none", {sourceName=null}={}) {
|
|
if (sourceType=="none")
|
|
this.getSource("none").addPause(this, duration);
|
|
else
|
|
this.getSource(sourceType, sourceName).addPause(this, duration);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a keyDown event for the current default key source
|
|
*
|
|
* @param {String} key - Key to press
|
|
* @param {String?} sourceName - Named key source to use or null for the default key source
|
|
* @returns {Actions}
|
|
*/
|
|
keyDown: function(key, {sourceName=null}={}) {
|
|
let source = this.getSource("key", sourceName);
|
|
source.keyDown(this, key);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a keyDown event for the current default key source
|
|
*
|
|
* @param {String} key - Key to release
|
|
* @param {String?} sourceName - Named key source to use or null for the default key source
|
|
* @returns {Actions}
|
|
*/
|
|
keyUp: function(key, {sourceName=null}={}) {
|
|
let source = this.getSource("key", sourceName);
|
|
source.keyUp(this, key);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a pointerDown event for the current default pointer source
|
|
*
|
|
* @param {String} button - Button to press
|
|
* @param {String?} sourceName - Named pointer source to use or null for the default
|
|
* pointer source
|
|
* @returns {Actions}
|
|
*/
|
|
pointerDown: function({button=this.ButtonType.LEFT, sourceName=null,
|
|
width, height, pressure, tangentialPressure,
|
|
tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) {
|
|
let source = this.getSource("pointer", sourceName);
|
|
source.pointerDown(this, button, width, height, pressure, tangentialPressure,
|
|
tiltX, tiltY, twist, altitudeAngle, azimuthAngle);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a pointerUp event for the current default pointer source
|
|
*
|
|
* @param {String} button - Button to release
|
|
* @param {String?} sourceName - Named pointer source to use or null for the default pointer
|
|
* source
|
|
* @returns {Actions}
|
|
*/
|
|
pointerUp: function({button=this.ButtonType.LEFT, sourceName=null}={}) {
|
|
let source = this.getSource("pointer", sourceName);
|
|
source.pointerUp(this, button);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a move event for the current default pointer source
|
|
*
|
|
* @param {Number} x - Destination x coordinate
|
|
* @param {Number} y - Destination y coordinate
|
|
* @param {String|Element} origin - Origin of the coordinate system.
|
|
* Either "pointer", "viewport" or an Element
|
|
* @param {Number?} duration - Time in ms for the move
|
|
* @param {String?} sourceName - Named pointer source to use or null for the default pointer
|
|
* source
|
|
* @returns {Actions}
|
|
*/
|
|
pointerMove: function(x, y,
|
|
{origin="viewport", duration, sourceName=null,
|
|
width, height, pressure, tangentialPressure,
|
|
tiltX, tiltY, twist, altitudeAngle, azimuthAngle}={}) {
|
|
let source = this.getSource("pointer", sourceName);
|
|
source.pointerMove(this, x, y, duration, origin, width, height, pressure,
|
|
tangentialPressure, tiltX, tiltY, twist, altitudeAngle,
|
|
azimuthAngle);
|
|
return this;
|
|
},
|
|
|
|
/**
|
|
* Create a scroll event for the current default wheel source
|
|
*
|
|
* @param {Number} x - mouse cursor x coordinate
|
|
* @param {Number} y - mouse cursor y coordinate
|
|
* @param {Number} deltaX - scroll delta value along the x-axis in pixels
|
|
* @param {Number} deltaY - scroll delta value along the y-axis in pixels
|
|
* @param {String|Element} origin - Origin of the coordinate system.
|
|
* Either "viewport" or an Element
|
|
* @param {Number?} duration - Time in ms for the scroll
|
|
* @param {String?} sourceName - Named wheel source to use or null for the
|
|
* default wheel source
|
|
* @returns {Actions}
|
|
*/
|
|
scroll: function(x, y, deltaX, deltaY,
|
|
{origin="viewport", duration, sourceName=null}={}) {
|
|
let source = this.getSource("wheel", sourceName);
|
|
source.scroll(this, x, y, deltaX, deltaY, duration, origin);
|
|
return this;
|
|
},
|
|
};
|
|
|
|
function GeneralSource() {
|
|
this.actions = new Map();
|
|
}
|
|
|
|
GeneralSource.prototype = {
|
|
serialize: function(tickCount, defaultTickDuration) {
|
|
let actions = [];
|
|
let data = {"type": "none", "actions": actions};
|
|
for (let i=0; i<tickCount; i++) {
|
|
if (this.actions.has(i)) {
|
|
actions.push(this.actions.get(i));
|
|
} else {
|
|
actions.push({"type": "pause", duration: defaultTickDuration});
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
|
|
addPause: function(actions, duration) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
throw new Error(`Already have a pause action for the current tick`);
|
|
}
|
|
this.actions.set(tick, {type: "pause", duration: duration});
|
|
},
|
|
};
|
|
|
|
function KeySource() {
|
|
this.actions = new Map();
|
|
}
|
|
|
|
KeySource.prototype = {
|
|
serialize: function(tickCount) {
|
|
if (!this.actions.size) {
|
|
return undefined;
|
|
}
|
|
let actions = [];
|
|
let data = {"type": "key", "actions": actions};
|
|
for (let i=0; i<tickCount; i++) {
|
|
if (this.actions.has(i)) {
|
|
actions.push(this.actions.get(i));
|
|
} else {
|
|
actions.push({"type": "pause"});
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
|
|
keyDown: function(actions, key) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "keyDown", value: key});
|
|
},
|
|
|
|
keyUp: function(actions, key) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "keyUp", value: key});
|
|
},
|
|
|
|
addPause: function(actions, duration) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "pause", duration: duration});
|
|
},
|
|
};
|
|
|
|
function PointerSource(parameters={pointerType: "mouse"}) {
|
|
let pointerType = parameters.pointerType || "mouse";
|
|
if (!["mouse", "pen", "touch"].includes(pointerType)) {
|
|
throw new Error(`Invalid pointerType ${pointerType}`);
|
|
}
|
|
this.type = pointerType;
|
|
this.actions = new Map();
|
|
}
|
|
|
|
function setPointerProperties(action, width, height, pressure, tangentialPressure,
|
|
tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
|
if (width) {
|
|
action.width = width;
|
|
}
|
|
if (height) {
|
|
action.height = height;
|
|
}
|
|
if (pressure) {
|
|
action.pressure = pressure;
|
|
}
|
|
if (tangentialPressure) {
|
|
action.tangentialPressure = tangentialPressure;
|
|
}
|
|
if (tiltX) {
|
|
action.tiltX = tiltX;
|
|
}
|
|
if (tiltY) {
|
|
action.tiltY = tiltY;
|
|
}
|
|
if (twist) {
|
|
action.twist = twist;
|
|
}
|
|
if (altitudeAngle) {
|
|
action.altitudeAngle = altitudeAngle;
|
|
}
|
|
if (azimuthAngle) {
|
|
action.azimuthAngle = azimuthAngle;
|
|
}
|
|
return action;
|
|
}
|
|
|
|
PointerSource.prototype = {
|
|
serialize: function(tickCount) {
|
|
if (!this.actions.size) {
|
|
return undefined;
|
|
}
|
|
let actions = [];
|
|
let data = {"type": "pointer", "actions": actions, "parameters": {"pointerType": this.type}};
|
|
for (let i=0; i<tickCount; i++) {
|
|
if (this.actions.has(i)) {
|
|
actions.push(this.actions.get(i));
|
|
} else {
|
|
actions.push({"type": "pause"});
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
|
|
pointerDown: function(actions, button, width, height, pressure, tangentialPressure,
|
|
tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
let actionProperties = setPointerProperties({type: "pointerDown", button}, width, height,
|
|
pressure, tangentialPressure, tiltX, tiltY,
|
|
twist, altitudeAngle, azimuthAngle);
|
|
this.actions.set(tick, actionProperties);
|
|
},
|
|
|
|
pointerUp: function(actions, button) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "pointerUp", button});
|
|
},
|
|
|
|
pointerMove: function(actions, x, y, duration, origin, width, height, pressure,
|
|
tangentialPressure, tiltX, tiltY, twist, altitudeAngle, azimuthAngle) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
let moveAction = {type: "pointerMove", x, y, origin};
|
|
if (duration) {
|
|
moveAction.duration = duration;
|
|
}
|
|
let actionProperties = setPointerProperties(moveAction, width, height, pressure,
|
|
tangentialPressure, tiltX, tiltY, twist,
|
|
altitudeAngle, azimuthAngle);
|
|
this.actions.set(tick, actionProperties);
|
|
},
|
|
|
|
addPause: function(actions, duration) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "pause", duration: duration});
|
|
},
|
|
};
|
|
|
|
function WheelSource() {
|
|
this.actions = new Map();
|
|
}
|
|
|
|
WheelSource.prototype = {
|
|
serialize: function(tickCount) {
|
|
if (!this.actions.size) {
|
|
return undefined;
|
|
}
|
|
let actions = [];
|
|
let data = {"type": "wheel", "actions": actions};
|
|
for (let i=0; i<tickCount; i++) {
|
|
if (this.actions.has(i)) {
|
|
actions.push(this.actions.get(i));
|
|
} else {
|
|
actions.push({"type": "pause"});
|
|
}
|
|
}
|
|
return data;
|
|
},
|
|
|
|
scroll: function(actions, x, y, deltaX, deltaY, duration, origin) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "scroll", x, y, deltaX, deltaY, origin});
|
|
if (duration) {
|
|
this.actions.get(tick).duration = duration;
|
|
}
|
|
},
|
|
|
|
addPause: function(actions, duration) {
|
|
let tick = actions.tickIdx;
|
|
if (this.actions.has(tick)) {
|
|
tick = actions.addTick().tickIdx;
|
|
}
|
|
this.actions.set(tick, {type: "pause", duration: duration});
|
|
},
|
|
};
|
|
|
|
test_driver.Actions = Actions;
|
|
})();
|