Bug 1376128 - Lint testing/marionette; r=automatedtester

MozReview-Commit-ID: DY4yCSBEZrN

--HG--
extra : rebase_source : d4e25369418cc72a6ee9f78d44b050a87403391d
This commit is contained in:
Andreas Tolfsen 2017-06-29 16:40:24 -07:00
parent 32d341c354
commit de001d80b7
30 changed files with 1686 additions and 1345 deletions

View File

@ -6,7 +6,7 @@
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import('resource://gre/modules/Services.jsm');
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
@ -35,15 +35,16 @@ this.EXPORTED_SYMBOLS = ["accessibility"];
this.accessibility = {
get service() {
return service;
}
},
};
/**
* Accessible states used to check element"s state from the accessiblity API
* perspective.
* Note: if gecko is built with --disable-accessibility, the interfaces are not
* defined. This is why we use getters instead to be able to use these
* statically.
*
* Note: if gecko is built with --disable-accessibility, the interfaces
* are not defined. This is why we use getters instead to be able to use
* these statically.
*/
accessibility.State = {
get Unavailable() {
@ -57,7 +58,7 @@ accessibility.State = {
},
get Selected() {
return Ci.nsIAccessibleStates.STATE_SELECTED;
}
},
};
/**
@ -92,7 +93,7 @@ accessibility.ActionableRoles = new Set([
* Factory function that constructs a new {@code accessibility.Checks}
* object with enforced strictness or not.
*/
accessibility.get = function (strict = false) {
accessibility.get = function(strict = false) {
return new accessibility.Checks(!!strict);
};
@ -138,7 +139,8 @@ accessibility.Checks = class {
}
// First, check if accessibility is ready.
let docAcc = accessibility.service.getAccessibleFor(element.ownerDocument);
let docAcc = accessibility.service
.getAccessibleFor(element.ownerDocument);
let state = {};
docAcc.getState(state, {});
if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) {
@ -158,12 +160,14 @@ accessibility.Checks = class {
return;
}
let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
// If event type does not match expected type, skip the event.
let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
if (event.eventType !== Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
return;
}
// If event's accessible does not match expected accessible, skip the event.
// If event's accessible does not match expected accessible,
// skip the event.
if (event.accessible !== docAcc) {
return;
}
@ -175,12 +179,12 @@ accessibility.Checks = class {
} else {
resolve(acc);
}
}
},
};
Services.obs.addObserver(eventObserver, "accessible-event");
}).catch(() => this.error(
"Element does not have an accessible object", element));
};
}
/**
* Test if the accessible has a role that supports some arbitrary
@ -409,7 +413,8 @@ accessibility.Checks = class {
return;
}
let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected);
let selectedAccessibility =
this.matchState(accessible, accessibility.State.Selected);
let message;
if (selected && !selectedAccessibility) {

View File

@ -23,6 +23,8 @@ Cu.import("chrome://marionette/content/interaction.js");
this.EXPORTED_SYMBOLS = ["action"];
const {pprint} = error;
// TODO? With ES 2016 and Symbol you can make a safer approximation
// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
/**
@ -360,7 +362,7 @@ action.PointerOrigin.get = function(obj) {
origin = this.Viewport;
} else if (typeof obj == "string") {
let name = capitalize(obj);
assert.in(name, this, error.pprint`Unknown pointer-move origin: ${obj}`);
assert.in(name, this, pprint`Unknown pointer-move origin: ${obj}`);
origin = this[name];
} else if (!element.isWebElementReference(obj)) {
throw new InvalidArgumentError("Expected 'origin' to be a string or a " +
@ -389,24 +391,25 @@ action.PointerType = {
* @throws {InvalidArgumentError}
* If |str| is not a valid pointer type.
*/
action.PointerType.get = function (str) {
action.PointerType.get = function(str) {
let name = capitalize(str);
assert.in(name, this, error.pprint`Unknown pointerType: ${str}`);
assert.in(name, this, pprint`Unknown pointerType: ${str}`);
return this[name];
};
/**
* Input state associated with current session. This is a map between input ID and
* the device state for that input source, with one entry for each active input source.
* Input state associated with current session. This is a map between
* input ID and the device state for that input source, with one entry
* for each active input source.
*
* Initialized in listener.js
* Initialized in listener.js.
*/
action.inputStateMap = undefined;
/**
* List of |action.Action| associated with current session. Used to manage dispatching
* events when resetting the state of the input sources. Reset operations are assumed
* to be idempotent.
* List of |action.Action| associated with current session. Used to
* manage dispatching events when resetting the state of the input sources.
* Reset operations are assumed to be idempotent.
*
* Initialized in listener.js
*/
@ -441,8 +444,8 @@ class InputState {
/**
* @param {?} obj
* Object with property |type| and optionally |parameters| or |pointerType|,
* representing an action sequence or an action item.
* Object with property |type| and optionally |parameters| or
* |pointerType|, representing an action sequence or an action item.
*
* @return {action.InputState}
* An |action.InputState| object for the type of the |actionSequence|.
@ -452,18 +455,18 @@ class InputState {
*/
static fromJson(obj) {
let type = obj.type;
assert.in(type, ACTIONS, error.pprint`Unknown action type: ${type}`);
assert.in(type, ACTIONS, pprint`Unknown action type: ${type}`);
let name = type == "none" ? "Null" : capitalize(type);
if (name == "Pointer") {
if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) {
if (!obj.pointerType &&
(!obj.parameters || !obj.parameters.pointerType)) {
throw new InvalidArgumentError(
error.pprint`Expected obj to have pointerType, got: ${obj}`);
pprint`Expected obj to have pointerType, got: ${obj}`);
}
let pointerType = obj.pointerType || obj.parameters.pointerType;
return new action.InputState[name](pointerType);
} else {
return new action.InputState[name]();
}
return new action.InputState[name]();
}
}
@ -566,7 +569,8 @@ action.InputState.Pointer = class Pointer extends InputState {
constructor(subtype) {
super();
this.pressed = new Set();
assert.defined(subtype, error.pprint`Expected subtype to be defined, got: ${subtype}`);
assert.defined(subtype,
pprint`Expected subtype to be defined, got: ${subtype}`);
this.subtype = action.PointerType.get(subtype);
this.x = 0;
this.y = 0;
@ -616,14 +620,16 @@ action.InputState.Pointer = class Pointer extends InputState {
};
/**
* Repesents an action for dispatch. Used in |action.Chain| and |action.Sequence|.
* Repesents an action for dispatch. Used in |action.Chain| and
* |action.Sequence|.
*
* @param {string} id
* Input source ID.
* @param {string} type
* Action type: none, key, pointer.
* @param {string} subtype
* Action subtype: pause, keyUp, keyDown, pointerUp, pointerDown, pointerMove, pointerCancel.
* Action subtype: pause, keyUp, keyDown, pointerUp, pointerDown,
* pointerMove, pointerCancel.
*
* @throws {InvalidArgumentError}
* If any parameters are undefined.
@ -634,12 +640,12 @@ action.Action = class {
throw new InvalidArgumentError("Missing id, type or subtype");
}
for (let attr of [id, type, subtype]) {
assert.string(attr, error.pprint`Expected string, got: ${attr}`);
assert.string(attr, pprint`Expected string, got: ${attr}`);
}
this.id = id;
this.type = type;
this.subtype = subtype;
};
}
toString() {
return `[action ${this.type}]`;
@ -668,7 +674,8 @@ action.Action = class {
}
let subtype = actionItem.type;
if (!subtypes.has(subtype)) {
throw new InvalidArgumentError(`Unknown subtype for ${type} action: ${subtype}`);
throw new InvalidArgumentError(
`Unknown subtype for ${type} action: ${subtype}`);
}
let item = new action.Action(id, type, subtype);
@ -682,10 +689,10 @@ action.Action = class {
case action.KeyDown:
let key = actionItem.value;
// TODO countGraphemes
// TODO key.value could be a single code point like "\uE012" (see rawKey)
// or "grapheme cluster"
// TODO key.value could be a single code point like "\uE012"
// (see rawKey) or "grapheme cluster"
assert.string(key,
error.pprint("Expected 'value' to be a string that represents single code point " +
pprint("Expected 'value' to be a string that represents single code point " +
`or grapheme cluster, got: ${key}`));
item.value = key;
break;
@ -693,36 +700,38 @@ action.Action = class {
case action.PointerDown:
case action.PointerUp:
assert.positiveInteger(actionItem.button,
error.pprint`Expected 'button' (${actionItem.button}) to be >= 0`);
pprint`Expected 'button' (${actionItem.button}) to be >= 0`);
item.button = actionItem.button;
break;
case action.PointerMove:
item.duration = actionItem.duration;
if (typeof item.duration != "undefined"){
if (typeof item.duration != "undefined") {
assert.positiveInteger(item.duration,
error.pprint`Expected 'duration' (${item.duration}) to be >= 0`);
pprint`Expected 'duration' (${item.duration}) to be >= 0`);
}
item.origin = action.PointerOrigin.get(actionItem.origin);
item.x = actionItem.x;
if (typeof item.x != "undefined") {
assert.integer(item.x, error.pprint`Expected 'x' (${item.x}) to be an Integer`);
assert.integer(item.x,
pprint`Expected 'x' (${item.x}) to be an Integer`);
}
item.y = actionItem.y;
if (typeof item.y != "undefined") {
assert.integer(item.y, error.pprint`Expected 'y' (${item.y}) to be an Integer`);
assert.integer(item.y,
pprint`Expected 'y' (${item.y}) to be an Integer`);
}
break;
case action.PointerCancel:
throw new UnsupportedOperationError();
break;
case action.Pause:
item.duration = actionItem.duration;
if (typeof item.duration != "undefined") {
// eslint-disable-next-line
assert.positiveInteger(item.duration,
error.pprint`Expected 'duration' (${item.duration}) to be >= 0`);
pprint`Expected 'duration' (${item.duration}) to be >= 0`);
}
break;
}
@ -732,7 +741,8 @@ action.Action = class {
};
/**
* Represents a series of ticks, specifying which actions to perform at each tick.
* Represents a series of ticks, specifying which actions to perform at
* each tick.
*/
action.Chain = class extends Array {
toString() {
@ -744,17 +754,18 @@ action.Chain = class extends Array {
* Array of objects that each represent an action sequence.
*
* @return {action.Chain}
* Transpose of |actions| such that actions to be performed in a single tick
* are grouped together.
* Transpose of |actions| such that actions to be performed in a
* single tick are grouped together.
*
* @throws {InvalidArgumentError}
* If |actions| is not an Array.
*/
static fromJson(actions) {
assert.array(actions,
error.pprint`Expected 'actions' to be an Array, got: ${actions}`);
pprint`Expected 'actions' to be an Array, got: ${actions}`);
let actionsByTick = new action.Chain();
// TODO check that each actionSequence in actions refers to a different input ID
// TODO check that each actionSequence in actions refers to a
// different input ID
for (let actionSequence of actions) {
let inputSourceActions = action.Sequence.fromJson(actionSequence);
for (let i = 0; i < inputSourceActions.length; i++) {
@ -770,7 +781,8 @@ action.Chain = class extends Array {
};
/**
* Represents one input source action sequence; this is essentially an |Array.<action.Action>|.
* Represents one input source action sequence; this is essentially an
* |Array.<action.Action>|.
*/
action.Sequence = class extends Array {
toString() {
@ -794,10 +806,10 @@ action.Sequence = class extends Array {
let inputSourceState = InputState.fromJson(actionSequence);
let id = actionSequence.id;
assert.defined(id, "Expected 'id' to be defined");
assert.string(id, error.pprint`Expected 'id' to be a string, got: ${id}`);
assert.string(id, pprint`Expected 'id' to be a string, got: ${id}`);
let actionItems = actionSequence.actions;
assert.array(actionItems,
error.pprint("Expected 'actionSequence.actions' to be an Array, " +
pprint("Expected 'actionSequence.actions' to be an Array, " +
`got: ${actionSequence.actions}`));
if (!action.inputStateMap.has(id)) {
action.inputStateMap.set(id, inputSourceState);
@ -818,7 +830,8 @@ action.Sequence = class extends Array {
* Represents parameters in an action for a pointer input source.
*
* @param {string=} pointerType
* Type of pointing device. If the parameter is undefined, "mouse" is used.
* Type of pointing device. If the parameter is undefined, "mouse"
* is used.
*/
action.PointerParameters = class {
constructor(pointerType = "mouse") {
@ -839,9 +852,8 @@ action.PointerParameters = class {
static fromJson(parametersData) {
if (typeof parametersData == "undefined") {
return new action.PointerParameters();
} else {
return new action.PointerParameters(parametersData.pointerType);
}
return new action.PointerParameters(parametersData.pointerType);
}
};
@ -860,14 +872,16 @@ action.PointerParameters = class {
* If |id| is already mapped to an |action.InputState| that is
* not compatible with |act.type| or |pointerParams.pointerType|.
*/
action.processPointerAction = function processPointerAction(id, pointerParams, act) {
if (action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type) {
action.processPointerAction = function(id, pointerParams, act) {
if (action.inputStateMap.has(id) &&
action.inputStateMap.get(id).type !== act.type) {
throw new InvalidArgumentError(
`Expected 'id' ${id} to be mapped to InputState whose type is ` +
`${action.inputStateMap.get(id).type}, got: ${act.type}`);
}
let pointerType = pointerParams.pointerType;
if (action.inputStateMap.has(id) && action.inputStateMap.get(id).subtype !== pointerType) {
if (action.inputStateMap.has(id) &&
action.inputStateMap.get(id).subtype !== pointerType) {
throw new InvalidArgumentError(
`Expected 'id' ${id} to be mapped to InputState whose subtype is ` +
`${action.inputStateMap.get(id).subtype}, got: ${pointerType}`);
@ -930,9 +944,10 @@ action.Mouse = class {
/**
* Dispatch a chain of actions over |chain.length| ticks.
*
* This is done by creating a Promise for each tick that resolves once all the
* Promises for individual tick-actions are resolved. The next tick's actions are
* not dispatched until the Promise for the current tick is resolved.
* This is done by creating a Promise for each tick that resolves once
* all the Promises for individual tick-actions are resolved. The next
* tick's actions are not dispatched until the Promise for the current
* tick is resolved.
*
* @param {action.Chain} chain
* Actions grouped by tick; each element in |chain| is a sequence of
@ -949,7 +964,10 @@ action.dispatch = function(chain, seenEls, container) {
let chainEvents = Task.spawn(function*() {
for (let tickActions of chain) {
yield action.dispatchTickActions(
tickActions, action.computeTickDuration(tickActions), seenEls, container);
tickActions,
action.computeTickDuration(tickActions),
seenEls,
container);
}
});
return chainEvents;
@ -958,12 +976,13 @@ action.dispatch = function(chain, seenEls, container) {
/**
* Dispatch sequence of actions for one tick.
*
* This creates a Promise for one tick that resolves once the Promise for each
* tick-action is resolved, which takes at least |tickDuration| milliseconds.
* The resolved set of events for each tick is followed by firing of pending DOM events.
* This creates a Promise for one tick that resolves once the Promise
* for each tick-action is resolved, which takes at least |tickDuration|
* milliseconds. The resolved set of events for each tick is followed by
* firing of pending DOM events.
*
* Note that the tick-actions are dispatched in order, but they may have different
* durations and therefore may not end in the same order.
* Note that the tick-actions are dispatched in order, but they may have
* different durations and therefore may not end in the same order.
*
* @param {Array.<action.Action>} tickActions
* List of actions for one tick.
@ -977,8 +996,10 @@ action.dispatch = function(chain, seenEls, container) {
* @return {Promise}
* Promise for dispatching all tick-actions and pending DOM events.
*/
action.dispatchTickActions = function(tickActions, tickDuration, seenEls, container) {
let pendingEvents = tickActions.map(toEvents(tickDuration, seenEls, container));
action.dispatchTickActions = function(
tickActions, tickDuration, seenEls, container) {
let pendingEvents = tickActions.map(
toEvents(tickDuration, seenEls, container));
return Promise.all(pendingEvents).then(
() => interaction.flushEventLoop(container.frame));
};
@ -1018,7 +1039,8 @@ action.computeTickDuration = function(tickActions) {
* @return {Map.<string, number>}
* x and y coordinates of pointer destination.
*/
action.computePointerDestination = function(a, inputState, center = undefined) {
action.computePointerDestination = function(
a, inputState, center = undefined) {
let {x, y} = a;
switch (a.origin) {
case action.PointerOrigin.Viewport:
@ -1053,7 +1075,7 @@ action.computePointerDestination = function(a, inputState, center = undefined) {
* the event that corresponds to that action.
*/
function toEvents(tickDuration, seenEls, container) {
return function (a) {
return a => {
let inputState = action.inputStateMap.get(a.id);
switch (a.subtype) {
case action.KeyUp:
@ -1069,7 +1091,8 @@ function toEvents(tickDuration, seenEls, container) {
return dispatchPointerUp(a, inputState, container.frame);
case action.PointerMove:
return dispatchPointerMove(a, inputState, tickDuration, seenEls, container);
return dispatchPointerMove(
a, inputState, tickDuration, seenEls, container);
case action.PointerCancel:
throw new UnsupportedOperationError();
@ -1077,6 +1100,7 @@ function toEvents(tickDuration, seenEls, container) {
case action.Pause:
return dispatchPause(a, tickDuration);
}
return undefined;
};
}
@ -1091,7 +1115,8 @@ function toEvents(tickDuration, seenEls, container) {
* Current window.
*
* @return {Promise}
* Promise to dispatch at least a keydown event, and keypress if appropriate.
* Promise to dispatch at least a keydown event, and keypress if
* appropriate.
*/
function dispatchKeyDown(a, inputState, win) {
return new Promise(resolve => {
@ -1161,22 +1186,31 @@ function dispatchPointerDown(a, inputState, win) {
resolve();
return;
}
inputState.press(a.button);
// Append a copy of |a| with pointerUp subtype
action.inputsToCancel.push(Object.assign({}, a, {subtype: action.PointerUp}));
let copy = Object.assign({}, a, {subtype: action.PointerUp});
action.inputsToCancel.push(copy);
switch (inputState.subtype) {
case action.PointerType.Mouse:
let mouseEvent = new action.Mouse("mousedown", a.button);
mouseEvent.update(inputState);
event.synthesizeMouseAtPoint(inputState.x, inputState.y, mouseEvent, win);
event.synthesizeMouseAtPoint(
inputState.x,
inputState.y,
mouseEvent,
win);
break;
case action.PointerType.Pen:
case action.PointerType.Touch:
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
break;
default:
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
}
resolve();
});
}
@ -1220,12 +1254,13 @@ function dispatchPointerUp(a, inputState, win) {
}
/**
* Dispatch a pointerMove action equivalent to moving pointer device in a line.
* Dispatch a pointerMove action equivalent to moving pointer device in
* a line.
*
* If the action duration is 0, the pointer jumps immediately to the target coordinates.
* Otherwise, events are synthesized to mimic a pointer travelling in a discontinuous,
* approximately straight line, with the pointer coordinates being updated around 60
* times per second.
* If the action duration is 0, the pointer jumps immediately to the
* target coordinates. Otherwise, events are synthesized to mimic a
* pointer travelling in a discontinuous, approximately straight line,
* with the pointer coordinates being updated around 60 times per second.
*
* @param {action.Action} a
* Action to dispatch.
@ -1237,10 +1272,11 @@ function dispatchPointerUp(a, inputState, win) {
* Object with |frame| attribute of type |nsIDOMWindow|.
*
* @return {Promise}
* Promise to dispatch at least one pointermove event, as well as mousemove events
* as appropriate.
* Promise to dispatch at least one pointermove event, as well as
* mousemove events as appropriate.
*/
function dispatchPointerMove(a, inputState, tickDuration, seenEls, container) {
function dispatchPointerMove(
a, inputState, tickDuration, seenEls, container) {
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
// interval between pointermove increments in ms, based on common vsync
const fps60 = 17;
@ -1253,7 +1289,8 @@ function dispatchPointerMove(a, inputState, tickDuration, seenEls, container) {
if (!inViewPort(targetX, targetY, container.frame)) {
throw new MoveTargetOutOfBoundsError(
`(${targetX}, ${targetY}) is out of bounds of viewport ` +
`width (${container.frame.innerWidth}) and height (${container.frame.innerHeight})`);
`width (${container.frame.innerWidth}) ` +
`and height (${container.frame.innerHeight})`);
}
const duration = typeof a.duration == "undefined" ? tickDuration : a.duration;
@ -1298,26 +1335,30 @@ function performOnePointerMove(inputState, targetX, targetY, win) {
if (targetX == inputState.x && targetY == inputState.y) {
return;
}
switch (inputState.subtype) {
case action.PointerType.Mouse:
let mouseEvent = new action.Mouse("mousemove");
mouseEvent.update(inputState);
//TODO both pointermove (if available) and mousemove
// TODO both pointermove (if available) and mousemove
event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win);
break;
case action.PointerType.Pen:
case action.PointerType.Touch:
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
default:
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
}
inputState.x = targetX;
inputState.y = targetY;
}
/**
* Dispatch a pause action equivalent waiting for |a.duration| milliseconds, or a
* default time interval of |tickDuration|.
* Dispatch a pause action equivalent waiting for |a.duration|
* milliseconds, or a default time interval of |tickDuration|.
*
* @param {action.Action} a
* Action to dispatch.
@ -1351,8 +1392,10 @@ function inViewPort(x, y, win) {
function getElementCenter(elementReference, seenEls, container) {
if (element.isWebElementReference(elementReference)) {
let uuid = elementReference[element.Key] || elementReference[element.LegacyKey];
let uuid = elementReference[element.Key] ||
elementReference[element.LegacyKey];
let el = seenEls.get(uuid, container);
return element.coordinates(el);
}
return {};
}

View File

@ -49,27 +49,27 @@ function lookupError(code) {
* @throws {UnknownError}
* If there is a problem installing the addon.
*/
addon.install = function (path, temporary = false) {
addon.install = function(path, temporary = false) {
return new Promise((resolve, reject) => {
let file = new FileUtils.File(path);
let listener = {
onInstallEnded: function (install, addon) {
onInstallEnded(install, addon) {
resolve(addon.id);
},
onInstallFailed: function (install) {
onInstallFailed(install) {
reject(lookupError(install.error));
},
onInstalled: function (addon) {
onInstalled(addon) {
AddonManager.removeAddonListener(listener);
resolve(addon.id);
}
},
};
if (!temporary) {
AddonManager.getInstallForFile(file, function (aInstall) {
AddonManager.getInstallForFile(file, function(aInstall) {
if (aInstall.error !== 0) {
reject(lookupError(aInstall.error));
}
@ -94,9 +94,9 @@ addon.install = function (path, temporary = false) {
*
* @return {Promise}
*/
addon.uninstall = function (id) {
addon.uninstall = function(id) {
return new Promise(resolve => {
AddonManager.getAddonByID(id, function (addon) {
AddonManager.getAddonByID(id, function(addon) {
addon.uninstall();
resolve();
});

View File

@ -41,7 +41,7 @@ this.assert = {};
* @throws {InvalidSessionIDError}
* If |driver| does not have a session ID.
*/
assert.session = function (driver, msg = "") {
assert.session = function(driver, msg = "") {
assert.that(sessionID => sessionID,
msg, InvalidSessionIDError)(driver.sessionId);
return driver.sessionId;
@ -56,7 +56,7 @@ assert.session = function (driver, msg = "") {
* @throws {UnsupportedOperationError}
* If current browser is not Firefox.
*/
assert.firefox = function (msg = "") {
assert.firefox = function(msg = "") {
msg = msg || "Only supported in Firefox";
assert.that(isFirefox, msg, UnsupportedOperationError)();
};
@ -70,7 +70,7 @@ assert.firefox = function (msg = "") {
* @throws {UnsupportedOperationError}
* If current browser is not Fennec.
*/
assert.fennec = function (msg = "") {
assert.fennec = function(msg = "") {
msg = msg || "Only supported in Fennec";
assert.that(isFennec, msg, UnsupportedOperationError)();
};
@ -89,7 +89,7 @@ assert.fennec = function (msg = "") {
* @throws {UnsupportedOperationError}
* If |context| is not content.
*/
assert.content = function (context, msg = "") {
assert.content = function(context, msg = "") {
msg = msg || "Only supported in content context";
assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context);
};
@ -108,7 +108,7 @@ assert.content = function (context, msg = "") {
* @throws {NoSuchWindowError}
* If |win| has been closed.
*/
assert.window = function (win, msg = "") {
assert.window = function(win, msg = "") {
msg = msg || "Unable to locate window";
return assert.that(w => w && !w.closed,
msg,
@ -126,7 +126,7 @@ assert.window = function (win, msg = "") {
* @throws {NoSuchWindowError}
* If |context| is invalid.
*/
assert.contentBrowser = function (context, msg = "") {
assert.contentBrowser = function(context, msg = "") {
// TODO: The contentBrowser uses a cached tab, which is only updated when
// switchToTab is called. Because of that an additional check is needed to
// make sure that the chrome window has not already been closed.
@ -149,7 +149,7 @@ assert.contentBrowser = function (context, msg = "") {
* @throws {UnexpectedAlertOpenError}
* If there is a user prompt.
*/
assert.noUserPrompt = function (dialog, msg = "") {
assert.noUserPrompt = function(dialog, msg = "") {
assert.that(d => d === null || typeof d == "undefined",
msg,
UnexpectedAlertOpenError)(dialog);
@ -169,7 +169,7 @@ assert.noUserPrompt = function (dialog, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not defined.
*/
assert.defined = function (obj, msg = "") {
assert.defined = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be defined`;
return assert.that(o => typeof o != "undefined", msg)(obj);
};
@ -188,7 +188,7 @@ assert.defined = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not a number.
*/
assert.number = function (obj, msg = "") {
assert.number = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be finite number`;
return assert.that(Number.isFinite, msg)(obj);
};
@ -207,7 +207,7 @@ assert.number = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not callable.
*/
assert.callable = function (obj, msg = "") {
assert.callable = function(obj, msg = "") {
msg = msg || error.pprint`${obj} is not callable`;
return assert.that(o => typeof o == "function", msg)(obj);
};
@ -226,7 +226,7 @@ assert.callable = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not an integer.
*/
assert.integer = function (obj, msg = "") {
assert.integer = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an integer`;
return assert.that(Number.isInteger, msg)(obj);
};
@ -245,7 +245,7 @@ assert.integer = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not a positive integer.
*/
assert.positiveInteger = function (obj, msg = "") {
assert.positiveInteger = function(obj, msg = "") {
assert.integer(obj, msg);
msg = msg || error.pprint`Expected ${obj} to be >= 0`;
return assert.that(n => n >= 0, msg)(obj);
@ -265,7 +265,7 @@ assert.positiveInteger = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not a boolean.
*/
assert.boolean = function (obj, msg = "") {
assert.boolean = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be boolean`;
return assert.that(b => typeof b == "boolean", msg)(obj);
};
@ -284,7 +284,7 @@ assert.boolean = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not a string.
*/
assert.string = function (obj, msg = "") {
assert.string = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be a string`;
return assert.that(s => typeof s == "string", msg)(obj);
};
@ -303,7 +303,7 @@ assert.string = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not an object.
*/
assert.object = function (obj, msg = "") {
assert.object = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an object`;
return assert.that(o => {
// unable to use instanceof because LHS and RHS may come from
@ -329,7 +329,7 @@ assert.object = function (obj, msg = "") {
* @throws {InvalidArgumentError}
* If |prop| is not in |obj|, or |obj| is not an object.
*/
assert.in = function (prop, obj, msg = "") {
assert.in = function(prop, obj, msg = "") {
assert.object(obj, msg);
msg = msg || error.pprint`Expected ${prop} in ${obj}`;
assert.that(p => obj.hasOwnProperty(p), msg)(prop);
@ -350,7 +350,7 @@ assert.in = function (prop, obj, msg = "") {
* @throws {InvalidArgumentError}
* If |obj| is not an Array.
*/
assert.array = function (obj, msg = "") {
assert.array = function(obj, msg = "") {
msg = msg || error.pprint`Expected ${obj} to be an Array`;
return assert.that(Array.isArray, msg)(obj);
};
@ -372,7 +372,7 @@ assert.array = function (obj, msg = "") {
* which may throw |error| with |message| if |predicate| evaluates
* to false.
*/
assert.that = function (
assert.that = function(
predicate, message = "", error = InvalidArgumentError) {
return obj => {
if (!predicate(obj)) {

View File

@ -29,18 +29,17 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
* @return {<xul:browser>}
* The linked browser for the tab or null if no browser can be found.
*/
browser.getBrowserForTab = function (tab) {
browser.getBrowserForTab = function(tab) {
// Fennec
if ("browser" in tab) {
// Fennec
return tab.browser;
// Firefox
} else if ("linkedBrowser" in tab) {
// Firefox
return tab.linkedBrowser;
} else {
return null;
}
return null;
};
/**
@ -52,18 +51,17 @@ browser.getBrowserForTab = function (tab) {
* @return {<xul:tabbrowser>}
* Tab browser or null if it's not a browser window.
*/
browser.getTabBrowser = function (win) {
browser.getTabBrowser = function(win) {
// Fennec
if ("BrowserApp" in win) {
// Fennec
return win.BrowserApp;
// Firefox
} else if ("gBrowser" in win) {
// Firefox
return win.gBrowser;
} else {
return null;
}
return null;
};
/**
@ -100,13 +98,13 @@ browser.Context = class {
this.seenEls = new element.Store();
// A reference to the tab corresponding to the current window handle, if any.
// Specifically, this.tab refers to the last tab that Marionette switched
// to in this browser window. Note that this may not equal the currently
// selected tab. For example, if Marionette switches to tab A, and then
// clicks on a button that opens a new tab B in the same browser window,
// this.tab will still point to tab A, despite tab B being the currently
// selected tab.
// A reference to the tab corresponding to the current window handle,
// if any. Specifically, this.tab refers to the last tab that Marionette
// switched to in this browser window. Note that this may not equal the
// currently selected tab. For example, if Marionette switches to tab
// A, and then clicks on a button that opens a new tab B in the same
// browser window, this.tab will still point to tab A, despite tab B
// being the currently selected tab.
this.tab = null;
this.pendingCommands = [];
@ -130,7 +128,8 @@ browser.Context = class {
get contentBrowser() {
if (this.tab) {
return browser.getBrowserForTab(this.tab);
} else if (this.tabBrowser && this.driver.isReftestBrowser(this.tabBrowser)) {
} else if (this.tabBrowser &&
this.driver.isReftestBrowser(this.tabBrowser)) {
return this.tabBrowser;
}
@ -144,7 +143,7 @@ browser.Context = class {
*/
get curFrameId() {
let rv = null;
if (this.tab || this.driver.isReftestBrowser(this.contentBrowser) ) {
if (this.tab || this.driver.isReftestBrowser(this.contentBrowser)) {
rv = this.getIdForBrowser(this.contentBrowser);
}
return rv;
@ -164,10 +163,9 @@ browser.Context = class {
// initialization been finished
if (this.contentBrowser) {
return this.contentBrowser.currentURI;
} else {
throw new NoSuchWindowError(
"Current window does not have a content browser");
}
throw new NoSuchWindowError(
"Current window does not have a content browser");
}
/**
@ -234,7 +232,10 @@ browser.Context = class {
closeTab() {
// If the current window is not a browser then close it directly. Do the
// same if only one remaining tab is open, or no tab selected at all.
if (!this.tabBrowser || !this.tabBrowser.tabs || this.tabBrowser.tabs.length === 1 || !this.tab) {
if (!this.tabBrowser ||
!this.tabBrowser.tabs ||
this.tabBrowser.tabs.length === 1 ||
!this.tab) {
return this.closeWindow();
}

View File

@ -34,7 +34,7 @@ capture.Format = {
* @return {HTMLCanvasElement}
* The canvas element where the element has been painted on.
*/
capture.element = function (node, highlights = []) {
capture.element = function(node, highlights = []) {
let win = node.ownerGlobal;
let rect = node.getBoundingClientRect();
@ -61,7 +61,7 @@ capture.element = function (node, highlights = []) {
* @return {HTMLCanvasElement}
* The canvas element where the viewport has been painted on.
*/
capture.viewport = function (win, highlights = []) {
capture.viewport = function(win, highlights = []) {
let rootNode = win.document.documentElement;
return capture.canvas(
@ -100,9 +100,9 @@ capture.viewport = function (win, highlights = []) {
* The canvas on which the selection from the window's framebuffer
* has been painted on.
*/
capture.canvas = function (win, left, top, width, height,
capture.canvas = function(win, left, top, width, height,
{highlights = [], canvas = null, flags = null} = {}) {
let scale = win.devicePixelRatio;
const scale = win.devicePixelRatio;
if (canvas === null) {
canvas = win.document.createElementNS(XHTML_NS, "canvas");
@ -113,12 +113,19 @@ capture.canvas = function (win, left, top, width, height,
let ctx = canvas.getContext(CONTEXT_2D);
if (flags === null) {
flags = ctx.DRAWWINDOW_DRAW_CARET;
// Disabled in bug 1243415 for webplatform-test failures due to out of view elements.
// Needs https://github.com/w3c/web-platform-tests/issues/4383 fixed.
// ctx.DRAWWINDOW_DRAW_VIEW;
// TODO(ato): https://bugzil.la/1377335
//
// Disabled in bug 1243415 for webplatform-test
// failures due to out of view elements. Needs
// https://github.com/w3c/web-platform-tests/issues/4383 fixed.
/*
ctx.DRAWWINDOW_DRAW_VIEW;
*/
// Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
// ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
}
/*
ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
*/
}
ctx.scale(scale, scale);
ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
@ -129,9 +136,9 @@ capture.canvas = function (win, left, top, width, height,
return canvas;
};
capture.highlight_ = function (context, highlights, top = 0, left = 0) {
capture.highlight_ = function(context, highlights, top = 0, left = 0) {
if (!highlights) {
return;
throw new TypeError("Missing highlights");
}
context.lineWidth = "2";
@ -162,7 +169,7 @@ capture.highlight_ = function (context, highlights, top = 0, left = 0) {
* @return {string}
* A Base64 encoded string.
*/
capture.toBase64 = function (canvas) {
capture.toBase64 = function(canvas) {
let u = canvas.toDataURL(PNG_MIME);
return u.substring(u.indexOf(",") + 1);
};
@ -176,7 +183,7 @@ capture.toBase64 = function (canvas) {
* @return {string}
* A hex digest of the SHA-256 hash of the base64 encoded string.
*/
capture.toHash = function (canvas) {
capture.toHash = function(canvas) {
let u = capture.toBase64(canvas);
let buffer = new TextEncoder("utf-8").encode(u);
return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
@ -197,9 +204,9 @@ function hex(buffer) {
for (let i = 0; i < view.byteLength; i += 4) {
let value = view.getUint32(i);
let stringValue = value.toString(16);
let padding = '00000000';
let padding = "00000000";
let paddedValue = (padding + stringValue).slice(-padding.length);
hexCodes.push(paddedValue);
}
return hexCodes.join("");
};
}

View File

@ -53,7 +53,7 @@ this.cert = {
* @throws {Components.Exception}
* If unable to register or initialise |service|.
*/
cert.installOverride = function (service) {
cert.installOverride = function(service) {
if (this.currentOverride) {
return;
}
@ -102,7 +102,7 @@ cert.InsecureSweepingOverride = function() {
// make your life miserable.
let service = function() {};
service.prototype = {
hasMatchingOverride: function (
hasMatchingOverride(
aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
aIsTemporary.value = false;
aOverrideBits.value =
@ -116,7 +116,7 @@ cert.InsecureSweepingOverride = function() {
let factory = XPCOMUtils.generateSingletonFactory(service);
return {
register: function() {
register() {
// make it possible to register certificate overrides for domains
// that use HSTS or HPKP
Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
@ -125,7 +125,7 @@ cert.InsecureSweepingOverride = function() {
registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
},
unregister: function() {
unregister() {
registrar.unregisterFactory(CID, factory);
Preferences.reset(HSTS_PRELOAD_LIST_PREF);

View File

@ -24,7 +24,7 @@ const PREF_LOG_LEVEL_FALLBACK = "marionette.logging";
const DEFAULT_LOG_LEVEL = "info";
const LOG_LEVELS = new class extends Map {
constructor () {
constructor() {
super([
["fatal", Log.Level.Fatal],
["error", Log.Level.Error],
@ -36,7 +36,7 @@ const LOG_LEVELS = new class extends Map {
]);
}
get (level) {
get(level) {
let s = new String(level).toLowerCase();
if (!this.has(s)) {
return DEFAULT_LOG_LEVEL;
@ -69,7 +69,7 @@ const ServerSocket = CC("@mozilla.org/network/server-socket;1",
// Get preference value of |preferred|, falling back to |fallback|
// if |preferred| is not user-modified and |fallback| exists.
function getPref (preferred, fallback) {
function getPref(preferred, fallback) {
if (!Preferences.isSet(preferred) && Preferences.has(fallback)) {
return Preferences.get(fallback, Preferences.get(preferred));
}
@ -82,16 +82,16 @@ function getPref (preferred, fallback) {
//
// This shim can be removed when Firefox 55 ships.
const prefs = {
get port () {
get port() {
return getPref(PREF_PORT, PREF_PORT_FALLBACK);
},
get logLevel () {
get logLevel() {
let s = getPref(PREF_LOG_LEVEL, PREF_LOG_LEVEL_FALLBACK);
return LOG_LEVELS.get(s);
},
readFromEnvironment (key) {
readFromEnvironment(key) {
const env = Cc["@mozilla.org/process/environment;1"]
.getService(Ci.nsIEnvironment);
@ -146,23 +146,14 @@ MarionetteComponent.prototype = {
helpInfo: " --marionette Enable remote control server.\n",
};
MarionetteComponent.prototype.onSocketAccepted = function (socket, transport) {
this.logger.info("onSocketAccepted for Marionette dummy socket");
};
MarionetteComponent.prototype.onStopListening = function (socket, status) {
this.logger.info(`onStopListening for Marionette dummy socket, code ${status}`);
socket.close();
};
// Handle -marionette flag
MarionetteComponent.prototype.handle = function (cmdLine) {
MarionetteComponent.prototype.handle = function(cmdLine) {
if (cmdLine.handleFlag("marionette", false)) {
this.enabled = true;
}
};
MarionetteComponent.prototype.observe = function (subject, topic, data) {
MarionetteComponent.prototype.observe = function(subject, topic, data) {
switch (topic) {
case "profile-after-change":
// Using sessionstore-windows-restored as the xpcom category doesn't
@ -227,14 +218,14 @@ MarionetteComponent.prototype.observe = function (subject, topic, data) {
}
};
MarionetteComponent.prototype.setupLogger = function (level) {
MarionetteComponent.prototype.setupLogger = function(level) {
let logger = Log.repository.getLogger("Marionette");
logger.level = level;
logger.addAppender(new Log.DumpAppender());
return logger;
};
MarionetteComponent.prototype.suppressSafeModeDialog = function (win) {
MarionetteComponent.prototype.suppressSafeModeDialog = function(win) {
win.addEventListener("load", () => {
if (win.document.getElementById("safeModeDialog")) {
// accept the dialog to start in safe-mode
@ -245,7 +236,7 @@ MarionetteComponent.prototype.suppressSafeModeDialog = function (win) {
}, {once: true});
};
MarionetteComponent.prototype.init = function () {
MarionetteComponent.prototype.init = function() {
if (this.running || !this.enabled || !this.finalUIStartup) {
return;
}
@ -270,7 +261,7 @@ MarionetteComponent.prototype.init = function () {
});
};
MarionetteComponent.prototype.uninit = function () {
MarionetteComponent.prototype.uninit = function() {
if (!this.running) {
return;
}

View File

@ -41,7 +41,7 @@ this.cookie = {
* @throws {InvalidArgumentError}
* If any of the properties are invalid.
*/
cookie.fromJSON = function (json) {
cookie.fromJSON = function(json) {
let newCookie = {};
assert.object(json, error.pprint`Expected cookie object, got ${json}`);
@ -85,7 +85,7 @@ cookie.fromJSON = function (json) {
* @throws {InvalidCookieDomainError}
* If |restrictToHost| is set and |newCookie|'s domain does not match.
*/
cookie.add = function (newCookie, opts = {}) {
cookie.add = function(newCookie, opts = {}) {
assert.string(newCookie.name, "Cookie name must be string");
assert.string(newCookie.value, "Cookie value must be string");
assert.string(newCookie.domain, "Cookie domain must be string");
@ -108,7 +108,8 @@ cookie.add = function (newCookie, opts = {}) {
if (newCookie.domain !== opts.restrictToHost) {
throw new InvalidCookieDomainError(
`Cookies may only be set for the current domain (${opts.restrictToHost})`);
`Cookies may only be set ` +
` for the current domain (${opts.restrictToHost})`);
}
}
@ -135,7 +136,7 @@ cookie.add = function (newCookie, opts = {}) {
* @param {Map.<string, (string|number|boolean)} toDelete
* Cookie to remove.
*/
cookie.remove = function (toDelete) {
cookie.remove = function(toDelete) {
cookie.manager.remove(
toDelete.domain,
toDelete.name,
@ -158,7 +159,7 @@ cookie.remove = function (toDelete) {
* @return {[Symbol.Iterator]}
* Iterator.
*/
cookie.iter = function* (host, currentPath = "/") {
cookie.iter = function*(host, currentPath = "/") {
assert.string(host, "host must be string");
assert.string(currentPath, "currentPath must be string");

File diff suppressed because it is too large Load Diff

View File

@ -247,7 +247,7 @@ element.Store = class {
* If a single element is requested, this error will throw if the
* element is not found.
*/
element.find = function (container, strategy, selector, opts = {}) {
element.find = function(container, strategy, selector, opts = {}) {
opts.all = !!opts.all;
opts.timeout = opts.timeout || 0;
@ -275,7 +275,8 @@ element.find = function (container, strategy, selector, opts = {}) {
let msg;
switch (strategy) {
case element.Strategy.AnonAttribute:
msg = "Unable to locate anonymous element: " + JSON.stringify(selector);
msg = "Unable to locate anonymous element: " +
JSON.stringify(selector);
break;
default:
@ -301,8 +302,8 @@ function find_(container, strategy, selector, searchFn, opts) {
startNode = opts.startNode;
} else {
switch (strategy) {
// For anonymous nodes the start node needs to be of type DOMElement, which
// will refer to :root in case of a DOMDocument.
// For anonymous nodes the start node needs to be of type
// DOMElement, which will refer to :root in case of a DOMDocument.
case element.Strategy.Anon:
case element.Strategy.AnonAttribute:
if (rootNode instanceof Ci.nsIDOMDocument) {
@ -345,7 +346,7 @@ function find_(container, strategy, selector, searchFn, opts) {
* @return {DOMElement}
* First element matching expression.
*/
element.findByXPath = function (root, startNode, expr) {
element.findByXPath = function(root, startNode, expr) {
let iter = root.evaluate(expr, startNode, null,
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
return iter.singleNodeValue;
@ -364,7 +365,7 @@ element.findByXPath = function (root, startNode, expr) {
* @return {Array.<DOMElement>}
* Sequence of found elements matching expression.
*/
element.findByXPathAll = function (root, startNode, expr) {
element.findByXPathAll = function(root, startNode, expr) {
let rv = [];
let iter = root.evaluate(expr, startNode, null,
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
@ -387,7 +388,7 @@ element.findByXPathAll = function (root, startNode, expr) {
* @return {Array.<DOMAnchorElement>}
* Sequence of link elements which text is |s|.
*/
element.findByLinkText = function (node, s) {
element.findByLinkText = function(node, s) {
return filterLinks(node, link => link.text.trim() === s);
};
@ -402,7 +403,7 @@ element.findByLinkText = function (node, s) {
* @return {Array.<DOMAnchorElement>}
* Sequence of link elements which text containins |s|.
*/
element.findByPartialLinkText = function (node, s) {
element.findByPartialLinkText = function(node, s) {
return filterLinks(node, link => link.text.indexOf(s) != -1);
};
@ -547,7 +548,8 @@ function findElements(using, value, rootNode, startNode) {
if (startNode.getElementsByName) {
return startNode.getElementsByName(value);
}
return element.findByXPathAll(rootNode, startNode, `.//*[@name="${value}"]`);
return element.findByXPathAll(
rootNode, startNode, `.//*[@name="${value}"]`);
case element.Strategy.ClassName:
return startNode.getElementsByClassName(value);
@ -569,7 +571,8 @@ function findElements(using, value, rootNode, startNode) {
case element.Strategy.AnonAttribute:
let attr = Object.keys(value)[0];
let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
let el = rootNode.getAnonymousElementByAttribute(
startNode, attr, value[attr]);
if (el) {
return [el];
}
@ -581,7 +584,7 @@ function findElements(using, value, rootNode, startNode) {
}
/** Determines if |obj| is an HTML or JS collection. */
element.isCollection = function (seq) {
element.isCollection = function(seq) {
switch (Object.prototype.toString.call(seq)) {
case "[object Arguments]":
case "[object Array]":
@ -598,7 +601,7 @@ element.isCollection = function (seq) {
}
};
element.makeWebElement = function (uuid) {
element.makeWebElement = function(uuid) {
return {
[element.Key]: uuid,
[element.LegacyKey]: uuid,
@ -606,16 +609,18 @@ element.makeWebElement = function (uuid) {
};
/**
* Checks if |ref| has either |element.Key| or |element.LegacyKey| as properties.
* Checks if |ref| has either |element.Key| or |element.LegacyKey|
* as properties.
*
* @param {?} ref
* Object that represents a web element reference.
* @return {boolean}
* True if |ref| has either expected property.
*/
element.isWebElementReference = function (ref) {
element.isWebElementReference = function(ref) {
let properties = Object.getOwnPropertyNames(ref);
return properties.includes(element.Key) || properties.includes(element.LegacyKey);
return properties.includes(element.Key) ||
properties.includes(element.LegacyKey);
};
element.generateUUID = function() {
@ -636,11 +641,11 @@ element.generateUUID = function() {
* @return {boolean}
* Flag indicating that the element is disconnected.
*/
element.isDisconnected = function (el, container = {}) {
element.isDisconnected = function(el, container = {}) {
const {frame, shadowRoot} = container;
assert.defined(frame);
// shadow dom
// shadow DOM
if (frame.ShadowRoot && shadowRoot) {
if (el.compareDocumentPosition(shadowRoot) &
DOCUMENT_POSITION_DISCONNECTED) {
@ -654,14 +659,13 @@ element.isDisconnected = function (el, container = {}) {
}
return element.isDisconnected(
shadowRoot.host,
{frame: frame, shadowRoot: parent});
// outside shadow dom
} else {
let docEl = frame.document.documentElement;
return el.compareDocumentPosition(docEl) &
DOCUMENT_POSITION_DISCONNECTED;
{frame, shadowRoot: parent});
}
// outside shadow DOM
let docEl = frame.document.documentElement;
return el.compareDocumentPosition(docEl) &
DOCUMENT_POSITION_DISCONNECTED;
};
/**
@ -684,7 +688,7 @@ element.isDisconnected = function (el, container = {}) {
* @throws TypeError
* If |xOffset| or |yOffset| are not numbers.
*/
element.coordinates = function (
element.coordinates = function(
node, xOffset = undefined, yOffset = undefined) {
let box = node.getBoundingClientRect();
@ -721,14 +725,14 @@ element.coordinates = function (
* @return {boolean}
* True if if |el| is in viewport, false otherwise.
*/
element.inViewport = function (el, x = undefined, y = undefined) {
element.inViewport = function(el, x = undefined, y = undefined) {
let win = el.ownerGlobal;
let c = element.coordinates(el, x, y);
let vp = {
top: win.pageYOffset,
left: win.pageXOffset,
bottom: (win.pageYOffset + win.innerHeight),
right: (win.pageXOffset + win.innerWidth)
right: (win.pageXOffset + win.innerWidth),
};
return (vp.left <= c.x + win.pageXOffset &&
@ -754,7 +758,7 @@ element.inViewport = function (el, x = undefined, y = undefined) {
* @return {Element}
* Container element of |el|.
*/
element.getContainer = function (el) {
element.getContainer = function(el) {
if (el.localName != "option") {
return el;
}
@ -796,7 +800,7 @@ element.getContainer = function (el) {
* @return {boolean}
* True if |el| is inside the viewport, or false otherwise.
*/
element.isInView = function (el) {
element.isInView = function(el) {
let originalPointerEvents = el.style.pointerEvents;
try {
el.style.pointerEvents = "auto";
@ -823,7 +827,7 @@ element.isInView = function (el) {
* @return {boolean}
* True if visible, false otherwise.
*/
element.isVisible = function (el, x = undefined, y = undefined) {
element.isVisible = function(el, x = undefined, y = undefined) {
let win = el.ownerGlobal;
// Bug 1094246: webdriver's isShown doesn't work with content xul
@ -860,7 +864,7 @@ element.isVisible = function (el, x = undefined, y = undefined) {
* @return {boolean}
* True if element is obscured, false otherwise.
*/
element.isObscured = function (el) {
element.isObscured = function(el) {
let tree = element.getPointerInteractablePaintTree(el);
return !el.contains(tree[0]);
};
@ -878,7 +882,7 @@ element.isObscured = function (el) {
* @return {Map.<string, number>}
* X and Y coordinates that denotes the in-view centre point of |rect|.
*/
element.getInViewCentrePoint = function (rect, win) {
element.getInViewCentrePoint = function(rect, win) {
const {max, min} = Math;
let x = {
@ -909,7 +913,7 @@ element.getInViewCentrePoint = function (rect, win) {
* @return {Array.<DOMElement>}
* Sequence of elements in paint order.
*/
element.getPointerInteractablePaintTree = function (el) {
element.getPointerInteractablePaintTree = function(el) {
const doc = el.ownerDocument;
const win = doc.defaultView;
const container = {frame: win};
@ -941,7 +945,7 @@ element.getPointerInteractablePaintTree = function (el) {
// TODO(ato): Not implemented.
// In fact, it's not defined in the spec.
element.isKeyboardInteractable = function (el) {
element.isKeyboardInteractable = function(el) {
return true;
};
@ -951,13 +955,13 @@ element.isKeyboardInteractable = function (el) {
* @param {DOMElement} el
* Element to scroll into view.
*/
element.scrollIntoView = function (el) {
element.scrollIntoView = function(el) {
if (el.scrollIntoView) {
el.scrollIntoView({block: "end", inline: "nearest", behavior: "instant"});
}
};
element.isXULElement = function (el) {
element.isXULElement = function(el) {
let ns = atom.getElementAttribute(el, "namespaceURI");
return ns.indexOf("there.is.only.xul") >= 0;
};
@ -971,7 +975,15 @@ const boolEls = {
form: ["novalidate"],
iframe: ["allowfullscreen"],
img: ["ismap"],
input: ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
input: [
"autofocus",
"checked",
"disabled",
"formnovalidate",
"multiple",
"readonly",
"required",
],
keygen: ["autofocus", "disabled"],
menuitem: ["checked", "default", "disabled"],
object: ["typemustmatch"],
@ -996,14 +1008,15 @@ const boolEls = {
* @return {boolean}
* True if the attribute is boolean, false otherwise.
*/
element.isBooleanAttribute = function (el, attr) {
element.isBooleanAttribute = function(el, attr) {
if (el.namespaceURI !== XMLNS) {
return false;
}
// global boolean attributes that apply to all HTML elements,
// except for custom elements
if ((attr == "hidden" || attr == "itemscope") && !el.localName.includes("-")) {
const customElement = !el.localName.includes("-");
if ((attr == "hidden" || attr == "itemscope") && customElement) {
return true;
}

View File

@ -45,7 +45,7 @@ const BUILTIN_ERRORS = new Set([
"URIError",
]);
this.EXPORTED_SYMBOLS = ["error"].concat(Array.from(ERRORS));
this.EXPORTED_SYMBOLS = ["error", "error.pprint"].concat(Array.from(ERRORS));
this.error = {};
@ -71,26 +71,26 @@ this.error = {};
* @return {boolean}
* True if error, false otherwise.
*/
error.isError = function (val) {
error.isError = function(val) {
if (val === null || typeof val != "object") {
return false;
} else if (val instanceof Ci.nsIException) {
return true;
} else {
// DOMRectList errors on string comparison
try {
let proto = Object.getPrototypeOf(val);
return BUILTIN_ERRORS.has(proto.toString());
} catch (e) {
return false;
}
}
// DOMRectList errors on string comparison
try {
let proto = Object.getPrototypeOf(val);
return BUILTIN_ERRORS.has(proto.toString());
} catch (e) {
return false;
}
};
/**
* Checks if obj is an object in the WebDriverError prototypal chain.
*/
error.isWebDriverError = function (obj) {
error.isWebDriverError = function(obj) {
return error.isError(obj) &&
("name" in obj && ERRORS.has(obj.name));
};
@ -109,7 +109,7 @@ error.isWebDriverError = function (obj) {
* If |err| is a WebDriverError, it is returned unmodified.
* Otherwise an UnknownError type is returned.
*/
error.wrap = function (err) {
error.wrap = function(err) {
if (error.isWebDriverError(err)) {
return err;
}
@ -120,7 +120,7 @@ error.wrap = function (err) {
* Unhandled error reporter. Dumps the error and its stacktrace to console,
* and reports error to the Browser Console.
*/
error.report = function (err) {
error.report = function(err) {
let msg = "Marionette threw an error: " + error.stringify(err);
dump(msg + "\n");
if (Cu.reportError) {
@ -131,7 +131,7 @@ error.report = function (err) {
/**
* Prettifies an instance of Error and its stacktrace to a string.
*/
error.stringify = function (err) {
error.stringify = function(err) {
try {
let s = err.toString();
if ("stack" in err) {
@ -148,16 +148,17 @@ error.stringify = function (err) {
*
* Usage:
*
* const {pprint} = Cu.import("chrome://marionette/content/error.js", {});
* let bool = {value: true};
* error.pprint`Expected boolean, got ${bool}`;
* pprint`Expected boolean, got ${bool}`;
* => 'Expected boolean, got [object Object] {"value": true}'
*
* let htmlElement = document.querySelector("input#foo");
* error.pprint`Expected element ${htmlElement}`;
* pprint`Expected element ${htmlElement}`;
* => 'Expected element <input id="foo" class="bar baz">'
*/
error.pprint = function (ss, ...values) {
function prettyObject (obj) {
error.pprint = function(ss, ...values) {
function prettyObject(obj) {
let proto = Object.prototype.toString.call(obj);
let s = "";
try {
@ -172,7 +173,7 @@ error.pprint = function (ss, ...values) {
return proto + " " + s;
}
function prettyElement (el) {
function prettyElement(el) {
let ident = [];
if (el.id) {
ident.push(`id="${el.id}"`);
@ -194,7 +195,6 @@ error.pprint = function (ss, ...values) {
res.push(ss[i]);
if (i < values.length) {
let val = values[i];
let typ = Object.prototype.toString.call(val);
let s;
try {
if (val && val.nodeType === 1) {
@ -222,7 +222,7 @@ class WebDriverError extends Error {
* Optional string describing error situation or Error instance
* to propagate.
*/
constructor (x) {
constructor(x) {
super(x);
this.name = this.constructor.name;
this.status = "webdriver error";
@ -233,7 +233,7 @@ class WebDriverError extends Error {
}
}
toJSON () {
toJSON() {
return {
error: this.status,
message: this.message || "",
@ -241,7 +241,7 @@ class WebDriverError extends Error {
}
}
static fromJSON (json) {
static fromJSON(json) {
if (typeof json.error == "undefined") {
let s = JSON.stringify(json);
throw new TypeError("Undeserialisable error type: " + s);
@ -263,7 +263,7 @@ class WebDriverError extends Error {
}
class ElementNotAccessibleError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "element not accessible";
}
@ -281,7 +281,7 @@ class ElementNotAccessibleError extends WebDriverError {
* will produce a nicer error message.
*/
class ElementClickInterceptedError extends WebDriverError {
constructor (obscuredEl = undefined, coords = undefined) {
constructor(obscuredEl = undefined, coords = undefined) {
let msg = "";
if (obscuredEl && coords) {
const doc = obscuredEl.ownerDocument;
@ -311,49 +311,49 @@ class ElementClickInterceptedError extends WebDriverError {
}
class ElementNotInteractableError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "element not interactable";
}
}
class InsecureCertificateError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "insecure certificate";
}
}
class InvalidArgumentError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "invalid argument";
}
}
class InvalidCookieDomainError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "invalid cookie domain";
}
}
class InvalidElementStateError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "invalid element state";
}
}
class InvalidSelectorError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "invalid selector";
}
}
class InvalidSessionIDError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "invalid session id";
}
@ -412,98 +412,98 @@ class JavaScriptError extends WebDriverError {
}
class MoveTargetOutOfBoundsError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "move target out of bounds";
}
}
class NoAlertOpenError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "no such alert";
}
}
class NoSuchElementError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "no such element";
}
}
class NoSuchFrameError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "no such frame";
}
}
class NoSuchWindowError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "no such window";
}
}
class ScriptTimeoutError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "script timeout";
}
}
class SessionNotCreatedError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "session not created";
}
}
class StaleElementReferenceError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "stale element reference";
}
}
class TimeoutError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "timeout";
}
}
class UnableToSetCookieError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "unable to set cookie";
}
}
class UnexpectedAlertOpenError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "unexpected alert open";
}
}
class UnknownCommandError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "unknown command";
}
}
class UnknownError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "unknown error";
}
}
class UnsupportedOperationError extends WebDriverError {
constructor (message) {
constructor(message) {
super(message);
this.status = "unsupported operation";
}

View File

@ -70,7 +70,7 @@ this.evaluate = {};
*
* @param {nsISandbox) sb
* The sandbox the script will be evaluted in.
* @param {string} script
* @param {string} script
* The script to evaluate.
* @param {Array.<?>=} args
* A sequence of arguments to call the script with.
@ -99,12 +99,12 @@ this.evaluate = {};
* the script. Note that the return value requires serialisation before
* it can be sent to the client.
*
* @throws JavaScriptError
* @throws {JavaScriptError}
* If an Error was thrown whilst evaluating the script.
* @throws ScriptTimeoutError
* @throws {ScriptTimeoutError}
* If the script was interrupted due to script timeout.
*/
evaluate.sandbox = function (sb, script, args = [], opts = {}) {
evaluate.sandbox = function(sb, script, args = [], opts = {}) {
let scriptTimeoutID, timeoutHandler, unloadHandler;
let promise = new Promise((resolve, reject) => {
@ -112,7 +112,7 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
sb[COMPLETE] = resolve;
timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
unloadHandler = sandbox.cloneInto(
() => reject(new JavaScriptError("Document was unloaded during execution")),
() => reject(new JavaScriptError("Document was unloaded")),
sb);
// wrap in function
@ -151,12 +151,16 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
}
// timeout and unload handlers
scriptTimeoutID = setTimeout(timeoutHandler, opts.timeout || DEFAULT_TIMEOUT);
const timeout = opts.timeout || DEFAULT_TIMEOUT;
scriptTimeoutID = setTimeout(timeoutHandler, timeout);
sb.window.onunload = unloadHandler;
const file = opts.filename || "dummy file";
const line = opts.line || 0;
let res;
try {
res = Cu.evalInSandbox(src, sb, "1.8", opts.filename || "dummy file", 0);
res = Cu.evalInSandbox(src, sb, "1.8", file, 0);
} catch (e) {
let err = new JavaScriptError(e, {
fnName: "execute_script",
@ -196,42 +200,42 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
* Same object as provided by |obj| with the web elements replaced
* by DOM elements.
*/
evaluate.fromJSON = function (obj, seenEls, win, shadowRoot = undefined) {
evaluate.fromJSON = function(obj, seenEls, win, shadowRoot = undefined) {
switch (typeof obj) {
case "boolean":
case "number":
case "string":
default:
return obj;
case "object":
if (obj === null) {
return obj;
}
// arrays
else if (Array.isArray(obj)) {
} else if (Array.isArray(obj)) {
return obj.map(e => evaluate.fromJSON(e, seenEls, win, shadowRoot));
}
// web elements
else if (Object.keys(obj).includes(element.Key) ||
} else if (Object.keys(obj).includes(element.Key) ||
Object.keys(obj).includes(element.LegacyKey)) {
/* eslint-disable */
let uuid = obj[element.Key] || obj[element.LegacyKey];
let el = seenEls.get(uuid, {frame: win, shadowRoot: shadowRoot});
/* eslint-enable */
if (!el) {
throw new WebDriverError(`Unknown element: ${uuid}`);
}
return el;
}
// arbitrary objects
else {
let rv = {};
for (let prop in obj) {
rv[prop] = evaluate.fromJSON(obj[prop], seenEls, win, shadowRoot);
}
return rv;
let rv = {};
for (let prop in obj) {
rv[prop] = evaluate.fromJSON(obj[prop], seenEls, win, shadowRoot);
}
return rv;
}
};
@ -251,32 +255,30 @@ evaluate.fromJSON = function (obj, seenEls, win, shadowRoot = undefined) {
* Same object as provided by |obj| with the elements replaced by
* web elements.
*/
evaluate.toJSON = function (obj, seenEls) {
evaluate.toJSON = function(obj, seenEls) {
const t = Object.prototype.toString.call(obj);
// null
if (t == "[object Undefined]" || t == "[object Null]") {
return null;
}
// literals
else if (t == "[object Boolean]" || t == "[object Number]" || t == "[object String]") {
} else if (t == "[object Boolean]" ||
t == "[object Number]" ||
t == "[object String]") {
return obj;
}
// Array, NodeList, HTMLCollection, et al.
else if (element.isCollection(obj)) {
} else if (element.isCollection(obj)) {
return [...obj].map(el => evaluate.toJSON(el, seenEls));
}
// HTMLElement
else if ("nodeType" in obj && obj.nodeType == obj.ELEMENT_NODE) {
} else if ("nodeType" in obj && obj.nodeType == obj.ELEMENT_NODE) {
let uuid = seenEls.add(obj);
return element.makeWebElement(uuid);
}
// custom JSON representation
else if (typeof obj["toJSON"] == "function") {
} else if (typeof obj["toJSON"] == "function") {
let unsafeJSON = obj.toJSON();
return evaluate.toJSON(unsafeJSON, seenEls);
}
@ -293,8 +295,8 @@ evaluate.toJSON = function (obj, seenEls) {
throw e;
}
}
return rv;
}
return rv;
};
this.sandbox = {};
@ -307,7 +309,7 @@ this.sandbox = {};
* Unlike for |Components.utils.cloneInto|, |obj| may contain functions
* and DOM elemnets.
*/
sandbox.cloneInto = function (obj, sb) {
sandbox.cloneInto = function(obj, sb) {
return Cu.cloneInto(obj, sb, {cloneFunctions: true, wrapReflectors: true});
};
@ -316,7 +318,7 @@ sandbox.cloneInto = function (obj, sb) {
* map property, or a normal map, of function names and function
* references.
*
* @param {Sandbox} sb
* @param {Sandbox} sb
* The sandbox to augment.
* @param {Object} adapter
* Object that holds an {@code exports} property, or a map, of
@ -325,11 +327,11 @@ sandbox.cloneInto = function (obj, sb) {
* @return {Sandbox}
* The augmented sandbox.
*/
sandbox.augment = function (sb, adapter) {
sandbox.augment = function(sb, adapter) {
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
let funcs = adapter.exports || entries(adapter);
@ -352,7 +354,7 @@ sandbox.augment = function (sb, adapter) {
* @return {Sandbox}
* The created sandbox.
*/
sandbox.create = function (window, principal = null, opts = {}) {
sandbox.create = function(window, principal = null, opts = {}) {
let p = principal || window;
opts = Object.assign({
sameZoneAs: window,
@ -373,7 +375,7 @@ sandbox.create = function (window, principal = null, opts = {}) {
* @return {Sandbox}
* The created sandbox.
*/
sandbox.createMutable = function (window) {
sandbox.createMutable = function(window) {
let opts = {
wantComponents: false,
wantXrays: false,
@ -381,13 +383,13 @@ sandbox.createMutable = function (window) {
return sandbox.create(window, null, opts);
};
sandbox.createSystemPrincipal = function (window) {
sandbox.createSystemPrincipal = function(window) {
let principal = Cc["@mozilla.org/systemprincipal;1"]
.createInstance(Ci.nsIPrincipal);
return sandbox.create(window, principal);
};
sandbox.createSimpleTest = function (window, harness) {
sandbox.createSimpleTest = function(window, harness) {
let sb = sandbox.create(window);
sb = sandbox.augment(sb, harness);
sb[FINISH] = () => sb[COMPLETE](harness.generate_results());

View File

@ -2,7 +2,8 @@
* 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/. */
// Provides functionality for creating and sending DOM events.
/** Provides functionality for creating and sending DOM events. */
this.event = {};
"use strict";
/* global content, is */
@ -37,8 +38,6 @@ function getDOMWindowUtils(win) {
.getInterface(Ci.nsIDOMWindowUtils);
}
this.event = {};
event.MouseEvents = {
click: 0,
dblclick: 1,
@ -68,13 +67,14 @@ event.Modifiers = {
* @throws {TypeError}
* If the event is unsupported.
*/
event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
event.sendMouseEvent = function(mouseEvent, target, window = undefined) {
if (!event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
throw new TypeError("Unsupported event type: " + mouseEvent.type);
}
if (!target.nodeType && typeof target != "string") {
throw new TypeError("Target can only be a DOM element or a string: " + target);
throw new TypeError(
"Target can only be a DOM element or a string: " + target);
}
if (!target.nodeType) {
@ -85,7 +85,6 @@ event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
let ev = window.document.createEvent("MouseEvent");
let type = mouseEvent.type;
let view = window;
let detail = mouseEvent.detail;
@ -138,7 +137,7 @@ event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
* For now this method only works for English letters (lower and upper
* case) and the digits 0-9.
*/
event.sendChar = function (char, window = undefined) {
event.sendChar = function(char, window = undefined) {
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
let hasShift = (char == char.toUpperCase());
event.synthesizeKey(char, {shiftKey: hasShift}, window);
@ -150,7 +149,7 @@ event.sendChar = function (char, window = undefined) {
* For now this method only works for English letters (lower and upper
* case) and the digits 0-9.
*/
event.sendString = function (string, window = undefined) {
event.sendString = function(string, window = undefined) {
for (let i = 0; i < string.length; ++i) {
event.sendChar(string.charAt(i), window);
}
@ -163,14 +162,14 @@ event.sendString = function (string, window = undefined) {
* in the nsIDOMKeyEvent constant name for this key. No modifiers are
* handled at this point.
*/
event.sendKey = function (key, window = undefined) {
event.sendKey = function(key, window = undefined) {
let keyName = "VK_" + key.toUpperCase();
event.synthesizeKey(keyName, {shiftKey: false}, window);
};
// TODO(ato): Unexpose this when action.Chain#emitMouseEvent
// no longer emits its own events
event.parseModifiers_ = function (modifiers) {
event.parseModifiers_ = function(modifiers) {
let mval = 0;
if (modifiers.shiftKey) {
mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
@ -217,7 +216,7 @@ event.parseModifiers_ = function (modifiers) {
* @param {Window=} window
* Window object. Defaults to the current window.
*/
event.synthesizeMouse = function (
event.synthesizeMouse = function(
element, offsetX, offsetY, opts, window = undefined) {
let rect = element.getBoundingClientRect();
event.synthesizeMouseAtPoint(
@ -241,7 +240,7 @@ event.synthesizeMouse = function (
* @param {Window=} window
* Window object. Defaults to the current window.
*/
event.synthesizeMouseAtPoint = function (
event.synthesizeMouseAtPoint = function(
left, top, opts, window = undefined) {
let domutils = getDOMWindowUtils(window);
@ -254,21 +253,60 @@ event.synthesizeMouseAtPoint = function (
Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
let isDOMEventSynthesized =
("isSynthesized" in opts) ? opts.isSynthesized : true;
let isWidgetEventSynthesized =
("isWidgetEventSynthesized" in opts) ? opts.isWidgetEventSynthesized : false;
let buttons = ("buttons" in opts) ? opts.buttons : domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
let isWidgetEventSynthesized;
if ("isWidgetEventSynthesized" in opts) {
isWidgetEventSynthesized = opts.isWidgetEventSynthesized;
} else {
isWidgetEventSynthesized = false;
}
let buttons;
if ("buttons" in opts) {
buttons = opts.buttons;
} else {
buttons = domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
}
if (("type" in opts) && opts.type) {
domutils.sendMouseEvent(
opts.type, left, top, button, clickCount, modifiers, false, pressure, inputSource,
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
opts.type,
left,
top,
button,
clickCount,
modifiers,
false,
pressure,
inputSource,
isDOMEventSynthesized,
isWidgetEventSynthesized,
buttons);
} else {
domutils.sendMouseEvent(
"mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource,
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
"mousedown",
left,
top,
button,
clickCount,
modifiers,
false,
pressure,
inputSource,
isDOMEventSynthesized,
isWidgetEventSynthesized,
buttons);
domutils.sendMouseEvent(
"mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource,
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
"mouseup",
left,
top,
button,
clickCount,
modifiers,
false,
pressure,
inputSource,
isDOMEventSynthesized,
isWidgetEventSynthesized,
buttons);
}
};
@ -276,7 +314,7 @@ event.synthesizeMouseAtPoint = function (
* Call event.synthesizeMouse with coordinates at the centre of the
* target.
*/
event.synthesizeMouseAtCenter = function (element, event, window) {
event.synthesizeMouseAtCenter = function(element, event, window) {
let rect = element.getBoundingClientRect();
event.synthesizeMouse(
element,
@ -286,6 +324,7 @@ event.synthesizeMouseAtCenter = function (element, event, window) {
window);
};
/* eslint-disable */
function computeKeyCodeFromChar_(char) {
if (char.length != 1) {
return 0;
@ -388,6 +427,7 @@ function computeKeyCodeFromChar_(char) {
return 0;
}
}
/* eslint-enable */
/**
* Returns true if the given key should cause keypress event when widget
@ -396,7 +436,7 @@ function computeKeyCodeFromChar_(char) {
* The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
* or a key name begins with "VK_", or a character.
*/
event.isKeypressFiredKey = function (key) {
event.isKeypressFiredKey = function(key) {
if (typeof key == "string") {
if (key.indexOf("VK_") === 0) {
key = Ci.nsIDOMKeyEvent["DOM_" + key];
@ -445,8 +485,7 @@ event.isKeypressFiredKey = function (key) {
* @throws {TypeError}
* If unknown key.
*/
event.synthesizeKey = function (key, event, win = undefined)
{
event.synthesizeKey = function(key, event, win = undefined) {
var TIP = getTIP_(win);
if (!TIP) {
return;
@ -458,7 +497,7 @@ event.synthesizeKey = function (key, event, win = undefined)
var dispatchKeydown =
!("type" in event) || event.type === "keydown" || !event.type;
var dispatchKeyup =
!("type" in event) || event.type === "keyup" || !event.type;
!("type" in event) || event.type === "keyup" || !event.type;
try {
if (dispatchKeydown) {
@ -481,8 +520,7 @@ event.synthesizeKey = function (key, event, win = undefined)
var TIPMap = new WeakMap();
function getTIP_(win, callback)
{
function getTIP_(win, callback) {
if (!win) {
win = window;
}
@ -502,12 +540,12 @@ function getTIP_(win, callback)
return tip;
}
function getKeyboardEvent_(win = window)
{
function getKeyboardEvent_(win = window) {
if (typeof KeyboardEvent != "undefined") {
try {
// See if the object can be instantiated; sometimes this yields
// 'TypeError: can't access dead object' or 'KeyboardEvent is not a constructor'.
// 'TypeError: can't access dead object' or 'KeyboardEvent is not
// a constructor'.
new KeyboardEvent("", {});
return KeyboardEvent;
} catch (ex) {}
@ -519,8 +557,9 @@ function getKeyboardEvent_(win = window)
}
function createKeyboardEventDictionary_(key, keyEvent, win = window) {
var result = { dictionary: null, flags: 0 };
var keyCodeIsDefined = "keyCode" in keyEvent && keyEvent.keyCode != undefined;
var result = {dictionary: null, flags: 0};
var keyCodeIsDefined = "keyCode" in keyEvent &&
keyEvent.keyCode != undefined;
var keyCode =
(keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
keyEvent.keyCode : 0;
@ -535,7 +574,7 @@ function createKeyboardEventDictionary_(key, keyEvent, win = window) {
}
keyName = guessKeyNameFromKeyCode_(keyCode, win);
if (!isPrintable(keyCode, win)) {
result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
}
} else if (key != "") {
keyName = key;
@ -559,92 +598,90 @@ function createKeyboardEventDictionary_(key, keyEvent, win = window) {
code: "code" in keyEvent ? keyEvent.code : "",
location: locationIsDefined ? keyEvent.location : 0,
repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
keyCode: keyCode,
keyCode,
};
return result;
}
function emulateToActivateModifiers_(TIP, keyEvent, win = window)
{
function emulateToActivateModifiers_(TIP, keyEvent, win = window) {
if (!keyEvent) {
return null;
}
var KeyboardEvent = getKeyboardEvent_(win);
var navigator = getNavigator_(win);
var modifiers = {
normal: [
{ key: "Alt", attr: "altKey" },
{ key: "AltGraph", attr: "altGraphKey" },
{ key: "Control", attr: "ctrlKey" },
{ key: "Fn", attr: "fnKey" },
{ key: "Meta", attr: "metaKey" },
{ key: "OS", attr: "osKey" },
{ key: "Shift", attr: "shiftKey" },
{ key: "Symbol", attr: "symbolKey" },
{ key: isMac_(win) ? "Meta" : "Control",
attr: "accelKey" },
{key: "Alt", attr: "altKey"},
{key: "AltGraph", attr: "altGraphKey"},
{key: "Control", attr: "ctrlKey"},
{key: "Fn", attr: "fnKey"},
{key: "Meta", attr: "metaKey"},
{key: "OS", attr: "osKey"},
{key: "Shift", attr: "shiftKey"},
{key: "Symbol", attr: "symbolKey"},
{key: isMac_(win) ? "Meta" : "Control", attr: "accelKey"},
],
lockable: [
{ key: "CapsLock", attr: "capsLockKey" },
{ key: "FnLock", attr: "fnLockKey" },
{ key: "NumLock", attr: "numLockKey" },
{ key: "ScrollLock", attr: "scrollLockKey" },
{ key: "SymbolLock", attr: "symbolLockKey" },
]
{key: "CapsLock", attr: "capsLockKey"},
{key: "FnLock", attr: "fnLockKey"},
{key: "NumLock", attr: "numLockKey"},
{key: "ScrollLock", attr: "scrollLockKey"},
{key: "SymbolLock", attr: "symbolLockKey"},
],
}
for (var i = 0; i < modifiers.normal.length; i++) {
for (let i = 0; i < modifiers.normal.length; i++) {
if (!keyEvent[modifiers.normal[i].attr]) {
continue;
}
if (TIP.getModifierState(modifiers.normal[i].key)) {
continue; // already activated.
}
var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
let event = new KeyboardEvent("", {key: modifiers.normal[i].key});
TIP.keydown(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
modifiers.normal[i].activated = true;
}
for (var i = 0; i < modifiers.lockable.length; i++) {
if (!keyEvent[modifiers.lockable[i].attr]) {
for (let j = 0; j < modifiers.lockable.length; j++) {
if (!keyEvent[modifiers.lockable[j].attr]) {
continue;
}
if (TIP.getModifierState(modifiers.lockable[i].key)) {
if (TIP.getModifierState(modifiers.lockable[j].key)) {
continue; // already activated.
}
var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
let event = new KeyboardEvent("", {key: modifiers.lockable[j].key});
TIP.keydown(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
TIP.keyup(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
modifiers.lockable[i].activated = true;
modifiers.lockable[j].activated = true;
}
return modifiers;
}
function emulateToInactivateModifiers_(TIP, modifiers, win = window)
{
function emulateToInactivateModifiers_(TIP, modifiers, win = window) {
if (!modifiers) {
return;
}
var KeyboardEvent = getKeyboardEvent_(win);
for (var i = 0; i < modifiers.normal.length; i++) {
let KeyboardEvent = getKeyboardEvent_(win);
for (let i = 0; i < modifiers.normal.length; i++) {
if (!modifiers.normal[i].activated) {
continue;
}
var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
let event = new KeyboardEvent("", {key: modifiers.normal[i].key});
TIP.keyup(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
}
for (var i = 0; i < modifiers.lockable.length; i++) {
if (!modifiers.lockable[i].activated) {
for (let j = 0; j < modifiers.lockable.length; j++) {
if (!modifiers.lockable[j].activated) {
continue;
}
if (!TIP.getModifierState(modifiers.lockable[i].key)) {
if (!TIP.getModifierState(modifiers.lockable[j].key)) {
continue; // who already inactivated this?
}
var event = new KeyboardEvent("", { key: modifiers.lockable[i].key });
let event = new KeyboardEvent("", {key: modifiers.lockable[j].key});
TIP.keydown(event,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
TIP.keyup(event,
@ -661,8 +698,8 @@ function isMac_(win = window) {
return navigator.platform.indexOf("Mac") > -1;
}
function guessKeyNameFromKeyCode_(aKeyCode, win = window)
{
/* eslint-disable */
function guessKeyNameFromKeyCode_(aKeyCode, win = window) {
var KeyboardEvent = getKeyboardEvent_(win);
switch (aKeyCode) {
case KeyboardEvent.DOM_VK_CANCEL:
@ -807,11 +844,13 @@ function guessKeyNameFromKeyCode_(aKeyCode, win = window)
return "Unidentified";
}
}
/* eslint-enable */
/**
* Indicate that an event with an original target and type is expected
* to be fired, or not expected to be fired.
*/
/* eslint-disable */
function expectEvent_(expectedTarget, expectedEvent, testName) {
if (!expectedTarget || !expectedEvent) {
return null;
@ -835,6 +874,7 @@ function expectEvent_(expectedTarget, expectedEvent, testName) {
expectedTarget.addEventListener(type, handler);
return handler;
}
/* eslint-enable */
/**
* Check if the event was fired or not. The provided event handler will
@ -887,7 +927,7 @@ function checkExpectedEvent_(
* @param {Window=} window
* Window object. Defaults to the current window.
*/
event.synthesizeMouseExpectEvent = function (
event.synthesizeMouseExpectEvent = function(
target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
testName, window = undefined) {
@ -926,7 +966,7 @@ event.synthesizeMouseExpectEvent = function (
*
* aWindow is optional, and defaults to the current window object.
*/
event.synthesizeKeyExpectEvent = function (
event.synthesizeKeyExpectEvent = function(
key, ev, expectedTarget, expectedEvent, testName,
window = undefined) {
@ -955,7 +995,7 @@ event.synthesizeKeyExpectEvent = function (
* @param {Window=} window
* Window object. Defaults to the current window.
*/
event.synthesizeComposition = function (ev, window = undefined) {
event.synthesizeComposition = function(ev, window = undefined) {
let domutils = getDOMWindowUtils(window);
domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
};
@ -1000,7 +1040,7 @@ event.synthesizeComposition = function (ev, window = undefined) {
* @param {Window=} window
* Window object. Defaults to the current window.
*/
event.synthesizeText = function (ev, window = undefined) {
event.synthesizeText = function(ev, window = undefined) {
let domutils = getDOMWindowUtils(window);
if (!ev.composition ||
@ -1052,7 +1092,7 @@ event.synthesizeText = function (ev, window = undefined) {
* @return {(nsIQueryContentEventResult|null)}
* Event's result, or null if it failed.
*/
event.synthesizeQuerySelectedText = function (window = undefined) {
event.synthesizeQuerySelectedText = function(window = undefined) {
let domutils = getDOMWindowUtils(window);
return domutils.sendQueryContentEvent(
domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
@ -1075,7 +1115,7 @@ event.synthesizeQuerySelectedText = function (window = undefined) {
*
* @return True, if succeeded. Otherwise false.
*/
event.synthesizeSelectionSet = function (
event.synthesizeSelectionSet = function(
offset, length, reverse, window = undefined) {
let domutils = getDOMWindowUtils(window);
return domutils.sendSelectionSetEvent(offset, length, reverse);
@ -1228,19 +1268,21 @@ function isPrintable(c, win = window) {
return !(NON_PRINT_KEYS.includes(c));
}
event.sendKeyDown = function (keyToSend, modifiers, document) {
event.sendKeyDown = function(keyToSend, modifiers, document) {
modifiers.type = "keydown";
event.sendSingleKey(keyToSend, modifiers, document);
// TODO This doesn't do anything since |synthesizeKeyEvent| ignores explicit
// keypress request, and instead figures out itself when to send keypress
if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
// TODO: This doesn't do anything since |synthesizeKeyEvent| ignores
// explicit keypress request, and instead figures out itself when to
// send keypress.
if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"]
.indexOf(getKeyCode(keyToSend)) < 0) {
modifiers.type = "keypress";
event.sendSingleKey(keyToSend, modifiers, document);
}
delete modifiers.type;
};
event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
event.sendKeyUp = function(keyToSend, modifiers, window = undefined) {
modifiers.type = "keyup";
event.sendSingleKey(keyToSend, modifiers, window);
delete modifiers.type;
@ -1253,17 +1295,18 @@ event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
* Code point or normalized key value
* @param {?} modifiers
* Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
* as well as, the event |type| such as keydown. All properties are optional.
* as well as, the event |type| such as keydown. All properties
* are optional.
* @param {Window=} window
* Window object. If |window| is undefined, the event is synthesized in
* current window.
* Window object. If |window| is undefined, the event is synthesized
* in current window.
*/
event.sendSingleKey = function (keyToSend, modifiers, window = undefined) {
event.sendSingleKey = function(keyToSend, modifiers, window = undefined) {
let keyName = getKeyCode(keyToSend);
if (keyName in KEYCODES_LOOKUP) {
// We assume that if |keyToSend| is a raw code point (like "\uE009") then
// |modifiers| does not already have correct value for corresponding
// |modName| attribute (like ctrlKey), so that value needs to be flipped
// We assume that if |keyToSend| is a raw code point (like "\uE009")
// then |modifiers| does not already have correct value for corresponding
// |modName| attribute (like ctrlKey), so that value needs to be flipped.
let modName = KEYCODES_LOOKUP[keyName];
modifiers[modName] = !modifiers[modName];
} else if (modifiers.shiftKey && keyName != "Shift") {
@ -1296,7 +1339,7 @@ function focusElement(element) {
* @param {Object.<string, boolean>=} opts
* @param {Window=} window
*/
event.sendKeysToElement = function (
event.sendKeysToElement = function(
keyString, el, opts = {}, window = undefined) {
if (opts.ignoreVisibility || element.isVisible(el)) {
@ -1318,7 +1361,7 @@ event.sendKeysToElement = function (
}
};
event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
event.sendEvent = function(eventType, el, modifiers = {}, opts = {}) {
opts.canBubble = opts.canBubble || true;
let doc = el.ownerDocument || el.document;
@ -1333,7 +1376,7 @@ event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
el.dispatchEvent(ev);
};
event.focus = function (el, opts = {}) {
event.focus = function(el, opts = {}) {
opts.canBubble = opts.canBubble || true;
let doc = el.ownerDocument || el.document;
let win = doc.defaultView;
@ -1343,30 +1386,30 @@ event.focus = function (el, opts = {}) {
el.dispatchEvent(ev);
};
event.mouseover = function (el, modifiers = {}, opts = {}) {
event.mouseover = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("mouseover", el, modifiers, opts);
};
event.mousemove = function (el, modifiers = {}, opts = {}) {
event.mousemove = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("mousemove", el, modifiers, opts);
};
event.mousedown = function (el, modifiers = {}, opts = {}) {
event.mousedown = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("mousedown", el, modifiers, opts);
};
event.mouseup = function (el, modifiers = {}, opts = {}) {
event.mouseup = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("mouseup", el, modifiers, opts);
};
event.click = function (el, modifiers = {}, opts = {}) {
event.click = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("click", el, modifiers, opts);
};
event.change = function (el, modifiers = {}, opts = {}) {
event.change = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("change", el, modifiers, opts);
};
event.input = function (el, modifiers = {}, opts = {}) {
event.input = function(el, modifiers = {}, opts = {}) {
return event.sendEvent("input", el, modifiers, opts);
};

View File

@ -22,7 +22,7 @@ var remoteFrames = [];
* An object representing a frame that Marionette has loaded a
* frame script in.
*/
frame.RemoteFrame = function (windowId, frameId) {
frame.RemoteFrame = function(windowId, frameId) {
// outerWindowId relative to main process
this.windowId = windowId;
// actual frame relative to the windowId's frames list
@ -61,6 +61,7 @@ frame.Manager = class {
/**
* Receives all messages from content messageManager.
*/
/*eslint-disable*/
receiveMessage(message) {
switch (message.name) {
case "MarionetteFrame:getInterruptedState":
@ -123,6 +124,7 @@ frame.Manager = class {
}
}
}
/*eslint-enable*/
getOopFrame(winId, frameId) {
// get original frame window
@ -247,7 +249,8 @@ frame.Manager = class {
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.driver);
mm.removeWeakMessageListener("Marionette:getImportedScripts", this.driver.importedScripts);
mm.removeWeakMessageListener(
"Marionette:getImportedScripts", this.driver.importedScripts);
mm.removeWeakMessageListener("Marionette:listenersAttached", this.driver);
mm.removeWeakMessageListener("Marionette:register", this.driver);
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);

View File

@ -145,7 +145,8 @@ this.interaction = {};
* @throws {InvalidElementStateError}
* If |el| is not enabled.
*/
interaction.clickElement = function* (el, strict = false, specCompat = false) {
interaction.clickElement = function* (
el, strict = false, specCompat = false) {
const a11y = accessibility.get(strict);
if (element.isXULElement(el)) {
yield chromeClick(el, a11y);
@ -156,9 +157,8 @@ interaction.clickElement = function* (el, strict = false, specCompat = false) {
}
};
function* webdriverClickElement (el, a11y) {
function* webdriverClickElement(el, a11y) {
const win = getWindow(el);
const doc = win.document;
// step 3
if (el.localName == "input" && el.type == "file") {
@ -213,7 +213,7 @@ function* webdriverClickElement (el, a11y) {
// handled by the load listener in listener.js
}
function* chromeClick (el, a11y) {
function* chromeClick(el, a11y) {
if (!atom.isElementEnabled(el)) {
throw new InvalidElementStateError("Element is not enabled");
}
@ -231,7 +231,7 @@ function* chromeClick (el, a11y) {
}
}
function* seleniumClickElement (el, a11y) {
function* seleniumClickElement(el, a11y) {
let win = getWindow(el);
let visibilityCheckEl = el;
@ -261,7 +261,7 @@ function* seleniumClickElement (el, a11y) {
let opts = {};
event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
}
};
}
/**
* Select <option> element in a <select> list.
@ -280,7 +280,7 @@ function* seleniumClickElement (el, a11y) {
* @throws Error
* If unable to find |el|'s parent <select> element.
*/
interaction.selectOption = function (el) {
interaction.selectOption = function(el) {
if (element.isXULElement(el)) {
throw new Error("XUL dropdowns not supported");
}
@ -288,7 +288,6 @@ interaction.selectOption = function (el) {
throw new TypeError("Invalid elements");
}
let win = getWindow(el);
let containerEl = element.getContainer(el);
event.mouseover(containerEl);
@ -333,7 +332,7 @@ interaction.flushEventLoop = function* (win) {
return;
}
win.addEventListener("beforeunload", handleEvent, false);
win.addEventListener("beforeunload", handleEvent);
win.requestAnimationFrame(handleEvent);
});
};
@ -413,7 +412,8 @@ interaction.setFormControlValue = function* (el, value) {
* @param {boolean=} strict
* Enforce strict accessibility tests.
*/
interaction.sendKeysToElement = function (el, value, ignoreVisibility, strict = false) {
interaction.sendKeysToElement = function(
el, value, ignoreVisibility, strict = false) {
let win = getWindow(el);
let a11y = accessibility.get(strict);
return a11y.getAccessible(el, true).then(acc => {
@ -433,7 +433,7 @@ interaction.sendKeysToElement = function (el, value, ignoreVisibility, strict =
* @return {boolean}
* True if element is displayed, false otherwise.
*/
interaction.isElementDisplayed = function (el, strict = false) {
interaction.isElementDisplayed = function(el, strict = false) {
let win = getWindow(el);
let displayed = atom.isElementDisplayed(el, win);
@ -453,7 +453,7 @@ interaction.isElementDisplayed = function (el, strict = false) {
* @return {boolean}
* True if enabled, false otherwise.
*/
interaction.isElementEnabled = function (el, strict = false) {
interaction.isElementEnabled = function(el, strict = false) {
let enabled = true;
let win = getWindow(el);
@ -490,7 +490,7 @@ interaction.isElementEnabled = function (el, strict = false) {
* @return {boolean}
* True if element is selected, false otherwise.
*/
interaction.isElementSelected = function (el, strict = false) {
interaction.isElementSelected = function(el, strict = false) {
let selected = true;
let win = getWindow(el);

View File

@ -7,11 +7,12 @@
/**
* An API which allows Marionette to handle localized content.
*
* The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko based
* applications is done via entities and properties. For static values entities
* are used, which are located in .dtd files. Whereby for dynamically updated
* content the values come from .property files. Both types of elements can be
* identifed via a unique id, and the translated content retrieved.
* The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko
* based applications is done via entities and properties. For static
* values entities are used, which are located in .dtd files. Whereby for
* dynamically updated content the values come from .property files. Both
* types of elements can be identifed via a unique id, and the translated
* content retrieved.
*/
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
@ -43,7 +44,7 @@ this.l10n = {};
* @return {string}
* The localized string for the requested entity.
*/
l10n.localizeEntity = function (urls, id) {
l10n.localizeEntity = function(urls, id) {
// Build a string which contains all possible entity locations
let locations = [];
urls.forEach((url, index) => {
@ -67,7 +68,9 @@ l10n.localizeEntity = function (urls, id) {
* Retrieve the localized string for the specified property id.
*
* Example:
* localizeProperty(["chrome://global/locale/findbar.properties"], "FastFind")
*
* localizeProperty(
* ["chrome://global/locale/findbar.properties"], "FastFind");
*
* @param {Array.<string>} urls
* Array of .properties URLs.
@ -77,7 +80,7 @@ l10n.localizeEntity = function (urls, id) {
* @return {string}
* The localized string for the requested property.
*/
l10n.localizeProperty = function (urls, id) {
l10n.localizeProperty = function(urls, id) {
let property = null;
for (let url of urls) {
@ -86,10 +89,11 @@ l10n.localizeProperty = function (urls, id) {
property = bundle.GetStringFromName(id);
break;
} catch (e) {}
};
}
if (property === null) {
throw new NoSuchElementError(`Property with id='${id}' hasn't been found`);
throw new NoSuchElementError(
`Property with ID '${id}' hasn't been found`);
}
return property;

View File

@ -18,6 +18,7 @@ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
@ -52,7 +53,7 @@ var marionetteTestName;
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
var listenerId = null; // unique ID of this listener
var curContainer = { frame: content, shadowRoot: null };
var curContainer = {frame: content, shadowRoot: null};
var isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
var previousContainer = null;
@ -101,8 +102,10 @@ if (logger.ownAppenders.length == 0) {
}
var modalHandler = function() {
// This gets called on the system app only since it receives the mozbrowserprompt event
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null, storePrevious: true});
// This gets called on the system app only since it receives the
// mozbrowserprompt event
sendSyncMessage("Marionette:switchedToFrame",
{frameValue: null, storePrevious: true});
let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
if (isLocal) {
previousContainer = curContainer;
@ -115,10 +118,10 @@ var sandboxes = new Sandboxes(() => curContainer.frame);
var sandboxName = "default";
/**
* The load listener singleton helps to keep track of active page load activities,
* and can be used by any command which might cause a navigation to happen. In the
* specific case of remoteness changes it allows to continue observing the current
* page load.
* The load listener singleton helps to keep track of active page
* load activities, and can be used by any command which might cause a
* navigation to happen. In the specific case of remoteness changes it
* allows to continue observing the current page load.
*/
var loadListener = {
command_id: null,
@ -132,22 +135,26 @@ var loadListener = {
* Start listening for page unload/load events.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} timeout
* Timeout in seconds the method has to wait for the page being finished loading.
* Timeout in seconds the method has to wait for the page being
* finished loading.
* @param {number} startTime
* Unix timestap when the navitation request got triggered.
* @param {boolean=} waitForUnloaded
* If `true` wait for page unload events, otherwise only for page load events.
* If true wait for page unload events, otherwise only for page
* load events.
*/
start: function (command_id, timeout, startTime, waitForUnloaded = true) {
start(command_id, timeout, startTime, waitForUnloaded = true) {
this.command_id = command_id;
this.timeout = timeout;
this.seenBeforeUnload = false;
this.seenUnload = false;
this.timerPageLoad = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timerPageLoad = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
this.timerPageUnload = null;
// In case of a remoteness change, only wait the remaining time
@ -164,22 +171,23 @@ var loadListener = {
// The events can only be received when the event listeners are
// added to the currently selected frame.
curContainer.frame.addEventListener("beforeunload", this, false);
curContainer.frame.addEventListener("unload", this, false);
curContainer.frame.addEventListener("beforeunload", this);
curContainer.frame.addEventListener("unload", this);
Services.obs.addObserver(this, "outer-window-destroyed");
} else {
addEventListener("DOMContentLoaded", loadListener, false);
addEventListener("pageshow", loadListener, false);
addEventListener("DOMContentLoaded", loadListener);
addEventListener("pageshow", loadListener);
}
this.timerPageLoad.initWithCallback(this, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
this.timerPageLoad.initWithCallback(
this, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
},
/**
* Stop listening for page unload/load events.
*/
stop: function () {
stop() {
if (this.timerPageLoad) {
this.timerPageLoad.cancel();
}
@ -214,7 +222,7 @@ var loadListener = {
/**
* Callback for registered DOM events.
*/
handleEvent: function (event) {
handleEvent(event) {
let location = event.target.baseURI || event.target.location.href;
logger.debug(`Received DOM event "${event.type}" for "${location}"`);
@ -257,10 +265,11 @@ var loadListener = {
sendError(new UnknownError("Reached error page: " +
event.target.baseURI), this.command_id);
// Return early with a page load strategy of eager, and also special-case
// about:blocked pages which should be treated as non-error pages but do
// not raise a pageshow event.
} else if (capabilities.get("pageLoadStrategy") === session.PageLoadStrategy.Eager ||
// Return early with a page load strategy of eager, and also
// special-case about:blocked pages which should be treated as
// non-error pages but do not raise a pageshow event.
} else if ((capabilities.get("pageLoadStrategy") ===
session.PageLoadStrategy.Eager) ||
/about:blocked\?/.exec(event.target.baseURI)) {
this.stop();
sendOk(this.command_id);
@ -279,22 +288,24 @@ var loadListener = {
/**
* Callback for navigation timeout timer.
*/
notify: function (timer) {
notify(timer) {
switch (timer) {
case this.timerPageUnload:
// In the case when a document has a beforeunload handler registered,
// the currently active command will return immediately due to the
// modal dialog observer in proxy.js.
// Otherwise the timeout waiting for the document to start navigating
// is increased by 5000 ms to ensure a possible load event is not missed.
// In the common case such an event should occur pretty soon after
// beforeunload, and we optimise for this.
// In the case when a document has a beforeunload handler
// registered, the currently active command will return immediately
// due to the modal dialog observer in proxy.js.
//
// Otherwise the timeout waiting for the document to start
// navigating is increased by 5000 ms to ensure a possible load
// event is not missed. In the common case such an event should
// occur pretty soon after beforeunload, and we optimise for this.
if (this.seenBeforeUnload) {
this.seenBeforeUnload = null;
this.timerPageUnload.initWithCallback(this, 5000, Ci.nsITimer.TYPE_ONE_SHOT)
this.timerPageUnload.initWithCallback(
this, 5000, Ci.nsITimer.TYPE_ONE_SHOT)
// If no page unload has been detected, ensure to properly stop the load
// listener, and return from the currently active command.
// If no page unload has been detected, ensure to properly stop
// the load listener, and return from the currently active command.
} else if (!this.seenUnload) {
logger.debug("Canceled page load listener because no navigation " +
"has been detected");
@ -303,17 +314,19 @@ var loadListener = {
}
break;
case this.timerPageLoad:
this.stop();
sendError(new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
this.command_id);
break;
case this.timerPageLoad:
this.stop();
sendError(
new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
this.command_id);
break;
}
},
observe: function (subject, topic, data) {
const winID = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
const curWinID = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
observe(subject, topic, data) {
const win = curContainer.frame;
const winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
const curWinID = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
logger.debug(`Received observer notification "${topic}" for "${winID}"`);
@ -331,16 +344,19 @@ var loadListener = {
},
/**
* Continue to listen for page load events after a remoteness change happened.
* Continue to listen for page load events after a remoteness change
* happened.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} timeout
* Timeout in milliseconds the method has to wait for the page being finished loading.
* Timeout in milliseconds the method has to wait for the page
* being finished loading.
* @param {number} startTime
* Unix timestap when the navitation request got triggered.
*/
waitForLoadAfterRemotenessChange: function (command_id, timeout, startTime) {
waitForLoadAfterRemotenessChange(command_id, timeout, startTime) {
this.start(command_id, timeout, startTime, false);
},
@ -353,19 +369,21 @@ var loadListener = {
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* @param {number} pageTimeout
* Timeout in milliseconds the method has to wait for the page finished loading.
* Timeout in milliseconds the method has to wait for the page
* finished loading.
* @param {boolean=} loadEventExpected
* Optional flag, which indicates that navigate has to wait for the page
* finished loading.
* @param {string=} url
* Optional URL, which is used to check if a page load is expected.
*/
navigate: function (trigger, command_id, timeout, loadEventExpected = true,
navigate(trigger, command_id, timeout, loadEventExpected = true,
useUnloadTimer = false) {
// Only wait if the page load strategy is not `none`
loadEventExpected = loadEventExpected &&
capabilities.get("pageLoadStrategy") !== session.PageLoadStrategy.None;
(capabilities.get("pageLoadStrategy") !==
session.PageLoadStrategy.None);
if (loadEventExpected) {
let startTime = new Date().getTime();
@ -376,15 +394,17 @@ var loadListener = {
yield trigger();
}).then(val => {
if (!loadEventExpected) {
if (!loadEventExpected) {
sendOk(command_id);
return;
}
// If requested setup a timer to detect a possible page load
if (useUnloadTimer) {
this.timerPageUnload = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
this.timerPageUnload.initWithCallback(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
this.timerPageUnload = Cc["@mozilla.org/timer;1"]
.createInstance(Ci.nsITimer);
this.timerPageUnload.initWithCallback(
this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
}
}).catch(err => {
@ -393,21 +413,22 @@ var loadListener = {
}
sendError(err, command_id);
return;
});
},
}
/**
* Called when listener is first started up.
* The listener sends its unique window ID and its current URI to the actor.
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
* Called when listener is first started up. The listener sends its
* unique window ID and its current URI to the actor. If the actor returns
* an ID, we start the listeners. Otherwise, nothing happens.
*/
function registerSelf() {
let msg = {value: winUtil.outerWindowID};
logger.debug(`Register listener.js for window ${msg.value}`);
// register will have the ID and a boolean describing if this is the main process or not
// register will have the ID and a boolean describing if this is the
// main process or not
let register = sendSyncMessage("Marionette:register", msg);
if (register[0]) {
@ -435,15 +456,14 @@ function dispatch(fn) {
throw new TypeError("Provided dispatch handler is not a function");
}
return function (msg) {
return function(msg) {
let id = msg.json.command_id;
let req = Task.spawn(function* () {
if (typeof msg.json == "undefined" || msg.json instanceof Array) {
return yield fn.apply(null, msg.json);
} else {
return yield fn(msg.json);
}
return yield fn(msg.json);
});
let okOrValueResponse = rv => {
@ -521,15 +541,19 @@ function startListeners() {
addMessageListenerId("Marionette:goForward", goForward);
addMessageListenerId("Marionette:refresh", refresh);
addMessageListenerId("Marionette:findElementContent", findElementContentFn);
addMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
addMessageListenerId(
"Marionette:findElementsContent", findElementsContentFn);
addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
addMessageListenerId("Marionette:clickElement", clickElement);
addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
addMessageListenerId(
"Marionette:getElementAttribute", getElementAttributeFn);
addMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
addMessageListenerId("Marionette:getElementText", getElementTextFn);
addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
addMessageListenerId(
"Marionette:getElementValueOfCssProperty",
getElementValueOfCssPropertyFn);
addMessageListenerId("Marionette:getElementRect", getElementRectFn);
addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
@ -591,24 +615,37 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:goBack", goBack);
removeMessageListenerId("Marionette:goForward", goForward);
removeMessageListenerId("Marionette:refresh", refresh);
removeMessageListenerId("Marionette:findElementContent", findElementContentFn);
removeMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
removeMessageListenerId(
"Marionette:findElementContent", findElementContentFn);
removeMessageListenerId(
"Marionette:findElementsContent", findElementsContentFn);
removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
removeMessageListenerId("Marionette:clickElement", clickElement);
removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
removeMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
removeMessageListenerId("Marionette:getElementText", getElementTextFn);
removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
removeMessageListenerId(
"Marionette:getElementAttribute", getElementAttributeFn);
removeMessageListenerId(
"Marionette:getElementProperty", getElementPropertyFn);
removeMessageListenerId(
"Marionette:getElementText", getElementTextFn);
removeMessageListenerId(
"Marionette:getElementTagName", getElementTagNameFn);
removeMessageListenerId(
"Marionette:isElementDisplayed", isElementDisplayedFn);
removeMessageListenerId(
"Marionette:getElementValueOfCssProperty",
getElementValueOfCssPropertyFn);
removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
removeMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElementFn);
removeMessageListenerId(
"Marionette:isElementSelected", isElementSelectedFn);
removeMessageListenerId(
"Marionette:sendKeysToElement", sendKeysToElementFn);
removeMessageListenerId("Marionette:clearElement", clearElementFn);
removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
removeMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
removeMessageListenerId(
"Marionette:switchToParentFrame", switchToParentFrame);
removeMessageListenerId(
"Marionette:switchToShadowRoot", switchToShadowRootFn);
removeMessageListenerId("Marionette:deleteSession", deleteSession);
removeMessageListenerId("Marionette:sleepSession", sleepSession);
removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
@ -616,7 +653,7 @@ function deleteSession(msg) {
seenEls.clear();
// reset container frame to the top-most frame
curContainer = { frame: content, shadowRoot: null };
curContainer = {frame: content, shadowRoot: null};
curContainer.frame.focus();
legacyactions.touchIds = {};
if (action.inputStateMap !== undefined) {
@ -690,35 +727,32 @@ function resetValues() {
action.inputsToCancel = [];
}
/**
* Check if our context was interrupted
*/
function wasInterrupted() {
if (previousContainer) {
let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
let element = content.document.elementFromPoint(
(content.innerWidth / 2), (content.innerHeight / 2));
if (element.id.indexOf("modal-dialog") == -1) {
return true;
}
else {
return false;
}
return false;
}
return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
}
function checkForInterrupted() {
if (wasInterrupted()) {
if (previousContainer) {
// if previousContainer is set, then we're in a single process environment
curContainer = legacyactions.container = previousContainer;
previousContainer = null;
}
else {
//else we're in OOP environment, so we'll switch to the original OOP frame
sendSyncMessage("Marionette:switchToModalOrigin");
}
sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
if (wasInterrupted()) {
// if previousContainer is set, then we're in a single process
// environment
if (previousContainer) {
curContainer = legacyactions.container = previousContainer;
previousContainer = null;
// else we're in OOP environment, so we'll switch to the original
// OOP frame
} else {
sendSyncMessage("Marionette:switchToModalOrigin");
}
sendSyncMessage("Marionette:switchedToFrame", {restorePrevious: true});
}
}
function* execute(script, args, timeout, opts) {
@ -744,36 +778,58 @@ function* executeInSandbox(script, args, timeout, opts) {
return evaluate.toJSON(res, seenEls);
}
/**
* This function creates a touch event given a touch type and a touch
*/
function emitTouchEvent(type, touch) {
if (!wasInterrupted()) {
logger.info(`Emitting Touch event of type ${type} to element with id: ${touch.target.id} ` +
`and tag name: ${touch.target.tagName} at coordinates (${touch.clientX}, ` +
`${touch.clientY}) relative to the viewport`);
logger.info(`Emitting Touch event of type ${type} ` +
`to element with id: ${touch.target.id} ` +
`and tag name: ${touch.target.tagName} ` +
`at coordinates (${touch.clientX}), ` +
`${touch.clientY}) relative to the viewport`);
var docShell = curContainer.frame.document.defaultView.
QueryInterface(Components.interfaces.nsIInterfaceRequestor).
getInterface(Components.interfaces.nsIWebNavigation).
QueryInterface(Components.interfaces.nsIDocShell);
const win = curContainer.frame;
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell);
if (docShell.asyncPanZoomEnabled && legacyactions.scrolling) {
// if we're in APZ and we're scrolling, we must use sendNativeTouchPoint to dispatch our touchmove events
// if we're in APZ and we're scrolling, we must use
// sendNativeTouchPoint to dispatch our touchmove events
let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
// only call emitTouchEventForIFrame if we're inside an iframe.
if (index != null) {
sendSyncMessage("Marionette:emitTouchEvent",
{ index: index, type: type, id: touch.identifier,
clientX: touch.clientX, clientY: touch.clientY,
screenX: touch.screenX, screenY: touch.screenY,
radiusX: touch.radiusX, radiusY: touch.radiusY,
rotation: touch.rotationAngle, force: touch.force });
let ev = {
index,
type,
id: touch.identifier,
clientX: touch.clientX,
clientY: touch.clientY,
screenX: touch.screenX,
screenY: touch.screenY,
radiusX: touch.radiusX,
radiusY: touch.radiusY,
rotation: touch.rotationAngle,
force: touch.force,
};
sendSyncMessage("Marionette:emitTouchEvent", ev);
return;
}
}
// we get here if we're not in asyncPacZoomEnabled land, or if we're the main process
let domWindowUtils = curContainer.frame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
// we get here if we're not in asyncPacZoomEnabled land, or if we're
// the main process
let domWindowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(
type,
[touch.identifier],
[touch.clientX],
[touch.clientY],
[touch.radiusX],
[touch.radiusY],
[touch.rotationAngle],
[touch.force],
1,
0);
}
}
@ -785,7 +841,8 @@ function singleTap(id, corx, cory) {
// after this block, the element will be scrolled into view
let visible = element.isVisible(el, corx, cory);
if (!visible) {
throw new ElementNotInteractableError("Element is not currently visible and may not be manipulated");
throw new ElementNotInteractableError(
"Element is not currently visible and may not be manipulated");
}
let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
@ -799,8 +856,8 @@ function singleTap(id, corx, cory) {
if (!legacyactions.mouseEventsOnly) {
let touchId = legacyactions.nextTouchId++;
let touch = createATouch(el, c.x, c.y, touchId);
emitTouchEvent('touchstart', touch);
emitTouchEvent('touchend', touch);
emitTouchEvent("touchstart", touch);
emitTouchEvent("touchend", touch);
}
legacyactions.mouseTap(el.ownerDocument, c.x, c.y);
});
@ -814,8 +871,17 @@ function createATouch(el, corx, cory, touchId) {
let doc = el.ownerDocument;
let win = doc.defaultView;
let [clientX, clientY, pageX, pageY, screenX, screenY] =
legacyactions.getCoordinateInfo(el, corx, cory);
let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
legacyactions.getCoordinateInfo(el, corx, cory);
let atouch = doc.createTouch(
win,
el,
touchId,
pageX,
pageY,
screenX,
screenY,
clientX,
clientY);
return atouch;
}
@ -833,12 +899,13 @@ function* performActions(msg) {
/**
* The Release Actions command is used to release all the keys and pointer
* buttons that are currently depressed. This causes events to be fired as if
* the state was released by an explicit series of actions. It also clears all
* the internal state of the virtual devices.
* buttons that are currently depressed. This causes events to be fired
* as if the state was released by an explicit series of actions. It also
* clears all the internal state of the virtual devices.
*/
function* releaseActions() {
yield action.dispatchTickActions(action.inputsToCancel.reverse(), 0, seenEls, curContainer);
yield action.dispatchTickActions(
action.inputsToCancel.reverse(), 0, seenEls, curContainer);
action.inputsToCancel.length = 0;
action.inputStateMap.clear();
}
@ -859,26 +926,23 @@ function actionChain(chain, touchId) {
touchProvider);
}
/**
* Function to emit touch events which allow multi touch on the screen
* @param type represents the type of event, touch represents the current touch,touches are all pending touches
*/
function emitMultiEvents(type, touch, touches) {
let target = touch.target;
let doc = target.ownerDocument;
let win = doc.defaultView;
// touches that are in the same document
let documentTouches = doc.createTouchList(touches.filter(function (t) {
return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
let documentTouches = doc.createTouchList(touches.filter(function(t) {
return ((t.target.ownerDocument === doc) && (type != "touchcancel"));
}));
// touches on the same target
let targetTouches = doc.createTouchList(touches.filter(function (t) {
return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
let targetTouches = doc.createTouchList(touches.filter(function(t) {
return ((t.target === target) &&
((type != "touchcancel") || (type != "touchend")));
}));
// Create changed touches
let changedTouches = doc.createTouchList(touch);
// Create the event object
let event = doc.createEvent('TouchEvent');
let event = doc.createEvent("TouchEvent");
event.initTouchEvent(type,
true,
true,
@ -891,11 +955,7 @@ function emitMultiEvents(type, touch, touches) {
target.dispatchEvent(event);
}
/**
* Function to dispatch one set of actions
* @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now
*/
function setDispatch(batches, touches, batchIndex=0) {
function setDispatch(batches, touches, batchIndex = 0) {
// check if all the sets have been fired
if (batchIndex >= batches.length) {
multiLast = {};
@ -912,8 +972,6 @@ function setDispatch(batches, touches, batchIndex=0) {
let command;
// touch that will be created for the finger
let el;
let corx;
let cory;
let touch;
let lastTouch;
let touchIndex;
@ -940,7 +998,8 @@ function setDispatch(batches, touches, batchIndex=0) {
case "release":
touch = multiLast[touchId];
// the index of the previous touch for the finger may change in the touches array
// the index of the previous touch for the finger may change in
// the touches array
touchIndex = touches.indexOf(touch);
touches.splice(touchIndex, 1);
emitMultiEvents("touchend", touch, touches);
@ -962,14 +1021,24 @@ function setDispatch(batches, touches, batchIndex=0) {
touchIndex = touches.indexOf(lastTouch);
let doc = el.ownerDocument;
let win = doc.defaultView;
// since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
let clientX = lastTouch.clientX + pack[2],
clientY = lastTouch.clientY + pack[3];
let pageX = clientX + win.pageXOffset,
pageY = clientY + win.pageYOffset;
let screenX = clientX + win.mozInnerScreenX,
screenY = clientY + win.mozInnerScreenY;
touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
// since x and y are relative to the last touch, therefore,
// it's relative to the position of the last touch
let clientX = lastTouch.clientX + pack[2];
let clientY = lastTouch.clientY + pack[3];
let pageX = clientX + win.pageXOffset;
let pageY = clientY + win.pageYOffset;
let screenX = clientX + win.mozInnerScreenX;
let screenY = clientY + win.mozInnerScreenY;
touch = doc.createTouch(
win,
el,
touchId,
pageX,
pageY,
screenX,
screenY,
clientX,
clientY);
touches[touchIndex] = touch;
multiLast[touchId] = touch;
emitMultiEvents("touchmove", touch, touches);
@ -1012,7 +1081,8 @@ function multiAction(args, maxLen) {
let row = [];
for (let j = 0; j < commandArray.length; j++) {
if (typeof commandArray[j][i] != "undefined") {
// add finger id to the front of each action, i.e. [finger_id, action, element]
// add finger id to the front of each action,
// i.e. [finger_id, action, element]
temp = commandArray[j][i];
temp.unshift(j);
row.push(temp);
@ -1021,9 +1091,11 @@ function multiAction(args, maxLen) {
concurrentEvent.push(row);
}
// now concurrent event is made of sets where each set contain a list of actions that need to be fired.
// note: each action belongs to a different finger
// pendingTouches keeps track of current touches that's on the screen
// Now concurrent event is made of sets where each set contain a list
// of actions that need to be fired.
//
// But note that each action belongs to a different finger
// pendingTouches keeps track of current touches that's on the screen.
let pendingTouches = [];
setDispatch(concurrentEvent, pendingTouches);
}
@ -1038,31 +1110,35 @@ function cancelRequest() {
}
/**
* This implements the latter part of a get request (for the case we need to resume one
* when a remoteness update happens in the middle of a navigate request). This is most of
* of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
* This implements the latter part of a get request (for the case we
* need to resume one when a remoteness update happens in the middle of a
* navigate request). This is most of of the work of a navigate request,
* but doesn't assume DOMContentLoaded is yet to fire.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} pageTimeout
* Timeout in seconds the method has to wait for the page being finished loading.
* Timeout in seconds the method has to wait for the page being
* finished loading.
* @param {number} startTime
* Unix timestap when the navitation request got triggred.
*/
function waitForPageLoaded(msg) {
let {command_id, pageTimeout, startTime} = msg.json;
loadListener.waitForLoadAfterRemotenessChange(command_id, pageTimeout, startTime);
loadListener.waitForLoadAfterRemotenessChange(
command_id, pageTimeout, startTime);
}
/**
* Navigate to the given URL. The operation will be performed on the
* current browsing context, which means it handles the case where we
* navigate within an iframe. All other navigation is handled by the
* driver (in chrome space).
* navigate within an iframe. All other navigation is handled by the driver
* (in chrome space).
*/
function get(msg) {
let {command_id, pageTimeout, url, loadEventExpected=null} = msg.json;
let {command_id, pageTimeout, url, loadEventExpected = null} = msg.json;
try {
if (typeof url == "string") {
@ -1072,7 +1148,8 @@ function get(msg) {
loadEventExpected = navigate.isLoadEventExpected(requestedURL);
}
} catch (e) {
sendError(new InvalidArgumentError("Malformed URL: " + e.message), command_id);
let err = new InvalidArgumentError("Malformed URL: " + e.message);
sendError(err, command_id);
return;
}
}
@ -1095,9 +1172,11 @@ function get(msg) {
* of the current browsing context.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} pageTimeout
* Timeout in milliseconds the method has to wait for the page being finished loading.
* Timeout in milliseconds the method has to wait for the page being
* finished loading.
*/
function goBack(msg) {
let {command_id, pageTimeout} = msg.json;
@ -1117,9 +1196,11 @@ function goBack(msg) {
* of the current browsing context.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} pageTimeout
* Timeout in milliseconds the method has to wait for the page being finished loading.
* Timeout in milliseconds the method has to wait for the page being
* finished loading.
*/
function goForward(msg) {
let {command_id, pageTimeout} = msg.json;
@ -1135,12 +1216,15 @@ function goForward(msg) {
}
/**
* Causes the browser to reload the page in in current top-level browsing context.
* Causes the browser to reload the page in in current top-level browsing
* context.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {number} pageTimeout
* Timeout in milliseconds the method has to wait for the page being finished loading.
* Timeout in milliseconds the method has to wait for the page being
* finished loading.
*/
function refresh(msg) {
let {command_id, pageTimeout} = msg.json;
@ -1223,11 +1307,13 @@ function getActiveElement() {
* Send click event to element.
*
* @param {number} command_id
* ID of the currently handled message between the driver and listener.
* ID of the currently handled message between the driver and
* listener.
* @param {WebElement} id
* Reference to the web element to click.
* @param {number} pageTimeout
* Timeout in milliseconds the method has to wait for the page being finished loading.
* Timeout in milliseconds the method has to wait for the page being
* finished loading.
*/
function clickElement(msg) {
let {command_id, id, pageTimeout} = msg.json;
@ -1259,12 +1345,10 @@ function getElementAttribute(id, name) {
if (element.isBooleanAttribute(el, name)) {
if (el.hasAttribute(name)) {
return "true";
} else {
return null;
}
} else {
return el.getAttribute(name);
return null;
}
return el.getAttribute(name);
}
function getElementProperty(id, name) {
@ -1344,9 +1428,9 @@ function getElementRect(id) {
let clientRect = el.getBoundingClientRect();
return {
x: clientRect.x + curContainer.frame.pageXOffset,
y: clientRect.y + curContainer.frame.pageYOffset,
y: clientRect.y + curContainer.frame.pageYOffset,
width: clientRect.width,
height: clientRect.height
height: clientRect.height,
};
}
@ -1414,13 +1498,14 @@ function clearElement(id) {
/**
* Switch the current context to the specified host's Shadow DOM.
*
* @param {WebElement} id
* Reference to web element.
*/
function switchToShadowRoot(id) {
if (!id) {
// If no host element is passed, attempt to find a parent shadow root or, if
// none found, unset the current shadow root
// If no host element is passed, attempt to find a parent shadow
// root or, if none found, unset the current shadow root
if (curContainer.shadowRoot) {
let parent;
try {
@ -1443,25 +1528,24 @@ function switchToShadowRoot(id) {
let hostEl = seenEls.get(id, curContainer);
foundShadowRoot = hostEl.shadowRoot;
if (!foundShadowRoot) {
throw new NoSuchElementError('Unable to locate shadow root: ' + id);
throw new NoSuchElementError("Unable to locate shadow root: " + id);
}
curContainer.shadowRoot = foundShadowRoot;
}
/**
* Switch to the parent frame of the current Frame. If the frame is the top most
* is the current frame then no action will happen.
* Switch to the parent frame of the current frame. If the frame is the
* top most is the current frame then no action will happen.
*/
function switchToParentFrame(msg) {
let command_id = msg.json.command_id;
curContainer.frame = curContainer.frame.parent;
let parentElement = seenEls.add(curContainer.frame);
function switchToParentFrame(msg) {
curContainer.frame = curContainer.frame.parent;
let parentElement = seenEls.add(curContainer.frame);
sendSyncMessage(
"Marionette:switchedToFrame", {frameValue: parentElement});
sendSyncMessage(
"Marionette:switchedToFrame", {frameValue: parentElement});
sendOk(msg.json.command_id);
}
sendOk(msg.json.command_id);
}
/**
* Switch to frame given either the server-assigned element id,
@ -1476,24 +1560,27 @@ function switchToFrame(msg) {
// Check of the curContainer.frame reference is dead
try {
frames = curContainer.frame.frames;
//Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
//parWindow will refer to the iframe above the nested OOP frame.
// Until Bug 761935 lands, we won't have multiple nested OOP
// iframes. We will only have one. parWindow will refer to the iframe
// above the nested OOP frame.
parWindow = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
} catch (e) {
// We probably have a dead compartment so accessing it is going to make Firefox
// very upset. Let's now try redirect everything to the top frame even if the
// user has given us a frame since search doesnt look up.
// We probably have a dead compartment so accessing it is going to
// make Firefox very upset. Let's now try redirect everything to the
// top frame even if the user has given us a frame since search doesnt
// look up.
msg.json.id = null;
msg.json.element = null;
}
if ((msg.json.id === null || msg.json.id === undefined) && (msg.json.element == null)) {
if ((msg.json.id === null || msg.json.id === undefined) &&
(msg.json.element == null)) {
// returning to root frame
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
curContainer.frame = content;
if(msg.json.focus == true) {
if (msg.json.focus == true) {
curContainer.frame.focus();
}
@ -1513,8 +1600,11 @@ function switchToFrame(msg) {
if (frames.length > 0) {
for (let i = 0; i < frames.length; i++) {
// use XPCNativeWrapper to compare elements; see bug 834266
if (XPCNativeWrapper(frames[i].frameElement) == XPCNativeWrapper(wantedFrame)) {
curContainer.frame = frames[i].frameElement;
let frameEl = frames[i].frameElement;
let wrappedItem = new XPCNativeWrapper(frameEl);
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
if (wrappedItem == wrappedWanted) {
curContainer.frame = frameEl;
foundFrame = i;
}
}
@ -1524,9 +1614,13 @@ function switchToFrame(msg) {
// Either the frame has been removed or we have a OOP frame
// so lets just get all the iframes and do a quick loop before
// throwing in the towel
let iframes = curContainer.frame.document.getElementsByTagName("iframe");
const doc = curContainer.frame.document;
let iframes = doc.getElementsByTagName("iframe");
for (var i = 0; i < iframes.length; i++) {
if (XPCNativeWrapper(iframes[i]) == XPCNativeWrapper(wantedFrame)) {
let frameEl = iframes[i];
let wrappedEl = new XPCNativeWrapper(frameEl);
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
if (wrappedEl == wrappedWanted) {
curContainer.frame = iframes[i];
foundFrame = i;
}
@ -1535,20 +1629,19 @@ function switchToFrame(msg) {
}
if (foundFrame === null) {
if (typeof(msg.json.id) === 'number') {
if (typeof(msg.json.id) === "number") {
try {
foundFrame = frames[msg.json.id].frameElement;
if (foundFrame !== null) {
curContainer.frame = foundFrame;
foundFrame = seenEls.add(curContainer.frame);
}
else {
// If foundFrame is null at this point then we have the top level browsing
// context so should treat it accordingly.
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null});
} else {
// If foundFrame is null at this point then we have the top
// level browsing context so should treat it accordingly.
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
curContainer.frame = content;
if(msg.json.focus == true) {
if (msg.json.focus == true) {
curContainer.frame.focus();
}
@ -1559,7 +1652,8 @@ function switchToFrame(msg) {
// Since window.frames does not return OOP frames it will throw
// and we land up here. Let's not give up and check if there are
// iframes and switch to the indexed frame there
let iframes = curContainer.frame.document.getElementsByTagName("iframe");
let doc = curContainer.frame.document;
let iframes = doc.getElementsByTagName("iframe");
if (msg.json.id >= 0 && msg.json.id < iframes.length) {
curContainer.frame = iframes[msg.json.id];
foundFrame = msg.json.id;
@ -1569,15 +1663,17 @@ function switchToFrame(msg) {
}
if (foundFrame === null) {
sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
return true;
let failedFrame = msg.json.id || msg.json.element;
let err = new NoSuchFrameError(`Unable to locate frame: ${failedFrame}`);
sendError(err, command_id);
return;
}
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
let frameValue = evaluate.toJSON(
curContainer.frame.wrappedJSObject, seenEls)[element.Key];
sendSyncMessage("Marionette:switchedToFrame", {frameValue: frameValue});
sendSyncMessage("Marionette:switchedToFrame", {"frameValue": frameValue});
if (curContainer.frame.contentWindow === null) {
// The frame we want to switch to is a remote/OOP frame;
@ -1740,7 +1836,7 @@ function* reftestWait(url, remote) {
// so that setTimeout(fn, 0) in the load event has run
reftestWait = document.documentElement.classList.contains("reftest-wait");
yield new Promise(resolve => win.setTimeout(resolve, 0));
};
}
let root = document.documentElement;
if (reftestWait) {

View File

@ -21,7 +21,7 @@ this.EXPORTED_SYMBOLS = [
const logger = Log.repository.getLogger("Marionette");
this.MessageOrigin = {
const MessageOrigin = {
Client: 0,
Server: 1,
};
@ -42,7 +42,7 @@ this.Message = {};
* @throws {TypeError}
* If the message type is not recognised.
*/
Message.fromMsg = function (data) {
Message.fromMsg = function(data) {
switch (data[0]) {
case Command.TYPE:
return Command.fromMsg(data);
@ -98,7 +98,7 @@ Message.fromMsg = function (data) {
* @param {Object<string, ?>} params
* Command parameters.
*/
this.Command = class {
class Command {
constructor(msgID, name, params = {}) {
this.id = assert.integer(msgID);
this.name = assert.string(name);
@ -148,11 +148,10 @@ this.Command = class {
return new Command(msgID, name, params);
}
};
}
Command.TYPE = 0;
const validator = {
exclusionary: {
"capabilities": ["error", "value"],
@ -161,7 +160,7 @@ const validator = {
"value": ["error", "sessionId", "capabilities"],
},
set: function (obj, prop, val) {
set(obj, prop, val) {
let tests = this.exclusionary[prop];
if (tests) {
for (let t of tests) {
@ -174,7 +173,7 @@ const validator = {
obj[prop] = val;
return true;
},
};
}
/**
* The response body is exposed as an argument to commands.
@ -188,7 +187,7 @@ const validator = {
* {@code value}, {@code sessionId}, or {@code capabilities} have been
* set previously will cause an error.
*/
this.ResponseBody = () => new Proxy({}, validator);
const ResponseBody = () => new Proxy({}, validator);
/**
* Represents the response returned from the remote end after execution
@ -209,7 +208,7 @@ this.ResponseBody = () => new Proxy({}, validator);
* @param {function(Response|Message)} respHandler
* Function callback called on sending the response.
*/
this.Response = class {
class Response {
constructor(msgID, respHandler = () => {}) {
this.id = assert.integer(msgID);
this.respHandler_ = assert.callable(respHandler);
@ -292,6 +291,6 @@ this.Response = class {
resp.body = body;
return resp;
}
};
}
Response.TYPE = 1;

View File

@ -10,6 +10,8 @@ Cu.import("resource://gre/modules/Services.jsm");
this.EXPORTED_SYMBOLS = ["modal"];
const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
const isFirefox = () => Services.appinfo.name == "Firefox";
this.modal = {};
@ -18,8 +20,8 @@ modal = {
TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
handlers: {
"common-dialog-loaded": new Set(),
"tabmodal-dialog-loaded": new Set()
}
"tabmodal-dialog-loaded": new Set(),
},
};
/**
@ -37,7 +39,7 @@ modal = {
* {@code modal.COMMON_DIALOG_LOADED} or
* {@code modal.TABMODAL_DIALOG_LOADED}.
*/
modal.addHandler = function (handler) {
modal.addHandler = function(handler) {
if (!isFirefox()) {
return;
}
@ -55,28 +57,32 @@ modal.addHandler = function (handler) {
* Reference to the browser context to check for existent dialogs.
*
* @return {modal.Dialog}
* Returns instance of the Dialog class, or `null` if no modal dialog is present.
* Returns instance of the Dialog class, or `null` if no modal dialog
* is present.
*/
modal.findModalDialogs = function (context) {
// First check if there is a modal dialog already present for the current browser window.
modal.findModalDialogs = function(context) {
// First check if there is a modal dialog already present for the
// current browser window.
let winEn = Services.wm.getEnumerator(null);
while (winEn.hasMoreElements()) {
let win = winEn.getNext();
// Modal dialogs which do not have an opener set, we cannot detect as long
// as GetZOrderDOMWindowEnumerator doesn't work on Linux (Bug 156333).
if (win.document.documentURI === "chrome://global/content/commonDialog.xul" &&
// Modal dialogs which do not have an opener set, we cannot detect
// as long as GetZOrderDOMWindowEnumerator doesn't work on Linux
// (Bug 156333).
if (win.document.documentURI === COMMON_DIALOG &&
win.opener && win.opener === context.window) {
return new modal.Dialog(() => context, Cu.getWeakReference(win));
}
}
// If no modal dialog has been found, also check if there is an open tab modal
// dialog present for the current tab.
// If no modal dialog has been found, also check if there is an open
// tab modal dialog present for the current tab.
// TODO: Find an adequate implementation for Fennec.
if (context.tab && context.tabBrowser.getTabModalPromptBox) {
let contentBrowser = context.contentBrowser;
let promptManager = context.tabBrowser.getTabModalPromptBox(contentBrowser);
let promptManager =
context.tabBrowser.getTabModalPromptBox(contentBrowser);
let prompts = promptManager.listPrompts();
if (prompts.length) {
@ -96,7 +102,7 @@ modal.findModalDialogs = function (context) {
* The handler previously passed to modal.addHandler which will now
* be removed.
*/
modal.removeHandler = function (toRemove) {
modal.removeHandler = function(toRemove) {
if (!isFirefox()) {
return;
}

View File

@ -25,7 +25,7 @@ this.navigate = {};
* @throws TypeError
* If |url| is an invalid URL.
*/
navigate.isLoadEventExpected = function (url) {
navigate.isLoadEventExpected = function(url) {
// assume we will go somewhere exciting
if (typeof url == "undefined") {
throw TypeError("Expected destination URL");

View File

@ -25,27 +25,28 @@
*/
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
const { StreamUtils } = Cu.import("chrome://marionette/content/stream-utils.js");
const {StreamUtils} =
Cu.import("chrome://marionette/content/stream-utils.js", {});
const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
.createInstance(Ci.nsIScriptableUnicodeConverter);
.createInstance(Ci.nsIScriptableUnicodeConverter);
unicodeConverter.charset = "UTF-8";
const defer = function () {
const defer = function() {
let deferred = {
promise: new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
})
}),
};
return deferred;
};
this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"];
// The transport's previous check ensured the header length did not exceed 20
// characters. Here, we opt for the somewhat smaller, but still large limit of
// 1 TiB.
// The transport's previous check ensured the header length did not
// exceed 20 characters. Here, we opt for the somewhat smaller, but still
// large limit of 1 TiB.
const PACKET_LENGTH_MAX = Math.pow(2, 40);
/**
@ -57,17 +58,19 @@ function Packet(transport) {
}
/**
* Attempt to initialize a new Packet based on the incoming packet header we've
* received so far. We try each of the types in succession, trying JSON packets
* first since they are much more common.
* @param header string
* The packet header string to attempt parsing.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
* @return Packet
* The parsed packet of the matching type, or null if no types matched.
* Attempt to initialize a new Packet based on the incoming packet header
* we've received so far. We try each of the types in succession, trying
* JSON packets first since they are much more common.
*
* @param {string} header
* Packet header string to attempt parsing.
* @param {DebuggerTransport} transport
* Transport instance that will own the packet.
*
* @return {Packet}
* Parsed packet of the matching type, or null if no types matched.
*/
Packet.fromHeader = function (header, transport) {
Packet.fromHeader = function(header, transport) {
return JSONPacket.fromHeader(header, transport) ||
BulkPacket.fromHeader(header, transport);
};
@ -86,20 +89,20 @@ Packet.prototype = {
this._length = length;
},
destroy: function () {
destroy() {
this._transport = null;
}
},
};
/**
* With a JSON packet (the typical packet type sent via the transport), data is
* transferred as a JSON packet serialized into a string, with the string length
* prepended to the packet, followed by a colon ([length]:[packet]). The
* contents of the JSON packet are specified in the Remote Debugging Protocol
* specification.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
* With a JSON packet (the typical packet type sent via the transport),
* data is transferred as a JSON packet serialized into a string,
* with the string length prepended to the packet, followed by a colon
* ([length]:[packet]). The contents of the JSON packet are specified in
* the Remote Debugging Protocol specification.
*
* @param {DebuggerTransport} transport
* Transport instance that will own the packet.
*/
function JSONPacket(transport) {
Packet.call(this, transport);
@ -108,16 +111,18 @@ function JSONPacket(transport) {
}
/**
* Attempt to initialize a new JSONPacket based on the incoming packet header
* we've received so far.
* @param header string
* The packet header string to attempt parsing.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
* @return JSONPacket
* The parsed packet, or null if it's not a match.
* Attempt to initialize a new JSONPacket based on the incoming packet
* header we've received so far.
*
* @param {string} header
* Packet header string to attempt parsing.
* @param {DebuggerTransport} transport
* Transport instance that will own the packet.
*
* @return {JSONPacket}
* Parsed packet, or null if it's not a match.
*/
JSONPacket.fromHeader = function (header, transport) {
JSONPacket.fromHeader = function(header, transport) {
let match = this.HEADER_PATTERN.exec(header);
if (!match) {
@ -137,22 +142,22 @@ Object.defineProperty(JSONPacket.prototype, "object", {
/**
* Gets the object (not the serialized string) being read or written.
*/
get: function () {
get() {
return this._object;
},
/**
* Sets the object to be sent when write() is called.
*/
set: function (object) {
set(object) {
this._object = object;
let data = JSON.stringify(object);
this._data = unicodeConverter.ConvertFromUnicode(data);
this.length = this._data.length;
}
},
});
JSONPacket.prototype.read = function (stream, scriptableStream) {
JSONPacket.prototype.read = function(stream, scriptableStream) {
// Read in more packet data.
this._readData(stream, scriptableStream);
@ -177,14 +182,15 @@ JSONPacket.prototype.read = function (stream, scriptableStream) {
this._transport._onJSONObjectReady(this._object);
};
JSONPacket.prototype._readData = function (stream, scriptableStream) {
let bytesToRead = Math.min(this.length - this._data.length,
stream.available());
JSONPacket.prototype._readData = function(stream, scriptableStream) {
let bytesToRead = Math.min(
this.length - this._data.length,
stream.available());
this._data += scriptableStream.readBytes(bytesToRead);
this._done = this._data.length === this.length;
};
JSONPacket.prototype.write = function (stream) {
JSONPacket.prototype.write = function(stream) {
if (this._outgoing === undefined) {
// Format the serialized packet to a buffer
@ -197,29 +203,30 @@ JSONPacket.prototype.write = function (stream) {
};
Object.defineProperty(JSONPacket.prototype, "done", {
get: function () {
get() {
return this._done;
}
},
});
JSONPacket.prototype.toString = function () {
JSONPacket.prototype.toString = function() {
return JSON.stringify(this._object, null, 2);
};
/**
* With a bulk packet, data is transferred by temporarily handing over the
* transport's input or output stream to the application layer for writing data
* directly. This can be much faster for large data sets, and avoids various
* stages of copies and data duplication inherent in the JSON packet type. The
* bulk packet looks like:
* With a bulk packet, data is transferred by temporarily handing over
* the transport's input or output stream to the application layer for
* writing data directly. This can be much faster for large data sets,
* and avoids various stages of copies and data duplication inherent in
* the JSON packet type. The bulk packet looks like:
*
* bulk [actor] [type] [length]:[data]
* bulk [actor] [type] [length]:[data]
*
* The interpretation of the data portion depends on the kind of actor and the
* packet's type. See the Remote Debugging Protocol Stream Transport spec for
* more details.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
* The interpretation of the data portion depends on the kind of actor and
* the packet's type. See the Remote Debugging Protocol Stream Transport
* spec for more details.
*
* @param {DebuggerTransport} transport
* Transport instance that will own the packet.
*/
function BulkPacket(transport) {
Packet.call(this, transport);
@ -228,16 +235,18 @@ function BulkPacket(transport) {
}
/**
* Attempt to initialize a new BulkPacket based on the incoming packet header
* we've received so far.
* @param header string
* The packet header string to attempt parsing.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
* @return BulkPacket
* The parsed packet, or null if it's not a match.
* Attempt to initialize a new BulkPacket based on the incoming packet
* header we've received so far.
*
* @param {string} header
* Packet header string to attempt parsing.
* @param {DebuggerTransport} transport
* Transport instance that will own the packet.
*
* @return {BulkPacket}
* Parsed packet, or null if it's not a match.
*/
BulkPacket.fromHeader = function (header, transport) {
BulkPacket.fromHeader = function(header, transport) {
let match = this.HEADER_PATTERN.exec(header);
if (!match) {
@ -248,7 +257,7 @@ BulkPacket.fromHeader = function (header, transport) {
packet.header = {
actor: match[1],
type: match[2],
length: +match[3]
length: +match[3],
};
return packet;
};
@ -257,7 +266,7 @@ BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
BulkPacket.prototype = Object.create(Packet.prototype);
BulkPacket.prototype.read = function (stream) {
BulkPacket.prototype.read = function(stream) {
// Temporarily pause monitoring of the input stream
this._transport.pauseIncoming();
@ -272,8 +281,8 @@ BulkPacket.prototype.read = function (stream) {
deferred.resolve(copying);
return copying;
},
stream: stream,
done: deferred
stream,
done: deferred,
});
// Await the result of reading from the stream
@ -288,7 +297,7 @@ BulkPacket.prototype.read = function (stream) {
};
};
BulkPacket.prototype.write = function (stream) {
BulkPacket.prototype.write = function(stream) {
if (this._outgoingHeader === undefined) {
// Format the serialized packet header to a buffer
this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
@ -314,8 +323,8 @@ BulkPacket.prototype.write = function (stream) {
deferred.resolve(copying);
return copying;
},
stream: stream,
done: deferred
stream,
done: deferred,
});
// Await the result of writing to the stream
@ -331,21 +340,21 @@ BulkPacket.prototype.write = function (stream) {
};
Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
get: function () {
get() {
return this._readyForWriting.promise;
}
},
});
Object.defineProperty(BulkPacket.prototype, "header", {
get: function () {
get() {
return {
actor: this.actor,
type: this.type,
length: this.length
length: this.length,
};
},
set: function (header) {
set(header) {
this.actor = header.actor;
this.type = header.type;
this.length = header.length;
@ -353,12 +362,12 @@ Object.defineProperty(BulkPacket.prototype, "header", {
});
Object.defineProperty(BulkPacket.prototype, "done", {
get: function () {
get() {
return this._done;
},
});
BulkPacket.prototype.toString = function () {
BulkPacket.prototype.toString = function() {
return "Bulk: " + JSON.stringify(this.header, null, 2);
};
@ -379,19 +388,19 @@ function RawPacket(transport, data) {
RawPacket.prototype = Object.create(Packet.prototype);
RawPacket.prototype.read = function (stream) {
RawPacket.prototype.read = function(stream) {
// This hasn't yet been needed for testing.
throw Error("Not implmented.");
};
RawPacket.prototype.write = function (stream) {
RawPacket.prototype.write = function(stream) {
let written = stream.write(this._data, this._data.length);
this._data = this._data.slice(written);
this._done = !this._data.length;
};
Object.defineProperty(RawPacket.prototype, "done", {
get: function () {
get() {
return this._done;
}
},
});

View File

@ -30,7 +30,7 @@ var ownPriorityGetterTrap = {
return obj[prop];
}
return (...args) => obj.send(prop, args);
}
},
};
this.proxy = {};
@ -52,8 +52,9 @@ this.proxy = {};
* @param {function(string, Object, number)} sendAsyncFn
* Callback for sending async messages.
*/
proxy.toListener = function (mmFn, sendAsyncFn, browserFn) {
let sender = new proxy.AsyncMessageChannel(mmFn, sendAsyncFn, browserFn);
proxy.toListener = function(mmFn, sendAsyncFn, browserFn) {
let sender = new proxy.AsyncMessageChannel(
mmFn, sendAsyncFn, browserFn);
return new Proxy(sender, ownPriorityGetterTrap);
};
@ -100,8 +101,8 @@ proxy.AsyncMessageChannel = class {
* @param {string} name
* Function to call in the listener, e.g. for the message listener
* "Marionette:foo8", use "foo".
* @param {Array.<?>=} args
* Argument list to pass the function. If args has a single entry
* @param {Array.<?>=} args
* Argument list to pass the function. If args has a single entry
* that is an object, we assume it's an old style dispatch, and
* the object will passed literally.
*
@ -142,7 +143,7 @@ proxy.AsyncMessageChannel = class {
// The currently selected tab or window has been closed. No clean-up
// is necessary to do because all loaded listeners are gone.
this.closeHandler = event => {
logger.debug(`Received DOM event "${event.type}" for "${event.target}"`);
logger.debug(`Received DOM event ${event.type} for ${event.target}`);
switch (event.type) {
case "TabClose":
@ -183,15 +184,15 @@ proxy.AsyncMessageChannel = class {
addHandlers() {
modal.addHandler(this.dialogueObserver_);
// Register event handlers in case the command closes the current tab or window,
// and the promise has to be escaped.
// Register event handlers in case the command closes the current
// tab or window, and the promise has to be escaped.
if (this.browser) {
this.browser.window.addEventListener("unload", this.closeHandler, false);
this.browser.window.addEventListener("unload", this.closeHandler);
if (this.browser.tab) {
let node = this.browser.tab.addEventListener ?
this.browser.tab : this.browser.contentBrowser;
node.addEventListener("TabClose", this.closeHandler, false);
node.addEventListener("TabClose", this.closeHandler);
}
}
}
@ -203,13 +204,12 @@ proxy.AsyncMessageChannel = class {
modal.removeHandler(this.dialogueObserver_);
if (this.browser) {
this.browser.window.removeEventListener("unload", this.closeHandler, false);
this.browser.window.removeEventListener("unload", this.closeHandler);
if (this.browser.tab) {
let node = this.browser.tab.addEventListener ?
this.browser.tab : this.browser.contentBrowser;
node.removeEventListener("TabClose", this.closeHandler, false);
node.removeEventListener("TabClose", this.closeHandler);
}
}
}
@ -263,7 +263,7 @@ proxy.AsyncMessageChannel = class {
payload = data;
}
const msg = {type: type, data: payload};
const msg = {type, data: payload};
// here sendAsync is actually the content frame's
// sendAsyncMessage(path, message) global
@ -307,7 +307,7 @@ proxy.AsyncMessageChannel = class {
removeAllListeners_() {
let ok = true;
for (let [p, cb] of this.listeners_) {
for (let [p] of this.listeners_) {
ok |= this.removeListener_(p);
}
return ok;
@ -327,7 +327,7 @@ proxy.AsyncMessageChannel.ReplyType = {
* The content frame's message manager, which itself is usually an
* implementor of.
*/
proxy.toChromeAsync = function (frameMessageManager) {
proxy.toChromeAsync = function(frameMessageManager) {
let sender = new AsyncChromeSender(frameMessageManager);
return new Proxy(sender, ownPriorityGetterTrap);
};
@ -342,7 +342,7 @@ proxy.toChromeAsync = function (frameMessageManager) {
* let promise = sender.send("runEmulatorCmd", "my command");
* let rv = yield promise;
*/
this.AsyncChromeSender = class {
class AsyncChromeSender {
constructor(frameMessageManager) {
this.mm = frameMessageManager;
}
@ -353,7 +353,7 @@ this.AsyncChromeSender = class {
* @param {string} name
* Function to call in the chrome, e.g. for "Marionette:foo", use
* "foo".
* @param {?} args
* @param {?} args
* Argument list to pass the function. Must be JSON serialisable.
*
* @return {Promise}
@ -389,7 +389,7 @@ this.AsyncChromeSender = class {
return proxy;
}
};
}
/**
* Creates a transparent interface from the content- to the chrome context.
@ -406,7 +406,7 @@ this.AsyncChromeSender = class {
* @param {nsISyncMessageSender} sendSyncMessageFn
* The frame message manager's sendSyncMessage function.
*/
proxy.toChrome = function (sendSyncMessageFn) {
proxy.toChrome = function(sendSyncMessageFn) {
let sender = new proxy.SyncChromeSender(sendSyncMessageFn);
return new Proxy(sender, ownPriorityGetterTrap);
};
@ -432,7 +432,7 @@ proxy.SyncChromeSender = class {
}
};
var marshal = function (args) {
var marshal = function(args) {
if (args.length == 1 && typeof args[0] == "object") {
return args[0];
}

View File

@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/Task.jsm");
Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/capture.js");
const {InvalidArgumentError} =
Cu.import("chrome//marionette/content/error.js", {});
Cu.import("chrome://marionette/content/error.js", {});
this.EXPORTED_SYMBOLS = ["reftest"];
@ -25,7 +25,7 @@ const logger = Log.repository.getLogger("Marionette");
const SCREENSHOT_MODE = {
unexpected: 0,
fail: 1,
always: 2
always: 2,
};
const STATUS = {
@ -46,7 +46,7 @@ reftest.Runner = class {
this.driver = driver;
this.canvasCache = new Map([[null, []]]);
this.windowUtils = null;
this.lastUrl = null;
this.lastURL = null;
this.remote = Preferences.get(PREF_E10S);
}
@ -73,7 +73,7 @@ reftest.Runner = class {
.reduce((map, key) => map.set(key, urlCount[key]), new Map());
yield this.ensureWindow();
};
}
*ensureWindow() {
if (this.reftestWin && !this.reftestWin.closed) {
@ -189,7 +189,7 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let result = yield Promise.race([testRunner, timeoutPromise]);
this.parentWindow.clearTimeout(timeoutHandle);
if(result.status === STATUS.TIMEOUT) {
if (result.status === STATUS.TIMEOUT) {
this.abort();
}
@ -197,9 +197,13 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
}
*runTest(testUrl, references, expected, timeout) {
let win = yield this.ensureWindow();
function toBase64(screenshot) {
let dataURL = screenshot.canvas.toDataURL();
return dataURL.split(",")[1];
}
win.innerWidth = 600;
win.innerHeight = 600;
@ -208,7 +212,7 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let screenshotData = [];
let stack = [];
for (let i = references.length-1; i >= 0; i--) {
for (let i = references.length - 1; i >= 0; i--) {
let item = references[i];
stack.push([testUrl, item[0], item[1], item[2]]);
}
@ -219,13 +223,15 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let [lhsUrl, rhsUrl, references, relation] = stack.pop();
message += `Testing ${lhsUrl} ${relation} ${rhsUrl}\n`;
let comparison = yield this.compareUrls(win, lhsUrl, rhsUrl, relation, timeout);
let comparison = yield this.compareUrls(
win, lhsUrl, rhsUrl, relation, timeout);
function recordScreenshot() {
let toBase64 = screenshot => screenshot.canvas.toDataURL().split(",")[1];
screenshotData.push([{url: lhsUrl, screenshot: toBase64(comparison.lhs)},
relation,
{url:rhsUrl, screenshot: toBase64(comparison.rhs)}]);
let encodedLHS = toBase64(comparison.lhs);
let encodedRHS = toBase64(comparison.rhs);
screenshotData.push([{url: lhsUrl, screenshot: encodedLHS},
relation,
{url: rhsUrl, screenshot: encodedRHS}]);
}
if (this.screenshotMode === SCREENSHOT_MODE.always) {
@ -241,16 +247,18 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
} else {
// Reached a leaf node so all of one reference chain passed
status = STATUS.PASS;
if (this.screenshotMode <= SCREENSHOT_MODE.fail && expected != status) {
if (this.screenshotMode <= SCREENSHOT_MODE.fail &&
expected != status) {
recordScreenshot();
}
break;
}
} else if (!stack.length) {
// If we don't have any alternatives to try then this will be the last iteration,
// so save the failing screenshots if required
if (this.screenshotMode === SCREENSHOT_MODE.fail ||
(this.screenshotMode === SCREENSHOT_MODE.unexpected && expected != status)) {
// If we don't have any alternatives to try then this will be
// the last iteration, so save the failing screenshots if required.
let isFail = this.screenshotMode === SCREENSHOT_MODE.fail;
let isUnexpected = this.screenshotMode === SCREENSHOT_MODE.unexpected;
if (isFail || (isUnexpected && expected != status)) {
recordScreenshot();
}
}
@ -267,13 +275,14 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let result = {status, message, extra: {}};
if (screenshotData.length) {
// For now the tbpl formatter only accepts one screenshot, so just return the
// last one we took.
result.extra.reftest_screenshots = screenshotData[screenshotData.length - 1];
// For now the tbpl formatter only accepts one screenshot, so just
// return the last one we took.
let lastScreenshot = screenshotData[screenshotData.length - 1];
result.extra.reftest_screenshots = lastScreenshot;
}
return result;
};
}
*compareUrls(win, lhsUrl, rhsUrl, relation, timeout) {
logger.info(`Testing ${lhsUrl} ${relation} ${rhsUrl}`);
@ -285,21 +294,25 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let maxDifferences = {};
let differences = this.windowUtils.compareCanvases(lhs.canvas, rhs.canvas, maxDifferences);
let differences = this.windowUtils.compareCanvases(
lhs.canvas, rhs.canvas, maxDifferences);
let passed;
switch (relation) {
case "==":
passed = differences === 0;
if (!passed) {
logger.info(`Found ${differences} pixels different, maximum difference per channel ${maxDifferences.value}`);
}
break;
case "!=":
passed = differences !== 0;
break;
default:
throw new InvalidArgumentError("Reftest operator should be '==' or '!='");
case "==":
passed = differences === 0;
if (!passed) {
logger.info(`Found ${differences} pixels different, ` +
`maximum difference per channel ${maxDifferences.value}`);
}
break;
case "!=":
passed = differences !== 0;
break;
default:
throw new InvalidArgumentError("Reftest operator should be '==' or '!='");
}
return {lhs, rhs, passed};
@ -309,7 +322,8 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
let canvas = null;
let remainingCount = this.urlCount.get(url) || 1;
let cache = remainingCount > 1;
logger.debug(`screenshot ${url} remainingCount: ${remainingCount} cache: ${cache}`);
logger.debug(`screenshot ${url} remainingCount: ` +
`${remainingCount} cache: ${cache}`);
let reuseCanvas = false;
if (this.canvasCache.has(url)) {
logger.debug(`screenshot ${url} taken from cache`);
@ -332,26 +346,34 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
ctxInterface.DRAWWINDOW_DRAW_VIEW;
logger.debug(`Starting load of ${url}`);
if (this.lastUrl === url) {
let navigateOpts = {
commandId: this.driver.listener.activeMessageId,
pageTimeout: timeout,
};
if (this.lastURL === url) {
logger.debug(`Refreshing page`);
yield this.driver.listener.refresh({commandId: this.driver.listener.activeMessageId,
pageTimeout: timeout});
yield this.driver.listener.refresh(navigateOpts);
} else {
yield this.driver.listener.get({commandId: this.driver.listener.activeMessageId,
url: url,
pageTimeout: timeout,
loadEventExpected: false});
this.lastUrl = url;
navigateOpts.url = url;
navigateOpts.loadEventExpected = false;
yield this.driver.listener.get(navigateOpts);
this.lastURL = url;
}
this.driver.curBrowser.contentBrowser.focus();
yield this.driver.listener.reftestWait(url, this.remote);
canvas = capture.canvas(win, 0, 0, win.innerWidth, win.innerHeight, {canvas, flags});
canvas = capture.canvas(
win,
0, // left
0, // top
win.innerWidth,
win.innerHeight,
{canvas, flags});
}
if (cache) {
this.canvasCache.set(url, canvas);
};
}
this.urlCount.set(url, remainingCount - 1);
return {canvas, reuseCanvas};
}

View File

@ -20,13 +20,14 @@ Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("chrome://marionette/content/assert.js");
Cu.import("chrome://marionette/content/driver.js");
const {GeckoDriver} = Cu.import("chrome://marionette/content/driver.js", {});
const {
error,
UnknownCommandError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/message.js");
Cu.import("chrome://marionette/content/transport.js");
const {DebuggerTransport} =
Cu.import("chrome://marionette/content/transport.js", {});
XPCOMUtils.defineLazyServiceGetter(
this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");
@ -161,8 +162,14 @@ const RECOMMENDED_PREFS = new Map([
// Do not show datareporting policy notifications which can
// interfere with tests
["datareporting.healthreport.about.reportUrl", "http://%(server)s/dummy/abouthealthreport/"],
["datareporting.healthreport.documentServerURI", "http://%(server)s/dummy/healthreport/"],
[
"datareporting.healthreport.about.reportUrl",
"http://%(server)s/dummy/abouthealthreport/",
],
[
"datareporting.healthreport.documentServerURI",
"http://%(server)s/dummy/healthreport/",
],
["datareporting.healthreport.logging.consoleEnabled", false],
["datareporting.healthreport.service.enabled", false],
["datareporting.healthreport.service.firstRun", false],
@ -204,7 +211,10 @@ const RECOMMENDED_PREFS = new Map([
["extensions.update.notifyUser", false],
// Make sure opening about:addons will not hit the network
["extensions.webservice.discoverURL", "http://%(server)s/dummy/discoveryURL"],
[
"extensions.webservice.discoverURL",
"http://%(server)s/dummy/discoveryURL",
],
// Allow the application to have focus even it runs in the background
["focusmanager.testmode", true],
@ -277,7 +287,7 @@ server.TCPListener = class {
* @param {number} port
* Port for server to listen to.
*/
constructor (port) {
constructor(port) {
this.port = port;
this.socket = null;
this.conns = new Set();
@ -295,12 +305,12 @@ server.TCPListener = class {
* @return {GeckoDriver}
* A driver instance.
*/
driverFactory () {
driverFactory() {
Preferences.set(PREF_CONTENT_LISTENER, false);
return new GeckoDriver(Services.appinfo.name, this);
}
set acceptConnections (value) {
set acceptConnections(value) {
if (!value) {
logger.info("New connections will no longer be accepted");
} else {
@ -317,7 +327,7 @@ server.TCPListener = class {
* The marionette.port preference will be populated with the value
* of |this.port|.
*/
start () {
start() {
if (this.alive) {
return;
}
@ -347,7 +357,7 @@ server.TCPListener = class {
env.set(ENV_ENABLED, "1");
}
stop () {
stop() {
if (!this.alive) {
return;
}
@ -363,12 +373,12 @@ server.TCPListener = class {
}
this.alteredPrefs.clear();
Services.obs.notifyObservers(this, NOTIFY_RUNNING, false);
Services.obs.notifyObservers(this, NOTIFY_RUNNING);
this.alive = false;
}
onSocketAccepted (serverSocket, clientSocket) {
onSocketAccepted(serverSocket, clientSocket) {
if (!this._acceptConnections) {
logger.warn("New connections are currently not accepted");
return;
@ -383,12 +393,13 @@ server.TCPListener = class {
conn.onclose = this.onConnectionClosed.bind(this);
this.conns.add(conn);
logger.debug(`Accepted connection ${conn.id} from ${clientSocket.host}:${clientSocket.port}`);
logger.debug(`Accepted connection ${conn.id} ` +
`from ${clientSocket.host}:${clientSocket.port}`);
conn.sayHello();
transport.ready();
}
onConnectionClosed (conn) {
onConnectionClosed(conn) {
logger.debug(`Closed connection ${conn.id}`);
this.conns.delete(conn);
}
@ -408,7 +419,7 @@ server.TCPListener = class {
* Factory function that produces a |GeckoDriver|.
*/
server.TCPConnection = class {
constructor (connID, transport, driverFactory) {
constructor(connID, transport, driverFactory) {
this.id = connID;
this.conn = transport;
@ -432,7 +443,7 @@ server.TCPConnection = class {
* Debugger transport callback that cleans up
* after a connection is closed.
*/
onClosed (reason) {
onClosed(reason) {
this.driver.deleteSession();
if (this.onclose) {
this.onclose(this);
@ -451,7 +462,7 @@ server.TCPConnection = class {
* message type, message ID, method name or error, and parameters
* or result.
*/
onPacket (data) {
onPacket(data) {
// unable to determine how to respond
if (!Array.isArray(data)) {
let e = new TypeError(
@ -505,7 +516,7 @@ server.TCPConnection = class {
* @param {Command} cmd
* The requested command to execute.
*/
execute (cmd) {
execute(cmd) {
let resp = this.createResponse(cmd.id);
let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
let sendError = resp.sendError.bind(resp);
@ -543,14 +554,14 @@ server.TCPConnection = class {
* @return {message.Response}
* Response to the message with |msgID|.
*/
createResponse (msgID) {
createResponse(msgID) {
if (typeof msgID != "number") {
msgID = -1;
}
return new Response(msgID, this.send.bind(this));
}
sendError (err, cmdID) {
sendError(err, cmdID) {
let resp = new Response(cmdID, this.send.bind(this));
resp.sendError(err);
}
@ -562,13 +573,13 @@ server.TCPConnection = class {
* This is the only message sent by Marionette that does not follow
* the regular message format.
*/
sayHello () {
sayHello() {
let whatHo = {
applicationType: "gecko",
marionetteProtocol: PROTOCOL_VERSION,
};
this.sendRaw(whatHo);
};
}
/**
* Delegates message to client based on the provided {@code cmdID}.
@ -583,7 +594,7 @@ server.TCPConnection = class {
* @param {Command,Response} msg
* The command or response to send.
*/
send (msg) {
send(msg) {
msg.origin = MessageOrigin.Server;
if (msg instanceof Command) {
this.commands_.set(msg.id, msg);
@ -601,10 +612,10 @@ server.TCPConnection = class {
* @param {Response} resp
* The response to send back to the client.
*/
sendToClient (resp) {
sendToClient(resp) {
this.driver.responseCompleted();
this.sendMessage(resp);
};
}
/**
* Marshal message to the Marionette message format and send it.
@ -612,7 +623,7 @@ server.TCPConnection = class {
* @param {Command,Response} msg
* The message to send.
*/
sendMessage (msg) {
sendMessage(msg) {
this.log_(msg);
let payload = msg.toMsg();
this.sendRaw(payload);
@ -625,17 +636,17 @@ server.TCPConnection = class {
* @param {Object} payload
* The payload to ship.
*/
sendRaw (payload) {
sendRaw(payload) {
this.conn.send(payload);
}
log_ (msg) {
log_(msg) {
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
let s = JSON.stringify(msg.toMsg());
logger.trace(this.id + a + s);
}
toString () {
toString() {
return `[object server.TCPConnection ${this.id}]`;
}
};

View File

@ -32,7 +32,7 @@ this.session = {};
/** Representation of WebDriver session timeouts. */
session.Timeouts = class {
constructor () {
constructor() {
// disabled
this.implicit = 0;
// five mintues
@ -41,9 +41,9 @@ session.Timeouts = class {
this.script = 30000;
}
toString () { return "[object session.Timeouts]"; }
toString() { return "[object session.Timeouts]"; }
toJSON () {
toJSON() {
return {
implicit: this.implicit,
pageLoad: this.pageLoad,
@ -51,7 +51,7 @@ session.Timeouts = class {
};
}
static fromJSON (json) {
static fromJSON(json) {
assert.object(json);
let t = new session.Timeouts();
@ -136,7 +136,8 @@ session.Proxy = class {
case "pac":
Preferences.set("network.proxy.type", 2);
Preferences.set("network.proxy.autoconfig_url", this.proxyAutoconfigUrl);
Preferences.set(
"network.proxy.autoconfig_url", this.proxyAutoconfigUrl);
return true;
case "autodetect":
@ -156,13 +157,13 @@ session.Proxy = class {
}
}
toString () { return "[object session.Proxy]"; }
toString() { return "[object session.Proxy]"; }
toJSON () {
toJSON() {
return marshal({
proxyType: this.proxyType,
httpProxy: this.httpProxy,
httpProxyPort: this.httpProxyPort ,
httpProxyPort: this.httpProxyPort,
sslProxy: this.sslProxy,
sslProxyPort: this.sslProxyPort,
ftpProxy: this.ftpProxy,
@ -174,7 +175,7 @@ session.Proxy = class {
});
}
static fromJSON (json) {
static fromJSON(json) {
let p = new session.Proxy();
if (typeof json == "undefined" || json === null) {
return p;
@ -218,7 +219,7 @@ session.Proxy = class {
/** WebDriver session capabilities representation. */
session.Capabilities = class extends Map {
constructor () {
constructor() {
super([
// webdriver
["browserName", appinfo.name],
@ -241,7 +242,7 @@ session.Capabilities = class extends Map {
]);
}
set (key, value) {
set(key, value) {
if (key === "timeouts" && !(value instanceof session.Timeouts)) {
throw new TypeError();
} else if (key === "proxy" && !(value instanceof session.Proxy)) {
@ -272,7 +273,7 @@ session.Capabilities = class extends Map {
* @return {session.Capabilities}
* Internal representation of WebDriver capabilities.
*/
static fromJSON (json, {merge = false} = {}) {
static fromJSON(json, {merge = false} = {}) {
if (typeof json == "undefined" || json === null) {
json = {};
}
@ -285,12 +286,13 @@ session.Capabilities = class extends Map {
}
// Processes capabilities as described by WebDriver.
static merge_ (json) {
static merge_(json) {
for (let entry of [json.desiredCapabilities, json.requiredCapabilities]) {
if (typeof entry == "undefined" || entry === null) {
continue;
}
assert.object(entry, error.pprint`Expected ${entry} to be a capabilities object`);
assert.object(entry,
error.pprint`Expected ${entry} to be a capabilities object`);
}
let desired = json.desiredCapabilities || {};
@ -302,7 +304,7 @@ session.Capabilities = class extends Map {
}
// Matches capabilities as described by WebDriver.
static match_ (caps = {}) {
static match_(caps = {}) {
let matched = new session.Capabilities();
const defined = v => typeof v != "undefined" && v !== null;
@ -311,11 +313,11 @@ session.Capabilities = class extends Map {
// Iff |actual| provides some value, or is a wildcard or an exact
// match of |expected|. This means it can be null or undefined,
// or "*", or "firefox".
function stringMatch (actual, expected) {
function stringMatch(actual, expected) {
return !defined(actual) || (wildcard(actual) || actual === expected);
}
for (let [k,v] of Object.entries(caps)) {
for (let [k, v] of Object.entries(caps)) {
switch (k) {
case "browserName":
let bname = matched.get("browserName");
@ -368,7 +370,8 @@ session.Capabilities = class extends Map {
if (Object.values(session.PageLoadStrategy).includes(v)) {
matched.set("pageLoadStrategy", v);
} else {
throw new InvalidArgumentError("Unknown page load strategy: " + v);
throw new InvalidArgumentError(
"Unknown page load strategy: " + v);
}
}
@ -409,8 +412,8 @@ function marshal(obj) {
function* iter(mapOrObject) {
if (mapOrObject instanceof Map) {
for (const [k,v] of mapOrObject) {
yield [k,v];
for (const [k, v] of mapOrObject) {
yield [k, v];
}
} else {
for (const k of Object.keys(mapOrObject)) {
@ -419,7 +422,7 @@ function marshal(obj) {
}
}
for (let [k,v] of iter(obj)) {
for (let [k, v] of iter(obj)) {
// Skip empty values when serialising to JSON.
if (typeof v == "undefined" || v === null) {
continue;
@ -429,10 +432,9 @@ function marshal(obj) {
// JSON representation.
if (typeof v.toJSON == "function") {
v = marshal(v.toJSON());
}
// Or do the same for object literals.
else if (isObject(v)) {
} else if (isObject(v)) {
v = marshal(v);
}

View File

@ -4,7 +4,8 @@
"use strict";
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu, results: Cr} =
Components;
Cu.import("resource://gre/modules/EventEmitter.jsm");
Cu.import("resource://gre/modules/Services.jsm");
@ -19,37 +20,39 @@ this.EXPORTED_SYMBOLS = ["StreamUtils"];
const BUFFER_SIZE = 0x8000;
/**
* This helper function (and its companion object) are used by bulk senders and
* receivers to read and write data in and out of other streams. Functions that
* make use of this tool are passed to callers when it is time to read or write
* bulk data. It is highly recommended to use these copier functions instead of
* the stream directly because the copier enforces the agreed upon length.
* Since bulk mode reuses an existing stream, the sender and receiver must write
* and read exactly the agreed upon amount of data, or else the entire transport
* will be left in a invalid state. Additionally, other methods of stream
* copying (such as NetUtil.asyncCopy) close the streams involved, which would
* terminate the debugging transport, and so it is avoided here.
* This helper function (and its companion object) are used by bulk
* senders and receivers to read and write data in and out of other streams.
* Functions that make use of this tool are passed to callers when it is
* time to read or write bulk data. It is highly recommended to use these
* copier functions instead of the stream directly because the copier
* enforces the agreed upon length. Since bulk mode reuses an existing
* stream, the sender and receiver must write and read exactly the agreed
* upon amount of data, or else the entire transport will be left in a
* invalid state. Additionally, other methods of stream copying (such as
* NetUtil.asyncCopy) close the streams involved, which would terminate
* the debugging transport, and so it is avoided here.
*
* Overall, this *works*, but clearly the optimal solution would be able to just
* use the streams directly. If it were possible to fully implement
* nsIInputStream / nsIOutputStream in JS, wrapper streams could be created to
* enforce the length and avoid closing, and consumers could use familiar stream
* utilities like NetUtil.asyncCopy.
* Overall, this *works*, but clearly the optimal solution would be
* able to just use the streams directly. If it were possible to fully
* implement nsIInputStream/nsIOutputStream in JS, wrapper streams could
* be created to enforce the length and avoid closing, and consumers could
* use familiar stream utilities like NetUtil.asyncCopy.
*
* The function takes two async streams and copies a precise number of bytes
* from one to the other. Copying begins immediately, but may complete at some
* future time depending on data size. Use the returned promise to know when
* it's complete.
* The function takes two async streams and copies a precise number
* of bytes from one to the other. Copying begins immediately, but may
* complete at some future time depending on data size. Use the returned
* promise to know when it's complete.
*
* @param input nsIAsyncInputStream
* The stream to copy from.
* @param output nsIAsyncOutputStream
* The stream to copy to.
* @param length Integer
* The amount of data that needs to be copied.
* @return Promise
* The promise is resolved when copying completes or rejected if any
* (unexpected) errors occur.
* @param {nsIAsyncInputStream} input
* Stream to copy from.
* @param {nsIAsyncOutputStream} output
* Stream to copy to.
* @param {number} length
* Amount of data that needs to be copied.
*
* @return {Promise}
* Promise is resolved when copying completes or rejected if any
* (unexpected) errors occur.
*/
function copyStream(input, output, length) {
let copier = new StreamCopier(input, output, length);
@ -60,7 +63,8 @@ function StreamCopier(input, output, length) {
EventEmitter.decorate(this);
this._id = StreamCopier._nextId++;
this.input = input;
// Save off the base output stream, since we know it's async as we've required
// Save off the base output stream, since we know it's async as we've
// required
this.baseAsyncOutput = output;
if (IOUtil.outputStreamIsBuffered(output)) {
this.output = output;
@ -75,7 +79,7 @@ function StreamCopier(input, output, length) {
promise: new Promise((resolve, reject) => {
this._deferred.resolve = resolve;
this._deferred.reject = reject;
})
}),
};
this._copy = this._copy.bind(this);
@ -83,21 +87,22 @@ function StreamCopier(input, output, length) {
this._destroy = this._destroy.bind(this);
// Copy promise's then method up to this object.
// Allows the copier to offer a promise interface for the simple succeed or
// fail scenarios, but also emit events (due to the EventEmitter) for other
// states, like progress.
//
// Allows the copier to offer a promise interface for the simple succeed
// or fail scenarios, but also emit events (due to the EventEmitter)
// for other states, like progress.
this.then = this._deferred.promise.then.bind(this._deferred.promise);
this.then(this._destroy, this._destroy);
// Stream ready callback starts as |_copy|, but may switch to |_flush| at end
// if flushing would block the output stream.
// Stream ready callback starts as |_copy|, but may switch to |_flush|
// at end if flushing would block the output stream.
this._streamReadyCallback = this._copy;
}
StreamCopier._nextId = 0;
StreamCopier.prototype = {
copy: function () {
copy() {
// Dispatch to the next tick so that it's possible to attach a progress
// event listener, even for extremely fast copies (like when testing).
Services.tm.currentThread.dispatch(() => {
@ -110,7 +115,7 @@ StreamCopier.prototype = {
return this;
},
_copy: function () {
_copy() {
let bytesAvailable = this.input.available();
let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
this._debug("Trying to copy: " + amountToCopy);
@ -143,14 +148,14 @@ StreamCopier.prototype = {
this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
},
_emitProgress: function () {
_emitProgress() {
this.emit("progress", {
bytesSent: this._length - this._amountLeft,
totalBytes: this._length
totalBytes: this._length,
});
},
_flush: function () {
_flush() {
try {
this.output.flush();
} catch (e) {
@ -167,7 +172,7 @@ StreamCopier.prototype = {
this._deferred.resolve();
},
_destroy: function () {
_destroy() {
this._destroy = null;
this._copy = null;
this._flush = null;
@ -176,37 +181,40 @@ StreamCopier.prototype = {
},
// nsIInputStreamCallback
onInputStreamReady: function () {
onInputStreamReady() {
this._streamReadyCallback();
},
// nsIOutputStreamCallback
onOutputStreamReady: function () {
onOutputStreamReady() {
this._streamReadyCallback();
},
_debug: function (msg) {
}
_debug(msg) {
},
};
/**
* Read from a stream, one byte at a time, up to the next |delimiter|
* character, but stopping if we've read |count| without finding it. Reading
* also terminates early if there are less than |count| bytes available on the
* stream. In that case, we only read as many bytes as the stream currently has
* to offer.
* TODO: This implementation could be removed if bug 984651 is fixed, which
* provides a native version of the same idea.
* @param stream nsIInputStream
* The input stream to read from.
* @param delimiter string
* The character we're trying to find.
* @param count integer
* The max number of characters to read while searching.
* @return string
* The data collected. If the delimiter was found, this string will
* end with it.
* character, but stopping if we've read |count| without finding it.
* Reading also terminates early if there are less than |count| bytes
* available on the stream. In that case, we only read as many bytes as
* the stream currently has to offer.
*
* TODO: This implementation could be removed if bug 984651 is fixed,
* which provides a native version of the same idea.
*
* @param {nsIInputStream} stream
* Input stream to read from.
* @param {string} delimiter
* Character we're trying to find.
* @param {number} count
* Max number of characters to read while searching.
*
* @return {string}
* Collected data. If the delimiter was found, this string will
* end with it.
*/
function delimitedRead(stream, delimiter, count) {
let scriptableStream;
@ -235,8 +243,7 @@ function delimitedRead(stream, delimiter, count) {
return data;
}
const StreamUtils = {
this.StreamUtils = {
copyStream,
delimitedRead
delimitedRead,
};

View File

@ -128,7 +128,11 @@ add_test(function test_toJSON() {
equal(e1s.message, e1.message);
equal(e1s.stacktrace, e1.stack);
let e2 = new JavaScriptError("first", "second", "third", "fourth");
let e2 = new JavaScriptError("first", {
fnName: "second",
file: "third",
line: "fourth",
});
let e2s = e2.toJSON();
equal(e2.status, e2s.error);
equal(e2.message, e2s.message);
@ -311,9 +315,9 @@ add_test(function test_JavaScriptError() {
equal("undefined", new JavaScriptError(undefined).message);
// TODO(ato): Bug 1240550
//equal("funcname @file", new JavaScriptError("message", "funcname", "file").stack);
//equal("funcname @file", new JavaScriptError("message", {fnName: "funcname", file: "file"}).stack);
equal("funcname @file, line line",
new JavaScriptError("message", "funcname", "file", "line").stack);
new JavaScriptError("message", {fnName: "funcname", file: "file", line: "line"}).stack);
// TODO(ato): More exhaustive tests for JS stack computation

View File

@ -6,31 +6,36 @@
/* global Pipe, ScriptableInputStream, uneval */
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu, results: Cr} =
Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/EventEmitter.jsm");
Cu.import("chrome://marionette/content/stream-utils.js");
const { Packet, JSONPacket, BulkPacket } =
Cu.import("chrome://marionette/content/packets.js");
const defer = function () {
const {StreamUtils} =
Cu.import("chrome://marionette/content/stream-utils.js", {});
const {Packet, JSONPacket, BulkPacket} =
Cu.import("chrome://marionette/content/packets.js", {});
const defer = function() {
let deferred = {
promise: new Promise((resolve, reject) => {
deferred.resolve = resolve;
deferred.reject = reject;
})
}),
};
return deferred;
};
const executeSoon = function (func) {
const executeSoon = function(func) {
Services.tm.dispatchToMainThread(func);
};
const flags = { wantVerbose: false, wantLogging: false };
const flags = {wantVerbose: false, wantLogging: false};
const dumpv =
flags.wantVerbose ?
function (msg) {dump(msg + "\n");} :
function () {};
function(msg) { dump(msg + "\n"); } :
function() {};
const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
@ -42,23 +47,24 @@ this.EXPORTED_SYMBOLS = ["DebuggerTransport"];
const PACKET_HEADER_MAX = 200;
/**
* An adapter that handles data transfers between the debugger client and
* server. It can work with both nsIPipe and nsIServerSocket transports so
* long as the properly created input and output streams are specified.
* (However, for intra-process connections, LocalDebuggerTransport, below,
* is more efficient than using an nsIPipe pair with DebuggerTransport.)
* An adapter that handles data transfers between the debugger client
* and server. It can work with both nsIPipe and nsIServerSocket
* transports so long as the properly created input and output streams
* are specified. (However, for intra-process connections,
* LocalDebuggerTransport, below, is more efficient than using an nsIPipe
* pair with DebuggerTransport.)
*
* @param input nsIAsyncInputStream
* The input stream.
* @param output nsIAsyncOutputStream
* The output stream.
* @param {nsIAsyncInputStream} input
* The input stream.
* @param {nsIAsyncOutputStream} output
* The output stream.
*
* Given a DebuggerTransport instance dt:
* 1) Set dt.hooks to a packet handler object (described below).
* 2) Call dt.ready() to begin watching for input packets.
* 3) Call dt.send() / dt.startBulkSend() to send packets.
* 4) Call dt.close() to close the connection, and disengage from the event
* loop.
* 4) Call dt.close() to close the connection, and disengage from
* the event loop.
*
* A packet handler is an object with the following methods:
*
@ -71,32 +77,34 @@ const PACKET_HEADER_MAX = 200;
* * actor: Name of actor that will receive the packet
* * type: Name of actor's method that should be called on receipt
* * length: Size of the data to be read
* * stream: This input stream should only be used directly if you can ensure
* that you will read exactly |length| bytes and will not close the
* stream when reading is complete
* * done: If you use the stream directly (instead of |copyTo| below), you
* must signal completion by resolving / rejecting this deferred.
* If it's rejected, the transport will be closed. If an Error is
* supplied as a rejection value, it will be logged via |dump|.
* If you do use |copyTo|, resolving is taken care of for you when
* copying completes.
* * copyTo: A helper function for getting your data out of the stream that
* meets the stream handling requirements above, and has the
* following signature:
* @param output nsIAsyncOutputStream
* The stream to copy to.
* @return Promise
* The promise is resolved when copying completes or rejected if any
* (unexpected) errors occur.
* This object also emits "progress" events for each chunk that is
* copied. See stream-utils.js.
* * stream: This input stream should only be used directly if you
* can ensure that you will read exactly |length| bytes and
* will not close the stream when reading is complete
* * done: If you use the stream directly (instead of |copyTo|
* below), you must signal completion by resolving/rejecting
* this deferred. If it's rejected, the transport will
* be closed. If an Error is supplied as a rejection value,
* it will be logged via |dump|. If you do use |copyTo|,
* resolving is taken care of for you when copying completes.
* * copyTo: A helper function for getting your data out of the
* stream that meets the stream handling requirements above,
* and has the following signature:
*
* - onClosed(reason) - called when the connection is closed. |reason| is
* an optional nsresult or object, typically passed when the transport is
* closed due to some error in a underlying stream.
* @param nsIAsyncOutputStream {output}
* The stream to copy to.
*
* See ./packets.js and the Remote Debugging Protocol specification for more
* details on the format of these packets.
* @return {Promise}
* The promise is resolved when copying completes or
* rejected if any (unexpected) errors occur. This object
* also emits "progress" events for each chunk that is
* copied. See stream-utils.js.
*
* - onClosed(reason) - called when the connection is closed. |reason|
* is an optional nsresult or object, typically passed when the
* transport is closed due to some error in a underlying stream.
*
* See ./packets.js and the Remote Debugging Protocol specification for
* more details on the format of these packets.
*/
function DebuggerTransport(input, output) {
EventEmitter.decorate(this);
@ -105,8 +113,8 @@ function DebuggerTransport(input, output) {
this._scriptableInput = new ScriptableInputStream(input);
this._output = output;
// The current incoming (possibly partial) header, which will determine which
// type of Packet |_incoming| below will become.
// The current incoming (possibly partial) header, which will determine
// which type of Packet |_incoming| below will become.
this._incomingHeader = "";
// The current incoming Packet object
this._incoming = null;
@ -128,10 +136,10 @@ DebuggerTransport.prototype = {
*
* This method returns immediately, without waiting for the entire
* packet to be transmitted, registering event handlers as needed to
* transmit the entire packet. Packets are transmitted in the order
* they are passed to this method.
* transmit the entire packet. Packets are transmitted in the order they
* are passed to this method.
*/
send: function (object) {
send(object) {
this.emit("send", object);
let packet = new JSONPacket(this);
@ -143,45 +151,52 @@ DebuggerTransport.prototype = {
/**
* Transmit streaming data via a bulk packet.
*
* This method initiates the bulk send process by queuing up the header data.
* The caller receives eventual access to a stream for writing.
* This method initiates the bulk send process by queuing up the header
* data. The caller receives eventual access to a stream for writing.
*
* N.B.: Do *not* attempt to close the stream handed to you, as it will
* continue to be used by this transport afterwards. Most users should
* instead use the provided |copyFrom| function instead.
* N.B.: Do *not* attempt to close the stream handed to you, as it
* will continue to be used by this transport afterwards. Most users
* should instead use the provided |copyFrom| function instead.
*
* @param header Object
* This is modeled after the format of JSON packets above, but does not
* actually contain the data, but is instead just a routing header:
* * actor: Name of actor that will receive the packet
* * type: Name of actor's method that should be called on receipt
* * length: Size of the data to be sent
* @return Promise
* The promise will be resolved when you are allowed to write to the
* stream with an object containing:
* * stream: This output stream should only be used directly if
* you can ensure that you will write exactly |length|
* bytes and will not close the stream when writing is
* complete
* * done: If you use the stream directly (instead of |copyFrom|
* below), you must signal completion by resolving /
* rejecting this deferred. If it's rejected, the
* transport will be closed. If an Error is supplied as
* a rejection value, it will be logged via |dump|. If
* you do use |copyFrom|, resolving is taken care of for
* you when copying completes.
* * copyFrom: A helper function for getting your data onto the
* stream that meets the stream handling requirements
* above, and has the following signature:
* @param input nsIAsyncInputStream
* The stream to copy from.
* @return Promise
* The promise is resolved when copying completes or
* rejected if any (unexpected) errors occur.
* This object also emits "progress" events for each chunk
* that is copied. See stream-utils.js.
* @param {Object} header
* This is modeled after the format of JSON packets above, but does
* not actually contain the data, but is instead just a routing
* header:
*
* - actor: Name of actor that will receive the packet
* - type: Name of actor's method that should be called on receipt
* - length: Size of the data to be sent
*
* @return {Promise}
* The promise will be resolved when you are allowed to write to
* the stream with an object containing:
*
* - stream: This output stream should only be used directly
* if you can ensure that you will write exactly
* |length| bytes and will not close the stream when
* writing is complete.
* - done: If you use the stream directly (instead of
* |copyFrom| below), you must signal completion by
* resolving/rejecting this deferred. If it's
* rejected, the transport will be closed. If an
* Error is supplied as a rejection value, it will
* be logged via |dump|. If you do use |copyFrom|,
* resolving is taken care of for you when copying
* completes.
* - copyFrom: A helper function for getting your data onto the
* stream that meets the stream handling requirements
* above, and has the following signature:
*
* @param {nsIAsyncInputStream} input
* The stream to copy from.
*
* @return {Promise}
* The promise is resolved when copying completes
* or rejected if any (unexpected) errors occur.
* This object also emits "progress" events for
* each chunkthat is copied. See stream-utils.js.
*/
startBulkSend: function (header) {
startBulkSend(header) {
this.emit("startbulksend", header);
let packet = new BulkPacket(this);
@ -193,11 +208,13 @@ DebuggerTransport.prototype = {
/**
* Close the transport.
* @param reason nsresult / object (optional)
* The status code or error message that corresponds to the reason for
* closing the transport (likely because a stream closed or failed).
*
* @param {(nsresult|object)=} reason
* The status code or error message that corresponds to the reason
* for closing the transport (likely because a stream closed
* or failed).
*/
close: function (reason) {
close(reason) {
this.emit("close", reason);
this.active = false;
@ -225,10 +242,11 @@ DebuggerTransport.prototype = {
},
/**
* Flush data to the outgoing stream. Waits until the output stream notifies
* us that it is ready to be written to (via onOutputStreamReady).
* Flush data to the outgoing stream. Waits until the output
* stream notifies us that it is ready to be written to (via
* onOutputStreamReady).
*/
_flushOutgoing: function () {
_flushOutgoing() {
if (!this._outgoingEnabled || this._outgoing.length === 0) {
return;
}
@ -245,29 +263,29 @@ DebuggerTransport.prototype = {
},
/**
* Pause this transport's attempts to write to the output stream. This is
* used when we've temporarily handed off our output stream for writing bulk
* data.
* Pause this transport's attempts to write to the output stream.
* This is used when we've temporarily handed off our output stream for
* writing bulk data.
*/
pauseOutgoing: function () {
pauseOutgoing() {
this._outgoingEnabled = false;
},
/**
* Resume this transport's attempts to write to the output stream.
*/
resumeOutgoing: function () {
resumeOutgoing() {
this._outgoingEnabled = true;
this._flushOutgoing();
},
// nsIOutputStreamCallback
/**
* This is called when the output stream is ready for more data to be written.
* The current outgoing packet will attempt to write some amount of data, but
* may not complete.
* This is called when the output stream is ready for more data to
* be written. The current outgoing packet will attempt to write some
* amount of data, but may not complete.
*/
onOutputStreamReady: function (stream) {
onOutputStreamReady(stream) {
if (!this._outgoingEnabled || this._outgoing.length === 0) {
return;
}
@ -288,7 +306,7 @@ DebuggerTransport.prototype = {
/**
* Remove the current outgoing packet from the queue upon completion.
*/
_finishCurrentOutgoing: function () {
_finishCurrentOutgoing() {
if (this._currentOutgoing) {
this._currentOutgoing.destroy();
this._outgoing.shift();
@ -298,7 +316,7 @@ DebuggerTransport.prototype = {
/**
* Clear the entire outgoing queue.
*/
_destroyAllOutgoing: function () {
_destroyAllOutgoing() {
for (let packet of this._outgoing) {
packet.destroy();
}
@ -306,11 +324,11 @@ DebuggerTransport.prototype = {
},
/**
* Initialize the input stream for reading. Once this method has been called,
* we watch for packets on the input stream, and pass them to the appropriate
* handlers via this.hooks.
* Initialize the input stream for reading. Once this method has been
* called, we watch for packets on the input stream, and pass them to
* the appropriate handlers via this.hooks.
*/
ready: function () {
ready() {
this.active = true;
this._waitForIncoming();
},
@ -319,7 +337,7 @@ DebuggerTransport.prototype = {
* Asks the input stream to notify us (via onInputStreamReady) when it is
* ready for reading.
*/
_waitForIncoming: function () {
_waitForIncoming() {
if (this._incomingEnabled) {
let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
this._input.asyncWait(this, 0, 0, threadManager.currentThread);
@ -327,18 +345,18 @@ DebuggerTransport.prototype = {
},
/**
* Pause this transport's attempts to read from the input stream. This is
* used when we've temporarily handed off our input stream for reading bulk
* data.
* Pause this transport's attempts to read from the input stream.
* This is used when we've temporarily handed off our input stream for
* reading bulk data.
*/
pauseIncoming: function () {
pauseIncoming() {
this._incomingEnabled = false;
},
/**
* Resume this transport's attempts to read from the input stream.
*/
resumeIncoming: function () {
resumeIncoming() {
this._incomingEnabled = true;
this._flushIncoming();
this._waitForIncoming();
@ -348,7 +366,7 @@ DebuggerTransport.prototype = {
/**
* Called when the stream is either readable or closed.
*/
onInputStreamReady: function (stream) {
onInputStreamReady(stream) {
try {
while (stream.available() && this._incomingEnabled &&
this._processIncoming(stream, stream.available())) {
@ -365,16 +383,17 @@ DebuggerTransport.prototype = {
},
/**
* Process the incoming data. Will create a new currently incoming Packet if
* needed. Tells the incoming Packet to read as much data as it can, but
* reading may not complete. The Packet signals that its data is ready for
* delivery by calling one of this transport's _on*Ready methods (see
* ./packets.js and the _on*Ready methods below).
* @return boolean
* Whether incoming stream processing should continue for any
* remaining data.
* Process the incoming data. Will create a new currently incoming
* Packet if needed. Tells the incoming Packet to read as much data
* as it can, but reading may not complete. The Packet signals that
* its data is ready for delivery by calling one of this transport's
* _on*Ready methods (see ./packets.js and the _on*Ready methods below).
*
* @return {boolean}
* Whether incoming stream processing should continue for any
* remaining data.
*/
_processIncoming: function (stream, count) {
_processIncoming(stream, count) {
dumpv("Data available: " + count);
if (!count) {
@ -406,8 +425,7 @@ DebuggerTransport.prototype = {
this._incoming.read(stream, this._scriptableInput);
}
} catch (e) {
let msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
dump(msg + "\n");
dump(`Error reading incoming packet: (${e} - ${e.stack})\n`);
// Now in an invalid state, shut down the transport.
this.close();
@ -426,13 +444,14 @@ DebuggerTransport.prototype = {
},
/**
* Read as far as we can into the incoming data, attempting to build up a
* complete packet header (which terminates with ":"). We'll only read up to
* PACKET_HEADER_MAX characters.
* @return boolean
* True if we now have a complete header.
* Read as far as we can into the incoming data, attempting to build
* up a complete packet header (which terminates with ":"). We'll only
* read up to PACKET_HEADER_MAX characters.
*
* @return {boolean}
* True if we now have a complete header.
*/
_readHeader: function () {
_readHeader() {
let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
this._incomingHeader +=
StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
@ -458,7 +477,7 @@ DebuggerTransport.prototype = {
/**
* If the incoming packet is done, log it as needed and clear the buffer.
*/
_flushIncoming: function () {
_flushIncoming() {
if (!this._incoming.done) {
return;
}
@ -469,10 +488,10 @@ DebuggerTransport.prototype = {
},
/**
* Handler triggered by an incoming JSONPacket completing it's |read| method.
* Delivers the packet to this.hooks.onPacket.
* Handler triggered by an incoming JSONPacket completing it's |read|
* method. Delivers the packet to this.hooks.onPacket.
*/
_onJSONObjectReady: function (object) {
_onJSONObjectReady(object) {
executeSoon(() => {
// Ensure the transport is still alive by the time this runs.
if (this.active) {
@ -483,12 +502,12 @@ DebuggerTransport.prototype = {
},
/**
* Handler triggered by an incoming BulkPacket entering the |read| phase for
* the stream portion of the packet. Delivers info about the incoming
* streaming data to this.hooks.onBulkPacket. See the main comment on the
* transport at the top of this file for more details.
* Handler triggered by an incoming BulkPacket entering the |read|
* phase for the stream portion of the packet. Delivers info about the
* incoming streaming data to this.hooks.onBulkPacket. See the main
* comment on the transport at the top of this file for more details.
*/
_onBulkReadReady: function (...args) {
_onBulkReadReady(...args) {
executeSoon(() => {
// Ensure the transport is still alive by the time this runs.
if (this.active) {
@ -499,29 +518,30 @@ DebuggerTransport.prototype = {
},
/**
* Remove all handlers and references related to the current incoming packet,
* either because it is now complete or because the transport is closing.
* Remove all handlers and references related to the current incoming
* packet, either because it is now complete or because the transport
* is closing.
*/
_destroyIncoming: function () {
_destroyIncoming() {
if (this._incoming) {
this._incoming.destroy();
}
this._incomingHeader = "";
this._incoming = null;
}
},
};
/**
* An adapter that handles data transfers between the debugger client and
* server when they both run in the same process. It presents the same API as
* DebuggerTransport, but instead of transmitting serialized messages across a
* connection it merely calls the packet dispatcher of the other side.
* An adapter that handles data transfers between the debugger client
* and server when they both run in the same process. It presents the
* same API as DebuggerTransport, but instead of transmitting serialized
* messages across a connection it merely calls the packet dispatcher of
* the other side.
*
* @param other LocalDebuggerTransport
* The other endpoint for this debugger connection.
* @param {LocalDebuggerTransport} other
* The other endpoint for this debugger connection.
*
* @see DebuggerTransport
* @see {DebuggerTransport}
*/
function LocalDebuggerTransport(other) {
EventEmitter.decorate(this);
@ -529,9 +549,10 @@ function LocalDebuggerTransport(other) {
this.other = other;
this.hooks = null;
// A packet number, shared between this and this.other. This isn't used by the
// protocol at all, but it makes the packet traces a lot easier to follow.
this._serial = this.other ? this.other._serial : { count: 0 };
// A packet number, shared between this and this.other. This isn't
// used by the protocol at all, but it makes the packet traces a lot
// easier to follow.
this._serial = this.other ? this.other._serial : {count: 0};
this.close = this.close.bind(this);
}
@ -540,7 +561,7 @@ LocalDebuggerTransport.prototype = {
* Transmit a message by directly calling the onPacket handler of the other
* endpoint.
*/
send: function (packet) {
send(packet) {
this.emit("send", packet);
let serial = this._serial.count++;
@ -558,7 +579,8 @@ LocalDebuggerTransport.prototype = {
executeSoon(() => {
// Avoid the cost of JSON.stringify() when logging is disabled.
if (flags.wantLogging) {
dumpv("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
dumpv(`Received packet ${serial}: ` +
JSON.stringify(packet, null, 2));
}
if (other.hooks) {
other.emit("packet", packet);
@ -569,15 +591,15 @@ LocalDebuggerTransport.prototype = {
},
/**
* Send a streaming bulk packet directly to the onBulkPacket handler of the
* other endpoint.
* Send a streaming bulk packet directly to the onBulkPacket handler
* of the other endpoint.
*
* This case is much simpler than the full DebuggerTransport, since there is
* no primary stream we have to worry about managing while we hand it off to
* others temporarily. Instead, we can just make a single use pipe and be
* done with it.
* This case is much simpler than the full DebuggerTransport, since
* there is no primary stream we have to worry about managing while
* we hand it off to others temporarily. Instead, we can just make a
* single use pipe and be done with it.
*/
startBulkSend: function ({actor, type, length}) {
startBulkSend({actor, type, length}) {
this.emit("startbulksend", {actor, type, length});
let serial = this._serial.count++;
@ -599,9 +621,9 @@ LocalDebuggerTransport.prototype = {
// Receiver
let deferred = defer();
let packet = {
actor: actor,
type: type,
length: length,
actor,
type,
length,
copyTo: (output) => {
let copying =
StreamUtils.copyStream(pipe.inputStream, output, length);
@ -609,7 +631,7 @@ LocalDebuggerTransport.prototype = {
return copying;
},
stream: pipe.inputStream,
done: deferred
done: deferred,
};
this.other.emit("bulkpacket", packet);
@ -622,8 +644,8 @@ LocalDebuggerTransport.prototype = {
// Sender
let sendDeferred = defer();
// The remote transport is not capable of resolving immediately here, so we
// shouldn't be able to either.
// The remote transport is not capable of resolving immediately here,
// so we shouldn't be able to either.
executeSoon(() => {
let copyDeferred = defer();
@ -635,7 +657,7 @@ LocalDebuggerTransport.prototype = {
return copying;
},
stream: pipe.outputStream,
done: copyDeferred
done: copyDeferred,
});
// Await the result of writing to the stream
@ -648,7 +670,7 @@ LocalDebuggerTransport.prototype = {
/**
* Close the transport.
*/
close: function () {
close() {
this.emit("close");
if (this.other) {
@ -671,24 +693,24 @@ LocalDebuggerTransport.prototype = {
/**
* An empty method for emulating the DebuggerTransport API.
*/
ready: function () {},
ready() {},
/**
* Helper function that makes an object fully immutable.
*/
_deepFreeze: function (object) {
_deepFreeze(object) {
Object.freeze(object);
for (let prop in object) {
// Freeze the properties that are objects, not on the prototype, and not
// already frozen. Note that this might leave an unfrozen reference
// somewhere in the object if there is an already frozen object containing
// an unfrozen object.
// Freeze the properties that are objects, not on the prototype,
// and not already frozen. Note that this might leave an unfrozen
// reference somewhere in the object if there is an already frozen
// object containing an unfrozen object.
if (object.hasOwnProperty(prop) && typeof object === "object" &&
!Object.isFrozen(object)) {
this._deepFreeze(object[prop]);
}
}
}
},
};
/**
@ -733,28 +755,29 @@ ChildDebuggerTransport.prototype = {
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
throw e;
}
// In some cases, especially when using messageManagers in non-e10s mode, we reach
// this point with a dead messageManager which only throws errors but does not
// seem to indicate in any other way that it is dead.
// In some cases, especially when using messageManagers in non-e10s
// mode, we reach this point with a dead messageManager which only
// throws errors but does not seem to indicate in any other way that
// it is dead.
}
},
ready: function () {
ready() {
this._addListener();
},
close: function () {
close() {
this._removeListener();
this.emit("close");
this.hooks.onClosed();
},
receiveMessage: function ({data}) {
receiveMessage({data}) {
this.emit("packet", data);
this.hooks.onPacket(data);
},
send: function (packet) {
send(packet) {
this.emit("send", packet);
try {
this._mm.sendAsyncMessage(this._messageName, packet);
@ -762,13 +785,14 @@ ChildDebuggerTransport.prototype = {
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
throw e;
}
// In some cases, especially when using messageManagers in non-e10s mode, we reach
// this point with a dead messageManager which only throws errors but does not
// seem to indicate in any other way that it is dead.
// In some cases, especially when using messageManagers in non-e10s
// mode, we reach this point with a dead messageManager which only
// throws errors but does not seem to indicate in any other way that
// it is dead.
}
},
startBulkSend: function () {
startBulkSend() {
throw new Error("Can't send bulk data to child processes.");
},
@ -791,7 +815,7 @@ ChildDebuggerTransport.prototype = {
if (!this.isWorker) {
// Main thread
(function () {
(function() {
/**
* A transport that uses a WorkerDebugger to send packets from the main
* thread to a worker thread.
@ -805,30 +829,30 @@ if (!this.isWorker) {
WorkerDebuggerTransport.prototype = {
constructor: WorkerDebuggerTransport,
ready: function () {
ready() {
this._dbg.addListener(this);
},
close: function () {
close() {
this._dbg.removeListener(this);
if (this.hooks) {
this.hooks.onClosed();
}
},
send: function (packet) {
send(packet) {
this._dbg.postMessage(JSON.stringify({
type: "message",
id: this._id,
message: packet
message: packet,
}));
},
startBulkSend: function () {
startBulkSend() {
throw new Error("Can't send bulk data from worker threads!");
},
_onMessage: function (message) {
_onMessage(message) {
let packet = JSON.parse(message);
if (packet.type !== "message" || packet.id !== this._id) {
return;
@ -837,16 +861,16 @@ if (!this.isWorker) {
if (this.hooks) {
this.hooks.onPacket(packet.message);
}
}
},
};
}).call(this);
} else {
// Worker thread
(function () {
(function() {
/**
* A transport that uses a WorkerDebuggerGlobalScope to send packets from a
* worker thread to the main thread.
* A transport that uses a WorkerDebuggerGlobalScope to send packets
* from a worker thread to the main thread.
*/
function WorkerDebuggerTransport(scope, id) {
this._scope = scope;
@ -857,30 +881,30 @@ if (!this.isWorker) {
WorkerDebuggerTransport.prototype = {
constructor: WorkerDebuggerTransport,
ready: function () {
ready() {
this._scope.addEventListener("message", this._onMessage);
},
close: function () {
close() {
this._scope.removeEventListener("message", this._onMessage);
if (this.hooks) {
this.hooks.onClosed();
}
},
send: function (packet) {
send(packet) {
this._scope.postMessage(JSON.stringify({
type: "message",
id: this._id,
message: packet
message: packet,
}));
},
startBulkSend: function () {
startBulkSend() {
throw new Error("Can't send bulk data from worker threads!");
},
_onMessage: function (event) {
_onMessage(event) {
let packet = JSON.parse(event.data);
if (packet.type !== "message" || packet.id !== this._id) {
return;
@ -889,7 +913,7 @@ if (!this.isWorker) {
if (this.hooks) {
this.hooks.onPacket(packet.message);
}
}
},
};
}).call(this);

View File

@ -59,7 +59,7 @@ this.wait = {};
* @throws {?}
* If |func| throws, its error is propagated.
*/
wait.until = function (func, timeout = 2000, interval = 10) {
wait.until = function(func, timeout = 2000, interval = 10) {
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
return new Promise((resolve, reject) => {
@ -72,7 +72,8 @@ wait.until = function (func, timeout = 2000, interval = 10) {
throw rejected;
}
// return if timeout is 0, allowing |func| to be evaluated at least once
// return if timeout is 0, allowing |func| to be evaluated at
// least once
if (start == end || new Date().getTime() >= end) {
resolve(rejected);
}