diff --git a/.eslintignore b/.eslintignore
index cdd0426e798b..eaad499bbcac 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -334,7 +334,6 @@ testing/xpcshell/node-http2/**
# Third party services
services/common/kinto-http-client.js
services/common/kinto-offline-client.js
-services/sync/tps/extensions/mozmill
# toolkit/ exclusions
diff --git a/services/sync/tests/tps/.eslintrc.js b/services/sync/tests/tps/.eslintrc.js
index fc7c3a4c6a61..162f1a996b81 100644
--- a/services/sync/tests/tps/.eslintrc.js
+++ b/services/sync/tests/tps/.eslintrc.js
@@ -6,11 +6,6 @@ module.exports = {
],
globals: {
- // Globals specific to mozmill
- "assert": false,
- "controller": false,
- "findElement": false,
- "mozmill": false,
// Injected into tests via tps.jsm
"Addons": false,
"Bookmarks": false,
@@ -22,7 +17,6 @@ module.exports = {
"Passwords": false,
"Phase": false,
"Prefs": false,
- "RunMozmillTest": false,
"STATE_DISABLED": false,
"STATE_ENABLED": false,
"Sync": false,
diff --git a/services/sync/tests/tps/mozmill_sanity.js b/services/sync/tests/tps/mozmill_sanity.js
deleted file mode 100644
index f66a9dcf587d..000000000000
--- a/services/sync/tests/tps/mozmill_sanity.js
+++ /dev/null
@@ -1,30 +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/. */
-
-Components.utils.import("resource://tps/tps.jsm");
-
-var setupModule = function(module) {
- module.controller = mozmill.getBrowserController();
- assert.ok(true, "SetupModule passes");
-};
-
-var setupTest = function(module) {
- assert.ok(true, "SetupTest passes");
-};
-
-var testTestStep = function() {
- assert.ok(true, "test Passes");
- controller.open("http://www.mozilla.org");
-
- TPS.Login();
- TPS.Sync(ACTIONS.ACTION_SYNC_WIPE_CLIENT);
-};
-
-var teardownTest = function() {
- assert.ok(true, "teardownTest passes");
-};
-
-var teardownModule = function() {
- assert.ok(true, "teardownModule passes");
-};
diff --git a/services/sync/tests/tps/mozmill_sanity2.js b/services/sync/tests/tps/mozmill_sanity2.js
deleted file mode 100644
index 60d2f85d3d2c..000000000000
--- a/services/sync/tests/tps/mozmill_sanity2.js
+++ /dev/null
@@ -1,15 +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 setupModule = function(module) {
- module.controller = mozmill.getBrowserController();
-};
-
-var testGetNode = function() {
- controller.open("about:support");
- controller.waitForPageLoad();
-
- var appbox = findElement.ID(controller.tabs.activeTab, "application-box");
- assert.waitFor(() => appbox.getNode().textContent == "Firefox", "correct app name");
-};
diff --git a/services/sync/tests/tps/test_mozmill_sanity.js b/services/sync/tests/tps/test_mozmill_sanity.js
deleted file mode 100644
index 000b0b08d425..000000000000
--- a/services/sync/tests/tps/test_mozmill_sanity.js
+++ /dev/null
@@ -1,24 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/*
- * The list of phases mapped to their corresponding profiles. The object
- * here must be in strict JSON format, as it will get parsed by the Python
- * testrunner (no single quotes, extra comma's, etc).
- */
-
-var phases = { "phase1": "profile1",
- "phase2": "profile2" };
-
-/*
- * Test phases
- */
-
-Phase("phase1", [
- [RunMozmillTest, "mozmill_sanity.js"],
-]);
-
-Phase("phase2", [
- [Sync],
- [RunMozmillTest, "mozmill_sanity2.js"],
-]);
diff --git a/services/sync/tests/unit/test_doctor.js b/services/sync/tests/unit/test_doctor.js
index 1075ec2b2ecf..63906c377325 100644
--- a/services/sync/tests/unit/test_doctor.js
+++ b/services/sync/tests/unit/test_doctor.js
@@ -166,7 +166,7 @@ add_task(async function test_repairs_skip_if_cant_vaidate() {
};
let requestor = {
async startRepairs(validationInfo, flowID) {
- assert.ok(false, "Never should start repairs");
+ ok(false, "Never should start repairs");
},
tryServerOnlyRepairs() {
return false;
diff --git a/services/sync/tps/extensions/mozmill/chrome.manifest b/services/sync/tps/extensions/mozmill/chrome.manifest
deleted file mode 100755
index dfb370321beb..000000000000
--- a/services/sync/tps/extensions/mozmill/chrome.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-resource mozmill resource/
-
diff --git a/services/sync/tps/extensions/mozmill/install.rdf b/services/sync/tps/extensions/mozmill/install.rdf
deleted file mode 100755
index 997a7dfec5ab..000000000000
--- a/services/sync/tps/extensions/mozmill/install.rdf
+++ /dev/null
@@ -1,25 +0,0 @@
-
-
-
-
- mozmill@mozilla.com
- Mozmill
- 2.0.8
- UI Automation tool for Mozilla applications
- true
-
- Mozilla Automation and Testing Team
- Adam Christian
- Mikeal Rogers
-
-
-
- toolkit@mozilla.org
- 10.0
- 38.*
-
-
- true
-
-
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/controller.js b/services/sync/tps/extensions/mozmill/resource/driver/controller.js
deleted file mode 100644
index 9a6f2717ec65..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/driver/controller.js
+++ /dev/null
@@ -1,1139 +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", "windowMap"];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var 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;
-
-var 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 = {};
-
- if (!events) {
- return;
- }
- for (var key in events) {
- var e = events[key];
- 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 '" + e + "' 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":
- // 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.
- * @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.
- * @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') {
- let 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.ownerGlobal,
- 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 EventUtils.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;
- return browser.tabContainer.selectedIndex;
-});
-
-Tabs.prototype.selectTabIndex = function (aIndex) {
- var browser = this.controller.browserObject;
- 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)) {
- throw ex;
- }
- timed_out = true;
- }
- finally {
- let state = 'URI=' + win.document.location.href +
- ', readyState=' + win.document.readyState;
- let 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/driver/elementslib.js b/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
deleted file mode 100644
index a5dbf9bafeb8..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/driver/elementslib.js
+++ /dev/null
@@ -1,537 +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 = ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath",
- "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib",
- ];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-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) {
- i = str.indexOf('"', i);
- if (i != -1) {
- count++;
- i++;
- } else {
- break;
- }
- }
-
- return count;
-};
-
-/**
- * smartSplit()
- *
- * Takes a lookup string as input and returns
- * a list of each node in the string
- */
-var smartSplit = function (str) {
- // Ensure we have an even number of quotes
- if (countQuotes(str) % 2 != 0) {
- throw new Error ("Invalid Lookup Expression");
- }
-
- /**
- * This regex matches a single "node" in a lookup string.
- * In otherwords, it matches the part between the two '/'s
- *
- * Regex Explanation:
- * \/ - start matching at the first forward slash
- * ([^\/"]*"[^"]*")* - match as many pairs of quotes as possible until we hit a slash (ignore slashes inside quotes)
- * [^\/]* - match the remainder of text outside of last quote but before next slash
- */
- var re = /\/([^\/"]*"[^"]*")*[^\/]*/g
- var ret = []
- var match = re.exec(str);
-
- while (match != null) {
- ret.push(match[0].replace(/^\//, ""));
- match = re.exec(str);
- }
-
- return ret;
-};
-
-/**
- * defaultDocuments()
- *
- * Returns a list of default documents in which to search for elements
- * if no document is provided
- */
-function defaultDocuments() {
- var win = Services.wm.getMostRecentWindow("navigator:browser");
-
- return [
- win.document,
- utils.getBrowserObject(win).selectedBrowser.contentWindow.document
- ];
-};
-
-/**
- * nodeSearch()
- *
- * Takes an optional document, callback and locator string
- * Returns a handle to the located element or null
- */
-function nodeSearch(doc, func, string) {
- if (doc != undefined) {
- var documents = [doc];
- } 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) {
- 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++) {
- search(frames[i], func, string);
- }
- } else {
- e = element;
- }
- };
-
- for (var i = 0; i < documents.length; ++i) {
- var win = documents[i].defaultView;
- search(win, func, string);
- if (e) {
- break;
- }
- }
-
- return e;
-};
-
-/**
- * Selector()
- *
- * Finds an element by selector string
- */
-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;
-};
-
-/**
- * ID()
- *
- * Finds an element by ID
- */
-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);
-};
-
-/**
- * Link()
- *
- * Finds a link by innerHTML
- */
-function Link(_document, linkName) {
- if (linkName == undefined) {
- throw new Error('Link constructor did not recieve enough arguments.');
- }
-
- this.getNodeForDocument = function (linkName) {
- var getText = function (el) {
- var text = "";
-
- if (el.nodeType == 3) { //textNode
- if (el.data != undefined) {
- text = el.data;
- } else {
- text = el.innerHTML;
- }
-
- text = text.replace(/n|r|t/g, " ");
- }
- 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";
- }
- }
-
- return text;
- };
-
- //sometimes the windows won't have this function
- try {
- 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) {
- return el;
- }
- }
-
- return null;
- };
-
- return nodeSearch(_document, this.getNodeForDocument, linkName);
-};
-
-/**
- * XPath()
- *
- * Finds an element by XPath
- */
-function XPath(_document, expr) {
- if (expr == undefined) {
- throw new Error('XPath constructor did not recieve enough arguments.');
- }
-
- this.getNodeForDocument = function (s) {
- var aNode = this.document;
- var aExpr = s;
- var xpe = null;
-
- if (this.document.defaultView == null) {
- xpe = new getMethodInWindows('XPathEvaluator')();
- } else {
- xpe = new this.document.defaultView.XPathEvaluator();
- }
-
- 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()) {
- found.push(res);
- }
-
- return found[0];
- };
-
- return nodeSearch(_document, this.getNodeForDocument, expr);
-};
-
-/**
- * Name()
- *
- * Finds an element by Name
- */
-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];
- }
- } catch (e) {
- }
-
- return null;
- };
-
- return nodeSearch(_document, this.getNodeForDocument, nName);
-};
-
-
-var _returnResult = function (results) {
- if (results.length == 0) {
- return null
- }
- else if (results.length == 1) {
- return results[0];
- } else {
- return results;
- }
-}
-
-var _forChildren = function (element, name, value) {
- var results = [];
- var nodes = Array.from(element.childNodes).filter(e => 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 = Array.from(_document.getAnoymousNodes(element)).filter(e => 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];
- let requirementPass = 0;
- let requirementLength = 0;
-
- for (var a in attributes) {
- requirementLength++;
- try {
- if (n.getAttribute(a) == attributes[a]) {
- requirementPass++;
- }
- } 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);
- if (result) {
- return result;
- }
- }
-
- var nodes = Array.from(_document.getAnonymousNodes(parent)).filter(n => 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);
- if (results.length == 0) {
- resultsForNodes(Array.from(parent.childNodes).filter(n => 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];
-}
-
-/**
- * Lookup()
- *
- * Finds an element by Lookup expression
- */
-function Lookup(_document, expression) {
- if (expression == undefined) {
- throw new Error('Lookup constructor did not recieve enough arguments.');
- }
-
- var expSplit = smartSplit(expression).filter(e => e != '');
- 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;
- }
-
- // Handle case where only index is provided
- var cases = nCases;
-
- // Handle ending index before any of the expression gets mangled
- if (withs.endsWith(exp, ']')) {
- var expIndex = json2.JSON.parse(strings.vslice(exp, '[', ']'));
- }
-
- // Handle anon
- if (withs.startsWith(exp, 'anon')) {
- exp = strings.vslice(exp, '(', ')');
- cases = aCases;
- }
-
- if (withs.startsWith(exp, '[')) {
- try {
- var obj = json2.JSON.parse(strings.vslice(exp, '[', ']'));
- } catch (e) {
- throw new SyntaxError(e + '. String to be parsed was || ' +
- strings.vslice(exp, '[', ']') + ' ||');
- }
-
- var r = cases['index'](_document, parentNode, obj);
- if (r == null) {
- throw new SyntaxError('Expression "' + exp +
- '" returned null. Anonymous == ' + (cases == aCases));
- }
-
- return r;
- }
-
- for (var c in cases) {
- if (withs.startsWith(exp, c)) {
- try {
- var obj = json2.JSON.parse(strings.vslice(exp, '(', ')'))
- } catch (e) {
- throw new SyntaxError(e + '. String to be parsed was || ' +
- strings.vslice(exp, '(', ')') + ' ||');
- }
- var result = cases[c](_document, parentNode, obj);
- }
- }
-
- if (!result) {
- if (withs.startsWith(exp, '{')) {
- try {
- 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, parentNode, obj);
- } else {
- var result = _byAttrib(parentNode, obj);
- }
- }
- }
-
- // Final return
- if (expIndex) {
- // TODO: Check length and raise error
- return result[expIndex];
- } else {
- // 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
deleted file mode 100644
index 43c88c27d3de..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js
+++ /dev/null
@@ -1,1157 +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"
- ];
-
-const NAMESPACE_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var 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 EventUtils.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.ownerGlobal || srcNode;
- var destWindow = aDestWindow || destNode.ownerGlobal || 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("DragEvent");
- 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.ownerGlobal || 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.ownerGlobal);
- } else {
- EventUtils.synthesizeMouse(this.element, aOffsetX, aOffsetY, aEvent,
- this.element.ownerGlobal);
- }
-
- // 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.ownerGlobal);
-
- 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.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.ownerGlobal || 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/driver/mozmill.js b/services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
deleted file mode 100644
index c49eec972b8e..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/driver/mozmill.js
+++ /dev/null
@@ -1,283 +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 = ["controller", "utils", "elementslib", "os",
- "getBrowserController", "newBrowserController",
- "getAddonsController", "getPreferencesController",
- "newMail3PaneController", "getMail3PaneController",
- "wm", "platform", "getAddrbkController",
- "getMsgComposeController", "getDownloadsController",
- "Application", "findElement",
- "getPlacesController", 'isMac', 'isLinux', 'isWindows',
- "firePythonCallback", "getAddons"
- ];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-
-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 = Services.wm;
-
-var appInfo = Services.appinfo;
-var Application = utils.applicationName;
-
-
-/**
- * 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;
- }
-}
-
-/**
- * Retrieves application details for the Mozmill report
- *
- * @return {String} JSON data of application details
- */
-function getApplicationDetails() {
- var locale = Services.locale.getAppLocaleAsLangTag();
-
- // 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/
-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) {
- startupInfo = null;
- }
-
- return startupInfo;
-}
-
-
-
-function newBrowserController () {
- return new controller.MozMillController(utils.getMethodInWindows('OpenBrowserWindow')());
-}
-
-function getBrowserController () {
- var browserWindow = wm.getMostRecentWindow("navigator:browser");
-
- if (browserWindow == null) {
- return newBrowserController();
- } 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') {
- utils.getMethodInWindows('openAddonsMgr')();
- }
- 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(''));
-}
-
-function getPreferencesController() {
- if (Application == 'Thunderbird') {
- utils.getMethodInWindows('openOptionsDialog')();
- } else {
- utils.getMethodInWindows('openPreferences')();
- }
-
- return new controller.MozMillController(wm.getMostRecentWindow(''));
-}
-
-// Thunderbird functions
-function newMail3PaneController () {
- return new controller.MozMillController(utils.getMethodInWindows('toMessengerWindow')());
-}
-
-function getMail3PaneController () {
- var mail3PaneWindow = wm.getMostRecentWindow("mail:3pane");
-
- if (mail3PaneWindow == null) {
- return newMail3PaneController();
- } else {
- return new controller.MozMillController(mail3PaneWindow);
- }
-}
-
-// Thunderbird - Address book window
-function newAddrbkController () {
- utils.getMethodInWindows("toAddressBook")();
- utils.sleep(2000);
- var addyWin = wm.getMostRecentWindow("mail:addressbook");
-
- return new controller.MozMillController(addyWin);
-}
-
-function getAddrbkController () {
- var addrbkWindow = wm.getMostRecentWindow("mail:addressbook");
- if (addrbkWindow == null) {
- return newAddrbkController();
- } else {
- return new controller.MozMillController(addrbkWindow);
- }
-}
-
-function firePythonCallback (filename, method, args, kwargs) {
- let obj = {'filename': filename, 'method': method};
- obj['args'] = args || [];
- obj['kwargs'] = kwargs || {};
-
- broker.sendMessage("firePythonCallback", obj);
-}
-
-function timer (name) {
- this.name = name;
- this.timers = {};
- 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
deleted file mode 100644
index 95e431f084d2..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js
+++ /dev/null
@@ -1,58 +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 = ['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
deleted file mode 100644
index 352f5eb44a4a..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/assertions.js
+++ /dev/null
@@ -1,672 +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', 'Expect'];
-
-var 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.
- */
-
-/**
- * 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 () {}
-
-Assert.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 this._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 throwing an AssertionException.
- *
- * @param {object} aResult
- * Test result details used for reporting.
- *
- * - fileName
- * - Name of the file in which the assertion failed.
- * - 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 Assert__logFail(aResult) {
- throw new errors.AssertionError(aResult.message,
- aResult.fileName,
- aResult.lineNumber,
- aResult.functionName,
- aResult.name);
- },
-
- /**
- * Log a test as passing by adding a pass frame.
- *
- * @param {object} aResult
- * Test result details used for reporting.
- *
- * - fileName
- * - Name of the file in which the assertion failed.
- * - functionName
- * - Function in which the assertion failed.
- * - lineNumber
- * - Line number of the file in which the assertion failed.
- * - message
- * - Message why the assertion failed.
- *
- */
- _logPass: function Assert__logPass(aResult) {
- broker.pass({pass: aResult});
- },
-
- /**
- * Test the condition and mark test as passed or failed
- *
- * @param {boolean} aCondition
- * Condition to test.
- * @param {string} aMessage
- * 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 Assert__test(aCondition, aMessage, aDiagnosis) {
- let diagnosis = aDiagnosis || "";
- let message = aMessage || "";
-
- if (diagnosis)
- message = aMessage ? message + " - " + diagnosis : diagnosis;
-
- // Build result data
- let frame = stack.findCallerFrame(Components.stack);
-
- let result = {
- 'fileName' : frame.filename.replace(/(.*)-> /, ""),
- 'functionName' : frame.name,
- 'lineNumber' : frame.lineNumber,
- 'message' : message
- };
-
- // Log test result
- if (aCondition) {
- this._logPass(result);
- }
- else {
- result.stack = Components.stack;
- this._logFail(result);
- }
-
- return aCondition;
- },
-
- /**
- * Perform an always passing test
- *
- * @param {string} aMessage
- * Message to show for the test result.
- * @returns {boolean} Always returns true.
- */
- pass: function Assert_pass(aMessage) {
- return this._test(true, aMessage, undefined);
- },
-
- /**
- * Perform an always failing test
- *
- * @param {string} aMessage
- * Message to show for the test result.
- * @throws {errors.AssertionError}
- *
- * @returns {boolean} Always returns false.
- */
- fail: function Assert_fail(aMessage) {
- return this._test(false, aMessage, undefined);
- },
-
- /**
- * Test if the value pass
- *
- * @param {boolean|string|number|object} aValue
- * Value to test.
- * @param {string} aMessage
- * Message to show for the test result.
- * @throws {errors.AssertionError}
- *
- * @returns {boolean} Result of the test.
- */
- 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
- * Value to test.
- * @param {boolean|string|number|object} aExpected
- * 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 Assert_equal(aValue, aExpected, aMessage) {
- let condition = (aValue === 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
- * Value to test.
- * @param {boolean|string|number|object} aExpected
- * 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 Assert_notEqual(aValue, aExpected, aMessage) {
- let condition = (aValue !== 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.
- *
- * @param {string} aString
- * String to test.
- * @param {RegEx} aRegex
- * 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 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
- let pattern = "";
- let flags = "";
- try {
- let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
-
- pattern = matches[1];
- flags = matches[2];
- } catch (e) {
- }
-
- let regex = new RegExp(pattern, flags);
- let condition = (aString.match(regex) !== null);
- let diagnosis = "'" + regex + "' matches for '" + aString + "'";
-
- return this._test(condition, aMessage, diagnosis);
- },
-
- /**
- * Test if the regular expression does not match the string.
- *
- * @param {string} aString
- * String to test.
- * @param {RegEx} aRegex
- * 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 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
- let pattern = flags = "";
- try {
- let matches = aRegex.toString().match(/\/(.*)\/(.*)/);
-
- pattern = matches[1];
- flags = matches[2];
- } catch (e) {
- }
-
- let regex = new RegExp(pattern, flags);
- let condition = (aString.match(regex) === null);
- let diagnosis = "'" + regex + "' doesn't match for '" + aString + "'";
-
- return this._test(condition, aMessage, diagnosis);
- },
-
-
- /**
- * Test if a code block throws an exception.
- *
- * @param {string} block
- * function to call to test for exception
- * @param {RegEx} error
- * the expected error class
- * @param {string} message
- * message to present if assertion fails
- * @throws {errors.AssertionError}
- *
- * @returns {boolean} Result of the test.
- */
- throws : function Assert_throws(block, /*optional*/error, /*optional*/message) {
- return this._throws.apply(this, [true].concat(Array.prototype.slice.call(arguments)));
- },
-
- /**
- * Test if a code block doesn't throw an exception.
- *
- * @param {string} block
- * function to call to test for exception
- * @param {RegEx} error
- * the expected error class
- * @param {string} message
- * message to present if assertion fails
- * @throws {errors.AssertionError}
- *
- * @returns {boolean} Result of the test.
- */
- doesNotThrow : function Assert_doesNotThrow(block, /*optional*/error, /*optional*/message) {
- return this._throws.apply(this, [false].concat(Array.prototype.slice.call(arguments)));
- },
-
- /* Tests whether a code block throws the expected exception
- class. helper for throws() and doesNotThrow()
-
- adapted from node.js's assert._throws()
- https://github.com/joyent/node/blob/master/lib/assert.js
- */
- _throws : function Assert__throws(shouldThrow, block, expected, message) {
- var actual;
-
- if (typeof expected === 'string') {
- message = expected;
- expected = null;
- }
-
- try {
- block();
- } catch (e) {
- actual = e;
- }
-
- message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
- (message ? ' ' + message : '.');
-
- if (shouldThrow && !actual) {
- return this._test(false, message, 'Missing expected exception');
- }
-
- if (!shouldThrow && this._expectedException(actual, expected)) {
- return this._test(false, message, 'Got unwanted exception');
- }
-
- if ((shouldThrow && actual && expected &&
- !this._expectedException(actual, expected)) || (!shouldThrow && actual)) {
- throw actual;
- }
-
- return this._test(true, message);
- },
-
- /**
- * 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;
- }
- }
-
- var hwindow = Services.appShell.hiddenDOMWindow;
- var timeoutInterval = hwindow.setInterval(wait, interval);
- var thread = Services.tm.currentThread;
-
- Services.tm.spinEventLoopUntil(() => {
- let type = typeof(self.result);
- if (type !== 'boolean') {
- throw TypeError("waitFor() callback has to return a boolean" +
- " instead of '" + type + "'");
- }
-
- return self.result === true || self.timeIsUp;
- });
-
- 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;
- }
-}
-
-/* non-fatal assertions */
-var Expect = function () {}
-
-Expect.prototype = new Assert();
-
-/**
- * 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.
- * - functionName
- * - Function in which the assertion failed.
- * - lineNumber
- * - Line number of the file in which the assertion failed.
- * - message
- * - Message why the assertion failed.
- *
- */
-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;
-
- try {
- Assert.prototype.waitFor.apply(this, arguments);
- }
- catch (ex) {
- if (!(ex instanceof errors.AssertionError)) {
- throw ex;
- }
- message = ex.message;
- condition = false;
- }
-
- return this._test(condition, message);
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/driver.js b/services/sync/tps/extensions/mozmill/resource/modules/driver.js
deleted file mode 100644
index 17fcfbde60f2..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/driver.js
+++ /dev/null
@@ -1,290 +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/. */
-
-/**
- * @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
deleted file mode 100644
index 58d1a918a2da..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/errors.js
+++ /dev/null
@@ -1,102 +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 = ['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
deleted file mode 100644
index 717b1e3b2845..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/frame.js
+++ /dev/null
@@ -1,787 +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 = ['Collector','Runner','events', 'runTestFile', 'log',
- 'timers', 'persisted', 'shutdownApplication'];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var Cu = Components.utils;
-
-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 assert = new assertions.Assert();
-var expect = new assertions.Expect();
-
-var mozmill = undefined;
-var mozelement = undefined;
-var modules = undefined;
-
-var timers = [];
-
-
-/**
- * Shutdown or restart the application
- *
- * @param {boolean} [aFlags=undefined]
- * Additional flags how to handle the shutdown or restart.
- * @see https://developer.mozilla.org/nsIAppStartup#Attributes
- */
-function shutdownApplication(aFlags) {
- var flags = Ci.nsIAppStartup.eForceQuit;
-
- if (aFlags) {
- flags |= aFlags;
- }
-
- // 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");
-
- // 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);
-}
-
-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];
- if (!r(v)) {
- // TODO error value did not pass restriction
- return;
- }
- }
- }
-
- // Fire jsbridge notification, logging notification, listener notifications
- events[target] = v;
- events.fireEvent(cmeta, target);
-}
-
-
-var events = {
- 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',
- 'test', 'setupTest', 'teardownTest', 'collection'],
- null, 'currentState', 'setState', v);
-}
-
-events.toggleUserShutdown = function (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);
- }
- }
-}
-
-events.isUserShutdown = function () {
- return this.userShutdown ? this.userShutdown["user"] : false;
-}
-
-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__ = [];
-
- events.currentTest = test;
-
- 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.__end__ = Date.now();
- 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 failing
- if (withs.startsWith(test.__name__, "test") || test.__fails__.length > 0) {
- events.fireEvent('endTest', obj);
- }
-}
-
-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) {
- // a low level event, such as a keystroke, succeeds
- if (events.currentTest) {
- events.currentTest.__passes__.push(obj);
- }
-
- for (var timer of timers) {
- timer.actions.push(
- {"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) {
- // Error objects aren't enumerable https://bugzilla.mozilla.org/show_bug.cgi?id=637207
- obj.exception = {
- name: error.name,
- message: error.message,
- lineNumber: error.lineNumber,
- fileName: error.fileName,
- stack: error.stack
- };
- }
-
- // a low level event, such as a keystroke, fails
- if (events.currentTest) {
- events.currentTest.__fails__.push(obj);
- }
-
- for (var timer of timers) {
- timer.actions.push(
- {"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 nothing else
- events.currentTest.skipped = true;
- events.currentTest.skipped_reason = reason;
-
- for (var timer of timers) {
- timer.actions.push(
- {"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 (var listener of this.globalListeners) {
- listener(name, obj);
- }
-}
-
-events.addListener = function (name, listener) {
- if (this.listeners[name]) {
- this.listeners[name].push(listener);
- } else if (name == '') {
- this.globalListeners.push(listener)
- } else {
- this.listeners[name] = [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] = arrays.remove(e, i);
- }
- }
- }
-
- for (var i in this.globalListeners) {
- if (this.globalListeners[i] == listener) {
- 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 {
- Cu.import('resource://jsbridge/modules/Events.jsm');
-
- events.addListener('', function (name, obj) {
- Events.fireEvent('mozmill.' + name, obj);
- });
-} catch (e) {
- Services.console.logStringMessage("Event module of JSBridge not available.");
-}
-
-
-/**
- * Observer for notifications when the application is going to shutdown
- */
-function AppQuitObserver() {
- this.runner = null;
-
- Services.obs.addObserver(this, "quit-application-requested");
-}
-
-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 = [];
-}
-
-Collector.prototype.addHttpResource = function (aDirectory, aPath) {
- var fp = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- fp.initWithPath(os.abspath(aDirectory, this.current_file));
-
- return httpd.addHttpResource(fp, aPath);
-}
-
-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;
-
- // 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 (testname && (i != testname)) {
- continue;
- }
-
- testname = null;
- test_module.__tests__.push(test_module[i]);
- }
- }
- }
-
- test_module.collector = this;
- test_module.status = 'loaded';
-
- this.test_modules_by_filename[filename] = test_module;
-
- return test_module;
-}
-
-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.nsIFile);
- 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 = 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 = 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 : { assert: assert,
- expect: expect,
- 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;
-}
-
-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 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) {
- var module = this.collector.initTestModule(filename, name);
- this.runTestModule(module);
-};
-
-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");
- events.endTest(func);
- return false;
- }
- }
-
- // skip function if requested
- if (func.__force_skip__ != undefined) {
- events.skip(func.__force_skip__);
- events.endTest(func);
- return false;
- }
-
- // execute the test function
- try {
- func(arg);
- } catch (e) {
- if (e instanceof errors.ApplicationQuitError) {
- events.shutdownRequested = true;
- } else {
- events.fail({'exception': e, 'test': func})
- }
- }
-
- // 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);
- }
-
- events.endTest(func);
- return events.currentTest.__fails__.length == 0;
-};
-
-function runTestFile(filename, name) {
- var runner = new Runner();
- runner.runTestFile(filename, name);
- runner.end();
-
- return true;
-}
-
-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/l10n.js b/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
deleted file mode 100644
index 78280ac0bcfd..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/l10n.js
+++ /dev/null
@@ -1,71 +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/. */
-
-/**
- * @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
- *
- * @memberOf l10n
- * @param {String[]} aDTDs Array of URLs for DTD files.
- * @param {String} aEntityId ID of the entity to get the localized content of.
- *
- * @returns {String} Localized content
- */
-function getEntity(aDTDs, aEntityId) {
- // Add xhtml11.dtd to prevent missing entity errors with XHTML files
- aDTDs.push("resource:///res/dtd/xhtml11.dtd");
-
- // Build a string of external entities
- var references = "";
- for (let i = 0; i < aDTDs.length; i++) {
- var id = 'dtd' + i;
- references += '%' + id + ';';
- }
-
- var header = '';
- var element = '&' + aEntityId + ';';
- var content = header + element;
-
- var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
- createInstance(Ci.nsIDOMParser);
- var doc = parser.parseFromString(content, 'text/xml');
- var node = doc.querySelector('elem[id="entity"]');
-
- if (!node) {
- throw new Error("Unkown entity '" + aEntityId + "'");
- }
-
- return node.textContent;
-}
-
-
-/**
- * Retrieve the localized content for a given property
- *
- * @memberOf l10n
- * @param {String} aURL URL of the .properties file.
- * @param {String} aProperty The property to get the value of.
- *
- * @returns {String} Value of the requested property
- */
-function getProperty(aURL, aProperty) {
- var bundle = Services.strings.createBundle(aURL);
-
- try {
- return bundle.GetStringFromName(aProperty);
- } catch (ex) {
- throw new Error("Unkown property '" + aProperty + "'");
- }
-}
-
-
-// Export of functions
-l10n.getEntity = getEntity;
-l10n.getProperty = getProperty;
diff --git a/services/sync/tps/extensions/mozmill/resource/modules/stack.js b/services/sync/tps/extensions/mozmill/resource/modules/stack.js
deleted file mode 100644
index 889316bf18e4..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/stack.js
+++ /dev/null
@@ -1,43 +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 = ['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/windows.js b/services/sync/tps/extensions/mozmill/resource/modules/windows.js
deleted file mode 100644
index 870b17066158..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/modules/windows.js
+++ /dev/null
@@ -1,292 +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 = ["init", "map"];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var 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);
- }
-}
-
-// 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");
- observerService.addObserver(windowCloseObserver, "outer-window-destroyed");
- observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing");
-
- handleAttachEventListeners();
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js b/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
deleted file mode 100644
index d1b7a9ca376e..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js
+++ /dev/null
@@ -1,823 +0,0 @@
-// Export all available functions for Mozmill
-var EXPORTED_SYMBOLS = ["disableNonTestMouseEvents","sendMouseEvent", "sendChar",
- "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch",
- "synthesizeMouseAtPoint", "synthesizeTouchAtPoint",
- "synthesizeMouseAtCenter", "synthesizeTouchAtCenter",
- "synthesizeWheel", "synthesizeKey",
- "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent",
- "synthesizeText",
- "synthesizeComposition", "synthesizeQuerySelectedText"];
-
-var Ci = Components.interfaces;
-var 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);
- }
-}
-
-/**
- * EventUtils provides some utility methods for creating and sending DOM events.
- * Current methods:
- * sendMouseEvent
- * sendChar
- * sendString
- * sendKey
- * synthesizeMouse
- * synthesizeMouseAtCenter
- * synthesizeWheel
- * synthesizeKey
- * synthesizeMouseExpectEvent
- * synthesizeKeyExpectEvent
- *
- * When adding methods to this file, please add a performance test for it.
- */
-
-/**
- * Send a mouse event to the node aTarget (aTarget can be an id, or an
- * actual node) . The "event" passed in to aEvent is just a JavaScript
- * object with the properties set that the real mouse event object should
- * have. This includes the type of the mouse event.
- * E.g. to send an click event to the node with id 'node' you might do this:
- *
- * 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', '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 aWindow.Element)) {
- aTarget = aWindow.document.getElementById(aTarget);
- }
-
- var event = aWindow.document.createEvent('MouseEvent');
-
- var typeArg = aEvent.type;
- var canBubbleArg = true;
- var cancelableArg = true;
- var viewArg = aWindow;
- var detailArg = aEvent.detail || (aEvent.type == 'click' ||
- aEvent.type == 'mousedown' ||
- aEvent.type == 'mouseup' ? 1 :
- aEvent.type == 'dblclick'? 2 : 0);
- var screenXArg = aEvent.screenX || 0;
- var screenYArg = aEvent.screenY || 0;
- var clientXArg = aEvent.clientX || 0;
- var clientYArg = aEvent.clientY || 0;
- var ctrlKeyArg = aEvent.ctrlKey || false;
- var altKeyArg = aEvent.altKey || false;
- var shiftKeyArg = aEvent.shiftKey || false;
- var metaKeyArg = aEvent.metaKey || false;
- var buttonArg = aEvent.button || 0;
- var relatedTargetArg = aEvent.relatedTarget || null;
-
- event.initMouseEvent(typeArg, canBubbleArg, cancelableArg, viewArg, detailArg,
- screenXArg, screenYArg, clientXArg, clientYArg,
- ctrlKeyArg, altKeyArg, shiftKeyArg, metaKeyArg,
- buttonArg, relatedTargetArg);
-
- SpecialPowers.dispatchEvent(aWindow, aTarget, event);
-}
-
-/**
- * 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 ASCII characters and emulates the shift
- * key state on US keyboard layout.
- */
-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;
- }
- synthesizeKey(aChar, { shiftKey: hasShift }, aWindow);
-}
-
-/**
- * Send the string aStr to the focused element.
- *
- * For now this method only works for ASCII characters and emulates the shift
- * key state on US keyboard layout.
- */
-function sendString(aStr, aWindow) {
- for (var i = 0; i < aStr.length; ++i) {
- sendChar(aStr.charAt(i), aWindow);
- }
-}
-
-/**
- * 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, aWindow) {
- var keyName = "VK_" + aKey.toUpperCase();
- synthesizeKey(keyName, { shiftKey: false }, aWindow);
-}
-
-/**
- * Parse the key modifier flags from aEvent. Used to share code between
- * synthesizeMouse and synthesizeKey.
- */
-function _parseModifiers(aEvent)
-{
- const nsIDOMWindowUtils = _EU_Ci.nsIDOMWindowUtils;
- var mval = 0;
- 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;
-}
-
-/**
- * Synthesize a mouse event on a target. The actual client point is determined
- * by taking the aTarget's client box and offseting it by aOffsetX and
- * aOffsetY. This allows mouse clicks to be simulated by calling this method.
- *
- * 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.
- *
- * Returns whether the event had preventDefault() called on it.
- */
-function synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
-{
- 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;
-
- 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;
-
- 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, 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 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, deltaX, deltaY, deltaZ,
- * deltaMode, lineOrPageDeltaX, lineOrPageDeltaY, isMomentum, isPixelOnlyDevice,
- * isCustomizedByPrefs, expectedOverflowDeltaX, expectedOverflowDeltaY
- *
- * deltaMode must be defined, others are ok even if undefined.
- *
- * 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 synthesizeWheel(aTarget, aOffsetX, aOffsetY, aEvent, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return;
- }
-
- var modifiers = _parseModifiers(aEvent);
- var options = 0;
- if (aEvent.isPixelOnlyDevice &&
- (aEvent.deltaMode == WheelEvent.DOM_DELTA_PIXEL)) {
- options |= utils.WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_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;
- }
- }
- 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 new Error(`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;
- }
-}
-
-/**
- * Synthesize a key event. It is targeted at whatever would be targeted by an
- * actual keypress by the user, typically the focused element.
- *
- * aKey should be either a character or a keycode starting with VK_ such as
- * VK_ENTER.
- *
- * aEvent is an object which may contain the properties:
- * 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.
- *
- * aWindow is optional, and defaults to the current window object.
- */
-function synthesizeKey(aKey, aEvent, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (utils) {
- var keyCode = 0, charCode = 0;
- if (aKey.indexOf("VK_") == 0) {
- keyCode = KeyEvent["DOM_" + aKey];
- if (!keyCode) {
- throw new Error(`Unknown key: ${aKey}`);
- }
- } else {
- charCode = aKey.charCodeAt(0);
- keyCode = _computeKeyCodeFromChar(aKey.charAt(0));
- }
-
- var modifiers = _parseModifiers(aEvent);
- 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;
- }
- }
-
- if (!("type" in aEvent) || !aEvent.type) {
- // Send keydown + (optional) keypress + keyup events.
- var keyDownDefaultHappened =
- 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);
- }
- }
-}
-
-var _gSeenEvent = false;
-
-/**
- * Indicate that an event with an original target of aExpectedTarget and
- * a type of aExpectedEvent is expected to be fired, or not expected to
- * be fired.
- */
-function _expectEvent(aExpectedTarget, aExpectedEvent, aTestName)
-{
- if (!aExpectedTarget || !aExpectedEvent)
- return null;
-
- _gSeenEvent = false;
-
- var type = (aExpectedEvent.charAt(0) == "!") ?
- aExpectedEvent.substring(1) : aExpectedEvent;
- var eventHandler = function(event) {
- var epassed = (!_gSeenEvent && event.originalTarget == aExpectedTarget &&
- event.type == type);
- is(epassed, true, aTestName + " " + type + " event target " + (_gSeenEvent ? "twice" : ""));
- _gSeenEvent = true;
- };
-
- aExpectedTarget.addEventListener(type, eventHandler);
- return eventHandler;
-}
-
-/**
- * Check if the event was fired or not. The event handler aEventHandler
- * will be removed.
- */
-function _checkExpectedEvent(aExpectedTarget, aExpectedEvent, aEventHandler, aTestName)
-{
- if (aEventHandler) {
- var expectEvent = (aExpectedEvent.charAt(0) != "!");
- var type = expectEvent ? aExpectedEvent : aExpectedEvent.substring(1);
- aExpectedTarget.removeEventListener(type, aEventHandler);
- var desc = type + " event";
- if (!expectEvent)
- desc += " not";
- is(_gSeenEvent, expectEvent, aTestName + " " + desc + " fired");
- }
-
- _gSeenEvent = false;
-}
-
-/**
- * Similar to synthesizeMouse except that a test is performed to see if an
- * event is fired at the right target as a result.
- *
- * aExpectedTarget - the expected originalTarget of the event.
- * aExpectedEvent - the expected type of the event, such as 'select'.
- * aTestName - the test name when outputing results
- *
- * To test that an event is not fired, use an expected type preceded by an
- * exclamation mark, such as '!select'. This might be used to test that a
- * click on a disabled element doesn't fire certain events for instance.
- *
- * aWindow is optional, and defaults to the current window object.
- */
-function synthesizeMouseExpectEvent(aTarget, aOffsetX, aOffsetY, aEvent,
- aExpectedTarget, aExpectedEvent, aTestName,
- aWindow)
-{
- var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
- synthesizeMouse(aTarget, aOffsetX, aOffsetY, aEvent, aWindow);
- _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
-}
-
-/**
- * Similar to synthesizeKey except that a test is performed to see if an
- * event is fired at the right target as a result.
- *
- * aExpectedTarget - the expected originalTarget of the event.
- * aExpectedEvent - the expected type of the event, such as 'select'.
- * aTestName - the test name when outputing results
- *
- * To test that an event is not fired, use an expected type preceded by an
- * exclamation mark, such as '!select'.
- *
- * aWindow is optional, and defaults to the current window object.
- */
-function synthesizeKeyExpectEvent(key, aEvent, aExpectedTarget, aExpectedEvent,
- aTestName, aWindow)
-{
- var eventHandler = _expectEvent(aExpectedTarget, aExpectedEvent, aTestName);
- synthesizeKey(key, aEvent, aWindow);
- _checkExpectedEvent(aExpectedTarget, aExpectedEvent, eventHandler, aTestName);
-}
-
-function disableNonTestMouseEvents(aDisable)
-{
- var domutils = _getDOMWindowUtils();
- domutils.disableNonTestMouseEvents(aDisable);
-}
-
-function _getDOMWindowUtils(aWindow)
-{
- if (!aWindow) {
- aWindow = window;
- }
-
- // 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 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(aEvent, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return;
- }
-
- utils.sendCompositionEvent(aEvent.type, aEvent.data ? aEvent.data : "",
- aEvent.locale ? aEvent.locale : "");
-}
-/**
- * Synthesize a text event.
- *
- * @param aEvent The text event's information, this has |composition|
- * and |caret| members. |composition| has |string| and
- * |clauses| members. |clauses| must be array object. Each
- * object has |length| and |attr|. And |caret| has |start| and
- * |length|. See the following tree image.
- *
- * aEvent
- * +-- composition
- * | +-- string
- * | +-- clauses[]
- * | +-- length
- * | +-- attr
- * +-- caret
- * +-- start
- * +-- length
- *
- * Set the composition string to |composition.string|. Set its
- * clauses information to the |clauses| array.
- *
- * When it's composing, set the each clauses' length to the
- * |composition.clauses[n].length|. The sum of the all length
- * values must be same as the length of |composition.string|.
- * Set nsIDOMWindowUtils.COMPOSITION_ATTR_* to the
- * |composition.clauses[n].attr|.
- *
- * When it's not composing, set 0 to the
- * |composition.clauses[0].length| and
- * |composition.clauses[0].attr|.
- *
- * Set caret position to the |caret.start|. It's offset from
- * the start of the composition string. Set caret length to
- * |caret.length|. If it's larger than 0, it should be wide
- * caret. However, current nsEditor doesn't support wide
- * caret, therefore, you should always set 0 now.
- *
- * @param aWindow Optional (If null, current |window| will be used)
- */
-function synthesizeText(aEvent, aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return;
- }
-
- if (!aEvent.composition || !aEvent.composition.clauses ||
- !aEvent.composition.clauses[0]) {
- return;
- }
-
- var firstClauseLength = aEvent.composition.clauses[0].length;
- var firstClauseAttr = aEvent.composition.clauses[0].attr;
- var secondClauseLength = 0;
- var secondClauseAttr = 0;
- var thirdClauseLength = 0;
- var thirdClauseAttr = 0;
- if (aEvent.composition.clauses[1]) {
- secondClauseLength = aEvent.composition.clauses[1].length;
- secondClauseAttr = aEvent.composition.clauses[1].attr;
- if (aEvent.composition.clauses[2]) {
- thirdClauseLength = aEvent.composition.clauses[2].length;
- thirdClauseAttr = aEvent.composition.clauses[2].attr;
- }
- }
-
- var caretStart = -1;
- var caretLength = 0;
- if (aEvent.caret) {
- caretStart = aEvent.caret.start;
- caretLength = aEvent.caret.length;
- }
-
- utils.sendTextEvent(aEvent.composition.string,
- firstClauseLength, firstClauseAttr,
- secondClauseLength, secondClauseAttr,
- thirdClauseLength, thirdClauseAttr,
- caretStart, caretLength);
-}
-
-/**
- * Synthesize a query selected text event.
- *
- * @param aWindow Optional (If null, current |window| will be used)
- * @return An nsIQueryContentEventResult object. If this failed,
- * the result might be null.
- */
-function synthesizeQuerySelectedText(aWindow)
-{
- var utils = _getDOMWindowUtils(aWindow);
- if (!utils) {
- return null;
- }
-
- return utils.sendQueryContentEvent(utils.QUERY_SELECTED_TEXT, 0, 0, 0, 0);
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js b/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
deleted file mode 100644
index c70a262c9671..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js
+++ /dev/null
@@ -1,78 +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 = ['inArray', 'getSet', 'indexOf',
- 'remove', 'rindexOf', 'compare'];
-
-
-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) {
- var narray = [];
-
- for (var i in array) {
- if (!inArray(narray, array[i])) {
- narray.push(array[i]);
- }
- }
-
- return narray;
-}
-
-function indexOf(array, v, offset) {
- for (var i in array) {
- if (offset == undefined || i >= offset) {
- if (!isNaN(i) && array[i] == v) {
- return new Number(i);
- }
- }
- }
-
- return -1;
-}
-
-function rindexOf (array, v) {
- var l = array.length;
-
- for (var i in array) {
- if (!isNaN(i)) {
- var i = new Number(i);
- }
-
- if (!isNaN(i) && array[l - i] == v) {
- return l - i;
- }
- }
-
- return -1;
-}
-
-function compare (array, carray) {
- if (array.length != carray.length) {
- return false;
- }
-
- 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
deleted file mode 100644
index 06bfcb529be3..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/dom.js
+++ /dev/null
@@ -1,24 +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 = ['getAttributes'];
-
-
-var getAttributes = function (node) {
- var attributes = {};
-
- for (var i in node.attributes) {
- if (!isNaN(i)) {
- try {
- var attr = node.attributes[i];
- attributes[attr.name] = attr.value;
- }
- 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
deleted file mode 100644
index e38d36a2600c..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js
+++ /dev/null
@@ -1,5349 +0,0 @@
-/* -*- Mode: JavaScript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et: */
-/* 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/. */
-
-/*
- * An implementation of an HTTP server both as a loadable script and as an XPCOM
- * component. See the accompanying README file for user documentation on
- * 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");
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cr = Components.results;
-const Cu = Components.utils;
-const CC = Components.Constructor;
-
-const PR_UINT32_MAX = Math.pow(2, 32) - 1;
-
-/** True if debugging output is enabled, false otherwise. */
-var DEBUG = false; // non-const *only* so tweakable in server tests
-
-/** True if debugging output should be timestamped. */
-var DEBUG_TIMESTAMP = false; // non-const so tweakable in server tests
-
-var gGlobalObject = Cu.getGlobalForObject(this);
-
-/**
- * Asserts that the given condition holds. If it doesn't, the given message is
- * dumped, a stack trace is printed, and an exception is thrown to attempt to
- * stop execution (which unfortunately must rely upon the exception not being
- * accidentally swallowed by the code that uses it).
- */
-function NS_ASSERT(cond, msg)
-{
- if (DEBUG && !cond)
- {
- dumpn("###!!!");
- dumpn("###!!! ASSERTION" + (msg ? ": " + msg : "!"));
- dumpn("###!!! Stack follows:");
-
- var stack = new Error().stack.split(/\n/);
- dumpn(stack.map(function(val) { return "###!!! " + val; }).join("\n"));
-
- throw Cr.NS_ERROR_ABORT;
- }
-}
-
-/** Constructs an HTTP error object. */
-this.HttpError = function HttpError(code, description)
-{
- this.code = code;
- this.description = description;
-}
-HttpError.prototype =
-{
- toString: function()
- {
- return this.code + " " + this.description;
- }
-};
-
-/**
- * Errors thrown to trigger specific HTTP server responses.
- */
-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");
-
-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)
-{
- var obj = {};
- for (var i = 0; i < arr.length; i++)
- obj[arr[i]] = arr[i];
- return obj;
-}
-
-/** Returns an array of the integers x through y, inclusive. */
-function range(x, y)
-{
- var arr = [];
- for (var i = x; i <= y; i++)
- arr.push(i);
- return arr;
-}
-
-/** An object (hash) whose fields are the numbers of all HTTP error codes. */
-const HTTP_ERROR_CODES = array2obj(range(400, 417).concat(range(500, 505)));
-
-
-/**
- * The character used to distinguish hidden files from non-hidden files, a la
- * the leading dot in Apache. Since that mechanism also hides files from
- * easy display in LXR, ls output, etc. however, we choose instead to use a
- * suffix character. If a requested file ends with it, we append another
- * when getting the file on the server. If it doesn't, we just look up that
- * file. Therefore, any file whose name ends with exactly one of the character
- * is "hidden" and available for use by the server.
- */
-const HIDDEN_CHAR = "^";
-
-/**
- * The file name suffix indicating the file containing overridden headers for
- * a requested file.
- */
-const HEADERS_SUFFIX = HIDDEN_CHAR + "headers" + HIDDEN_CHAR;
-
-/** Type used to denote SJS scripts for CGI-like functionality. */
-const SJS_TYPE = "sjs";
-
-/** Base for relative timestamps produced by dumpn(). */
-var firstStamp = 0;
-
-/** dump(str) with a trailing "\n" -- only outputs if DEBUG. */
-function dumpn(str)
-{
- if (DEBUG)
- {
- var prefix = "HTTPD-INFO | ";
- if (DEBUG_TIMESTAMP)
- {
- if (firstStamp === 0)
- firstStamp = Date.now();
-
- var elapsed = Date.now() - firstStamp; // milliseconds
- var min = Math.floor(elapsed / 60000);
- var sec = (elapsed % 60000) / 1000;
-
- if (sec < 10)
- prefix += min + ":0" + sec.toFixed(3) + " | ";
- else
- prefix += min + ":" + sec.toFixed(3) + " | ";
- }
-
- dump(prefix + str + "\n");
- }
-}
-
-/** Dumps the current JS stack if DEBUG. */
-function dumpStack()
-{
- // peel off the frames for dumpStack() and Error()
- var stack = new Error().stack.split(/\n/).slice(2);
- stack.forEach(dumpn);
-}
-
-
-/** The XPCOM thread manager. */
-var gThreadManager = null;
-
-/** The XPCOM prefs service. */
-var gRootPrefBranch = null;
-function getRootPrefBranch()
-{
- if (!gRootPrefBranch)
- {
- gRootPrefBranch = Cc["@mozilla.org/preferences-service;1"]
- .getService(Ci.nsIPrefBranch);
- }
- return gRootPrefBranch;
-}
-
-/**
- * JavaScript constructors for commonly-used classes; precreating these is a
- * speedup over doing the same from base principles. See the docs at
- * http://developer.mozilla.org/en/docs/Components.Constructor for details.
- */
-const ServerSocket = CC("@mozilla.org/network/server-socket;1",
- "nsIServerSocket",
- "init");
-const ScriptableInputStream = CC("@mozilla.org/scriptableinputstream;1",
- "nsIScriptableInputStream",
- "init");
-const Pipe = CC("@mozilla.org/pipe;1",
- "nsIPipe",
- "init");
-const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
- "nsIFileInputStream",
- "init");
-const ConverterInputStream = CC("@mozilla.org/intl/converter-input-stream;1",
- "nsIConverterInputStream",
- "init");
-const WritablePropertyBag = CC("@mozilla.org/hash-property-bag;1",
- "nsIWritablePropertyBag2");
-const SupportsString = CC("@mozilla.org/supports-string;1",
- "nsISupportsString");
-
-/* These two are non-const only so a test can overwrite them. */
-var BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
- "nsIBinaryInputStream",
- "setInputStream");
-var BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
- "nsIBinaryOutputStream",
- "setOutputStream");
-
-/**
- * Returns the RFC 822/1123 representation of a date.
- *
- * @param date : Number
- * the date, in milliseconds from midnight (00:00:00), January 1, 1970 GMT
- * @returns string
- * the representation of the given date
- */
-function toDateString(date)
-{
- //
- // rfc1123-date = wkday "," SP date1 SP time SP "GMT"
- // date1 = 2DIGIT SP month SP 4DIGIT
- // ; day month year (e.g., 02 Jun 1982)
- // time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
- // ; 00:00:00 - 23:59:59
- // wkday = "Mon" | "Tue" | "Wed"
- // | "Thu" | "Fri" | "Sat" | "Sun"
- // month = "Jan" | "Feb" | "Mar" | "Apr"
- // | "May" | "Jun" | "Jul" | "Aug"
- // | "Sep" | "Oct" | "Nov" | "Dec"
- //
-
- const wkdayStrings = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
- const monthStrings = ["Jan", "Feb", "Mar", "Apr", "May", "Jun",
- "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
-
- /**
- * Processes a date and returns the encoded UTC time as a string according to
- * the format specified in RFC 2616.
- *
- * @param date : Date
- * the date to process
- * @returns string
- * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
- */
- function toTime(date)
- {
- var hrs = date.getUTCHours();
- var rv = (hrs < 10) ? "0" + hrs : hrs;
-
- var mins = date.getUTCMinutes();
- rv += ":";
- rv += (mins < 10) ? "0" + mins : mins;
-
- var secs = date.getUTCSeconds();
- rv += ":";
- rv += (secs < 10) ? "0" + secs : secs;
-
- return rv;
- }
-
- /**
- * Processes a date and returns the encoded UTC date as a string according to
- * the date1 format specified in RFC 2616.
- *
- * @param date : Date
- * the date to process
- * @returns string
- * a string of the form "HH:MM:SS", ranging from "00:00:00" to "23:59:59"
- */
- function toDate1(date)
- {
- var day = date.getUTCDate();
- var month = date.getUTCMonth();
- var year = date.getUTCFullYear();
-
- var rv = (day < 10) ? "0" + day : day;
- rv += " " + monthStrings[month];
- rv += " " + year;
-
- return rv;
- }
-
- date = new Date(date);
-
- const fmtString = "%wkday%, %date1% %time% GMT";
- var rv = fmtString.replace("%wkday%", wkdayStrings[date.getUTCDay()]);
- rv = rv.replace("%time%", toTime(date));
- return rv.replace("%date1%", toDate1(date));
-}
-
-/**
- * Prints out a human-readable representation of the object o and its fields,
- * omitting those whose names begin with "_" if showMembers != true (to ignore
- * "private" properties exposed via getters/setters).
- */
-function printObj(o, showMembers)
-{
- var s = "******************************\n";
- s += "o = {\n";
- for (var i in o)
- {
- if (typeof(i) != "string" ||
- (showMembers || (i.length > 0 && i[0] != "_")))
- s+= " " + i + ": " + o[i] + ",\n";
- }
- s += " };\n";
- s += "******************************";
- dumpn(s);
-}
-
-/**
- * Instantiates a new HTTP server.
- */
-function nsHttpServer()
-{
- if (!gThreadManager)
- gThreadManager = Cc["@mozilla.org/thread-manager;1"].getService();
-
- /** The port on which this server listens. */
- this._port = undefined;
-
- /** The socket associated with this. */
- this._socket = null;
-
- /** The handler used to process requests to this server. */
- this._handler = new ServerHandler(this);
-
- /** Naming information for this server. */
- this._identity = new ServerIdentity();
-
- /**
- * Indicates when the server is to be shut down at the end of the request.
- */
- this._doQuit = false;
-
- /**
- * True if the socket in this is closed (and closure notifications have been
- * sent and processed if the socket was ever opened), false otherwise.
- */
- this._socketClosed = true;
-
- /**
- * Used for tracking existing connections and ensuring that all connections
- * are properly cleaned up before server shutdown; increases by 1 for every
- * new incoming connection.
- */
- this._connectionGen = 0;
-
- /**
- * Hash of all open connections, indexed by connection number at time of
- * creation.
- */
- this._connections = {};
-}
-nsHttpServer.prototype =
-{
- classID: Components.ID("{54ef6f81-30af-4b1d-ac55-8ba811293e41}"),
-
- // NSISERVERSOCKETLISTENER
-
- /**
- * Processes an incoming request coming in on the given socket and contained
- * in the given transport.
- *
- * @param socket : nsIServerSocket
- * the socket through which the request was served
- * @param trans : nsISocketTransport
- * the transport for the request/response
- * @see nsIServerSocketListener.onSocketAccepted
- */
- onSocketAccepted: function(socket, trans)
- {
- dumpn("*** onSocketAccepted(socket=" + socket + ", trans=" + trans + ")");
-
- dumpn(">>> new connection on " + trans.host + ":" + trans.port);
-
- const SEGMENT_SIZE = 8192;
- const SEGMENT_COUNT = 1024;
- try
- {
- var input = trans.openInputStream(0, SEGMENT_SIZE, SEGMENT_COUNT)
- .QueryInterface(Ci.nsIAsyncInputStream);
- var output = trans.openOutputStream(0, 0, 0);
- }
- catch (e)
- {
- dumpn("*** error opening transport streams: " + e);
- trans.close(Cr.NS_BINDING_ABORTED);
- return;
- }
-
- var connectionNumber = ++this._connectionGen;
-
- try
- {
- var conn = new Connection(input, output, this, socket.port, trans.port,
- connectionNumber);
- var reader = new RequestReader(conn);
-
- // XXX add request timeout functionality here!
-
- // Note: must use main thread here, or we might get a GC that will cause
- // threadsafety assertions. We really need to fix XPConnect so that
- // you can actually do things in multi-threaded JS. :-(
- input.asyncWait(reader, 0, 0, gThreadManager.mainThread);
- }
- catch (e)
- {
- // Assume this connection can't be salvaged and bail on it completely;
- // don't attempt to close it so that we can assert that any connection
- // being closed is in this._connections.
- dumpn("*** error in initial request-processing stages: " + e);
- trans.close(Cr.NS_BINDING_ABORTED);
- return;
- }
-
- this._connections[connectionNumber] = conn;
- dumpn("*** starting connection " + connectionNumber);
- },
-
- /**
- * Called when the socket associated with this is closed.
- *
- * @param socket : nsIServerSocket
- * the socket being closed
- * @param status : nsresult
- * the reason the socket stopped listening (NS_BINDING_ABORTED if the server
- * was stopped using nsIHttpServer.stop)
- * @see nsIServerSocketListener.onStopListening
- */
- 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");
-
- // Notify asynchronously so that any pending teardown in stop() has a
- // chance to run first.
- var self = this;
- var stopEvent =
- {
- run: function()
- {
- dumpn("*** _notifyStopped async callback");
- self._notifyStopped();
- }
- };
- gThreadManager.dispatchToMainThread(stopEvent);
- }
- },
-
- // NSIHTTPSERVER
-
- //
- // see nsIHttpServer.start
- //
- start: function(port)
- {
- this._start(port, "localhost")
- },
-
- _start: function(port, host)
- {
- if (this._socket)
- throw Cr.NS_ERROR_ALREADY_INITIALIZED;
-
- this._port = port;
- this._doQuit = this._socketClosed = false;
-
- this._host = host;
-
- // The listen queue needs to be long enough to handle
- // 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 = 5 + Math.max(
- prefs.getIntPref("network.http.max-persistent-connections-per-server"),
- prefs.getIntPref("network.http.max-persistent-connections-per-proxy"));
-
- try
- {
- var loopback = true;
- if (this._host != "127.0.0.1" && this._host != "localhost") {
- var loopback = false;
- }
-
- // 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._port = socket.port;
- this._identity._initialize(socket.port, host, true);
- this._socket = socket;
- }
- catch (e)
- {
- dump("\n!!! could not start server on port " + port + ": " + e + "\n\n");
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- }
- },
-
- //
- // see nsIHttpServer.stop
- //
- stop: function(callback)
- {
- if (!callback)
- throw Cr.NS_ERROR_NULL_POINTER;
- if (!this._socket)
- throw Cr.NS_ERROR_UNEXPECTED;
-
- this._stopCallback = typeof callback === "function"
- ? callback
- : function() { callback.onStopped(); };
-
- dumpn(">>> stopping listening on port " + this._socket.port);
- this._socket.close();
- this._socket = null;
-
- // We can't have this identity any more, and the port on which we're running
- // this server now could be meaningless the next time around.
- this._identity._teardown();
-
- this._doQuit = false;
-
- // socket-close notification and pending request completion happen async
- },
-
- //
- // see nsIHttpServer.registerFile
- //
- registerFile: function(path, file)
- {
- if (file && (!file.exists() || file.isDirectory()))
- throw Cr.NS_ERROR_INVALID_ARG;
-
- this._handler.registerFile(path, file);
- },
-
- //
- // see nsIHttpServer.registerDirectory
- //
- registerDirectory: function(path, directory)
- {
- // XXX true path validation!
- if (path.charAt(0) != "/" ||
- path.charAt(path.length - 1) != "/" ||
- (directory &&
- (!directory.exists() || !directory.isDirectory())))
- throw Cr.NS_ERROR_INVALID_ARG;
-
- // XXX determine behavior of nonexistent /foo/bar when a /foo/bar/ mapping
- // exists!
-
- this._handler.registerDirectory(path, directory);
- },
-
- //
- // see nsIHttpServer.registerPathHandler
- //
- registerPathHandler: function(path, handler)
- {
- this._handler.registerPathHandler(path, handler);
- },
-
- //
- // see nsIHttpServer.registerPrefixHandler
- //
- registerPrefixHandler: function(prefix, handler)
- {
- this._handler.registerPrefixHandler(prefix, handler);
- },
-
- //
- // see nsIHttpServer.registerErrorHandler
- //
- registerErrorHandler: function(code, handler)
- {
- this._handler.registerErrorHandler(code, handler);
- },
-
- //
- // see nsIHttpServer.setIndexHandler
- //
- setIndexHandler: function(handler)
- {
- this._handler.setIndexHandler(handler);
- },
-
- //
- // see nsIHttpServer.registerContentType
- //
- registerContentType: function(ext, type)
- {
- this._handler.registerContentType(ext, type);
- },
-
- //
- // see nsIHttpServer.serverIdentity
- //
- get identity()
- {
- return this._identity;
- },
-
- //
- // see nsIHttpServer.getState
- //
- getState: function(path, k)
- {
- return this._handler._getState(path, k);
- },
-
- //
- // see nsIHttpServer.setState
- //
- setState: function(path, k, v)
- {
- return this._handler._setState(path, k, v);
- },
-
- //
- // see nsIHttpServer.getSharedState
- //
- getSharedState: function(k)
- {
- return this._handler._getSharedState(k);
- },
-
- //
- // see nsIHttpServer.setSharedState
- //
- setSharedState: function(k, v)
- {
- return this._handler._setSharedState(k, v);
- },
-
- //
- // see nsIHttpServer.getObjectState
- //
- getObjectState: function(k)
- {
- return this._handler._getObjectState(k);
- },
-
- //
- // see nsIHttpServer.setObjectState
- //
- setObjectState: function(k, v)
- {
- return this._handler._setObjectState(k, v);
- },
-
-
- // NSISUPPORTS
-
- //
- // see nsISupports.QueryInterface
- //
- QueryInterface: function(iid)
- {
- if (iid.equals(Ci.nsIHttpServer) ||
- iid.equals(Ci.nsIServerSocketListener) ||
- iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // NON-XPCOM PUBLIC API
-
- /**
- * Returns true iff this server is not running (and is not in the process of
- * serving any requests still to be processed when the server was last
- * stopped after being run).
- */
- isStopped: function()
- {
- return this._socketClosed && !this._hasOpenConnections();
- },
-
- // PRIVATE IMPLEMENTATION
-
- /** True if this server has any open connections to it, false otherwise. */
- _hasOpenConnections: function()
- {
- //
- // If we have any open connections, they're tracked as numeric properties on
- // |this._connections|. The non-standard __count__ property could be used
- // to check whether there are any properties, but standard-wise, even
- // looking forward to ES5, there's no less ugly yet still O(1) way to do
- // this.
- //
- for (var n in this._connections)
- return true;
- return false;
- },
-
- /** Calls the server-stopped callback provided when stop() was called. */
- _notifyStopped: function()
- {
- NS_ASSERT(this._stopCallback !== null, "double-notifying?");
- NS_ASSERT(!this._hasOpenConnections(), "should be done serving by now");
-
- //
- // NB: We have to grab this now, null out the member, *then* call the
- // callback here, or otherwise the callback could (indirectly) futz with
- // this._stopCallback by starting and immediately stopping this, at
- // which point we'd be nulling out a field we no longer have a right to
- // modify.
- //
- var callback = this._stopCallback;
- this._stopCallback = null;
- try
- {
- callback();
- }
- catch (e)
- {
- // not throwing because this is specified as being usually (but not
- // always) asynchronous
- dump("!!! error running onStopped callback: " + e + "\n");
- }
- },
-
- /**
- * Notifies this server that the given connection has been closed.
- *
- * @param connection : Connection
- * the connection that was closed
- */
- _connectionClosed: function(connection)
- {
- NS_ASSERT(connection.number in this._connections,
- "closing a connection " + this + " that we never added to the " +
- "set of open connections?");
- NS_ASSERT(this._connections[connection.number] === connection,
- "connection number mismatch? " +
- this._connections[connection.number]);
- delete this._connections[connection.number];
-
- // 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();
- },
-
- /**
- * Requests that the server be shut down when possible.
- */
- _requestQuit: function()
- {
- dumpn(">>> requesting a quit");
- dumpStack();
- this._doQuit = true;
- }
-};
-
-this.HttpServer = nsHttpServer;
-
-//
-// RFC 2396 section 3.2.2:
-//
-// host = hostname | IPv4address
-// hostname = *( domainlabel "." ) toplabel [ "." ]
-// domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum
-// toplabel = alpha | alpha *( alphanum | "-" ) alphanum
-// IPv4address = 1*digit "." 1*digit "." 1*digit "." 1*digit
-//
-
-const HOST_REGEX =
- new RegExp("^(?:" +
- // *( domainlabel "." )
- "(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)*" +
- // toplabel
- "[a-z](?:[a-z0-9-]*[a-z0-9])?" +
- "|" +
- // IPv4 address
- "\\d+\\.\\d+\\.\\d+\\.\\d+" +
- ")$",
- "i");
-
-
-/**
- * Represents the identity of a server. An identity consists of a set of
- * (scheme, host, port) tuples denoted as locations (allowing a single server to
- * serve multiple sites or to be used behind both HTTP and HTTPS proxies for any
- * host/port). Any incoming request must be to one of these locations, or it
- * will be rejected with an HTTP 400 error. One location, denoted as the
- * primary location, is the location assigned in contexts where a location
- * cannot otherwise be endogenously derived, such as for HTTP/1.0 requests.
- *
- * A single identity may contain at most one location per unique host/port pair;
- * other than that, no restrictions are placed upon what locations may
- * constitute an identity.
- */
-function ServerIdentity()
-{
- /** The scheme of the primary location. */
- this._primaryScheme = "http";
-
- /** The hostname of the primary location. */
- this._primaryHost = "127.0.0.1"
-
- /** The port number of the primary location. */
- this._primaryPort = -1;
-
- /**
- * The current port number for the corresponding server, stored so that a new
- * primary location can always be set if the current one is removed.
- */
- this._defaultPort = -1;
-
- /**
- * Maps hosts to maps of ports to schemes, e.g. the following would represent
- * https://example.com:789/ and http://example.org/:
- *
- * {
- * "xexample.com": { 789: "https" },
- * "xexample.org": { 80: "http" }
- * }
- *
- * Note the "x" prefix on hostnames, which prevents collisions with special
- * JS names like "prototype".
- */
- this._locations = { "xlocalhost": {} };
-}
-ServerIdentity.prototype =
-{
- // NSIHTTPSERVERIDENTITY
-
- //
- // see nsIHttpServerIdentity.primaryScheme
- //
- get primaryScheme()
- {
- if (this._primaryPort === -1)
- throw Cr.NS_ERROR_NOT_INITIALIZED;
- return this._primaryScheme;
- },
-
- //
- // see nsIHttpServerIdentity.primaryHost
- //
- get primaryHost()
- {
- if (this._primaryPort === -1)
- throw Cr.NS_ERROR_NOT_INITIALIZED;
- return this._primaryHost;
- },
-
- //
- // see nsIHttpServerIdentity.primaryPort
- //
- get primaryPort()
- {
- if (this._primaryPort === -1)
- throw Cr.NS_ERROR_NOT_INITIALIZED;
- return this._primaryPort;
- },
-
- //
- // see nsIHttpServerIdentity.add
- //
- add: function(scheme, host, port)
- {
- this._validate(scheme, host, port);
-
- var entry = this._locations["x" + host];
- if (!entry)
- this._locations["x" + host] = entry = {};
-
- entry[port] = scheme;
- },
-
- //
- // see nsIHttpServerIdentity.remove
- //
- remove: function(scheme, host, port)
- {
- this._validate(scheme, host, port);
-
- var entry = this._locations["x" + host];
- if (!entry)
- return false;
-
- var present = port in entry;
- delete entry[port];
-
- if (this._primaryScheme == scheme &&
- this._primaryHost == host &&
- this._primaryPort == port &&
- this._defaultPort !== -1)
- {
- // Always keep at least one identity in existence at any time, unless
- // we're in the process of shutting down (the last condition above).
- this._primaryPort = -1;
- this._initialize(this._defaultPort, host, false);
- }
-
- return present;
- },
-
- //
- // see nsIHttpServerIdentity.has
- //
- has: function(scheme, host, port)
- {
- this._validate(scheme, host, port);
-
- return "x" + host in this._locations &&
- scheme === this._locations["x" + host][port];
- },
-
- //
- // see nsIHttpServerIdentity.has
- //
- getScheme: function(host, port)
- {
- this._validate("http", host, port);
-
- var entry = this._locations["x" + host];
- if (!entry)
- return "";
-
- return entry[port] || "";
- },
-
- //
- // see nsIHttpServerIdentity.setPrimary
- //
- setPrimary: function(scheme, host, port)
- {
- this._validate(scheme, host, port);
-
- this.add(scheme, host, port);
-
- this._primaryScheme = scheme;
- this._primaryHost = host;
- this._primaryPort = port;
- },
-
-
- // NSISUPPORTS
-
- //
- // see nsISupports.QueryInterface
- //
- QueryInterface: function(iid)
- {
- if (iid.equals(Ci.nsIHttpServerIdentity) || iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // PRIVATE IMPLEMENTATION
-
- /**
- * Initializes the primary name for the corresponding server, based on the
- * provided port number.
- */
- _initialize: function(port, host, addSecondaryDefault)
- {
- this._host = host;
- if (this._primaryPort !== -1)
- this.add("http", host, port);
- else
- this.setPrimary("http", "localhost", port);
- this._defaultPort = port;
-
- // Only add this if we're being called at server startup
- if (addSecondaryDefault && host != "127.0.0.1")
- this.add("http", "127.0.0.1", port);
- },
-
- /**
- * Called at server shutdown time, unsets the primary location only if it was
- * the default-assigned location and removes the default location from the
- * set of locations used.
- */
- _teardown: function()
- {
- if (this._host != "127.0.0.1") {
- // 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" &&
- this._primaryHost == this._host &&
- this._primaryPort == this._defaultPort)
- {
- // Make sure we don't trigger the readding logic in .remove(), then remove
- // the default location.
- var port = this._defaultPort;
- this._defaultPort = -1;
- this.remove("http", this._host, port);
-
- // Ensure a server start triggers the setPrimary() path in ._initialize()
- this._primaryPort = -1;
- }
- else
- {
- // No reason not to remove directly as it's not our primary location
- this.remove("http", this._host, this._defaultPort);
- }
- },
-
- /**
- * Ensures scheme, host, and port are all valid with respect to RFC 2396.
- *
- * @throws NS_ERROR_ILLEGAL_VALUE
- * if any argument doesn't match the corresponding production
- */
- _validate: function(scheme, host, port)
- {
- if (scheme !== "http" && scheme !== "https")
- {
- dumpn("*** server only supports http/https schemes: '" + scheme + "'");
- dumpStack();
- throw Cr.NS_ERROR_ILLEGAL_VALUE;
- }
- if (!HOST_REGEX.test(host))
- {
- dumpn("*** unexpected host: '" + host + "'");
- throw Cr.NS_ERROR_ILLEGAL_VALUE;
- }
- if (port < 0 || port > 65535)
- {
- dumpn("*** unexpected port: '" + port + "'");
- throw Cr.NS_ERROR_ILLEGAL_VALUE;
- }
- }
-};
-
-
-/**
- * Represents a connection to the server (and possibly in the future the thread
- * on which the connection is processed).
- *
- * @param input : nsIInputStream
- * stream from which incoming data on the connection is read
- * @param output : nsIOutputStream
- * stream to write data out the connection
- * @param server : nsHttpServer
- * the server handling the connection
- * @param port : int
- * the port on which the server is running
- * @param outgoingPort : int
- * the outgoing port used by this connection
- * @param number : uint
- * a serial number used to uniquely identify this connection
- */
-function Connection(input, output, server, port, outgoingPort, number)
-{
- dumpn("*** opening new connection " + number + " on port " + outgoingPort);
-
- /** Stream of incoming data. */
- this.input = input;
-
- /** Stream for outgoing data. */
- this.output = output;
-
- /** The server associated with this request. */
- this.server = server;
-
- /** The port on which the server is running. */
- this.port = port;
-
- /** The outgoing poort used by this connection. */
- this._outgoingPort = outgoingPort;
-
- /** The serial number of this connection. */
- this.number = number;
-
- /**
- * The request for which a response is being generated, null if the
- * incoming request has not been fully received or if it had errors.
- */
- this.request = null;
-
- /** 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);
-
- this.input.close();
- this.output.close();
- this._closed = true;
-
- var server = this.server;
- server._connectionClosed(this);
-
- // If an error triggered a server shutdown, act on it now
- if (server._doQuit)
- server.stop(function() { /* not like we can do anything better */ });
- },
-
- /**
- * Initiates processing of this connection, using the data in the given
- * request.
- *
- * @param request : Request
- * the request which should be processed
- */
- process: function(request)
- {
- NS_ASSERT(!this._closed && !this._processed);
-
- this._processed = true;
-
- this.request = request;
- this.server._handler.handleResponse(this);
- },
-
- /**
- * Initiates processing of this connection, generating a response with the
- * given HTTP error code.
- *
- * @param code : uint
- * an HTTP code, so in the range [0, 1000)
- * @param request : Request
- * incomplete data about the incoming request (since there were errors
- * during its processing
- */
- processError: function(code, request)
- {
- NS_ASSERT(!this._closed && !this._processed);
-
- this._processed = true;
- this.request = request;
- this.server._handler.handleError(code, this);
- },
-
- /** Converts this to a string for debugging purposes. */
- toString: function()
- {
- return "";
- },
-
- requestStarted: function()
- {
- this._requestStarted = true;
- }
-};
-
-
-
-/** Returns an array of count bytes from the given input stream. */
-function readBytes(inputStream, count)
-{
- return new BinaryInputStream(inputStream).readByteArray(count);
-}
-
-
-
-/** Request reader processing states; see RequestReader for details. */
-const READER_IN_REQUEST_LINE = 0;
-const READER_IN_HEADERS = 1;
-const READER_IN_BODY = 2;
-const READER_FINISHED = 3;
-
-
-/**
- * Reads incoming request data asynchronously, does any necessary preprocessing,
- * and forwards it to the request handler. Processing occurs in three states:
- *
- * READER_IN_REQUEST_LINE Reading the request's status line
- * READER_IN_HEADERS Reading headers in the request
- * READER_IN_BODY Reading the body of the request
- * READER_FINISHED Entire request has been read and processed
- *
- * During the first two stages, initial metadata about the request is gathered
- * into a Request object. Once the status line and headers have been processed,
- * we start processing the body of the request into the Request. Finally, when
- * the entire body has been read, we create a Response and hand it off to the
- * ServerHandler to be given to the appropriate request handler.
- *
- * @param connection : Connection
- * the connection for the request being read
- */
-function RequestReader(connection)
-{
- /** Connection metadata for this request. */
- this._connection = connection;
-
- /**
- * A container providing line-by-line access to the raw bytes that make up the
- * data which has been read from the connection but has not yet been acted
- * upon (by passing it to the request handler or by extracting request
- * metadata from it).
- */
- this._data = new LineData();
-
- /**
- * The amount of data remaining to be read from the body of this request.
- * After all headers in the request have been read this is the value in the
- * Content-Length header, but as the body is read its value decreases to zero.
- */
- this._contentLength = 0;
-
- /** The current state of parsing the incoming request. */
- this._state = READER_IN_REQUEST_LINE;
-
- /** Metadata constructed from the incoming request for the request handler. */
- this._metadata = new Request(connection.port);
-
- /**
- * Used to preserve state if we run out of line data midway through a
- * multi-line header. _lastHeaderName stores the name of the header, while
- * _lastHeaderValue stores the value we've seen so far for the header.
- *
- * These fields are always either both undefined or both strings.
- */
- this._lastHeaderName = this._lastHeaderValue = undefined;
-}
-RequestReader.prototype =
-{
- // NSIINPUTSTREAMCALLBACK
-
- /**
- * Called when more data from the incoming request is available. This method
- * then reads the available data from input and deals with that data as
- * necessary, depending upon the syntax of already-downloaded data.
- *
- * @param input : nsIAsyncInputStream
- * the stream of incoming data from the connection
- */
- onInputStreamReady: function(input)
- {
- dumpn("*** onInputStreamReady(input=" + input + ") on thread " +
- gThreadManager.currentThread + " (main is " +
- gThreadManager.mainThread + ")");
- dumpn("*** this._state == " + this._state);
-
- // Handle cases where we get more data after a request error has been
- // discovered but *before* we can close the connection.
- var data = this._data;
- if (!data)
- return;
-
- try
- {
- data.appendBytes(readBytes(input, input.available()));
- }
- catch (e)
- {
- if (streamClosed(e))
- {
- dumpn("*** WARNING: unexpected error when reading from socket; will " +
- "be treated as if the input stream had been closed");
- dumpn("*** WARNING: actual error was: " + e);
- }
-
- // We've lost a race -- input has been closed, but we're still expecting
- // to read more data. available() will throw in this case, and since
- // we're dead in the water now, destroy the connection.
- dumpn("*** onInputStreamReady called on a closed input, destroying " +
- "connection");
- this._connection.close();
- return;
- }
-
- switch (this._state)
- {
- default:
- NS_ASSERT(false, "invalid state: " + this._state);
- break;
-
- case READER_IN_REQUEST_LINE:
- if (!this._processRequestLine())
- break;
- /* fall through */
-
- case READER_IN_HEADERS:
- if (!this._processHeaders())
- break;
- /* fall through */
-
- case READER_IN_BODY:
- this._processBody();
- }
-
- if (this._state != READER_FINISHED)
- input.asyncWait(this, 0, 0, gThreadManager.currentThread);
- },
-
- //
- // see nsISupports.QueryInterface
- //
- QueryInterface: function(aIID)
- {
- if (aIID.equals(Ci.nsIInputStreamCallback) ||
- aIID.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // PRIVATE API
-
- /**
- * Processes unprocessed, downloaded data as a request line.
- *
- * @returns boolean
- * true iff the request line has been fully processed
- */
- _processRequestLine: function()
- {
- NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
-
- // Servers SHOULD ignore any empty line(s) received where a Request-Line
- // is expected (section 4.1).
- var data = this._data;
- var line = {};
- var readSuccess;
- while ((readSuccess = data.readLine(line)) && line.value == "")
- dumpn("*** ignoring beginning blank line...");
-
- // if we don't have a full line, wait until we do
- if (!readSuccess)
- return false;
-
- // we have the first non-blank line
- try
- {
- this._parseRequestLine(line.value);
- this._state = READER_IN_HEADERS;
- this._connection.requestStarted();
- return true;
- }
- catch (e)
- {
- this._handleError(e);
- return false;
- }
- },
-
- /**
- * Processes stored data, assuming it is either at the beginning or in
- * the middle of processing request headers.
- *
- * @returns boolean
- * true iff header data in the request has been fully processed
- */
- _processHeaders: function()
- {
- NS_ASSERT(this._state == READER_IN_HEADERS);
-
- // XXX things to fix here:
- //
- // - need to support RFC 2047-encoded non-US-ASCII characters
-
- try
- {
- var done = this._parseHeaders();
- if (done)
- {
- var request = this._metadata;
-
- // XXX this is wrong for requests with transfer-encodings applied to
- // them, particularly chunked (which by its nature can have no
- // meaningful Content-Length header)!
- this._contentLength = request.hasHeader("Content-Length")
- ? parseInt(request.getHeader("Content-Length"), 10)
- : 0;
- dumpn("_processHeaders, Content-length=" + this._contentLength);
-
- this._state = READER_IN_BODY;
- }
- return done;
- }
- catch (e)
- {
- this._handleError(e);
- return false;
- }
- },
-
- /**
- * Processes stored data, assuming it is either at the beginning or in
- * the middle of processing the request body.
- *
- * @returns boolean
- * true iff the request body has been fully processed
- */
- _processBody: function()
- {
- NS_ASSERT(this._state == READER_IN_BODY);
-
- // XXX handle chunked transfer-coding request bodies!
-
- try
- {
- if (this._contentLength > 0)
- {
- var data = this._data.purge();
- var count = Math.min(data.length, this._contentLength);
- dumpn("*** loading data=" + data + " len=" + data.length +
- " excess=" + (data.length - count));
-
- var bos = new BinaryOutputStream(this._metadata._bodyOutputStream);
- bos.writeByteArray(data, count);
- this._contentLength -= count;
- }
-
- dumpn("*** remaining body data len=" + this._contentLength);
- if (this._contentLength == 0)
- {
- this._validateRequest();
- this._state = READER_FINISHED;
- this._handleResponse();
- return true;
- }
-
- return false;
- }
- catch (e)
- {
- this._handleError(e);
- return false;
- }
- },
-
- /**
- * Does various post-header checks on the data in this request.
- *
- * @throws : HttpError
- * if the request was malformed in some way
- */
- _validateRequest: function()
- {
- NS_ASSERT(this._state == READER_IN_BODY);
-
- dumpn("*** _validateRequest");
-
- var metadata = this._metadata;
- var headers = metadata._headers;
-
- // 19.6.1.1 -- servers MUST report 400 to HTTP/1.1 requests w/o Host header
- var identity = this._connection.server.identity;
- if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1))
- {
- if (!headers.hasHeader("Host"))
- {
- dumpn("*** malformed HTTP/1.1 or greater request with no Host header!");
- throw HTTP_400;
- }
-
- // If the Request-URI wasn't absolute, then we need to determine our host.
- // We have to determine what scheme was used to access us based on the
- // server identity data at this point, because the request just doesn't
- // contain enough data on its own to do this, sadly.
- if (!metadata._host)
- {
- var host, port;
- var hostPort = headers.getHeader("Host");
- var colon = hostPort.indexOf(":");
- if (colon < 0)
- {
- host = hostPort;
- port = "";
- }
- else
- {
- host = hostPort.substring(0, colon);
- port = hostPort.substring(colon + 1);
- }
-
- // NB: We allow an empty port here because, oddly, a colon may be
- // present even without a port number, e.g. "example.com:"; in this
- // case the default port applies.
- if (!HOST_REGEX.test(host) || !/^\d*$/.test(port))
- {
- dumpn("*** malformed hostname (" + hostPort + ") in Host " +
- "header, 400 time");
- throw HTTP_400;
- }
-
- // If we're not given a port, we're stuck, because we don't know what
- // scheme to use to look up the correct port here, in general. Since
- // the HTTPS case requires a tunnel/proxy and thus requires that the
- // requested URI be absolute (and thus contain the necessary
- // information), let's assume HTTP will prevail and use that.
- port = +port || 80;
-
- var scheme = identity.getScheme(host, port);
- if (!scheme)
- {
- dumpn("*** unrecognized hostname (" + hostPort + ") in Host " +
- "header, 400 time");
- throw HTTP_400;
- }
-
- metadata._scheme = scheme;
- metadata._host = host;
- metadata._port = port;
- }
- }
- else
- {
- NS_ASSERT(metadata._host === undefined,
- "HTTP/1.0 doesn't allow absolute paths in the request line!");
-
- metadata._scheme = identity.primaryScheme;
- metadata._host = identity.primaryHost;
- metadata._port = identity.primaryPort;
- }
-
- NS_ASSERT(identity.has(metadata._scheme, metadata._host, metadata._port),
- "must have a location we recognize by now!");
- },
-
- /**
- * Handles responses in case of error, either in the server or in the request.
- *
- * @param e
- * the specific error encountered, which is an HttpError in the case where
- * the request is in some way invalid or cannot be fulfilled; if this isn't
- * an HttpError we're going to be paranoid and shut down, because that
- * shouldn't happen, ever
- */
- _handleError: function(e)
- {
- // Don't fall back into normal processing!
- this._state = READER_FINISHED;
-
- var server = this._connection.server;
- if (e instanceof HttpError)
- {
- var code = e.code;
- }
- else
- {
- dumpn("!!! UNEXPECTED ERROR: " + e +
- (e.lineNumber ? ", line " + e.lineNumber : ""));
-
- // no idea what happened -- be paranoid and shut down
- code = 500;
- server._requestQuit();
- }
-
- // make attempted reuse of data an error
- this._data = null;
-
- this._connection.processError(code, this._metadata);
- },
-
- /**
- * Now that we've read the request line and headers, we can actually hand off
- * the request to be handled.
- *
- * This method is called once per request, after the request line and all
- * headers and the body, if any, have been received.
- */
- _handleResponse: function()
- {
- NS_ASSERT(this._state == READER_FINISHED);
-
- // We don't need the line-based data any more, so make attempted reuse an
- // error.
- this._data = null;
-
- this._connection.process(this._metadata);
- },
-
-
- // PARSING
-
- /**
- * Parses the request line for the HTTP request associated with this.
- *
- * @param line : string
- * the request line
- */
- _parseRequestLine: function(line)
- {
- NS_ASSERT(this._state == READER_IN_REQUEST_LINE);
-
- dumpn("*** _parseRequestLine('" + line + "')");
-
- var metadata = this._metadata;
-
- // clients and servers SHOULD accept any amount of SP or HT characters
- // 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];
-
- // get the HTTP version
- 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
- {
- metadata._httpVersion = new nsHttpVersion(match[1]);
- if (!metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_0))
- throw new Error("unsupported HTTP version");
- }
- catch (e)
- {
- // we support HTTP/1.0 and HTTP/1.1 only
- throw HTTP_501;
- }
-
-
- var fullPath = request[1];
- var serverIdentity = this._connection.server.identity;
-
- var scheme, host, port;
-
- if (fullPath.charAt(0) != "/")
- {
- // 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
- {
- var uri = Cc["@mozilla.org/network/io-service;1"]
- .getService(Ci.nsIIOService)
- .newURI(fullPath);
- fullPath = uri.pathQueryRef;
- scheme = uri.scheme;
- host = metadata._host = uri.asciiHost;
- port = uri.port;
- if (port === -1)
- {
- if (scheme === "http")
- {
- port = 80;
- }
- else if (scheme === "https")
- {
- port = 443;
- }
- else
- {
- dumpn("*** Unknown scheme: " + scheme);
- throw HTTP_400;
- }
- }
- }
- catch (e)
- {
- // 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("?");
- if (splitter < 0)
- {
- // _queryString already set in ctor
- metadata._path = fullPath;
- }
- else
- {
- metadata._path = fullPath.substring(0, splitter);
- metadata._queryString = fullPath.substring(splitter + 1);
- }
-
- metadata._scheme = scheme;
- metadata._host = host;
- metadata._port = port;
- },
-
- /**
- * Parses all available HTTP headers in this until the header-ending CRLFCRLF,
- * adding them to the store of headers in the request.
- *
- * @throws
- * HTTP_400 if the headers are malformed
- * @returns boolean
- * true if all headers have now been processed, false otherwise
- */
- _parseHeaders: function()
- {
- NS_ASSERT(this._state == READER_IN_HEADERS);
-
- dumpn("*** _parseHeaders");
-
- var data = this._data;
-
- var headers = this._metadata._headers;
- var lastName = this._lastHeaderName;
- var lastVal = this._lastHeaderValue;
-
- 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 + "'" :
- "lastName without lastVal? lastName: '" + lastName + "'");
-
- if (!data.readLine(line))
- {
- // save any data we have from the header we might still be processing
- this._lastHeaderName = lastName;
- this._lastHeaderValue = lastVal;
- return false;
- }
-
- var lineText = line.value;
- dumpn("*** Line text: '" + lineText + "'");
- var firstChar = lineText.charAt(0);
-
- // blank line means end of headers
- if (lineText == "")
- {
- // we're finished with the previous header
- if (lastName)
- {
- try
- {
- headers.setHeader(lastName, lastVal, true);
- }
- catch (e)
- {
- dumpn("*** setHeader threw on last header, e == " + e);
- throw HTTP_400;
- }
- }
- else
- {
- // no headers in request -- valid for HTTP/1.0 requests
- }
-
- // either way, we're done processing headers
- this._state = READER_IN_BODY;
- return true;
- }
- else if (firstChar == " " || firstChar == "\t")
- {
- // multi-line header if we've already seen a header line
- if (!lastName)
- {
- dumpn("We don't have a header to continue!");
- throw HTTP_400;
- }
-
- // append this line's text to the value; starts with SP/HT, so no need
- // for separating whitespace
- lastVal += lineText;
- }
- else
- {
- // we have a new header, so set the old one (if one existed)
- if (lastName)
- {
- try
- {
- headers.setHeader(lastName, lastVal, true);
- }
- catch (e)
- {
- dumpn("*** setHeader threw on a header, e == " + e);
- throw HTTP_400;
- }
- }
-
- var colon = lineText.indexOf(":"); // first colon must be splitter
- if (colon < 1)
- {
- dumpn("*** No colon or missing header field-name");
- throw HTTP_400;
- }
-
- // set header name, value (to be set in the next loop, usually)
- lastName = lineText.substring(0, colon);
- lastVal = lineText.substring(colon + 1);
- } // empty, continuation, start of header
- } // while (true)
- }
-};
-
-
-/** The character codes for CR and LF. */
-const CR = 0x0D, LF = 0x0A;
-
-/**
- * Calculates the number of characters before the first CRLF pair in array, or
- * -1 if the array contains no CRLF pair.
- *
- * @param array : Array
- * an array of numbers in the range [0, 256), each representing a single
- * 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, start)
-{
- for (var i = array.indexOf(CR, start); i >= 0; i = array.indexOf(CR, i + 1))
- {
- if (array[i + 1] == LF)
- return i;
- }
- return -1;
-}
-
-
-/**
- * A container which provides line-by-line access to the arrays of bytes with
- * which it is seeded.
- */
-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 =
-{
- /**
- * Appends the bytes in the given array to the internal data cache maintained
- * by this.
- */
- appendBytes: function(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);
- }
- },
-
- /**
- * Removes and returns a line of data, delimited by CRLF, from this.
- *
- * @param out
- * an object whose "value" property will be set to the first line of text
- * present in this, sans CRLF, if this contains a full CRLF-delimited line
- * of text; if this doesn't contain enough data, the value of the property
- * is undefined
- * @returns boolean
- * true if a full line of data could be read from the data in this, false
- * otherwise
- */
- readLine: function(out)
- {
- var data = this._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
- // (excluding the trailing CRLF characters) into the corresponding string.
- //
- 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;
- },
-
- /**
- * Removes the bytes currently within this and returns them in an array.
- *
- * @returns Array
- * the bytes within this when this method is called
- */
- purge: function()
- {
- var data = this._data;
- this._data = [];
- return data;
- }
-};
-
-
-
-/**
- * Creates a request-handling function for an nsIHttpRequestHandler object.
- */
-function createHandlerFunc(handler)
-{
- return function(metadata, response) { handler.handle(metadata, response); };
-}
-
-
-/**
- * The default handler for directories; writes an HTML response containing a
- * slightly-formatted directory listing.
- */
-function defaultIndexHandler(metadata, response)
-{
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var path = htmlEscape(decodeURI(metadata.path));
-
- //
- // Just do a very basic bit of directory listings -- no need for too much
- // fanciness, especially since we don't have a style sheet in which we can
- // stick rules (don't want to pollute the default path-space).
- //
-
- var body = '\
- \
- ' + path + '\
- \
- \
- ' + path + '
\
- ';
-
- var directory = metadata.getProperty("directory");
- NS_ASSERT(directory && directory.isDirectory());
-
- var fileList = [];
- var files = directory.directoryEntries;
- while (files.hasMoreElements())
- {
- var f = files.getNext().QueryInterface(Ci.nsIFile);
- var name = f.leafName;
- if (!f.isHidden() &&
- (name.charAt(name.length - 1) != HIDDEN_CHAR ||
- name.charAt(name.length - 2) == HIDDEN_CHAR))
- fileList.push(f);
- }
-
- fileList.sort(fileSort);
-
- for (var i = 0; i < fileList.length; i++)
- {
- var file = fileList[i];
- try
- {
- var name = file.leafName;
- if (name.charAt(name.length - 1) == HIDDEN_CHAR)
- name = name.substring(0, name.length - 1);
- var sep = file.isDirectory() ? "/" : "";
-
- // Note: using " to delimit the attribute here because encodeURIComponent
- // passes through '.
- var item = '- ' +
- htmlEscape(name) + sep +
- '
';
-
- body += item;
- }
- catch (e) { /* some file system error, ignore the file */ }
- }
-
- body += '
\
- \
- ';
-
- response.bodyOutputStream.write(body, body.length);
-}
-
-/**
- * Sorts a and b (nsIFile objects) into an aesthetically pleasing order.
- */
-function fileSort(a, b)
-{
- var dira = a.isDirectory(), dirb = b.isDirectory();
-
- if (dira && !dirb)
- return -1;
- if (dirb && !dira)
- return 1;
-
- var namea = a.leafName.toLowerCase(), nameb = b.leafName.toLowerCase();
- return nameb > namea ? -1 : 1;
-}
-
-
-/**
- * Converts an externally-provided path into an internal path for use in
- * determining file mappings.
- *
- * @param path
- * the path to convert
- * @param encoded
- * true if the given path should be passed through decodeURI prior to
- * conversion
- * @throws URIError
- * if path is incorrectly encoded
- */
-function toInternalPath(path, encoded)
-{
- if (encoded)
- path = decodeURI(path);
-
- var comps = path.split("/");
- for (var i = 0, sz = comps.length; i < sz; i++)
- {
- var comp = comps[i];
- if (comp.charAt(comp.length - 1) == HIDDEN_CHAR)
- comps[i] = comp + HIDDEN_CHAR;
- }
- return comps.join("/");
-}
-
-const PERMS_READONLY = (4 << 6) | (4 << 3) | 4;
-
-/**
- * Adds custom-specified headers for the given file to the given response, if
- * any such headers are specified.
- *
- * @param file
- * the file on the disk which is to be written
- * @param metadata
- * metadata about the incoming request
- * @param response
- * the Response to which any specified headers/data should be written
- * @throws HTTP_500
- * if an error occurred while processing custom-specified headers
- */
-function maybeAddHeaders(file, metadata, response)
-{
- var name = file.leafName;
- if (name.charAt(name.length - 1) == HIDDEN_CHAR)
- name = name.substring(0, name.length - 1);
-
- var headerFile = file.parent;
- headerFile.append(name + HEADERS_SUFFIX);
-
- if (!headerFile.exists())
- return;
-
- const PR_RDONLY = 0x01;
- var fis = new FileInputStream(headerFile, PR_RDONLY, PERMS_READONLY,
- Ci.nsIFileInputStream.CLOSE_ON_EOF);
-
- try
- {
- var lis = new ConverterInputStream(fis, "UTF-8", 1024, 0x0);
- lis.QueryInterface(Ci.nsIUnicharLineInputStream);
-
- var line = {value: ""};
- var more = lis.readLine(line);
-
- if (!more && line.value == "")
- return;
-
-
- // request line
-
- var status = line.value;
- if (status.indexOf("HTTP ") == 0)
- {
- status = status.substring(5);
- var space = status.indexOf(" ");
- var code, description;
- if (space < 0)
- {
- code = status;
- description = "";
- }
- else
- {
- code = status.substring(0, space);
- description = status.substring(space + 1, status.length);
- }
-
- response.setStatusLine(metadata.httpVersion, parseInt(code, 10), description);
-
- line.value = "";
- more = lis.readLine(line);
- }
-
- // headers
- while (more || line.value != "")
- {
- var header = line.value;
- var colon = header.indexOf(":");
-
- response.setHeader(header.substring(0, colon),
- header.substring(colon + 1, header.length),
- false); // allow overriding server-set headers
-
- line.value = "";
- more = lis.readLine(line);
- }
- }
- catch (e)
- {
- dumpn("WARNING: error in headers for " + metadata.path + ": " + e);
- throw HTTP_500;
- }
- finally
- {
- fis.close();
- }
-}
-
-
-/**
- * An object which handles requests for a server, executing default and
- * overridden behaviors as instructed by the code which uses and manipulates it.
- * Default behavior includes the paths / and /trace (diagnostics), with some
- * support for HTTP error pages for various codes and fallback to HTTP 500 if
- * those codes fail for any reason.
- *
- * @param server : nsHttpServer
- * the server in which this handler is being used
- */
-function ServerHandler(server)
-{
- // FIELDS
-
- /**
- * The nsHttpServer instance associated with this handler.
- */
- this._server = server;
-
- /**
- * A FileMap object containing the set of path->nsIFile mappings for
- * all directory mappings set in the server (e.g., "/" for /var/www/html/,
- * "/foo/bar/" for /local/path/, and "/foo/bar/baz/" for /local/path2).
- *
- * Note carefully: the leading and trailing "/" in each path (not file) are
- * removed before insertion to simplify the code which uses this. You have
- * been warned!
- */
- this._pathDirectoryMap = new FileMap();
-
- /**
- * Custom request handlers for the server in which this resides. Path-handler
- * pairs are stored as property-value pairs in this property.
- *
- * @see ServerHandler.prototype._defaultPaths
- */
- 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
- * property.
- *
- * @see ServerHandler.prototype._defaultErrors
- */
- this._overrideErrors = {};
-
- /**
- * Maps file extensions to their MIME types in the server, overriding any
- * mapping that might or might not exist in the MIME service.
- */
- this._mimeMappings = {};
-
- /**
- * The default handler for requests for directories, used to serve directories
- * when no index file is present.
- */
- this._indexHandler = defaultIndexHandler;
-
- /** Per-path state storage for the server. */
- this._state = {};
-
- /** Entire-server state storage. */
- this._sharedState = {};
-
- /** Entire-server state storage for nsISupports values. */
- this._objectState = {};
-}
-ServerHandler.prototype =
-{
- // PUBLIC API
-
- /**
- * Handles a request to this server, responding to the request appropriately
- * and initiating server shutdown if necessary.
- *
- * This method never throws an exception.
- *
- * @param connection : Connection
- * the connection for this request
- */
- handleResponse: function(connection)
- {
- var request = connection.request;
- var response = new Response(connection);
-
- var path = request.path;
- dumpn("*** path == " + path);
-
- try
- {
- try
- {
- if (path in this._overridePaths)
- {
- // explicit paths first, then files based on existing directory mappings,
- // then (if the file doesn't exist) built-in server default paths
- dumpn("calling override for " + path);
- this._overridePaths[path](request, response);
- }
- else
- {
- 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)
- {
- if (response.partiallySent())
- {
- response.abort(e);
- return;
- }
-
- if (!(e instanceof HttpError))
- {
- dumpn("*** unexpected error: e == " + e);
- throw HTTP_500;
- }
- if (e.code !== 404)
- throw e;
-
- dumpn("*** default: " + (path in this._defaultPaths));
-
- response = new Response(connection);
- if (path in this._defaultPaths)
- this._defaultPaths[path](request, response);
- else
- throw HTTP_404;
- }
- }
- catch (e)
- {
- if (response.partiallySent())
- {
- response.abort(e);
- return;
- }
-
- var errorCode = "internal";
-
- try
- {
- if (!(e instanceof HttpError))
- throw e;
-
- errorCode = e.code;
- dumpn("*** errorCode == " + errorCode);
-
- response = new Response(connection);
- if (e.customErrorHandling)
- e.customErrorHandling(response);
- this._handleError(errorCode, request, response);
- return;
- }
- catch (e2)
- {
- dumpn("*** error handling " + errorCode + " error: " +
- "e2 == " + e2 + ", shutting down server");
-
- connection.server._requestQuit();
- response.abort(e2);
- return;
- }
- }
-
- response.complete();
- },
-
- //
- // see nsIHttpServer.registerFile
- //
- registerFile: function(path, file)
- {
- if (!file)
- {
- dumpn("*** unregistering '" + path + "' mapping");
- delete this._overridePaths[path];
- return;
- }
-
- dumpn("*** registering '" + path + "' as mapping to " + file.path);
- file = file.clone();
-
- var self = this;
- this._overridePaths[path] =
- function(request, response)
- {
- if (!file.exists())
- throw HTTP_404;
-
- response.setStatusLine(request.httpVersion, 200, "OK");
- self._writeFileResponse(request, file, response, 0, file.fileSize);
- };
- },
-
- //
- // see nsIHttpServer.registerPathHandler
- //
- registerPathHandler: function(path, handler)
- {
- // XXX true path validation!
- if (path.charAt(0) != "/")
- throw Cr.NS_ERROR_INVALID_ARG;
-
- 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
- //
- registerDirectory: function(path, directory)
- {
- // strip off leading and trailing '/' so that we can use lastIndexOf when
- // determining exactly how a path maps onto a mapped directory --
- // conditional is required here to deal with "/".substring(1, 0) being
- // converted to "/".substring(0, 1) per the JS specification
- var key = path.length == 1 ? "" : path.substring(1, path.length - 1);
-
- // the path-to-directory mapping code requires that the first character not
- // be "/", or it will go into an infinite loop
- if (key.charAt(0) == "/")
- throw Cr.NS_ERROR_INVALID_ARG;
-
- key = toInternalPath(key, false);
-
- if (directory)
- {
- dumpn("*** mapping '" + path + "' to the location " + directory.path);
- this._pathDirectoryMap.put(key, directory);
- }
- else
- {
- dumpn("*** removing mapping for '" + path + "'");
- this._pathDirectoryMap.put(key, null);
- }
- },
-
- //
- // see nsIHttpServer.registerErrorHandler
- //
- registerErrorHandler: function(err, handler)
- {
- if (!(err in HTTP_ERROR_CODES))
- dumpn("*** WARNING: registering non-HTTP/1.1 error code " +
- "(" + err + ") handler -- was this intentional?");
-
- this._handlerToField(handler, this._overrideErrors, err);
- },
-
- //
- // see nsIHttpServer.setIndexHandler
- //
- setIndexHandler: function(handler)
- {
- if (!handler)
- handler = defaultIndexHandler;
- else if (typeof(handler) != "function")
- handler = createHandlerFunc(handler);
-
- this._indexHandler = handler;
- },
-
- //
- // see nsIHttpServer.registerContentType
- //
- registerContentType: function(ext, type)
- {
- if (!type)
- delete this._mimeMappings[ext];
- else
- this._mimeMappings[ext] = headerUtils.normalizeFieldValue(type);
- },
-
- // PRIVATE API
-
- /**
- * Sets or remove (if handler is null) a handler in an object with a key.
- *
- * @param handler
- * a handler, either function or an nsIHttpRequestHandler
- * @param dict
- * The object to attach the handler to.
- * @param key
- * The field name of the handler.
- */
- _handlerToField: function(handler, dict, key)
- {
- // for convenience, handler can be a function if this is run from xpcshell
- if (typeof(handler) == "function")
- dict[key] = handler;
- else if (handler)
- dict[key] = createHandlerFunc(handler);
- else
- delete dict[key];
- },
-
- /**
- * Handles a request which maps to a file in the local filesystem (if a base
- * path has already been set; otherwise the 404 error is thrown).
- *
- * @param metadata : Request
- * metadata for the incoming request
- * @param response : Response
- * an uninitialized Response to the given request, to be initialized by a
- * request handler
- * @throws HTTP_###
- * if an HTTP error occurred (usually HTTP_404); note that in this case the
- * calling code must handle post-processing of the response
- */
- _handleDefault: function(metadata, response)
- {
- dumpn("*** _handleDefault()");
-
- response.setStatusLine(metadata.httpVersion, 200, "OK");
-
- var path = metadata.path;
- NS_ASSERT(path.charAt(0) == "/", "invalid path: <" + path + ">");
-
- // determine the actual on-disk file; this requires finding the deepest
- // path-to-directory mapping in the requested URL
- var file = this._getFileForPath(path);
-
- // the "file" might be a directory, in which case we either serve the
- // contained index.html or make the index handler write the response
- if (file.exists() && file.isDirectory())
- {
- file.append("index.html"); // make configurable?
- if (!file.exists() || file.isDirectory())
- {
- metadata._ensurePropertyBag();
- metadata._bag.setPropertyAsInterface("directory", file.parent);
- this._indexHandler(metadata, response);
- return;
- }
- }
-
- // alternately, the file might not exist
- if (!file.exists())
- throw HTTP_404;
-
- var start, end;
- if (metadata._httpVersion.atLeast(nsHttpVersion.HTTP_1_1) &&
- metadata.hasHeader("Range") &&
- this._getTypeFromFile(file) !== SJS_TYPE)
- {
- 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);
-
- if (rangeMatch[2] !== undefined)
- 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.
- if (start === undefined)
- {
- start = Math.max(0, file.fileSize - end);
- end = file.fileSize - 1;
- }
-
- // start and end are inclusive
- if (end === undefined || end >= file.fileSize)
- end = file.fileSize - 1;
-
- if (start !== undefined && start >= file.fileSize) {
- var HTTP_416 = new HttpError(416, "Requested Range Not Satisfiable");
- HTTP_416.customErrorHandling = function(errorResponse)
- {
- maybeAddHeaders(file, metadata, errorResponse);
- };
- throw HTTP_416;
- }
-
- if (end < start)
- {
- response.setStatusLine(metadata.httpVersion, 200, "OK");
- start = 0;
- end = file.fileSize - 1;
- }
- else
- {
- response.setStatusLine(metadata.httpVersion, 206, "Partial Content");
- var contentRange = "bytes " + start + "-" + end + "/" + file.fileSize;
- response.setHeader("Content-Range", contentRange);
- }
- }
- else
- {
- start = 0;
- end = file.fileSize - 1;
- }
-
- // finally...
- dumpn("*** handling '" + path + "' as mapping to " + file.path + " from " +
- start + " to " + end + " inclusive");
- this._writeFileResponse(metadata, file, response, start, end - start + 1);
- },
-
- /**
- * Writes an HTTP response for the given file, including setting headers for
- * file metadata.
- *
- * @param metadata : Request
- * the Request for which a response is being generated
- * @param file : nsIFile
- * the file which is to be sent in the response
- * @param response : Response
- * the response to which the file should be written
- * @param offset: uint
- * the byte offset to skip to when writing
- * @param count: uint
- * the number of bytes to write
- */
- _writeFileResponse: function(metadata, file, response, offset, count)
- {
- const PR_RDONLY = 0x01;
-
- var type = this._getTypeFromFile(file);
- if (type === SJS_TYPE)
- {
- var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
- Ci.nsIFileInputStream.CLOSE_ON_EOF);
-
- try
- {
- var sis = new ScriptableInputStream(fis);
- var s = Cu.Sandbox(gGlobalObject);
- s.importFunction(dump, "dump");
-
- // Define a basic key-value state-preservation API across requests, with
- // keys initially corresponding to the empty string.
- var self = this;
- var path = metadata.path;
- s.importFunction(function getState(k)
- {
- return self._getState(path, k);
- });
- s.importFunction(function setState(k, v)
- {
- self._setState(path, k, v);
- });
- s.importFunction(function getSharedState(k)
- {
- return self._getSharedState(k);
- });
- s.importFunction(function setSharedState(k, v)
- {
- self._setSharedState(k, v);
- });
- s.importFunction(function getObjectState(k, callback)
- {
- callback(self._getObjectState(k));
- });
- s.importFunction(function setObjectState(k, v)
- {
- 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);
-
- try
- {
- // Alas, the line number in errors dumped to console when calling the
- // request handler is simply an offset from where we load the SJS file.
- // Work around this in a reasonably non-fragile way by dynamically
- // 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, "latest");
- }
- catch (e)
- {
- dumpn("*** syntax error in SJS at " + file.path + ": " + e);
- throw HTTP_500;
- }
-
- try
- {
- s.handleRequest(metadata, response);
- }
- catch (e)
- {
- dump("*** error running SJS at " + file.path + ": " +
- e + " on line " +
- (e instanceof Error
- ? e.lineNumber + " in httpd.js"
- : (e.lineNumber - line)) + "\n");
- throw HTTP_500;
- }
- }
- finally
- {
- fis.close();
- }
- }
- else
- {
- try
- {
- response.setHeader("Last-Modified",
- toDateString(file.lastModifiedTime),
- false);
- }
- catch (e) { /* lastModifiedTime threw, ignore */ }
-
- response.setHeader("Content-Type", type, false);
- maybeAddHeaders(file, metadata, response);
- response.setHeader("Content-Length", "" + count, false);
-
- var fis = new FileInputStream(file, PR_RDONLY, PERMS_READONLY,
- Ci.nsIFileInputStream.CLOSE_ON_EOF);
-
- offset = offset || 0;
- count = count || file.fileSize;
- NS_ASSERT(offset === 0 || offset < file.fileSize, "bad offset");
- NS_ASSERT(count >= 0, "bad count");
- NS_ASSERT(offset + count <= file.fileSize, "bad total data size");
-
- try
- {
- if (offset !== 0)
- {
- // Seek (or read, if seeking isn't supported) to the correct offset so
- // the data sent to the client matches the requested range.
- if (fis instanceof Ci.nsISeekableStream)
- fis.seek(Ci.nsISeekableStream.NS_SEEK_SET, offset);
- else
- new ScriptableInputStream(fis).read(offset);
- }
- }
- catch (e)
- {
- fis.close();
- throw e;
- }
-
- function writeMore()
- {
- gThreadManager.dispatchToMainThread(writeData);
- }
-
- var input = new BinaryInputStream(fis);
- var output = new BinaryOutputStream(response.bodyOutputStream);
- var writeData =
- {
- run: function()
- {
- var chunkSize = Math.min(65536, count);
- count -= chunkSize;
- NS_ASSERT(count >= 0, "underflow");
-
- try
- {
- var data = input.readByteArray(chunkSize);
- NS_ASSERT(data.length === chunkSize,
- "incorrect data returned? got " + data.length +
- ", expected " + chunkSize);
- output.writeByteArray(data, data.length);
- if (count === 0)
- {
- fis.close();
- response.finish();
- }
- else
- {
- writeMore();
- }
- }
- catch (e)
- {
- try
- {
- fis.close();
- }
- finally
- {
- response.finish();
- }
- throw e;
- }
- }
- };
-
- writeMore();
-
- // Now that we know copying will start, flag the response as async.
- response.processAsync();
- }
- },
-
- /**
- * Get the value corresponding to a given key for the given path for SJS state
- * preservation across requests.
- *
- * @param path : string
- * the path from which the given state is to be retrieved
- * @param k : string
- * the key whose corresponding value is to be returned
- * @returns string
- * the corresponding value, which is initially the empty string
- */
- _getState: function(path, k)
- {
- var state = this._state;
- if (path in state && k in state[path])
- return state[path][k];
- return "";
- },
-
- /**
- * Set the value corresponding to a given key for the given path for SJS state
- * preservation across requests.
- *
- * @param path : string
- * the path from which the given state is to be retrieved
- * @param k : string
- * the key whose corresponding value is to be set
- * @param v : string
- * the value to be set
- */
- _setState: function(path, k, v)
- {
- if (typeof v !== "string")
- throw new Error("non-string value passed");
- var state = this._state;
- if (!(path in state))
- state[path] = {};
- state[path][k] = v;
- },
-
- /**
- * Get the value corresponding to a given key for SJS state preservation
- * across requests.
- *
- * @param k : string
- * the key whose corresponding value is to be returned
- * @returns string
- * the corresponding value, which is initially the empty string
- */
- _getSharedState: function(k)
- {
- var state = this._sharedState;
- if (k in state)
- return state[k];
- return "";
- },
-
- /**
- * Set the value corresponding to a given key for SJS state preservation
- * across requests.
- *
- * @param k : string
- * the key whose corresponding value is to be set
- * @param v : string
- * the value to be set
- */
- _setSharedState: function(k, v)
- {
- if (typeof v !== "string")
- throw new Error("non-string value passed");
- this._sharedState[k] = v;
- },
-
- /**
- * Returns the object associated with the given key in the server for SJS
- * state preservation across requests.
- *
- * @param k : string
- * the key whose corresponding object is to be returned
- * @returns nsISupports
- * the corresponding object, or null if none was present
- */
- _getObjectState: function(k)
- {
- if (typeof k !== "string")
- throw new Error("non-string key passed");
- return this._objectState[k] || null;
- },
-
- /**
- * Sets the object associated with the given key in the server for SJS
- * state preservation across requests.
- *
- * @param k : string
- * the key whose corresponding object is to be set
- * @param v : nsISupports
- * the object to be associated with the given key; may be null
- */
- _setObjectState: function(k, v)
- {
- if (typeof k !== "string")
- throw new Error("non-string key passed");
- if (typeof v !== "object")
- throw new Error("non-object value passed");
- if (v && !("QueryInterface" in v))
- {
- throw new Error("must pass an nsISupports; use wrappedJSObject to ease " +
- "pain when using the server from JS");
- }
-
- this._objectState[k] = v;
- },
-
- /**
- * Gets a content-type for the given file, first by checking for any custom
- * MIME-types registered with this handler for the file's extension, second by
- * asking the global MIME service for a content-type, and finally by failing
- * over to application/octet-stream.
- *
- * @param file : nsIFile
- * the nsIFile for which to get a file type
- * @returns string
- * the best content-type which can be determined for the file
- */
- _getTypeFromFile: function(file)
- {
- try
- {
- var name = file.leafName;
- var dot = name.lastIndexOf(".");
- if (dot > 0)
- {
- var ext = name.slice(dot + 1);
- if (ext in this._mimeMappings)
- return this._mimeMappings[ext];
- }
- return Cc["@mozilla.org/uriloader/external-helper-app-service;1"]
- .getService(Ci.nsIMIMEService)
- .getTypeFromFile(file);
- }
- catch (e)
- {
- return "application/octet-stream";
- }
- },
-
- /**
- * Returns the nsIFile which corresponds to the path, as determined using
- * all registered path->directory mappings and any paths which are explicitly
- * overridden.
- *
- * @param path : string
- * the server path for which a file should be retrieved, e.g. "/foo/bar"
- * @throws HttpError
- * when the correct action is the corresponding HTTP error (i.e., because no
- * mapping was found for a directory in path, the referenced file doesn't
- * exist, etc.)
- * @returns nsIFile
- * the file to be sent as the response to a request for the path
- */
- _getFileForPath: function(path)
- {
- // decode and add underscores as necessary
- try
- {
- path = toInternalPath(path, true);
- }
- catch (e)
- {
- dumpn("*** toInternalPath threw " + e);
- throw HTTP_400; // malformed path
- }
-
- // next, get the directory which contains this path
- var pathMap = this._pathDirectoryMap;
-
- // An example progression of tmp for a path "/foo/bar/baz/" might be:
- // "foo/bar/baz/", "foo/bar/baz", "foo/bar", "foo", ""
- var tmp = path.substring(1);
- while (true)
- {
- // do we have a match for current head of the path?
- var file = pathMap.get(tmp);
- if (file)
- {
- // XXX hack; basically disable showing mapping for /foo/bar/ when the
- // requested path was /foo/bar, because relative links on the page
- // will all be incorrect -- we really need the ability to easily
- // redirect here instead
- if (tmp == path.substring(1) &&
- tmp.length != 0 &&
- tmp.charAt(tmp.length - 1) != "/")
- file = null;
- else
- break;
- }
-
- // if we've finished trying all prefixes, exit
- if (tmp == "")
- break;
-
- tmp = tmp.substring(0, tmp.lastIndexOf("/"));
- }
-
- // no mapping applies, so 404
- if (!file)
- throw HTTP_404;
-
-
- // last, get the file for the path within the determined directory
- var parentFolder = file.parent;
- var dirIsRoot = (parentFolder == null);
-
- // Strategy here is to append components individually, making sure we
- // never move above the given directory; this allows paths such as
- // "/foo/../bar" but prevents paths such as "/../base-sibling";
- // this component-wise approach also means the code works even on platforms
- // which don't use "/" as the directory separator, such as Windows
- var leafPath = path.substring(tmp.length + 1);
- var comps = leafPath.split("/");
- for (var i = 0, sz = comps.length; i < sz; i++)
- {
- var comp = comps[i];
-
- if (comp == "..")
- file = file.parent;
- else if (comp == "." || comp == "")
- continue;
- else
- file.append(comp);
-
- if (!dirIsRoot && file.equals(parentFolder))
- throw HTTP_403;
- }
-
- return file;
- },
-
- /**
- * Writes the error page for the given HTTP error code over the given
- * connection.
- *
- * @param errorCode : uint
- * the HTTP error code to be used
- * @param connection : Connection
- * the connection on which the error occurred
- */
- handleError: function(errorCode, connection)
- {
- var response = new Response(connection);
-
- dumpn("*** error in request: " + errorCode);
-
- this._handleError(errorCode, new Request(connection.port), response);
- },
-
- /**
- * Handles a request which generates the given error code, using the
- * user-defined error handler if one has been set, gracefully falling back to
- * the x00 status code if the code has no handler, and failing to status code
- * 500 if all else fails.
- *
- * @param errorCode : uint
- * the HTTP error which is to be returned
- * @param metadata : Request
- * metadata for the request, which will often be incomplete since this is an
- * error
- * @param response : Response
- * an uninitialized Response should be initialized when this method
- * completes with information which represents the desired error code in the
- * ideal case or a fallback code in abnormal circumstances (i.e., 500 is a
- * fallback for 505, per HTTP specs)
- */
- _handleError: function(errorCode, metadata, response)
- {
- if (!metadata)
- throw Cr.NS_ERROR_NULL_POINTER;
-
- var errorX00 = errorCode - (errorCode % 100);
-
- try
- {
- if (!(errorCode in HTTP_ERROR_CODES))
- dumpn("*** WARNING: requested invalid error: " + errorCode);
-
- // RFC 2616 says that we should try to handle an error by its class if we
- // can't otherwise handle it -- if that fails, we revert to handling it as
- // a 500 internal server error, and if that fails we throw and shut down
- // the server
-
- // actually handle the error
- try
- {
- if (errorCode in this._overrideErrors)
- this._overrideErrors[errorCode](metadata, response);
- else
- this._defaultErrors[errorCode](metadata, response);
- }
- catch (e)
- {
- if (response.partiallySent())
- {
- response.abort(e);
- return;
- }
-
- // don't retry the handler that threw
- if (errorX00 == errorCode)
- throw HTTP_500;
-
- dumpn("*** error in handling for error code " + errorCode + ", " +
- "falling back to " + errorX00 + "...");
- response = new Response(response._connection);
- if (errorX00 in this._overrideErrors)
- this._overrideErrors[errorX00](metadata, response);
- else if (errorX00 in this._defaultErrors)
- this._defaultErrors[errorX00](metadata, response);
- else
- throw HTTP_500;
- }
- }
- catch (e)
- {
- if (response.partiallySent())
- {
- response.abort();
- return;
- }
-
- // we've tried everything possible for a meaningful error -- now try 500
- dumpn("*** error in handling for error code " + errorX00 + ", falling " +
- "back to 500...");
-
- try
- {
- response = new Response(response._connection);
- if (500 in this._overrideErrors)
- this._overrideErrors[500](metadata, response);
- else
- this._defaultErrors[500](metadata, response);
- }
- catch (e2)
- {
- dumpn("*** multiple errors in default error handlers!");
- dumpn("*** e == " + e + ", e2 == " + e2);
- response.abort(e2);
- return;
- }
- }
-
- response.complete();
- },
-
- // FIELDS
-
- /**
- * This object contains the default handlers for the various HTTP error codes.
- */
- _defaultErrors:
- {
- 400: function(metadata, response)
- {
- // 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;charset=utf-8", false);
-
- var body = "Bad request\n";
- response.bodyOutputStream.write(body, body.length);
- },
- 403: function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion, 403, "Forbidden");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- 403 Forbidden\
- \
- 403 Forbidden
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- },
- 404: function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion, 404, "Not Found");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- 404 Not Found\
- \
- 404 Not Found
\
- \
- " +
- htmlEscape(metadata.path) +
- " was not found.\
-
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- },
- 416: function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion,
- 416,
- "Requested Range Not Satisfiable");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- \
- 416 Requested Range Not Satisfiable\
- \
- 416 Requested Range Not Satisfiable
\
- The byte range was not valid for the\
- requested resource.\
-
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- },
- 500: function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion,
- 500,
- "Internal Server Error");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- 500 Internal Server Error\
- \
- 500 Internal Server Error
\
- Something's broken in this server and\
- needs to be fixed.
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- },
- 501: function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion, 501, "Not Implemented");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- 501 Not Implemented\
- \
- 501 Not Implemented
\
- This server is not (yet) Apache.
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- },
- 505: function(metadata, response)
- {
- response.setStatusLine("1.1", 505, "HTTP Version Not Supported");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- 505 HTTP Version Not Supported\
- \
- 505 HTTP Version Not Supported
\
- This server only supports HTTP/1.0 and HTTP/1.1\
- connections.
\
- \
- ";
- response.bodyOutputStream.write(body, body.length);
- }
- },
-
- /**
- * Contains handlers for the default set of URIs contained in this server.
- */
- _defaultPaths:
- {
- "/": function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "text/html;charset=utf-8", false);
-
- var body = "\
- httpd.js\
- \
- httpd.js
\
- If you're seeing this page, httpd.js is up and\
- serving requests! Now set a base path and serve some\
- files!
\
- \
- ";
-
- response.bodyOutputStream.write(body, body.length);
- },
-
- "/trace": function(metadata, response)
- {
- response.setStatusLine(metadata.httpVersion, 200, "OK");
- response.setHeader("Content-Type", "text/plain;charset=utf-8", false);
-
- var body = "Request-URI: " +
- metadata.scheme + "://" + metadata.host + ":" + metadata.port +
- metadata.path + "\n\n";
- body += "Request (semantically equivalent, slightly reformatted):\n\n";
- body += metadata.method + " " + metadata.path;
-
- if (metadata.queryString)
- body += "?" + metadata.queryString;
-
- body += " HTTP/" + metadata.httpVersion + "\r\n";
-
- var headEnum = metadata.headers;
- while (headEnum.hasMoreElements())
- {
- var fieldName = headEnum.getNext()
- .QueryInterface(Ci.nsISupportsString)
- .data;
- body += fieldName + ": " + metadata.getHeader(fieldName) + "\r\n";
- }
-
- response.bodyOutputStream.write(body, body.length);
- }
- }
-};
-
-
-/**
- * Maps absolute paths to files on the local file system (as nsILocalFiles).
- */
-function FileMap()
-{
- /** Hash which will map paths to nsILocalFiles. */
- this._map = {};
-}
-FileMap.prototype =
-{
- // PUBLIC API
-
- /**
- * Maps key to a clone of the nsIFile value if value is non-null;
- * otherwise, removes any extant mapping for key.
- *
- * @param key : string
- * string to which a clone of value is mapped
- * @param value : nsIFile
- * the file to map to key, or null to remove a mapping
- */
- put: function(key, value)
- {
- if (value)
- this._map[key] = value.clone();
- else
- delete this._map[key];
- },
-
- /**
- * Returns a clone of the nsIFile mapped to key, or null if no such
- * mapping exists.
- *
- * @param key : string
- * key to which the returned file maps
- * @returns nsIFile
- * a clone of the mapped file, or null if no mapping exists
- */
- get: function(key)
- {
- var val = this._map[key];
- return val ? val.clone() : null;
- }
-};
-
-
-// Response CONSTANTS
-
-// token = *
-// CHAR =
-// CTL =
-// separators = "(" | ")" | "<" | ">" | "@"
-// | "," | ";" | ":" | "\" | <">
-// | "/" | "[" | "]" | "?" | "="
-// | "{" | "}" | SP | HT
-const IS_TOKEN_ARRAY =
- [0, 0, 0, 0, 0, 0, 0, 0, // 0
- 0, 0, 0, 0, 0, 0, 0, 0, // 8
- 0, 0, 0, 0, 0, 0, 0, 0, // 16
- 0, 0, 0, 0, 0, 0, 0, 0, // 24
-
- 0, 1, 0, 1, 1, 1, 1, 1, // 32
- 0, 0, 1, 1, 0, 1, 1, 0, // 40
- 1, 1, 1, 1, 1, 1, 1, 1, // 48
- 1, 1, 0, 0, 0, 0, 0, 0, // 56
-
- 0, 1, 1, 1, 1, 1, 1, 1, // 64
- 1, 1, 1, 1, 1, 1, 1, 1, // 72
- 1, 1, 1, 1, 1, 1, 1, 1, // 80
- 1, 1, 1, 0, 0, 0, 1, 1, // 88
-
- 1, 1, 1, 1, 1, 1, 1, 1, // 96
- 1, 1, 1, 1, 1, 1, 1, 1, // 104
- 1, 1, 1, 1, 1, 1, 1, 1, // 112
- 1, 1, 1, 0, 1, 0, 1]; // 120
-
-
-/**
- * Determines whether the given character code is a CTL.
- *
- * @param code : uint
- * the character code
- * @returns boolean
- * true if code is a CTL, false otherwise
- */
-function isCTL(code)
-{
- return (code >= 0 && code <= 31) || (code == 127);
-}
-
-/**
- * Represents a response to an HTTP request, encapsulating all details of that
- * response. This includes all headers, the HTTP version, status code and
- * explanation, and the entity itself.
- *
- * @param connection : Connection
- * the connection over which this response is to be written
- */
-function Response(connection)
-{
- /** The connection over which this response will be written. */
- this._connection = connection;
-
- /**
- * The HTTP version of this response; defaults to 1.1 if not set by the
- * handler.
- */
- this._httpVersion = nsHttpVersion.HTTP_1_1;
-
- /**
- * The HTTP code of this response; defaults to 200.
- */
- this._httpCode = 200;
-
- /**
- * The description of the HTTP code in this response; defaults to "OK".
- */
- this._httpDescription = "OK";
-
- /**
- * An nsIHttpHeaders object in which the headers in this response should be
- * stored. This property is null after the status line and headers have been
- * written to the network, and it may be modified up until it is cleared,
- * except if this._finished is set first (in which case headers are written
- * asynchronously in response to a finish() call not preceded by
- * flushHeaders()).
- */
- this._headers = new nsHttpHeaders();
-
- /**
- * Set to true when this response is ended (completely constructed if possible
- * and the connection closed); further actions on this will then fail.
- */
- this._ended = false;
-
- /**
- * A stream used to hold data written to the body of this response.
- */
- this._bodyOutputStream = null;
-
- /**
- * A stream containing all data that has been written to the body of this
- * response so far. (Async handlers make the data contained in this
- * unreliable as a way of determining content length in general, but auxiliary
- * saved information can sometimes be used to guarantee reliability.)
- */
- this._bodyInputStream = null;
-
- /**
- * A stream copier which copies data to the network. It is initially null
- * until replaced with a copier for response headers; when headers have been
- * fully sent it is replaced with a copier for the response body, remaining
- * so for the duration of response processing.
- */
- this._asyncCopier = null;
-
- /**
- * True if this response has been designated as being processed
- * asynchronously rather than for the duration of a single call to
- * nsIHttpRequestHandler.handle.
- */
- this._processAsync = false;
-
- /**
- * True iff finish() has been called on this, signaling that no more changes
- * to this may be made.
- */
- this._finished = false;
-
- /**
- * True iff powerSeized() has been called on this, signaling that this
- * response is to be handled manually by the response handler (which may then
- * send arbitrary data in response, even non-HTTP responses).
- */
- this._powerSeized = false;
-}
-Response.prototype =
-{
- // PUBLIC CONSTRUCTION API
-
- //
- // see nsIHttpResponse.bodyOutputStream
- //
- get bodyOutputStream()
- {
- if (this._finished)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
-
- if (!this._bodyOutputStream)
- {
- var pipe = new Pipe(true, false, Response.SEGMENT_SIZE, PR_UINT32_MAX,
- null);
- this._bodyOutputStream = pipe.outputStream;
- this._bodyInputStream = pipe.inputStream;
- if (this._processAsync || this._powerSeized)
- this._startAsyncProcessor();
- }
-
- return this._bodyOutputStream;
- },
-
- //
- // see nsIHttpResponse.write
- //
- write: function(data)
- {
- if (this._finished)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
-
- var dataAsString = String(data);
- this.bodyOutputStream.write(dataAsString, dataAsString.length);
- },
-
- //
- // see nsIHttpResponse.setStatusLine
- //
- setStatusLine: function(httpVersion, code, description)
- {
- if (!this._headers || this._finished || this._powerSeized)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- this._ensureAlive();
-
- if (!(code >= 0 && code < 1000))
- throw Cr.NS_ERROR_INVALID_ARG;
-
- try
- {
- var httpVer;
- // avoid version construction for the most common cases
- if (!httpVersion || httpVersion == "1.1")
- httpVer = nsHttpVersion.HTTP_1_1;
- else if (httpVersion == "1.0")
- httpVer = nsHttpVersion.HTTP_1_0;
- else
- httpVer = new nsHttpVersion(httpVersion);
- }
- catch (e)
- {
- throw Cr.NS_ERROR_INVALID_ARG;
- }
-
- // Reason-Phrase = *
- // TEXT =
- //
- // XXX this ends up disallowing octets which aren't Unicode, I think -- not
- // much to do if description is IDL'd as string
- if (!description)
- description = "";
- for (var i = 0; i < description.length; i++)
- if (isCTL(description.charCodeAt(i)) && description.charAt(i) != "\t")
- throw Cr.NS_ERROR_INVALID_ARG;
-
- // set the values only after validation to preserve atomicity
- this._httpDescription = description;
- this._httpCode = code;
- this._httpVersion = httpVer;
- },
-
- //
- // see nsIHttpResponse.setHeader
- //
- setHeader: function(name, value, merge)
- {
- if (!this._headers || this._finished || this._powerSeized)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- this._ensureAlive();
-
- this._headers.setHeader(name, value, merge);
- },
-
- //
- // see nsIHttpResponse.processAsync
- //
- processAsync: function()
- {
- if (this._finished)
- throw Cr.NS_ERROR_UNEXPECTED;
- if (this._powerSeized)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- if (this._processAsync)
- return;
- this._ensureAlive();
-
- dumpn("*** processing connection " + this._connection.number + " async");
- this._processAsync = true;
-
- /*
- * Either the bodyOutputStream getter or this method is responsible for
- * starting the asynchronous processor and catching writes of data to the
- * response body of async responses as they happen, for the purpose of
- * forwarding those writes to the actual connection's output stream.
- * If bodyOutputStream is accessed first, calling this method will create
- * the processor (when it first is clear that body data is to be written
- * immediately, not buffered). If this method is called first, accessing
- * bodyOutputStream will create the processor. If only this method is
- * called, we'll write nothing, neither headers nor the nonexistent body,
- * until finish() is called. Since that delay is easily avoided by simply
- * getting bodyOutputStream or calling write(""), we don't worry about it.
- */
- if (this._bodyOutputStream && !this._asyncCopier)
- this._startAsyncProcessor();
- },
-
- //
- // see nsIHttpResponse.seizePower
- //
- seizePower: function()
- {
- if (this._processAsync)
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- if (this._finished)
- throw Cr.NS_ERROR_UNEXPECTED;
- if (this._powerSeized)
- return;
- this._ensureAlive();
-
- dumpn("*** forcefully seizing power over connection " +
- this._connection.number + "...");
-
- // Purge any already-written data without sending it. We could as easily
- // swap out the streams entirely, but that makes it possible to acquire and
- // unknowingly use a stale reference, so we require there only be one of
- // each stream ever for any response to avoid this complication.
- if (this._asyncCopier)
- this._asyncCopier.cancel(Cr.NS_BINDING_ABORTED);
- this._asyncCopier = null;
- if (this._bodyOutputStream)
- {
- var input = new BinaryInputStream(this._bodyInputStream);
- var avail;
- while ((avail = input.available()) > 0)
- input.readByteArray(avail);
- }
-
- this._powerSeized = true;
- if (this._bodyOutputStream)
- this._startAsyncProcessor();
- },
-
- //
- // see nsIHttpResponse.finish
- //
- finish: function()
- {
- if (!this._processAsync && !this._powerSeized)
- throw Cr.NS_ERROR_UNEXPECTED;
- if (this._finished)
- return;
-
- dumpn("*** finishing connection " + this._connection.number);
- this._startAsyncProcessor(); // in case bodyOutputStream was never accessed
- if (this._bodyOutputStream)
- this._bodyOutputStream.close();
- this._finished = true;
- },
-
-
- // NSISUPPORTS
-
- //
- // see nsISupports.QueryInterface
- //
- QueryInterface: function(iid)
- {
- if (iid.equals(Ci.nsIHttpResponse) || iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // POST-CONSTRUCTION API (not exposed externally)
-
- /**
- * The HTTP version number of this, as a string (e.g. "1.1").
- */
- get httpVersion()
- {
- this._ensureAlive();
- return this._httpVersion.toString();
- },
-
- /**
- * The HTTP status code of this response, as a string of three characters per
- * RFC 2616.
- */
- get httpCode()
- {
- this._ensureAlive();
-
- var codeString = (this._httpCode < 10 ? "0" : "") +
- (this._httpCode < 100 ? "0" : "") +
- this._httpCode;
- return codeString;
- },
-
- /**
- * The description of the HTTP status code of this response, or "" if none is
- * set.
- */
- get httpDescription()
- {
- this._ensureAlive();
-
- return this._httpDescription;
- },
-
- /**
- * The headers in this response, as an nsHttpHeaders object.
- */
- get headers()
- {
- this._ensureAlive();
-
- return this._headers;
- },
-
- //
- // see nsHttpHeaders.getHeader
- //
- getHeader: function(name)
- {
- this._ensureAlive();
-
- return this._headers.getHeader(name);
- },
-
- /**
- * Determines whether this response may be abandoned in favor of a newly
- * constructed response. A response may be abandoned only if it is not being
- * sent asynchronously and if raw control over it has not been taken from the
- * server.
- *
- * @returns boolean
- * true iff no data has been written to the network
- */
- partiallySent: function()
- {
- dumpn("*** partiallySent()");
- return this._processAsync || this._powerSeized;
- },
-
- /**
- * If necessary, kicks off the remaining request processing needed to be done
- * after a request handler performs its initial work upon this response.
- */
- complete: function()
- {
- dumpn("*** complete()");
- if (this._processAsync || this._powerSeized)
- {
- NS_ASSERT(this._processAsync ^ this._powerSeized,
- "can't both send async and relinquish power");
- return;
- }
-
- NS_ASSERT(!this.partiallySent(), "completing a partially-sent response?");
-
- this._startAsyncProcessor();
-
- // Now make sure we finish processing this request!
- if (this._bodyOutputStream)
- this._bodyOutputStream.close();
- },
-
- /**
- * Abruptly ends processing of this response, usually due to an error in an
- * incoming request but potentially due to a bad error handler. Since we
- * cannot handle the error in the usual way (giving an HTTP error page in
- * response) because data may already have been sent (or because the response
- * might be expected to have been generated asynchronously or completely from
- * scratch by the handler), we stop processing this response and abruptly
- * close the connection.
- *
- * @param e : Error
- * the exception which precipitated this abort, or null if no such exception
- * was generated
- */
- abort: function(e)
- {
- dumpn("*** abort(<" + e + ">)");
-
- // This response will be ended by the processor if one was created.
- var copier = this._asyncCopier;
- if (copier)
- {
- // We dispatch asynchronously here so that any pending writes of data to
- // the connection will be deterministically written. This makes it easier
- // to specify exact behavior, and it makes observable behavior more
- // predictable for clients. Note that the correctness of this depends on
- // callbacks in response to _waitToReadData in WriteThroughCopier
- // happening asynchronously with respect to the actual writing of data to
- // bodyOutputStream, as they currently do; if they happened synchronously,
- // an event which ran before this one could write more data to the
- // response body before we get around to canceling the copier. We have
- // tests for this in test_seizepower.js, however, and I can't think of a
- // way to handle both cases without removing bodyOutputStream access and
- // moving its effective write(data, length) method onto Response, which
- // would be slower and require more code than this anyway.
- gThreadManager.dispatchToMainThread({
- run: function()
- {
- dumpn("*** canceling copy asynchronously...");
- copier.cancel(Cr.NS_ERROR_UNEXPECTED);
- }
- });
- }
- else
- {
- this.end();
- }
- },
-
- /**
- * Closes this response's network connection, marks the response as finished,
- * and notifies the server handler that the request is done being processed.
- */
- end: function()
- {
- NS_ASSERT(!this._ended, "ending this response twice?!?!");
-
- this._connection.close();
- if (this._bodyOutputStream)
- this._bodyOutputStream.close();
-
- this._finished = true;
- this._ended = true;
- },
-
- // PRIVATE IMPLEMENTATION
-
- /**
- * Sends the status line and headers of this response if they haven't been
- * sent and initiates the process of copying data written to this response's
- * body to the network.
- */
- _startAsyncProcessor: function()
- {
- dumpn("*** _startAsyncProcessor()");
-
- // Handle cases where we're being called a second time. The former case
- // happens when this is triggered both by complete() and by processAsync(),
- // while the latter happens when processAsync() in conjunction with sent
- // data causes abort() to be called.
- if (this._asyncCopier || this._ended)
- {
- dumpn("*** ignoring second call to _startAsyncProcessor");
- return;
- }
-
- // Send headers if they haven't been sent already and should be sent, then
- // asynchronously continue to send the body.
- if (this._headers && !this._powerSeized)
- {
- this._sendHeaders();
- return;
- }
-
- this._headers = null;
- this._sendBody();
- },
-
- /**
- * Signals that all modifications to the response status line and headers are
- * complete and then sends that data over the network to the client. Once
- * this method completes, a different response to the request that resulted
- * in this response cannot be sent -- the only possible action in case of
- * error is to abort the response and close the connection.
- */
- _sendHeaders: function()
- {
- dumpn("*** _sendHeaders()");
-
- NS_ASSERT(this._headers);
- NS_ASSERT(!this._powerSeized);
-
- // request-line
- var statusLine = "HTTP/" + this.httpVersion + " " +
- this.httpCode + " " +
- this.httpDescription + "\r\n";
-
- // header post-processing
-
- var headers = this._headers;
- headers.setHeader("Connection", "close", false);
- headers.setHeader("Server", "httpd.js", false);
- if (!headers.hasHeader("Date"))
- headers.setHeader("Date", toDateString(Date.now()), false);
-
- // Any response not being processed asynchronously must have an associated
- // Content-Length header for reasons of backwards compatibility with the
- // initial server, which fully buffered every response before sending it.
- // Beyond that, however, it's good to do this anyway because otherwise it's
- // impossible to test behaviors that depend on the presence or absence of a
- // Content-Length header.
- if (!this._processAsync)
- {
- dumpn("*** non-async response, set Content-Length");
-
- var bodyStream = this._bodyInputStream;
- var avail = bodyStream ? bodyStream.available() : 0;
-
- // XXX assumes stream will always report the full amount of data available
- headers.setHeader("Content-Length", "" + avail, false);
- }
-
-
- // construct and send response
- dumpn("*** header post-processing completed, sending response head...");
-
- // request-line
- var preambleData = [statusLine];
-
- // headers
- var headEnum = headers.enumerator;
- while (headEnum.hasMoreElements())
- {
- var fieldName = headEnum.getNext()
- .QueryInterface(Ci.nsISupportsString)
- .data;
- var values = headers.getHeaderValues(fieldName);
- for (var i = 0, sz = values.length; i < sz; i++)
- preambleData.push(fieldName + ": " + values[i] + "\r\n");
- }
-
- // end request-line/headers
- preambleData.push("\r\n");
-
- var preamble = preambleData.join("");
-
- var responseHeadPipe = new Pipe(true, false, 0, PR_UINT32_MAX, null);
- responseHeadPipe.outputStream.write(preamble, preamble.length);
-
- var response = this;
- var copyObserver =
- {
- onStartRequest: function(request, cx)
- {
- dumpn("*** preamble copying started");
- },
-
- onStopRequest: function(request, cx, statusCode)
- {
- dumpn("*** preamble copying complete " +
- "[status=0x" + statusCode.toString(16) + "]");
-
- if (!Components.isSuccessCode(statusCode))
- {
- dumpn("!!! header copying problems: non-success statusCode, " +
- "ending response");
-
- response.end();
- }
- else
- {
- response._sendBody();
- }
- },
-
- QueryInterface: function(aIID)
- {
- if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- };
-
- var headerCopier = this._asyncCopier =
- new WriteThroughCopier(responseHeadPipe.inputStream,
- this._connection.output,
- copyObserver, null);
-
- responseHeadPipe.outputStream.close();
-
- // Forbid setting any more headers or modifying the request line.
- this._headers = null;
- },
-
- /**
- * Asynchronously writes the body of the response (or the entire response, if
- * seizePower() has been called) to the network.
- */
- _sendBody: function()
- {
- dumpn("*** _sendBody");
-
- NS_ASSERT(!this._headers, "still have headers around but sending body?");
-
- // If no body data was written, we're done
- if (!this._bodyInputStream)
- {
- dumpn("*** empty body, response finished");
- this.end();
- return;
- }
-
- var response = this;
- var copyObserver =
- {
- onStartRequest: function(request, context)
- {
- dumpn("*** onStartRequest");
- },
-
- onStopRequest: function(request, cx, statusCode)
- {
- dumpn("*** onStopRequest [status=0x" + statusCode.toString(16) + "]");
-
- if (statusCode === Cr.NS_BINDING_ABORTED)
- {
- dumpn("*** terminating copy observer without ending the response");
- }
- else
- {
- if (!Components.isSuccessCode(statusCode))
- dumpn("*** WARNING: non-success statusCode in onStopRequest");
-
- response.end();
- }
- },
-
- QueryInterface: function(aIID)
- {
- if (aIID.equals(Ci.nsIRequestObserver) || aIID.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- };
-
- dumpn("*** starting async copier of body data...");
- this._asyncCopier =
- new WriteThroughCopier(this._bodyInputStream, this._connection.output,
- copyObserver, null);
- },
-
- /** Ensures that this hasn't been ended. */
- _ensureAlive: function()
- {
- NS_ASSERT(!this._ended, "not handling response lifetime correctly");
- }
-};
-
-/**
- * Size of the segments in the buffer used in storing response data and writing
- * it to the socket.
- */
-Response.SEGMENT_SIZE = 8192;
-
-/** Serves double duty in WriteThroughCopier implementation. */
-function notImplemented()
-{
- throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-}
-
-/** Returns true iff the given exception represents stream closure. */
-function streamClosed(e)
-{
- return e === Cr.NS_BASE_STREAM_CLOSED ||
- (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_CLOSED);
-}
-
-/** Returns true iff the given exception represents a blocked stream. */
-function wouldBlock(e)
-{
- return e === Cr.NS_BASE_STREAM_WOULD_BLOCK ||
- (typeof e === "object" && e.result === Cr.NS_BASE_STREAM_WOULD_BLOCK);
-}
-
-/**
- * Copies data from source to sink as it becomes available, when that data can
- * be written to sink without blocking.
- *
- * @param source : nsIAsyncInputStream
- * the stream from which data is to be read
- * @param sink : nsIAsyncOutputStream
- * the stream to which data is to be copied
- * @param observer : nsIRequestObserver
- * an observer which will be notified when the copy starts and finishes
- * @param context : nsISupports
- * context passed to observer when notified of start/stop
- * @throws NS_ERROR_NULL_POINTER
- * if source, sink, or observer are null
- */
-function WriteThroughCopier(source, sink, observer, context)
-{
- if (!source || !sink || !observer)
- throw Cr.NS_ERROR_NULL_POINTER;
-
- /** Stream from which data is being read. */
- this._source = source;
-
- /** Stream to which data is being written. */
- this._sink = sink;
-
- /** Observer watching this copy. */
- this._observer = observer;
-
- /** Context for the observer watching this. */
- this._context = context;
-
- /**
- * True iff this is currently being canceled (cancel has been called, the
- * callback may not yet have been made).
- */
- this._canceled = false;
-
- /**
- * False until all data has been read from input and written to output, at
- * which point this copy is completed and cancel() is asynchronously called.
- */
- this._completed = false;
-
- /** Required by nsIRequest, meaningless. */
- this.loadFlags = 0;
- /** Required by nsIRequest, meaningless. */
- this.loadGroup = null;
- /** Required by nsIRequest, meaningless. */
- this.name = "response-body-copy";
-
- /** Status of this request. */
- this.status = Cr.NS_OK;
-
- /** Arrays of byte strings waiting to be written to output. */
- this._pendingData = [];
-
- // start copying
- try
- {
- observer.onStartRequest(this, context);
- this._waitToReadData();
- this._waitForSinkClosure();
- }
- catch (e)
- {
- dumpn("!!! error starting copy: " + e +
- ("lineNumber" in e ? ", line " + e.lineNumber : ""));
- dumpn(e.stack);
- this.cancel(Cr.NS_ERROR_UNEXPECTED);
- }
-}
-WriteThroughCopier.prototype =
-{
- /* nsISupports implementation */
-
- QueryInterface: function(iid)
- {
- if (iid.equals(Ci.nsIInputStreamCallback) ||
- iid.equals(Ci.nsIOutputStreamCallback) ||
- iid.equals(Ci.nsIRequest) ||
- iid.equals(Ci.nsISupports))
- {
- return this;
- }
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // NSIINPUTSTREAMCALLBACK
-
- /**
- * Receives a more-data-in-input notification and writes the corresponding
- * data to the output.
- *
- * @param input : nsIAsyncInputStream
- * the input stream on whose data we have been waiting
- */
- onInputStreamReady: function(input)
- {
- if (this._source === null)
- return;
-
- dumpn("*** onInputStreamReady");
-
- //
- // Ordinarily we'll read a non-zero amount of data from input, queue it up
- // to be written and then wait for further callbacks. The complications in
- // this method are the cases where we deviate from that behavior when errors
- // occur or when copying is drawing to a finish.
- //
- // The edge cases when reading data are:
- //
- // Zero data is read
- // If zero data was read, we're at the end of available data, so we can
- // should stop reading and move on to writing out what we have (or, if
- // we've already done that, onto notifying of completion).
- // A stream-closed exception is thrown
- // This is effectively a less kind version of zero data being read; the
- // only difference is that we notify of completion with that result
- // rather than with NS_OK.
- // Some other exception is thrown
- // This is the least kind result. We don't know what happened, so we
- // act as though the stream closed except that we notify of completion
- // with the result NS_ERROR_UNEXPECTED.
- //
-
- var bytesWanted = 0, bytesConsumed = -1;
- try
- {
- input = new BinaryInputStream(input);
-
- bytesWanted = Math.min(input.available(), Response.SEGMENT_SIZE);
- dumpn("*** input wanted: " + bytesWanted);
-
- if (bytesWanted > 0)
- {
- var data = input.readByteArray(bytesWanted);
- bytesConsumed = data.length;
- this._pendingData.push(String.fromCharCode.apply(String, data));
- }
-
- dumpn("*** " + bytesConsumed + " bytes read");
-
- // Handle the zero-data edge case in the same place as all other edge
- // cases are handled.
- if (bytesWanted === 0)
- throw Cr.NS_BASE_STREAM_CLOSED;
- }
- catch (e)
- {
- if (streamClosed(e))
- {
- dumpn("*** input stream closed");
- e = bytesWanted === 0 ? Cr.NS_OK : Cr.NS_ERROR_UNEXPECTED;
- }
- else
- {
- dumpn("!!! unexpected error reading from input, canceling: " + e);
- e = Cr.NS_ERROR_UNEXPECTED;
- }
-
- this._doneReadingSource(e);
- return;
- }
-
- var pendingData = this._pendingData;
-
- NS_ASSERT(bytesConsumed > 0);
- NS_ASSERT(pendingData.length > 0, "no pending data somehow?");
- NS_ASSERT(pendingData[pendingData.length - 1].length > 0,
- "buffered zero bytes of data?");
-
- NS_ASSERT(this._source !== null);
-
- // Reading has gone great, and we've gotten data to write now. What if we
- // don't have a place to write that data, because output went away just
- // before this read? Drop everything on the floor, including new data, and
- // cancel at this point.
- if (this._sink === null)
- {
- pendingData.length = 0;
- this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
- return;
- }
-
- // Okay, we've read the data, and we know we have a place to write it. We
- // need to queue up the data to be written, but *only* if none is queued
- // already -- if data's already queued, the code that actually writes the
- // data will make sure to wait on unconsumed pending data.
- try
- {
- if (pendingData.length === 1)
- this._waitToWriteData();
- }
- catch (e)
- {
- dumpn("!!! error waiting to write data just read, swallowing and " +
- "writing only what we already have: " + e);
- this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
- return;
- }
-
- // Whee! We successfully read some data, and it's successfully queued up to
- // be written. All that remains now is to wait for more data to read.
- try
- {
- this._waitToReadData();
- }
- catch (e)
- {
- dumpn("!!! error waiting to read more data: " + e);
- this._doneReadingSource(Cr.NS_ERROR_UNEXPECTED);
- }
- },
-
-
- // NSIOUTPUTSTREAMCALLBACK
-
- /**
- * Callback when data may be written to the output stream without blocking, or
- * when the output stream has been closed.
- *
- * @param output : nsIAsyncOutputStream
- * the output stream on whose writability we've been waiting, also known as
- * this._sink
- */
- onOutputStreamReady: function(output)
- {
- if (this._sink === null)
- return;
-
- dumpn("*** onOutputStreamReady");
-
- var pendingData = this._pendingData;
- if (pendingData.length === 0)
- {
- // There's no pending data to write. The only way this can happen is if
- // we're waiting on the output stream's closure, so we can respond to a
- // copying failure as quickly as possible (rather than waiting for data to
- // be available to read and then fail to be copied). Therefore, we must
- // be done now -- don't bother to attempt to write anything and wrap
- // things up.
- dumpn("!!! output stream closed prematurely, ending copy");
-
- this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
- return;
- }
-
-
- NS_ASSERT(pendingData[0].length > 0, "queued up an empty quantum?");
-
- //
- // Write out the first pending quantum of data. The possible errors here
- // are:
- //
- // The write might fail because we can't write that much data
- // Okay, we've written what we can now, so re-queue what's left and
- // finish writing it out later.
- // The write failed because the stream was closed
- // Discard pending data that we can no longer write, stop reading, and
- // signal that copying finished.
- // Some other error occurred.
- // Same as if the stream were closed, but notify with the status
- // NS_ERROR_UNEXPECTED so the observer knows something was wonky.
- //
-
- try
- {
- var quantum = pendingData[0];
-
- // XXX |quantum| isn't guaranteed to be ASCII, so we're relying on
- // undefined behavior! We're only using this because writeByteArray
- // is unusably broken for asynchronous output streams; see bug 532834
- // for details.
- var bytesWritten = output.write(quantum, quantum.length);
- if (bytesWritten === quantum.length)
- pendingData.shift();
- else
- pendingData[0] = quantum.substring(bytesWritten);
-
- dumpn("*** wrote " + bytesWritten + " bytes of data");
- }
- catch (e)
- {
- if (wouldBlock(e))
- {
- NS_ASSERT(pendingData.length > 0,
- "stream-blocking exception with no data to write?");
- NS_ASSERT(pendingData[0].length > 0,
- "stream-blocking exception with empty quantum?");
- this._waitToWriteData();
- return;
- }
-
- if (streamClosed(e))
- dumpn("!!! output stream prematurely closed, signaling error...");
- else
- dumpn("!!! unknown error: " + e + ", quantum=" + quantum);
-
- this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
- return;
- }
-
- // The day is ours! Quantum written, now let's see if we have more data
- // still to write.
- try
- {
- if (pendingData.length > 0)
- {
- this._waitToWriteData();
- return;
- }
- }
- catch (e)
- {
- dumpn("!!! unexpected error waiting to write pending data: " + e);
- this._doneWritingToSink(Cr.NS_ERROR_UNEXPECTED);
- return;
- }
-
- // Okay, we have no more pending data to write -- but might we get more in
- // the future?
- if (this._source !== null)
- {
- /*
- * If we might, then wait for the output stream to be closed. (We wait
- * only for closure because we have no data to write -- and if we waited
- * for a specific amount of data, we would get repeatedly notified for no
- * reason if over time the output stream permitted more and more data to
- * be written to it without blocking.)
- */
- this._waitForSinkClosure();
- }
- else
- {
- /*
- * On the other hand, if we can't have more data because the input
- * stream's gone away, then it's time to notify of copy completion.
- * Victory!
- */
- this._sink = null;
- this._cancelOrDispatchCancelCallback(Cr.NS_OK);
- }
- },
-
-
- // NSIREQUEST
-
- /** Returns true if the cancel observer hasn't been notified yet. */
- isPending: function()
- {
- return !this._completed;
- },
-
- /** Not implemented, don't use! */
- suspend: notImplemented,
- /** Not implemented, don't use! */
- resume: notImplemented,
-
- /**
- * Cancels data reading from input, asynchronously writes out any pending
- * data, and causes the observer to be notified with the given error code when
- * all writing has finished.
- *
- * @param status : nsresult
- * the status to pass to the observer when data copying has been canceled
- */
- cancel: function(status)
- {
- dumpn("*** cancel(" + status.toString(16) + ")");
-
- if (this._canceled)
- {
- dumpn("*** suppressing a late cancel");
- return;
- }
-
- this._canceled = true;
- this.status = status;
-
- // We could be in the middle of absolutely anything at this point. Both
- // input and output might still be around, we might have pending data to
- // write, and in general we know nothing about the state of the world. We
- // therefore must assume everything's in progress and take everything to its
- // final steady state (or so far as it can go before we need to finish
- // writing out remaining data).
-
- this._doneReadingSource(status);
- },
-
-
- // PRIVATE IMPLEMENTATION
-
- /**
- * Stop reading input if we haven't already done so, passing e as the status
- * when closing the stream, and kick off a copy-completion notice if no more
- * data remains to be written.
- *
- * @param e : nsresult
- * the status to be used when closing the input stream
- */
- _doneReadingSource: function(e)
- {
- dumpn("*** _doneReadingSource(0x" + e.toString(16) + ")");
-
- this._finishSource(e);
- if (this._pendingData.length === 0)
- this._sink = null;
- else
- NS_ASSERT(this._sink !== null, "null output?");
-
- // If we've written out all data read up to this point, then it's time to
- // signal completion.
- if (this._sink === null)
- {
- NS_ASSERT(this._pendingData.length === 0, "pending data still?");
- this._cancelOrDispatchCancelCallback(e);
- }
- },
-
- /**
- * Stop writing output if we haven't already done so, discard any data that
- * remained to be sent, close off input if it wasn't already closed, and kick
- * off a copy-completion notice.
- *
- * @param e : nsresult
- * the status to be used when closing input if it wasn't already closed
- */
- _doneWritingToSink: function(e)
- {
- dumpn("*** _doneWritingToSink(0x" + e.toString(16) + ")");
-
- this._pendingData.length = 0;
- this._sink = null;
- this._doneReadingSource(e);
- },
-
- /**
- * Completes processing of this copy: either by canceling the copy if it
- * hasn't already been canceled using the provided status, or by dispatching
- * the cancel callback event (with the originally provided status, of course)
- * if it already has been canceled.
- *
- * @param status : nsresult
- * the status code to use to cancel this, if this hasn't already been
- * canceled
- */
- _cancelOrDispatchCancelCallback: function(status)
- {
- dumpn("*** _cancelOrDispatchCancelCallback(" + status + ")");
-
- NS_ASSERT(this._source === null, "should have finished input");
- NS_ASSERT(this._sink === null, "should have finished output");
- NS_ASSERT(this._pendingData.length === 0, "should have no pending data");
-
- if (!this._canceled)
- {
- this.cancel(status);
- return;
- }
-
- var self = this;
- var event =
- {
- run: function()
- {
- dumpn("*** onStopRequest async callback");
-
- self._completed = true;
- try
- {
- self._observer.onStopRequest(self, self._context, self.status);
- }
- catch (e)
- {
- NS_ASSERT(false,
- "how are we throwing an exception here? we control " +
- "all the callers! " + e);
- }
- }
- };
-
- gThreadManager.dispatchToMainThread(event);
- },
-
- /**
- * Kicks off another wait for more data to be available from the input stream.
- */
- _waitToReadData: function()
- {
- dumpn("*** _waitToReadData");
- this._source.asyncWait(this, 0, Response.SEGMENT_SIZE,
- gThreadManager.mainThread);
- },
-
- /**
- * Kicks off another wait until data can be written to the output stream.
- */
- _waitToWriteData: function()
- {
- dumpn("*** _waitToWriteData");
-
- var pendingData = this._pendingData;
- NS_ASSERT(pendingData.length > 0, "no pending data to write?");
- NS_ASSERT(pendingData[0].length > 0, "buffered an empty write?");
-
- this._sink.asyncWait(this, 0, pendingData[0].length,
- gThreadManager.mainThread);
- },
-
- /**
- * Kicks off a wait for the sink to which data is being copied to be closed.
- * We wait for stream closure when we don't have any data to be copied, rather
- * than waiting to write a specific amount of data. We can't wait to write
- * data because the sink might be infinitely writable, and if no data appears
- * in the source for a long time we might have to spin quite a bit waiting to
- * write, waiting to write again, &c. Waiting on stream closure instead means
- * we'll get just one notification if the sink dies. Note that when data
- * starts arriving from the sink we'll resume waiting for data to be written,
- * dropping this closure-only callback entirely.
- */
- _waitForSinkClosure: function()
- {
- dumpn("*** _waitForSinkClosure");
-
- this._sink.asyncWait(this, Ci.nsIAsyncOutputStream.WAIT_CLOSURE_ONLY, 0,
- gThreadManager.mainThread);
- },
-
- /**
- * Closes input with the given status, if it hasn't already been closed;
- * otherwise a no-op.
- *
- * @param status : nsresult
- * status code use to close the source stream if necessary
- */
- _finishSource: function(status)
- {
- dumpn("*** _finishSource(" + status.toString(16) + ")");
-
- if (this._source !== null)
- {
- this._source.closeWithStatus(status);
- this._source = null;
- }
- }
-};
-
-
-/**
- * A container for utility functions used with HTTP headers.
- */
-const headerUtils =
-{
- /**
- * Normalizes fieldName (by converting it to lowercase) and ensures it is a
- * valid header field name (although not necessarily one specified in RFC
- * 2616).
- *
- * @throws NS_ERROR_INVALID_ARG
- * if fieldName does not match the field-name production in RFC 2616
- * @returns string
- * fieldName converted to lowercase if it is a valid header, for characters
- * where case conversion is possible
- */
- normalizeFieldName: function(fieldName)
- {
- if (fieldName == "")
- {
- dumpn("*** Empty fieldName");
- throw Cr.NS_ERROR_INVALID_ARG;
- }
-
- for (var i = 0, sz = fieldName.length; i < sz; i++)
- {
- if (!IS_TOKEN_ARRAY[fieldName.charCodeAt(i)])
- {
- dumpn(fieldName + " is not a valid header field name!");
- throw Cr.NS_ERROR_INVALID_ARG;
- }
- }
-
- return fieldName.toLowerCase();
- },
-
- /**
- * Ensures that fieldValue is a valid header field value (although not
- * necessarily as specified in RFC 2616 if the corresponding field name is
- * part of the HTTP protocol), normalizes the value if it is, and
- * returns the normalized value.
- *
- * @param fieldValue : string
- * a value to be normalized as an HTTP header field value
- * @throws NS_ERROR_INVALID_ARG
- * if fieldValue does not match the field-value production in RFC 2616
- * @returns string
- * fieldValue as a normalized HTTP header field value
- */
- normalizeFieldValue: function(fieldValue)
- {
- // field-value = *( field-content | LWS )
- // field-content =
- // TEXT =
- // LWS = [CRLF] 1*( SP | HT )
- //
- // quoted-string = ( <"> *(qdtext | quoted-pair ) <"> )
- // qdtext = >
- // quoted-pair = "\" CHAR
- // CHAR =
-
- // Any LWS that occurs between field-content MAY be replaced with a single
- // SP before interpreting the field value or forwarding the message
- // downstream (section 4.2); we replace 1*LWS with a single SP
- var val = fieldValue.replace(/(?:(?:\r\n)?[ \t]+)+/g, " ");
-
- // remove leading/trailing LWS (which has been converted to SP)
- 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
- // spec and not entirely as a bug
- return val;
- }
-};
-
-
-
-/**
- * Converts the given string into a string which is safe for use in an HTML
- * context.
- *
- * @param str : string
- * the string to make HTML-safe
- * @returns string
- * an HTML-safe version of str
- */
-function htmlEscape(str)
-{
- // this is naive, but it'll work
- var s = "";
- for (var i = 0; i < str.length; i++)
- s += "" + str.charCodeAt(i) + ";";
- return s;
-}
-
-
-/**
- * Constructs an object representing an HTTP version (see section 3.1).
- *
- * @param versionString
- * a string of the form "#.#", where # is an non-negative decimal integer with
- * or without leading zeros
- * @throws
- * if versionString does not specify a valid HTTP version number
- */
-function nsHttpVersion(versionString)
-{
- var matches = /^(\d+)\.(\d+)$/.exec(versionString);
- if (!matches)
- throw new Error("Not a valid HTTP version!");
-
- /** The major version number of this, as a number. */
- this.major = parseInt(matches[1], 10);
-
- /** The minor version number of this, as a number. */
- this.minor = parseInt(matches[2], 10);
-
- if (isNaN(this.major) || isNaN(this.minor) ||
- this.major < 0 || this.minor < 0)
- throw new Error("Not a valid HTTP version!");
-}
-nsHttpVersion.prototype =
-{
- /**
- * Returns the standard string representation of the HTTP version represented
- * by this (e.g., "1.1").
- */
- toString: function ()
- {
- return this.major + "." + this.minor;
- },
-
- /**
- * Returns true if this represents the same HTTP version as otherVersion,
- * false otherwise.
- *
- * @param otherVersion : nsHttpVersion
- * the version to compare against this
- */
- equals: function (otherVersion)
- {
- return this.major == otherVersion.major &&
- this.minor == otherVersion.minor;
- },
-
- /** True if this >= otherVersion, false otherwise. */
- atLeast: function(otherVersion)
- {
- return this.major > otherVersion.major ||
- (this.major == otherVersion.major &&
- this.minor >= otherVersion.minor);
- }
-};
-
-nsHttpVersion.HTTP_1_0 = new nsHttpVersion("1.0");
-nsHttpVersion.HTTP_1_1 = new nsHttpVersion("1.1");
-
-
-/**
- * An object which stores HTTP headers for a request or response.
- *
- * Note that since headers are case-insensitive, this object converts headers to
- * lowercase before storing them. This allows the getHeader and hasHeader
- * methods to work correctly for any case of a header, but it means that the
- * values returned by .enumerator may not be equal case-sensitively to the
- * values passed to setHeader when adding headers to this.
- */
-function nsHttpHeaders()
-{
- /**
- * A hash of headers, with header field names as the keys and header field
- * values as the values. Header field names are case-insensitive, but upon
- * insertion here they are converted to lowercase. Header field values are
- * normalized upon insertion to contain no leading or trailing whitespace.
- *
- * Note also that per RFC 2616, section 4.2, two headers with the same name in
- * a message may be treated as one header with the same field name and a field
- * value consisting of the separate field values joined together with a "," in
- * their original order. This hash stores multiple headers with the same name
- * in this manner.
- */
- this._headers = {};
-}
-nsHttpHeaders.prototype =
-{
- /**
- * Sets the header represented by name and value in this.
- *
- * @param name : string
- * the header name
- * @param value : string
- * the header value
- * @throws NS_ERROR_INVALID_ARG
- * if name or value is not a valid header component
- */
- setHeader: function(fieldName, fieldValue, merge)
- {
- var name = headerUtils.normalizeFieldName(fieldName);
- 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
- // ",". See also
- if (merge && name in this._headers)
- {
- if (name === "www-authenticate" ||
- name === "proxy-authenticate" ||
- name === "set-cookie")
- {
- this._headers[name].push(value);
- }
- else
- {
- this._headers[name][0] += "," + value;
- NS_ASSERT(this._headers[name].length === 1,
- "how'd a non-special header have multiple values?")
- }
- }
- else
- {
- this._headers[name] = [value];
- }
- },
-
- /**
- * Returns the value for the header specified by this.
- *
- * @throws NS_ERROR_INVALID_ARG
- * if fieldName does not constitute a valid header field name
- * @throws NS_ERROR_NOT_AVAILABLE
- * if the given header does not exist in this
- * @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
- * the three headers noted in the description of getHeaderValues
- */
- getHeader: function(fieldName)
- {
- return this.getHeaderValues(fieldName).join("\n");
- },
-
- /**
- * Returns the value for the header specified by fieldName as an array.
- *
- * @throws NS_ERROR_INVALID_ARG
- * if fieldName does not constitute a valid header field name
- * @throws NS_ERROR_NOT_AVAILABLE
- * if the given header does not exist in this
- * @returns [string]
- * an array of all the header values in this for the given
- * header name. Header values will generally be collapsed
- * into a single header by joining all header values together
- * with commas, but certain headers (Proxy-Authenticate,
- * WWW-Authenticate, and Set-Cookie) violate the HTTP spec
- * and cannot be collapsed in this manner. For these headers
- * only, the returned array may contain multiple elements if
- * that header has been added more than once.
- */
- getHeaderValues: function(fieldName)
- {
- var name = headerUtils.normalizeFieldName(fieldName);
-
- if (name in this._headers)
- return this._headers[name];
- else
- throw Cr.NS_ERROR_NOT_AVAILABLE;
- },
-
- /**
- * Returns true if a header with the given field name exists in this, false
- * otherwise.
- *
- * @param fieldName : string
- * the field name whose existence is to be determined in this
- * @throws NS_ERROR_INVALID_ARG
- * if fieldName does not constitute a valid header field name
- * @returns boolean
- * true if the header's present, false otherwise
- */
- hasHeader: function(fieldName)
- {
- var name = headerUtils.normalizeFieldName(fieldName);
- return (name in this._headers);
- },
-
- /**
- * Returns a new enumerator over the field names of the headers in this, as
- * nsISupportsStrings. The names returned will be in lowercase, regardless of
- * how they were input using setHeader (header names are case-insensitive per
- * RFC 2616).
- */
- get enumerator()
- {
- var headers = [];
- for (var i in this._headers)
- {
- var supports = new SupportsString();
- supports.data = i;
- headers.push(supports);
- }
-
- return new nsSimpleEnumerator(headers);
- }
-};
-
-
-/**
- * Constructs an nsISimpleEnumerator for the given array of items.
- *
- * @param items : Array
- * the items, which must all implement nsISupports
- */
-function nsSimpleEnumerator(items)
-{
- this._items = items;
- this._nextIndex = 0;
-}
-nsSimpleEnumerator.prototype =
-{
- hasMoreElements: function()
- {
- return this._nextIndex < this._items.length;
- },
- getNext: function()
- {
- if (!this.hasMoreElements())
- throw Cr.NS_ERROR_NOT_AVAILABLE;
-
- return this._items[this._nextIndex++];
- },
- QueryInterface: function(aIID)
- {
- if (Ci.nsISimpleEnumerator.equals(aIID) ||
- Ci.nsISupports.equals(aIID))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
-};
-
-
-/**
- * A representation of the data in an HTTP request.
- *
- * @param port : uint
- * the port on which the server receiving this request runs
- */
-function Request(port)
-{
- /** Method of this request, e.g. GET or POST. */
- this._method = "";
-
- /** Path of the requested resource; empty paths are converted to '/'. */
- this._path = "";
-
- /** Query string, if any, associated with this request (not including '?'). */
- this._queryString = "";
-
- /** Scheme of requested resource, usually http, always lowercase. */
- this._scheme = "http";
-
- /** Hostname on which the requested resource resides. */
- this._host = undefined;
-
- /** Port number over which the request was received. */
- this._port = port;
-
- var bodyPipe = new Pipe(false, false, 0, PR_UINT32_MAX, null);
-
- /** Stream from which data in this request's body may be read. */
- this._bodyInputStream = bodyPipe.inputStream;
-
- /** Stream to which data in this request's body is written. */
- this._bodyOutputStream = bodyPipe.outputStream;
-
- /**
- * The headers in this request.
- */
- this._headers = new nsHttpHeaders();
-
- /**
- * For the addition of ad-hoc properties and new functionality without having
- * to change nsIHttpRequest every time; currently lazily created, as its only
- * use is in directory listings.
- */
- this._bag = null;
-}
-Request.prototype =
-{
- // SERVER METADATA
-
- //
- // see nsIHttpRequest.scheme
- //
- get scheme()
- {
- return this._scheme;
- },
-
- //
- // see nsIHttpRequest.host
- //
- get host()
- {
- return this._host;
- },
-
- //
- // see nsIHttpRequest.port
- //
- get port()
- {
- return this._port;
- },
-
- // REQUEST LINE
-
- //
- // see nsIHttpRequest.method
- //
- get method()
- {
- return this._method;
- },
-
- //
- // see nsIHttpRequest.httpVersion
- //
- get httpVersion()
- {
- return this._httpVersion.toString();
- },
-
- //
- // see nsIHttpRequest.path
- //
- get path()
- {
- return this._path;
- },
-
- //
- // see nsIHttpRequest.queryString
- //
- get queryString()
- {
- return this._queryString;
- },
-
- // HEADERS
-
- //
- // see nsIHttpRequest.getHeader
- //
- getHeader: function(name)
- {
- return this._headers.getHeader(name);
- },
-
- //
- // see nsIHttpRequest.hasHeader
- //
- hasHeader: function(name)
- {
- return this._headers.hasHeader(name);
- },
-
- //
- // see nsIHttpRequest.headers
- //
- get headers()
- {
- return this._headers.enumerator;
- },
-
- //
- // see nsIPropertyBag.enumerator
- //
- get enumerator()
- {
- this._ensurePropertyBag();
- return this._bag.enumerator;
- },
-
- //
- // see nsIHttpRequest.headers
- //
- get bodyInputStream()
- {
- return this._bodyInputStream;
- },
-
- //
- // see nsIPropertyBag.getProperty
- //
- getProperty: function(name)
- {
- this._ensurePropertyBag();
- return this._bag.getProperty(name);
- },
-
-
- // NSISUPPORTS
-
- //
- // see nsISupports.QueryInterface
- //
- QueryInterface: function(iid)
- {
- if (iid.equals(Ci.nsIHttpRequest) || iid.equals(Ci.nsISupports))
- return this;
-
- throw Cr.NS_ERROR_NO_INTERFACE;
- },
-
-
- // PRIVATE IMPLEMENTATION
-
- /** Ensures a property bag has been created for ad-hoc behaviors. */
- _ensurePropertyBag: function()
- {
- if (!this._bag)
- this._bag = new WritablePropertyBag();
- }
-};
-
-
-// XPCOM trappings
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([nsHttpServer]);
-
-/**
- * Creates a new HTTP server listening for loopback traffic on the given port,
- * starts it, and runs the server until the server processes a shutdown request,
- * spinning an event loop so that events posted by the server's socket are
- * processed.
- *
- * This method is primarily intended for use in running this script from within
- * xpcshell and running a functional HTTP server without having to deal with
- * non-essential details.
- *
- * Note that running multiple servers using variants of this method probably
- * doesn't work, simply due to how the internal event loop is spun and stopped.
- *
- * @note
- * This method only works with Mozilla 1.9 (i.e., Firefox 3 or trunk code);
- * you should use this server as a component in Mozilla 1.8.
- * @param port
- * the port on which the server will run, or -1 if there exists no preference
- * for a specific port; note that attempting to use some values for this
- * parameter (particularly those below 1024) may cause this method to throw or
- * may result in the server being prematurely shut down
- * @param basePath
- * a local directory from which requests will be served (i.e., if this is
- * "/home/jwalden/" then a request to /index.html will load
- * /home/jwalden/index.html); if this is omitted, only the default URLs in
- * this server implementation will be functional
- */
-function server(port, basePath)
-{
- if (basePath)
- {
- var lp = Cc["@mozilla.org/file/local;1"]
- .createInstance(Ci.nsIFile);
- lp.initWithPath(basePath);
- }
-
- // if you're running this, you probably want to see debugging info
- DEBUG = true;
-
- var srv = new nsHttpServer();
- if (lp)
- srv.registerDirectory("/", lp);
- srv.registerContentType("sjs", SJS_TYPE);
- srv.identity.setPrimary("http", "localhost", port);
- srv.start(port);
-
- gThreadManager.spinEventLoopUntil(() => srv.isStopped());
-
- gThreadManager.spinEventLoopUntilEmpty();
-
- DEBUG = false;
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/json2.js b/services/sync/tps/extensions/mozmill/resource/stdlib/json2.js
deleted file mode 100644
index 281a7f71365d..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/json2.js
+++ /dev/null
@@ -1,469 +0,0 @@
-/*
- http://www.JSON.org/json2.js
- 2008-05-25
-
- Public Domain.
-
- NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
-
- See http://www.JSON.org/js.html
-
- This file creates a global JSON object containing two methods: stringify
- and parse.
-
- JSON.stringify(value, replacer, space)
- value any JavaScript value, usually an object or array.
-
- replacer an optional parameter that determines how object
- values are stringified for objects without a toJSON
- method. It can be a function or an array.
-
- space an optional parameter that specifies the indentation
- of nested structures. If it is omitted, the text will
- be packed without extra whitespace. If it is a number,
- it will specify the number of spaces to indent at each
- level. If it is a string (such as '\t' or ' '),
- it contains the characters used to indent at each level.
-
- This method produces a JSON text from a JavaScript value.
-
- When an object value is found, if the object contains a toJSON
- method, its toJSON method will be called and the result will be
- stringified. A toJSON method does not serialize: it returns the
- value represented by the name/value pair that should be serialized,
- or undefined if nothing should be serialized. The toJSON method
- will be passed the key associated with the value, and this will be
- bound to the object holding the key.
-
- For example, this would serialize Dates as ISO strings.
-
- Date.prototype.toJSON = function (key) {
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- You can provide an optional replacer method. It will be passed the
- key and value of each member, with this bound to the containing
- object. The value that is returned from your method will be
- serialized. If your method returns undefined, then the member will
- be excluded from the serialization.
-
- If the replacer parameter is an array, then it will be used to
- select the members to be serialized. It filters the results such
- that only members with keys listed in the replacer array are
- stringified.
-
- Values that do not have JSON representations, such as undefined or
- functions, will not be serialized. Such values in objects will be
- dropped; in arrays they will be replaced with null. You can use
- a replacer function to replace those with JSON values.
- JSON.stringify(undefined) returns undefined.
-
- The optional space parameter produces a stringification of the
- value that is filled with line breaks and indentation to make it
- easier to read.
-
- If the space parameter is a non-empty string, then that string will
- be used for indentation. If the space parameter is a number, then
- the indentation will be that many spaces.
-
- Example:
-
- text = JSON.stringify(['e', {pluribus: 'unum'}]);
- // text is '["e",{"pluribus":"unum"}]'
-
-
- text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
- // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
-
- text = JSON.stringify([new Date()], function (key, value) {
- return this[key] instanceof Date ?
- 'Date(' + this[key] + ')' : value;
- });
- // text is '["Date(---current time---)"]'
-
-
- JSON.parse(text, reviver)
- This method parses a JSON text to produce an object or array.
- It can throw a SyntaxError exception.
-
- The optional reviver parameter is a function that can filter and
- transform the results. It receives each of the keys and values,
- and its return value is used instead of the original value.
- If it returns what it received, then the structure is not modified.
- If it returns undefined then the member is deleted.
-
- Example:
-
- // Parse the text. Values that look like ISO date strings will
- // be converted to Date objects.
-
- myData = JSON.parse(text, function (key, value) {
- var a;
- if (typeof value === 'string') {
- a =
-/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
- if (a) {
- return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
- +a[5], +a[6]));
- }
- }
- return value;
- });
-
- myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
- var d;
- if (typeof value === 'string' &&
- value.slice(0, 5) === 'Date(' &&
- value.slice(-1) === ')') {
- d = new Date(value.slice(5, -1));
- if (d) {
- return d;
- }
- }
- return value;
- });
-
-
- This is a reference implementation. You are free to copy, modify, or
- redistribute.
-
- This code should be minified before deployment.
- See http://javascript.crockford.com/jsmin.html
-
- USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
- NOT CONTROL.
-*/
-
-/*jslint evil: true */
-
-/*global JSON */
-
-/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", call,
- charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, getUTCMinutes,
- getUTCMonth, getUTCSeconds, hasOwnProperty, join, lastIndex, length,
- parse, propertyIsEnumerable, prototype, push, replace, slice, stringify,
- test, toJSON, toString
-*/
-
-var EXPORTED_SYMBOLS = ["JSON"];
-
-// Create a JSON object only if one does not already exist. We create the
-// object in a closure to avoid creating global variables.
-
- JSON = function () {
-
- function f(n) {
- // Format integers to have at least two digits.
- return n < 10 ? '0' + n : n;
- }
-
- Date.prototype.toJSON = function (key) {
-
- return this.getUTCFullYear() + '-' +
- f(this.getUTCMonth() + 1) + '-' +
- f(this.getUTCDate()) + 'T' +
- f(this.getUTCHours()) + ':' +
- f(this.getUTCMinutes()) + ':' +
- f(this.getUTCSeconds()) + 'Z';
- };
-
- var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- escapeable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
- gap,
- indent,
- meta = { // table of character substitutions
- '\b': '\\b',
- '\t': '\\t',
- '\n': '\\n',
- '\f': '\\f',
- '\r': '\\r',
- '"' : '\\"',
- '\\': '\\\\'
- },
- rep;
-
-
- function quote(string) {
-
-// If the string contains no control characters, no quote characters, and no
-// backslash characters, then we can safely slap some quotes around it.
-// Otherwise we must also replace the offending characters with safe escape
-// sequences.
-
- escapeable.lastIndex = 0;
- return escapeable.test(string) ?
- '"' + string.replace(escapeable, function (a) {
- var c = meta[a];
- if (typeof c === 'string') {
- return c;
- }
- return '\\u' + ('0000' +
- (+(a.charCodeAt(0))).toString(16)).slice(-4);
- }) + '"' :
- '"' + string + '"';
- }
-
-
- function str(key, holder) {
-
-// Produce a string from holder[key].
-
- var i, // The loop counter.
- k, // The member key.
- v, // The member value.
- length,
- mind = gap,
- partial,
- value = holder[key];
-
-// If the value has a toJSON method, call it to obtain a replacement value.
-
- if (value && typeof value === 'object' &&
- typeof value.toJSON === 'function') {
- value = value.toJSON(key);
- }
-
-// If we were called with a replacer function, then call the replacer to
-// obtain a replacement value.
-
- if (typeof rep === 'function') {
- value = rep.call(holder, key, value);
- }
-
-// What happens next depends on the value's type.
-
- switch (typeof value) {
- case 'string':
- return quote(value);
-
- case 'number':
-
-// JSON numbers must be finite. Encode non-finite numbers as null.
-
- return isFinite(value) ? String(value) : 'null';
-
- case 'boolean':
- case 'null':
-
-// If the value is a boolean or null, convert it to a string. Note:
-// typeof null does not produce 'null'. The case is included here in
-// the remote chance that this gets fixed someday.
-
- return String(value);
-
-// If the type is 'object', we might be dealing with an object or an array or
-// null.
-
- case 'object':
-
-// Due to a specification blunder in ECMAScript, typeof null is 'object',
-// so watch out for that case.
-
- if (!value) {
- return 'null';
- }
-
-// Make an array to hold the partial results of stringifying this object value.
-
- gap += indent;
- partial = [];
-
-// If the object has a dontEnum length property, we'll treat it as an array.
-
- if (typeof value.length === 'number' &&
- !(value.propertyIsEnumerable('length'))) {
-
-// The object is an array. Stringify every element. Use null as a placeholder
-// for non-JSON values.
-
- length = value.length;
- for (i = 0; i < length; i += 1) {
- partial[i] = str(i, value) || 'null';
- }
-
-// Join all of the elements together, separated with commas, and wrap them in
-// brackets.
-
- v = partial.length === 0 ? '[]' :
- gap ? '[\n' + gap +
- partial.join(',\n' + gap) + '\n' +
- mind + ']' :
- '[' + partial.join(',') + ']';
- gap = mind;
- return v;
- }
-
-// If the replacer is an array, use it to select the members to be stringified.
-
- if (rep && typeof rep === 'object') {
- length = rep.length;
- for (i = 0; i < length; i += 1) {
- k = rep[i];
- if (typeof k === 'string') {
- v = str(k, value, rep);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- } else {
-
-// Otherwise, iterate through all of the keys in the object.
-
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = str(k, value, rep);
- if (v) {
- partial.push(quote(k) + (gap ? ': ' : ':') + v);
- }
- }
- }
- }
-
-// Join all of the member texts together, separated with commas,
-// and wrap them in braces.
-
- v = partial.length === 0 ? '{}' :
- gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
- mind + '}' : '{' + partial.join(',') + '}';
- gap = mind;
- return v;
- }
- }
-
-// Return the JSON object containing the stringify and parse methods.
-
- return {
- stringify: function (value, replacer, space) {
-
-// The stringify method takes a value and an optional replacer, and an optional
-// space parameter, and returns a JSON text. The replacer can be a function
-// that can replace values, or an array of strings that will select the keys.
-// A default replacer method can be provided. Use of the space parameter can
-// produce text that is more easily readable.
-
- var i;
- gap = '';
- indent = '';
-
-// If the space parameter is a number, make an indent string containing that
-// many spaces.
-
- if (typeof space === 'number') {
- for (i = 0; i < space; i += 1) {
- indent += ' ';
- }
-
-// If the space parameter is a string, it will be used as the indent string.
-
- } else if (typeof space === 'string') {
- indent = space;
- }
-
-// If there is a replacer, it must be a function or an array.
-// Otherwise, throw an error.
-
- rep = replacer;
- if (replacer && typeof replacer !== 'function' &&
- (typeof replacer !== 'object' ||
- typeof replacer.length !== 'number')) {
- throw new Error('JSON.stringify');
- }
-
-// Make a fake root object containing our value under the key of ''.
-// Return the result of stringifying the value.
-
- return str('', {'': value});
- },
-
-
- parse: function (text, reviver) {
-
-// The parse method takes a text and an optional reviver function, and returns
-// a JavaScript value if the text is a valid JSON text.
-
- var j;
-
- function walk(holder, key) {
-
-// The walk method is used to recursively walk the resulting structure so
-// that modifications can be made.
-
- var k, v, value = holder[key];
- if (value && typeof value === 'object') {
- for (k in value) {
- if (Object.hasOwnProperty.call(value, k)) {
- v = walk(value, k);
- if (v !== undefined) {
- value[k] = v;
- } else {
- delete value[k];
- }
- }
- }
- }
- return reviver.call(holder, key, value);
- }
-
-
-// Parsing happens in four stages. In the first stage, we replace certain
-// Unicode characters with escape sequences. JavaScript handles many characters
-// incorrectly, either silently deleting them, or treating them as line endings.
-
- cx.lastIndex = 0;
- if (cx.test(text)) {
- text = text.replace(cx, function (a) {
- return '\\u' + ('0000' +
- (+(a.charCodeAt(0))).toString(16)).slice(-4);
- });
- }
-
-// In the second stage, we run the text against regular expressions that look
-// for non-JSON patterns. We are especially concerned with '()' and 'new'
-// because they can cause invocation, and '=' because it can cause mutation.
-// But just to be safe, we want to reject all unexpected forms.
-
-// We split the second stage into 4 regexp operations in order to work around
-// crippling inefficiencies in IE's and Safari's regexp engines. First we
-// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
-// replace all simple value tokens with ']' characters. Third, we delete all
-// open brackets that follow a colon or comma or that begin the text. Finally,
-// we look to see that the remaining characters are only whitespace or ']' or
-// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
-
- if (/^[\],:{}\s]*$/.
-test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
-replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
-replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
-
-// In the third stage we use the eval function to compile the text into a
-// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
-// in JavaScript: it can begin a block or an object literal. We wrap the text
-// in parens to eliminate the ambiguity.
-
- j = eval('(' + text + ')');
-
-// In the optional fourth stage, we recursively walk the new structure, passing
-// each name/value pair to a reviver function for possible transformation.
-
- return typeof reviver === 'function' ?
- walk({'': j}, '') : j;
- }
-
-// If the text is not JSON parseable, then a SyntaxError is thrown.
-
- throw new SyntaxError('JSON.parse');
- }
- };
- }();
-
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js b/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
deleted file mode 100644
index 35d57504dfd1..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/objects.js
+++ /dev/null
@@ -1,54 +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 = ['getLength', ];//'compare'];
-
-var getLength = function (obj) {
- var len = 0;
- for (let i in obj) {
- len++;
- }
-
- return len;
-}
-
-// var logging = {}; Components.utils.import('resource://mozmill/stdlib/logging.js', logging);
-
-// var objectsLogger = logging.getLogger('objectsLogger');
-
-// var compare = function (obj1, obj2, depth, recursion) {
-// if (depth == undefined) {
-// var depth = 4;
-// }
-// if (recursion == undefined) {
-// var recursion = 0;
-// }
-//
-// if (recursion > depth) {
-// return true;
-// }
-//
-// if (typeof(obj1) != typeof(obj2)) {
-// return false;
-// }
-//
-// if (typeof(obj1) == "object" && typeof(obj2) == "object") {
-// if ([x for (x in obj1)].length != [x for (x in obj2)].length) {
-// return false;
-// }
-// for (i in obj1) {
-// recursion++;
-// var result = compare(obj1[i], obj2[i], depth, recursion);
-// objectsLogger.info(i+' in recursion '+result);
-// if (result == false) {
-// return false;
-// }
-// }
-// } else {
-// if (obj1 != obj2) {
-// return false;
-// }
-// }
-// return true;
-// }
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/os.js b/services/sync/tps/extensions/mozmill/resource/stdlib/os.js
deleted file mode 100644
index ab3e016a7de2..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/os.js
+++ /dev/null
@@ -1,57 +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 = ['listDirectory', 'getFileForPath', 'abspath', 'getPlatform'];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var 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()) {
- var entry = entries.getNext();
- entry.QueryInterface(Ci.nsIFile);
- array.push(entry);
- }
-
- return array;
-}
-
-function getFileForPath(path) {
- var file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.initWithPath(path);
- return file;
-}
-
-function abspath(rel, file) {
- var relSplit = rel.split('/');
-
- if (relSplit[0] == '..' && !file.isDirectory()) {
- file = file.parent;
- }
-
- for (var p of relSplit) {
- if (p == '..') {
- file = file.parent;
- } else if (p == '.') {
- if (!file.isDirectory()) {
- file = file.parent;
- }
- } else {
- file.append(p);
- }
- }
-
- return file.path;
-}
-
-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
deleted file mode 100644
index ce030bedcb46..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js
+++ /dev/null
@@ -1,370 +0,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;
- const Ci = Components.interfaces;
- const Cu = Components.utils;
- const Cr = Components.results;
-
- Cu.import("resource://gre/modules/NetUtil.jsm");
-
- var exports = {};
-
- var ios = Cc['@mozilla.org/network/io-service;1']
- .getService(Ci.nsIIOService);
-
- var systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
- .createInstance(Ci.nsIPrincipal);
-
- function resolvePrincipal(principal, defaultPrincipal) {
- if (principal === undefined)
- return defaultPrincipal;
- if (principal == "system")
- return systemPrincipal;
- return principal;
- }
-
- // The base URI to we use when we're given relative URLs, if any.
- var baseURI = null;
- if (global.window)
- baseURI = ios.newURI(global.location.href);
- exports.baseURI = baseURI;
-
- // The "parent" chrome URI to use if we're loading code that
- // needs chrome privileges but may not have a filename that
- // matches any of SpiderMonkey's defined system filename prefixes.
- // The latter is needed so that wrappers can be automatically
- // made for the code. For more information on this, see
- // bug 418356:
- //
- // https://bugzilla.mozilla.org/show_bug.cgi?id=418356
- var parentChromeURIString;
- if (baseURI)
- // We're being loaded from a chrome-privileged document, so
- // use its URL as the parent string.
- parentChromeURIString = baseURI.spec;
- else
- // We're being loaded from a chrome-privileged JS module or
- // SecurableModule, so use its filename (which may itself
- // contain a reference to a parent).
- parentChromeURIString = Components.stack.filename;
-
- function maybeParentifyFilename(filename) {
- var doParentifyFilename = true;
- try {
- // TODO: Ideally we should just make
- // nsIChromeRegistry.wrappersEnabled() available from script
- // and use it here. Until that's in the platform, though,
- // we'll play it safe and parentify the filename unless
- // we're absolutely certain things will be ok if we don't.
- var filenameURI = ios.newURI(filename,
- null,
- baseURI);
- if (filenameURI.scheme == 'chrome' &&
- filenameURI.pathQueryRef.indexOf('/content/') == 0)
- // Content packages will always have wrappers made for them;
- // if automatic wrappers have been disabled for the
- // chrome package via a chrome manifest flag, then
- // this still works too, to the extent that the
- // content package is insecure anyways.
- doParentifyFilename = false;
- } catch (e) {}
- if (doParentifyFilename)
- return parentChromeURIString + " -> " + filename;
- return filename;
- }
-
- function getRootDir(urlStr) {
- // TODO: This feels hacky, and like there will be edge cases.
- return urlStr.slice(0, urlStr.lastIndexOf("/") + 1);
- }
-
- exports.SandboxFactory = function SandboxFactory(defaultPrincipal) {
- // Unless specified otherwise, use a principal with limited
- // privileges.
- this._defaultPrincipal = resolvePrincipal(defaultPrincipal,
- "http://www.mozilla.org");
- },
-
- exports.SandboxFactory.prototype = {
- createSandbox: function createSandbox(options) {
- var principal = resolvePrincipal(options.principal,
- this._defaultPrincipal);
-
- return {
- _sandbox: new Cu.Sandbox(principal),
- _principal: principal,
- get globalScope() {
- return this._sandbox;
- },
- defineProperty: function defineProperty(name, value) {
- this._sandbox[name] = value;
- },
- getProperty: function getProperty(name) {
- return this._sandbox[name];
- },
- evaluate: function evaluate(options) {
- if (typeof(options) == 'string')
- options = {contents: options};
- options = {__proto__: options};
- if (typeof(options.contents) != 'string')
- throw new Error('Expected string for options.contents');
- if (options.lineNo === undefined)
- options.lineNo = 1;
- if (options.jsVersion === undefined)
- options.jsVersion = "1.8";
- if (typeof(options.filename) != 'string')
- options.filename = '';
-
- if (this._principal == systemPrincipal)
- options.filename = maybeParentifyFilename(options.filename);
-
- return Cu.evalInSandbox(options.contents,
- this._sandbox,
- options.jsVersion,
- options.filename,
- options.lineNo);
- }
- };
- }
- };
-
- exports.Loader = function Loader(options) {
- options = {__proto__: options};
- if (options.fs === undefined) {
- var rootPaths = options.rootPath || options.rootPaths;
- if (rootPaths) {
- if (rootPaths.constructor.name != "Array")
- rootPaths = [rootPaths];
- var fses = rootPaths.map(path => new exports.LocalFileSystem(path));
- options.fs = new exports.CompositeFileSystem(fses);
- } else
- options.fs = new exports.LocalFileSystem();
- }
- if (options.sandboxFactory === undefined)
- options.sandboxFactory = new exports.SandboxFactory(
- options.defaultPrincipal
- );
- if (options.modules === undefined)
- options.modules = {};
- if (options.globals === undefined)
- options.globals = {};
-
- this.fs = options.fs;
- this.sandboxFactory = options.sandboxFactory;
- this.sandboxes = {};
- this.modules = options.modules;
- this.globals = options.globals;
- };
-
- exports.Loader.prototype = {
- _makeRequire: function _makeRequire(rootDir) {
- var self = this;
- return function require(module) {
- if (module == "chrome") {
- var chrome = { Cc: Components.classes,
- Ci: Components.interfaces,
- Cu: Components.utils,
- Cr: Components.results,
- Cm: Components.manager,
- components: Components
- };
- return chrome;
- }
- var path = self.fs.resolveModule(rootDir, module);
- if (!path)
- throw new Error('Module "' + module + '" not found');
- if (!(path in self.modules)) {
- var options = self.fs.getFile(path);
- if (options.filename === undefined)
- options.filename = path;
-
- var exports = {};
- var sandbox = self.sandboxFactory.createSandbox(options);
- self.sandboxes[path] = sandbox;
- for (name in self.globals)
- sandbox.defineProperty(name, self.globals[name]);
- sandbox.defineProperty('require', self._makeRequire(path));
- sandbox.evaluate("var exports = {};");
- let ES5 = self.modules.es5;
- if (ES5) {
- let { Object, Array, Function } = sandbox.globalScope;
- ES5.init(Object, Array, Function);
- }
- self.modules[path] = sandbox.getProperty("exports");
- sandbox.evaluate(options);
- }
- return self.modules[path];
- };
- },
-
- // This is only really used by unit tests and other
- // development-related facilities, allowing access to symbols
- // defined in the global scope of a module.
- findSandboxForModule: function findSandboxForModule(module) {
- var path = this.fs.resolveModule(null, module);
- if (!path)
- throw new Error('Module "' + module + '" not found');
- if (!(path in this.sandboxes))
- this.require(module);
- if (!(path in this.sandboxes))
- throw new Error('Internal error: path not in sandboxes: ' +
- path);
- return this.sandboxes[path];
- },
-
- require: function require(module) {
- return (this._makeRequire(null))(module);
- },
-
- runScript: function runScript(options, extraOutput) {
- if (typeof(options) == 'string')
- options = {contents: options};
- options = {__proto__: options};
- var sandbox = this.sandboxFactory.createSandbox(options);
- if (extraOutput)
- extraOutput.sandbox = sandbox;
- for (name in this.globals)
- sandbox.defineProperty(name, this.globals[name]);
- sandbox.defineProperty('require', this._makeRequire(null));
- return sandbox.evaluate(options);
- }
- };
-
- exports.CompositeFileSystem = function CompositeFileSystem(fses) {
- this.fses = fses;
- this._pathMap = {};
- };
-
- exports.CompositeFileSystem.prototype = {
- resolveModule: function resolveModule(base, path) {
- for (var i = 0; i < this.fses.length; i++) {
- var fs = this.fses[i];
- var absPath = fs.resolveModule(base, path);
- if (absPath) {
- this._pathMap[absPath] = fs;
- return absPath;
- }
- }
- return null;
- },
- getFile: function getFile(path) {
- return this._pathMap[path].getFile(path);
- }
- };
-
- exports.LocalFileSystem = function LocalFileSystem(root) {
- if (root === undefined) {
- if (!baseURI)
- throw new Error("Need a root path for module filesystem");
- root = baseURI;
- }
- if (typeof(root) == 'string')
- root = ios.newURI(root, null, baseURI);
- if (root instanceof Ci.nsIFile)
- root = ios.newFileURI(root);
- if (!(root instanceof Ci.nsIURI))
- throw new Error('Expected nsIFile, nsIURI, or string for root');
-
- this.root = root.spec;
- this._rootURI = root;
- this._rootURIDir = getRootDir(root.spec);
- };
-
- exports.LocalFileSystem.prototype = {
- resolveModule: function resolveModule(base, path) {
- path = path + ".js";
-
- var baseURI;
- if (!base)
- baseURI = this._rootURI;
- else
- baseURI = ios.newURI(base);
- var newURI = ios.newURI(path, null, baseURI);
- var channel = NetUtil.newChannel({
- uri: newURI,
- loadUsingSystemPrincipal: true
- });
- try {
- channel.open2().close();
- } catch (e) {
- if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) {
- throw e;
- }
- return null;
- }
- return newURI.spec;
- },
- getFile: function getFile(path) {
- var channel = NetUtil.newChannel({
- uri: path,
- loadUsingSystemPrincipal: true
- });
- var iStream = channel.open2();
- var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"].
- createInstance(Ci.nsIConverterInputStream);
- var bufLen = 0x8000;
- ciStream.init(iStream, "UTF-8", bufLen,
- Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
- var chunk = {};
- var data = "";
- while (ciStream.readString(bufLen, chunk) > 0)
- data += chunk.value;
- ciStream.close();
- iStream.close();
- return {contents: data};
- }
- };
-
- if (global.window) {
- // We're being loaded in a chrome window, or a web page with
- // UniversalXPConnect privileges.
- global.SecurableModule = exports;
- } else if (global.exports) {
- // We're being loaded in a SecurableModule.
- for (name in exports) {
- global.exports[name] = exports[name];
- }
- } else {
- // We're being loaded in a JS module.
- global.EXPORTED_SYMBOLS = [];
- for (name in exports) {
- global.EXPORTED_SYMBOLS.push(name);
- global[name] = exports[name];
- }
- }
- })(this);
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js b/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
deleted file mode 100644
index 24a93d958110..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/strings.js
+++ /dev/null
@@ -1,17 +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 = ['trim', 'vslice'];
-
-var arrays = {}; Components.utils.import('resource://mozmill/stdlib/arrays.js', arrays);
-
-var trim = function (str) {
- return (str.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""));
-}
-
-var vslice = function (str, svalue, evalue) {
- var sindex = arrays.indexOf(str, svalue);
- var eindex = arrays.rindexOf(str, evalue);
- return str.slice(sindex + 1, eindex);
-}
diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
deleted file mode 100644
index 16432944dc92..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js
+++ /dev/null
@@ -1,451 +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 = ["applicationName", "assert", "Copy", "getBrowserObject",
- "getChromeWindow", "getWindows", "getWindowByTitle",
- "getWindowByType", "getWindowId", "getMethodInWindows",
- "getPreference", "saveDataURL", "setPreference",
- "sleep", "startTimer", "stopTimer", "takeScreenshot",
- "unwrapNode", "waitFor"
- ];
-
-var Cc = Components.classes;
-var Ci = Components.interfaces;
-var 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'
-}
-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) {
- 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 (var w of 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 (var w of 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);
- Services.tm.spinEventLoopUntil(() => timeup);
-
- 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.ownerGlobal;
- // 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.ownerGlobal;
- 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.nsIFile);
- 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");
- 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/mozmill/resource/stdlib/withs.js b/services/sync/tps/extensions/mozmill/resource/stdlib/withs.js
deleted file mode 100644
index baa3d18d6fd0..000000000000
--- a/services/sync/tps/extensions/mozmill/resource/stdlib/withs.js
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- Copyright (c) 2006 Lawrence Oluyede
-
- 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 OR COPYRIGHT HOLDERS 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.
-*/
-
-/*
- startsWith(str, prefix[, start[, end]]) -> bool
-
- Return true if str ends with the specified prefix, false otherwise.
- With optional start, test str beginning at that position.
- With optional end, stop comparing str at that position.
- prefix can also be an array of strings to try.
-*/
-
-var EXPORTED_SYMBOLS = ['startsWith', 'endsWith'];
-
-function startsWith(str, prefix, start, end) {
- if (arguments.length < 2) {
- throw new TypeError('startsWith() requires at least 2 arguments');
- }
-
- // check if start and end are null/undefined or a 'number'
- if ((start == null) || (isNaN(new Number(start)))) {
- start = 0;
- }
- if ((end == null) || (isNaN(new Number(end)))) {
- end = Number.MAX_VALUE;
- }
-
- // if it's an array
- if (typeof prefix == "object") {
- for (var i = 0, j = prefix.length; i < j; i++) {
- var res = _stringTailMatch(str, prefix[i], start, end, true);
- if (res) {
- return true;
- }
- }
- return false;
- }
-
- return _stringTailMatch(str, prefix, start, end, true);
-}
-
-/*
- endsWith(str, suffix[, start[, end]]) -> bool
-
- Return true if str ends with the specified suffix, false otherwise.
- With optional start, test str beginning at that position.
- With optional end, stop comparing str at that position.
- suffix can also be an array of strings to try.
-*/
-function endsWith(str, suffix, start, end) {
- if (arguments.length < 2) {
- throw new TypeError('endsWith() requires at least 2 arguments');
- }
-
- // check if start and end are null/undefined or a 'number'
- if ((start == null) || (isNaN(new Number(start)))) {
- start = 0;
- }
- if ((end == null) || (isNaN(new Number(end)))) {
- end = Number.MAX_VALUE;
- }
-
- // if it's an array
- if (typeof suffix == "object") {
- for (var i = 0, j = suffix.length; i < j; i++) {
- var res = _stringTailMatch(str, suffix[i], start, end, false);
- if (res) {
- return true;
- }
- }
- return false;
- }
-
- return _stringTailMatch(str, suffix, start, end, false);
-}
-
-/*
- Matches the end (direction == false) or start (direction == true) of str
- against substr, using the start and end arguments. Returns false
- if not found and true if found.
-*/
-function _stringTailMatch(str, substr, start, end, fromStart) {
- var len = str.length;
- var slen = substr.length;
-
- var indices = _adjustIndices(start, end, len);
- start = indices[0]; end = indices[1]; len = indices[2];
-
- if (fromStart) {
- if (start + slen > len) {
- return false;
- }
- } else {
- if (end - start < slen || start > len) {
- return false;
- }
- if (end - slen > start) {
- start = end - slen;
- }
- }
-
- if (end - start >= slen) {
- return str.substr(start, slen) == substr;
- }
- return false;
-}
-
-function _adjustIndices(start, end, len)
-{
- if (end > len) {
- end = len;
- } else if (end < 0) {
- end += len;
- }
-
- if (end < 0) {
- end = 0;
- }
- if (start < 0) {
- start += len;
- }
- if (start < 0) {
- start = 0;
- }
-
- return [start, end, len];
-}
diff --git a/services/sync/tps/extensions/tps/resource/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm
index 4ba842341e15..037b1e0c223b 100644
--- a/services/sync/tps/extensions/tps/resource/tps.jsm
+++ b/services/sync/tps/extensions/tps/resource/tps.jsm
@@ -51,9 +51,6 @@ var hh = Cc["@mozilla.org/network/protocol;1?name=http"]
var prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefBranch);
-var mozmillInit = {};
-Cu.import("resource://mozmill/driver/mozmill.js", mozmillInit);
-
XPCOMUtils.defineLazyGetter(this, "fileProtocolHandler", () => {
let fileHandler = Services.io.getProtocolHandler("file");
return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler);
@@ -574,23 +571,6 @@ var TPS = {
}
},
- MozmillEndTestListener: function TPS__MozmillEndTestListener(obj) {
- Logger.logInfo("mozmill endTest: " + JSON.stringify(obj));
- if (obj.failed > 0) {
- this.DumpError("mozmill test failed, name: " + obj.name + ", reason: " + JSON.stringify(obj.fails));
- } else if ("skipped" in obj && obj.skipped) {
- this.DumpError("mozmill test failed, name: " + obj.name + ", reason: " + obj.skipped_reason);
- } else {
- CommonUtils.namedTimer(function() {
- this.FinishAsyncOperation();
- }, 2000, this, "postmozmilltest");
- }
- },
-
- MozmillSetTestListener: function TPS__MozmillSetTestListener(obj) {
- Logger.logInfo("mozmill setTest: " + obj.name);
- },
-
async Cleanup() {
try {
await this.WipeServer();
@@ -1039,25 +1019,6 @@ var TPS = {
this._enabledEngines = names;
},
- RunMozmillTest: function TPS__RunMozmillTest(testfile) {
- var mozmillfile = Cc["@mozilla.org/file/local;1"]
- .createInstance(Ci.nsIFile);
- if (hh.oscpu.toLowerCase().indexOf("windows") > -1) {
- let re = /\/(\w)\/(.*)/;
- this.config.testdir = this.config.testdir.replace(re, "$1://$2").replace(/\//g, "\\");
- }
- mozmillfile.initWithPath(this.config.testdir);
- mozmillfile.appendRelativePath(testfile);
- Logger.logInfo("Running mozmill test " + mozmillfile.path);
-
- var frame = {};
- Cu.import("resource://mozmill/modules/frame.js", frame);
- frame.events.addListener("setTest", this.MozmillSetTestListener.bind(this));
- frame.events.addListener("endTest", this.MozmillEndTestListener.bind(this));
- this.StartAsyncOperation();
- frame.runTestFile(mozmillfile.path, null);
- },
-
/**
* Returns a promise that resolves when a specific observer notification is
* resolved. This is similar to the various waitFor* functions, although is
diff --git a/testing/tps/tps/testrunner.py b/testing/tps/tps/testrunner.py
index 407fd8132960..fc0e07431db1 100644
--- a/testing/tps/tps/testrunner.py
+++ b/testing/tps/tps/testrunner.py
@@ -446,7 +446,6 @@ class TPSTestRunner(object):
# build our tps.xpi extension
self.extensions = []
self.extensions.append(os.path.join(self.extensionDir, 'tps'))
- self.extensions.append(os.path.join(self.extensionDir, "mozmill"))
# build the test list
try:
diff --git a/tools/lint/eslint/modules.json b/tools/lint/eslint/modules.json
index 177075fd6405..81c698fb150d 100644
--- a/tools/lint/eslint/modules.json
+++ b/tools/lint/eslint/modules.json
@@ -10,8 +10,6 @@
"AlertsHelper.jsm": [],
"AppData.jsm": ["makeFakeAppDir"],
"AppInfo.jsm": ["newAppInfo", "getAppInfo", "updateAppInfo"],
- "arrays.js": ["inArray", "getSet", "indexOf", "remove", "rindexOf", "compare"],
- "assertions.js": ["Assert", "Expect"],
"async.js": ["Async"],
"AsyncSpellCheckTestHelper.jsm": ["onSpellCheck"],
"AutoMigrate.jsm": ["AutoMigrate"],
@@ -39,7 +37,6 @@
"ContentCrashHandlers.jsm": ["TabCrashHandler", "PluginCrashReporter", "UnsubmittedCrashHandler"],
"ContentObservers.js": [],
"ContentPrefUtils.jsm": ["ContentPref", "cbHandleResult", "cbHandleError", "cbHandleCompletion", "safeCallback", "_methodsCallableFromChild"],
- "controller.js": ["MozMillController", "globalEventRegistry", "sleep", "windowMap"],
"cookies.js": ["Cookies"],
"CoverageUtils.jsm": ["CoverageCollector"],
"CrashManagerTest.jsm": ["configureLogging", "getManager", "sleep", "TestingCrashManager"],
@@ -51,17 +48,13 @@
"distribution.js": ["DistributionCustomizer"],
"DNSTypes.jsm": ["DNS_QUERY_RESPONSE_CODES", "DNS_AUTHORITATIVE_ANSWER_CODES", "DNS_CLASS_CODES", "DNS_RECORD_TYPES"],
"doctor.js": ["Doctor"],
- "dom.js": ["getAttributes"],
"DOMRequestHelper.jsm": ["DOMRequestIpcHelper"],
"DownloadCore.jsm": ["Download", "DownloadSource", "DownloadTarget", "DownloadError", "DownloadSaver", "DownloadCopySaver", "DownloadLegacySaver", "DownloadPDFSaver"],
"DownloadList.jsm": ["DownloadList", "DownloadCombinedList", "DownloadSummary"],
- "elementslib.js": ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib"],
"engines.js": ["EngineManager", "Engine", "SyncEngine", "Tracker", "Store", "Changeset"],
"enginesync.js": ["EngineSynchronizer"],
- "errors.js": ["BaseError", "ApplicationQuitError", "AssertionError", "TimeoutError"],
"evaluate.js": ["evaluate", "sandbox", "Sandboxes"],
"event-emitter.js": ["EventEmitter"],
- "EventUtils.js": ["disableNonTestMouseEvents", "sendMouseEvent", "sendChar", "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", "synthesizeWheel", "synthesizeKey", "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", "synthesizeText", "synthesizeComposition", "synthesizeQuerySelectedText"],
"Extension.jsm": ["Extension", "ExtensionData"],
"ExtensionAPI.jsm": ["ExtensionAPI", "ExtensionAPIs"],
"ExtensionsUI.jsm": ["ExtensionsUI"],
@@ -79,7 +72,6 @@
"forms.jsm": ["FormData"],
"FormAutofillHeuristics.jsm": ["FormAutofillHeuristics", "LabelUtils"],
"FormAutofillSync.jsm": ["AddressesEngine", "CreditCardsEngine"],
- "frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"],
"FrameScriptManager.jsm": ["getNewLoaderID"],
"fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
"fxaccounts.jsm": ["Authentication"],
@@ -135,18 +127,13 @@
"Messaging.jsm": ["sendMessageToJava", "Messaging", "EventDispatcher"],
"microformat-shiv.js": ["Microformats"],
"MigrationUtils.jsm": ["MigrationUtils", "MigratorPrototype"],
- "mozelement.js": ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup", "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList", "MozMillTextBox", "subclasses"],
- "mozmill.js": ["controller", "utils", "elementslib", "os", "getBrowserController", "newBrowserController", "getAddonsController", "getPreferencesController", "newMail3PaneController", "getMail3PaneController", "wm", "platform", "getAddrbkController", "getMsgComposeController", "getDownloadsController", "Application", "findElement", "getPlacesController", "isMac", "isLinux", "isWindows", "firePythonCallback", "getAddons"],
- "msgbroker.js": ["addListener", "addObject", "removeListener", "sendMessage", "log", "pass", "fail"],
"MulticastDNSAndroid.jsm": ["MulticastDNS"],
"NativeMessaging.jsm": ["NativeApp"],
"NotificationDB.jsm": [],
"nsFormAutoCompleteResult.jsm": ["FormAutoCompleteResult"],
- "objects.js": ["getLength"],
"observers.js": ["Observers"],
"offlineAppCache.jsm": ["OfflineAppCacheHelper"],
"OrientationChangeHandler.jsm": [],
- "os.js": ["listDirectory", "getFileForPath", "abspath", "getPlatform"],
"osfile.jsm": ["OS", "require"],
"osfile_async_front.jsm": ["OS"],
"osfile_native.jsm": ["read"],
@@ -195,11 +182,9 @@
"SignInToWebsite.jsm": ["SignInToWebsiteController"],
"Social.jsm": ["Social", "OpenGraphBuilder", "DynamicResizeWatcher", "sizeSocialPanelToContent"],
"SpecialPowersObserver.jsm": ["SpecialPowersObserver", "SpecialPowersObserverFactory"],
- "stack.js": ["findCallerFrame"],
"StateMachineHelper.jsm": ["State", "CommandType"],
"status.js": ["Status"],
"storageserver.js": ["ServerBSO", "StorageServerCallback", "StorageServerCollection", "StorageServer", "storageServerForUsers"],
- "strings.js": ["trim", "vslice"],
"StructuredLog.jsm": ["StructuredLogger", "StructuredFormatter"],
"StyleEditorUtil.jsm": ["getString", "assert", "log", "text", "wire", "showFilePicker"],
"subprocess_common.jsm": ["BaseProcess", "PromiseWorker", "SubprocessConstants"],
@@ -222,7 +207,7 @@
"UpdateTelemetry.jsm": ["AUSTLMY"],
"UpdateTopLevelContentWindowIDHelper.jsm": ["trackBrowserWindow"],
"util.js": ["getChromeWindow", "Utils", "Svc"],
- "utils.js": ["applicationName", "assert", "Copy", "getBrowserObject", "getChromeWindow", "getWindows", "getWindowByTitle", "getWindowByType", "getWindowId", "getMethodInWindows", "getPreference", "saveDataURL", "setPreference", "sleep", "startTimer", "stopTimer", "takeScreenshot", "unwrapNode", "waitFor", "btoa", "encryptPayload", "makeIdentityConfig", "makeFxAccountsInternalMock", "configureFxAccountIdentity", "configureIdentity", "SyncTestingInfrastructure", "waitForZeroTimer", "Promise", "MockFxaStorageManager", "AccountState", "sumHistogram", "CommonUtils", "CryptoUtils", "TestingUtils", "promiseZeroTimer", "promiseNamedTimer", "getLoginTelemetryScalar"],
+ "utils.js": ["btoa", "encryptPayload", "makeIdentityConfig", "makeFxAccountsInternalMock", "configureFxAccountIdentity", "configureIdentity", "SyncTestingInfrastructure", "waitForZeroTimer", "Promise", "MockFxaStorageManager", "AccountState", "sumHistogram", "CommonUtils", "CryptoUtils", "TestingUtils", "promiseZeroTimer", "promiseNamedTimer", "getLoginTelemetryScalar"],
"Utils.jsm": ["Utils", "Logger", "PivotContext", "PrefCache"],
"VariablesView.jsm": ["VariablesView", "escapeHTML"],
"VariablesViewController.jsm": ["VariablesViewController", "StackFrameUtils"],
@@ -230,10 +215,8 @@
"vtt.jsm": ["WebVTT"],
"WebChannel.jsm": ["WebChannel", "WebChannelBroker"],
"WindowDraggingUtils.jsm": ["WindowDraggingElement"],
- "windows.js": ["init", "map"],
"windows.jsm": ["BrowserWindows"],
"WindowsJumpLists.jsm": ["WinTaskbarJumpList"],
"WindowsPreviewPerTab.jsm": ["AeroPeek"],
- "withs.js": ["startsWith", "endsWith"],
"xul-app.jsm": ["XulApp"]
}