mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 18:04:46 +00:00
Bug 1376128 - Lint testing/marionette; r=automatedtester
MozReview-Commit-ID: DY4yCSBEZrN --HG-- extra : rebase_source : d4e25369418cc72a6ee9f78d44b050a87403391d
This commit is contained in:
parent
32d341c354
commit
de001d80b7
@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
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/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Log.jsm");
|
Cu.import("resource://gre/modules/Log.jsm");
|
||||||
|
|
||||||
@ -35,15 +35,16 @@ this.EXPORTED_SYMBOLS = ["accessibility"];
|
|||||||
this.accessibility = {
|
this.accessibility = {
|
||||||
get service() {
|
get service() {
|
||||||
return service;
|
return service;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Accessible states used to check element"s state from the accessiblity API
|
* Accessible states used to check element"s state from the accessiblity API
|
||||||
* perspective.
|
* 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
|
* Note: if gecko is built with --disable-accessibility, the interfaces
|
||||||
* statically.
|
* are not defined. This is why we use getters instead to be able to use
|
||||||
|
* these statically.
|
||||||
*/
|
*/
|
||||||
accessibility.State = {
|
accessibility.State = {
|
||||||
get Unavailable() {
|
get Unavailable() {
|
||||||
@ -57,7 +58,7 @@ accessibility.State = {
|
|||||||
},
|
},
|
||||||
get Selected() {
|
get Selected() {
|
||||||
return Ci.nsIAccessibleStates.STATE_SELECTED;
|
return Ci.nsIAccessibleStates.STATE_SELECTED;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +93,7 @@ accessibility.ActionableRoles = new Set([
|
|||||||
* Factory function that constructs a new {@code accessibility.Checks}
|
* Factory function that constructs a new {@code accessibility.Checks}
|
||||||
* object with enforced strictness or not.
|
* object with enforced strictness or not.
|
||||||
*/
|
*/
|
||||||
accessibility.get = function (strict = false) {
|
accessibility.get = function(strict = false) {
|
||||||
return new accessibility.Checks(!!strict);
|
return new accessibility.Checks(!!strict);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -138,7 +139,8 @@ accessibility.Checks = class {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// First, check if accessibility is ready.
|
// First, check if accessibility is ready.
|
||||||
let docAcc = accessibility.service.getAccessibleFor(element.ownerDocument);
|
let docAcc = accessibility.service
|
||||||
|
.getAccessibleFor(element.ownerDocument);
|
||||||
let state = {};
|
let state = {};
|
||||||
docAcc.getState(state, {});
|
docAcc.getState(state, {});
|
||||||
if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) {
|
if ((state.value & Ci.nsIAccessibleStates.STATE_BUSY) == 0) {
|
||||||
@ -158,12 +160,14 @@ accessibility.Checks = class {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
|
|
||||||
// If event type does not match expected type, skip the event.
|
// If event type does not match expected type, skip the event.
|
||||||
|
let event = subject.QueryInterface(Ci.nsIAccessibleEvent);
|
||||||
if (event.eventType !== Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
|
if (event.eventType !== Ci.nsIAccessibleEvent.EVENT_STATE_CHANGE) {
|
||||||
return;
|
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) {
|
if (event.accessible !== docAcc) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -175,12 +179,12 @@ accessibility.Checks = class {
|
|||||||
} else {
|
} else {
|
||||||
resolve(acc);
|
resolve(acc);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
Services.obs.addObserver(eventObserver, "accessible-event");
|
Services.obs.addObserver(eventObserver, "accessible-event");
|
||||||
}).catch(() => this.error(
|
}).catch(() => this.error(
|
||||||
"Element does not have an accessible object", element));
|
"Element does not have an accessible object", element));
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test if the accessible has a role that supports some arbitrary
|
* Test if the accessible has a role that supports some arbitrary
|
||||||
@ -409,7 +413,8 @@ accessibility.Checks = class {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectedAccessibility = this.matchState(accessible, accessibility.State.Selected);
|
let selectedAccessibility =
|
||||||
|
this.matchState(accessible, accessibility.State.Selected);
|
||||||
|
|
||||||
let message;
|
let message;
|
||||||
if (selected && !selectedAccessibility) {
|
if (selected && !selectedAccessibility) {
|
||||||
|
@ -23,6 +23,8 @@ Cu.import("chrome://marionette/content/interaction.js");
|
|||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["action"];
|
this.EXPORTED_SYMBOLS = ["action"];
|
||||||
|
|
||||||
|
const {pprint} = error;
|
||||||
|
|
||||||
// TODO? With ES 2016 and Symbol you can make a safer approximation
|
// TODO? With ES 2016 and Symbol you can make a safer approximation
|
||||||
// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
|
// to an enum e.g. https://gist.github.com/xmlking/e86e4f15ec32b12c4689
|
||||||
/**
|
/**
|
||||||
@ -360,7 +362,7 @@ action.PointerOrigin.get = function(obj) {
|
|||||||
origin = this.Viewport;
|
origin = this.Viewport;
|
||||||
} else if (typeof obj == "string") {
|
} else if (typeof obj == "string") {
|
||||||
let name = capitalize(obj);
|
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];
|
origin = this[name];
|
||||||
} else if (!element.isWebElementReference(obj)) {
|
} else if (!element.isWebElementReference(obj)) {
|
||||||
throw new InvalidArgumentError("Expected 'origin' to be a string or a " +
|
throw new InvalidArgumentError("Expected 'origin' to be a string or a " +
|
||||||
@ -389,24 +391,25 @@ action.PointerType = {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |str| is not a valid pointer type.
|
* If |str| is not a valid pointer type.
|
||||||
*/
|
*/
|
||||||
action.PointerType.get = function (str) {
|
action.PointerType.get = function(str) {
|
||||||
let name = capitalize(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];
|
return this[name];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Input state associated with current session. This is a map between input ID and
|
* Input state associated with current session. This is a map between
|
||||||
* the device state for that input source, with one entry for each active input source.
|
* 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;
|
action.inputStateMap = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List of |action.Action| associated with current session. Used to manage dispatching
|
* List of |action.Action| associated with current session. Used to
|
||||||
* events when resetting the state of the input sources. Reset operations are assumed
|
* manage dispatching events when resetting the state of the input sources.
|
||||||
* to be idempotent.
|
* Reset operations are assumed to be idempotent.
|
||||||
*
|
*
|
||||||
* Initialized in listener.js
|
* Initialized in listener.js
|
||||||
*/
|
*/
|
||||||
@ -441,8 +444,8 @@ class InputState {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {?} obj
|
* @param {?} obj
|
||||||
* Object with property |type| and optionally |parameters| or |pointerType|,
|
* Object with property |type| and optionally |parameters| or
|
||||||
* representing an action sequence or an action item.
|
* |pointerType|, representing an action sequence or an action item.
|
||||||
*
|
*
|
||||||
* @return {action.InputState}
|
* @return {action.InputState}
|
||||||
* An |action.InputState| object for the type of the |actionSequence|.
|
* An |action.InputState| object for the type of the |actionSequence|.
|
||||||
@ -452,18 +455,18 @@ class InputState {
|
|||||||
*/
|
*/
|
||||||
static fromJson(obj) {
|
static fromJson(obj) {
|
||||||
let type = obj.type;
|
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);
|
let name = type == "none" ? "Null" : capitalize(type);
|
||||||
if (name == "Pointer") {
|
if (name == "Pointer") {
|
||||||
if (!obj.pointerType && (!obj.parameters || !obj.parameters.pointerType)) {
|
if (!obj.pointerType &&
|
||||||
|
(!obj.parameters || !obj.parameters.pointerType)) {
|
||||||
throw new InvalidArgumentError(
|
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;
|
let pointerType = obj.pointerType || obj.parameters.pointerType;
|
||||||
return new action.InputState[name](pointerType);
|
return new action.InputState[name](pointerType);
|
||||||
} else {
|
|
||||||
return new action.InputState[name]();
|
|
||||||
}
|
}
|
||||||
|
return new action.InputState[name]();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,7 +569,8 @@ action.InputState.Pointer = class Pointer extends InputState {
|
|||||||
constructor(subtype) {
|
constructor(subtype) {
|
||||||
super();
|
super();
|
||||||
this.pressed = new Set();
|
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.subtype = action.PointerType.get(subtype);
|
||||||
this.x = 0;
|
this.x = 0;
|
||||||
this.y = 0;
|
this.y = 0;
|
||||||
@ -616,14 +620,16 @@ action.InputState.Pointer = class Pointer extends InputState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repesents an action for dispatch. Used in |action.Chain| and |action.Sequence|.
|
* Repesents an action for dispatch. Used in |action.Chain| and
|
||||||
|
* |action.Sequence|.
|
||||||
*
|
*
|
||||||
* @param {string} id
|
* @param {string} id
|
||||||
* Input source ID.
|
* Input source ID.
|
||||||
* @param {string} type
|
* @param {string} type
|
||||||
* Action type: none, key, pointer.
|
* Action type: none, key, pointer.
|
||||||
* @param {string} subtype
|
* @param {string} subtype
|
||||||
* Action subtype: pause, keyUp, keyDown, pointerUp, pointerDown, pointerMove, pointerCancel.
|
* Action subtype: pause, keyUp, keyDown, pointerUp, pointerDown,
|
||||||
|
* pointerMove, pointerCancel.
|
||||||
*
|
*
|
||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If any parameters are undefined.
|
* If any parameters are undefined.
|
||||||
@ -634,12 +640,12 @@ action.Action = class {
|
|||||||
throw new InvalidArgumentError("Missing id, type or subtype");
|
throw new InvalidArgumentError("Missing id, type or subtype");
|
||||||
}
|
}
|
||||||
for (let attr of [id, type, 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.id = id;
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.subtype = subtype;
|
this.subtype = subtype;
|
||||||
};
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `[action ${this.type}]`;
|
return `[action ${this.type}]`;
|
||||||
@ -668,7 +674,8 @@ action.Action = class {
|
|||||||
}
|
}
|
||||||
let subtype = actionItem.type;
|
let subtype = actionItem.type;
|
||||||
if (!subtypes.has(subtype)) {
|
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);
|
let item = new action.Action(id, type, subtype);
|
||||||
@ -682,10 +689,10 @@ action.Action = class {
|
|||||||
case action.KeyDown:
|
case action.KeyDown:
|
||||||
let key = actionItem.value;
|
let key = actionItem.value;
|
||||||
// TODO countGraphemes
|
// TODO countGraphemes
|
||||||
// TODO key.value could be a single code point like "\uE012" (see rawKey)
|
// TODO key.value could be a single code point like "\uE012"
|
||||||
// or "grapheme cluster"
|
// (see rawKey) or "grapheme cluster"
|
||||||
assert.string(key,
|
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}`));
|
`or grapheme cluster, got: ${key}`));
|
||||||
item.value = key;
|
item.value = key;
|
||||||
break;
|
break;
|
||||||
@ -693,36 +700,38 @@ action.Action = class {
|
|||||||
case action.PointerDown:
|
case action.PointerDown:
|
||||||
case action.PointerUp:
|
case action.PointerUp:
|
||||||
assert.positiveInteger(actionItem.button,
|
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;
|
item.button = actionItem.button;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case action.PointerMove:
|
case action.PointerMove:
|
||||||
item.duration = actionItem.duration;
|
item.duration = actionItem.duration;
|
||||||
if (typeof item.duration != "undefined"){
|
if (typeof item.duration != "undefined") {
|
||||||
assert.positiveInteger(item.duration,
|
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.origin = action.PointerOrigin.get(actionItem.origin);
|
||||||
item.x = actionItem.x;
|
item.x = actionItem.x;
|
||||||
if (typeof item.x != "undefined") {
|
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;
|
item.y = actionItem.y;
|
||||||
if (typeof item.y != "undefined") {
|
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;
|
break;
|
||||||
|
|
||||||
case action.PointerCancel:
|
case action.PointerCancel:
|
||||||
throw new UnsupportedOperationError();
|
throw new UnsupportedOperationError();
|
||||||
break;
|
|
||||||
|
|
||||||
case action.Pause:
|
case action.Pause:
|
||||||
item.duration = actionItem.duration;
|
item.duration = actionItem.duration;
|
||||||
if (typeof item.duration != "undefined") {
|
if (typeof item.duration != "undefined") {
|
||||||
|
// eslint-disable-next-line
|
||||||
assert.positiveInteger(item.duration,
|
assert.positiveInteger(item.duration,
|
||||||
error.pprint`Expected 'duration' (${item.duration}) to be >= 0`);
|
pprint`Expected 'duration' (${item.duration}) to be >= 0`);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -732,7 +741,8 @@ action.Action = class {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a series of ticks, specifying which actions to perform at each tick.
|
* Represents a series of ticks, specifying which actions to perform at
|
||||||
|
* each tick.
|
||||||
*/
|
*/
|
||||||
action.Chain = class extends Array {
|
action.Chain = class extends Array {
|
||||||
toString() {
|
toString() {
|
||||||
@ -744,17 +754,18 @@ action.Chain = class extends Array {
|
|||||||
* Array of objects that each represent an action sequence.
|
* Array of objects that each represent an action sequence.
|
||||||
*
|
*
|
||||||
* @return {action.Chain}
|
* @return {action.Chain}
|
||||||
* Transpose of |actions| such that actions to be performed in a single tick
|
* Transpose of |actions| such that actions to be performed in a
|
||||||
* are grouped together.
|
* single tick are grouped together.
|
||||||
*
|
*
|
||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |actions| is not an Array.
|
* If |actions| is not an Array.
|
||||||
*/
|
*/
|
||||||
static fromJson(actions) {
|
static fromJson(actions) {
|
||||||
assert.array(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();
|
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) {
|
for (let actionSequence of actions) {
|
||||||
let inputSourceActions = action.Sequence.fromJson(actionSequence);
|
let inputSourceActions = action.Sequence.fromJson(actionSequence);
|
||||||
for (let i = 0; i < inputSourceActions.length; i++) {
|
for (let i = 0; i < inputSourceActions.length; i++) {
|
||||||
@ -770,7 +781,8 @@ action.Chain = class extends Array {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents one input source action sequence; this is essentially an |Array.<action.Action>|.
|
* Represents one input source action sequence; this is essentially an
|
||||||
|
* |Array.<action.Action>|.
|
||||||
*/
|
*/
|
||||||
action.Sequence = class extends Array {
|
action.Sequence = class extends Array {
|
||||||
toString() {
|
toString() {
|
||||||
@ -794,10 +806,10 @@ action.Sequence = class extends Array {
|
|||||||
let inputSourceState = InputState.fromJson(actionSequence);
|
let inputSourceState = InputState.fromJson(actionSequence);
|
||||||
let id = actionSequence.id;
|
let id = actionSequence.id;
|
||||||
assert.defined(id, "Expected 'id' to be defined");
|
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;
|
let actionItems = actionSequence.actions;
|
||||||
assert.array(actionItems,
|
assert.array(actionItems,
|
||||||
error.pprint("Expected 'actionSequence.actions' to be an Array, " +
|
pprint("Expected 'actionSequence.actions' to be an Array, " +
|
||||||
`got: ${actionSequence.actions}`));
|
`got: ${actionSequence.actions}`));
|
||||||
if (!action.inputStateMap.has(id)) {
|
if (!action.inputStateMap.has(id)) {
|
||||||
action.inputStateMap.set(id, inputSourceState);
|
action.inputStateMap.set(id, inputSourceState);
|
||||||
@ -818,7 +830,8 @@ action.Sequence = class extends Array {
|
|||||||
* Represents parameters in an action for a pointer input source.
|
* Represents parameters in an action for a pointer input source.
|
||||||
*
|
*
|
||||||
* @param {string=} pointerType
|
* @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 {
|
action.PointerParameters = class {
|
||||||
constructor(pointerType = "mouse") {
|
constructor(pointerType = "mouse") {
|
||||||
@ -839,9 +852,8 @@ action.PointerParameters = class {
|
|||||||
static fromJson(parametersData) {
|
static fromJson(parametersData) {
|
||||||
if (typeof parametersData == "undefined") {
|
if (typeof parametersData == "undefined") {
|
||||||
return new action.PointerParameters();
|
return new action.PointerParameters();
|
||||||
} else {
|
|
||||||
return new action.PointerParameters(parametersData.pointerType);
|
|
||||||
}
|
}
|
||||||
|
return new action.PointerParameters(parametersData.pointerType);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -860,14 +872,16 @@ action.PointerParameters = class {
|
|||||||
* If |id| is already mapped to an |action.InputState| that is
|
* If |id| is already mapped to an |action.InputState| that is
|
||||||
* not compatible with |act.type| or |pointerParams.pointerType|.
|
* not compatible with |act.type| or |pointerParams.pointerType|.
|
||||||
*/
|
*/
|
||||||
action.processPointerAction = function processPointerAction(id, pointerParams, act) {
|
action.processPointerAction = function(id, pointerParams, act) {
|
||||||
if (action.inputStateMap.has(id) && action.inputStateMap.get(id).type !== act.type) {
|
if (action.inputStateMap.has(id) &&
|
||||||
|
action.inputStateMap.get(id).type !== act.type) {
|
||||||
throw new InvalidArgumentError(
|
throw new InvalidArgumentError(
|
||||||
`Expected 'id' ${id} to be mapped to InputState whose type is ` +
|
`Expected 'id' ${id} to be mapped to InputState whose type is ` +
|
||||||
`${action.inputStateMap.get(id).type}, got: ${act.type}`);
|
`${action.inputStateMap.get(id).type}, got: ${act.type}`);
|
||||||
}
|
}
|
||||||
let pointerType = pointerParams.pointerType;
|
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(
|
throw new InvalidArgumentError(
|
||||||
`Expected 'id' ${id} to be mapped to InputState whose subtype is ` +
|
`Expected 'id' ${id} to be mapped to InputState whose subtype is ` +
|
||||||
`${action.inputStateMap.get(id).subtype}, got: ${pointerType}`);
|
`${action.inputStateMap.get(id).subtype}, got: ${pointerType}`);
|
||||||
@ -930,9 +944,10 @@ action.Mouse = class {
|
|||||||
/**
|
/**
|
||||||
* Dispatch a chain of actions over |chain.length| ticks.
|
* Dispatch a chain of actions over |chain.length| ticks.
|
||||||
*
|
*
|
||||||
* This is done by creating a Promise for each tick that resolves once all the
|
* This is done by creating a Promise for each tick that resolves once
|
||||||
* Promises for individual tick-actions are resolved. The next tick's actions are
|
* all the Promises for individual tick-actions are resolved. The next
|
||||||
* not dispatched until the Promise for the current tick is resolved.
|
* tick's actions are not dispatched until the Promise for the current
|
||||||
|
* tick is resolved.
|
||||||
*
|
*
|
||||||
* @param {action.Chain} chain
|
* @param {action.Chain} chain
|
||||||
* Actions grouped by tick; each element in |chain| is a sequence of
|
* Actions grouped by tick; each element in |chain| is a sequence of
|
||||||
@ -949,7 +964,10 @@ action.dispatch = function(chain, seenEls, container) {
|
|||||||
let chainEvents = Task.spawn(function*() {
|
let chainEvents = Task.spawn(function*() {
|
||||||
for (let tickActions of chain) {
|
for (let tickActions of chain) {
|
||||||
yield action.dispatchTickActions(
|
yield action.dispatchTickActions(
|
||||||
tickActions, action.computeTickDuration(tickActions), seenEls, container);
|
tickActions,
|
||||||
|
action.computeTickDuration(tickActions),
|
||||||
|
seenEls,
|
||||||
|
container);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return chainEvents;
|
return chainEvents;
|
||||||
@ -958,12 +976,13 @@ action.dispatch = function(chain, seenEls, container) {
|
|||||||
/**
|
/**
|
||||||
* Dispatch sequence of actions for one tick.
|
* Dispatch sequence of actions for one tick.
|
||||||
*
|
*
|
||||||
* This creates a Promise for one tick that resolves once the Promise for each
|
* This creates a Promise for one tick that resolves once the Promise
|
||||||
* tick-action is resolved, which takes at least |tickDuration| milliseconds.
|
* for each tick-action is resolved, which takes at least |tickDuration|
|
||||||
* The resolved set of events for each tick is followed by firing of pending DOM events.
|
* 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
|
* Note that the tick-actions are dispatched in order, but they may have
|
||||||
* durations and therefore may not end in the same order.
|
* different durations and therefore may not end in the same order.
|
||||||
*
|
*
|
||||||
* @param {Array.<action.Action>} tickActions
|
* @param {Array.<action.Action>} tickActions
|
||||||
* List of actions for one tick.
|
* List of actions for one tick.
|
||||||
@ -977,8 +996,10 @@ action.dispatch = function(chain, seenEls, container) {
|
|||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
* Promise for dispatching all tick-actions and pending DOM events.
|
* Promise for dispatching all tick-actions and pending DOM events.
|
||||||
*/
|
*/
|
||||||
action.dispatchTickActions = function(tickActions, tickDuration, seenEls, container) {
|
action.dispatchTickActions = function(
|
||||||
let pendingEvents = tickActions.map(toEvents(tickDuration, seenEls, container));
|
tickActions, tickDuration, seenEls, container) {
|
||||||
|
let pendingEvents = tickActions.map(
|
||||||
|
toEvents(tickDuration, seenEls, container));
|
||||||
return Promise.all(pendingEvents).then(
|
return Promise.all(pendingEvents).then(
|
||||||
() => interaction.flushEventLoop(container.frame));
|
() => interaction.flushEventLoop(container.frame));
|
||||||
};
|
};
|
||||||
@ -1018,7 +1039,8 @@ action.computeTickDuration = function(tickActions) {
|
|||||||
* @return {Map.<string, number>}
|
* @return {Map.<string, number>}
|
||||||
* x and y coordinates of pointer destination.
|
* 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;
|
let {x, y} = a;
|
||||||
switch (a.origin) {
|
switch (a.origin) {
|
||||||
case action.PointerOrigin.Viewport:
|
case action.PointerOrigin.Viewport:
|
||||||
@ -1053,7 +1075,7 @@ action.computePointerDestination = function(a, inputState, center = undefined) {
|
|||||||
* the event that corresponds to that action.
|
* the event that corresponds to that action.
|
||||||
*/
|
*/
|
||||||
function toEvents(tickDuration, seenEls, container) {
|
function toEvents(tickDuration, seenEls, container) {
|
||||||
return function (a) {
|
return a => {
|
||||||
let inputState = action.inputStateMap.get(a.id);
|
let inputState = action.inputStateMap.get(a.id);
|
||||||
switch (a.subtype) {
|
switch (a.subtype) {
|
||||||
case action.KeyUp:
|
case action.KeyUp:
|
||||||
@ -1069,7 +1091,8 @@ function toEvents(tickDuration, seenEls, container) {
|
|||||||
return dispatchPointerUp(a, inputState, container.frame);
|
return dispatchPointerUp(a, inputState, container.frame);
|
||||||
|
|
||||||
case action.PointerMove:
|
case action.PointerMove:
|
||||||
return dispatchPointerMove(a, inputState, tickDuration, seenEls, container);
|
return dispatchPointerMove(
|
||||||
|
a, inputState, tickDuration, seenEls, container);
|
||||||
|
|
||||||
case action.PointerCancel:
|
case action.PointerCancel:
|
||||||
throw new UnsupportedOperationError();
|
throw new UnsupportedOperationError();
|
||||||
@ -1077,6 +1100,7 @@ function toEvents(tickDuration, seenEls, container) {
|
|||||||
case action.Pause:
|
case action.Pause:
|
||||||
return dispatchPause(a, tickDuration);
|
return dispatchPause(a, tickDuration);
|
||||||
}
|
}
|
||||||
|
return undefined;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1091,7 +1115,8 @@ function toEvents(tickDuration, seenEls, container) {
|
|||||||
* Current window.
|
* Current window.
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @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) {
|
function dispatchKeyDown(a, inputState, win) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
@ -1161,22 +1186,31 @@ function dispatchPointerDown(a, inputState, win) {
|
|||||||
resolve();
|
resolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
inputState.press(a.button);
|
inputState.press(a.button);
|
||||||
// Append a copy of |a| with pointerUp subtype
|
// 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) {
|
switch (inputState.subtype) {
|
||||||
case action.PointerType.Mouse:
|
case action.PointerType.Mouse:
|
||||||
let mouseEvent = new action.Mouse("mousedown", a.button);
|
let mouseEvent = new action.Mouse("mousedown", a.button);
|
||||||
mouseEvent.update(inputState);
|
mouseEvent.update(inputState);
|
||||||
event.synthesizeMouseAtPoint(inputState.x, inputState.y, mouseEvent, win);
|
event.synthesizeMouseAtPoint(
|
||||||
|
inputState.x,
|
||||||
|
inputState.y,
|
||||||
|
mouseEvent,
|
||||||
|
win);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case action.PointerType.Pen:
|
case action.PointerType.Pen:
|
||||||
case action.PointerType.Touch:
|
case action.PointerType.Touch:
|
||||||
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
|
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
|
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1220,12 +1254,13 @@ function dispatchPointerUp(a, inputState, win) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a pointerMove action equivalent to moving pointer device in a line.
|
* Dispatch a pointerMove action equivalent to moving pointer device in
|
||||||
|
* a line.
|
||||||
*
|
*
|
||||||
* If the action duration is 0, the pointer jumps immediately to the target coordinates.
|
* If the action duration is 0, the pointer jumps immediately to the
|
||||||
* Otherwise, events are synthesized to mimic a pointer travelling in a discontinuous,
|
* target coordinates. Otherwise, events are synthesized to mimic a
|
||||||
* approximately straight line, with the pointer coordinates being updated around 60
|
* pointer travelling in a discontinuous, approximately straight line,
|
||||||
* times per second.
|
* with the pointer coordinates being updated around 60 times per second.
|
||||||
*
|
*
|
||||||
* @param {action.Action} a
|
* @param {action.Action} a
|
||||||
* Action to dispatch.
|
* Action to dispatch.
|
||||||
@ -1237,10 +1272,11 @@ function dispatchPointerUp(a, inputState, win) {
|
|||||||
* Object with |frame| attribute of type |nsIDOMWindow|.
|
* Object with |frame| attribute of type |nsIDOMWindow|.
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
* Promise to dispatch at least one pointermove event, as well as mousemove events
|
* Promise to dispatch at least one pointermove event, as well as
|
||||||
* as appropriate.
|
* 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);
|
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||||
// interval between pointermove increments in ms, based on common vsync
|
// interval between pointermove increments in ms, based on common vsync
|
||||||
const fps60 = 17;
|
const fps60 = 17;
|
||||||
@ -1253,7 +1289,8 @@ function dispatchPointerMove(a, inputState, tickDuration, seenEls, container) {
|
|||||||
if (!inViewPort(targetX, targetY, container.frame)) {
|
if (!inViewPort(targetX, targetY, container.frame)) {
|
||||||
throw new MoveTargetOutOfBoundsError(
|
throw new MoveTargetOutOfBoundsError(
|
||||||
`(${targetX}, ${targetY}) is out of bounds of viewport ` +
|
`(${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;
|
const duration = typeof a.duration == "undefined" ? tickDuration : a.duration;
|
||||||
@ -1298,26 +1335,30 @@ function performOnePointerMove(inputState, targetX, targetY, win) {
|
|||||||
if (targetX == inputState.x && targetY == inputState.y) {
|
if (targetX == inputState.x && targetY == inputState.y) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (inputState.subtype) {
|
switch (inputState.subtype) {
|
||||||
case action.PointerType.Mouse:
|
case action.PointerType.Mouse:
|
||||||
let mouseEvent = new action.Mouse("mousemove");
|
let mouseEvent = new action.Mouse("mousemove");
|
||||||
mouseEvent.update(inputState);
|
mouseEvent.update(inputState);
|
||||||
//TODO both pointermove (if available) and mousemove
|
// TODO both pointermove (if available) and mousemove
|
||||||
event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win);
|
event.synthesizeMouseAtPoint(targetX, targetY, mouseEvent, win);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case action.PointerType.Pen:
|
case action.PointerType.Pen:
|
||||||
case action.PointerType.Touch:
|
case action.PointerType.Touch:
|
||||||
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
|
throw new UnsupportedOperationError("Only 'mouse' pointer type is supported");
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
|
throw new TypeError(`Unknown pointer type: ${inputState.subtype}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
inputState.x = targetX;
|
inputState.x = targetX;
|
||||||
inputState.y = targetY;
|
inputState.y = targetY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dispatch a pause action equivalent waiting for |a.duration| milliseconds, or a
|
* Dispatch a pause action equivalent waiting for |a.duration|
|
||||||
* default time interval of |tickDuration|.
|
* milliseconds, or a default time interval of |tickDuration|.
|
||||||
*
|
*
|
||||||
* @param {action.Action} a
|
* @param {action.Action} a
|
||||||
* Action to dispatch.
|
* Action to dispatch.
|
||||||
@ -1351,8 +1392,10 @@ function inViewPort(x, y, win) {
|
|||||||
|
|
||||||
function getElementCenter(elementReference, seenEls, container) {
|
function getElementCenter(elementReference, seenEls, container) {
|
||||||
if (element.isWebElementReference(elementReference)) {
|
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);
|
let el = seenEls.get(uuid, container);
|
||||||
return element.coordinates(el);
|
return element.coordinates(el);
|
||||||
}
|
}
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -49,27 +49,27 @@ function lookupError(code) {
|
|||||||
* @throws {UnknownError}
|
* @throws {UnknownError}
|
||||||
* If there is a problem installing the addon.
|
* 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) => {
|
return new Promise((resolve, reject) => {
|
||||||
let file = new FileUtils.File(path);
|
let file = new FileUtils.File(path);
|
||||||
|
|
||||||
let listener = {
|
let listener = {
|
||||||
onInstallEnded: function (install, addon) {
|
onInstallEnded(install, addon) {
|
||||||
resolve(addon.id);
|
resolve(addon.id);
|
||||||
},
|
},
|
||||||
|
|
||||||
onInstallFailed: function (install) {
|
onInstallFailed(install) {
|
||||||
reject(lookupError(install.error));
|
reject(lookupError(install.error));
|
||||||
},
|
},
|
||||||
|
|
||||||
onInstalled: function (addon) {
|
onInstalled(addon) {
|
||||||
AddonManager.removeAddonListener(listener);
|
AddonManager.removeAddonListener(listener);
|
||||||
resolve(addon.id);
|
resolve(addon.id);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!temporary) {
|
if (!temporary) {
|
||||||
AddonManager.getInstallForFile(file, function (aInstall) {
|
AddonManager.getInstallForFile(file, function(aInstall) {
|
||||||
if (aInstall.error !== 0) {
|
if (aInstall.error !== 0) {
|
||||||
reject(lookupError(aInstall.error));
|
reject(lookupError(aInstall.error));
|
||||||
}
|
}
|
||||||
@ -94,9 +94,9 @@ addon.install = function (path, temporary = false) {
|
|||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
addon.uninstall = function (id) {
|
addon.uninstall = function(id) {
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
AddonManager.getAddonByID(id, function (addon) {
|
AddonManager.getAddonByID(id, function(addon) {
|
||||||
addon.uninstall();
|
addon.uninstall();
|
||||||
resolve();
|
resolve();
|
||||||
});
|
});
|
||||||
|
@ -41,7 +41,7 @@ this.assert = {};
|
|||||||
* @throws {InvalidSessionIDError}
|
* @throws {InvalidSessionIDError}
|
||||||
* If |driver| does not have a session ID.
|
* If |driver| does not have a session ID.
|
||||||
*/
|
*/
|
||||||
assert.session = function (driver, msg = "") {
|
assert.session = function(driver, msg = "") {
|
||||||
assert.that(sessionID => sessionID,
|
assert.that(sessionID => sessionID,
|
||||||
msg, InvalidSessionIDError)(driver.sessionId);
|
msg, InvalidSessionIDError)(driver.sessionId);
|
||||||
return driver.sessionId;
|
return driver.sessionId;
|
||||||
@ -56,7 +56,7 @@ assert.session = function (driver, msg = "") {
|
|||||||
* @throws {UnsupportedOperationError}
|
* @throws {UnsupportedOperationError}
|
||||||
* If current browser is not Firefox.
|
* If current browser is not Firefox.
|
||||||
*/
|
*/
|
||||||
assert.firefox = function (msg = "") {
|
assert.firefox = function(msg = "") {
|
||||||
msg = msg || "Only supported in Firefox";
|
msg = msg || "Only supported in Firefox";
|
||||||
assert.that(isFirefox, msg, UnsupportedOperationError)();
|
assert.that(isFirefox, msg, UnsupportedOperationError)();
|
||||||
};
|
};
|
||||||
@ -70,7 +70,7 @@ assert.firefox = function (msg = "") {
|
|||||||
* @throws {UnsupportedOperationError}
|
* @throws {UnsupportedOperationError}
|
||||||
* If current browser is not Fennec.
|
* If current browser is not Fennec.
|
||||||
*/
|
*/
|
||||||
assert.fennec = function (msg = "") {
|
assert.fennec = function(msg = "") {
|
||||||
msg = msg || "Only supported in Fennec";
|
msg = msg || "Only supported in Fennec";
|
||||||
assert.that(isFennec, msg, UnsupportedOperationError)();
|
assert.that(isFennec, msg, UnsupportedOperationError)();
|
||||||
};
|
};
|
||||||
@ -89,7 +89,7 @@ assert.fennec = function (msg = "") {
|
|||||||
* @throws {UnsupportedOperationError}
|
* @throws {UnsupportedOperationError}
|
||||||
* If |context| is not content.
|
* If |context| is not content.
|
||||||
*/
|
*/
|
||||||
assert.content = function (context, msg = "") {
|
assert.content = function(context, msg = "") {
|
||||||
msg = msg || "Only supported in content context";
|
msg = msg || "Only supported in content context";
|
||||||
assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context);
|
assert.that(c => c.toString() == "content", msg, UnsupportedOperationError)(context);
|
||||||
};
|
};
|
||||||
@ -108,7 +108,7 @@ assert.content = function (context, msg = "") {
|
|||||||
* @throws {NoSuchWindowError}
|
* @throws {NoSuchWindowError}
|
||||||
* If |win| has been closed.
|
* If |win| has been closed.
|
||||||
*/
|
*/
|
||||||
assert.window = function (win, msg = "") {
|
assert.window = function(win, msg = "") {
|
||||||
msg = msg || "Unable to locate window";
|
msg = msg || "Unable to locate window";
|
||||||
return assert.that(w => w && !w.closed,
|
return assert.that(w => w && !w.closed,
|
||||||
msg,
|
msg,
|
||||||
@ -126,7 +126,7 @@ assert.window = function (win, msg = "") {
|
|||||||
* @throws {NoSuchWindowError}
|
* @throws {NoSuchWindowError}
|
||||||
* If |context| is invalid.
|
* 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
|
// TODO: The contentBrowser uses a cached tab, which is only updated when
|
||||||
// switchToTab is called. Because of that an additional check is needed to
|
// switchToTab is called. Because of that an additional check is needed to
|
||||||
// make sure that the chrome window has not already been closed.
|
// make sure that the chrome window has not already been closed.
|
||||||
@ -149,7 +149,7 @@ assert.contentBrowser = function (context, msg = "") {
|
|||||||
* @throws {UnexpectedAlertOpenError}
|
* @throws {UnexpectedAlertOpenError}
|
||||||
* If there is a user prompt.
|
* If there is a user prompt.
|
||||||
*/
|
*/
|
||||||
assert.noUserPrompt = function (dialog, msg = "") {
|
assert.noUserPrompt = function(dialog, msg = "") {
|
||||||
assert.that(d => d === null || typeof d == "undefined",
|
assert.that(d => d === null || typeof d == "undefined",
|
||||||
msg,
|
msg,
|
||||||
UnexpectedAlertOpenError)(dialog);
|
UnexpectedAlertOpenError)(dialog);
|
||||||
@ -169,7 +169,7 @@ assert.noUserPrompt = function (dialog, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not defined.
|
* If |obj| is not defined.
|
||||||
*/
|
*/
|
||||||
assert.defined = function (obj, msg = "") {
|
assert.defined = function(obj, msg = "") {
|
||||||
msg = msg || error.pprint`Expected ${obj} to be defined`;
|
msg = msg || error.pprint`Expected ${obj} to be defined`;
|
||||||
return assert.that(o => typeof o != "undefined", msg)(obj);
|
return assert.that(o => typeof o != "undefined", msg)(obj);
|
||||||
};
|
};
|
||||||
@ -188,7 +188,7 @@ assert.defined = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not a number.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be finite number`;
|
||||||
return assert.that(Number.isFinite, msg)(obj);
|
return assert.that(Number.isFinite, msg)(obj);
|
||||||
};
|
};
|
||||||
@ -207,7 +207,7 @@ assert.number = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not callable.
|
* If |obj| is not callable.
|
||||||
*/
|
*/
|
||||||
assert.callable = function (obj, msg = "") {
|
assert.callable = function(obj, msg = "") {
|
||||||
msg = msg || error.pprint`${obj} is not callable`;
|
msg = msg || error.pprint`${obj} is not callable`;
|
||||||
return assert.that(o => typeof o == "function", msg)(obj);
|
return assert.that(o => typeof o == "function", msg)(obj);
|
||||||
};
|
};
|
||||||
@ -226,7 +226,7 @@ assert.callable = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not an integer.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be an integer`;
|
||||||
return assert.that(Number.isInteger, msg)(obj);
|
return assert.that(Number.isInteger, msg)(obj);
|
||||||
};
|
};
|
||||||
@ -245,7 +245,7 @@ assert.integer = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not a positive integer.
|
* If |obj| is not a positive integer.
|
||||||
*/
|
*/
|
||||||
assert.positiveInteger = function (obj, msg = "") {
|
assert.positiveInteger = function(obj, msg = "") {
|
||||||
assert.integer(obj, msg);
|
assert.integer(obj, msg);
|
||||||
msg = msg || error.pprint`Expected ${obj} to be >= 0`;
|
msg = msg || error.pprint`Expected ${obj} to be >= 0`;
|
||||||
return assert.that(n => n >= 0, msg)(obj);
|
return assert.that(n => n >= 0, msg)(obj);
|
||||||
@ -265,7 +265,7 @@ assert.positiveInteger = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not a boolean.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be boolean`;
|
||||||
return assert.that(b => typeof b == "boolean", msg)(obj);
|
return assert.that(b => typeof b == "boolean", msg)(obj);
|
||||||
};
|
};
|
||||||
@ -284,7 +284,7 @@ assert.boolean = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not a string.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be a string`;
|
||||||
return assert.that(s => typeof s == "string", msg)(obj);
|
return assert.that(s => typeof s == "string", msg)(obj);
|
||||||
};
|
};
|
||||||
@ -303,7 +303,7 @@ assert.string = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not an object.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be an object`;
|
||||||
return assert.that(o => {
|
return assert.that(o => {
|
||||||
// unable to use instanceof because LHS and RHS may come from
|
// unable to use instanceof because LHS and RHS may come from
|
||||||
@ -329,7 +329,7 @@ assert.object = function (obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |prop| is not in |obj|, or |obj| is not an object.
|
* 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);
|
assert.object(obj, msg);
|
||||||
msg = msg || error.pprint`Expected ${prop} in ${obj}`;
|
msg = msg || error.pprint`Expected ${prop} in ${obj}`;
|
||||||
assert.that(p => obj.hasOwnProperty(p), msg)(prop);
|
assert.that(p => obj.hasOwnProperty(p), msg)(prop);
|
||||||
@ -350,7 +350,7 @@ assert.in = function (prop, obj, msg = "") {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If |obj| is not an Array.
|
* 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`;
|
msg = msg || error.pprint`Expected ${obj} to be an Array`;
|
||||||
return assert.that(Array.isArray, msg)(obj);
|
return assert.that(Array.isArray, msg)(obj);
|
||||||
};
|
};
|
||||||
@ -372,7 +372,7 @@ assert.array = function (obj, msg = "") {
|
|||||||
* which may throw |error| with |message| if |predicate| evaluates
|
* which may throw |error| with |message| if |predicate| evaluates
|
||||||
* to false.
|
* to false.
|
||||||
*/
|
*/
|
||||||
assert.that = function (
|
assert.that = function(
|
||||||
predicate, message = "", error = InvalidArgumentError) {
|
predicate, message = "", error = InvalidArgumentError) {
|
||||||
return obj => {
|
return obj => {
|
||||||
if (!predicate(obj)) {
|
if (!predicate(obj)) {
|
||||||
|
@ -29,18 +29,17 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
|
|||||||
* @return {<xul:browser>}
|
* @return {<xul:browser>}
|
||||||
* The linked browser for the tab or null if no browser can be found.
|
* 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) {
|
if ("browser" in tab) {
|
||||||
// Fennec
|
|
||||||
return tab.browser;
|
return tab.browser;
|
||||||
|
|
||||||
|
// Firefox
|
||||||
} else if ("linkedBrowser" in tab) {
|
} else if ("linkedBrowser" in tab) {
|
||||||
// Firefox
|
|
||||||
return tab.linkedBrowser;
|
return tab.linkedBrowser;
|
||||||
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -52,18 +51,17 @@ browser.getBrowserForTab = function (tab) {
|
|||||||
* @return {<xul:tabbrowser>}
|
* @return {<xul:tabbrowser>}
|
||||||
* Tab browser or null if it's not a browser window.
|
* Tab browser or null if it's not a browser window.
|
||||||
*/
|
*/
|
||||||
browser.getTabBrowser = function (win) {
|
browser.getTabBrowser = function(win) {
|
||||||
|
// Fennec
|
||||||
if ("BrowserApp" in win) {
|
if ("BrowserApp" in win) {
|
||||||
// Fennec
|
|
||||||
return win.BrowserApp;
|
return win.BrowserApp;
|
||||||
|
|
||||||
|
// Firefox
|
||||||
} else if ("gBrowser" in win) {
|
} else if ("gBrowser" in win) {
|
||||||
// Firefox
|
|
||||||
return win.gBrowser;
|
return win.gBrowser;
|
||||||
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,13 +98,13 @@ browser.Context = class {
|
|||||||
|
|
||||||
this.seenEls = new element.Store();
|
this.seenEls = new element.Store();
|
||||||
|
|
||||||
// A reference to the tab corresponding to the current window handle, if any.
|
// A reference to the tab corresponding to the current window handle,
|
||||||
// Specifically, this.tab refers to the last tab that Marionette switched
|
// if any. Specifically, this.tab refers to the last tab that Marionette
|
||||||
// to in this browser window. Note that this may not equal the currently
|
// switched to in this browser window. Note that this may not equal the
|
||||||
// selected tab. For example, if Marionette switches to tab A, and then
|
// currently selected tab. For example, if Marionette switches to tab
|
||||||
// clicks on a button that opens a new tab B in the same browser window,
|
// A, and then clicks on a button that opens a new tab B in the same
|
||||||
// this.tab will still point to tab A, despite tab B being the currently
|
// browser window, this.tab will still point to tab A, despite tab B
|
||||||
// selected tab.
|
// being the currently selected tab.
|
||||||
this.tab = null;
|
this.tab = null;
|
||||||
this.pendingCommands = [];
|
this.pendingCommands = [];
|
||||||
|
|
||||||
@ -130,7 +128,8 @@ browser.Context = class {
|
|||||||
get contentBrowser() {
|
get contentBrowser() {
|
||||||
if (this.tab) {
|
if (this.tab) {
|
||||||
return browser.getBrowserForTab(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 this.tabBrowser;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,7 +143,7 @@ browser.Context = class {
|
|||||||
*/
|
*/
|
||||||
get curFrameId() {
|
get curFrameId() {
|
||||||
let rv = null;
|
let rv = null;
|
||||||
if (this.tab || this.driver.isReftestBrowser(this.contentBrowser) ) {
|
if (this.tab || this.driver.isReftestBrowser(this.contentBrowser)) {
|
||||||
rv = this.getIdForBrowser(this.contentBrowser);
|
rv = this.getIdForBrowser(this.contentBrowser);
|
||||||
}
|
}
|
||||||
return rv;
|
return rv;
|
||||||
@ -164,10 +163,9 @@ browser.Context = class {
|
|||||||
// initialization been finished
|
// initialization been finished
|
||||||
if (this.contentBrowser) {
|
if (this.contentBrowser) {
|
||||||
return this.contentBrowser.currentURI;
|
return this.contentBrowser.currentURI;
|
||||||
} else {
|
|
||||||
throw new NoSuchWindowError(
|
|
||||||
"Current window does not have a content browser");
|
|
||||||
}
|
}
|
||||||
|
throw new NoSuchWindowError(
|
||||||
|
"Current window does not have a content browser");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,7 +232,10 @@ browser.Context = class {
|
|||||||
closeTab() {
|
closeTab() {
|
||||||
// If the current window is not a browser then close it directly. Do the
|
// 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.
|
// 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 this.closeWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ capture.Format = {
|
|||||||
* @return {HTMLCanvasElement}
|
* @return {HTMLCanvasElement}
|
||||||
* The canvas element where the element has been painted on.
|
* 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 win = node.ownerGlobal;
|
||||||
let rect = node.getBoundingClientRect();
|
let rect = node.getBoundingClientRect();
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ capture.element = function (node, highlights = []) {
|
|||||||
* @return {HTMLCanvasElement}
|
* @return {HTMLCanvasElement}
|
||||||
* The canvas element where the viewport has been painted on.
|
* 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;
|
let rootNode = win.document.documentElement;
|
||||||
|
|
||||||
return capture.canvas(
|
return capture.canvas(
|
||||||
@ -100,9 +100,9 @@ capture.viewport = function (win, highlights = []) {
|
|||||||
* The canvas on which the selection from the window's framebuffer
|
* The canvas on which the selection from the window's framebuffer
|
||||||
* has been painted on.
|
* 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} = {}) {
|
{highlights = [], canvas = null, flags = null} = {}) {
|
||||||
let scale = win.devicePixelRatio;
|
const scale = win.devicePixelRatio;
|
||||||
|
|
||||||
if (canvas === null) {
|
if (canvas === null) {
|
||||||
canvas = win.document.createElementNS(XHTML_NS, "canvas");
|
canvas = win.document.createElementNS(XHTML_NS, "canvas");
|
||||||
@ -113,12 +113,19 @@ capture.canvas = function (win, left, top, width, height,
|
|||||||
let ctx = canvas.getContext(CONTEXT_2D);
|
let ctx = canvas.getContext(CONTEXT_2D);
|
||||||
if (flags === null) {
|
if (flags === null) {
|
||||||
flags = ctx.DRAWWINDOW_DRAW_CARET;
|
flags = ctx.DRAWWINDOW_DRAW_CARET;
|
||||||
// Disabled in bug 1243415 for webplatform-test failures due to out of view elements.
|
// TODO(ato): https://bugzil.la/1377335
|
||||||
// Needs https://github.com/w3c/web-platform-tests/issues/4383 fixed.
|
//
|
||||||
// ctx.DRAWWINDOW_DRAW_VIEW;
|
// 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]
|
// Bug 1009762 - Crash in [@ mozilla::gl::ReadPixelsIntoDataSurface]
|
||||||
// ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
|
/*
|
||||||
}
|
ctx.DRAWWINDOW_USE_WIDGET_LAYERS;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
ctx.scale(scale, scale);
|
ctx.scale(scale, scale);
|
||||||
ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
|
ctx.drawWindow(win, left, top, width, height, BG_COLOUR, flags);
|
||||||
@ -129,9 +136,9 @@ capture.canvas = function (win, left, top, width, height,
|
|||||||
return canvas;
|
return canvas;
|
||||||
};
|
};
|
||||||
|
|
||||||
capture.highlight_ = function (context, highlights, top = 0, left = 0) {
|
capture.highlight_ = function(context, highlights, top = 0, left = 0) {
|
||||||
if (!highlights) {
|
if (!highlights) {
|
||||||
return;
|
throw new TypeError("Missing highlights");
|
||||||
}
|
}
|
||||||
|
|
||||||
context.lineWidth = "2";
|
context.lineWidth = "2";
|
||||||
@ -162,7 +169,7 @@ capture.highlight_ = function (context, highlights, top = 0, left = 0) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
* A Base64 encoded string.
|
* A Base64 encoded string.
|
||||||
*/
|
*/
|
||||||
capture.toBase64 = function (canvas) {
|
capture.toBase64 = function(canvas) {
|
||||||
let u = canvas.toDataURL(PNG_MIME);
|
let u = canvas.toDataURL(PNG_MIME);
|
||||||
return u.substring(u.indexOf(",") + 1);
|
return u.substring(u.indexOf(",") + 1);
|
||||||
};
|
};
|
||||||
@ -176,7 +183,7 @@ capture.toBase64 = function (canvas) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
* A hex digest of the SHA-256 hash of the base64 encoded 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 u = capture.toBase64(canvas);
|
||||||
let buffer = new TextEncoder("utf-8").encode(u);
|
let buffer = new TextEncoder("utf-8").encode(u);
|
||||||
return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
|
return crypto.subtle.digest("SHA-256", buffer).then(hash => hex(hash));
|
||||||
@ -197,9 +204,9 @@ function hex(buffer) {
|
|||||||
for (let i = 0; i < view.byteLength; i += 4) {
|
for (let i = 0; i < view.byteLength; i += 4) {
|
||||||
let value = view.getUint32(i);
|
let value = view.getUint32(i);
|
||||||
let stringValue = value.toString(16);
|
let stringValue = value.toString(16);
|
||||||
let padding = '00000000';
|
let padding = "00000000";
|
||||||
let paddedValue = (padding + stringValue).slice(-padding.length);
|
let paddedValue = (padding + stringValue).slice(-padding.length);
|
||||||
hexCodes.push(paddedValue);
|
hexCodes.push(paddedValue);
|
||||||
}
|
}
|
||||||
return hexCodes.join("");
|
return hexCodes.join("");
|
||||||
};
|
}
|
||||||
|
@ -53,7 +53,7 @@ this.cert = {
|
|||||||
* @throws {Components.Exception}
|
* @throws {Components.Exception}
|
||||||
* If unable to register or initialise |service|.
|
* If unable to register or initialise |service|.
|
||||||
*/
|
*/
|
||||||
cert.installOverride = function (service) {
|
cert.installOverride = function(service) {
|
||||||
if (this.currentOverride) {
|
if (this.currentOverride) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -102,7 +102,7 @@ cert.InsecureSweepingOverride = function() {
|
|||||||
// make your life miserable.
|
// make your life miserable.
|
||||||
let service = function() {};
|
let service = function() {};
|
||||||
service.prototype = {
|
service.prototype = {
|
||||||
hasMatchingOverride: function (
|
hasMatchingOverride(
|
||||||
aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
|
aHostName, aPort, aCert, aOverrideBits, aIsTemporary) {
|
||||||
aIsTemporary.value = false;
|
aIsTemporary.value = false;
|
||||||
aOverrideBits.value =
|
aOverrideBits.value =
|
||||||
@ -116,7 +116,7 @@ cert.InsecureSweepingOverride = function() {
|
|||||||
let factory = XPCOMUtils.generateSingletonFactory(service);
|
let factory = XPCOMUtils.generateSingletonFactory(service);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
register: function() {
|
register() {
|
||||||
// make it possible to register certificate overrides for domains
|
// make it possible to register certificate overrides for domains
|
||||||
// that use HSTS or HPKP
|
// that use HSTS or HPKP
|
||||||
Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
|
Preferences.set(HSTS_PRELOAD_LIST_PREF, false);
|
||||||
@ -125,7 +125,7 @@ cert.InsecureSweepingOverride = function() {
|
|||||||
registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
|
registrar.registerFactory(CID, DESC, CONTRACT_ID, factory);
|
||||||
},
|
},
|
||||||
|
|
||||||
unregister: function() {
|
unregister() {
|
||||||
registrar.unregisterFactory(CID, factory);
|
registrar.unregisterFactory(CID, factory);
|
||||||
|
|
||||||
Preferences.reset(HSTS_PRELOAD_LIST_PREF);
|
Preferences.reset(HSTS_PRELOAD_LIST_PREF);
|
||||||
|
@ -24,7 +24,7 @@ const PREF_LOG_LEVEL_FALLBACK = "marionette.logging";
|
|||||||
|
|
||||||
const DEFAULT_LOG_LEVEL = "info";
|
const DEFAULT_LOG_LEVEL = "info";
|
||||||
const LOG_LEVELS = new class extends Map {
|
const LOG_LEVELS = new class extends Map {
|
||||||
constructor () {
|
constructor() {
|
||||||
super([
|
super([
|
||||||
["fatal", Log.Level.Fatal],
|
["fatal", Log.Level.Fatal],
|
||||||
["error", Log.Level.Error],
|
["error", Log.Level.Error],
|
||||||
@ -36,7 +36,7 @@ const LOG_LEVELS = new class extends Map {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get (level) {
|
get(level) {
|
||||||
let s = new String(level).toLowerCase();
|
let s = new String(level).toLowerCase();
|
||||||
if (!this.has(s)) {
|
if (!this.has(s)) {
|
||||||
return DEFAULT_LOG_LEVEL;
|
return DEFAULT_LOG_LEVEL;
|
||||||
@ -69,7 +69,7 @@ const ServerSocket = CC("@mozilla.org/network/server-socket;1",
|
|||||||
|
|
||||||
// Get preference value of |preferred|, falling back to |fallback|
|
// Get preference value of |preferred|, falling back to |fallback|
|
||||||
// if |preferred| is not user-modified and |fallback| exists.
|
// if |preferred| is not user-modified and |fallback| exists.
|
||||||
function getPref (preferred, fallback) {
|
function getPref(preferred, fallback) {
|
||||||
if (!Preferences.isSet(preferred) && Preferences.has(fallback)) {
|
if (!Preferences.isSet(preferred) && Preferences.has(fallback)) {
|
||||||
return Preferences.get(fallback, Preferences.get(preferred));
|
return Preferences.get(fallback, Preferences.get(preferred));
|
||||||
}
|
}
|
||||||
@ -82,16 +82,16 @@ function getPref (preferred, fallback) {
|
|||||||
//
|
//
|
||||||
// This shim can be removed when Firefox 55 ships.
|
// This shim can be removed when Firefox 55 ships.
|
||||||
const prefs = {
|
const prefs = {
|
||||||
get port () {
|
get port() {
|
||||||
return getPref(PREF_PORT, PREF_PORT_FALLBACK);
|
return getPref(PREF_PORT, PREF_PORT_FALLBACK);
|
||||||
},
|
},
|
||||||
|
|
||||||
get logLevel () {
|
get logLevel() {
|
||||||
let s = getPref(PREF_LOG_LEVEL, PREF_LOG_LEVEL_FALLBACK);
|
let s = getPref(PREF_LOG_LEVEL, PREF_LOG_LEVEL_FALLBACK);
|
||||||
return LOG_LEVELS.get(s);
|
return LOG_LEVELS.get(s);
|
||||||
},
|
},
|
||||||
|
|
||||||
readFromEnvironment (key) {
|
readFromEnvironment(key) {
|
||||||
const env = Cc["@mozilla.org/process/environment;1"]
|
const env = Cc["@mozilla.org/process/environment;1"]
|
||||||
.getService(Ci.nsIEnvironment);
|
.getService(Ci.nsIEnvironment);
|
||||||
|
|
||||||
@ -146,23 +146,14 @@ MarionetteComponent.prototype = {
|
|||||||
helpInfo: " --marionette Enable remote control server.\n",
|
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
|
// Handle -marionette flag
|
||||||
MarionetteComponent.prototype.handle = function (cmdLine) {
|
MarionetteComponent.prototype.handle = function(cmdLine) {
|
||||||
if (cmdLine.handleFlag("marionette", false)) {
|
if (cmdLine.handleFlag("marionette", false)) {
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MarionetteComponent.prototype.observe = function (subject, topic, data) {
|
MarionetteComponent.prototype.observe = function(subject, topic, data) {
|
||||||
switch (topic) {
|
switch (topic) {
|
||||||
case "profile-after-change":
|
case "profile-after-change":
|
||||||
// Using sessionstore-windows-restored as the xpcom category doesn't
|
// Using sessionstore-windows-restored as the xpcom category doesn't
|
||||||
@ -227,14 +218,14 @@ MarionetteComponent.prototype.observe = function (subject, topic, data) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
MarionetteComponent.prototype.setupLogger = function (level) {
|
MarionetteComponent.prototype.setupLogger = function(level) {
|
||||||
let logger = Log.repository.getLogger("Marionette");
|
let logger = Log.repository.getLogger("Marionette");
|
||||||
logger.level = level;
|
logger.level = level;
|
||||||
logger.addAppender(new Log.DumpAppender());
|
logger.addAppender(new Log.DumpAppender());
|
||||||
return logger;
|
return logger;
|
||||||
};
|
};
|
||||||
|
|
||||||
MarionetteComponent.prototype.suppressSafeModeDialog = function (win) {
|
MarionetteComponent.prototype.suppressSafeModeDialog = function(win) {
|
||||||
win.addEventListener("load", () => {
|
win.addEventListener("load", () => {
|
||||||
if (win.document.getElementById("safeModeDialog")) {
|
if (win.document.getElementById("safeModeDialog")) {
|
||||||
// accept the dialog to start in safe-mode
|
// accept the dialog to start in safe-mode
|
||||||
@ -245,7 +236,7 @@ MarionetteComponent.prototype.suppressSafeModeDialog = function (win) {
|
|||||||
}, {once: true});
|
}, {once: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
MarionetteComponent.prototype.init = function () {
|
MarionetteComponent.prototype.init = function() {
|
||||||
if (this.running || !this.enabled || !this.finalUIStartup) {
|
if (this.running || !this.enabled || !this.finalUIStartup) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -270,7 +261,7 @@ MarionetteComponent.prototype.init = function () {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
MarionetteComponent.prototype.uninit = function () {
|
MarionetteComponent.prototype.uninit = function() {
|
||||||
if (!this.running) {
|
if (!this.running) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ this.cookie = {
|
|||||||
* @throws {InvalidArgumentError}
|
* @throws {InvalidArgumentError}
|
||||||
* If any of the properties are invalid.
|
* If any of the properties are invalid.
|
||||||
*/
|
*/
|
||||||
cookie.fromJSON = function (json) {
|
cookie.fromJSON = function(json) {
|
||||||
let newCookie = {};
|
let newCookie = {};
|
||||||
|
|
||||||
assert.object(json, error.pprint`Expected cookie object, got ${json}`);
|
assert.object(json, error.pprint`Expected cookie object, got ${json}`);
|
||||||
@ -85,7 +85,7 @@ cookie.fromJSON = function (json) {
|
|||||||
* @throws {InvalidCookieDomainError}
|
* @throws {InvalidCookieDomainError}
|
||||||
* If |restrictToHost| is set and |newCookie|'s domain does not match.
|
* 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.name, "Cookie name must be string");
|
||||||
assert.string(newCookie.value, "Cookie value must be string");
|
assert.string(newCookie.value, "Cookie value must be string");
|
||||||
assert.string(newCookie.domain, "Cookie domain must be string");
|
assert.string(newCookie.domain, "Cookie domain must be string");
|
||||||
@ -108,7 +108,8 @@ cookie.add = function (newCookie, opts = {}) {
|
|||||||
|
|
||||||
if (newCookie.domain !== opts.restrictToHost) {
|
if (newCookie.domain !== opts.restrictToHost) {
|
||||||
throw new InvalidCookieDomainError(
|
throw new InvalidCookieDomainError(
|
||||||
`Cookies may only be set for the current domain (${opts.restrictToHost})`);
|
`Cookies may only be set ` +
|
||||||
|
` for the current domain (${opts.restrictToHost})`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +136,7 @@ cookie.add = function (newCookie, opts = {}) {
|
|||||||
* @param {Map.<string, (string|number|boolean)} toDelete
|
* @param {Map.<string, (string|number|boolean)} toDelete
|
||||||
* Cookie to remove.
|
* Cookie to remove.
|
||||||
*/
|
*/
|
||||||
cookie.remove = function (toDelete) {
|
cookie.remove = function(toDelete) {
|
||||||
cookie.manager.remove(
|
cookie.manager.remove(
|
||||||
toDelete.domain,
|
toDelete.domain,
|
||||||
toDelete.name,
|
toDelete.name,
|
||||||
@ -158,7 +159,7 @@ cookie.remove = function (toDelete) {
|
|||||||
* @return {[Symbol.Iterator]}
|
* @return {[Symbol.Iterator]}
|
||||||
* Iterator.
|
* Iterator.
|
||||||
*/
|
*/
|
||||||
cookie.iter = function* (host, currentPath = "/") {
|
cookie.iter = function*(host, currentPath = "/") {
|
||||||
assert.string(host, "host must be string");
|
assert.string(host, "host must be string");
|
||||||
assert.string(currentPath, "currentPath must be string");
|
assert.string(currentPath, "currentPath must be string");
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -247,7 +247,7 @@ element.Store = class {
|
|||||||
* If a single element is requested, this error will throw if the
|
* If a single element is requested, this error will throw if the
|
||||||
* element is not found.
|
* element is not found.
|
||||||
*/
|
*/
|
||||||
element.find = function (container, strategy, selector, opts = {}) {
|
element.find = function(container, strategy, selector, opts = {}) {
|
||||||
opts.all = !!opts.all;
|
opts.all = !!opts.all;
|
||||||
opts.timeout = opts.timeout || 0;
|
opts.timeout = opts.timeout || 0;
|
||||||
|
|
||||||
@ -275,7 +275,8 @@ element.find = function (container, strategy, selector, opts = {}) {
|
|||||||
let msg;
|
let msg;
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
case element.Strategy.AnonAttribute:
|
case element.Strategy.AnonAttribute:
|
||||||
msg = "Unable to locate anonymous element: " + JSON.stringify(selector);
|
msg = "Unable to locate anonymous element: " +
|
||||||
|
JSON.stringify(selector);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -301,8 +302,8 @@ function find_(container, strategy, selector, searchFn, opts) {
|
|||||||
startNode = opts.startNode;
|
startNode = opts.startNode;
|
||||||
} else {
|
} else {
|
||||||
switch (strategy) {
|
switch (strategy) {
|
||||||
// For anonymous nodes the start node needs to be of type DOMElement, which
|
// For anonymous nodes the start node needs to be of type
|
||||||
// will refer to :root in case of a DOMDocument.
|
// DOMElement, which will refer to :root in case of a DOMDocument.
|
||||||
case element.Strategy.Anon:
|
case element.Strategy.Anon:
|
||||||
case element.Strategy.AnonAttribute:
|
case element.Strategy.AnonAttribute:
|
||||||
if (rootNode instanceof Ci.nsIDOMDocument) {
|
if (rootNode instanceof Ci.nsIDOMDocument) {
|
||||||
@ -345,7 +346,7 @@ function find_(container, strategy, selector, searchFn, opts) {
|
|||||||
* @return {DOMElement}
|
* @return {DOMElement}
|
||||||
* First element matching expression.
|
* First element matching expression.
|
||||||
*/
|
*/
|
||||||
element.findByXPath = function (root, startNode, expr) {
|
element.findByXPath = function(root, startNode, expr) {
|
||||||
let iter = root.evaluate(expr, startNode, null,
|
let iter = root.evaluate(expr, startNode, null,
|
||||||
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
Ci.nsIDOMXPathResult.FIRST_ORDERED_NODE_TYPE, null);
|
||||||
return iter.singleNodeValue;
|
return iter.singleNodeValue;
|
||||||
@ -364,7 +365,7 @@ element.findByXPath = function (root, startNode, expr) {
|
|||||||
* @return {Array.<DOMElement>}
|
* @return {Array.<DOMElement>}
|
||||||
* Sequence of found elements matching expression.
|
* Sequence of found elements matching expression.
|
||||||
*/
|
*/
|
||||||
element.findByXPathAll = function (root, startNode, expr) {
|
element.findByXPathAll = function(root, startNode, expr) {
|
||||||
let rv = [];
|
let rv = [];
|
||||||
let iter = root.evaluate(expr, startNode, null,
|
let iter = root.evaluate(expr, startNode, null,
|
||||||
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
|
||||||
@ -387,7 +388,7 @@ element.findByXPathAll = function (root, startNode, expr) {
|
|||||||
* @return {Array.<DOMAnchorElement>}
|
* @return {Array.<DOMAnchorElement>}
|
||||||
* Sequence of link elements which text is |s|.
|
* 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);
|
return filterLinks(node, link => link.text.trim() === s);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -402,7 +403,7 @@ element.findByLinkText = function (node, s) {
|
|||||||
* @return {Array.<DOMAnchorElement>}
|
* @return {Array.<DOMAnchorElement>}
|
||||||
* Sequence of link elements which text containins |s|.
|
* 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);
|
return filterLinks(node, link => link.text.indexOf(s) != -1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -547,7 +548,8 @@ function findElements(using, value, rootNode, startNode) {
|
|||||||
if (startNode.getElementsByName) {
|
if (startNode.getElementsByName) {
|
||||||
return startNode.getElementsByName(value);
|
return startNode.getElementsByName(value);
|
||||||
}
|
}
|
||||||
return element.findByXPathAll(rootNode, startNode, `.//*[@name="${value}"]`);
|
return element.findByXPathAll(
|
||||||
|
rootNode, startNode, `.//*[@name="${value}"]`);
|
||||||
|
|
||||||
case element.Strategy.ClassName:
|
case element.Strategy.ClassName:
|
||||||
return startNode.getElementsByClassName(value);
|
return startNode.getElementsByClassName(value);
|
||||||
@ -569,7 +571,8 @@ function findElements(using, value, rootNode, startNode) {
|
|||||||
|
|
||||||
case element.Strategy.AnonAttribute:
|
case element.Strategy.AnonAttribute:
|
||||||
let attr = Object.keys(value)[0];
|
let attr = Object.keys(value)[0];
|
||||||
let el = rootNode.getAnonymousElementByAttribute(startNode, attr, value[attr]);
|
let el = rootNode.getAnonymousElementByAttribute(
|
||||||
|
startNode, attr, value[attr]);
|
||||||
if (el) {
|
if (el) {
|
||||||
return [el];
|
return [el];
|
||||||
}
|
}
|
||||||
@ -581,7 +584,7 @@ function findElements(using, value, rootNode, startNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Determines if |obj| is an HTML or JS collection. */
|
/** Determines if |obj| is an HTML or JS collection. */
|
||||||
element.isCollection = function (seq) {
|
element.isCollection = function(seq) {
|
||||||
switch (Object.prototype.toString.call(seq)) {
|
switch (Object.prototype.toString.call(seq)) {
|
||||||
case "[object Arguments]":
|
case "[object Arguments]":
|
||||||
case "[object Array]":
|
case "[object Array]":
|
||||||
@ -598,7 +601,7 @@ element.isCollection = function (seq) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
element.makeWebElement = function (uuid) {
|
element.makeWebElement = function(uuid) {
|
||||||
return {
|
return {
|
||||||
[element.Key]: uuid,
|
[element.Key]: uuid,
|
||||||
[element.LegacyKey]: uuid,
|
[element.LegacyKey]: uuid,
|
||||||
@ -606,16 +609,18 @@ element.makeWebElement = function (uuid) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if |ref| has either |element.Key| or |element.LegacyKey| as properties.
|
* Checks if |ref| has either |element.Key| or |element.LegacyKey|
|
||||||
|
* as properties.
|
||||||
*
|
*
|
||||||
* @param {?} ref
|
* @param {?} ref
|
||||||
* Object that represents a web element reference.
|
* Object that represents a web element reference.
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if |ref| has either expected property.
|
* True if |ref| has either expected property.
|
||||||
*/
|
*/
|
||||||
element.isWebElementReference = function (ref) {
|
element.isWebElementReference = function(ref) {
|
||||||
let properties = Object.getOwnPropertyNames(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() {
|
element.generateUUID = function() {
|
||||||
@ -636,11 +641,11 @@ element.generateUUID = function() {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* Flag indicating that the element is disconnected.
|
* Flag indicating that the element is disconnected.
|
||||||
*/
|
*/
|
||||||
element.isDisconnected = function (el, container = {}) {
|
element.isDisconnected = function(el, container = {}) {
|
||||||
const {frame, shadowRoot} = container;
|
const {frame, shadowRoot} = container;
|
||||||
assert.defined(frame);
|
assert.defined(frame);
|
||||||
|
|
||||||
// shadow dom
|
// shadow DOM
|
||||||
if (frame.ShadowRoot && shadowRoot) {
|
if (frame.ShadowRoot && shadowRoot) {
|
||||||
if (el.compareDocumentPosition(shadowRoot) &
|
if (el.compareDocumentPosition(shadowRoot) &
|
||||||
DOCUMENT_POSITION_DISCONNECTED) {
|
DOCUMENT_POSITION_DISCONNECTED) {
|
||||||
@ -654,14 +659,13 @@ element.isDisconnected = function (el, container = {}) {
|
|||||||
}
|
}
|
||||||
return element.isDisconnected(
|
return element.isDisconnected(
|
||||||
shadowRoot.host,
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -684,7 +688,7 @@ element.isDisconnected = function (el, container = {}) {
|
|||||||
* @throws TypeError
|
* @throws TypeError
|
||||||
* If |xOffset| or |yOffset| are not numbers.
|
* If |xOffset| or |yOffset| are not numbers.
|
||||||
*/
|
*/
|
||||||
element.coordinates = function (
|
element.coordinates = function(
|
||||||
node, xOffset = undefined, yOffset = undefined) {
|
node, xOffset = undefined, yOffset = undefined) {
|
||||||
|
|
||||||
let box = node.getBoundingClientRect();
|
let box = node.getBoundingClientRect();
|
||||||
@ -721,14 +725,14 @@ element.coordinates = function (
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if if |el| is in viewport, false otherwise.
|
* 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 win = el.ownerGlobal;
|
||||||
let c = element.coordinates(el, x, y);
|
let c = element.coordinates(el, x, y);
|
||||||
let vp = {
|
let vp = {
|
||||||
top: win.pageYOffset,
|
top: win.pageYOffset,
|
||||||
left: win.pageXOffset,
|
left: win.pageXOffset,
|
||||||
bottom: (win.pageYOffset + win.innerHeight),
|
bottom: (win.pageYOffset + win.innerHeight),
|
||||||
right: (win.pageXOffset + win.innerWidth)
|
right: (win.pageXOffset + win.innerWidth),
|
||||||
};
|
};
|
||||||
|
|
||||||
return (vp.left <= c.x + win.pageXOffset &&
|
return (vp.left <= c.x + win.pageXOffset &&
|
||||||
@ -754,7 +758,7 @@ element.inViewport = function (el, x = undefined, y = undefined) {
|
|||||||
* @return {Element}
|
* @return {Element}
|
||||||
* Container element of |el|.
|
* Container element of |el|.
|
||||||
*/
|
*/
|
||||||
element.getContainer = function (el) {
|
element.getContainer = function(el) {
|
||||||
if (el.localName != "option") {
|
if (el.localName != "option") {
|
||||||
return el;
|
return el;
|
||||||
}
|
}
|
||||||
@ -796,7 +800,7 @@ element.getContainer = function (el) {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if |el| is inside the viewport, or false otherwise.
|
* True if |el| is inside the viewport, or false otherwise.
|
||||||
*/
|
*/
|
||||||
element.isInView = function (el) {
|
element.isInView = function(el) {
|
||||||
let originalPointerEvents = el.style.pointerEvents;
|
let originalPointerEvents = el.style.pointerEvents;
|
||||||
try {
|
try {
|
||||||
el.style.pointerEvents = "auto";
|
el.style.pointerEvents = "auto";
|
||||||
@ -823,7 +827,7 @@ element.isInView = function (el) {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if visible, false otherwise.
|
* 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;
|
let win = el.ownerGlobal;
|
||||||
|
|
||||||
// Bug 1094246: webdriver's isShown doesn't work with content xul
|
// Bug 1094246: webdriver's isShown doesn't work with content xul
|
||||||
@ -860,7 +864,7 @@ element.isVisible = function (el, x = undefined, y = undefined) {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if element is obscured, false otherwise.
|
* True if element is obscured, false otherwise.
|
||||||
*/
|
*/
|
||||||
element.isObscured = function (el) {
|
element.isObscured = function(el) {
|
||||||
let tree = element.getPointerInteractablePaintTree(el);
|
let tree = element.getPointerInteractablePaintTree(el);
|
||||||
return !el.contains(tree[0]);
|
return !el.contains(tree[0]);
|
||||||
};
|
};
|
||||||
@ -878,7 +882,7 @@ element.isObscured = function (el) {
|
|||||||
* @return {Map.<string, number>}
|
* @return {Map.<string, number>}
|
||||||
* X and Y coordinates that denotes the in-view centre point of |rect|.
|
* 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;
|
const {max, min} = Math;
|
||||||
|
|
||||||
let x = {
|
let x = {
|
||||||
@ -909,7 +913,7 @@ element.getInViewCentrePoint = function (rect, win) {
|
|||||||
* @return {Array.<DOMElement>}
|
* @return {Array.<DOMElement>}
|
||||||
* Sequence of elements in paint order.
|
* Sequence of elements in paint order.
|
||||||
*/
|
*/
|
||||||
element.getPointerInteractablePaintTree = function (el) {
|
element.getPointerInteractablePaintTree = function(el) {
|
||||||
const doc = el.ownerDocument;
|
const doc = el.ownerDocument;
|
||||||
const win = doc.defaultView;
|
const win = doc.defaultView;
|
||||||
const container = {frame: win};
|
const container = {frame: win};
|
||||||
@ -941,7 +945,7 @@ element.getPointerInteractablePaintTree = function (el) {
|
|||||||
|
|
||||||
// TODO(ato): Not implemented.
|
// TODO(ato): Not implemented.
|
||||||
// In fact, it's not defined in the spec.
|
// In fact, it's not defined in the spec.
|
||||||
element.isKeyboardInteractable = function (el) {
|
element.isKeyboardInteractable = function(el) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -951,13 +955,13 @@ element.isKeyboardInteractable = function (el) {
|
|||||||
* @param {DOMElement} el
|
* @param {DOMElement} el
|
||||||
* Element to scroll into view.
|
* Element to scroll into view.
|
||||||
*/
|
*/
|
||||||
element.scrollIntoView = function (el) {
|
element.scrollIntoView = function(el) {
|
||||||
if (el.scrollIntoView) {
|
if (el.scrollIntoView) {
|
||||||
el.scrollIntoView({block: "end", inline: "nearest", behavior: "instant"});
|
el.scrollIntoView({block: "end", inline: "nearest", behavior: "instant"});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
element.isXULElement = function (el) {
|
element.isXULElement = function(el) {
|
||||||
let ns = atom.getElementAttribute(el, "namespaceURI");
|
let ns = atom.getElementAttribute(el, "namespaceURI");
|
||||||
return ns.indexOf("there.is.only.xul") >= 0;
|
return ns.indexOf("there.is.only.xul") >= 0;
|
||||||
};
|
};
|
||||||
@ -971,7 +975,15 @@ const boolEls = {
|
|||||||
form: ["novalidate"],
|
form: ["novalidate"],
|
||||||
iframe: ["allowfullscreen"],
|
iframe: ["allowfullscreen"],
|
||||||
img: ["ismap"],
|
img: ["ismap"],
|
||||||
input: ["autofocus", "checked", "disabled", "formnovalidate", "multiple", "readonly", "required"],
|
input: [
|
||||||
|
"autofocus",
|
||||||
|
"checked",
|
||||||
|
"disabled",
|
||||||
|
"formnovalidate",
|
||||||
|
"multiple",
|
||||||
|
"readonly",
|
||||||
|
"required",
|
||||||
|
],
|
||||||
keygen: ["autofocus", "disabled"],
|
keygen: ["autofocus", "disabled"],
|
||||||
menuitem: ["checked", "default", "disabled"],
|
menuitem: ["checked", "default", "disabled"],
|
||||||
object: ["typemustmatch"],
|
object: ["typemustmatch"],
|
||||||
@ -996,14 +1008,15 @@ const boolEls = {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if the attribute is boolean, false otherwise.
|
* True if the attribute is boolean, false otherwise.
|
||||||
*/
|
*/
|
||||||
element.isBooleanAttribute = function (el, attr) {
|
element.isBooleanAttribute = function(el, attr) {
|
||||||
if (el.namespaceURI !== XMLNS) {
|
if (el.namespaceURI !== XMLNS) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// global boolean attributes that apply to all HTML elements,
|
// global boolean attributes that apply to all HTML elements,
|
||||||
// except for custom 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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ const BUILTIN_ERRORS = new Set([
|
|||||||
"URIError",
|
"URIError",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["error"].concat(Array.from(ERRORS));
|
this.EXPORTED_SYMBOLS = ["error", "error.pprint"].concat(Array.from(ERRORS));
|
||||||
|
|
||||||
this.error = {};
|
this.error = {};
|
||||||
|
|
||||||
@ -71,26 +71,26 @@ this.error = {};
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if error, false otherwise.
|
* True if error, false otherwise.
|
||||||
*/
|
*/
|
||||||
error.isError = function (val) {
|
error.isError = function(val) {
|
||||||
if (val === null || typeof val != "object") {
|
if (val === null || typeof val != "object") {
|
||||||
return false;
|
return false;
|
||||||
} else if (val instanceof Ci.nsIException) {
|
} else if (val instanceof Ci.nsIException) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
}
|
||||||
// DOMRectList errors on string comparison
|
|
||||||
try {
|
// DOMRectList errors on string comparison
|
||||||
let proto = Object.getPrototypeOf(val);
|
try {
|
||||||
return BUILTIN_ERRORS.has(proto.toString());
|
let proto = Object.getPrototypeOf(val);
|
||||||
} catch (e) {
|
return BUILTIN_ERRORS.has(proto.toString());
|
||||||
return false;
|
} catch (e) {
|
||||||
}
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if obj is an object in the WebDriverError prototypal chain.
|
* Checks if obj is an object in the WebDriverError prototypal chain.
|
||||||
*/
|
*/
|
||||||
error.isWebDriverError = function (obj) {
|
error.isWebDriverError = function(obj) {
|
||||||
return error.isError(obj) &&
|
return error.isError(obj) &&
|
||||||
("name" in obj && ERRORS.has(obj.name));
|
("name" in obj && ERRORS.has(obj.name));
|
||||||
};
|
};
|
||||||
@ -109,7 +109,7 @@ error.isWebDriverError = function (obj) {
|
|||||||
* If |err| is a WebDriverError, it is returned unmodified.
|
* If |err| is a WebDriverError, it is returned unmodified.
|
||||||
* Otherwise an UnknownError type is returned.
|
* Otherwise an UnknownError type is returned.
|
||||||
*/
|
*/
|
||||||
error.wrap = function (err) {
|
error.wrap = function(err) {
|
||||||
if (error.isWebDriverError(err)) {
|
if (error.isWebDriverError(err)) {
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
@ -120,7 +120,7 @@ error.wrap = function (err) {
|
|||||||
* Unhandled error reporter. Dumps the error and its stacktrace to console,
|
* Unhandled error reporter. Dumps the error and its stacktrace to console,
|
||||||
* and reports error to the Browser 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);
|
let msg = "Marionette threw an error: " + error.stringify(err);
|
||||||
dump(msg + "\n");
|
dump(msg + "\n");
|
||||||
if (Cu.reportError) {
|
if (Cu.reportError) {
|
||||||
@ -131,7 +131,7 @@ error.report = function (err) {
|
|||||||
/**
|
/**
|
||||||
* Prettifies an instance of Error and its stacktrace to a string.
|
* Prettifies an instance of Error and its stacktrace to a string.
|
||||||
*/
|
*/
|
||||||
error.stringify = function (err) {
|
error.stringify = function(err) {
|
||||||
try {
|
try {
|
||||||
let s = err.toString();
|
let s = err.toString();
|
||||||
if ("stack" in err) {
|
if ("stack" in err) {
|
||||||
@ -148,16 +148,17 @@ error.stringify = function (err) {
|
|||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
*
|
*
|
||||||
|
* const {pprint} = Cu.import("chrome://marionette/content/error.js", {});
|
||||||
* let bool = {value: true};
|
* let bool = {value: true};
|
||||||
* error.pprint`Expected boolean, got ${bool}`;
|
* pprint`Expected boolean, got ${bool}`;
|
||||||
* => 'Expected boolean, got [object Object] {"value": true}'
|
* => 'Expected boolean, got [object Object] {"value": true}'
|
||||||
*
|
*
|
||||||
* let htmlElement = document.querySelector("input#foo");
|
* let htmlElement = document.querySelector("input#foo");
|
||||||
* error.pprint`Expected element ${htmlElement}`;
|
* pprint`Expected element ${htmlElement}`;
|
||||||
* => 'Expected element <input id="foo" class="bar baz">'
|
* => 'Expected element <input id="foo" class="bar baz">'
|
||||||
*/
|
*/
|
||||||
error.pprint = function (ss, ...values) {
|
error.pprint = function(ss, ...values) {
|
||||||
function prettyObject (obj) {
|
function prettyObject(obj) {
|
||||||
let proto = Object.prototype.toString.call(obj);
|
let proto = Object.prototype.toString.call(obj);
|
||||||
let s = "";
|
let s = "";
|
||||||
try {
|
try {
|
||||||
@ -172,7 +173,7 @@ error.pprint = function (ss, ...values) {
|
|||||||
return proto + " " + s;
|
return proto + " " + s;
|
||||||
}
|
}
|
||||||
|
|
||||||
function prettyElement (el) {
|
function prettyElement(el) {
|
||||||
let ident = [];
|
let ident = [];
|
||||||
if (el.id) {
|
if (el.id) {
|
||||||
ident.push(`id="${el.id}"`);
|
ident.push(`id="${el.id}"`);
|
||||||
@ -194,7 +195,6 @@ error.pprint = function (ss, ...values) {
|
|||||||
res.push(ss[i]);
|
res.push(ss[i]);
|
||||||
if (i < values.length) {
|
if (i < values.length) {
|
||||||
let val = values[i];
|
let val = values[i];
|
||||||
let typ = Object.prototype.toString.call(val);
|
|
||||||
let s;
|
let s;
|
||||||
try {
|
try {
|
||||||
if (val && val.nodeType === 1) {
|
if (val && val.nodeType === 1) {
|
||||||
@ -222,7 +222,7 @@ class WebDriverError extends Error {
|
|||||||
* Optional string describing error situation or Error instance
|
* Optional string describing error situation or Error instance
|
||||||
* to propagate.
|
* to propagate.
|
||||||
*/
|
*/
|
||||||
constructor (x) {
|
constructor(x) {
|
||||||
super(x);
|
super(x);
|
||||||
this.name = this.constructor.name;
|
this.name = this.constructor.name;
|
||||||
this.status = "webdriver error";
|
this.status = "webdriver error";
|
||||||
@ -233,7 +233,7 @@ class WebDriverError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toJSON () {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
error: this.status,
|
error: this.status,
|
||||||
message: this.message || "",
|
message: this.message || "",
|
||||||
@ -241,7 +241,7 @@ class WebDriverError extends Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON (json) {
|
static fromJSON(json) {
|
||||||
if (typeof json.error == "undefined") {
|
if (typeof json.error == "undefined") {
|
||||||
let s = JSON.stringify(json);
|
let s = JSON.stringify(json);
|
||||||
throw new TypeError("Undeserialisable error type: " + s);
|
throw new TypeError("Undeserialisable error type: " + s);
|
||||||
@ -263,7 +263,7 @@ class WebDriverError extends Error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ElementNotAccessibleError extends WebDriverError {
|
class ElementNotAccessibleError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "element not accessible";
|
this.status = "element not accessible";
|
||||||
}
|
}
|
||||||
@ -281,7 +281,7 @@ class ElementNotAccessibleError extends WebDriverError {
|
|||||||
* will produce a nicer error message.
|
* will produce a nicer error message.
|
||||||
*/
|
*/
|
||||||
class ElementClickInterceptedError extends WebDriverError {
|
class ElementClickInterceptedError extends WebDriverError {
|
||||||
constructor (obscuredEl = undefined, coords = undefined) {
|
constructor(obscuredEl = undefined, coords = undefined) {
|
||||||
let msg = "";
|
let msg = "";
|
||||||
if (obscuredEl && coords) {
|
if (obscuredEl && coords) {
|
||||||
const doc = obscuredEl.ownerDocument;
|
const doc = obscuredEl.ownerDocument;
|
||||||
@ -311,49 +311,49 @@ class ElementClickInterceptedError extends WebDriverError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ElementNotInteractableError extends WebDriverError {
|
class ElementNotInteractableError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "element not interactable";
|
this.status = "element not interactable";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InsecureCertificateError extends WebDriverError {
|
class InsecureCertificateError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "insecure certificate";
|
this.status = "insecure certificate";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidArgumentError extends WebDriverError {
|
class InvalidArgumentError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "invalid argument";
|
this.status = "invalid argument";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidCookieDomainError extends WebDriverError {
|
class InvalidCookieDomainError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "invalid cookie domain";
|
this.status = "invalid cookie domain";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidElementStateError extends WebDriverError {
|
class InvalidElementStateError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "invalid element state";
|
this.status = "invalid element state";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidSelectorError extends WebDriverError {
|
class InvalidSelectorError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "invalid selector";
|
this.status = "invalid selector";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InvalidSessionIDError extends WebDriverError {
|
class InvalidSessionIDError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "invalid session id";
|
this.status = "invalid session id";
|
||||||
}
|
}
|
||||||
@ -412,98 +412,98 @@ class JavaScriptError extends WebDriverError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MoveTargetOutOfBoundsError extends WebDriverError {
|
class MoveTargetOutOfBoundsError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "move target out of bounds";
|
this.status = "move target out of bounds";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoAlertOpenError extends WebDriverError {
|
class NoAlertOpenError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "no such alert";
|
this.status = "no such alert";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoSuchElementError extends WebDriverError {
|
class NoSuchElementError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "no such element";
|
this.status = "no such element";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoSuchFrameError extends WebDriverError {
|
class NoSuchFrameError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "no such frame";
|
this.status = "no such frame";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoSuchWindowError extends WebDriverError {
|
class NoSuchWindowError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "no such window";
|
this.status = "no such window";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ScriptTimeoutError extends WebDriverError {
|
class ScriptTimeoutError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "script timeout";
|
this.status = "script timeout";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SessionNotCreatedError extends WebDriverError {
|
class SessionNotCreatedError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "session not created";
|
this.status = "session not created";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StaleElementReferenceError extends WebDriverError {
|
class StaleElementReferenceError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "stale element reference";
|
this.status = "stale element reference";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TimeoutError extends WebDriverError {
|
class TimeoutError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "timeout";
|
this.status = "timeout";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnableToSetCookieError extends WebDriverError {
|
class UnableToSetCookieError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "unable to set cookie";
|
this.status = "unable to set cookie";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnexpectedAlertOpenError extends WebDriverError {
|
class UnexpectedAlertOpenError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "unexpected alert open";
|
this.status = "unexpected alert open";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnknownCommandError extends WebDriverError {
|
class UnknownCommandError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "unknown command";
|
this.status = "unknown command";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnknownError extends WebDriverError {
|
class UnknownError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "unknown error";
|
this.status = "unknown error";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnsupportedOperationError extends WebDriverError {
|
class UnsupportedOperationError extends WebDriverError {
|
||||||
constructor (message) {
|
constructor(message) {
|
||||||
super(message);
|
super(message);
|
||||||
this.status = "unsupported operation";
|
this.status = "unsupported operation";
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ this.evaluate = {};
|
|||||||
*
|
*
|
||||||
* @param {nsISandbox) sb
|
* @param {nsISandbox) sb
|
||||||
* The sandbox the script will be evaluted in.
|
* The sandbox the script will be evaluted in.
|
||||||
* @param {string} script
|
* @param {string} script
|
||||||
* The script to evaluate.
|
* The script to evaluate.
|
||||||
* @param {Array.<?>=} args
|
* @param {Array.<?>=} args
|
||||||
* A sequence of arguments to call the script with.
|
* A sequence of arguments to call the script with.
|
||||||
@ -99,12 +99,12 @@ this.evaluate = {};
|
|||||||
* the script. Note that the return value requires serialisation before
|
* the script. Note that the return value requires serialisation before
|
||||||
* it can be sent to the client.
|
* it can be sent to the client.
|
||||||
*
|
*
|
||||||
* @throws JavaScriptError
|
* @throws {JavaScriptError}
|
||||||
* If an Error was thrown whilst evaluating the script.
|
* If an Error was thrown whilst evaluating the script.
|
||||||
* @throws ScriptTimeoutError
|
* @throws {ScriptTimeoutError}
|
||||||
* If the script was interrupted due to script timeout.
|
* 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 scriptTimeoutID, timeoutHandler, unloadHandler;
|
||||||
|
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
@ -112,7 +112,7 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
|
|||||||
sb[COMPLETE] = resolve;
|
sb[COMPLETE] = resolve;
|
||||||
timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
|
timeoutHandler = () => reject(new ScriptTimeoutError("Timed out"));
|
||||||
unloadHandler = sandbox.cloneInto(
|
unloadHandler = sandbox.cloneInto(
|
||||||
() => reject(new JavaScriptError("Document was unloaded during execution")),
|
() => reject(new JavaScriptError("Document was unloaded")),
|
||||||
sb);
|
sb);
|
||||||
|
|
||||||
// wrap in function
|
// wrap in function
|
||||||
@ -151,12 +151,16 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// timeout and unload handlers
|
// 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;
|
sb.window.onunload = unloadHandler;
|
||||||
|
|
||||||
|
const file = opts.filename || "dummy file";
|
||||||
|
const line = opts.line || 0;
|
||||||
|
|
||||||
let res;
|
let res;
|
||||||
try {
|
try {
|
||||||
res = Cu.evalInSandbox(src, sb, "1.8", opts.filename || "dummy file", 0);
|
res = Cu.evalInSandbox(src, sb, "1.8", file, 0);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let err = new JavaScriptError(e, {
|
let err = new JavaScriptError(e, {
|
||||||
fnName: "execute_script",
|
fnName: "execute_script",
|
||||||
@ -196,42 +200,42 @@ evaluate.sandbox = function (sb, script, args = [], opts = {}) {
|
|||||||
* Same object as provided by |obj| with the web elements replaced
|
* Same object as provided by |obj| with the web elements replaced
|
||||||
* by DOM elements.
|
* by DOM elements.
|
||||||
*/
|
*/
|
||||||
evaluate.fromJSON = function (obj, seenEls, win, shadowRoot = undefined) {
|
evaluate.fromJSON = function(obj, seenEls, win, shadowRoot = undefined) {
|
||||||
switch (typeof obj) {
|
switch (typeof obj) {
|
||||||
case "boolean":
|
case "boolean":
|
||||||
case "number":
|
case "number":
|
||||||
case "string":
|
case "string":
|
||||||
|
default:
|
||||||
return obj;
|
return obj;
|
||||||
|
|
||||||
case "object":
|
case "object":
|
||||||
if (obj === null) {
|
if (obj === null) {
|
||||||
return obj;
|
return obj;
|
||||||
}
|
|
||||||
|
|
||||||
// arrays
|
// arrays
|
||||||
else if (Array.isArray(obj)) {
|
} else if (Array.isArray(obj)) {
|
||||||
return obj.map(e => evaluate.fromJSON(e, seenEls, win, shadowRoot));
|
return obj.map(e => evaluate.fromJSON(e, seenEls, win, shadowRoot));
|
||||||
}
|
|
||||||
|
|
||||||
// web elements
|
// web elements
|
||||||
else if (Object.keys(obj).includes(element.Key) ||
|
} else if (Object.keys(obj).includes(element.Key) ||
|
||||||
Object.keys(obj).includes(element.LegacyKey)) {
|
Object.keys(obj).includes(element.LegacyKey)) {
|
||||||
|
/* eslint-disable */
|
||||||
let uuid = obj[element.Key] || obj[element.LegacyKey];
|
let uuid = obj[element.Key] || obj[element.LegacyKey];
|
||||||
let el = seenEls.get(uuid, {frame: win, shadowRoot: shadowRoot});
|
let el = seenEls.get(uuid, {frame: win, shadowRoot: shadowRoot});
|
||||||
|
/* eslint-enable */
|
||||||
if (!el) {
|
if (!el) {
|
||||||
throw new WebDriverError(`Unknown element: ${uuid}`);
|
throw new WebDriverError(`Unknown element: ${uuid}`);
|
||||||
}
|
}
|
||||||
return el;
|
return el;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// arbitrary objects
|
// arbitrary objects
|
||||||
else {
|
let rv = {};
|
||||||
let rv = {};
|
for (let prop in obj) {
|
||||||
for (let prop in obj) {
|
rv[prop] = evaluate.fromJSON(obj[prop], seenEls, win, shadowRoot);
|
||||||
rv[prop] = evaluate.fromJSON(obj[prop], seenEls, win, shadowRoot);
|
|
||||||
}
|
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -251,32 +255,30 @@ evaluate.fromJSON = function (obj, seenEls, win, shadowRoot = undefined) {
|
|||||||
* Same object as provided by |obj| with the elements replaced by
|
* Same object as provided by |obj| with the elements replaced by
|
||||||
* web elements.
|
* web elements.
|
||||||
*/
|
*/
|
||||||
evaluate.toJSON = function (obj, seenEls) {
|
evaluate.toJSON = function(obj, seenEls) {
|
||||||
const t = Object.prototype.toString.call(obj);
|
const t = Object.prototype.toString.call(obj);
|
||||||
|
|
||||||
// null
|
// null
|
||||||
if (t == "[object Undefined]" || t == "[object Null]") {
|
if (t == "[object Undefined]" || t == "[object Null]") {
|
||||||
return null;
|
return null;
|
||||||
}
|
|
||||||
|
|
||||||
// literals
|
// 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;
|
return obj;
|
||||||
}
|
|
||||||
|
|
||||||
// Array, NodeList, HTMLCollection, et al.
|
// Array, NodeList, HTMLCollection, et al.
|
||||||
else if (element.isCollection(obj)) {
|
} else if (element.isCollection(obj)) {
|
||||||
return [...obj].map(el => evaluate.toJSON(el, seenEls));
|
return [...obj].map(el => evaluate.toJSON(el, seenEls));
|
||||||
}
|
|
||||||
|
|
||||||
// HTMLElement
|
// 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);
|
let uuid = seenEls.add(obj);
|
||||||
return element.makeWebElement(uuid);
|
return element.makeWebElement(uuid);
|
||||||
}
|
|
||||||
|
|
||||||
// custom JSON representation
|
// custom JSON representation
|
||||||
else if (typeof obj["toJSON"] == "function") {
|
} else if (typeof obj["toJSON"] == "function") {
|
||||||
let unsafeJSON = obj.toJSON();
|
let unsafeJSON = obj.toJSON();
|
||||||
return evaluate.toJSON(unsafeJSON, seenEls);
|
return evaluate.toJSON(unsafeJSON, seenEls);
|
||||||
}
|
}
|
||||||
@ -293,8 +295,8 @@ evaluate.toJSON = function (obj, seenEls) {
|
|||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rv;
|
|
||||||
}
|
}
|
||||||
|
return rv;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.sandbox = {};
|
this.sandbox = {};
|
||||||
@ -307,7 +309,7 @@ this.sandbox = {};
|
|||||||
* Unlike for |Components.utils.cloneInto|, |obj| may contain functions
|
* Unlike for |Components.utils.cloneInto|, |obj| may contain functions
|
||||||
* and DOM elemnets.
|
* and DOM elemnets.
|
||||||
*/
|
*/
|
||||||
sandbox.cloneInto = function (obj, sb) {
|
sandbox.cloneInto = function(obj, sb) {
|
||||||
return Cu.cloneInto(obj, sb, {cloneFunctions: true, wrapReflectors: true});
|
return Cu.cloneInto(obj, sb, {cloneFunctions: true, wrapReflectors: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -316,7 +318,7 @@ sandbox.cloneInto = function (obj, sb) {
|
|||||||
* map property, or a normal map, of function names and function
|
* map property, or a normal map, of function names and function
|
||||||
* references.
|
* references.
|
||||||
*
|
*
|
||||||
* @param {Sandbox} sb
|
* @param {Sandbox} sb
|
||||||
* The sandbox to augment.
|
* The sandbox to augment.
|
||||||
* @param {Object} adapter
|
* @param {Object} adapter
|
||||||
* Object that holds an {@code exports} property, or a map, of
|
* Object that holds an {@code exports} property, or a map, of
|
||||||
@ -325,11 +327,11 @@ sandbox.cloneInto = function (obj, sb) {
|
|||||||
* @return {Sandbox}
|
* @return {Sandbox}
|
||||||
* The augmented sandbox.
|
* The augmented sandbox.
|
||||||
*/
|
*/
|
||||||
sandbox.augment = function (sb, adapter) {
|
sandbox.augment = function(sb, adapter) {
|
||||||
function* entries(obj) {
|
function* entries(obj) {
|
||||||
for (let key of Object.keys(obj)) {
|
for (let key of Object.keys(obj)) {
|
||||||
yield [key, obj[key]];
|
yield [key, obj[key]];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let funcs = adapter.exports || entries(adapter);
|
let funcs = adapter.exports || entries(adapter);
|
||||||
@ -352,7 +354,7 @@ sandbox.augment = function (sb, adapter) {
|
|||||||
* @return {Sandbox}
|
* @return {Sandbox}
|
||||||
* The created sandbox.
|
* The created sandbox.
|
||||||
*/
|
*/
|
||||||
sandbox.create = function (window, principal = null, opts = {}) {
|
sandbox.create = function(window, principal = null, opts = {}) {
|
||||||
let p = principal || window;
|
let p = principal || window;
|
||||||
opts = Object.assign({
|
opts = Object.assign({
|
||||||
sameZoneAs: window,
|
sameZoneAs: window,
|
||||||
@ -373,7 +375,7 @@ sandbox.create = function (window, principal = null, opts = {}) {
|
|||||||
* @return {Sandbox}
|
* @return {Sandbox}
|
||||||
* The created sandbox.
|
* The created sandbox.
|
||||||
*/
|
*/
|
||||||
sandbox.createMutable = function (window) {
|
sandbox.createMutable = function(window) {
|
||||||
let opts = {
|
let opts = {
|
||||||
wantComponents: false,
|
wantComponents: false,
|
||||||
wantXrays: false,
|
wantXrays: false,
|
||||||
@ -381,13 +383,13 @@ sandbox.createMutable = function (window) {
|
|||||||
return sandbox.create(window, null, opts);
|
return sandbox.create(window, null, opts);
|
||||||
};
|
};
|
||||||
|
|
||||||
sandbox.createSystemPrincipal = function (window) {
|
sandbox.createSystemPrincipal = function(window) {
|
||||||
let principal = Cc["@mozilla.org/systemprincipal;1"]
|
let principal = Cc["@mozilla.org/systemprincipal;1"]
|
||||||
.createInstance(Ci.nsIPrincipal);
|
.createInstance(Ci.nsIPrincipal);
|
||||||
return sandbox.create(window, principal);
|
return sandbox.create(window, principal);
|
||||||
};
|
};
|
||||||
|
|
||||||
sandbox.createSimpleTest = function (window, harness) {
|
sandbox.createSimpleTest = function(window, harness) {
|
||||||
let sb = sandbox.create(window);
|
let sb = sandbox.create(window);
|
||||||
sb = sandbox.augment(sb, harness);
|
sb = sandbox.augment(sb, harness);
|
||||||
sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
|
sb[FINISH] = () => sb[COMPLETE](harness.generate_results());
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
* 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/. */
|
* 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";
|
"use strict";
|
||||||
/* global content, is */
|
/* global content, is */
|
||||||
@ -37,8 +38,6 @@ function getDOMWindowUtils(win) {
|
|||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.event = {};
|
|
||||||
|
|
||||||
event.MouseEvents = {
|
event.MouseEvents = {
|
||||||
click: 0,
|
click: 0,
|
||||||
dblclick: 1,
|
dblclick: 1,
|
||||||
@ -68,13 +67,14 @@ event.Modifiers = {
|
|||||||
* @throws {TypeError}
|
* @throws {TypeError}
|
||||||
* If the event is unsupported.
|
* 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)) {
|
if (!event.MouseEvents.hasOwnProperty(mouseEvent.type)) {
|
||||||
throw new TypeError("Unsupported event type: " + mouseEvent.type);
|
throw new TypeError("Unsupported event type: " + mouseEvent.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!target.nodeType && typeof target != "string") {
|
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) {
|
if (!target.nodeType) {
|
||||||
@ -85,7 +85,6 @@ event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
|
|||||||
|
|
||||||
let ev = window.document.createEvent("MouseEvent");
|
let ev = window.document.createEvent("MouseEvent");
|
||||||
|
|
||||||
let type = mouseEvent.type;
|
|
||||||
let view = window;
|
let view = window;
|
||||||
|
|
||||||
let detail = mouseEvent.detail;
|
let detail = mouseEvent.detail;
|
||||||
@ -138,7 +137,7 @@ event.sendMouseEvent = function (mouseEvent, target, window = undefined) {
|
|||||||
* For now this method only works for English letters (lower and upper
|
* For now this method only works for English letters (lower and upper
|
||||||
* case) and the digits 0-9.
|
* 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
|
// DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9
|
||||||
let hasShift = (char == char.toUpperCase());
|
let hasShift = (char == char.toUpperCase());
|
||||||
event.synthesizeKey(char, {shiftKey: hasShift}, window);
|
event.synthesizeKey(char, {shiftKey: hasShift}, window);
|
||||||
@ -150,7 +149,7 @@ event.sendChar = function (char, window = undefined) {
|
|||||||
* For now this method only works for English letters (lower and upper
|
* For now this method only works for English letters (lower and upper
|
||||||
* case) and the digits 0-9.
|
* 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) {
|
for (let i = 0; i < string.length; ++i) {
|
||||||
event.sendChar(string.charAt(i), window);
|
event.sendChar(string.charAt(i), window);
|
||||||
}
|
}
|
||||||
@ -163,14 +162,14 @@ event.sendString = function (string, window = undefined) {
|
|||||||
* in the nsIDOMKeyEvent constant name for this key. No modifiers are
|
* in the nsIDOMKeyEvent constant name for this key. No modifiers are
|
||||||
* handled at this point.
|
* handled at this point.
|
||||||
*/
|
*/
|
||||||
event.sendKey = function (key, window = undefined) {
|
event.sendKey = function(key, window = undefined) {
|
||||||
let keyName = "VK_" + key.toUpperCase();
|
let keyName = "VK_" + key.toUpperCase();
|
||||||
event.synthesizeKey(keyName, {shiftKey: false}, window);
|
event.synthesizeKey(keyName, {shiftKey: false}, window);
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO(ato): Unexpose this when action.Chain#emitMouseEvent
|
// TODO(ato): Unexpose this when action.Chain#emitMouseEvent
|
||||||
// no longer emits its own events
|
// no longer emits its own events
|
||||||
event.parseModifiers_ = function (modifiers) {
|
event.parseModifiers_ = function(modifiers) {
|
||||||
let mval = 0;
|
let mval = 0;
|
||||||
if (modifiers.shiftKey) {
|
if (modifiers.shiftKey) {
|
||||||
mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
|
mval |= Ci.nsIDOMNSEvent.SHIFT_MASK;
|
||||||
@ -217,7 +216,7 @@ event.parseModifiers_ = function (modifiers) {
|
|||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
* Window object. Defaults to the current window.
|
* Window object. Defaults to the current window.
|
||||||
*/
|
*/
|
||||||
event.synthesizeMouse = function (
|
event.synthesizeMouse = function(
|
||||||
element, offsetX, offsetY, opts, window = undefined) {
|
element, offsetX, offsetY, opts, window = undefined) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
event.synthesizeMouseAtPoint(
|
event.synthesizeMouseAtPoint(
|
||||||
@ -241,7 +240,7 @@ event.synthesizeMouse = function (
|
|||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
* Window object. Defaults to the current window.
|
* Window object. Defaults to the current window.
|
||||||
*/
|
*/
|
||||||
event.synthesizeMouseAtPoint = function (
|
event.synthesizeMouseAtPoint = function(
|
||||||
left, top, opts, window = undefined) {
|
left, top, opts, window = undefined) {
|
||||||
|
|
||||||
let domutils = getDOMWindowUtils(window);
|
let domutils = getDOMWindowUtils(window);
|
||||||
@ -254,21 +253,60 @@ event.synthesizeMouseAtPoint = function (
|
|||||||
Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
|
Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE;
|
||||||
let isDOMEventSynthesized =
|
let isDOMEventSynthesized =
|
||||||
("isSynthesized" in opts) ? opts.isSynthesized : true;
|
("isSynthesized" in opts) ? opts.isSynthesized : true;
|
||||||
let isWidgetEventSynthesized =
|
let isWidgetEventSynthesized;
|
||||||
("isWidgetEventSynthesized" in opts) ? opts.isWidgetEventSynthesized : false;
|
if ("isWidgetEventSynthesized" in opts) {
|
||||||
let buttons = ("buttons" in opts) ? opts.buttons : domutils.MOUSE_BUTTONS_NOT_SPECIFIED;
|
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) {
|
if (("type" in opts) && opts.type) {
|
||||||
domutils.sendMouseEvent(
|
domutils.sendMouseEvent(
|
||||||
opts.type, left, top, button, clickCount, modifiers, false, pressure, inputSource,
|
opts.type,
|
||||||
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
|
left,
|
||||||
|
top,
|
||||||
|
button,
|
||||||
|
clickCount,
|
||||||
|
modifiers,
|
||||||
|
false,
|
||||||
|
pressure,
|
||||||
|
inputSource,
|
||||||
|
isDOMEventSynthesized,
|
||||||
|
isWidgetEventSynthesized,
|
||||||
|
buttons);
|
||||||
} else {
|
} else {
|
||||||
domutils.sendMouseEvent(
|
domutils.sendMouseEvent(
|
||||||
"mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource,
|
"mousedown",
|
||||||
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
|
left,
|
||||||
|
top,
|
||||||
|
button,
|
||||||
|
clickCount,
|
||||||
|
modifiers,
|
||||||
|
false,
|
||||||
|
pressure,
|
||||||
|
inputSource,
|
||||||
|
isDOMEventSynthesized,
|
||||||
|
isWidgetEventSynthesized,
|
||||||
|
buttons);
|
||||||
domutils.sendMouseEvent(
|
domutils.sendMouseEvent(
|
||||||
"mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource,
|
"mouseup",
|
||||||
isDOMEventSynthesized, isWidgetEventSynthesized, buttons);
|
left,
|
||||||
|
top,
|
||||||
|
button,
|
||||||
|
clickCount,
|
||||||
|
modifiers,
|
||||||
|
false,
|
||||||
|
pressure,
|
||||||
|
inputSource,
|
||||||
|
isDOMEventSynthesized,
|
||||||
|
isWidgetEventSynthesized,
|
||||||
|
buttons);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -276,7 +314,7 @@ event.synthesizeMouseAtPoint = function (
|
|||||||
* Call event.synthesizeMouse with coordinates at the centre of the
|
* Call event.synthesizeMouse with coordinates at the centre of the
|
||||||
* target.
|
* target.
|
||||||
*/
|
*/
|
||||||
event.synthesizeMouseAtCenter = function (element, event, window) {
|
event.synthesizeMouseAtCenter = function(element, event, window) {
|
||||||
let rect = element.getBoundingClientRect();
|
let rect = element.getBoundingClientRect();
|
||||||
event.synthesizeMouse(
|
event.synthesizeMouse(
|
||||||
element,
|
element,
|
||||||
@ -286,6 +324,7 @@ event.synthesizeMouseAtCenter = function (element, event, window) {
|
|||||||
window);
|
window);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
function computeKeyCodeFromChar_(char) {
|
function computeKeyCodeFromChar_(char) {
|
||||||
if (char.length != 1) {
|
if (char.length != 1) {
|
||||||
return 0;
|
return 0;
|
||||||
@ -388,6 +427,7 @@ function computeKeyCodeFromChar_(char) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the given key should cause keypress event when widget
|
* Returns true if the given key should cause keypress event when widget
|
||||||
@ -396,7 +436,7 @@ function computeKeyCodeFromChar_(char) {
|
|||||||
* The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
|
* The key code should be one of consts of nsIDOMKeyEvent.DOM_VK_*,
|
||||||
* or a key name begins with "VK_", or a character.
|
* or a key name begins with "VK_", or a character.
|
||||||
*/
|
*/
|
||||||
event.isKeypressFiredKey = function (key) {
|
event.isKeypressFiredKey = function(key) {
|
||||||
if (typeof key == "string") {
|
if (typeof key == "string") {
|
||||||
if (key.indexOf("VK_") === 0) {
|
if (key.indexOf("VK_") === 0) {
|
||||||
key = Ci.nsIDOMKeyEvent["DOM_" + key];
|
key = Ci.nsIDOMKeyEvent["DOM_" + key];
|
||||||
@ -445,8 +485,7 @@ event.isKeypressFiredKey = function (key) {
|
|||||||
* @throws {TypeError}
|
* @throws {TypeError}
|
||||||
* If unknown key.
|
* If unknown key.
|
||||||
*/
|
*/
|
||||||
event.synthesizeKey = function (key, event, win = undefined)
|
event.synthesizeKey = function(key, event, win = undefined) {
|
||||||
{
|
|
||||||
var TIP = getTIP_(win);
|
var TIP = getTIP_(win);
|
||||||
if (!TIP) {
|
if (!TIP) {
|
||||||
return;
|
return;
|
||||||
@ -458,7 +497,7 @@ event.synthesizeKey = function (key, event, win = undefined)
|
|||||||
var dispatchKeydown =
|
var dispatchKeydown =
|
||||||
!("type" in event) || event.type === "keydown" || !event.type;
|
!("type" in event) || event.type === "keydown" || !event.type;
|
||||||
var dispatchKeyup =
|
var dispatchKeyup =
|
||||||
!("type" in event) || event.type === "keyup" || !event.type;
|
!("type" in event) || event.type === "keyup" || !event.type;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (dispatchKeydown) {
|
if (dispatchKeydown) {
|
||||||
@ -481,8 +520,7 @@ event.synthesizeKey = function (key, event, win = undefined)
|
|||||||
|
|
||||||
var TIPMap = new WeakMap();
|
var TIPMap = new WeakMap();
|
||||||
|
|
||||||
function getTIP_(win, callback)
|
function getTIP_(win, callback) {
|
||||||
{
|
|
||||||
if (!win) {
|
if (!win) {
|
||||||
win = window;
|
win = window;
|
||||||
}
|
}
|
||||||
@ -502,12 +540,12 @@ function getTIP_(win, callback)
|
|||||||
return tip;
|
return tip;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getKeyboardEvent_(win = window)
|
function getKeyboardEvent_(win = window) {
|
||||||
{
|
|
||||||
if (typeof KeyboardEvent != "undefined") {
|
if (typeof KeyboardEvent != "undefined") {
|
||||||
try {
|
try {
|
||||||
// See if the object can be instantiated; sometimes this yields
|
// 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("", {});
|
new KeyboardEvent("", {});
|
||||||
return KeyboardEvent;
|
return KeyboardEvent;
|
||||||
} catch (ex) {}
|
} catch (ex) {}
|
||||||
@ -519,8 +557,9 @@ function getKeyboardEvent_(win = window)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createKeyboardEventDictionary_(key, keyEvent, win = window) {
|
function createKeyboardEventDictionary_(key, keyEvent, win = window) {
|
||||||
var result = { dictionary: null, flags: 0 };
|
var result = {dictionary: null, flags: 0};
|
||||||
var keyCodeIsDefined = "keyCode" in keyEvent && keyEvent.keyCode != undefined;
|
var keyCodeIsDefined = "keyCode" in keyEvent &&
|
||||||
|
keyEvent.keyCode != undefined;
|
||||||
var keyCode =
|
var keyCode =
|
||||||
(keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
|
(keyCodeIsDefined && keyEvent.keyCode >= 0 && keyEvent.keyCode <= 255) ?
|
||||||
keyEvent.keyCode : 0;
|
keyEvent.keyCode : 0;
|
||||||
@ -535,7 +574,7 @@ function createKeyboardEventDictionary_(key, keyEvent, win = window) {
|
|||||||
}
|
}
|
||||||
keyName = guessKeyNameFromKeyCode_(keyCode, win);
|
keyName = guessKeyNameFromKeyCode_(keyCode, win);
|
||||||
if (!isPrintable(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 != "") {
|
} else if (key != "") {
|
||||||
keyName = key;
|
keyName = key;
|
||||||
@ -559,92 +598,90 @@ function createKeyboardEventDictionary_(key, keyEvent, win = window) {
|
|||||||
code: "code" in keyEvent ? keyEvent.code : "",
|
code: "code" in keyEvent ? keyEvent.code : "",
|
||||||
location: locationIsDefined ? keyEvent.location : 0,
|
location: locationIsDefined ? keyEvent.location : 0,
|
||||||
repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
|
repeat: "repeat" in keyEvent ? keyEvent.repeat === true : false,
|
||||||
keyCode: keyCode,
|
keyCode,
|
||||||
};
|
};
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emulateToActivateModifiers_(TIP, keyEvent, win = window)
|
function emulateToActivateModifiers_(TIP, keyEvent, win = window) {
|
||||||
{
|
|
||||||
if (!keyEvent) {
|
if (!keyEvent) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
var KeyboardEvent = getKeyboardEvent_(win);
|
var KeyboardEvent = getKeyboardEvent_(win);
|
||||||
var navigator = getNavigator_(win);
|
|
||||||
|
|
||||||
var modifiers = {
|
var modifiers = {
|
||||||
normal: [
|
normal: [
|
||||||
{ key: "Alt", attr: "altKey" },
|
{key: "Alt", attr: "altKey"},
|
||||||
{ key: "AltGraph", attr: "altGraphKey" },
|
{key: "AltGraph", attr: "altGraphKey"},
|
||||||
{ key: "Control", attr: "ctrlKey" },
|
{key: "Control", attr: "ctrlKey"},
|
||||||
{ key: "Fn", attr: "fnKey" },
|
{key: "Fn", attr: "fnKey"},
|
||||||
{ key: "Meta", attr: "metaKey" },
|
{key: "Meta", attr: "metaKey"},
|
||||||
{ key: "OS", attr: "osKey" },
|
{key: "OS", attr: "osKey"},
|
||||||
{ key: "Shift", attr: "shiftKey" },
|
{key: "Shift", attr: "shiftKey"},
|
||||||
{ key: "Symbol", attr: "symbolKey" },
|
{key: "Symbol", attr: "symbolKey"},
|
||||||
{ key: isMac_(win) ? "Meta" : "Control",
|
{key: isMac_(win) ? "Meta" : "Control", attr: "accelKey"},
|
||||||
attr: "accelKey" },
|
|
||||||
],
|
],
|
||||||
lockable: [
|
lockable: [
|
||||||
{ key: "CapsLock", attr: "capsLockKey" },
|
{key: "CapsLock", attr: "capsLockKey"},
|
||||||
{ key: "FnLock", attr: "fnLockKey" },
|
{key: "FnLock", attr: "fnLockKey"},
|
||||||
{ key: "NumLock", attr: "numLockKey" },
|
{key: "NumLock", attr: "numLockKey"},
|
||||||
{ key: "ScrollLock", attr: "scrollLockKey" },
|
{key: "ScrollLock", attr: "scrollLockKey"},
|
||||||
{ key: "SymbolLock", attr: "symbolLockKey" },
|
{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]) {
|
if (!keyEvent[modifiers.normal[i].attr]) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (TIP.getModifierState(modifiers.normal[i].key)) {
|
if (TIP.getModifierState(modifiers.normal[i].key)) {
|
||||||
continue; // already activated.
|
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.keydown(event,
|
||||||
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||||
modifiers.normal[i].activated = true;
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (TIP.getModifierState(modifiers.lockable[i].key)) {
|
if (TIP.getModifierState(modifiers.lockable[j].key)) {
|
||||||
continue; // already activated.
|
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.keydown(event,
|
||||||
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||||
TIP.keyup(event,
|
TIP.keyup(event,
|
||||||
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_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;
|
return modifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
function emulateToInactivateModifiers_(TIP, modifiers, win = window)
|
function emulateToInactivateModifiers_(TIP, modifiers, win = window) {
|
||||||
{
|
|
||||||
if (!modifiers) {
|
if (!modifiers) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var KeyboardEvent = getKeyboardEvent_(win);
|
let KeyboardEvent = getKeyboardEvent_(win);
|
||||||
for (var i = 0; i < modifiers.normal.length; i++) {
|
for (let i = 0; i < modifiers.normal.length; i++) {
|
||||||
if (!modifiers.normal[i].activated) {
|
if (!modifiers.normal[i].activated) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var event = new KeyboardEvent("", { key: modifiers.normal[i].key });
|
let event = new KeyboardEvent("", {key: modifiers.normal[i].key});
|
||||||
TIP.keyup(event,
|
TIP.keyup(event,
|
||||||
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||||
}
|
}
|
||||||
for (var i = 0; i < modifiers.lockable.length; i++) {
|
for (let j = 0; j < modifiers.lockable.length; j++) {
|
||||||
if (!modifiers.lockable[i].activated) {
|
if (!modifiers.lockable[j].activated) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!TIP.getModifierState(modifiers.lockable[i].key)) {
|
if (!TIP.getModifierState(modifiers.lockable[j].key)) {
|
||||||
continue; // who already inactivated this?
|
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.keydown(event,
|
||||||
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
TIP.KEY_NON_PRINTABLE_KEY | TIP.KEY_DONT_DISPATCH_MODIFIER_KEY_EVENT);
|
||||||
TIP.keyup(event,
|
TIP.keyup(event,
|
||||||
@ -661,8 +698,8 @@ function isMac_(win = window) {
|
|||||||
return navigator.platform.indexOf("Mac") > -1;
|
return navigator.platform.indexOf("Mac") > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
function guessKeyNameFromKeyCode_(aKeyCode, win = window)
|
/* eslint-disable */
|
||||||
{
|
function guessKeyNameFromKeyCode_(aKeyCode, win = window) {
|
||||||
var KeyboardEvent = getKeyboardEvent_(win);
|
var KeyboardEvent = getKeyboardEvent_(win);
|
||||||
switch (aKeyCode) {
|
switch (aKeyCode) {
|
||||||
case KeyboardEvent.DOM_VK_CANCEL:
|
case KeyboardEvent.DOM_VK_CANCEL:
|
||||||
@ -807,11 +844,13 @@ function guessKeyNameFromKeyCode_(aKeyCode, win = window)
|
|||||||
return "Unidentified";
|
return "Unidentified";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicate that an event with an original target and type is expected
|
* Indicate that an event with an original target and type is expected
|
||||||
* to be fired, or not expected to be fired.
|
* to be fired, or not expected to be fired.
|
||||||
*/
|
*/
|
||||||
|
/* eslint-disable */
|
||||||
function expectEvent_(expectedTarget, expectedEvent, testName) {
|
function expectEvent_(expectedTarget, expectedEvent, testName) {
|
||||||
if (!expectedTarget || !expectedEvent) {
|
if (!expectedTarget || !expectedEvent) {
|
||||||
return null;
|
return null;
|
||||||
@ -835,6 +874,7 @@ function expectEvent_(expectedTarget, expectedEvent, testName) {
|
|||||||
expectedTarget.addEventListener(type, handler);
|
expectedTarget.addEventListener(type, handler);
|
||||||
return handler;
|
return handler;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the event was fired or not. The provided event handler will
|
* Check if the event was fired or not. The provided event handler will
|
||||||
@ -887,7 +927,7 @@ function checkExpectedEvent_(
|
|||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
* Window object. Defaults to the current window.
|
* Window object. Defaults to the current window.
|
||||||
*/
|
*/
|
||||||
event.synthesizeMouseExpectEvent = function (
|
event.synthesizeMouseExpectEvent = function(
|
||||||
target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
|
target, offsetX, offsetY, ev, expectedTarget, expectedEvent,
|
||||||
testName, window = undefined) {
|
testName, window = undefined) {
|
||||||
|
|
||||||
@ -926,7 +966,7 @@ event.synthesizeMouseExpectEvent = function (
|
|||||||
*
|
*
|
||||||
* aWindow is optional, and defaults to the current window object.
|
* aWindow is optional, and defaults to the current window object.
|
||||||
*/
|
*/
|
||||||
event.synthesizeKeyExpectEvent = function (
|
event.synthesizeKeyExpectEvent = function(
|
||||||
key, ev, expectedTarget, expectedEvent, testName,
|
key, ev, expectedTarget, expectedEvent, testName,
|
||||||
window = undefined) {
|
window = undefined) {
|
||||||
|
|
||||||
@ -955,7 +995,7 @@ event.synthesizeKeyExpectEvent = function (
|
|||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
* Window object. Defaults to the current window.
|
* Window object. Defaults to the current window.
|
||||||
*/
|
*/
|
||||||
event.synthesizeComposition = function (ev, window = undefined) {
|
event.synthesizeComposition = function(ev, window = undefined) {
|
||||||
let domutils = getDOMWindowUtils(window);
|
let domutils = getDOMWindowUtils(window);
|
||||||
domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
|
domutils.sendCompositionEvent(ev.type, ev.data || "", ev.locale || "");
|
||||||
};
|
};
|
||||||
@ -1000,7 +1040,7 @@ event.synthesizeComposition = function (ev, window = undefined) {
|
|||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
* Window object. Defaults to the current window.
|
* Window object. Defaults to the current window.
|
||||||
*/
|
*/
|
||||||
event.synthesizeText = function (ev, window = undefined) {
|
event.synthesizeText = function(ev, window = undefined) {
|
||||||
let domutils = getDOMWindowUtils(window);
|
let domutils = getDOMWindowUtils(window);
|
||||||
|
|
||||||
if (!ev.composition ||
|
if (!ev.composition ||
|
||||||
@ -1052,7 +1092,7 @@ event.synthesizeText = function (ev, window = undefined) {
|
|||||||
* @return {(nsIQueryContentEventResult|null)}
|
* @return {(nsIQueryContentEventResult|null)}
|
||||||
* Event's result, or null if it failed.
|
* Event's result, or null if it failed.
|
||||||
*/
|
*/
|
||||||
event.synthesizeQuerySelectedText = function (window = undefined) {
|
event.synthesizeQuerySelectedText = function(window = undefined) {
|
||||||
let domutils = getDOMWindowUtils(window);
|
let domutils = getDOMWindowUtils(window);
|
||||||
return domutils.sendQueryContentEvent(
|
return domutils.sendQueryContentEvent(
|
||||||
domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
|
domutils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
|
||||||
@ -1075,7 +1115,7 @@ event.synthesizeQuerySelectedText = function (window = undefined) {
|
|||||||
*
|
*
|
||||||
* @return True, if succeeded. Otherwise false.
|
* @return True, if succeeded. Otherwise false.
|
||||||
*/
|
*/
|
||||||
event.synthesizeSelectionSet = function (
|
event.synthesizeSelectionSet = function(
|
||||||
offset, length, reverse, window = undefined) {
|
offset, length, reverse, window = undefined) {
|
||||||
let domutils = getDOMWindowUtils(window);
|
let domutils = getDOMWindowUtils(window);
|
||||||
return domutils.sendSelectionSetEvent(offset, length, reverse);
|
return domutils.sendSelectionSetEvent(offset, length, reverse);
|
||||||
@ -1228,19 +1268,21 @@ function isPrintable(c, win = window) {
|
|||||||
return !(NON_PRINT_KEYS.includes(c));
|
return !(NON_PRINT_KEYS.includes(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
event.sendKeyDown = function (keyToSend, modifiers, document) {
|
event.sendKeyDown = function(keyToSend, modifiers, document) {
|
||||||
modifiers.type = "keydown";
|
modifiers.type = "keydown";
|
||||||
event.sendSingleKey(keyToSend, modifiers, document);
|
event.sendSingleKey(keyToSend, modifiers, document);
|
||||||
// TODO This doesn't do anything since |synthesizeKeyEvent| ignores explicit
|
// TODO: This doesn't do anything since |synthesizeKeyEvent| ignores
|
||||||
// keypress request, and instead figures out itself when to send keypress
|
// explicit keypress request, and instead figures out itself when to
|
||||||
if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"].indexOf(getKeyCode(keyToSend)) < 0) {
|
// send keypress.
|
||||||
|
if (["VK_SHIFT", "VK_CONTROL", "VK_ALT", "VK_META"]
|
||||||
|
.indexOf(getKeyCode(keyToSend)) < 0) {
|
||||||
modifiers.type = "keypress";
|
modifiers.type = "keypress";
|
||||||
event.sendSingleKey(keyToSend, modifiers, document);
|
event.sendSingleKey(keyToSend, modifiers, document);
|
||||||
}
|
}
|
||||||
delete modifiers.type;
|
delete modifiers.type;
|
||||||
};
|
};
|
||||||
|
|
||||||
event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
|
event.sendKeyUp = function(keyToSend, modifiers, window = undefined) {
|
||||||
modifiers.type = "keyup";
|
modifiers.type = "keyup";
|
||||||
event.sendSingleKey(keyToSend, modifiers, window);
|
event.sendSingleKey(keyToSend, modifiers, window);
|
||||||
delete modifiers.type;
|
delete modifiers.type;
|
||||||
@ -1253,17 +1295,18 @@ event.sendKeyUp = function (keyToSend, modifiers, window = undefined) {
|
|||||||
* Code point or normalized key value
|
* Code point or normalized key value
|
||||||
* @param {?} modifiers
|
* @param {?} modifiers
|
||||||
* Object with properties used in KeyboardEvent (shiftkey, repeat, ...)
|
* 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
|
* @param {Window=} window
|
||||||
* Window object. If |window| is undefined, the event is synthesized in
|
* Window object. If |window| is undefined, the event is synthesized
|
||||||
* current window.
|
* in current window.
|
||||||
*/
|
*/
|
||||||
event.sendSingleKey = function (keyToSend, modifiers, window = undefined) {
|
event.sendSingleKey = function(keyToSend, modifiers, window = undefined) {
|
||||||
let keyName = getKeyCode(keyToSend);
|
let keyName = getKeyCode(keyToSend);
|
||||||
if (keyName in KEYCODES_LOOKUP) {
|
if (keyName in KEYCODES_LOOKUP) {
|
||||||
// We assume that if |keyToSend| is a raw code point (like "\uE009") then
|
// We assume that if |keyToSend| is a raw code point (like "\uE009")
|
||||||
// |modifiers| does not already have correct value for corresponding
|
// then |modifiers| does not already have correct value for corresponding
|
||||||
// |modName| attribute (like ctrlKey), so that value needs to be flipped
|
// |modName| attribute (like ctrlKey), so that value needs to be flipped.
|
||||||
let modName = KEYCODES_LOOKUP[keyName];
|
let modName = KEYCODES_LOOKUP[keyName];
|
||||||
modifiers[modName] = !modifiers[modName];
|
modifiers[modName] = !modifiers[modName];
|
||||||
} else if (modifiers.shiftKey && keyName != "Shift") {
|
} else if (modifiers.shiftKey && keyName != "Shift") {
|
||||||
@ -1296,7 +1339,7 @@ function focusElement(element) {
|
|||||||
* @param {Object.<string, boolean>=} opts
|
* @param {Object.<string, boolean>=} opts
|
||||||
* @param {Window=} window
|
* @param {Window=} window
|
||||||
*/
|
*/
|
||||||
event.sendKeysToElement = function (
|
event.sendKeysToElement = function(
|
||||||
keyString, el, opts = {}, window = undefined) {
|
keyString, el, opts = {}, window = undefined) {
|
||||||
|
|
||||||
if (opts.ignoreVisibility || element.isVisible(el)) {
|
if (opts.ignoreVisibility || element.isVisible(el)) {
|
||||||
@ -1318,7 +1361,7 @@ event.sendKeysToElement = function (
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
|
event.sendEvent = function(eventType, el, modifiers = {}, opts = {}) {
|
||||||
opts.canBubble = opts.canBubble || true;
|
opts.canBubble = opts.canBubble || true;
|
||||||
|
|
||||||
let doc = el.ownerDocument || el.document;
|
let doc = el.ownerDocument || el.document;
|
||||||
@ -1333,7 +1376,7 @@ event.sendEvent = function (eventType, el, modifiers = {}, opts = {}) {
|
|||||||
el.dispatchEvent(ev);
|
el.dispatchEvent(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
event.focus = function (el, opts = {}) {
|
event.focus = function(el, opts = {}) {
|
||||||
opts.canBubble = opts.canBubble || true;
|
opts.canBubble = opts.canBubble || true;
|
||||||
let doc = el.ownerDocument || el.document;
|
let doc = el.ownerDocument || el.document;
|
||||||
let win = doc.defaultView;
|
let win = doc.defaultView;
|
||||||
@ -1343,30 +1386,30 @@ event.focus = function (el, opts = {}) {
|
|||||||
el.dispatchEvent(ev);
|
el.dispatchEvent(ev);
|
||||||
};
|
};
|
||||||
|
|
||||||
event.mouseover = function (el, modifiers = {}, opts = {}) {
|
event.mouseover = function(el, modifiers = {}, opts = {}) {
|
||||||
return event.sendEvent("mouseover", 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);
|
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);
|
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);
|
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);
|
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);
|
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);
|
return event.sendEvent("input", el, modifiers, opts);
|
||||||
};
|
};
|
||||||
|
@ -22,7 +22,7 @@ var remoteFrames = [];
|
|||||||
* An object representing a frame that Marionette has loaded a
|
* An object representing a frame that Marionette has loaded a
|
||||||
* frame script in.
|
* frame script in.
|
||||||
*/
|
*/
|
||||||
frame.RemoteFrame = function (windowId, frameId) {
|
frame.RemoteFrame = function(windowId, frameId) {
|
||||||
// outerWindowId relative to main process
|
// outerWindowId relative to main process
|
||||||
this.windowId = windowId;
|
this.windowId = windowId;
|
||||||
// actual frame relative to the windowId's frames list
|
// actual frame relative to the windowId's frames list
|
||||||
@ -61,6 +61,7 @@ frame.Manager = class {
|
|||||||
/**
|
/**
|
||||||
* Receives all messages from content messageManager.
|
* Receives all messages from content messageManager.
|
||||||
*/
|
*/
|
||||||
|
/*eslint-disable*/
|
||||||
receiveMessage(message) {
|
receiveMessage(message) {
|
||||||
switch (message.name) {
|
switch (message.name) {
|
||||||
case "MarionetteFrame:getInterruptedState":
|
case "MarionetteFrame:getInterruptedState":
|
||||||
@ -123,6 +124,7 @@ frame.Manager = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/*eslint-enable*/
|
||||||
|
|
||||||
getOopFrame(winId, frameId) {
|
getOopFrame(winId, frameId) {
|
||||||
// get original frame window
|
// get original frame window
|
||||||
@ -247,7 +249,8 @@ frame.Manager = class {
|
|||||||
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
|
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
|
||||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
|
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
|
||||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", 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:listenersAttached", this.driver);
|
||||||
mm.removeWeakMessageListener("Marionette:register", this.driver);
|
mm.removeWeakMessageListener("Marionette:register", this.driver);
|
||||||
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||||
|
@ -145,7 +145,8 @@ this.interaction = {};
|
|||||||
* @throws {InvalidElementStateError}
|
* @throws {InvalidElementStateError}
|
||||||
* If |el| is not enabled.
|
* 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);
|
const a11y = accessibility.get(strict);
|
||||||
if (element.isXULElement(el)) {
|
if (element.isXULElement(el)) {
|
||||||
yield chromeClick(el, a11y);
|
yield chromeClick(el, a11y);
|
||||||
@ -156,9 +157,8 @@ interaction.clickElement = function* (el, strict = false, specCompat = false) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
function* webdriverClickElement (el, a11y) {
|
function* webdriverClickElement(el, a11y) {
|
||||||
const win = getWindow(el);
|
const win = getWindow(el);
|
||||||
const doc = win.document;
|
|
||||||
|
|
||||||
// step 3
|
// step 3
|
||||||
if (el.localName == "input" && el.type == "file") {
|
if (el.localName == "input" && el.type == "file") {
|
||||||
@ -213,7 +213,7 @@ function* webdriverClickElement (el, a11y) {
|
|||||||
// handled by the load listener in listener.js
|
// handled by the load listener in listener.js
|
||||||
}
|
}
|
||||||
|
|
||||||
function* chromeClick (el, a11y) {
|
function* chromeClick(el, a11y) {
|
||||||
if (!atom.isElementEnabled(el)) {
|
if (!atom.isElementEnabled(el)) {
|
||||||
throw new InvalidElementStateError("Element is not enabled");
|
throw new InvalidElementStateError("Element is not enabled");
|
||||||
}
|
}
|
||||||
@ -231,7 +231,7 @@ function* chromeClick (el, a11y) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* seleniumClickElement (el, a11y) {
|
function* seleniumClickElement(el, a11y) {
|
||||||
let win = getWindow(el);
|
let win = getWindow(el);
|
||||||
|
|
||||||
let visibilityCheckEl = el;
|
let visibilityCheckEl = el;
|
||||||
@ -261,7 +261,7 @@ function* seleniumClickElement (el, a11y) {
|
|||||||
let opts = {};
|
let opts = {};
|
||||||
event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
|
event.synthesizeMouseAtPoint(centre.x, centre.y, opts, win);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select <option> element in a <select> list.
|
* Select <option> element in a <select> list.
|
||||||
@ -280,7 +280,7 @@ function* seleniumClickElement (el, a11y) {
|
|||||||
* @throws Error
|
* @throws Error
|
||||||
* If unable to find |el|'s parent <select> element.
|
* If unable to find |el|'s parent <select> element.
|
||||||
*/
|
*/
|
||||||
interaction.selectOption = function (el) {
|
interaction.selectOption = function(el) {
|
||||||
if (element.isXULElement(el)) {
|
if (element.isXULElement(el)) {
|
||||||
throw new Error("XUL dropdowns not supported");
|
throw new Error("XUL dropdowns not supported");
|
||||||
}
|
}
|
||||||
@ -288,7 +288,6 @@ interaction.selectOption = function (el) {
|
|||||||
throw new TypeError("Invalid elements");
|
throw new TypeError("Invalid elements");
|
||||||
}
|
}
|
||||||
|
|
||||||
let win = getWindow(el);
|
|
||||||
let containerEl = element.getContainer(el);
|
let containerEl = element.getContainer(el);
|
||||||
|
|
||||||
event.mouseover(containerEl);
|
event.mouseover(containerEl);
|
||||||
@ -333,7 +332,7 @@ interaction.flushEventLoop = function* (win) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
win.addEventListener("beforeunload", handleEvent, false);
|
win.addEventListener("beforeunload", handleEvent);
|
||||||
win.requestAnimationFrame(handleEvent);
|
win.requestAnimationFrame(handleEvent);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -413,7 +412,8 @@ interaction.setFormControlValue = function* (el, value) {
|
|||||||
* @param {boolean=} strict
|
* @param {boolean=} strict
|
||||||
* Enforce strict accessibility tests.
|
* 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 win = getWindow(el);
|
||||||
let a11y = accessibility.get(strict);
|
let a11y = accessibility.get(strict);
|
||||||
return a11y.getAccessible(el, true).then(acc => {
|
return a11y.getAccessible(el, true).then(acc => {
|
||||||
@ -433,7 +433,7 @@ interaction.sendKeysToElement = function (el, value, ignoreVisibility, strict =
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if element is displayed, false otherwise.
|
* True if element is displayed, false otherwise.
|
||||||
*/
|
*/
|
||||||
interaction.isElementDisplayed = function (el, strict = false) {
|
interaction.isElementDisplayed = function(el, strict = false) {
|
||||||
let win = getWindow(el);
|
let win = getWindow(el);
|
||||||
let displayed = atom.isElementDisplayed(el, win);
|
let displayed = atom.isElementDisplayed(el, win);
|
||||||
|
|
||||||
@ -453,7 +453,7 @@ interaction.isElementDisplayed = function (el, strict = false) {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if enabled, false otherwise.
|
* True if enabled, false otherwise.
|
||||||
*/
|
*/
|
||||||
interaction.isElementEnabled = function (el, strict = false) {
|
interaction.isElementEnabled = function(el, strict = false) {
|
||||||
let enabled = true;
|
let enabled = true;
|
||||||
let win = getWindow(el);
|
let win = getWindow(el);
|
||||||
|
|
||||||
@ -490,7 +490,7 @@ interaction.isElementEnabled = function (el, strict = false) {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
* True if element is selected, false otherwise.
|
* True if element is selected, false otherwise.
|
||||||
*/
|
*/
|
||||||
interaction.isElementSelected = function (el, strict = false) {
|
interaction.isElementSelected = function(el, strict = false) {
|
||||||
let selected = true;
|
let selected = true;
|
||||||
let win = getWindow(el);
|
let win = getWindow(el);
|
||||||
|
|
||||||
|
@ -7,11 +7,12 @@
|
|||||||
/**
|
/**
|
||||||
* An API which allows Marionette to handle localized content.
|
* An API which allows Marionette to handle localized content.
|
||||||
*
|
*
|
||||||
* The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko based
|
* The localization (https://mzl.la/2eUMjyF) of UI elements in Gecko
|
||||||
* applications is done via entities and properties. For static values entities
|
* based applications is done via entities and properties. For static
|
||||||
* are used, which are located in .dtd files. Whereby for dynamically updated
|
* values entities are used, which are located in .dtd files. Whereby for
|
||||||
* content the values come from .property files. Both types of elements can be
|
* dynamically updated content the values come from .property files. Both
|
||||||
* identifed via a unique id, and the translated content retrieved.
|
* types of elements can be identifed via a unique id, and the translated
|
||||||
|
* content retrieved.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
@ -43,7 +44,7 @@ this.l10n = {};
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
* The localized string for the requested entity.
|
* 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
|
// Build a string which contains all possible entity locations
|
||||||
let locations = [];
|
let locations = [];
|
||||||
urls.forEach((url, index) => {
|
urls.forEach((url, index) => {
|
||||||
@ -67,7 +68,9 @@ l10n.localizeEntity = function (urls, id) {
|
|||||||
* Retrieve the localized string for the specified property id.
|
* Retrieve the localized string for the specified property id.
|
||||||
*
|
*
|
||||||
* Example:
|
* Example:
|
||||||
* localizeProperty(["chrome://global/locale/findbar.properties"], "FastFind")
|
*
|
||||||
|
* localizeProperty(
|
||||||
|
* ["chrome://global/locale/findbar.properties"], "FastFind");
|
||||||
*
|
*
|
||||||
* @param {Array.<string>} urls
|
* @param {Array.<string>} urls
|
||||||
* Array of .properties URLs.
|
* Array of .properties URLs.
|
||||||
@ -77,7 +80,7 @@ l10n.localizeEntity = function (urls, id) {
|
|||||||
* @return {string}
|
* @return {string}
|
||||||
* The localized string for the requested property.
|
* The localized string for the requested property.
|
||||||
*/
|
*/
|
||||||
l10n.localizeProperty = function (urls, id) {
|
l10n.localizeProperty = function(urls, id) {
|
||||||
let property = null;
|
let property = null;
|
||||||
|
|
||||||
for (let url of urls) {
|
for (let url of urls) {
|
||||||
@ -86,10 +89,11 @@ l10n.localizeProperty = function (urls, id) {
|
|||||||
property = bundle.GetStringFromName(id);
|
property = bundle.GetStringFromName(id);
|
||||||
break;
|
break;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
};
|
}
|
||||||
|
|
||||||
if (property === null) {
|
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;
|
return property;
|
||||||
|
@ -18,6 +18,7 @@ var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
|||||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Log.jsm");
|
Cu.import("resource://gre/modules/Log.jsm");
|
||||||
Cu.import("resource://gre/modules/Preferences.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/Task.jsm");
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
@ -52,7 +53,7 @@ var marionetteTestName;
|
|||||||
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
var winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIDOMWindowUtils);
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
var listenerId = null; // unique ID of this listener
|
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 isRemoteBrowser = () => curContainer.frame.contentWindow !== null;
|
||||||
var previousContainer = null;
|
var previousContainer = null;
|
||||||
|
|
||||||
@ -101,8 +102,10 @@ if (logger.ownAppenders.length == 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var modalHandler = function() {
|
var modalHandler = function() {
|
||||||
// This gets called on the system app only since it receives the mozbrowserprompt event
|
// This gets called on the system app only since it receives the
|
||||||
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null, storePrevious: true});
|
// mozbrowserprompt event
|
||||||
|
sendSyncMessage("Marionette:switchedToFrame",
|
||||||
|
{frameValue: null, storePrevious: true});
|
||||||
let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
|
let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
previousContainer = curContainer;
|
previousContainer = curContainer;
|
||||||
@ -115,10 +118,10 @@ var sandboxes = new Sandboxes(() => curContainer.frame);
|
|||||||
var sandboxName = "default";
|
var sandboxName = "default";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The load listener singleton helps to keep track of active page load activities,
|
* The load listener singleton helps to keep track of active page
|
||||||
* and can be used by any command which might cause a navigation to happen. In the
|
* load activities, and can be used by any command which might cause a
|
||||||
* specific case of remoteness changes it allows to continue observing the current
|
* navigation to happen. In the specific case of remoteness changes it
|
||||||
* page load.
|
* allows to continue observing the current page load.
|
||||||
*/
|
*/
|
||||||
var loadListener = {
|
var loadListener = {
|
||||||
command_id: null,
|
command_id: null,
|
||||||
@ -132,22 +135,26 @@ var loadListener = {
|
|||||||
* Start listening for page unload/load events.
|
* Start listening for page unload/load events.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @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
|
* @param {number} startTime
|
||||||
* Unix timestap when the navitation request got triggered.
|
* Unix timestap when the navitation request got triggered.
|
||||||
* @param {boolean=} waitForUnloaded
|
* @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.command_id = command_id;
|
||||||
this.timeout = timeout;
|
this.timeout = timeout;
|
||||||
|
|
||||||
this.seenBeforeUnload = false;
|
this.seenBeforeUnload = false;
|
||||||
this.seenUnload = 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;
|
this.timerPageUnload = null;
|
||||||
|
|
||||||
// In case of a remoteness change, only wait the remaining time
|
// In case of a remoteness change, only wait the remaining time
|
||||||
@ -164,22 +171,23 @@ var loadListener = {
|
|||||||
|
|
||||||
// The events can only be received when the event listeners are
|
// The events can only be received when the event listeners are
|
||||||
// added to the currently selected frame.
|
// added to the currently selected frame.
|
||||||
curContainer.frame.addEventListener("beforeunload", this, false);
|
curContainer.frame.addEventListener("beforeunload", this);
|
||||||
curContainer.frame.addEventListener("unload", this, false);
|
curContainer.frame.addEventListener("unload", this);
|
||||||
|
|
||||||
Services.obs.addObserver(this, "outer-window-destroyed");
|
Services.obs.addObserver(this, "outer-window-destroyed");
|
||||||
} else {
|
} else {
|
||||||
addEventListener("DOMContentLoaded", loadListener, false);
|
addEventListener("DOMContentLoaded", loadListener);
|
||||||
addEventListener("pageshow", loadListener, false);
|
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 listening for page unload/load events.
|
||||||
*/
|
*/
|
||||||
stop: function () {
|
stop() {
|
||||||
if (this.timerPageLoad) {
|
if (this.timerPageLoad) {
|
||||||
this.timerPageLoad.cancel();
|
this.timerPageLoad.cancel();
|
||||||
}
|
}
|
||||||
@ -214,7 +222,7 @@ var loadListener = {
|
|||||||
/**
|
/**
|
||||||
* Callback for registered DOM events.
|
* Callback for registered DOM events.
|
||||||
*/
|
*/
|
||||||
handleEvent: function (event) {
|
handleEvent(event) {
|
||||||
let location = event.target.baseURI || event.target.location.href;
|
let location = event.target.baseURI || event.target.location.href;
|
||||||
logger.debug(`Received DOM event "${event.type}" for "${location}"`);
|
logger.debug(`Received DOM event "${event.type}" for "${location}"`);
|
||||||
|
|
||||||
@ -257,10 +265,11 @@ var loadListener = {
|
|||||||
sendError(new UnknownError("Reached error page: " +
|
sendError(new UnknownError("Reached error page: " +
|
||||||
event.target.baseURI), this.command_id);
|
event.target.baseURI), this.command_id);
|
||||||
|
|
||||||
// Return early with a page load strategy of eager, and also special-case
|
// Return early with a page load strategy of eager, and also
|
||||||
// about:blocked pages which should be treated as non-error pages but do
|
// special-case about:blocked pages which should be treated as
|
||||||
// not raise a pageshow event.
|
// non-error pages but do not raise a pageshow event.
|
||||||
} else if (capabilities.get("pageLoadStrategy") === session.PageLoadStrategy.Eager ||
|
} else if ((capabilities.get("pageLoadStrategy") ===
|
||||||
|
session.PageLoadStrategy.Eager) ||
|
||||||
/about:blocked\?/.exec(event.target.baseURI)) {
|
/about:blocked\?/.exec(event.target.baseURI)) {
|
||||||
this.stop();
|
this.stop();
|
||||||
sendOk(this.command_id);
|
sendOk(this.command_id);
|
||||||
@ -279,22 +288,24 @@ var loadListener = {
|
|||||||
/**
|
/**
|
||||||
* Callback for navigation timeout timer.
|
* Callback for navigation timeout timer.
|
||||||
*/
|
*/
|
||||||
notify: function (timer) {
|
notify(timer) {
|
||||||
switch (timer) {
|
switch (timer) {
|
||||||
case this.timerPageUnload:
|
case this.timerPageUnload:
|
||||||
// In the case when a document has a beforeunload handler registered,
|
// In the case when a document has a beforeunload handler
|
||||||
// the currently active command will return immediately due to the
|
// registered, the currently active command will return immediately
|
||||||
// modal dialog observer in proxy.js.
|
// 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.
|
// Otherwise the timeout waiting for the document to start
|
||||||
// In the common case such an event should occur pretty soon after
|
// navigating is increased by 5000 ms to ensure a possible load
|
||||||
// beforeunload, and we optimise for this.
|
// 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) {
|
if (this.seenBeforeUnload) {
|
||||||
this.seenBeforeUnload = null;
|
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
|
// If no page unload has been detected, ensure to properly stop
|
||||||
// listener, and return from the currently active command.
|
// the load listener, and return from the currently active command.
|
||||||
} else if (!this.seenUnload) {
|
} else if (!this.seenUnload) {
|
||||||
logger.debug("Canceled page load listener because no navigation " +
|
logger.debug("Canceled page load listener because no navigation " +
|
||||||
"has been detected");
|
"has been detected");
|
||||||
@ -303,17 +314,19 @@ var loadListener = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case this.timerPageLoad:
|
case this.timerPageLoad:
|
||||||
this.stop();
|
this.stop();
|
||||||
sendError(new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
|
sendError(
|
||||||
this.command_id);
|
new TimeoutError(`Timeout loading page after ${this.timeout}ms`),
|
||||||
break;
|
this.command_id);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
observe: function (subject, topic, data) {
|
observe(subject, topic, data) {
|
||||||
const winID = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
|
const win = curContainer.frame;
|
||||||
const curWinID = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
|
const winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
|
||||||
|
const curWinID = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||||
|
|
||||||
logger.debug(`Received observer notification "${topic}" for "${winID}"`);
|
logger.debug(`Received observer notification "${topic}" for "${winID}"`);
|
||||||
@ -331,16 +344,19 @@ var loadListener = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Continue to listen for page load events after a remoteness change happened.
|
* Continue to listen for page load events after a remoteness change
|
||||||
|
* happened.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @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
|
* @param {number} startTime
|
||||||
* Unix timestap when the navitation request got triggered.
|
* 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);
|
this.start(command_id, timeout, startTime, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -353,19 +369,21 @@ var loadListener = {
|
|||||||
* @param {number} command_id
|
* @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
|
* @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
|
* @param {boolean=} loadEventExpected
|
||||||
* Optional flag, which indicates that navigate has to wait for the page
|
* Optional flag, which indicates that navigate has to wait for the page
|
||||||
* finished loading.
|
* finished loading.
|
||||||
* @param {string=} url
|
* @param {string=} url
|
||||||
* Optional URL, which is used to check if a page load is expected.
|
* 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) {
|
useUnloadTimer = false) {
|
||||||
|
|
||||||
// Only wait if the page load strategy is not `none`
|
// Only wait if the page load strategy is not `none`
|
||||||
loadEventExpected = loadEventExpected &&
|
loadEventExpected = loadEventExpected &&
|
||||||
capabilities.get("pageLoadStrategy") !== session.PageLoadStrategy.None;
|
(capabilities.get("pageLoadStrategy") !==
|
||||||
|
session.PageLoadStrategy.None);
|
||||||
|
|
||||||
if (loadEventExpected) {
|
if (loadEventExpected) {
|
||||||
let startTime = new Date().getTime();
|
let startTime = new Date().getTime();
|
||||||
@ -376,15 +394,17 @@ var loadListener = {
|
|||||||
yield trigger();
|
yield trigger();
|
||||||
|
|
||||||
}).then(val => {
|
}).then(val => {
|
||||||
if (!loadEventExpected) {
|
if (!loadEventExpected) {
|
||||||
sendOk(command_id);
|
sendOk(command_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If requested setup a timer to detect a possible page load
|
// If requested setup a timer to detect a possible page load
|
||||||
if (useUnloadTimer) {
|
if (useUnloadTimer) {
|
||||||
this.timerPageUnload = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
this.timerPageUnload = Cc["@mozilla.org/timer;1"]
|
||||||
this.timerPageUnload.initWithCallback(this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
|
.createInstance(Ci.nsITimer);
|
||||||
|
this.timerPageUnload.initWithCallback(
|
||||||
|
this, 200, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||||
}
|
}
|
||||||
|
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
@ -393,21 +413,22 @@ var loadListener = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendError(err, command_id);
|
sendError(err, command_id);
|
||||||
return;
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when listener is first started up.
|
* Called when listener is first started up. The listener sends its
|
||||||
* The listener sends its unique window ID and its current URI to the actor.
|
* unique window ID and its current URI to the actor. If the actor returns
|
||||||
* If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
|
* an ID, we start the listeners. Otherwise, nothing happens.
|
||||||
*/
|
*/
|
||||||
function registerSelf() {
|
function registerSelf() {
|
||||||
let msg = {value: winUtil.outerWindowID};
|
let msg = {value: winUtil.outerWindowID};
|
||||||
logger.debug(`Register listener.js for window ${msg.value}`);
|
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);
|
let register = sendSyncMessage("Marionette:register", msg);
|
||||||
|
|
||||||
if (register[0]) {
|
if (register[0]) {
|
||||||
@ -435,15 +456,14 @@ function dispatch(fn) {
|
|||||||
throw new TypeError("Provided dispatch handler is not a function");
|
throw new TypeError("Provided dispatch handler is not a function");
|
||||||
}
|
}
|
||||||
|
|
||||||
return function (msg) {
|
return function(msg) {
|
||||||
let id = msg.json.command_id;
|
let id = msg.json.command_id;
|
||||||
|
|
||||||
let req = Task.spawn(function* () {
|
let req = Task.spawn(function* () {
|
||||||
if (typeof msg.json == "undefined" || msg.json instanceof Array) {
|
if (typeof msg.json == "undefined" || msg.json instanceof Array) {
|
||||||
return yield fn.apply(null, msg.json);
|
return yield fn.apply(null, msg.json);
|
||||||
} else {
|
|
||||||
return yield fn(msg.json);
|
|
||||||
}
|
}
|
||||||
|
return yield fn(msg.json);
|
||||||
});
|
});
|
||||||
|
|
||||||
let okOrValueResponse = rv => {
|
let okOrValueResponse = rv => {
|
||||||
@ -521,15 +541,19 @@ function startListeners() {
|
|||||||
addMessageListenerId("Marionette:goForward", goForward);
|
addMessageListenerId("Marionette:goForward", goForward);
|
||||||
addMessageListenerId("Marionette:refresh", refresh);
|
addMessageListenerId("Marionette:refresh", refresh);
|
||||||
addMessageListenerId("Marionette:findElementContent", findElementContentFn);
|
addMessageListenerId("Marionette:findElementContent", findElementContentFn);
|
||||||
addMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
|
addMessageListenerId(
|
||||||
|
"Marionette:findElementsContent", findElementsContentFn);
|
||||||
addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
|
addMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
|
||||||
addMessageListenerId("Marionette:clickElement", clickElement);
|
addMessageListenerId("Marionette:clickElement", clickElement);
|
||||||
addMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
|
addMessageListenerId(
|
||||||
|
"Marionette:getElementAttribute", getElementAttributeFn);
|
||||||
addMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
|
addMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
|
||||||
addMessageListenerId("Marionette:getElementText", getElementTextFn);
|
addMessageListenerId("Marionette:getElementText", getElementTextFn);
|
||||||
addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
|
addMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
|
||||||
addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
|
addMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
|
||||||
addMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
|
addMessageListenerId(
|
||||||
|
"Marionette:getElementValueOfCssProperty",
|
||||||
|
getElementValueOfCssPropertyFn);
|
||||||
addMessageListenerId("Marionette:getElementRect", getElementRectFn);
|
addMessageListenerId("Marionette:getElementRect", getElementRectFn);
|
||||||
addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
|
addMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
|
||||||
addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
|
addMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
|
||||||
@ -591,24 +615,37 @@ function deleteSession(msg) {
|
|||||||
removeMessageListenerId("Marionette:goBack", goBack);
|
removeMessageListenerId("Marionette:goBack", goBack);
|
||||||
removeMessageListenerId("Marionette:goForward", goForward);
|
removeMessageListenerId("Marionette:goForward", goForward);
|
||||||
removeMessageListenerId("Marionette:refresh", refresh);
|
removeMessageListenerId("Marionette:refresh", refresh);
|
||||||
removeMessageListenerId("Marionette:findElementContent", findElementContentFn);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:findElementsContent", findElementsContentFn);
|
"Marionette:findElementContent", findElementContentFn);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:findElementsContent", findElementsContentFn);
|
||||||
removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
|
removeMessageListenerId("Marionette:getActiveElement", getActiveElementFn);
|
||||||
removeMessageListenerId("Marionette:clickElement", clickElement);
|
removeMessageListenerId("Marionette:clickElement", clickElement);
|
||||||
removeMessageListenerId("Marionette:getElementAttribute", getElementAttributeFn);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:getElementProperty", getElementPropertyFn);
|
"Marionette:getElementAttribute", getElementAttributeFn);
|
||||||
removeMessageListenerId("Marionette:getElementText", getElementTextFn);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:getElementTagName", getElementTagNameFn);
|
"Marionette:getElementProperty", getElementPropertyFn);
|
||||||
removeMessageListenerId("Marionette:isElementDisplayed", isElementDisplayedFn);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:getElementValueOfCssProperty", getElementValueOfCssPropertyFn);
|
"Marionette:getElementText", getElementTextFn);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:getElementTagName", getElementTagNameFn);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:isElementDisplayed", isElementDisplayedFn);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:getElementValueOfCssProperty",
|
||||||
|
getElementValueOfCssPropertyFn);
|
||||||
removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
|
removeMessageListenerId("Marionette:getElementRect", getElementRectFn);
|
||||||
removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
|
removeMessageListenerId("Marionette:isElementEnabled", isElementEnabledFn);
|
||||||
removeMessageListenerId("Marionette:isElementSelected", isElementSelectedFn);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:sendKeysToElement", sendKeysToElementFn);
|
"Marionette:isElementSelected", isElementSelectedFn);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:sendKeysToElement", sendKeysToElementFn);
|
||||||
removeMessageListenerId("Marionette:clearElement", clearElementFn);
|
removeMessageListenerId("Marionette:clearElement", clearElementFn);
|
||||||
removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
|
removeMessageListenerId("Marionette:switchToFrame", switchToFrame);
|
||||||
removeMessageListenerId("Marionette:switchToParentFrame", switchToParentFrame);
|
removeMessageListenerId(
|
||||||
removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
|
"Marionette:switchToParentFrame", switchToParentFrame);
|
||||||
|
removeMessageListenerId(
|
||||||
|
"Marionette:switchToShadowRoot", switchToShadowRootFn);
|
||||||
removeMessageListenerId("Marionette:deleteSession", deleteSession);
|
removeMessageListenerId("Marionette:deleteSession", deleteSession);
|
||||||
removeMessageListenerId("Marionette:sleepSession", sleepSession);
|
removeMessageListenerId("Marionette:sleepSession", sleepSession);
|
||||||
removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
|
removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
|
||||||
@ -616,7 +653,7 @@ function deleteSession(msg) {
|
|||||||
|
|
||||||
seenEls.clear();
|
seenEls.clear();
|
||||||
// reset container frame to the top-most frame
|
// reset container frame to the top-most frame
|
||||||
curContainer = { frame: content, shadowRoot: null };
|
curContainer = {frame: content, shadowRoot: null};
|
||||||
curContainer.frame.focus();
|
curContainer.frame.focus();
|
||||||
legacyactions.touchIds = {};
|
legacyactions.touchIds = {};
|
||||||
if (action.inputStateMap !== undefined) {
|
if (action.inputStateMap !== undefined) {
|
||||||
@ -690,35 +727,32 @@ function resetValues() {
|
|||||||
action.inputsToCancel = [];
|
action.inputsToCancel = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if our context was interrupted
|
|
||||||
*/
|
|
||||||
function wasInterrupted() {
|
function wasInterrupted() {
|
||||||
if (previousContainer) {
|
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) {
|
if (element.id.indexOf("modal-dialog") == -1) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
|
return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkForInterrupted() {
|
function checkForInterrupted() {
|
||||||
if (wasInterrupted()) {
|
if (wasInterrupted()) {
|
||||||
if (previousContainer) {
|
// if previousContainer is set, then we're in a single process
|
||||||
// if previousContainer is set, then we're in a single process environment
|
// environment
|
||||||
curContainer = legacyactions.container = previousContainer;
|
if (previousContainer) {
|
||||||
previousContainer = null;
|
curContainer = legacyactions.container = previousContainer;
|
||||||
}
|
previousContainer = null;
|
||||||
else {
|
// else we're in OOP environment, so we'll switch to the original
|
||||||
//else we're in OOP environment, so we'll switch to the original OOP frame
|
// OOP frame
|
||||||
sendSyncMessage("Marionette:switchToModalOrigin");
|
} else {
|
||||||
}
|
sendSyncMessage("Marionette:switchToModalOrigin");
|
||||||
sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
|
|
||||||
}
|
}
|
||||||
|
sendSyncMessage("Marionette:switchedToFrame", {restorePrevious: true});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function* execute(script, args, timeout, opts) {
|
function* execute(script, args, timeout, opts) {
|
||||||
@ -744,36 +778,58 @@ function* executeInSandbox(script, args, timeout, opts) {
|
|||||||
return evaluate.toJSON(res, seenEls);
|
return evaluate.toJSON(res, seenEls);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* This function creates a touch event given a touch type and a touch
|
|
||||||
*/
|
|
||||||
function emitTouchEvent(type, touch) {
|
function emitTouchEvent(type, touch) {
|
||||||
if (!wasInterrupted()) {
|
if (!wasInterrupted()) {
|
||||||
logger.info(`Emitting Touch event of type ${type} to element with id: ${touch.target.id} ` +
|
logger.info(`Emitting Touch event of type ${type} ` +
|
||||||
`and tag name: ${touch.target.tagName} at coordinates (${touch.clientX}, ` +
|
`to element with id: ${touch.target.id} ` +
|
||||||
`${touch.clientY}) relative to the viewport`);
|
`and tag name: ${touch.target.tagName} ` +
|
||||||
|
`at coordinates (${touch.clientX}), ` +
|
||||||
|
`${touch.clientY}) relative to the viewport`);
|
||||||
|
|
||||||
var docShell = curContainer.frame.document.defaultView.
|
const win = curContainer.frame;
|
||||||
QueryInterface(Components.interfaces.nsIInterfaceRequestor).
|
let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
getInterface(Components.interfaces.nsIWebNavigation).
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
QueryInterface(Components.interfaces.nsIDocShell);
|
.QueryInterface(Ci.nsIDocShell);
|
||||||
if (docShell.asyncPanZoomEnabled && legacyactions.scrolling) {
|
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");
|
let index = sendSyncMessage("MarionetteFrame:getCurrentFrameId");
|
||||||
|
|
||||||
// only call emitTouchEventForIFrame if we're inside an iframe.
|
// only call emitTouchEventForIFrame if we're inside an iframe.
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
sendSyncMessage("Marionette:emitTouchEvent",
|
let ev = {
|
||||||
{ index: index, type: type, id: touch.identifier,
|
index,
|
||||||
clientX: touch.clientX, clientY: touch.clientY,
|
type,
|
||||||
screenX: touch.screenX, screenY: touch.screenY,
|
id: touch.identifier,
|
||||||
radiusX: touch.radiusX, radiusY: touch.radiusY,
|
clientX: touch.clientX,
|
||||||
rotation: touch.rotationAngle, force: touch.force });
|
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;
|
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);
|
// we get here if we're not in asyncPacZoomEnabled land, or if we're
|
||||||
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.clientX], [touch.clientY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
|
// the main process
|
||||||
|
let domWindowUtils = win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
domWindowUtils.sendTouchEvent(
|
||||||
|
type,
|
||||||
|
[touch.identifier],
|
||||||
|
[touch.clientX],
|
||||||
|
[touch.clientY],
|
||||||
|
[touch.radiusX],
|
||||||
|
[touch.radiusY],
|
||||||
|
[touch.rotationAngle],
|
||||||
|
[touch.force],
|
||||||
|
1,
|
||||||
|
0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -785,7 +841,8 @@ function singleTap(id, corx, cory) {
|
|||||||
// after this block, the element will be scrolled into view
|
// after this block, the element will be scrolled into view
|
||||||
let visible = element.isVisible(el, corx, cory);
|
let visible = element.isVisible(el, corx, cory);
|
||||||
if (!visible) {
|
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"));
|
let a11y = accessibility.get(capabilities.get("moz:accessibilityChecks"));
|
||||||
@ -799,8 +856,8 @@ function singleTap(id, corx, cory) {
|
|||||||
if (!legacyactions.mouseEventsOnly) {
|
if (!legacyactions.mouseEventsOnly) {
|
||||||
let touchId = legacyactions.nextTouchId++;
|
let touchId = legacyactions.nextTouchId++;
|
||||||
let touch = createATouch(el, c.x, c.y, touchId);
|
let touch = createATouch(el, c.x, c.y, touchId);
|
||||||
emitTouchEvent('touchstart', touch);
|
emitTouchEvent("touchstart", touch);
|
||||||
emitTouchEvent('touchend', touch);
|
emitTouchEvent("touchend", touch);
|
||||||
}
|
}
|
||||||
legacyactions.mouseTap(el.ownerDocument, c.x, c.y);
|
legacyactions.mouseTap(el.ownerDocument, c.x, c.y);
|
||||||
});
|
});
|
||||||
@ -814,8 +871,17 @@ function createATouch(el, corx, cory, touchId) {
|
|||||||
let doc = el.ownerDocument;
|
let doc = el.ownerDocument;
|
||||||
let win = doc.defaultView;
|
let win = doc.defaultView;
|
||||||
let [clientX, clientY, pageX, pageY, screenX, screenY] =
|
let [clientX, clientY, pageX, pageY, screenX, screenY] =
|
||||||
legacyactions.getCoordinateInfo(el, corx, cory);
|
legacyactions.getCoordinateInfo(el, corx, cory);
|
||||||
let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
|
let atouch = doc.createTouch(
|
||||||
|
win,
|
||||||
|
el,
|
||||||
|
touchId,
|
||||||
|
pageX,
|
||||||
|
pageY,
|
||||||
|
screenX,
|
||||||
|
screenY,
|
||||||
|
clientX,
|
||||||
|
clientY);
|
||||||
return atouch;
|
return atouch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -833,12 +899,13 @@ function* performActions(msg) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* The Release Actions command is used to release all the keys and pointer
|
* 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
|
* buttons that are currently depressed. This causes events to be fired
|
||||||
* the state was released by an explicit series of actions. It also clears all
|
* as if the state was released by an explicit series of actions. It also
|
||||||
* the internal state of the virtual devices.
|
* clears all the internal state of the virtual devices.
|
||||||
*/
|
*/
|
||||||
function* releaseActions() {
|
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.inputsToCancel.length = 0;
|
||||||
action.inputStateMap.clear();
|
action.inputStateMap.clear();
|
||||||
}
|
}
|
||||||
@ -859,26 +926,23 @@ function actionChain(chain, touchId) {
|
|||||||
touchProvider);
|
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) {
|
function emitMultiEvents(type, touch, touches) {
|
||||||
let target = touch.target;
|
let target = touch.target;
|
||||||
let doc = target.ownerDocument;
|
let doc = target.ownerDocument;
|
||||||
let win = doc.defaultView;
|
let win = doc.defaultView;
|
||||||
// touches that are in the same document
|
// touches that are in the same document
|
||||||
let documentTouches = doc.createTouchList(touches.filter(function (t) {
|
let documentTouches = doc.createTouchList(touches.filter(function(t) {
|
||||||
return ((t.target.ownerDocument === doc) && (type != 'touchcancel'));
|
return ((t.target.ownerDocument === doc) && (type != "touchcancel"));
|
||||||
}));
|
}));
|
||||||
// touches on the same target
|
// touches on the same target
|
||||||
let targetTouches = doc.createTouchList(touches.filter(function (t) {
|
let targetTouches = doc.createTouchList(touches.filter(function(t) {
|
||||||
return ((t.target === target) && ((type != 'touchcancel') || (type != 'touchend')));
|
return ((t.target === target) &&
|
||||||
|
((type != "touchcancel") || (type != "touchend")));
|
||||||
}));
|
}));
|
||||||
// Create changed touches
|
// Create changed touches
|
||||||
let changedTouches = doc.createTouchList(touch);
|
let changedTouches = doc.createTouchList(touch);
|
||||||
// Create the event object
|
// Create the event object
|
||||||
let event = doc.createEvent('TouchEvent');
|
let event = doc.createEvent("TouchEvent");
|
||||||
event.initTouchEvent(type,
|
event.initTouchEvent(type,
|
||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
@ -891,11 +955,7 @@ function emitMultiEvents(type, touch, touches) {
|
|||||||
target.dispatchEvent(event);
|
target.dispatchEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function setDispatch(batches, touches, batchIndex = 0) {
|
||||||
* 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) {
|
|
||||||
// check if all the sets have been fired
|
// check if all the sets have been fired
|
||||||
if (batchIndex >= batches.length) {
|
if (batchIndex >= batches.length) {
|
||||||
multiLast = {};
|
multiLast = {};
|
||||||
@ -912,8 +972,6 @@ function setDispatch(batches, touches, batchIndex=0) {
|
|||||||
let command;
|
let command;
|
||||||
// touch that will be created for the finger
|
// touch that will be created for the finger
|
||||||
let el;
|
let el;
|
||||||
let corx;
|
|
||||||
let cory;
|
|
||||||
let touch;
|
let touch;
|
||||||
let lastTouch;
|
let lastTouch;
|
||||||
let touchIndex;
|
let touchIndex;
|
||||||
@ -940,7 +998,8 @@ function setDispatch(batches, touches, batchIndex=0) {
|
|||||||
|
|
||||||
case "release":
|
case "release":
|
||||||
touch = multiLast[touchId];
|
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);
|
touchIndex = touches.indexOf(touch);
|
||||||
touches.splice(touchIndex, 1);
|
touches.splice(touchIndex, 1);
|
||||||
emitMultiEvents("touchend", touch, touches);
|
emitMultiEvents("touchend", touch, touches);
|
||||||
@ -962,14 +1021,24 @@ function setDispatch(batches, touches, batchIndex=0) {
|
|||||||
touchIndex = touches.indexOf(lastTouch);
|
touchIndex = touches.indexOf(lastTouch);
|
||||||
let doc = el.ownerDocument;
|
let doc = el.ownerDocument;
|
||||||
let win = doc.defaultView;
|
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
|
// since x and y are relative to the last touch, therefore,
|
||||||
let clientX = lastTouch.clientX + pack[2],
|
// it's relative to the position of the last touch
|
||||||
clientY = lastTouch.clientY + pack[3];
|
let clientX = lastTouch.clientX + pack[2];
|
||||||
let pageX = clientX + win.pageXOffset,
|
let clientY = lastTouch.clientY + pack[3];
|
||||||
pageY = clientY + win.pageYOffset;
|
let pageX = clientX + win.pageXOffset;
|
||||||
let screenX = clientX + win.mozInnerScreenX,
|
let pageY = clientY + win.pageYOffset;
|
||||||
screenY = clientY + win.mozInnerScreenY;
|
let screenX = clientX + win.mozInnerScreenX;
|
||||||
touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
|
let screenY = clientY + win.mozInnerScreenY;
|
||||||
|
touch = doc.createTouch(
|
||||||
|
win,
|
||||||
|
el,
|
||||||
|
touchId,
|
||||||
|
pageX,
|
||||||
|
pageY,
|
||||||
|
screenX,
|
||||||
|
screenY,
|
||||||
|
clientX,
|
||||||
|
clientY);
|
||||||
touches[touchIndex] = touch;
|
touches[touchIndex] = touch;
|
||||||
multiLast[touchId] = touch;
|
multiLast[touchId] = touch;
|
||||||
emitMultiEvents("touchmove", touch, touches);
|
emitMultiEvents("touchmove", touch, touches);
|
||||||
@ -1012,7 +1081,8 @@ function multiAction(args, maxLen) {
|
|||||||
let row = [];
|
let row = [];
|
||||||
for (let j = 0; j < commandArray.length; j++) {
|
for (let j = 0; j < commandArray.length; j++) {
|
||||||
if (typeof commandArray[j][i] != "undefined") {
|
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 = commandArray[j][i];
|
||||||
temp.unshift(j);
|
temp.unshift(j);
|
||||||
row.push(temp);
|
row.push(temp);
|
||||||
@ -1021,9 +1091,11 @@ function multiAction(args, maxLen) {
|
|||||||
concurrentEvent.push(row);
|
concurrentEvent.push(row);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now concurrent event is made of sets where each set contain a list of actions that need to be fired.
|
// Now concurrent event is made of sets where each set contain a list
|
||||||
// note: each action belongs to a different finger
|
// of actions that need to be fired.
|
||||||
// pendingTouches keeps track of current touches that's on the screen
|
//
|
||||||
|
// But note that each action belongs to a different finger
|
||||||
|
// pendingTouches keeps track of current touches that's on the screen.
|
||||||
let pendingTouches = [];
|
let pendingTouches = [];
|
||||||
setDispatch(concurrentEvent, pendingTouches);
|
setDispatch(concurrentEvent, pendingTouches);
|
||||||
}
|
}
|
||||||
@ -1038,31 +1110,35 @@ function cancelRequest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This implements the latter part of a get request (for the case we need to resume one
|
* This implements the latter part of a get request (for the case we
|
||||||
* when a remoteness update happens in the middle of a navigate request). This is most of
|
* need to resume one when a remoteness update happens in the middle of a
|
||||||
* of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
|
* 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
|
* @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
|
* @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
|
* @param {number} startTime
|
||||||
* Unix timestap when the navitation request got triggred.
|
* Unix timestap when the navitation request got triggred.
|
||||||
*/
|
*/
|
||||||
function waitForPageLoaded(msg) {
|
function waitForPageLoaded(msg) {
|
||||||
let {command_id, pageTimeout, startTime} = msg.json;
|
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
|
* Navigate to the given URL. The operation will be performed on the
|
||||||
* current browsing context, which means it handles the case where we
|
* current browsing context, which means it handles the case where we
|
||||||
* navigate within an iframe. All other navigation is handled by the
|
* navigate within an iframe. All other navigation is handled by the driver
|
||||||
* driver (in chrome space).
|
* (in chrome space).
|
||||||
*/
|
*/
|
||||||
function get(msg) {
|
function get(msg) {
|
||||||
let {command_id, pageTimeout, url, loadEventExpected=null} = msg.json;
|
let {command_id, pageTimeout, url, loadEventExpected = null} = msg.json;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof url == "string") {
|
if (typeof url == "string") {
|
||||||
@ -1072,7 +1148,8 @@ function get(msg) {
|
|||||||
loadEventExpected = navigate.isLoadEventExpected(requestedURL);
|
loadEventExpected = navigate.isLoadEventExpected(requestedURL);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
sendError(new InvalidArgumentError("Malformed URL: " + e.message), command_id);
|
let err = new InvalidArgumentError("Malformed URL: " + e.message);
|
||||||
|
sendError(err, command_id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1095,9 +1172,11 @@ function get(msg) {
|
|||||||
* of the current browsing context.
|
* of the current browsing context.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @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) {
|
function goBack(msg) {
|
||||||
let {command_id, pageTimeout} = msg.json;
|
let {command_id, pageTimeout} = msg.json;
|
||||||
@ -1117,9 +1196,11 @@ function goBack(msg) {
|
|||||||
* of the current browsing context.
|
* of the current browsing context.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @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) {
|
function goForward(msg) {
|
||||||
let {command_id, pageTimeout} = msg.json;
|
let {command_id, pageTimeout} = msg.json;
|
||||||
@ -1135,12 +1216,15 @@ function goForward(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Causes the browser to reload the page in in current top-level browsing context.
|
* Causes the browser to reload the page in in current top-level browsing
|
||||||
|
* context.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @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) {
|
function refresh(msg) {
|
||||||
let {command_id, pageTimeout} = msg.json;
|
let {command_id, pageTimeout} = msg.json;
|
||||||
@ -1223,11 +1307,13 @@ function getActiveElement() {
|
|||||||
* Send click event to element.
|
* Send click event to element.
|
||||||
*
|
*
|
||||||
* @param {number} command_id
|
* @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
|
* @param {WebElement} id
|
||||||
* Reference to the web element to click.
|
* Reference to the web element to click.
|
||||||
* @param {number} pageTimeout
|
* @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) {
|
function clickElement(msg) {
|
||||||
let {command_id, id, pageTimeout} = msg.json;
|
let {command_id, id, pageTimeout} = msg.json;
|
||||||
@ -1259,12 +1345,10 @@ function getElementAttribute(id, name) {
|
|||||||
if (element.isBooleanAttribute(el, name)) {
|
if (element.isBooleanAttribute(el, name)) {
|
||||||
if (el.hasAttribute(name)) {
|
if (el.hasAttribute(name)) {
|
||||||
return "true";
|
return "true";
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
} else {
|
return null;
|
||||||
return el.getAttribute(name);
|
|
||||||
}
|
}
|
||||||
|
return el.getAttribute(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getElementProperty(id, name) {
|
function getElementProperty(id, name) {
|
||||||
@ -1344,9 +1428,9 @@ function getElementRect(id) {
|
|||||||
let clientRect = el.getBoundingClientRect();
|
let clientRect = el.getBoundingClientRect();
|
||||||
return {
|
return {
|
||||||
x: clientRect.x + curContainer.frame.pageXOffset,
|
x: clientRect.x + curContainer.frame.pageXOffset,
|
||||||
y: clientRect.y + curContainer.frame.pageYOffset,
|
y: clientRect.y + curContainer.frame.pageYOffset,
|
||||||
width: clientRect.width,
|
width: clientRect.width,
|
||||||
height: clientRect.height
|
height: clientRect.height,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1414,13 +1498,14 @@ function clearElement(id) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch the current context to the specified host's Shadow DOM.
|
* Switch the current context to the specified host's Shadow DOM.
|
||||||
|
*
|
||||||
* @param {WebElement} id
|
* @param {WebElement} id
|
||||||
* Reference to web element.
|
* Reference to web element.
|
||||||
*/
|
*/
|
||||||
function switchToShadowRoot(id) {
|
function switchToShadowRoot(id) {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
// If no host element is passed, attempt to find a parent shadow root or, if
|
// If no host element is passed, attempt to find a parent shadow
|
||||||
// none found, unset the current shadow root
|
// root or, if none found, unset the current shadow root
|
||||||
if (curContainer.shadowRoot) {
|
if (curContainer.shadowRoot) {
|
||||||
let parent;
|
let parent;
|
||||||
try {
|
try {
|
||||||
@ -1443,25 +1528,24 @@ function switchToShadowRoot(id) {
|
|||||||
let hostEl = seenEls.get(id, curContainer);
|
let hostEl = seenEls.get(id, curContainer);
|
||||||
foundShadowRoot = hostEl.shadowRoot;
|
foundShadowRoot = hostEl.shadowRoot;
|
||||||
if (!foundShadowRoot) {
|
if (!foundShadowRoot) {
|
||||||
throw new NoSuchElementError('Unable to locate shadow root: ' + id);
|
throw new NoSuchElementError("Unable to locate shadow root: " + id);
|
||||||
}
|
}
|
||||||
curContainer.shadowRoot = foundShadowRoot;
|
curContainer.shadowRoot = foundShadowRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch to the parent frame of the current Frame. If the frame is the top most
|
* Switch to the parent frame of the current frame. If the frame is the
|
||||||
* is the current frame then no action will happen.
|
* top most is the current frame then no action will happen.
|
||||||
*/
|
*/
|
||||||
function switchToParentFrame(msg) {
|
function switchToParentFrame(msg) {
|
||||||
let command_id = msg.json.command_id;
|
curContainer.frame = curContainer.frame.parent;
|
||||||
curContainer.frame = curContainer.frame.parent;
|
let parentElement = seenEls.add(curContainer.frame);
|
||||||
let parentElement = seenEls.add(curContainer.frame);
|
|
||||||
|
|
||||||
sendSyncMessage(
|
sendSyncMessage(
|
||||||
"Marionette:switchedToFrame", {frameValue: parentElement});
|
"Marionette:switchedToFrame", {frameValue: parentElement});
|
||||||
|
|
||||||
sendOk(msg.json.command_id);
|
sendOk(msg.json.command_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Switch to frame given either the server-assigned element id,
|
* Switch to frame given either the server-assigned element id,
|
||||||
@ -1476,24 +1560,27 @@ function switchToFrame(msg) {
|
|||||||
// Check of the curContainer.frame reference is dead
|
// Check of the curContainer.frame reference is dead
|
||||||
try {
|
try {
|
||||||
frames = curContainer.frame.frames;
|
frames = curContainer.frame.frames;
|
||||||
//Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
|
// Until Bug 761935 lands, we won't have multiple nested OOP
|
||||||
//parWindow will refer to the iframe above the nested OOP frame.
|
// iframes. We will only have one. parWindow will refer to the iframe
|
||||||
|
// above the nested OOP frame.
|
||||||
parWindow = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
|
parWindow = curContainer.frame.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// We probably have a dead compartment so accessing it is going to make Firefox
|
// We probably have a dead compartment so accessing it is going to
|
||||||
// very upset. Let's now try redirect everything to the top frame even if the
|
// make Firefox very upset. Let's now try redirect everything to the
|
||||||
// user has given us a frame since search doesnt look up.
|
// top frame even if the user has given us a frame since search doesnt
|
||||||
|
// look up.
|
||||||
msg.json.id = null;
|
msg.json.id = null;
|
||||||
msg.json.element = 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
|
// returning to root frame
|
||||||
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
|
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
|
||||||
|
|
||||||
curContainer.frame = content;
|
curContainer.frame = content;
|
||||||
if(msg.json.focus == true) {
|
if (msg.json.focus == true) {
|
||||||
curContainer.frame.focus();
|
curContainer.frame.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1513,8 +1600,11 @@ function switchToFrame(msg) {
|
|||||||
if (frames.length > 0) {
|
if (frames.length > 0) {
|
||||||
for (let i = 0; i < frames.length; i++) {
|
for (let i = 0; i < frames.length; i++) {
|
||||||
// use XPCNativeWrapper to compare elements; see bug 834266
|
// use XPCNativeWrapper to compare elements; see bug 834266
|
||||||
if (XPCNativeWrapper(frames[i].frameElement) == XPCNativeWrapper(wantedFrame)) {
|
let frameEl = frames[i].frameElement;
|
||||||
curContainer.frame = frames[i].frameElement;
|
let wrappedItem = new XPCNativeWrapper(frameEl);
|
||||||
|
let wrappedWanted = new XPCNativeWrapper(wantedFrame);
|
||||||
|
if (wrappedItem == wrappedWanted) {
|
||||||
|
curContainer.frame = frameEl;
|
||||||
foundFrame = i;
|
foundFrame = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1524,9 +1614,13 @@ function switchToFrame(msg) {
|
|||||||
// Either the frame has been removed or we have a OOP frame
|
// 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
|
// so lets just get all the iframes and do a quick loop before
|
||||||
// throwing in the towel
|
// 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++) {
|
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];
|
curContainer.frame = iframes[i];
|
||||||
foundFrame = i;
|
foundFrame = i;
|
||||||
}
|
}
|
||||||
@ -1535,20 +1629,19 @@ function switchToFrame(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundFrame === null) {
|
if (foundFrame === null) {
|
||||||
if (typeof(msg.json.id) === 'number') {
|
if (typeof(msg.json.id) === "number") {
|
||||||
try {
|
try {
|
||||||
foundFrame = frames[msg.json.id].frameElement;
|
foundFrame = frames[msg.json.id].frameElement;
|
||||||
if (foundFrame !== null) {
|
if (foundFrame !== null) {
|
||||||
curContainer.frame = foundFrame;
|
curContainer.frame = foundFrame;
|
||||||
foundFrame = seenEls.add(curContainer.frame);
|
foundFrame = seenEls.add(curContainer.frame);
|
||||||
}
|
} else {
|
||||||
else {
|
// If foundFrame is null at this point then we have the top
|
||||||
// If foundFrame is null at this point then we have the top level browsing
|
// level browsing context so should treat it accordingly.
|
||||||
// context so should treat it accordingly.
|
sendSyncMessage("Marionette:switchedToFrame", {frameValue: null});
|
||||||
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null});
|
|
||||||
curContainer.frame = content;
|
curContainer.frame = content;
|
||||||
|
|
||||||
if(msg.json.focus == true) {
|
if (msg.json.focus == true) {
|
||||||
curContainer.frame.focus();
|
curContainer.frame.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1559,7 +1652,8 @@ function switchToFrame(msg) {
|
|||||||
// Since window.frames does not return OOP frames it will throw
|
// 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
|
// and we land up here. Let's not give up and check if there are
|
||||||
// iframes and switch to the indexed frame there
|
// 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) {
|
if (msg.json.id >= 0 && msg.json.id < iframes.length) {
|
||||||
curContainer.frame = iframes[msg.json.id];
|
curContainer.frame = iframes[msg.json.id];
|
||||||
foundFrame = msg.json.id;
|
foundFrame = msg.json.id;
|
||||||
@ -1569,15 +1663,17 @@ function switchToFrame(msg) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (foundFrame === null) {
|
if (foundFrame === null) {
|
||||||
sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
|
let failedFrame = msg.json.id || msg.json.element;
|
||||||
return true;
|
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
|
// send a synchronous message to let the server update the currently active
|
||||||
// frame element (for getActiveFrame)
|
// frame element (for getActiveFrame)
|
||||||
let frameValue = evaluate.toJSON(
|
let frameValue = evaluate.toJSON(
|
||||||
curContainer.frame.wrappedJSObject, seenEls)[element.Key];
|
curContainer.frame.wrappedJSObject, seenEls)[element.Key];
|
||||||
sendSyncMessage("Marionette:switchedToFrame", {frameValue: frameValue});
|
sendSyncMessage("Marionette:switchedToFrame", {"frameValue": frameValue});
|
||||||
|
|
||||||
if (curContainer.frame.contentWindow === null) {
|
if (curContainer.frame.contentWindow === null) {
|
||||||
// The frame we want to switch to is a remote/OOP frame;
|
// The frame we want to switch to is a remote/OOP frame;
|
||||||
@ -1740,7 +1836,7 @@ function* reftestWait(url, remote) {
|
|||||||
// so that setTimeout(fn, 0) in the load event has run
|
// so that setTimeout(fn, 0) in the load event has run
|
||||||
reftestWait = document.documentElement.classList.contains("reftest-wait");
|
reftestWait = document.documentElement.classList.contains("reftest-wait");
|
||||||
yield new Promise(resolve => win.setTimeout(resolve, 0));
|
yield new Promise(resolve => win.setTimeout(resolve, 0));
|
||||||
};
|
}
|
||||||
|
|
||||||
let root = document.documentElement;
|
let root = document.documentElement;
|
||||||
if (reftestWait) {
|
if (reftestWait) {
|
||||||
|
@ -21,7 +21,7 @@ this.EXPORTED_SYMBOLS = [
|
|||||||
|
|
||||||
const logger = Log.repository.getLogger("Marionette");
|
const logger = Log.repository.getLogger("Marionette");
|
||||||
|
|
||||||
this.MessageOrigin = {
|
const MessageOrigin = {
|
||||||
Client: 0,
|
Client: 0,
|
||||||
Server: 1,
|
Server: 1,
|
||||||
};
|
};
|
||||||
@ -42,7 +42,7 @@ this.Message = {};
|
|||||||
* @throws {TypeError}
|
* @throws {TypeError}
|
||||||
* If the message type is not recognised.
|
* If the message type is not recognised.
|
||||||
*/
|
*/
|
||||||
Message.fromMsg = function (data) {
|
Message.fromMsg = function(data) {
|
||||||
switch (data[0]) {
|
switch (data[0]) {
|
||||||
case Command.TYPE:
|
case Command.TYPE:
|
||||||
return Command.fromMsg(data);
|
return Command.fromMsg(data);
|
||||||
@ -98,7 +98,7 @@ Message.fromMsg = function (data) {
|
|||||||
* @param {Object<string, ?>} params
|
* @param {Object<string, ?>} params
|
||||||
* Command parameters.
|
* Command parameters.
|
||||||
*/
|
*/
|
||||||
this.Command = class {
|
class Command {
|
||||||
constructor(msgID, name, params = {}) {
|
constructor(msgID, name, params = {}) {
|
||||||
this.id = assert.integer(msgID);
|
this.id = assert.integer(msgID);
|
||||||
this.name = assert.string(name);
|
this.name = assert.string(name);
|
||||||
@ -148,11 +148,10 @@ this.Command = class {
|
|||||||
|
|
||||||
return new Command(msgID, name, params);
|
return new Command(msgID, name, params);
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Command.TYPE = 0;
|
Command.TYPE = 0;
|
||||||
|
|
||||||
|
|
||||||
const validator = {
|
const validator = {
|
||||||
exclusionary: {
|
exclusionary: {
|
||||||
"capabilities": ["error", "value"],
|
"capabilities": ["error", "value"],
|
||||||
@ -161,7 +160,7 @@ const validator = {
|
|||||||
"value": ["error", "sessionId", "capabilities"],
|
"value": ["error", "sessionId", "capabilities"],
|
||||||
},
|
},
|
||||||
|
|
||||||
set: function (obj, prop, val) {
|
set(obj, prop, val) {
|
||||||
let tests = this.exclusionary[prop];
|
let tests = this.exclusionary[prop];
|
||||||
if (tests) {
|
if (tests) {
|
||||||
for (let t of tests) {
|
for (let t of tests) {
|
||||||
@ -174,7 +173,7 @@ const validator = {
|
|||||||
obj[prop] = val;
|
obj[prop] = val;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The response body is exposed as an argument to commands.
|
* The response body is exposed as an argument to commands.
|
||||||
@ -188,7 +187,7 @@ const validator = {
|
|||||||
* {@code value}, {@code sessionId}, or {@code capabilities} have been
|
* {@code value}, {@code sessionId}, or {@code capabilities} have been
|
||||||
* set previously will cause an error.
|
* 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
|
* Represents the response returned from the remote end after execution
|
||||||
@ -209,7 +208,7 @@ this.ResponseBody = () => new Proxy({}, validator);
|
|||||||
* @param {function(Response|Message)} respHandler
|
* @param {function(Response|Message)} respHandler
|
||||||
* Function callback called on sending the response.
|
* Function callback called on sending the response.
|
||||||
*/
|
*/
|
||||||
this.Response = class {
|
class Response {
|
||||||
constructor(msgID, respHandler = () => {}) {
|
constructor(msgID, respHandler = () => {}) {
|
||||||
this.id = assert.integer(msgID);
|
this.id = assert.integer(msgID);
|
||||||
this.respHandler_ = assert.callable(respHandler);
|
this.respHandler_ = assert.callable(respHandler);
|
||||||
@ -292,6 +291,6 @@ this.Response = class {
|
|||||||
resp.body = body;
|
resp.body = body;
|
||||||
return resp;
|
return resp;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
Response.TYPE = 1;
|
Response.TYPE = 1;
|
||||||
|
@ -10,6 +10,8 @@ Cu.import("resource://gre/modules/Services.jsm");
|
|||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["modal"];
|
this.EXPORTED_SYMBOLS = ["modal"];
|
||||||
|
|
||||||
|
const COMMON_DIALOG = "chrome://global/content/commonDialog.xul";
|
||||||
|
|
||||||
const isFirefox = () => Services.appinfo.name == "Firefox";
|
const isFirefox = () => Services.appinfo.name == "Firefox";
|
||||||
|
|
||||||
this.modal = {};
|
this.modal = {};
|
||||||
@ -18,8 +20,8 @@ modal = {
|
|||||||
TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
|
TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
|
||||||
handlers: {
|
handlers: {
|
||||||
"common-dialog-loaded": new Set(),
|
"common-dialog-loaded": new Set(),
|
||||||
"tabmodal-dialog-loaded": new Set()
|
"tabmodal-dialog-loaded": new Set(),
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,7 +39,7 @@ modal = {
|
|||||||
* {@code modal.COMMON_DIALOG_LOADED} or
|
* {@code modal.COMMON_DIALOG_LOADED} or
|
||||||
* {@code modal.TABMODAL_DIALOG_LOADED}.
|
* {@code modal.TABMODAL_DIALOG_LOADED}.
|
||||||
*/
|
*/
|
||||||
modal.addHandler = function (handler) {
|
modal.addHandler = function(handler) {
|
||||||
if (!isFirefox()) {
|
if (!isFirefox()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -55,28 +57,32 @@ modal.addHandler = function (handler) {
|
|||||||
* Reference to the browser context to check for existent dialogs.
|
* Reference to the browser context to check for existent dialogs.
|
||||||
*
|
*
|
||||||
* @return {modal.Dialog}
|
* @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) {
|
modal.findModalDialogs = function(context) {
|
||||||
// First check if there is a modal dialog already present for the current browser window.
|
// First check if there is a modal dialog already present for the
|
||||||
|
// current browser window.
|
||||||
let winEn = Services.wm.getEnumerator(null);
|
let winEn = Services.wm.getEnumerator(null);
|
||||||
while (winEn.hasMoreElements()) {
|
while (winEn.hasMoreElements()) {
|
||||||
let win = winEn.getNext();
|
let win = winEn.getNext();
|
||||||
|
|
||||||
// Modal dialogs which do not have an opener set, we cannot detect as long
|
// Modal dialogs which do not have an opener set, we cannot detect
|
||||||
// as GetZOrderDOMWindowEnumerator doesn't work on Linux (Bug 156333).
|
// as long as GetZOrderDOMWindowEnumerator doesn't work on Linux
|
||||||
if (win.document.documentURI === "chrome://global/content/commonDialog.xul" &&
|
// (Bug 156333).
|
||||||
|
if (win.document.documentURI === COMMON_DIALOG &&
|
||||||
win.opener && win.opener === context.window) {
|
win.opener && win.opener === context.window) {
|
||||||
return new modal.Dialog(() => context, Cu.getWeakReference(win));
|
return new modal.Dialog(() => context, Cu.getWeakReference(win));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no modal dialog has been found, also check if there is an open tab modal
|
// If no modal dialog has been found, also check if there is an open
|
||||||
// dialog present for the current tab.
|
// tab modal dialog present for the current tab.
|
||||||
// TODO: Find an adequate implementation for Fennec.
|
// TODO: Find an adequate implementation for Fennec.
|
||||||
if (context.tab && context.tabBrowser.getTabModalPromptBox) {
|
if (context.tab && context.tabBrowser.getTabModalPromptBox) {
|
||||||
let contentBrowser = context.contentBrowser;
|
let contentBrowser = context.contentBrowser;
|
||||||
let promptManager = context.tabBrowser.getTabModalPromptBox(contentBrowser);
|
let promptManager =
|
||||||
|
context.tabBrowser.getTabModalPromptBox(contentBrowser);
|
||||||
let prompts = promptManager.listPrompts();
|
let prompts = promptManager.listPrompts();
|
||||||
|
|
||||||
if (prompts.length) {
|
if (prompts.length) {
|
||||||
@ -96,7 +102,7 @@ modal.findModalDialogs = function (context) {
|
|||||||
* The handler previously passed to modal.addHandler which will now
|
* The handler previously passed to modal.addHandler which will now
|
||||||
* be removed.
|
* be removed.
|
||||||
*/
|
*/
|
||||||
modal.removeHandler = function (toRemove) {
|
modal.removeHandler = function(toRemove) {
|
||||||
if (!isFirefox()) {
|
if (!isFirefox()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ this.navigate = {};
|
|||||||
* @throws TypeError
|
* @throws TypeError
|
||||||
* If |url| is an invalid URL.
|
* If |url| is an invalid URL.
|
||||||
*/
|
*/
|
||||||
navigate.isLoadEventExpected = function (url) {
|
navigate.isLoadEventExpected = function(url) {
|
||||||
// assume we will go somewhere exciting
|
// assume we will go somewhere exciting
|
||||||
if (typeof url == "undefined") {
|
if (typeof url == "undefined") {
|
||||||
throw TypeError("Expected destination URL");
|
throw TypeError("Expected destination URL");
|
||||||
|
@ -25,27 +25,28 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
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"]
|
const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||||
unicodeConverter.charset = "UTF-8";
|
unicodeConverter.charset = "UTF-8";
|
||||||
|
|
||||||
const defer = function () {
|
const defer = function() {
|
||||||
let deferred = {
|
let deferred = {
|
||||||
promise: new Promise((resolve, reject) => {
|
promise: new Promise((resolve, reject) => {
|
||||||
deferred.resolve = resolve;
|
deferred.resolve = resolve;
|
||||||
deferred.reject = reject;
|
deferred.reject = reject;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
return deferred;
|
return deferred;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"];
|
this.EXPORTED_SYMBOLS = ["RawPacket", "Packet", "JSONPacket", "BulkPacket"];
|
||||||
|
|
||||||
// The transport's previous check ensured the header length did not exceed 20
|
// The transport's previous check ensured the header length did not
|
||||||
// characters. Here, we opt for the somewhat smaller, but still large limit of
|
// exceed 20 characters. Here, we opt for the somewhat smaller, but still
|
||||||
// 1 TiB.
|
// large limit of 1 TiB.
|
||||||
const PACKET_LENGTH_MAX = Math.pow(2, 40);
|
const PACKET_LENGTH_MAX = Math.pow(2, 40);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,17 +58,19 @@ function Packet(transport) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to initialize a new Packet based on the incoming packet header we've
|
* Attempt to initialize a new Packet based on the incoming packet header
|
||||||
* received so far. We try each of the types in succession, trying JSON packets
|
* we've received so far. We try each of the types in succession, trying
|
||||||
* first since they are much more common.
|
* JSON packets first since they are much more common.
|
||||||
* @param header string
|
*
|
||||||
* The packet header string to attempt parsing.
|
* @param {string} header
|
||||||
* @param transport DebuggerTransport
|
* Packet header string to attempt parsing.
|
||||||
* The transport instance that will own the packet.
|
* @param {DebuggerTransport} transport
|
||||||
* @return Packet
|
* Transport instance that will own the packet.
|
||||||
* The parsed packet of the matching type, or null if no types matched.
|
*
|
||||||
|
* @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) ||
|
return JSONPacket.fromHeader(header, transport) ||
|
||||||
BulkPacket.fromHeader(header, transport);
|
BulkPacket.fromHeader(header, transport);
|
||||||
};
|
};
|
||||||
@ -86,20 +89,20 @@ Packet.prototype = {
|
|||||||
this._length = length;
|
this._length = length;
|
||||||
},
|
},
|
||||||
|
|
||||||
destroy: function () {
|
destroy() {
|
||||||
this._transport = null;
|
this._transport = null;
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* With a JSON packet (the typical packet type sent via the transport), data is
|
* With a JSON packet (the typical packet type sent via the transport),
|
||||||
* transferred as a JSON packet serialized into a string, with the string length
|
* data is transferred as a JSON packet serialized into a string,
|
||||||
* prepended to the packet, followed by a colon ([length]:[packet]). The
|
* with the string length prepended to the packet, followed by a colon
|
||||||
* contents of the JSON packet are specified in the Remote Debugging Protocol
|
* ([length]:[packet]). The contents of the JSON packet are specified in
|
||||||
* specification.
|
* the Remote Debugging Protocol specification.
|
||||||
* @param transport DebuggerTransport
|
*
|
||||||
* The transport instance that will own the packet.
|
* @param {DebuggerTransport} transport
|
||||||
|
* Transport instance that will own the packet.
|
||||||
*/
|
*/
|
||||||
function JSONPacket(transport) {
|
function JSONPacket(transport) {
|
||||||
Packet.call(this, transport);
|
Packet.call(this, transport);
|
||||||
@ -108,16 +111,18 @@ function JSONPacket(transport) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to initialize a new JSONPacket based on the incoming packet header
|
* Attempt to initialize a new JSONPacket based on the incoming packet
|
||||||
* we've received so far.
|
* header we've received so far.
|
||||||
* @param header string
|
*
|
||||||
* The packet header string to attempt parsing.
|
* @param {string} header
|
||||||
* @param transport DebuggerTransport
|
* Packet header string to attempt parsing.
|
||||||
* The transport instance that will own the packet.
|
* @param {DebuggerTransport} transport
|
||||||
* @return JSONPacket
|
* Transport instance that will own the packet.
|
||||||
* The parsed packet, or null if it's not a match.
|
*
|
||||||
|
* @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);
|
let match = this.HEADER_PATTERN.exec(header);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@ -137,22 +142,22 @@ Object.defineProperty(JSONPacket.prototype, "object", {
|
|||||||
/**
|
/**
|
||||||
* Gets the object (not the serialized string) being read or written.
|
* Gets the object (not the serialized string) being read or written.
|
||||||
*/
|
*/
|
||||||
get: function () {
|
get() {
|
||||||
return this._object;
|
return this._object;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the object to be sent when write() is called.
|
* Sets the object to be sent when write() is called.
|
||||||
*/
|
*/
|
||||||
set: function (object) {
|
set(object) {
|
||||||
this._object = object;
|
this._object = object;
|
||||||
let data = JSON.stringify(object);
|
let data = JSON.stringify(object);
|
||||||
this._data = unicodeConverter.ConvertFromUnicode(data);
|
this._data = unicodeConverter.ConvertFromUnicode(data);
|
||||||
this.length = this._data.length;
|
this.length = this._data.length;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
JSONPacket.prototype.read = function (stream, scriptableStream) {
|
JSONPacket.prototype.read = function(stream, scriptableStream) {
|
||||||
|
|
||||||
// Read in more packet data.
|
// Read in more packet data.
|
||||||
this._readData(stream, scriptableStream);
|
this._readData(stream, scriptableStream);
|
||||||
@ -177,14 +182,15 @@ JSONPacket.prototype.read = function (stream, scriptableStream) {
|
|||||||
this._transport._onJSONObjectReady(this._object);
|
this._transport._onJSONObjectReady(this._object);
|
||||||
};
|
};
|
||||||
|
|
||||||
JSONPacket.prototype._readData = function (stream, scriptableStream) {
|
JSONPacket.prototype._readData = function(stream, scriptableStream) {
|
||||||
let bytesToRead = Math.min(this.length - this._data.length,
|
let bytesToRead = Math.min(
|
||||||
stream.available());
|
this.length - this._data.length,
|
||||||
|
stream.available());
|
||||||
this._data += scriptableStream.readBytes(bytesToRead);
|
this._data += scriptableStream.readBytes(bytesToRead);
|
||||||
this._done = this._data.length === this.length;
|
this._done = this._data.length === this.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
JSONPacket.prototype.write = function (stream) {
|
JSONPacket.prototype.write = function(stream) {
|
||||||
|
|
||||||
if (this._outgoing === undefined) {
|
if (this._outgoing === undefined) {
|
||||||
// Format the serialized packet to a buffer
|
// Format the serialized packet to a buffer
|
||||||
@ -197,29 +203,30 @@ JSONPacket.prototype.write = function (stream) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(JSONPacket.prototype, "done", {
|
Object.defineProperty(JSONPacket.prototype, "done", {
|
||||||
get: function () {
|
get() {
|
||||||
return this._done;
|
return this._done;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
JSONPacket.prototype.toString = function () {
|
JSONPacket.prototype.toString = function() {
|
||||||
return JSON.stringify(this._object, null, 2);
|
return JSON.stringify(this._object, null, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* With a bulk packet, data is transferred by temporarily handing over the
|
* With a bulk packet, data is transferred by temporarily handing over
|
||||||
* transport's input or output stream to the application layer for writing data
|
* the transport's input or output stream to the application layer for
|
||||||
* directly. This can be much faster for large data sets, and avoids various
|
* writing data directly. This can be much faster for large data sets,
|
||||||
* stages of copies and data duplication inherent in the JSON packet type. The
|
* and avoids various stages of copies and data duplication inherent in
|
||||||
* bulk packet looks like:
|
* 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
|
* The interpretation of the data portion depends on the kind of actor and
|
||||||
* packet's type. See the Remote Debugging Protocol Stream Transport spec for
|
* the packet's type. See the Remote Debugging Protocol Stream Transport
|
||||||
* more details.
|
* spec for more details.
|
||||||
* @param transport DebuggerTransport
|
*
|
||||||
* The transport instance that will own the packet.
|
* @param {DebuggerTransport} transport
|
||||||
|
* Transport instance that will own the packet.
|
||||||
*/
|
*/
|
||||||
function BulkPacket(transport) {
|
function BulkPacket(transport) {
|
||||||
Packet.call(this, transport);
|
Packet.call(this, transport);
|
||||||
@ -228,16 +235,18 @@ function BulkPacket(transport) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempt to initialize a new BulkPacket based on the incoming packet header
|
* Attempt to initialize a new BulkPacket based on the incoming packet
|
||||||
* we've received so far.
|
* header we've received so far.
|
||||||
* @param header string
|
*
|
||||||
* The packet header string to attempt parsing.
|
* @param {string} header
|
||||||
* @param transport DebuggerTransport
|
* Packet header string to attempt parsing.
|
||||||
* The transport instance that will own the packet.
|
* @param {DebuggerTransport} transport
|
||||||
* @return BulkPacket
|
* Transport instance that will own the packet.
|
||||||
* The parsed packet, or null if it's not a match.
|
*
|
||||||
|
* @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);
|
let match = this.HEADER_PATTERN.exec(header);
|
||||||
|
|
||||||
if (!match) {
|
if (!match) {
|
||||||
@ -248,7 +257,7 @@ BulkPacket.fromHeader = function (header, transport) {
|
|||||||
packet.header = {
|
packet.header = {
|
||||||
actor: match[1],
|
actor: match[1],
|
||||||
type: match[2],
|
type: match[2],
|
||||||
length: +match[3]
|
length: +match[3],
|
||||||
};
|
};
|
||||||
return packet;
|
return packet;
|
||||||
};
|
};
|
||||||
@ -257,7 +266,7 @@ BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
|
|||||||
|
|
||||||
BulkPacket.prototype = Object.create(Packet.prototype);
|
BulkPacket.prototype = Object.create(Packet.prototype);
|
||||||
|
|
||||||
BulkPacket.prototype.read = function (stream) {
|
BulkPacket.prototype.read = function(stream) {
|
||||||
// Temporarily pause monitoring of the input stream
|
// Temporarily pause monitoring of the input stream
|
||||||
this._transport.pauseIncoming();
|
this._transport.pauseIncoming();
|
||||||
|
|
||||||
@ -272,8 +281,8 @@ BulkPacket.prototype.read = function (stream) {
|
|||||||
deferred.resolve(copying);
|
deferred.resolve(copying);
|
||||||
return copying;
|
return copying;
|
||||||
},
|
},
|
||||||
stream: stream,
|
stream,
|
||||||
done: deferred
|
done: deferred,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Await the result of reading from the stream
|
// Await the result of reading from the stream
|
||||||
@ -288,7 +297,7 @@ BulkPacket.prototype.read = function (stream) {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
BulkPacket.prototype.write = function (stream) {
|
BulkPacket.prototype.write = function(stream) {
|
||||||
if (this._outgoingHeader === undefined) {
|
if (this._outgoingHeader === undefined) {
|
||||||
// Format the serialized packet header to a buffer
|
// Format the serialized packet header to a buffer
|
||||||
this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
|
this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
|
||||||
@ -314,8 +323,8 @@ BulkPacket.prototype.write = function (stream) {
|
|||||||
deferred.resolve(copying);
|
deferred.resolve(copying);
|
||||||
return copying;
|
return copying;
|
||||||
},
|
},
|
||||||
stream: stream,
|
stream,
|
||||||
done: deferred
|
done: deferred,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Await the result of writing to the stream
|
// Await the result of writing to the stream
|
||||||
@ -331,21 +340,21 @@ BulkPacket.prototype.write = function (stream) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
|
Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
|
||||||
get: function () {
|
get() {
|
||||||
return this._readyForWriting.promise;
|
return this._readyForWriting.promise;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BulkPacket.prototype, "header", {
|
Object.defineProperty(BulkPacket.prototype, "header", {
|
||||||
get: function () {
|
get() {
|
||||||
return {
|
return {
|
||||||
actor: this.actor,
|
actor: this.actor,
|
||||||
type: this.type,
|
type: this.type,
|
||||||
length: this.length
|
length: this.length,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
set: function (header) {
|
set(header) {
|
||||||
this.actor = header.actor;
|
this.actor = header.actor;
|
||||||
this.type = header.type;
|
this.type = header.type;
|
||||||
this.length = header.length;
|
this.length = header.length;
|
||||||
@ -353,12 +362,12 @@ Object.defineProperty(BulkPacket.prototype, "header", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BulkPacket.prototype, "done", {
|
Object.defineProperty(BulkPacket.prototype, "done", {
|
||||||
get: function () {
|
get() {
|
||||||
return this._done;
|
return this._done;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
BulkPacket.prototype.toString = function () {
|
BulkPacket.prototype.toString = function() {
|
||||||
return "Bulk: " + JSON.stringify(this.header, null, 2);
|
return "Bulk: " + JSON.stringify(this.header, null, 2);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -379,19 +388,19 @@ function RawPacket(transport, data) {
|
|||||||
|
|
||||||
RawPacket.prototype = Object.create(Packet.prototype);
|
RawPacket.prototype = Object.create(Packet.prototype);
|
||||||
|
|
||||||
RawPacket.prototype.read = function (stream) {
|
RawPacket.prototype.read = function(stream) {
|
||||||
// This hasn't yet been needed for testing.
|
// This hasn't yet been needed for testing.
|
||||||
throw Error("Not implmented.");
|
throw Error("Not implmented.");
|
||||||
};
|
};
|
||||||
|
|
||||||
RawPacket.prototype.write = function (stream) {
|
RawPacket.prototype.write = function(stream) {
|
||||||
let written = stream.write(this._data, this._data.length);
|
let written = stream.write(this._data, this._data.length);
|
||||||
this._data = this._data.slice(written);
|
this._data = this._data.slice(written);
|
||||||
this._done = !this._data.length;
|
this._done = !this._data.length;
|
||||||
};
|
};
|
||||||
|
|
||||||
Object.defineProperty(RawPacket.prototype, "done", {
|
Object.defineProperty(RawPacket.prototype, "done", {
|
||||||
get: function () {
|
get() {
|
||||||
return this._done;
|
return this._done;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
@ -30,7 +30,7 @@ var ownPriorityGetterTrap = {
|
|||||||
return obj[prop];
|
return obj[prop];
|
||||||
}
|
}
|
||||||
return (...args) => obj.send(prop, args);
|
return (...args) => obj.send(prop, args);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
this.proxy = {};
|
this.proxy = {};
|
||||||
@ -52,8 +52,9 @@ this.proxy = {};
|
|||||||
* @param {function(string, Object, number)} sendAsyncFn
|
* @param {function(string, Object, number)} sendAsyncFn
|
||||||
* Callback for sending async messages.
|
* Callback for sending async messages.
|
||||||
*/
|
*/
|
||||||
proxy.toListener = function (mmFn, sendAsyncFn, browserFn) {
|
proxy.toListener = function(mmFn, sendAsyncFn, browserFn) {
|
||||||
let sender = new proxy.AsyncMessageChannel(mmFn, sendAsyncFn, browserFn);
|
let sender = new proxy.AsyncMessageChannel(
|
||||||
|
mmFn, sendAsyncFn, browserFn);
|
||||||
return new Proxy(sender, ownPriorityGetterTrap);
|
return new Proxy(sender, ownPriorityGetterTrap);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -100,8 +101,8 @@ proxy.AsyncMessageChannel = class {
|
|||||||
* @param {string} name
|
* @param {string} name
|
||||||
* Function to call in the listener, e.g. for the message listener
|
* Function to call in the listener, e.g. for the message listener
|
||||||
* "Marionette:foo8", use "foo".
|
* "Marionette:foo8", use "foo".
|
||||||
* @param {Array.<?>=} args
|
* @param {Array.<?>=} args
|
||||||
* Argument list to pass the function. If args has a single entry
|
* 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
|
* that is an object, we assume it's an old style dispatch, and
|
||||||
* the object will passed literally.
|
* the object will passed literally.
|
||||||
*
|
*
|
||||||
@ -142,7 +143,7 @@ proxy.AsyncMessageChannel = class {
|
|||||||
// The currently selected tab or window has been closed. No clean-up
|
// The currently selected tab or window has been closed. No clean-up
|
||||||
// is necessary to do because all loaded listeners are gone.
|
// is necessary to do because all loaded listeners are gone.
|
||||||
this.closeHandler = event => {
|
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) {
|
switch (event.type) {
|
||||||
case "TabClose":
|
case "TabClose":
|
||||||
@ -183,15 +184,15 @@ proxy.AsyncMessageChannel = class {
|
|||||||
addHandlers() {
|
addHandlers() {
|
||||||
modal.addHandler(this.dialogueObserver_);
|
modal.addHandler(this.dialogueObserver_);
|
||||||
|
|
||||||
// Register event handlers in case the command closes the current tab or window,
|
// Register event handlers in case the command closes the current
|
||||||
// and the promise has to be escaped.
|
// tab or window, and the promise has to be escaped.
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
this.browser.window.addEventListener("unload", this.closeHandler, false);
|
this.browser.window.addEventListener("unload", this.closeHandler);
|
||||||
|
|
||||||
if (this.browser.tab) {
|
if (this.browser.tab) {
|
||||||
let node = this.browser.tab.addEventListener ?
|
let node = this.browser.tab.addEventListener ?
|
||||||
this.browser.tab : this.browser.contentBrowser;
|
this.browser.tab : this.browser.contentBrowser;
|
||||||
node.addEventListener("TabClose", this.closeHandler, false);
|
node.addEventListener("TabClose", this.closeHandler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,13 +204,12 @@ proxy.AsyncMessageChannel = class {
|
|||||||
modal.removeHandler(this.dialogueObserver_);
|
modal.removeHandler(this.dialogueObserver_);
|
||||||
|
|
||||||
if (this.browser) {
|
if (this.browser) {
|
||||||
this.browser.window.removeEventListener("unload", this.closeHandler, false);
|
this.browser.window.removeEventListener("unload", this.closeHandler);
|
||||||
|
|
||||||
if (this.browser.tab) {
|
if (this.browser.tab) {
|
||||||
let node = this.browser.tab.addEventListener ?
|
let node = this.browser.tab.addEventListener ?
|
||||||
this.browser.tab : this.browser.contentBrowser;
|
this.browser.tab : this.browser.contentBrowser;
|
||||||
|
node.removeEventListener("TabClose", this.closeHandler);
|
||||||
node.removeEventListener("TabClose", this.closeHandler, false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -263,7 +263,7 @@ proxy.AsyncMessageChannel = class {
|
|||||||
payload = data;
|
payload = data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const msg = {type: type, data: payload};
|
const msg = {type, data: payload};
|
||||||
|
|
||||||
// here sendAsync is actually the content frame's
|
// here sendAsync is actually the content frame's
|
||||||
// sendAsyncMessage(path, message) global
|
// sendAsyncMessage(path, message) global
|
||||||
@ -307,7 +307,7 @@ proxy.AsyncMessageChannel = class {
|
|||||||
|
|
||||||
removeAllListeners_() {
|
removeAllListeners_() {
|
||||||
let ok = true;
|
let ok = true;
|
||||||
for (let [p, cb] of this.listeners_) {
|
for (let [p] of this.listeners_) {
|
||||||
ok |= this.removeListener_(p);
|
ok |= this.removeListener_(p);
|
||||||
}
|
}
|
||||||
return ok;
|
return ok;
|
||||||
@ -327,7 +327,7 @@ proxy.AsyncMessageChannel.ReplyType = {
|
|||||||
* The content frame's message manager, which itself is usually an
|
* The content frame's message manager, which itself is usually an
|
||||||
* implementor of.
|
* implementor of.
|
||||||
*/
|
*/
|
||||||
proxy.toChromeAsync = function (frameMessageManager) {
|
proxy.toChromeAsync = function(frameMessageManager) {
|
||||||
let sender = new AsyncChromeSender(frameMessageManager);
|
let sender = new AsyncChromeSender(frameMessageManager);
|
||||||
return new Proxy(sender, ownPriorityGetterTrap);
|
return new Proxy(sender, ownPriorityGetterTrap);
|
||||||
};
|
};
|
||||||
@ -342,7 +342,7 @@ proxy.toChromeAsync = function (frameMessageManager) {
|
|||||||
* let promise = sender.send("runEmulatorCmd", "my command");
|
* let promise = sender.send("runEmulatorCmd", "my command");
|
||||||
* let rv = yield promise;
|
* let rv = yield promise;
|
||||||
*/
|
*/
|
||||||
this.AsyncChromeSender = class {
|
class AsyncChromeSender {
|
||||||
constructor(frameMessageManager) {
|
constructor(frameMessageManager) {
|
||||||
this.mm = frameMessageManager;
|
this.mm = frameMessageManager;
|
||||||
}
|
}
|
||||||
@ -353,7 +353,7 @@ this.AsyncChromeSender = class {
|
|||||||
* @param {string} name
|
* @param {string} name
|
||||||
* Function to call in the chrome, e.g. for "Marionette:foo", use
|
* Function to call in the chrome, e.g. for "Marionette:foo", use
|
||||||
* "foo".
|
* "foo".
|
||||||
* @param {?} args
|
* @param {?} args
|
||||||
* Argument list to pass the function. Must be JSON serialisable.
|
* Argument list to pass the function. Must be JSON serialisable.
|
||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
@ -389,7 +389,7 @@ this.AsyncChromeSender = class {
|
|||||||
|
|
||||||
return proxy;
|
return proxy;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a transparent interface from the content- to the chrome context.
|
* Creates a transparent interface from the content- to the chrome context.
|
||||||
@ -406,7 +406,7 @@ this.AsyncChromeSender = class {
|
|||||||
* @param {nsISyncMessageSender} sendSyncMessageFn
|
* @param {nsISyncMessageSender} sendSyncMessageFn
|
||||||
* The frame message manager's sendSyncMessage function.
|
* The frame message manager's sendSyncMessage function.
|
||||||
*/
|
*/
|
||||||
proxy.toChrome = function (sendSyncMessageFn) {
|
proxy.toChrome = function(sendSyncMessageFn) {
|
||||||
let sender = new proxy.SyncChromeSender(sendSyncMessageFn);
|
let sender = new proxy.SyncChromeSender(sendSyncMessageFn);
|
||||||
return new Proxy(sender, ownPriorityGetterTrap);
|
return new Proxy(sender, ownPriorityGetterTrap);
|
||||||
};
|
};
|
||||||
@ -432,7 +432,7 @@ proxy.SyncChromeSender = class {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var marshal = function (args) {
|
var marshal = function(args) {
|
||||||
if (args.length == 1 && typeof args[0] == "object") {
|
if (args.length == 1 && typeof args[0] == "object") {
|
||||||
return args[0];
|
return args[0];
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ Cu.import("resource://gre/modules/Task.jsm");
|
|||||||
Cu.import("chrome://marionette/content/assert.js");
|
Cu.import("chrome://marionette/content/assert.js");
|
||||||
Cu.import("chrome://marionette/content/capture.js");
|
Cu.import("chrome://marionette/content/capture.js");
|
||||||
const {InvalidArgumentError} =
|
const {InvalidArgumentError} =
|
||||||
Cu.import("chrome//marionette/content/error.js", {});
|
Cu.import("chrome://marionette/content/error.js", {});
|
||||||
|
|
||||||
this.EXPORTED_SYMBOLS = ["reftest"];
|
this.EXPORTED_SYMBOLS = ["reftest"];
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ const logger = Log.repository.getLogger("Marionette");
|
|||||||
const SCREENSHOT_MODE = {
|
const SCREENSHOT_MODE = {
|
||||||
unexpected: 0,
|
unexpected: 0,
|
||||||
fail: 1,
|
fail: 1,
|
||||||
always: 2
|
always: 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS = {
|
const STATUS = {
|
||||||
@ -46,7 +46,7 @@ reftest.Runner = class {
|
|||||||
this.driver = driver;
|
this.driver = driver;
|
||||||
this.canvasCache = new Map([[null, []]]);
|
this.canvasCache = new Map([[null, []]]);
|
||||||
this.windowUtils = null;
|
this.windowUtils = null;
|
||||||
this.lastUrl = null;
|
this.lastURL = null;
|
||||||
this.remote = Preferences.get(PREF_E10S);
|
this.remote = Preferences.get(PREF_E10S);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -73,7 +73,7 @@ reftest.Runner = class {
|
|||||||
.reduce((map, key) => map.set(key, urlCount[key]), new Map());
|
.reduce((map, key) => map.set(key, urlCount[key]), new Map());
|
||||||
|
|
||||||
yield this.ensureWindow();
|
yield this.ensureWindow();
|
||||||
};
|
}
|
||||||
|
|
||||||
*ensureWindow() {
|
*ensureWindow() {
|
||||||
if (this.reftestWin && !this.reftestWin.closed) {
|
if (this.reftestWin && !this.reftestWin.closed) {
|
||||||
@ -189,7 +189,7 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
|
|
||||||
let result = yield Promise.race([testRunner, timeoutPromise]);
|
let result = yield Promise.race([testRunner, timeoutPromise]);
|
||||||
this.parentWindow.clearTimeout(timeoutHandle);
|
this.parentWindow.clearTimeout(timeoutHandle);
|
||||||
if(result.status === STATUS.TIMEOUT) {
|
if (result.status === STATUS.TIMEOUT) {
|
||||||
this.abort();
|
this.abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,9 +197,13 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
*runTest(testUrl, references, expected, timeout) {
|
*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.innerWidth = 600;
|
||||||
win.innerHeight = 600;
|
win.innerHeight = 600;
|
||||||
|
|
||||||
@ -208,7 +212,7 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
let screenshotData = [];
|
let screenshotData = [];
|
||||||
|
|
||||||
let stack = [];
|
let stack = [];
|
||||||
for (let i = references.length-1; i >= 0; i--) {
|
for (let i = references.length - 1; i >= 0; i--) {
|
||||||
let item = references[i];
|
let item = references[i];
|
||||||
stack.push([testUrl, item[0], item[1], item[2]]);
|
stack.push([testUrl, item[0], item[1], item[2]]);
|
||||||
}
|
}
|
||||||
@ -219,13 +223,15 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
let [lhsUrl, rhsUrl, references, relation] = stack.pop();
|
let [lhsUrl, rhsUrl, references, relation] = stack.pop();
|
||||||
message += `Testing ${lhsUrl} ${relation} ${rhsUrl}\n`;
|
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() {
|
function recordScreenshot() {
|
||||||
let toBase64 = screenshot => screenshot.canvas.toDataURL().split(",")[1];
|
let encodedLHS = toBase64(comparison.lhs);
|
||||||
screenshotData.push([{url: lhsUrl, screenshot: toBase64(comparison.lhs)},
|
let encodedRHS = toBase64(comparison.rhs);
|
||||||
relation,
|
screenshotData.push([{url: lhsUrl, screenshot: encodedLHS},
|
||||||
{url:rhsUrl, screenshot: toBase64(comparison.rhs)}]);
|
relation,
|
||||||
|
{url: rhsUrl, screenshot: encodedRHS}]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.screenshotMode === SCREENSHOT_MODE.always) {
|
if (this.screenshotMode === SCREENSHOT_MODE.always) {
|
||||||
@ -241,16 +247,18 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
} else {
|
} else {
|
||||||
// Reached a leaf node so all of one reference chain passed
|
// Reached a leaf node so all of one reference chain passed
|
||||||
status = STATUS.PASS;
|
status = STATUS.PASS;
|
||||||
if (this.screenshotMode <= SCREENSHOT_MODE.fail && expected != status) {
|
if (this.screenshotMode <= SCREENSHOT_MODE.fail &&
|
||||||
|
expected != status) {
|
||||||
recordScreenshot();
|
recordScreenshot();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (!stack.length) {
|
} else if (!stack.length) {
|
||||||
// If we don't have any alternatives to try then this will be the last iteration,
|
// If we don't have any alternatives to try then this will be
|
||||||
// so save the failing screenshots if required
|
// the last iteration, so save the failing screenshots if required.
|
||||||
if (this.screenshotMode === SCREENSHOT_MODE.fail ||
|
let isFail = this.screenshotMode === SCREENSHOT_MODE.fail;
|
||||||
(this.screenshotMode === SCREENSHOT_MODE.unexpected && expected != status)) {
|
let isUnexpected = this.screenshotMode === SCREENSHOT_MODE.unexpected;
|
||||||
|
if (isFail || (isUnexpected && expected != status)) {
|
||||||
recordScreenshot();
|
recordScreenshot();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -267,13 +275,14 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
|
|
||||||
let result = {status, message, extra: {}};
|
let result = {status, message, extra: {}};
|
||||||
if (screenshotData.length) {
|
if (screenshotData.length) {
|
||||||
// For now the tbpl formatter only accepts one screenshot, so just return the
|
// For now the tbpl formatter only accepts one screenshot, so just
|
||||||
// last one we took.
|
// return the last one we took.
|
||||||
result.extra.reftest_screenshots = screenshotData[screenshotData.length - 1];
|
let lastScreenshot = screenshotData[screenshotData.length - 1];
|
||||||
|
result.extra.reftest_screenshots = lastScreenshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
};
|
}
|
||||||
|
|
||||||
*compareUrls(win, lhsUrl, rhsUrl, relation, timeout) {
|
*compareUrls(win, lhsUrl, rhsUrl, relation, timeout) {
|
||||||
logger.info(`Testing ${lhsUrl} ${relation} ${rhsUrl}`);
|
logger.info(`Testing ${lhsUrl} ${relation} ${rhsUrl}`);
|
||||||
@ -285,21 +294,25 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
|
|
||||||
let maxDifferences = {};
|
let maxDifferences = {};
|
||||||
|
|
||||||
let differences = this.windowUtils.compareCanvases(lhs.canvas, rhs.canvas, maxDifferences);
|
let differences = this.windowUtils.compareCanvases(
|
||||||
|
lhs.canvas, rhs.canvas, maxDifferences);
|
||||||
|
|
||||||
let passed;
|
let passed;
|
||||||
switch (relation) {
|
switch (relation) {
|
||||||
case "==":
|
case "==":
|
||||||
passed = differences === 0;
|
passed = differences === 0;
|
||||||
if (!passed) {
|
if (!passed) {
|
||||||
logger.info(`Found ${differences} pixels different, maximum difference per channel ${maxDifferences.value}`);
|
logger.info(`Found ${differences} pixels different, ` +
|
||||||
}
|
`maximum difference per channel ${maxDifferences.value}`);
|
||||||
break;
|
}
|
||||||
case "!=":
|
break;
|
||||||
passed = differences !== 0;
|
|
||||||
break;
|
case "!=":
|
||||||
default:
|
passed = differences !== 0;
|
||||||
throw new InvalidArgumentError("Reftest operator should be '==' or '!='");
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new InvalidArgumentError("Reftest operator should be '==' or '!='");
|
||||||
}
|
}
|
||||||
|
|
||||||
return {lhs, rhs, passed};
|
return {lhs, rhs, passed};
|
||||||
@ -309,7 +322,8 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
let canvas = null;
|
let canvas = null;
|
||||||
let remainingCount = this.urlCount.get(url) || 1;
|
let remainingCount = this.urlCount.get(url) || 1;
|
||||||
let cache = remainingCount > 1;
|
let cache = remainingCount > 1;
|
||||||
logger.debug(`screenshot ${url} remainingCount: ${remainingCount} cache: ${cache}`);
|
logger.debug(`screenshot ${url} remainingCount: ` +
|
||||||
|
`${remainingCount} cache: ${cache}`);
|
||||||
let reuseCanvas = false;
|
let reuseCanvas = false;
|
||||||
if (this.canvasCache.has(url)) {
|
if (this.canvasCache.has(url)) {
|
||||||
logger.debug(`screenshot ${url} taken from cache`);
|
logger.debug(`screenshot ${url} taken from cache`);
|
||||||
@ -332,26 +346,34 @@ min-width: 600px; min-height: 600px; max-width: 600px; max-height: 600px`;
|
|||||||
ctxInterface.DRAWWINDOW_DRAW_VIEW;
|
ctxInterface.DRAWWINDOW_DRAW_VIEW;
|
||||||
|
|
||||||
logger.debug(`Starting load of ${url}`);
|
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`);
|
logger.debug(`Refreshing page`);
|
||||||
yield this.driver.listener.refresh({commandId: this.driver.listener.activeMessageId,
|
yield this.driver.listener.refresh(navigateOpts);
|
||||||
pageTimeout: timeout});
|
|
||||||
} else {
|
} else {
|
||||||
yield this.driver.listener.get({commandId: this.driver.listener.activeMessageId,
|
navigateOpts.url = url;
|
||||||
url: url,
|
navigateOpts.loadEventExpected = false;
|
||||||
pageTimeout: timeout,
|
yield this.driver.listener.get(navigateOpts);
|
||||||
loadEventExpected: false});
|
this.lastURL = url;
|
||||||
this.lastUrl = url;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.driver.curBrowser.contentBrowser.focus();
|
this.driver.curBrowser.contentBrowser.focus();
|
||||||
yield this.driver.listener.reftestWait(url, this.remote);
|
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) {
|
if (cache) {
|
||||||
this.canvasCache.set(url, canvas);
|
this.canvasCache.set(url, canvas);
|
||||||
};
|
}
|
||||||
this.urlCount.set(url, remainingCount - 1);
|
this.urlCount.set(url, remainingCount - 1);
|
||||||
return {canvas, reuseCanvas};
|
return {canvas, reuseCanvas};
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,14 @@ Cu.import("resource://gre/modules/Task.jsm");
|
|||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
|
||||||
Cu.import("chrome://marionette/content/assert.js");
|
Cu.import("chrome://marionette/content/assert.js");
|
||||||
Cu.import("chrome://marionette/content/driver.js");
|
const {GeckoDriver} = Cu.import("chrome://marionette/content/driver.js", {});
|
||||||
const {
|
const {
|
||||||
error,
|
error,
|
||||||
UnknownCommandError,
|
UnknownCommandError,
|
||||||
} = Cu.import("chrome://marionette/content/error.js", {});
|
} = Cu.import("chrome://marionette/content/error.js", {});
|
||||||
Cu.import("chrome://marionette/content/message.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(
|
XPCOMUtils.defineLazyServiceGetter(
|
||||||
this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");
|
this, "env", "@mozilla.org/process/environment;1", "nsIEnvironment");
|
||||||
@ -161,8 +162,14 @@ const RECOMMENDED_PREFS = new Map([
|
|||||||
|
|
||||||
// Do not show datareporting policy notifications which can
|
// Do not show datareporting policy notifications which can
|
||||||
// interfere with tests
|
// 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.logging.consoleEnabled", false],
|
||||||
["datareporting.healthreport.service.enabled", false],
|
["datareporting.healthreport.service.enabled", false],
|
||||||
["datareporting.healthreport.service.firstRun", false],
|
["datareporting.healthreport.service.firstRun", false],
|
||||||
@ -204,7 +211,10 @@ const RECOMMENDED_PREFS = new Map([
|
|||||||
["extensions.update.notifyUser", false],
|
["extensions.update.notifyUser", false],
|
||||||
|
|
||||||
// Make sure opening about:addons will not hit the network
|
// 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
|
// Allow the application to have focus even it runs in the background
|
||||||
["focusmanager.testmode", true],
|
["focusmanager.testmode", true],
|
||||||
@ -277,7 +287,7 @@ server.TCPListener = class {
|
|||||||
* @param {number} port
|
* @param {number} port
|
||||||
* Port for server to listen to.
|
* Port for server to listen to.
|
||||||
*/
|
*/
|
||||||
constructor (port) {
|
constructor(port) {
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.conns = new Set();
|
this.conns = new Set();
|
||||||
@ -295,12 +305,12 @@ server.TCPListener = class {
|
|||||||
* @return {GeckoDriver}
|
* @return {GeckoDriver}
|
||||||
* A driver instance.
|
* A driver instance.
|
||||||
*/
|
*/
|
||||||
driverFactory () {
|
driverFactory() {
|
||||||
Preferences.set(PREF_CONTENT_LISTENER, false);
|
Preferences.set(PREF_CONTENT_LISTENER, false);
|
||||||
return new GeckoDriver(Services.appinfo.name, this);
|
return new GeckoDriver(Services.appinfo.name, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
set acceptConnections (value) {
|
set acceptConnections(value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
logger.info("New connections will no longer be accepted");
|
logger.info("New connections will no longer be accepted");
|
||||||
} else {
|
} else {
|
||||||
@ -317,7 +327,7 @@ server.TCPListener = class {
|
|||||||
* The marionette.port preference will be populated with the value
|
* The marionette.port preference will be populated with the value
|
||||||
* of |this.port|.
|
* of |this.port|.
|
||||||
*/
|
*/
|
||||||
start () {
|
start() {
|
||||||
if (this.alive) {
|
if (this.alive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -347,7 +357,7 @@ server.TCPListener = class {
|
|||||||
env.set(ENV_ENABLED, "1");
|
env.set(ENV_ENABLED, "1");
|
||||||
}
|
}
|
||||||
|
|
||||||
stop () {
|
stop() {
|
||||||
if (!this.alive) {
|
if (!this.alive) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -363,12 +373,12 @@ server.TCPListener = class {
|
|||||||
}
|
}
|
||||||
this.alteredPrefs.clear();
|
this.alteredPrefs.clear();
|
||||||
|
|
||||||
Services.obs.notifyObservers(this, NOTIFY_RUNNING, false);
|
Services.obs.notifyObservers(this, NOTIFY_RUNNING);
|
||||||
|
|
||||||
this.alive = false;
|
this.alive = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSocketAccepted (serverSocket, clientSocket) {
|
onSocketAccepted(serverSocket, clientSocket) {
|
||||||
if (!this._acceptConnections) {
|
if (!this._acceptConnections) {
|
||||||
logger.warn("New connections are currently not accepted");
|
logger.warn("New connections are currently not accepted");
|
||||||
return;
|
return;
|
||||||
@ -383,12 +393,13 @@ server.TCPListener = class {
|
|||||||
conn.onclose = this.onConnectionClosed.bind(this);
|
conn.onclose = this.onConnectionClosed.bind(this);
|
||||||
this.conns.add(conn);
|
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();
|
conn.sayHello();
|
||||||
transport.ready();
|
transport.ready();
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnectionClosed (conn) {
|
onConnectionClosed(conn) {
|
||||||
logger.debug(`Closed connection ${conn.id}`);
|
logger.debug(`Closed connection ${conn.id}`);
|
||||||
this.conns.delete(conn);
|
this.conns.delete(conn);
|
||||||
}
|
}
|
||||||
@ -408,7 +419,7 @@ server.TCPListener = class {
|
|||||||
* Factory function that produces a |GeckoDriver|.
|
* Factory function that produces a |GeckoDriver|.
|
||||||
*/
|
*/
|
||||||
server.TCPConnection = class {
|
server.TCPConnection = class {
|
||||||
constructor (connID, transport, driverFactory) {
|
constructor(connID, transport, driverFactory) {
|
||||||
this.id = connID;
|
this.id = connID;
|
||||||
this.conn = transport;
|
this.conn = transport;
|
||||||
|
|
||||||
@ -432,7 +443,7 @@ server.TCPConnection = class {
|
|||||||
* Debugger transport callback that cleans up
|
* Debugger transport callback that cleans up
|
||||||
* after a connection is closed.
|
* after a connection is closed.
|
||||||
*/
|
*/
|
||||||
onClosed (reason) {
|
onClosed(reason) {
|
||||||
this.driver.deleteSession();
|
this.driver.deleteSession();
|
||||||
if (this.onclose) {
|
if (this.onclose) {
|
||||||
this.onclose(this);
|
this.onclose(this);
|
||||||
@ -451,7 +462,7 @@ server.TCPConnection = class {
|
|||||||
* message type, message ID, method name or error, and parameters
|
* message type, message ID, method name or error, and parameters
|
||||||
* or result.
|
* or result.
|
||||||
*/
|
*/
|
||||||
onPacket (data) {
|
onPacket(data) {
|
||||||
// unable to determine how to respond
|
// unable to determine how to respond
|
||||||
if (!Array.isArray(data)) {
|
if (!Array.isArray(data)) {
|
||||||
let e = new TypeError(
|
let e = new TypeError(
|
||||||
@ -505,7 +516,7 @@ server.TCPConnection = class {
|
|||||||
* @param {Command} cmd
|
* @param {Command} cmd
|
||||||
* The requested command to execute.
|
* The requested command to execute.
|
||||||
*/
|
*/
|
||||||
execute (cmd) {
|
execute(cmd) {
|
||||||
let resp = this.createResponse(cmd.id);
|
let resp = this.createResponse(cmd.id);
|
||||||
let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
|
let sendResponse = () => resp.sendConditionally(resp => !resp.sent);
|
||||||
let sendError = resp.sendError.bind(resp);
|
let sendError = resp.sendError.bind(resp);
|
||||||
@ -543,14 +554,14 @@ server.TCPConnection = class {
|
|||||||
* @return {message.Response}
|
* @return {message.Response}
|
||||||
* Response to the message with |msgID|.
|
* Response to the message with |msgID|.
|
||||||
*/
|
*/
|
||||||
createResponse (msgID) {
|
createResponse(msgID) {
|
||||||
if (typeof msgID != "number") {
|
if (typeof msgID != "number") {
|
||||||
msgID = -1;
|
msgID = -1;
|
||||||
}
|
}
|
||||||
return new Response(msgID, this.send.bind(this));
|
return new Response(msgID, this.send.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
sendError (err, cmdID) {
|
sendError(err, cmdID) {
|
||||||
let resp = new Response(cmdID, this.send.bind(this));
|
let resp = new Response(cmdID, this.send.bind(this));
|
||||||
resp.sendError(err);
|
resp.sendError(err);
|
||||||
}
|
}
|
||||||
@ -562,13 +573,13 @@ server.TCPConnection = class {
|
|||||||
* This is the only message sent by Marionette that does not follow
|
* This is the only message sent by Marionette that does not follow
|
||||||
* the regular message format.
|
* the regular message format.
|
||||||
*/
|
*/
|
||||||
sayHello () {
|
sayHello() {
|
||||||
let whatHo = {
|
let whatHo = {
|
||||||
applicationType: "gecko",
|
applicationType: "gecko",
|
||||||
marionetteProtocol: PROTOCOL_VERSION,
|
marionetteProtocol: PROTOCOL_VERSION,
|
||||||
};
|
};
|
||||||
this.sendRaw(whatHo);
|
this.sendRaw(whatHo);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delegates message to client based on the provided {@code cmdID}.
|
* Delegates message to client based on the provided {@code cmdID}.
|
||||||
@ -583,7 +594,7 @@ server.TCPConnection = class {
|
|||||||
* @param {Command,Response} msg
|
* @param {Command,Response} msg
|
||||||
* The command or response to send.
|
* The command or response to send.
|
||||||
*/
|
*/
|
||||||
send (msg) {
|
send(msg) {
|
||||||
msg.origin = MessageOrigin.Server;
|
msg.origin = MessageOrigin.Server;
|
||||||
if (msg instanceof Command) {
|
if (msg instanceof Command) {
|
||||||
this.commands_.set(msg.id, msg);
|
this.commands_.set(msg.id, msg);
|
||||||
@ -601,10 +612,10 @@ server.TCPConnection = class {
|
|||||||
* @param {Response} resp
|
* @param {Response} resp
|
||||||
* The response to send back to the client.
|
* The response to send back to the client.
|
||||||
*/
|
*/
|
||||||
sendToClient (resp) {
|
sendToClient(resp) {
|
||||||
this.driver.responseCompleted();
|
this.driver.responseCompleted();
|
||||||
this.sendMessage(resp);
|
this.sendMessage(resp);
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marshal message to the Marionette message format and send it.
|
* Marshal message to the Marionette message format and send it.
|
||||||
@ -612,7 +623,7 @@ server.TCPConnection = class {
|
|||||||
* @param {Command,Response} msg
|
* @param {Command,Response} msg
|
||||||
* The message to send.
|
* The message to send.
|
||||||
*/
|
*/
|
||||||
sendMessage (msg) {
|
sendMessage(msg) {
|
||||||
this.log_(msg);
|
this.log_(msg);
|
||||||
let payload = msg.toMsg();
|
let payload = msg.toMsg();
|
||||||
this.sendRaw(payload);
|
this.sendRaw(payload);
|
||||||
@ -625,17 +636,17 @@ server.TCPConnection = class {
|
|||||||
* @param {Object} payload
|
* @param {Object} payload
|
||||||
* The payload to ship.
|
* The payload to ship.
|
||||||
*/
|
*/
|
||||||
sendRaw (payload) {
|
sendRaw(payload) {
|
||||||
this.conn.send(payload);
|
this.conn.send(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
log_ (msg) {
|
log_(msg) {
|
||||||
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
|
let a = (msg.origin == MessageOrigin.Client ? " -> " : " <- ");
|
||||||
let s = JSON.stringify(msg.toMsg());
|
let s = JSON.stringify(msg.toMsg());
|
||||||
logger.trace(this.id + a + s);
|
logger.trace(this.id + a + s);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString () {
|
toString() {
|
||||||
return `[object server.TCPConnection ${this.id}]`;
|
return `[object server.TCPConnection ${this.id}]`;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -32,7 +32,7 @@ this.session = {};
|
|||||||
|
|
||||||
/** Representation of WebDriver session timeouts. */
|
/** Representation of WebDriver session timeouts. */
|
||||||
session.Timeouts = class {
|
session.Timeouts = class {
|
||||||
constructor () {
|
constructor() {
|
||||||
// disabled
|
// disabled
|
||||||
this.implicit = 0;
|
this.implicit = 0;
|
||||||
// five mintues
|
// five mintues
|
||||||
@ -41,9 +41,9 @@ session.Timeouts = class {
|
|||||||
this.script = 30000;
|
this.script = 30000;
|
||||||
}
|
}
|
||||||
|
|
||||||
toString () { return "[object session.Timeouts]"; }
|
toString() { return "[object session.Timeouts]"; }
|
||||||
|
|
||||||
toJSON () {
|
toJSON() {
|
||||||
return {
|
return {
|
||||||
implicit: this.implicit,
|
implicit: this.implicit,
|
||||||
pageLoad: this.pageLoad,
|
pageLoad: this.pageLoad,
|
||||||
@ -51,7 +51,7 @@ session.Timeouts = class {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON (json) {
|
static fromJSON(json) {
|
||||||
assert.object(json);
|
assert.object(json);
|
||||||
let t = new session.Timeouts();
|
let t = new session.Timeouts();
|
||||||
|
|
||||||
@ -136,7 +136,8 @@ session.Proxy = class {
|
|||||||
|
|
||||||
case "pac":
|
case "pac":
|
||||||
Preferences.set("network.proxy.type", 2);
|
Preferences.set("network.proxy.type", 2);
|
||||||
Preferences.set("network.proxy.autoconfig_url", this.proxyAutoconfigUrl);
|
Preferences.set(
|
||||||
|
"network.proxy.autoconfig_url", this.proxyAutoconfigUrl);
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
case "autodetect":
|
case "autodetect":
|
||||||
@ -156,13 +157,13 @@ session.Proxy = class {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
toString () { return "[object session.Proxy]"; }
|
toString() { return "[object session.Proxy]"; }
|
||||||
|
|
||||||
toJSON () {
|
toJSON() {
|
||||||
return marshal({
|
return marshal({
|
||||||
proxyType: this.proxyType,
|
proxyType: this.proxyType,
|
||||||
httpProxy: this.httpProxy,
|
httpProxy: this.httpProxy,
|
||||||
httpProxyPort: this.httpProxyPort ,
|
httpProxyPort: this.httpProxyPort,
|
||||||
sslProxy: this.sslProxy,
|
sslProxy: this.sslProxy,
|
||||||
sslProxyPort: this.sslProxyPort,
|
sslProxyPort: this.sslProxyPort,
|
||||||
ftpProxy: this.ftpProxy,
|
ftpProxy: this.ftpProxy,
|
||||||
@ -174,7 +175,7 @@ session.Proxy = class {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromJSON (json) {
|
static fromJSON(json) {
|
||||||
let p = new session.Proxy();
|
let p = new session.Proxy();
|
||||||
if (typeof json == "undefined" || json === null) {
|
if (typeof json == "undefined" || json === null) {
|
||||||
return p;
|
return p;
|
||||||
@ -218,7 +219,7 @@ session.Proxy = class {
|
|||||||
|
|
||||||
/** WebDriver session capabilities representation. */
|
/** WebDriver session capabilities representation. */
|
||||||
session.Capabilities = class extends Map {
|
session.Capabilities = class extends Map {
|
||||||
constructor () {
|
constructor() {
|
||||||
super([
|
super([
|
||||||
// webdriver
|
// webdriver
|
||||||
["browserName", appinfo.name],
|
["browserName", appinfo.name],
|
||||||
@ -241,7 +242,7 @@ session.Capabilities = class extends Map {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
set (key, value) {
|
set(key, value) {
|
||||||
if (key === "timeouts" && !(value instanceof session.Timeouts)) {
|
if (key === "timeouts" && !(value instanceof session.Timeouts)) {
|
||||||
throw new TypeError();
|
throw new TypeError();
|
||||||
} else if (key === "proxy" && !(value instanceof session.Proxy)) {
|
} else if (key === "proxy" && !(value instanceof session.Proxy)) {
|
||||||
@ -272,7 +273,7 @@ session.Capabilities = class extends Map {
|
|||||||
* @return {session.Capabilities}
|
* @return {session.Capabilities}
|
||||||
* Internal representation of WebDriver capabilities.
|
* Internal representation of WebDriver capabilities.
|
||||||
*/
|
*/
|
||||||
static fromJSON (json, {merge = false} = {}) {
|
static fromJSON(json, {merge = false} = {}) {
|
||||||
if (typeof json == "undefined" || json === null) {
|
if (typeof json == "undefined" || json === null) {
|
||||||
json = {};
|
json = {};
|
||||||
}
|
}
|
||||||
@ -285,12 +286,13 @@ session.Capabilities = class extends Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Processes capabilities as described by WebDriver.
|
// Processes capabilities as described by WebDriver.
|
||||||
static merge_ (json) {
|
static merge_(json) {
|
||||||
for (let entry of [json.desiredCapabilities, json.requiredCapabilities]) {
|
for (let entry of [json.desiredCapabilities, json.requiredCapabilities]) {
|
||||||
if (typeof entry == "undefined" || entry === null) {
|
if (typeof entry == "undefined" || entry === null) {
|
||||||
continue;
|
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 desired = json.desiredCapabilities || {};
|
||||||
@ -302,7 +304,7 @@ session.Capabilities = class extends Map {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Matches capabilities as described by WebDriver.
|
// Matches capabilities as described by WebDriver.
|
||||||
static match_ (caps = {}) {
|
static match_(caps = {}) {
|
||||||
let matched = new session.Capabilities();
|
let matched = new session.Capabilities();
|
||||||
|
|
||||||
const defined = v => typeof v != "undefined" && v !== null;
|
const defined = v => typeof v != "undefined" && v !== null;
|
||||||
@ -311,11 +313,11 @@ session.Capabilities = class extends Map {
|
|||||||
// Iff |actual| provides some value, or is a wildcard or an exact
|
// Iff |actual| provides some value, or is a wildcard or an exact
|
||||||
// match of |expected|. This means it can be null or undefined,
|
// match of |expected|. This means it can be null or undefined,
|
||||||
// or "*", or "firefox".
|
// or "*", or "firefox".
|
||||||
function stringMatch (actual, expected) {
|
function stringMatch(actual, expected) {
|
||||||
return !defined(actual) || (wildcard(actual) || 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) {
|
switch (k) {
|
||||||
case "browserName":
|
case "browserName":
|
||||||
let bname = matched.get("browserName");
|
let bname = matched.get("browserName");
|
||||||
@ -368,7 +370,8 @@ session.Capabilities = class extends Map {
|
|||||||
if (Object.values(session.PageLoadStrategy).includes(v)) {
|
if (Object.values(session.PageLoadStrategy).includes(v)) {
|
||||||
matched.set("pageLoadStrategy", v);
|
matched.set("pageLoadStrategy", v);
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidArgumentError("Unknown page load strategy: " + v);
|
throw new InvalidArgumentError(
|
||||||
|
"Unknown page load strategy: " + v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -409,8 +412,8 @@ function marshal(obj) {
|
|||||||
|
|
||||||
function* iter(mapOrObject) {
|
function* iter(mapOrObject) {
|
||||||
if (mapOrObject instanceof Map) {
|
if (mapOrObject instanceof Map) {
|
||||||
for (const [k,v] of mapOrObject) {
|
for (const [k, v] of mapOrObject) {
|
||||||
yield [k,v];
|
yield [k, v];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (const k of Object.keys(mapOrObject)) {
|
for (const k of Object.keys(mapOrObject)) {
|
||||||
@ -419,7 +422,7 @@ function marshal(obj) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let [k,v] of iter(obj)) {
|
for (let [k, v] of iter(obj)) {
|
||||||
// Skip empty values when serialising to JSON.
|
// Skip empty values when serialising to JSON.
|
||||||
if (typeof v == "undefined" || v === null) {
|
if (typeof v == "undefined" || v === null) {
|
||||||
continue;
|
continue;
|
||||||
@ -429,10 +432,9 @@ function marshal(obj) {
|
|||||||
// JSON representation.
|
// JSON representation.
|
||||||
if (typeof v.toJSON == "function") {
|
if (typeof v.toJSON == "function") {
|
||||||
v = marshal(v.toJSON());
|
v = marshal(v.toJSON());
|
||||||
}
|
|
||||||
|
|
||||||
// Or do the same for object literals.
|
// Or do the same for object literals.
|
||||||
else if (isObject(v)) {
|
} else if (isObject(v)) {
|
||||||
v = marshal(v);
|
v = marshal(v);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
|
|
||||||
"use strict";
|
"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/EventEmitter.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
@ -19,37 +20,39 @@ this.EXPORTED_SYMBOLS = ["StreamUtils"];
|
|||||||
const BUFFER_SIZE = 0x8000;
|
const BUFFER_SIZE = 0x8000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This helper function (and its companion object) are used by bulk senders and
|
* This helper function (and its companion object) are used by bulk
|
||||||
* receivers to read and write data in and out of other streams. Functions that
|
* senders and receivers to read and write data in and out of other streams.
|
||||||
* make use of this tool are passed to callers when it is time to read or write
|
* Functions that make use of this tool are passed to callers when it is
|
||||||
* bulk data. It is highly recommended to use these copier functions instead of
|
* time to read or write bulk data. It is highly recommended to use these
|
||||||
* the stream directly because the copier enforces the agreed upon length.
|
* copier functions instead of the stream directly because the copier
|
||||||
* Since bulk mode reuses an existing stream, the sender and receiver must write
|
* enforces the agreed upon length. Since bulk mode reuses an existing
|
||||||
* and read exactly the agreed upon amount of data, or else the entire transport
|
* stream, the sender and receiver must write and read exactly the agreed
|
||||||
* will be left in a invalid state. Additionally, other methods of stream
|
* upon amount of data, or else the entire transport will be left in a
|
||||||
* copying (such as NetUtil.asyncCopy) close the streams involved, which would
|
* invalid state. Additionally, other methods of stream copying (such as
|
||||||
* terminate the debugging transport, and so it is avoided here.
|
* 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
|
* Overall, this *works*, but clearly the optimal solution would be
|
||||||
* use the streams directly. If it were possible to fully implement
|
* able to just use the streams directly. If it were possible to fully
|
||||||
* nsIInputStream / nsIOutputStream in JS, wrapper streams could be created to
|
* implement nsIInputStream/nsIOutputStream in JS, wrapper streams could
|
||||||
* enforce the length and avoid closing, and consumers could use familiar stream
|
* be created to enforce the length and avoid closing, and consumers could
|
||||||
* utilities like NetUtil.asyncCopy.
|
* use familiar stream utilities like NetUtil.asyncCopy.
|
||||||
*
|
*
|
||||||
* The function takes two async streams and copies a precise number of bytes
|
* The function takes two async streams and copies a precise number
|
||||||
* from one to the other. Copying begins immediately, but may complete at some
|
* of bytes from one to the other. Copying begins immediately, but may
|
||||||
* future time depending on data size. Use the returned promise to know when
|
* complete at some future time depending on data size. Use the returned
|
||||||
* it's complete.
|
* promise to know when it's complete.
|
||||||
*
|
*
|
||||||
* @param input nsIAsyncInputStream
|
* @param {nsIAsyncInputStream} input
|
||||||
* The stream to copy from.
|
* Stream to copy from.
|
||||||
* @param output nsIAsyncOutputStream
|
* @param {nsIAsyncOutputStream} output
|
||||||
* The stream to copy to.
|
* Stream to copy to.
|
||||||
* @param length Integer
|
* @param {number} length
|
||||||
* The amount of data that needs to be copied.
|
* Amount of data that needs to be copied.
|
||||||
* @return Promise
|
*
|
||||||
* The promise is resolved when copying completes or rejected if any
|
* @return {Promise}
|
||||||
* (unexpected) errors occur.
|
* Promise is resolved when copying completes or rejected if any
|
||||||
|
* (unexpected) errors occur.
|
||||||
*/
|
*/
|
||||||
function copyStream(input, output, length) {
|
function copyStream(input, output, length) {
|
||||||
let copier = new StreamCopier(input, output, length);
|
let copier = new StreamCopier(input, output, length);
|
||||||
@ -60,7 +63,8 @@ function StreamCopier(input, output, length) {
|
|||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
this._id = StreamCopier._nextId++;
|
this._id = StreamCopier._nextId++;
|
||||||
this.input = input;
|
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;
|
this.baseAsyncOutput = output;
|
||||||
if (IOUtil.outputStreamIsBuffered(output)) {
|
if (IOUtil.outputStreamIsBuffered(output)) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
@ -75,7 +79,7 @@ function StreamCopier(input, output, length) {
|
|||||||
promise: new Promise((resolve, reject) => {
|
promise: new Promise((resolve, reject) => {
|
||||||
this._deferred.resolve = resolve;
|
this._deferred.resolve = resolve;
|
||||||
this._deferred.reject = reject;
|
this._deferred.reject = reject;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
this._copy = this._copy.bind(this);
|
this._copy = this._copy.bind(this);
|
||||||
@ -83,21 +87,22 @@ function StreamCopier(input, output, length) {
|
|||||||
this._destroy = this._destroy.bind(this);
|
this._destroy = this._destroy.bind(this);
|
||||||
|
|
||||||
// Copy promise's then method up to this object.
|
// 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
|
// Allows the copier to offer a promise interface for the simple succeed
|
||||||
// states, like progress.
|
// 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._deferred.promise.then.bind(this._deferred.promise);
|
||||||
this.then(this._destroy, this._destroy);
|
this.then(this._destroy, this._destroy);
|
||||||
|
|
||||||
// Stream ready callback starts as |_copy|, but may switch to |_flush| at end
|
// Stream ready callback starts as |_copy|, but may switch to |_flush|
|
||||||
// if flushing would block the output stream.
|
// at end if flushing would block the output stream.
|
||||||
this._streamReadyCallback = this._copy;
|
this._streamReadyCallback = this._copy;
|
||||||
}
|
}
|
||||||
StreamCopier._nextId = 0;
|
StreamCopier._nextId = 0;
|
||||||
|
|
||||||
StreamCopier.prototype = {
|
StreamCopier.prototype = {
|
||||||
|
|
||||||
copy: function () {
|
copy() {
|
||||||
// Dispatch to the next tick so that it's possible to attach a progress
|
// Dispatch to the next tick so that it's possible to attach a progress
|
||||||
// event listener, even for extremely fast copies (like when testing).
|
// event listener, even for extremely fast copies (like when testing).
|
||||||
Services.tm.currentThread.dispatch(() => {
|
Services.tm.currentThread.dispatch(() => {
|
||||||
@ -110,7 +115,7 @@ StreamCopier.prototype = {
|
|||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
_copy: function () {
|
_copy() {
|
||||||
let bytesAvailable = this.input.available();
|
let bytesAvailable = this.input.available();
|
||||||
let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
|
let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
|
||||||
this._debug("Trying to copy: " + amountToCopy);
|
this._debug("Trying to copy: " + amountToCopy);
|
||||||
@ -143,14 +148,14 @@ StreamCopier.prototype = {
|
|||||||
this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
|
this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
|
||||||
},
|
},
|
||||||
|
|
||||||
_emitProgress: function () {
|
_emitProgress() {
|
||||||
this.emit("progress", {
|
this.emit("progress", {
|
||||||
bytesSent: this._length - this._amountLeft,
|
bytesSent: this._length - this._amountLeft,
|
||||||
totalBytes: this._length
|
totalBytes: this._length,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_flush: function () {
|
_flush() {
|
||||||
try {
|
try {
|
||||||
this.output.flush();
|
this.output.flush();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -167,7 +172,7 @@ StreamCopier.prototype = {
|
|||||||
this._deferred.resolve();
|
this._deferred.resolve();
|
||||||
},
|
},
|
||||||
|
|
||||||
_destroy: function () {
|
_destroy() {
|
||||||
this._destroy = null;
|
this._destroy = null;
|
||||||
this._copy = null;
|
this._copy = null;
|
||||||
this._flush = null;
|
this._flush = null;
|
||||||
@ -176,37 +181,40 @@ StreamCopier.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// nsIInputStreamCallback
|
// nsIInputStreamCallback
|
||||||
onInputStreamReady: function () {
|
onInputStreamReady() {
|
||||||
this._streamReadyCallback();
|
this._streamReadyCallback();
|
||||||
},
|
},
|
||||||
|
|
||||||
// nsIOutputStreamCallback
|
// nsIOutputStreamCallback
|
||||||
onOutputStreamReady: function () {
|
onOutputStreamReady() {
|
||||||
this._streamReadyCallback();
|
this._streamReadyCallback();
|
||||||
},
|
},
|
||||||
|
|
||||||
_debug: function (msg) {
|
_debug(msg) {
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read from a stream, one byte at a time, up to the next |delimiter|
|
* 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
|
* character, but stopping if we've read |count| without finding it.
|
||||||
* also terminates early if there are less than |count| bytes available on the
|
* Reading also terminates early if there are less than |count| bytes
|
||||||
* stream. In that case, we only read as many bytes as the stream currently has
|
* available on the stream. In that case, we only read as many bytes as
|
||||||
* to offer.
|
* 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.
|
* TODO: This implementation could be removed if bug 984651 is fixed,
|
||||||
* @param stream nsIInputStream
|
* which provides a native version of the same idea.
|
||||||
* The input stream to read from.
|
*
|
||||||
* @param delimiter string
|
* @param {nsIInputStream} stream
|
||||||
* The character we're trying to find.
|
* Input stream to read from.
|
||||||
* @param count integer
|
* @param {string} delimiter
|
||||||
* The max number of characters to read while searching.
|
* Character we're trying to find.
|
||||||
* @return string
|
* @param {number} count
|
||||||
* The data collected. If the delimiter was found, this string will
|
* Max number of characters to read while searching.
|
||||||
* end with it.
|
*
|
||||||
|
* @return {string}
|
||||||
|
* Collected data. If the delimiter was found, this string will
|
||||||
|
* end with it.
|
||||||
*/
|
*/
|
||||||
function delimitedRead(stream, delimiter, count) {
|
function delimitedRead(stream, delimiter, count) {
|
||||||
let scriptableStream;
|
let scriptableStream;
|
||||||
@ -235,8 +243,7 @@ function delimitedRead(stream, delimiter, count) {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
const StreamUtils = {
|
this.StreamUtils = {
|
||||||
copyStream,
|
copyStream,
|
||||||
delimitedRead
|
delimitedRead,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -128,7 +128,11 @@ add_test(function test_toJSON() {
|
|||||||
equal(e1s.message, e1.message);
|
equal(e1s.message, e1.message);
|
||||||
equal(e1s.stacktrace, e1.stack);
|
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();
|
let e2s = e2.toJSON();
|
||||||
equal(e2.status, e2s.error);
|
equal(e2.status, e2s.error);
|
||||||
equal(e2.message, e2s.message);
|
equal(e2.message, e2s.message);
|
||||||
@ -311,9 +315,9 @@ add_test(function test_JavaScriptError() {
|
|||||||
|
|
||||||
equal("undefined", new JavaScriptError(undefined).message);
|
equal("undefined", new JavaScriptError(undefined).message);
|
||||||
// TODO(ato): Bug 1240550
|
// 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",
|
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
|
// TODO(ato): More exhaustive tests for JS stack computation
|
||||||
|
|
||||||
|
@ -6,31 +6,36 @@
|
|||||||
|
|
||||||
/* global Pipe, ScriptableInputStream, uneval */
|
/* 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/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/EventEmitter.jsm");
|
Cu.import("resource://gre/modules/EventEmitter.jsm");
|
||||||
Cu.import("chrome://marionette/content/stream-utils.js");
|
const {StreamUtils} =
|
||||||
const { Packet, JSONPacket, BulkPacket } =
|
Cu.import("chrome://marionette/content/stream-utils.js", {});
|
||||||
Cu.import("chrome://marionette/content/packets.js");
|
const {Packet, JSONPacket, BulkPacket} =
|
||||||
const defer = function () {
|
Cu.import("chrome://marionette/content/packets.js", {});
|
||||||
|
|
||||||
|
const defer = function() {
|
||||||
let deferred = {
|
let deferred = {
|
||||||
promise: new Promise((resolve, reject) => {
|
promise: new Promise((resolve, reject) => {
|
||||||
deferred.resolve = resolve;
|
deferred.resolve = resolve;
|
||||||
deferred.reject = reject;
|
deferred.reject = reject;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
return deferred;
|
return deferred;
|
||||||
};
|
};
|
||||||
const executeSoon = function (func) {
|
|
||||||
|
const executeSoon = function(func) {
|
||||||
Services.tm.dispatchToMainThread(func);
|
Services.tm.dispatchToMainThread(func);
|
||||||
};
|
};
|
||||||
const flags = { wantVerbose: false, wantLogging: false };
|
|
||||||
|
const flags = {wantVerbose: false, wantLogging: false};
|
||||||
|
|
||||||
const dumpv =
|
const dumpv =
|
||||||
flags.wantVerbose ?
|
flags.wantVerbose ?
|
||||||
function (msg) {dump(msg + "\n");} :
|
function(msg) { dump(msg + "\n"); } :
|
||||||
function () {};
|
function() {};
|
||||||
|
|
||||||
const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
|
const Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
|
||||||
|
|
||||||
@ -42,23 +47,24 @@ this.EXPORTED_SYMBOLS = ["DebuggerTransport"];
|
|||||||
const PACKET_HEADER_MAX = 200;
|
const PACKET_HEADER_MAX = 200;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter that handles data transfers between the debugger client and
|
* An adapter that handles data transfers between the debugger client
|
||||||
* server. It can work with both nsIPipe and nsIServerSocket transports so
|
* and server. It can work with both nsIPipe and nsIServerSocket
|
||||||
* long as the properly created input and output streams are specified.
|
* transports so long as the properly created input and output streams
|
||||||
* (However, for intra-process connections, LocalDebuggerTransport, below,
|
* are specified. (However, for intra-process connections,
|
||||||
* is more efficient than using an nsIPipe pair with DebuggerTransport.)
|
* LocalDebuggerTransport, below, is more efficient than using an nsIPipe
|
||||||
|
* pair with DebuggerTransport.)
|
||||||
*
|
*
|
||||||
* @param input nsIAsyncInputStream
|
* @param {nsIAsyncInputStream} input
|
||||||
* The input stream.
|
* The input stream.
|
||||||
* @param output nsIAsyncOutputStream
|
* @param {nsIAsyncOutputStream} output
|
||||||
* The output stream.
|
* The output stream.
|
||||||
*
|
*
|
||||||
* Given a DebuggerTransport instance dt:
|
* Given a DebuggerTransport instance dt:
|
||||||
* 1) Set dt.hooks to a packet handler object (described below).
|
* 1) Set dt.hooks to a packet handler object (described below).
|
||||||
* 2) Call dt.ready() to begin watching for input packets.
|
* 2) Call dt.ready() to begin watching for input packets.
|
||||||
* 3) Call dt.send() / dt.startBulkSend() to send packets.
|
* 3) Call dt.send() / dt.startBulkSend() to send packets.
|
||||||
* 4) Call dt.close() to close the connection, and disengage from the event
|
* 4) Call dt.close() to close the connection, and disengage from
|
||||||
* loop.
|
* the event loop.
|
||||||
*
|
*
|
||||||
* A packet handler is an object with the following methods:
|
* A packet handler is an object with the following methods:
|
||||||
*
|
*
|
||||||
@ -71,32 +77,34 @@ const PACKET_HEADER_MAX = 200;
|
|||||||
* * actor: Name of actor that will receive the packet
|
* * actor: Name of actor that will receive the packet
|
||||||
* * type: Name of actor's method that should be called on receipt
|
* * type: Name of actor's method that should be called on receipt
|
||||||
* * length: Size of the data to be read
|
* * length: Size of the data to be read
|
||||||
* * stream: This input stream should only be used directly if you can ensure
|
* * stream: This input stream should only be used directly if you
|
||||||
* that you will read exactly |length| bytes and will not close the
|
* can ensure that you will read exactly |length| bytes and
|
||||||
* stream when reading is complete
|
* will not close the stream when reading is complete
|
||||||
* * done: If you use the stream directly (instead of |copyTo| below), you
|
* * done: If you use the stream directly (instead of |copyTo|
|
||||||
* must signal completion by resolving / rejecting this deferred.
|
* below), you must signal completion by resolving/rejecting
|
||||||
* If it's rejected, the transport will be closed. If an Error is
|
* this deferred. If it's rejected, the transport will
|
||||||
* supplied as a rejection value, it will be logged via |dump|.
|
* be closed. If an Error is supplied as a rejection value,
|
||||||
* If you do use |copyTo|, resolving is taken care of for you when
|
* it will be logged via |dump|. If you do use |copyTo|,
|
||||||
* copying completes.
|
* resolving is taken care of for you when copying completes.
|
||||||
* * copyTo: A helper function for getting your data out of the stream that
|
* * copyTo: A helper function for getting your data out of the
|
||||||
* meets the stream handling requirements above, and has the
|
* stream that meets the stream handling requirements above,
|
||||||
* following signature:
|
* 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.
|
|
||||||
*
|
*
|
||||||
* - onClosed(reason) - called when the connection is closed. |reason| is
|
* @param nsIAsyncOutputStream {output}
|
||||||
* an optional nsresult or object, typically passed when the transport is
|
* The stream to copy to.
|
||||||
* closed due to some error in a underlying stream.
|
|
||||||
*
|
*
|
||||||
* See ./packets.js and the Remote Debugging Protocol specification for more
|
* @return {Promise}
|
||||||
* details on the format of these packets.
|
* 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) {
|
function DebuggerTransport(input, output) {
|
||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
@ -105,8 +113,8 @@ function DebuggerTransport(input, output) {
|
|||||||
this._scriptableInput = new ScriptableInputStream(input);
|
this._scriptableInput = new ScriptableInputStream(input);
|
||||||
this._output = output;
|
this._output = output;
|
||||||
|
|
||||||
// The current incoming (possibly partial) header, which will determine which
|
// The current incoming (possibly partial) header, which will determine
|
||||||
// type of Packet |_incoming| below will become.
|
// which type of Packet |_incoming| below will become.
|
||||||
this._incomingHeader = "";
|
this._incomingHeader = "";
|
||||||
// The current incoming Packet object
|
// The current incoming Packet object
|
||||||
this._incoming = null;
|
this._incoming = null;
|
||||||
@ -128,10 +136,10 @@ DebuggerTransport.prototype = {
|
|||||||
*
|
*
|
||||||
* This method returns immediately, without waiting for the entire
|
* This method returns immediately, without waiting for the entire
|
||||||
* packet to be transmitted, registering event handlers as needed to
|
* packet to be transmitted, registering event handlers as needed to
|
||||||
* transmit the entire packet. Packets are transmitted in the order
|
* transmit the entire packet. Packets are transmitted in the order they
|
||||||
* they are passed to this method.
|
* are passed to this method.
|
||||||
*/
|
*/
|
||||||
send: function (object) {
|
send(object) {
|
||||||
this.emit("send", object);
|
this.emit("send", object);
|
||||||
|
|
||||||
let packet = new JSONPacket(this);
|
let packet = new JSONPacket(this);
|
||||||
@ -143,45 +151,52 @@ DebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Transmit streaming data via a bulk packet.
|
* Transmit streaming data via a bulk packet.
|
||||||
*
|
*
|
||||||
* This method initiates the bulk send process by queuing up the header data.
|
* This method initiates the bulk send process by queuing up the header
|
||||||
* The caller receives eventual access to a stream for writing.
|
* 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
|
* N.B.: Do *not* attempt to close the stream handed to you, as it
|
||||||
* continue to be used by this transport afterwards. Most users should
|
* will continue to be used by this transport afterwards. Most users
|
||||||
* instead use the provided |copyFrom| function instead.
|
* should instead use the provided |copyFrom| function instead.
|
||||||
*
|
*
|
||||||
* @param header Object
|
* @param {Object} header
|
||||||
* This is modeled after the format of JSON packets above, but does not
|
* This is modeled after the format of JSON packets above, but does
|
||||||
* actually contain the data, but is instead just a routing header:
|
* not actually contain the data, but is instead just a routing
|
||||||
* * actor: Name of actor that will receive the packet
|
* header:
|
||||||
* * type: Name of actor's method that should be called on receipt
|
*
|
||||||
* * length: Size of the data to be sent
|
* - actor: Name of actor that will receive the packet
|
||||||
* @return Promise
|
* - type: Name of actor's method that should be called on receipt
|
||||||
* The promise will be resolved when you are allowed to write to the
|
* - length: Size of the data to be sent
|
||||||
* stream with an object containing:
|
*
|
||||||
* * stream: This output stream should only be used directly if
|
* @return {Promise}
|
||||||
* you can ensure that you will write exactly |length|
|
* The promise will be resolved when you are allowed to write to
|
||||||
* bytes and will not close the stream when writing is
|
* the stream with an object containing:
|
||||||
* complete
|
*
|
||||||
* * done: If you use the stream directly (instead of |copyFrom|
|
* - stream: This output stream should only be used directly
|
||||||
* below), you must signal completion by resolving /
|
* if you can ensure that you will write exactly
|
||||||
* rejecting this deferred. If it's rejected, the
|
* |length| bytes and will not close the stream when
|
||||||
* transport will be closed. If an Error is supplied as
|
* writing is complete.
|
||||||
* a rejection value, it will be logged via |dump|. If
|
* - done: If you use the stream directly (instead of
|
||||||
* you do use |copyFrom|, resolving is taken care of for
|
* |copyFrom| below), you must signal completion by
|
||||||
* you when copying completes.
|
* resolving/rejecting this deferred. If it's
|
||||||
* * copyFrom: A helper function for getting your data onto the
|
* rejected, the transport will be closed. If an
|
||||||
* stream that meets the stream handling requirements
|
* Error is supplied as a rejection value, it will
|
||||||
* above, and has the following signature:
|
* be logged via |dump|. If you do use |copyFrom|,
|
||||||
* @param input nsIAsyncInputStream
|
* resolving is taken care of for you when copying
|
||||||
* The stream to copy from.
|
* completes.
|
||||||
* @return Promise
|
* - copyFrom: A helper function for getting your data onto the
|
||||||
* The promise is resolved when copying completes or
|
* stream that meets the stream handling requirements
|
||||||
* rejected if any (unexpected) errors occur.
|
* above, and has the following signature:
|
||||||
* This object also emits "progress" events for each chunk
|
*
|
||||||
* that is copied. See stream-utils.js.
|
* @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);
|
this.emit("startbulksend", header);
|
||||||
|
|
||||||
let packet = new BulkPacket(this);
|
let packet = new BulkPacket(this);
|
||||||
@ -193,11 +208,13 @@ DebuggerTransport.prototype = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the transport.
|
* Close the transport.
|
||||||
* @param reason nsresult / object (optional)
|
*
|
||||||
* The status code or error message that corresponds to the reason for
|
* @param {(nsresult|object)=} reason
|
||||||
* closing the transport (likely because a stream closed or failed).
|
* 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.emit("close", reason);
|
||||||
|
|
||||||
this.active = false;
|
this.active = false;
|
||||||
@ -225,10 +242,11 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush data to the outgoing stream. Waits until the output stream notifies
|
* Flush data to the outgoing stream. Waits until the output
|
||||||
* us that it is ready to be written to (via onOutputStreamReady).
|
* stream notifies us that it is ready to be written to (via
|
||||||
|
* onOutputStreamReady).
|
||||||
*/
|
*/
|
||||||
_flushOutgoing: function () {
|
_flushOutgoing() {
|
||||||
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -245,29 +263,29 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause this transport's attempts to write to the output stream. This is
|
* Pause this transport's attempts to write to the output stream.
|
||||||
* used when we've temporarily handed off our output stream for writing bulk
|
* This is used when we've temporarily handed off our output stream for
|
||||||
* data.
|
* writing bulk data.
|
||||||
*/
|
*/
|
||||||
pauseOutgoing: function () {
|
pauseOutgoing() {
|
||||||
this._outgoingEnabled = false;
|
this._outgoingEnabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume this transport's attempts to write to the output stream.
|
* Resume this transport's attempts to write to the output stream.
|
||||||
*/
|
*/
|
||||||
resumeOutgoing: function () {
|
resumeOutgoing() {
|
||||||
this._outgoingEnabled = true;
|
this._outgoingEnabled = true;
|
||||||
this._flushOutgoing();
|
this._flushOutgoing();
|
||||||
},
|
},
|
||||||
|
|
||||||
// nsIOutputStreamCallback
|
// nsIOutputStreamCallback
|
||||||
/**
|
/**
|
||||||
* This is called when the output stream is ready for more data to be written.
|
* This is called when the output stream is ready for more data to
|
||||||
* The current outgoing packet will attempt to write some amount of data, but
|
* be written. The current outgoing packet will attempt to write some
|
||||||
* may not complete.
|
* amount of data, but may not complete.
|
||||||
*/
|
*/
|
||||||
onOutputStreamReady: function (stream) {
|
onOutputStreamReady(stream) {
|
||||||
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
if (!this._outgoingEnabled || this._outgoing.length === 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -288,7 +306,7 @@ DebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Remove the current outgoing packet from the queue upon completion.
|
* Remove the current outgoing packet from the queue upon completion.
|
||||||
*/
|
*/
|
||||||
_finishCurrentOutgoing: function () {
|
_finishCurrentOutgoing() {
|
||||||
if (this._currentOutgoing) {
|
if (this._currentOutgoing) {
|
||||||
this._currentOutgoing.destroy();
|
this._currentOutgoing.destroy();
|
||||||
this._outgoing.shift();
|
this._outgoing.shift();
|
||||||
@ -298,7 +316,7 @@ DebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Clear the entire outgoing queue.
|
* Clear the entire outgoing queue.
|
||||||
*/
|
*/
|
||||||
_destroyAllOutgoing: function () {
|
_destroyAllOutgoing() {
|
||||||
for (let packet of this._outgoing) {
|
for (let packet of this._outgoing) {
|
||||||
packet.destroy();
|
packet.destroy();
|
||||||
}
|
}
|
||||||
@ -306,11 +324,11 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the input stream for reading. Once this method has been called,
|
* Initialize the input stream for reading. Once this method has been
|
||||||
* we watch for packets on the input stream, and pass them to the appropriate
|
* called, we watch for packets on the input stream, and pass them to
|
||||||
* handlers via this.hooks.
|
* the appropriate handlers via this.hooks.
|
||||||
*/
|
*/
|
||||||
ready: function () {
|
ready() {
|
||||||
this.active = true;
|
this.active = true;
|
||||||
this._waitForIncoming();
|
this._waitForIncoming();
|
||||||
},
|
},
|
||||||
@ -319,7 +337,7 @@ DebuggerTransport.prototype = {
|
|||||||
* Asks the input stream to notify us (via onInputStreamReady) when it is
|
* Asks the input stream to notify us (via onInputStreamReady) when it is
|
||||||
* ready for reading.
|
* ready for reading.
|
||||||
*/
|
*/
|
||||||
_waitForIncoming: function () {
|
_waitForIncoming() {
|
||||||
if (this._incomingEnabled) {
|
if (this._incomingEnabled) {
|
||||||
let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
|
let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
|
||||||
this._input.asyncWait(this, 0, 0, threadManager.currentThread);
|
this._input.asyncWait(this, 0, 0, threadManager.currentThread);
|
||||||
@ -327,18 +345,18 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pause this transport's attempts to read from the input stream. This is
|
* Pause this transport's attempts to read from the input stream.
|
||||||
* used when we've temporarily handed off our input stream for reading bulk
|
* This is used when we've temporarily handed off our input stream for
|
||||||
* data.
|
* reading bulk data.
|
||||||
*/
|
*/
|
||||||
pauseIncoming: function () {
|
pauseIncoming() {
|
||||||
this._incomingEnabled = false;
|
this._incomingEnabled = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resume this transport's attempts to read from the input stream.
|
* Resume this transport's attempts to read from the input stream.
|
||||||
*/
|
*/
|
||||||
resumeIncoming: function () {
|
resumeIncoming() {
|
||||||
this._incomingEnabled = true;
|
this._incomingEnabled = true;
|
||||||
this._flushIncoming();
|
this._flushIncoming();
|
||||||
this._waitForIncoming();
|
this._waitForIncoming();
|
||||||
@ -348,7 +366,7 @@ DebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Called when the stream is either readable or closed.
|
* Called when the stream is either readable or closed.
|
||||||
*/
|
*/
|
||||||
onInputStreamReady: function (stream) {
|
onInputStreamReady(stream) {
|
||||||
try {
|
try {
|
||||||
while (stream.available() && this._incomingEnabled &&
|
while (stream.available() && this._incomingEnabled &&
|
||||||
this._processIncoming(stream, stream.available())) {
|
this._processIncoming(stream, stream.available())) {
|
||||||
@ -365,16 +383,17 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process the incoming data. Will create a new currently incoming Packet if
|
* Process the incoming data. Will create a new currently incoming
|
||||||
* needed. Tells the incoming Packet to read as much data as it can, but
|
* Packet if needed. Tells the incoming Packet to read as much data
|
||||||
* reading may not complete. The Packet signals that its data is ready for
|
* as it can, but reading may not complete. The Packet signals that
|
||||||
* delivery by calling one of this transport's _on*Ready methods (see
|
* its data is ready for delivery by calling one of this transport's
|
||||||
* ./packets.js and the _on*Ready methods below).
|
* _on*Ready methods (see ./packets.js and the _on*Ready methods below).
|
||||||
* @return boolean
|
*
|
||||||
* Whether incoming stream processing should continue for any
|
* @return {boolean}
|
||||||
* remaining data.
|
* Whether incoming stream processing should continue for any
|
||||||
|
* remaining data.
|
||||||
*/
|
*/
|
||||||
_processIncoming: function (stream, count) {
|
_processIncoming(stream, count) {
|
||||||
dumpv("Data available: " + count);
|
dumpv("Data available: " + count);
|
||||||
|
|
||||||
if (!count) {
|
if (!count) {
|
||||||
@ -406,8 +425,7 @@ DebuggerTransport.prototype = {
|
|||||||
this._incoming.read(stream, this._scriptableInput);
|
this._incoming.read(stream, this._scriptableInput);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
let msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
|
dump(`Error reading incoming packet: (${e} - ${e.stack})\n`);
|
||||||
dump(msg + "\n");
|
|
||||||
|
|
||||||
// Now in an invalid state, shut down the transport.
|
// Now in an invalid state, shut down the transport.
|
||||||
this.close();
|
this.close();
|
||||||
@ -426,13 +444,14 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read as far as we can into the incoming data, attempting to build up a
|
* Read as far as we can into the incoming data, attempting to build
|
||||||
* complete packet header (which terminates with ":"). We'll only read up to
|
* up a complete packet header (which terminates with ":"). We'll only
|
||||||
* PACKET_HEADER_MAX characters.
|
* read up to PACKET_HEADER_MAX characters.
|
||||||
* @return boolean
|
*
|
||||||
* True if we now have a complete header.
|
* @return {boolean}
|
||||||
|
* True if we now have a complete header.
|
||||||
*/
|
*/
|
||||||
_readHeader: function () {
|
_readHeader() {
|
||||||
let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
|
let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
|
||||||
this._incomingHeader +=
|
this._incomingHeader +=
|
||||||
StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
|
StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
|
||||||
@ -458,7 +477,7 @@ DebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* If the incoming packet is done, log it as needed and clear the buffer.
|
* If the incoming packet is done, log it as needed and clear the buffer.
|
||||||
*/
|
*/
|
||||||
_flushIncoming: function () {
|
_flushIncoming() {
|
||||||
if (!this._incoming.done) {
|
if (!this._incoming.done) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -469,10 +488,10 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler triggered by an incoming JSONPacket completing it's |read| method.
|
* Handler triggered by an incoming JSONPacket completing it's |read|
|
||||||
* Delivers the packet to this.hooks.onPacket.
|
* method. Delivers the packet to this.hooks.onPacket.
|
||||||
*/
|
*/
|
||||||
_onJSONObjectReady: function (object) {
|
_onJSONObjectReady(object) {
|
||||||
executeSoon(() => {
|
executeSoon(() => {
|
||||||
// Ensure the transport is still alive by the time this runs.
|
// Ensure the transport is still alive by the time this runs.
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
@ -483,12 +502,12 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler triggered by an incoming BulkPacket entering the |read| phase for
|
* Handler triggered by an incoming BulkPacket entering the |read|
|
||||||
* the stream portion of the packet. Delivers info about the incoming
|
* phase for the stream portion of the packet. Delivers info about the
|
||||||
* streaming data to this.hooks.onBulkPacket. See the main comment on the
|
* incoming streaming data to this.hooks.onBulkPacket. See the main
|
||||||
* transport at the top of this file for more details.
|
* comment on the transport at the top of this file for more details.
|
||||||
*/
|
*/
|
||||||
_onBulkReadReady: function (...args) {
|
_onBulkReadReady(...args) {
|
||||||
executeSoon(() => {
|
executeSoon(() => {
|
||||||
// Ensure the transport is still alive by the time this runs.
|
// Ensure the transport is still alive by the time this runs.
|
||||||
if (this.active) {
|
if (this.active) {
|
||||||
@ -499,29 +518,30 @@ DebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove all handlers and references related to the current incoming packet,
|
* Remove all handlers and references related to the current incoming
|
||||||
* either because it is now complete or because the transport is closing.
|
* packet, either because it is now complete or because the transport
|
||||||
|
* is closing.
|
||||||
*/
|
*/
|
||||||
_destroyIncoming: function () {
|
_destroyIncoming() {
|
||||||
if (this._incoming) {
|
if (this._incoming) {
|
||||||
this._incoming.destroy();
|
this._incoming.destroy();
|
||||||
}
|
}
|
||||||
this._incomingHeader = "";
|
this._incomingHeader = "";
|
||||||
this._incoming = null;
|
this._incoming = null;
|
||||||
}
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An adapter that handles data transfers between the debugger client and
|
* An adapter that handles data transfers between the debugger client
|
||||||
* server when they both run in the same process. It presents the same API as
|
* and server when they both run in the same process. It presents the
|
||||||
* DebuggerTransport, but instead of transmitting serialized messages across a
|
* same API as DebuggerTransport, but instead of transmitting serialized
|
||||||
* connection it merely calls the packet dispatcher of the other side.
|
* messages across a connection it merely calls the packet dispatcher of
|
||||||
|
* the other side.
|
||||||
*
|
*
|
||||||
* @param other LocalDebuggerTransport
|
* @param {LocalDebuggerTransport} other
|
||||||
* The other endpoint for this debugger connection.
|
* The other endpoint for this debugger connection.
|
||||||
*
|
*
|
||||||
* @see DebuggerTransport
|
* @see {DebuggerTransport}
|
||||||
*/
|
*/
|
||||||
function LocalDebuggerTransport(other) {
|
function LocalDebuggerTransport(other) {
|
||||||
EventEmitter.decorate(this);
|
EventEmitter.decorate(this);
|
||||||
@ -529,9 +549,10 @@ function LocalDebuggerTransport(other) {
|
|||||||
this.other = other;
|
this.other = other;
|
||||||
this.hooks = null;
|
this.hooks = null;
|
||||||
|
|
||||||
// A packet number, shared between this and this.other. This isn't used by the
|
// A packet number, shared between this and this.other. This isn't
|
||||||
// protocol at all, but it makes the packet traces a lot easier to follow.
|
// used by the protocol at all, but it makes the packet traces a lot
|
||||||
this._serial = this.other ? this.other._serial : { count: 0 };
|
// easier to follow.
|
||||||
|
this._serial = this.other ? this.other._serial : {count: 0};
|
||||||
this.close = this.close.bind(this);
|
this.close = this.close.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -540,7 +561,7 @@ LocalDebuggerTransport.prototype = {
|
|||||||
* Transmit a message by directly calling the onPacket handler of the other
|
* Transmit a message by directly calling the onPacket handler of the other
|
||||||
* endpoint.
|
* endpoint.
|
||||||
*/
|
*/
|
||||||
send: function (packet) {
|
send(packet) {
|
||||||
this.emit("send", packet);
|
this.emit("send", packet);
|
||||||
|
|
||||||
let serial = this._serial.count++;
|
let serial = this._serial.count++;
|
||||||
@ -558,7 +579,8 @@ LocalDebuggerTransport.prototype = {
|
|||||||
executeSoon(() => {
|
executeSoon(() => {
|
||||||
// Avoid the cost of JSON.stringify() when logging is disabled.
|
// Avoid the cost of JSON.stringify() when logging is disabled.
|
||||||
if (flags.wantLogging) {
|
if (flags.wantLogging) {
|
||||||
dumpv("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
|
dumpv(`Received packet ${serial}: ` +
|
||||||
|
JSON.stringify(packet, null, 2));
|
||||||
}
|
}
|
||||||
if (other.hooks) {
|
if (other.hooks) {
|
||||||
other.emit("packet", packet);
|
other.emit("packet", packet);
|
||||||
@ -569,15 +591,15 @@ LocalDebuggerTransport.prototype = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send a streaming bulk packet directly to the onBulkPacket handler of the
|
* Send a streaming bulk packet directly to the onBulkPacket handler
|
||||||
* other endpoint.
|
* of the other endpoint.
|
||||||
*
|
*
|
||||||
* This case is much simpler than the full DebuggerTransport, since there is
|
* This case is much simpler than the full DebuggerTransport, since
|
||||||
* no primary stream we have to worry about managing while we hand it off to
|
* there is no primary stream we have to worry about managing while
|
||||||
* others temporarily. Instead, we can just make a single use pipe and be
|
* we hand it off to others temporarily. Instead, we can just make a
|
||||||
* done with it.
|
* single use pipe and be done with it.
|
||||||
*/
|
*/
|
||||||
startBulkSend: function ({actor, type, length}) {
|
startBulkSend({actor, type, length}) {
|
||||||
this.emit("startbulksend", {actor, type, length});
|
this.emit("startbulksend", {actor, type, length});
|
||||||
|
|
||||||
let serial = this._serial.count++;
|
let serial = this._serial.count++;
|
||||||
@ -599,9 +621,9 @@ LocalDebuggerTransport.prototype = {
|
|||||||
// Receiver
|
// Receiver
|
||||||
let deferred = defer();
|
let deferred = defer();
|
||||||
let packet = {
|
let packet = {
|
||||||
actor: actor,
|
actor,
|
||||||
type: type,
|
type,
|
||||||
length: length,
|
length,
|
||||||
copyTo: (output) => {
|
copyTo: (output) => {
|
||||||
let copying =
|
let copying =
|
||||||
StreamUtils.copyStream(pipe.inputStream, output, length);
|
StreamUtils.copyStream(pipe.inputStream, output, length);
|
||||||
@ -609,7 +631,7 @@ LocalDebuggerTransport.prototype = {
|
|||||||
return copying;
|
return copying;
|
||||||
},
|
},
|
||||||
stream: pipe.inputStream,
|
stream: pipe.inputStream,
|
||||||
done: deferred
|
done: deferred,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.other.emit("bulkpacket", packet);
|
this.other.emit("bulkpacket", packet);
|
||||||
@ -622,8 +644,8 @@ LocalDebuggerTransport.prototype = {
|
|||||||
// Sender
|
// Sender
|
||||||
let sendDeferred = defer();
|
let sendDeferred = defer();
|
||||||
|
|
||||||
// The remote transport is not capable of resolving immediately here, so we
|
// The remote transport is not capable of resolving immediately here,
|
||||||
// shouldn't be able to either.
|
// so we shouldn't be able to either.
|
||||||
executeSoon(() => {
|
executeSoon(() => {
|
||||||
let copyDeferred = defer();
|
let copyDeferred = defer();
|
||||||
|
|
||||||
@ -635,7 +657,7 @@ LocalDebuggerTransport.prototype = {
|
|||||||
return copying;
|
return copying;
|
||||||
},
|
},
|
||||||
stream: pipe.outputStream,
|
stream: pipe.outputStream,
|
||||||
done: copyDeferred
|
done: copyDeferred,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Await the result of writing to the stream
|
// Await the result of writing to the stream
|
||||||
@ -648,7 +670,7 @@ LocalDebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* Close the transport.
|
* Close the transport.
|
||||||
*/
|
*/
|
||||||
close: function () {
|
close() {
|
||||||
this.emit("close");
|
this.emit("close");
|
||||||
|
|
||||||
if (this.other) {
|
if (this.other) {
|
||||||
@ -671,24 +693,24 @@ LocalDebuggerTransport.prototype = {
|
|||||||
/**
|
/**
|
||||||
* An empty method for emulating the DebuggerTransport API.
|
* An empty method for emulating the DebuggerTransport API.
|
||||||
*/
|
*/
|
||||||
ready: function () {},
|
ready() {},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function that makes an object fully immutable.
|
* Helper function that makes an object fully immutable.
|
||||||
*/
|
*/
|
||||||
_deepFreeze: function (object) {
|
_deepFreeze(object) {
|
||||||
Object.freeze(object);
|
Object.freeze(object);
|
||||||
for (let prop in object) {
|
for (let prop in object) {
|
||||||
// Freeze the properties that are objects, not on the prototype, and not
|
// Freeze the properties that are objects, not on the prototype,
|
||||||
// already frozen. Note that this might leave an unfrozen reference
|
// and not already frozen. Note that this might leave an unfrozen
|
||||||
// somewhere in the object if there is an already frozen object containing
|
// reference somewhere in the object if there is an already frozen
|
||||||
// an unfrozen object.
|
// object containing an unfrozen object.
|
||||||
if (object.hasOwnProperty(prop) && typeof object === "object" &&
|
if (object.hasOwnProperty(prop) && typeof object === "object" &&
|
||||||
!Object.isFrozen(object)) {
|
!Object.isFrozen(object)) {
|
||||||
this._deepFreeze(object[prop]);
|
this._deepFreeze(object[prop]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -733,28 +755,29 @@ ChildDebuggerTransport.prototype = {
|
|||||||
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
// In some cases, especially when using messageManagers in non-e10s mode, we reach
|
// In some cases, especially when using messageManagers in non-e10s
|
||||||
// this point with a dead messageManager which only throws errors but does not
|
// mode, we reach this point with a dead messageManager which only
|
||||||
// seem to indicate in any other way that it is dead.
|
// throws errors but does not seem to indicate in any other way that
|
||||||
|
// it is dead.
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
ready: function () {
|
ready() {
|
||||||
this._addListener();
|
this._addListener();
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function () {
|
close() {
|
||||||
this._removeListener();
|
this._removeListener();
|
||||||
this.emit("close");
|
this.emit("close");
|
||||||
this.hooks.onClosed();
|
this.hooks.onClosed();
|
||||||
},
|
},
|
||||||
|
|
||||||
receiveMessage: function ({data}) {
|
receiveMessage({data}) {
|
||||||
this.emit("packet", data);
|
this.emit("packet", data);
|
||||||
this.hooks.onPacket(data);
|
this.hooks.onPacket(data);
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function (packet) {
|
send(packet) {
|
||||||
this.emit("send", packet);
|
this.emit("send", packet);
|
||||||
try {
|
try {
|
||||||
this._mm.sendAsyncMessage(this._messageName, packet);
|
this._mm.sendAsyncMessage(this._messageName, packet);
|
||||||
@ -762,13 +785,14 @@ ChildDebuggerTransport.prototype = {
|
|||||||
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
if (e.result != Cr.NS_ERROR_NULL_POINTER) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
// In some cases, especially when using messageManagers in non-e10s mode, we reach
|
// In some cases, especially when using messageManagers in non-e10s
|
||||||
// this point with a dead messageManager which only throws errors but does not
|
// mode, we reach this point with a dead messageManager which only
|
||||||
// seem to indicate in any other way that it is dead.
|
// 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.");
|
throw new Error("Can't send bulk data to child processes.");
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -791,7 +815,7 @@ ChildDebuggerTransport.prototype = {
|
|||||||
|
|
||||||
if (!this.isWorker) {
|
if (!this.isWorker) {
|
||||||
// Main thread
|
// Main thread
|
||||||
(function () {
|
(function() {
|
||||||
/**
|
/**
|
||||||
* A transport that uses a WorkerDebugger to send packets from the main
|
* A transport that uses a WorkerDebugger to send packets from the main
|
||||||
* thread to a worker thread.
|
* thread to a worker thread.
|
||||||
@ -805,30 +829,30 @@ if (!this.isWorker) {
|
|||||||
WorkerDebuggerTransport.prototype = {
|
WorkerDebuggerTransport.prototype = {
|
||||||
constructor: WorkerDebuggerTransport,
|
constructor: WorkerDebuggerTransport,
|
||||||
|
|
||||||
ready: function () {
|
ready() {
|
||||||
this._dbg.addListener(this);
|
this._dbg.addListener(this);
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function () {
|
close() {
|
||||||
this._dbg.removeListener(this);
|
this._dbg.removeListener(this);
|
||||||
if (this.hooks) {
|
if (this.hooks) {
|
||||||
this.hooks.onClosed();
|
this.hooks.onClosed();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function (packet) {
|
send(packet) {
|
||||||
this._dbg.postMessage(JSON.stringify({
|
this._dbg.postMessage(JSON.stringify({
|
||||||
type: "message",
|
type: "message",
|
||||||
id: this._id,
|
id: this._id,
|
||||||
message: packet
|
message: packet,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
startBulkSend: function () {
|
startBulkSend() {
|
||||||
throw new Error("Can't send bulk data from worker threads!");
|
throw new Error("Can't send bulk data from worker threads!");
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMessage: function (message) {
|
_onMessage(message) {
|
||||||
let packet = JSON.parse(message);
|
let packet = JSON.parse(message);
|
||||||
if (packet.type !== "message" || packet.id !== this._id) {
|
if (packet.type !== "message" || packet.id !== this._id) {
|
||||||
return;
|
return;
|
||||||
@ -837,16 +861,16 @@ if (!this.isWorker) {
|
|||||||
if (this.hooks) {
|
if (this.hooks) {
|
||||||
this.hooks.onPacket(packet.message);
|
this.hooks.onPacket(packet.message);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
} else {
|
} else {
|
||||||
// Worker thread
|
// Worker thread
|
||||||
(function () {
|
(function() {
|
||||||
/**
|
/**
|
||||||
* A transport that uses a WorkerDebuggerGlobalScope to send packets from a
|
* A transport that uses a WorkerDebuggerGlobalScope to send packets
|
||||||
* worker thread to the main thread.
|
* from a worker thread to the main thread.
|
||||||
*/
|
*/
|
||||||
function WorkerDebuggerTransport(scope, id) {
|
function WorkerDebuggerTransport(scope, id) {
|
||||||
this._scope = scope;
|
this._scope = scope;
|
||||||
@ -857,30 +881,30 @@ if (!this.isWorker) {
|
|||||||
WorkerDebuggerTransport.prototype = {
|
WorkerDebuggerTransport.prototype = {
|
||||||
constructor: WorkerDebuggerTransport,
|
constructor: WorkerDebuggerTransport,
|
||||||
|
|
||||||
ready: function () {
|
ready() {
|
||||||
this._scope.addEventListener("message", this._onMessage);
|
this._scope.addEventListener("message", this._onMessage);
|
||||||
},
|
},
|
||||||
|
|
||||||
close: function () {
|
close() {
|
||||||
this._scope.removeEventListener("message", this._onMessage);
|
this._scope.removeEventListener("message", this._onMessage);
|
||||||
if (this.hooks) {
|
if (this.hooks) {
|
||||||
this.hooks.onClosed();
|
this.hooks.onClosed();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
send: function (packet) {
|
send(packet) {
|
||||||
this._scope.postMessage(JSON.stringify({
|
this._scope.postMessage(JSON.stringify({
|
||||||
type: "message",
|
type: "message",
|
||||||
id: this._id,
|
id: this._id,
|
||||||
message: packet
|
message: packet,
|
||||||
}));
|
}));
|
||||||
},
|
},
|
||||||
|
|
||||||
startBulkSend: function () {
|
startBulkSend() {
|
||||||
throw new Error("Can't send bulk data from worker threads!");
|
throw new Error("Can't send bulk data from worker threads!");
|
||||||
},
|
},
|
||||||
|
|
||||||
_onMessage: function (event) {
|
_onMessage(event) {
|
||||||
let packet = JSON.parse(event.data);
|
let packet = JSON.parse(event.data);
|
||||||
if (packet.type !== "message" || packet.id !== this._id) {
|
if (packet.type !== "message" || packet.id !== this._id) {
|
||||||
return;
|
return;
|
||||||
@ -889,7 +913,7 @@ if (!this.isWorker) {
|
|||||||
if (this.hooks) {
|
if (this.hooks) {
|
||||||
this.hooks.onPacket(packet.message);
|
this.hooks.onPacket(packet.message);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
}).call(this);
|
}).call(this);
|
||||||
|
@ -59,7 +59,7 @@ this.wait = {};
|
|||||||
* @throws {?}
|
* @throws {?}
|
||||||
* If |func| throws, its error is propagated.
|
* 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);
|
const timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -72,7 +72,8 @@ wait.until = function (func, timeout = 2000, interval = 10) {
|
|||||||
throw 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) {
|
if (start == end || new Date().getTime() >= end) {
|
||||||
resolve(rejected);
|
resolve(rejected);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user