--- a/.eslintignore
+++ b/.eslintignore
@@ -329,17 +329,16 @@ testing/talos/talos/tests/kraken/**
testing/web-platform/**
testing/xpcshell/moz-http2/**
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
# Not part of the default build
toolkit/components/help/**
# Intentionally invalid JS
toolkit/components/workerloader/tests/moduleF-syntax-error.js
--- a/services/sync/tests/tps/.eslintrc.js
+++ b/services/sync/tests/tps/.eslintrc.js
@@ -1,33 +1,27 @@
"use strict";
module.exports = {
"extends": [
"plugin:mozilla/mochitest-test"
],
globals: {
- // Globals specific to mozmill
- "assert": false,
- "controller": false,
- "findElement": false,
- "mozmill": false,
// Injected into tests via tps.jsm
"Addons": false,
"Bookmarks": false,
"EnableEngines": false,
"EnsureTracking": false,
"Formdata": false,
"History": false,
"Login": false,
"Passwords": false,
"Phase": false,
"Prefs": false,
- "RunMozmillTest": false,
"STATE_DISABLED": false,
"STATE_ENABLED": false,
"Sync": false,
"SYNC_WIPE_CLIENT": false,
"SYNC_WIPE_REMOTE": false,
"Tabs": false,
"Windows": false,
"WipeServer": false,
deleted file mode 100644
--- 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");
-};
deleted file mode 100644
--- 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");
-};
deleted file mode 100644
--- 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"],
-]);
--- a/services/sync/tests/unit/test_doctor.js
+++ b/services/sync/tests/unit/test_doctor.js
@@ -161,17 +161,17 @@ add_task(async function test_repairs_ski
let engine = {
name: "test-engine",
getValidator() {
return validator;
}
};
let requestor = {
async startRepairs(validationInfo, flowID) {
- assert.ok(false, "Never should start repairs");
+ ok(false, "Never should start repairs");
},
tryServerOnlyRepairs() {
return false;
}
};
let doctor = mockDoctor({
_getEnginesToValidate(recentlySyncedEngines) {
deepEqual(recentlySyncedEngines, [engine]);
deleted file mode 100755
--- a/services/sync/tps/extensions/mozmill/chrome.manifest
+++ /dev/null
@@ -1,2 +0,0 @@
-resource mozmill resource/
-
deleted file mode 100755
--- a/services/sync/tps/extensions/mozmill/install.rdf
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0"?>
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
- xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
- <Description about="urn:mozilla:install-manifest">
- <em:id>mozmill@mozilla.com</em:id>
- <em:name>Mozmill</em:name>
- <em:version>2.0.8</em:version>
- <em:description>UI Automation tool for Mozilla applications</em:description>
- <em:unpack>true</em:unpack>
-
- <em:creator>Mozilla Automation and Testing Team</em:creator>
- <em:contributor>Adam Christian</em:contributor>
- <em:contributor>Mikeal Rogers</em:contributor>
-
- <em:targetApplication>
- <Description>
- <em:id>toolkit@mozilla.org</em:id>
- <em:minVersion>10.0</em:minVersion>
- <em:maxVersion>38.*</em:maxVersion>
- </Description>
- </em:targetApplication>
- <em:multiprocessCompatible>true</em:multiprocessCompatible>
- </Description>
-</RDF>
deleted file mode 100644
--- 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);
-}
deleted file mode 100644
--- 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);
-};
deleted file mode 100644
--- 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'));
-};
deleted file mode 100644
--- 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();
deleted file mode 100644
--- 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);
-}
deleted file mode 100644
--- 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.
- * <dl>
- * <dd>fileName</dd>
- * <dt>Name of the file in which the assertion failed.</dt>
- * <dd>functionName</dd>
- * <dt>Function in which the assertion failed.</dt>
- * <dd>lineNumber</dd>
- * <dt>Line number of the file in which the assertion failed.</dt>
- * <dd>message</dd>
- * <dt>Message why the assertion failed.</dt>
- * </dl>
- * @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.
- * <dl>
- * <dd>fileName</dd>
- * <dt>Name of the file in which the assertion failed.</dt>
- * <dd>functionName</dd>
- * <dt>Function in which the assertion failed.</dt>
- * <dd>lineNumber</dd>
- * <dt>Line number of the file in which the assertion failed.</dt>
- * <dd>message</dd>
- * <dt>Message why the assertion failed.</dt>
- * </dl>
- */
- _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.
- * <dl>
- * <dd>fileName</dd>
- * <dt>Name of the file in which the assertion failed.</dt>
- * <dd>functionName</dd>
- * <dt>Function in which the assertion failed.</dt>
- * <dd>lineNumber</dd>
- * <dt>Line number of the file in which the assertion failed.</dt>
- * <dd>message</dd>
- * <dt>Message why the assertion failed.</dt>
- * </dl>
- */
-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);
-}
deleted file mode 100644
--- 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;
deleted file mode 100644
--- 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 }
-});
deleted file mode 100644
--- 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' :'<TOP_LEVEL>'
- };
-
- 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);
-};
deleted file mode 100644
--- 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 += '<!ENTITY % ' + id + ' SYSTEM "' + aDTDs[i] + '">%' + id + ';';
- }
-
- var header = '<?xml version="1.0"?><!DOCTYPE elem [' + references + ']>';
- var element = '<elem id="entity">&' + aEntityId + ';</elem>';
- 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;
deleted file mode 100644
--- 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;
-}
deleted file mode 100644
--- 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();
-}
deleted file mode 100644
--- 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);
-}
deleted file mode 100644
--- 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;
-}
deleted file mode 100644
--- 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;
-}
-
deleted file mode 100644
--- 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 "<Connection(" + this.number +
- (this.request ? ", " + this.request.path : "") +"): " +
- (this._closed ? "closed" : "open") + ">";
- },
-
- 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 = '<html>\
- <head>\
- <title>' + path + '</title>\
- </head>\
- <body>\
- <h1>' + path + '</h1>\
- <ol style="list-style-type: none">';
-
- 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 = '<li><a href="' + encodeURIComponent(name) + sep + '">' +
- htmlEscape(name) + sep +
- '</a></li>';
-
- body += item;
- }
- catch (e) { /* some file system error, ignore the file */ }
- }
-
- body += ' </ol>\
- </body>\
- </html>';
-
- 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
- // "<file>/foo/../bar" but prevents paths such as "<file>/../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)
- {