diff --git a/services/sync/tests/tps/mozmill_sanity.js b/services/sync/tests/tps/mozmill_sanity.js
index 922a015ef5dd..fbaed8f257ae 100644
--- a/services/sync/tests/tps/mozmill_sanity.js
+++ b/services/sync/tests/tps/mozmill_sanity.js
@@ -2,10 +2,10 @@
* 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/. */
-Components.utils.import('resource://tps/mozmill/sync.jsm');
+Components.utils.import('resource://tps/tps.jsm');
var setupModule = function(module) {
- controller = mozmill.getBrowserController();
+ module.controller = mozmill.getBrowserController();
assert.ok(true, "SetupModule passes");
}
@@ -16,8 +16,9 @@ var setupTest = function(module) {
var testTestStep = function() {
assert.ok(true, "test Passes");
controller.open("http://www.mozilla.org");
- TPS.SetupSyncAccount();
- assert.equal(TPS.Sync(SYNC_WIPE_SERVER), 0, "sync succeeded");
+
+ TPS.Login();
+ TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
}
var teardownTest = function () {
diff --git a/services/sync/tests/tps/mozmill_sanity2.js b/services/sync/tests/tps/mozmill_sanity2.js
index 027e87f1b75b..f0fd0e3d5df5 100644
--- a/services/sync/tests/tps/mozmill_sanity2.js
+++ b/services/sync/tests/tps/mozmill_sanity2.js
@@ -1,7 +1,6 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-var jum = {}; Components.utils.import('resource://mozmill/modules/jum.js', jum);
+/* 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/. */
var setupModule = function(module) {
module.controller = mozmill.getBrowserController();
@@ -11,43 +10,6 @@ var testGetNode = function() {
controller.open("about:support");
controller.waitForPageLoad();
- var appbox = new elementslib.ID(controller.tabs.activeTab, "application-box");
- jum.assert(appbox.getNode().innerHTML == 'Firefox', 'correct app name');
-};
-
-const NAV_BAR = '/id("main-window")/id("tab-view-deck")/{"flex":"1"}' +
- '/id("navigator-toolbox")/id("nav-bar")';
-const SEARCH_BAR = NAV_BAR + '/id("search-container")/id("searchbar")';
-const SEARCH_TEXTBOX = SEARCH_BAR + '/anon({"anonid":"searchbar-textbox"})';
-const SEARCH_DROPDOWN = SEARCH_TEXTBOX + '/[0]/anon({"anonid":"searchbar-engine-button"})';
-const SEARCH_POPUP = SEARCH_DROPDOWN + '/anon({"anonid":"searchbar-popup"})';
-const SEARCH_INPUT = SEARCH_TEXTBOX + '/anon({"class":"autocomplete-textbox-container"})' +
- '/anon({"anonid":"textbox-input-box"})' +
- '/anon({"anonid":"input"})';
-const SEARCH_CONTEXT = SEARCH_TEXTBOX + '/anon({"anonid":"textbox-input-box"})' +
- '/anon({"anonid":"input-box-contextmenu"})';
-const SEARCH_GO_BUTTON = SEARCH_TEXTBOX + '/anon({"class":"search-go-container"})' +
- '/anon({"class":"search-go-button"})';
-const SEARCH_AUTOCOMPLETE = '/id("main-window")/id("mainPopupSet")/id("PopupAutoComplete")';
-
-var testLookupExpressions = function() {
- var item;
- item = new elementslib.Lookup(controller.window.document, NAV_BAR);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_BAR);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_TEXTBOX);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_DROPDOWN);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_POPUP);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_INPUT);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_CONTEXT);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_GO_BUTTON);
- controller.click(item);
- item = new elementslib.Lookup(controller.window.document, SEARCH_AUTOCOMPLETE);
- controller.click(item);
+ var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
+ assert.waitFor(() => appbox.getNode().textContent == 'Firefox', 'correct app name');
};
diff --git a/services/sync/tps/extensions/mozmill/chrome.manifest b/services/sync/tps/extensions/mozmill/chrome.manifest
old mode 100644
new mode 100755
diff --git a/services/sync/tps/extensions/mozmill/defaults/preferences/debug.js b/services/sync/tps/extensions/mozmill/defaults/preferences/debug.js
deleted file mode 100644
index 03b780e8d8f8..000000000000
--- a/services/sync/tps/extensions/mozmill/defaults/preferences/debug.js
+++ /dev/null
@@ -1,7 +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/. */
-
-/* debugging prefs */
-pref("browser.dom.window.dump.enabled", true);
-pref("javascript.options.showInConsole", true);
diff --git a/services/sync/tps/extensions/mozmill/install.rdf b/services/sync/tps/extensions/mozmill/install.rdf
old mode 100644
new mode 100755
index 9e01a9923e16..1f1d34e449e6
--- a/services/sync/tps/extensions/mozmill/install.rdf
+++ b/services/sync/tps/extensions/mozmill/install.rdf
@@ -5,59 +5,24 @@
+
mozmill@mozilla.com
- MozMill
- 2.0b1
- Adam Christian
- A testing extension based on the Windmill Testing Framework client source
+ Mozmill
+ 2.0.6
+ UI Automation tool for Mozilla applications
true
+
+ Mozilla Automation and Testing Team
+ Adam Christian
+ Mikeal Rogers
+
-
- {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
- 3.5
- 12.*
+ toolkit@mozilla.org
+ 10.0
+ 31.*
-
-
-
- {3550f703-e582-4d05-9a08-453d09bdfdc6}
- 3.0a1pre
- 9.*
-
-
-
-
-
- {718e30fb-e89b-41dd-9da7-e25a45638b28}
- 0.6a1
- 1.0pre
-
-
-
-
-
- {92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}
- 2.0a1
- 9.*
-
-
-
-
-
- songbird@songbirdnest.com
- 0.3pre
- 1.3.0a
-
-
-
-
- toolkit@mozilla.org
- 1.9.1
- 9.*
-
-
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/controller.js b/services/sync/tps/extensions/mozmill/resource/driver/controller.js
new file mode 100644
index 000000000000..c3539bcb367b
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/controller.js
@@ -0,0 +1,1150 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry",
+ "sleep", "windowMap"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
+
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var mozelement = {}; Cu.import('resource://mozmill/driver/mozelement.js', mozelement);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
+
+// Declare most used utils functions in the controller namespace
+var assert = new assertions.Assert();
+var waitFor = assert.waitFor;
+
+var sleep = utils.sleep;
+
+// For Mozmill 1.5 backward compatibility
+var windowMap = windows.map;
+
+waitForEvents = function () {
+}
+
+waitForEvents.prototype = {
+ /**
+ * Initialize list of events for given node
+ */
+ init: function waitForEvents_init(node, events) {
+ if (node.getNode != undefined)
+ node = node.getNode();
+
+ this.events = events;
+ this.node = node;
+ node.firedEvents = {};
+ this.registry = {};
+
+ for each (var e in events) {
+ var listener = function (event) {
+ this.firedEvents[event.type] = true;
+ }
+
+ this.registry[e] = listener;
+ this.registry[e].result = false;
+ this.node.addEventListener(e, this.registry[e], true);
+ }
+ },
+
+ /**
+ * Wait until all assigned events have been fired
+ */
+ wait: function waitForEvents_wait(timeout, interval) {
+ for (var e in this.registry) {
+ assert.waitFor(function () {
+ return this.node.firedEvents[e] == true;
+ }, "waitForEvents.wait(): Event '" + ex + "' has been fired.", timeout, interval);
+
+ this.node.removeEventListener(e, this.registry[e], true);
+ }
+ }
+}
+
+/**
+ * Class to handle menus and context menus
+ *
+ * @constructor
+ * @param {MozMillController} controller
+ * Mozmill controller of the window under test
+ * @param {string} menuSelector
+ * jQuery like selector string of the element
+ * @param {object} document
+ * Document to use for finding the menu
+ * [optional - default: aController.window.document]
+ */
+var Menu = function (controller, menuSelector, document) {
+ this._controller = controller;
+ this._menu = null;
+
+ document = document || controller.window.document;
+ var node = document.querySelector(menuSelector);
+ if (node) {
+ // We don't unwrap nodes automatically yet (Bug 573185)
+ node = node.wrappedJSObject || node;
+ this._menu = new mozelement.Elem(node);
+ } else {
+ throw new Error("Menu element '" + menuSelector + "' not found.");
+ }
+}
+
+Menu.prototype = {
+
+ /**
+ * Open and populate the menu
+ *
+ * @param {ElemBase} contextElement
+ * Element whose context menu has to be opened
+ * @returns {Menu} The Menu instance
+ */
+ open: function Menu_open(contextElement) {
+ // We have to open the context menu
+ var menu = this._menu.getNode();
+ if ((menu.localName == "popup" || menu.localName == "menupopup") &&
+ contextElement && contextElement.exists()) {
+ this._controller.rightClick(contextElement);
+ assert.waitFor(function () {
+ return menu.state == "open";
+ }, "Context menu has been opened.");
+ }
+
+ // Run through the entire menu and populate with dynamic entries
+ this._buildMenu(menu);
+
+ return this;
+ },
+
+ /**
+ * Close the menu
+ *
+ * @returns {Menu} The Menu instance
+ */
+ close: function Menu_close() {
+ var menu = this._menu.getNode();
+
+ this._controller.keypress(this._menu, "VK_ESCAPE", {});
+ assert.waitFor(function () {
+ return menu.state == "closed";
+ }, "Context menu has been closed.");
+
+ return this;
+ },
+
+ /**
+ * Retrieve the specified menu entry
+ *
+ * @param {string} itemSelector
+ * jQuery like selector string of the menu item
+ * @returns {ElemBase} Menu element
+ * @throws Error If menu element has not been found
+ */
+ getItem: function Menu_getItem(itemSelector) {
+ // Run through the entire menu and populate with dynamic entries
+ this._buildMenu(this._menu.getNode());
+
+ var node = this._menu.getNode().querySelector(itemSelector);
+
+ if (!node) {
+ throw new Error("Menu entry '" + itemSelector + "' not found.");
+ }
+
+ return new mozelement.Elem(node);
+ },
+
+ /**
+ * Click the specified menu entry
+ *
+ * @param {string} itemSelector
+ * jQuery like selector string of the menu item
+ *
+ * @returns {Menu} The Menu instance
+ */
+ click: function Menu_click(itemSelector) {
+ this._controller.click(this.getItem(itemSelector));
+
+ return this;
+ },
+
+ /**
+ * Synthesize a keypress against the menu
+ *
+ * @param {string} key
+ * Key to press
+ * @param {object} modifier
+ * Key modifiers
+ * @see MozMillController#keypress
+ *
+ * @returns {Menu} The Menu instance
+ */
+ keypress: function Menu_keypress(key, modifier) {
+ this._controller.keypress(this._menu, key, modifier);
+
+ return this;
+ },
+
+ /**
+ * Opens the context menu, click the specified entry and
+ * make sure that the menu has been closed.
+ *
+ * @param {string} itemSelector
+ * jQuery like selector string of the element
+ * @param {ElemBase} contextElement
+ * Element whose context menu has to be opened
+ *
+ * @returns {Menu} The Menu instance
+ */
+ select: function Menu_select(itemSelector, contextElement) {
+ this.open(contextElement);
+ this.click(itemSelector);
+ this.close();
+ },
+
+ /**
+ * Recursive function which iterates through all menu elements and
+ * populates the menus with dynamic menu entries.
+ *
+ * @param {node} menu
+ * Top menu node whose elements have to be populated
+ */
+ _buildMenu: function Menu__buildMenu(menu) {
+ var items = menu ? menu.childNodes : null;
+
+ Array.forEach(items, function (item) {
+ // When we have a menu node, fake a click onto it to populate
+ // the sub menu with dynamic entries
+ if (item.tagName == "menu") {
+ var popup = item.querySelector("menupopup");
+
+ if (popup) {
+ var popupEvent = this._controller.window.document.createEvent("MouseEvent");
+ popupEvent.initMouseEvent("popupshowing", true, true,
+ this._controller.window, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null);
+ popup.dispatchEvent(popupEvent);
+
+ this._buildMenu(popup);
+ }
+ }
+ }, this);
+ }
+};
+
+var MozMillController = function (window) {
+ this.window = window;
+
+ this.mozmillModule = {};
+ Cu.import('resource://mozmill/driver/mozmill.js', this.mozmillModule);
+
+ var self = this;
+ assert.waitFor(function () {
+ return window != null && self.isLoaded();
+ }, "controller(): Window has been initialized.");
+
+ // Ensure to focus the window which will move it virtually into the foreground
+ // when focusmanager.testmode is set enabled.
+ this.window.focus();
+
+ var windowType = window.document.documentElement.getAttribute('windowtype');
+ if (controllerAdditions[windowType] != undefined ) {
+ this.prototype = new utils.Copy(this.prototype);
+ controllerAdditions[windowType](this);
+ this.windowtype = windowType;
+ }
+}
+
+/**
+ * Returns the global browser object of the window
+ *
+ * @returns {Object} The browser object
+ */
+MozMillController.prototype.__defineGetter__("browserObject", function () {
+ return utils.getBrowserObject(this.window);
+});
+
+// constructs a MozMillElement from the controller's window
+MozMillController.prototype.__defineGetter__("rootElement", function () {
+ if (this._rootElement == undefined) {
+ let docElement = this.window.document.documentElement;
+ this._rootElement = new mozelement.MozMillElement("Elem", docElement);
+ }
+
+ return this._rootElement;
+});
+
+MozMillController.prototype.sleep = utils.sleep;
+MozMillController.prototype.waitFor = assert.waitFor;
+
+// Open the specified url in the current tab
+MozMillController.prototype.open = function (url) {
+ switch (this.mozmillModule.Application) {
+ case "Firefox":
+ case "MetroFirefox":
+ // Stop a running page load to not overlap requests
+ if (this.browserObject.selectedBrowser) {
+ this.browserObject.selectedBrowser.stop();
+ }
+
+ this.browserObject.loadURI(url);
+ break;
+
+ default:
+ throw new Error("MozMillController.open not supported.");
+ }
+
+ broker.pass({'function':'Controller.open()'});
+}
+
+/**
+ * Take a screenshot of specified node
+ *
+ * @param {Element} node
+ * The window or DOM element to capture
+ * @param {String} name
+ * The name of the screenshot used in reporting and as filename
+ * @param {Boolean} save
+ * If true saves the screenshot as 'name.jpg' in tempdir,
+ * otherwise returns a dataURL
+ * @param {Element[]} highlights
+ * A list of DOM elements to highlight by drawing a red rectangle around them
+ *
+ * @returns {Object} Object which contains properties like filename, dataURL,
+ * name and timestamp of the screenshot
+ */
+MozMillController.prototype.screenshot = function (node, name, save, highlights) {
+ if (!node) {
+ throw new Error("node is undefined");
+ }
+
+ // Unwrap the node and highlights
+ if ("getNode" in node) {
+ node = node.getNode();
+ }
+
+ if (highlights) {
+ for (var i = 0; i < highlights.length; ++i) {
+ if ("getNode" in highlights[i]) {
+ highlights[i] = highlights[i].getNode();
+ }
+ }
+ }
+
+ // If save is false, a dataURL is used
+ // Include both in the report anyway to avoid confusion and make the report easier to parse
+ var screenshot = {"filename": undefined,
+ "dataURL": utils.takeScreenshot(node, highlights),
+ "name": name,
+ "timestamp": new Date().toLocaleString()};
+
+ if (!save) {
+ return screenshot;
+ }
+
+ // Save the screenshot to disk
+
+ let {filename, failure} = utils.saveDataURL(screenshot.dataURL, name);
+ screenshot.filename = filename;
+ screenshot.failure = failure;
+
+ if (failure) {
+ broker.log({'function': 'controller.screenshot()',
+ 'message': 'Error writing to file: ' + screenshot.filename});
+ } else {
+ // Send the screenshot object to python over jsbridge
+ broker.sendMessage("screenshot", screenshot);
+ broker.pass({'function': 'controller.screenshot()'});
+ }
+
+ return screenshot;
+}
+
+/**
+ * Checks if the specified window has been loaded
+ *
+ * @param {DOMWindow} [aWindow=this.window] Window object to check for loaded state
+ */
+MozMillController.prototype.isLoaded = function (aWindow) {
+ var win = aWindow || this.window;
+
+ return windows.map.getValue(utils.getWindowId(win), "loaded") || false;
+};
+
+MozMillController.prototype.__defineGetter__("waitForEvents", function () {
+ if (this._waitForEvents == undefined) {
+ this._waitForEvents = new waitForEvents();
+ }
+
+ return this._waitForEvents;
+});
+
+/**
+ * Wrapper function to create a new instance of a menu
+ * @see Menu
+ */
+MozMillController.prototype.getMenu = function (menuSelector, document) {
+ return new Menu(this, menuSelector, document);
+};
+
+MozMillController.prototype.__defineGetter__("mainMenu", function () {
+ return this.getMenu("menubar");
+});
+
+MozMillController.prototype.__defineGetter__("menus", function () {
+ logDeprecated('controller.menus', 'Use controller.mainMenu instead');
+});
+
+MozMillController.prototype.waitForImage = function (aElement, timeout, interval) {
+ this.waitFor(function () {
+ return aElement.getNode().complete == true;
+ }, "timeout exceeded for waitForImage " + aElement.getInfo(), timeout, interval);
+
+ broker.pass({'function':'Controller.waitForImage()'});
+}
+
+MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) {
+ if (restart && resetProfile) {
+ throw new Error("You can't have a user-restart and reset the profile; there is a race condition");
+ }
+
+ let shutdownObj = {
+ 'user': true,
+ 'restart': Boolean(restart),
+ 'next': next,
+ 'resetProfile': Boolean(resetProfile),
+ 'timeout': timeout
+ };
+
+ broker.sendMessage('shutdown', shutdownObj);
+}
+
+/**
+ * Restart the application
+ *
+ * @param {string} aNext
+ * Name of the next test function to run after restart
+ * @param {boolean} [aFlags=undefined]
+ * Additional flags how to handle the shutdown or restart. The attributes
+ * eRestarti386 (0x20) and eRestartx86_64 (0x30) have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+MozMillController.prototype.restartApplication = function (aNext, aFlags) {
+ var flags = Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart;
+
+ if (aFlags) {
+ flags |= aFlags;
+ }
+
+ broker.sendMessage('shutdown', {'user': false,
+ 'restart': true,
+ 'flags': flags,
+ 'next': aNext,
+ 'timeout': 0 });
+
+ // We have to ensure to stop the test from continuing until the application is
+ // shutting down. The only way to do that is by throwing an exception.
+ throw new errors.ApplicationQuitError();
+}
+
+/**
+ * Stop the application
+ *
+ * @param {boolean} [aResetProfile=false]
+ * Whether to reset the profile during restart
+ * @param {boolean} [aFlags=undefined]
+ * Additional flags how to handle the shutdown or restart. The attributes
+ * eRestarti386 and eRestartx86_64 have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+MozMillController.prototype.stopApplication = function (aResetProfile, aFlags) {
+ var flags = Ci.nsIAppStartup.eAttemptQuit;
+
+ if (aFlags) {
+ flags |= aFlags;
+ }
+
+ broker.sendMessage('shutdown', {'user': false,
+ 'restart': false,
+ 'flags': flags,
+ 'resetProfile': aResetProfile,
+ 'timeout': 0 });
+
+ // We have to ensure to stop the test from continuing until the application is
+ // shutting down. The only way to do that is by throwing an exception.
+ throw new errors.ApplicationQuitError();
+}
+
+//Browser navigation functions
+MozMillController.prototype.goBack = function () {
+ this.window.content.history.back();
+ broker.pass({'function':'Controller.goBack()'});
+
+ return true;
+}
+
+MozMillController.prototype.goForward = function () {
+ this.window.content.history.forward();
+ broker.pass({'function':'Controller.goForward()'});
+
+ return true;
+}
+
+MozMillController.prototype.refresh = function () {
+ this.window.content.location.reload(true);
+ broker.pass({'function':'Controller.refresh()'});
+
+ return true;
+}
+
+function logDeprecated(funcName, message) {
+ broker.log({'function': funcName + '() - DEPRECATED',
+ 'message': funcName + '() is deprecated. ' + message});
+}
+
+function logDeprecatedAssert(funcName) {
+ logDeprecated('controller.' + funcName,
+ '. Use the generic `assertion` module instead.');
+}
+
+MozMillController.prototype.assertText = function (el, text) {
+ logDeprecatedAssert("assertText");
+
+ var n = el.getNode();
+
+ if (n && n.innerHTML == text) {
+ broker.pass({'function': 'Controller.assertText()'});
+ } else {
+ throw new Error("could not validate element " + el.getInfo() +
+ " with text "+ text);
+ }
+
+ return true;
+};
+
+/**
+ * Assert that a specified node exists
+ */
+MozMillController.prototype.assertNode = function (el) {
+ logDeprecatedAssert("assertNode");
+
+ //this.window.focus();
+ var element = el.getNode();
+ if (!element) {
+ throw new Error("could not find element " + el.getInfo());
+ }
+
+ broker.pass({'function': 'Controller.assertNode()'});
+ return true;
+};
+
+/**
+ * Assert that a specified node doesn't exist
+ */
+MozMillController.prototype.assertNodeNotExist = function (el) {
+ logDeprecatedAssert("assertNodeNotExist");
+
+ try {
+ var element = el.getNode();
+ } catch (e) {
+ broker.pass({'function': 'Controller.assertNodeNotExist()'});
+ }
+
+ if (element) {
+ throw new Error("Unexpectedly found element " + el.getInfo());
+ } else {
+ broker.pass({'function':'Controller.assertNodeNotExist()'});
+ }
+
+ return true;
+};
+
+/**
+ * Assert that a form element contains the expected value
+ */
+MozMillController.prototype.assertValue = function (el, value) {
+ logDeprecatedAssert("assertValue");
+
+ var n = el.getNode();
+
+ if (n && n.value == value) {
+ broker.pass({'function': 'Controller.assertValue()'});
+ } else {
+ throw new Error("could not validate element " + el.getInfo() +
+ " with value " + value);
+ }
+
+ return false;
+};
+
+/**
+ * Check if the callback function evaluates to true
+ */
+MozMillController.prototype.assert = function (callback, message, thisObject) {
+ logDeprecatedAssert("assert");
+
+ utils.assert(callback, message, thisObject);
+ broker.pass({'function': ": controller.assert('" + callback + "')"});
+
+ return true;
+}
+
+/**
+ * Assert that a provided value is selected in a select element
+ */
+MozMillController.prototype.assertSelected = function (el, value) {
+ logDeprecatedAssert("assertSelected");
+
+ var n = el.getNode();
+ var validator = value;
+
+ if (n && n.options[n.selectedIndex].value == validator) {
+ broker.pass({'function':'Controller.assertSelected()'});
+ } else {
+ throw new Error("could not assert value for element " + el.getInfo() +
+ " with value " + value);
+ }
+
+ return true;
+};
+
+/**
+ * Assert that a provided checkbox is checked
+ */
+MozMillController.prototype.assertChecked = function (el) {
+ logDeprecatedAssert("assertChecked");
+
+ var element = el.getNode();
+
+ if (element && element.checked == true) {
+ broker.pass({'function':'Controller.assertChecked()'});
+ } else {
+ throw new Error("assert failed for checked element " + el.getInfo());
+ }
+
+ return true;
+};
+
+/**
+ * Assert that a provided checkbox is not checked
+ */
+MozMillController.prototype.assertNotChecked = function (el) {
+ logDeprecatedAssert("assertNotChecked");
+
+ var element = el.getNode();
+
+ if (!element) {
+ throw new Error("Could not find element" + el.getInfo());
+ }
+
+ if (!element.hasAttribute("checked") || element.checked != true) {
+ broker.pass({'function': 'Controller.assertNotChecked()'});
+ } else {
+ throw new Error("assert failed for not checked element " + el.getInfo());
+ }
+
+ return true;
+};
+
+/**
+ * Assert that an element's javascript property exists or has a particular value
+ *
+ * if val is undefined, will return true if the property exists.
+ * if val is specified, will return true if the property exists and has the correct value
+ */
+MozMillController.prototype.assertJSProperty = function (el, attrib, val) {
+ logDeprecatedAssert("assertJSProperty");
+
+ var element = el.getNode();
+
+ if (!element){
+ throw new Error("could not find element " + el.getInfo());
+ }
+
+ var value = element[attrib];
+ var res = (value !== undefined && (val === undefined ? true :
+ String(value) == String(val)));
+ if (res) {
+ broker.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val});
+ } else {
+ throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " +
+ (val === undefined ? "property '" + attrib +
+ "' doesn't exist" : val + " == " + value));
+ }
+
+ return true;
+};
+
+/**
+ * Assert that an element's javascript property doesn't exist or doesn't have a particular value
+ *
+ * if val is undefined, will return true if the property doesn't exist.
+ * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
+ */
+MozMillController.prototype.assertNotJSProperty = function (el, attrib, val) {
+ logDeprecatedAssert("assertNotJSProperty");
+
+ var element = el.getNode();
+
+ if (!element){
+ throw new Error("could not find element " + el.getInfo());
+ }
+
+ var value = element[attrib];
+ var res = (val === undefined ? value === undefined : String(value) != String(val));
+ if (res) {
+ broker.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val});
+ } else {
+ throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " +
+ (val === undefined ? "property '" + attrib +
+ "' exists" : val + " != " + value));
+ }
+
+ return true;
+};
+
+/**
+ * Assert that an element's dom property exists or has a particular value
+ *
+ * if val is undefined, will return true if the property exists.
+ * if val is specified, will return true if the property exists and has the correct value
+ */
+MozMillController.prototype.assertDOMProperty = function (el, attrib, val) {
+ logDeprecatedAssert("assertDOMProperty");
+
+ var element = el.getNode();
+
+ if (!element){
+ throw new Error("could not find element " + el.getInfo());
+ }
+
+ var value, res = element.hasAttribute(attrib);
+ if (res && val !== undefined) {
+ value = element.getAttribute(attrib);
+ res = (String(value) == String(val));
+ }
+
+ if (res) {
+ broker.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val});
+ } else {
+ throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " +
+ (val === undefined ? "property '" + attrib +
+ "' doesn't exist" : val + " == " + value));
+ }
+
+ return true;
+};
+
+/**
+ * Assert that an element's dom property doesn't exist or doesn't have a particular value
+ *
+ * if val is undefined, will return true if the property doesn't exist.
+ * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
+ */
+MozMillController.prototype.assertNotDOMProperty = function (el, attrib, val) {
+ logDeprecatedAssert("assertNotDOMProperty");
+
+ var element = el.getNode();
+
+ if (!element) {
+ throw new Error("could not find element " + el.getInfo());
+ }
+
+ var value, res = element.hasAttribute(attrib);
+ if (res && val !== undefined) {
+ value = element.getAttribute(attrib);
+ res = (String(value) == String(val));
+ }
+
+ if (!res) {
+ broker.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val});
+ } else {
+ throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " +
+ (val == undefined ? "property '" + attrib +
+ "' exists" : val + " == " + value));
+ }
+
+ return true;
+};
+
+/**
+ * Assert that a specified image has actually loaded. The Safari workaround results
+ * in additional requests for broken images (in Safari only) but works reliably
+ */
+MozMillController.prototype.assertImageLoaded = function (el) {
+ logDeprecatedAssert("assertImageLoaded");
+
+ var img = el.getNode();
+
+ if (!img || img.tagName != 'IMG') {
+ throw new Error('Controller.assertImageLoaded() failed.')
+ return false;
+ }
+
+ var comp = img.complete;
+ var ret = null; // Return value
+
+ // Workaround for Safari -- it only supports the
+ // complete attrib on script-created images
+ if (typeof comp == 'undefined') {
+ test = new Image();
+ // If the original image was successfully loaded,
+ // src for new one should be pulled from cache
+ test.src = img.src;
+ comp = test.complete;
+ }
+
+ // Check the complete attrib. Note the strict
+ // equality check -- we don't want undefined, null, etc.
+ // --------------------------
+ if (comp === false) {
+ // False -- Img failed to load in IE/Safari, or is
+ // still trying to load in FF
+ ret = false;
+ } else if (comp === true && img.naturalWidth == 0) {
+ // True, but image has no size -- image failed to
+ // load in FF
+ ret = false;
+ } else {
+ // Otherwise all we can do is assume everything's
+ // hunky-dory
+ ret = true;
+ }
+
+ if (ret) {
+ broker.pass({'function':'Controller.assertImageLoaded'});
+ } else {
+ throw new Error('Controller.assertImageLoaded() failed.')
+ }
+
+ return true;
+};
+
+/**
+ * Drag one element to the top x,y coords of another specified element
+ */
+MozMillController.prototype.mouseMove = function (doc, start, dest) {
+ // if one of these elements couldn't be looked up
+ if (typeof start != 'object'){
+ throw new Error("received bad coordinates");
+ }
+
+ if (typeof dest != 'object'){
+ throw new Error("received bad coordinates");
+ }
+
+ var triggerMouseEvent = function (element, clientX, clientY) {
+ clientX = clientX ? clientX: 0;
+ clientY = clientY ? clientY: 0;
+
+ // make the mouse understand where it is on the screen
+ var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0;
+ var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0;
+
+ var evt = element.ownerDocument.createEvent('MouseEvents');
+ if (evt.initMouseEvent) {
+ evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView,
+ 1, screenX, screenY, clientX, clientY);
+ } else {
+ evt.initEvent('mousemove', true, true);
+ }
+
+ element.dispatchEvent(evt);
+ };
+
+ // Do the initial move to the drag element position
+ triggerMouseEvent(doc.body, start[0], start[1]);
+ triggerMouseEvent(doc.body, dest[0], dest[1]);
+
+ broker.pass({'function':'Controller.mouseMove()'});
+ return true;
+}
+
+/**
+ * Drag an element to the specified offset on another element, firing mouse and
+ * drag events. Adapted from ChromeUtils.js synthesizeDrop()
+ *
+ * @deprecated Use the MozMillElement object
+ *
+ * @param {MozElement} aSrc
+ * Source element to be dragged
+ * @param {MozElement} aDest
+ * Destination element over which the drop occurs
+ * @param {Number} [aOffsetX=element.width/2]
+ * Relative x offset for dropping on the aDest element
+ * @param {Number} [aOffsetY=element.height/2]
+ * Relative y offset for dropping on the aDest element
+ * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
+ * Custom source Window to be used.
+ * @param {String} [aDropEffect="move"]
+ * Effect used for the drop event
+ * @param {Object[]} [aDragData]
+ * An array holding custom drag data to be used during the drag event
+ * Format: [{ type: "text/plain", "Text to drag"}, ...]
+ *
+ * @returns {String} the captured dropEffect
+ */
+MozMillController.prototype.dragToElement = function (aSrc, aDest, aOffsetX,
+ aOffsetY, aSourceWindow,
+ aDropEffect, aDragData) {
+ logDeprecated("controller.dragToElement", "Use the MozMillElement object.");
+ return aSrc.dragToElement(aDest, aOffsetX, aOffsetY, aSourceWindow, null,
+ aDropEffect, aDragData);
+};
+
+function Tabs(controller) {
+ this.controller = controller;
+}
+
+Tabs.prototype.getTab = function (index) {
+ return this.controller.browserObject.browsers[index].contentDocument;
+}
+
+Tabs.prototype.__defineGetter__("activeTab", function () {
+ return this.controller.browserObject.selectedBrowser.contentDocument;
+});
+
+Tabs.prototype.selectTab = function (index) {
+ // GO in to tab manager and grab the tab by index and call focus.
+}
+
+Tabs.prototype.findWindow = function (doc) {
+ for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) {
+ if (this.controller.window.frames[i].document == doc) {
+ return this.controller.window.frames[i];
+ }
+ }
+
+ throw new Error("Cannot find window for document. Doc title == " + doc.title);
+}
+
+Tabs.prototype.getTabWindow = function (index) {
+ return this.findWindow(this.getTab(index));
+}
+
+Tabs.prototype.__defineGetter__("activeTabWindow", function () {
+ return this.findWindow(this.activeTab);
+});
+
+Tabs.prototype.__defineGetter__("length", function () {
+ return this.controller.browserObject.browsers.length;
+});
+
+Tabs.prototype.__defineGetter__("activeTabIndex", function () {
+ var browser = this.controller.browserObject;
+
+ switch(this.controller.mozmillModule.Application) {
+ case "MetroFirefox":
+ return browser.tabs.indexOf(browser.selectedTab);
+ case "Firefox":
+ default:
+ return browser.tabContainer.selectedIndex;
+ }
+});
+
+Tabs.prototype.selectTabIndex = function (aIndex) {
+ var browser = this.controller.browserObject;
+
+ switch(this.controller.mozmillModule.Application) {
+ case "MetroFirefox":
+ browser.selectedTab = browser.tabs[aIndex];
+ break;
+ case "Firefox":
+ default:
+ browser.selectTabAtIndex(aIndex);
+ }
+}
+
+function browserAdditions (controller) {
+ controller.tabs = new Tabs(controller);
+
+ controller.waitForPageLoad = function (aDocument, aTimeout, aInterval) {
+ var timeout = aTimeout || 30000;
+ var win = null;
+ var timed_out = false;
+
+ // If a user tries to do waitForPageLoad(2000), this will assign the
+ // interval the first arg which is most likely what they were expecting
+ if (typeof(aDocument) == "number"){
+ timeout = aDocument;
+ }
+
+ // If we have a real document use its default view
+ if (aDocument && (typeof(aDocument) === "object") &&
+ "defaultView" in aDocument)
+ win = aDocument.defaultView;
+
+ // If no document has been specified, fallback to the default view of the
+ // currently selected tab browser
+ win = win || this.browserObject.selectedBrowser.contentWindow;
+
+ // Wait until the content in the tab has been loaded
+ try {
+ this.waitFor(function () {
+ return windows.map.hasPageLoaded(utils.getWindowId(win));
+ }, "Timeout", timeout, aInterval);
+ }
+ catch (ex if ex instanceof errors.TimeoutError) {
+ timed_out = true;
+ }
+ finally {
+ state = 'URI=' + win.document.location.href +
+ ', readyState=' + win.document.readyState;
+ message = "controller.waitForPageLoad(" + state + ")";
+
+ if (timed_out) {
+ throw new errors.AssertionError(message);
+ }
+
+ broker.pass({'function': message});
+ }
+ }
+}
+
+var controllerAdditions = {
+ 'navigator:browser' :browserAdditions
+};
+
+/**
+ * DEPRECATION WARNING
+ *
+ * The following methods have all been DEPRECATED as of Mozmill 2.0
+ */
+MozMillController.prototype.assertProperty = function (el, attrib, val) {
+ logDeprecatedAssert("assertProperty");
+
+ return this.assertJSProperty(el, attrib, val);
+};
+
+MozMillController.prototype.assertPropertyNotExist = function (el, attrib) {
+ logDeprecatedAssert("assertPropertyNotExist");
+ return this.assertNotJSProperty(el, attrib);
+};
+
+/**
+ * DEPRECATION WARNING
+ *
+ * The following methods have all been DEPRECATED as of Mozmill 2.0
+ * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object)
+ */
+MozMillController.prototype.select = function (aElement, index, option, value) {
+ logDeprecated("controller.select", "Use the MozMillElement object.");
+
+ return aElement.select(index, option, value);
+};
+
+MozMillController.prototype.keypress = function (aElement, aKey, aModifiers, aExpectedEvent) {
+ logDeprecated("controller.keypress", "Use the MozMillElement object.");
+
+ if (!aElement) {
+ aElement = new mozelement.MozMillElement("Elem", this.window);
+ }
+
+ return aElement.keypress(aKey, aModifiers, aExpectedEvent);
+}
+
+MozMillController.prototype.type = function (aElement, aText, aExpectedEvent) {
+ logDeprecated("controller.type", "Use the MozMillElement object.");
+
+ if (!aElement) {
+ aElement = new mozelement.MozMillElement("Elem", this.window);
+ }
+
+ var that = this;
+ var retval = true;
+ Array.forEach(aText, function (letter) {
+ if (!that.keypress(aElement, letter, {}, aExpectedEvent)) {
+ retval = false; }
+ });
+
+ return retval;
+}
+
+MozMillController.prototype.mouseEvent = function (aElement, aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
+ logDeprecated("controller.mouseEvent", "Use the MozMillElement object.");
+
+ return aElement.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent);
+}
+
+MozMillController.prototype.click = function (aElement, left, top, expectedEvent) {
+ logDeprecated("controller.click", "Use the MozMillElement object.");
+
+ return aElement.click(left, top, expectedEvent);
+}
+
+MozMillController.prototype.doubleClick = function (aElement, left, top, expectedEvent) {
+ logDeprecated("controller.doubleClick", "Use the MozMillElement object.");
+
+ return aElement.doubleClick(left, top, expectedEvent);
+}
+
+MozMillController.prototype.mouseDown = function (aElement, button, left, top, expectedEvent) {
+ logDeprecated("controller.mouseDown", "Use the MozMillElement object.");
+
+ return aElement.mouseDown(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseOut = function (aElement, button, left, top, expectedEvent) {
+ logDeprecated("controller.mouseOut", "Use the MozMillElement object.");
+
+ return aElement.mouseOut(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseOver = function (aElement, button, left, top, expectedEvent) {
+ logDeprecated("controller.mouseOver", "Use the MozMillElement object.");
+
+ return aElement.mouseOver(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.mouseUp = function (aElement, button, left, top, expectedEvent) {
+ logDeprecated("controller.mouseUp", "Use the MozMillElement object.");
+
+ return aElement.mouseUp(button, left, top, expectedEvent);
+};
+
+MozMillController.prototype.middleClick = function (aElement, left, top, expectedEvent) {
+ logDeprecated("controller.middleClick", "Use the MozMillElement object.");
+
+ return aElement.middleClick(aElement, left, top, expectedEvent);
+}
+
+MozMillController.prototype.rightClick = function (aElement, left, top, expectedEvent) {
+ logDeprecated("controller.rightClick", "Use the MozMillElement object.");
+
+ return aElement.rightClick(left, top, expectedEvent);
+}
+
+MozMillController.prototype.check = function (aElement, state) {
+ logDeprecated("controller.check", "Use the MozMillElement object.");
+
+ return aElement.check(state);
+}
+
+MozMillController.prototype.radio = function (aElement) {
+ logDeprecated("controller.radio", "Use the MozMillElement object.");
+
+ return aElement.select();
+}
+
+MozMillController.prototype.waitThenClick = function (aElement, timeout, interval) {
+ logDeprecated("controller.waitThenClick", "Use the MozMillElement object.");
+
+ return aElement.waitThenClick(timeout, interval);
+}
+
+MozMillController.prototype.waitForElement = function (aElement, timeout, interval) {
+ logDeprecated("controller.waitForElement", "Use the MozMillElement object.");
+
+ return aElement.waitForElement(timeout, interval);
+}
+
+MozMillController.prototype.waitForElementNotPresent = function (aElement, timeout, interval) {
+ logDeprecated("controller.waitForElementNotPresent", "Use the MozMillElement object.");
+
+ return aElement.waitForElementNotPresent(timeout, interval);
+}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/elementslib.js b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
similarity index 71%
rename from services/sync/tps/extensions/mozmill/resource/modules/elementslib.js
rename to services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
index e59429f06193..f08cf42f3774 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/elementslib.js
+++ b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
@@ -1,23 +1,30 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
-var EXPORTED_SYMBOLS = ["Elem", "ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
+var EXPORTED_SYMBOLS = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
"Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
];
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
-var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
-var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
-var countQuotes = function(str){
+Cu.import("resource://gre/modules/Services.jsm");
+
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
+var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
+var json2 = {}; Cu.import('resource://mozmill/stdlib/json2.js', json2);
+var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
+var dom = {}; Cu.import('resource://mozmill/stdlib/dom.js', dom);
+var objects = {}; Cu.import('resource://mozmill/stdlib/objects.js', objects);
+
+var countQuotes = function (str) {
var count = 0;
var i = 0;
- while(i < str.length) {
+
+ while (i < str.length) {
i = str.indexOf('"', i);
if (i != -1) {
count++;
@@ -26,6 +33,7 @@ var countQuotes = function(str){
break;
}
}
+
return count;
};
@@ -53,10 +61,12 @@ var smartSplit = function (str) {
var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
var ret = []
var match = re.exec(str);
+
while (match != null) {
ret.push(match[0].replace(/^\//, ""));
match = re.exec(str);
}
+
return ret;
};
@@ -67,9 +77,12 @@ var smartSplit = function (str) {
* if no document is provided
*/
function defaultDocuments() {
- var windowManager = Components.classes['@mozilla.org/appshell/window-mediator;1'].getService(Components.interfaces.nsIWindowMediator);
- win = windowManager.getMostRecentWindow("navigator:browser");
- return [win.gBrowser.selectedBrowser.contentDocument, win.document];
+ var win = Services.wm.getMostRecentWindow("navigator:browser");
+
+ return [
+ win.document,
+ utils.getBrowserObject(win).selectedBrowser.contentWindow.document
+ ];
};
/**
@@ -84,30 +97,37 @@ function nodeSearch(doc, func, string) {
} else {
var documents = defaultDocuments();
}
+
var e = null;
var element = null;
+
//inline function to recursively find the element in the DOM, cross frame.
- var search = function(win, func, string) {
- if (win == null)
+ var search = function (win, func, string) {
+ if (win == null) {
return;
+ }
//do the lookup in the current window
element = func.call(win, string);
if (!element || (element.length == 0)) {
var frames = win.frames;
- for (var i=0; i < frames.length; i++) {
+ for (var i = 0; i < frames.length; i++) {
search(frames[i], func, string);
}
+ } else {
+ e = element;
}
- else { e = element; }
};
for (var i = 0; i < documents.length; ++i) {
var win = documents[i].defaultView;
search(win, func, string);
- if (e) break;
+ if (e) {
+ break;
+ }
}
+
return e;
};
@@ -120,11 +140,15 @@ function Selector(_document, selector, index) {
if (selector == undefined) {
throw new Error('Selector constructor did not recieve enough arguments.');
}
+
this.selector = selector;
+
this.getNodeForDocument = function (s) {
return this.document.querySelectorAll(s);
};
+
var nodes = nodeSearch(_document, this.getNodeForDocument, this.selector);
+
return nodes ? nodes[index || 0] : null;
};
@@ -137,9 +161,11 @@ function ID(_document, nodeID) {
if (nodeID == undefined) {
throw new Error('ID constructor did not recieve enough arguments.');
}
+
this.getNodeForDocument = function (nodeID) {
return this.document.getElementById(nodeID);
};
+
return nodeSearch(_document, this.getNodeForDocument, nodeID);
};
@@ -154,40 +180,48 @@ function Link(_document, linkName) {
}
this.getNodeForDocument = function (linkName) {
- var getText = function(el){
+ var getText = function (el) {
var text = "";
- if (el.nodeType == 3){ //textNode
- if (el.data != undefined){
+
+ if (el.nodeType == 3) { //textNode
+ if (el.data != undefined) {
text = el.data;
} else {
text = el.innerHTML;
}
- text = text.replace(/n|r|t/g, " ");
+
+ text = text.replace(/n|r|t/g, " ");
}
- if (el.nodeType == 1){ //elementNode
+ else if (el.nodeType == 1) { //elementNode
for (var i = 0; i < el.childNodes.length; i++) {
var child = el.childNodes.item(i);
text += getText(child);
}
- if (el.tagName == "P" || el.tagName == "BR" || el.tagName == "HR" || el.tagName == "DIV") {
- text += "n";
+
+ if (el.tagName == "P" || el.tagName == "BR" ||
+ el.tagName == "HR" || el.tagName == "DIV") {
+ text += "\n";
}
}
+
return text;
};
//sometimes the windows won't have this function
try {
- var links = this.document.getElementsByTagName('a'); }
- catch(err){ // ADD LOG LINE mresults.write('Error: '+ err, 'lightred');
+ var links = this.document.getElementsByTagName('a');
+ } catch (e) {
+ // ADD LOG LINE mresults.write('Error: '+ e, 'lightred');
}
+
for (var i = 0; i < links.length; i++) {
var el = links[i];
//if (getText(el).indexOf(this.linkName) != -1) {
- if (el.innerHTML.indexOf(linkName) != -1){
+ if (el.innerHTML.indexOf(linkName) != -1) {
return el;
}
}
+
return null;
};
@@ -214,14 +248,20 @@ function XPath(_document, expr) {
} else {
xpe = new this.document.defaultView.XPathEvaluator();
}
- var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement : aNode.ownerDocument.documentElement);
+
+ var nsResolver = xpe.createNSResolver(aNode.ownerDocument == null ? aNode.documentElement
+ : aNode.ownerDocument.documentElement);
var result = xpe.evaluate(aExpr, aNode, nsResolver, 0, null);
var found = [];
var res;
- while (res = result.iterateNext())
+
+ while (res = result.iterateNext()) {
found.push(res);
+ }
+
return found[0];
};
+
return nodeSearch(_document, this.getNodeForDocument, expr);
};
@@ -234,14 +274,19 @@ function Name(_document, nName) {
if (nName == undefined) {
throw new Error('Name constructor did not recieve enough arguments.');
}
+
this.getNodeForDocument = function (s) {
try{
var els = this.document.getElementsByName(s);
- if (els.length > 0) { return els[0]; }
+ if (els.length > 0) {
+ return els[0];
+ }
+ } catch (e) {
}
- catch(err){};
+
return null;
};
+
return nodeSearch(_document, this.getNodeForDocument, nName);
};
@@ -249,110 +294,138 @@ function Name(_document, nName) {
var _returnResult = function (results) {
if (results.length == 0) {
return null
- } else if (results.length == 1) {
+ }
+ else if (results.length == 1) {
return results[0];
} else {
return results;
}
}
+
var _forChildren = function (element, name, value) {
var results = [];
var nodes = [e for each (e in element.childNodes) if (e)]
+
for (var i in nodes) {
var n = nodes[i];
if (n[name] == value) {
results.push(n);
}
}
+
return results;
}
+
var _forAnonChildren = function (_document, element, name, value) {
var results = [];
var nodes = [e for each (e in _document.getAnoymousNodes(element)) if (e)];
+
for (var i in nodes ) {
var n = nodes[i];
if (n[name] == value) {
results.push(n);
}
}
+
return results;
}
+
var _byID = function (_document, parent, value) {
return _returnResult(_forChildren(parent, 'id', value));
}
+
var _byName = function (_document, parent, value) {
return _returnResult(_forChildren(parent, 'tagName', value));
}
+
var _byAttrib = function (parent, attributes) {
var results = [];
-
var nodes = parent.childNodes;
+
for (var i in nodes) {
var n = nodes[i];
requirementPass = 0;
requirementLength = 0;
+
for (var a in attributes) {
requirementLength++;
try {
if (n.getAttribute(a) == attributes[a]) {
requirementPass++;
}
- } catch (err) {
+ } catch (e) {
// Workaround any bugs in custom attribute crap in XUL elements
}
}
+
if (requirementPass == requirementLength) {
results.push(n);
}
}
+
return _returnResult(results)
}
+
var _byAnonAttrib = function (_document, parent, attributes) {
var results = [];
if (objects.getLength(attributes) == 1) {
- for (var i in attributes) {var k = i; var v = attributes[i]; }
- var result = _document.getAnonymousElementByAttribute(parent, k, v)
+ for (var i in attributes) {
+ var k = i;
+ var v = attributes[i];
+ }
+
+ var result = _document.getAnonymousElementByAttribute(parent, k, v);
if (result) {
return result;
-
}
}
+
var nodes = [n for each (n in _document.getAnonymousNodes(parent)) if (n.getAttribute)];
+
function resultsForNodes (nodes) {
for (var i in nodes) {
var n = nodes[i];
requirementPass = 0;
requirementLength = 0;
+
for (var a in attributes) {
requirementLength++;
if (n.getAttribute(a) == attributes[a]) {
requirementPass++;
}
}
+
if (requirementPass == requirementLength) {
results.push(n);
}
}
}
- resultsForNodes(nodes)
+
+ resultsForNodes(nodes);
if (results.length == 0) {
resultsForNodes([n for each (n in parent.childNodes) if (n != undefined && n.getAttribute)])
}
+
return _returnResult(results)
}
+
var _byIndex = function (_document, parent, i) {
if (parent instanceof Array) {
return parent[i];
}
+
return parent.childNodes[i];
}
+
var _anonByName = function (_document, parent, value) {
return _returnResult(_forAnonChildren(_document, parent, 'tagName', value));
}
+
var _anonByAttrib = function (_document, parent, value) {
return _byAnonAttrib(_document, parent, value);
}
+
var _anonByIndex = function (_document, parent, i) {
return _document.getAnonymousNodes(parent)[i];
}
@@ -362,18 +435,32 @@ var _anonByIndex = function (_document, parent, i) {
*
* Finds an element by Lookup expression
*/
-function Lookup (_document, expression) {
+function Lookup(_document, expression) {
if (expression == undefined) {
throw new Error('Lookup constructor did not recieve enough arguments.');
}
var expSplit = [e for each (e in smartSplit(expression) ) if (e != '')];
- expSplit.unshift(_document)
+ expSplit.unshift(_document);
+
var nCases = {'id':_byID, 'name':_byName, 'attrib':_byAttrib, 'index':_byIndex};
var aCases = {'name':_anonByName, 'attrib':_anonByAttrib, 'index':_anonByIndex};
+ /**
+ * Reduces the lookup expression
+ * @param {Object} parentNode
+ * Parent node (previousValue of the formerly executed reduce callback)
+ * @param {String} exp
+ * Lookup expression for the parents child node
+ *
+ * @returns {Object} Node found by the given expression
+ */
+ var reduceLookup = function (parentNode, exp) {
+ // Abort in case the parent node was not found
+ if (!parentNode) {
+ return false;
+ }
- var reduceLookup = function (parent, exp) {
// Handle case where only index is provided
var cases = nCases;
@@ -381,21 +468,27 @@ function Lookup (_document, expression) {
if (withs.endsWith(exp, ']')) {
var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
}
+
// Handle anon
if (withs.startsWith(exp, 'anon')) {
- var exp = strings.vslice(exp, '(', ')');
- var cases = aCases;
+ exp = strings.vslice(exp, '(', ')');
+ cases = aCases;
}
+
if (withs.startsWith(exp, '[')) {
try {
var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
- } catch (err) {
- throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '[', ']')+' ||');
+ } catch (e) {
+ throw new SyntaxError(e + '. String to be parsed was || ' +
+ strings.vslice(exp, '[', ']') + ' ||');
}
- var r = cases['index'](_document, parent, obj);
+
+ var r = cases['index'](_document, parentNode, obj);
if (r == null) {
- throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
+ throw new SyntaxError('Expression "' + exp +
+ '" returned null. Anonymous == ' + (cases == aCases));
}
+
return r;
}
@@ -403,30 +496,28 @@ function Lookup (_document, expression) {
if (withs.startsWith(exp, c)) {
try {
var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
- } catch(err) {
- throw new Error(err+'. String to be parsed was || '+strings.vslice(exp, '(', ')')+' ||');
+ } catch (e) {
+ throw new SyntaxError(e + '. String to be parsed was || ' +
+ strings.vslice(exp, '(', ')') + ' ||');
}
- var result = cases[c](_document, parent, obj);
+ var result = cases[c](_document, parentNode, obj);
}
}
if (!result) {
- if ( withs.startsWith(exp, '{') ) {
+ if (withs.startsWith(exp, '{')) {
try {
- var obj = json2.JSON.parse(exp)
- } catch(err) {
- throw new Error(err+'. String to be parsed was || '+exp+' ||');
+ var obj = json2.JSON.parse(exp);
+ } catch (e) {
+ throw new SyntaxError(e + '. String to be parsed was || ' + exp + ' ||');
}
if (cases == aCases) {
- var result = _anonByAttrib(_document, parent, obj)
+ var result = _anonByAttrib(_document, parentNode, obj);
} else {
- var result = _byAttrib(parent, obj)
+ var result = _byAttrib(parentNode, obj);
}
}
- if (!result) {
- throw new Error('Expression "'+exp+'" returned null. Anonymous == '+(cases == aCases));
- }
}
// Final return
@@ -437,8 +528,10 @@ function Lookup (_document, expression) {
// TODO: Check length and raise error
return result;
}
+
// Maybe we should cause an exception here
return false;
};
+
return expSplit.reduce(reduceLookup);
};
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js b/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
new file mode 100644
index 000000000000..ebf493d00f6b
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
@@ -0,0 +1,1163 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
+ "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
+ "MozMillTextBox", "subclasses"
+ ];
+
+const NAMESPACE_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+var EventUtils = {}; Cu.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
+
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+var assert = new assertions.Assert();
+
+// A list of all the subclasses available. Shared modules can push their own subclasses onto this list
+var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
+
+/**
+ * createInstance()
+ *
+ * Returns an new instance of a MozMillElement
+ * The type of the element is automatically determined
+ */
+function createInstance(locatorType, locator, elem, document) {
+ var args = { "document": document, "element": elem };
+
+ // If we already have an element lets determine the best MozMillElement type
+ if (elem) {
+ for (var i = 0; i < subclasses.length; ++i) {
+ if (subclasses[i].isType(elem)) {
+ return new subclasses[i](locatorType, locator, args);
+ }
+ }
+ }
+
+ // By default we create a base MozMillElement
+ if (MozMillElement.isType(elem)) {
+ return new MozMillElement(locatorType, locator, args);
+ }
+
+ throw new Error("Unsupported element type " + locatorType + ": " + locator);
+}
+
+var Elem = function (node) {
+ return createInstance("Elem", node, node);
+};
+
+var Selector = function (document, selector, index) {
+ return createInstance("Selector", selector, elementslib.Selector(document, selector, index), document);
+};
+
+var ID = function (document, nodeID) {
+ return createInstance("ID", nodeID, elementslib.ID(document, nodeID), document);
+};
+
+var Link = function (document, linkName) {
+ return createInstance("Link", linkName, elementslib.Link(document, linkName), document);
+};
+
+var XPath = function (document, expr) {
+ return createInstance("XPath", expr, elementslib.XPath(document, expr), document);
+};
+
+var Name = function (document, nName) {
+ return createInstance("Name", nName, elementslib.Name(document, nName), document);
+};
+
+var Lookup = function (document, expression) {
+ var elem = createInstance("Lookup", expression, elementslib.Lookup(document, expression), document);
+
+ // Bug 864268 - Expose the expression property to maintain backwards compatibility
+ elem.expression = elem._locator;
+
+ return elem;
+};
+
+/**
+ * MozMillElement
+ * The base class for all mozmill elements
+ */
+function MozMillElement(locatorType, locator, args) {
+ args = args || {};
+ this._locatorType = locatorType;
+ this._locator = locator;
+ this._element = args["element"];
+ this._owner = args["owner"];
+
+ this._document = this._element ? this._element.ownerDocument : args["document"];
+ this._defaultView = this._document ? this._document.defaultView : null;
+
+ // Used to maintain backwards compatibility with controller.js
+ this.isElement = true;
+}
+
+// Static method that returns true if node is of this element type
+MozMillElement.isType = function (node) {
+ return true;
+};
+
+// This getter is the magic behind lazy loading (note distinction between _element and element)
+MozMillElement.prototype.__defineGetter__("element", function () {
+ // If the document is invalid (e.g. reload of the page), invalidate the cached
+ // element and update the document cache
+ if (this._defaultView && this._defaultView.document !== this._document) {
+ this._document = this._defaultView.document;
+ this._element = undefined;
+ }
+
+ if (this._element == undefined) {
+ if (elementslib[this._locatorType]) {
+ this._element = elementslib[this._locatorType](this._document, this._locator);
+ } else if (this._locatorType == "Elem") {
+ this._element = this._locator;
+ } else {
+ throw new Error("Unknown locator type: " + this._locatorType);
+ }
+ }
+
+ return this._element;
+});
+
+/**
+ * Drag an element to the specified offset on another element, firing mouse and
+ * drag events. Adapted from ChromeUtils.js synthesizeDrop()
+ *
+ * By default it will drag the source element over the destination's element
+ * center with a "move" dropEffect.
+ *
+ * @param {MozElement} aElement
+ * Destination element over which the drop occurs
+ * @param {Number} [aOffsetX=aElement.width/2]
+ * Relative x offset for dropping on aElement
+ * @param {Number} [aOffsetY=aElement.height/2]
+ * Relative y offset for dropping on aElement
+ * @param {DOMWindow} [aSourceWindow=this.element.ownerDocument.defaultView]
+ * Custom source Window to be used.
+ * @param {DOMWindow} [aDestWindow=aElement.getNode().ownerDocument.defaultView]
+ * Custom destination Window to be used.
+ * @param {String} [aDropEffect="move"]
+ * Possible values: copy, move, link, none
+ * @param {Object[]} [aDragData]
+ * An array holding custom drag data to be used during the drag event
+ * Format: [{ type: "text/plain", "Text to drag"}, ...]
+ *
+ * @returns {String} the captured dropEffect
+ */
+MozMillElement.prototype.dragToElement = function(aElement, aOffsetX, aOffsetY,
+ aSourceWindow, aDestWindow,
+ aDropEffect, aDragData) {
+ if (!this.element) {
+ throw new Error("Could not find element " + this.getInfo());
+ }
+ if (!aElement) {
+ throw new Error("Missing destination element");
+ }
+
+ var srcNode = this.element;
+ var destNode = aElement.getNode();
+ var srcWindow = aSourceWindow ||
+ (srcNode.ownerDocument ? srcNode.ownerDocument.defaultView
+ : srcNode);
+ var destWindow = aDestWindow ||
+ (destNode.ownerDocument ? destNode.ownerDocument.defaultView
+ : destNode);
+
+ var srcRect = srcNode.getBoundingClientRect();
+ var srcCoords = {
+ x: srcRect.width / 2,
+ y: srcRect.height / 2
+ };
+ var destRect = destNode.getBoundingClientRect();
+ var destCoords = {
+ x: (!aOffsetX || isNaN(aOffsetX)) ? (destRect.width / 2) : aOffsetX,
+ y: (!aOffsetY || isNaN(aOffsetY)) ? (destRect.height / 2) : aOffsetY
+ };
+
+ var windowUtils = destWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ var ds = Cc["@mozilla.org/widget/dragservice;1"].getService(Ci.nsIDragService);
+
+ var dataTransfer;
+ var trapDrag = function (event) {
+ srcWindow.removeEventListener("dragstart", trapDrag, true);
+ dataTransfer = event.dataTransfer;
+
+ if (!aDragData) {
+ return;
+ }
+
+ for (var i = 0; i < aDragData.length; i++) {
+ var item = aDragData[i];
+ for (var j = 0; j < item.length; j++) {
+ dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
+ }
+ }
+
+ dataTransfer.dropEffect = aDropEffect || "move";
+ event.preventDefault();
+ event.stopPropagation();
+ }
+
+ ds.startDragSession();
+
+ try {
+ srcWindow.addEventListener("dragstart", trapDrag, true);
+ EventUtils.synthesizeMouse(srcNode, srcCoords.x, srcCoords.y,
+ { type: "mousedown" }, srcWindow);
+ EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
+ { type: "mousemove" }, destWindow);
+
+ var event = destWindow.document.createEvent("DragEvents");
+ event.initDragEvent("dragenter", true, true, destWindow, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+ event.initDragEvent("dragover", true, true, destWindow, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+ event.initDragEvent("drop", true, true, destWindow, 0, 0, 0, 0, 0,
+ false, false, false, false, 0, null, dataTransfer);
+ windowUtils.dispatchDOMEventViaPresShell(destNode, event, true);
+
+ EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y,
+ { type: "mouseup" }, destWindow);
+
+ return dataTransfer.dropEffect;
+ } finally {
+ ds.endDragSession(true);
+ }
+
+};
+
+// Returns the actual wrapped DOM node
+MozMillElement.prototype.getNode = function () {
+ return this.element;
+};
+
+MozMillElement.prototype.getInfo = function () {
+ return this._locatorType + ": " + this._locator;
+};
+
+/**
+ * Sometimes an element which once existed will no longer exist in the DOM
+ * This function re-searches for the element
+ */
+MozMillElement.prototype.exists = function () {
+ this._element = undefined;
+ if (this.element) {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Synthesize a keypress event on the given element
+ *
+ * @param {string} aKey
+ * Key to use for synthesizing the keypress event. It can be a simple
+ * character like "k" or a string like "VK_ESCAPE" for command keys
+ * @param {object} aModifiers
+ * Information about the modifier keys to send
+ * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
+ * [optional - default: false]
+ * altKey - Hold down the alt key
+ * [optional - default: false]
+ * ctrlKey - Hold down the ctrl key
+ * [optional - default: false]
+ * metaKey - Hold down the meta key (command key on Mac)
+ * [optional - default: false]
+ * shiftKey - Hold down the shift key
+ * [optional - default: false]
+ * @param {object} aExpectedEvent
+ * Information about the expected event to occur
+ * Elements: target - Element which should receive the event
+ * [optional - default: current element]
+ * type - Type of the expected key event
+ */
+MozMillElement.prototype.keypress = function (aKey, aModifiers, aExpectedEvent) {
+ if (!this.element) {
+ throw new Error("Could not find element " + this.getInfo());
+ }
+
+ var win = this.element.ownerDocument ? this.element.ownerDocument.defaultView
+ : this.element;
+ this.element.focus();
+
+ if (aExpectedEvent) {
+ if (!aExpectedEvent.type) {
+ throw new Error(arguments.callee.name + ": Expected event type not specified");
+ }
+
+ var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+ : this.element;
+ EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
+ "MozMillElement.keypress()", win);
+ } else {
+ EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
+ }
+
+ broker.pass({'function':'MozMillElement.keypress()'});
+
+ return true;
+};
+
+
+/**
+ * Synthesize a general mouse event on the given element
+ *
+ * @param {number} aOffsetX
+ * Relative x offset in the elements bounds to click on
+ * @param {number} aOffsetY
+ * Relative y offset in the elements bounds to click on
+ * @param {object} aEvent
+ * Information about the event to send
+ * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
+ * [optional - default: false]
+ * altKey - Hold down the alt key
+ * [optional - default: false]
+ * button - Mouse button to use
+ * [optional - default: 0]
+ * clickCount - Number of counts to click
+ * [optional - default: 1]
+ * ctrlKey - Hold down the ctrl key
+ * [optional - default: false]
+ * metaKey - Hold down the meta key (command key on Mac)
+ * [optional - default: false]
+ * shiftKey - Hold down the shift key
+ * [optional - default: false]
+ * type - Type of the mouse event ('click', 'mousedown',
+ * 'mouseup', 'mouseover', 'mouseout')
+ * [optional - default: 'mousedown' + 'mouseup']
+ * @param {object} aExpectedEvent
+ * Information about the expected event to occur
+ * Elements: target - Element which should receive the event
+ * [optional - default: current element]
+ * type - Type of the expected mouse event
+ */
+MozMillElement.prototype.mouseEvent = function (aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
+ if (!this.element) {
+ throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
+ }
+
+ if ("document" in this.element) {
+ throw new Error("A window cannot be a target for mouse events.");
+ }
+
+ var rect = this.element.getBoundingClientRect();
+
+ if (!aOffsetX || isNaN(aOffsetX)) {
+ aOffsetX = rect.width / 2;
+ }
+
+ if (!aOffsetY || isNaN(aOffsetY)) {
+ aOffsetY = rect.height / 2;
+ }
+
+ // Scroll element into view otherwise the click will fail
+ if ("scrollIntoView" in this.element)
+ this.element.scrollIntoView();
+
+ if (aExpectedEvent) {
+ // The expected event type has to be set
+ if (!aExpectedEvent.type) {
+ throw new Error(arguments.callee.name + ": Expected event type not specified");
+ }
+
+ // If no target has been specified use the specified element
+ var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+ : this.element;
+ if (!target) {
+ throw new Error(arguments.callee.name + ": could not find element " +
+ aExpectedEvent.target.getInfo());
+ }
+
+ EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
+ target, aExpectedEvent.type,
+ "MozMillElement.mouseEvent()",
+ this.element.ownerDocument.defaultView);
+ } else {
+ EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
+ this.element.ownerDocument.defaultView);
+ }
+
+ // Bug 555347
+ // We don't know why this sleep is necessary but more investigation is needed
+ // before it can be removed
+ utils.sleep(0);
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse click event on the given element
+ */
+MozMillElement.prototype.click = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ // Handle menu items differently
+ if (this.element && this.element.tagName == "menuitem") {
+ this.element.click();
+ } else {
+ this.mouseEvent(aOffsetX, aOffsetY, {}, aExpectedEvent);
+ }
+
+ broker.pass({'function':'MozMillElement.click()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a double click on the given element
+ */
+MozMillElement.prototype.doubleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {clickCount: 2}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.doubleClick()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse down event on the given element
+ */
+MozMillElement.prototype.mouseDown = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mousedown"}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.mouseDown()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse out event on the given element
+ */
+MozMillElement.prototype.mouseOut = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseout"}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.mouseOut()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse over event on the given element
+ */
+MozMillElement.prototype.mouseOver = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseover"}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.mouseOver()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse up event on the given element
+ */
+MozMillElement.prototype.mouseUp = function (aButton, aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {button: aButton, type: "mouseup"}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.mouseUp()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse middle click event on the given element
+ */
+MozMillElement.prototype.middleClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {button: 1}, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.middleClick()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a mouse right click event on the given element
+ */
+MozMillElement.prototype.rightClick = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {type : "contextmenu", button: 2 }, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.rightClick()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a general touch event on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ * Relative x offset in the elements bounds to click on
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ * Relative y offset in the elements bounds to click on
+ * @param {Object} [aEvent]
+ * Information about the event to send
+ * @param {Boolean} [aEvent.altKey=false]
+ * A Boolean value indicating whether or not the alt key was down when
+ * the touch event was fired
+ * @param {Number} [aEvent.angle=0]
+ * The angle (in degrees) that the ellipse described by rx and
+ * ry must be rotated, clockwise, to most accurately cover the area
+ * of contact between the user and the surface.
+ * @param {Touch[]} [aEvent.changedTouches]
+ * A TouchList of all the Touch objects representing individual points of
+ * contact whose states changed between the previous touch event and
+ * this one
+ * @param {Boolean} [aEvent.ctrlKey]
+ * A Boolean value indicating whether or not the control key was down
+ * when the touch event was fired
+ * @param {Number} [aEvent.force=1]
+ * The amount of pressure being applied to the surface by the user, as a
+ * float between 0.0 (no pressure) and 1.0 (maximum pressure)
+ * @param {Number} [aEvent.id=0]
+ * A unique identifier for this Touch object. A given touch (say, by a
+ * finger) will have the same identifier for the duration of its movement
+ * around the surface. This lets you ensure that you're tracking the same
+ * touch all the time
+ * @param {Boolean} [aEvent.metaKey]
+ * A Boolean value indicating whether or not the meta key was down when
+ * the touch event was fired.
+ * @param {Number} [aEvent.rx=1]
+ * The X radius of the ellipse that most closely circumscribes the area
+ * of contact with the screen.
+ * @param {Number} [aEvent.ry=1]
+ * The Y radius of the ellipse that most closely circumscribes the area
+ * of contact with the screen.
+ * @param {Boolean} [aEvent.shiftKey]
+ * A Boolean value indicating whether or not the shift key was down when
+ * the touch event was fired
+ * @param {Touch[]} [aEvent.targetTouches]
+ * A TouchList of all the Touch objects that are both currently in
+ * contact with the touch surface and were also started on the same
+ * element that is the target of the event
+ * @param {Touch[]} [aEvent.touches]
+ * A TouchList of all the Touch objects representing all current points
+ * of contact with the surface, regardless of target or changed status
+ * @param {Number} [aEvent.type=*|touchstart|touchend|touchmove|touchenter|touchleave|touchcancel]
+ * The type of touch event that occurred
+ * @param {Element} [aEvent.target]
+ * The target of the touches associated with this event. This target
+ * corresponds to the target of all the touches in the targetTouches
+ * attribute, but note that other touches in this event may have a
+ * different target. To be careful, you should use the target associated
+ * with individual touches
+ */
+MozMillElement.prototype.touchEvent = function (aOffsetX, aOffsetY, aEvent) {
+ if (!this.element) {
+ throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
+ }
+
+ if ("document" in this.element) {
+ throw new Error("A window cannot be a target for touch events.");
+ }
+
+ var rect = this.element.getBoundingClientRect();
+
+ if (!aOffsetX || isNaN(aOffsetX)) {
+ aOffsetX = rect.width / 2;
+ }
+
+ if (!aOffsetY || isNaN(aOffsetY)) {
+ aOffsetY = rect.height / 2;
+ }
+
+ // Scroll element into view otherwise the click will fail
+ if ("scrollIntoView" in this.element) {
+ this.element.scrollIntoView();
+ }
+
+ EventUtils.synthesizeTouch(this.element, aOffsetX, aOffsetY, aEvent,
+ this.element.ownerDocument.defaultView);
+
+ return true;
+};
+
+/**
+ * Synthesize a touch tap event on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ * Left offset in px where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ * Top offset in px where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ * Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ * Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ * Type of the expected mouse event
+ */
+MozMillElement.prototype.tap = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {
+ clickCount: 1,
+ inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ }, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.tap()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a double tap on the given element
+ *
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ * Left offset in px where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ * Top offset in px where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ * Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ * Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ * Type of the expected mouse event
+ */
+MozMillElement.prototype.doubleTap = function (aOffsetX, aOffsetY, aExpectedEvent) {
+ this.mouseEvent(aOffsetX, aOffsetY, {
+ clickCount: 2,
+ inputSource: Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH
+ }, aExpectedEvent);
+
+ broker.pass({'function':'MozMillElement.doubleTap()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a long press
+ *
+ * @param {Number} aOffsetX
+ * Left offset in px where the event is triggered
+ * @param {Number} aOffsetY
+ * Top offset in px where the event is triggered
+ * @param {Number} [aTime=1000]
+ * Duration of the "press" event in ms
+ */
+MozMillElement.prototype.longPress = function (aOffsetX, aOffsetY, aTime) {
+ var time = aTime || 1000;
+
+ this.touchStart(aOffsetX, aOffsetY);
+ utils.sleep(time);
+ this.touchEnd(aOffsetX, aOffsetY);
+
+ broker.pass({'function':'MozMillElement.longPress()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a touch & drag event on the given element
+ *
+ * @param {Number} aOffsetX1
+ * Left offset of the start position
+ * @param {Number} aOffsetY1
+ * Top offset of the start position
+ * @param {Number} aOffsetX2
+ * Left offset of the end position
+ * @param {Number} aOffsetY2
+ * Top offset of the end position
+ */
+MozMillElement.prototype.touchDrag = function (aOffsetX1, aOffsetY1, aOffsetX2, aOffsetY2) {
+ this.touchStart(aOffsetX1, aOffsetY1);
+ this.touchMove(aOffsetX2, aOffsetY2);
+ this.touchEnd(aOffsetX2, aOffsetY2);
+
+ broker.pass({'function':'MozMillElement.move()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a press / touchstart event on the given element
+ *
+ * @param {Number} aOffsetX
+ * Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ * Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchStart = function (aOffsetX, aOffsetY) {
+ this.touchEvent(aOffsetX, aOffsetY, { type: "touchstart" });
+
+ broker.pass({'function':'MozMillElement.touchStart()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a release / touchend event on the given element
+ *
+ * @param {Number} aOffsetX
+ * Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ * Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchEnd = function (aOffsetX, aOffsetY) {
+ this.touchEvent(aOffsetX, aOffsetY, { type: "touchend" });
+
+ broker.pass({'function':'MozMillElement.touchEnd()'});
+
+ return true;
+};
+
+/**
+ * Synthesize a touchMove event on the given element
+ *
+ * @param {Number} aOffsetX
+ * Left offset where the event is triggered
+ * @param {Number} aOffsetY
+ * Top offset where the event is triggered
+ */
+MozMillElement.prototype.touchMove = function (aOffsetX, aOffsetY) {
+ this.touchEvent(aOffsetX, aOffsetY, { type: "touchmove" });
+
+ broker.pass({'function':'MozMillElement.touchMove()'});
+
+ return true;
+};
+
+MozMillElement.prototype.waitForElement = function (timeout, interval) {
+ var elem = this;
+
+ assert.waitFor(function () {
+ return elem.exists();
+ }, "Element.waitForElement(): Element '" + this.getInfo() +
+ "' has been found", timeout, interval);
+
+ broker.pass({'function':'MozMillElement.waitForElement()'});
+};
+
+MozMillElement.prototype.waitForElementNotPresent = function (timeout, interval) {
+ var elem = this;
+
+ assert.waitFor(function () {
+ return !elem.exists();
+ }, "Element.waitForElementNotPresent(): Element '" + this.getInfo() +
+ "' has not been found", timeout, interval);
+
+ broker.pass({'function':'MozMillElement.waitForElementNotPresent()'});
+};
+
+MozMillElement.prototype.waitThenClick = function (timeout, interval,
+ aOffsetX, aOffsetY, aExpectedEvent) {
+ this.waitForElement(timeout, interval);
+ this.click(aOffsetX, aOffsetY, aExpectedEvent);
+};
+
+/**
+ * Waits for the element to be available in the DOM, then trigger a tap event
+ *
+ * @param {Number} [aTimeout=5000]
+ * Time to wait for the element to be available
+ * @param {Number} [aInterval=100]
+ * Interval to check for availability
+ * @param {Number} [aOffsetX=aElement.width / 2]
+ * Left offset where the event is triggered
+ * @param {Number} [aOffsetY=aElement.height / 2]
+ * Top offset where the event is triggered
+ * @param {Object} [aExpectedEvent]
+ * Information about the expected event to occur
+ * @param {MozMillElement} [aExpectedEvent.target=this.element]
+ * Element which should receive the event
+ * @param {MozMillElement} [aExpectedEvent.type]
+ * Type of the expected mouse event
+ */
+MozMillElement.prototype.waitThenTap = function (aTimeout, aInterval,
+ aOffsetX, aOffsetY, aExpectedEvent) {
+ this.waitForElement(aTimeout, aInterval);
+ this.tap(aOffsetX, aOffsetY, aExpectedEvent);
+};
+
+// Dispatches an HTMLEvent
+MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
+ canBubble = canBubble || true;
+ modifiers = modifiers || { };
+
+ let document = 'ownerDocument' in this.element ? this.element.ownerDocument
+ : this.element.document;
+
+ let evt = document.createEvent('HTMLEvents');
+ evt.shiftKey = modifiers["shift"];
+ evt.metaKey = modifiers["meta"];
+ evt.altKey = modifiers["alt"];
+ evt.ctrlKey = modifiers["ctrl"];
+ evt.initEvent(eventType, canBubble, true);
+
+ this.element.dispatchEvent(evt);
+};
+
+
+/**
+ * MozMillCheckBox, which inherits from MozMillElement
+ */
+function MozMillCheckBox(locatorType, locator, args) {
+ MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillCheckBox.prototype = Object.create(MozMillElement.prototype, {
+ check : {
+ /**
+ * Enable/Disable a checkbox depending on the target state
+ *
+ * @param {boolean} state State to set
+ * @return {boolean} Success state
+ */
+ value : function MMCB_check(state) {
+ var result = false;
+
+ if (!this.element) {
+ throw new Error("could not find element " + this.getInfo());
+ }
+
+ // If we have a XUL element, unwrap its XPCNativeWrapper
+ if (this.element.namespaceURI == NAMESPACE_XUL) {
+ this.element = utils.unwrapNode(this.element);
+ }
+
+ state = (typeof(state) == "boolean") ? state : false;
+ if (state != this.element.checked) {
+ this.click();
+ var element = this.element;
+
+ assert.waitFor(function () {
+ return element.checked == state;
+ }, "CheckBox.check(): Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
+
+ result = true;
+ }
+
+ broker.pass({'function':'MozMillCheckBox.check(' + this.getInfo() +
+ ', state: ' + state + ')'});
+
+ return result;
+ }
+ }
+});
+
+
+/**
+ * Returns true if node is of type MozMillCheckBox
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type checkbox
+ */
+MozMillCheckBox.isType = function MMCB_isType(node) {
+ return ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
+ (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
+ (node.localName.toLowerCase() == 'checkbox'));
+};
+
+
+/**
+ * MozMillRadio, which inherits from MozMillElement
+ */
+function MozMillRadio(locatorType, locator, args) {
+ MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillRadio.prototype = Object.create(MozMillElement.prototype, {
+ select : {
+ /**
+ * Select the given radio button
+ *
+ * @param {number} [index=0]
+ * Specifies which radio button in the group to select (only
+ * applicable to radiogroup elements)
+ * @return {boolean} Success state
+ */
+ value : function MMR_select(index) {
+ if (!this.element) {
+ throw new Error("could not find element " + this.getInfo());
+ }
+
+ if (this.element.localName.toLowerCase() == "radiogroup") {
+ var element = this.element.getElementsByTagName("radio")[index || 0];
+ new MozMillRadio("Elem", element).click();
+ } else {
+ var element = this.element;
+ this.click();
+ }
+
+ assert.waitFor(function () {
+ // If we have a XUL element, unwrap its XPCNativeWrapper
+ if (element.namespaceURI == NAMESPACE_XUL) {
+ element = utils.unwrapNode(element);
+ return element.selected == true;
+ }
+
+ return element.checked == true;
+ }, "Radio.select(): Radio button " + this.getInfo() + " has been selected", 500);
+
+ broker.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
+
+ return true;
+ }
+ }
+});
+
+
+/**
+ * Returns true if node is of type MozMillRadio
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type radio
+ */
+MozMillRadio.isType = function MMR_isType(node) {
+ return ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
+ (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
+ (node.localName.toLowerCase() == 'radio') ||
+ (node.localName.toLowerCase() == 'radiogroup'));
+};
+
+
+/**
+ * MozMillDropList, which inherits from MozMillElement
+ */
+function MozMillDropList(locatorType, locator, args) {
+ MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillDropList.prototype = Object.create(MozMillElement.prototype, {
+ select : {
+ /**
+ * Select the specified option and trigger the relevant events of the element
+ * @return {boolean}
+ */
+ value : function MMDL_select(index, option, value) {
+ if (!this.element){
+ throw new Error("Could not find element " + this.getInfo());
+ }
+
+ //if we have a select drop down
+ if (this.element.localName.toLowerCase() == "select"){
+ var item = null;
+
+ // The selected item should be set via its index
+ if (index != undefined) {
+ // Resetting a menulist has to be handled separately
+ if (index == -1) {
+ this.dispatchEvent('focus', false);
+ this.element.selectedIndex = index;
+ this.dispatchEvent('change', true);
+
+ broker.pass({'function':'MozMillDropList.select()'});
+
+ return true;
+ } else {
+ item = this.element.options.item(index);
+ }
+ } else {
+ for (var i = 0; i < this.element.options.length; i++) {
+ var entry = this.element.options.item(i);
+ if (option != undefined && entry.innerHTML == option ||
+ value != undefined && entry.value == value) {
+ item = entry;
+ break;
+ }
+ }
+ }
+
+ // Click the item
+ try {
+ // EventUtils.synthesizeMouse doesn't work.
+ this.dispatchEvent('focus', false);
+ item.selected = true;
+ this.dispatchEvent('change', true);
+
+ var self = this;
+ var selected = index || option || value;
+ assert.waitFor(function () {
+ switch (selected) {
+ case index:
+ return selected === self.element.selectedIndex;
+ break;
+ case option:
+ return selected === item.label;
+ break;
+ case value:
+ return selected === item.value;
+ break;
+ }
+ }, "DropList.select(): The correct item has been selected");
+
+ broker.pass({'function':'MozMillDropList.select()'});
+
+ return true;
+ } catch (e) {
+ throw new Error("No item selected for element " + this.getInfo());
+ }
+ }
+ //if we have a xul menupopup select accordingly
+ else if (this.element.namespaceURI.toLowerCase() == NAMESPACE_XUL) {
+ var ownerDoc = this.element.ownerDocument;
+ // Unwrap the XUL element's XPCNativeWrapper
+ this.element = utils.unwrapNode(this.element);
+ // Get the list of menuitems
+ var menuitems = this.element.
+ getElementsByTagNameNS(NAMESPACE_XUL, "menupopup")[0].
+ getElementsByTagNameNS(NAMESPACE_XUL, "menuitem");
+
+ var item = null;
+
+ if (index != undefined) {
+ if (index == -1) {
+ this.dispatchEvent('focus', false);
+ this.element.boxObject.QueryInterface(Ci.nsIMenuBoxObject).activeChild = null;
+ this.dispatchEvent('change', true);
+
+ broker.pass({'function':'MozMillDropList.select()'});
+
+ return true;
+ } else {
+ item = menuitems[index];
+ }
+ } else {
+ for (var i = 0; i < menuitems.length; i++) {
+ var entry = menuitems[i];
+ if (option != undefined && entry.label == option ||
+ value != undefined && entry.value == value) {
+ item = entry;
+ break;
+ }
+ }
+ }
+
+ // Click the item
+ try {
+ item.click();
+
+ var self = this;
+ var selected = index || option || value;
+ assert.waitFor(function () {
+ switch (selected) {
+ case index:
+ return selected === self.element.selectedIndex;
+ break;
+ case option:
+ return selected === self.element.label;
+ break;
+ case value:
+ return selected === self.element.value;
+ break;
+ }
+ }, "DropList.select(): The correct item has been selected");
+
+ broker.pass({'function':'MozMillDropList.select()'});
+
+ return true;
+ } catch (e) {
+ throw new Error('No item selected for element ' + this.getInfo());
+ }
+ }
+ }
+ }
+});
+
+
+/**
+ * Returns true if node is of type MozMillDropList
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type dropdown list
+ */
+MozMillDropList.isType = function MMR_isType(node) {
+ return ((node.localName.toLowerCase() == 'toolbarbutton' &&
+ (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
+ (node.localName.toLowerCase() == 'menu') ||
+ (node.localName.toLowerCase() == 'menulist') ||
+ (node.localName.toLowerCase() == 'select' ));
+};
+
+
+/**
+ * MozMillTextBox, which inherits from MozMillElement
+ */
+function MozMillTextBox(locatorType, locator, args) {
+ MozMillElement.call(this, locatorType, locator, args);
+}
+
+
+MozMillTextBox.prototype = Object.create(MozMillElement.prototype, {
+ sendKeys : {
+ /**
+ * Synthesize keypress events for each character on the given element
+ *
+ * @param {string} aText
+ * The text to send as single keypress events
+ * @param {object} aModifiers
+ * Information about the modifier keys to send
+ * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
+ * [optional - default: false]
+ * altKey - Hold down the alt key
+ * [optional - default: false]
+ * ctrlKey - Hold down the ctrl key
+ * [optional - default: false]
+ * metaKey - Hold down the meta key (command key on Mac)
+ * [optional - default: false]
+ * shiftKey - Hold down the shift key
+ * [optional - default: false]
+ * @param {object} aExpectedEvent
+ * Information about the expected event to occur
+ * Elements: target - Element which should receive the event
+ * [optional - default: current element]
+ * type - Type of the expected key event
+ * @return {boolean} Success state
+ */
+ value : function MMTB_sendKeys(aText, aModifiers, aExpectedEvent) {
+ if (!this.element) {
+ throw new Error("could not find element " + this.getInfo());
+ }
+
+ var element = this.element;
+ Array.forEach(aText, function (letter) {
+ var win = element.ownerDocument ? element.ownerDocument.defaultView
+ : element;
+ element.focus();
+
+ if (aExpectedEvent) {
+ if (!aExpectedEvent.type) {
+ throw new Error(arguments.callee.name + ": Expected event type not specified");
+ }
+
+ var target = aExpectedEvent.target ? aExpectedEvent.target.getNode()
+ : element;
+ EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target,
+ aExpectedEvent.type,
+ "MozMillTextBox.sendKeys()", win);
+ } else {
+ EventUtils.synthesizeKey(letter, aModifiers || {}, win);
+ }
+ });
+
+ broker.pass({'function':'MozMillTextBox.type()'});
+
+ return true;
+ }
+ }
+});
+
+
+/**
+ * Returns true if node is of type MozMillTextBox
+ *
+ * @static
+ * @param {DOMNode} node Node to check for its type
+ * @return {boolean} True if node is of type textbox
+ */
+MozMillTextBox.isType = function MMR_isType(node) {
+ return ((node.localName.toLowerCase() == 'input' &&
+ (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
+ (node.localName.toLowerCase() == 'textarea') ||
+ (node.localName.toLowerCase() == 'textbox'));
+};
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/mozmill.js b/services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
similarity index 52%
rename from services/sync/tps/extensions/mozmill/resource/modules/mozmill.js
rename to services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
index b6e439202333..283c9bfb4246 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/mozmill.js
+++ b/services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
@@ -1,6 +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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
"getBrowserController", "newBrowserController",
@@ -8,119 +8,153 @@ var EXPORTED_SYMBOLS = ["controller", "utils", "elementslib", "os",
"newMail3PaneController", "getMail3PaneController",
"wm", "platform", "getAddrbkController",
"getMsgComposeController", "getDownloadsController",
- "Application", "cleanQuit",
+ "Application", "findElement",
"getPlacesController", 'isMac', 'isLinux', 'isWindows',
- "firePythonCallback"
+ "firePythonCallback", "getAddons"
];
-// imports
-var controller = {}; Components.utils.import('resource://mozmill/modules/controller.js', controller);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
-try {
- Components.utils.import("resource://gre/modules/AddonManager.jsm");
-} catch(e) { /* Firefox 4 only */ }
+
+Cu.import("resource://gre/modules/AddonManager.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+// imports
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var controller = {}; Cu.import('resource://mozmill/driver/controller.js', controller);
+var elementslib = {}; Cu.import('resource://mozmill/driver/elementslib.js', elementslib);
+var findElement = {}; Cu.import('resource://mozmill/driver/mozelement.js', findElement);
+var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+var windows = {}; Cu.import('resource://mozmill/modules/windows.js', windows);
+
+
+const DEBUG = false;
+
+// This is a useful "check" timer. See utils.js, good for debugging
+if (DEBUG) {
+ utils.startTimer();
+}
+
+var assert = new assertions.Assert();
// platform information
var platform = os.getPlatform();
var isMac = false;
var isWindows = false;
var isLinux = false;
+
if (platform == "darwin"){
isMac = true;
}
+
if (platform == "winnt"){
isWindows = true;
}
+
if (platform == "linux"){
isLinux = true;
}
-var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
+var wm = Services.wm;
-var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
- .getService(Components.interfaces.nsIXULAppInfo);
-
-var locale = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
- .getService(Components.interfaces.nsIXULChromeRegistry)
- .getSelectedLocale("global");
-
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
- getService(Components.interfaces.nsIConsoleService);
+var appInfo = Services.appinfo;
+var Application = utils.applicationName;
-applicationDictionary = {
- "{718e30fb-e89b-41dd-9da7-e25a45638b28}": "Sunbird",
- "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "SeaMonkey",
- "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "Firefox",
- "{3550f703-e582-4d05-9a08-453d09bdfdc6}": 'Thunderbird',
+/**
+ * Retrieves the list with information about installed add-ons.
+ *
+ * @returns {String} JSON data of installed add-ons
+ */
+function getAddons() {
+ var addons = null;
+
+ AddonManager.getAllAddons(function (addonList) {
+ var tmp_list = [ ];
+
+ addonList.forEach(function (addon) {
+ var tmp = { };
+
+ // We have to filter out properties of type 'function' of the addon
+ // object, which will break JSON.stringify() and result in incomplete
+ // addon information.
+ for (var key in addon) {
+ if (typeof(addon[key]) !== "function") {
+ tmp[key] = addon[key];
+ }
+ }
+
+ tmp_list.push(tmp);
+ });
+
+ addons = tmp_list;
+ });
+
+ try {
+ // Sychronize with getAllAddons so we do not return too early
+ assert.waitFor(function () {
+ return !!addons;
+ })
+
+ return addons;
+ } catch (e) {
+ return null;
+ }
}
-var Application = applicationDictionary[appInfo.ID];
+/**
+ * Retrieves application details for the Mozmill report
+ *
+ * @return {String} JSON data of application details
+ */
+function getApplicationDetails() {
+ var locale = Cc["@mozilla.org/chrome/chrome-registry;1"]
+ .getService(Ci.nsIXULChromeRegistry)
+ .getSelectedLocale("global");
-if (Application == undefined) {
- // Default to Firefox
- var Application = 'Firefox';
+ // Put all our necessary information into JSON and return it:
+ // appinfo, startupinfo, and addons
+ var details = {
+ application_id: appInfo.ID,
+ application_name: Application,
+ application_version: appInfo.version,
+ application_locale: locale,
+ platform_buildid: appInfo.platformBuildID,
+ platform_version: appInfo.platformVersion,
+ addons: getAddons(),
+ startupinfo: getStartupInfo(),
+ paths: {
+ appdata: Services.dirsvc.get('UAppData', Ci.nsIFile).path,
+ profile: Services.dirsvc.get('ProfD', Ci.nsIFile).path
+ }
+ };
+
+ return JSON.stringify(details);
}
// get startup time if available
// see http://blog.mozilla.com/tglek/2011/04/26/measuring-startup-speed-correctly/
-var startupInfo = {};
-try {
- var _startupInfo = Components.classes["@mozilla.org/toolkit/app-startup;1"]
- .getService(Components.interfaces.nsIAppStartup).getStartupInfo();
- for (var i in _startupInfo) {
- startupInfo[i] = _startupInfo[i].getTime(); // convert from Date object to ms since epoch
+function getStartupInfo() {
+ var startupInfo = {};
+
+ try {
+ var _startupInfo = Services.startup.getStartupInfo();
+ for (var time in _startupInfo) {
+ // convert from Date object to ms since epoch
+ startupInfo[time] = _startupInfo[time].getTime();
}
-} catch(e) {
+ } catch (e) {
startupInfo = null;
+ }
+
+ return startupInfo;
}
-// keep list of installed addons to send to jsbridge for test run report
-var addons = "null"; // this will be JSON parsed
-if(typeof AddonManager != "undefined") {
- AddonManager.getAllAddons(function(addonList) {
- var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
- converter.charset = 'utf-8';
-
- function replacer(key, value) {
- if (typeof(value) == "string") {
- try {
- return converter.ConvertToUnicode(value);
- } catch(e) {
- var newstring = '';
- for (var i=0; i < value.length; i++) {
- replacement = '';
- if ((32 <= value.charCodeAt(i)) && (value.charCodeAt(i) < 127)) {
- // eliminate non-convertable characters;
- newstring += value.charAt(i);
- } else {
- newstring += replacement;
- }
- }
- return newstring;
- }
- }
- return value;
- }
-
- addons = converter.ConvertToUnicode(JSON.stringify(addonList, replacer))
- });
-}
-
-function cleanQuit () {
- utils.getMethodInWindows('goQuitApplication')();
-}
-
-function addHttpResource (directory, namespace) {
- return 'http://localhost:4545/'+namespace;
-}
function newBrowserController () {
return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
@@ -128,34 +162,39 @@ function newBrowserController () {
function getBrowserController () {
var browserWindow = wm.getMostRecentWindow("navigator:browser");
+
if (browserWindow == null) {
return newBrowserController();
- }
- else {
+ } else {
return new controller.MozMillController(browserWindow);
}
}
function getPlacesController () {
utils.getMethodInWindows('PlacesCommandHook').showPlacesOrganizer('AllBookmarks');
+
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
function getAddonsController () {
if (Application == 'SeaMonkey') {
utils.getMethodInWindows('toEM')();
- } else if (Application == 'Thunderbird') {
+ }
+ else if (Application == 'Thunderbird') {
utils.getMethodInWindows('openAddonsMgr')();
- } else if (Application == 'Sunbird') {
+ }
+ else if (Application == 'Sunbird') {
utils.getMethodInWindows('goOpenAddons')();
} else {
utils.getMethodInWindows('BrowserOpenAddonsMgr')();
}
+
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
function getDownloadsController() {
utils.getMethodInWindows('BrowserDownloadsUI')();
+
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
@@ -165,6 +204,7 @@ function getPreferencesController() {
} else {
utils.getMethodInWindows('openPreferences')();
}
+
return new controller.MozMillController(wm.getMostRecentWindow(''));
}
@@ -175,10 +215,10 @@ function newMail3PaneController () {
function getMail3PaneController () {
var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
+
if (mail3PaneWindow == null) {
return newMail3PaneController();
- }
- else {
+ } else {
return new controller.MozMillController(mail3PaneWindow);
}
}
@@ -188,6 +228,7 @@ function newAddrbkController () {
utils.getMethodInWindows("toAddressBook")();
utils.sleep(2000);
var addyWin = wm.getMostRecentWindow("mail:addressbook");
+
return new controller.MozMillController(addyWin);
}
@@ -195,35 +236,50 @@ function getAddrbkController () {
var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
if (addrbkWindow == null) {
return newAddrbkController();
- }
- else {
+ } else {
return new controller.MozMillController(addrbkWindow);
}
}
function firePythonCallback (filename, method, args, kwargs) {
obj = {'filename': filename, 'method': method};
- obj['test'] = frame.events.currentModule.__file__;
obj['args'] = args || [];
obj['kwargs'] = kwargs || {};
- frame.events.fireEvent("firePythonCallback", obj);
+
+ broker.sendMessage("firePythonCallback", obj);
}
function timer (name) {
this.name = name;
this.timers = {};
- frame.timers.push(this);
this.actions = [];
+
+ frame.timers.push(this);
}
+
timer.prototype.start = function (name) {
this.timers[name].startTime = (new Date).getTime();
}
+
timer.prototype.stop = function (name) {
var t = this.timers[name];
+
t.endTime = (new Date).getTime();
t.totalTime = (t.endTime - t.startTime);
}
+
timer.prototype.end = function () {
frame.events.fireEvent("timer", this);
frame.timers.remove(this);
}
+
+// Initialization
+
+/**
+ * Initialize Mozmill
+ */
+function initialize() {
+ windows.init();
+}
+
+initialize();
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js b/services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js
new file mode 100644
index 000000000000..95e431f084d2
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js
@@ -0,0 +1,58 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['addListener', 'addObject',
+ 'removeListener',
+ 'sendMessage', 'log', 'pass', 'fail'];
+
+var listeners = {};
+
+// add a listener for a specific message type
+function addListener(msgType, listener) {
+ if (listeners[msgType] === undefined) {
+ listeners[msgType] = [];
+ }
+
+ listeners[msgType].push(listener);
+}
+
+// add each method in an object as a message listener
+function addObject(object) {
+ for (var msgType in object) {
+ addListener(msgType, object[msgType]);
+ }
+}
+
+// remove a listener for all message types
+function removeListener(listener) {
+ for (var msgType in listeners) {
+ for (let i = 0; i < listeners.length; ++i) {
+ if (listeners[msgType][i] == listener) {
+ listeners[msgType].splice(i, 1); // remove listener from array
+ }
+ }
+ }
+}
+
+function sendMessage(msgType, obj) {
+ if (listeners[msgType] === undefined) {
+ return;
+ }
+
+ for (let i = 0; i < listeners[msgType].length; ++i) {
+ listeners[msgType][i](obj);
+ }
+}
+
+function log(obj) {
+ sendMessage('log', obj);
+}
+
+function pass(obj) {
+ sendMessage('pass', obj);
+}
+
+function fail(obj) {
+ sendMessage('fail', obj);
+}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js b/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
index 1f0b92b8a174..b49502057af0 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
@@ -1,42 +1,174 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
-// Use the frame module of Mozmill to raise non-fatal failures
-var mozmillFrame = {};
-Cu.import('resource://mozmill/modules/frame.js', mozmillFrame);
+var EXPORTED_SYMBOLS = ['Assert', 'Expect'];
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var stack = {}; Cu.import('resource://mozmill/modules/stack.js', stack);
/**
* @name assertions
* @namespace Defines expect and assert methods to be used for assertions.
*/
-var assertions = exports;
+/**
+ * The Assert class implements fatal assertions, and can be used in cases
+ * when a failing test has to directly abort the current test function. All
+ * remaining tasks will not be performed.
+ *
+ */
+var Assert = function () {}
-/* non-fatal assertions */
-var Expect = function() {}
+Assert.prototype = {
-Expect.prototype = {
+ // The following deepEquals implementation is from Narwhal under this license:
+
+ // http://wiki.commonjs.org/wiki/Unit_Testing/1.0
+ //
+ // THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
+ //
+ // Originally from narwhal.js (http://narwhaljs.org)
+ // Copyright (c) 2009 Thomas Robinson <280north.com>
+ //
+ // Permission is hereby granted, free of charge, to any person obtaining a copy
+ // of this software and associated documentation files (the 'Software'), to
+ // deal in the Software without restriction, including without limitation the
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ // sell copies of the Software, and to permit persons to whom the Software is
+ // furnished to do so, subject to the following conditions:
+ //
+ // The above copyright notice and this permission notice shall be included in
+ // all copies or substantial portions of the Software.
+ //
+ // THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ // AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+ _deepEqual: function (actual, expected) {
+ // 7.1. All identical values are equivalent, as determined by ===.
+ if (actual === expected) {
+ return true;
+
+ // 7.2. If the expected value is a Date object, the actual value is
+ // equivalent if it is also a Date object that refers to the same time.
+ } else if (actual instanceof Date && expected instanceof Date) {
+ return actual.getTime() === expected.getTime();
+
+ // 7.3. Other pairs that do not both pass typeof value == 'object',
+ // equivalence is determined by ==.
+ } else if (typeof actual != 'object' && typeof expected != 'object') {
+ return actual == expected;
+
+ // 7.4. For all other Object pairs, including Array objects, equivalence is
+ // determined by having the same number of owned properties (as verified
+ // with Object.prototype.hasOwnProperty.call), the same set of keys
+ // (although not necessarily the same order), equivalent values for every
+ // corresponding key, and an identical 'prototype' property. Note: this
+ // accounts for both named and indexed properties on Arrays.
+ } else {
+ return this._objEquiv(actual, expected);
+ }
+ },
+
+ _objEquiv: function (a, b) {
+ if (a == null || a == undefined || b == null || b == undefined)
+ return false;
+ // an identical 'prototype' property.
+ if (a.prototype !== b.prototype) return false;
+
+ function isArguments(object) {
+ return Object.prototype.toString.call(object) == '[object Arguments]';
+ }
+
+ //~~~I've managed to break Object.keys through screwy arguments passing.
+ // Converting to array solves the problem.
+ if (isArguments(a)) {
+ if (!isArguments(b)) {
+ return false;
+ }
+ a = pSlice.call(a);
+ b = pSlice.call(b);
+ return _deepEqual(a, b);
+ }
+ try {
+ var ka = Object.keys(a),
+ kb = Object.keys(b),
+ key, i;
+ } catch (e) {//happens when one is a string literal and the other isn't
+ return false;
+ }
+ // having the same number of owned properties (keys incorporates
+ // hasOwnProperty)
+ if (ka.length != kb.length)
+ return false;
+ //the same set of keys (although not necessarily the same order),
+ ka.sort();
+ kb.sort();
+ //~~~cheap key test
+ for (i = ka.length - 1; i >= 0; i--) {
+ if (ka[i] != kb[i])
+ return false;
+ }
+ //equivalent values for every corresponding key, and
+ //~~~possibly expensive deep test
+ for (i = ka.length - 1; i >= 0; i--) {
+ key = ka[i];
+ if (!this._deepEqual(a[key], b[key])) return false;
+ }
+ return true;
+ },
+
+ _expectedException : function Assert__expectedException(actual, expected) {
+ if (!actual || !expected) {
+ return false;
+ }
+
+ if (expected instanceof RegExp) {
+ return expected.test(actual);
+ } else if (actual instanceof expected) {
+ return true;
+ } else if (expected.call({}, actual) === true) {
+ return true;
+ } else if (actual.name === expected.name) {
+ return true;
+ }
+
+ return false;
+ },
/**
- * Log a test as failing by adding a fail frame.
+ * Log a test as failing by throwing an AssertionException.
*
* @param {object} aResult
* Test result details used for reporting.
*
* - fileName
* - Name of the file in which the assertion failed.
- * - function
+ * - functionName
* - Function in which the assertion failed.
* - lineNumber
* - Line number of the file in which the assertion failed.
* - message
* - Message why the assertion failed.
*
+ * @throws {errors.AssertionError}
+ *
*/
- _logFail: function Expect__logFail(aResult) {
- mozmillFrame.events.fail({fail: aResult});
+ _logFail: function Assert__logFail(aResult) {
+ throw new errors.AssertionError(aResult.message,
+ aResult.fileName,
+ aResult.lineNumber,
+ aResult.functionName,
+ aResult.name);
},
/**
@@ -47,7 +179,7 @@ Expect.prototype = {
*
* - fileName
* - Name of the file in which the assertion failed.
- * - function
+ * - functionName
* - Function in which the assertion failed.
* - lineNumber
* - Line number of the file in which the assertion failed.
@@ -55,8 +187,8 @@ Expect.prototype = {
* - Message why the assertion failed.
*
*/
- _logPass: function Expect__logPass(aResult) {
- mozmillFrame.events.pass({pass: aResult});
+ _logPass: function Assert__logPass(aResult) {
+ broker.pass({pass: aResult});
},
/**
@@ -68,9 +200,11 @@ Expect.prototype = {
* Message to show for the test result
* @param {string} aDiagnosis
* Diagnose message to show for the test result
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- _test: function Expect__test(aCondition, aMessage, aDiagnosis) {
+ _test: function Assert__test(aCondition, aMessage, aDiagnosis) {
let diagnosis = aDiagnosis || "";
let message = aMessage || "";
@@ -78,19 +212,23 @@ Expect.prototype = {
message = aMessage ? message + " - " + diagnosis : diagnosis;
// Build result data
- let frame = Components.stack;
+ let frame = stack.findCallerFrame(Components.stack);
+
let result = {
- 'fileName' : frame.filename.replace(/(.*)-> /, ""),
- 'function' : frame.name,
- 'lineNumber' : frame.lineNumber,
- 'message' : message
+ 'fileName' : frame.filename.replace(/(.*)-> /, ""),
+ 'functionName' : frame.name,
+ 'lineNumber' : frame.lineNumber,
+ 'message' : message
};
// Log test result
- if (aCondition)
+ if (aCondition) {
this._logPass(result);
- else
+ }
+ else {
+ result.stack = Components.stack;
this._logFail(result);
+ }
return aCondition;
},
@@ -102,7 +240,7 @@ Expect.prototype = {
* Message to show for the test result.
* @returns {boolean} Always returns true.
*/
- pass: function Expect_pass(aMessage) {
+ pass: function Assert_pass(aMessage) {
return this._test(true, aMessage, undefined);
},
@@ -111,9 +249,11 @@ Expect.prototype = {
*
* @param {string} aMessage
* Message to show for the test result.
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Always returns false.
*/
- fail: function Expect_fail(aMessage) {
+ fail: function Assert_fail(aMessage) {
return this._test(false, aMessage, undefined);
},
@@ -124,16 +264,18 @@ Expect.prototype = {
* Value to test.
* @param {string} aMessage
* Message to show for the test result.
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- ok: function Expect_ok(aValue, aMessage) {
+ ok: function Assert_ok(aValue, aMessage) {
let condition = !!aValue;
let diagnosis = "got '" + aValue + "'";
return this._test(condition, aMessage, diagnosis);
},
- /**
+ /**
* Test if both specified values are identical.
*
* @param {boolean|string|number|object} aValue
@@ -142,16 +284,18 @@ Expect.prototype = {
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- equal: function Expect_equal(aValue, aExpected, aMessage) {
+ equal: function Assert_equal(aValue, aExpected, aMessage) {
let condition = (aValue === aExpected);
- let diagnosis = "got '" + aValue + "', expected '" + aExpected + "'";
+ let diagnosis = "'" + aValue + "' should equal '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
- /**
+ /**
* Test if both specified values are not identical.
*
* @param {boolean|string|number|object} aValue
@@ -160,15 +304,81 @@ Expect.prototype = {
* Value to strictly compare with.
* @param {string} aMessage
* Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- notEqual: function Expect_notEqual(aValue, aExpected, aMessage) {
+ notEqual: function Assert_notEqual(aValue, aExpected, aMessage) {
let condition = (aValue !== aExpected);
- let diagnosis = "got '" + aValue + "', not expected '" + aExpected + "'";
+ let diagnosis = "'" + aValue + "' should not equal '" + aExpected + "'";
return this._test(condition, aMessage, diagnosis);
},
+ /**
+ * Test if an object equals another object
+ *
+ * @param {object} aValue
+ * The object to test.
+ * @param {object} aExpected
+ * The object to strictly compare with.
+ * @param {string} aMessage
+ * Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
+ * @returns {boolean} Result of the test.
+ */
+ deepEqual: function equal(aValue, aExpected, aMessage) {
+ let condition = this._deepEqual(aValue, aExpected);
+ try {
+ var aValueString = JSON.stringify(aValue);
+ } catch (e) {
+ var aValueString = String(aValue);
+ }
+ try {
+ var aExpectedString = JSON.stringify(aExpected);
+ } catch (e) {
+ var aExpectedString = String(aExpected);
+ }
+
+ let diagnosis = "'" + aValueString + "' should equal '" +
+ aExpectedString + "'";
+
+ return this._test(condition, aMessage, diagnosis);
+ },
+
+ /**
+ * Test if an object does not equal another object
+ *
+ * @param {object} aValue
+ * The object to test.
+ * @param {object} aExpected
+ * The object to strictly compare with.
+ * @param {string} aMessage
+ * Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
+ * @returns {boolean} Result of the test.
+ */
+ notDeepEqual: function notEqual(aValue, aExpected, aMessage) {
+ let condition = !this._deepEqual(aValue, aExpected);
+ try {
+ var aValueString = JSON.stringify(aValue);
+ } catch (e) {
+ var aValueString = String(aValue);
+ }
+ try {
+ var aExpectedString = JSON.stringify(aExpected);
+ } catch (e) {
+ var aExpectedString = String(aExpected);
+ }
+
+ let diagnosis = "'" + aValueString + "' should not equal '" +
+ aExpectedString + "'";
+
+ return this._test(condition, aMessage, diagnosis);
+ },
+
/**
* Test if the regular expression matches the string.
*
@@ -178,9 +388,11 @@ Expect.prototype = {
* Regular expression to use for testing that a match exists.
* @param {string} aMessage
* Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- match: function Expect_match(aString, aRegex, aMessage) {
+ match: function Assert_match(aString, aRegex, aMessage) {
// XXX Bug 634948
// Regex objects are transformed to strings when evaluated in a sandbox
// For now lets re-create the regex from its string representation
@@ -190,8 +402,7 @@ Expect.prototype = {
pattern = matches[1];
flags = matches[2];
- }
- catch (ex) {
+ } catch (e) {
}
let regex = new RegExp(pattern, flags);
@@ -210,9 +421,11 @@ Expect.prototype = {
* Regular expression to use for testing that a match does not exist.
* @param {string} aMessage
* Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- notMatch: function Expect_notMatch(aString, aRegex, aMessage) {
+ notMatch: function Assert_notMatch(aString, aRegex, aMessage) {
// XXX Bug 634948
// Regex objects are transformed to strings when evaluated in a sandbox
// For now lets re-create the regex from its string representation
@@ -222,8 +435,7 @@ Expect.prototype = {
pattern = matches[1];
flags = matches[2];
- }
- catch (ex) {
+ } catch (e) {
}
let regex = new RegExp(pattern, flags);
@@ -243,9 +455,11 @@ Expect.prototype = {
* the expected error class
* @param {string} message
* message to present if assertion fails
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- throws : function Expect_throws(block, /*optional*/error, /*optional*/message) {
+ throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
},
@@ -258,9 +472,11 @@ Expect.prototype = {
* the expected error class
* @param {string} message
* message to present if assertion fails
+ * @throws {errors.AssertionError}
+ *
* @returns {boolean} Result of the test.
*/
- doesNotThrow : function Expect_doesNotThrow(block, /*optional*/error, /*optional*/message) {
+ doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
},
@@ -270,7 +486,7 @@ Expect.prototype = {
adapted from node.js's assert._throws()
https://github.com/joyent/node/blob/master/lib/assert.js
*/
- _throws : function Expect__throws(shouldThrow, block, expected, message) {
+ _throws : function Assert__throws(shouldThrow, block, expected, message) {
var actual;
if (typeof expected === 'string') {
@@ -299,80 +515,153 @@ Expect.prototype = {
!this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
+
return this._test(true, message);
},
- _expectedException : function Expect__expectedException(actual, expected) {
- if (!actual || !expected) {
- return false;
+ /**
+ * Test if the string contains the pattern.
+ *
+ * @param {String} aString String to test.
+ * @param {String} aPattern Pattern to look for in the string
+ * @param {String} aMessage Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
+ * @returns {Boolean} Result of the test.
+ */
+ contain: function Assert_contain(aString, aPattern, aMessage) {
+ let condition = (aString.indexOf(aPattern) !== -1);
+ let diagnosis = "'" + aString + "' should contain '" + aPattern + "'";
+
+ return this._test(condition, aMessage, diagnosis);
+ },
+
+ /**
+ * Test if the string does not contain the pattern.
+ *
+ * @param {String} aString String to test.
+ * @param {String} aPattern Pattern to look for in the string
+ * @param {String} aMessage Message to show for the test result
+ * @throws {errors.AssertionError}
+ *
+ * @returns {Boolean} Result of the test.
+ */
+ notContain: function Assert_notContain(aString, aPattern, aMessage) {
+ let condition = (aString.indexOf(aPattern) === -1);
+ let diagnosis = "'" + aString + "' should not contain '" + aPattern + "'";
+
+ return this._test(condition, aMessage, diagnosis);
+ },
+
+ /**
+ * Waits for the callback evaluates to true
+ *
+ * @param {Function} aCallback
+ * Callback for evaluation
+ * @param {String} aMessage
+ * Message to show for result
+ * @param {Number} aTimeout
+ * Timeout in waiting for evaluation
+ * @param {Number} aInterval
+ * Interval between evaluation attempts
+ * @param {Object} aThisObject
+ * this object
+ * @throws {errors.AssertionError}
+ *
+ * @returns {Boolean} Result of the test.
+ */
+ waitFor: function Assert_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
+ var timeout = aTimeout || 5000;
+ var interval = aInterval || 100;
+
+ var self = {
+ timeIsUp: false,
+ result: aCallback.call(aThisObject)
+ };
+ var deadline = Date.now() + timeout;
+
+ function wait() {
+ if (self.result !== true) {
+ self.result = aCallback.call(aThisObject);
+ self.timeIsUp = Date.now() > deadline;
+ }
}
- if (expected instanceof RegExp) {
- return expected.test(actual);
- } else if (actual instanceof expected) {
- return true;
- } else if (expected.call({}, actual) === true) {
- return true;
+ var hwindow = Services.appShell.hiddenDOMWindow;
+ var timeoutInterval = hwindow.setInterval(wait, interval);
+ var thread = Services.tm.currentThread;
+
+ while (self.result !== true && !self.timeIsUp) {
+ thread.processNextEvent(true);
+
+ let type = typeof(self.result);
+ if (type !== 'boolean')
+ throw TypeError("waitFor() callback has to return a boolean" +
+ " instead of '" + type + "'");
}
- return false;
+ hwindow.clearInterval(timeoutInterval);
+
+ if (self.result !== true && self.timeIsUp) {
+ aMessage = aMessage || arguments.callee.name + ": Timeout exceeded for '" + aCallback + "'";
+ throw new errors.TimeoutError(aMessage);
+ }
+
+ broker.pass({'function':'assert.waitFor()'});
+ return true;
}
}
-/**
-* AssertionError
-*
-* Error object thrown by failing assertions
-*/
-function AssertionError(message, fileName, lineNumber) {
- var err = new Error();
- if (err.stack) {
- this.stack = err.stack;
- }
- this.message = message === undefined ? err.message : message;
- this.fileName = fileName === undefined ? err.fileName : fileName;
- this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
-};
-AssertionError.prototype = new Error();
-AssertionError.prototype.constructor = AssertionError;
-AssertionError.prototype.name = 'AssertionError';
+/* non-fatal assertions */
+var Expect = function () {}
-
-var Assert = function() {}
-
-Assert.prototype = new Expect();
-
-Assert.prototype.AssertionError = AssertionError;
+Expect.prototype = new Assert();
/**
- * The Assert class implements fatal assertions, and can be used in cases
- * when a failing test has to directly abort the current test function. All
- * remaining tasks will not be performed.
- *
- */
-
-/**
- * Log a test as failing by throwing an AssertionException.
+ * Log a test as failing by adding a fail frame.
*
* @param {object} aResult
* Test result details used for reporting.
*
* - fileName
* - Name of the file in which the assertion failed.
- * - function
+ * - functionName
* - Function in which the assertion failed.
* - lineNumber
* - Line number of the file in which the assertion failed.
* - message
* - Message why the assertion failed.
*
- * @throws {AssertionError }
*/
-Assert.prototype._logFail = function Assert__logFail(aResult) {
- throw new AssertionError(aResult);
+Expect.prototype._logFail = function Expect__logFail(aResult) {
+ broker.fail({fail: aResult});
}
+/**
+ * Waits for the callback evaluates to true
+ *
+ * @param {Function} aCallback
+ * Callback for evaluation
+ * @param {String} aMessage
+ * Message to show for result
+ * @param {Number} aTimeout
+ * Timeout in waiting for evaluation
+ * @param {Number} aInterval
+ * Interval between evaluation attempts
+ * @param {Object} aThisObject
+ * this object
+ */
+Expect.prototype.waitFor = function Expect_waitFor(aCallback, aMessage, aTimeout, aInterval, aThisObject) {
+ let condition = true;
+ let message = aMessage;
-// Export of variables
-assertions.Expect = Expect;
-assertions.Assert = Assert;
+ try {
+ Assert.prototype.waitFor.apply(this, arguments);
+ }
+ catch (ex if ex instanceof errors.AssertionError) {
+ message = ex.message;
+ condition = false;
+ }
+
+ return this._test(condition, message);
+}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/controller.js b/services/sync/tps/extensions/mozmill/resource/modules/controller.js
deleted file mode 100644
index a703ce95844f..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/controller.js
+++ /dev/null
@@ -1,1002 +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/. */
-
-var EXPORTED_SYMBOLS = ["MozMillController", "globalEventRegistry", "sleep"];
-
-var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
-
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var mozelement = {}; Components.utils.import('resource://mozmill/modules/mozelement.js', mozelement);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-
-var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
- .getService(Components.interfaces.nsIAppShellService)
- .hiddenDOMWindow;
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
- getService(Components.interfaces.nsIConsoleService);
-
-// Declare most used utils functions in the controller namespace
-var sleep = utils.sleep;
-var assert = utils.assert;
-var waitFor = utils.waitFor;
-
-waitForEvents = function() {}
-
-waitForEvents.prototype = {
- /**
- * Initialize list of events for given node
- */
- init : function waitForEvents_init(node, events) {
- if (node.getNode != undefined)
- node = node.getNode();
-
- this.events = events;
- this.node = node;
- node.firedEvents = {};
- this.registry = {};
-
- for each(e in events) {
- var listener = function(event) {
- this.firedEvents[event.type] = true;
- }
- this.registry[e] = listener;
- this.registry[e].result = false;
- this.node.addEventListener(e, this.registry[e], true);
- }
- },
-
- /**
- * Wait until all assigned events have been fired
- */
- wait : function waitForEvents_wait(timeout, interval)
- {
- for (var e in this.registry) {
- utils.waitFor(function() {
- return this.node.firedEvents[e] == true;
- }, "Timeout happened before event '" + ex +"' was fired.", timeout, interval);
-
- this.node.removeEventListener(e, this.registry[e], true);
- }
- }
-}
-
-/**
- * Class to handle menus and context menus
- *
- * @constructor
- * @param {MozMillController} controller
- * Mozmill controller of the window under test
- * @param {string} menuSelector
- * jQuery like selector string of the element
- * @param {object} document
- * Document to use for finding the menu
- * [optional - default: aController.window.document]
- */
-var Menu = function(controller, menuSelector, document) {
- this._controller = controller;
- this._menu = null;
-
- document = document || controller.window.document;
- var node = document.querySelector(menuSelector);
- if (node) {
- // We don't unwrap nodes automatically yet (Bug 573185)
- node = node.wrappedJSObject || node;
- this._menu = new mozelement.Elem(node);
- }
- else {
- throw new Error("Menu element '" + menuSelector + "' not found.");
- }
-}
-
-Menu.prototype = {
-
- /**
- * Open and populate the menu
- *
- * @param {ElemBase} contextElement
- * Element whose context menu has to be opened
- * @returns {Menu} The Menu instance
- */
- open : function(contextElement) {
- // We have to open the context menu
- var menu = this._menu.getNode();
- if ((menu.localName == "popup" || menu.localName == "menupopup") &&
- contextElement && contextElement.exists()) {
- this._controller.rightClick(contextElement);
- this._controller.waitFor(function() {
- return menu.state == "open";
- }, "Context menu has been opened.");
- }
-
- // Run through the entire menu and populate with dynamic entries
- this._buildMenu(menu);
-
- return this;
- },
-
- /**
- * Close the menu
- *
- * @returns {Menu} The Menu instance
- */
- close : function() {
- var menu = this._menu.getNode();
-
- this._controller.keypress(this._menu, "VK_ESCAPE", {});
- this._controller.waitFor(function() {
- return menu.state == "closed";
- }, "Context menu has been closed.");
-
- return this;
- },
-
- /**
- * Retrieve the specified menu entry
- *
- * @param {string} itemSelector
- * jQuery like selector string of the menu item
- * @returns {ElemBase} Menu element
- * @throws Error If menu element has not been found
- */
- getItem : function(itemSelector) {
- var node = this._menu.getNode().querySelector(itemSelector);
-
- if (!node) {
- throw new Error("Menu entry '" + itemSelector + "' not found.");
- }
-
- return new mozelement.Elem(node);
- },
-
- /**
- * Click the specified menu entry
- *
- * @param {string} itemSelector
- * jQuery like selector string of the menu item
- *
- * @returns {Menu} The Menu instance
- */
- click : function(itemSelector) {
- this._controller.click(this.getItem(itemSelector));
-
- return this;
- },
-
- /**
- * Synthesize a keypress against the menu
- *
- * @param {string} key
- * Key to press
- * @param {object} modifier
- * Key modifiers
- * @see MozMillController#keypress
- *
- * @returns {Menu} The Menu instance
- */
- keypress : function(key, modifier) {
- this._controller.keypress(this._menu, key, modifier);
-
- return this;
- },
-
- /**
- * Opens the context menu, click the specified entry and
- * make sure that the menu has been closed.
- *
- * @param {string} itemSelector
- * jQuery like selector string of the element
- * @param {ElemBase} contextElement
- * Element whose context menu has to be opened
- *
- * @returns {Menu} The Menu instance
- */
- select : function(itemSelector, contextElement) {
- this.open(contextElement);
- this.click(itemSelector);
- this.close();
- },
-
- /**
- * Recursive function which iterates through all menu elements and
- * populates the menus with dynamic menu entries.
- *
- * @param {node} menu
- * Top menu node whose elements have to be populated
- */
- _buildMenu : function(menu) {
- var items = menu ? menu.childNodes : null;
-
- Array.forEach(items, function(item) {
- // When we have a menu node, fake a click onto it to populate
- // the sub menu with dynamic entries
- if (item.tagName == "menu") {
- var popup = item.querySelector("menupopup");
- if (popup) {
- if (popup.allowevents) {
- var popupEvent = this._controller.window.document.createEvent("MouseEvent");
- popupEvent.initMouseEvent("popupshowing", true, true, this._controller.window, 0,
- 0, 0, 0, 0, false, false, false, false, 0, null);
- popup.dispatchEvent(popupEvent);
- }
- this._buildMenu(popup);
- }
- }
- }, this);
- }
-};
-
-var MozMillController = function (window) {
- this.window = window;
-
- this.mozmillModule = {};
- Components.utils.import('resource://mozmill/modules/mozmill.js', this.mozmillModule);
-
- utils.waitFor(function() {
- return window != null && this.isLoaded();
- }, "controller(): Window could not be initialized.", undefined, undefined, this);
-
- if ( controllerAdditions[window.document.documentElement.getAttribute('windowtype')] != undefined ) {
- this.prototype = new utils.Copy(this.prototype);
- controllerAdditions[window.document.documentElement.getAttribute('windowtype')](this);
- this.windowtype = window.document.documentElement.getAttribute('windowtype');
- }
-}
-
-MozMillController.prototype.sleep = utils.sleep;
-
-// Open the specified url in the current tab
-MozMillController.prototype.open = function(url)
-{
- switch(this.mozmillModule.Application) {
- case "Firefox":
- this.window.gBrowser.loadURI(url);
- break;
- case "SeaMonkey":
- this.window.getBrowser().loadURI(url);
- break;
- default:
- throw new Error("MozMillController.open not supported.");
- }
-
- frame.events.pass({'function':'Controller.open()'});
-}
-
-/**
- * Take a screenshot of specified node
- *
- * @param {element} node
- * the window or DOM element to capture
- * @param {string} name
- * the name of the screenshot used in reporting and as filename
- * @param {boolean} save
- * if true saves the screenshot as 'name.png' in tempdir, otherwise returns a dataURL
- * @param {element list} highlights
- * a list of DOM elements to highlight by drawing a red rectangle around them
- */
-MozMillController.prototype.screenShot = function _screenShot(node, name, save, highlights) {
- if (!node) {
- throw new Error("node is undefined");
- }
-
- // Unwrap the node and highlights
- if ("getNode" in node) node = node.getNode();
- if (highlights) {
- for (var i = 0; i < highlights.length; ++i) {
- if ("getNode" in highlights[i]) {
- highlights[i] = highlights[i].getNode();
- }
- }
- }
-
- // If save is false, a dataURL is used
- // Include both in the report anyway to avoid confusion and make the report easier to parse
- var filepath, dataURL;
- try {
- if (save) {
- filepath = utils.takeScreenshot(node, name, highlights);
- } else {
- dataURL = utils.takeScreenshot(node, undefined, highlights);
- }
- } catch (e) {
- throw new Error("controller.screenShot() failed: " + e);
- }
-
- // Find the name of the test function
- for (var attr in frame.events.currentModule) {
- if (frame.events.currentModule[attr] == frame.events.currentTest) {
- var testName = attr;
- break;
- }
- }
-
- // Create a timestamp
- var d = new Date();
- // Report object
- var obj = { "filepath": filepath,
- "dataURL": dataURL,
- "name": name,
- "timestamp": d.toLocaleString(),
- "test_file": frame.events.currentModule.__file__,
- "test_name": testName,
- }
- // Send the screenshot object to python over jsbridge
- this.fireEvent("screenShot", obj);
-
- frame.events.pass({'function':'controller.screenShot()'});
-}
-
-/**
- * Checks if the specified window has been loaded
- *
- * @param {DOMWindow} [window=this.window] Window object to check for loaded state
- */
-MozMillController.prototype.isLoaded = function(window) {
- var win = window || this.window;
-
- return ("mozmillDocumentLoaded" in win) && win.mozmillDocumentLoaded;
-};
-
-MozMillController.prototype.waitFor = function(callback, message, timeout,
- interval, thisObject) {
- utils.waitFor(callback, message, timeout, interval, thisObject);
-
- frame.events.pass({'function':'controller.waitFor()'});
-}
-
-MozMillController.prototype.__defineGetter__("waitForEvents", function() {
- if (this._waitForEvents == undefined)
- this._waitForEvents = new waitForEvents();
- return this._waitForEvents;
-});
-
-/**
- * Wrapper function to create a new instance of a menu
- * @see Menu
- */
-MozMillController.prototype.getMenu = function (menuSelector, document) {
- return new Menu(this, menuSelector, document);
-};
-
-MozMillController.prototype.__defineGetter__("mainMenu", function() {
- return this.getMenu("menubar");
-});
-
-MozMillController.prototype.__defineGetter__("menus", function() {
- throw('controller.menus - DEPRECATED Use controller.mainMenu instead.');
-
-});
-
-MozMillController.prototype.waitForImage = function (elem, timeout, interval) {
- this.waitFor(function() {
- return elem.getNode().complete == true;
- }, "timeout exceeded for waitForImage " + elem.getInfo(), timeout, interval);
-
- frame.events.pass({'function':'Controller.waitForImage()'});
-}
-
-MozMillController.prototype.fireEvent = function (name, obj) {
- if (name == "userShutdown") {
- frame.events.toggleUserShutdown(obj);
- }
- frame.events.fireEvent(name, obj);
-}
-
-MozMillController.prototype.startUserShutdown = function (timeout, restart, next, resetProfile) {
- if (restart && resetProfile) {
- throw new Error("You can't have a user-restart and reset the profile; there is a race condition");
- }
- this.fireEvent('userShutdown', {'user': true,
- 'restart': Boolean(restart),
- 'next': next,
- 'resetProfile': Boolean(resetProfile)});
- this.window.setTimeout(this.fireEvent, timeout, 'userShutdown', 0);
-}
-
-MozMillController.prototype.restartApplication = function (next, resetProfile)
-{
- // restart the application via the python runner
- // - next : name of the next test function to run after restart
- // - resetProfile : whether to reset the profile after restart
- this.fireEvent('userShutdown', {'user': false,
- 'restart': true,
- 'next': next,
- 'resetProfile': Boolean(resetProfile)});
- utils.getMethodInWindows('goQuitApplication')();
-}
-
-MozMillController.prototype.stopApplication = function (resetProfile)
-{
- // stop the application via the python runner
- // - resetProfile : whether to reset the profile after shutdown
- this.fireEvent('userShutdown', {'user': false,
- 'restart': false,
- 'resetProfile': Boolean(resetProfile)});
- utils.getMethodInWindows('goQuitApplication')();
-}
-
-//Browser navigation functions
-MozMillController.prototype.goBack = function(){
- //this.window.focus();
- this.window.content.history.back();
- frame.events.pass({'function':'Controller.goBack()'});
- return true;
-}
-MozMillController.prototype.goForward = function(){
- //this.window.focus();
- this.window.content.history.forward();
- frame.events.pass({'function':'Controller.goForward()'});
- return true;
-}
-MozMillController.prototype.refresh = function(){
- //this.window.focus();
- this.window.content.location.reload(true);
- frame.events.pass({'function':'Controller.refresh()'});
- return true;
-}
-
-function logDeprecated(funcName, message) {
- frame.log({'function': funcName + '() - DEPRECATED', 'message': funcName + '() is deprecated' + message});
-}
-
-function logDeprecatedAssert(funcName) {
- logDeprecated('controller.' + funcName, '. use the generic `assert` module instead');
-}
-
-MozMillController.prototype.assertText = function (el, text) {
- logDeprecatedAssert("assertText");
- //this.window.focus();
- var n = el.getNode();
-
- if (n && n.innerHTML == text){
- frame.events.pass({'function':'Controller.assertText()'});
- return true;
- }
-
- throw new Error("could not validate element " + el.getInfo()+" with text "+ text);
- return false;
-
-};
-
-//Assert that a specified node exists
-MozMillController.prototype.assertNode = function (el) {
- logDeprecatedAssert("assertNode");
-
- //this.window.focus();
- var element = el.getNode();
- if (!element){
- throw new Error("could not find element " + el.getInfo());
- return false;
- }
- frame.events.pass({'function':'Controller.assertNode()'});
- return true;
-};
-
-// Assert that a specified node doesn't exist
-MozMillController.prototype.assertNodeNotExist = function (el) {
- logDeprecatedAssert("assertNodeNotExist");
-
- //this.window.focus();
- try {
- var element = el.getNode();
- } catch(err){
- frame.events.pass({'function':'Controller.assertNodeNotExist()'});
- return true;
- }
-
- if (element) {
- throw new Error("Unexpectedly found element " + el.getInfo());
- return false;
- } else {
- frame.events.pass({'function':'Controller.assertNodeNotExist()'});
- return true;
- }
-};
-
-//Assert that a form element contains the expected value
-MozMillController.prototype.assertValue = function (el, value) {
- logDeprecatedAssert("assertValue");
-
- //this.window.focus();
- var n = el.getNode();
-
- if (n && n.value == value){
- frame.events.pass({'function':'Controller.assertValue()'});
- return true;
- }
- throw new Error("could not validate element " + el.getInfo()+" with value "+ value);
- return false;
-};
-
-/**
- * Check if the callback function evaluates to true
- */
-MozMillController.prototype.assert = function(callback, message, thisObject)
-{
- logDeprecatedAssert("assert");
- utils.assert(callback, message, thisObject);
-
- frame.events.pass({'function': ": controller.assert('" + callback + "')"});
- return true;
-}
-
-//Assert that a provided value is selected in a select element
-MozMillController.prototype.assertSelected = function (el, value) {
- logDeprecatedAssert("assertSelected");
-
- //this.window.focus();
- var n = el.getNode();
- var validator = value;
-
- if (n && n.options[n.selectedIndex].value == validator){
- frame.events.pass({'function':'Controller.assertSelected()'});
- return true;
- }
- throw new Error("could not assert value for element " + el.getInfo()+" with value "+ value);
- return false;
-};
-
-//Assert that a provided checkbox is checked
-MozMillController.prototype.assertChecked = function (el) {
- logDeprecatedAssert("assertChecked");
-
- //this.window.focus();
- var element = el.getNode();
-
- if (element && element.checked == true){
- frame.events.pass({'function':'Controller.assertChecked()'});
- return true;
- }
- throw new Error("assert failed for checked element " + el.getInfo());
- return false;
-};
-
-// Assert that a provided checkbox is not checked
-MozMillController.prototype.assertNotChecked = function (el) {
- logDeprecatedAssert("assertNotChecked");
-
- var element = el.getNode();
-
- if (!element) {
- throw new Error("Could not find element" + el.getInfo());
- }
-
- if (!element.hasAttribute("checked") || element.checked != true){
- frame.events.pass({'function':'Controller.assertNotChecked()'});
- return true;
- }
- throw new Error("assert failed for not checked element " + el.getInfo());
- return false;
-};
-
-/**
- * Assert that an element's javascript property exists or has a particular value
- *
- * if val is undefined, will return true if the property exists.
- * if val is specified, will return true if the property exists and has the correct value
- */
-MozMillController.prototype.assertJSProperty = function(el, attrib, val) {
- logDeprecatedAssert("assertJSProperty");
-
- var element = el.getNode();
- if (!element){
- throw new Error("could not find element " + el.getInfo());
- return false;
- }
- var value = element[attrib];
- var res = (value !== undefined && (val === undefined ? true : String(value) == String(val)));
- if (res) {
- frame.events.pass({'function':'Controller.assertJSProperty("' + el.getInfo() + '") : ' + val});
- } else {
- throw new Error("Controller.assertJSProperty(" + el.getInfo() + ") : " +
- (val === undefined ? "property '" + attrib + "' doesn't exist" : val + " == " + value));
- }
- return res;
-};
-
-/**
- * Assert that an element's javascript property doesn't exist or doesn't have a particular value
- *
- * if val is undefined, will return true if the property doesn't exist.
- * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
- */
-MozMillController.prototype.assertNotJSProperty = function(el, attrib, val) {
- logDeprecatedAssert("assertNotJSProperty");
-
- var element = el.getNode();
- if (!element){
- throw new Error("could not find element " + el.getInfo());
- return false;
- }
- var value = element[attrib];
- var res = (val === undefined ? value === undefined : String(value) != String(val));
- if (res) {
- frame.events.pass({'function':'Controller.assertNotProperty("' + el.getInfo() + '") : ' + val});
- } else {
- throw new Error("Controller.assertNotJSProperty(" + el.getInfo() + ") : " +
- (val === undefined ? "property '" + attrib + "' exists" : val + " != " + value));
- }
- return res;
-};
-
-/**
- * Assert that an element's dom property exists or has a particular value
- *
- * if val is undefined, will return true if the property exists.
- * if val is specified, will return true if the property exists and has the correct value
- */
-MozMillController.prototype.assertDOMProperty = function(el, attrib, val) {
- logDeprecatedAssert("assertDOMProperty");
-
- var element = el.getNode();
- if (!element){
- throw new Error("could not find element " + el.getInfo());
- return false;
- }
- var value, res = element.hasAttribute(attrib);
- if (res && val !== undefined) {
- value = element.getAttribute(attrib);
- res = (String(value) == String(val));
- }
-
- if (res) {
- frame.events.pass({'function':'Controller.assertDOMProperty("' + el.getInfo() + '") : ' + val});
- } else {
- throw new Error("Controller.assertDOMProperty(" + el.getInfo() + ") : " +
- (val === undefined ? "property '" + attrib + "' doesn't exist" : val + " == " + value));
- }
- return res;
-};
-
-/**
- * Assert that an element's dom property doesn't exist or doesn't have a particular value
- *
- * if val is undefined, will return true if the property doesn't exist.
- * if val is specified, will return true if the property doesn't exist or doesn't have the specified value
- */
-MozMillController.prototype.assertNotDOMProperty = function(el, attrib, val) {
- logDeprecatedAssert("assertNotDOMProperty");
-
- var element = el.getNode();
- if (!element){
- throw new Error("could not find element " + el.getInfo());
- return false;
- }
- var value, res = element.hasAttribute(attrib);
- if (res && val !== undefined) {
- value = element.getAttribute(attrib);
- res = (String(value) == String(val));
- }
- if (!res) {
- frame.events.pass({'function':'Controller.assertNotDOMProperty("' + el.getInfo() + '") : ' + val});
- } else {
- throw new Error("Controller.assertNotDOMProperty(" + el.getInfo() + ") : " +
- (val == undefined ? "property '" + attrib + "' exists" : val + " == " + value));
- }
- return !res;
-};
-
-// deprecated - Use assertNotJSProperty or assertNotDOMProperty instead
-MozMillController.prototype.assertProperty = function(el, attrib, val) {
- logDeprecatedAssert("assertProperty");
- return this.assertJSProperty(el, attrib, val);
-};
-
-// deprecated - Use assertNotJSProperty or assertNotDOMProperty instead
-MozMillController.prototype.assertPropertyNotExist = function(el, attrib) {
- logDeprecatedAssert("assertPropertyNotExist");
- return this.assertNotJSProperty(el, attrib);
-};
-
-// Assert that a specified image has actually loaded
-// The Safari workaround results in additional requests
-// for broken images (in Safari only) but works reliably
-MozMillController.prototype.assertImageLoaded = function (el) {
- logDeprecatedAssert("assertImageLoaded");
-
- //this.window.focus();
- var img = el.getNode();
- if (!img || img.tagName != 'IMG') {
- throw new Error('Controller.assertImageLoaded() failed.')
- return false;
- }
- var comp = img.complete;
- var ret = null; // Return value
-
- // Workaround for Safari -- it only supports the
- // complete attrib on script-created images
- if (typeof comp == 'undefined') {
- test = new Image();
- // If the original image was successfully loaded,
- // src for new one should be pulled from cache
- test.src = img.src;
- comp = test.complete;
- }
-
- // Check the complete attrib. Note the strict
- // equality check -- we don't want undefined, null, etc.
- // --------------------------
- // False -- Img failed to load in IE/Safari, or is
- // still trying to load in FF
- if (comp === false) {
- ret = false;
- }
- // True, but image has no size -- image failed to
- // load in FF
- else if (comp === true && img.naturalWidth == 0) {
- ret = false;
- }
- // Otherwise all we can do is assume everything's
- // hunky-dory
- else {
- ret = true;
- }
- if (ret) {
- frame.events.pass({'function':'Controller.assertImageLoaded'});
- } else {
- throw new Error('Controller.assertImageLoaded() failed.')
- }
-
- return ret;
-};
-
-// Drag one element to the top x,y coords of another specified element
-MozMillController.prototype.mouseMove = function (doc, start, dest) {
- // if one of these elements couldn't be looked up
- if (typeof start != 'object'){
- throw new Error("received bad coordinates");
- return false;
- }
- if (typeof dest != 'object'){
- throw new Error("received bad coordinates");
- return false;
- }
-
- var triggerMouseEvent = function(element, clientX, clientY) {
- clientX = clientX ? clientX: 0;
- clientY = clientY ? clientY: 0;
-
- // make the mouse understand where it is on the screen
- var screenX = element.boxObject.screenX ? element.boxObject.screenX : 0;
- var screenY = element.boxObject.screenY ? element.boxObject.screenY : 0;
-
- var evt = element.ownerDocument.createEvent('MouseEvents');
- if (evt.initMouseEvent) {
- evt.initMouseEvent('mousemove', true, true, element.ownerDocument.defaultView, 1, screenX, screenY, clientX, clientY)
- }
- else {
- //LOG.warn("element doesn't have initMouseEvent; firing an event which should -- but doesn't -- have other mouse-event related attributes here, as well as controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown");
- evt.initEvent('mousemove', true, true);
- }
- element.dispatchEvent(evt);
- };
-
- // Do the initial move to the drag element position
- triggerMouseEvent(doc.body, start[0], start[1]);
- triggerMouseEvent(doc.body, dest[0], dest[1]);
- frame.events.pass({'function':'Controller.mouseMove()'});
- return true;
-}
-
-// Drag an element to the specified offset on another element, firing mouse and drag events.
-// Returns the captured dropEffect. Adapted from EventUtils' synthesizeDrop()
-MozMillController.prototype.dragToElement = function(src, dest, offsetX,
- offsetY, aWindow, dropEffect, dragData) {
- srcElement = src.getNode();
- destElement = dest.getNode();
- aWindow = aWindow || srcElement.ownerDocument.defaultView;
- offsetX = offsetX || 20;
- offsetY = offsetY || 20;
-
- var dataTransfer;
-
- var trapDrag = function(event) {
- dataTransfer = event.dataTransfer;
- if(!dragData)
- return;
-
- for (var i = 0; i < dragData.length; i++) {
- var item = dragData[i];
- for (var j = 0; j < item.length; j++) {
- dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
- }
- }
- dataTransfer.dropEffect = dropEffect || "move";
- event.preventDefault();
- event.stopPropagation();
- }
-
- aWindow.addEventListener("dragstart", trapDrag, true);
- EventUtils.synthesizeMouse(srcElement, 2, 2, { type: "mousedown" }, aWindow); // fire mousedown 2 pixels from corner of element
- EventUtils.synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
- EventUtils.synthesizeMouse(srcElement, offsetX, offsetY, { type: "mousemove" }, aWindow);
- aWindow.removeEventListener("dragstart", trapDrag, true);
-
- var event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- destElement.dispatchEvent(event);
-
- var event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- if (destElement.dispatchEvent(event)) {
- EventUtils.synthesizeMouse(destElement, offsetX, offsetY, { type: "mouseup" }, aWindow);
- return "none";
- }
-
- event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- destElement.dispatchEvent(event);
- EventUtils.synthesizeMouse(destElement, offsetX, offsetY, { type: "mouseup" }, aWindow);
-
- return dataTransfer.dropEffect;
-}
-
-function preferencesAdditions(controller) {
- var mainTabs = controller.window.document.getAnonymousElementByAttribute(controller.window.document.documentElement, 'anonid', 'selector');
- controller.tabs = {};
- for (var i = 0; i < mainTabs.childNodes.length; i++) {
- var node = mainTabs.childNodes[i];
- var obj = {'button':node}
- controller.tabs[i] = obj;
- var label = node.attributes.item('label').value.replace('pane', '');
- controller.tabs[label] = obj;
- }
- controller.prototype.__defineGetter__("activeTabButton",
- function () {return mainTabs.getElementsByAttribute('selected', true)[0];
- })
-}
-
-function Tabs (controller) {
- this.controller = controller;
-}
-Tabs.prototype.getTab = function(index) {
- return this.controller.window.gBrowser.browsers[index].contentDocument;
-}
-Tabs.prototype.__defineGetter__("activeTab", function() {
- return this.controller.window.gBrowser.selectedBrowser.contentDocument;
-})
-Tabs.prototype.selectTab = function(index) {
- // GO in to tab manager and grab the tab by index and call focus.
-}
-Tabs.prototype.findWindow = function (doc) {
- for (var i = 0; i <= (this.controller.window.frames.length - 1); i++) {
- if (this.controller.window.frames[i].document == doc) {
- return this.controller.window.frames[i];
- }
- }
- throw new Error("Cannot find window for document. Doc title == " + doc.title);
-}
-Tabs.prototype.getTabWindow = function(index) {
- return this.findWindow(this.getTab(index));
-}
-Tabs.prototype.__defineGetter__("activeTabWindow", function () {
- return this.findWindow(this.activeTab);
-})
-Tabs.prototype.__defineGetter__("length", function () {
- return this.controller.window.gBrowser.browsers.length;
-})
-Tabs.prototype.__defineGetter__("activeTabIndex", function() {
- return this.controller.window.gBrowser.tabContainer.selectedIndex;
-})
-Tabs.prototype.selectTabIndex = function(i) {
- this.controller.window.gBrowser.selectTabAtIndex(i);
-}
-
-function browserAdditions (controller) {
- controller.tabs = new Tabs(controller);
-
- controller.waitForPageLoad = function(aDocument, aTimeout, aInterval) {
- var timeout = aTimeout || 30000;
- var owner;
-
- // If a user tries to do waitForPageLoad(2000), this will assign the
- // interval the first arg which is most likely what they were expecting
- if (typeof(aDocument) == "number"){
- timeout = aDocument;
- }
-
- // If the document is a tab find the corresponding browser element.
- // Otherwise we have to handle an embedded web page.
- if (aDocument && typeof(aDocument) == "object") {
- owner = this.window.gBrowser.getBrowserForDocument(aDocument);
-
- if (!owner) {
- // If the document doesn't belong to a tab it will be a
- // HTML element (e.g. iframe) embedded inside a tab.
- // In such a case use the default window of the document.
- owner = aDocument.defaultView;
- }
- }
-
- // If no owner has been specified, fallback to the selected tab browser
- owner = owner || this.window.gBrowser.selectedBrowser;
-
- // Wait until the content in the tab has been loaded
- this.waitFor(function() {
- return this.isLoaded(owner);
- }, "controller.waitForPageLoad(): Timeout waiting for page loaded.",
- timeout, aInterval, this);
- frame.events.pass({'function':'controller.waitForPageLoad()'});
- }
-}
-
-controllerAdditions = {
- 'Browser:Preferences':preferencesAdditions,
- 'navigator:browser' :browserAdditions,
-}
-
-/**
- * DEPRECATION WARNING
- *
- * The following methods have all been DEPRECATED as of Mozmill 2.0
- * Use the MozMillElement object instead (https://developer.mozilla.org/en/Mozmill/Mozmill_Element_Object)
- */
-MozMillController.prototype.select = function (elem, index, option, value) {
- return elem.select(index, option, value);
-};
-
-MozMillController.prototype.keypress = function(aTarget, aKey, aModifiers, aExpectedEvent) {
- return aTarget.keypress(aKey, aModifiers, aExpectedEvent);
-}
-
-MozMillController.prototype.type = function (aTarget, aText, aExpectedEvent) {
- return aTarget.sendKeys(aText, aExpectedEvent);
-}
-
-MozMillController.prototype.mouseEvent = function(aTarget, aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
- return aTarget.mouseEvent(aOffsetX, aOffsetY, aEvent, aExpectedEvent);
-}
-
-MozMillController.prototype.click = function(elem, left, top, expectedEvent) {
- return elem.click(left, top, expectedEvent);
-}
-
-MozMillController.prototype.doubleClick = function(elem, left, top, expectedEvent) {
- return elem.doubleClick(left, top, expectedEvent);
-}
-
-MozMillController.prototype.mouseDown = function (elem, button, left, top, expectedEvent) {
- return elem.mouseDown(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseOut = function (elem, button, left, top, expectedEvent) {
- return elem.mouseOut(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseOver = function (elem, button, left, top, expectedEvent) {
- return elem.mouseOver(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.mouseUp = function (elem, button, left, top, expectedEvent) {
- return elem.mouseUp(button, left, top, expectedEvent);
-};
-
-MozMillController.prototype.middleClick = function(elem, left, top, expectedEvent) {
- return elem.middleClick(elem, left, top, expectedEvent);
-}
-
-MozMillController.prototype.rightClick = function(elem, left, top, expectedEvent) {
- return elem.rightClick(left, top, expectedEvent);
-}
-
-MozMillController.prototype.check = function(elem, state) {
- return elem.check(state);
-}
-
-MozMillController.prototype.radio = function(elem) {
- return elem.select();
-}
-
-MozMillController.prototype.waitThenClick = function (elem, timeout, interval) {
- return elem.waitThenClick(timeout, interval);
-}
-
-MozMillController.prototype.waitForElement = function(elem, timeout, interval) {
- return elem.waitForElement(timeout, interval);
-}
-
-MozMillController.prototype.waitForElementNotPresent = function(elem, timeout, interval) {
- return elem.waitForElementNotPresent(timeout, interval);
-}
-
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/driver.js b/services/sync/tps/extensions/mozmill/resource/modules/driver.js
new file mode 100644
index 000000000000..17fcfbde60f2
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/driver.js
@@ -0,0 +1,290 @@
+/* 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/. */
+
+/**
+ * @namespace Defines the Mozmill driver for global actions
+ */
+var driver = exports;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+// Temporarily include utils module to re-use sleep
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var mozmill = {}; Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+/**
+ * Gets the topmost browser window. If there are none at that time, optionally
+ * opens one. Otherwise will raise an exception if none are found.
+ *
+ * @memberOf driver
+ * @param {Boolean] [aOpenIfNone=true] Open a new browser window if none are found.
+ * @returns {DOMWindow}
+ */
+function getBrowserWindow(aOpenIfNone) {
+ // Set default
+ if (typeof aOpenIfNone === 'undefined') {
+ aOpenIfNone = true;
+ }
+
+ // If implicit open is off, turn on strict checking, and vice versa.
+ let win = getTopmostWindowByType("navigator:browser", !aOpenIfNone);
+
+ // Can just assume automatic open here. If we didn't want it and nothing found,
+ // we already raised above when getTopmostWindow was called.
+ if (!win)
+ win = openBrowserWindow();
+
+ return win;
+}
+
+
+/**
+ * Retrieves the hidden window on OS X
+ *
+ * @memberOf driver
+ * @returns {DOMWindow} The hidden window
+ */
+function getHiddenWindow() {
+ return Services.appShell.hiddenDOMWindow;
+}
+
+
+/**
+ * Opens a new browser window
+ *
+ * @memberOf driver
+ * @returns {DOMWindow}
+ */
+function openBrowserWindow() {
+ // On OS X we have to be able to create a new browser window even with no other
+ // window open. Therefore we have to use the hidden window. On other platforms
+ // at least one remaining browser window has to exist.
+ var win = mozmill.isMac ? getHiddenWindow() :
+ getTopmostWindowByType("navigator:browser", true);
+ return win.OpenBrowserWindow();
+}
+
+
+/**
+ * Pause the test execution for the given amount of time
+ *
+ * @type utils.sleep
+ * @memberOf driver
+ */
+var sleep = utils.sleep;
+
+/**
+ * Wait until the given condition via the callback returns true.
+ *
+ * @type utils.waitFor
+ * @memberOf driver
+ */
+var waitFor = assertions.Assert.waitFor;
+
+//
+// INTERNAL WINDOW ENUMERATIONS
+//
+
+/**
+ * Internal function to build a list of DOM windows using a given enumerator
+ * and filter.
+ *
+ * @private
+ * @memberOf driver
+ * @param {nsISimpleEnumerator} aEnumerator Window enumerator to use.
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} The windows found, in the same order as the enumerator.
+ */
+function _getWindows(aEnumerator, aFilterCallback, aStrict) {
+ // Set default
+ if (typeof aStrict === 'undefined')
+ aStrict = true;
+
+ let windows = [];
+
+ while (aEnumerator.hasMoreElements()) {
+ let window = aEnumerator.getNext();
+
+ if (!aFilterCallback || aFilterCallback(window)) {
+ windows.push(window);
+ }
+ }
+
+ // If this list is empty and we're strict, throw an error
+ if (windows.length === 0 && aStrict) {
+ var message = 'No windows were found';
+
+ // We'll throw a more detailed error if a filter was used.
+ if (aFilterCallback && aFilterCallback.name)
+ message += ' using filter "' + aFilterCallback.name + '"';
+
+ throw new Error(message);
+ }
+
+ return windows;
+}
+
+//
+// FILTER CALLBACKS
+//
+
+/**
+ * Generator of a closure to filter a window based by a method
+ *
+ * @memberOf driver
+ * @param {String} aName Name of the method in the window object.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByMethod(aName) {
+ return function byMethod(aWindow) { return (aName in aWindow); }
+}
+
+
+/**
+ * Generator of a closure to filter a window based by the its title
+ *
+ * @param {String} aTitle Title of the window.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByTitle(aTitle) {
+ return function byTitle(aWindow) { return (aWindow.document.title === aTitle); }
+}
+
+
+/**
+ * Generator of a closure to filter a window based by the its type
+ *
+ * @memberOf driver
+ * @param {String} aType Type of the window.
+ * @returns {Boolean} True if the condition is met.
+ */
+function windowFilterByType(aType) {
+ return function byType(aWindow) {
+ var type = aWindow.document.documentElement.getAttribute("windowtype");
+ return (type === aType);
+ }
+}
+
+//
+// WINDOW LIST RETRIEVAL FUNCTIONS
+//
+
+/**
+ * Retrieves a sorted list of open windows based on their age (newest to oldest),
+ * optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} List of windows.
+ */
+function getWindowsByAge(aFilterCallback, aStrict) {
+ var windows = _getWindows(Services.wm.getEnumerator(""),
+ aFilterCallback, aStrict);
+
+ // Reverse the list, since naturally comes back old->new
+ return windows.reverse();
+}
+
+
+/**
+ * Retrieves a sorted list of open windows based on their z order (topmost first),
+ * optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow[]} List of windows.
+ */
+function getWindowsByZOrder(aFilterCallback, aStrict) {
+ return _getWindows(Services.wm.getZOrderDOMWindowEnumerator("", true),
+ aFilterCallback, aStrict);
+}
+
+//
+// SINGLE WINDOW RETRIEVAL FUNCTIONS
+//
+
+/**
+ * Retrieves the last opened window, optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] If true, throws error if no window found.
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getNewestWindow(aFilterCallback, aStrict) {
+ var windows = getWindowsByAge(aFilterCallback, aStrict);
+ return windows.length ? windows[0] : null;
+}
+
+/**
+ * Retrieves the topmost window, optionally matching filter criteria.
+ *
+ * @memberOf driver
+ * @param {Function} [aFilterCallback] Function which is used to filter windows.
+ * @param {Boolean} [aStrict=true] If true, throws error if no window found.
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getTopmostWindow(aFilterCallback, aStrict) {
+ var windows = getWindowsByZOrder(aFilterCallback, aStrict);
+ return windows.length ? windows[0] : null;
+}
+
+
+/**
+ * Retrieves the topmost window given by the window type
+ *
+ * XXX: Bug 462222
+ * This function has to be used instead of getTopmostWindow until the
+ * underlying platform bug has been fixed.
+ *
+ * @memberOf driver
+ * @param {String} [aWindowType=null] Window type to query for
+ * @param {Boolean} [aStrict=true] Throw an error if no windows found
+ *
+ * @returns {DOMWindow} The window, or null if none found and aStrict == false
+ */
+function getTopmostWindowByType(aWindowType, aStrict) {
+ if (typeof aStrict === 'undefined')
+ aStrict = true;
+
+ var win = Services.wm.getMostRecentWindow(aWindowType);
+
+ if (win === null && aStrict) {
+ var message = 'No windows of type "' + aWindowType + '" were found';
+ throw new errors.UnexpectedError(message);
+ }
+
+ return win;
+}
+
+
+// Export of functions
+driver.getBrowserWindow = getBrowserWindow;
+driver.getHiddenWindow = getHiddenWindow;
+driver.openBrowserWindow = openBrowserWindow;
+driver.sleep = sleep;
+driver.waitFor = waitFor;
+
+driver.windowFilterByMethod = windowFilterByMethod;
+driver.windowFilterByTitle = windowFilterByTitle;
+driver.windowFilterByType = windowFilterByType;
+
+driver.getWindowsByAge = getWindowsByAge;
+driver.getNewestWindow = getNewestWindow;
+driver.getTopmostWindowByType = getTopmostWindowByType;
+
+
+// XXX Bug: 462222
+// Currently those functions cannot be used. So they shouldn't be exported.
+//driver.getWindowsByZOrder = getWindowsByZOrder;
+//driver.getTopmostWindow = getTopmostWindow;
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/errors.js b/services/sync/tps/extensions/mozmill/resource/modules/errors.js
new file mode 100644
index 000000000000..58d1a918a2da
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/errors.js
@@ -0,0 +1,102 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['BaseError',
+ 'ApplicationQuitError',
+ 'AssertionError',
+ 'TimeoutError'];
+
+
+/**
+ * Creates a new instance of a base error
+ *
+ * @class Represents the base for custom errors
+ * @param {string} [aMessage=Error().message]
+ * The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ * The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ * The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ * The function name in which the error has been raised
+ */
+function BaseError(aMessage, aFileName, aLineNumber, aFunctionName) {
+ this.name = this.constructor.name;
+
+ var err = new Error();
+ if (err.stack) {
+ this.stack = err.stack;
+ }
+
+ this.message = aMessage || err.message;
+ this.fileName = aFileName || err.fileName;
+ this.lineNumber = aLineNumber || err.lineNumber;
+ this.functionName = aFunctionName;
+}
+
+
+/**
+ * Creates a new instance of an application quit error used by Mozmill to
+ * indicate that the application is going to shutdown
+ *
+ * @class Represents an error object thrown when the application is going to shutdown
+ * @param {string} [aMessage=Error().message]
+ * The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ * The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ * The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ * The function name in which the error has been raised
+ */
+function ApplicationQuitError(aMessage, aFileName, aLineNumber, aFunctionName) {
+ BaseError.apply(this, arguments);
+}
+
+ApplicationQuitError.prototype = Object.create(BaseError.prototype, {
+ constructor : { value : ApplicationQuitError }
+});
+
+
+/**
+ * Creates a new instance of an assertion error
+ *
+ * @class Represents an error object thrown by failing assertions
+ * @param {string} [aMessage=Error().message]
+ * The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ * The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ * The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ * The function name in which the error has been raised
+ */
+function AssertionError(aMessage, aFileName, aLineNumber, aFunctionName) {
+ BaseError.apply(this, arguments);
+}
+
+AssertionError.prototype = Object.create(BaseError.prototype, {
+ constructor : { value : AssertionError }
+});
+
+/**
+ * Creates a new instance of a timeout error
+ *
+ * @class Represents an error object thrown by failing assertions
+ * @param {string} [aMessage=Error().message]
+ * The error message to show
+ * @param {string} [aFileName=Error().fileName]
+ * The file name where the error has been raised
+ * @param {string} [aLineNumber=Error().lineNumber]
+ * The line number of the file where the error has been raised
+ * @param {string} [aFunctionName=undefined]
+ * The function name in which the error has been raised
+ */
+function TimeoutError(aMessage, aFileName, aLineNumber, aFunctionName) {
+ AssertionError.apply(this, arguments);
+}
+
+TimeoutError.prototype = Object.create(AssertionError.prototype, {
+ constructor : { value : TimeoutError }
+});
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/frame.js b/services/sync/tps/extensions/mozmill/resource/modules/frame.js
index 59f8b68c6079..fffc054650b4 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/frame.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/frame.js
@@ -1,132 +1,89 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
-var EXPORTED_SYMBOLS = ['loadFile','Collector','Runner','events',
- 'jsbridge', 'runTestFile', 'log', 'getThread',
- 'timers', 'persisted'];
+var EXPORTED_SYMBOLS = ['Collector','Runner','events', 'runTestFile', 'log',
+ 'timers', 'persisted', 'shutdownApplication'];
-var httpd = {}; Components.utils.import('resource://mozmill/stdlib/httpd.js', httpd);
-var os = {}; Components.utils.import('resource://mozmill/stdlib/os.js', os);
-var strings = {}; Components.utils.import('resource://mozmill/stdlib/strings.js', strings);
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var securableModule = {}; Components.utils.import('resource://mozmill/stdlib/securable-module.js', securableModule);
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
-var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"].
- getService(Components.interfaces.nsIConsoleService);
-var ios = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
-var subscriptLoader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
- .getService(Components.interfaces.mozIJSSubScriptLoader);
-var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
- .getService(Components.interfaces.nsIUUIDGenerator);
+const TIMEOUT_SHUTDOWN_HTTPD = 15000;
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import('resource://mozmill/stdlib/httpd.js');
+
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+var os = {}; Cu.import('resource://mozmill/stdlib/os.js', os);
+var strings = {}; Cu.import('resource://mozmill/stdlib/strings.js', strings);
+var arrays = {}; Cu.import('resource://mozmill/stdlib/arrays.js', arrays);
+var withs = {}; Cu.import('resource://mozmill/stdlib/withs.js', withs);
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+var securableModule = {};
+Cu.import('resource://mozmill/stdlib/securable-module.js', securableModule);
+
+var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+var httpd = null;
var persisted = {};
-var moduleLoader = new securableModule.Loader({
- rootPaths: ["resource://mozmill/modules/"],
- defaultPrincipal: "system",
- globals : { Cc: Components.classes,
- Ci: Components.interfaces,
- Cu: Components.utils,
- Cr: Components.results}
-});
+var assert = new assertions.Assert();
-arrayRemove = function(array, from, to) {
- var rest = array.slice((to || from) + 1 || array.length);
- array.length = from < 0 ? array.length + from : from;
- return array.push.apply(array, rest);
-};
+var mozmill = undefined;
+var mozelement = undefined;
+var modules = undefined;
-mozmill = undefined; mozelement = undefined;
+var timers = [];
-var loadTestResources = function () {
- // load resources we want in our tests
- if (mozmill == undefined) {
- mozmill = {};
- Components.utils.import("resource://mozmill/modules/mozmill.js", mozmill);
+
+/**
+ * Shutdown or restart the application
+ *
+ * @param {boolean} [aFlags=undefined]
+ * Additional flags how to handle the shutdown or restart. The attributes
+ * eRestarti386 and eRestartx86_64 have not been documented yet.
+ * @see https://developer.mozilla.org/nsIAppStartup#Attributes
+ */
+function shutdownApplication(aFlags) {
+ var flags = Ci.nsIAppStartup.eForceQuit;
+
+ if (aFlags) {
+ flags |= aFlags;
}
- if (mozelement == undefined) {
- mozelement = {};
- Components.utils.import("resource://mozmill/modules/mozelement.js", mozelement);
+
+ // Send a request to shutdown the application. That will allow us and other
+ // components to finish up with any shutdown code. Please note that we don't
+ // care if other components or add-ons want to prevent this via cancelQuit,
+ // we really force the shutdown.
+ let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"].
+ createInstance(Components.interfaces.nsISupportsPRBool);
+ Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null);
+
+ // Use a timer to trigger the application restart, which will allow us to
+ // send an ACK packet via jsbridge if the method has been called via Python.
+ var event = {
+ notify: function(timer) {
+ Services.startup.quit(flags);
+ }
}
+
+ var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(event, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
-var loadFile = function(path, collector) {
- // load a test module from a file and add some candy
- var file = Components.classes["@mozilla.org/file/local;1"]
- .createInstance(Components.interfaces.nsILocalFile);
- file.initWithPath(path);
- var uri = ios.newFileURI(file).spec;
-
- loadTestResources();
- var assertions = moduleLoader.require("./assertions");
- var module = {
- collector: collector,
- mozmill: mozmill,
- elementslib: mozelement,
- findElement: mozelement,
- persisted: persisted,
- Cc: Components.classes,
- Ci: Components.interfaces,
- Cu: Components.utils,
- Cr: Components.results,
- log: log,
- assert: new assertions.Assert(),
- expect: new assertions.Expect()
- }
-
- module.require = function (mod) {
- var loader = new securableModule.Loader({
- rootPaths: [ios.newFileURI(file.parent).spec,
- "resource://mozmill/modules/"],
- defaultPrincipal: "system",
- globals : { mozmill: mozmill,
- elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x
- findElement: mozelement,
- persisted: persisted,
- Cc: Components.classes,
- Ci: Components.interfaces,
- Cu: Components.utils,
- log: log }
- });
- return loader.require(mod);
- }
-
- if (collector != undefined) {
- collector.current_file = file;
- collector.current_path = path;
- }
- try {
- subscriptLoader.loadSubScript(uri, module, "UTF-8");
- } catch(e) {
- events.fail(e);
- var obj = {
- 'filename':path,
- 'passed':false,
- 'failed':true,
- 'passes':0,
- 'fails' :1,
- 'name' :'Unknown Test',
- };
- events.fireEvent('endTest', obj);
- Components.utils.reportError(e);
- }
-
- module.__file__ = path;
- module.__uri__ = uri;
- return module;
-}
-
-function stateChangeBase (possibilties, restrictions, target, cmeta, v) {
+function stateChangeBase(possibilties, restrictions, target, cmeta, v) {
if (possibilties) {
if (!arrays.inArray(possibilties, v)) {
// TODO Error value not in this.poss
return;
}
}
+
if (restrictions) {
for (var i in restrictions) {
var r = restrictions[i];
@@ -136,87 +93,160 @@ function stateChangeBase (possibilties, restrictions, target, cmeta, v) {
}
}
}
+
// Fire jsbridge notification, logging notification, listener notifications
events[target] = v;
events.fireEvent(cmeta, target);
}
-timers = [];
var events = {
- 'currentState' : null,
- 'currentModule': null,
- 'currentTest' : null,
- 'userShutdown' : false,
- 'appQuit' : false,
- 'listeners' : {},
+ appQuit : false,
+ currentModule : null,
+ currentState : null,
+ currentTest : null,
+ shutdownRequested : false,
+ userShutdown : null,
+ userShutdownTimer : null,
+
+ listeners : {},
+ globalListeners : []
}
+
events.setState = function (v) {
- return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
- 'setupTest', 'teardownTest', 'test', 'collection'],
- null, 'currentState', 'setState', v);
+ return stateChangeBase(['dependencies', 'setupModule', 'teardownModule',
+ 'test', 'setupTest', 'teardownTest', 'collection'],
+ null, 'currentState', 'setState', v);
}
+
events.toggleUserShutdown = function (obj){
- if (this.userShutdown) {
- this.fail({'function':'frame.events.toggleUserShutdown', 'message':'Shutdown expected but none detected before timeout', 'userShutdown': obj});
+ if (!this.userShutdown) {
+ this.userShutdown = obj;
+
+ var event = {
+ notify: function(timer) {
+ events.toggleUserShutdown(obj);
+ }
+ }
+
+ this.userShutdownTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.userShutdownTimer.initWithCallback(event, obj.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+
+ } else {
+ this.userShutdownTimer.cancel();
+
+ // If the application is not going to shutdown, the user shutdown failed and
+ // we have to force a shutdown.
+ if (!events.appQuit) {
+ this.fail({'function':'events.toggleUserShutdown',
+ 'message':'Shutdown expected but none detected before timeout',
+ 'userShutdown': obj});
+
+ var flags = Ci.nsIAppStartup.eAttemptQuit;
+ if (events.isRestartShutdown()) {
+ flags |= Ci.nsIAppStartup.eRestart;
+ }
+
+ shutdownApplication(flags);
+ }
}
- this.userShutdown = obj;
}
+
events.isUserShutdown = function () {
- return Boolean(this.userShutdown);
+ return this.userShutdown ? this.userShutdown["user"] : false;
}
-events.setTest = function (test, invokedFromIDE) {
+
+events.isRestartShutdown = function () {
+ return this.userShutdown.restart;
+}
+
+events.startShutdown = function (obj) {
+ events.fireEvent('shutdown', obj);
+
+ if (obj["user"]) {
+ events.toggleUserShutdown(obj);
+ } else {
+ shutdownApplication(obj.flags);
+ }
+}
+
+events.setTest = function (test) {
+ test.__start__ = Date.now();
test.__passes__ = [];
test.__fails__ = [];
- test.__invokedFromIDE__ = invokedFromIDE;
+
events.currentTest = test;
- test.__start__ = Date.now();
- var obj = {'filename':events.currentModule.__file__,
- 'name':test.__name__,
- }
+
+ var obj = {'filename': events.currentModule.__file__,
+ 'name': test.__name__}
events.fireEvent('setTest', obj);
}
+
events.endTest = function (test) {
+ // use the current test unless specified
+ if (test === undefined) {
+ test = events.currentTest;
+ }
+
+ // If no test is set it has already been reported. Beside that we don't want
+ // to report it a second time.
+ if (!test || test.status === 'done')
+ return;
+
// report the end of a test
- test.status = 'done';
- events.currentTest = null;
test.__end__ = Date.now();
- var obj = {'filename':events.currentModule.__file__,
- 'passed':test.__passes__.length,
- 'failed':test.__fails__.length,
- 'passes':test.__passes__,
- 'fails' :test.__fails__,
- 'name' :test.__name__,
- 'time_start':test.__start__,
- 'time_end':test.__end__
- }
+ test.status = 'done';
+
+ var obj = {'filename': events.currentModule.__file__,
+ 'passed': test.__passes__.length,
+ 'failed': test.__fails__.length,
+ 'passes': test.__passes__,
+ 'fails' : test.__fails__,
+ 'name' : test.__name__,
+ 'time_start': test.__start__,
+ 'time_end': test.__end__}
+
if (test.skipped) {
obj['skipped'] = true;
obj.skipped_reason = test.skipped_reason;
}
+
if (test.meta) {
obj.meta = test.meta;
}
- // Report the test result only if the test is a true test or if it is a
- // failing setup/teardown
- var shouldSkipReporting = false;
- if (test.__passes__ &&
- (test.__name__ == 'setupModule' ||
- test.__name__ == 'setupTest' ||
- test.__name__ == 'teardownTest' ||
- test.__name__ == 'teardownModule')) {
- shouldSkipReporting = true;
- }
-
- if (!shouldSkipReporting) {
+ // Report the test result only if the test is a true test or if it is failing
+ if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) {
events.fireEvent('endTest', obj);
}
}
-events.setModule = function (v) {
- return stateChangeBase( null, [function (v) {return (v.__file__ != undefined)}],
- 'currentModule', 'setModule', v);
+events.setModule = function (aModule) {
+ aModule.__start__ = Date.now();
+ aModule.__status__ = 'running';
+
+ var result = stateChangeBase(null,
+ [function (aModule) {return (aModule.__file__ != undefined)}],
+ 'currentModule', 'setModule', aModule);
+
+ return result;
+}
+
+events.endModule = function (aModule) {
+ // It should only reported once, so check if it already has been done
+ if (aModule.__status__ === 'done')
+ return;
+
+ aModule.__end__ = Date.now();
+ aModule.__status__ = 'done';
+
+ var obj = {
+ 'filename': aModule.__file__,
+ 'time_start': aModule.__start__,
+ 'time_end': aModule.__end__
+ }
+
+ events.fireEvent('endModule', obj);
}
events.pass = function (obj) {
@@ -224,17 +254,22 @@ events.pass = function (obj) {
if (events.currentTest) {
events.currentTest.__passes__.push(obj);
}
- for each(var timer in timers) {
+
+ for each (var timer in timers) {
timer.actions.push(
- {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
- "result":"pass"}
+ {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+ "obj": obj,
+ "result": "pass"}
);
}
+
events.fireEvent('pass', obj);
}
+
events.fail = function (obj) {
var error = obj.exception;
- if(error) {
+
+ if (error) {
// Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207
obj.exception = {
name: error.name,
@@ -244,147 +279,216 @@ events.fail = function (obj) {
stack: error.stack
};
}
+
// a low level event, such as a keystroke, fails
if (events.currentTest) {
events.currentTest.__fails__.push(obj);
}
- for each(var time in timers) {
+
+ for each (var time in timers) {
timer.actions.push(
- {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":obj,
- "result":"fail"}
+ {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+ "obj": obj,
+ "result": "fail"}
);
}
+
events.fireEvent('fail', obj);
}
+
events.skip = function (reason) {
- // this is used to report skips associated with setupModule and setupTest
- // and nothing else
+ // this is used to report skips associated with setupModule and nothing else
events.currentTest.skipped = true;
events.currentTest.skipped_reason = reason;
- for each(var timer in timers) {
+
+ for (var timer of timers) {
timer.actions.push(
- {"currentTest":events.currentModule.__file__+"::"+events.currentTest.__name__, "obj":reason,
- "result":"skip"}
+ {"currentTest": events.currentModule.__file__ + "::" + events.currentTest.__name__,
+ "obj": reason,
+ "result": "skip"}
);
}
+
events.fireEvent('skip', reason);
}
+
events.fireEvent = function (name, obj) {
+ if (events.appQuit) {
+ // dump('* Event discarded: ' + name + ' ' + JSON.stringify(obj) + '\n');
+ return;
+ }
+
if (this.listeners[name]) {
for (var i in this.listeners[name]) {
this.listeners[name][i](obj);
}
}
+
for each(var listener in this.globalListeners) {
listener(name, obj);
}
}
-events.globalListeners = [];
+
events.addListener = function (name, listener) {
if (this.listeners[name]) {
this.listeners[name].push(listener);
- } else if (name =='') {
+ } else if (name == '') {
this.globalListeners.push(listener)
} else {
this.listeners[name] = [listener];
}
}
-events.removeListener = function(listener) {
+
+events.removeListener = function (listener) {
for (var listenerIndex in this.listeners) {
var e = this.listeners[listenerIndex];
+
for (var i in e){
if (e[i] == listener) {
- this.listeners[listenerIndex] = arrayRemove(e, i);
+ this.listeners[listenerIndex] = arrays.remove(e, i);
}
}
}
+
for (var i in this.globalListeners) {
if (this.globalListeners[i] == listener) {
- this.globalListeners = arrayRemove(this.globalListeners, i);
+ this.globalListeners = arrays.remove(this.globalListeners, i);
}
}
}
+events.persist = function () {
+ try {
+ events.fireEvent('persist', persisted);
+ } catch (e) {
+ events.fireEvent('error', "persist serialization failed.")
+ }
+}
+
+events.firePythonCallback = function (obj) {
+ obj['test'] = events.currentModule.__file__;
+ events.fireEvent('firePythonCallback', obj);
+}
+
+events.screenshot = function (obj) {
+ // Find the name of the test function
+ for (var attr in events.currentModule) {
+ if (events.currentModule[attr] == events.currentTest) {
+ var testName = attr;
+ break;
+ }
+ }
+
+ obj['test_file'] = events.currentModule.__file__;
+ obj['test_name'] = testName;
+ events.fireEvent('screenshot', obj);
+}
+
var log = function (obj) {
events.fireEvent('log', obj);
}
+// Register the listeners
+broker.addObject({'endTest': events.endTest,
+ 'fail': events.fail,
+ 'firePythonCallback': events.firePythonCallback,
+ 'log': log,
+ 'pass': events.pass,
+ 'persist': events.persist,
+ 'screenshot': events.screenshot,
+ 'shutdown': events.startShutdown,
+ });
+
try {
- var jsbridge = {}; Components.utils.import('resource://jsbridge/modules/events.js', jsbridge);
-} catch(err) {
- var jsbridge = null;
+ Cu.import('resource://jsbridge/modules/Events.jsm');
- aConsoleService.logStringMessage("jsbridge not available.");
+ events.addListener('', function (name, obj) {
+ Events.fireEvent('mozmill.' + name, obj);
+ });
+} catch (e) {
+ Services.console.logStringMessage("Event module of JSBridge not available.");
}
-if (jsbridge) {
- events.addListener('', function (name, obj) {jsbridge.fireEvent('mozmill.'+name, obj)} );
+
+/**
+ * Observer for notifications when the application is going to shutdown
+ */
+function AppQuitObserver() {
+ this.runner = null;
+
+ Services.obs.addObserver(this, "quit-application-requested", false);
}
-function Collector () {
- // the collector handles HTTPD and initilizing the module
+AppQuitObserver.prototype = {
+ observe: function (aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "quit-application-requested":
+ Services.obs.removeObserver(this, "quit-application-requested");
+
+ // If we observe a quit notification make sure to send the
+ // results of the current test. In those cases we don't reach
+ // the equivalent code in runTestModule()
+ events.pass({'message': 'AppQuitObserver: ' + JSON.stringify(aData),
+ 'userShutdown': events.userShutdown});
+
+ if (this.runner) {
+ this.runner.end();
+ }
+
+ if (httpd) {
+ httpd.stop();
+ }
+
+ events.appQuit = true;
+
+ break;
+ }
+ }
+}
+
+var appQuitObserver = new AppQuitObserver();
+
+/**
+ * The collector handles HTTPd.js and initilizing the module
+ */
+function Collector() {
this.test_modules_by_filename = {};
this.testing = [];
- this.httpd_started = false;
- this.http_port = 43336;
- this.http_server = httpd.getServer(this.http_port);
}
-Collector.prototype.startHttpd = function () {
- while (this.httpd == undefined) {
- try {
- this.http_server.start(this.http_port);
- this.httpd = this.http_server;
- } catch(e) { // Failure most likely due to port conflict
- this.http_port++;
- this.http_server = httpd.getServer(this.http_port);
- };
- }
-}
-Collector.prototype.stopHttpd = function () {
- if (this.httpd) {
- this.httpd.stop(function(){}); // Callback needed to pause execution until the server has been properly shutdown
- this.httpd = null;
- }
-}
-Collector.prototype.addHttpResource = function (directory, ns) {
- if (!this.httpd) {
- this.startHttpd();
- }
+Collector.prototype.addHttpResource = function (aDirectory, aPath) {
+ var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ fp.initWithPath(os.abspath(aDirectory, this.current_file));
- if (!ns) {
- ns = '/';
- } else {
- ns = '/' + ns + '/';
- }
-
- var lp = Components.classes["@mozilla.org/file/local;1"].
- createInstance(Components.interfaces.nsILocalFile);
- lp.initWithPath(os.abspath(directory, this.current_file));
- this.httpd.registerDirectory(ns, lp);
-
- return 'http://localhost:' + this.http_port + ns
+ return httpd.addHttpResource(fp, aPath);
}
-Collector.prototype.initTestModule = function (filename, name) {
- var test_module = loadFile(filename, this);
+Collector.prototype.initTestModule = function (filename, testname) {
+ var test_module = this.loadFile(filename, this);
+ var has_restarted = !(testname == null);
test_module.__tests__ = [];
+
for (var i in test_module) {
if (typeof(test_module[i]) == "function") {
test_module[i].__name__ = i;
- if (i == "setupTest") {
- test_module.__setupTest__ = test_module[i];
- } else if (i == "setupModule") {
+
+ // Only run setupModule if we are a single test OR if we are the first
+ // test of a restart chain (don't run it prior to members in a restart
+ // chain)
+ if (i == "setupModule" && !has_restarted) {
test_module.__setupModule__ = test_module[i];
+ } else if (i == "setupTest") {
+ test_module.__setupTest__ = test_module[i];
} else if (i == "teardownTest") {
test_module.__teardownTest__ = test_module[i];
} else if (i == "teardownModule") {
test_module.__teardownModule__ = test_module[i];
} else if (withs.startsWith(i, "test")) {
- if (name && (i != name)) {
- continue;
+ if (testname && (i != testname)) {
+ continue;
}
- name = null;
+
+ testname = null;
test_module.__tests__.push(test_module[i]);
}
}
@@ -392,171 +496,290 @@ Collector.prototype.initTestModule = function (filename, name) {
test_module.collector = this;
test_module.status = 'loaded';
+
this.test_modules_by_filename[filename] = test_module;
+
return test_module;
}
-// Observer which gets notified when the application quits
-function AppQuitObserver() {
- this.register();
+Collector.prototype.loadFile = function (path, collector) {
+ var moduleLoader = new securableModule.Loader({
+ rootPaths: ["resource://mozmill/modules/"],
+ defaultPrincipal: "system",
+ globals : { Cc: Cc,
+ Ci: Ci,
+ Cu: Cu,
+ Cr: Components.results}
+ });
+
+ // load a test module from a file and add some candy
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ var uri = Services.io.newFileURI(file).spec;
+
+ this.loadTestResources();
+
+ var systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+ var module = new Components.utils.Sandbox(systemPrincipal);
+ module.assert = new assertions.Assert();
+ module.Cc = Cc;
+ module.Ci = Ci;
+ module.Cr = Components.results;
+ module.Cu = Cu;
+ module.collector = collector;
+ module.driver = moduleLoader.require("driver");
+ module.elementslib = mozelement;
+ module.errors = errors;
+ module.expect = new assertions.Expect();
+ module.findElement = mozelement;
+ module.log = log;
+ module.mozmill = mozmill;
+ module.persisted = persisted;
+
+ module.require = function (mod) {
+ var loader = new securableModule.Loader({
+ rootPaths: [Services.io.newFileURI(file.parent).spec,
+ "resource://mozmill/modules/"],
+ defaultPrincipal: "system",
+ globals : { mozmill: mozmill,
+ elementslib: mozelement, // This a quick hack to maintain backwards compatibility with 1.5.x
+ findElement: mozelement,
+ persisted: persisted,
+ Cc: Cc,
+ Ci: Ci,
+ Cu: Cu,
+ log: log }
+ });
+
+ if (modules != undefined) {
+ loader.modules = modules;
+ }
+
+ var retval = loader.require(mod);
+ modules = loader.modules;
+
+ return retval;
+ }
+
+ if (collector != undefined) {
+ collector.current_file = file;
+ collector.current_path = path;
+ }
+
+ try {
+ Services.scriptloader.loadSubScript(uri, module, "UTF-8");
+ } catch (e) {
+ var obj = {
+ 'filename': path,
+ 'passed': 0,
+ 'failed': 1,
+ 'passes': [],
+ 'fails' : [{'exception' : {
+ message: e.message,
+ filename: e.filename,
+ lineNumber: e.lineNumber}}],
+ 'name' :''
+ };
+
+ events.fail({'exception': e});
+ events.fireEvent('endTest', obj);
+ }
+
+ module.__file__ = path;
+ module.__uri__ = uri;
+
+ return module;
}
-AppQuitObserver.prototype = {
- observe: function(subject, topic, data) {
- events.appQuit = true;
- },
- register: function() {
- var obsService = Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- obsService.addObserver(this, "quit-application", false);
- },
- unregister: function() {
- var obsService = Components.classes["@mozilla.org/observer-service;1"]
- .getService(Components.interfaces.nsIObserverService);
- obsService.removeObserver(this, "quit-application");
+
+Collector.prototype.loadTestResources = function () {
+ // load resources we want in our tests
+ if (mozmill === undefined) {
+ mozmill = {};
+ Cu.import("resource://mozmill/driver/mozmill.js", mozmill);
+ }
+ if (mozelement === undefined) {
+ mozelement = {};
+ Cu.import("resource://mozmill/driver/mozelement.js", mozelement);
}
}
-function Runner (collector, invokedFromIDE) {
- this.collector = collector;
- this.invokedFromIDE = invokedFromIDE
- events.fireEvent('startRunner', true);
- var m = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', m);
- this.platform = m.platform;
+/**
+ *
+ */
+function Httpd(aPort) {
+ this.http_port = aPort;
+
+ while (true) {
+ try {
+ var srv = new HttpServer();
+ srv.registerContentType("sjs", "sjs");
+ srv.identity.setPrimary("http", "localhost", this.http_port);
+ srv.start(this.http_port);
+
+ this._httpd = srv;
+ break;
+ }
+ catch (e) {
+ // Failure most likely due to port conflict
+ this.http_port++;
+ }
+ }
}
+Httpd.prototype.addHttpResource = function (aDir, aPath) {
+ var path = aPath ? ("/" + aPath + "/") : "/";
+
+ try {
+ this._httpd.registerDirectory(path, aDir);
+ return 'http://localhost:' + this.http_port + path;
+ }
+ catch (e) {
+ throw Error("Failure to register directory: " + aDir.path);
+ }
+};
+
+Httpd.prototype.stop = function () {
+ if (!this._httpd) {
+ return;
+ }
+
+ var shutdown = false;
+ this._httpd.stop(function () { shutdown = true; });
+
+ assert.waitFor(function () {
+ return shutdown;
+ }, "Local HTTP server has been stopped", TIMEOUT_SHUTDOWN_HTTPD);
+
+ this._httpd = null;
+};
+
+function startHTTPd() {
+ if (!httpd) {
+ // Ensure that we start the HTTP server only once during a session
+ httpd = new Httpd(43336);
+ }
+}
+
+
+function Runner() {
+ this.collector = new Collector();
+ this.ended = false;
+
+ var m = {}; Cu.import('resource://mozmill/driver/mozmill.js', m);
+ this.platform = m.platform;
+
+ events.fireEvent('startRunner', true);
+}
+
+Runner.prototype.end = function () {
+ if (!this.ended) {
+ this.ended = true;
+
+ appQuitObserver.runner = null;
+
+ events.endTest();
+ events.endModule(events.currentModule);
+ events.fireEvent('endRunner', true);
+ events.persist();
+ }
+};
+
Runner.prototype.runTestFile = function (filename, name) {
- this.collector.initTestModule(filename, name);
- this.runTestModule(this.collector.test_modules_by_filename[filename]);
-}
-Runner.prototype.end = function () {
- try {
- events.fireEvent('persist', persisted);
- } catch(e) {
- events.fireEvent('error', "persist serialization failed.");
- }
- this.collector.stopHttpd();
- events.fireEvent('endRunner', true);
-}
+ var module = this.collector.initTestModule(filename, name);
+ this.runTestModule(module);
+};
-Runner.prototype.wrapper = function (func, arg) {
- thread = Components.classes["@mozilla.org/thread-manager;1"]
- .getService(Components.interfaces.nsIThreadManager)
- .currentThread;
+Runner.prototype.runTestModule = function (module) {
+ appQuitObserver.runner = this;
+ events.setModule(module);
+
+ // If setupModule passes, run all the tests. Otherwise mark them as skipped.
+ if (this.execFunction(module.__setupModule__, module)) {
+ for (var test of module.__tests__) {
+ if (events.shutdownRequested) {
+ break;
+ }
+
+ // If setupTest passes, run the test. Otherwise mark it as skipped.
+ if (this.execFunction(module.__setupTest__, module)) {
+ this.execFunction(test);
+ } else {
+ this.skipFunction(test, module.__setupTest__.__name__ + " failed");
+ }
+
+ this.execFunction(module.__teardownTest__, module);
+ }
+
+ } else {
+ for (var test of module.__tests__) {
+ this.skipFunction(test, module.__setupModule__.__name__ + " failed");
+ }
+ }
+
+ this.execFunction(module.__teardownModule__, module);
+ events.endModule(module);
+};
+
+Runner.prototype.execFunction = function (func, arg) {
+ if (typeof func !== "function" || events.shutdownRequested) {
+ return true;
+ }
+
+ var isTest = withs.startsWith(func.__name__, "test");
+
+ events.setState(isTest ? "test" : func.__name);
+ events.setTest(func);
// skip excluded platforms
if (func.EXCLUDED_PLATFORMS != undefined) {
if (arrays.inArray(func.EXCLUDED_PLATFORMS, this.platform)) {
events.skip("Platform exclusion");
- return;
+ events.endTest(func);
+ return false;
}
}
// skip function if requested
if (func.__force_skip__ != undefined) {
events.skip(func.__force_skip__);
- return;
+ events.endTest(func);
+ return false;
}
// execute the test function
try {
- if (arg) {
- func(arg);
- } else {
- func();
- }
-
- // If a user shutdown was expected but the application hasn't quit, throw a failure
- if (events.isUserShutdown()) {
- utils.sleep(500); // Prevents race condition between mozrunner hard process kill and normal FFx shutdown
- if (events.userShutdown['user'] && !events.appQuit) {
- events.fail({'function':'Runner.wrapper',
- 'message':'Shutdown expected but none detected before end of test',
- 'userShutdown': events.userShutdown});
- }
- }
+ func(arg);
} catch (e) {
- // Allow the exception if a user shutdown was expected
- if (!events.isUserShutdown()) {
- events.fail({'exception': e, 'test':func})
- Components.utils.reportError(e);
+ if (e instanceof errors.ApplicationQuitError) {
+ events.shutdownRequested = true;
+ } else {
+ events.fail({'exception': e, 'test': func})
}
}
-}
-Runner.prototype.runTestModule = function (module) {
- events.setModule(module);
- module.__status__ = 'running';
- if (module.__setupModule__) {
- events.setState('setupModule');
- events.setTest(module.__setupModule__);
- this.wrapper(module.__setupModule__, module);
- var setupModulePassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
- events.endTest(module.__setupModule__);
- } else {
- var setupModulePassed = true;
+ // If a user shutdown has been requested and the function already returned,
+ // we can assume that a shutdown will not happen anymore. We should force a
+ // shutdown then, to prevent the next test from being executed.
+ if (events.isUserShutdown()) {
+ events.shutdownRequested = true;
+ events.toggleUserShutdown(events.userShutdown);
}
- if (setupModulePassed) {
- var observer = new AppQuitObserver();
- for (var i in module.__tests__) {
- events.appQuit = false;
- var test = module.__tests__[i];
- // TODO: introduce per-test timeout:
- // https://bugzilla.mozilla.org/show_bug.cgi?id=574871
+ events.endTest(func);
+ return events.currentTest.__fails__.length == 0;
+};
- if (module.__setupTest__) {
- events.setState('setupTest');
- events.setTest(module.__setupTest__);
- this.wrapper(module.__setupTest__, test);
- var setupTestPassed = (events.currentTest.__fails__.length == 0 && !events.currentTest.skipped);
- events.endTest(module.__setupTest__);
- } else {
- var setupTestPassed = true;
- }
- events.setState('test');
- events.setTest(test, this.invokedFromIDE);
- if (setupTestPassed) {
- this.wrapper(test);
- if (events.userShutdown && !events.userShutdown['user']) {
- events.endTest(test);
- break;
- }
- } else {
- events.skip("setupTest failed.");
- }
- if (module.__teardownTest__) {
- events.setState('teardownTest');
- events.setTest(module.__teardownTest__);
- this.wrapper(module.__teardownTest__, test);
- events.endTest(module.__teardownTest__);
- }
- events.endTest(test)
- }
- observer.unregister();
- } else {
- for each(var test in module.__tests__) {
- events.setTest(test);
- events.skip("setupModule failed.");
- events.endTest(test);
- }
- }
- if (module.__teardownModule__) {
- events.setState('teardownModule');
- events.setTest(module.__teardownModule__);
- this.wrapper(module.__teardownModule__, module);
- events.endTest(module.__teardownModule__);
- }
- module.__status__ = 'done';
-}
-
-var runTestFile = function (filename, invokedFromIDE, name) {
- var runner = new Runner(new Collector(), invokedFromIDE);
+function runTestFile(filename, name) {
+ var runner = new Runner();
runner.runTestFile(filename, name);
runner.end();
+
return true;
}
-var getThread = function () {
- return thread;
-}
+Runner.prototype.skipFunction = function (func, message) {
+ events.setTest(func);
+ events.skip(message);
+ events.endTest(func);
+};
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/init.js b/services/sync/tps/extensions/mozmill/resource/modules/init.js
deleted file mode 100644
index 9ec4a4a29a75..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/init.js
+++ /dev/null
@@ -1,177 +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/. */
-
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-
-/**
-* Console listener which listens for error messages in the console and forwards
-* them to the Mozmill reporting system for output.
-*/
-function ConsoleListener() {
- this.register();
-}
-ConsoleListener.prototype = {
- observe: function(aMessage) {
- var msg = aMessage.message;
- var re = /^\[.*Error:.*(chrome|resource):\/\/.*/i;
- if (msg.match(re)) {
- frame.events.fail(aMessage);
- }
- },
- QueryInterface: function (iid) {
- if (!iid.equals(Components.interfaces.nsIConsoleListener) && !iid.equals(Components.interfaces.nsISupports)) {
- throw Components.results.NS_ERROR_NO_INTERFACE;
- }
- return this;
- },
- register: function() {
- var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService);
- aConsoleService.registerListener(this);
- },
- unregister: function() {
- var aConsoleService = Components.classes["@mozilla.org/consoleservice;1"]
- .getService(Components.interfaces.nsIConsoleService);
- aConsoleService.unregisterListener(this);
- }
-}
-
-// start listening
-var consoleListener = new ConsoleListener();
-
-var EXPORTED_SYMBOLS = ["mozmill"];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-var mozmill = Cu.import('resource://mozmill/modules/mozmill.js');
-
-// Observer for new top level windows
-var windowObserver = {
- observe: function(subject, topic, data) {
- attachEventListeners(subject);
- }
-};
-
-/**
- * Attach event listeners
- */
-function attachEventListeners(window) {
- // These are the event handlers
- function pageShowHandler(event) {
- var doc = event.originalTarget;
- var tab = window.gBrowser.getBrowserForDocument(doc);
-
- if (tab) {
- //log("*** Loaded tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- tab.mozmillDocumentLoaded = true;
- } else {
- //log("*** Loaded HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- doc.defaultView.mozmillDocumentLoaded = true;
- }
-
- // We need to add/remove the unload/pagehide event listeners to preserve caching.
- window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
- window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
- };
-
- var DOMContentLoadedHandler = function(event) {
- var errorRegex = /about:.+(error)|(blocked)\?/;
- if (errorRegex.exec(event.target.baseURI)) {
- // Wait about 1s to be sure the DOM is ready
- mozmill.utils.sleep(1000);
-
- var tab = window.gBrowser.getBrowserForDocument(event.target);
- if (tab)
- tab.mozmillDocumentLoaded = true;
-
- // We need to add/remove the unload event listener to preserve caching.
- window.gBrowser.addEventListener("beforeunload", beforeUnloadHandler, true);
- }
- };
-
- // beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
- // still use pagehide for cases when beforeunload doesn't get fired
- function beforeUnloadHandler(event) {
- var doc = event.originalTarget;
- var tab = window.gBrowser.getBrowserForDocument(event.target);
-
- if (tab) {
- //log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- tab.mozmillDocumentLoaded = false;
- } else {
- //log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- doc.defaultView.mozmillDocumentLoaded = false;
- }
-
- window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
- };
-
- var pageHideHandler = function(event) {
- // If event.persisted is false, the beforeUnloadHandler should fire
- // and there is no need for this event handler.
- if (event.persisted) {
- var doc = event.originalTarget;
- var tab = window.gBrowser.getBrowserForDocument(event.target);
-
- if (tab) {
- //log("*** Unload tab: location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- tab.mozmillDocumentLoaded = false;
- } else {
- //log("*** Unload HTML location=" + doc.location + ", baseURI=" + doc.baseURI + "\n");
- doc.defaultView.mozmillDocumentLoaded = false;
- }
-
- window.gBrowser.removeEventListener("beforeunload", beforeUnloadHandler, true);
- }
-
- };
-
- // Add the event handlers to the tabbedbrowser once its window has loaded
- window.addEventListener("load", function(event) {
- window.mozmillDocumentLoaded = true;
-
-
- if (window.gBrowser) {
- // Page is ready
- window.gBrowser.addEventListener("pageshow", pageShowHandler, true);
-
- // Note: Error pages will never fire a "load" event. For those we
- // have to wait for the "DOMContentLoaded" event. That's the final state.
- // Error pages will always have a baseURI starting with
- // "about:" followed by "error" or "blocked".
- window.gBrowser.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
-
- // Leave page (use caching)
- window.gBrowser.addEventListener("pagehide", pageHideHandler, true);
- }
- }, false);
-}
-
-/**
- * Initialize Mozmill
- */
-function initialize() {
- // Activate observer for new top level windows
- var observerService = Cc["@mozilla.org/observer-service;1"].
- getService(Ci.nsIObserverService);
- observerService.addObserver(windowObserver, "toplevel-window-ready", false);
-
- // Attach event listeners to all open windows
- var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
- getService(Ci.nsIWindowMediator).getEnumerator("");
- while (enumerator.hasMoreElements()) {
- var win = enumerator.getNext();
- attachEventListeners(win);
-
- // For windows or dialogs already open we have to explicitly set the property
- // otherwise windows which load really quick never gets the property set and
- // we fail to create the controller
- win.mozmillDocumentLoaded = true;
- };
-}
-
-initialize();
-
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/inspection.js b/services/sync/tps/extensions/mozmill/resource/modules/inspection.js
deleted file mode 100644
index 399952f122a1..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/inspection.js
+++ /dev/null
@@ -1,363 +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/. */
-
-var EXPORTED_SYMBOLS = ["inspectElement"]
-
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-var mozmill = {}; Components.utils.import('resource://mozmill/modules/mozmill.js', mozmill);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-var dom = {}; Components.utils.import('resource://mozmill/stdlib/dom.js', dom);
-var objects = {}; Components.utils.import('resource://mozmill/stdlib/objects.js', objects);
-var json2 = {}; Components.utils.import('resource://mozmill/stdlib/json2.js', json2);
-var withs = {}; Components.utils.import('resource://mozmill/stdlib/withs.js', withs);
-
-var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
-
-var isNotAnonymous = function (elem, result) {
- if (result == undefined) {
- var result = true;
- }
- if ( elem.parentNode ) {
- var p = elem.parentNode;
- return isNotAnonymous(p, result == arrays.inArray(p.childNodes, elem) == true);
- } else {
- return result;
- }
-}
-
-var elemIsAnonymous = function (elem) {
- if (elem.getAttribute('anonid') || !arrays.inArray(elem.parentNode.childNodes, elem)) {
- return true;
- }
- return false;
-}
-
-var getXPath = function (node, path) {
- path = path || [];
-
- if(node.parentNode) {
- path = getXPath(node.parentNode, path);
- }
-
- if(node.previousSibling) {
- var count = 1;
- var sibling = node.previousSibling
- do {
- if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {count++;}
- sibling = sibling.previousSibling;
- } while(sibling);
- if(count == 1) {count = null;}
- } else if(node.nextSibling) {
- var sibling = node.nextSibling;
- do {
- if(sibling.nodeType == 1 && sibling.nodeName == node.nodeName) {
- var count = 1;
- sibling = null;
- } else {
- var count = null;
- sibling = sibling.previousSibling;
- }
- } while(sibling);
- }
-
- if(node.nodeType == 1) {
- // if ($('absXpaths').checked){
- path.push(node.nodeName.toLowerCase() + (node.id ? "[@id='"+node.id+"']" : count > 0 ? "["+count+"]" : ''));
- // }
- // else{
- // path.push(node.nodeName.toLowerCase() + (node.id ? "" : count > 0 ? "["+count+"]" : ''));
- // }
- }
- return path;
-};
-
-function getXSPath(node){
- var xpArray = getXPath(node);
- var stringXpath = xpArray.join('/');
- stringXpath = '/'+stringXpath;
- stringXpath = stringXpath.replace('//','/');
- return stringXpath;
-}
-function getXULXpath (el, xml) {
- var xpath = '';
- var pos, tempitem2;
-
- while(el !== xml.documentElement) {
- pos = 0;
- tempitem2 = el;
- while(tempitem2) {
- if (tempitem2.nodeType === 1 && tempitem2.nodeName === el.nodeName) {
- // If it is ELEMENT_NODE of the same name
- pos += 1;
- }
- tempitem2 = tempitem2.previousSibling;
- }
-
- xpath = "*[name()='"+el.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']["+pos+']'+'/'+xpath;
-
- el = el.parentNode;
- }
- xpath = '/*'+"[name()='"+xml.documentElement.nodeName+"' and namespace-uri()='"+(el.namespaceURI===null?'':el.namespaceURI)+"']"+'/'+xpath;
- xpath = xpath.replace(/\/$/, '');
- return xpath;
-}
-
-var getDocument = function (elem) {
- while (elem.parentNode) {
- var elem = elem.parentNode;
- }
- return elem;
-}
-
-var getTopWindow = function(doc) {
- return utils.getChromeWindow(doc.defaultView);
-}
-
-var attributeToIgnore = ['focus', 'focused', 'selected', 'select', 'flex', // General Omissions
- 'linkedpanel', 'last-tab', 'afterselected', // From Tabs UI, thanks Farhad
- 'style', // Gets set dynamically all the time, also effected by dx display code
- ];
-
-var getUniqueAttributesReduction = function (attributes, node) {
- for (var i in attributes) {
- if ( node.getAttribute(i) == attributes[i] || arrays.inArray(attributeToIgnore, i) || arrays.inArray(attributeToIgnore, attributes[i]) || i == 'id') {
- delete attributes[i];
- }
- }
- return attributes;
-}
-
-var getLookupExpression = function (_document, elem) {
- expArray = [];
- while ( elem.parentNode ) {
- var exp = getLookupForElem(_document, elem);
- expArray.push(exp);
- var elem = elem.parentNode;
- }
- expArray.reverse();
- return '/' + expArray.join('/');
-}
-
-var getLookupForElem = function (_document, elem) {
- if ( !elemIsAnonymous(elem) ) {
- if (elem.id != "" && !withs.startsWith(elem.id, 'panel')) {
- identifier = {'name':'id', 'value':elem.id};
- } else if ((elem.name != "") && (typeof(elem.name) != "undefined")) {
- identifier = {'name':'name', 'value':elem.name};
- } else {
- identifier = null;
- }
-
- if (identifier) {
- var result = {'id':elementslib._byID, 'name':elementslib._byName}[identifier.name](_document, elem.parentNode, identifier.value);
- if ( typeof(result != 'array') ) {
- return identifier.name+'('+json2.JSON.stringify(identifier.value)+')';
- }
- }
-
- // At this point there is either no identifier or it returns multiple
- var parse = [n for each (n in elem.parentNode.childNodes) if
- (n.getAttribute && n != elem)
- ];
- parse.unshift(dom.getAttributes(elem));
- var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
-
- if (!result) {
- var result = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
- }
-
- if (!identifier && typeof(result) == 'array' ) {
- return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(result, elem)+']'
- } else {
- var aresult = elementslib._byAttrib(elem.parentNode, uniqueAttributes);
- if ( typeof(aresult != 'array') ) {
- if (objects.getLength(uniqueAttributes) == 0) {
- return '['+arrays.indexOf(elem.parentNode.childNodes, elem)+']'
- }
- return json2.JSON.stringify(uniqueAttributes)
- } else if ( result.length > aresult.length ) {
- return json2.JSON.stringify(uniqueAttributes) + '['+arrays.indexOf(aresult, elem)+']'
- } else {
- return identifier.name+'('+json2.JSON.stringify(identifier.value)+')' + '['+arrays.indexOf(result, elem)+']'
- }
- }
-
- } else {
- // Handle Anonymous Nodes
- var parse = [n for each (n in _document.getAnonymousNodes(elem.parentNode)) if
- (n.getAttribute && n != elem)
- ];
- parse.unshift(dom.getAttributes(elem));
- var uniqueAttributes = parse.reduce(getUniqueAttributesReduction);
- if (uniqueAttributes.anonid && typeof(elementslib._byAnonAttrib(_document,
- elem.parentNode, {'anonid':uniqueAttributes.anonid})) != 'array') {
- uniqueAttributes = {'anonid':uniqueAttributes.anonid};
- }
-
- if (objects.getLength(uniqueAttributes) == 0) {
- return 'anon(['+arrays.indexOf(_document.getAnonymousNodes(elem.parentNode), elem)+'])';
- } else if (arrays.inArray(uniqueAttributes, 'anonid')) {
- return 'anon({"anonid":"'+uniqueAttributes['anonid']+'"})';
- } else {
- return 'anon('+json2.JSON.stringify(uniqueAttributes)+')';
- }
-
- }
- return 'broken '+elemIsAnonymous(elem)
-}
-
-var removeHTMLTags = function(str){
- str = str.replace(/&(lt|gt);/g, function (strMatch, p1){
- return (p1 == "lt")? "<" : ">";
- });
- var strTagStrippedText = str.replace(/<\/?[^>]+(>|$)/g, "");
- strTagStrippedText = strTagStrippedText.replace(/ /g,"");
- return strTagStrippedText;
-}
-
-var isMagicAnonymousDiv = function (_document, node) {
- if (node.getAttribute && node.getAttribute('class') == 'anonymous-div') {
- if (!arrays.inArray(node.parentNode.childNodes, node) && (_document.getAnonymousNodes(node) == null ||
- !arrays.inArray(_document.getAnonymousNodes(node), node) ) ) {
- return true;
- }
- }
- return false;
-}
-
-var copyToClipboard = function(str){
- const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"] .getService(Components.interfaces.nsIClipboardHelper);
- gClipboardHelper.copyString(str, _window.document);
-}
-
-var getControllerAndDocument = function (_document, _window) {
- var windowtype = _window.document.documentElement.getAttribute('windowtype');
- var controllerString, documentString, activeTab;
-
- // TODO replace with object based cases
- switch(windowtype) {
- case 'navigator:browser':
- controllerString = 'mozmill.getBrowserController()';
- activeTab = mozmill.getBrowserController().tabs.activeTab;
- break;
- case 'Browser:Preferences':
- controllerString = 'mozmill.getPreferencesController()';
- break;
- case 'Extension:Manager':
- controllerString = 'mozmill.getAddonsController()';
- break;
- default:
- if(windowtype)
- controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByType("' + windowtype + '"))';
- else if(_window.document.title)
- controllerString = 'new mozmill.controller.MozMillController(mozmill.utils.getWindowByTitle("'+_window.document.title+'"))';
- else
- controllerString = 'Cannot find window';
- break;
- }
-
- if(activeTab == _document) {
- documentString = 'controller.tabs.activeTab';
- } else if(activeTab == _document.defaultView.top.document) {
- // if this document is from an iframe in the active tab
- var stub = getDocumentStub(_document, activeTab.defaultView);
- documentString = 'controller.tabs.activeTab.defaultView' + stub;
- } else {
- var stub = getDocumentStub(_document, _window);
- if(stub)
- documentString = 'controller.window' + stub;
- else
- documentString = 'Cannot find document';
- }
- return {'controllerString':controllerString, 'documentString':documentString}
-}
-
-getDocumentStub = function( _document, _window) {
- if(_window.document == _document)
- return '.document';
- for(var i = 0; i < _window.frames.length; i++) {
- var stub = getDocumentStub(_document, _window.frames[i]);
- if (stub)
- return '.frames['+i+']' + stub;
- }
- return '';
-}
-
-var inspectElement = function(e){
- if (e.originalTarget != undefined) {
- target = e.originalTarget;
- } else {
- target = e.target;
- }
-
- //Element highlighting
- try {
- if (this.lastEvent)
- this.lastEvent.target.style.outline = "";
- } catch(err) {}
-
- this.lastEvent = e;
-
- try {
- e.target.style.outline = "1px solid darkblue";
- } catch(err){}
-
- var _document = getDocument(target);
-
-
- if (isMagicAnonymousDiv(_document, target)) {
- target = target.parentNode;
- }
-
- var windowtype = _document.documentElement.getAttribute('windowtype');
- var _window = getTopWindow(_document);
- r = getControllerAndDocument(_document, _window);
-
- // displayText = "Controller: " + r.controllerString + '\n\n';
- if ( isNotAnonymous(target) ) {
- // Logic for which identifier to use is duplicated above
- if (target.id != "" && !withs.startsWith(target.id, 'panel')) {
- elemText = "new elementslib.ID("+ r.documentString + ', "' + target.id + '")';
- var telem = new elementslib.ID(_document, target.id);
- } else if ((target.name != "") && (typeof(target.name) != "undefined")) {
- elemText = "new elementslib.Name("+ r.documentString + ', "' + target.name + '")';
- var telem = new elementslib.Name(_document, target.name);
- } else if (target.nodeName == "A") {
- var linkText = removeHTMLTags(target.innerHTML);
- elemText = "new elementslib.Link("+ r.documentString + ', "' + linkText + '")';
- var telem = new elementslib.Link(_document, linkText);
- }
- }
- // Fallback on XPath
- if (telem == undefined || telem.getNode() != target) {
- if (windowtype == null) {
- var stringXpath = getXSPath(target);
- } else {
- var stringXpath = getXULXpath(target, _document);
- }
- var telem = new elementslib.XPath(_document, stringXpath);
- if ( telem.getNode() == target ) {
- elemText = "new elementslib.XPath("+ r.documentString + ', "' + stringXpath + '")';
- }
- }
- // Fallback to Lookup
- if (telem == undefined || telem.getNode() != target) {
- var exp = getLookupExpression(_document, target);
- elemText = "new elementslib.Lookup("+ r.documentString + ", '" + exp + "')";
- var telem = new elementslib.Lookup(_document, exp);
- }
-
- return {'validation':( target == telem.getNode() ),
- 'elementText':elemText,
- 'elementType':telem.constructor.name,
- 'controllerText':r.controllerString,
- 'documentString':r.documentString,
- }
-}
-
-
-
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/jum.js b/services/sync/tps/extensions/mozmill/resource/modules/jum.js
deleted file mode 100644
index b451a97a0f40..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/jum.js
+++ /dev/null
@@ -1,231 +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/. */
-
-var EXPORTED_SYMBOLS = ["assert", "assertTrue", "assertFalse", "assertEquals", "assertNotEquals",
- "assertNull", "assertNotNull", "assertUndefined", "assertNotUndefined",
- "assertNaN", "assertNotNaN", "assertArrayContains", "fail", "pass"];
-
-
-// Array.isArray comes with JavaScript 1.8.5 (Firefox 4)
-// cf. https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/isArray
-Array.isArray = Array.isArray || function(o) { return Object.prototype.toString.call(o) === '[object Array]'; };
-
-var frame = {}; Components.utils.import("resource://mozmill/modules/frame.js", frame);
-
-var ifJSONable = function (v) {
- if (typeof(v) == 'function') {
- return undefined;
- } else {
- return v;
- }
-}
-
-var assert = function (booleanValue, comment) {
- if (booleanValue) {
- frame.events.pass({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
- return true;
- } else {
- frame.events.fail({'function':'jum.assert', 'value':ifJSONable(booleanValue), 'comment':comment});
- return false;
- }
-}
-
-var assertTrue = function (booleanValue, comment) {
- if (typeof(booleanValue) != 'boolean') {
- frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
- 'message':'Bad argument, value type '+typeof(booleanValue)+' != "boolean"',
- 'comment':comment});
- return false;
- }
-
- if (booleanValue) {
- frame.events.pass({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
- 'comment':comment});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertTrue', 'value':ifJSONable(booleanValue),
- 'comment':comment});
- return false;
- }
-}
-
-var assertFalse = function (booleanValue, comment) {
- if (typeof(booleanValue) != 'boolean') {
- frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
- 'message':'Bad argument, value type '+typeof(booleanValue)+' != "boolean"',
- 'comment':comment});
- return false;
- }
-
- if (!booleanValue) {
- frame.events.pass({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
- 'comment':comment});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertFalse', 'value':ifJSONable(booleanValue),
- 'comment':comment});
- return false;
- }
-}
-
-var assertEquals = function (value1, value2, comment) {
- // Case where value1 is an array
- if (Array.isArray(value1)) {
-
- if (!Array.isArray(value2)) {
- frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
- 'message':'Bad argument, value1 is an array and value2 type ' +
- typeof(value2)+' != "array"',
- 'value2':ifJSONable(value2)});
- return false;
- }
-
- if (value1.length != value2.length) {
- frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
- 'message':"The arrays do not have the same length",
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return false;
- }
-
- for (var i = 0; i < value1.length; i++) {
- if (value1[i] !== value2[i]) {
- frame.events.fail(
- {'function':'jum.assertEquals', 'comment':comment,
- 'message':"The element of the arrays are different at index " + i,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return false;
- }
- }
- frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return true;
- }
-
- // Case where value1 is not an array
- if (value1 == value2) {
- frame.events.pass({'function':'jum.assertEquals', 'comment':comment,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertEquals', 'comment':comment,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return false;
- }
-}
-
-var assertNotEquals = function (value1, value2, comment) {
- if (value1 != value2) {
- frame.events.pass({'function':'jum.assertNotEquals', 'comment':comment,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNotEquals', 'comment':comment,
- 'value1':ifJSONable(value1), 'value2':ifJSONable(value2)});
- return false;
- }
-}
-
-var assertNull = function (value, comment) {
- if (value == null) {
- frame.events.pass({'function':'jum.assertNull', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNull', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertNotNull = function (value, comment) {
- if (value != null) {
- frame.events.pass({'function':'jum.assertNotNull', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNotNull', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertUndefined = function (value, comment) {
- if (value == undefined) {
- frame.events.pass({'function':'jum.assertUndefined', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertUndefined', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertNotUndefined = function (value, comment) {
- if (value != undefined) {
- frame.events.pass({'function':'jum.assertNotUndefined', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNotUndefined', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertNaN = function (value, comment) {
- if (isNaN(value)) {
- frame.events.pass({'function':'jum.assertNaN', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNaN', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertNotNaN = function (value, comment) {
- if (!isNaN(value)) {
- frame.events.pass({'function':'jum.assertNotNaN', 'comment':comment,
- 'value':ifJSONable(value)});
- return true;
- } else {
- frame.events.fail({'function':'jum.assertNotNaN', 'comment':comment,
- 'value':ifJSONable(value)});
- return false;
- }
-}
-
-var assertArrayContains = function(array, value, comment) {
- if (!Array.isArray(array)) {
- frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
- 'message':'Bad argument, value type '+typeof(array)+' != "array"',
- 'value':ifJSONable(array)});
- return false;
- }
-
- for (var i = 0; i < array.length; i++) {
- if (array[i] === value) {
- frame.events.pass({'function':'jum.assertArrayContains', 'comment':comment,
- 'value1':ifJSONable(array), 'value2':ifJSONable(value)});
- return true;
- }
- }
- frame.events.fail({'function':'jum.assertArrayContains', 'comment':comment,
- 'value1':ifJSONable(array), 'value2':ifJSONable(value)});
- return false;
-}
-
-var fail = function (comment) {
- frame.events.fail({'function':'jum.fail', 'comment':comment});
- return false;
-}
-
-var pass = function (comment) {
- frame.events.pass({'function':'jum.pass', 'comment':comment});
- return true;
-}
-
-
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/l10n.js b/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
index c764f7a71dbb..63a35542166b 100644
--- a/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
+++ b/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
@@ -1,12 +1,14 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* @namespace Defines useful methods to work with localized content
*/
var l10n = exports;
+Cu.import("resource://gre/modules/Services.jsm");
+
/**
* Retrieve the localized content for a given DTD entity
*
@@ -54,14 +56,11 @@ function getEntity(aDTDs, aEntityId) {
* @returns {String} Value of the requested property
*/
function getProperty(aURL, aProperty) {
- var sbs = Cc["@mozilla.org/intl/stringbundle;1"].
- getService(Ci.nsIStringBundleService);
- var bundle = sbs.createBundle(aURL);
+ var bundle = Services.strings.createBundle(aURL);
try {
return bundle.GetStringFromName(aProperty);
- }
- catch (ex) {
+ } catch (ex) {
throw new Error("Unkown property '" + aProperty + "'");
}
}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/mozelement.js b/services/sync/tps/extensions/mozmill/resource/modules/mozelement.js
deleted file mode 100644
index 07b122d2406c..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/mozelement.js
+++ /dev/null
@@ -1,668 +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/. */
-
-var EXPORTED_SYMBOLS = ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup",
- "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList",
- "MozMillTextBox", "subclasses",
- ];
-
-var EventUtils = {}; Components.utils.import('resource://mozmill/stdlib/EventUtils.js', EventUtils);
-var frame = {}; Components.utils.import('resource://mozmill/modules/frame.js', frame);
-var utils = {}; Components.utils.import('resource://mozmill/modules/utils.js', utils);
-var elementslib = {}; Components.utils.import('resource://mozmill/modules/elementslib.js', elementslib);
-
-// A list of all the subclasses available. Shared modules can push their own subclasses onto this list
-var subclasses = [MozMillCheckBox, MozMillRadio, MozMillDropList, MozMillTextBox];
-
-/**
- * createInstance()
- *
- * Returns an new instance of a MozMillElement
- * The type of the element is automatically determined
- */
-function createInstance(locatorType, locator, elem) {
- if (elem) {
- var args = {"element":elem};
- for (var i = 0; i < subclasses.length; ++i) {
- if (subclasses[i].isType(elem)) {
- return new subclasses[i](locatorType, locator, args);
- }
- }
- if (MozMillElement.isType(elem)) return new MozMillElement(locatorType, locator, args);
- }
- throw new Error("could not find element " + locatorType + ": " + locator);
-};
-
-var Elem = function(node) {
- return createInstance("Elem", node, node);
-};
-
-var Selector = function(_document, selector, index) {
- return createInstance("Selector", selector, elementslib.Selector(_document, selector, index));
-};
-
-var ID = function(_document, nodeID) {
- return createInstance("ID", nodeID, elementslib.ID(_document, nodeID));
-};
-
-var Link = function(_document, linkName) {
- return createInstance("Link", linkName, elementslib.Link(_document, linkName));
-};
-
-var XPath = function(_document, expr) {
- return createInstance("XPath", expr, elementslib.XPath(_document, expr));
-};
-
-var Name = function(_document, nName) {
- return createInstance("Name", nName, elementslib.Name(_document, nName));
-};
-
-var Lookup = function(_document, expression) {
- return createInstance("Lookup", expression, elementslib.Lookup(_document, expression));
-};
-
-
-/**
- * MozMillElement
- * The base class for all mozmill elements
- */
-function MozMillElement(locatorType, locator, args) {
- args = args || {};
- this._locatorType = locatorType;
- this._locator = locator;
- this._element = args["element"];
- this._document = args["document"];
- this._owner = args["owner"];
- // Used to maintain backwards compatibility with controller.js
- this.isElement = true;
-}
-
-// Static method that returns true if node is of this element type
-MozMillElement.isType = function(node) {
- return true;
-};
-
-// This getter is the magic behind lazy loading (note distinction between _element and element)
-MozMillElement.prototype.__defineGetter__("element", function() {
- if (this._element == undefined) {
- if (elementslib[this._locatorType]) {
- this._element = elementslib[this._locatorType](this._document, this._locator);
- } else if (this._locatorType == "Elem") {
- this._element = this._locator;
- } else {
- throw new Error("Unknown locator type: " + this._locatorType);
- }
- }
- return this._element;
-});
-
-// Returns the actual wrapped DOM node
-MozMillElement.prototype.getNode = function() {
- return this.element;
-};
-
-MozMillElement.prototype.getInfo = function() {
- return this._locatorType + ": " + this._locator;
-};
-
-/**
- * Sometimes an element which once existed will no longer exist in the DOM
- * This function re-searches for the element
- */
-MozMillElement.prototype.exists = function() {
- this._element = undefined;
- if (this.element) return true;
- return false;
-};
-
-/**
- * Synthesize a keypress event on the given element
- *
- * @param {string} aKey
- * Key to use for synthesizing the keypress event. It can be a simple
- * character like "k" or a string like "VK_ESCAPE" for command keys
- * @param {object} aModifiers
- * Information about the modifier keys to send
- * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
- * [optional - default: false]
- * altKey - Hold down the alt key
- * [optional - default: false]
- * ctrlKey - Hold down the ctrl key
- * [optional - default: false]
- * metaKey - Hold down the meta key (command key on Mac)
- * [optional - default: false]
- * shiftKey - Hold down the shift key
- * [optional - default: false]
- * @param {object} aExpectedEvent
- * Information about the expected event to occur
- * Elements: target - Element which should receive the event
- * [optional - default: current element]
- * type - Type of the expected key event
- */
-MozMillElement.prototype.keypress = function(aKey, aModifiers, aExpectedEvent) {
- if (!this.element) {
- throw new Error("Could not find element " + this.getInfo());
- }
-
- var win = this.element.ownerDocument? this.element.ownerDocument.defaultView : this.element;
- this.element.focus();
-
- if (aExpectedEvent) {
- var target = aExpectedEvent.target? aExpectedEvent.target.getNode() : this.element;
- EventUtils.synthesizeKeyExpectEvent(aKey, aModifiers || {}, target, aExpectedEvent.type,
- "MozMillElement.keypress()", win);
- } else {
- EventUtils.synthesizeKey(aKey, aModifiers || {}, win);
- }
-
- frame.events.pass({'function':'MozMillElement.keypress()'});
- return true;
-};
-
-
-/**
- * Synthesize a general mouse event on the given element
- *
- * @param {ElemBase} aTarget
- * Element which will receive the mouse event
- * @param {number} aOffsetX
- * Relative x offset in the elements bounds to click on
- * @param {number} aOffsetY
- * Relative y offset in the elements bounds to click on
- * @param {object} aEvent
- * Information about the event to send
- * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
- * [optional - default: false]
- * altKey - Hold down the alt key
- * [optional - default: false]
- * button - Mouse button to use
- * [optional - default: 0]
- * clickCount - Number of counts to click
- * [optional - default: 1]
- * ctrlKey - Hold down the ctrl key
- * [optional - default: false]
- * metaKey - Hold down the meta key (command key on Mac)
- * [optional - default: false]
- * shiftKey - Hold down the shift key
- * [optional - default: false]
- * type - Type of the mouse event ('click', 'mousedown',
- * 'mouseup', 'mouseover', 'mouseout')
- * [optional - default: 'mousedown' + 'mouseup']
- * @param {object} aExpectedEvent
- * Information about the expected event to occur
- * Elements: target - Element which should receive the event
- * [optional - default: current element]
- * type - Type of the expected mouse event
- */
-MozMillElement.prototype.mouseEvent = function(aOffsetX, aOffsetY, aEvent, aExpectedEvent) {
- if (!this.element) {
- throw new Error(arguments.callee.name + ": could not find element " + this.getInfo());
- }
-
- // If no offset is given we will use the center of the element to click on.
- var rect = this.element.getBoundingClientRect();
- if (isNaN(aOffsetX)) {
- aOffsetX = rect.width / 2;
- }
- if (isNaN(aOffsetY)) {
- aOffsetY = rect.height / 2;
- }
-
- // Scroll element into view otherwise the click will fail
- if (this.element.scrollIntoView) {
- this.element.scrollIntoView();
- }
-
- if (aExpectedEvent) {
- // The expected event type has to be set
- if (!aExpectedEvent.type)
- throw new Error(arguments.callee.name + ": Expected event type not specified");
-
- // If no target has been specified use the specified element
- var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : this.element;
- if (!target) {
- throw new Error(arguments.callee.name + ": could not find element " + aExpectedEvent.target.getInfo());
- }
-
- EventUtils.synthesizeMouseExpectEvent(this.element, aOffsetX, aOffsetY, aEvent,
- target, aExpectedEvent.event,
- "MozMillElement.mouseEvent()",
- this.element.ownerDocument.defaultView);
- } else {
- EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
- this.element.ownerDocument.defaultView);
- }
-};
-
-/**
- * Synthesize a mouse click event on the given element
- */
-MozMillElement.prototype.click = function(left, top, expectedEvent) {
- // Handle menu items differently
- if (this.element && this.element.tagName == "menuitem") {
- this.element.click();
- } else {
- this.mouseEvent(left, top, {}, expectedEvent);
- }
-
- frame.events.pass({'function':'MozMillElement.click()'});
-};
-
-/**
- * Synthesize a double click on the given element
- */
-MozMillElement.prototype.doubleClick = function(left, top, expectedEvent) {
- this.mouseEvent(left, top, {clickCount: 2}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.doubleClick()'});
- return true;
-};
-
-/**
- * Synthesize a mouse down event on the given element
- */
-MozMillElement.prototype.mouseDown = function (button, left, top, expectedEvent) {
- this.mouseEvent(left, top, {button: button, type: "mousedown"}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.mouseDown()'});
- return true;
-};
-
-/**
- * Synthesize a mouse out event on the given element
- */
-MozMillElement.prototype.mouseOut = function (button, left, top, expectedEvent) {
- this.mouseEvent(left, top, {button: button, type: "mouseout"}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.mouseOut()'});
- return true;
-};
-
-/**
- * Synthesize a mouse over event on the given element
- */
-MozMillElement.prototype.mouseOver = function (button, left, top, expectedEvent) {
- this.mouseEvent(left, top, {button: button, type: "mouseover"}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.mouseOver()'});
- return true;
-};
-
-/**
- * Synthesize a mouse up event on the given element
- */
-MozMillElement.prototype.mouseUp = function (button, left, top, expectedEvent) {
- this.mouseEvent(left, top, {button: button, type: "mouseup"}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.mouseUp()'});
- return true;
-};
-
-/**
- * Synthesize a mouse middle click event on the given element
- */
-MozMillElement.prototype.middleClick = function(left, top, expectedEvent) {
- this.mouseEvent(left, top, {button: 1}, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.middleClick()'});
- return true;
-};
-
-/**
- * Synthesize a mouse right click event on the given element
- */
-MozMillElement.prototype.rightClick = function(left, top, expectedEvent) {
- this.mouseEvent(left, top, {type : "contextmenu", button: 2 }, expectedEvent);
-
- frame.events.pass({'function':'MozMillElement.rightClick()'});
- return true;
-};
-
-MozMillElement.prototype.waitForElement = function(timeout, interval) {
- var elem = this;
- utils.waitFor(function() {
- return elem.exists();
- }, "Timeout exceeded for waitForElement " + this.getInfo(), timeout, interval);
-
- frame.events.pass({'function':'MozMillElement.waitForElement()'});
-};
-
-MozMillElement.prototype.waitForElementNotPresent = function(timeout, interval) {
- var elem = this;
- utils.waitFor(function() {
- return !elem.exists();
- }, "Timeout exceeded for waitForElementNotPresent " + this.getInfo(), timeout, interval);
-
- frame.events.pass({'function':'MozMillElement.waitForElementNotPresent()'});
-};
-
-MozMillElement.prototype.waitThenClick = function (timeout, interval, left, top, expectedEvent) {
- this.waitForElement(timeout, interval);
- this.click(left, top, expectedEvent);
-};
-
-// Dispatches an HTMLEvent
-MozMillElement.prototype.dispatchEvent = function (eventType, canBubble, modifiers) {
- canBubble = canBubble || true;
- var evt = this.element.ownerDocument.createEvent('HTMLEvents');
- evt.shiftKey = modifiers["shift"];
- evt.metaKey = modifiers["meta"];
- evt.altKey = modifiers["alt"];
- evt.ctrlKey = modifiers["ctrl"];
- evt.initEvent(eventType, canBubble, true);
- this.element.dispatchEvent(evt);
-};
-
-
-//---------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillCheckBox
- * Checkbox element, inherits from MozMillElement
- */
-MozMillCheckBox.prototype = new MozMillElement();
-MozMillCheckBox.prototype.parent = MozMillElement.prototype;
-MozMillCheckBox.prototype.constructor = MozMillCheckBox;
-function MozMillCheckBox(locatorType, locator, args) {
- this.parent.constructor.call(this, locatorType, locator, args);
-}
-
-// Static method returns true if node is this type of element
-MozMillCheckBox.isType = function(node) {
- if ((node.localName.toLowerCase() == "input" && node.getAttribute("type") == "checkbox") ||
- (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'checkbox') ||
- (node.localName.toLowerCase() == 'checkbox')) {
- return true;
- }
- return false;
-};
-
-/**
- * Enable/Disable a checkbox depending on the target state
- */
-MozMillCheckBox.prototype.check = function(state) {
- var result = false;
-
- if (!this.element) {
- throw new Error("could not find element " + this.getInfo());
- return false;
- }
-
- // If we have a XUL element, unwrap its XPCNativeWrapper
- if (this.element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
- this.element = utils.unwrapNode(this.element);
- }
-
- state = (typeof(state) == "boolean") ? state : false;
- if (state != this.element.checked) {
- this.click();
- var element = this.element;
- utils.waitFor(function() {
- return element.checked == state;
- }, "Checkbox " + this.getInfo() + " could not be checked/unchecked", 500);
-
- result = true;
- }
-
- frame.events.pass({'function':'MozMillCheckBox.check(' + this.getInfo() + ', state: ' + state + ')'});
- return result;
-};
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillRadio
- * Radio button inherits from MozMillElement
- */
-MozMillRadio.prototype = new MozMillElement();
-MozMillRadio.prototype.parent = MozMillElement.prototype;
-MozMillRadio.prototype.constructor = MozMillRadio;
-function MozMillRadio(locatorType, locator, args) {
- this.parent.constructor.call(this, locatorType, locator, args);
-}
-
-// Static method returns true if node is this type of element
-MozMillRadio.isType = function(node) {
- if ((node.localName.toLowerCase() == 'input' && node.getAttribute('type') == 'radio') ||
- (node.localName.toLowerCase() == 'toolbarbutton' && node.getAttribute('type') == 'radio') ||
- (node.localName.toLowerCase() == 'radio') ||
- (node.localName.toLowerCase() == 'radiogroup')) {
- return true;
- }
- return false;
-};
-
-/**
- * Select the given radio button
- *
- * index - Specifies which radio button in the group to select (only applicable to radiogroup elements)
- * Defaults to the first radio button in the group
- */
-MozMillRadio.prototype.select = function(index) {
- if (!this.element) {
- throw new Error("could not find element " + this.getInfo());
- }
-
- if (this.element.localName.toLowerCase() == "radiogroup") {
- var element = this.element.getElementsByTagName("radio")[index || 0];
- new MozMillRadio("Elem", element).click();
- } else {
- var element = this.element;
- this.click();
- }
-
- utils.waitFor(function() {
- // If we have a XUL element, unwrap its XPCNativeWrapper
- if (element.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
- element = utils.unwrapNode(element);
- return element.selected == true;
- }
- return element.checked == true;
- }, "Radio button " + this.getInfo() + " could not be selected", 500);
-
- frame.events.pass({'function':'MozMillRadio.select(' + this.getInfo() + ')'});
- return true;
-};
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillDropList
- * DropList inherits from MozMillElement
- */
-MozMillDropList.prototype = new MozMillElement();
-MozMillDropList.prototype.parent = MozMillElement.prototype;
-MozMillDropList.prototype.constructor = MozMillDropList;
-function MozMillDropList(locatorType, locator, args) {
- this.parent.constructor.call(this, locatorType, locator, args);
-};
-
-// Static method returns true if node is this type of element
-MozMillDropList.isType = function(node) {
- if ((node.localName.toLowerCase() == 'toolbarbutton' && (node.getAttribute('type') == 'menu' || node.getAttribute('type') == 'menu-button')) ||
- (node.localName.toLowerCase() == 'menu') ||
- (node.localName.toLowerCase() == 'menulist') ||
- (node.localName.toLowerCase() == 'select' )) {
- return true;
- }
- return false;
-};
-
-/* Select the specified option and trigger the relevant events of the element */
-MozMillDropList.prototype.select = function (indx, option, value) {
- if (!this.element){
- throw new Error("Could not find element " + this.getInfo());
- }
-
- //if we have a select drop down
- if (this.element.localName.toLowerCase() == "select"){
- var item = null;
-
- // The selected item should be set via its index
- if (indx != undefined) {
- // Resetting a menulist has to be handled separately
- if (indx == -1) {
- this.dispatchEvent('focus', false);
- this.element.selectedIndex = indx;
- this.dispatchEvent('change', true);
-
- frame.events.pass({'function':'MozMillDropList.select()'});
- return true;
- } else {
- item = this.element.options.item(indx);
- }
- } else {
- for (var i = 0; i < this.element.options.length; i++) {
- var entry = this.element.options.item(i);
- if (option != undefined && entry.innerHTML == option ||
- value != undefined && entry.value == value) {
- item = entry;
- break;
- }
- }
- }
-
- // Click the item
- try {
- // EventUtils.synthesizeMouse doesn't work.
- this.dispatchEvent('focus', false);
- item.selected = true;
- this.dispatchEvent('change', true);
-
- frame.events.pass({'function':'MozMillDropList.select()'});
- return true;
- } catch (ex) {
- throw new Error("No item selected for element " + this.getInfo());
- return false;
- }
- }
- //if we have a xul menupopup select accordingly
- else if (this.element.namespaceURI.toLowerCase() == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
- var ownerDoc = this.element.ownerDocument;
- // Unwrap the XUL element's XPCNativeWrapper
- this.element = utils.unwrapNode(this.element);
- // Get the list of menuitems
- menuitems = this.element.getElementsByTagName("menupopup")[0].getElementsByTagName("menuitem");
-
- var item = null;
-
- if (indx != undefined) {
- if (indx == -1) {
- this.dispatchEvent('focus', false);
- this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild = null;
- this.dispatchEvent('change', true);
-
- frame.events.pass({'function':'MozMillDropList.select()'});
- return true;
- } else {
- item = menuitems[indx];
- }
- } else {
- for (var i = 0; i < menuitems.length; i++) {
- var entry = menuitems[i];
- if (option != undefined && entry.label == option ||
- value != undefined && entry.value == value) {
- item = entry;
- break;
- }
- }
- }
-
- // Click the item
- try {
- EventUtils.synthesizeMouse(this.element, 1, 1, {}, ownerDoc.defaultView);
-
- // Scroll down until item is visible
- for (var i = 0; i <= menuitems.length; ++i) {
- var selected = this.element.boxObject.QueryInterface(Components.interfaces.nsIMenuBoxObject).activeChild;
- if (item == selected) {
- break;
- }
- EventUtils.synthesizeKey("VK_DOWN", {}, ownerDoc.defaultView);
- }
-
- EventUtils.synthesizeMouse(item, 1, 1, {}, ownerDoc.defaultView);
-
- frame.events.pass({'function':'MozMillDropList.select()'});
- return true;
- } catch (ex) {
- throw new Error('No item selected for element ' + this.getInfo());
- return false;
- }
- }
-};
-
-
-//----------------------------------------------------------------------------------------------------------------------------------------
-
-
-/**
- * MozMillTextBox
- * TextBox inherits from MozMillElement
- */
-MozMillTextBox.prototype = new MozMillElement();
-MozMillTextBox.prototype.parent = MozMillElement.prototype;
-MozMillTextBox.prototype.constructor = MozMillTextBox;
-function MozMillTextBox(locatorType, locator, args) {
- this.parent.constructor.call(this, locatorType, locator, args);
-};
-
-// Static method returns true if node is this type of element
-MozMillTextBox.isType = function(node) {
- if ((node.localName.toLowerCase() == 'input' && (node.getAttribute('type') == 'text' || node.getAttribute('type') == 'search')) ||
- (node.localName.toLowerCase() == 'textarea') ||
- (node.localName.toLowerCase() == 'textbox')) {
- return true;
- }
- return false;
-};
-
-/**
- * Synthesize keypress events for each character on the given element
- *
- * @param {string} aText
- * The text to send as single keypress events
- * @param {object} aModifiers
- * Information about the modifier keys to send
- * Elements: accelKey - Hold down the accelerator key (ctrl/meta)
- * [optional - default: false]
- * altKey - Hold down the alt key
- * [optional - default: false]
- * ctrlKey - Hold down the ctrl key
- * [optional - default: false]
- * metaKey - Hold down the meta key (command key on Mac)
- * [optional - default: false]
- * shiftKey - Hold down the shift key
- * [optional - default: false]
- * @param {object} aExpectedEvent
- * Information about the expected event to occur
- * Elements: target - Element which should receive the event
- * [optional - default: current element]
- * type - Type of the expected key event
- */
-MozMillTextBox.prototype.sendKeys = function (aText, aModifiers, aExpectedEvent) {
- if (!this.element) {
- throw new Error("could not find element " + this.getInfo());
- }
-
- var element = this.element;
- Array.forEach(aText, function(letter) {
- var win = element.ownerDocument? element.ownerDocument.defaultView : element;
- element.focus();
-
- if (aExpectedEvent) {
- var target = aExpectedEvent.target ? aExpectedEvent.target.getNode() : element;
- EventUtils.synthesizeKeyExpectEvent(letter, aModifiers || {}, target, aExpectedEvent.type,
- "MozMillTextBox.sendKeys()", win);
- } else {
- EventUtils.synthesizeKey(letter, aModifiers || {}, win);
- }
- });
-
- frame.events.pass({'function':'MozMillTextBox.type()'});
- return true;
-};
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/stack.js b/services/sync/tps/extensions/mozmill/resource/modules/stack.js
new file mode 100644
index 000000000000..889316bf18e4
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/stack.js
@@ -0,0 +1,43 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ['findCallerFrame'];
+
+
+/**
+ * @namespace Defines utility methods for handling stack frames
+ */
+
+/**
+ * Find the frame to use for logging the test result. If a start frame has
+ * been specified, we walk down the stack until a frame with the same filename
+ * as the start frame has been found. The next file in the stack will be the
+ * frame to use for logging the result.
+ *
+ * @memberOf stack
+ * @param {Object} [aStartFrame=Components.stack] Frame to start from walking up the stack.
+ * @returns {Object} Frame of the stack to use for logging the result.
+ */
+function findCallerFrame(aStartFrame) {
+ let frame = Components.stack;
+ let filename = frame.filename.replace(/(.*)-> /, "");
+
+ // If a start frame has been specified, walk up the stack until we have
+ // found the corresponding file
+ if (aStartFrame) {
+ filename = aStartFrame.filename.replace(/(.*)-> /, "");
+
+ while (frame.caller &&
+ frame.filename && (frame.filename.indexOf(filename) == -1)) {
+ frame = frame.caller;
+ }
+ }
+
+ // Walk even up more until the next file has been found
+ while (frame.caller &&
+ (!frame.filename || (frame.filename.indexOf(filename) != -1)))
+ frame = frame.caller;
+
+ return frame;
+}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/utils.js b/services/sync/tps/extensions/mozmill/resource/modules/utils.js
deleted file mode 100644
index 92b860f5a80c..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/utils.js
+++ /dev/null
@@ -1,522 +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/. */
-
-var EXPORTED_SYMBOLS = ["openFile", "saveFile", "saveAsFile", "genBoiler",
- "getFile", "Copy", "getChromeWindow", "getWindows", "runEditor",
- "runFile", "getWindowByTitle", "getWindowByType", "tempfile",
- "getMethodInWindows", "getPreference", "setPreference",
- "sleep", "assert", "unwrapNode", "TimeoutError", "waitFor",
- "takeScreenshot",
- ];
-
-var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
- .getService(Components.interfaces.nsIAppShellService)
- .hiddenDOMWindow;
-
-var uuidgen = Components.classes["@mozilla.org/uuid-generator;1"]
- .getService(Components.interfaces.nsIUUIDGenerator);
-
-function Copy (obj) {
- for (var n in obj) {
- this[n] = obj[n];
- }
-}
-
-function getChromeWindow(aWindow) {
- var chromeWin = aWindow
- .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- .getInterface(Components.interfaces.nsIWebNavigation)
- .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
- .rootTreeItem
- .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
- .getInterface(Components.interfaces.nsIDOMWindow)
- .QueryInterface(Components.interfaces.nsIDOMChromeWindow);
- return chromeWin;
-}
-
-function getWindows(type) {
- if (type == undefined) {
- type = "";
- }
- var windows = []
- var enumerator = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator)
- .getEnumerator(type);
- while(enumerator.hasMoreElements()) {
- windows.push(enumerator.getNext());
- }
- if (type == "") {
- windows.push(hwindow);
- }
- return windows;
-}
-
-function getMethodInWindows (methodName) {
- for each(w in getWindows()) {
- if (w[methodName] != undefined) {
- return w[methodName];
- }
- }
- throw new Error("Method with name: '" + methodName + "' is not in any open window.");
-}
-
-function getWindowByTitle(title) {
- for each(w in getWindows()) {
- if (w.document.title && w.document.title == title) {
- return w;
- }
- }
-}
-
-function getWindowByType(type) {
- var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
- .getService(Components.interfaces.nsIWindowMediator);
- return wm.getMostRecentWindow(type);
-}
-
-function tempfile(appention) {
- if (appention == undefined) {
- var appention = "mozmill.utils.tempfile"
- }
- var tempfile = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties).get("TmpD", Components.interfaces.nsIFile);
- tempfile.append(uuidgen.generateUUID().toString().replace('-', '').replace('{', '').replace('}',''))
- tempfile.create(Components.interfaces.nsIFile.DIRECTORY_TYPE, 0777);
- tempfile.append(appention);
- tempfile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
- // do whatever you need to the created file
- return tempfile.clone()
-}
-
-var checkChrome = function() {
- var loc = window.document.location.href;
- try {
- loc = window.top.document.location.href;
- } catch (e) {}
-
- if (/^chrome:\/\//.test(loc)) { return true; }
- else { return false; }
-}
-
-
- var runFile = function(w){
- //define the interface
- var nsIFilePicker = Components.interfaces.nsIFilePicker;
- var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- //define the file picker window
- fp.init(w, "Select a File", nsIFilePicker.modeOpen);
- fp.appendFilter("JavaScript Files","*.js");
- //show the window
- var res = fp.show();
- //if we got a file
- if (res == nsIFilePicker.returnOK){
- var thefile = fp.file;
- //create the paramObj with a files array attrib
- var paramObj = {};
- paramObj.files = [];
- paramObj.files.push(thefile.path);
- }
- };
-
- var saveFile = function(w, content, filename){
- //define the file interface
- var file = Components.classes["@mozilla.org/file/local;1"]
- .createInstance(Components.interfaces.nsILocalFile);
- //point it at the file we want to get at
- file.initWithPath(filename);
-
- // file is nsIFile, data is a string
- var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
- .createInstance(Components.interfaces.nsIFileOutputStream);
-
- // use 0x02 | 0x10 to open file for appending.
- foStream.init(file, 0x02 | 0x08 | 0x20, 0666, 0);
- // write, create, truncate
- // In a c file operation, we have no need to set file mode with or operation,
- // directly using "r" or "w" usually.
-
- foStream.write(content, content.length);
- foStream.close();
- };
-
- var saveAsFile = function(w, content){
- //define the interface
- var nsIFilePicker = Components.interfaces.nsIFilePicker;
- var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- //define the file picker window
- fp.init(w, "Select a File", nsIFilePicker.modeSave);
- fp.appendFilter("JavaScript Files","*.js");
- //show the window
- var res = fp.show();
- //if we got a file
- if ((res == nsIFilePicker.returnOK) || (res == nsIFilePicker.returnReplace)){
- var thefile = fp.file;
-
- //forcing the user to save as a .js file
- if (thefile.path.indexOf(".js") == -1){
- //define the file interface
- var file = Components.classes["@mozilla.org/file/local;1"]
- .createInstance(Components.interfaces.nsILocalFile);
- //point it at the file we want to get at
- file.initWithPath(thefile.path+".js");
- var thefile = file;
- }
-
- // file is nsIFile, data is a string
- var foStream = Components.classes["@mozilla.org/network/file-output-stream;1"]
- .createInstance(Components.interfaces.nsIFileOutputStream);
-
- // use 0x02 | 0x10 to open file for appending.
- foStream.init(thefile, 0x02 | 0x08 | 0x20, 0666, 0);
- // write, create, truncate
- // In a c file operation, we have no need to set file mode with or operation,
- // directly using "r" or "w" usually.
- foStream.write(content, content.length);
- foStream.close();
- return thefile.path;
- }
- };
-
- var openFile = function(w){
- //define the interface
- var nsIFilePicker = Components.interfaces.nsIFilePicker;
- var fp = Components.classes["@mozilla.org/filepicker;1"].createInstance(nsIFilePicker);
- //define the file picker window
- fp.init(w, "Select a File", nsIFilePicker.modeOpen);
- fp.appendFilter("JavaScript Files","*.js");
- //show the window
- var res = fp.show();
- //if we got a file
- if (res == nsIFilePicker.returnOK){
- var thefile = fp.file;
- //create the paramObj with a files array attrib
- var data = getFile(thefile.path);
-
- return {path:thefile.path, data:data};
- }
- };
-
- var getFile = function(path){
- //define the file interface
- var file = Components.classes["@mozilla.org/file/local;1"]
- .createInstance(Components.interfaces.nsILocalFile);
- //point it at the file we want to get at
- file.initWithPath(path);
- // define file stream interfaces
- var data = "";
- var fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
- .createInstance(Components.interfaces.nsIFileInputStream);
- var sstream = Components.classes["@mozilla.org/scriptableinputstream;1"]
- .createInstance(Components.interfaces.nsIScriptableInputStream);
- fstream.init(file, -1, 0, 0);
- sstream.init(fstream);
-
- //pull the contents of the file out
- var str = sstream.read(4096);
- while (str.length > 0) {
- data += str;
- str = sstream.read(4096);
- }
-
- sstream.close();
- fstream.close();
-
- //data = data.replace(/\r|\n|\r\n/g, "");
- return data;
- };
-
-/**
- * Called to get the state of an individual preference.
- *
- * @param aPrefName string The preference to get the state of.
- * @param aDefaultValue any The default value if preference was not found.
- *
- * @returns any The value of the requested preference
- *
- * @see setPref
- * Code by Henrik Skupin:
- */
-function getPreference(aPrefName, aDefaultValue) {
- try {
- var branch = Components.classes["@mozilla.org/preferences-service;1"].
- getService(Components.interfaces.nsIPrefBranch);
- switch (typeof aDefaultValue) {
- case ('boolean'):
- return branch.getBoolPref(aPrefName);
- case ('string'):
- return branch.getCharPref(aPrefName);
- case ('number'):
- return branch.getIntPref(aPrefName);
- default:
- return branch.getComplexValue(aPrefName);
- }
- } catch(e) {
- return aDefaultValue;
- }
-}
-
-/**
- * Called to set the state of an individual preference.
- *
- * @param aPrefName string The preference to set the state of.
- * @param aValue any The value to set the preference to.
- *
- * @returns boolean Returns true if value was successfully set.
- *
- * @see getPref
- * Code by Henrik Skupin:
- */
-function setPreference(aName, aValue) {
- try {
- var branch = Components.classes["@mozilla.org/preferences-service;1"].
- getService(Components.interfaces.nsIPrefBranch);
- switch (typeof aValue) {
- case ('boolean'):
- branch.setBoolPref(aName, aValue);
- break;
- case ('string'):
- branch.setCharPref(aName, aValue);
- break;
- case ('number'):
- branch.setIntPref(aName, aValue);
- break;
- default:
- branch.setComplexValue(aName, aValue);
- }
- } catch(e) {
- return false;
- }
-
- return true;
-}
-
-/**
- * Sleep for the given amount of milliseconds
- *
- * @param {number} milliseconds
- * Sleeps the given number of milliseconds
- */
-function sleep(milliseconds) {
- // We basically just call this once after the specified number of milliseconds
- var timeup = false;
- function wait() { timeup = true; }
- hwindow.setTimeout(wait, milliseconds);
-
- var thread = Components.classes["@mozilla.org/thread-manager;1"].
- getService().currentThread;
- while(!timeup) {
- thread.processNextEvent(true);
- }
-}
-
-/**
- * Check if the callback function evaluates to true
- */
-function assert(callback, message, thisObject) {
- var result = callback.call(thisObject);
-
- if (!result) {
- throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'");
- }
-
- return true;
-}
-
-/**
- * Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper
- *
- * @param {DOMnode} Wrapped DOM node
- * @returns {DOMNode} Unwrapped DOM node
- */
-function unwrapNode(aNode) {
- var node = aNode;
- if (node) {
- // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
- if ("unwrap" in XPCNativeWrapper) {
- node = XPCNativeWrapper.unwrap(node);
- }
- else if (node.wrappedJSObject != null) {
- node = node.wrappedJSObject;
- }
- }
- return node;
-}
-
-/**
- * TimeoutError
- *
- * Error object used for timeouts
- */
-function TimeoutError(message, fileName, lineNumber) {
- var err = new Error();
- if (err.stack) {
- this.stack = err.stack;
- }
- this.message = message === undefined ? err.message : message;
- this.fileName = fileName === undefined ? err.fileName : fileName;
- this.lineNumber = lineNumber === undefined ? err.lineNumber : lineNumber;
-};
-TimeoutError.prototype = new Error();
-TimeoutError.prototype.constructor = TimeoutError;
-TimeoutError.prototype.name = 'TimeoutError';
-
-/**
- * Waits for the callback evaluates to true
- */
-function waitFor(callback, message, timeout, interval, thisObject) {
- timeout = timeout || 5000;
- interval = interval || 100;
-
- var self = {counter: 0, result: callback.call(thisObject)};
-
- function wait() {
- self.counter += interval;
- self.result = callback.call(thisObject);
- }
-
- var timeoutInterval = hwindow.setInterval(wait, interval);
- var thread = Components.classes["@mozilla.org/thread-manager;1"].
- getService().currentThread;
-
- while((self.result != true) && (self.counter < timeout)) {
- thread.processNextEvent(true);
- }
-
- hwindow.clearInterval(timeoutInterval);
-
- if (self.counter >= timeout) {
- message = message || arguments.callee.name + ": Timeout exceeded for '" + callback + "'";
- throw new TimeoutError(message);
- }
-
- return true;
-}
-
-/**
- * Calculates the x and y chrome offset for an element
- * See https://developer.mozilla.org/en/DOM/window.innerHeight
- *
- * Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen
- */
-function getChromeOffset(elem) {
- var win = elem.ownerDocument.defaultView;
- // Calculate x offset
- var chromeWidth = 0;
- if (win["name"] != "sidebar") {
- chromeWidth = win.outerWidth - win.innerWidth;
- }
-
- // Calculate y offset
- var chromeHeight = win.outerHeight - win.innerHeight;
- // chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset
- if (chromeHeight > 0) {
- // window.innerHeight doesn't include the addon or find bar, so account for these if present
- var addonbar = win.document.getElementById("addon-bar");
- if (addonbar) {
- chromeHeight -= addonbar.scrollHeight;
- }
- var findbar = win.document.getElementById("FindToolbar");
- if (findbar) {
- chromeHeight -= findbar.scrollHeight;
- }
- }
-
- return {'x':chromeWidth, 'y':chromeHeight};
-}
-
-/**
- * Takes a screenshot of the specified DOM node
- */
-function takeScreenshot(node, name, highlights) {
- var rect, win, width, height, left, top, needsOffset;
- // node can be either a window or an arbitrary DOM node
- try {
- win = node.ownerDocument.defaultView; // node is an arbitrary DOM node
- rect = node.getBoundingClientRect();
- width = rect.width;
- height = rect.height;
- top = rect.top;
- left = rect.left;
- // offset for highlights not needed as they will be relative to this node
- needsOffset = false;
- } catch (e) {
- win = node; // node is a window
- width = win.innerWidth;
- height = win.innerHeight;
- top = 0;
- left = 0;
- // offset needed for highlights to take 'outerHeight' of window into account
- needsOffset = true;
- }
-
- var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
- canvas.width = width;
- canvas.height = height;
-
- var ctx = canvas.getContext("2d");
- // Draws the DOM contents of the window to the canvas
- ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
-
- // This section is for drawing a red rectangle around each element passed in via the highlights array
- if (highlights) {
- ctx.lineWidth = "2";
- ctx.strokeStyle = "red";
- ctx.save();
-
- for (var i = 0; i < highlights.length; ++i) {
- var elem = highlights[i];
- rect = elem.getBoundingClientRect();
-
- var offsetY = 0, offsetX = 0;
- if (needsOffset) {
- var offset = getChromeOffset(elem);
- offsetX = offset.x;
- offsetY = offset.y;
- } else {
- // Don't need to offset the window chrome, just make relative to containing node
- offsetY = -top;
- offsetX = -left;
- }
-
- // Draw the rectangle
- ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height);
- }
- } // end highlights
-
- // if there is a name save the file, else return dataURL
- if (name) {
- return saveCanvas(canvas, name);
- }
- return canvas.toDataURL("image/png","");
-}
-
-/**
- * Takes a canvas as input and saves it to the file tempdir/name.png
- * Returns the filepath of the saved file
- */
-function saveCanvas(canvas, name) {
- var file = Components.classes["@mozilla.org/file/directory_service;1"]
- .getService(Components.interfaces.nsIProperties)
- .get("TmpD", Components.interfaces.nsIFile);
- file.append("mozmill_screens");
- file.append(name + ".png");
- file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666);
-
- // create a data url from the canvas and then create URIs of the source and targets
- var io = Components.classes["@mozilla.org/network/io-service;1"]
- .getService(Components.interfaces.nsIIOService);
- var source = io.newURI(canvas.toDataURL("image/png", ""), "UTF8", null);
- var target = io.newFileURI(file)
-
- // prepare to save the canvas data
- var persist = Components.classes["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
- .createInstance(Components.interfaces.nsIWebBrowserPersist);
-
- persist.persistFlags = Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_REPLACE_EXISTING_FILES;
- persist.persistFlags |= Components.interfaces.nsIWebBrowserPersist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
-
- // save the canvas data to the file
- persist.saveURI(source, null, null, null, null, file);
-
- return file.path;
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/windows.js b/services/sync/tps/extensions/mozmill/resource/modules/windows.js
new file mode 100644
index 000000000000..fe9cfaa01f95
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/modules/windows.js
@@ -0,0 +1,292 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["init", "map"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+// imports
+var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
+
+var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+/**
+ * The window map is used to store information about the current state of
+ * open windows, e.g. loaded state
+ */
+var map = {
+ _windows : { },
+
+ /**
+ * Check if a given window id is contained in the map of windows
+ *
+ * @param {Number} aWindowId
+ * Outer ID of the window to check.
+ * @returns {Boolean} True if the window is part of the map, otherwise false.
+ */
+ contains : function (aWindowId) {
+ return (aWindowId in this._windows);
+ },
+
+ /**
+ * Retrieve the value of the specified window's property.
+ *
+ * @param {Number} aWindowId
+ * Outer ID of the window to check.
+ * @param {String} aProperty
+ * Property to retrieve the value from
+ * @return {Object} Value of the window's property
+ */
+ getValue : function (aWindowId, aProperty) {
+ if (!this.contains(aWindowId)) {
+ return undefined;
+ } else {
+ var win = this._windows[aWindowId];
+
+ return (aProperty in win) ? win[aProperty]
+ : undefined;
+ }
+ },
+
+ /**
+ * Remove the entry for a given window
+ *
+ * @param {Number} aWindowId
+ * Outer ID of the window to check.
+ */
+ remove : function (aWindowId) {
+ if (this.contains(aWindowId)) {
+ delete this._windows[aWindowId];
+ }
+
+ // dump("* current map: " + JSON.stringify(this._windows) + "\n");
+ },
+
+ /**
+ * Update the property value of a given window
+ *
+ * @param {Number} aWindowId
+ * Outer ID of the window to check.
+ * @param {String} aProperty
+ * Property to update the value for
+ * @param {Object}
+ * Value to set
+ */
+ update : function (aWindowId, aProperty, aValue) {
+ if (!this.contains(aWindowId)) {
+ this._windows[aWindowId] = { };
+ }
+
+ this._windows[aWindowId][aProperty] = aValue;
+ // dump("* current map: " + JSON.stringify(this._windows) + "\n");
+ },
+
+ /**
+ * Update the internal loaded state of the given content window. To identify
+ * an active (re)load action we make use of an uuid.
+ *
+ * @param {Window} aId - The outer id of the window to update
+ * @param {Boolean} aIsLoaded - Has the window been loaded
+ */
+ updatePageLoadStatus : function (aId, aIsLoaded) {
+ this.update(aId, "loaded", aIsLoaded);
+
+ var uuid = this.getValue(aId, "id_load_in_transition");
+
+ // If no uuid has been set yet or when the page gets unloaded create a new id
+ if (!uuid || !aIsLoaded) {
+ uuid = uuidgen.generateUUID();
+ this.update(aId, "id_load_in_transition", uuid);
+ }
+
+ // dump("*** Page status updated: id=" + aId + ", loaded=" + aIsLoaded + ", uuid=" + uuid + "\n");
+ },
+
+ /**
+ * This method only applies to content windows, where we have to check if it has
+ * been successfully loaded or reloaded. An uuid allows us to wait for the next
+ * load action triggered by e.g. controller.open().
+ *
+ * @param {Window} aId - The outer id of the content window to check
+ *
+ * @returns {Boolean} True if the content window has been loaded
+ */
+ hasPageLoaded : function (aId) {
+ var load_current = this.getValue(aId, "id_load_in_transition");
+ var load_handled = this.getValue(aId, "id_load_handled");
+
+ var isLoaded = this.contains(aId) && this.getValue(aId, "loaded") &&
+ (load_current !== load_handled);
+
+ if (isLoaded) {
+ // Backup the current uuid so we can check later if another page load happened.
+ this.update(aId, "id_load_handled", load_current);
+ }
+
+ // dump("** Page has been finished loading: id=" + aId + ", status=" + isLoaded + ", uuid=" + load_current + "\n");
+
+ return isLoaded;
+ }
+};
+
+
+// Observer when a new top-level window is ready
+var windowReadyObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ // Not in all cases we get a ChromeWindow. So ensure we really operate
+ // on such an instance. Otherwise load events will not be handled.
+ var win = utils.getChromeWindow(aSubject);
+
+ // var id = utils.getWindowId(win);
+ // dump("*** 'toplevel-window-ready' observer notification: id=" + id + "\n");
+ attachEventListeners(win);
+ }
+};
+
+
+// Observer when a top-level window is closed
+var windowCloseObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ var id = utils.getWindowId(aSubject);
+ // dump("*** 'outer-window-destroyed' observer notification: id=" + id + "\n");
+
+ map.remove(id);
+ }
+};
+
+// Bug 915554
+// Support for the old Private Browsing Mode (eg. ESR17)
+// TODO: remove once ESR17 is no longer supported
+var enterLeavePrivateBrowsingObserver = {
+ observe: function (aSubject, aTopic, aData) {
+ handleAttachEventListeners();
+ }
+};
+
+/**
+ * Attach event listeners
+ *
+ * @param {ChromeWindow} aWindow
+ * Window to attach listeners on.
+ */
+function attachEventListeners(aWindow) {
+ // These are the event handlers
+ var pageShowHandler = function (aEvent) {
+ var doc = aEvent.originalTarget;
+
+ // Only update the flag if we have a document as target
+ // see https://bugzilla.mozilla.org/show_bug.cgi?id=690829
+ if ("defaultView" in doc) {
+ var id = utils.getWindowId(doc.defaultView);
+ // dump("*** 'pageshow' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+ map.updatePageLoadStatus(id, true);
+ }
+
+ // We need to add/remove the unload/pagehide event listeners to preserve caching.
+ aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
+ aWindow.addEventListener("pagehide", pageHideHandler, true);
+ };
+
+ var DOMContentLoadedHandler = function (aEvent) {
+ var doc = aEvent.originalTarget;
+
+ // Only update the flag if we have a document as target
+ if ("defaultView" in doc) {
+ var id = utils.getWindowId(doc.defaultView);
+ // dump("*** 'DOMContentLoaded' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+
+ // We only care about error pages for DOMContentLoaded
+ var errorRegex = /about:.+(error)|(blocked)\?/;
+ if (errorRegex.exec(doc.baseURI)) {
+ // Wait about 1s to be sure the DOM is ready
+ utils.sleep(1000);
+
+ map.updatePageLoadStatus(id, true);
+ }
+
+ // We need to add/remove the unload event listener to preserve caching.
+ aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
+ }
+ };
+
+ // beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
+ // still use pagehide for cases when beforeunload doesn't get fired
+ var beforeUnloadHandler = function (aEvent) {
+ var doc = aEvent.originalTarget;
+
+ // Only update the flag if we have a document as target
+ if ("defaultView" in doc) {
+ var id = utils.getWindowId(doc.defaultView);
+ // dump("*** 'beforeunload' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+ map.updatePageLoadStatus(id, false);
+ }
+
+ aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
+ };
+
+ var pageHideHandler = function (aEvent) {
+ var doc = aEvent.originalTarget;
+
+ // Only update the flag if we have a document as target
+ if ("defaultView" in doc) {
+ var id = utils.getWindowId(doc.defaultView);
+ // dump("*** 'pagehide' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
+ map.updatePageLoadStatus(id, false);
+ }
+ // If event.persisted is true the beforeUnloadHandler would never fire
+ // and we have to remove the event handler here to avoid memory leaks.
+ if (aEvent.persisted)
+ aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
+ };
+
+ var onWindowLoaded = function (aEvent) {
+ var id = utils.getWindowId(aWindow);
+ // dump("*** 'load' event: id=" + id + ", baseURI=" + aWindow.document.baseURI + "\n");
+
+ map.update(id, "loaded", true);
+
+ // Note: Error pages will never fire a "pageshow" event. For those we
+ // have to wait for the "DOMContentLoaded" event. That's the final state.
+ // Error pages will always have a baseURI starting with
+ // "about:" followed by "error" or "blocked".
+ aWindow.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
+
+ // Page is ready
+ aWindow.addEventListener("pageshow", pageShowHandler, true);
+
+ // Leave page (use caching)
+ aWindow.addEventListener("pagehide", pageHideHandler, true);
+ };
+
+ // If the window has already been finished loading, call the load handler
+ // directly. Otherwise attach it to the current window.
+ if (aWindow.document.readyState === 'complete') {
+ onWindowLoaded();
+ } else {
+ aWindow.addEventListener("load", onWindowLoaded, false);
+ }
+}
+
+// Attach event listeners to all already open top-level windows
+function handleAttachEventListeners() {
+ var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
+ getService(Ci.nsIWindowMediator).getEnumerator("");
+ while (enumerator.hasMoreElements()) {
+ var win = enumerator.getNext();
+ attachEventListeners(win);
+ }
+}
+
+function init() {
+ // Activate observer for new top level windows
+ var observerService = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ observerService.addObserver(windowReadyObserver, "toplevel-window-ready", false);
+ observerService.addObserver(windowCloseObserver, "outer-window-destroyed", false);
+ observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing", false);
+
+ handleAttachEventListeners();
+}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js b/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
index fdaf307e475e..dcc5e726f5d7 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
@@ -1,24 +1,28 @@
-/* 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/. */
-
// Export all available functions for Mozmill
-var EXPORTED_SYMBOLS = ["sendMouseEvent", "sendChar", "sendString", "sendKey",
- "synthesizeMouse", "synthesizeMouseScroll", "synthesizeKey",
+var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar",
+ "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch",
+ "synthesizeMouseAtPoint", "synthesizeTouchAtPoint",
+ "synthesizeMouseAtCenter", "synthesizeTouchAtCenter",
+ "synthesizeWheel", "synthesizeKey",
"synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
- "synthesizeDragStart", "synthesizeDrop", "synthesizeText",
- "disableNonTestMouseEvents", "synthesizeComposition",
- "synthesizeQuerySelectedText", "synthesizeQueryTextContent",
- "synthesizeQueryCaretRect", "synthesizeQueryTextRect",
- "synthesizeQueryEditorRect", "synthesizeCharAtPoint",
- "synthesizeSelectionSet"];
+ "synthesizeText",
+ "synthesizeComposition", "synthesizeQuerySelectedText"];
-/**
- * Get the array with available key events
- */
-function getKeyEvent(aWindow) {
- var win = aWindow.wrappedJSObject ? aWindow.wrappedJSObject : aWindow;
- return win.KeyEvent;
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+var window = Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService).hiddenDOMWindow;
+
+var _EU_Ci = Ci;
+var navigator = window.navigator;
+var KeyEvent = window.KeyEvent;
+var parent = window.parent;
+
+function is(aExpression1, aExpression2, aMessage) {
+ if (aExpression1 !== aExpression2) {
+ throw new Error(aMessage);
+ }
}
/**
@@ -28,6 +32,14 @@ function getKeyEvent(aWindow) {
* sendChar
* sendString
* sendKey
+ * synthesizeMouse
+ * synthesizeMouseAtCenter
+ * synthesizeWheel
+ * synthesizeKey
+ * synthesizeMouseExpectEvent
+ * synthesizeKeyExpectEvent
+ *
+ * When adding methods to this file, please add a performance test for it.
*/
/**
@@ -39,16 +51,23 @@ function getKeyEvent(aWindow) {
*
* sendMouseEvent({type:'click'}, 'node');
*/
+function getElement(id) {
+ return ((typeof(id) == "string") ?
+ document.getElementById(id) : id);
+};
+
+this.$ = this.getElement;
+
function sendMouseEvent(aEvent, aTarget, aWindow) {
- if (['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
- throw new Error("sendMouseEvent doesn't know about event type '"+aEvent.type+"'");
+ if (['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', 'mouseout'].indexOf(aEvent.type) == -1) {
+ throw new Error("sendMouseEvent doesn't know about event type '" + aEvent.type + "'");
}
if (!aWindow) {
aWindow = window;
}
- if (!(aTarget instanceof Element)) {
+ if (!(aTarget instanceof aWindow.Element)) {
aTarget = aWindow.document.getElementById(aTarget);
}
@@ -60,7 +79,8 @@ function sendMouseEvent(aEvent, aTarget, aWindow) {
var viewArg = aWindow;
var detailArg = aEvent.detail || (aEvent.type == 'click' ||
aEvent.type == 'mousedown' ||
- aEvent.type == 'mouseup' ? 1 : 0);
+ aEvent.type == 'mouseup' ? 1 :
+ aEvent.type == 'dblclick'? 2 : 0);
var screenXArg = aEvent.screenX || 0;
var screenYArg = aEvent.screenY || 0;
var clientXArg = aEvent.clientX || 0;
@@ -77,112 +97,71 @@ function sendMouseEvent(aEvent, aTarget, aWindow) {
ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
buttonArg, relatedTargetArg);
- aTarget.dispatchEvent(event);
+ SpecialPowers.dispatchEvent(aWindow, aTarget, event);
}
/**
- * Send the char aChar to the node with id aTarget. If aTarget is not
- * provided, use "target". This method handles casing of chars (sends the
- * right charcode, and sends a shift key for uppercase chars). No other
- * modifiers are handled at this point.
+ * Send the char aChar to the focused element. This method handles casing of
+ * chars (sends the right charcode, and sends a shift key for uppercase chars).
+ * No other modifiers are handled at this point.
*
- * For now this method only works for English letters (lower and upper case)
- * and the digits 0-9.
- *
- * Returns true if the keypress event was accepted (no calls to preventDefault
- * or anything like that), false otherwise.
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
*/
-function sendChar(aChar, aTarget) {
- // DOM event charcodes match ASCII (JS charcodes) for a-zA-Z0-9.
- var hasShift = (aChar == aChar.toUpperCase());
- var charCode = aChar.charCodeAt(0);
- var keyCode = charCode;
- if (!hasShift) {
- // For lowercase letters, the keyCode is actually 32 less than the charCode
- keyCode -= 0x20;
+function sendChar(aChar, aWindow) {
+ var hasShift;
+ // Emulate US keyboard layout for the shiftKey state.
+ switch (aChar) {
+ case "!":
+ case "@":
+ case "#":
+ case "$":
+ case "%":
+ case "^":
+ case "&":
+ case "*":
+ case "(":
+ case ")":
+ case "_":
+ case "+":
+ case "{":
+ case "}":
+ case ":":
+ case "\"":
+ case "|":
+ case "<":
+ case ">":
+ case "?":
+ hasShift = true;
+ break;
+ default:
+ hasShift = (aChar == aChar.toUpperCase());
+ break;
}
-
- return __doEventDispatch(aTarget, charCode, keyCode, hasShift);
+ synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
}
/**
- * Send the string aStr to the node with id aTarget. If aTarget is not
- * provided, use "target".
+ * Send the string aStr to the focused element.
*
- * For now this method only works for English letters (lower and upper case)
- * and the digits 0-9.
+ * For now this method only works for ASCII characters and emulates the shift
+ * key state on US keyboard layout.
*/
-function sendString(aStr, aTarget) {
+function sendString(aStr, aWindow) {
for (var i = 0; i < aStr.length; ++i) {
- sendChar(aStr.charAt(i), aTarget);
+ sendChar(aStr.charAt(i), aWindow);
}
}
/**
- * Send the non-character key aKey to the node with id aTarget. If aTarget is
- * not provided, use "target". The name of the key should be a lowercase
- * version of the part that comes after "DOM_VK_" in the KeyEvent constant
- * name for this key. No modifiers are handled at this point.
- *
- * Returns true if the keypress event was accepted (no calls to preventDefault
- * or anything like that), false otherwise.
+ * Send the non-character key aKey to the focused node.
+ * The name of the key should be the part that comes after "DOM_VK_" in the
+ * KeyEvent constant name for this key.
+ * No modifiers are handled at this point.
*/
-function sendKey(aKey, aTarget, aWindow) {
- if (!aWindow)
- aWindow = window;
-
- keyName = "DOM_VK_" + aKey.toUpperCase();
-
- if (!getKeyEvent(aWindow)[keyName]) {
- throw "Unknown key: " + keyName;
- }
-
- return __doEventDispatch(aTarget, 0, getKeyEvent(aWindow)[keyName], false);
-}
-
-/**
- * Actually perform event dispatch given a charCode, keyCode, and boolean for
- * whether "shift" was pressed. Send the event to the node with id aTarget. If
- * aTarget is not provided, use "target".
- *
- * Returns true if the keypress event was accepted (no calls to preventDefault
- * or anything like that), false otherwise.
- */
-function __doEventDispatch(aTarget, aCharCode, aKeyCode, aHasShift) {
- if (aTarget === undefined) {
- aTarget = "target";
- }
-
- var event = document.createEvent("KeyEvents");
- event.initKeyEvent("keydown", true, true, document.defaultView,
- false, false, aHasShift, false,
- aKeyCode, 0);
- var accepted = $(aTarget).dispatchEvent(event);
-
- // Preventing the default keydown action also prevents the default
- // keypress action.
- event = document.createEvent("KeyEvents");
- if (aCharCode) {
- event.initKeyEvent("keypress", true, true, document.defaultView,
- false, false, aHasShift, false,
- 0, aCharCode);
- } else {
- event.initKeyEvent("keypress", true, true, document.defaultView,
- false, false, aHasShift, false,
- aKeyCode, 0);
- }
- if (!accepted) {
- event.preventDefault();
- }
- accepted = $(aTarget).dispatchEvent(event);
-
- // Always send keyup
- var event = document.createEvent("KeyEvents");
- event.initKeyEvent("keyup", true, true, document.defaultView,
- false, false, aHasShift, false,
- aKeyCode, 0);
- $(aTarget).dispatchEvent(event);
- return accepted;
+function sendKey(aKey, aWindow) {
+ var keyName = "VK_" + aKey.toUpperCase();
+ synthesizeKey(keyName, { shiftKey: false }, aWindow);
}
/**
@@ -191,23 +170,45 @@ function __doEventDispatch(aTarget, aCharCode, aKeyCode, aHasShift) {
*/
function _parseModifiers(aEvent)
{
- var hwindow = Components.classes["@mozilla.org/appshell/appShellService;1"]
- .getService(Components.interfaces.nsIAppShellService)
- .hiddenDOMWindow;
-
- const masks = Components.interfaces.nsIDOMNSEvent;
+ const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
var mval = 0;
- if (aEvent.shiftKey)
- mval |= masks.SHIFT_MASK;
- if (aEvent.ctrlKey)
- mval |= masks.CONTROL_MASK;
- if (aEvent.altKey)
- mval |= masks.ALT_MASK;
- if (aEvent.metaKey)
- mval |= masks.META_MASK;
- if (aEvent.accelKey)
- mval |= (hwindow.navigator.platform.indexOf("Mac") >= 0) ? masks.META_MASK :
- masks.CONTROL_MASK;
+ if (aEvent.shiftKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_SHIFT;
+ }
+ if (aEvent.ctrlKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_CONTROL;
+ }
+ if (aEvent.altKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_ALT;
+ }
+ if (aEvent.metaKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_META;
+ }
+ if (aEvent.accelKey) {
+ mval |= (navigator.platform.indexOf("Mac") >= 0) ?
+ nsIDOMWindowUtils.MODIFIER_META : nsIDOMWindowUtils.MODIFIER_CONTROL;
+ }
+ if (aEvent.altGrKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_ALTGRAPH;
+ }
+ if (aEvent.capsLockKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_CAPSLOCK;
+ }
+ if (aEvent.fnKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_FN;
+ }
+ if (aEvent.numLockKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_NUMLOCK;
+ }
+ if (aEvent.scrollLockKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_SCROLLLOCK;
+ }
+ if (aEvent.symbolLockKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_SYMBOLLOCK;
+ }
+ if (aEvent.osKey) {
+ mval |= nsIDOMWindowUtils.MODIFIER_OS;
+ }
return mval;
}
@@ -224,84 +225,281 @@ function _parseModifiers(aEvent)
* a mousedown followed by a mouse up is performed.
*
* aWindow is optional, and defaults to the current window object.
+ *
+ * Returns whether the event had preventDefault() called on it.
*/
function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
- if (!aWindow)
- aWindow = window;
+ var rect = aTarget.getBoundingClientRect();
+ return synthesizeMouseAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
+ aEvent, aWindow);
+}
+function synthesizeTouch(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
+{
+ var rect = aTarget.getBoundingClientRect();
+ synthesizeTouchAtPoint(rect.left + aOffsetX, rect.top + aOffsetY,
+ aEvent, aWindow);
+}
+
+/*
+ * Synthesize a mouse event at a particular point in aWindow.
+ *
+ * aEvent is an object which may contain the properties:
+ * shiftKey, ctrlKey, altKey, metaKey, accessKey, clickCount, button, type
+ *
+ * If the type is specified, an mouse event of that type is fired. Otherwise,
+ * a mousedown followed by a mouse up is performed.
+ *
+ * aWindow is optional, and defaults to the current window object.
+ */
+function synthesizeMouseAtPoint(left, top, aEvent, aWindow)
+{
+ var utils = _getDOMWindowUtils(aWindow);
+ var defaultPrevented = false;
- var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
- getInterface(Components.interfaces.nsIDOMWindowUtils);
if (utils) {
var button = aEvent.button || 0;
var clickCount = aEvent.clickCount || 1;
var modifiers = _parseModifiers(aEvent);
+ var pressure = ("pressure" in aEvent) ? aEvent.pressure : 0;
+ var inputSource = ("inputSource" in aEvent) ? aEvent.inputSource : 0;
- var rect = aTarget.getBoundingClientRect();
-
- var left = rect.left + aOffsetX;
- var top = rect.top + aOffsetY;
-
- if (aEvent.type) {
- utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers);
+ if (("type" in aEvent) && aEvent.type) {
+ defaultPrevented = utils.sendMouseEvent(aEvent.type, left, top, button, clickCount, modifiers, false, pressure, inputSource);
}
else {
- utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers);
- utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers);
+ utils.sendMouseEvent("mousedown", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+ utils.sendMouseEvent("mouseup", left, top, button, clickCount, modifiers, false, pressure, inputSource);
+ }
+ }
+
+ return defaultPrevented;
+}
+function synthesizeTouchAtPoint(left, top, aEvent, aWindow)
+{
+ var utils = _getDOMWindowUtils(aWindow);
+
+ if (utils) {
+ var id = aEvent.id || 0;
+ var rx = aEvent.rx || 1;
+ var ry = aEvent.rx || 1;
+ var angle = aEvent.angle || 0;
+ var force = aEvent.force || 1;
+ var modifiers = _parseModifiers(aEvent);
+
+ if (("type" in aEvent) && aEvent.type) {
+ utils.sendTouchEvent(aEvent.type, [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
+ }
+ else {
+ utils.sendTouchEvent("touchstart", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
+ utils.sendTouchEvent("touchend", [id], [left], [top], [rx], [ry], [angle], [force], 1, modifiers);
}
}
}
+// Call synthesizeMouse with coordinates at the center of aTarget.
+function synthesizeMouseAtCenter(aTarget, aEvent, aWindow)
+{
+ var rect = aTarget.getBoundingClientRect();
+ synthesizeMouse(aTarget, rect.width / 2, rect.height / 2, aEvent,
+ aWindow);
+}
+function synthesizeTouchAtCenter(aTarget, aEvent, aWindow)
+{
+ var rect = aTarget.getBoundingClientRect();
+ synthesizeTouch(aTarget, rect.width / 2, rect.height / 2, aEvent,
+ aWindow);
+}
/**
- * Synthesize a mouse scroll event on a target. The actual client point is determined
+ * Synthesize a wheel event on a target. The actual client point is determined
* by taking the aTarget's client box and offseting it by aOffsetX and
* aOffsetY.
*
* aEvent is an object which may contain the properties:
- * shiftKey, ctrlKey, altKey, metaKey, accessKey, button, type, axis, delta, hasPixels
+ * shiftKey, ctrlKey, altKey, metaKey, accessKey, deltaX, deltaY, deltaZ,
+ * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice,
+ * isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY
*
- * If the type is specified, a mouse scroll event of that type is fired. Otherwise,
- * "DOMMouseScroll" is used.
+ * deltaMode must be defined, others are ok even if undefined.
*
- * If the axis is specified, it must be one of "horizontal" or "vertical". If not specified,
- * "vertical" is used.
- *
- * 'delta' is the amount to scroll by (can be positive or negative). It must
- * be specified.
- *
- * 'hasPixels' specifies whether kHasPixels should be set in the scrollFlags.
+ * expectedOverflowDeltaX and expectedOverflowDeltaY take integer value. The
+ * value is just checked as 0 or positive or negative.
*
* aWindow is optional, and defaults to the current window object.
*/
-function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
+function synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
{
- if (!aWindow)
- aWindow = window;
+ var utils = _getDOMWindowUtils(aWindow);
+ if (!utils) {
+ return;
+ }
- var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
- getInterface(Components.interfaces.nsIDOMWindowUtils);
- if (utils) {
- // See nsMouseScrollFlags in nsGUIEvent.h
- const kIsVertical = 0x02;
- const kIsHorizontal = 0x04;
- const kHasPixels = 0x08;
-
- var button = aEvent.button || 0;
- var modifiers = _parseModifiers(aEvent);
-
- var rect = aTarget.getBoundingClientRect();
-
- var left = rect.left;
- var top = rect.top;
-
- var type = aEvent.type || "DOMMouseScroll";
- var axis = aEvent.axis || "vertical";
- var scrollFlags = (axis == "horizontal") ? kIsHorizontal : kIsVertical;
- if (aEvent.hasPixels) {
- scrollFlags |= kHasPixels;
+ var modifiers = _parseModifiers(aEvent);
+ var options = 0;
+ if (aEvent.isPixelOnlyDevice &&
+ (aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) {
+ options |= utils.WHEEL_EVENT_CAUSED_BY_PIXEL_ONLY_DEVICE;
+ }
+ if (aEvent.isMomentum) {
+ options |= utils.WHEEL_EVENT_CAUSED_BY_MOMENTUM;
+ }
+ if (aEvent.isCustomizedByPrefs) {
+ options |= utils.WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS;
+ }
+ if (typeof aEvent.expectedOverflowDeltaX !== "undefined") {
+ if (aEvent.expectedOverflowDeltaX === 0) {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_ZERO;
+ } else if (aEvent.expectedOverflowDeltaX > 0) {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_POSITIVE;
+ } else {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_X_NEGATIVE;
}
- utils.sendMouseScrollEvent(type, left + aOffsetX, top + aOffsetY, button,
- scrollFlags, aEvent.delta, modifiers);
+ }
+ if (typeof aEvent.expectedOverflowDeltaY !== "undefined") {
+ if (aEvent.expectedOverflowDeltaY === 0) {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_ZERO;
+ } else if (aEvent.expectedOverflowDeltaY > 0) {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_POSITIVE;
+ } else {
+ options |= utils.WHEEL_EVENT_EXPECTED_OVERFLOW_DELTA_Y_NEGATIVE;
+ }
+ }
+ var isPixelOnlyDevice =
+ aEvent.isPixelOnlyDevice && aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL;
+
+ // Avoid the JS warnings "reference to undefined property"
+ if (!aEvent.deltaX) {
+ aEvent.deltaX = 0;
+ }
+ if (!aEvent.deltaY) {
+ aEvent.deltaY = 0;
+ }
+ if (!aEvent.deltaZ) {
+ aEvent.deltaZ = 0;
+ }
+
+ var lineOrPageDeltaX =
+ aEvent.lineOrPageDeltaX != null ? aEvent.lineOrPageDeltaX :
+ aEvent.deltaX > 0 ? Math.floor(aEvent.deltaX) :
+ Math.ceil(aEvent.deltaX);
+ var lineOrPageDeltaY =
+ aEvent.lineOrPageDeltaY != null ? aEvent.lineOrPageDeltaY :
+ aEvent.deltaY > 0 ? Math.floor(aEvent.deltaY) :
+ Math.ceil(aEvent.deltaY);
+
+ var rect = aTarget.getBoundingClientRect();
+ utils.sendWheelEvent(rect.left + aOffsetX, rect.top + aOffsetY,
+ aEvent.deltaX, aEvent.deltaY, aEvent.deltaZ,
+ aEvent.deltaMode, modifiers,
+ lineOrPageDeltaX, lineOrPageDeltaY, options);
+}
+
+function _computeKeyCodeFromChar(aChar)
+{
+ if (aChar.length != 1) {
+ return 0;
+ }
+ const nsIDOMKeyEvent = _EU_Ci.nsIDOMKeyEvent;
+ if (aChar >= 'a' && aChar <= 'z') {
+ return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'a'.charCodeAt(0);
+ }
+ if (aChar >= 'A' && aChar <= 'Z') {
+ return nsIDOMKeyEvent.DOM_VK_A + aChar.charCodeAt(0) - 'A'.charCodeAt(0);
+ }
+ if (aChar >= '0' && aChar <= '9') {
+ return nsIDOMKeyEvent.DOM_VK_0 + aChar.charCodeAt(0) - '0'.charCodeAt(0);
+ }
+ // returns US keyboard layout's keycode
+ switch (aChar) {
+ case '~':
+ case '`':
+ return nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
+ case '!':
+ return nsIDOMKeyEvent.DOM_VK_1;
+ case '@':
+ return nsIDOMKeyEvent.DOM_VK_2;
+ case '#':
+ return nsIDOMKeyEvent.DOM_VK_3;
+ case '$':
+ return nsIDOMKeyEvent.DOM_VK_4;
+ case '%':
+ return nsIDOMKeyEvent.DOM_VK_5;
+ case '^':
+ return nsIDOMKeyEvent.DOM_VK_6;
+ case '&':
+ return nsIDOMKeyEvent.DOM_VK_7;
+ case '*':
+ return nsIDOMKeyEvent.DOM_VK_8;
+ case '(':
+ return nsIDOMKeyEvent.DOM_VK_9;
+ case ')':
+ return nsIDOMKeyEvent.DOM_VK_0;
+ case '-':
+ case '_':
+ return nsIDOMKeyEvent.DOM_VK_SUBTRACT;
+ case '+':
+ case '=':
+ return nsIDOMKeyEvent.DOM_VK_EQUALS;
+ case '{':
+ case '[':
+ return nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
+ case '}':
+ case ']':
+ return nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
+ case '|':
+ case '\\':
+ return nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
+ case ':':
+ case ';':
+ return nsIDOMKeyEvent.DOM_VK_SEMICOLON;
+ case '\'':
+ case '"':
+ return nsIDOMKeyEvent.DOM_VK_QUOTE;
+ case '<':
+ case ',':
+ return nsIDOMKeyEvent.DOM_VK_COMMA;
+ case '>':
+ case '.':
+ return nsIDOMKeyEvent.DOM_VK_PERIOD;
+ case '?':
+ case '/':
+ return nsIDOMKeyEvent.DOM_VK_SLASH;
+ default:
+ return 0;
+ }
+}
+
+/**
+ * isKeypressFiredKey() returns TRUE if the given key should cause keypress
+ * event when widget handles the native key event. Otherwise, FALSE.
+ *
+ * aDOMKeyCode should be one of consts of nsIDOMKeyEvent::DOM_VK_*, or a key
+ * name begins with "VK_", or a character.
+ */
+function isKeypressFiredKey(aDOMKeyCode)
+{
+ if (typeof(aDOMKeyCode) == "string") {
+ if (aDOMKeyCode.indexOf("VK_") == 0) {
+ aDOMKeyCode = KeyEvent["DOM_" + aDOMKeyCode];
+ if (!aDOMKeyCode) {
+ throw "Unknown key: " + aDOMKeyCode;
+ }
+ } else {
+ // If the key generates a character, it must cause a keypress event.
+ return true;
+ }
+ }
+ switch (aDOMKeyCode) {
+ case KeyEvent.DOM_VK_SHIFT:
+ case KeyEvent.DOM_VK_CONTROL:
+ case KeyEvent.DOM_VK_ALT:
+ case KeyEvent.DOM_VK_CAPS_LOCK:
+ case KeyEvent.DOM_VK_NUM_LOCK:
+ case KeyEvent.DOM_VK_SCROLL_LOCK:
+ case KeyEvent.DOM_VK_META:
+ return false;
+ default:
+ return true;
}
}
@@ -310,10 +508,13 @@ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
* actual keypress by the user, typically the focused element.
*
* aKey should be either a character or a keycode starting with VK_ such as
- * VK_RETURN.
+ * VK_ENTER.
*
* aEvent is an object which may contain the properties:
- * shiftKey, ctrlKey, altKey, metaKey, accessKey, type
+ * shiftKey, ctrlKey, altKey, metaKey, accessKey, type, location
+ *
+ * Sets one of KeyboardEvent.DOM_KEY_LOCATION_* to location. Otherwise,
+ * DOMWindowUtils will choose good location from the keycode.
*
* If the type is specified, a key event of that type is fired. Otherwise,
* a keydown, a keypress and then a keyup event are fired in sequence.
@@ -322,29 +523,61 @@ function synthesizeMouseScroll(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
*/
function synthesizeKey(aKey, aEvent, aWindow)
{
- if (!aWindow)
- aWindow = window;
-
- var utils = aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
- getInterface(Components.interfaces.nsIDOMWindowUtils);
+ var utils = _getDOMWindowUtils(aWindow);
if (utils) {
var keyCode = 0, charCode = 0;
- if (aKey.indexOf("VK_") == 0)
- keyCode = getKeyEvent(aWindow)["DOM_" + aKey];
- else
+ if (aKey.indexOf("VK_") == 0) {
+ keyCode = KeyEvent["DOM_" + aKey];
+ if (!keyCode) {
+ throw "Unknown key: " + aKey;
+ }
+ } else {
charCode = aKey.charCodeAt(0);
+ keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
+ }
var modifiers = _parseModifiers(aEvent);
-
- if (aEvent.type) {
- utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers);
+ var flags = 0;
+ if (aEvent.location != undefined) {
+ switch (aEvent.location) {
+ case KeyboardEvent.DOM_KEY_LOCATION_STANDARD:
+ flags |= utils.KEY_FLAG_LOCATION_STANDARD;
+ break;
+ case KeyboardEvent.DOM_KEY_LOCATION_LEFT:
+ flags |= utils.KEY_FLAG_LOCATION_LEFT;
+ break;
+ case KeyboardEvent.DOM_KEY_LOCATION_RIGHT:
+ flags |= utils.KEY_FLAG_LOCATION_RIGHT;
+ break;
+ case KeyboardEvent.DOM_KEY_LOCATION_NUMPAD:
+ flags |= utils.KEY_FLAG_LOCATION_NUMPAD;
+ break;
+ case KeyboardEvent.DOM_KEY_LOCATION_MOBILE:
+ flags |= utils.KEY_FLAG_LOCATION_MOBILE;
+ break;
+ case KeyboardEvent.DOM_KEY_LOCATION_JOYSTICK:
+ flags |= utils.KEY_FLAG_LOCATION_JOYSTICK;
+ break;
+ }
}
- else {
+
+ if (!("type" in aEvent) || !aEvent.type) {
+ // Send keydown + (optional) keypress + keyup events.
var keyDownDefaultHappened =
- utils.sendKeyEvent("keydown", keyCode, charCode, modifiers);
- utils.sendKeyEvent("keypress", keyCode, charCode, modifiers,
- !keyDownDefaultHappened);
- utils.sendKeyEvent("keyup", keyCode, charCode, modifiers);
+ utils.sendKeyEvent("keydown", keyCode, 0, modifiers, flags);
+ if (isKeypressFiredKey(keyCode)) {
+ if (!keyDownDefaultHappened) {
+ flags |= utils.KEY_FLAG_PREVENT_DEFAULT;
+ }
+ utils.sendKeyEvent("keypress", keyCode, charCode, modifiers, flags);
+ }
+ utils.sendKeyEvent("keyup", keyCode, 0, modifiers, flags);
+ } else if (aEvent.type == "keypress") {
+ // Send standalone keypress event.
+ utils.sendKeyEvent(aEvent.type, keyCode, charCode, modifiers, flags);
+ } else {
+ // Send other standalone event than keypress.
+ utils.sendKeyEvent(aEvent.type, keyCode, 0, modifiers, flags);
}
}
}
@@ -368,9 +601,7 @@ function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
var eventHandler = function(event) {
var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
event.type == type);
- if (!epassed)
- throw new Error(aTestName + " " + type + " event target " +
- (_gSeenEvent ? "twice" : ""));
+ is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
_gSeenEvent = true;
};
@@ -389,10 +620,9 @@ function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTe
var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
aExpectedTarget.removeEventListener(type, aEventHandler, false);
var desc = type + " event";
- if (expectEvent)
+ if (!expectEvent)
desc += " not";
- if (_gSeenEvent != expectEvent)
- throw new Error(aTestName + ": " + desc + " fired.");
+ is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
}
_gSeenEvent = false;
@@ -442,145 +672,10 @@ function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
_checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
}
-/**
- * Emulate a dragstart event.
- * element - element to fire the dragstart event on
- * expectedDragData - the data you expect the data transfer to contain afterwards
- * This data is in the format:
- * [ [ {type: value, data: value, test: function}, ... ], ... ]
- * can be null
- * aWindow - optional; defaults to the current window object.
- * x - optional; initial x coordinate
- * y - optional; initial y coordinate
- * Returns null if data matches.
- * Returns the event.dataTransfer if data does not match
- *
- * eqTest is an optional function if comparison can't be done with x == y;
- * function (actualData, expectedData) {return boolean}
- * @param actualData from dataTransfer
- * @param expectedData from expectedDragData
- * see bug 462172 for example of use
- *
- */
-function synthesizeDragStart(element, expectedDragData, aWindow, x, y)
-{
- if (!aWindow)
- aWindow = window;
- x = x || 2;
- y = y || 2;
- const step = 9;
-
- var result = "trapDrag was not called";
- var trapDrag = function(event) {
- try {
- var dataTransfer = event.dataTransfer;
- result = null;
- if (!dataTransfer)
- throw "no dataTransfer";
- if (expectedDragData == null ||
- dataTransfer.mozItemCount != expectedDragData.length)
- throw dataTransfer;
- for (var i = 0; i < dataTransfer.mozItemCount; i++) {
- var dtTypes = dataTransfer.mozTypesAt(i);
- if (dtTypes.length != expectedDragData[i].length)
- throw dataTransfer;
- for (var j = 0; j < dtTypes.length; j++) {
- if (dtTypes[j] != expectedDragData[i][j].type)
- throw dataTransfer;
- var dtData = dataTransfer.mozGetDataAt(dtTypes[j],i);
- if (expectedDragData[i][j].eqTest) {
- if (!expectedDragData[i][j].eqTest(dtData, expectedDragData[i][j].data))
- throw dataTransfer;
- }
- else if (expectedDragData[i][j].data != dtData)
- throw dataTransfer;
- }
- }
- } catch(ex) {
- result = ex;
- }
- event.preventDefault();
- event.stopPropagation();
- }
- aWindow.addEventListener("dragstart", trapDrag, false);
- synthesizeMouse(element, x, y, { type: "mousedown" }, aWindow);
- x += step; y += step;
- synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
- x += step; y += step;
- synthesizeMouse(element, x, y, { type: "mousemove" }, aWindow);
- aWindow.removeEventListener("dragstart", trapDrag, false);
- synthesizeMouse(element, x, y, { type: "mouseup" }, aWindow);
- return result;
-}
-
-/**
- * Emulate a drop by emulating a dragstart and firing events dragenter, dragover, and drop.
- * srcElement - the element to use to start the drag, usually the same as destElement
- * but if destElement isn't suitable to start a drag on pass a suitable
- * element for srcElement
- * destElement - the element to fire the dragover, dragleave and drop events
- * dragData - the data to supply for the data transfer
- * This data is in the format:
- * [ [ {type: value, data: value}, ...], ... ]
- * dropEffect - the drop effect to set during the dragstart event, or 'move' if null
- * aWindow - optional; defaults to the current window object.
- *
- * Returns the drop effect that was desired.
- */
-function synthesizeDrop(srcElement, destElement, dragData, dropEffect, aWindow)
-{
- if (!aWindow)
- aWindow = window;
-
- var dataTransfer;
- var trapDrag = function(event) {
- dataTransfer = event.dataTransfer;
- for (var i = 0; i < dragData.length; i++) {
- var item = dragData[i];
- for (var j = 0; j < item.length; j++) {
- dataTransfer.mozSetDataAt(item[j].type, item[j].data, i);
- }
- }
- dataTransfer.dropEffect = dropEffect || "move";
- event.preventDefault();
- event.stopPropagation();
- }
-
- // need to use real mouse action
- aWindow.addEventListener("dragstart", trapDrag, true);
- synthesizeMouse(srcElement, 2, 2, { type: "mousedown" }, aWindow);
- synthesizeMouse(srcElement, 11, 11, { type: "mousemove" }, aWindow);
- synthesizeMouse(srcElement, 20, 20, { type: "mousemove" }, aWindow);
- aWindow.removeEventListener("dragstart", trapDrag, true);
-
- event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("dragenter", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- destElement.dispatchEvent(event);
-
- var event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("dragover", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- if (destElement.dispatchEvent(event)) {
- synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
- return "none";
- }
-
- if (dataTransfer.dropEffect != "none") {
- event = aWindow.document.createEvent("DragEvents");
- event.initDragEvent("drop", true, true, aWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer);
- destElement.dispatchEvent(event);
- }
- synthesizeMouse(destElement, 20, 20, { type: "mouseup" }, aWindow);
-
- return dataTransfer.dropEffect;
-}
-
function disableNonTestMouseEvents(aDisable)
{
- var utils =
- window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
- getInterface(Components.interfaces.nsIDOMWindowUtils);
- if (utils)
- utils.disableNonTestMouseEvents(aDisable);
+ var domutils = _getDOMWindowUtils();
+ domutils.disableNonTestMouseEvents(aDisable);
}
function _getDOMWindowUtils(aWindow)
@@ -588,28 +683,53 @@ function _getDOMWindowUtils(aWindow)
if (!aWindow) {
aWindow = window;
}
- return aWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
- getInterface(Components.interfaces.nsIDOMWindowUtils);
+
+ // we need parent.SpecialPowers for:
+ // layout/base/tests/test_reftests_with_caret.html
+ // chrome: toolkit/content/tests/chrome/test_findbar.xul
+ // chrome: toolkit/content/tests/chrome/test_popup_anchor.xul
+ if ("SpecialPowers" in window && window.SpecialPowers != undefined) {
+ return SpecialPowers.getDOMWindowUtils(aWindow);
+ }
+ if ("SpecialPowers" in parent && parent.SpecialPowers != undefined) {
+ return parent.SpecialPowers.getDOMWindowUtils(aWindow);
+ }
+
+ //TODO: this is assuming we are in chrome space
+ return aWindow.QueryInterface(_EU_Ci.nsIInterfaceRequestor).
+ getInterface(_EU_Ci.nsIDOMWindowUtils);
}
+// Must be synchronized with nsIDOMWindowUtils.
+const COMPOSITION_ATTR_RAWINPUT = 0x02;
+const COMPOSITION_ATTR_SELECTEDRAWTEXT = 0x03;
+const COMPOSITION_ATTR_CONVERTEDTEXT = 0x04;
+const COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT = 0x05;
+
/**
* Synthesize a composition event.
*
- * @param aIsCompositionStart If true, this synthesize compositionstart event.
- * Otherwise, compositionend event.
+ * @param aEvent The composition event information. This must
+ * have |type| member. The value must be
+ * "compositionstart", "compositionend" or
+ * "compositionupdate".
+ * And also this may have |data| and |locale| which
+ * would be used for the value of each property of
+ * the composition event. Note that the data would
+ * be ignored if the event type were
+ * "compositionstart".
* @param aWindow Optional (If null, current |window| will be used)
*/
-function synthesizeComposition(aIsCompositionStart, aWindow)
+function synthesizeComposition(aEvent, aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
return;
}
- utils.sendCompositionEvent(aIsCompositionStart ?
- "compositionstart" : "compositionend");
+ utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
+ aEvent.locale ? aEvent.locale : "");
}
-
/**
* Synthesize a text event.
*
@@ -702,123 +822,8 @@ function synthesizeQuerySelectedText(aWindow)
{
var utils = _getDOMWindowUtils(aWindow);
if (!utils) {
- return nullptr;
+ return null;
}
+
return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
}
-
-/**
- * Synthesize a query text content event.
- *
- * @param aOffset The character offset. 0 means the first character in the
- * selection root.
- * @param aLength The length of getting text. If the length is too long,
- * the extra length is ignored.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQueryTextContent(aOffset, aLength, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_TEXT_CONTENT,
- aOffset, aLength, 0, 0);
-}
-
-/**
- * Synthesize a query caret rect event.
- *
- * @param aOffset The caret offset. 0 means left side of the first character
- * in the selection root.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQueryCaretRect(aOffset, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_CARET_RECT,
- aOffset, 0, 0, 0);
-}
-
-/**
- * Synthesize a query text rect event.
- *
- * @param aOffset The character offset. 0 means the first character in the
- * selection root.
- * @param aLength The length of the text. If the length is too long,
- * the extra length is ignored.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQueryTextRect(aOffset, aLength, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_TEXT_RECT,
- aOffset, aLength, 0, 0);
-}
-
-/**
- * Synthesize a query editor rect event.
- *
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQueryEditorRect(aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_EDITOR_RECT, 0, 0, 0, 0);
-}
-
-/**
- * Synthesize a character at point event.
- *
- * @param aX, aY The offset in the client area of the DOM window.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeCharAtPoint(aX, aY, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return nullptr;
- }
- return utils.sendQueryContentEvent(utils.QUERY_CHARACTER_AT_POINT,
- 0, 0, aX, aY);
-}
-
-/**
- * Synthesize a selection set event.
- *
- * @param aOffset The character offset. 0 means the first character in the
- * selection root.
- * @param aLength The length of the text. If the length is too long,
- * the extra length is ignored.
- * @param aReverse If true, the selection is from |aOffset + aLength| to
- * |aOffset|. Otherwise, from |aOffset| to |aOffset + aLength|.
- * @param aWindow Optional (If null, current |window| will be used)
- * @return True, if succeeded. Otherwise false.
- */
-function synthesizeSelectionSet(aOffset, aLength, aReverse, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return false;
- }
- return utils.sendSelectionSetEvent(aOffset, aLength, aReverse);
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js b/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
index f33beda38197..c70a262c9671 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
@@ -1,49 +1,65 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
-var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf', 'rindexOf', 'compare'];
+var EXPORTED_SYMBOLS = ['inArray', 'getSet', 'indexOf',
+ 'remove', 'rindexOf', 'compare'];
-function inArray (array, value) {
- for (i in array) {
+
+function remove(array, from, to) {
+ var rest = array.slice((to || from) + 1 || array.length);
+ array.length = from < 0 ? array.length + from : from;
+
+ return array.push.apply(array, rest);
+}
+
+function inArray(array, value) {
+ for (var i in array) {
if (value == array[i]) {
return true;
}
}
+
return false;
}
-function getSet (array) {
+function getSet(array) {
var narray = [];
- for (i in array) {
- if ( !inArray(narray, array[i]) ) {
+
+ for (var i in array) {
+ if (!inArray(narray, array[i])) {
narray.push(array[i]);
}
}
+
return narray;
}
-function indexOf (array, v, offset) {
- for (i in array) {
+function indexOf(array, v, offset) {
+ for (var i in array) {
if (offset == undefined || i >= offset) {
- if ( !isNaN(i) && array[i] == v) {
+ if (!isNaN(i) && array[i] == v) {
return new Number(i);
}
}
}
+
return -1;
}
function rindexOf (array, v) {
var l = array.length;
- for (i in array) {
+
+ for (var i in array) {
if (!isNaN(i)) {
- var i = new Number(i)
+ var i = new Number(i);
}
+
if (!isNaN(i) && array[l - i] == v) {
return l - i;
}
}
+
return -1;
}
@@ -51,10 +67,12 @@ function compare (array, carray) {
if (array.length != carray.length) {
return false;
}
- for (i in array) {
+
+ for (var i in array) {
if (array[i] != carray[i]) {
return false;
}
}
+
return true;
}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/dom.js b/services/sync/tps/extensions/mozmill/resource/stdlib/dom.js
index 1592a741130c..06bfcb529be3 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/dom.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/dom.js
@@ -1,21 +1,24 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['getAttributes'];
var getAttributes = function (node) {
var attributes = {};
- for (i in node.attributes) {
- if ( !isNaN(i) ) {
+
+ for (var i in node.attributes) {
+ if (!isNaN(i)) {
try {
var attr = node.attributes[i];
attributes[attr.name] = attr.value;
- } catch (err) {
+ }
+ catch (e) {
}
}
}
+
return attributes;
}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js b/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
index 6b58b6607920..c5eea62515cb 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
@@ -10,16 +10,36 @@
* httpd.js.
*/
+this.EXPORTED_SYMBOLS = [
+ "HTTP_400",
+ "HTTP_401",
+ "HTTP_402",
+ "HTTP_403",
+ "HTTP_404",
+ "HTTP_405",
+ "HTTP_406",
+ "HTTP_407",
+ "HTTP_408",
+ "HTTP_409",
+ "HTTP_410",
+ "HTTP_411",
+ "HTTP_412",
+ "HTTP_413",
+ "HTTP_414",
+ "HTTP_415",
+ "HTTP_417",
+ "HTTP_500",
+ "HTTP_501",
+ "HTTP_502",
+ "HTTP_503",
+ "HTTP_504",
+ "HTTP_505",
+ "HttpError",
+ "HttpServer",
+];
+
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
-var EXPORTED_SYMBOLS = ['getServer'];
-
-/**
- * Overwrite both dump functions because we do not wanna have this output for Mozmill
- */
-function dump() {}
-function dumpn() {}
-
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
@@ -58,7 +78,7 @@ function NS_ASSERT(cond, msg)
}
/** Constructs an HTTP error object. */
-function HttpError(code, description)
+this.HttpError = function HttpError(code, description)
{
this.code = code;
this.description = description;
@@ -74,30 +94,30 @@ HttpError.prototype =
/**
* Errors thrown to trigger specific HTTP server responses.
*/
-const HTTP_400 = new HttpError(400, "Bad Request");
-const HTTP_401 = new HttpError(401, "Unauthorized");
-const HTTP_402 = new HttpError(402, "Payment Required");
-const HTTP_403 = new HttpError(403, "Forbidden");
-const HTTP_404 = new HttpError(404, "Not Found");
-const HTTP_405 = new HttpError(405, "Method Not Allowed");
-const HTTP_406 = new HttpError(406, "Not Acceptable");
-const HTTP_407 = new HttpError(407, "Proxy Authentication Required");
-const HTTP_408 = new HttpError(408, "Request Timeout");
-const HTTP_409 = new HttpError(409, "Conflict");
-const HTTP_410 = new HttpError(410, "Gone");
-const HTTP_411 = new HttpError(411, "Length Required");
-const HTTP_412 = new HttpError(412, "Precondition Failed");
-const HTTP_413 = new HttpError(413, "Request Entity Too Large");
-const HTTP_414 = new HttpError(414, "Request-URI Too Long");
-const HTTP_415 = new HttpError(415, "Unsupported Media Type");
-const HTTP_417 = new HttpError(417, "Expectation Failed");
+this.HTTP_400 = new HttpError(400, "Bad Request");
+this.HTTP_401 = new HttpError(401, "Unauthorized");
+this.HTTP_402 = new HttpError(402, "Payment Required");
+this.HTTP_403 = new HttpError(403, "Forbidden");
+this.HTTP_404 = new HttpError(404, "Not Found");
+this.HTTP_405 = new HttpError(405, "Method Not Allowed");
+this.HTTP_406 = new HttpError(406, "Not Acceptable");
+this.HTTP_407 = new HttpError(407, "Proxy Authentication Required");
+this.HTTP_408 = new HttpError(408, "Request Timeout");
+this.HTTP_409 = new HttpError(409, "Conflict");
+this.HTTP_410 = new HttpError(410, "Gone");
+this.HTTP_411 = new HttpError(411, "Length Required");
+this.HTTP_412 = new HttpError(412, "Precondition Failed");
+this.HTTP_413 = new HttpError(413, "Request Entity Too Large");
+this.HTTP_414 = new HttpError(414, "Request-URI Too Long");
+this.HTTP_415 = new HttpError(415, "Unsupported Media Type");
+this.HTTP_417 = new HttpError(417, "Expectation Failed");
-const HTTP_500 = new HttpError(500, "Internal Server Error");
-const HTTP_501 = new HttpError(501, "Not Implemented");
-const HTTP_502 = new HttpError(502, "Bad Gateway");
-const HTTP_503 = new HttpError(503, "Service Unavailable");
-const HTTP_504 = new HttpError(504, "Gateway Timeout");
-const HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
+this.HTTP_500 = new HttpError(500, "Internal Server Error");
+this.HTTP_501 = new HttpError(501, "Not Implemented");
+this.HTTP_502 = new HttpError(502, "Bad Gateway");
+this.HTTP_503 = new HttpError(503, "Service Unavailable");
+this.HTTP_504 = new HttpError(504, "Gateway Timeout");
+this.HTTP_505 = new HttpError(505, "HTTP Version Not Supported");
/** Creates a hash with fields corresponding to the values in arr. */
function array2obj(arr)
@@ -266,7 +286,7 @@ function toDateString(date)
{
var hrs = date.getUTCHours();
var rv = (hrs < 10) ? "0" + hrs : hrs;
-
+
var mins = date.getUTCMinutes();
rv += ":";
rv += (mins < 10) ? "0" + mins : mins;
@@ -451,7 +471,15 @@ nsHttpServer.prototype =
onStopListening: function(socket, status)
{
dumpn(">>> shutting down server on port " + socket.port);
+ for (var n in this._connections) {
+ if (!this._connections[n]._requestStarted) {
+ this._connections[n].close();
+ }
+ }
this._socketClosed = true;
+ if (this._hasOpenConnections()) {
+ dumpn("*** open connections!!!");
+ }
if (!this._hasOpenConnections())
{
dumpn("*** no open connections, notifying async from onStopListening");
@@ -493,12 +521,14 @@ nsHttpServer.prototype =
this._host = host;
// The listen queue needs to be long enough to handle
- // network.http.max-persistent-connections-per-server concurrent connections,
- // plus a safety margin in case some other process is talking to
- // the server as well.
+ // network.http.max-persistent-connections-per-server or
+ // network.http.max-persistent-connections-per-proxy concurrent
+ // connections, plus a safety margin in case some other process is
+ // talking to the server as well.
var prefs = getRootPrefBranch();
- var maxConnections =
- prefs.getIntPref("network.http.max-persistent-connections-per-server") + 5;
+ var maxConnections = 5 + Math.max(
+ prefs.getIntPref("network.http.max-persistent-connections-per-server"),
+ prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
try
{
@@ -507,18 +537,52 @@ nsHttpServer.prototype =
var loopback = false;
}
- var socket = new ServerSocket(this._port,
+ // When automatically selecting a port, sometimes the chosen port is
+ // "blocked" from clients. We don't want to use these ports because
+ // tests will intermittently fail. So, we simply keep trying to to
+ // get a server socket until a valid port is obtained. We limit
+ // ourselves to finite attempts just so we don't loop forever.
+ var ios = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ var socket;
+ for (var i = 100; i; i--)
+ {
+ var temp = new ServerSocket(this._port,
loopback, // true = localhost, false = everybody
maxConnections);
+
+ var allowed = ios.allowPort(temp.port, "http");
+ if (!allowed)
+ {
+ dumpn(">>>Warning: obtained ServerSocket listens on a blocked " +
+ "port: " + temp.port);
+ }
+
+ if (!allowed && this._port == -1)
+ {
+ dumpn(">>>Throwing away ServerSocket with bad port.");
+ temp.close();
+ continue;
+ }
+
+ socket = temp;
+ break;
+ }
+
+ if (!socket) {
+ throw new Error("No socket server available. Are there no available ports?");
+ }
+
dumpn(">>> listening on port " + socket.port + ", " + maxConnections +
" pending connections");
socket.asyncListen(this);
- this._identity._initialize(port, host, true);
+ this._port = socket.port;
+ this._identity._initialize(socket.port, host, true);
this._socket = socket;
}
catch (e)
{
- dumpn("!!! could not start server on port " + port + ": " + e);
+ dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
throw Cr.NS_ERROR_NOT_AVAILABLE;
}
},
@@ -587,6 +651,14 @@ nsHttpServer.prototype =
this._handler.registerPathHandler(path, handler);
},
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(prefix, handler)
+ {
+ this._handler.registerPrefixHandler(prefix, handler);
+ },
+
//
// see nsIHttpServer.registerErrorHandler
//
@@ -759,6 +831,10 @@ nsHttpServer.prototype =
// Fire a pending server-stopped notification if it's our responsibility.
if (!this._hasOpenConnections() && this._socketClosed)
this._notifyStopped();
+ // Bug 508125: Add a GC here else we'll use gigabytes of memory running
+ // mochitests. We can't rely on xpcshell doing an automated GC, as that
+ // would interfere with testing GC stuff...
+ Components.utils.forceGC();
},
/**
@@ -772,6 +848,7 @@ nsHttpServer.prototype =
}
};
+this.HttpServer = nsHttpServer;
//
// RFC 2396 section 3.2.2:
@@ -790,7 +867,7 @@ const HOST_REGEX =
// toplabel
"[a-z](?:[a-z0-9-]*[a-z0-9])?" +
"|" +
- // IPv4 address
+ // IPv4 address
"\\d+\\.\\d+\\.\\d+\\.\\d+" +
")$",
"i");
@@ -1001,7 +1078,7 @@ ServerIdentity.prototype =
// Not the default primary location, nothing special to do here
this.remove("http", "127.0.0.1", this._defaultPort);
}
-
+
// This is a *very* tricky bit of reasoning here; make absolutely sure the
// tests for this code pass before you commit changes to it.
if (this._primaryScheme == "http" &&
@@ -1097,14 +1174,25 @@ function Connection(input, output, server, port, outgoingPort, number)
*/
this.request = null;
- /** State variables for debugging. */
- this._closed = this._processed = false;
+ /** This allows a connection to disambiguate between a peer initiating a
+ * close and the socket being forced closed on shutdown.
+ */
+ this._closed = false;
+
+ /** State variable for debugging. */
+ this._processed = false;
+
+ /** whether or not 1st line of request has been received */
+ this._requestStarted = false;
}
Connection.prototype =
{
/** Closes this connection's input/output streams. */
close: function()
{
+ if (this._closed)
+ return;
+
dumpn("*** closing connection " + this.number +
" on port " + this._outgoingPort);
@@ -1162,6 +1250,11 @@ Connection.prototype =
return "";
+ },
+
+ requestStarted: function()
+ {
+ this._requestStarted = true;
}
};
@@ -1348,6 +1441,7 @@ RequestReader.prototype =
{
this._parseRequestLine(line.value);
this._state = READER_IN_HEADERS;
+ this._connection.requestStarted();
return true;
}
catch (e)
@@ -1433,7 +1527,7 @@ RequestReader.prototype =
this._handleResponse();
return true;
}
-
+
return false;
}
catch (e)
@@ -1606,7 +1700,10 @@ RequestReader.prototype =
// between fields, even though only a single SP is required (section 19.3)
var request = line.split(/[ \t]+/);
if (!request || request.length != 3)
+ {
+ dumpn("*** No request in line");
throw HTTP_400;
+ }
metadata._method = request[0];
@@ -1614,7 +1711,10 @@ RequestReader.prototype =
var ver = request[2];
var match = ver.match(/^HTTP\/(\d+\.\d+)$/);
if (!match)
+ {
+ dumpn("*** No HTTP version in line");
throw HTTP_400;
+ }
// determine HTTP version
try
@@ -1639,7 +1739,10 @@ RequestReader.prototype =
{
// No absolute paths in the request line in HTTP prior to 1.1
if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
+ {
+ dumpn("*** Metadata version too low");
throw HTTP_400;
+ }
try
{
@@ -1653,11 +1756,18 @@ RequestReader.prototype =
if (port === -1)
{
if (scheme === "http")
+ {
port = 80;
+ }
else if (scheme === "https")
+ {
port = 443;
+ }
else
+ {
+ dumpn("*** Unknown scheme: " + scheme);
throw HTTP_400;
+ }
}
}
catch (e)
@@ -1665,11 +1775,15 @@ RequestReader.prototype =
// If the host is not a valid host on the server, the response MUST be a
// 400 (Bad Request) error message (section 5.2). Alternately, the URI
// is malformed.
+ dumpn("*** Threw when dealing with URI: " + e);
throw HTTP_400;
}
if (!serverIdentity.has(scheme, host, port) || fullPath.charAt(0) != "/")
+ {
+ dumpn("*** serverIdentity unknown or path does not start with '/'");
throw HTTP_400;
+ }
}
var splitter = fullPath.indexOf("?");
@@ -1713,6 +1827,8 @@ RequestReader.prototype =
var line = {};
while (true)
{
+ dumpn("*** Last name: '" + lastName + "'");
+ dumpn("*** Last val: '" + lastVal + "'");
NS_ASSERT(!((lastVal === undefined) ^ (lastName === undefined)),
lastName === undefined ?
"lastVal without lastName? lastVal: '" + lastVal + "'" :
@@ -1727,6 +1843,7 @@ RequestReader.prototype =
}
var lineText = line.value;
+ dumpn("*** Line text: '" + lineText + "'");
var firstChar = lineText.charAt(0);
// blank line means end of headers
@@ -1741,7 +1858,7 @@ RequestReader.prototype =
}
catch (e)
{
- dumpn("*** e == " + e);
+ dumpn("*** setHeader threw on last header, e == " + e);
throw HTTP_400;
}
}
@@ -1759,7 +1876,7 @@ RequestReader.prototype =
// multi-line header if we've already seen a header line
if (!lastName)
{
- // we don't have a header to continue!
+ dumpn("We don't have a header to continue!");
throw HTTP_400;
}
@@ -1778,7 +1895,7 @@ RequestReader.prototype =
}
catch (e)
{
- dumpn("*** e == " + e);
+ dumpn("*** setHeader threw on a header, e == " + e);
throw HTTP_400;
}
}
@@ -1786,7 +1903,7 @@ RequestReader.prototype =
var colon = lineText.indexOf(":"); // first colon must be splitter
if (colon < 1)
{
- // no colon or missing header field-name
+ dumpn("*** No colon or missing header field-name");
throw HTTP_400;
}
@@ -1811,12 +1928,14 @@ const CR = 0x0D, LF = 0x0A;
* character; the first CRLF is the lowest index i where
* |array[i] == "\r".charCodeAt(0)| and |array[i+1] == "\n".charCodeAt(0)|,
* if such an |i| exists, and -1 otherwise
+ * @param start : uint
+ * start index from which to begin searching in array
* @returns int
* the index of the first CRLF if any were present, -1 otherwise
*/
-function findCRLF(array)
+function findCRLF(array, start)
{
- for (var i = array.indexOf(CR); i >= 0; i = array.indexOf(CR, i + 1))
+ for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
{
if (array[i + 1] == LF)
return i;
@@ -1833,6 +1952,9 @@ function LineData()
{
/** An array of queued bytes from which to get line-based characters. */
this._data = [];
+
+ /** Start index from which to search for CRLF. */
+ this._start = 0;
}
LineData.prototype =
{
@@ -1842,7 +1964,22 @@ LineData.prototype =
*/
appendBytes: function(bytes)
{
- Array.prototype.push.apply(this._data, bytes);
+ var count = bytes.length;
+ var quantum = 262144; // just above half SpiderMonkey's argument-count limit
+ if (count < quantum)
+ {
+ Array.prototype.push.apply(this._data, bytes);
+ return;
+ }
+
+ // Large numbers of bytes may cause Array.prototype.push to be called with
+ // more arguments than the JavaScript engine supports. In that case append
+ // bytes in fixed-size amounts until all bytes are appended.
+ for (var start = 0; start < count; start += quantum)
+ {
+ var slice = bytes.slice(start, Math.min(start + quantum, count));
+ Array.prototype.push.apply(this._data, slice);
+ }
},
/**
@@ -1860,23 +1997,38 @@ LineData.prototype =
readLine: function(out)
{
var data = this._data;
- var length = findCRLF(data);
+ var length = findCRLF(data, this._start);
if (length < 0)
+ {
+ this._start = data.length;
+
+ // But if our data ends in a CR, we have to back up one, because
+ // the first byte in the next packet might be an LF and if we
+ // start looking at data.length we won't find it.
+ if (data.length > 0 && data[data.length - 1] === CR)
+ --this._start;
+
return false;
+ }
+
+ // Reset for future lines.
+ this._start = 0;
//
// We have the index of the CR, so remove all the characters, including
- // CRLF, from the array with splice, and convert the removed array into the
- // corresponding string, from which we then strip the trailing CRLF.
+ // CRLF, from the array with splice, and convert the removed array
+ // (excluding the trailing CRLF characters) into the corresponding string.
//
- // Getting the line in this matter acknowledges that substring is an O(1)
- // operation in SpiderMonkey because strings are immutable, whereas two
- // splices, both from the beginning of the data, are less likely to be as
- // cheap as a single splice plus two extra character conversions.
- //
- var line = String.fromCharCode.apply(null, data.splice(0, length + 2));
- out.value = line.substring(0, length);
+ var leading = data.splice(0, length + 2);
+ var quantum = 262144;
+ var line = "";
+ for (var start = 0; start < length; start += quantum)
+ {
+ var slice = leading.slice(start, Math.min(start + quantum, length));
+ line += String.fromCharCode.apply(null, slice);
+ }
+ out.value = line;
return true;
},
@@ -1911,7 +2063,7 @@ function createHandlerFunc(handler)
*/
function defaultIndexHandler(metadata, response)
{
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var path = htmlEscape(decodeURI(metadata.path));
@@ -2018,6 +2170,7 @@ function toInternalPath(path, encoded)
return comps.join("/");
}
+const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
/**
* Adds custom-specified headers for the given file to the given response, if
@@ -2045,7 +2198,7 @@ function maybeAddHeaders(file, metadata, response)
return;
const PR_RDONLY = 0x01;
- var fis = new FileInputStream(headerFile, PR_RDONLY, 0444,
+ var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
try
@@ -2078,7 +2231,7 @@ function maybeAddHeaders(file, metadata, response)
code = status.substring(0, space);
description = status.substring(space + 1, status.length);
}
-
+
response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
line.value = "";
@@ -2149,6 +2302,15 @@ function ServerHandler(server)
*/
this._overridePaths = {};
+ /**
+ * Custom request handlers for the path prefixes on the server in which this
+ * resides. Path-handler pairs are stored as property-value pairs in this
+ * property.
+ *
+ * @see ServerHandler.prototype._defaultPaths
+ */
+ this._overridePrefixes = {};
+
/**
* Custom request handlers for the error handlers in the server in which this
* resides. Path-handler pairs are stored as property-value pairs in this
@@ -2213,7 +2375,23 @@ ServerHandler.prototype =
}
else
{
- this._handleDefault(request, response);
+ var longestPrefix = "";
+ for (let prefix in this._overridePrefixes) {
+ if (prefix.length > longestPrefix.length &&
+ path.substr(0, prefix.length) == prefix)
+ {
+ longestPrefix = prefix;
+ }
+ }
+ if (longestPrefix.length > 0)
+ {
+ dumpn("calling prefix override for " + longestPrefix);
+ this._overridePrefixes[longestPrefix](request, response);
+ }
+ else
+ {
+ this._handleDefault(request, response);
+ }
}
}
catch (e)
@@ -2318,6 +2496,18 @@ ServerHandler.prototype =
this._handlerToField(handler, this._overridePaths, path);
},
+ //
+ // see nsIHttpServer.registerPrefixHandler
+ //
+ registerPrefixHandler: function(path, handler)
+ {
+ // XXX true path validation!
+ if (path.charAt(0) != "/" || path.charAt(path.length - 1) != "/")
+ throw Cr.NS_ERROR_INVALID_ARG;
+
+ this._handlerToField(handler, this._overridePrefixes, path);
+ },
+
//
// see nsIHttpServer.registerDirectory
//
@@ -2458,7 +2648,10 @@ ServerHandler.prototype =
{
var rangeMatch = metadata.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
if (!rangeMatch)
+ {
+ dumpn("*** Range header bogosity: '" + metadata.getHeader("Range") + "'");
throw HTTP_400;
+ }
if (rangeMatch[1] !== undefined)
start = parseInt(rangeMatch[1], 10);
@@ -2467,7 +2660,10 @@ ServerHandler.prototype =
end = parseInt(rangeMatch[2], 10);
if (start === undefined && end === undefined)
+ {
+ dumpn("*** More Range header bogosity: '" + metadata.getHeader("Range") + "'");
throw HTTP_400;
+ }
// No start given, so the end is really the count of bytes from the
// end of the file.
@@ -2537,7 +2733,7 @@ ServerHandler.prototype =
var type = this._getTypeFromFile(file);
if (type === SJS_TYPE)
{
- var fis = new FileInputStream(file, PR_RDONLY, 0444,
+ var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
try
@@ -2574,6 +2770,10 @@ ServerHandler.prototype =
{
self._setObjectState(k, v);
});
+ s.importFunction(function registerPathHandler(p, h)
+ {
+ self.registerPathHandler(p, h);
+ });
// Make it possible for sjs files to access their location
this._setState(path, "__LOCATION__", file.path);
@@ -2586,7 +2786,7 @@ ServerHandler.prototype =
// getting the line number where we evaluate the SJS file. Don't
// separate these two lines!
var line = new Error().lineNumber;
- Cu.evalInSandbox(sis.read(file.fileSize), s);
+ Cu.evalInSandbox(sis.read(file.fileSize), s, "latest");
}
catch (e)
{
@@ -2627,7 +2827,7 @@ ServerHandler.prototype =
maybeAddHeaders(file, metadata, response);
response.setHeader("Content-Length", "" + count, false);
- var fis = new FileInputStream(file, PR_RDONLY, 0444,
+ var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
Ci.nsIFileInputStream.CLOSE_ON_EOF);
offset = offset || 0;
@@ -2878,6 +3078,7 @@ ServerHandler.prototype =
}
catch (e)
{
+ dumpn("*** toInternalPath threw " + e);
throw HTTP_400; // malformed path
}
@@ -2962,7 +3163,7 @@ ServerHandler.prototype =
dumpn("*** error in request: " + errorCode);
this._handleError(errorCode, new Request(connection.port), response);
- },
+ },
/**
* Handles a request which generates the given error code, using the
@@ -3072,7 +3273,7 @@ ServerHandler.prototype =
{
// none of the data in metadata is reliable, so hard-code everything here
response.setStatusLine("1.1", 400, "Bad Request");
- response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
var body = "Bad request\n";
response.bodyOutputStream.write(body, body.length);
@@ -3080,7 +3281,7 @@ ServerHandler.prototype =
403: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
403 Forbidden\
@@ -3093,7 +3294,7 @@ ServerHandler.prototype =
404: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 404, "Not Found");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
404 Not Found\
@@ -3113,7 +3314,7 @@ ServerHandler.prototype =
response.setStatusLine(metadata.httpVersion,
416,
"Requested Range Not Satisfiable");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
\
@@ -3132,7 +3333,7 @@ ServerHandler.prototype =
response.setStatusLine(metadata.httpVersion,
500,
"Internal Server Error");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
500 Internal Server Error\
@@ -3147,7 +3348,7 @@ ServerHandler.prototype =
501: function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
501 Not Implemented\
@@ -3161,7 +3362,7 @@ ServerHandler.prototype =
505: function(metadata, response)
{
response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
505 HTTP Version Not Supported\
@@ -3183,7 +3384,7 @@ ServerHandler.prototype =
"/": function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "text/html", false);
+ response.setHeader("Content-Type", "text/html;charset=utf-8", false);
var body = "\
httpd.js\
@@ -3201,7 +3402,7 @@ ServerHandler.prototype =
"/trace": function(metadata, response)
{
response.setStatusLine(metadata.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "text/plain", false);
+ response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
var body = "Request-URI: " +
metadata.scheme + "://" + metadata.host + ":" + metadata.port +
@@ -3211,7 +3412,7 @@ ServerHandler.prototype =
if (metadata.queryString)
body += "?" + metadata.queryString;
-
+
body += " HTTP/" + metadata.httpVersion + "\r\n";
var headEnum = metadata.headers;
@@ -4569,7 +4770,10 @@ const headerUtils =
normalizeFieldName: function(fieldName)
{
if (fieldName == "")
+ {
+ dumpn("*** Empty fieldName");
throw Cr.NS_ERROR_INVALID_ARG;
+ }
for (var i = 0, sz = fieldName.length; i < sz; i++)
{
@@ -4620,9 +4824,13 @@ const headerUtils =
val = val.replace(/^ +/, "").replace(/ +$/, "");
// that should have taken care of all CTLs, so val should contain no CTLs
+ dumpn("*** Normalized value: '" + val + "'");
for (var i = 0, len = val.length; i < len; i++)
if (isCTL(val.charCodeAt(i)))
+ {
+ dump("*** Char " + i + " has charcode " + val.charCodeAt(i));
throw Cr.NS_ERROR_INVALID_ARG;
+ }
// XXX disallows quoted-pair where CHAR is a CTL -- will not invalidly
// normalize, however, so this can be construed as a tightening of the
@@ -4757,17 +4965,17 @@ nsHttpHeaders.prototype =
var value = headerUtils.normalizeFieldValue(fieldValue);
// The following three headers are stored as arrays because their real-world
- // syntax prevents joining individual headers into a single header using
+ // syntax prevents joining individual headers into a single header using
// ",". See also
if (merge && name in this._headers)
{
if (name === "www-authenticate" ||
name === "proxy-authenticate" ||
- name === "set-cookie")
+ name === "set-cookie")
{
this._headers[name].push(value);
}
- else
+ else
{
this._headers[name][0] += "," + value;
NS_ASSERT(this._headers[name].length === 1,
@@ -4790,8 +4998,8 @@ nsHttpHeaders.prototype =
* @returns string
* the field value for the given header, possibly with non-semantic changes
* (i.e., leading/trailing whitespace stripped, whitespace runs replaced
- * with spaces, etc.) at the option of the implementation; multiple
- * instances of the header will be combined with a comma, except for
+ * with spaces, etc.) at the option of the implementation; multiple
+ * instances of the header will be combined with a comma, except for
* the three headers noted in the description of getHeaderValues
*/
getHeader: function(fieldName)
@@ -5053,7 +5261,7 @@ Request.prototype =
//
// see nsIPropertyBag.getProperty
//
- getProperty: function(name)
+ getProperty: function(name)
{
this._ensurePropertyBag();
return this._bag.getProperty(name);
@@ -5075,7 +5283,7 @@ Request.prototype =
// PRIVATE IMPLEMENTATION
-
+
/** Ensures a property bag has been created for ad-hoc behaviors. */
_ensurePropertyBag: function()
{
@@ -5086,10 +5294,8 @@ Request.prototype =
// XPCOM trappings
-if (XPCOMUtils.generateNSGetFactory)
- var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
-else
- var NSGetModule = XPCOMUtils.generateNSGetModule([nsHttpServer]);
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
/**
* Creates a new HTTP server listening for loopback traffic on the given port,
@@ -5147,20 +5353,3 @@ function server(port, basePath)
DEBUG = false;
}
-
-function getServer (port, basePath) {
- if (basePath) {
- var lp = Cc["@mozilla.org/file/local;1"]
- .createInstance(Ci.nsILocalFile);
- lp.initWithPath(basePath);
- }
-
- var srv = new nsHttpServer();
- if (lp)
- srv.registerDirectory("/", lp);
- srv.registerContentType("sjs", SJS_TYPE);
- srv.identity.setPrimary("http", "localhost", port);
- srv._port = port;
-
- return srv;
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js b/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
index 5c2b024a1c8d..576117145cc9 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
@@ -1,6 +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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['getLength', ];//'compare'];
@@ -9,6 +9,7 @@ var getLength = function (obj) {
for (i in obj) {
len++;
}
+
return len;
}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/os.js b/services/sync/tps/extensions/mozmill/resource/stdlib/os.js
index f515b9a01f13..fcda305726fc 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/os.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/os.js
@@ -1,38 +1,46 @@
/* 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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
-function listDirectory (file) {
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+function listDirectory(file) {
// file is the given directory (nsIFile)
var entries = file.directoryEntries;
var array = [];
- while (entries.hasMoreElements())
- {
+
+ while (entries.hasMoreElements()) {
var entry = entries.getNext();
- entry.QueryInterface(Components.interfaces.nsIFile);
+ entry.QueryInterface(Ci.nsIFile);
array.push(entry);
}
+
return array;
}
-function getFileForPath (path) {
- var file = Components.classes["@mozilla.org/file/local;1"]
- .createInstance(Components.interfaces.nsILocalFile);
+function getFileForPath(path) {
+ var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
file.initWithPath(path);
return file;
}
-function abspath (rel, file) {
+function abspath(rel, file) {
var relSplit = rel.split('/');
+
if (relSplit[0] == '..' && !file.isDirectory()) {
file = file.parent;
}
- for each(p in relSplit) {
+
+ for each(var p in relSplit) {
if (p == '..') {
file = file.parent;
- } else if (p == '.'){
+ } else if (p == '.') {
if (!file.isDirectory()) {
file = file.parent;
}
@@ -40,14 +48,10 @@ function abspath (rel, file) {
file.append(p);
}
}
+
return file.path;
}
-function getPlatform () {
- var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
- .getService(Components.interfaces.nsIXULRuntime);
- mPlatform = xulRuntime.OS.toLowerCase();
- return mPlatform;
+function getPlatform() {
+ return Services.appinfo.OS.toLowerCase();
}
-
-
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js
index 7a7d8af1476e..794c3e2c24c4 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js
@@ -1,6 +1,38 @@
-/* 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/. */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Jetpack.
+ *
+ * The Initial Developer of the Original Code is Mozilla.
+ * Portions created by the Initial Developer are Copyright (C) 2007
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Atul Varma
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
(function(global) {
const Cc = Components.classes;
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js b/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
index d702dd0a0d14..24a93d958110 100644
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
@@ -1,6 +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/. */
+ * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
var EXPORTED_SYMBOLS = ['trim', 'vslice'];
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
new file mode 100644
index 000000000000..42e1f2fb9e82
--- /dev/null
+++ b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
@@ -0,0 +1,462 @@
+/* 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/. */
+
+var EXPORTED_SYMBOLS = ["applicationName", "assert", "Copy", "getBrowserObject",
+ "getChromeWindow", "getWindows", "getWindowByTitle",
+ "getWindowByType", "getWindowId", "getMethodInWindows",
+ "getPreference", "saveDataURL", "setPreference",
+ "sleep", "startTimer", "stopTimer", "takeScreenshot",
+ "unwrapNode", "waitFor"
+ ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+const applicationIdMap = {
+ '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox',
+ '{99bceaaa-e3c6-48c1-b981-ef9b46b67d60}': 'MetroFirefox'
+}
+const applicationName = applicationIdMap[Services.appinfo.ID] || Services.appinfo.name;
+
+var assertions = {}; Cu.import('resource://mozmill/modules/assertions.js', assertions);
+var broker = {}; Cu.import('resource://mozmill/driver/msgbroker.js', broker);
+var errors = {}; Cu.import('resource://mozmill/modules/errors.js', errors);
+
+var assert = new assertions.Assert();
+
+var hwindow = Services.appShell.hiddenDOMWindow;
+
+var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+
+function Copy (obj) {
+ for (var n in obj) {
+ this[n] = obj[n];
+ }
+}
+
+/**
+ * Returns the browser object of the specified window
+ *
+ * @param {Window} aWindow
+ * Window to get the browser element from.
+ *
+ * @returns {Object} The browser element
+ */
+function getBrowserObject(aWindow) {
+ switch(applicationName) {
+ case "MetroFirefox":
+ return aWindow.Browser;
+ case "Firefox":
+ default:
+ return aWindow.gBrowser;
+ }
+}
+
+function getChromeWindow(aWindow) {
+ var chromeWin = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShellTreeItem)
+ .rootTreeItem
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow)
+ .QueryInterface(Ci.nsIDOMChromeWindow);
+
+ return chromeWin;
+}
+
+function getWindows(type) {
+ if (type == undefined) {
+ type = "";
+ }
+
+ var windows = [];
+ var enumerator = Services.wm.getEnumerator(type);
+
+ while (enumerator.hasMoreElements()) {
+ windows.push(enumerator.getNext());
+ }
+
+ if (type == "") {
+ windows.push(hwindow);
+ }
+
+ return windows;
+}
+
+function getMethodInWindows(methodName) {
+ for each (var w in getWindows()) {
+ if (w[methodName] != undefined) {
+ return w[methodName];
+ }
+ }
+
+ throw new Error("Method with name: '" + methodName + "' is not in any open window.");
+}
+
+function getWindowByTitle(title) {
+ for each (var w in getWindows()) {
+ if (w.document.title && w.document.title == title) {
+ return w;
+ }
+ }
+
+ throw new Error("Window with title: '" + title + "' not found.");
+}
+
+function getWindowByType(type) {
+ return Services.wm.getMostRecentWindow(type);
+}
+
+/**
+ * Retrieve the outer window id for the given window.
+ *
+ * @param {Number} aWindow
+ * Window to retrieve the id from.
+ * @returns {Boolean} The outer window id
+ **/
+function getWindowId(aWindow) {
+ try {
+ // Normally we can retrieve the id via window utils
+ return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+ getInterface(Ci.nsIDOMWindowUtils).
+ outerWindowID;
+ } catch (e) {
+ // ... but for observer notifications we need another interface
+ return aWindow.QueryInterface(Ci.nsISupportsPRUint64).data;
+ }
+}
+
+var checkChrome = function () {
+ var loc = window.document.location.href;
+ try {
+ loc = window.top.document.location.href;
+ } catch (e) {
+ }
+
+ return /^chrome:\/\//.test(loc);
+}
+
+/**
+ * Called to get the state of an individual preference.
+ *
+ * @param aPrefName string The preference to get the state of.
+ * @param aDefaultValue any The default value if preference was not found.
+ *
+ * @returns any The value of the requested preference
+ *
+ * @see setPref
+ * Code by Henrik Skupin:
+ */
+function getPreference(aPrefName, aDefaultValue) {
+ try {
+ var branch = Services.prefs;
+
+ switch (typeof aDefaultValue) {
+ case ('boolean'):
+ return branch.getBoolPref(aPrefName);
+ case ('string'):
+ return branch.getCharPref(aPrefName);
+ case ('number'):
+ return branch.getIntPref(aPrefName);
+ default:
+ return branch.getComplexValue(aPrefName);
+ }
+ } catch (e) {
+ return aDefaultValue;
+ }
+}
+
+/**
+ * Called to set the state of an individual preference.
+ *
+ * @param aPrefName string The preference to set the state of.
+ * @param aValue any The value to set the preference to.
+ *
+ * @returns boolean Returns true if value was successfully set.
+ *
+ * @see getPref
+ * Code by Henrik Skupin:
+ */
+function setPreference(aName, aValue) {
+ try {
+ var branch = Services.prefs;
+
+ switch (typeof aValue) {
+ case ('boolean'):
+ branch.setBoolPref(aName, aValue);
+ break;
+ case ('string'):
+ branch.setCharPref(aName, aValue);
+ break;
+ case ('number'):
+ branch.setIntPref(aName, aValue);
+ break;
+ default:
+ branch.setComplexValue(aName, aValue);
+ }
+ } catch (e) {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * Sleep for the given amount of milliseconds
+ *
+ * @param {number} milliseconds
+ * Sleeps the given number of milliseconds
+ */
+function sleep(milliseconds) {
+ var timeup = false;
+
+ hwindow.setTimeout(function () { timeup = true; }, milliseconds);
+ var thread = Services.tm.currentThread;
+
+ while (!timeup) {
+ thread.processNextEvent(true);
+ }
+
+ broker.pass({'function':'utils.sleep()'});
+}
+
+/**
+ * Check if the callback function evaluates to true
+ */
+function assert(callback, message, thisObject) {
+ var result = callback.call(thisObject);
+
+ if (!result) {
+ throw new Error(message || arguments.callee.name + ": Failed for '" + callback + "'");
+ }
+
+ return true;
+}
+
+/**
+ * Unwraps a node which is wrapped into a XPCNativeWrapper or XrayWrapper
+ *
+ * @param {DOMnode} Wrapped DOM node
+ * @returns {DOMNode} Unwrapped DOM node
+ */
+function unwrapNode(aNode) {
+ var node = aNode;
+ if (node) {
+ // unwrap is not available on older branches (3.5 and 3.6) - Bug 533596
+ if ("unwrap" in XPCNativeWrapper) {
+ node = XPCNativeWrapper.unwrap(node);
+ }
+ else if (node.wrappedJSObject != null) {
+ node = node.wrappedJSObject;
+ }
+ }
+
+ return node;
+}
+
+/**
+ * Waits for the callback evaluates to true
+ */
+function waitFor(callback, message, timeout, interval, thisObject) {
+ broker.log({'function': 'utils.waitFor() - DEPRECATED',
+ 'message': 'utils.waitFor() is deprecated. Use assert.waitFor() instead'});
+ assert.waitFor(callback, message, timeout, interval, thisObject);
+}
+
+/**
+ * Calculates the x and y chrome offset for an element
+ * See https://developer.mozilla.org/en/DOM/window.innerHeight
+ *
+ * Note this function will not work if the user has custom toolbars (via extension) at the bottom or left/right of the screen
+ */
+function getChromeOffset(elem) {
+ var win = elem.ownerDocument.defaultView;
+ // Calculate x offset
+ var chromeWidth = 0;
+
+ if (win["name"] != "sidebar") {
+ chromeWidth = win.outerWidth - win.innerWidth;
+ }
+
+ // Calculate y offset
+ var chromeHeight = win.outerHeight - win.innerHeight;
+ // chromeHeight == 0 means elem is already in the chrome and doesn't need the addonbar offset
+ if (chromeHeight > 0) {
+ // window.innerHeight doesn't include the addon or find bar, so account for these if present
+ var addonbar = win.document.getElementById("addon-bar");
+ if (addonbar) {
+ chromeHeight -= addonbar.scrollHeight;
+ }
+
+ var findbar = win.document.getElementById("FindToolbar");
+ if (findbar) {
+ chromeHeight -= findbar.scrollHeight;
+ }
+ }
+
+ return {'x':chromeWidth, 'y':chromeHeight};
+}
+
+/**
+ * Takes a screenshot of the specified DOM node
+ */
+function takeScreenshot(node, highlights) {
+ var rect, win, width, height, left, top, needsOffset;
+ // node can be either a window or an arbitrary DOM node
+ try {
+ // node is an arbitrary DOM node
+ win = node.ownerDocument.defaultView;
+ rect = node.getBoundingClientRect();
+ width = rect.width;
+ height = rect.height;
+ top = rect.top;
+ left = rect.left;
+ // offset for highlights not needed as they will be relative to this node
+ needsOffset = false;
+ } catch (e) {
+ // node is a window
+ win = node;
+ width = win.innerWidth;
+ height = win.innerHeight;
+ top = 0;
+ left = 0;
+ // offset needed for highlights to take 'outerHeight' of window into account
+ needsOffset = true;
+ }
+
+ var canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ canvas.width = width;
+ canvas.height = height;
+
+ var ctx = canvas.getContext("2d");
+ // Draws the DOM contents of the window to the canvas
+ ctx.drawWindow(win, left, top, width, height, "rgb(255,255,255)");
+
+ // This section is for drawing a red rectangle around each element passed in via the highlights array
+ if (highlights) {
+ ctx.lineWidth = "2";
+ ctx.strokeStyle = "red";
+ ctx.save();
+
+ for (var i = 0; i < highlights.length; ++i) {
+ var elem = highlights[i];
+ rect = elem.getBoundingClientRect();
+
+ var offsetY = 0, offsetX = 0;
+ if (needsOffset) {
+ var offset = getChromeOffset(elem);
+ offsetX = offset.x;
+ offsetY = offset.y;
+ } else {
+ // Don't need to offset the window chrome, just make relative to containing node
+ offsetY = -top;
+ offsetX = -left;
+ }
+
+ // Draw the rectangle
+ ctx.strokeRect(rect.left + offsetX, rect.top + offsetY, rect.width, rect.height);
+ }
+ }
+
+ return canvas.toDataURL("image/jpeg", 0.5);
+}
+
+/**
+ * Save the dataURL content to the specified file. It will be stored in either the persisted screenshot or temporary folder.
+ *
+ * @param {String} aDataURL
+ * The dataURL to save
+ * @param {String} aFilename
+ * Target file name without extension
+ *
+ * @returns {Object} The hash containing the path of saved file, and the failure bit
+ */
+function saveDataURL(aDataURL, aFilename) {
+ var frame = {}; Cu.import('resource://mozmill/modules/frame.js', frame);
+ const FILE_PERMISSIONS = parseInt("0644", 8);
+
+ var file;
+ file = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ file.initWithPath(frame.persisted['screenshots']['path']);
+ file.append(aFilename + ".jpg");
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FILE_PERMISSIONS);
+
+ // Create an output stream to write to file
+ let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ foStream.init(file, 0x02 | 0x08 | 0x10, FILE_PERMISSIONS, foStream.DEFER_OPEN);
+
+ let dataURI = NetUtil.newURI(aDataURL, "UTF8", null);
+ if (!dataURI.schemeIs("data")) {
+ throw TypeError("aDataURL parameter has to have 'data'" +
+ " scheme instead of '" + dataURI.scheme + "'");
+ }
+
+ // Write asynchronously to buffer;
+ // Input and output streams are closed after write
+
+ let ready = false;
+ let failure = false;
+
+ function sync(aStatus) {
+ if (!Components.isSuccessCode(aStatus)) {
+ failure = true;
+ }
+ ready = true;
+ }
+
+ NetUtil.asyncFetch(dataURI, function (aInputStream, aAsyncFetchResult) {
+ if (!Components.isSuccessCode(aAsyncFetchResult)) {
+ // An error occurred!
+ sync(aAsyncFetchResult);
+ } else {
+ // Consume the input stream.
+ NetUtil.asyncCopy(aInputStream, foStream, function (aAsyncCopyResult) {
+ sync(aAsyncCopyResult);
+ });
+ }
+ });
+
+ assert.waitFor(function () {
+ return ready;
+ }, "DataURL has been saved to '" + file.path + "'");
+
+ return {filename: file.path, failure: failure};
+}
+
+/**
+ * Some very brain-dead timer functions useful for performance optimizations
+ * This is only enabled in debug mode
+ *
+ **/
+var gutility_mzmltimer = 0;
+/**
+ * Starts timer initializing with current EPOC time in milliseconds
+ *
+ * @returns none
+ **/
+function startTimer(){
+ dump("TIMERCHECK:: starting now: " + Date.now() + "\n");
+ gutility_mzmltimer = Date.now();
+}
+
+/**
+ * Checks the timer and outputs current elapsed time since start of timer. It
+ * will print out a message you provide with its "time check" so you can
+ * correlate in the log file and figure out elapsed time of specific functions.
+ *
+ * @param aMsg string The debug message to print with the timer check
+ *
+ * @returns none
+ **/
+function checkTimer(aMsg){
+ var end = Date.now();
+ dump("TIMERCHECK:: at " + aMsg + " is: " + (end - gutility_mzmltimer) + "\n");
+}
diff --git a/services/sync/tps/extensions/tps/resource/mozmill/sync.jsm b/services/sync/tps/extensions/tps/resource/mozmill/sync.jsm
deleted file mode 100644
index 160c17aa6787..000000000000
--- a/services/sync/tps/extensions/tps/resource/mozmill/sync.jsm
+++ /dev/null
@@ -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/. */
-
-var EXPORTED_SYMBOLS = ["TPS", "SYNC_WIPE_SERVER", "SYNC_RESET_CLIENT",
- "SYNC_WIPE_CLIENT"];
-
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-sync/util.js");
-Cu.import("resource://tps/logger.jsm");
-
-var utils = {}; Cu.import('resource://mozmill/modules/utils.js', utils);
-
-const SYNC_RESET_CLIENT = "reset-client";
-const SYNC_WIPE_CLIENT = "wipe-client";
-const SYNC_WIPE_REMOTE = "wipe-remote";
-const SYNC_WIPE_SERVER = "wipe-server";
-
-var prefs = Cc["@mozilla.org/preferences-service;1"]
- .getService(CI.nsIPrefBranch);
-
-var syncFinishedCallback = function() {
- Logger.logInfo('syncFinishedCallback returned ' + !TPS._waitingForSync);
- return !TPS._waitingForSync;
-};
-
-var TPS = {
- _waitingForSync: false,
- _syncErrors: 0,
-
- QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
- Ci.nsISupportsWeakReference]),
-
- observe: function TPS__observe(subject, topic, data) {
- Logger.logInfo('Mozmill observed: ' + topic);
- switch(topic) {
- case "weave:service:sync:error":
- if (this._waitingForSync && this._syncErrors == 0) {
- Logger.logInfo("sync error; retrying...");
- this._syncErrors++;
- Utils.namedTimer(function() {
- Weave.service.sync();
- }, 1000, this, "resync");
- }
- else if (this._waitingForSync) {
- this._syncErrors = "sync error, see log";
- this._waitingForSync = false;
- }
- break;
- case "weave:service:sync:finish":
- if (this._waitingForSync) {
- this._syncErrors = 0;
- this._waitingForSync = false;
- }
- break;
- }
- },
-
- SetupSyncAccount: function TPS__SetupSyncAccount() {
- try {
- let serverURL = prefs.getCharPref('tps.serverURL');
- if (serverURL) {
- Weave.Service.serverURL = serverURL;
- }
- }
- catch(e) {}
-
- // Needs to be updated if this Mozmill sanity test is needed for Firefox Accounts
- Weave.Service.identity.account = prefs.getCharPref('tps.account.username');
- Weave.Service.Identity.basicPassword = prefs.getCharPref('tps.account.password');
- Weave.Service.identity.syncKey = prefs.getCharPref('tps.account.passphrase');
- Weave.Svc.Obs.notify("weave:service:setup-complete");
- },
-
- Sync: function TPS__Sync(options) {
- Logger.logInfo('Mozmill starting sync operation: ' + options);
- switch(options) {
- case SYNC_WIPE_REMOTE:
- Weave.Svc.Prefs.set("firstSync", "wipeRemote");
- break;
- case SYNC_WIPE_CLIENT:
- Weave.Svc.Prefs.set("firstSync", "wipeClient");
- break;
- case SYNC_RESET_CLIENT:
- Weave.Svc.Prefs.set("firstSync", "resetClient");
- break;
- default:
- Weave.Svc.Prefs.reset("firstSync");
- }
-
- if (Weave.Status.service != Weave.STATUS_OK) {
- return "Sync status not ok: " + Weave.Status.service;
- }
-
- this._syncErrors = 0;
-
- if (options == SYNC_WIPE_SERVER) {
- Weave.Service.wipeServer();
- } else {
- this._waitingForSync = true;
- Weave.Service.sync();
- utils.waitFor(syncFinishedCallback, null, 20000, 500, TPS);
- }
- return this._syncErrors;
- },
-};
-
-Services.obs.addObserver(TPS, "weave:service:sync:finish", true);
-Services.obs.addObserver(TPS, "weave:service:sync:error", true);
-Logger.init();
-
-
diff --git a/services/sync/tps/extensions/tps/resource/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm
index ef8706085f90..7809f355b027 100644
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -7,7 +7,7 @@
* listed symbols will exposed on import, and only when and where imported.
*/
-let EXPORTED_SYMBOLS = ["TPS"];
+let EXPORTED_SYMBOLS = ["ACTIONS", "TPS"];
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
@@ -40,7 +40,7 @@ var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
var mozmillInit = {};
-Cu.import('resource://mozmill/modules/init.js', mozmillInit);
+Cu.import('resource://mozmill/driver/mozmill.js', mozmillInit);
// Options for wiping data during a sync
const SYNC_RESET_CLIENT = "resetClient";
@@ -795,7 +795,7 @@ let TPS = {
frame.events.addListener('setTest', this.MozmillSetTestListener.bind(this));
frame.events.addListener('endTest', this.MozmillEndTestListener.bind(this));
this.StartAsyncOperation();
- frame.runTestFile(mozmillfile.path, false);
+ frame.runTestFile(mozmillfile.path, null);
},
/**