From 1873a289f7bf5402f34417c388223d2cd0fd5c7e Mon Sep 17 00:00:00 2001 From: Edouard Oger Date: Thu, 9 Nov 2017 15:34:06 -0500 Subject: [PATCH] Bug 1415957 - Remove MozMill from TPS. r=tcsc MozReview-Commit-ID: HyBXrNqhzIf --HG-- extra : rebase_source : da197fa045f70621d5549e961876cfc6ce15264b --- .eslintignore | 1 - services/sync/tests/tps/.eslintrc.js | 6 - services/sync/tests/tps/mozmill_sanity.js | 30 - services/sync/tests/tps/mozmill_sanity2.js | 15 - .../sync/tests/tps/test_mozmill_sanity.js | 24 - services/sync/tests/unit/test_doctor.js | 2 +- .../tps/extensions/mozmill/chrome.manifest | 2 - .../sync/tps/extensions/mozmill/install.rdf | 25 - .../mozmill/resource/driver/controller.js | 1139 ---- .../mozmill/resource/driver/elementslib.js | 537 -- .../mozmill/resource/driver/mozelement.js | 1157 ---- .../mozmill/resource/driver/mozmill.js | 283 - .../mozmill/resource/driver/msgbroker.js | 58 - .../mozmill/resource/modules/assertions.js | 672 --- .../mozmill/resource/modules/driver.js | 290 - .../mozmill/resource/modules/errors.js | 102 - .../mozmill/resource/modules/frame.js | 787 --- .../mozmill/resource/modules/l10n.js | 71 - .../mozmill/resource/modules/stack.js | 43 - .../mozmill/resource/modules/windows.js | 292 - .../mozmill/resource/stdlib/EventUtils.js | 823 --- .../mozmill/resource/stdlib/arrays.js | 78 - .../extensions/mozmill/resource/stdlib/dom.js | 24 - .../mozmill/resource/stdlib/httpd.js | 5349 ----------------- .../mozmill/resource/stdlib/json2.js | 469 -- .../mozmill/resource/stdlib/objects.js | 54 - .../extensions/mozmill/resource/stdlib/os.js | 57 - .../resource/stdlib/securable-module.js | 370 -- .../mozmill/resource/stdlib/strings.js | 17 - .../mozmill/resource/stdlib/utils.js | 451 -- .../mozmill/resource/stdlib/withs.js | 146 - .../sync/tps/extensions/tps/resource/tps.jsm | 39 - testing/tps/tps/testrunner.py | 1 - tools/lint/eslint/modules.json | 19 +- 34 files changed, 2 insertions(+), 13431 deletions(-) delete mode 100644 services/sync/tests/tps/mozmill_sanity.js delete mode 100644 services/sync/tests/tps/mozmill_sanity2.js delete mode 100644 services/sync/tests/tps/test_mozmill_sanity.js delete mode 100755 services/sync/tps/extensions/mozmill/chrome.manifest delete mode 100755 services/sync/tps/extensions/mozmill/install.rdf delete mode 100644 services/sync/tps/extensions/mozmill/resource/driver/controller.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/driver/elementslib.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/driver/mozelement.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/driver/mozmill.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/driver/msgbroker.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/assertions.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/driver.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/errors.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/frame.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/l10n.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/stack.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/modules/windows.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/EventUtils.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/arrays.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/dom.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/httpd.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/json2.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/objects.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/os.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/strings.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/utils.js delete mode 100644 services/sync/tps/extensions/mozmill/resource/stdlib/withs.js 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 = '
  1. ' + - htmlEscape(name) + sep + - '
  2. '; - - 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"] }