--- a/testing/marionette/accessibility.js
+++ b/testing/marionette/accessibility.js
@@ -1,17 +1,17 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
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");
const logger = Log.repository.getLogger("Marionette");
const {ElementNotAccessibleError} =
Cu.import("chrome://marionette/content/error.js", {});
@@ -30,39 +30,40 @@ XPCOMUtils.defineLazyGetter(this, "servi
}
});
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() {
return Ci.nsIAccessibleStates.STATE_UNAVAILABLE;
},
get Focusable() {
return Ci.nsIAccessibleStates.STATE_FOCUSABLE;
},
get Selectable() {
return Ci.nsIAccessibleStates.STATE_SELECTABLE;
},
get Selected() {
return Ci.nsIAccessibleStates.STATE_SELECTED;
- }
+ },
};
/**
* Accessible object roles that support some action.
*/
accessibility.ActionableRoles = new Set([
"checkbutton",
"check menu item",
@@ -87,17 +88,17 @@ accessibility.ActionableRoles = new Set(
"switch",
]);
/**
* 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);
};
/**
* Component responsible for interacting with platform accessibility
* API.
*
* Its methods serve as wrappers for testing content and chrome
@@ -133,17 +134,18 @@ accessibility.Checks = class {
return new Promise((resolve, reject) => {
if (!accessibility.service) {
reject();
return;
}
// 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) {
// Accessibility is ready, resolve immediately.
let acc = accessibility.service.getAccessibleFor(element);
if (mustHaveAccessible && !acc) {
reject();
} else {
@@ -153,39 +155,41 @@ accessibility.Checks = class {
}
// Accessibility for the doc is busy, so wait for the state to change.
let eventObserver = {
observe(subject, topic, data) {
if (topic !== "accessible-event") {
return;
}
+ // If event type does not match expected type, skip the event.
let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
- // If event type does not match expected type, skip the event.
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;
}
Services.obs.removeObserver(this, "accessible-event");
let acc = accessibility.service.getAccessibleFor(element);
if (mustHaveAccessible && !acc) {
reject();
} 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
* action.
*
* @param {nsIAccessible} accessible
* Accessible object.
*
@@ -404,17 +408,18 @@ accessibility.Checks = class {
return;
}
// element is not selectable via the accessibility API
if (!this.matchState(accessible, accessibility.State.Selectable)) {
return;
}
- let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected);
+ let selectedAccessibility =
+ this.matchState(accessible, accessibility.State.Selected);
let message;
if (selected && !selectedAccessibility) {
message = "Element is selected but not selected via the accessibility API";
} else if (!selected && selectedAccessibility) {
message = "Element is not selected but selected via the accessibility API";
}
this.error(message, element);
--- a/testing/marionette/action.js
+++ b/testing/marionette/action.js
@@ -18,16 +18,18 @@ const {
MoveTargetOutOfBoundsError,
UnsupportedOperationError,
} = Cu.import("chrome://marionette/content/error.js", {});
Cu.import("chrome://marionette/content/event.js");
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
/**
* Implements WebDriver Actions API: a low-level interface for providing
* virtualised device input to the web browser.
*/
this.action = {
Pause: "pause",
@@ -355,17 +357,17 @@ action.PointerOrigin = {
* If |obj| is not a valid origin.
*/
action.PointerOrigin.get = function(obj) {
let origin = obj;
if (typeof obj == "undefined") {
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 " +
`web element reference, got: ${obj}`);
}
return origin;
};
@@ -384,34 +386,35 @@ action.PointerType = {
* Name of pointer type.
*
* @return {string}
* A pointer type for processing pointer parameters.
*
* @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
*/
action.inputsToCancel = undefined;
/**
* Represents device state for an input source.
*/
@@ -436,39 +439,39 @@ class InputState {
}
toString() {
return `[object ${this.constructor.name}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|.
*
* @throws {InvalidArgumentError}
* If |actionSequence.type| is not valid.
*/
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]();
}
}
/** Possible kinds of |InputState| for supported input sources. */
action.InputState = {};
/**
* Input state associated with a keyboard-type device.
@@ -561,17 +564,18 @@ action.InputState.Null = class Null exte
*
* @throws {InvalidArgumentError}
* If subtype is undefined or an invalid pointer type.
*/
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;
}
/**
* Check whether |button| is pressed.
*
@@ -611,40 +615,42 @@ action.InputState.Pointer = class Pointe
*/
release(button) {
assert.positiveInteger(button);
return this.pressed.delete(button);
}
};
/**
- * 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.
*/
action.Action = class {
constructor(id, type, subtype) {
if ([id, type, subtype].includes(undefined)) {
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}]`;
}
/**
* @param {?} actionSequence
* Object representing sequence of actions from one input source.
@@ -663,119 +669,125 @@ action.Action = class {
let type = actionSequence.type;
let id = actionSequence.id;
let subtypes = ACTIONS[type];
if (!subtypes) {
throw new InvalidArgumentError("Unknown type: " + type);
}
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);
if (type === "pointer") {
action.processPointerAction(id,
action.PointerParameters.fromJson(actionSequence.parameters), item);
}
switch (item.subtype) {
case action.KeyUp:
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;
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;
}
return item;
}
};
/**
- * 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() {
return `[chain ${super.toString()}]`;
}
/**
* @param {Array.<?>} actions
* 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++) {
// new tick
if (actionsByTick.length < (i + 1)) {
actionsByTick.push([]);
}
actionsByTick[i].push(inputSourceActions[i]);
}
}
return actionsByTick;
}
};
/**
- * 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() {
return `[sequence ${super.toString()}]`;
}
/**
* @param {?} actionSequence
@@ -789,20 +801,20 @@ action.Sequence = class extends Array {
* to an |action.InputState} incompatible with |actionSequence.type|.
* If |actionSequence.actions| is not an Array.
*/
static fromJson(actionSequence) {
// used here to validate 'type' in addition to InputState type below
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);
} else if (!action.inputStateMap.get(id).is(inputSourceState)) {
throw new InvalidArgumentError(
`Expected ${id} to be mapped to ${inputSourceState}, ` +
`got: ${action.inputStateMap.get(id)}`);
}
@@ -813,17 +825,18 @@ action.Sequence = class extends Array {
return actions;
}
};
/**
* 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") {
this.pointerType = action.PointerType.get(pointerType);
}
toString() {
return `[pointerParameters ${this.pointerType}]`;
@@ -834,19 +847,18 @@ action.PointerParameters = class {
* Object that represents pointer parameters.
*
* @return {action.PointerParameters}
* Validated pointer paramters.
*/
static fromJson(parametersData) {
if (typeof parametersData == "undefined") {
return new action.PointerParameters();
- } else {
- return new action.PointerParameters(parametersData.pointerType);
}
+ return new action.PointerParameters(parametersData.pointerType);
}
};
/**
* Adds |pointerType| attribute to Action |act|. Helper function
* for |action.Action.fromJson|.
*
* @param {string} id
@@ -855,24 +867,26 @@ action.PointerParameters = class {
* Input source pointer parameters.
* @param {action.Action} act
* Action to be updated.
*
* @throws {InvalidArgumentError}
* 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}`);
}
act.pointerType = pointerParams.pointerType;
};
/** Collect properties associated with KeyboardEvent */
@@ -925,65 +939,72 @@ action.Mouse = class {
let allButtons = Array.from(inputState.pressed);
this.buttons = allButtons.reduce((a, i) => a + Math.pow(2, i), 0);
}
};
/**
* 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
* actions for one tick.
* @param {element.Store} seenEls
* Element store.
* @param {?} container
* Object with |frame| attribute of type |nsIDOMWindow|.
*
* @return {Promise}
* Promise for dispatching all actions in |chain|.
*/
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;
};
/**
* 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.
* @param {number} tickDuration
* Duration in milliseconds of this tick.
* @param {element.Store} seenEls
* Element store.
* @param {?} container
* Object with |frame| attribute of type |nsIDOMWindow|.
*
* @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));
};
/**
* Compute tick duration in milliseconds for a collection of actions.
*
* @param {Array.<action.Action>} tickActions
@@ -1013,17 +1034,18 @@ action.computeTickDuration = function(ti
* Input state that specifies current x and y coordinates of pointer.
* @param {Map.<string, number>=} center
* Object representing x and y coordinates of an element center-point.
* This is only used if |a.origin| is a web element reference.
*
* @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:
break;
case action.PointerOrigin.Pointer:
x += inputState.x;
y += inputState.y;
break;
@@ -1048,55 +1070,58 @@ action.computePointerDestination = funct
* @param {?} container
* Object with |frame| attribute of type |nsIDOMWindow|.
*
* @return {function(action.Action): Promise}
* Function that takes an action and returns a Promise for dispatching
* 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:
return dispatchKeyUp(a, inputState, container.frame);
case action.KeyDown:
return dispatchKeyDown(a, inputState, container.frame);
case action.PointerDown:
return dispatchPointerDown(a, inputState, container.frame);
case action.PointerUp:
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();
case action.Pause:
return dispatchPause(a, tickDuration);
}
+ return undefined;
};
}
/**
* Dispatch a keyDown action equivalent to pressing a key on a keyboard.
*
* @param {action.Action} a
* Action to dispatch.
* @param {action.InputState} inputState
* Input state for this action's input source.
* @param {nsIDOMWindow} win
* 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 => {
let keyEvent = new action.Key(a.value);
keyEvent.repeat = inputState.isPressed(keyEvent.key);
inputState.press(keyEvent.key);
if (keyEvent.key in MODIFIER_NAME_LOOKUP) {
inputState.setModState(keyEvent.key, true);
@@ -1156,32 +1181,41 @@ function dispatchKeyUp(a, inputState, wi
* Promise to dispatch at least a pointerdown event.
*/
function dispatchPointerDown(a, inputState, win) {
return new Promise(resolve => {
if (inputState.isPressed(a.button)) {
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();
});
}
/**
* Dispatch a pointerUp action equivalent to releasing a pointer-device
* button.
*
@@ -1215,50 +1249,53 @@ function dispatchPointerUp(a, inputState
default:
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
}
resolve();
});
}
/**
- * 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.
* @param {action.InputState} inputState
* Input state for this action's input source.
* @param {element.Store} seenEls
* Element store.
* @param {?} container
* 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;
return new Promise(resolve => {
const start = Date.now();
const [startX, startY] = [inputState.x, inputState.y];
let target = action.computePointerDestination(a, inputState,
getElementCenter(a.origin, seenEls, container));
const [targetX, targetY] = [target.x, target.y];
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;
if (duration === 0) {
// move pointer to destination in one step
performOnePointerMove(inputState, targetX, targetY, container.frame);
resolve();
return;
@@ -1293,36 +1330,40 @@ function dispatchPointerMove(a, inputSta
});
}
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.
* @param {number} tickDuration
* Duration in milliseconds of this tick.
*
* @return {Promise}
* Promise that is resolved after the specified time interval.
@@ -1346,13 +1387,15 @@ function inViewPort(x, y, win) {
assert.number(x, `Expected x to be finite number`);
assert.number(y, `Expected y to be finite number`);
// Viewport includes scrollbars if rendered.
return !(x < 0 || y < 0 || x > win.innerWidth || y > win.innerHeight);
}
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 {};
}
--- a/testing/marionette/addon.js
+++ b/testing/marionette/addon.js
@@ -44,37 +44,37 @@ function lookupError(code) {
* True to install the addon temporarily, false (default) otherwise.
*
* @return {Promise: string}
* Addon ID.
*
* @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));
}
aInstall.addListener(listener);
aInstall.install();
});
} else {
AddonManager.addAddonListener(listener);
@@ -89,16 +89,16 @@ addon.install = function (path, temporar
* If the addon is restartless it will be uninstalled right away.
* Otherwise, Firefox must be restarted for the change to take effect.
*
* @param {string} id
* ID of the addon to uninstall.
*
* @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();
});
});
};
--- a/testing/marionette/assert.js
+++ b/testing/marionette/assert.js
@@ -36,46 +36,46 @@ this.assert = {};
* Custom error message.
*
* @return {string}
* Session ID.
*
* @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;
};
/**
* Asserts that the current browser is Firefox Desktop.
*
* @param {string=} msg
* Custom error message.
*
* @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)();
};
/**
* Asserts that the current browser is Fennec, or Firefox for Android.
*
* @param {string=} msg
* Custom error message.
*
* @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)();
};
/**
* Asserts that the current |context| is content.
*
* @param {string} context
@@ -84,17 +84,17 @@ assert.fennec = function (msg = "") {
* Custom error message.
*
* @return {string}
* |context| is returned unaltered.
*
* @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);
};
/**
* Asserts that |win| is open.
*
* @param {ChromeWindow} win
@@ -103,17 +103,17 @@ assert.content = function (context, msg
* Custom error message.
*
* @return {ChromeWindow}
* |win| is returned unaltered.
*
* @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,
NoSuchWindowError)(win);
};
/**
* Asserts that |context| is a valid browsing context.
@@ -121,17 +121,17 @@ assert.window = function (win, msg = "")
* @param {browser.Context} context
* Browsing context to test.
* @param {string=} msg
* Custom error message.
*
* @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.
assert.window(context && context.window);
msg = msg || "Current window does not have a content browser";
assert.that(c => c.contentBrowser,
msg,
@@ -144,17 +144,17 @@ assert.contentBrowser = function (contex
* @param {modal.Dialog} dialog
* Reference to current dialogue.
* @param {string=} msg
* Custom error message.
*
* @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);
};
/**
* Asserts that |obj| is defined.
*
@@ -164,17 +164,17 @@ assert.noUserPrompt = function (dialog,
* Custom error message.
*
* @return {?}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is a finite number.
*
* @param {?} obj
@@ -183,17 +183,17 @@ assert.defined = function (obj, msg = ""
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is callable.
*
* @param {?} obj
@@ -202,17 +202,17 @@ assert.number = function (obj, msg = "")
* Custom error message.
*
* @return {Function}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is an integer.
*
* @param {?} obj
@@ -221,17 +221,17 @@ assert.callable = function (obj, msg = "
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is a positive integer.
*
* @param {?} obj
@@ -240,17 +240,17 @@ assert.integer = function (obj, msg = ""
* Custom error message.
*
* @return {number}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is a boolean.
*
@@ -260,17 +260,17 @@ assert.positiveInteger = function (obj,
* Custom error message.
*
* @return {boolean}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is a string.
*
* @param {?} obj
@@ -279,17 +279,17 @@ assert.boolean = function (obj, msg = ""
* Custom error message.
*
* @return {string}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Asserts that |obj| is an object.
*
* @param {?} obj
@@ -298,17 +298,17 @@ assert.string = function (obj, msg = "")
* Custom error message.
*
* @return {Object}
* |obj| is returned unaltered.
*
* @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
// different globals
let s = Object.prototype.toString.call(o);
return s == "[object Object]" || s == "[object nsJSIID]";
}, msg)(obj);
};
@@ -324,17 +324,17 @@ assert.object = function (obj, msg = "")
* Custom error message.
*
* @return {?}
* Value of |obj|'s own property |prop|.
*
* @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);
return obj[prop];
};
/**
* Asserts that |obj| is an Array.
@@ -345,17 +345,17 @@ assert.in = function (prop, obj, msg = "
* Custom error message.
*
* @return {Object}
* |obj| is returned unaltered.
*
* @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);
};
/**
* Returns a function that is used to assert the |predicate|.
*
* @param {function(?): boolean} predicate
@@ -367,17 +367,17 @@ assert.array = function (obj, msg = "")
* @param {Error=} error
* Custom error type by its class.
*
* @return {function(?): ?}
* Function that takes and returns the passed in value unaltered, and
* 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)) {
throw new error(message);
}
return obj;
};
};
--- a/testing/marionette/browser.js
+++ b/testing/marionette/browser.js
@@ -24,51 +24,49 @@ const XUL_NS = "http://www.mozilla.org/k
* Get the <xul:browser> for the specified tab.
*
* @param {<xul:tab>} tab
* The tab whose browser needs to be returned.
*
* @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;
};
/**
* Return the tab browser for the specified chrome window.
*
* @param {nsIDOMWindow} win
* The window whose tabbrowser needs to be accessed.
*
* @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;
};
/**
* Creates a browsing context wrapper.
*
* Browsing contexts handle interactions with the browser, according to
* the current environment (Firefox, Fennec).
*
@@ -95,23 +93,23 @@ browser.Context = class {
this.knownFrames = [];
// Used to set curFrameId upon new session
this.newSession = true;
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 = [];
// We should have one frame.Manager per browser.Context so that we
// can handle modals in each <xul:browser>.
this.frameManager = new frame.Manager(driver);
this.frameRegsPending = 0;
@@ -125,31 +123,32 @@ browser.Context = class {
/**
* Returns the content browser for the currently selected tab.
* If there is no tab selected, null will be returned.
*/
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;
}
return null;
}
/**
* The current frame ID is managed per browser element on desktop in
* case the ID needs to be refreshed. The currently selected window is
* identified by a tab.
*/
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;
}
/**
* Returns the current URI of the content browser.
*
@@ -159,20 +158,19 @@ browser.Context = class {
* @throws {NoSuchWindowError}
* If the current ChromeWindow does not have a content browser.
*/
get currentURI() {
// Bug 1363368 - contentBrowser could be null until we wait for its
// 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");
}
/**
* Gets the position and dimensions of the top-level browsing context.
*
* @return {Map.<string, number>}
* Object with |x|, |y|, |width|, and |height| properties.
*/
@@ -229,17 +227,20 @@ browser.Context = class {
* A promise which is resolved when the current tab has been closed.
*
* @throws UnsupportedOperationError
* If tab handling for the current application isn't supported.
*/
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();
}
return new Promise((resolve, reject) => {
if (this.tabBrowser.closeTab) {
// Fennec
this.tabBrowser.deck.addEventListener("TabClose", ev => {
resolve();
--- a/testing/marionette/capture.js
+++ b/testing/marionette/capture.js
@@ -29,17 +29,17 @@ capture.Format = {
* The node to take a screenshot of.
* @param {Array.<Node>=} highlights
* Optional array of nodes, around which a border will be marked to
* highlight them in the screenshot.
*
* @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();
return capture.canvas(
win,
rect.left,
rect.top,
rect.width,
@@ -56,17 +56,17 @@ capture.element = function (node, highli
* and the offsets for the viewport.
* @param {Array.<Node>=} highlights
* Optional array of nodes, around which a border will be marked to
* highlight them in the screenshot.
*
* @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(
win,
win.pageXOffset,
win.pageYOffset,
rootNode.clientWidth,
rootNode.clientHeight,
@@ -95,48 +95,55 @@ capture.viewport = function (win, highli
* @param {number=} flags
* Optional integer representing flags to pass to drawWindow; these
* are defined on CanvasRenderingContext2D.
*
* @return {HTMLCanvasElement}
* 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");
canvas.width = width * scale;
canvas.height = height * scale;
}
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);
if (highlights.length) {
ctx = capture.highlight_(ctx, highlights, top, left);
}
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";
context.strokeStyle = "red";
context.save();
for (let el of highlights) {
let rect = el.getBoundingClientRect();
@@ -157,31 +164,31 @@ capture.highlight_ = function (context,
* Encode the contents of an HTMLCanvasElement to a Base64 encoded string.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
*
* @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);
};
/**
* Hash the contents of an HTMLCanvasElement to a SHA-256 hex digest.
*
* @param {HTMLCanvasElement} canvas
* The canvas to encode.
*
* @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));
};
/**
* Convert buffer into to hex.
*
@@ -192,14 +199,14 @@ capture.toHash = function (canvas) {
* A hex digest of the input buffer.
*/
function hex(buffer) {
let hexCodes = [];
let view = new DataView(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("");
-};
+}
--- a/testing/marionette/cert.js
+++ b/testing/marionette/cert.js
@@ -48,17 +48,17 @@ this.cert = {
* is not null, this functions acts as a NOOP.
*
* @param {cert.Override} service
* Service generator that registers and unregisters the XPCOM service.
*
* @throws {Components.Exception}
* If unable to register or initialise |service|.
*/
-cert.installOverride = function (service) {
+cert.installOverride = function(service) {
if (this.currentOverride) {
return;
}
service.register();
cert.currentOverride = service;
};
@@ -97,40 +97,40 @@ cert.InsecureSweepingOverride = function
const DESC = "All-encompassing cert service that matches on a bitflag";
// This needs to be an old-style class with a function constructor
// and prototype assignment because... XPCOM. Any attempt at
// modernisation will be met with cryptic error messages which will
// make your life miserable.
let service = function() {};
service.prototype = {
- hasMatchingOverride: function (
+ hasMatchingOverride(
aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
aIsTemporary.value = false;
aOverrideBits.value =
cert.Error.Untrusted | cert.Error.Mismatch | cert.Error.Time;
return true;
},
QueryInterface: XPCOMUtils.generateQI([Ci.nsICertOverrideService]),
};
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);
Preferences.set(CERT_PINNING_ENFORCEMENT_PREF, 0);
registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
},
- unregister: function() {
+ unregister() {
registrar.unregisterFactory(CID, factory);
Preferences.reset(HSTS_PRELOAD_LIST_PREF);
Preferences.reset(CERT_PINNING_ENFORCEMENT_PREF);
// clear collected HSTS and HPKP state
// through the site security service
sss.clearAll();
--- a/testing/marionette/components/marionette.js
+++ b/testing/marionette/components/marionette.js
@@ -19,29 +19,29 @@ const MARIONETTE_CID = Components.ID("{7
const PREF_PORT = "marionette.port";
const PREF_PORT_FALLBACK = "marionette.defaultPrefs.port";
const PREF_LOG_LEVEL = "marionette.log.level";
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],
["warn", Log.Level.Warn],
["info", Log.Level.Info],
["config", Log.Level.Config],
["debug", Log.Level.Debug],
["trace", Log.Level.Trace],
]);
}
- get (level) {
+ get(level) {
let s = new String(level).toLowerCase();
if (!this.has(s)) {
return DEFAULT_LOG_LEVEL;
}
return super.get(s);
}
};
@@ -64,39 +64,39 @@ const ENV_ENABLED = "MOZ_MARIONETTE";
const ENV_PRESERVE_PREFS = "MOZ_MARIONETTE_PREF_STATE_ACROSS_RESTARTS";
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
"nsIServerSocket",
"initSpecialConnection");
// 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));
}
return Preferences.get(preferred);
}
// Marionette preferences recently changed names. This is an abstraction
// that first looks for the new name, but falls back to using the old name
// if the new does not exist.
//
// 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);
if (env.exists(key)) {
let prefs;
try {
prefs = JSON.parse(env.get(key));
} catch (e) {
@@ -141,33 +141,24 @@ MarionetteComponent.prototype = {
]),
_xpcom_categories: [
{category: "command-line-handler", entry: "b-marionette"},
{category: "profile-after-change", service: true},
],
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
// seem to work, so we wait for that by adding an observer here.
Services.obs.addObserver(this, "sessionstore-windows-restored");
prefs.readFromEnvironment(ENV_PRESERVE_PREFS);
@@ -222,35 +213,35 @@ MarionetteComponent.prototype.observe =
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
this.uninit();
break;
}
};
-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
win.setTimeout(() => {
win.document.documentElement.getButton("accept").click();
});
}
}, {once: true});
};
-MarionetteComponent.prototype.init = function () {
+MarionetteComponent.prototype.init = function() {
if (this.running || !this.enabled || !this.finalUIStartup) {
return;
}
// Delay initialization until we are done with delayed startup...
Services.tm.idleDispatchToMainThread(() => {
// ... and with startup tests.
Services.tm.idleDispatchToMainThread(() => {
@@ -265,17 +256,17 @@ MarionetteComponent.prototype.init = fun
this.server = s;
this.running = true;
}
}
});
});
};
-MarionetteComponent.prototype.uninit = function () {
+MarionetteComponent.prototype.uninit = function() {
if (!this.running) {
return;
}
this.server.stop();
this.running = false;
};
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);
--- a/testing/marionette/cookie.js
+++ b/testing/marionette/cookie.js
@@ -36,17 +36,17 @@ this.cookie = {
* the |expiry| field is optional but must be unsigned integer.
*
* @return {Map.<string, (number|boolean|string)>
* Valid cookie object.
*
* @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}`);
newCookie.name = assert.string(json.name, "Cookie name must be string");
newCookie.value = assert.string(json.value, "Cookie value must be string");
if (typeof json.path != "undefined") {
@@ -80,17 +80,17 @@ cookie.fromJSON = function (json) {
* Perform test that |newCookie|'s domain matches this.
*
* @throws {TypeError}
* If |name|, |value|, or |domain| are not present and of the
* correct type.
* @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");
if (typeof newCookie.path == "undefined") {
newCookie.path = "/";
}
@@ -103,17 +103,18 @@ cookie.add = function (newCookie, opts =
}
if (opts.restrictToHost) {
assert.in("restrictToHost", opts,
"Missing cookie domain for host restriction test");
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})`);
}
}
// remove port from domain, if present.
// unfortunately this catches IPv6 addresses by mistake
// TODO: Bug 814416
newCookie.domain = newCookie.domain.replace(IPV4_PORT_EXPR, "");
@@ -130,17 +131,17 @@ cookie.add = function (newCookie, opts =
};
/**
* Remove cookie from the cookie store.
*
* @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,
toDelete.path,
false,
{} /* originAttributes */);
};
@@ -153,17 +154,17 @@ cookie.remove = function (toDelete) {
* Hostname to retrieve cookies for.
* @param {string=} currentPath
* Optionally filter the cookies for |host| for the specific path.
* Defautls to "/", meaning all cookies for |host| are included.
*
* @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");
const isForCurrentPath = path => currentPath.indexOf(path) != -1;
let en = cookie.manager.getCookiesFromHost(host, {});
while (en.hasMoreElements()) {
let cookie = en.getNext().QueryInterface(Ci.nsICookie2);
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -84,31 +84,31 @@ Services.obs.addObserver(function() {
systemMessageListenerReady = true;
}, "system-message-listener-ready");
this.Context = {
CHROME: "chrome",
CONTENT: "content",
};
-this.Context.fromString = function (s) {
+this.Context.fromString = function(s) {
s = s.toUpperCase();
if (s in this) {
return this[s];
}
return null;
};
/**
* Helper function for converting an nsISimpleEnumerator to
* a javascript iterator
* @param{nsISimpleEnumerator} enumerator
* enumerator to convert
*/
-function* enumeratorIterator (enumerator) {
+function* enumeratorIterator(enumerator) {
while (enumerator.hasMoreElements()) {
yield enumerator.getNext();
}
}
/**
* Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
* in chrome space and mediates calls to the message listener of the current
@@ -118,17 +118,17 @@ function* enumeratorIterator (enumerator
* documentation refers to the contents of the {@code cmd.parameters}
* object.
*
* @param {string} appName
* Description of the product, for example "Firefox".
* @param {MarionetteServer} server
* The instance of Marionette server.
*/
-this.GeckoDriver = function (appName, server) {
+this.GeckoDriver = function(appName, server) {
this.appName = appName;
this._server = server;
this.sessionId = null;
this.wins = new browser.Windows();
this.browsers = {};
// points to current browser
this.curBrowser = null;
@@ -162,71 +162,74 @@ this.GeckoDriver = function (appName, se
() => this.curBrowser);
// points to an alert instance if a modal dialog is present
this.dialog = null;
this.dialogHandler = this.globalModalDialogHandler.bind(this);
};
Object.defineProperty(GeckoDriver.prototype, "a11yChecks", {
- get: function () {
+ get() {
return this.capabilities.get("moz:accessibilityChecks");
- }
+ },
});
/**
* Returns the current URL of the ChromeWindow or content browser,
* depending on context.
*
* @return {URL}
* Read-only property containing the currently loaded URL.
*/
Object.defineProperty(GeckoDriver.prototype, "currentURL", {
- get: function () {
+ get() {
switch (this.context) {
case Context.CHROME:
let chromeWin = this.getCurrentWindow();
return new URL(chromeWin.location.href);
case Context.CONTENT:
return new URL(this.curBrowser.currentURI.spec);
+
+ default:
+ throw TypeError(`Unknown context: ${this.context}`);
}
- }
+ },
});
Object.defineProperty(GeckoDriver.prototype, "proxy", {
- get: function () {
+ get() {
return this.capabilities.get("proxy");
- }
+ },
});
Object.defineProperty(GeckoDriver.prototype, "secureTLS", {
- get: function () {
+ get() {
return !this.capabilities.get("acceptInsecureCerts");
- }
+ },
});
Object.defineProperty(GeckoDriver.prototype, "timeouts", {
- get: function () {
+ get() {
return this.capabilities.get("timeouts");
},
- set: function (newTimeouts) {
+ set(newTimeouts) {
this.capabilities.set("timeouts", newTimeouts);
},
});
Object.defineProperty(GeckoDriver.prototype, "windows", {
- get: function () {
+ get() {
return enumeratorIterator(Services.wm.getEnumerator(null));
- }
+ },
});
Object.defineProperty(GeckoDriver.prototype, "windowHandles", {
- get: function () {
+ get() {
let hs = [];
for (let win of this.windows) {
let tabBrowser = browser.getTabBrowser(win);
// Only return handles for browser windows
if (tabBrowser && tabBrowser.tabs) {
tabBrowser.tabs.forEach(tab => {
@@ -238,17 +241,17 @@ Object.defineProperty(GeckoDriver.protot
}
}
return hs;
},
});
Object.defineProperty(GeckoDriver.prototype, "chromeWindowHandles", {
- get: function () {
+ get() {
let hs = [];
for (let win of this.windows) {
hs.push(getOuterWindowId(win));
}
return hs;
},
@@ -259,17 +262,17 @@ GeckoDriver.prototype.QueryInterface = X
Ci.nsIObserver,
Ci.nsISupportsWeakReference,
]);
/**
* Callback used to observe the creation of new modal or tab modal dialogs
* during the session's lifetime.
*/
-GeckoDriver.prototype.globalModalDialogHandler = function (subject, topic) {
+GeckoDriver.prototype.globalModalDialogHandler = function(subject, topic) {
let winr;
if (topic === modal.COMMON_DIALOG_LOADED) {
// Always keep a weak reference to the current dialog
winr = Cu.getWeakReference(subject);
}
this.dialog = new modal.Dialog(() => this.curBrowser, winr);
};
@@ -277,17 +280,18 @@ GeckoDriver.prototype.globalModalDialogH
* Switches to the global ChromeMessageBroadcaster, potentially replacing
* a frame-specific ChromeMessageSender. Has no effect if the global
* ChromeMessageBroadcaster is already in use. If this replaces a
* frame-specific ChromeMessageSender, it removes the message listeners
* from that sender, and then puts the corresponding frame script "to
* sleep", which removes most of the message listeners from it as well.
*/
GeckoDriver.prototype.switchToGlobalMessageManager = function() {
- if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) {
+ if (this.curBrowser &&
+ this.curBrowser.frameManager.currentRemoteFrame !== null) {
this.curBrowser.frameManager.removeMessageManagerListeners(this.mm);
this.sendAsync("sleepSession");
this.curBrowser.frameManager.currentRemoteFrame = null;
}
this.mm = globalMessageManager;
};
/**
@@ -299,17 +303,17 @@ GeckoDriver.prototype.switchToGlobalMess
* @param {string} name
* Suffix of the targetted message listener
* ({@code Marionette:<suffix>}).
* @param {Object=} msg
* Optional JSON serialisable object to send to the listener.
* @param {number=} commandID
* Optional command ID to ensure synchronisity.
*/
-GeckoDriver.prototype.sendAsync = function (name, data, commandID) {
+GeckoDriver.prototype.sendAsync = function(name, data, commandID) {
name = "Marionette:" + name;
let payload = copy(data);
// TODO(ato): When proxy.AsyncMessageChannel
// is used for all chrome <-> content communication
// this can be removed.
if (commandID) {
payload.command_id = commandID;
@@ -317,29 +321,30 @@ GeckoDriver.prototype.sendAsync = functi
if (!this.curBrowser.frameManager.currentRemoteFrame) {
this.broadcastDelayedAsyncMessage_(name, payload);
} else {
this.sendTargettedAsyncMessage_(name, payload);
}
};
-GeckoDriver.prototype.broadcastDelayedAsyncMessage_ = function (name, payload) {
+// eslint-disable-next-line
+GeckoDriver.prototype.broadcastDelayedAsyncMessage_ = function(name, payload) {
this.curBrowser.executeWhenReady(() => {
if (this.curBrowser.curFrameId) {
const target = name + this.curBrowser.curFrameId;
this.mm.broadcastAsyncMessage(target, payload);
} else {
throw new NoSuchWindowError(
"No such content frame; perhaps the listener was not registered?");
}
});
};
-GeckoDriver.prototype.sendTargettedAsyncMessage_ = function (name, payload) {
+GeckoDriver.prototype.sendTargettedAsyncMessage_ = function(name, payload) {
const curRemoteFrame = this.curBrowser.frameManager.currentRemoteFrame;
const target = name + curRemoteFrame.targetFrameId;
try {
this.mm.sendAsyncMessage(target, payload);
} catch (e) {
switch (e.result) {
case Cr.NS_ERROR_FAILURE:
@@ -350,63 +355,63 @@ GeckoDriver.prototype.sendTargettedAsync
throw new WebDriverError(e);
}
}
};
/**
* Get the session's current top-level browsing context.
*
- * It will return the outer {@ChromeWindow} previously selected by window handle
- * through {@code #switchToWindow}, or the first window that was registered.
+ * It will return the outer {@ChromeWindow} previously selected by window
+ * handle through {@code #switchToWindow}, or the first window that was
+ * registered.
*
* @param {Context=} forcedContext
- * Optional name of the context to use for finding the window. It will be required
- * if a command always needs a specific context, whether which context is
- * currently set. Defaults to the current context.
+ * Optional name of the context to use for finding the window.
+ * It will be required if a command always needs a specific context,
+ * whether which context is currently set. Defaults to the current
+ * context.
*
* @return {ChromeWindow}
* The current top-level browsing context.
*/
-GeckoDriver.prototype.getCurrentWindow = function (forcedContext = undefined) {
+GeckoDriver.prototype.getCurrentWindow = function(forcedContext = undefined) {
let context = typeof forcedContext == "undefined" ? this.context : forcedContext;
let win = null;
switch (context) {
case Context.CHROME:
if (this.curFrame !== null) {
win = this.curFrame;
-
} else if (this.curBrowser !== null) {
win = this.curBrowser.window;
}
-
break;
case Context.CONTENT:
if (this.curFrame !== null) {
win = this.curFrame;
} else if (this.curBrowser !== null && this.curBrowser.contentBrowser) {
win = this.curBrowser.window;
}
break;
}
return win;
};
-GeckoDriver.prototype.isReftestBrowser = function (element) {
+GeckoDriver.prototype.isReftestBrowser = function(element) {
return this._reftest &&
element &&
element.tagName === "xul:browser" &&
element.parentElement &&
element.parentElement.id === "reftest";
-}
-
-GeckoDriver.prototype.addFrameCloseListener = function (action) {
+};
+
+GeckoDriver.prototype.addFrameCloseListener = function(action) {
let win = this.getCurrentWindow();
this.mozBrowserClose = e => {
if (e.target.id == this.oopFrameId) {
win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
this.switchToGlobalMessageManager();
throw new NoSuchWindowError("The window closed during action: " + action);
}
};
@@ -417,17 +422,17 @@ GeckoDriver.prototype.addFrameCloseListe
* Create a new browsing context for window and add to known browsers.
*
* @param {nsIDOMWindow} win
* Window for which we will create a browsing context.
*
* @return {string}
* Returns the unique server-assigned ID of the window.
*/
-GeckoDriver.prototype.addBrowser = function (win) {
+GeckoDriver.prototype.addBrowser = function(win) {
let bc = new browser.Context(win, this);
let winId = getOuterWindowId(win);
this.browsers[winId] = bc;
this.curBrowser = this.browsers[winId];
if (!this.wins.has(winId)) {
// add this to seenItems so we can guarantee
// the user will get winId as this window's id
@@ -442,45 +447,46 @@ GeckoDriver.prototype.addBrowser = funct
* frame script will be loaded into it. If isNewSession is true, we will
* switch focus to the start frame when it registers.
*
* @param {nsIDOMWindow} win
* Window whose browser we need to access.
* @param {boolean=false} isNewSession
* True if this is the first time we're talking to this browser.
*/
-GeckoDriver.prototype.startBrowser = function (win, isNewSession = false) {
+GeckoDriver.prototype.startBrowser = function(win, isNewSession = false) {
this.mainFrame = win;
this.curFrame = null;
this.addBrowser(win);
this.curBrowser.isNewSession = isNewSession;
- this.curBrowser.startSession(isNewSession, win, this.whenBrowserStarted.bind(this));
+ this.curBrowser.startSession(
+ isNewSession, win, this.whenBrowserStarted.bind(this));
};
/**
* Callback invoked after a new session has been started in a browser.
* Loads the Marionette frame script into the browser if needed.
*
* @param {nsIDOMWindow} win
* Window whose browser we need to access.
* @param {boolean} isNewSession
* True if this is the first time we're talking to this browser.
*/
-GeckoDriver.prototype.whenBrowserStarted = function (win, isNewSession) {
+GeckoDriver.prototype.whenBrowserStarted = function(win, isNewSession) {
let mm = win.window.messageManager;
if (mm) {
if (!isNewSession) {
// Loading the frame script corresponds to a situation we need to
// return to the server. If the messageManager is a message broadcaster
- // with no children, we don't have a hope of coming back from this call,
- // so send the ack here. Otherwise, make a note of how many child scripts
- // will be loaded so we known when it's safe to return.
- // Child managers may not have child scripts yet (e.g. socialapi), only
- // count child managers that have children, but only count the top level
- // children as they are the ones that we expect a response from.
+ // with no children, we don't have a hope of coming back from this
+ // call, so send the ack here. Otherwise, make a note of how many
+ // child scripts will be loaded so we known when it's safe to return.
+ // Child managers may not have child scripts yet (e.g. socialapi),
+ // only count child managers that have children, but only count the top
+ // level children as they are the ones that we expect a response from.
if (mm.childCount !== 0) {
this.curBrowser.frameRegsPending = 0;
for (let i = 0; i < mm.childCount; i++) {
if (mm.getChildAt(i).childCount !== 0) {
this.curBrowser.frameRegsPending += 1;
}
}
}
@@ -502,17 +508,17 @@ GeckoDriver.prototype.whenBrowserStarted
/**
* Recursively get all labeled text.
*
* @param {nsIDOMElement} el
* The parent element.
* @param {Array.<string>} lines
* Array that holds the text lines.
*/
-GeckoDriver.prototype.getVisibleText = function (el, lines) {
+GeckoDriver.prototype.getVisibleText = function(el, lines) {
try {
if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
if (el.value) {
lines.push(el.value);
}
for (let child in el.childNodes) {
this.getVisibleText(el.childNodes[child], lines);
}
@@ -523,17 +529,17 @@ GeckoDriver.prototype.getVisibleText = f
}
}
};
/**
* Handles registration of new content listener browsers. Depending on
* their type they are either accepted or ignored.
*/
-GeckoDriver.prototype.registerBrowser = function (id, be) {
+GeckoDriver.prototype.registerBrowser = function(id, be) {
let nullPrevious = this.curBrowser.curFrameId === null;
let listenerWindow = Services.wm.getOuterWindowWithId(id);
// go in here if we're already in a remote frame
if (this.curBrowser.frameManager.currentRemoteFrame !== null &&
(!listenerWindow || this.mm == this.curBrowser.frameManager
.currentRemoteFrame.messageManager.get())) {
// The outerWindowID from an OOP frame will not be meaningful to
@@ -573,17 +579,17 @@ GeckoDriver.prototype.registerBrowser =
if (this.curBrowser.isNewSession) {
this.newSessionCommandId = null;
}
}
return [reg, this.capabilities.toJSON()];
};
-GeckoDriver.prototype.registerPromise = function () {
+GeckoDriver.prototype.registerPromise = function() {
const li = "Marionette:register";
return new Promise(resolve => {
let cb = msg => {
let wid = msg.json.value;
let be = msg.target;
let rv = this.registerBrowser(wid, be);
@@ -598,17 +604,17 @@ GeckoDriver.prototype.registerPromise =
// this is a sync message and listeners expect the ID back
return rv;
};
this.mm.addMessageListener(li, cb);
});
};
-GeckoDriver.prototype.listeningPromise = function () {
+GeckoDriver.prototype.listeningPromise = function() {
const li = "Marionette:listenersAttached";
return new Promise(resolve => {
let cb = () => {
this.mm.removeMessageListener(li, cb);
resolve();
};
this.mm.addMessageListener(li, cb);
});
@@ -718,39 +724,39 @@ GeckoDriver.prototype.newSession = funct
* Capabilities informs the client of which WebDriver features are
* supported by Firefox and Marionette. They are immutable for the
* length of the session.
*
* The return value is an immutable map of string keys
* ("capabilities") to values, which may be of types boolean,
* numerical or string.
*/
-GeckoDriver.prototype.getSessionCapabilities = function (cmd, resp) {
+GeckoDriver.prototype.getSessionCapabilities = function(cmd, resp) {
resp.body.capabilities = this.capabilities;
};
/**
* Sets the context of the subsequent commands to be either "chrome" or
* "content".
*
* @param {string} value
* Name of the context to be switched to. Must be one of "chrome" or
* "content".
*/
-GeckoDriver.prototype.setContext = function (cmd, resp) {
+GeckoDriver.prototype.setContext = function(cmd, resp) {
let val = cmd.parameters.value;
let ctx = Context.fromString(val);
if (ctx === null) {
throw new WebDriverError(`Invalid context: ${val}`);
}
this.context = ctx;
};
/** Gets the context of the server, either "chrome" or "content". */
-GeckoDriver.prototype.getContext = function (cmd, resp) {
+GeckoDriver.prototype.getContext = function(cmd, resp) {
resp.body.value = this.context.toString();
};
/**
* Executes a JavaScript function in the context of the current browsing
* context, if in content space, or in chrome space otherwise, and returns
* the return value of the function.
*
@@ -887,36 +893,38 @@ GeckoDriver.prototype.executeAsyncScript
line: cmd.parameters.line,
debug: cmd.parameters.debug_script,
async: true,
};
resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
};
-GeckoDriver.prototype.execute_ = function (script, args, timeout, opts = {}) {
+GeckoDriver.prototype.execute_ = function(script, args, timeout, opts = {}) {
switch (this.context) {
case Context.CONTENT:
// evaluate in content with lasting side-effects
if (!opts.sandboxName) {
return this.listener.execute(script, args, timeout, opts)
.then(evaluate.toJSON);
+ }
// evaluate in content with sandbox
- } else {
- return this.listener.executeInSandbox(script, args, timeout, opts)
- .then(evaluate.toJSON);
- }
+ return this.listener.executeInSandbox(script, args, timeout, opts)
+ .then(evaluate.toJSON);
case Context.CHROME:
let sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
opts.timeout = timeout;
let wargs = evaluate.fromJSON(args, this.curBrowser.seenEls, sb.window);
return evaluate.sandbox(sb, script, wargs, opts)
.then(res => evaluate.toJSON(res, this.curBrowser.seenEls));
+
+ default:
+ throw new TypeError(`Unknown context: ${this.context}`);
}
};
/**
* Navigate to given URL.
*
* Navigates the current browsing context to the given URL and waits for
* the document to load or the session's page timeout duration to elapse
@@ -947,17 +955,17 @@ GeckoDriver.prototype.execute_ = functio
*/
GeckoDriver.prototype.get = function* (cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let url = cmd.parameters.url;
- let get = this.listener.get({url: url, pageTimeout: this.timeouts.pageLoad});
+ let get = this.listener.get({url, pageTimeout: this.timeouts.pageLoad});
// If a remoteness update interrupts our page load, this will never return
// We need to re-issue this request to correctly poll for readyState and
// send errors.
this.curBrowser.pendingCommands.push(() => {
let parameters = {
// TODO(ato): Bug 1242595
command_id: this.listener.activeMessageId,
@@ -984,17 +992,17 @@ GeckoDriver.prototype.get = function* (c
* When in the context of the chrome, this returns the canonical URL
* of the current resource.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getCurrentUrl = function (cmd) {
+GeckoDriver.prototype.getCurrentUrl = function(cmd) {
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
return this.currentURL.toString();
};
/**
* Gets the current title of the window.
@@ -1018,17 +1026,17 @@ GeckoDriver.prototype.getTitle = functio
case Context.CONTENT:
resp.body.value = yield this.listener.getTitle();
break;
}
};
/** Gets the current type of the window. */
-GeckoDriver.prototype.getWindowType = function (cmd, resp) {
+GeckoDriver.prototype.getWindowType = function(cmd, resp) {
let win = assert.window(this.getCurrentWindow());
resp.body.value = win.document.documentElement.getAttribute("windowtype");
};
/**
* Gets the page source of the content document.
*
@@ -1117,17 +1125,18 @@ GeckoDriver.prototype.goForward = functi
assert.noUserPrompt(this.dialog);
// If there is no history, just return
if (!this.curBrowser.contentBrowser.webNavigation.canGoForward) {
return;
}
let lastURL = this.currentURL;
- let goForward = this.listener.goForward({pageTimeout: this.timeouts.pageLoad});
+ let goForward = this.listener.goForward(
+ {pageTimeout: this.timeouts.pageLoad});
// If a remoteness update interrupts our page load, this will never return
// We need to re-issue this request to correctly poll for readyState and
// send errors.
this.curBrowser.pendingCommands.push(() => {
let parameters = {
// TODO(ato): Bug 1242595
command_id: this.listener.activeMessageId,
@@ -1160,26 +1169,26 @@ GeckoDriver.prototype.refresh = function
assert.noUserPrompt(this.dialog);
yield this.listener.refresh({pageTimeout: this.timeouts.pageLoad});
};
/**
* Forces an update for the given browser's id.
*/
-GeckoDriver.prototype.updateIdForBrowser = function (browser, newId) {
+GeckoDriver.prototype.updateIdForBrowser = function(browser, newId) {
this._browserIds.set(browser.permanentKey, newId);
};
/**
* Retrieves a listener id for the given xul browser element. In case
* the browser is not known, an attempt is made to retrieve the id from
* a CPOW, and null is returned if this fails.
*/
-GeckoDriver.prototype.getIdForBrowser = function (browser) {
+GeckoDriver.prototype.getIdForBrowser = function(browser) {
if (browser === null) {
return null;
}
let permKey = browser.permanentKey;
if (this._browserIds.has(permKey)) {
return this._browserIds.get(permKey);
}
@@ -1200,34 +1209,34 @@ GeckoDriver.prototype.getIdForBrowser =
* be used to switch to this window at a later point.
*
* @return {string}
* Unique window handle.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
-GeckoDriver.prototype.getWindowHandle = function (cmd, resp) {
+GeckoDriver.prototype.getWindowHandle = function(cmd, resp) {
assert.contentBrowser(this.curBrowser);
return this.curBrowser.curFrameId.toString();
};
/**
* Get a list of top-level browsing contexts. On desktop this typically
- * corresponds to the set of open tabs for browser windows, or the window itself
- * for non-browser chrome windows.
+ * corresponds to the set of open tabs for browser windows, or the window
+ * itself for non-browser chrome windows.
*
* Each window handle is assigned by the server and is guaranteed unique,
* however the return array does not have a specified ordering.
*
* @return {Array.<string>}
* Unique window handles.
*/
-GeckoDriver.prototype.getWindowHandles = function (cmd, resp) {
+GeckoDriver.prototype.getWindowHandles = function(cmd, resp) {
return this.windowHandles.map(String);
}
/**
* Get the current window's handle. This corresponds to a window that
* may itself contain tabs.
*
* Return an opaque server-assigned identifier to this window that
@@ -1235,17 +1244,17 @@ GeckoDriver.prototype.getWindowHandles =
* be used to switch to this window at a later point.
*
* @return {string}
* Unique window handle.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
*/
-GeckoDriver.prototype.getChromeWindowHandle = function (cmd, resp) {
+GeckoDriver.prototype.getChromeWindowHandle = function(cmd, resp) {
assert.window(this.getCurrentWindow(Context.CHROME));
for (let i in this.browsers) {
if (this.curBrowser == this.browsers[i]) {
resp.body.value = i;
return;
}
}
@@ -1253,17 +1262,17 @@ GeckoDriver.prototype.getChromeWindowHan
/**
* Returns identifiers for each open chrome window for tests interested in
* managing a set of chrome windows and tabs separately.
*
* @return {Array.<string>}
* Unique window handles.
*/
-GeckoDriver.prototype.getChromeWindowHandles = function (cmd, resp) {
+GeckoDriver.prototype.getChromeWindowHandles = function(cmd, resp) {
return this.chromeWindowHandles.map(String);
}
/**
* Get the current position and size of the browser window currently in focus.
*
* Will return the current browser window size in pixels. Refers to
* window outerWidth and outerHeight values, which include scroll bars,
@@ -1273,20 +1282,18 @@ GeckoDriver.prototype.getChromeWindowHan
* Object with |x| and |y| coordinates, and |width| and |height|
* of browser window.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getWindowRect = function (cmd, resp) {
- const win = assert.window(this.getCurrentWindow());
+GeckoDriver.prototype.getWindowRect = function(cmd, resp) {
assert.noUserPrompt(this.dialog);
-
return this.curBrowser.rect;
};
/**
* Set the window position and size of the browser on the operating
* system window manager.
*
* The supplied |width| and |height| values refer to the window outerWidth
@@ -1310,42 +1317,42 @@ GeckoDriver.prototype.getWindowRect = fu
*
* @throws {UnsupportedOperationError}
* Not applicable to application.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.setWindowRect = async function (cmd, resp) {
+GeckoDriver.prototype.setWindowRect = async function(cmd, resp) {
assert.firefox()
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {x, y, width, height} = cmd.parameters;
let origRect = this.curBrowser.rect;
// Throttle resize event by forcing the event queue to flush and delay
// until the main thread is idle.
- function optimisedResize (resolve) {
+ function optimisedResize(resolve) {
return () => Services.tm.idleDispatchToMainThread(() => {
win.requestAnimationFrame(resolve);
});
}
// Exit fullscreen and wait for window to resize.
- async function exitFullscreen () {
+ async function exitFullscreen() {
return new Promise(resolve => {
win.addEventListener("sizemodechange", optimisedResize(resolve), {once: true});
win.fullScreen = false;
});
}
// Synchronous resize to |width| and |height| dimensions.
- async function resizeWindow (width, height) {
+ async function resizeWindow(width, height) {
return new Promise(resolve => {
win.addEventListener("resize", optimisedResize(resolve), {once: true});
win.resizeTo(width, height);
});
}
// Wait until window size has changed. We can't wait for the
// user-requested window size as this may not be achievable on the
@@ -1358,17 +1365,17 @@ GeckoDriver.prototype.setWindowRect = as
resolve();
} else {
reject();
}
});
};
// Wait for the window position to change.
- async function windowPosition (x, y) {
+ async function windowPosition(x, y) {
return wait.until((resolve, reject) => {
if ((x == win.screenX && y == win.screenY) ||
(win.screenX != origRect.x || win.screenY != origRect.y)) {
resolve();
} else {
reject();
}
});
@@ -1395,43 +1402,47 @@ GeckoDriver.prototype.setWindowRect = as
win.moveTo(x, y);
await windowPosition(x, y);
}
return this.curBrowser.rect;
};
/**
- * Switch current top-level browsing context by name or server-assigned ID.
- * Searches for windows by name, then ID. Content windows take precedence.
+ * Switch current top-level browsing context by name or server-assigned
+ * ID. Searches for windows by name, then ID. Content windows take
+ * precedence.
*
* @param {string} name
* Target name or ID of the window to switch to.
* @param {boolean=} focus
* A boolean value which determines whether to focus
* the window. Defaults to true.
*/
GeckoDriver.prototype.switchToWindow = function* (cmd, resp) {
- let focus = (cmd.parameters.focus !== undefined) ? cmd.parameters.focus : true;
-
- // Window IDs are internally handled as numbers, but here it could also be the
- // name of the window.
+ let focus = true;
+ if (typeof cmd.parameters.focus != "undefined") {
+ focus = cmd.parameters.focus;
+ }
+
+ // Window IDs are internally handled as numbers, but here it could
+ // also be the name of the window.
let switchTo = parseInt(cmd.parameters.name);
if (isNaN(switchTo)) {
switchTo = cmd.parameters.name;
}
- let byNameOrId = function (win, windowId) {
+ let byNameOrId = function(win, windowId) {
return switchTo === win.name || switchTo === windowId;
};
let found = this.findWindow(this.windows, byNameOrId);
if (found) {
- yield this.setWindowHandle(found, focus);
+ yield this.setWindowHandle(found, focus);
} else {
throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
}
};
/**
* Find a specific window according to some filter function.
*
@@ -1441,58 +1452,62 @@ GeckoDriver.prototype.switchToWindow = f
* A callback function taking two arguments; the window and
* the outerId of the window, and returning a boolean indicating
* whether the window is the target.
*
* @return {Object}
* A window handle object containing the window and some
* associated metadata.
*/
-GeckoDriver.prototype.findWindow = function (winIterable, filter) {
+GeckoDriver.prototype.findWindow = function(winIterable, filter) {
for (let win of winIterable) {
let outerId = getOuterWindowId(win);
let tabBrowser = browser.getTabBrowser(win);
+
+ // In case the wanted window is a chrome window, we are done.
if (filter(win, outerId)) {
- // In case the wanted window is a chrome window, we are done.
- return {win: win, outerId: outerId, hasTabBrowser: !!tabBrowser};
+ return {win, outerId, hasTabBrowser: !!tabBrowser};
+
+ // Otherwise check if the chrome window has a tab browser, and that it
+ // contains a tab with the wanted window handle.
} else if (tabBrowser && tabBrowser.tabs) {
- // Otherwise check if the chrome window has a tab browser, and that it
- // contains a tab with the wanted window handle.
for (let i = 0; i < tabBrowser.tabs.length; ++i) {
let contentBrowser = browser.getBrowserForTab(tabBrowser.tabs[i]);
let contentWindowId = this.getIdForBrowser(contentBrowser);
if (filter(win, contentWindowId)) {
return {
- win: win,
- outerId: outerId,
+ win,
+ outerId,
hasTabBrowser: true,
tabIndex: i,
};
}
}
}
}
+
return null;
};
/**
* Switch the marionette window to a given window. If the browser in
* the window is unregistered, registers that browser and waits for
* the registration is complete. If |focus| is true then set the focus
* on the window.
*
* @param {Object} winProperties
* Object containing window properties such as returned from
* GeckoDriver#findWindow
* @param {boolean=} focus
* A boolean value which determines whether to focus the window.
* Defaults to true.
*/
-GeckoDriver.prototype.setWindowHandle = function* (winProperties, focus = true) {
+GeckoDriver.prototype.setWindowHandle = function* (
+ winProperties, focus = true) {
if (!(winProperties.outerId in this.browsers)) {
// Initialise Marionette if the current chrome window has not been seen
// before. Also register the initial tab, if one exists.
let registerBrowsers, browserListening;
if (winProperties.hasTabBrowser) {
registerBrowsers = this.registerPromise();
browserListening = this.listeningPromise();
@@ -1506,22 +1521,23 @@ GeckoDriver.prototype.setWindowHandle =
}
} else {
// Otherwise switch to the known chrome window, and activate the tab
// if it's a content browser.
this.curBrowser = this.browsers[winProperties.outerId];
if ("tabIndex" in winProperties) {
- this.curBrowser.switchToTab(winProperties.tabIndex, winProperties.win, focus);
+ this.curBrowser.switchToTab(
+ winProperties.tabIndex, winProperties.win, focus);
}
}
};
-GeckoDriver.prototype.getActiveFrame = function (cmd, resp) {
+GeckoDriver.prototype.getActiveFrame = function(cmd, resp) {
assert.window(this.getCurrentWindow());
switch (this.context) {
case Context.CHROME:
// no frame means top-level
resp.body.value = null;
if (this.curFrame) {
let elRef = this.curBrowser.seenEls
@@ -1590,92 +1606,104 @@ GeckoDriver.prototype.switchToFrame = fu
let baseURI = win.document.baseURI;
if (baseURI.startsWith("about:certerror")) {
throw new InsecureCertificateError();
} else if (otherErrorsExpr.exec(win.document.baseURI)) {
throw new UnknownError("Error loading page");
}
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
};
if (this.context == Context.CHROME) {
let foundFrame = null;
- // just focus
- if (typeof id == "undefined" && typeof element == "undefined") {
+ // just focus
+ if (typeof id == "undefined" && typeof element == "undefined") {
this.curFrame = null;
if (focus) {
this.mainFrame.focus();
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
// by element
if (this.curBrowser.seenEls.has(element)) {
// HTMLIFrameElement
- let wantedFrame = this.curBrowser.seenEls.get(element, {frame: curWindow});
+ let wantedFrame = this.curBrowser.seenEls.get(
+ element, {frame: curWindow});
// Deal with an embedded xul:browser case
- if (wantedFrame.tagName == "xul:browser" || wantedFrame.tagName == "browser") {
+ if (wantedFrame.tagName == "xul:browser" ||
+ wantedFrame.tagName == "browser") {
curWindow = wantedFrame.contentWindow;
this.curFrame = curWindow;
if (focus) {
this.curFrame.focus();
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
// Check if the frame is XBL anonymous
let parent = curWindow.document.getBindingParent(wantedFrame);
- // Shadow nodes also show up in getAnonymousNodes, we should ignore them.
- if (parent && !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
- let anonNodes = [...curWindow.document.getAnonymousNodes(parent) || []];
+ // Shadow nodes also show up in getAnonymousNodes, we should
+ // ignore them.
+ if (parent &&
+ !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
+ const doc = curWindow.document;
+ let anonNodes = [...doc.getAnonymousNodes(parent) || []];
if (anonNodes.length > 0) {
let el = wantedFrame;
while (el) {
if (anonNodes.indexOf(el) > -1) {
curWindow = wantedFrame.contentWindow;
this.curFrame = curWindow;
if (focus) {
this.curFrame.focus();
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
el = el.parentNode;
}
}
}
// else, assume iframe
let frames = curWindow.document.getElementsByTagName("iframe");
let numFrames = frames.length;
for (let i = 0; i < numFrames; i++) {
- if (new XPCNativeWrapper(frames[i]) == new XPCNativeWrapper(wantedFrame)) {
+ let wrappedEl = new XPCNativeWrapper(frames[i]);
+ let wrappedWanted = new XPCNativeWrapper(wantedFrame);
+ if (wrappedEl == wrappedWanted) {
curWindow = frames[i].contentWindow;
this.curFrame = curWindow;
if (focus) {
this.curFrame.focus();
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
return;
}
}
}
switch (typeof id) {
case "string" :
let foundById = null;
let frames = curWindow.document.getElementsByTagName("iframe");
let numFrames = frames.length;
for (let i = 0; i < numFrames; i++) {
- //give precedence to name
+ // give precedence to name
let frame = frames[i];
if (frame.getAttribute("name") == id) {
foundFrame = i;
curWindow = frame.contentWindow;
break;
} else if (foundById === null && frame.id == id) {
foundById = i;
}
@@ -1683,38 +1711,41 @@ GeckoDriver.prototype.switchToFrame = fu
if (foundFrame === null && foundById !== null) {
foundFrame = foundById;
curWindow = frames[foundById].contentWindow;
}
break;
case "number":
if (typeof curWindow.frames[id] != "undefined") {
foundFrame = id;
- curWindow = curWindow.frames[foundFrame].frameElement.contentWindow;
+ let frameEl = curWindow.frames[foundFrame].frameElement;
+ curWindow = frameEl.contentWindow;
}
break;
}
if (foundFrame !== null) {
this.curFrame = curWindow;
if (focus) {
this.curFrame.focus();
}
- checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
+ checkTimer.initWithCallback(
+ checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
} else {
throw new NoSuchFrameError(`Unable to locate frame: ${id}`);
}
} else if (this.context == Context.CONTENT) {
if (!id && !element &&
this.curBrowser.frameManager.currentRemoteFrame !== null) {
- // We're currently using a ChromeMessageSender for a remote frame, so this
- // request indicates we need to switch back to the top-level (parent) frame.
- // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
- // we send the message to the right listener.
+ // We're currently using a ChromeMessageSender for a remote frame,
+ // so this request indicates we need to switch back to the top-level
+ // (parent) frame. We'll first switch to the parent's (global)
+ // ChromeMessageBroadcaster, so we send the message to the right
+ // listener.
this.switchToGlobalMessageManager();
}
cmd.command_id = cmd.id;
let res = yield this.listener.switchToFrame(cmd.parameters);
if (res) {
let {win: winId, frame: frameId} = res;
this.mm = this.curBrowser.frameManager.getFrameMM(winId, frameId);
@@ -1726,32 +1757,32 @@ GeckoDriver.prototype.switchToFrame = fu
this.curBrowser.frameManager.switchToFrame(winId, frameId);
yield registerBrowsers;
yield browserListening;
}
}
};
-GeckoDriver.prototype.getTimeouts = function (cmd, resp) {
+GeckoDriver.prototype.getTimeouts = function(cmd, resp) {
return this.timeouts;
};
/**
* Set timeout for page loading, searching, and scripts.
*
* @param {Object.<string, number>}
* Dictionary of timeout types and their new value, where all timeout
* types are optional.
*
* @throws {InvalidArgumentError}
* If timeout type key is unknown, or the value provided with it is
* not an integer.
*/
-GeckoDriver.prototype.setTimeouts = function (cmd, resp) {
+GeckoDriver.prototype.setTimeouts = function(cmd, resp) {
// merge with existing timeouts
let merged = Object.assign(this.timeouts.toJSON(), cmd.parameters);
this.timeouts = session.Timeouts.fromJSON(merged);
};
/** Single tap. */
GeckoDriver.prototype.singleTap = function*(cmd, resp) {
assert.window(this.getCurrentWindow());
@@ -1778,17 +1809,17 @@ GeckoDriver.prototype.singleTap = functi
*
* @throws {UnsupportedOperationError}
* Not yet available in current context.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.performActions = function (cmd, resp) {
+GeckoDriver.prototype.performActions = function* (cmd, resp) {
assert.content(this.context,
"Command 'performActions' is not yet available in chrome context");
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let actions = cmd.parameters.actions;
yield this.listener.performActions({"actions": actions});
};
@@ -1798,17 +1829,17 @@ GeckoDriver.prototype.performActions = f
*
* @throws {UnsupportedOperationError}
* Not available in current context.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.releaseActions = function(cmd, resp) {
+GeckoDriver.prototype.releaseActions = function*(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
yield this.listener.releaseActions();
};
/**
@@ -1905,17 +1936,18 @@ GeckoDriver.prototype.findElement = func
switch (this.context) {
case Context.CHROME:
if (!SUPPORTED_STRATEGIES.has(strategy)) {
throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
}
let container = {frame: win};
if (opts.startNode) {
- opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
+ opts.startNode = this.curBrowser.seenEls.get(
+ opts.startNode, container);
}
let el = yield element.find(container, strategy, expr, opts);
let elRef = this.curBrowser.seenEls.add(el);
let webEl = element.makeWebElement(elRef);
resp.body.value = webEl;
break;
@@ -1950,17 +1982,18 @@ GeckoDriver.prototype.findElements = fun
switch (this.context) {
case Context.CHROME:
if (!SUPPORTED_STRATEGIES.has(strategy)) {
throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
}
let container = {frame: win};
if (opts.startNode) {
- opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
+ opts.startNode = this.curBrowser.seenEls.get(
+ opts.startNode, container);
}
let els = yield element.find(container, strategy, expr, opts);
let elRefs = this.curBrowser.seenEls.addAll(els);
let webEls = elRefs.map(element.makeWebElement);
resp.body = webEls;
break;
@@ -2013,27 +2046,29 @@ GeckoDriver.prototype.clickElement = fun
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
yield interaction.clickElement(el, this.a11yChecks);
break;
case Context.CONTENT:
- // We need to protect against the click causing an OOP frame to close.
- // This fires the mozbrowserclose event when it closes so we need to
- // listen for it and then just send an error back. The person making the
- // call should be aware something isnt right and handle accordingly
+ // We need to protect against the click causing an OOP frame
+ // to close. This fires the mozbrowserclose event when it closes
+ // so we need to listen for it and then just send an error back.
+ // The person making the call should be aware something is not right
+ // and handle accordingly.
this.addFrameCloseListener("click");
- let click = this.listener.clickElement({id: id, pageTimeout: this.timeouts.pageLoad});
-
- // If a remoteness update interrupts our page load, this will never return
- // We need to re-issue this request to correctly poll for readyState and
- // send errors.
+ let click = this.listener.clickElement(
+ {id, pageTimeout: this.timeouts.pageLoad});
+
+ // If a remoteness update interrupts our page load, this will
+ // never return We need to re-issue this request to correctly poll
+ // for readyState and send errors.
this.curBrowser.pendingCommands.push(() => {
let parameters = {
// TODO(ato): Bug 1242595
command_id: this.listener.activeMessageId,
pageTimeout: this.timeouts.pageLoad,
startTime: new Date().getTime(),
};
this.mm.broadcastAsyncMessage(
@@ -2057,17 +2092,17 @@ GeckoDriver.prototype.clickElement = fun
* @return {string}
* Value of the attribute.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementAttribute = function* (cmd, resp) {
+GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {id, name} = cmd.parameters;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
@@ -2091,17 +2126,17 @@ GeckoDriver.prototype.getElementAttribut
* @return {string}
* Value of the property.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementProperty = function* (cmd, resp) {
+GeckoDriver.prototype.getElementProperty = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {id, name} = cmd.parameters;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
@@ -2124,17 +2159,17 @@ GeckoDriver.prototype.getElementProperty
* @return {string}
* Element's text "as rendered".
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementText = function* (cmd, resp) {
+GeckoDriver.prototype.getElementText = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
// for chrome, we look at text nodes, and any node with a "label" field
@@ -2159,17 +2194,17 @@ GeckoDriver.prototype.getElementText = f
* @return {string}
* Local tag name of element.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementTagName = function* (cmd, resp) {
+GeckoDriver.prototype.getElementTagName = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
@@ -2191,17 +2226,17 @@ GeckoDriver.prototype.getElementTagName
* @return {boolean}
* True if displayed, false otherwise.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.isElementDisplayed = function* (cmd, resp) {
+GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
@@ -2226,31 +2261,32 @@ GeckoDriver.prototype.isElementDisplayed
* @return {string}
* Value of |propertyName|.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementValueOfCssProperty = function* (cmd, resp) {
+GeckoDriver.prototype.getElementValueOfCssProperty = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {id, propertyName: prop} = cmd.parameters;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
let sty = win.document.defaultView.getComputedStyle(el);
resp.body.value = sty.getPropertyValue(prop);
break;
case Context.CONTENT:
- resp.body.value = yield this.listener.getElementValueOfCssProperty(id, prop);
+ resp.body.value = yield this.listener
+ .getElementValueOfCssProperty(id, prop);
break;
}
};
/**
* Check if element is enabled.
*
* @param {string} id
@@ -2259,17 +2295,17 @@ GeckoDriver.prototype.getElementValueOfC
* @return {boolean}
* True if enabled, false if disabled.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.isElementEnabled = function* (cmd, resp) {
+GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
// Selenium atom doesn't quite work here
@@ -2293,17 +2329,17 @@ GeckoDriver.prototype.isElementEnabled =
* @return {boolean}
* True if selected, false if unselected.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.isElementSelected = function* (cmd, resp) {
+GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
// Selenium atom doesn't quite work here
@@ -2319,31 +2355,31 @@ GeckoDriver.prototype.isElementSelected
};
/**
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getElementRect = function* (cmd, resp) {
+GeckoDriver.prototype.getElementRect = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
let el = this.curBrowser.seenEls.get(id, {frame: win});
let rect = el.getBoundingClientRect();
resp.body = {
x: rect.x + win.pageXOffset,
y: rect.y + win.pageYOffset,
width: rect.width,
- height: rect.height
+ height: rect.height,
};
break;
case Context.CONTENT:
resp.body = yield this.listener.getElementRect(id);
break;
}
};
@@ -2356,17 +2392,17 @@ GeckoDriver.prototype.getElementRect = f
* @param {string} value
* Value to send to the element.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.sendKeysToElement = function* (cmd, resp) {
+GeckoDriver.prototype.sendKeysToElement = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {id, text} = cmd.parameters;
assert.string(text);
switch (this.context) {
case Context.CHROME:
@@ -2387,17 +2423,17 @@ GeckoDriver.prototype.sendKeysToElement
* @param {string} id
* Reference ID to the element that will be cleared.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.clearElement = function* (cmd, resp) {
+GeckoDriver.prototype.clearElement = function*(cmd, resp) {
const win = assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let id = cmd.parameters.id;
switch (this.context) {
case Context.CHROME:
// the selenium atom doesn't work here
@@ -2415,17 +2451,17 @@ GeckoDriver.prototype.clearElement = fun
}
};
/**
* Switch to shadow root of the given host element.
*
* @param {string} id element id.
*/
-GeckoDriver.prototype.switchToShadowRoot = function* (cmd, resp) {
+GeckoDriver.prototype.switchToShadowRoot = function*(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
let id = cmd.parameters.id;
yield this.listener.switchToShadowRoot(id);
};
/**
@@ -2440,17 +2476,17 @@ GeckoDriver.prototype.switchToShadowRoot
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
* @throws {InvalidCookieDomainError}
* If |cookie| is for a different domain than the active document's
* host.
*/
-GeckoDriver.prototype.addCookie = function (cmd, resp) {
+GeckoDriver.prototype.addCookie = function(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {protocol, hostname} = this.currentURL;
const networkSchemes = ["ftp:", "http:", "https:"];
if (!networkSchemes.includes(protocol)) {
@@ -2473,17 +2509,17 @@ GeckoDriver.prototype.addCookie = functi
*
* @throws {UnsupportedOperationError}
* Not available in current context.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.getCookies = function (cmd, resp) {
+GeckoDriver.prototype.getCookies = function(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {hostname, pathname} = this.currentURL;
resp.body = [...cookie.iter(hostname, pathname)];
};
@@ -2492,17 +2528,17 @@ GeckoDriver.prototype.getCookies = funct
*
* @throws {UnsupportedOperationError}
* Not available in current context.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.deleteAllCookies = function (cmd, resp) {
+GeckoDriver.prototype.deleteAllCookies = function(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {hostname, pathname} = this.currentURL;
for (let toDelete of cookie.iter(hostname, pathname)) {
cookie.remove(toDelete);
}
@@ -2513,28 +2549,30 @@ GeckoDriver.prototype.deleteAllCookies =
*
* @throws {UnsupportedOperationError}
* Not available in current context.
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.deleteCookie = function (cmd, resp) {
+GeckoDriver.prototype.deleteCookie = function(cmd, resp) {
assert.content(this.context);
assert.window(this.getCurrentWindow());
assert.noUserPrompt(this.dialog);
let {hostname, pathname} = this.currentURL;
let candidateName = assert.string(cmd.parameters.name);
for (let toDelete of cookie.iter(hostname, pathname)) {
if (toDelete.name === candidateName) {
return cookie.remove(toDelete);
}
}
+
+ throw UnknownError("Unable to find cookie");
};
/**
* Close the currently selected tab/window.
*
* With multiple open tabs present the currently selected tab will
* be closed. Otherwise the window itself will be closed. If it is the
* last window currently open, the window will not be closed to prevent
@@ -2544,85 +2582,88 @@ GeckoDriver.prototype.deleteCookie = fun
* @return {Array.<string>}
* Unique window handles of remaining windows.
*
* @throws {NoSuchWindowError}
* Top-level browsing context has been discarded.
* @throws {UnexpectedAlertOpenError}
* A modal dialog is open, blocking this operation.
*/
-GeckoDriver.prototype.close = function (cmd, resp) {
+GeckoDriver.prototype.close = function(cmd, resp) {
assert.contentBrowser(this.curBrowser);
assert.noUserPrompt(this.dialog);
let nwins = 0;
for (let win of this.windows) {
// For browser windows count the tabs. Otherwise take the window itself.
let tabbrowser = browser.getTabBrowser(win);
if (tabbrowser && tabbrowser.tabs) {
nwins += tabbrowser.tabs.length;
} else {
nwins += 1;
}
}
- // If there is only 1 window left, do not close it. Instead return a faked
- // empty array of window handles. This will instruct geckodriver to terminate
- // the application.
+ // If there is only one window left, do not close it. Instead return
+ // a faked empty array of window handles. This will instruct geckodriver
+ // to terminate the application.
if (nwins === 1) {
return [];
}
if (this.mm != globalMessageManager) {
this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
}
- return this.curBrowser.closeTab().then(() => this.windowHandles.map(String));
+ return this.curBrowser.closeTab()
+ .then(() => this.windowHandles.map(String));
};
/**
* Close the currently selected chrome window.
*
* If it is the last window currently open, the chrome window will not be
* closed to prevent a shutdown of the application. Instead the returned
* list of chrome window handles is empty.
*
* @return {Array.<string>}
* Unique chrome window handles of remaining chrome windows.
*/
-GeckoDriver.prototype.closeChromeWindow = function (cmd, resp) {
+GeckoDriver.prototype.closeChromeWindow = function(cmd, resp) {
assert.firefox();
assert.window(this.getCurrentWindow(Context.CHROME));
let nwins = 0;
+ // eslint-disable-next-line
for (let _ of this.windows) {
nwins++;
}
- // If there is only 1 window left, do not close it. Instead return a faked
- // empty array of window handles. This will instruct geckodriver to terminate
- // the application.
+ // If there is only one window left, do not close it. Instead return
+ // a faked empty array of window handles. This will instruct geckodriver
+ // to terminate the application.
if (nwins == 1) {
return [];
}
// reset frame to the top-most frame
this.curFrame = null;
if (this.mm != globalMessageManager) {
this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
}
- return this.curBrowser.closeWindow().then(() => this.chromeWindowHandles.map(String));
+ return this.curBrowser.closeWindow()
+ .then(() => this.chromeWindowHandles.map(String));
};
/** Delete Marionette session. */
-GeckoDriver.prototype.deleteSession = function (cmd, resp) {
+GeckoDriver.prototype.deleteSession = function(cmd, resp) {
if (this.curBrowser !== null) {
// frame scripts can be safely reused
Preferences.set(CONTENT_LISTENER_PREF, false);
// delete session in each frame in each browser
for (let win in this.browsers) {
let browser = this.browsers[win];
for (let i in browser.knownFrames) {
@@ -2688,47 +2729,47 @@ GeckoDriver.prototype.getAppCacheStatus
};
/**
* Takes a screenshot of a web element, current frame, or viewport.
*
* The screen capture is returned as a lossless PNG image encoded as
* a base 64 string.
*
- * If called in the content context, the <code>id</code> argument is not null
- * and refers to a present and visible web element's ID, the capture area
- * will be limited to the bounding box of that element. Otherwise, the
- * capture area will be the bounding box of the current frame.
+ * If called in the content context, the |id| argument is not null and
+ * refers to a present and visible web element's ID, the capture area will
+ * be limited to the bounding box of that element. Otherwise, the capture
+ * area will be the bounding box of the current frame.
*
- * If called in the chrome context, the screenshot will always represent the
- * entire viewport.
+ * If called in the chrome context, the screenshot will always represent
+ * the entire viewport.
*
* @param {string=} id
* Optional web element reference to take a screenshot of.
* If undefined, a screenshot will be taken of the document element.
* @param {Array.<string>=} highlights
* List of web elements to highlight.
* @param {boolean} full
* True to take a screenshot of the entire document element. Is not
* considered if {@code id} is not defined. Defaults to true.
* @param {boolean=} hash
* True if the user requests a hash of the image data.
* @param {boolean=} scroll
* Scroll to element if |id| is provided. If undefined, it will
* scroll to the element.
*
* @return {string}
- * If {@code hash} is false, PNG image encoded as base64 encoded string. If
- * 'hash' is True, hex digest of the SHA-256 hash of the base64 encoded
- * string.
+ * If |hash| is false, PNG image encoded as Base64 encoded string.
+ * If |hash| is True, hex digest of the SHA-256 hash of the base64
+ * encoded string.
*/
-GeckoDriver.prototype.takeScreenshot = function (cmd, resp) {
+GeckoDriver.prototype.takeScreenshot = function(cmd, resp) {
let win = assert.window(this.getCurrentWindow());
- let {id, highlights, full, hash, scroll} = cmd.parameters;
+ let {id, highlights, full, hash} = cmd.parameters;
highlights = highlights || [];
let format = hash ? capture.Format.Hash : capture.Format.Base64;
switch (this.context) {
case Context.CHROME:
let container = {frame: win.document.defaultView};
let highlightEls = highlights.map(
@@ -2758,26 +2799,28 @@ GeckoDriver.prototype.takeScreenshot = f
case capture.Format.Base64:
return capture.toBase64(canvas);
}
break;
case Context.CONTENT:
return this.listener.takeScreenshot(format, cmd.parameters);
}
+
+ throw new TypeError(`Unknown context: ${this.context}`);
};
/**
* Get the current browser orientation.
*
* Will return one of the valid primary orientation values
* portrait-primary, landscape-primary, portrait-secondary, or
* landscape-secondary.
*/
-GeckoDriver.prototype.getScreenOrientation = function (cmd, resp) {
+GeckoDriver.prototype.getScreenOrientation = function(cmd, resp) {
assert.fennec();
let win = assert.window(this.getCurrentWindow());
resp.body.value = win.screen.mozOrientation;
};
/**
* Set the current browser orientation.
@@ -2785,17 +2828,17 @@ GeckoDriver.prototype.getScreenOrientati
* The supplied orientation should be given as one of the valid
* orientation values. If the orientation is unknown, an error will
* be raised.
*
* Valid orientations are "portrait" and "landscape", which fall
* back to "portrait-primary" and "landscape-primary" respectively,
* and "portrait-secondary" as well as "landscape-secondary".
*/
-GeckoDriver.prototype.setScreenOrientation = function (cmd, resp) {
+GeckoDriver.prototype.setScreenOrientation = function(cmd, resp) {
assert.fennec();
let win = assert.window(this.getCurrentWindow());
const ors = [
"portrait", "landscape",
"portrait-primary", "landscape-primary",
"portrait-secondary", "landscape-secondary",
];
@@ -2886,43 +2929,43 @@ GeckoDriver.prototype.fullscreen = funct
height: win.outerHeight,
};
};
/**
* Dismisses a currently displayed tab modal, or returns no such alert if
* no modal is displayed.
*/
-GeckoDriver.prototype.dismissDialog = function (cmd, resp) {
+GeckoDriver.prototype.dismissDialog = function(cmd, resp) {
assert.window(this.getCurrentWindow());
this._checkIfAlertIsPresent();
let {button0, button1} = this.dialog.ui;
(button1 ? button1 : button0).click();
this.dialog = null;
};
/**
* Accepts a currently displayed tab modal, or returns no such alert if
* no modal is displayed.
*/
-GeckoDriver.prototype.acceptDialog = function (cmd, resp) {
+GeckoDriver.prototype.acceptDialog = function(cmd, resp) {
assert.window(this.getCurrentWindow());
this._checkIfAlertIsPresent();
let {button0} = this.dialog.ui;
button0.click();
this.dialog = null;
};
/**
- * Returns the message shown in a currently displayed modal, or returns a no such
- * alert error if no modal is currently displayed.
+ * Returns the message shown in a currently displayed modal, or returns
+ * a no such alert error if no modal is currently displayed.
*/
-GeckoDriver.prototype.getTextFromDialog = function (cmd, resp) {
+GeckoDriver.prototype.getTextFromDialog = function(cmd, resp) {
assert.window(this.getCurrentWindow());
this._checkIfAlertIsPresent();
let {infoBody} = this.dialog.ui;
resp.body.value = infoBody.textContent;
};
/**
@@ -2939,17 +2982,17 @@ GeckoDriver.prototype.getTextFromDialog
* @throws {ElementNotInteractableError}
* If the current user prompt is an alert or confirm.
* @throws {NoSuchAlertError}
* If there is no current user prompt.
* @throws {UnsupportedOperationError}
* If the current user prompt is something other than an alert,
* confirm, or a prompt.
*/
-GeckoDriver.prototype.sendKeysToDialog = function (cmd, resp) {
+GeckoDriver.prototype.sendKeysToDialog = function(cmd, resp) {
let win = assert.window(this.getCurrentWindow());
this._checkIfAlertIsPresent();
// see toolkit/components/prompts/content/commonDialog.js
let {loginContainer, loginTextbox} = this.dialog.ui;
if (loginContainer.hidden) {
throw new ElementNotInteractableError(
"This prompt does not accept text input");
@@ -2957,39 +3000,39 @@ GeckoDriver.prototype.sendKeysToDialog =
event.sendKeysToElement(
cmd.parameters.text,
loginTextbox,
{ignoreVisibility: true},
this.dialog.window ? this.dialog.window : win);
};
-GeckoDriver.prototype._checkIfAlertIsPresent = function () {
+GeckoDriver.prototype._checkIfAlertIsPresent = function() {
if (!this.dialog || !this.dialog.ui) {
throw new NoAlertOpenError("No modal dialog is currently open");
}
};
/**
* Enables or disables accepting new socket connections.
*
- * By calling this method with `false` the server will not accept any further
- * connections, but existing connections will not be forcible closed. Use `true`
- * to re-enable accepting connections.
+ * By calling this method with `false` the server will not accept any
+ * further connections, but existing connections will not be forcible
+ * closed. Use `true` to re-enable accepting connections.
*
- * Please note that when closing the connection via the client you can end-up in
- * a non-recoverable state if it hasn't been enabled before.
+ * Please note that when closing the connection via the client you can
+ * end-up in a non-recoverable state if it hasn't been enabled before.
*
- * This method is used for custom in application shutdowns via marionette.quit()
- * or marionette.restart(), like File -> Quit.
+ * This method is used for custom in application shutdowns via
+ * marionette.quit() or marionette.restart(), like File -> Quit.
*
* @param {boolean} state
* True if the server should accept new socket connections.
*/
-GeckoDriver.prototype.acceptConnections = function (cmd, resp) {
+GeckoDriver.prototype.acceptConnections = function(cmd, resp) {
assert.boolean(cmd.parameters.value);
this._server.acceptConnections = cmd.parameters.value;
}
/**
* Quits the application with the provided flags.
*
* Marionette will stop accepting new connections before ending the
@@ -3060,42 +3103,43 @@ GeckoDriver.prototype.quit = function* (
Services.startup.quit(mode);
yield quitApplication
.then(cause => resp.body.cause = cause)
.then(() => resp.send());
};
-GeckoDriver.prototype.installAddon = function (cmd, resp) {
+GeckoDriver.prototype.installAddon = function(cmd, resp) {
assert.firefox()
let path = cmd.parameters.path;
let temp = cmd.parameters.temporary || false;
if (typeof path == "undefined" || typeof path != "string" ||
typeof temp != "boolean") {
throw InvalidArgumentError();
}
return addon.install(path, temp);
};
-GeckoDriver.prototype.uninstallAddon = function (cmd, resp) {
+GeckoDriver.prototype.uninstallAddon = function(cmd, resp) {
assert.firefox()
let id = cmd.parameters.id;
if (typeof id == "undefined" || typeof id != "string") {
throw new InvalidArgumentError();
}
return addon.uninstall(id);
};
/** Receives all messages from content messageManager. */
-GeckoDriver.prototype.receiveMessage = function (message) {
+/* eslint-disable consistent-return */
+GeckoDriver.prototype.receiveMessage = function(message) {
switch (message.name) {
case "Marionette:ok":
case "Marionette:done":
case "Marionette:error":
// check if we need to remove the mozbrowserclose listener
if (this.mozBrowserClose !== null) {
let win = this.getCurrentWindow();
win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
@@ -3145,18 +3189,19 @@ GeckoDriver.prototype.receiveMessage = f
// of desktop this just sets up a small amount of state that doesn't
// change over the course of a session.
this.sendAsync("newSession", this.capabilities);
this.curBrowser.flushPendingCommands();
}
break;
}
};
-
-GeckoDriver.prototype.responseCompleted = function () {
+/* eslint-enable consistent-return */
+
+GeckoDriver.prototype.responseCompleted = function() {
if (this.curBrowser !== null) {
this.curBrowser.pendingCommands = [];
}
};
/**
* Retrieve the localized string for the specified entity id.
*
@@ -3166,44 +3211,46 @@ GeckoDriver.prototype.responseCompleted
* @param {Array.<string>} urls
* Array of .dtd URLs.
* @param {string} id
* The ID of the entity to retrieve the localized string for.
*
* @return {string}
* The localized string for the requested entity.
*/
-GeckoDriver.prototype.localizeEntity = function (cmd, resp) {
+GeckoDriver.prototype.localizeEntity = function(cmd, resp) {
let {urls, id} = cmd.parameters;
if (!Array.isArray(urls)) {
throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
}
if (typeof id != "string") {
throw new InvalidArgumentError("Value of `id` should be of type 'string'");
}
resp.body.value = l10n.localizeEntity(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.
* @param {string} id
* The ID of the property to retrieve the localized string for.
*
* @return {string}
* The localized string for the requested property.
*/
-GeckoDriver.prototype.localizeProperty = function (cmd, resp) {
+GeckoDriver.prototype.localizeProperty = function(cmd, resp) {
let {urls, id} = cmd.parameters;
if (!Array.isArray(urls)) {
throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
}
if (typeof id != "string") {
throw new InvalidArgumentError("Value of `id` should be of type 'string'");
}
@@ -3426,17 +3473,17 @@ GeckoDriver.prototype.commands = {
"singleTap": GeckoDriver.prototype.singleTap,
"switchToFrame": GeckoDriver.prototype.switchToFrame,
"switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
"switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
"switchToWindow": GeckoDriver.prototype.switchToWindow,
"takeScreenshot": GeckoDriver.prototype.takeScreenshot,
};
-function copy (obj) {
+function copy(obj) {
if (Array.isArray(obj)) {
return obj.slice();
} else if (typeof obj == "object") {
return Object.assign({}, obj);
}
return obj;
}
--- a/testing/marionette/element.js
+++ b/testing/marionette/element.js
@@ -242,17 +242,17 @@ element.Store = class {
* @throws InvalidSelectorError
* If |strategy| is unknown.
* @throws InvalidSelectorError
* If |selector| is malformed.
* @throws NoSuchElementError
* 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;
let searchFn;
if (opts.all) {
searchFn = findElements.bind(this);
} else {
searchFn = findElement.bind(this);
@@ -270,17 +270,18 @@ element.find = function (container, stra
findElements.then(foundEls => {
// the following code ought to be moved into findElement
// and findElements when bug 1254486 is addressed
if (!opts.all && (!foundEls || foundEls.length == 0)) {
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:
msg = "Unable to locate element: " + selector;
}
reject(new NoSuchElementError(msg));
}
@@ -296,18 +297,18 @@ element.find = function (container, stra
function find_(container, strategy, selector, searchFn, opts) {
let rootNode = container.shadowRoot || container.frame.document;
let startNode;
if (opts.startNode) {
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) {
startNode = rootNode.documentElement;
}
break;
default:
@@ -340,17 +341,17 @@ function find_(container, strategy, sele
* @param {DOMElement} startNode
* Where in the DOM hiearchy to begin searching.
* @param {string} expr
* XPath search expression.
*
* @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;
};
/**
* Find elements by XPath expression.
*
@@ -359,17 +360,17 @@ element.findByXPath = function (root, st
* @param {DOMElement} startNode
* Where in the DOM hierarchy to begin searching.
* @param {string} expr
* XPath search expression.
*
* @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);
let el = iter.iterateNext();
while (el) {
rv.push(el);
el = iter.iterateNext();
}
@@ -382,32 +383,32 @@ element.findByXPathAll = function (root,
* @param {DOMElement} node
* Where in the DOM hierarchy to being searching.
* @param {string} s
* Link text to search for.
*
* @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);
};
/**
* Find all hyperlinks descendant of |node| which link text contains |s|.
*
* @param {DOMElement} node
* Where in the DOM hierachy to begin searching.
* @param {string} s
* Link text to search for.
*
* @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);
};
/**
* Filters all hyperlinks that are descendant of |node| by |predicate|.
*
* @param {DOMElement} node
* Where in the DOM hierarchy to begin searching.
@@ -542,17 +543,18 @@ function findElements(using, value, root
// fall through
case element.Strategy.XPath:
return element.findByXPathAll(rootNode, startNode, value);
case element.Strategy.Name:
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);
case element.Strategy.TagName:
return startNode.getElementsByTagName(value);
case element.Strategy.LinkText:
@@ -564,63 +566,66 @@ function findElements(using, value, root
case element.Strategy.Selector:
return startNode.querySelectorAll(value);
case element.Strategy.Anon:
return rootNode.getAnonymousNodes(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];
}
return [];
default:
throw new InvalidSelectorError(`No such strategy: ${using}`);
}
}
/** 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]":
case "[object FileList]":
case "[object HTMLAllCollection]":
case "[object HTMLCollection]":
case "[object HTMLFormControlsCollection]":
case "[object HTMLOptionsCollection]":
case "[object NodeList]":
return true;
default:
return false;
}
};
-element.makeWebElement = function (uuid) {
+element.makeWebElement = function(uuid) {
return {
[element.Key]: uuid,
[element.LegacyKey]: 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() {
let uuid = uuidGen.generateUUID().toString();
return uuid.substring(1, uuid.length - 1);
};
/**
@@ -631,42 +636,41 @@ element.generateUUID = function() {
* Element to be checked.
* @param {Container} container
* Container with |frame|, which is the window object that contains
* the element, and an optional |shadowRoot|.
*
* @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) {
return true;
}
// looking for next possible ShadowRoot ancestor
let parent = shadowRoot.host;
while (parent && !(parent instanceof frame.ShadowRoot)) {
parent = parent.parentNode;
}
return element.isDisconnected(
shadowRoot.host,
- {frame: frame, shadowRoot: parent});
+ {frame, shadowRoot: parent});
+ }
- // outside shadow dom
- } else {
- let docEl = frame.document.documentElement;
- return el.compareDocumentPosition(docEl) &
- DOCUMENT_POSITION_DISCONNECTED;
- }
+ // outside shadow DOM
+ let docEl = frame.document.documentElement;
+ return el.compareDocumentPosition(docEl) &
+ DOCUMENT_POSITION_DISCONNECTED;
};
/**
* This function generates a pair of coordinates relative to the viewport
* given a target element and coordinates relative to that element's
* top-left corner.
*
* @param {Node} node
@@ -679,17 +683,17 @@ element.isDisconnected = function (el, c
* the centre of the target's bounding box.
*
* @return {Object.<string, number>}
* X- and Y coordinates.
*
* @throws TypeError
* If |xOffset| or |yOffset| are not numbers.
*/
-element.coordinates = function (
+element.coordinates = function(
node, xOffset = undefined, yOffset = undefined) {
let box = node.getBoundingClientRect();
if (typeof xOffset == "undefined" || xOffset === null) {
xOffset = box.width / 2.0;
}
if (typeof yOffset == "undefined" || yOffset === null) {
@@ -716,24 +720,24 @@ element.coordinates = function (
* the target's bounding box.
* @param {number=} y
* Vertical offset relative to target. Defaults to the centre of
* the target's bounding box.
*
* @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 &&
c.x + win.pageXOffset <= vp.right &&
vp.top <= c.y + win.pageYOffset &&
c.y + win.pageYOffset <= vp.bottom);
};
@@ -749,17 +753,17 @@ element.inViewport = function (el, x = u
* is itself.
*
* @param {Element} el
* Element to get the container of.
*
* @return {Element}
* Container element of |el|.
*/
-element.getContainer = function (el) {
+element.getContainer = function(el) {
if (el.localName != "option") {
return el;
}
function validContext(ctx) {
return ctx.localName == "datalist" || ctx.localName == "select";
}
@@ -791,17 +795,17 @@ element.getContainer = function (el) {
* in view.
*
* @param {Element} el
* Element to check if is in view.
*
* @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";
const tree = element.getPointerInteractablePaintTree(el);
return tree.includes(el);
} finally {
el.style.pointerEvents = originalPointerEvents;
}
@@ -818,17 +822,17 @@ element.isInView = function (el) {
* the target's bounding box.
* @param {number=} y
* Vertical offset relative to target. Defaults to the centre of
* the target's bounding box.
*
* @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
if (!element.isXULElement(el) && !atom.isElementDisplayed(el, win)) {
return false;
}
if (el.tagName.toLowerCase() == "body") {
@@ -855,17 +859,17 @@ element.isVisible = function (el, x = un
* inclusive descendant of itself.
*
* @param {DOMElement} el
* Element determine if is pointer-interactable.
*
* @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]);
};
/**
* Calculate the in-view centre point of the area of the given DOM client
* rectangle that is inside the viewport.
*
@@ -873,17 +877,17 @@ element.isObscured = function (el) {
* Element off a DOMRect sequence produced by calling |getClientRects|
* on a |DOMElement|.
* @param {nsIDOMWindow} win
* Current browsing context.
*
* @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 = {
left: max(0, min(rect.x, rect.x + rect.width)),
right: min(win.innerWidth, max(rect.x, rect.x + rect.width)),
};
let y = {
top: max(0, min(rect.y, rect.y + rect.height)),
@@ -904,17 +908,17 @@ element.getInViewCentrePoint = function
* of any rendered scrollbars.
*
* @param {DOMElement} el
* Element to determine if is pointer-interactable.
*
* @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};
const rootNode = el.getRootNode();
// Include shadow DOM host only if the element's root node is not the
// owner document.
if (rootNode !== doc) {
@@ -936,47 +940,55 @@ element.getPointerInteractablePaintTree
let centre = element.getInViewCentrePoint(rects[0], win);
// step 5
return doc.elementsFromPoint(centre.x, centre.y);
};
// TODO(ato): Not implemented.
// In fact, it's not defined in the spec.
-element.isKeyboardInteractable = function (el) {
+element.isKeyboardInteractable = function(el) {
return true;
};
/**
* Attempts to scroll into view |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;
};
const boolEls = {
audio: ["autoplay", "controls", "loop", "muted"],
button: ["autofocus", "disabled", "formnovalidate"],
details: ["open"],
dialog: ["open"],
fieldset: ["disabled"],
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"],
ol: ["reversed"],
optgroup: ["disabled"],
option: ["disabled", "selected"],
script: ["async", "defer"],
select: ["autofocus", "disabled", "multiple", "required"],
@@ -991,24 +1003,25 @@ const boolEls = {
* @param {DOMElement} el
* Element to test if |attr| is a boolean attribute on.
* @param {string} attr
* Attribute to test is a boolean attribute.
*
* @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;
}
if (!boolEls.hasOwnProperty(el.localName)) {
return false;
}
return boolEls[el.localName].includes(attr)
};
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -40,17 +40,17 @@ const BUILTIN_ERRORS = new Set([
"InternalError",
"RangeError",
"ReferenceError",
"SyntaxError",
"TypeError",
"URIError",
]);
-this.EXPORTED_SYMBOLS = ["error"].concat(Array.from(ERRORS));
+this.EXPORTED_SYMBOLS = ["error", "error.pprint"].concat(Array.from(ERRORS));
this.error = {};
/**
* Check if |val| is an instance of the |Error| prototype.
*
* Because error objects may originate from different globals, comparing
* the prototype of the left hand side with the prototype property from
@@ -66,36 +66,36 @@ this.error = {};
* |nsIException| because they are special snowflakes and may indeed
* cause Firefox to crash if used with |instanceof|.
*
* @param {*} val
* Any value that should be undergo the test for errorness.
* @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));
};
/**
* Ensures error instance is a WebDriverError.
*
* If the given error is already in the WebDriverError prototype
@@ -104,80 +104,81 @@ error.isWebDriverError = function (obj)
*
* @param {Error} err
* Error to conditionally turn into a WebDriverError.
*
* @return {WebDriverError}
* 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;
}
return new UnknownError(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) {
Cu.reportError(msg);
}
};
/**
* 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) {
s += "\n" + err.stack;
}
return s;
} catch (e) {
return "<unprintable error>";
}
};
/**
* Pretty-print values passed to template strings.
*
* 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 {
s = JSON.stringify(obj);
} catch (e) {
if (e instanceof TypeError) {
s = `<${e.message}>`;
} else {
throw e;
}
}
return proto + " " + s;
}
- function prettyElement (el) {
+ function prettyElement(el) {
let ident = [];
if (el.id) {
ident.push(`id="${el.id}"`);
}
if (el.classList.length > 0) {
ident.push(`class="${el.className}"`);
}
@@ -189,17 +190,16 @@ error.pprint = function (ss, ...values)
return `<${el.localName}${idents}>`;
}
let res = [];
for (let i = 0; i < ss.length; i++) {
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) {
s = prettyElement(val);
} else {
s = prettyObject(val);
}
} catch (e) {
@@ -217,36 +217,36 @@ error.pprint = function (ss, ...values)
* error in the specification.
*/
class WebDriverError extends Error {
/**
* @param {(string|Error)=} x
* 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";
// Error's ctor does not preserve x' stack
if (error.isError(x)) {
this.stack = x.stack;
}
}
- toJSON () {
+ toJSON() {
return {
error: this.status,
message: this.message || "",
stacktrace: this.stack || "",
}
}
- static fromJSON (json) {
+ static fromJSON(json) {
if (typeof json.error == "undefined") {
let s = JSON.stringify(json);
throw new TypeError("Undeserialisable error type: " + s);
}
if (!STATUSES.has(json.error)) {
throw new TypeError("Not of WebDriverError descent: " + json.error);
}
@@ -258,17 +258,17 @@ class WebDriverError extends Error {
if ("stacktrace" in json) {
err.stack = json.stacktrace;
}
return err;
}
}
class ElementNotAccessibleError extends WebDriverError {
- constructor (message) {
+ constructor(message) {
super(message);
this.status = "element not accessible";
}
}
/**
* An element click could not be completed because the element receiving
* the events is obscuring the element that was requested clicked.
@@ -276,17 +276,17 @@ class ElementNotAccessibleError extends
* @param {Element=} obscuredEl
* Element obscuring the element receiving the click. Providing this
* is not required, but will produce a nicer error message.
* @param {Map.<string, number>} coords
* Original click location. Providing this is not required, but
* 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;
const overlayingEl = doc.elementFromPoint(coords.x, coords.y);
switch (obscuredEl.style.pointerEvents) {
case "none":
msg = error.pprint`Element ${obscuredEl} is not clickable ` +
@@ -306,59 +306,59 @@ class ElementClickInterceptedError exten
}
super(msg);
this.status = "element click intercepted";
}
}
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";
}
}
/**
* Creates a richly annotated error for an error situation that occurred
* whilst evaluating injected scripts.
@@ -407,108 +407,108 @@ class JavaScriptError extends WebDriverE
super(msg);
this.status = "javascript error";
this.stack = trace;
}
}
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";
}
}
const STATUSES = new Map([
["element click intercepted", ElementClickInterceptedError],
["element not accessible", ElementNotAccessibleError],
--- a/testing/marionette/evaluate.js
+++ b/testing/marionette/evaluate.js
@@ -65,17 +65,17 @@ this.evaluate = {};
* information on the origin script file in the local end.
*
* The {@code line} option is used in error messages, along with
* {@code filename}, to provide the line number in the origin script
* file on the local end.
*
* @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.
* @param {Object.<string, ?>=} opts
* Dictionary of options:
*
* async (boolean)
* Indicates if the script should return immediately or wait
@@ -94,30 +94,30 @@ this.evaluate = {};
* timeout (boolean)
* Duration in milliseconds before interrupting the script.
*
* @return {Promise}
* A promise that when resolved will give you the return value from
* 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) => {
let src = "";
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
if (!opts.directInject) {
if (opts.async) {
sb[CALLBACK] = sb[COMPLETE];
}
sb[ARGUMENTS] = sandbox.cloneInto(args, sb);
@@ -146,22 +146,26 @@ evaluate.sandbox = function (sb, script,
if (opts.debug) {
sb.window.onerror = (msg, url, line) => {
let err = new JavaScriptError(`${msg} at ${url}:${line}`);
reject(err);
};
}
// 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",
file,
line,
script,
});
reject(err);
@@ -191,52 +195,52 @@ evaluate.sandbox = function (sb, script,
* Window.
* @param {ShadowRoot} shadowRoot
* Shadow root.
*
* @return {?}
* 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;
}
};
/**
* Convert arbitrary objects to JSON-safe primitives that can be
* transported over the Marionette protocol.
*
* Any DOM elements are converted to web elements by looking them up
@@ -246,95 +250,93 @@ evaluate.fromJSON = function (obj, seenE
* Object to be marshaled.
* @param {element.Store} seenEls
* Element store to use for lookup of web element references.
*
* @return {?}
* 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);
}
// arbitrary objects + files
let rv = {};
for (let prop in obj) {
try {
rv[prop] = evaluate.toJSON(obj[prop], seenEls);
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_IMPLEMENTED) {
logger.debug(`Skipping ${prop}: ${e.message}`);
} else {
throw e;
}
}
- return rv;
}
+ return rv;
};
this.sandbox = {};
/**
* Provides a safe way to take an object defined in a privileged scope and
* create a structured clone of it in a less-privileged scope. It returns
* a reference to the clone.
*
* 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});
};
/**
* Augment given sandbox by an adapter that has an {@code exports}
* 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
* function names and function references.
*
* @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);
for (let [name, func] of funcs) {
sb[name] = func;
}
return sb;
@@ -347,17 +349,17 @@ sandbox.augment = function (sb, adapter)
* The DOM Window object.
* @param {nsIPrincipal=} principal
* An optional, custom principal to prefer over the Window. Useful if
* you need elevated security permissions.
*
* @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,
sandboxPrototype: window,
wantComponents: true,
wantXrays: true,
}, opts);
return new Cu.Sandbox(p, opts);
@@ -368,31 +370,31 @@ sandbox.create = function (window, princ
* will have lasting side-effects.
*
* @param {Window} window
* The DOM Window object.
*
* @return {Sandbox}
* The created sandbox.
*/
-sandbox.createMutable = function (window) {
+sandbox.createMutable = function(window) {
let opts = {
wantComponents: false,
wantXrays: false,
};
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());
return sb;
};
/**
* Sandbox storage. When the user requests a sandbox by a specific name,
--- a/testing/marionette/event.js
+++ b/testing/marionette/event.js
@@ -1,13 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-// Provides functionality for creating and sending DOM events.
+/** Provides functionality for creating and sending DOM events. */
+this.event = {};
"use strict";
/* global content, is */
const {interfaces: Ci, utils: Cu, classes: Cc} = Components;
Cu.import("resource://gre/modules/Log.jsm");
const logger = Log.repository.getLogger("Marionette");
@@ -32,18 +33,16 @@ function getDOMWindowUtils(win) {
win = window;
}
// this assumes we are operating in chrome space
return win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
}
-this.event = {};
-
event.MouseEvents = {
click: 0,
dblclick: 1,
mousedown: 2,
mouseup: 3,
mouseover: 4,
mouseout: 5,
};
@@ -63,34 +62,34 @@ event.Modifiers = {
* @param {(DOMElement|string)} target
* Target of event. Can either be an element or the ID of an element.
* @param {Window=} window
* Window object. Defaults to the current window.
*
* @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) {
target = window.document.getElementById(target);
} else {
window = window || target.ownerGlobal;
}
let ev = window.document.createEvent("MouseEvent");
- let type = mouseEvent.type;
let view = window;
let detail = mouseEvent.detail;
if (!detail) {
if (mouseEvent.type in ["click", "mousedown", "mouseup"]) {
detail = 1;
} else if (mouseEvent.type == "dblclick") {
detail = 2;
@@ -133,49 +132,49 @@ event.sendMouseEvent = function (mouseEv
*
* This function handles casing of characters (sends the right charcode,
* and sends a shift key for uppercase chars). No other modifiers are
* handled at this point.
*
* 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);
};
/**
* Send string to the focused element.
*
* 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);
}
};
/**
* Send the non-character key to the focused element.
*
* The name of the key should be the part that comes after "DOM_VK_"
* 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;
}
if (modifiers.ctrlKey) {
mval |= Ci.nsIDOMNSEvent.CONTROL_MASK;
}
if (modifiers.altKey) {
@@ -212,17 +211,17 @@ event.parseModifiers_ = function (modifi
* Vertical offset to click from the target's bounding box.
* @param {Object.<string, ?>} opts
* Object which may contain the properties "shiftKey", "ctrlKey",
* "altKey", "metaKey", "accessKey", "clickCount", "button", and
* "type".
* @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(
rect.left + offsetX, rect.top + offsetY, opts, window);
};
/*
* Synthesize a mouse event at a particular point in a window.
@@ -236,61 +235,101 @@ event.synthesizeMouse = function (
* CSS pixels from the top document margin.
* @param {Object.<string, ?>} opts
* Object which may contain the properties "shiftKey", "ctrlKey",
* "altKey", "metaKey", "accessKey", "clickCount", "button", and
* "type".
* @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);
let button = opts.button || 0;
let clickCount = opts.clickCount || 1;
let modifiers = event.parseModifiers_(opts);
let pressure = ("pressure" in opts) ? opts.pressure : 0;
let inputSource = ("inputSource" in opts) ? opts.inputSource :
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);
}
};
/**
* 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,
rect.width / 2,
rect.height / 2,
event,
window);
};
+/* eslint-disable */
function computeKeyCodeFromChar_(char) {
if (char.length != 1) {
return 0;
}
if (char in VIRTUAL_KEYCODE_LOOKUP) {
return Ci.nsIDOMKeyEvent["DOM_" + VIRTUAL_KEYCODE_LOOKUP[char]];
}
@@ -383,25 +422,26 @@ function computeKeyCodeFromChar_(char) {
case "\n":
return Ci.nsIDOMKeyEvent.DOM_VK_RETURN;
default:
return 0;
}
}
+/* eslint-enable */
/**
* Returns true if the given key should cause keypress event when widget
* handles the native key event. Otherwise, false.
*
* 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];
if (!key) {
throw new TypeError("Unknown key: " + key);
}
// if key generates a character, it must cause a keypress event
@@ -440,30 +480,29 @@ event.isKeypressFiredKey = function (key
* a key event of that type is fired. Otherwise, a keydown, a keypress,
* and then a keyup event are fired in sequence.
* @param {Window=} window
* Window object. Defaults to the current window.
*
* @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;
}
var KeyboardEvent = getKeyboardEvent_(win);
var modifiers = emulateToActivateModifiers_(TIP, event, win);
var keyEventDict = createKeyboardEventDictionary_(key, event, win);
var keyEvent = new KeyboardEvent("", keyEventDict.dictionary);
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) {
TIP.keydown(keyEvent, keyEventDict.flags);
if ("repeat" in event && event.repeat > 1) {
keyEventDict.dictionary.repeat = true;
var repeatedKeyEvent = new KeyboardEvent("", keyEventDict.dictionary);
for (var i = 1; i < event.repeat; i++) {
@@ -476,18 +515,17 @@ event.synthesizeKey = function (key, eve
}
} finally {
emulateToInactivateModifiers_(TIP, modifiers, win);
}
};
var TIPMap = new WeakMap();
-function getTIP_(win, callback)
-{
+function getTIP_(win, callback) {
if (!win) {
win = window;
}
var tip;
if (TIPMap.has(win)) {
tip = TIPMap.get(win);
} else {
tip =
@@ -497,50 +535,51 @@ function getTIP_(win, callback)
}
if (!tip.beginInputTransactionForTests(win, callback)) {
tip = null;
TIPMap.delete(win);
}
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) {}
}
if (typeof content != "undefined" && ("KeyboardEvent" in content)) {
return content.KeyboardEvent;
}
return win.KeyboardEvent;
}
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;
var keyName = "Unidentified";
if (key.indexOf("KEY_") == 0) {
keyName = key.substr("KEY_".length);
result.flags |= Ci.nsITextInputProcessor.KEY_NON_PRINTABLE_KEY;
} else if (key.indexOf("VK_") == 0) {
keyCode = Ci.nsIDOMKeyEvent["DOM_" + key];
if (!keyCode) {
throw "Unknown key: " + key;
}
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;
if (!keyCodeIsDefined) {
keyCode = computeKeyCodeFromChar_(key.charAt(0));
}
if (!keyCode) {
result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEYCODE_ZERO;
@@ -554,120 +593,118 @@ function createKeyboardEventDictionary_(
if (locationIsDefined && keyEvent.location === 0) {
result.flags |= Ci.nsITextInputProcessor.KEY_KEEP_KEY_LOCATION_STANDARD;
}
result.dictionary = {
key: "key" in keyEvent ? keyEvent.key : keyName,
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,
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
}
}
function isMac_(win = window) {
if (win) {
try {
return win.navigator.platform.indexOf("Mac") > -1;
} catch (ex) {}
}
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:
return "Cancel";
case KeyboardEvent.DOM_VK_HELP:
return "Help";
case KeyboardEvent.DOM_VK_BACK_SPACE:
return "Backspace";
@@ -802,21 +839,23 @@ function guessKeyNameFromKeyCode_(aKeyCo
case KeyboardEvent.DOM_VK_EREOF:
return "EraseEof";
case KeyboardEvent.DOM_VK_PLAY:
return "Play";
default:
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;
}
seenEvent = false;
let type;
@@ -830,16 +869,17 @@ function expectEvent_(expectedTarget, ex
let pass = (!seenEvent && ev.originalTarget == expectedTarget && ev.type == type);
is(pass, true, `${testName} ${type} event target ${seenEvent ? "twice" : ""}`);
seenEvent = true;
};
expectedTarget.addEventListener(type, handler);
return handler;
}
+/* eslint-enable */
/**
* Check if the event was fired or not. The provided event handler will
* be removed.
*/
function checkExpectedEvent_(
expectedTarget, expectedEvent, eventHandler, testName) {
@@ -882,17 +922,17 @@ function checkExpectedEvent_(
* Expected originalTarget of the event.
* @param {DOMEvent} expectedEvent
* Expected type of the event, such as "select".
* @param {string} testName
* Test name when outputing results.
* @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) {
let eventHandler = expectEvent_(
expectedTarget,
expectedEvent,
testName);
event.synthesizeMouse(target, offsetX, offsetY, ev, window);
@@ -921,17 +961,17 @@ event.synthesizeMouseExpectEvent = funct
* @param {Window=} window
* Window object. Defaults to the current window.
*
* To test that an event is not fired, use an expected type preceded by an
* exclamation mark, such as "!select".
*
* aWindow is optional, and defaults to the current window object.
*/
-event.synthesizeKeyExpectEvent = function (
+event.synthesizeKeyExpectEvent = function(
key, ev, expectedTarget, expectedEvent, testName,
window = undefined) {
let eventHandler = expectEvent_(
expectedTarget,
expectedEvent,
testName);
event.synthesizeKey(key, ev, window);
@@ -950,17 +990,17 @@ event.synthesizeKeyExpectEvent = functio
* member. The value must be "compositionstart", "compositionend" or
* "compositionupdate". And also this may have |data| and |locale|
* which would be used for the value of each property of the
* composition event. Note that the data would be ignored if the
* event type were "compositionstart".
* @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 || "");
};
/**
* Synthesize a text event.
*
* The text event's information, this has |composition| and |caret|
@@ -995,17 +1035,17 @@ event.synthesizeComposition = function (
* larger than 0, it should be wide caret. However, current nsEditor
* doesn't support wide caret, therefore, you should always set 0 now.
*
* @param {Object.<string, ?>} ev
* The text event's information,
* @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 ||
!ev.composition.clauses ||
!ev.composition.clauses[0]) {
return;
}
@@ -1047,17 +1087,17 @@ event.synthesizeText = function (ev, win
* Synthesize a query selected text event.
*
* @param {Window=}
* Window object. Defaults to the current window.
*
* @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);
};
/**
* Synthesize a selection set event.
*
@@ -1070,17 +1110,17 @@ event.synthesizeQuerySelectedText = func
* @param {boolean} reverse
* If true, the selection is from |aOffset + aLength| to |aOffset|.
* Otherwise, from |aOffset| to |aOffset + aLength|.
* @param {Window=} window
* Window object. Defaults to the current window.
*
* @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);
};
const KEYCODES_LOOKUP = {
"VK_SHIFT": "shiftKey",
"VK_CONTROL": "ctrlKey",
@@ -1223,52 +1263,55 @@ function isPrintable(c, win = window) {
KeyboardEvent.DOM_VK_EXSEL,
KeyboardEvent.DOM_VK_EREOF,
KeyboardEvent.DOM_VK_PLAY,
KeyboardEvent.DOM_VK_RETURN,
];
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;
};
/**
* Synthesize a key event for a single key.
*
* @param {string} keyToSend
* 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") {
keyName = keyName.toUpperCase();
}
event.synthesizeKey(keyName, modifiers, window);
};
@@ -1291,17 +1334,17 @@ function focusElement(element) {
}
/**
* @param {string} keyString
* @param {Element} 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)) {
focusElement(el);
// make Object.<modifier, false> map
let modifiers = Object.create(event.Modifiers);
for (let modifier in event.Modifiers) {
@@ -1313,60 +1356,60 @@ event.sendKeysToElement = function (
event.sendSingleKey(c, modifiers, window);
}
} else {
throw new ElementNotInteractableError("Element is not visible");
}
};
-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;
let ev = doc.createEvent("Event");
ev.shiftKey = modifiers["shift"];
ev.metaKey = modifiers["meta"];
ev.altKey = modifiers["alt"];
ev.ctrlKey = modifiers["ctrl"];
ev.initEvent(eventType, opts.canBubble, true);
el.dispatchEvent(ev);
};
-event.focus = function (el, opts = {}) {
+event.focus = function(el, opts = {}) {
opts.canBubble = opts.canBubble || true;
let doc = el.ownerDocument || el.document;
let win = doc.defaultView;
let ev = new win.FocusEvent(el);
ev.initEvent("focus", opts.canBubble, true);
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);
};
--- a/testing/marionette/frame.js
+++ b/testing/marionette/frame.js
@@ -17,17 +17,17 @@ const FRAME_SCRIPT = "chrome://marionett
// list of OOP frames that has the frame script loaded
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
this.frameId = frameId;
// assigned frame ID, used for messaging
this.targetFrameId = this.frameId;
// list of OOP frames that has the frame script loaded
this.remoteFrames = [];
@@ -56,16 +56,17 @@ frame.Manager = class {
// set to true when we have been interrupted by a modal
this.handledModal = false;
this.driver = driver;
}
/**
* Receives all messages from content messageManager.
*/
+ /*eslint-disable*/
receiveMessage(message) {
switch (message.name) {
case "MarionetteFrame:getInterruptedState":
// this will return true if the calling frame was interrupted by a modal dialog
if (this.previousRemoteFrame) {
// get the frame window of the interrupted frame
let interruptedFrame = Services.wm.getOuterWindowWithId(
this.previousRemoteFrame.windowId);
@@ -118,16 +119,17 @@ frame.Manager = class {
return {value: isLocal};
case "MarionetteFrame:getCurrentFrameId":
if (this.currentRemoteFrame !== null) {
return this.currentRemoteFrame.frameId;
}
}
}
+ /*eslint-enable*/
getOopFrame(winId, frameId) {
// get original frame window
let outerWin = Services.wm.getOuterWindowWithId(winId);
// find the OOP frame
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
return f;
}
@@ -242,17 +244,18 @@ frame.Manager = class {
removeMessageManagerListeners(mm) {
mm.removeWeakMessageListener("Marionette:ok", this.driver);
mm.removeWeakMessageListener("Marionette:done", this.driver);
mm.removeWeakMessageListener("Marionette:error", this.driver);
mm.removeWeakMessageListener("Marionette:log", this.driver);
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);
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
}
};
frame.Manager.prototype.QueryInterface = XPCOMUtils.generateQI(
--- a/testing/marionette/interaction.js
+++ b/testing/marionette/interaction.js
@@ -140,30 +140,30 @@ this.interaction = {};
* @throws {ElementClickInterceptedError}
* If |el| is obscured by another element and a click would not hit,
* in |specCompat| mode.
* @throws {ElementNotAccessibleError}
* If |strict| is true and element is not accessible.
* @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);
} else if (specCompat) {
yield webdriverClickElement(el, a11y);
} else {
yield seleniumClickElement(el, a11y);
}
};
-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") {
throw new InvalidArgumentError(
"Cannot click <input type=file> elements");
}
let containerEl = element.getContainer(el);
@@ -208,17 +208,17 @@ function* webdriverClickElement (el, a11
// step 9
yield interaction.flushEventLoop(win);
// step 10
// if the click causes navigation, the post-navigation checks are
// 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");
}
yield a11y.getAccessible(el, true).then(acc => {
a11y.assertVisible(acc, el, true);
a11y.assertEnabled(acc, el, true);
a11y.assertActionable(acc, el);
@@ -226,17 +226,17 @@ function* chromeClick (el, a11y) {
if (el.localName == "option") {
interaction.selectOption(el);
} else {
el.click();
}
}
-function* seleniumClickElement (el, a11y) {
+function* seleniumClickElement(el, a11y) {
let win = getWindow(el);
let visibilityCheckEl = el;
if (el.localName == "option") {
visibilityCheckEl = element.getContainer(el);
}
if (!element.isVisible(visibilityCheckEl)) {
@@ -256,17 +256,17 @@ function* seleniumClickElement (el, a11y
if (el.localName == "option") {
interaction.selectOption(el);
} else {
let rects = el.getClientRects();
let centre = element.getInViewCentrePoint(rects[0], win);
let opts = {};
event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
}
-};
+}
/**
* Select <option> element in a <select> list.
*
* Because the dropdown list of select elements are implemented using
* native widget technology, our trusted synthesised events are not able
* to reach them. Dropdowns are instead handled mimicking DOM events,
* which for obvious reasons is not ideal, but at the current point in
@@ -275,25 +275,24 @@ function* seleniumClickElement (el, a11y
* @param {HTMLOptionElement} option
* Option element to select.
*
* @throws TypeError
* If |el| is a XUL element or not an <option> element.
* @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");
}
if (el.localName != "option") {
throw new TypeError("Invalid elements");
}
- let win = getWindow(el);
let containerEl = element.getContainer(el);
event.mouseover(containerEl);
event.mousemove(containerEl);
event.mousedown(containerEl);
event.focus(containerEl);
event.input(containerEl);
@@ -328,17 +327,17 @@ interaction.flushEventLoop = function* (
resolve();
};
if (win.closed) {
resolve();
return;
}
- win.addEventListener("beforeunload", handleEvent, false);
+ win.addEventListener("beforeunload", handleEvent);
win.requestAnimationFrame(handleEvent);
});
};
/**
* Appends |path| to an <input type=file>'s file list.
*
* @param {HTMLInputElement} el
@@ -408,17 +407,18 @@ interaction.setFormControlValue = functi
* Element to send key events to.
* @param {Array.<string>} value
* Sequence of keystrokes to send to the element.
* @param {boolean} ignoreVisibility
* Flag to enable or disable element visibility tests.
* @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 => {
a11y.assertActionable(acc, el);
event.sendKeysToElement(value, el, {ignoreVisibility: false}, win);
});
};
@@ -428,17 +428,17 @@ interaction.sendKeysToElement = function
* @param {DOMElement|XULElement} el
* Element to determine displayedness of.
* @param {boolean=} strict
* Enforce strict accessibility tests.
*
* @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);
let a11y = accessibility.get(strict);
return a11y.getAccessible(el).then(acc => {
a11y.assertVisible(acc, el, displayed);
return displayed;
});
@@ -448,17 +448,17 @@ interaction.isElementDisplayed = functio
* Check if element is enabled.
*
* @param {DOMElement|XULElement} el
* Element to test if is enabled.
*
* @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);
if (element.isXULElement(el)) {
// check if XUL element supports disabled attribute
if (DISABLED_ATTRIBUTE_SUPPORTED_XUL.has(el.tagName.toUpperCase())) {
let disabled = atom.getElementAttribute(el, "disabled", win);
if (disabled && disabled === "true") {
@@ -485,17 +485,17 @@ interaction.isElementEnabled = function
* @param {DOMElement|XULElement} el
* Element to test if is selected.
* @param {boolean=} strict
* Enforce strict accessibility tests.
*
* @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);
if (element.isXULElement(el)) {
let tagName = el.tagName.toUpperCase();
if (CHECKED_PROPERTY_SUPPORTED_XUL.has(tagName)) {
selected = el.checked;
}
--- a/testing/marionette/l10n.js
+++ b/testing/marionette/l10n.js
@@ -2,21 +2,22 @@
* 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/. */
"use strict";
/**
* 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;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(
@@ -38,17 +39,17 @@ this.l10n = {};
* @param {Array.<string>} urls
* Array of .dtd URLs.
* @param {string} id
* The ID of the entity to retrieve the localized string for.
*
* @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) => {
locations.push(`<!ENTITY % dtd_${index} SYSTEM "${url}">%dtd_${index};`);
})
// Use the DOM parser to resolve the entity and extract its real value
let header = `<?xml version="1.0"?><!DOCTYPE elem [${locations.join("")}]>`;
@@ -62,35 +63,38 @@ l10n.localizeEntity = function (urls, id
return element.textContent;
};
/**
* 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.
* @param {string} id
* The ID of the property to retrieve the localized string for.
*
* @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) {
let bundle = Services.strings.createBundle(url);
try {
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;
};
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -13,16 +13,17 @@ var uuidGen = Cc["@mozilla.org/uuid-gene
.getService(Ci.nsIUUIDGenerator);
var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
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");
Cu.import("chrome://marionette/content/accessibility.js");
Cu.import("chrome://marionette/content/action.js");
Cu.import("chrome://marionette/content/atom.js");
Cu.import("chrome://marionette/content/capture.js");
Cu.import("chrome://marionette/content/element.js");
@@ -47,17 +48,17 @@ Cu.import("chrome://marionette/content/p
Cu.import("chrome://marionette/content/session.js");
Cu.importGlobalProperties(["URL"]);
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;
var seenEls = new element.Store();
var SUPPORTED_STRATEGIES = new Set([
element.Strategy.ClassName,
element.Strategy.Selector,
element.Strategy.ID,
@@ -96,95 +97,102 @@ var syncChrome = proxy.toChrome(sendSync
var logger = Log.repository.getLogger("Marionette");
// Append only once to avoid duplicated output after listener.js gets reloaded
if (logger.ownAppenders.length == 0) {
logger.addAppender(new Log.DumpAppender());
}
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;
}
curContainer = {frame: content, shadowRoot: null};
};
// sandbox storage and name of the current sandbox
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,
seenBeforeUnload: false,
seenUnload: false,
timeout: null,
timerPageLoad: null,
timerPageUnload: null,
/**
* 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
timeout = startTime + timeout - new Date().getTime();
if (timeout <= 0) {
this.notify(this.timerPageLoad);
return;
}
if (waitForUnloaded) {
addEventListener("hashchange", this, false);
addEventListener("pagehide", this, false);
// 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();
}
if (this.timerPageUnload) {
this.timerPageUnload.cancel();
}
@@ -209,17 +217,17 @@ var loadListener = {
try {
Services.obs.removeObserver(this, "outer-window-destroyed");
} catch (e) {}
},
/**
* 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}"`);
switch (event.type) {
case "beforeunload":
this.seenBeforeUnload = true;
break;
@@ -252,20 +260,21 @@ var loadListener = {
this.stop();
sendError(new InsecureCertificateError(), this.command_id);
} else if (/about:.*(error)\?/.exec(event.target.baseURI)) {
this.stop();
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);
}
break;
case "pageshow":
if (event.target === curContainer.frame.document) {
@@ -274,51 +283,55 @@ var loadListener = {
}
break;
}
},
/**
* 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");
this.stop();
sendOk(this.command_id);
}
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}"`);
switch (topic) {
// In the case when the currently selected frame is closed,
// there will be no further load events. Stop listening immediately.
case "outer-window-destroyed":
@@ -326,93 +339,101 @@ var loadListener = {
this.stop();
sendOk(this.command_id);
}
break;
}
},
/**
- * 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);
},
/**
* Use a trigger callback to initiate a page load, and attach listeners if
* a page load is expected.
*
* @param {function} trigger
* Callback that triggers the page load.
* @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();
this.start(command_id, timeout, startTime, true);
}
return Task.spawn(function* () {
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 => {
if (loadEventExpected) {
this.stop();
}
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]) {
let {id, remotenessChange} = register[0][0];
capabilities = session.Capabilities.fromJSON(register[0][1]);
listenerId = id;
if (typeof id != "undefined") {
startListeners();
@@ -430,25 +451,24 @@ function registerSelf() {
//
// Perhaps one could even conceive having a separate instance of
// CommandProcessor for the listener, because the code is mostly the same.
function dispatch(fn) {
if (typeof fn != "function") {
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 => {
if (typeof rv == "undefined") {
sendOk(id);
} else {
sendResponse(rv, id);
}
@@ -516,25 +536,29 @@ function startListeners() {
addMessageListenerId("Marionette:waitForPageLoaded", waitForPageLoaded);
addMessageListenerId("Marionette:cancelRequest", cancelRequest);
addMessageListenerId("Marionette:getTitle", getTitleFn);
addMessageListenerId("Marionette:getPageSource", getPageSourceFn);
addMessageListenerId("Marionette:goBack", goBack);
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);
addMessageListenerId("Marionette:sendKeysToElement", sendKeysToElementFn);
addMessageListenerId("Marionette:clearElement", clearElementFn);
addMessageListenerId("Marionette:switchToFrame", switchToFrame);
addMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
@@ -586,42 +610,55 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:get", get);
removeMessageListenerId("Marionette:waitForPageLoaded", waitForPageLoaded);
removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
removeMessageListenerId("Marionette:getTitle", getTitleFn);
removeMessageListenerId("Marionette:getPageSource", getPageSourceFn);
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);
removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
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) {
action.inputStateMap.clear();
}
if (action.inputsToCancel !== undefined) {
action.inputsToCancel.length = 0;
}
@@ -685,45 +722,42 @@ function sendError(err, uuid) {
function resetValues() {
sandboxes.clear();
curContainer = {frame: content, shadowRoot: null};
legacyactions.mouseEventsOnly = false;
action.inputStateMap = new Map();
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) {
opts.timeout = timeout;
let sb = sandbox.createMutable(curContainer.frame);
let wargs = evaluate.fromJSON(
args, seenEls, curContainer.frame, curContainer.shadowRoot);
@@ -739,88 +773,120 @@ function* executeInSandbox(script, args,
let wargs = evaluate.fromJSON(
args, seenEls, curContainer.frame, curContainer.shadowRoot);
let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
let res = yield evaluatePromise;
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);
}
}
/**
* Function that perform a single tap
*/
function singleTap(id, corx, cory) {
let el = seenEls.get(id, curContainer);
// 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"));
return a11y.getAccessible(el, true).then(acc => {
a11y.assertVisible(acc, el, visible);
a11y.assertActionable(acc, el);
if (!curContainer.frame.document.createTouch) {
legacyactions.mouseEventsOnly = true;
}
let c = element.coordinates(el, 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);
});
}
/**
* Function to create a touch based on the element
* corx and cory are relative to the viewport, id is the touchId
*/
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;
}
/**
* Perform a series of grouped actions at the specified points in time.
*
* @param {obj} msg
* Object with an |actions| attribute that is an Array of objects
@@ -828,22 +894,23 @@ function createATouch(el, corx, cory, to
*/
function* performActions(msg) {
let chain = action.Chain.fromJson(msg.actions);
yield action.dispatch(chain, seenEls, curContainer);
}
/**
* 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();
}
/**
* Start action chain on one finger.
*/
function actionChain(chain, touchId) {
@@ -854,71 +921,62 @@ function actionChain(chain, touchId) {
return legacyactions.dispatchActions(
chain,
touchId,
curContainer,
seenEls,
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,
win,
0,
false, false, false, false,
documentTouches,
targetTouches,
changedTouches);
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 = {};
return;
}
// a set of actions need to be done
let batch = batches[batchIndex];
// each action for some finger
let pack;
// the touch id for the finger (pack)
let touchId;
// command for the finger
let command;
// touch that will be created for the finger
let el;
- let corx;
- let cory;
let touch;
let lastTouch;
let touchIndex;
let waitTime = 0;
let maxTime = 0;
let c;
// loop through the batch
@@ -935,17 +993,18 @@ function setDispatch(batches, touches, b
touch = createATouch(el, c.x, c.y, touchId);
multiLast[touchId] = touch;
touches.push(touch);
emitMultiEvents("touchstart", touch, touches);
break;
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);
break;
case "move":
el = seenEls.get(pack[2], curContainer);
c = element.coordinates(el);
@@ -957,24 +1016,34 @@ function setDispatch(batches, touches, b
break;
case "moveByOffset":
el = multiLast[touchId].target;
lastTouch = multiLast[touchId];
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);
break;
case "wait":
if (typeof pack[2] != "undefined") {
waitTime = pack[2] * 1000;
@@ -1007,77 +1076,85 @@ function multiAction(args, maxLen) {
let commandArray = evaluate.fromJSON(
args, seenEls, curContainer.frame, curContainer.shadowRoot);
let concurrentEvent = [];
let temp;
for (let i = 0; i < maxLen; i++) {
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);
}
}
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);
}
/**
* Cancel the polling and remove the event listener associated with a
* current navigation request in case we're interupted by an onbeforeunload
* handler and navigation doesn't complete.
*/
function cancelRequest() {
loadListener.stop();
}
/**
- * 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") {
try {
let requestedURL = new URL(url).toString();
if (loadEventExpected === null) {
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;
}
}
// We need to move to the top frame before navigating
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
curContainer.frame = content;
@@ -1090,19 +1167,21 @@ function get(msg) {
}
}
/**
* Cause the browser to traverse one step backward in the joint history
* 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;
try {
loadListener.navigate(() => {
curContainer.frame.history.back();
}, command_id, pageTimeout);
@@ -1112,40 +1191,45 @@ function goBack(msg) {
}
}
/**
* Cause the browser to traverse one step forward in the joint history
* 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;
try {
loadListener.navigate(() => {
curContainer.frame.history.forward();
}, command_id, pageTimeout);
} catch (e) {
sendError(e, command_id);
}
}
/**
- * 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;
try {
// We need to move to the top frame before navigating
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
curContainer.frame = content;
@@ -1218,21 +1302,23 @@ function getActiveElement() {
let el = curContainer.frame.document.activeElement;
return evaluate.toJSON(el, seenEls);
}
/**
* 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;
try {
let loadEventExpected = true;
let target = getElementAttribute(id, "target");
@@ -1254,22 +1340,20 @@ function clickElement(msg) {
}
}
function getElementAttribute(id, name) {
let el = seenEls.get(id, curContainer);
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) {
let el = seenEls.get(id, curContainer);
return typeof el[name] != "undefined" ? el[name] : null;
}
/**
@@ -1339,19 +1423,19 @@ function getElementValueOfCssProperty(id
* @return {Object.<string, number>}
* The x, y, width, and height properties of the element.
*/
function getElementRect(id) {
let el = seenEls.get(id, curContainer);
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,
};
}
/**
* Check if element is enabled.
*
* @param {WebElement} id
* Reference to web element.
@@ -1409,23 +1493,24 @@ function clearElement(id) {
} else {
throw e;
}
}
}
/**
* 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 {
parent = curContainer.shadowRoot.host;
} catch (e) {
// There is a chance that host element is dead and we are trying to
// access a dead object.
curContainer.shadowRoot = null;
@@ -1438,67 +1523,69 @@ function switchToShadowRoot(id) {
}
return;
}
let foundShadowRoot;
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,
* its index in window.frames, or the iframe's name or id.
*/
function switchToFrame(msg) {
let command_id = msg.json.command_id;
let foundFrame = null;
let frames = [];
let parWindow = null;
// 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();
}
sendOk(command_id);
return;
}
let id = msg.json.element;
@@ -1508,81 +1595,90 @@ function switchToFrame(msg) {
wantedFrame = seenEls.get(id, curContainer);
} catch (e) {
sendError(e, command_id);
}
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;
}
}
}
if (foundFrame === null) {
// 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;
}
}
}
}
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();
}
sendOk(command_id);
return;
}
} catch (e) {
// 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;
}
}
}
}
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;
// notify our parent to handle the switch
curContainer.frame = content;
let rv = {win: parWindow, frame: foundFrame};
sendResponse(rv, command_id);
@@ -1735,17 +1831,17 @@ function* reftestWait(url, remote) {
};
addEventListener("load", maybeResolve, true);
});
} else {
// Ensure that the event loop has spun at least once since load,
// 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) {
// Check again in case reftest-wait was removed since the load event
if (root.classList.contains("reftest-wait")) {
logger.debug("Waiting for reftest-wait removal");
yield new Promise(resolve => {
let observer = new win.MutationObserver(() => {
--- a/testing/marionette/message.js
+++ b/testing/marionette/message.js
@@ -16,17 +16,17 @@ this.EXPORTED_SYMBOLS = [
"Command",
"Message",
"MessageOrigin",
"Response",
];
const logger = Log.repository.getLogger("Marionette");
-this.MessageOrigin = {
+const MessageOrigin = {
Client: 0,
Server: 1,
};
this.Message = {};
/**
* Converts a data packet into a Command or Response type.
@@ -37,17 +37,17 @@ this.Message = {};
* or result.
*
* @return {(Command,Response)}
* Based on the message type, a Command or Response instance.
*
* @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);
case Response.TYPE:
return Response.fromMsg(data);
default:
@@ -93,17 +93,17 @@ Message.fromMsg = function (data) {
*
* @param {number} msgId
* Message ID unique identifying this message.
* @param {string} name
* Command name.
* @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);
this.parameters = assert.object(params);
this.onerror = null;
this.onresult = null;
@@ -143,57 +143,56 @@ this.Command = class {
// if parameters are given but null, treat them as undefined
if (params === null) {
params = undefined;
}
return new Command(msgID, name, params);
}
-};
+}
Command.TYPE = 0;
-
const validator = {
exclusionary: {
"capabilities": ["error", "value"],
"error": ["value", "sessionId", "capabilities"],
"sessionId": ["error", "value"],
"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) {
if (obj.hasOwnProperty(t)) {
throw new TypeError(`${t} set, cannot set ${prop}`);
}
}
}
obj[prop] = val;
return true;
},
-};
+}
/**
* The response body is exposed as an argument to commands.
* Commands can set fields on the body through defining properties.
*
* Setting properties invokes a validator that performs tests for
* mutually exclusionary fields on the input against the existing data
* in the body.
*
* For example setting the {@code error} property on the body when
* {@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
* of its corresponding command.
*
* The response is a mutable object passed to each command for
* modification through the available setters. To send data in a response,
* you modify the body property on the response. The body property can
@@ -204,17 +203,17 @@ this.ResponseBody = () => new Proxy({},
* will have no effect.
*
* @param {number} msgID
* Message ID tied to the corresponding command request this is a
* response for.
* @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);
this.error = null;
this.body = ResponseBody();
this.origin = MessageOrigin.Server;
@@ -287,11 +286,11 @@ this.Response = class {
assert.that(n => n === Response.TYPE)(type);
let resp = new Response(msgID);
resp.error = assert.string(err);
resp.body = body;
return resp;
}
-};
+}
Response.TYPE = 1;
--- a/testing/marionette/modal.js
+++ b/testing/marionette/modal.js
@@ -5,26 +5,28 @@
"use strict";
const {utils: Cu} = Components;
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 = {};
modal = {
COMMON_DIALOG_LOADED: "common-dialog-loaded",
TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
handlers: {
"common-dialog-loaded": new Set(),
- "tabmodal-dialog-loaded": new Set()
- }
+ "tabmodal-dialog-loaded": new Set(),
+ },
};
/**
* Add handler that will be called when a global- or tab modal dialogue
* appears.
*
* This is achieved by installing observers for common-
* and tab modal loaded events.
@@ -32,17 +34,17 @@ modal = {
* This function is a no-op if called on any other product than Firefox.
*
* @param {function(Object, string)} handler
* The handler to be called, which is passed the
* subject (e.g. ChromeWindow) and the topic (one of
* {@code modal.COMMON_DIALOG_LOADED} or
* {@code modal.TABMODAL_DIALOG_LOADED}.
*/
-modal.addHandler = function (handler) {
+modal.addHandler = function(handler) {
if (!isFirefox()) {
return;
}
Object.keys(this.handlers).map(topic => {
this.handlers[topic].add(handler);
Services.obs.addObserver(handler, topic);
});
@@ -50,38 +52,42 @@ modal.addHandler = function (handler) {
/**
* Check for already existing modal or tab modal dialogs
*
* @param {browser.Context} context
* 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) {
return new modal.Dialog(() => context, null);
}
}
return null;
@@ -91,17 +97,17 @@ modal.findModalDialogs = function (conte
* Remove modal dialogue handler by function reference.
*
* This function is a no-op if called on any other product than Firefox.
*
* @param {function} toRemove
* The handler previously passed to modal.addHandler which will now
* be removed.
*/
-modal.removeHandler = function (toRemove) {
+modal.removeHandler = function(toRemove) {
if (!isFirefox()) {
return;
}
for (let topic of Object.keys(this.handlers)) {
let handlers = this.handlers[topic];
for (let handler of handlers) {
if (handler == toRemove) {
--- a/testing/marionette/navigate.js
+++ b/testing/marionette/navigate.js
@@ -20,17 +20,17 @@ this.navigate = {};
* Destination URL
*
* @return {boolean}
* Full page load would be expected if url gets loaded.
*
* @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");
}
switch (new URL(url).protocol) {
// assume javascript:<whatever> will modify current document
// but this is not an entirely safe assumption to make,
--- a/testing/marionette/packets.js
+++ b/testing/marionette/packets.js
@@ -20,59 +20,62 @@
* Called when the output stream is ready to write
* * get done()
* Returns true once the packet is done being read / written
* * destroy()
* Called to clean up at the end of use
*/
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);
/**
* A generic Packet processing object (extended by two subtypes below).
*/
function Packet(transport) {
this._transport = transport;
this._length = 0;
}
/**
- * 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);
};
Packet.prototype = {
get length() {
return this._length;
@@ -81,48 +84,50 @@ Packet.prototype = {
set length(length) {
if (length > PACKET_LENGTH_MAX) {
throw Error("Packet length " + length + " exceeds the max length of " +
PACKET_LENGTH_MAX);
}
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);
this._data = "";
this._done = false;
}
/**
- * 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) {
return null;
}
let packet = new JSONPacket(transport);
packet.length = +match[1];
@@ -132,32 +137,32 @@ JSONPacket.fromHeader = function (header
JSONPacket.HEADER_PATTERN = /^(\d+):$/;
JSONPacket.prototype = Object.create(Packet.prototype);
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);
if (!this.done) {
// Don't have a complete packet yet.
return;
}
@@ -172,128 +177,132 @@ JSONPacket.prototype.read = function (st
console.error(msg);
dump(msg + "\n");
return;
}
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
this._outgoing = this.length + ":" + this._data;
}
let written = stream.write(this._outgoing, this._outgoing.length);
this._outgoing = this._outgoing.slice(written);
this._done = !this._outgoing.length;
};
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);
this._done = false;
this._readyForWriting = defer();
}
/**
- * 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) {
return null;
}
let packet = new BulkPacket(transport);
packet.header = {
actor: match[1],
type: match[2],
- length: +match[3]
+ length: +match[3],
};
return packet;
};
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();
let deferred = defer();
this._transport._onBulkReadReady({
actor: this.actor,
type: this.type,
length: this.length,
copyTo: (output) => {
let copying = StreamUtils.copyStream(stream, output, this.length);
deferred.resolve(copying);
return copying;
},
- stream: stream,
- done: deferred
+ stream,
+ done: deferred,
});
// Await the result of reading from the stream
deferred.promise.then(() => {
this._done = true;
this._transport.resumeIncoming();
}, this._transport.close);
// Ensure this is only done once
this.read = () => {
throw new Error("Tried to read() a BulkPacket's stream multiple times.");
};
};
-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 + " " +
this.length + ":";
}
// Write the header, or whatever's left of it to write.
if (this._outgoingHeader.length) {
@@ -309,61 +318,61 @@ BulkPacket.prototype.write = function (s
let deferred = defer();
this._readyForWriting.resolve({
copyFrom: (input) => {
let copying = StreamUtils.copyStream(input, stream, this.length);
deferred.resolve(copying);
return copying;
},
- stream: stream,
- done: deferred
+ stream,
+ done: deferred,
});
// Await the result of writing to the stream
deferred.promise.then(() => {
this._done = true;
this._transport.resumeOutgoing();
}, this._transport.close);
// Ensure this is only done once
this.write = () => {
throw new Error("Tried to write() a BulkPacket's stream multiple times.");
};
};
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;
},
});
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);
};
/**
* RawPacket is used to test the transport's error handling of malformed
* packets, by writing data directly onto the stream.
* @param transport DebuggerTransport
* The transport instance that will own the packet.
@@ -374,24 +383,24 @@ function RawPacket(transport, data) {
Packet.call(this, transport);
this._data = data;
this.length = data.length;
this._done = false;
}
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;
- }
+ },
});
--- a/testing/marionette/proxy.js
+++ b/testing/marionette/proxy.js
@@ -25,17 +25,17 @@ const logger = Log.repository.getLogger(
// Proxy handler that traps requests to get a property. Will prioritise
// properties that exist on the object's own prototype.
var ownPriorityGetterTrap = {
get: (obj, prop) => {
if (obj.hasOwnProperty(prop)) {
return obj[prop];
}
return (...args) => obj.send(prop, args);
- }
+ },
};
this.proxy = {};
/**
* Creates a transparent interface between the chrome- and content
* contexts.
*
@@ -47,18 +47,19 @@ this.proxy = {};
* passed literally. The latter specialisation is temporary to achieve
* backwards compatibility with listener.js.
*
* @param {function(): (nsIMessageSender|nsIMessageBroadcaster)} mmFn
* Closure function returning the current message manager.
* @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);
};
/**
* Provides a transparent interface between chrome- and content space.
*
* The AsyncMessageChannel is an abstraction of the message manager
* IPC architecture allowing calls to be made to any registered message
@@ -95,18 +96,18 @@ proxy.AsyncMessageChannel = class {
*
* let channel = new AsyncMessageChannel(
* messageManager, sendAsyncMessage.bind(this));
* let rv = yield channel.send("remoteFunction", ["argument"]);
*
* @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.
*
* @return {Promise}
* A promise that resolves to the result of the command.
* @throws {TypeError}
* If an unsupported reply type is received.
* @throws {WebDriverError}
@@ -137,17 +138,17 @@ proxy.AsyncMessageChannel = class {
throw new TypeError(
`Unknown async response type: ${msg.json.type}`);
}
};
// 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":
case "unload":
this.removeHandlers();
resolve();
break;
}
@@ -178,43 +179,42 @@ proxy.AsyncMessageChannel = class {
}
/**
* Add all necessary handlers for events and observer notifications.
*/
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);
}
}
}
/**
* Remove all registered handlers for events and observer notifications.
*/
removeHandlers() {
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);
}
}
}
/**
* Reply to an asynchronous request.
*
* Passing an WebDriverError prototype will cause the receiving channel
@@ -258,17 +258,17 @@ proxy.AsyncMessageChannel = class {
let payload;
if (data && typeof data.toJSON == "function") {
payload = data.toJSON();
} else {
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
this.sendAsync(path, msg);
}
/**
* Produces a path, or a name, for the message listener handler that
@@ -302,17 +302,17 @@ proxy.AsyncMessageChannel = class {
let l = this.listeners_.get(path);
this.mm.removeMessageListener(path, l[1]);
return this.listeners_.delete(path);
}
removeAllListeners_() {
let ok = true;
- for (let [p, cb] of this.listeners_) {
+ for (let [p] of this.listeners_) {
ok |= this.removeListener_(p);
}
return ok;
}
};
proxy.AsyncMessageChannel.ReplyType = {
Ok: 0,
Value: 1,
@@ -322,43 +322,43 @@ proxy.AsyncMessageChannel.ReplyType = {
/**
* A transparent content-to-chrome RPC interface where responses are
* presented as promises.
*
* @param {nsIFrameMessageManager} frameMessageManager
* 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);
};
/**
* Sends asynchronous RPC messages to chrome space using a frame's
* sendAsyncMessage (nsIAsyncMessageSender) function.
*
* Example on how to use from a frame content script:
*
* let sender = new AsyncChromeSender(messageManager);
* let promise = sender.send("runEmulatorCmd", "my command");
* let rv = yield promise;
*/
-this.AsyncChromeSender = class {
+class AsyncChromeSender {
constructor(frameMessageManager) {
this.mm = frameMessageManager;
}
/**
* Call registered function in chrome context.
*
* @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}
* A promise that resolves to the value sent back.
*/
send(name, args) {
let uuid = uuidgen.generateUUID().toString();
@@ -384,34 +384,34 @@ this.AsyncChromeSender = class {
let msg = {arguments: marshal(args), id: uuid};
this.mm.addMessageListener(
"Marionette:listenerResponse", responseListener);
this.mm.sendAsyncMessage("Marionette:" + name, msg);
});
return proxy;
}
-};
+}
/**
* Creates a transparent interface from the content- to the chrome context.
*
* Calls to this object will be proxied via the frame's sendSyncMessage
* (nsISyncMessageSender) function. Since the message is synchronous,
* the return value is presented as a return value.
*
* Example on how to use from a frame content script:
*
* let chrome = proxy.toChrome(sendSyncMessage.bind(this));
* let cookie = chrome.getCookie("foo");
*
* @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);
};
/**
* The SyncChromeSender sends synchronous RPC messages to the chrome
* context, using a frame's sendSyncMessage (nsISyncMessageSender)
* function.
@@ -427,14 +427,14 @@ proxy.SyncChromeSender = class {
}
send(func, args) {
let name = "Marionette:" + func.toString();
return this.sendSyncMessage_(name, marshal(args));
}
};
-var marshal = function (args) {
+var marshal = function(args) {
if (args.length == 1 && typeof args[0] == "object") {
return args[0];
}
return args;
};
--- a/testing/marionette/reftest.js
+++ b/testing/marionette/reftest.js
@@ -8,29 +8,29 @@ const {classes: Cc, interfaces: Ci, util
Cu.import("resource://gre/modules/Log.jsm");
Cu.import("resource://gre/modules/Preferences.jsm");
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"];
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const PREF_E10S = "browser.tabs.remote.autostart";
const logger = Log.repository.getLogger("Marionette");
const SCREENSHOT_MODE = {
unexpected: 0,
fail: 1,
- always: 2
+ always: 2,
};
const STATUS = {
PASS: "PASS",
FAIL: "FAIL",
ERROR: "ERROR",
TIMEOUT: "TIMEOUT",
};
@@ -41,17 +41,17 @@ const STATUS = {
*/
let reftest = {};
reftest.Runner = class {
constructor(driver) {
this.driver = driver;
this.canvasCache = new Map([[null, []]]);
this.windowUtils = null;
- this.lastUrl = null;
+ this.lastURL = null;
this.remote = Preferences.get(PREF_E10S);
}
/**
* Setup the required environment for running reftests.
*
* This will open a non-browser window in which the tests will
* be loaded, and set up various caches for the reftest run.
@@ -68,17 +68,17 @@ reftest.Runner = class {
this.screenshotMode = SCREENSHOT_MODE[screenshotMode] ||
SCREENSHOT_MODE["unexpected"];
this.urlCount = Object.keys(urlCount || {})
.reduce((map, key) => map.set(key, urlCount[key]), new Map());
yield this.ensureWindow();
- };
+ }
*ensureWindow() {
if (this.reftestWin && !this.reftestWin.closed) {
return this.reftestWin;
}
let reftestWin = yield this.openWindow();
@@ -184,137 +184,151 @@ min-width: 600px; min-height: 600px; max
} catch (e) {
result = {status: STATUS.ERROR, message: e.stack, extra: {}};
}
return result;
}.bind(this));
let result = yield Promise.race([testRunner, timeoutPromise]);
this.parentWindow.clearTimeout(timeoutHandle);
- if(result.status === STATUS.TIMEOUT) {
+ if (result.status === STATUS.TIMEOUT) {
this.abort();
}
return result;
}
*runTest(testUrl, references, expected, timeout) {
+ let win = yield this.ensureWindow();
- let win = yield this.ensureWindow();
+ function toBase64(screenshot) {
+ let dataURL = screenshot.canvas.toDataURL();
+ return dataURL.split(",")[1];
+ }
win.innerWidth = 600;
win.innerHeight = 600;
let message = "";
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]]);
}
let status = STATUS.FAIL;
while (stack.length) {
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) {
recordScreenshot();
}
if (comparison.passed) {
if (references.length) {
for (let i = references.length - 1; i >= 0; i--) {
let item = references[i];
stack.push([testUrl, item[0], item[1], item[2]]);
}
} 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();
}
}
// Return any reusable canvases to the pool
let canvasPool = this.canvasCache.get(null);
[comparison.lhs, comparison.rhs].map(screenshot => {
if (screenshot.reuseCanvas) {
canvasPool.push(screenshot.canvas);
}
});
logger.debug(`Canvas pool is of length ${canvasPool.length}`);
}
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}`);
// Take the reference screenshot first so that if we pause
// we see the test rendering
let rhs = yield this.screenshot(win, rhsUrl, timeout);
let lhs = yield this.screenshot(win, lhsUrl, timeout);
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};
}
*screenshot(win, url, timeout) {
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`);
canvas = this.canvasCache.get(url);
if (!cache) {
this.canvasCache.delete(url);
}
} else {
@@ -327,32 +341,40 @@ min-width: 600px; min-height: 600px; max
reuseCanvas = !cache;
let ctxInterface = win.CanvasRenderingContext2D;
let flags = ctxInterface.DRAWWINDOW_DRAW_CARET |
ctxInterface.DRAWWINDOW_USE_WIDGET_LAYERS |
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};
}
};
--- a/testing/marionette/server.js
+++ b/testing/marionette/server.js
@@ -15,23 +15,24 @@ const ServerSocket = CC(
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");
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");
const logger = Log.repository.getLogger("Marionette");
const {KeepWhenOffline, LoopbackOnly} = Ci.nsIServerSocket;
@@ -156,18 +157,24 @@ const RECOMMENDED_PREFS = new Map([
// Disable the UI tour.
//
// Should be set in profile.
["browser.uitour.enabled", false],
// 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],
["datareporting.healthreport.uploadEnabled", false],
["datareporting.policy.dataSubmissionEnabled", false],
["datareporting.policy.dataSubmissionPolicyAccepted", false],
["datareporting.policy.dataSubmissionPolicyBypassNotification", true],
@@ -199,17 +206,20 @@ const RECOMMENDED_PREFS = new Map([
["extensions.installDistroAddons", false],
["extensions.showMismatchUI", false],
// Turn off extension updates so they do not bother tests
["extensions.update.enabled", false],
["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],
// Disable useragent updates
["general.useragent.updates.enabled", false],
// Always use network provider for geolocation tests so we bypass the
@@ -272,17 +282,17 @@ const RECOMMENDED_PREFS = new Map([
* debugger transport interface on the provided |port|. For every new
* connection, a |server.TCPConnection| is created.
*/
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();
this.nextConnID = 0;
this.alive = false;
this._acceptConnections = false;
this.alteredPrefs = new Set();
}
@@ -290,39 +300,39 @@ server.TCPListener = class {
/**
* Function produces a GeckoDriver.
*
* Determines application name to initialise the driver with.
*
* @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 {
logger.info("New connections are accepted again");
}
this._acceptConnections = value;
}
/**
* Bind this listener to |port| and start accepting incoming socket
* connections on |onSocketAccepted|.
*
* The marionette.port preference will be populated with the value
* of |this.port|.
*/
- start () {
+ start() {
if (this.alive) {
return;
}
Services.obs.notifyObservers(this, NOTIFY_RUNNING, true);
if (Preferences.get(PREF_RECOMMENDED)) {
// set recommended prefs if they are not already user-defined
@@ -342,58 +352,59 @@ server.TCPListener = class {
this.port = this.socket.port;
Preferences.set(PREF_PORT, this.port);
this.alive = true;
this._acceptConnections = true;
env.set(ENV_ENABLED, "1");
}
- stop () {
+ stop() {
if (!this.alive) {
return;
}
this._acceptConnections = false;
this.socket.close();
this.socket = null;
for (let k of this.alteredPrefs) {
logger.debug(`Resetting recommended pref ${k}`);
Preferences.reset(k);
}
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;
}
let input = clientSocket.openInputStream(0, 0, 0);
let output = clientSocket.openOutputStream(0, 0, 0);
let transport = new DebuggerTransport(input, output);
let conn = new server.TCPConnection(
this.nextConnID++, transport, this.driverFactory.bind(this));
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);
}
};
/**
* Marionette client connection.
*
@@ -403,17 +414,17 @@ server.TCPListener = class {
* @param {number} connID
* Unique identifier of the connection this dispatcher should handle.
* @param {DebuggerTransport} transport
* Debugger transport connection to the client.
* @param {function(): GeckoDriver} driverFactory
* Factory function that produces a |GeckoDriver|.
*/
server.TCPConnection = class {
- constructor (connID, transport, driverFactory) {
+ constructor(connID, transport, driverFactory) {
this.id = connID;
this.conn = transport;
// transport hooks are TCPConnection#onPacket
// and TCPConnection#onClosed
this.conn.hooks = this;
// callback for when connection is closed
@@ -427,17 +438,17 @@ server.TCPConnection = class {
// lookup of commands sent by server to client by message ID
this.commands_ = new Map();
}
/**
* Debugger transport callback that cleans up
* after a connection is closed.
*/
- onClosed (reason) {
+ onClosed(reason) {
this.driver.deleteSession();
if (this.onclose) {
this.onclose(this);
}
}
/**
* Callback that receives data packets from the client.
@@ -446,17 +457,17 @@ server.TCPConnection = class {
* issued to the client and run its callback, if any. In case of
* a Command, the corresponding is executed.
*
* @param {Array.<number, number, ?, ?>} data
* A four element array where the elements, in sequence, signifies
* 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(
"Unable to unmarshal packet data: " + JSON.stringify(data));
error.report(e);
return;
}
@@ -500,17 +511,17 @@ server.TCPConnection = class {
*
* Errors thrown in commands are marshaled and sent back, and if they
* are not WebDriverError instances, they are additionally propagated
* and reported to {@code Components.utils.reportError}.
*
* @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);
let req = Task.spawn(function* () {
let fn = this.driver.commands[cmd.name];
if (typeof fn == "undefined") {
throw new UnknownCommandError(cmd.name);
@@ -538,57 +549,57 @@ server.TCPConnection = class {
* Fail-safe creation of a new instance of |message.Response|.
*
* @param {?} msgID
* Message ID to respond to. If it is not a number, -1 is used.
*
* @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);
}
/**
* When a client connects we send across a JSON Object defining the
* protocol level.
*
* 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}.
* The message is sent over the debugger transport socket.
*
* The command ID is a unique identifier assigned to the client's request
* that is used to distinguish the asynchronous responses.
*
* Whilst responses to commands are synchronous and must be sent in the
* correct order.
*
* @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);
this.sendToEmulator(msg);
} else if (msg instanceof Response) {
this.sendToClient(msg);
}
}
@@ -596,46 +607,46 @@ server.TCPConnection = class {
// Low-level methods:
/**
* Send given response to the client over the debugger transport socket.
*
* @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.
*
* @param {Command,Response} msg
* The message to send.
*/
- sendMessage (msg) {
+ sendMessage(msg) {
this.log_(msg);
let payload = msg.toMsg();
this.sendRaw(payload);
}
/**
* Send the given payload over the debugger transport socket to the
* connected client.
*
* @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}]`;
}
};
--- a/testing/marionette/session.js
+++ b/testing/marionette/session.js
@@ -27,36 +27,36 @@ const appinfo = {name: "<missing>", vers
try { appinfo.name = Services.appinfo.name.toLowerCase(); } catch (e) {}
try { appinfo.version = Services.appinfo.version; } catch (e) {}
/** State associated with a WebDriver session. */
this.session = {};
/** Representation of WebDriver session timeouts. */
session.Timeouts = class {
- constructor () {
+ constructor() {
// disabled
this.implicit = 0;
// five mintues
this.pageLoad = 300000;
// 30 seconds
this.script = 30000;
}
- toString () { return "[object session.Timeouts]"; }
+ toString() { return "[object session.Timeouts]"; }
- toJSON () {
+ toJSON() {
return {
implicit: this.implicit,
pageLoad: this.pageLoad,
script: this.script,
};
}
- static fromJSON (json) {
+ static fromJSON(json) {
assert.object(json);
let t = new session.Timeouts();
for (let [typ, ms] of Object.entries(json)) {
switch (typ) {
case "implicit":
t.implicit = assert.positiveInteger(ms);
break;
@@ -131,17 +131,18 @@ session.Proxy = class {
if (this.socksVersion) {
Preferences.set("network.proxy.socks_version", this.socksVersion);
}
}
return true;
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":
Preferences.set("network.proxy.type", 4);
return true;
case "system":
Preferences.set("network.proxy.type", 5);
@@ -151,35 +152,35 @@ session.Proxy = class {
Preferences.set("network.proxy.type", 0);
return true;
default:
return false;
}
}
- 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,
ftpProxyPort: this.ftpProxyPort,
socksProxy: this.socksProxy,
socksProxyPort: this.socksProxyPort,
socksProxyVersion: this.socksProxyVersion,
proxyAutoconfigUrl: this.proxyAutoconfigUrl,
});
}
- static fromJSON (json) {
+ static fromJSON(json) {
let p = new session.Proxy();
if (typeof json == "undefined" || json === null) {
return p;
}
assert.object(json);
assert.in("proxyType", json);
@@ -213,17 +214,17 @@ session.Proxy = class {
}
return p;
}
};
/** WebDriver session capabilities representation. */
session.Capabilities = class extends Map {
- constructor () {
+ constructor() {
super([
// webdriver
["browserName", appinfo.name],
["browserVersion", appinfo.version],
["platformName", Services.sysinfo.getProperty("name").toLowerCase()],
["platformVersion", Services.sysinfo.getProperty("version")],
["pageLoadStrategy", session.PageLoadStrategy.Normal],
["acceptInsecureCerts", false],
@@ -236,17 +237,17 @@ session.Capabilities = class extends Map
// proprietary
["specificationLevel", 0],
["moz:processID", Services.appinfo.processID],
["moz:profile", maybeProfile()],
["moz:accessibilityChecks", false],
]);
}
- set (key, value) {
+ set(key, value) {
if (key === "timeouts" && !(value instanceof session.Timeouts)) {
throw new TypeError();
} else if (key === "proxy" && !(value instanceof session.Proxy)) {
throw new TypeError();
}
return super.set(key, value);
}
@@ -267,60 +268,61 @@ session.Capabilities = class extends Map
* |requiredCapabilities| fields, or both, it should be set to
* true to merge these before parsing. This indicates
* that the input provided is from a client and not from
* |session.Capabilities#toJSON|.
*
* @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 = {};
}
assert.object(json);
if (merge) {
json = session.Capabilities.merge_(json);
}
return session.Capabilities.match_(json);
}
// 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 || {};
let required = json.requiredCapabilities || {};
// One level deep union merge of desired- and required capabilities
// with preference on required
return Object.assign({}, desired, required);
}
// 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;
const wildcard = v => v === "*";
// 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");
if (!stringMatch(v, bname)) {
throw new TypeError(
pprint`Given browserName ${v}, but my name is ${bname}`);
}
break;
@@ -363,17 +365,18 @@ session.Capabilities = class extends Map
if (v === null) {
matched.set("pageLoadStrategy", session.PageLoadStrategy.Normal);
} else {
assert.string(v);
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);
}
}
break;
case "proxy":
let proxy = session.Proxy.fromJSON(v);
matched.set("proxy", proxy);
@@ -404,40 +407,39 @@ session.Capabilities = class extends Map
// literals, dropping empty objects and entries which values are undefined
// or null. Objects are allowed to produce their own JSON representations
// by implementing a |toJSON| function.
function marshal(obj) {
let rv = Object.create(null);
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)) {
yield [k, mapOrObject[k]];
}
}
}
- 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;
}
// Recursively marshal objects that are able to produce their own
// 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);
}
// And finally drop (possibly marshaled) objects which have no
// entries.
if (!isObjectEmpty(v)) {
rv[k] = v;
}
--- a/testing/marionette/stream-utils.js
+++ b/testing/marionette/stream-utils.js
@@ -1,121 +1,126 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"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");
const IOUtil = Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
const ScriptableInputStream =
CC("@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream", "init");
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 {nsIAsyncInputStream} input
+ * Stream to copy from.
+ * @param {nsIAsyncOutputStream} output
+ * Stream to copy to.
+ * @param {number} length
+ * Amount of data that needs to be copied.
*
- * @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.
+ * @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);
return copier.copy();
}
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;
} else {
this.output = Cc["@mozilla.org/network/buffered-output-stream;1"]
.createInstance(Ci.nsIBufferedOutputStream);
this.output.init(output, BUFFER_SIZE);
}
this._length = length;
this._amountLeft = length;
this._deferred = {
promise: new Promise((resolve, reject) => {
this._deferred.resolve = resolve;
this._deferred.reject = reject;
- })
+ }),
};
this._copy = this._copy.bind(this);
this._flush = this._flush.bind(this);
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(() => {
try {
this._copy();
} catch (e) {
this._deferred.reject(e);
}
}, 0);
return this;
},
- _copy: function () {
+ _copy() {
let bytesAvailable = this.input.available();
let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
this._debug("Trying to copy: " + amountToCopy);
let bytesCopied;
try {
bytesCopied = this.output.writeFrom(this.input, amountToCopy);
} catch (e) {
@@ -138,80 +143,83 @@ StreamCopier.prototype = {
this._flush();
return;
}
this._debug("Waiting for input stream");
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) {
if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK ||
e.result == Cr.NS_ERROR_FAILURE) {
this._debug("Flush would block, will retry");
this._streamReadyCallback = this._flush;
this._debug("Waiting for output stream");
this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
return;
}
throw e;
}
this._deferred.resolve();
},
- _destroy: function () {
+ _destroy() {
this._destroy = null;
this._copy = null;
this._flush = null;
this.input = null;
this.output = null;
},
// 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;
if (stream instanceof Ci.nsIScriptableInputStream) {
scriptableStream = stream;
} else {
scriptableStream = new ScriptableInputStream(stream);
}
@@ -230,13 +238,12 @@ function delimitedRead(stream, delimiter
char = scriptableStream.readBytes(1);
count--;
data += char;
}
return data;
}
-const StreamUtils = {
+this.StreamUtils = {
copyStream,
- delimitedRead
+ delimitedRead,
};
-
--- a/testing/marionette/test_error.js
+++ b/testing/marionette/test_error.js
@@ -123,17 +123,21 @@ add_test(function test_toJSON() {
equal(e0s.message, "");
equal(e0s.stacktrace, e0.stack);
let e1 = new WebDriverError("a");
let e1s = e1.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);
ok(e2s.stacktrace.match(/second/));
ok(e2s.stacktrace.match(/third/));
ok(e2s.stacktrace.match(/fourth/));
run_next_test();
@@ -306,19 +310,19 @@ add_test(function test_JavaScriptError()
let err = new JavaScriptError("foo");
equal("JavaScriptError", err.name);
equal("foo", err.message);
equal("javascript error", err.status);
ok(err instanceof WebDriverError);
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
run_next_test();
});
add_test(function test_NoAlertOpenError() {
let err = new NoAlertOpenError("foo");
--- a/testing/marionette/transport.js
+++ b/testing/marionette/transport.js
@@ -1,117 +1,125 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
/* 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");
const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
"nsIScriptableInputStream", "init");
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:
*
* - onPacket(packet) - called when we have received a complete packet.
* |packet| is the parsed form of the packet --- a JavaScript value, not
* a JSON-syntax string.
*
* - onBulkPacket(packet) - called when we have switched to bulk packet
* receiving mode. |packet| is an object containing:
* * 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);
this._input = input;
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;
// A queue of outgoing Packet objects
this._outgoing = [];
this.hooks = null;
this.active = false;
@@ -123,86 +131,95 @@ function DebuggerTransport(input, output
}
DebuggerTransport.prototype = {
/**
* Transmit an object as a JSON packet.
*
* 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);
packet.object = object;
this._outgoing.push(packet);
this._flushOutgoing();
},
/**
* 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 {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:
*
- * @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.
+ * - 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);
packet.header = header;
this._outgoing.push(packet);
this._flushOutgoing();
return packet.streamReadyForWriting;
},
/**
* 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;
this._input.close();
this._scriptableInput.close();
this._output.close();
this._destroyIncoming();
this._destroyAllOutgoing();
@@ -220,59 +237,60 @@ DebuggerTransport.prototype = {
/**
* The currently outgoing packet (at the top of the queue).
*/
get _currentOutgoing() {
return this._outgoing[0];
},
/**
- * 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;
}
// If the top of the packet queue has nothing more to send, remove it.
if (this._currentOutgoing.done) {
this._finishCurrentOutgoing();
}
if (this._outgoing.length > 0) {
let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
this._output.asyncWait(this, 0, 0, threadManager.currentThread);
}
},
/**
- * 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;
}
try {
this._currentOutgoing.write(stream);
} catch (e) {
if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
@@ -283,103 +301,104 @@ DebuggerTransport.prototype = {
}
this._flushOutgoing();
},
/**
* Remove the current outgoing packet from the queue upon completion.
*/
- _finishCurrentOutgoing: function () {
+ _finishCurrentOutgoing() {
if (this._currentOutgoing) {
this._currentOutgoing.destroy();
this._outgoing.shift();
}
},
/**
* Clear the entire outgoing queue.
*/
- _destroyAllOutgoing: function () {
+ _destroyAllOutgoing() {
for (let packet of this._outgoing) {
packet.destroy();
}
this._outgoing = [];
},
/**
- * 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();
},
/**
* 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);
}
},
/**
- * 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();
},
// nsIInputStreamCallback
/**
* 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())) {
// Loop until there is nothing more to process
}
this._waitForIncoming();
} catch (e) {
if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
this.close(e.result);
} else {
throw e;
}
}
},
/**
- * 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) {
dumpv("Nothing to read, skipping");
return false;
}
try {
@@ -401,18 +420,17 @@ DebuggerTransport.prototype = {
}
if (!this._incoming.done) {
// We have an incomplete packet, keep reading it.
dumpv("Existing packet incomplete, keep reading");
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();
return false;
}
if (!this._incoming.done) {
// Still not complete, we'll wait for more data.
@@ -421,23 +439,24 @@ DebuggerTransport.prototype = {
}
// Ready for next packet
this._flushIncoming();
return true;
},
/**
- * 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);
if (flags.wantVerbose) {
dumpv("Header read: " + this._incomingHeader);
}
if (this._incomingHeader.endsWith(":")) {
@@ -453,99 +472,101 @@ DebuggerTransport.prototype = {
// Not enough data yet.
return false;
},
/**
* If the incoming packet is done, log it as needed and clear the buffer.
*/
- _flushIncoming: function () {
+ _flushIncoming() {
if (!this._incoming.done) {
return;
}
if (flags.wantLogging) {
dumpv("Got: " + this._incoming);
}
this._destroyIncoming();
},
/**
- * 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) {
this.emit("packet", object);
this.hooks.onPacket(object);
}
});
},
/**
- * 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) {
this.emit("bulkpacket", ...args);
this.hooks.onBulkPacket(...args);
}
});
},
/**
- * 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);
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);
}
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++;
if (flags.wantLogging) {
// Check 'from' first, as 'echo' packets have both.
if (packet.from) {
dumpv("Packet " + serial + " sent from " + uneval(packet.from));
} else if (packet.to) {
@@ -553,36 +574,37 @@ LocalDebuggerTransport.prototype = {
}
}
this._deepFreeze(packet);
let other = this.other;
if (other) {
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);
other.hooks.onPacket(packet);
}
});
}
},
/**
- * 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++;
dumpv("Sent bulk packet " + serial + " for actor " + actor);
if (!this.other) {
let error = new Error("startBulkSend: other side of transport missing");
return Promise.reject(error);
@@ -594,66 +616,66 @@ LocalDebuggerTransport.prototype = {
dumpv("Received bulk packet " + serial);
if (!this.other.hooks) {
return;
}
// 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);
deferred.resolve(copying);
return copying;
},
stream: pipe.inputStream,
- done: deferred
+ done: deferred,
};
this.other.emit("bulkpacket", packet);
this.other.hooks.onBulkPacket(packet);
// Await the result of reading from the stream
deferred.promise.then(() => pipe.inputStream.close(), this.close);
});
// 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();
sendDeferred.resolve({
copyFrom: (input) => {
let copying =
StreamUtils.copyStream(input, pipe.outputStream, length);
copyDeferred.resolve(copying);
return copying;
},
stream: pipe.outputStream,
- done: copyDeferred
+ done: copyDeferred,
});
// Await the result of writing to the stream
copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
});
return sendDeferred.promise;
},
/**
* Close the transport.
*/
- close: function () {
+ close() {
this.emit("close");
if (this.other) {
// Remove the reference to the other endpoint before calling close(), to
// avoid infinite recursion.
let other = this.other;
this.other = null;
other.close();
@@ -666,34 +688,34 @@ LocalDebuggerTransport.prototype = {
}
this.hooks = null;
}
},
/**
* 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]);
}
}
- }
+ },
};
/**
* A transport for the debugging protocol that uses nsIMessageManagers to
* exchange packets with servers running in child processes.
*
* In the parent process, |mm| should be the nsIMessageSender for the
* child process. In a child process, |mm| should be the child process
@@ -728,52 +750,54 @@ ChildDebuggerTransport.prototype = {
_removeListener() {
try {
this._mm.removeMessageListener(this._messageName, this);
} catch (e) {
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);
} catch (e) {
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.");
},
swapBrowser(mm) {
this._removeListener();
this._mm = mm;
this._addListener();
},
@@ -786,111 +810,111 @@ ChildDebuggerTransport.prototype = {
//
// Each worker debugger supports only a single connection to the main thread.
// However, its theoretically possible for multiple servers to connect to the
// same worker. Consequently, each transport has a connection id, to allow
// messages from multiple connections to be multiplexed on a single channel.
if (!this.isWorker) {
// Main thread
- (function () {
+ (function() {
/**
* A transport that uses a WorkerDebugger to send packets from the main
* thread to a worker thread.
*/
function WorkerDebuggerTransport(dbg, id) {
this._dbg = dbg;
this._id = id;
this.onMessage = this._onMessage.bind(this);
}
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;
}
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;
this._id = id;
this._onMessage = this._onMessage.bind(this);
}
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;
}
if (this.hooks) {
this.hooks.onPacket(packet.message);
}
- }
+ },
};
}).call(this);
}
--- a/testing/marionette/wait.js
+++ b/testing/marionette/wait.js
@@ -54,30 +54,31 @@ this.wait = {};
*
* @return {Promise: ?}
* Yields the value passed to |func|'s |resolve| or |reject|
* callbacks.
*
* @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) => {
const start = new Date().getTime();
const end = start + timeout;
let evalFn = () => {
new Promise(func).then(resolve, rejected => {
if (error.isError(rejected)) {
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);
}
}).catch(reject);
};
// the repeating slack timer waits |interval|
// before invoking |evalFn|