mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Backed out 16 changesets (bug 1107706) for Gaia JS integration test failures.
Backed out changeset d026794b4c0b (bug 1107706) Backed out changeset bb481b2d170a (bug 1107706) Backed out changeset 71eba829a8b4 (bug 1107706) Backed out changeset 3ca5a996676e (bug 1107706) Backed out changeset 18c48c6a0cd5 (bug 1107706) Backed out changeset 5dce917aeb92 (bug 1107706) Backed out changeset 933d7aa1c709 (bug 1107706) Backed out changeset 0c6e1484ae7a (bug 1107706) Backed out changeset 9972f443d70e (bug 1107706) Backed out changeset 20f9b7b24fc5 (bug 1107706) Backed out changeset 1f4ba5b0fc4f (bug 1107706) Backed out changeset a4a8e755d815 (bug 1107706) Backed out changeset 593a7917f917 (bug 1107706) Backed out changeset 502320aec21f (bug 1107706) Backed out changeset 60b58aed6d27 (bug 1107706) Backed out changeset c8315bbbc104 (bug 1107706) CLOSED TREE
This commit is contained in:
parent
763392b3ce
commit
aa17f3167b
@ -1,492 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ActionChain"];
|
||||
|
||||
/**
|
||||
* Functionality for (single finger) action chains.
|
||||
*/
|
||||
this.ActionChain = function(utils, checkForInterrupted) {
|
||||
// for assigning unique ids to all touches
|
||||
this.nextTouchId = 1000;
|
||||
// keep track of active Touches
|
||||
this.touchIds = {};
|
||||
// last touch for each fingerId
|
||||
this.lastCoordinates = null;
|
||||
this.isTap = false;
|
||||
this.scrolling = false;
|
||||
// whether to send mouse event
|
||||
this.mouseEventsOnly = false;
|
||||
this.checkTimer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
|
||||
// callbacks for command completion
|
||||
this.onSuccess = null;
|
||||
this.onError = null;
|
||||
if (typeof checkForInterrupted == "function") {
|
||||
this.checkForInterrupted = checkForInterrupted;
|
||||
} else {
|
||||
this.checkForInterrupted = () => {};
|
||||
}
|
||||
|
||||
// determines if we create touch events
|
||||
this.inputSource = null;
|
||||
|
||||
// test utilities providing some event synthesis code
|
||||
this.utils = utils;
|
||||
};
|
||||
|
||||
ActionChain.prototype.dispatchActions = function(
|
||||
args,
|
||||
touchId,
|
||||
frame,
|
||||
elementManager,
|
||||
callbacks,
|
||||
touchProvider) {
|
||||
// Some touch events code in the listener needs to do ipc, so we can't
|
||||
// share this code across chrome/content.
|
||||
if (touchProvider) {
|
||||
this.touchProvider = touchProvider;
|
||||
}
|
||||
|
||||
this.elementManager = elementManager;
|
||||
let commandArray = elementManager.convertWrappedArguments(args, frame);
|
||||
this.onSuccess = callbacks.onSuccess;
|
||||
this.onError = callbacks.onError;
|
||||
this.frame = frame;
|
||||
|
||||
if (touchId == null) {
|
||||
touchId = this.nextTouchId++;
|
||||
}
|
||||
|
||||
if (!frame.document.createTouch) {
|
||||
this.mouseEventsOnly = true;
|
||||
}
|
||||
|
||||
let keyModifiers = {
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
metaKey: false
|
||||
};
|
||||
|
||||
try {
|
||||
this.actions(commandArray, touchId, 0, keyModifiers);
|
||||
} catch (e) {
|
||||
this.onError(e);
|
||||
this.resetValues();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function emit mouse event.
|
||||
*
|
||||
* @param {Document} doc
|
||||
* Current document.
|
||||
* @param {string} type
|
||||
* Type of event to dispatch.
|
||||
* @param {number} clickCount
|
||||
* Number of clicks, button notes the mouse button.
|
||||
* @param {number} elClientX
|
||||
* X coordinate of the mouse relative to the viewport.
|
||||
* @param {number} elClientY
|
||||
* Y coordinate of the mouse relative to the viewport.
|
||||
* @param {Object} modifiers
|
||||
* An object of modifier keys present.
|
||||
*/
|
||||
ActionChain.prototype.emitMouseEvent = function(
|
||||
doc,
|
||||
type,
|
||||
elClientX,
|
||||
elClientY,
|
||||
button,
|
||||
clickCount,
|
||||
modifiers) {
|
||||
if (!this.checkForInterrupted()) {
|
||||
let loggingInfo = "emitting Mouse event of type " + type +
|
||||
" at coordinates (" + elClientX + ", " + elClientY +
|
||||
") relative to the viewport\n" +
|
||||
" button: " + button + "\n" +
|
||||
" clickCount: " + clickCount + "\n";
|
||||
dump(Date.now() + " Marionette: " + loggingInfo);
|
||||
|
||||
let win = doc.defaultView;
|
||||
let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
|
||||
let mods;
|
||||
if (typeof modifiers != "undefined") {
|
||||
mods = this.utils._parseModifiers(modifiers);
|
||||
} else {
|
||||
mods = 0;
|
||||
}
|
||||
|
||||
domUtils.sendMouseEvent(
|
||||
type,
|
||||
elClientX,
|
||||
elClientY,
|
||||
button || 0,
|
||||
clickCount || 1,
|
||||
mods,
|
||||
false,
|
||||
0,
|
||||
this.inputSource);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reset any persisted values after a command completes.
|
||||
*/
|
||||
ActionChain.prototype.resetValues = function() {
|
||||
this.onSuccess = null;
|
||||
this.onError = null;
|
||||
this.frame = null;
|
||||
this.elementManager = null;
|
||||
this.touchProvider = null;
|
||||
this.mouseEventsOnly = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function to emit touch events for each finger. e.g.
|
||||
* finger=[['press', id], ['wait', 5], ['release']] touchId represents
|
||||
* the finger id, i keeps track of the current action of the chain
|
||||
* keyModifiers is an object keeping track keyDown/keyUp pairs through
|
||||
* an action chain.
|
||||
*/
|
||||
ActionChain.prototype.actions = function(chain, touchId, i, keyModifiers) {
|
||||
if (i == chain.length) {
|
||||
this.onSuccess({value: touchId});
|
||||
this.resetValues();
|
||||
return;
|
||||
}
|
||||
|
||||
let pack = chain[i];
|
||||
let command = pack[0];
|
||||
let el;
|
||||
let c;
|
||||
i++;
|
||||
|
||||
if (["press", "wait", "keyDown", "keyUp", "click"].indexOf(command) == -1) {
|
||||
// if mouseEventsOnly, then touchIds isn't used
|
||||
if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
|
||||
this.resetValues();
|
||||
throw new WebDriverError("Element has not been pressed");
|
||||
}
|
||||
}
|
||||
|
||||
switch(command) {
|
||||
case "keyDown":
|
||||
this.utils.sendKeyDown(pack[1], keyModifiers, this.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "keyUp":
|
||||
this.utils.sendKeyUp(pack[1], keyModifiers, this.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "click":
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
let button = pack[2];
|
||||
let clickCount = pack[3];
|
||||
c = this.coordinates(el, null, null);
|
||||
this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount, keyModifiers);
|
||||
if (button == 2) {
|
||||
this.emitMouseEvent(el.ownerDocument, "contextmenu", c.x, c.y,
|
||||
button, clickCount, keyModifiers);
|
||||
}
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "press":
|
||||
if (this.lastCoordinates) {
|
||||
this.generateEvents(
|
||||
"cancel",
|
||||
this.lastCoordinates[0],
|
||||
this.lastCoordinates[1],
|
||||
touchId,
|
||||
null,
|
||||
keyModifiers);
|
||||
this.resetValues();
|
||||
throw new WebDriverError(
|
||||
"Invalid Command: press cannot follow an active touch event");
|
||||
}
|
||||
|
||||
// look ahead to check if we're scrolling. Needed for APZ touch dispatching.
|
||||
if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
|
||||
this.scrolling = true;
|
||||
}
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
c = this.coordinates(el, pack[2], pack[3]);
|
||||
touchId = this.generateEvents("press", c.x, c.y, null, el, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "release":
|
||||
this.generateEvents(
|
||||
"release",
|
||||
this.lastCoordinates[0],
|
||||
this.lastCoordinates[1],
|
||||
touchId,
|
||||
null,
|
||||
keyModifiers);
|
||||
this.actions(chain, null, i, keyModifiers);
|
||||
this.scrolling = false;
|
||||
break;
|
||||
|
||||
case "move":
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
c = this.coordinates(el);
|
||||
this.generateEvents("move", c.x, c.y, touchId, null, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "moveByOffset":
|
||||
this.generateEvents(
|
||||
"move",
|
||||
this.lastCoordinates[0] + pack[1],
|
||||
this.lastCoordinates[1] + pack[2],
|
||||
touchId,
|
||||
null,
|
||||
keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
case "wait":
|
||||
if (pack[1] != null) {
|
||||
let time = pack[1] * 1000;
|
||||
|
||||
// standard waiting time to fire contextmenu
|
||||
let standard = 750;
|
||||
try {
|
||||
standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
} catch (e) {}
|
||||
|
||||
if (time >= standard && this.isTap) {
|
||||
chain.splice(i, 0, ["longPress"], ["wait", (time - standard) / 1000]);
|
||||
time = standard;
|
||||
}
|
||||
this.checkTimer.initWithCallback(
|
||||
() => this.actions(chain, touchId, i, keyModifiers),
|
||||
time, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
} else {
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
}
|
||||
break;
|
||||
|
||||
case "cancel":
|
||||
this.generateEvents(
|
||||
"cancel",
|
||||
this.lastCoordinates[0],
|
||||
this.lastCoordinates[1],
|
||||
touchId,
|
||||
null,
|
||||
keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
this.scrolling = false;
|
||||
break;
|
||||
|
||||
case "longPress":
|
||||
this.generateEvents(
|
||||
"contextmenu",
|
||||
this.lastCoordinates[0],
|
||||
this.lastCoordinates[1],
|
||||
touchId,
|
||||
null,
|
||||
keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function generates a pair of coordinates relative to the viewport given a
|
||||
* target element and coordinates relative to that element's top-left corner.
|
||||
*
|
||||
* @param {DOMElement} target
|
||||
* The target to calculate coordinates of.
|
||||
* @param {number} x
|
||||
* X coordinate relative to target. If unspecified, the centre of
|
||||
* the target is used.
|
||||
* @param {number} y
|
||||
* Y coordinate relative to target. If unspecified, the centre of
|
||||
* the target is used.
|
||||
*/
|
||||
ActionChain.prototype.coordinates = function(target, x, y) {
|
||||
let box = target.getBoundingClientRect();
|
||||
if (x == null) {
|
||||
x = box.width / 2;
|
||||
}
|
||||
if (y == null) {
|
||||
y = box.height / 2;
|
||||
}
|
||||
let coords = {};
|
||||
coords.x = box.left + x;
|
||||
coords.y = box.top + y;
|
||||
return coords;
|
||||
};
|
||||
|
||||
/**
|
||||
* Given an element and a pair of coordinates, returns an array of the
|
||||
* form [clientX, clientY, pageX, pageY, screenX, screenY].
|
||||
*/
|
||||
ActionChain.prototype.getCoordinateInfo = function(el, corx, cory) {
|
||||
let win = el.ownerDocument.defaultView;
|
||||
return [
|
||||
corx, // clientX
|
||||
cory, // clientY
|
||||
corx + win.pageXOffset, // pageX
|
||||
cory + win.pageYOffset, // pageY
|
||||
corx + win.mozInnerScreenX, // screenX
|
||||
cory + win.mozInnerScreenY // screenY
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {number} x
|
||||
* X coordinate of the location to generate the event that is relative
|
||||
* to the viewport.
|
||||
* @param {number} y
|
||||
* Y coordinate of the location to generate the event that is relative
|
||||
* to the viewport.
|
||||
*/
|
||||
ActionChain.prototype.generateEvents = function(
|
||||
type, x, y, touchId, target, keyModifiers) {
|
||||
this.lastCoordinates = [x, y];
|
||||
let doc = this.frame.document;
|
||||
|
||||
switch (type) {
|
||||
case "tap":
|
||||
if (this.mouseEventsOnly) {
|
||||
this.mouseTap(
|
||||
touch.target.ownerDocument,
|
||||
touch.clientX,
|
||||
touch.clientY,
|
||||
null,
|
||||
null,
|
||||
keyModifiers);
|
||||
} else {
|
||||
touchId = this.nextTouchId++;
|
||||
let touch = this.touchProvider.createATouch(target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent("touchstart", touch);
|
||||
this.touchProvider.emitTouchEvent("touchend", touch);
|
||||
this.mouseTap(
|
||||
touch.target.ownerDocument,
|
||||
touch.clientX,
|
||||
touch.clientY,
|
||||
null,
|
||||
null,
|
||||
keyModifiers);
|
||||
}
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
|
||||
case "press":
|
||||
this.isTap = true;
|
||||
if (this.mouseEventsOnly) {
|
||||
this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
|
||||
this.emitMouseEvent(doc, "mousedown", x, y, null, null, keyModifiers);
|
||||
} else {
|
||||
touchId = this.nextTouchId++;
|
||||
let touch = this.touchProvider.createATouch(target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent("touchstart", touch);
|
||||
this.touchIds[touchId] = touch;
|
||||
return touchId;
|
||||
}
|
||||
break;
|
||||
|
||||
case "release":
|
||||
if (this.mouseEventsOnly) {
|
||||
let [x, y] = this.lastCoordinates;
|
||||
this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
|
||||
} else {
|
||||
let touch = this.touchIds[touchId];
|
||||
let [x, y] = this.lastCoordinates;
|
||||
|
||||
touch = this.touchProvider.createATouch(touch.target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent("touchend", touch);
|
||||
|
||||
if (this.isTap) {
|
||||
this.mouseTap(
|
||||
touch.target.ownerDocument,
|
||||
touch.clientX,
|
||||
touch.clientY,
|
||||
null,
|
||||
null,
|
||||
keyModifiers);
|
||||
}
|
||||
delete this.touchIds[touchId];
|
||||
}
|
||||
|
||||
this.isTap = false;
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
|
||||
case "cancel":
|
||||
this.isTap = false;
|
||||
if (this.mouseEventsOnly) {
|
||||
let [x, y] = this.lastCoordinates;
|
||||
this.emitMouseEvent(doc, "mouseup", x, y, null, null, keyModifiers);
|
||||
} else {
|
||||
this.touchProvider.emitTouchEvent("touchcancel", this.touchIds[touchId]);
|
||||
delete this.touchIds[touchId];
|
||||
}
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
|
||||
case "move":
|
||||
this.isTap = false;
|
||||
if (this.mouseEventsOnly) {
|
||||
this.emitMouseEvent(doc, "mousemove", x, y, null, null, keyModifiers);
|
||||
} else {
|
||||
let touch = this.touchProvider.createATouch(
|
||||
this.touchIds[touchId].target, x, y, touchId);
|
||||
this.touchIds[touchId] = touch;
|
||||
this.touchProvider.emitTouchEvent("touchmove", touch);
|
||||
}
|
||||
break;
|
||||
|
||||
case "contextmenu":
|
||||
this.isTap = false;
|
||||
let event = this.frame.document.createEvent("MouseEvents");
|
||||
if (this.mouseEventsOnly) {
|
||||
target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]);
|
||||
} else {
|
||||
target = this.touchIds[touchId].target;
|
||||
}
|
||||
|
||||
let [clientX, clientY, pageX, pageY, screenX, screenY] =
|
||||
this.getCoordinateInfo(target, x, y);
|
||||
|
||||
event.initMouseEvent(
|
||||
"contextmenu",
|
||||
true,
|
||||
true,
|
||||
target.ownerDocument.defaultView,
|
||||
1,
|
||||
screenX,
|
||||
screenY,
|
||||
clientX,
|
||||
clientY,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0,
|
||||
null);
|
||||
target.dispatchEvent(event);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new WebDriverError("Unknown event type: " + type);
|
||||
}
|
||||
this.checkForInterrupted();
|
||||
};
|
||||
|
||||
ActionChain.prototype.mouseTap = function(doc, x, y, button, count, mod) {
|
||||
this.emitMouseEvent(doc, "mousemove", x, y, button, count, mod);
|
||||
this.emitMouseEvent(doc, "mousedown", x, y, button, count, mod);
|
||||
this.emitMouseEvent(doc, "mouseup", x, y, button, count, mod);
|
||||
};
|
@ -2,8 +2,7 @@
|
||||
# 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/.
|
||||
|
||||
from marionette.marionette_test import MarionetteTestCase, skip_if_b2g
|
||||
|
||||
from marionette import MarionetteTestCase
|
||||
|
||||
class TestElementSize(MarionetteTestCase):
|
||||
def testShouldReturnTheSizeOfALink(self):
|
||||
@ -14,15 +13,12 @@ class TestElementSize(MarionetteTestCase):
|
||||
self.assertTrue(size['width'] > 0)
|
||||
self.assertTrue(size['height'] > 0)
|
||||
|
||||
|
||||
@skip_if_b2g
|
||||
class TestElementSizeChrome(MarionetteTestCase):
|
||||
def setUp(self):
|
||||
MarionetteTestCase.setUp(self)
|
||||
self.marionette.set_context("chrome")
|
||||
self.win = self.marionette.current_window_handle
|
||||
self.marionette.execute_script(
|
||||
"window.open('chrome://marionette/content/test2.xul', 'foo', 'chrome,centerscreen');")
|
||||
self.marionette.execute_script("window.open('chrome://marionette/content/test2.xul', 'foo', 'chrome,centerscreen');")
|
||||
self.marionette.switch_to_window('foo')
|
||||
self.assertNotEqual(self.win, self.marionette.current_window_handle)
|
||||
|
||||
@ -41,3 +37,4 @@ class TestElementSizeChrome(MarionetteTestCase):
|
||||
size = shrinko.rect
|
||||
self.assertTrue(size['width'] > 0)
|
||||
self.assertTrue(size['height'] > 0)
|
||||
|
||||
|
@ -4,17 +4,15 @@
|
||||
|
||||
import urllib
|
||||
|
||||
from marionette_driver import By, errors
|
||||
from marionette.marionette_test import MarionetteTestCase, skip_if_b2g
|
||||
|
||||
from marionette_driver.by import By
|
||||
from marionette_driver.errors import JavascriptException
|
||||
from marionette import MarionetteTestCase
|
||||
|
||||
def inline(doc):
|
||||
return "data:text/html;charset=utf-8,%s" % urllib.quote(doc)
|
||||
|
||||
|
||||
elements = inline("<p>foo</p> <p>bar</p>")
|
||||
|
||||
|
||||
class TestExecuteContent(MarionetteTestCase):
|
||||
def test_stack_trace(self):
|
||||
try:
|
||||
@ -23,7 +21,7 @@ class TestExecuteContent(MarionetteTestCase):
|
||||
return b;
|
||||
""")
|
||||
self.assertFalse(True)
|
||||
except errors.JavascriptException as inst:
|
||||
except JavascriptException, inst:
|
||||
self.assertTrue('return b' in inst.stacktrace)
|
||||
|
||||
def test_execute_simple(self):
|
||||
@ -36,11 +34,11 @@ class TestExecuteContent(MarionetteTestCase):
|
||||
self.assertEqual(self.marionette.execute_script("1;"), None)
|
||||
|
||||
def test_execute_js_exception(self):
|
||||
self.assertRaises(errors.JavascriptException,
|
||||
self.assertRaises(JavascriptException,
|
||||
self.marionette.execute_script, "return foo(bar);")
|
||||
|
||||
def test_execute_permission(self):
|
||||
self.assertRaises(errors.JavascriptException,
|
||||
self.assertRaises(JavascriptException,
|
||||
self.marionette.execute_script,
|
||||
"""
|
||||
let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
@ -98,8 +96,6 @@ let prefs = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
[None])
|
||||
self.assertIs(result, None)
|
||||
|
||||
|
||||
@skip_if_b2g
|
||||
class TestExecuteChrome(TestExecuteContent):
|
||||
def setUp(self):
|
||||
super(TestExecuteChrome, self).setUp()
|
||||
@ -125,10 +121,3 @@ class TestExecuteChrome(TestExecuteContent):
|
||||
actual = self.marionette.execute_script(
|
||||
"return document.querySelectorAll('textbox')")
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
def test_async_script_timeout(self):
|
||||
with self.assertRaises(errors.ScriptTimeoutException):
|
||||
self.marionette.execute_async_script("""
|
||||
var cb = arguments[arguments.length - 1];
|
||||
setTimeout(function() { cb() }, 250);
|
||||
""", script_timeout=100)
|
||||
|
@ -96,7 +96,6 @@ browser = false
|
||||
[test_simpletest_timeout.js]
|
||||
[test_specialpowers.py]
|
||||
[test_anonymous_content.py]
|
||||
b2g = false
|
||||
[test_switch_frame.py]
|
||||
b2g = false
|
||||
skip-if = os == "win" # Bug 1078237
|
||||
|
@ -1,168 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["CommandProcessor", "Response"];
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
/**
|
||||
* Represents the response returned from the remote end after execution
|
||||
* of its corresponding command.
|
||||
*
|
||||
* The Response is a mutable object passed to each command for
|
||||
* modification through the available setters. The response is sent
|
||||
* implicitly by CommandProcessor when a command is finished executing,
|
||||
* and any modifications made subsequent to this will have no effect.
|
||||
*
|
||||
* @param {number} cmdId
|
||||
* UUID tied to the corresponding command request this is
|
||||
* a response for.
|
||||
* @param {function(number)} okHandler
|
||||
* Callback function called on successful responses with no body.
|
||||
* @param {function(Object, number)} respHandler
|
||||
* Callback function called on successful responses with body.
|
||||
* @param {Object=} msg
|
||||
* A message to populate the response, containing the properties
|
||||
* "sessionId", "status", and "value".
|
||||
* @param {function(Map)=} sanitizer
|
||||
* Run before sending message.
|
||||
*/
|
||||
this.Response = function(cmdId, okHandler, respHandler, msg, sanitizer) {
|
||||
const removeEmpty = function(map) {
|
||||
let rv = {};
|
||||
for (let [key, value] of map) {
|
||||
if (typeof value == "undefined") {
|
||||
value = null;
|
||||
}
|
||||
rv[key] = value;
|
||||
}
|
||||
return rv;
|
||||
};
|
||||
|
||||
this.id = cmdId;
|
||||
this.ok = true;
|
||||
this.okHandler = okHandler;
|
||||
this.respHandler = respHandler;
|
||||
this.sanitizer = sanitizer || removeEmpty;
|
||||
|
||||
this.data = new Map([
|
||||
["sessionId", msg.sessionId ? msg.sessionId : null],
|
||||
["status", msg.status ? msg.status : 0 /* success */],
|
||||
["value", msg.value ? msg.value : undefined],
|
||||
]);
|
||||
};
|
||||
|
||||
Response.prototype = {
|
||||
get name() { return this.data.get("name"); },
|
||||
set name(n) { this.data.set("name", n); },
|
||||
get sessionId() { return this.data.get("sessionId"); },
|
||||
set sessionId(id) { this.data.set("sessionId", id); },
|
||||
get status() { return this.data.get("status"); },
|
||||
set status(ns) { this.data.set("status", ns); },
|
||||
get value() { return this.data.get("value"); },
|
||||
set value(val) {
|
||||
this.data.set("value", val);
|
||||
this.ok = false;
|
||||
}
|
||||
};
|
||||
|
||||
Response.prototype.send = function() {
|
||||
if (this.sent) {
|
||||
logger.warn("Skipped sending response to command ID " +
|
||||
this.id + " because response has already been sent");
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.ok) {
|
||||
this.okHandler(this.id);
|
||||
} else {
|
||||
let rawData = this.sanitizer(this.data);
|
||||
this.respHandler(rawData, this.id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {(Error|Object)} err
|
||||
* The error to send, either an instance of the Error prototype,
|
||||
* or an object with the properties "message", "code", and "stack".
|
||||
*/
|
||||
Response.prototype.sendError = function(err) {
|
||||
this.status = "code" in err ? err.code : new UnknownError().code;
|
||||
this.value = error.toJSON(err);
|
||||
this.send();
|
||||
|
||||
// propagate errors that are implementation problems
|
||||
if (!error.isWebDriverError(err)) {
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The command processor receives messages on execute(payload, …)
|
||||
* from the dispatcher, processes them, and wraps the functions that
|
||||
* it executes from the WebDriver implementation, driver.
|
||||
*
|
||||
* @param {GeckoDriver} driver
|
||||
* Reference to the driver implementation.
|
||||
*/
|
||||
this.CommandProcessor = function(driver) {
|
||||
this.driver = driver;
|
||||
};
|
||||
|
||||
/**
|
||||
* Executes a WebDriver command based on the received payload,
|
||||
* which is expected to be an object with a "parameters" property
|
||||
* that is a simple key/value collection of arguments.
|
||||
*
|
||||
* The respHandler function will be called with the JSON object to
|
||||
* send back to the client.
|
||||
*
|
||||
* The cmdId is the UUID tied to this request that prevents
|
||||
* the dispatcher from sending responses in the wrong order.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* Message as received from client.
|
||||
* @param {function(number)} okHandler
|
||||
* Callback function called on successful responses with no body.
|
||||
* @param {function(Object, number)} respHandler
|
||||
* Callback function called on successful responses with body.
|
||||
* @param {number} cmdId
|
||||
* The unique identifier for the command to execute.
|
||||
*/
|
||||
CommandProcessor.prototype.execute = function(payload, okHandler, respHandler, cmdId) {
|
||||
let cmd = payload;
|
||||
let resp = new Response(
|
||||
cmdId, okHandler, respHandler, {sessionId: this.driver.sessionId});
|
||||
let sendResponse = resp.send.bind(resp);
|
||||
let sendError = resp.sendError.bind(resp);
|
||||
|
||||
// Ideally handlers shouldn't have to care about the command ID,
|
||||
// but some methods (newSession, executeScript, et al.) have not
|
||||
// yet been converted to use the new form of request dispatching.
|
||||
cmd.id = cmdId;
|
||||
|
||||
// For as long as the old form of request dispatching is in use,
|
||||
// we need to tell ListenerProxy what the current command ID is
|
||||
// so that individual commands in driver.js can define it explicitly.
|
||||
this.driver.listener.curCmdId = cmd.id;
|
||||
|
||||
let req = Task.spawn(function*() {
|
||||
let fn = this.driver.commands[cmd.name];
|
||||
if (typeof fn == "undefined") {
|
||||
throw new UnknownCommandError(cmd.name);
|
||||
}
|
||||
|
||||
yield fn.bind(this.driver)(cmd, resp);
|
||||
}.bind(this));
|
||||
|
||||
req.then(sendResponse, sendError).catch(error.report);
|
||||
};
|
@ -2,34 +2,37 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Constructor: CC, interfaces: Ci, utils: Cu} = Components;
|
||||
this.CC = Components.Constructor;
|
||||
this.Cc = Components.classes;
|
||||
this.Ci = Components.interfaces;
|
||||
this.Cu = Components.utils;
|
||||
this.Cr = Components.results;
|
||||
|
||||
const MARIONETTE_CONTRACTID = "@mozilla.org/marionette;1";
|
||||
const MARIONETTE_CID = Components.ID("{786a1369-dca5-4adc-8486-33d23c88010a}");
|
||||
const MARIONETTE_ENABLED_PREF = 'marionette.defaultPrefs.enabled';
|
||||
const MARIONETTE_FORCELOCAL_PREF = 'marionette.force-local';
|
||||
const MARIONETTE_LOG_PREF = 'marionette.logging';
|
||||
|
||||
const DEFAULT_PORT = 2828;
|
||||
const ENABLED_PREF = "marionette.defaultPrefs.enabled";
|
||||
const PORT_PREF = "marionette.defaultPrefs.port";
|
||||
const FORCELOCAL_PREF = "marionette.force-local";
|
||||
const LOG_PREF = "marionette.logging";
|
||||
|
||||
const ServerSocket = CC("@mozilla.org/network/server-socket;1",
|
||||
"nsIServerSocket",
|
||||
"initSpecialConnection");
|
||||
this.ServerSocket = CC("@mozilla.org/network/server-socket;1",
|
||||
"nsIServerSocket",
|
||||
"initSpecialConnection");
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
function MarionetteComponent() {
|
||||
this.loaded_ = false;
|
||||
this._loaded = false;
|
||||
this.observerService = Services.obs;
|
||||
|
||||
// set up the logger
|
||||
this.logger = Log.repository.getLogger("Marionette");
|
||||
this.logger.level = Log.Level.Trace;
|
||||
this.logger.level = Log.Level["Trace"];
|
||||
let dumper = false;
|
||||
#ifdef DEBUG
|
||||
dumper = true;
|
||||
@ -38,11 +41,12 @@ function MarionetteComponent() {
|
||||
dumper = true;
|
||||
#endif
|
||||
try {
|
||||
if (dumper || Services.prefs.getBoolPref(LOG_PREF)) {
|
||||
if (dumper || Services.prefs.getBoolPref(MARIONETTE_LOG_PREF)) {
|
||||
let formatter = new Log.BasicFormatter();
|
||||
this.logger.addAppender(new Log.DumpAppender(formatter));
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
catch(e) {}
|
||||
}
|
||||
|
||||
MarionetteComponent.prototype = {
|
||||
@ -50,141 +54,134 @@ MarionetteComponent.prototype = {
|
||||
classID: MARIONETTE_CID,
|
||||
contractID: MARIONETTE_CONTRACTID,
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler, Ci.nsIObserver]),
|
||||
_xpcom_categories: [
|
||||
{category: "command-line-handler", entry: "b-marionette"},
|
||||
{category: "profile-after-change", service: true}
|
||||
],
|
||||
_xpcom_categories: [{category: "command-line-handler", entry: "b-marionette"},
|
||||
{category: "profile-after-change", service: true}],
|
||||
appName: Services.appinfo.name,
|
||||
enabled: false,
|
||||
finalUiStartup: false,
|
||||
server: null
|
||||
};
|
||||
_marionetteServer: null,
|
||||
|
||||
MarionetteComponent.prototype.onSocketAccepted = function(
|
||||
socket, transport) {
|
||||
this.logger.info("onSocketAccepted for Marionette dummy socket");
|
||||
};
|
||||
onSocketAccepted: function mc_onSocketAccepted(aSocket, aTransport) {
|
||||
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();
|
||||
};
|
||||
onStopListening: function mc_onStopListening(aSocket, status) {
|
||||
this.logger.info("onStopListening for Marionette dummy socket, code " + status);
|
||||
aSocket.close();
|
||||
},
|
||||
|
||||
/** Check cmdLine argument for {@code --marionette}. */
|
||||
MarionetteComponent.prototype.handle = function(cmdLine) {
|
||||
// if the CLI is there then lets do work otherwise nothing to see
|
||||
if (cmdLine.handleFlag("marionette", false)) {
|
||||
this.enabled = true;
|
||||
this.logger.info("Marionette enabled via command-line flag");
|
||||
this.init();
|
||||
}
|
||||
};
|
||||
|
||||
MarionetteComponent.prototype.observe = function(subj, topic, data) {
|
||||
switch (topic) {
|
||||
case "profile-after-change":
|
||||
// Using final-ui-startup as the xpcom category doesn't seem to work,
|
||||
// so we wait for that by adding an observer here.
|
||||
this.observerService.addObserver(this, "final-ui-startup", false);
|
||||
#ifdef ENABLE_MARIONETTE
|
||||
try {
|
||||
this.enabled = Services.prefs.getBoolPref(ENABLED_PREF);
|
||||
} catch(e) {}
|
||||
if (this.enabled) {
|
||||
this.logger.info("Marionette enabled via build flag and pref");
|
||||
|
||||
// We want to suppress the modal dialog that's shown
|
||||
// when starting up in safe-mode to enable testing.
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
this.observerService.addObserver(this, "domwindowopened", false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
|
||||
case "final-ui-startup":
|
||||
this.finalUiStartup = true;
|
||||
this.observerService.removeObserver(this, topic);
|
||||
this.observerService.addObserver(this, "xpcom-shutdown", false);
|
||||
// Check cmdLine argument for --marionette
|
||||
handle: function mc_handle(cmdLine) {
|
||||
// If the CLI is there then lets do work otherwise nothing to see
|
||||
if (cmdLine.handleFlag("marionette", false)) {
|
||||
this.enabled = true;
|
||||
this.logger.info("marionette enabled via command-line");
|
||||
this.init();
|
||||
break;
|
||||
|
||||
case "domwindowopened":
|
||||
this.observerService.removeObserver(this, topic);
|
||||
this.suppressSafeModeDialog_(subj);
|
||||
break;
|
||||
|
||||
case "xpcom-shutdown":
|
||||
this.observerService.removeObserver(this, "xpcom-shutdown");
|
||||
this.uninit();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
MarionetteComponent.prototype.suppressSafeModeDialog_ = function(win) {
|
||||
// Wait for the modal dialog to finish loading.
|
||||
win.addEventListener("load", function onload() {
|
||||
win.removeEventListener("load", onload);
|
||||
|
||||
if (win.document.getElementById("safeModeDialog")) {
|
||||
// Accept the dialog to start in safe-mode
|
||||
win.setTimeout(() => {
|
||||
win.document.documentElement.getButton("accept").click();
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
},
|
||||
|
||||
MarionetteComponent.prototype.init = function() {
|
||||
if (this.loaded_ || !this.enabled || !this.finalUiStartup) {
|
||||
return;
|
||||
}
|
||||
observe: function mc_observe(aSubject, aTopic, aData) {
|
||||
switch (aTopic) {
|
||||
case "profile-after-change":
|
||||
// Using final-ui-startup as the xpcom category doesn't seem to work,
|
||||
// so we wait for that by adding an observer here.
|
||||
this.observerService.addObserver(this, "final-ui-startup", false);
|
||||
#ifdef ENABLE_MARIONETTE
|
||||
let enabledPref = false;
|
||||
try {
|
||||
enabledPref = Services.prefs.getBoolPref(MARIONETTE_ENABLED_PREF);
|
||||
} catch(e) {}
|
||||
if (enabledPref) {
|
||||
this.enabled = true;
|
||||
this.logger.info("marionette enabled via build flag and pref");
|
||||
|
||||
this.loaded_ = true;
|
||||
|
||||
let forceLocal = Services.appinfo.name == "B2G" ? false : true;
|
||||
try {
|
||||
forceLocal = Services.prefs.getBoolPref(FORCELOCAL_PREF);
|
||||
} catch (e) {}
|
||||
Services.prefs.setBoolPref(FORCELOCAL_PREF, forceLocal);
|
||||
|
||||
if (!forceLocal) {
|
||||
// See bug 800138. Because the first socket that opens with
|
||||
// force-local=false fails, we open a dummy socket that will fail.
|
||||
// keepWhenOffline=true so that it still work when offline (local).
|
||||
// This allows the following attempt by Marionette to open a socket
|
||||
// to succeed.
|
||||
let insaneSacrificialGoat =
|
||||
new ServerSocket(666, Ci.nsIServerSocket.KeepWhenOffline, 4);
|
||||
insaneSacrificialGoat.asyncListen(this);
|
||||
}
|
||||
|
||||
let port = DEFAULT_PORT;
|
||||
try {
|
||||
port = Services.prefs.getIntPref(PORT_PREF);
|
||||
} catch (e) {}
|
||||
|
||||
let s;
|
||||
try {
|
||||
Cu.import("chrome://marionette/content/server.js");
|
||||
s = new MarionetteServer(port, forceLocal);
|
||||
s.start();
|
||||
this.logger.info(`Listening on port ${s.port}`);
|
||||
} catch (e) {
|
||||
this.logger.error(`Error on starting server: ${e}`);
|
||||
dump(e.toString() + "\n" + e.stack + "\n");
|
||||
} finally {
|
||||
if (s) {
|
||||
this.server = s;
|
||||
// We want to suppress the modal dialog that's shown
|
||||
// when starting up in safe-mode to enable testing.
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
this.observerService.addObserver(this, "domwindowopened", false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case "final-ui-startup":
|
||||
this.finalUiStartup = true;
|
||||
this.observerService.removeObserver(this, aTopic);
|
||||
this.observerService.addObserver(this, "xpcom-shutdown", false);
|
||||
this.init();
|
||||
break;
|
||||
case "domwindowopened":
|
||||
this.observerService.removeObserver(this, aTopic);
|
||||
this._suppressSafeModeDialog(aSubject);
|
||||
break;
|
||||
case "xpcom-shutdown":
|
||||
this.observerService.removeObserver(this, "xpcom-shutdown");
|
||||
this.uninit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
_suppressSafeModeDialog: function mc_suppressSafeModeDialog(aWindow) {
|
||||
// Wait for the modal dialog to finish loading.
|
||||
aWindow.addEventListener("load", function onLoad() {
|
||||
aWindow.removeEventListener("load", onLoad);
|
||||
|
||||
if (aWindow.document.getElementById("safeModeDialog")) {
|
||||
aWindow.setTimeout(() => {
|
||||
// Accept the dialog to start in safe-mode.
|
||||
aWindow.document.documentElement.getButton("accept").click();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
init: function mc_init() {
|
||||
if (!this._loaded && this.enabled && this.finalUiStartup) {
|
||||
this._loaded = true;
|
||||
|
||||
let marionette_forcelocal = this.appName == 'B2G' ? false : true;
|
||||
try {
|
||||
marionette_forcelocal = Services.prefs.getBoolPref(MARIONETTE_FORCELOCAL_PREF);
|
||||
}
|
||||
catch(e) {}
|
||||
Services.prefs.setBoolPref(MARIONETTE_FORCELOCAL_PREF, marionette_forcelocal);
|
||||
|
||||
if (!marionette_forcelocal) {
|
||||
// See bug 800138. Because the first socket that opens with
|
||||
// force-local=false fails, we open a dummy socket that will fail.
|
||||
// keepWhenOffline=true so that it still work when offline (local).
|
||||
// This allows the following attempt by Marionette to open a socket
|
||||
// to succeed.
|
||||
let insaneSacrificialGoat = new ServerSocket(666, Ci.nsIServerSocket.KeepWhenOffline, 4);
|
||||
insaneSacrificialGoat.asyncListen(this);
|
||||
}
|
||||
|
||||
let port;
|
||||
try {
|
||||
port = Services.prefs.getIntPref('marionette.defaultPrefs.port');
|
||||
}
|
||||
catch(e) {
|
||||
port = 2828;
|
||||
}
|
||||
try {
|
||||
loader.loadSubScript("chrome://marionette/content/marionette-server.js");
|
||||
let forceLocal = Services.prefs.getBoolPref(MARIONETTE_FORCELOCAL_PREF);
|
||||
this._marionetteServer = new MarionetteServer(port, forceLocal);
|
||||
this.logger.info("Marionette server ready");
|
||||
}
|
||||
catch(e) {
|
||||
this.logger.error('exception: ' + e.name + ', ' + e.message + ': ' +
|
||||
e.fileName + " :: " + e.lineNumber);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function mc_uninit() {
|
||||
if (this._marionetteServer) {
|
||||
this._marionetteServer.closeListener();
|
||||
}
|
||||
this._loaded = false;
|
||||
},
|
||||
|
||||
MarionetteComponent.prototype.uninit = function() {
|
||||
if (!this.loaded_) {
|
||||
return;
|
||||
}
|
||||
this.server.stop();
|
||||
this.loaded_ = false;
|
||||
};
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MarionetteComponent]);
|
||||
|
@ -1,288 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/command.js");
|
||||
Cu.import("chrome://marionette/content/emulator.js");
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
Cu.import("chrome://marionette/content/driver.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Dispatcher"];
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
|
||||
/**
|
||||
* Manages a Marionette connection, and dispatches packets received to
|
||||
* their correct destinations.
|
||||
*
|
||||
* @param {number} connId
|
||||
* Unique identifier of the connection this dispatcher should handle.
|
||||
* @param {DebuggerTransport} transport
|
||||
* Debugger transport connection to the client.
|
||||
* @param {function(Emulator): GeckoDriver} driverFactory
|
||||
* A factory function that takes an Emulator as argument and produces
|
||||
* a GeckoDriver.
|
||||
* @param {function()} stopSignal
|
||||
* Signal to stop the Marionette server.
|
||||
*/
|
||||
this.Dispatcher = function(connId, transport, driverFactory, stopSignal) {
|
||||
this.id = connId;
|
||||
this.conn = transport;
|
||||
|
||||
// Marionette uses a protocol based on the debugger server, which
|
||||
// requires passing back actor ID's with responses. Unlike the debugger
|
||||
// server, we don't actually have multiple actors, so just use a dummy
|
||||
// value of "0".
|
||||
this.actorId = "0";
|
||||
|
||||
// callback for when connection is closed
|
||||
this.onclose = null;
|
||||
|
||||
// transport hooks are Dispatcher.prototype.onPacket
|
||||
// and Dispatcher.prototype.onClosed
|
||||
this.conn.hooks = this;
|
||||
|
||||
this.emulator = new Emulator(msg => this.sendResponse(msg, -1));
|
||||
this.driver = driverFactory(this.emulator);
|
||||
this.commandProcessor = new CommandProcessor(this.driver);
|
||||
|
||||
this.stopSignal_ = stopSignal;
|
||||
};
|
||||
|
||||
/**
|
||||
* Debugger transport callback that dispatches the request.
|
||||
* Request handlers defined in this.requests take presedence
|
||||
* over those defined in this.driver.commands.
|
||||
*/
|
||||
Dispatcher.prototype.onPacket = function(packet) {
|
||||
logger.debug(`${this.id} -> ${packet.toSource()}`);
|
||||
|
||||
if (this.requests && this.requests[packet.name]) {
|
||||
this.requests[packet.name].bind(this)(packet);
|
||||
} else {
|
||||
let id = this.beginNewCommand();
|
||||
let ok = this.sendOk.bind(this);
|
||||
let send = this.send.bind(this);
|
||||
this.commandProcessor.execute(packet, ok, send, id);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Debugger transport callback that cleans up
|
||||
* after a connection is closed.
|
||||
*/
|
||||
Dispatcher.prototype.onClosed = function(status) {
|
||||
this.driver.sessionTearDown();
|
||||
if (this.onclose) {
|
||||
this.onclose(this);
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatcher specific command handlers:
|
||||
|
||||
Dispatcher.prototype.getMarionetteID = function() {
|
||||
let id = this.beginNewCommand();
|
||||
this.sendResponse({from: "root", id: this.actorId}, id);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.emulatorCmdResult = function(msg) {
|
||||
switch (this.driver.context) {
|
||||
case Context.CONTENT:
|
||||
this.driver.sendAsync("emulatorCmdResult", msg);
|
||||
break;
|
||||
case Context.CHROME:
|
||||
let cb = this.emulator.popCallback(msg.id);
|
||||
if (!cb) {
|
||||
return;
|
||||
}
|
||||
cb.result(msg);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Quits Firefox with the provided flags and tears down the current
|
||||
* session.
|
||||
*/
|
||||
Dispatcher.prototype.quitApplication = function(msg) {
|
||||
let id = this.beginNewCommand();
|
||||
|
||||
if (this.driver.appName != "Firefox") {
|
||||
this.sendError({
|
||||
"message": "In app initiated quit only supported on Firefox",
|
||||
"status": 500
|
||||
}, id);
|
||||
return;
|
||||
}
|
||||
|
||||
let flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
for (let k of msg.parameters.flags) {
|
||||
flags |= Ci.nsIAppStartup[k];
|
||||
}
|
||||
|
||||
this.stopSignal_();
|
||||
this.sendOk(id);
|
||||
|
||||
this.driver.sessionTearDown();
|
||||
Services.startup.quit(flags);
|
||||
};
|
||||
|
||||
// Convenience methods:
|
||||
|
||||
Dispatcher.prototype.sayHello = function() {
|
||||
let id = this.beginNewCommand();
|
||||
let yo = {from: "root", applicationType: "gecko", traits: []};
|
||||
this.sendResponse(yo, id);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.sendOk = function(cmdId) {
|
||||
this.sendResponse({from: this.actorId, ok: true}, cmdId);
|
||||
};
|
||||
|
||||
Dispatcher.prototype.sendError = function(err, cmdId) {
|
||||
let packet = {
|
||||
from: this.actorId,
|
||||
status: err.status,
|
||||
sessionId: this.driver.sessionId,
|
||||
error: err
|
||||
};
|
||||
this.sendResponse(packet, cmdId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Marshals and sends message to either client or emulator based on the
|
||||
* provided {@code cmdId}.
|
||||
*
|
||||
* This routine produces a Marionette protocol packet, which is different
|
||||
* to a WebDriver protocol response in that it contains an extra key
|
||||
* {@code from} for the debugger transport actor ID. It also replaces the
|
||||
* key {@code value} with {@code error} when {@code msg.status} isn't
|
||||
* {@code 0}.
|
||||
*
|
||||
* @param {Object} msg
|
||||
* Object with the properties {@code value}, {@code status}, and
|
||||
* {@code sessionId}.
|
||||
* @param {UUID} cmdId
|
||||
* The unique identifier for the command the message is a response to.
|
||||
*/
|
||||
Dispatcher.prototype.send = function(msg, cmdId) {
|
||||
let packet = {
|
||||
from: this.actorId,
|
||||
value: msg.value,
|
||||
status: msg.status,
|
||||
sessionId: msg.sessionId,
|
||||
};
|
||||
|
||||
if (typeof packet.value == "undefined") {
|
||||
packet.value = null;
|
||||
}
|
||||
|
||||
// the Marionette protocol sends errors using the "error"
|
||||
// key instead of, as Selenium, "value"
|
||||
if (!error.isSuccess(msg.status)) {
|
||||
packet.error = packet.value;
|
||||
delete packet.value;
|
||||
}
|
||||
|
||||
this.sendResponse(packet, cmdId);
|
||||
};
|
||||
|
||||
// Low-level methods:
|
||||
|
||||
/**
|
||||
* Delegates message to client or emulator based on the provided
|
||||
* {@code cmdId}. The message is sent over the debugger transport socket.
|
||||
*
|
||||
* The command ID is a unique identifier assigned to the client's request
|
||||
* that is used to distinguish the asynchronous responses.
|
||||
*
|
||||
* Whilst responses to commands are synchronous and must be sent in the
|
||||
* correct order, emulator callbacks are more transparent and can be sent
|
||||
* at any time. These callbacks won't change the current command state.
|
||||
*
|
||||
* @param {Object} payload
|
||||
* The payload to send.
|
||||
* @param {UUID} cmdId
|
||||
* The unique identifier for this payload. {@code -1} signifies
|
||||
* that it's an emulator callback.
|
||||
*/
|
||||
Dispatcher.prototype.sendResponse = function(payload, cmdId) {
|
||||
if (emulator.isCallback(cmdId)) {
|
||||
this.sendToEmulator(payload);
|
||||
} else {
|
||||
this.sendToClient(payload, cmdId);
|
||||
this.commandId = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Send message to emulator over the debugger transport socket.
|
||||
* Notably this skips out-of-sync command checks.
|
||||
*/
|
||||
Dispatcher.prototype.sendToEmulator = function(payload) {
|
||||
this.sendRaw("emulator", payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Send given payload as-is to the connected client over the debugger
|
||||
* transport socket.
|
||||
*
|
||||
* If {@code cmdId} evaluates to false, the current command state isn't
|
||||
* set, or the response is out-of-sync, a warning is logged and this
|
||||
* routine will return (no-op).
|
||||
*/
|
||||
Dispatcher.prototype.sendToClient = function(payload, cmdId) {
|
||||
if (!cmdId) {
|
||||
logger.warn("Got response with no command ID");
|
||||
return;
|
||||
} else if (this.commandId === null) {
|
||||
logger.warn(`No current command, ignoring response: ${payload.toSource}`);
|
||||
return;
|
||||
} else if (this.isOutOfSync(cmdId)) {
|
||||
logger.warn(`Ignoring out-of-sync response with command ID: ${cmdId}`);
|
||||
return;
|
||||
}
|
||||
this.driver.responseCompleted();
|
||||
this.sendRaw("client", payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sends payload as-is over debugger transport socket to client,
|
||||
* and logs it.
|
||||
*/
|
||||
Dispatcher.prototype.sendRaw = function(dest, payload) {
|
||||
logger.debug(`${this.id} ${dest} <- ${payload.toSource()}`);
|
||||
this.conn.send(payload);
|
||||
};
|
||||
|
||||
/**
|
||||
* Begins a new command by generating a unique identifier and assigning
|
||||
* it to the current command state {@code Dispatcher.prototype.commandId}.
|
||||
*
|
||||
* @return {UUID}
|
||||
* The generated unique identifier for the current command.
|
||||
*/
|
||||
Dispatcher.prototype.beginNewCommand = function() {
|
||||
let uuid = uuidGen.generateUUID().toString();
|
||||
this.commandId = uuid;
|
||||
return uuid;
|
||||
};
|
||||
|
||||
Dispatcher.prototype.isOutOfSync = function(cmdId) {
|
||||
return this.commandId !== cmdId;
|
||||
};
|
||||
|
||||
Dispatcher.prototype.requests = {
|
||||
getMarionetteID: Dispatcher.prototype.getMarionetteID,
|
||||
emulatorCmdResult: Dispatcher.prototype.emulatorCmdResult,
|
||||
quitApplication: Dispatcher.prototype.quitApplication
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -1,122 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {classes: Cc, interfaces: Ci} = Components;
|
||||
const uuidGen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||
this.EXPORTED_SYMBOLS = ["emulator", "Emulator", "EmulatorCallback"];
|
||||
|
||||
this.emulator = {};
|
||||
|
||||
/**
|
||||
* Determines if command ID is an emulator callback.
|
||||
*/
|
||||
this.emulator.isCallback = function(cmdId) {
|
||||
return cmdId < 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the connection between Marionette and the emulator it's
|
||||
* running on.
|
||||
*
|
||||
* When injected scripts call the JS routines {@code runEmulatorCmd} or
|
||||
* {@code runEmulatorShell}, the second argument to those is a callback
|
||||
* which is stored in cbs. They are later retreived by their unique ID
|
||||
* using popCallback.
|
||||
*
|
||||
* @param {function(Object)} sendFn
|
||||
* Callback function that sends a message to the emulator.
|
||||
*/
|
||||
this.Emulator = function(sendFn) {
|
||||
this.send = sendFn;
|
||||
this.cbs = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Pops a callback off the stack if found. Otherwise this is a no-op.
|
||||
*
|
||||
* @param {number} id
|
||||
* Unique ID associated with the callback.
|
||||
*
|
||||
* @return {?function(Object)}
|
||||
* Callback function that takes an emulator response message as
|
||||
* an argument.
|
||||
*/
|
||||
Emulator.prototype.popCallback = function(id) {
|
||||
let f, fi;
|
||||
for (let i = 0; i < this.cbs.length; ++i) {
|
||||
if (this.cbs[i].id == id) {
|
||||
f = this.cbs[i];
|
||||
fi = i;
|
||||
}
|
||||
}
|
||||
|
||||
if (!f) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.cbs.splice(fi, 1);
|
||||
return f;
|
||||
};
|
||||
|
||||
/**
|
||||
* Pushes callback on to the stack.
|
||||
*
|
||||
* @param {function(Object)} cb
|
||||
* Callback function that takes an emulator response message as
|
||||
* an argument.
|
||||
*/
|
||||
Emulator.prototype.pushCallback = function(cb) {
|
||||
cb.send_ = this.sendFn;
|
||||
this.cbs.push(cb);
|
||||
};
|
||||
|
||||
/**
|
||||
* Encapsulates a callback to the emulator and provides an execution
|
||||
* environment for them.
|
||||
*
|
||||
* Each callback is assigned a unique identifier, id, that can be used
|
||||
* to retrieve them from Emulator's stack using popCallback.
|
||||
*
|
||||
* The onresult event listener is triggered when a result arrives on
|
||||
* the callback.
|
||||
*
|
||||
* The onerror event listener is triggered when an error occurs during
|
||||
* the execution of that callback.
|
||||
*/
|
||||
this.EmulatorCallback = function() {
|
||||
this.id = uuidGen.generateUUID().toString();
|
||||
this.onresult = null;
|
||||
this.onerror = null;
|
||||
this.send_ = null;
|
||||
};
|
||||
|
||||
EmulatorCallback.prototype.command = function(cmd, cb) {
|
||||
this.onresult = cb;
|
||||
this.send_({emulator_cmd: cmd, id: this.id});
|
||||
};
|
||||
|
||||
EmulatorCallback.prototype.shell = function(args, cb) {
|
||||
this.onresult = cb;
|
||||
this.send_({emulator_shell: args, id: this.id});
|
||||
};
|
||||
|
||||
EmulatorCallback.prototype.result = function(msg) {
|
||||
if (this.send_ === null) {
|
||||
throw new TypeError(
|
||||
"EmulatorCallback must be registered with Emulator to fire");
|
||||
}
|
||||
|
||||
try {
|
||||
if (!this.onresult) {
|
||||
return;
|
||||
}
|
||||
this.onresult(msg.result);
|
||||
} catch (e) {
|
||||
if (this.onerror) {
|
||||
this.onerror(e);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,314 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
const errors = [
|
||||
"ElementNotVisibleError",
|
||||
"FrameSendFailureError",
|
||||
"FrameSendNotInitializedError",
|
||||
"JavaScriptError",
|
||||
"NoAlertOpenError",
|
||||
"NoSuchElementError",
|
||||
"NoSuchFrameError",
|
||||
"NoSuchWindowError",
|
||||
"ScriptTimeoutError",
|
||||
"SessionNotCreatedError",
|
||||
"TimeoutError",
|
||||
"UnknownCommandError",
|
||||
"UnknownError",
|
||||
"UnsupportedOperationError",
|
||||
"WebDriverError",
|
||||
];
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["error"].concat(errors);
|
||||
|
||||
this.error = {};
|
||||
|
||||
error.toJSON = function(err) {
|
||||
return {
|
||||
message: err.message,
|
||||
stacktrace: err.stack || null,
|
||||
status: err.code
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets WebDriver error by its Selenium status code number.
|
||||
*/
|
||||
error.byCode = n => lookup.get(n);
|
||||
|
||||
/**
|
||||
* Determines if the given status code is successful.
|
||||
*/
|
||||
error.isSuccess = code => code === 0;
|
||||
|
||||
/**
|
||||
* Old-style errors are objects that has all of the properties
|
||||
* "message", "code", and "stack".
|
||||
*
|
||||
* When listener.js starts forwarding real errors by CPOW
|
||||
* we can remove this.
|
||||
*/
|
||||
let isOldStyleError = function(obj) {
|
||||
return typeof obj == "object" &&
|
||||
["message", "code", "stack"].every(c => obj.hasOwnProperty(c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if obj is an instance of the Error prototype in a safe manner.
|
||||
* Prefer using this over using instanceof since the Error prototype
|
||||
* isn't unique across browsers, and XPCOM exceptions are special
|
||||
* snowflakes.
|
||||
*/
|
||||
error.isError = function(obj) {
|
||||
if (obj === null || typeof obj != "object") {
|
||||
return false;
|
||||
// XPCOM exception.
|
||||
// Object.getPrototypeOf(obj).result throws error,
|
||||
// consequently we must do the check for properties in its
|
||||
// prototypal chain (using in, instead of obj.hasOwnProperty) here.
|
||||
} else if ("result" in obj) {
|
||||
return true;
|
||||
} else {
|
||||
return Object.getPrototypeOf(obj) == "Error" || isOldStyleError(obj);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if obj is an object in the WebDriverError prototypal chain.
|
||||
*/
|
||||
error.isWebDriverError = function(obj) {
|
||||
return error.isError(obj) &&
|
||||
(("name" in obj && errors.indexOf(obj.name) > 0) ||
|
||||
isOldStyleError(obj));
|
||||
};
|
||||
|
||||
/**
|
||||
* Unhandled error reporter. Dumps the error and its stacktrace to console,
|
||||
* and reports error to the Browser Console.
|
||||
*/
|
||||
error.report = function(err) {
|
||||
let msg = `Marionette threw an error: ${error.stringify(err)}`;
|
||||
dump(msg + "\n");
|
||||
if (Cu.reportError) {
|
||||
Cu.reportError(msg);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Prettifies an instance of Error and its stacktrace to a string.
|
||||
*/
|
||||
error.stringify = function(err) {
|
||||
try {
|
||||
let s = err.toString();
|
||||
if ("stack" in err) {
|
||||
s += "\n" + err.stack;
|
||||
}
|
||||
return s;
|
||||
} catch (e) {
|
||||
return "<unprintable error>";
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* WebDriverError is the prototypal parent of all WebDriver errors.
|
||||
* It should not be used directly, as it does not correspond to a real
|
||||
* error in the specification.
|
||||
*/
|
||||
this.WebDriverError = function(msg) {
|
||||
Error.call(this, msg);
|
||||
this.name = "WebDriverError";
|
||||
this.message = msg;
|
||||
this.code = 500; // overridden
|
||||
};
|
||||
WebDriverError.prototype = Object.create(Error.prototype);
|
||||
|
||||
this.NoSuchElementError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "NoSuchElementError";
|
||||
this.status = "no such element";
|
||||
this.code = 7;
|
||||
};
|
||||
NoSuchElementError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.NoSuchFrameError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "NoSuchFrameError";
|
||||
this.status = "no such frame";
|
||||
this.code = 8;
|
||||
};
|
||||
NoSuchFrameError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.UnknownCommandError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "UnknownCommandError";
|
||||
this.status = "unknown command";
|
||||
this.code = 9;
|
||||
};
|
||||
UnknownCommandError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.ElementNotVisibleError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "ElementNotVisibleError";
|
||||
this.status = "element not visible";
|
||||
this.code = 11;
|
||||
};
|
||||
ElementNotVisibleError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.InvalidElementState = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "InvalidElementState";
|
||||
this.status = "invalid element state";
|
||||
this.code = 12;
|
||||
};
|
||||
InvalidElementState.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.UnknownError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "UnknownError";
|
||||
this.status = "unknown error";
|
||||
this.code = 13;
|
||||
};
|
||||
UnknownError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
/**
|
||||
* Creates an error message for a JavaScript error thrown during
|
||||
* executeScript or executeAsyncScript.
|
||||
*
|
||||
* @param {Error} err
|
||||
* An Error object passed to a catch block or a message.
|
||||
* @param {string} fnName
|
||||
* The name of the function to use in the stack trace message
|
||||
* (e.g. execute_script).
|
||||
* @param {string} file
|
||||
* The filename of the test file containing the Marionette
|
||||
* command that caused this error to occur.
|
||||
* @param {number} line
|
||||
* The line number of the above test file.
|
||||
* @param {string=} script
|
||||
* The JS script being executed in text form.
|
||||
*/
|
||||
this.JavaScriptError = function(err, fnName, file, line, script) {
|
||||
let msg = String(err);
|
||||
let trace = "";
|
||||
|
||||
if (fnName && line) {
|
||||
trace += `${fnName} @${file}`;
|
||||
if (line) {
|
||||
trace += `, line ${line}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof err == "object" && "name" in err && "stack" in err) {
|
||||
let jsStack = err.stack.split("\n");
|
||||
let match = jsStack[0].match(/:(\d+):\d+$/);
|
||||
let jsLine = match ? parseInt(match[1]) : 0;
|
||||
if (script) {
|
||||
let src = script.split("\n")[jsLine];
|
||||
trace += "\n" +
|
||||
"inline javascript, line " + jsLine + "\n" +
|
||||
"src: \"" + src + "\"";
|
||||
}
|
||||
}
|
||||
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "JavaScriptError";
|
||||
this.status = "javascript error";
|
||||
this.code = 17;
|
||||
this.stack = trace;
|
||||
};
|
||||
JavaScriptError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.TimeoutError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "TimeoutError";
|
||||
this.status = "timeout";
|
||||
this.code = 21;
|
||||
};
|
||||
TimeoutError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.NoSuchWindowError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "NoSuchWindowError";
|
||||
this.status = "no such window";
|
||||
this.code = 23;
|
||||
};
|
||||
NoSuchWindowError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.NoAlertOpenError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "NoAlertOpenError";
|
||||
this.status = "no such alert";
|
||||
this.code = 27;
|
||||
}
|
||||
NoAlertOpenError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.ScriptTimeoutError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "ScriptTimeoutError";
|
||||
this.status = "script timeout";
|
||||
this.code = 28;
|
||||
};
|
||||
ScriptTimeoutError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.SessionNotCreatedError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "SessionNotCreatedError";
|
||||
this.status = "session not created";
|
||||
// should be 33 to match Selenium
|
||||
this.code = 71;
|
||||
}
|
||||
SessionNotCreatedError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.FrameSendNotInitializedError = function(frame) {
|
||||
this.message = "Error sending message to frame (NS_ERROR_NOT_INITIALIZED)";
|
||||
WebDriverError.call(this, this.message);
|
||||
this.name = "FrameSendNotInitializedError";
|
||||
this.status = "frame send not initialized error";
|
||||
this.code = 54;
|
||||
this.frame = frame;
|
||||
this.errMsg = `${this.message} ${this.frame}; frame has closed.`;
|
||||
};
|
||||
FrameSendNotInitializedError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.FrameSendFailureError = function(frame) {
|
||||
this.message = "Error sending message to frame (NS_ERROR_FAILURE)";
|
||||
WebDriverError.call(this, this.message);
|
||||
this.name = "FrameSendFailureError";
|
||||
this.status = "frame send failure error";
|
||||
this.code = 55;
|
||||
this.frame = frame;
|
||||
this.errMsg = `${this.message} ${this.frame}; frame not responding.`;
|
||||
};
|
||||
FrameSendFailureError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
this.UnsupportedOperationError = function(msg) {
|
||||
WebDriverError.call(this, msg);
|
||||
this.name = "UnsupportedOperationError";
|
||||
this.status = "unsupported operation";
|
||||
this.code = 405;
|
||||
};
|
||||
UnsupportedOperationError.prototype = Object.create(WebDriverError.prototype);
|
||||
|
||||
const errorObjs = [
|
||||
this.ElementNotVisibleError,
|
||||
this.FrameSendFailureError,
|
||||
this.FrameSendNotInitializedError,
|
||||
this.JavaScriptError,
|
||||
this.NoAlertOpenError,
|
||||
this.NoSuchElementError,
|
||||
this.NoSuchFrameError,
|
||||
this.NoSuchWindowError,
|
||||
this.ScriptTimeoutError,
|
||||
this.SessionNotCreatedError,
|
||||
this.TimeoutError,
|
||||
this.UnknownCommandError,
|
||||
this.UnknownError,
|
||||
this.UnsupportedOperationError,
|
||||
this.WebDriverError,
|
||||
];
|
||||
const lookup = new Map(errorObjs.map(err => [new err().code, err]));
|
@ -4,22 +4,16 @@
|
||||
|
||||
marionette.jar:
|
||||
% content marionette %content/
|
||||
content/server.js (server.js)
|
||||
content/driver.js (driver.js)
|
||||
content/actions.js (actions.js)
|
||||
content/listener.js (listener.js)
|
||||
content/elements.js (elements.js)
|
||||
content/sendkeys.js (sendkeys.js)
|
||||
content/common.js (common.js)
|
||||
content/simpletest.js (simpletest.js)
|
||||
content/frame-manager.js (frame-manager.js)
|
||||
content/marionette-server.js (marionette-server.js)
|
||||
content/marionette-listener.js (marionette-listener.js)
|
||||
content/marionette-elements.js (marionette-elements.js)
|
||||
content/marionette-sendkeys.js (marionette-sendkeys.js)
|
||||
content/marionette-common.js (marionette-common.js)
|
||||
content/marionette-actions.js (marionette-actions.js)
|
||||
content/marionette-simpletest.js (marionette-simpletest.js)
|
||||
content/marionette-frame-manager.js (marionette-frame-manager.js)
|
||||
content/EventUtils.js (EventUtils.js)
|
||||
content/ChromeUtils.js (ChromeUtils.js)
|
||||
content/error.js (error.js)
|
||||
content/command.js (command.js)
|
||||
content/dispatcher.js (dispatcher.js)
|
||||
content/emulator.js (emulator.js)
|
||||
content/modal.js (modal.js)
|
||||
#ifdef ENABLE_TESTS
|
||||
content/test.xul (client/marionette/chrome/test.xul)
|
||||
content/test2.xul (client/marionette/chrome/test2.xul)
|
||||
|
382
testing/marionette/marionette-actions.js
Normal file
382
testing/marionette/marionette-actions.js
Normal file
@ -0,0 +1,382 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
/**
|
||||
* Functionality for (single finger) action chains.
|
||||
*/
|
||||
this.ActionChain = function (utils, checkForInterrupted) {
|
||||
// For assigning unique ids to all touches
|
||||
this.nextTouchId = 1000;
|
||||
// Keep track of active Touches
|
||||
this.touchIds = {};
|
||||
// last touch for each fingerId
|
||||
this.lastCoordinates = null;
|
||||
this.isTap = false;
|
||||
this.scrolling = false;
|
||||
// whether to send mouse event
|
||||
this.mouseEventsOnly = false;
|
||||
this.checkTimer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
|
||||
// Callbacks for command completion.
|
||||
this.onSuccess = null;
|
||||
this.onError = null;
|
||||
if (typeof checkForInterrupted == "function") {
|
||||
this.checkForInterrupted = checkForInterrupted;
|
||||
} else {
|
||||
this.checkForInterrupted = () => {};
|
||||
}
|
||||
|
||||
// Determines if we create touch events.
|
||||
this.inputSource = null;
|
||||
|
||||
// Test utilities providing some event synthesis code.
|
||||
this.utils = utils;
|
||||
}
|
||||
|
||||
ActionChain.prototype = {
|
||||
|
||||
dispatchActions: function (args, touchId, frame, elementManager, callbacks,
|
||||
touchProvider) {
|
||||
// Some touch events code in the listener needs to do ipc, so we can't
|
||||
// share this code across chrome/content.
|
||||
if (touchProvider) {
|
||||
this.touchProvider = touchProvider;
|
||||
}
|
||||
|
||||
this.elementManager = elementManager;
|
||||
let commandArray = elementManager.convertWrappedArguments(args, frame);
|
||||
let {onSuccess, onError} = callbacks;
|
||||
this.onSuccess = onSuccess;
|
||||
this.onError = onError;
|
||||
this.frame = frame;
|
||||
|
||||
if (touchId == null) {
|
||||
touchId = this.nextTouchId++;
|
||||
}
|
||||
|
||||
if (!frame.document.createTouch) {
|
||||
this.mouseEventsOnly = true;
|
||||
}
|
||||
|
||||
let keyModifiers = {
|
||||
shiftKey: false,
|
||||
ctrlKey: false,
|
||||
altKey: false,
|
||||
metaKey: false
|
||||
};
|
||||
|
||||
try {
|
||||
this.actions(commandArray, touchId, 0, keyModifiers);
|
||||
} catch (e) {
|
||||
this.onError(e.message, e.code, e.stack);
|
||||
this.resetValues();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function emit mouse event
|
||||
* @param: doc is the current document
|
||||
* type is the type of event to dispatch
|
||||
* clickCount is the number of clicks, button notes the mouse button
|
||||
* elClientX and elClientY are the coordinates of the mouse relative to the viewport
|
||||
* modifiers is an object of modifier keys present
|
||||
*/
|
||||
emitMouseEvent: function (doc, type, elClientX, elClientY, button, clickCount, modifiers) {
|
||||
if (!this.checkForInterrupted()) {
|
||||
let loggingInfo = "emitting Mouse event of type " + type +
|
||||
" at coordinates (" + elClientX + ", " + elClientY +
|
||||
") relative to the viewport\n" +
|
||||
" button: " + button + "\n" +
|
||||
" clickCount: " + clickCount + "\n";
|
||||
dump(Date.now() + " Marionette: " + loggingInfo);
|
||||
let win = doc.defaultView;
|
||||
let domUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
|
||||
.getInterface(Components.interfaces.nsIDOMWindowUtils);
|
||||
let mods;
|
||||
if (typeof modifiers != "undefined") {
|
||||
mods = this.utils._parseModifiers(modifiers);
|
||||
} else {
|
||||
mods = 0;
|
||||
}
|
||||
domUtils.sendMouseEvent(type, elClientX, elClientY, button || 0, clickCount || 1,
|
||||
mods, false, 0, this.inputSource);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset any persisted values after a command completes.
|
||||
*/
|
||||
resetValues: function () {
|
||||
this.onSuccess = null;
|
||||
this.onError = null;
|
||||
this.frame = null;
|
||||
this.elementManager = null;
|
||||
this.touchProvider = null;
|
||||
this.mouseEventsOnly = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Function to emit touch events for each finger. e.g. finger=[['press', id], ['wait', 5], ['release']]
|
||||
* touchId represents the finger id, i keeps track of the current action of the chain
|
||||
* keyModifiers is an object keeping track keyDown/keyUp pairs through an action chain.
|
||||
*/
|
||||
actions: function (chain, touchId, i, keyModifiers) {
|
||||
|
||||
if (i == chain.length) {
|
||||
this.onSuccess({value: touchId});
|
||||
this.resetValues();
|
||||
return;
|
||||
}
|
||||
|
||||
let pack = chain[i];
|
||||
let command = pack[0];
|
||||
let el;
|
||||
let c;
|
||||
i++;
|
||||
|
||||
if (['press', 'wait', 'keyDown', 'keyUp', 'click'].indexOf(command) == -1) {
|
||||
// if mouseEventsOnly, then touchIds isn't used
|
||||
if (!(touchId in this.touchIds) && !this.mouseEventsOnly) {
|
||||
this.onError("Element has not been pressed", 500, null);
|
||||
this.resetValues();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch(command) {
|
||||
case 'keyDown':
|
||||
this.utils.sendKeyDown(pack[1], keyModifiers, this.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'keyUp':
|
||||
this.utils.sendKeyUp(pack[1], keyModifiers, this.frame);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'click':
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
let button = pack[2];
|
||||
let clickCount = pack[3];
|
||||
c = this.coordinates(el, null, null);
|
||||
this.mouseTap(el.ownerDocument, c.x, c.y, button, clickCount,
|
||||
keyModifiers);
|
||||
if (button == 2) {
|
||||
this.emitMouseEvent(el.ownerDocument, 'contextmenu', c.x, c.y,
|
||||
button, clickCount, keyModifiers);
|
||||
}
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'press':
|
||||
if (this.lastCoordinates) {
|
||||
this.generateEvents('cancel', this.lastCoordinates[0], this.lastCoordinates[1],
|
||||
touchId, null, keyModifiers);
|
||||
this.onError("Invalid Command: press cannot follow an active touch event", 500, null);
|
||||
this.resetValues();
|
||||
return;
|
||||
}
|
||||
// look ahead to check if we're scrolling. Needed for APZ touch dispatching.
|
||||
if ((i != chain.length) && (chain[i][0].indexOf('move') !== -1)) {
|
||||
this.scrolling = true;
|
||||
}
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
c = this.coordinates(el, pack[2], pack[3]);
|
||||
touchId = this.generateEvents('press', c.x, c.y, null, el, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'release':
|
||||
this.generateEvents('release', this.lastCoordinates[0], this.lastCoordinates[1],
|
||||
touchId, null, keyModifiers);
|
||||
this.actions(chain, null, i, keyModifiers);
|
||||
this.scrolling = false;
|
||||
break;
|
||||
case 'move':
|
||||
el = this.elementManager.getKnownElement(pack[1], this.frame);
|
||||
c = this.coordinates(el);
|
||||
this.generateEvents('move', c.x, c.y, touchId, null, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'moveByOffset':
|
||||
this.generateEvents('move', this.lastCoordinates[0] + pack[1],
|
||||
this.lastCoordinates[1] + pack[2],
|
||||
touchId, null, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
case 'wait':
|
||||
if (pack[1] != null ) {
|
||||
let time = pack[1]*1000;
|
||||
// standard waiting time to fire contextmenu
|
||||
let standard = 750;
|
||||
try {
|
||||
standard = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
|
||||
}
|
||||
catch (e){}
|
||||
if (time >= standard && this.isTap) {
|
||||
chain.splice(i, 0, ['longPress'], ['wait', (time-standard)/1000]);
|
||||
time = standard;
|
||||
}
|
||||
this.checkTimer.initWithCallback(() => {
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
}, time, Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
else {
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
}
|
||||
break;
|
||||
case 'cancel':
|
||||
this.generateEvents('cancel', this.lastCoordinates[0], this.lastCoordinates[1],
|
||||
touchId, null, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
this.scrolling = false;
|
||||
break;
|
||||
case 'longPress':
|
||||
this.generateEvents('contextmenu', this.lastCoordinates[0], this.lastCoordinates[1],
|
||||
touchId, null, keyModifiers);
|
||||
this.actions(chain, touchId, i, keyModifiers);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This function generates a pair of coordinates relative to the viewport given a
|
||||
* target element and coordinates relative to that element's top-left corner.
|
||||
* @param 'x', and 'y' are the relative to the target.
|
||||
* If they are not specified, then the center of the target is used.
|
||||
*/
|
||||
coordinates: function (target, x, y) {
|
||||
let box = target.getBoundingClientRect();
|
||||
if (x == null) {
|
||||
x = box.width / 2;
|
||||
}
|
||||
if (y == null) {
|
||||
y = box.height / 2;
|
||||
}
|
||||
let coords = {};
|
||||
coords.x = box.left + x;
|
||||
coords.y = box.top + y;
|
||||
return coords;
|
||||
},
|
||||
|
||||
/**
|
||||
* Given an element and a pair of coordinates, returns an array of the form
|
||||
* [ clientX, clientY, pageX, pageY, screenX, screenY ]
|
||||
*/
|
||||
getCoordinateInfo: function (el, corx, cory) {
|
||||
let win = el.ownerDocument.defaultView;
|
||||
return [ corx, // clientX
|
||||
cory, // clientY
|
||||
corx + win.pageXOffset, // pageX
|
||||
cory + win.pageYOffset, // pageY
|
||||
corx + win.mozInnerScreenX, // screenX
|
||||
cory + win.mozInnerScreenY // screenY
|
||||
];
|
||||
},
|
||||
|
||||
//x and y are coordinates relative to the viewport
|
||||
generateEvents: function (type, x, y, touchId, target, keyModifiers) {
|
||||
this.lastCoordinates = [x, y];
|
||||
let doc = this.frame.document;
|
||||
switch (type) {
|
||||
case 'tap':
|
||||
if (this.mouseEventsOnly) {
|
||||
this.mouseTap(touch.target.ownerDocument, touch.clientX, touch.clientY,
|
||||
null, null, keyModifiers);
|
||||
} else {
|
||||
touchId = this.nextTouchId++;
|
||||
let touch = this.touchProvider.createATouch(target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent('touchstart', touch);
|
||||
this.touchProvider.emitTouchEvent('touchend', touch);
|
||||
this.mouseTap(touch.target.ownerDocument, touch.clientX, touch.clientY,
|
||||
null, null, keyModifiers);
|
||||
}
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
case 'press':
|
||||
this.isTap = true;
|
||||
if (this.mouseEventsOnly) {
|
||||
this.emitMouseEvent(doc, 'mousemove', x, y, null, null, keyModifiers);
|
||||
this.emitMouseEvent(doc, 'mousedown', x, y, null, null, keyModifiers);
|
||||
}
|
||||
else {
|
||||
touchId = this.nextTouchId++;
|
||||
let touch = this.touchProvider.createATouch(target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent('touchstart', touch);
|
||||
this.touchIds[touchId] = touch;
|
||||
return touchId;
|
||||
}
|
||||
break;
|
||||
case 'release':
|
||||
if (this.mouseEventsOnly) {
|
||||
let [x, y] = this.lastCoordinates;
|
||||
this.emitMouseEvent(doc, 'mouseup', x, y,
|
||||
null, null, keyModifiers);
|
||||
}
|
||||
else {
|
||||
let touch = this.touchIds[touchId];
|
||||
let [x, y] = this.lastCoordinates;
|
||||
touch = this.touchProvider.createATouch(touch.target, x, y, touchId);
|
||||
this.touchProvider.emitTouchEvent('touchend', touch);
|
||||
if (this.isTap) {
|
||||
this.mouseTap(touch.target.ownerDocument, touch.clientX, touch.clientY,
|
||||
null, null, keyModifiers);
|
||||
}
|
||||
delete this.touchIds[touchId];
|
||||
}
|
||||
this.isTap = false;
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
case 'cancel':
|
||||
this.isTap = false;
|
||||
if (this.mouseEventsOnly) {
|
||||
let [x, y] = this.lastCoordinates;
|
||||
this.emitMouseEvent(doc, 'mouseup', x, y,
|
||||
null, null, keyModifiers);
|
||||
}
|
||||
else {
|
||||
this.touchProvider.emitTouchEvent('touchcancel', this.touchIds[touchId]);
|
||||
delete this.touchIds[touchId];
|
||||
}
|
||||
this.lastCoordinates = null;
|
||||
break;
|
||||
case 'move':
|
||||
this.isTap = false;
|
||||
if (this.mouseEventsOnly) {
|
||||
this.emitMouseEvent(doc, 'mousemove', x, y, null, null, keyModifiers);
|
||||
}
|
||||
else {
|
||||
let touch = this.touchProvider.createATouch(this.touchIds[touchId].target,
|
||||
x, y, touchId);
|
||||
this.touchIds[touchId] = touch;
|
||||
this.touchProvider.emitTouchEvent('touchmove', touch);
|
||||
}
|
||||
break;
|
||||
case 'contextmenu':
|
||||
this.isTap = false;
|
||||
let event = this.frame.document.createEvent('MouseEvents');
|
||||
if (this.mouseEventsOnly) {
|
||||
target = doc.elementFromPoint(this.lastCoordinates[0], this.lastCoordinates[1]);
|
||||
}
|
||||
else {
|
||||
target = this.touchIds[touchId].target;
|
||||
}
|
||||
let [ clientX, clientY,
|
||||
pageX, pageY,
|
||||
screenX, screenY ] = this.getCoordinateInfo(target, x, y);
|
||||
event.initMouseEvent('contextmenu', true, true,
|
||||
target.ownerDocument.defaultView, 1,
|
||||
screenX, screenY, clientX, clientY,
|
||||
false, false, false, false, 0, null);
|
||||
target.dispatchEvent(event);
|
||||
break;
|
||||
default:
|
||||
throw {message:"Unknown event type: " + type, code: 500, stack:null};
|
||||
}
|
||||
this.checkForInterrupted();
|
||||
},
|
||||
|
||||
mouseTap: function (doc, x, y, button, clickCount, keyModifiers) {
|
||||
this.emitMouseEvent(doc, 'mousemove', x, y, button, clickCount, keyModifiers);
|
||||
this.emitMouseEvent(doc, 'mousedown', x, y, button, clickCount, keyModifiers);
|
||||
this.emitMouseEvent(doc, 'mouseup', x, y, button, clickCount, keyModifiers);
|
||||
},
|
||||
}
|
@ -2,8 +2,12 @@
|
||||
* 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/. */
|
||||
|
||||
// This file contains common code that is shared between
|
||||
// driver.jj and listener.js.
|
||||
/**
|
||||
*
|
||||
* This file contains common code that is shared between marionette-server.js
|
||||
* and marionette-listener.js.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an error message for a JavaScript exception thrown during
|
||||
@ -73,7 +77,7 @@ MarionetteLogObj.prototype = {
|
||||
this.logs.push(msgs[i]);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return all logged messages.
|
||||
*/
|
@ -427,18 +427,18 @@ ElementManager.prototype = {
|
||||
* as the start node instead of the document root
|
||||
* If this object has a 'time' member, this number will be
|
||||
* used to see if we have hit the search timelimit.
|
||||
* @param function on_success
|
||||
* The notification callback used when we are returning successfully.
|
||||
* @param function on_error
|
||||
The callback to invoke when an error occurs.
|
||||
* @param boolean all
|
||||
* If true, all found elements will be returned.
|
||||
* If false, only the first element will be returned.
|
||||
* @param function on_success
|
||||
* Callback used when operating is successful.
|
||||
* @param function on_error
|
||||
* Callback to invoke when an error occurs.
|
||||
*
|
||||
* @return nsIDOMElement or list of nsIDOMElements
|
||||
* Returns the element(s) by calling the on_success function.
|
||||
*/
|
||||
find: function EM_find(win, values, searchTimeout, all, on_success, on_error, command_id) {
|
||||
find: function EM_find(win, values, searchTimeout, on_success, on_error, all, command_id) {
|
||||
let startTime = values.time ? values.time : new Date().getTime();
|
||||
let startNode = (values.element != undefined) ?
|
||||
this.getKnownElement(values.element, win) : win.document;
|
||||
@ -461,13 +461,13 @@ ElementManager.prototype = {
|
||||
} else if (values.using == ANON_ATTRIBUTE) {
|
||||
message = "Unable to locate anonymous element: " + JSON.stringify(values.value);
|
||||
}
|
||||
on_error({message: message, code: 7}, command_id);
|
||||
on_error(message, 7, null, command_id);
|
||||
}
|
||||
} else {
|
||||
values.time = startTime;
|
||||
this.timer.initWithCallback(this.find.bind(this, win, values,
|
||||
searchTimeout, all,
|
||||
on_success, on_error,
|
||||
searchTimeout,
|
||||
on_success, on_error, all,
|
||||
command_id),
|
||||
100,
|
||||
Components.interfaces.nsITimer.TYPE_ONE_SHOT);
|
||||
@ -476,13 +476,14 @@ ElementManager.prototype = {
|
||||
if (isArrayLike) {
|
||||
let ids = []
|
||||
for (let i = 0 ; i < found.length ; i++) {
|
||||
ids.push({"ELEMENT": this.addToKnownElements(found[i])});
|
||||
ids.push({'ELEMENT': this.addToKnownElements(found[i])});
|
||||
}
|
||||
on_success(ids, command_id);
|
||||
} else {
|
||||
let id = this.addToKnownElements(found);
|
||||
on_success({"ELEMENT": id}, command_id);
|
||||
on_success({'ELEMENT':id}, command_id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
@ -2,15 +2,17 @@
|
||||
* 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/. */
|
||||
|
||||
let {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["FrameManager"];
|
||||
|
||||
let FRAME_SCRIPT = "chrome://marionette/content/listener.js";
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"FrameManager"
|
||||
];
|
||||
|
||||
let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
let logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
let specialpowers = {};
|
||||
@ -97,43 +99,30 @@ FrameManager.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
getOopFrame: function FM_getOopFrame(winId, frameId) {
|
||||
// get original frame window
|
||||
let outerWin = Services.wm.getOuterWindowWithId(winId);
|
||||
// find the OOP frame
|
||||
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
|
||||
return f;
|
||||
},
|
||||
|
||||
getFrameMM: function FM_getFrameMM(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
|
||||
.frameLoader.messageManager;
|
||||
return mm;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to OOP frame. We're handling this here
|
||||
* so we can maintain a list of remote frames.
|
||||
*/
|
||||
switchToFrame: function FM_switchToFrame(winId, frameId) {
|
||||
let oopFrame = this.getOopFrame(winId, frameId);
|
||||
let mm = this.getFrameMM(winId, frameId);
|
||||
//This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames.
|
||||
switchToFrame: function FM_switchToFrame(message) {
|
||||
// Switch to a remote frame.
|
||||
let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window
|
||||
let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame
|
||||
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm
|
||||
|
||||
if (!specialpowers.hasOwnProperty("specialPowersObserver")) {
|
||||
loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
|
||||
specialpowers);
|
||||
specialpowers);
|
||||
}
|
||||
|
||||
// See if this frame already has our frame script loaded in it;
|
||||
// if so, just wake it up.
|
||||
// See if this frame already has our frame script loaded in it; if so,
|
||||
// just wake it up.
|
||||
for (let i = 0; i < remoteFrames.length; i++) {
|
||||
let frame = remoteFrames[i];
|
||||
let frameMessageManager = frame.messageManager.get();
|
||||
logger.info("trying remote frame " + i);
|
||||
try {
|
||||
frameMessageManager.sendAsyncMessage("aliveCheck", {});
|
||||
} catch (e) {
|
||||
}
|
||||
catch(e) {
|
||||
if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) {
|
||||
logger.info("deleting frame");
|
||||
remoteFrames.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
@ -141,31 +130,29 @@ FrameManager.prototype = {
|
||||
if (frameMessageManager == mm) {
|
||||
this.currentRemoteFrame = frame;
|
||||
this.addMessageManagerListeners(mm);
|
||||
|
||||
if (!frame.specialPowersObserver) {
|
||||
frame.specialPowersObserver = new specialpowers.SpecialPowersObserver();
|
||||
frame.specialPowersObserver.init(mm);
|
||||
}
|
||||
|
||||
mm.sendAsyncMessage("Marionette:restart");
|
||||
mm.sendAsyncMessage("Marionette:restart", {});
|
||||
return oopFrame.id;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, then we need to load the frame script in this frame,
|
||||
// and set the frame's ChromeMessageSender as the active message manager
|
||||
// the server will listen to.
|
||||
// and set the frame's ChromeMessageSender as the active message manager the server will listen to
|
||||
this.addMessageManagerListeners(mm);
|
||||
let aFrame = new MarionetteRemoteFrame(winId, frameId);
|
||||
let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
|
||||
aFrame.messageManager = Cu.getWeakReference(mm);
|
||||
remoteFrames.push(aFrame);
|
||||
this.currentRemoteFrame = aFrame;
|
||||
|
||||
logger.info("frame-manager load script: " + mm.toString());
|
||||
mm.loadFrameScript(FRAME_SCRIPT, true, true);
|
||||
|
||||
aFrame.specialPowersObserver = new specialpowers.SpecialPowersObserver();
|
||||
aFrame.specialPowersObserver.init(mm);
|
||||
|
||||
return oopFrame.id;
|
||||
},
|
||||
|
||||
@ -197,58 +184,64 @@ FrameManager.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds message listeners to the server,
|
||||
* listening for messages from content frame scripts.
|
||||
* It also adds a MarionetteFrame:getInterruptedState
|
||||
* message listener to the FrameManager,
|
||||
* so the frame manager's state can be checked by the frame.
|
||||
* Adds message listeners to the server, listening for messages from content frame scripts.
|
||||
* It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager,
|
||||
* so the frame manager's state can be checked by the frame
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
* @param object messageManager
|
||||
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
|
||||
* to which the listeners should be added.
|
||||
*/
|
||||
addMessageManagerListeners: function FM_addMessageManagerListeners(mm) {
|
||||
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
|
||||
mm.addWeakMessageListener("Marionette:log", this.server);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
mm.addWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
|
||||
mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.addWeakMessageListener("Marionette:addCookie", this.server);
|
||||
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.addWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
mm.addWeakMessageListener("Marionette:register", this.server);
|
||||
mm.addWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
|
||||
addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
|
||||
messageManager.addWeakMessageListener("Marionette:ok", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:done", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:error", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:log", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:register", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:shareData", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:switchToFrame", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:addCookie", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
messageManager.addWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes listeners for messages from content frame scripts.
|
||||
* We do not remove the MarionetteFrame:getInterruptedState
|
||||
* or the Marionette:switchToModalOrigin message listener,
|
||||
* because we want to allow all known frames to contact the frame manager
|
||||
* so that it can check if it was interrupted, and if so,
|
||||
* it will call switchToModalOrigin when its process gets resumed.
|
||||
* We do not remove the "MarionetteFrame:getInterruptedState" or the
|
||||
* "Marioentte:switchToModalOrigin" message listener,
|
||||
* because we want to allow all known frames to contact the frame manager so that
|
||||
* it can check if it was interrupted, and if so, it will call switchToModalOrigin
|
||||
* when its process gets resumed.
|
||||
*
|
||||
* @param {nsIMessageListenerManager} mm
|
||||
* The message manager object, typically
|
||||
* ChromeMessageBroadcaster or ChromeMessageSender.
|
||||
* @param object messageManager
|
||||
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
|
||||
* from which the listeners should be removed.
|
||||
*/
|
||||
removeMessageManagerListeners: function FM_removeMessageManagerListeners(mm) {
|
||||
mm.removeWeakMessageListener("Marionette:log", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:shareData", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:addCookie", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
mm.removeWeakMessageListener("Marionette:register", this.server);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
}
|
||||
removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
|
||||
messageManager.removeWeakMessageListener("Marionette:ok", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:done", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:error", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:log", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:shareData", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:register", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:switchToFrame", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:addCookie", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:deleteCookie", this.server);
|
||||
messageManager.removeWeakMessageListener("Marionette:listenersAttached", this.server);
|
||||
messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this);
|
||||
messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
|
||||
},
|
||||
};
|
@ -11,10 +11,10 @@ let uuidGen = Cc["@mozilla.org/uuid-generator;1"]
|
||||
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Ci.mozIJSSubScriptLoader);
|
||||
|
||||
loader.loadSubScript("chrome://marionette/content/simpletest.js");
|
||||
loader.loadSubScript("chrome://marionette/content/common.js");
|
||||
loader.loadSubScript("chrome://marionette/content/actions.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
|
||||
loader.loadSubScript("chrome://marionette/content/marionette-common.js");
|
||||
loader.loadSubScript("chrome://marionette/content/marionette-actions.js");
|
||||
Cu.import("chrome://marionette/content/marionette-elements.js");
|
||||
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
@ -24,7 +24,7 @@ utils.window = content;
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/atoms.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/sendkeys.js", utils);
|
||||
loader.loadSubScript("chrome://marionette/content/marionette-sendkeys.js", utils);
|
||||
|
||||
loader.loadSubScript("chrome://specialpowers/content/specialpowersAPI.js");
|
||||
loader.loadSubScript("chrome://specialpowers/content/specialpowers.js");
|
||||
@ -74,7 +74,7 @@ let multiLast = {};
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
let logger = Log.repository.getLogger("Marionette");
|
||||
logger.info("loaded listener.js");
|
||||
logger.info("loaded marionette-listener.js");
|
||||
let modalHandler = function() {
|
||||
// This gets called on the system app only since it receives the mozbrowserprompt event
|
||||
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
|
||||
@ -107,11 +107,9 @@ function registerSelf() {
|
||||
importedScripts = FileUtils.getDir('TmpD', [], false);
|
||||
importedScripts.append('marionetteContentScripts');
|
||||
startListeners();
|
||||
let rv = {};
|
||||
if (remotenessChange) {
|
||||
rv.listenerId = id;
|
||||
sendAsyncMessage("Marionette:listenersAttached", {listenerId: id});
|
||||
}
|
||||
sendAsyncMessage("Marionette:listenersAttached", rv);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -361,9 +359,9 @@ function sendLog(msg) {
|
||||
/**
|
||||
* Send error message to server
|
||||
*/
|
||||
function sendError(msg, code, stack, cmdId) {
|
||||
let payload = {message: msg, code: code, stack: stack};
|
||||
sendToServer("Marionette:error", payload, cmdId);
|
||||
function sendError(message, status, trace, command_id) {
|
||||
let error_msg = { message: message, status: status, stacktrace: trace };
|
||||
sendToServer("Marionette:error", error_msg, command_id);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -434,8 +432,6 @@ function createExecuteContentSandbox(aWindow, timeout) {
|
||||
marionetteLogObj, timeout,
|
||||
heartbeatCallback,
|
||||
marionetteTestName);
|
||||
marionette.runEmulatorCmd = (cmd, cb) => this.runEmulatorCmd(cmd, cb);
|
||||
marionette.runEmulatorShell = (args, cb) => this.runEmulatorShell(args, cb);
|
||||
sandbox.marionette = marionette;
|
||||
marionette.exports.forEach(function(fn) {
|
||||
try {
|
||||
@ -969,17 +965,8 @@ function actionChain(msg) {
|
||||
touchProvider.createATouch = createATouch;
|
||||
touchProvider.emitTouchEvent = emitTouchEvent;
|
||||
|
||||
try {
|
||||
actions.dispatchActions(
|
||||
args,
|
||||
touchId,
|
||||
curFrame,
|
||||
elementManager,
|
||||
callbacks,
|
||||
touchProvider);
|
||||
} catch (e) {
|
||||
sendError(e.message, e.code, e.stack, command_id);
|
||||
}
|
||||
actions.dispatchActions(args, touchId, curFrame, elementManager, callbacks,
|
||||
touchProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1305,10 +1292,10 @@ function refresh(msg) {
|
||||
function findElementContent(msg) {
|
||||
let command_id = msg.json.command_id;
|
||||
try {
|
||||
let on_success = function(el, cmd_id) { sendResponse({value: el}, cmd_id) };
|
||||
let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
|
||||
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
|
||||
let on_error = sendError;
|
||||
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
|
||||
false /* all */, on_success, on_error, command_id);
|
||||
on_success, on_error, false, command_id);
|
||||
}
|
||||
catch (e) {
|
||||
sendError(e.message, e.code, e.stack, command_id);
|
||||
@ -1321,10 +1308,10 @@ function findElementContent(msg) {
|
||||
function findElementsContent(msg) {
|
||||
let command_id = msg.json.command_id;
|
||||
try {
|
||||
let on_success = function(els, cmd_id) { sendResponse({value: els}, cmd_id); };
|
||||
let on_error = function(e, cmd_id) { sendError(e.message, e.code, null, cmd_id); };
|
||||
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
|
||||
let on_error = sendError;
|
||||
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
|
||||
true /* all */, on_success, on_error, command_id);
|
||||
on_success, on_error, true, command_id);
|
||||
}
|
||||
catch (e) {
|
||||
sendError(e.message, e.code, e.stack, command_id);
|
||||
@ -1715,20 +1702,21 @@ function switchToFrame(msg) {
|
||||
let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
|
||||
sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
|
||||
|
||||
let rv = null;
|
||||
if (curFrame.contentWindow === null) {
|
||||
// The frame we want to switch to is a remote/OOP frame;
|
||||
// notify our parent to handle the switch
|
||||
if (curFrame.contentWindow == null) {
|
||||
// The frame we want to switch to is a remote (out-of-process) frame;
|
||||
// notify our parent to handle the switch.
|
||||
curFrame = content;
|
||||
rv = {win: parWindow, frame: foundFrame};
|
||||
} else {
|
||||
sendToServer('Marionette:switchToFrame', {win: parWindow,
|
||||
frame: foundFrame,
|
||||
command_id: command_id});
|
||||
}
|
||||
else {
|
||||
curFrame = curFrame.contentWindow;
|
||||
if (msg.json.focus)
|
||||
if(msg.json.focus == true) {
|
||||
curFrame.focus();
|
||||
}
|
||||
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
|
||||
}
|
||||
|
||||
sendResponse({value: rv}, command_id);
|
||||
}
|
||||
/**
|
||||
* Add a cookie to the document
|
@ -123,8 +123,8 @@ function sendSingleKey (keyToSend, modifiers, document) {
|
||||
utils.synthesizeKey(keyCode, modifiers, document);
|
||||
}
|
||||
|
||||
function sendKeysToElement (document, element, keysToSend, successCallback, errorCallback, command_id, ignoreVisibility) {
|
||||
if (ignoreVisibility || checkVisible(element)) {
|
||||
function sendKeysToElement (document, element, keysToSend, successCallback, errorCallback, command_id, context) {
|
||||
if (context == "chrome" || checkVisible(element)) {
|
||||
element.focus();
|
||||
let modifiers = {
|
||||
shiftKey: false,
|
3680
testing/marionette/marionette-server.js
Normal file
3680
testing/marionette/marionette-server.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,18 +1,12 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
let {utils: Cu} = Components;
|
||||
|
||||
Cu.import("chrome://marionette/content/error.js");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["Marionette"];
|
||||
|
||||
/*
|
||||
* The Marionette object, passed to the script context.
|
||||
*/
|
||||
this.Marionette = function(scope, window, context, logObj, timeout,
|
||||
heartbeatCallback, testName) {
|
||||
|
||||
this.Marionette = function Marionette(scope, window, context, logObj, timeout,
|
||||
heartbeatCallback, testName) {
|
||||
this.scope = scope;
|
||||
this.window = window;
|
||||
this.tests = [];
|
||||
@ -25,25 +19,12 @@ this.Marionette = function(scope, window, context, logObj, timeout,
|
||||
this.TEST_UNEXPECTED_PASS = "TEST-UNEXPECTED-PASS";
|
||||
this.TEST_PASS = "TEST-PASS";
|
||||
this.TEST_KNOWN_FAIL = "TEST-KNOWN-FAIL";
|
||||
};
|
||||
}
|
||||
|
||||
Marionette.prototype = {
|
||||
exports: [
|
||||
"ok",
|
||||
"is",
|
||||
"isnot",
|
||||
"todo",
|
||||
"log",
|
||||
"getLogs",
|
||||
"generate_results",
|
||||
"waitFor",
|
||||
"runEmulatorCmd",
|
||||
"runEmulatorShell",
|
||||
"TEST_PASS",
|
||||
"TEST_KNOWN_FAIL",
|
||||
"TEST_UNEXPECTED_FAIL",
|
||||
"TEST_UNEXPECTED_PASS"
|
||||
],
|
||||
exports: ['ok', 'is', 'isnot', 'todo', 'log', 'getLogs', 'generate_results', 'waitFor',
|
||||
'runEmulatorCmd', 'runEmulatorShell', 'TEST_PASS', 'TEST_KNOWN_FAIL',
|
||||
'TEST_UNEXPECTED_FAIL', 'TEST_UNEXPECTED_PASS'],
|
||||
|
||||
addTest: function Marionette__addTest(condition, name, passString, failString, diag, state) {
|
||||
|
||||
@ -213,4 +194,6 @@ Marionette.prototype = {
|
||||
this.heartbeatCallback();
|
||||
this.scope.runEmulatorShell(args, callback);
|
||||
},
|
||||
|
||||
};
|
||||
|
@ -1,115 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["modal"];
|
||||
|
||||
let isFirefox = () => Services.appinfo.name == "Firefox";
|
||||
|
||||
this.modal = {};
|
||||
modal = {
|
||||
COMMON_DIALOG_LOADED: "common-dialog-loaded",
|
||||
TABMODAL_DIALOG_LOADED: "tabmodal-dialog-loaded",
|
||||
handlers: {
|
||||
"common-dialog-loaded": new Set(),
|
||||
"tabmodal-dialog-loaded": new Set()
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add handler that will be called when a global- or tab modal dialogue
|
||||
* appears.
|
||||
*
|
||||
* This is achieved by installing observers for common-
|
||||
* and tab modal loaded events.
|
||||
*
|
||||
* This function is a no-op if called on any other product than Firefox.
|
||||
*
|
||||
* @param {function(Object, string)} handler
|
||||
* The handler to be called, which is passed the
|
||||
* subject (e.g. ChromeWindow) and the topic (one of
|
||||
* {@code modal.COMMON_DIALOG_LOADED} or
|
||||
* {@code modal.TABMODAL_DIALOG_LOADED}.
|
||||
*/
|
||||
modal.addHandler = function(handler) {
|
||||
if (!isFirefox()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys(this.handlers).map(topic => {
|
||||
this.handlers[topic].add(handler);
|
||||
Services.obs.addObserver(handler, topic, false);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove modal dialogue handler by function reference.
|
||||
*
|
||||
* This function is a no-op if called on any other product than Firefox.
|
||||
*
|
||||
* @param {function} toRemove
|
||||
* The handler previously passed to modal.addHandler which will now
|
||||
* be removed.
|
||||
*/
|
||||
modal.removeHandler = function(toRemove) {
|
||||
if (!isFirefox()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let topic of Object.keys(this.handlers)) {
|
||||
let handlers = this.handlers[topic];
|
||||
for (let handler of handlers) {
|
||||
if (handler == toRemove) {
|
||||
Services.obs.removeObserver(handler, topic);
|
||||
handlers.delete(handler);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents the current modal dialogue.
|
||||
*
|
||||
* @param {function(): BrowserObj} curBrowserFn
|
||||
* Function that returns the current BrowserObj.
|
||||
* @param {?nsIWeakReference} winRef
|
||||
* A weak reference to the current ChromeWindow.
|
||||
*/
|
||||
modal.Dialog = function(curBrowserFn, winRef=null) {
|
||||
Object.defineProperty(this, "curBrowser", {
|
||||
get() { return curBrowserFn(); }
|
||||
});
|
||||
this.win_ = winRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the ChromeWindow associated with an open dialog window if it
|
||||
* is currently attached to the DOM.
|
||||
*/
|
||||
Object.defineProperty(modal.Dialog.prototype, "window", {
|
||||
get() {
|
||||
if (this.win_ !== null) {
|
||||
let win = this.win_.get();
|
||||
if (win && win.parent) {
|
||||
return win;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
Object.defineProperty(modal.Dialog.prototype, "ui", {
|
||||
get() {
|
||||
let win = this.window;
|
||||
if (win) {
|
||||
return win.Dialog.ui;
|
||||
}
|
||||
return this.curBrowser.getTabModalUI();
|
||||
}
|
||||
});
|
@ -1,172 +0,0 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const {Constructor: CC, classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
const loader = Cc["@mozilla.org/moz/jssubscript-loader;1"].getService(Ci.mozIJSSubScriptLoader);
|
||||
const ServerSocket = CC("@mozilla.org/network/server-socket;1", "nsIServerSocket", "initSpecialConnection");
|
||||
|
||||
Cu.import("resource://gre/modules/Log.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
Cu.import("chrome://marionette/content/dispatcher.js");
|
||||
Cu.import("chrome://marionette/content/driver.js");
|
||||
Cu.import("chrome://marionette/content/elements.js");
|
||||
Cu.import("chrome://marionette/content/simpletest.js");
|
||||
|
||||
// Bug 1083711: Load transport.js as an SDK module instead of subscript
|
||||
loader.loadSubScript("resource://gre/modules/devtools/transport/transport.js");
|
||||
|
||||
// Preserve this import order:
|
||||
let events = {};
|
||||
loader.loadSubScript("chrome://marionette/content/EventUtils.js", events);
|
||||
loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", events);
|
||||
loader.loadSubScript("chrome://marionette/content/frame-manager.js");
|
||||
|
||||
const logger = Log.repository.getLogger("Marionette");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["MarionetteServer"];
|
||||
const SPECIAL_POWERS_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
|
||||
const CONTENT_LISTENER_PREF = "marionette.contentListener";
|
||||
|
||||
/**
|
||||
* Bootstraps Marionette and handles incoming client connections.
|
||||
*
|
||||
* Once started, it opens a TCP socket sporting the debugger transport
|
||||
* protocol on the provided port. For every new client a Dispatcher is
|
||||
* created.
|
||||
*
|
||||
* @param {number} port
|
||||
* Port for server to listen to.
|
||||
* @param {boolean} forceLocal
|
||||
* Listen only to connections from loopback if true. If false,
|
||||
* accept all connections.
|
||||
*/
|
||||
this.MarionetteServer = function(port, forceLocal) {
|
||||
this.port = port;
|
||||
this.forceLocal = forceLocal;
|
||||
this.conns = {};
|
||||
this.nextConnId = 0;
|
||||
this.alive = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialises the Marionette server by loading in the special powers
|
||||
* which is required to provide some automation-only features.
|
||||
*/
|
||||
MarionetteServer.prototype.init = function() {
|
||||
// SpecialPowers requires insecure automation-only features that we put behind a pref
|
||||
Services.prefs.setBoolPref(SPECIAL_POWERS_PREF, true);
|
||||
let specialpowers = {};
|
||||
loader.loadSubScript(
|
||||
"chrome://specialpowers/content/SpecialPowersObserver.js", specialpowers);
|
||||
specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
|
||||
specialpowers.specialPowersObserver.init();
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that takes an Emulator and produces a GeckoDriver.
|
||||
*
|
||||
* Determines application name and device type to initialise the driver
|
||||
* with. Also bypasses offline status if the device is a qemu or panda
|
||||
* type device.
|
||||
*
|
||||
* @return {GeckoDriver}
|
||||
* A driver instance.
|
||||
*/
|
||||
MarionetteServer.prototype.driverFactory = function(emulator) {
|
||||
let appName = isMulet() ? "B2G" : Services.appinfo.name;
|
||||
let qemu = "0";
|
||||
let device = null;
|
||||
let bypassOffline = false;
|
||||
|
||||
try {
|
||||
Cu.import("resource://gre/modules/systemlibs.js");
|
||||
qemu = libcutils.property_get("ro.kernel.qemu");
|
||||
logger.debug("B2G emulator: " + (qemu == "1" ? "yes" : "no"));
|
||||
device = libcutils.property_get("ro.product.device");
|
||||
logger.debug("Device detected is " + device);
|
||||
bypassOffline = (qemu == "1" || device == "panda");
|
||||
} catch (e) {}
|
||||
|
||||
if (qemu == "1") {
|
||||
device = "qemu";
|
||||
}
|
||||
if (!device) {
|
||||
device = "desktop";
|
||||
}
|
||||
|
||||
Services.prefs.setBoolPref(CONTENT_LISTENER_PREF, false);
|
||||
|
||||
if (bypassOffline) {
|
||||
logger.debug("Bypassing offline status");
|
||||
Services.prefs.setBoolPref("network.gonk.manage-offline-status", false);
|
||||
Services.io.manageOfflineStatus = false;
|
||||
Services.io.offline = false;
|
||||
}
|
||||
|
||||
return new GeckoDriver(appName, device, emulator);
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.start = function() {
|
||||
if (this.alive) {
|
||||
return;
|
||||
}
|
||||
this.init();
|
||||
let flags = Ci.nsIServerSocket.KeepWhenOffline;
|
||||
if (this.forceLocal) {
|
||||
flags |= Ci.nsIServerSocket.LoopbackOnly;
|
||||
}
|
||||
this.listener = new ServerSocket(this.port, flags, 0);
|
||||
this.listener.asyncListen(this);
|
||||
this.alive = true;
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.stop = function() {
|
||||
if (!this.alive) {
|
||||
return;
|
||||
}
|
||||
this.closeListener();
|
||||
this.alive = false;
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.closeListener = function() {
|
||||
this.listener.close();
|
||||
this.listener = null;
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.onSocketAccepted = function(
|
||||
serverSocket, clientSocket) {
|
||||
let input = clientSocket.openInputStream(0, 0, 0);
|
||||
let output = clientSocket.openOutputStream(0, 0, 0);
|
||||
let transport = new DebuggerTransport(input, output);
|
||||
let connId = "conn" + this.nextConnId++;
|
||||
|
||||
let stopSignal = () => this.stop();
|
||||
let dispatcher = new Dispatcher(connId, transport, this.driverFactory, stopSignal);
|
||||
dispatcher.onclose = this.onConnectionClosed.bind(this);
|
||||
this.conns[connId] = dispatcher;
|
||||
|
||||
logger.info(`Accepted connection ${connId} from ${clientSocket.host}:${clientSocket.port}`);
|
||||
|
||||
// Create a root actor for the connection and send the hello packet
|
||||
dispatcher.sayHello();
|
||||
transport.ready();
|
||||
};
|
||||
|
||||
MarionetteServer.prototype.onConnectionClosed = function(conn) {
|
||||
let id = conn.id;
|
||||
delete this.conns[id];
|
||||
logger.info(`Closed connection ${id}`);
|
||||
};
|
||||
|
||||
function isMulet() {
|
||||
try {
|
||||
return Services.prefs.getBoolPref("b2g.is_mulet");
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
Components.utils.import("resource://gre/modules/Services.jsm");
|
||||
@ -14,16 +13,18 @@ if (typeof(Cc) == 'undefined') {
|
||||
var Cc = Components.classes;
|
||||
}
|
||||
|
||||
this.SpecialPowersError = function(aMsg) {
|
||||
Error.call(this);
|
||||
let {stack} = new Error();
|
||||
/**
|
||||
* Special Powers Exception - used to throw exceptions nicely
|
||||
**/
|
||||
this.SpecialPowersException = function SpecialPowersException(aMsg) {
|
||||
this.message = aMsg;
|
||||
this.name = "SpecialPowersError";
|
||||
this.name = "SpecialPowersException";
|
||||
}
|
||||
SpecialPowersError.prototype = Object.create(Error.prototype);
|
||||
|
||||
SpecialPowersError.prototype.toString = function() {
|
||||
return `${this.name}: ${this.message}`;
|
||||
SpecialPowersException.prototype = {
|
||||
toString: function SPE_toString() {
|
||||
return this.name + ': "' + this.message + '"';
|
||||
}
|
||||
};
|
||||
|
||||
this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() {
|
||||
@ -220,7 +221,7 @@ SpecialPowersObserverAPI.prototype = {
|
||||
}
|
||||
|
||||
if (status == 404) {
|
||||
throw new SpecialPowersError(
|
||||
throw new SpecialPowersException(
|
||||
"Error while executing chrome script '" + aUrl + "':\n" +
|
||||
"The script doesn't exists. Ensure you have registered it in " +
|
||||
"'support-files' in your mochitest.ini.");
|
||||
@ -246,19 +247,19 @@ SpecialPowersObserverAPI.prototype = {
|
||||
|
||||
if (aMessage.json.op == "get") {
|
||||
if (!prefName || !prefType)
|
||||
throw new SpecialPowersError("Invalid parameters for get in SPPrefService");
|
||||
throw new SpecialPowersException("Invalid parameters for get in SPPrefService");
|
||||
|
||||
// return null if the pref doesn't exist
|
||||
if (prefs.getPrefType(prefName) == prefs.PREF_INVALID)
|
||||
return null;
|
||||
} else if (aMessage.json.op == "set") {
|
||||
if (!prefName || !prefType || prefValue === null)
|
||||
throw new SpecialPowersError("Invalid parameters for set in SPPrefService");
|
||||
throw new SpecialPowersException("Invalid parameters for set in SPPrefService");
|
||||
} else if (aMessage.json.op == "clear") {
|
||||
if (!prefName)
|
||||
throw new SpecialPowersError("Invalid parameters for clear in SPPrefService");
|
||||
throw new SpecialPowersException("Invalid parameters for clear in SPPrefService");
|
||||
} else {
|
||||
throw new SpecialPowersError("Invalid operation for SPPrefService");
|
||||
throw new SpecialPowersException("Invalid operation for SPPrefService");
|
||||
}
|
||||
|
||||
// Now we make the call
|
||||
@ -305,7 +306,7 @@ SpecialPowersObserverAPI.prototype = {
|
||||
case "find-crash-dump-files":
|
||||
return this._findCrashDumpFiles(aMessage.json.crashDumpFilesToIgnore);
|
||||
default:
|
||||
throw new SpecialPowersError("Invalid operation for SPProcessCrashService");
|
||||
throw new SpecialPowersException("Invalid operation for SPProcessCrashService");
|
||||
}
|
||||
return undefined; // See comment at the beginning of this function.
|
||||
}
|
||||
@ -337,8 +338,8 @@ SpecialPowersObserverAPI.prototype = {
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
throw new SpecialPowersError(
|
||||
"Invalid operation for SPPermissionManager");
|
||||
throw new SpecialPowersException("Invalid operation for " +
|
||||
"SPPermissionManager");
|
||||
}
|
||||
return undefined; // See comment at the beginning of this function.
|
||||
}
|
||||
@ -376,7 +377,7 @@ SpecialPowersObserverAPI.prototype = {
|
||||
return;
|
||||
}
|
||||
default:
|
||||
throw new SpecialPowersError("Invalid operation for SPWebAppsService");
|
||||
throw new SpecialPowersException("Invalid operation for SPWebAppsService");
|
||||
}
|
||||
return undefined; // See comment at the beginning of this function.
|
||||
}
|
||||
@ -389,7 +390,7 @@ SpecialPowersObserverAPI.prototype = {
|
||||
Services.obs.notifyObservers(null, topic, data);
|
||||
break;
|
||||
default:
|
||||
throw new SpecialPowersError("Invalid operation for SPObserverervice");
|
||||
throw new SpecialPowersException("Invalid operation for SPObserverervice");
|
||||
}
|
||||
return undefined; // See comment at the beginning of this function.
|
||||
}
|
||||
@ -442,10 +443,9 @@ SpecialPowersObserverAPI.prototype = {
|
||||
try {
|
||||
Components.utils.evalInSandbox(jsScript, sb, "1.8", url, 1);
|
||||
} catch(e) {
|
||||
throw new SpecialPowersError(
|
||||
"Error while executing chrome script '" + url + "':\n" +
|
||||
e + "\n" +
|
||||
e.fileName + ":" + e.lineNumber);
|
||||
throw new SpecialPowersException("Error while executing chrome " +
|
||||
"script '" + url + "':\n" + e + "\n" +
|
||||
e.fileName + ":" + e.lineNumber);
|
||||
}
|
||||
return undefined; // See comment at the beginning of this function.
|
||||
}
|
||||
@ -471,7 +471,7 @@ SpecialPowersObserverAPI.prototype = {
|
||||
let op = msg.op;
|
||||
|
||||
if (op != 'clear' && op != 'getUsage') {
|
||||
throw new SpecialPowersError('Invalid operation for SPQuotaManager');
|
||||
throw new SpecialPowersException('Invalid operation for SPQuotaManager');
|
||||
}
|
||||
|
||||
let uri = this._getURI(msg.uri);
|
||||
@ -510,12 +510,13 @@ SpecialPowersObserverAPI.prototype = {
|
||||
}
|
||||
|
||||
default:
|
||||
throw new SpecialPowersError("Unrecognized Special Powers API");
|
||||
throw new SpecialPowersException("Unrecognized Special Powers API");
|
||||
}
|
||||
|
||||
// We throw an exception before reaching this explicit return because
|
||||
// we should never be arriving here anyway.
|
||||
throw new SpecialPowersError("Unreached code");
|
||||
throw new SpecialPowersException("Unreached code");
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user