summaryrefslogtreecommitdiffstats
path: root/toolkit/jetpack/sdk/deprecated
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/jetpack/sdk/deprecated')
-rw-r--r--toolkit/jetpack/sdk/deprecated/api-utils.js197
-rw-r--r--toolkit/jetpack/sdk/deprecated/events/assembler.js54
-rw-r--r--toolkit/jetpack/sdk/deprecated/sync-worker.js288
-rw-r--r--toolkit/jetpack/sdk/deprecated/unit-test-finder.js199
-rw-r--r--toolkit/jetpack/sdk/deprecated/unit-test.js584
-rw-r--r--toolkit/jetpack/sdk/deprecated/window-utils.js193
6 files changed, 1515 insertions, 0 deletions
diff --git a/toolkit/jetpack/sdk/deprecated/api-utils.js b/toolkit/jetpack/sdk/deprecated/api-utils.js
new file mode 100644
index 000000000..856fc50cb
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/api-utils.js
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const { merge } = require("../util/object");
+const { union } = require("../util/array");
+const { isNil, isRegExp } = require("../lang/type");
+
+// The possible return values of getTypeOf.
+const VALID_TYPES = [
+ "array",
+ "boolean",
+ "function",
+ "null",
+ "number",
+ "object",
+ "string",
+ "undefined",
+ "regexp"
+];
+
+const { isArray } = Array;
+
+/**
+ * Returns a validated options dictionary given some requirements. If any of
+ * the requirements are not met, an exception is thrown.
+ *
+ * @param options
+ * An object, the options dictionary to validate. It's not modified.
+ * If it's null or otherwise falsey, an empty object is assumed.
+ * @param requirements
+ * An object whose keys are the expected keys in options. Any key in
+ * options that is not present in requirements is ignored. Each value
+ * in requirements is itself an object describing the requirements of
+ * its key. There are four optional keys in this object:
+ * map: A function that's passed the value of the key in options.
+ * map's return value is taken as the key's value in the final
+ * validated options, is, and ok. If map throws an exception
+ * it's caught and discarded, and the key's value is its value in
+ * options.
+ * is: An array containing any number of the typeof type names. If
+ * the key's value is none of these types, it fails validation.
+ * Arrays, null and regexps are identified by the special type names
+ * "array", "null", "regexp"; "object" will not match either. No type
+ * coercion is done.
+ * ok: A function that's passed the key's value. If it returns
+ * false, the value fails validation.
+ * msg: If the key's value fails validation, an exception is thrown.
+ * This string will be used as its message. If undefined, a
+ * generic message is used, unless is is defined, in which case
+ * the message will state that the value needs to be one of the
+ * given types.
+ * @return An object whose keys are those keys in requirements that are also in
+ * options and whose values are the corresponding return values of map
+ * or the corresponding values in options. Note that any keys not
+ * shared by both requirements and options are not in the returned
+ * object.
+ */
+exports.validateOptions = function validateOptions(options, requirements) {
+ options = options || {};
+ let validatedOptions = {};
+
+ for (let key in requirements) {
+ let isOptional = false;
+ let mapThrew = false;
+ let req = requirements[key];
+ let [optsVal, keyInOpts] = (key in options) ?
+ [options[key], true] :
+ [undefined, false];
+ if (req.map) {
+ try {
+ optsVal = req.map(optsVal);
+ }
+ catch (err) {
+ if (err instanceof RequirementError)
+ throw err;
+
+ mapThrew = true;
+ }
+ }
+ if (req.is) {
+ let types = req.is;
+
+ if (!isArray(types) && isArray(types.is))
+ types = types.is;
+
+ if (isArray(types)) {
+ isOptional = ['undefined', 'null'].every(v => ~types.indexOf(v));
+
+ // Sanity check the caller's type names.
+ types.forEach(function (typ) {
+ if (VALID_TYPES.indexOf(typ) < 0) {
+ let msg = 'Internal error: invalid requirement type "' + typ + '".';
+ throw new Error(msg);
+ }
+ });
+ if (types.indexOf(getTypeOf(optsVal)) < 0)
+ throw new RequirementError(key, req);
+ }
+ }
+
+ if (req.ok && ((!isOptional || !isNil(optsVal)) && !req.ok(optsVal)))
+ throw new RequirementError(key, req);
+
+ if (keyInOpts || (req.map && !mapThrew && optsVal !== undefined))
+ validatedOptions[key] = optsVal;
+ }
+
+ return validatedOptions;
+};
+
+exports.addIterator = function addIterator(obj, keysValsGenerator) {
+ obj.__iterator__ = function(keysOnly, keysVals) {
+ let keysValsIterator = keysValsGenerator.call(this);
+
+ // "for (.. in ..)" gets only keys, "for each (.. in ..)" gets values,
+ // and "for (.. in Iterator(..))" gets [key, value] pairs.
+ let index = keysOnly ? 0 : 1;
+ while (true)
+ yield keysVals ? keysValsIterator.next() : keysValsIterator.next()[index];
+ };
+};
+
+// Similar to typeof, except arrays, null and regexps are identified by "array" and
+// "null" and "regexp", not "object".
+var getTypeOf = exports.getTypeOf = function getTypeOf(val) {
+ let typ = typeof(val);
+ if (typ === "object") {
+ if (!val)
+ return "null";
+ if (isArray(val))
+ return "array";
+ if (isRegExp(val))
+ return "regexp";
+ }
+ return typ;
+}
+
+function RequirementError(key, requirement) {
+ Error.call(this);
+
+ this.name = "RequirementError";
+
+ let msg = requirement.msg;
+ if (!msg) {
+ msg = 'The option "' + key + '" ';
+ msg += requirement.is ?
+ "must be one of the following types: " + requirement.is.join(", ") :
+ "is invalid.";
+ }
+
+ this.message = msg;
+}
+RequirementError.prototype = Object.create(Error.prototype);
+
+var string = { is: ['string', 'undefined', 'null'] };
+exports.string = string;
+
+var number = { is: ['number', 'undefined', 'null'] };
+exports.number = number;
+
+var boolean = { is: ['boolean', 'undefined', 'null'] };
+exports.boolean = boolean;
+
+var object = { is: ['object', 'undefined', 'null'] };
+exports.object = object;
+
+var array = { is: ['array', 'undefined', 'null'] };
+exports.array = array;
+
+var isTruthyType = type => !(type === 'undefined' || type === 'null');
+var findTypes = v => { while (!isArray(v) && v.is) v = v.is; return v };
+
+function required(req) {
+ let types = (findTypes(req) || VALID_TYPES).filter(isTruthyType);
+
+ return merge({}, req, {is: types});
+}
+exports.required = required;
+
+function optional(req) {
+ req = merge({is: []}, req);
+ req.is = findTypes(req).filter(isTruthyType).concat('undefined', 'null');
+
+ return req;
+}
+exports.optional = optional;
+
+function either(...types) {
+ return union.apply(null, types.map(findTypes));
+}
+exports.either = either;
diff --git a/toolkit/jetpack/sdk/deprecated/events/assembler.js b/toolkit/jetpack/sdk/deprecated/events/assembler.js
new file mode 100644
index 000000000..bb297c24f
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/events/assembler.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Class } = require("../../core/heritage");
+const { removeListener, on } = require("../../dom/events");
+
+/**
+ * Event targets
+ * can be added / removed by calling `observe / ignore` methods. Composer should
+ * provide array of event types it wishes to handle as property
+ * `supportedEventsTypes` and function for handling all those events as
+ * `handleEvent` property.
+ */
+exports.DOMEventAssembler = Class({
+ /**
+ * Function that is supposed to handle all the supported events (that are
+ * present in the `supportedEventsTypes`) from all the observed
+ * `eventTargets`.
+ * @param {Event} event
+ * Event being dispatched.
+ */
+ handleEvent() {
+ throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` method");
+ },
+ /**
+ * Array of supported event names.
+ * @type {String[]}
+ */
+ get supportedEventsTypes() {
+ throw new TypeError("Instance of DOMEventAssembler must implement `handleEvent` field");
+ },
+ /**
+ * Adds `eventTarget` to the list of observed `eventTarget`s. Listeners for
+ * supported events will be registered on the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ observe: function observe(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ on(eventTarget, eventType, this);
+ }, this);
+ },
+ /**
+ * Removes `eventTarget` from the list of observed `eventTarget`s. Listeners
+ * for all supported events will be unregistered from the given `eventTarget`.
+ * @param {EventTarget} eventTarget
+ */
+ ignore: function ignore(eventTarget) {
+ this.supportedEventsTypes.forEach(function(eventType) {
+ removeListener(eventTarget, eventType, this);
+ }, this);
+ }
+});
diff --git a/toolkit/jetpack/sdk/deprecated/sync-worker.js b/toolkit/jetpack/sdk/deprecated/sync-worker.js
new file mode 100644
index 000000000..71cadac36
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/sync-worker.js
@@ -0,0 +1,288 @@
+/* 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/. */
+
+/**
+ *
+ * `deprecated/sync-worker` was previously `content/worker`, that was
+ * incompatible with e10s. we are in the process of switching to the new
+ * asynchronous `Worker`, which behaves slightly differently in some edge
+ * cases, so we are keeping this one around for a short period.
+ * try to switch to the new one as soon as possible..
+ *
+ */
+
+"use strict";
+
+module.metadata = {
+ "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { EventTarget } = require('../event/target');
+const { on, off, emit, setListeners } = require('../event/core');
+const {
+ attach, detach, destroy
+} = require('../content/utils');
+const { method } = require('../lang/functional');
+const { Ci, Cu, Cc } = require('chrome');
+const unload = require('../system/unload');
+const events = require('../system/events');
+const { getInnerId } = require("../window/utils");
+const { WorkerSandbox } = require('../content/sandbox');
+const { isPrivate } = require('../private-browsing/utils');
+
+// A weak map of workers to hold private attributes that
+// should not be exposed
+const workers = new WeakMap();
+
+var modelFor = (worker) => workers.get(worker);
+
+const ERR_DESTROYED =
+ "Couldn't find the worker to receive this message. " +
+ "The script may not be initialized yet, or may already have been unloaded.";
+
+const ERR_FROZEN = "The page is currently hidden and can no longer be used " +
+ "until it is visible again.";
+
+/**
+ * Message-passing facility for communication between code running
+ * in the content and add-on process.
+ * @see https://developer.mozilla.org/en-US/Add-ons/SDK/Low-Level_APIs/content_worker
+ */
+const Worker = Class({
+ implements: [EventTarget],
+ initialize: function WorkerConstructor (options) {
+ // Save model in weak map to not expose properties
+ let model = createModel();
+ workers.set(this, model);
+
+ options = options || {};
+
+ if ('contentScriptFile' in options)
+ this.contentScriptFile = options.contentScriptFile;
+ if ('contentScriptOptions' in options)
+ this.contentScriptOptions = options.contentScriptOptions;
+ if ('contentScript' in options)
+ this.contentScript = options.contentScript;
+ if ('injectInDocument' in options)
+ this.injectInDocument = !!options.injectInDocument;
+
+ setListeners(this, options);
+
+ unload.ensure(this, "destroy");
+
+ // Ensure that worker.port is initialized for contentWorker to be able
+ // to send events during worker initialization.
+ this.port = createPort(this);
+
+ model.documentUnload = documentUnload.bind(this);
+ model.pageShow = pageShow.bind(this);
+ model.pageHide = pageHide.bind(this);
+
+ if ('window' in options)
+ attach(this, options.window);
+ },
+
+ /**
+ * Sends a message to the worker's global scope. Method takes single
+ * argument, which represents data to be sent to the worker. The data may
+ * be any primitive type value or `JSON`. Call of this method asynchronously
+ * emits `message` event with data value in the global scope of this
+ * worker.
+ *
+ * `message` event listeners can be set either by calling
+ * `self.on` with a first argument string `"message"` or by
+ * implementing `onMessage` function in the global scope of this worker.
+ * @param {Number|String|JSON} data
+ */
+ postMessage: function (...data) {
+ let model = modelFor(this);
+ let args = ['message'].concat(data);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [this].concat(args));
+ },
+
+ get url () {
+ let model = modelFor(this);
+ // model.window will be null after detach
+ return model.window ? model.window.document.location.href : null;
+ },
+
+ get contentURL () {
+ let model = modelFor(this);
+ return model.window ? model.window.document.URL : null;
+ },
+
+ // Implemented to provide some of the previous features of exposing sandbox
+ // so that Worker can be extended
+ getSandbox: function () {
+ return modelFor(this).contentWorker;
+ },
+
+ toString: function () { return '[object Worker]'; },
+ attach: method(attach),
+ detach: method(detach),
+ destroy: method(destroy)
+});
+exports.Worker = Worker;
+
+attach.define(Worker, function (worker, window) {
+ let model = modelFor(worker);
+ model.window = window;
+ // Track document unload to destroy this worker.
+ // We can't watch for unload event on page's window object as it
+ // prevents bfcache from working:
+ // https://developer.mozilla.org/En/Working_with_BFCache
+ model.windowID = getInnerId(model.window);
+ events.on("inner-window-destroyed", model.documentUnload);
+
+ // will set model.contentWorker pointing to the private API:
+ model.contentWorker = WorkerSandbox(worker, model.window);
+
+ // Listen to pagehide event in order to freeze the content script
+ // while the document is frozen in bfcache:
+ model.window.addEventListener("pageshow", model.pageShow, true);
+ model.window.addEventListener("pagehide", model.pageHide, true);
+
+ // Mainly enable worker.port.emit to send event to the content worker
+ model.inited = true;
+ model.frozen = false;
+
+ // Fire off `attach` event
+ emit(worker, 'attach', window);
+
+ // Process all events and messages that were fired before the
+ // worker was initialized.
+ model.earlyEvents.forEach(args => processMessage.apply(null, [worker].concat(args)));
+});
+
+/**
+ * Remove all internal references to the attached document
+ * Tells _port to unload itself and removes all the references from itself.
+ */
+detach.define(Worker, function (worker, reason) {
+ let model = modelFor(worker);
+
+ // maybe unloaded before content side is created
+ if (model.contentWorker) {
+ model.contentWorker.destroy(reason);
+ }
+
+ model.contentWorker = null;
+ if (model.window) {
+ model.window.removeEventListener("pageshow", model.pageShow, true);
+ model.window.removeEventListener("pagehide", model.pageHide, true);
+ }
+ model.window = null;
+ // This method may be called multiple times,
+ // avoid dispatching `detach` event more than once
+ if (model.windowID) {
+ model.windowID = null;
+ events.off("inner-window-destroyed", model.documentUnload);
+ model.earlyEvents.length = 0;
+ emit(worker, 'detach');
+ }
+ model.inited = false;
+});
+
+isPrivate.define(Worker, ({ tab }) => isPrivate(tab));
+
+/**
+ * Tells content worker to unload itself and
+ * removes all the references from itself.
+ */
+destroy.define(Worker, function (worker, reason) {
+ detach(worker, reason);
+ modelFor(worker).inited = true;
+ // Specifying no type or listener removes all listeners
+ // from target
+ off(worker);
+ off(worker.port);
+});
+
+/**
+ * Events fired by workers
+ */
+function documentUnload ({ subject, data }) {
+ let model = modelFor(this);
+ let innerWinID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ if (innerWinID != model.windowID) return false;
+ detach(this);
+ return true;
+}
+
+function pageShow () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pageshow');
+ emit(this, 'pageshow');
+ model.frozen = false;
+}
+
+function pageHide () {
+ let model = modelFor(this);
+ model.contentWorker.emitSync('pagehide');
+ emit(this, 'pagehide');
+ model.frozen = true;
+}
+
+/**
+ * Fired from postMessage and emitEventToContent, or from the earlyMessage
+ * queue when fired before the content is loaded. Sends arguments to
+ * contentWorker if able
+ */
+
+function processMessage (worker, ...args) {
+ let model = modelFor(worker) || {};
+ if (!model.contentWorker)
+ throw new Error(ERR_DESTROYED);
+ if (model.frozen)
+ throw new Error(ERR_FROZEN);
+ model.contentWorker.emit.apply(null, args);
+}
+
+function createModel () {
+ return {
+ // List of messages fired before worker is initialized
+ earlyEvents: [],
+ // Is worker connected to the content worker sandbox ?
+ inited: false,
+ // Is worker being frozen? i.e related document is frozen in bfcache.
+ // Content script should not be reachable if frozen.
+ frozen: true,
+ /**
+ * Reference to the content side of the worker.
+ * @type {WorkerGlobalScope}
+ */
+ contentWorker: null,
+ /**
+ * Reference to the window that is accessible from
+ * the content scripts.
+ * @type {Object}
+ */
+ window: null
+ };
+}
+
+function createPort (worker) {
+ let port = EventTarget();
+ port.emit = emitEventToContent.bind(null, worker);
+ return port;
+}
+
+/**
+ * Emit a custom event to the content script,
+ * i.e. emit this event on `self.port`
+ */
+function emitEventToContent (worker, ...eventArgs) {
+ let model = modelFor(worker);
+ let args = ['event'].concat(eventArgs);
+ if (!model.inited) {
+ model.earlyEvents.push(args);
+ return;
+ }
+ processMessage.apply(null, [worker].concat(args));
+}
diff --git a/toolkit/jetpack/sdk/deprecated/unit-test-finder.js b/toolkit/jetpack/sdk/deprecated/unit-test-finder.js
new file mode 100644
index 000000000..e38629f45
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/unit-test-finder.js
@@ -0,0 +1,199 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const file = require("../io/file");
+const { Loader } = require("../test/loader");
+
+const { isNative } = require('@loader/options');
+
+const cuddlefish = isNative ? require("toolkit/loader") : require("../loader/cuddlefish");
+
+const { defer, resolve } = require("../core/promise");
+const { getAddon } = require("../addon/installer");
+const { id } = require("sdk/self");
+const { newURI } = require('sdk/url/utils');
+const { getZipReader } = require("../zip/utils");
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
+var ios = Cc['@mozilla.org/network/io-service;1']
+ .getService(Ci.nsIIOService);
+
+const CFX_TEST_REGEX = /(([^\/]+\/)(?:lib\/)?)?(tests?\/test-[^\.\/]+)\.js$/;
+const JPM_TEST_REGEX = /^()(tests?\/test-[^\.\/]+)\.js$/;
+
+const { mapcat, map, filter, fromEnumerator } = require("sdk/util/sequence");
+
+const toFile = x => x.QueryInterface(Ci.nsIFile);
+const isTestFile = ({leafName}) => leafName.substr(0, 5) == "test-" && leafName.substr(-3, 3) == ".js";
+const getFileURI = x => ios.newFileURI(x).spec;
+
+const getDirectoryEntries = file => map(toFile, fromEnumerator(_ => file.directoryEntries));
+const getTestFiles = directory => filter(isTestFile, getDirectoryEntries(directory));
+const getTestURIs = directory => map(getFileURI, getTestFiles(directory));
+
+const isDirectory = x => x.isDirectory();
+const getTestEntries = directory => mapcat(entry =>
+ /^tests?$/.test(entry.leafName) ? getTestURIs(entry) : getTestEntries(entry),
+ filter(isDirectory, getDirectoryEntries(directory)));
+
+const removeDups = (array) => array.reduce((result, value) => {
+ if (value != result[result.length - 1]) {
+ result.push(value);
+ }
+ return result;
+}, []);
+
+const getSuites = function getSuites({ id, filter }) {
+ const TEST_REGEX = isNative ? JPM_TEST_REGEX : CFX_TEST_REGEX;
+
+ return getAddon(id).then(addon => {
+ let fileURI = addon.getResourceURI("tests/");
+ let isPacked = fileURI.scheme == "jar";
+ let xpiURI = addon.getResourceURI();
+ let file = xpiURI.QueryInterface(Ci.nsIFileURL).file;
+ let suites = [];
+ let addEntry = (entry) => {
+ if (filter(entry) && TEST_REGEX.test(entry)) {
+ let suite = (isNative ? "./" : "") + (RegExp.$2 || "") + RegExp.$3;
+ suites.push(suite);
+ }
+ }
+
+ if (isPacked) {
+ return getZipReader(file).then(zip => {
+ let entries = zip.findEntries(null);
+ while (entries.hasMore()) {
+ let entry = entries.getNext();
+ addEntry(entry);
+ }
+ zip.close();
+
+ // sort and remove dups
+ suites = removeDups(suites.sort());
+ return suites;
+ })
+ }
+ else {
+ let tests = [...getTestEntries(file)];
+ let rootURI = addon.getResourceURI("/");
+ tests.forEach((entry) => {
+ addEntry(entry.replace(rootURI.spec, ""));
+ });
+ }
+
+ // sort and remove dups
+ suites = removeDups(suites.sort());
+ return suites;
+ });
+}
+exports.getSuites = getSuites;
+
+const makeFilters = function makeFilters(options) {
+ options = options || {};
+
+ // A filter string is {fileNameRegex}[:{testNameRegex}] - ie, a colon
+ // optionally separates a regex for the test fileName from a regex for the
+ // testName.
+ if (options.filter) {
+ let colonPos = options.filter.indexOf(':');
+ let filterFileRegex, filterNameRegex;
+
+ if (colonPos === -1) {
+ filterFileRegex = new RegExp(options.filter);
+ filterNameRegex = { test: () => true }
+ }
+ else {
+ filterFileRegex = new RegExp(options.filter.substr(0, colonPos));
+ filterNameRegex = new RegExp(options.filter.substr(colonPos + 1));
+ }
+
+ return {
+ fileFilter: (name) => filterFileRegex.test(name),
+ testFilter: (name) => filterNameRegex.test(name)
+ }
+ }
+
+ return {
+ fileFilter: () => true,
+ testFilter: () => true
+ };
+}
+exports.makeFilters = makeFilters;
+
+var loader = Loader(module);
+const NOT_TESTS = ['setup', 'teardown'];
+
+var TestFinder = exports.TestFinder = function TestFinder(options) {
+ this.filter = options.filter;
+ this.testInProcess = options.testInProcess === false ? false : true;
+ this.testOutOfProcess = options.testOutOfProcess === true ? true : false;
+};
+
+TestFinder.prototype = {
+ findTests: function findTests() {
+ let { fileFilter, testFilter } = makeFilters({ filter: this.filter });
+
+ return getSuites({ id: id, filter: fileFilter }).then(suites => {
+ let testsRemaining = [];
+
+ let getNextTest = () => {
+ if (testsRemaining.length) {
+ return testsRemaining.shift();
+ }
+
+ if (!suites.length) {
+ return null;
+ }
+
+ let suite = suites.shift();
+
+ // Load each test file as a main module in its own loader instance
+ // `suite` is defined by cuddlefish/manifest.py:ManifestBuilder.build
+ let suiteModule;
+
+ try {
+ suiteModule = cuddlefish.main(loader, suite);
+ }
+ catch (e) {
+ if (/Unsupported Application/i.test(e.message)) {
+ // If `Unsupported Application` error thrown during test,
+ // skip the test suite
+ suiteModule = {
+ 'test suite skipped': assert => assert.pass(e.message)
+ };
+ }
+ else {
+ console.exception(e);
+ throw e;
+ }
+ }
+
+ if (this.testInProcess) {
+ for (let name of Object.keys(suiteModule).sort()) {
+ if (NOT_TESTS.indexOf(name) === -1 && testFilter(name)) {
+ testsRemaining.push({
+ setup: suiteModule.setup,
+ teardown: suiteModule.teardown,
+ testFunction: suiteModule[name],
+ name: suite + "." + name
+ });
+ }
+ }
+ }
+
+ return getNextTest();
+ };
+
+ return {
+ getNext: () => resolve(getNextTest())
+ };
+ });
+ }
+};
diff --git a/toolkit/jetpack/sdk/deprecated/unit-test.js b/toolkit/jetpack/sdk/deprecated/unit-test.js
new file mode 100644
index 000000000..32bba8f6b
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/unit-test.js
@@ -0,0 +1,584 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+module.metadata = {
+ "stability": "deprecated"
+};
+
+const timer = require("../timers");
+const cfxArgs = require("../test/options");
+const { getTabs, closeTab, getURI, getTabId, getSelectedTab } = require("../tabs/utils");
+const { windows, isBrowser, getMostRecentBrowserWindow } = require("../window/utils");
+const { defer, all, Debugging: PromiseDebugging, resolve } = require("../core/promise");
+const { getInnerId } = require("../window/utils");
+const { cleanUI } = require("../test/utils");
+
+const findAndRunTests = function findAndRunTests(options) {
+ var TestFinder = require("./unit-test-finder").TestFinder;
+ var finder = new TestFinder({
+ filter: options.filter,
+ testInProcess: options.testInProcess,
+ testOutOfProcess: options.testOutOfProcess
+ });
+ var runner = new TestRunner({fs: options.fs});
+ finder.findTests().then(tests => {
+ runner.startMany({
+ tests: tests,
+ stopOnError: options.stopOnError,
+ onDone: options.onDone
+ });
+ });
+};
+exports.findAndRunTests = findAndRunTests;
+
+var runnerWindows = new WeakMap();
+var runnerTabs = new WeakMap();
+
+const TestRunner = function TestRunner(options) {
+ options = options || {};
+
+ // remember the id's for the open window and tab
+ let window = getMostRecentBrowserWindow();
+ runnerWindows.set(this, getInnerId(window));
+ runnerTabs.set(this, getTabId(getSelectedTab(window)));
+
+ this.fs = options.fs;
+ this.console = options.console || console;
+ this.passed = 0;
+ this.failed = 0;
+ this.testRunSummary = [];
+ this.expectFailNesting = 0;
+ this.done = TestRunner.prototype.done.bind(this);
+};
+
+TestRunner.prototype = {
+ toString: function toString() {
+ return "[object TestRunner]";
+ },
+
+ DEFAULT_PAUSE_TIMEOUT: (cfxArgs.parseable ? 300000 : 15000), //Five minutes (5*60*1000ms)
+ PAUSE_DELAY: 500,
+
+ _logTestFailed: function _logTestFailed(why) {
+ if (!(why in this.test.errors))
+ this.test.errors[why] = 0;
+ this.test.errors[why]++;
+ },
+
+ _uncaughtErrorObserver: function({message, date, fileName, stack, lineNumber}) {
+ this.fail("There was an uncaught Promise rejection: " + message + " @ " +
+ fileName + ":" + lineNumber + "\n" + stack);
+ },
+
+ pass: function pass(message) {
+ if(!this.expectFailure) {
+ if ("testMessage" in this.console)
+ this.console.testMessage(true, true, this.test.name, message);
+ else
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ this.test.last = message;
+ }
+ else {
+ this.expectFailure = false;
+ this._logTestFailed("failure");
+ if ("testMessage" in this.console) {
+ this.console.testMessage(true, false, this.test.name, message);
+ }
+ else {
+ this.console.error("fail:", 'Failure Expected: ' + message)
+ this.console.trace();
+ }
+ this.failed++;
+ this.test.failed++;
+ }
+ },
+
+ fail: function fail(message) {
+ if(!this.expectFailure) {
+ this._logTestFailed("failure");
+ if ("testMessage" in this.console) {
+ this.console.testMessage(false, false, this.test.name, message);
+ }
+ else {
+ this.console.error("fail:", message)
+ this.console.trace();
+ }
+ this.failed++;
+ this.test.failed++;
+ }
+ else {
+ this.expectFailure = false;
+ if ("testMessage" in this.console)
+ this.console.testMessage(false, true, this.test.name, message);
+ else
+ this.console.info("pass:", message);
+ this.passed++;
+ this.test.passed++;
+ this.test.last = message;
+ }
+ },
+
+ expectFail: function(callback) {
+ this.expectFailure = true;
+ callback();
+ this.expectFailure = false;
+ },
+
+ exception: function exception(e) {
+ this._logTestFailed("exception");
+ if (cfxArgs.parseable)
+ this.console.print("TEST-UNEXPECTED-FAIL | " + this.test.name + " | " + e + "\n");
+ this.console.exception(e);
+ this.failed++;
+ this.test.failed++;
+ },
+
+ assertMatches: function assertMatches(string, regexp, message) {
+ if (regexp.test(string)) {
+ if (!message)
+ message = uneval(string) + " matches " + uneval(regexp);
+ this.pass(message);
+ } else {
+ var no = uneval(string) + " doesn't match " + uneval(regexp);
+ if (!message)
+ message = no;
+ else
+ message = message + " (" + no + ")";
+ this.fail(message);
+ }
+ },
+
+ assertRaises: function assertRaises(func, predicate, message) {
+ try {
+ func();
+ if (message)
+ this.fail(message + " (no exception thrown)");
+ else
+ this.fail("function failed to throw exception");
+ } catch (e) {
+ var errorMessage;
+ if (typeof(e) == "string")
+ errorMessage = e;
+ else
+ errorMessage = e.message;
+ if (typeof(predicate) == "string")
+ this.assertEqual(errorMessage, predicate, message);
+ else
+ this.assertMatches(errorMessage, predicate, message);
+ }
+ },
+
+ assert: function assert(a, message) {
+ if (!a) {
+ if (!message)
+ message = "assertion failed, value is " + a;
+ this.fail(message);
+ } else
+ this.pass(message || "assertion successful");
+ },
+
+ assertNotEqual: function assertNotEqual(a, b, message) {
+ if (a != b) {
+ if (!message)
+ message = "a != b != " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " == " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertEqual: function assertEqual(a, b, message) {
+ if (a == b) {
+ if (!message)
+ message = "a == b == " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " != " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertNotStrictEqual: function assertNotStrictEqual(a, b, message) {
+ if (a !== b) {
+ if (!message)
+ message = "a !== b !== " + uneval(a);
+ this.pass(message);
+ } else {
+ var equality = uneval(a) + " === " + uneval(b);
+ if (!message)
+ message = equality;
+ else
+ message += " (" + equality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertStrictEqual: function assertStrictEqual(a, b, message) {
+ if (a === b) {
+ if (!message)
+ message = "a === b === " + uneval(a);
+ this.pass(message);
+ } else {
+ var inequality = uneval(a) + " !== " + uneval(b);
+ if (!message)
+ message = inequality;
+ else
+ message += " (" + inequality + ")";
+ this.fail(message);
+ }
+ },
+
+ assertFunction: function assertFunction(a, message) {
+ this.assertStrictEqual('function', typeof a, message);
+ },
+
+ assertUndefined: function(a, message) {
+ this.assertStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNotUndefined: function(a, message) {
+ this.assertNotStrictEqual('undefined', typeof a, message);
+ },
+
+ assertNull: function(a, message) {
+ this.assertStrictEqual(null, a, message);
+ },
+
+ assertNotNull: function(a, message) {
+ this.assertNotStrictEqual(null, a, message);
+ },
+
+ assertObject: function(a, message) {
+ this.assertStrictEqual('[object Object]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertString: function(a, message) {
+ this.assertStrictEqual('[object String]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertArray: function(a, message) {
+ this.assertStrictEqual('[object Array]', Object.prototype.toString.apply(a), message);
+ },
+
+ assertNumber: function(a, message) {
+ this.assertStrictEqual('[object Number]', Object.prototype.toString.apply(a), message);
+ },
+
+ done: function done() {
+ if (this.isDone) {
+ return resolve();
+ }
+
+ this.isDone = true;
+ this.pass("This test is done.");
+
+ if (this.test.teardown) {
+ this.test.teardown(this);
+ }
+
+ if (this.waitTimeout !== null) {
+ timer.clearTimeout(this.waitTimeout);
+ this.waitTimeout = null;
+ }
+
+ // Do not leave any callback set when calling to `waitUntil`
+ this.waitUntilCallback = null;
+ if (this.test.passed == 0 && this.test.failed == 0) {
+ this._logTestFailed("empty test");
+
+ if ("testMessage" in this.console) {
+ this.console.testMessage(false, false, this.test.name, "Empty test");
+ }
+ else {
+ this.console.error("fail:", "Empty test")
+ }
+
+ this.failed++;
+ this.test.failed++;
+ }
+
+ let wins = windows(null, { includePrivate: true });
+ let winPromises = wins.map(win => {
+ return new Promise(resolve => {
+ if (["interactive", "complete"].indexOf(win.document.readyState) >= 0) {
+ resolve()
+ }
+ else {
+ win.addEventListener("DOMContentLoaded", function onLoad() {
+ win.removeEventListener("DOMContentLoaded", onLoad, false);
+ resolve();
+ }, false);
+ }
+ });
+ });
+
+ PromiseDebugging.flushUncaughtErrors();
+ PromiseDebugging.removeUncaughtErrorObserver(this._uncaughtErrorObserver);
+
+
+ return all(winPromises).then(() => {
+ let browserWins = wins.filter(isBrowser);
+ let tabs = browserWins.reduce((tabs, window) => tabs.concat(getTabs(window)), []);
+ let newTabID = getTabId(getSelectedTab(wins[0]));
+ let oldTabID = runnerTabs.get(this);
+ let hasMoreTabsOpen = browserWins.length && tabs.length != 1;
+ let failure = false;
+
+ if (wins.length != 1 || getInnerId(wins[0]) !== runnerWindows.get(this)) {
+ failure = true;
+ this.fail("Should not be any unexpected windows open");
+ }
+ else if (hasMoreTabsOpen) {
+ failure = true;
+ this.fail("Should not be any unexpected tabs open");
+ }
+ else if (oldTabID != newTabID) {
+ failure = true;
+ runnerTabs.set(this, newTabID);
+ this.fail("Should not be any new tabs left open, old id: " + oldTabID + " new id: " + newTabID);
+ }
+
+ if (failure) {
+ console.log("Windows open:");
+ for (let win of wins) {
+ if (isBrowser(win)) {
+ tabs = getTabs(win);
+ console.log(win.location + " - " + tabs.map(getURI).join(", "));
+ }
+ else {
+ console.log(win.location);
+ }
+ }
+ }
+
+ return failure;
+ }).
+ then(failure => {
+ if (!failure) {
+ this.pass("There was a clean UI.");
+ return null;
+ }
+ return cleanUI().then(() => {
+ this.pass("There is a clean UI.");
+ });
+ }).
+ then(() => {
+ this.testRunSummary.push({
+ name: this.test.name,
+ passed: this.test.passed,
+ failed: this.test.failed,
+ errors: Object.keys(this.test.errors).join(", ")
+ });
+
+ if (this.onDone !== null) {
+ let onDone = this.onDone;
+ this.onDone = null;
+ timer.setTimeout(_ => onDone(this));
+ }
+ }).
+ catch(console.exception);
+ },
+
+ // Set of assertion functions to wait for an assertion to become true
+ // These functions take the same arguments as the TestRunner.assert* methods.
+ waitUntil: function waitUntil() {
+ return this._waitUntil(this.assert, arguments);
+ },
+
+ waitUntilNotEqual: function waitUntilNotEqual() {
+ return this._waitUntil(this.assertNotEqual, arguments);
+ },
+
+ waitUntilEqual: function waitUntilEqual() {
+ return this._waitUntil(this.assertEqual, arguments);
+ },
+
+ waitUntilMatches: function waitUntilMatches() {
+ return this._waitUntil(this.assertMatches, arguments);
+ },
+
+ /**
+ * Internal function that waits for an assertion to become true.
+ * @param {Function} assertionMethod
+ * Reference to a TestRunner assertion method like test.assert,
+ * test.assertEqual, ...
+ * @param {Array} args
+ * List of arguments to give to the previous assertion method.
+ * All functions in this list are going to be called to retrieve current
+ * assertion values.
+ */
+ _waitUntil: function waitUntil(assertionMethod, args) {
+ let { promise, resolve } = defer();
+ let count = 0;
+ let maxCount = this.DEFAULT_PAUSE_TIMEOUT / this.PAUSE_DELAY;
+
+ // We need to ensure that test is asynchronous
+ if (!this.waitTimeout)
+ this.waitUntilDone(this.DEFAULT_PAUSE_TIMEOUT);
+
+ let finished = false;
+ let test = this;
+
+ // capture a traceback before we go async.
+ let traceback = require("../console/traceback");
+ let stack = traceback.get();
+ stack.splice(-2, 2);
+ let currentWaitStack = traceback.format(stack);
+ let timeout = null;
+
+ function loop(stopIt) {
+ timeout = null;
+
+ // Build a mockup object to fake TestRunner API and intercept calls to
+ // pass and fail methods, in order to retrieve nice error messages
+ // and assertion result
+ let mock = {
+ pass: function (msg) {
+ test.pass(msg);
+ test.waitUntilCallback = null;
+ if (!stopIt)
+ resolve();
+ },
+ fail: function (msg) {
+ // If we are called on test timeout, we stop the loop
+ // and print which test keeps failing:
+ if (stopIt) {
+ test.console.error("test assertion never became true:\n",
+ msg + "\n",
+ currentWaitStack);
+ if (timeout)
+ timer.clearTimeout(timeout);
+ return;
+ }
+ timeout = timer.setTimeout(loop, test.PAUSE_DELAY);
+ }
+ };
+
+ // Automatically call args closures in order to build arguments for
+ // assertion function
+ let appliedArgs = [];
+ for (let i = 0, l = args.length; i < l; i++) {
+ let a = args[i];
+ if (typeof a == "function") {
+ try {
+ a = a();
+ }
+ catch(e) {
+ test.fail("Exception when calling asynchronous assertion: " + e +
+ "\n" + e.stack);
+ return resolve();
+ }
+ }
+ appliedArgs.push(a);
+ }
+
+ // Finally call assertion function with current assertion values
+ assertionMethod.apply(mock, appliedArgs);
+ }
+ loop();
+ this.waitUntilCallback = loop;
+
+ return promise;
+ },
+
+ waitUntilDone: function waitUntilDone(ms) {
+ if (ms === undefined)
+ ms = this.DEFAULT_PAUSE_TIMEOUT;
+
+ var self = this;
+
+ function tiredOfWaiting() {
+ self._logTestFailed("timed out");
+ if ("testMessage" in self.console) {
+ self.console.testMessage(false, false, self.test.name,
+ `Test timed out (after: ${self.test.last})`);
+ }
+ else {
+ self.console.error("fail:", `Timed out (after: ${self.test.last})`)
+ }
+ if (self.waitUntilCallback) {
+ self.waitUntilCallback(true);
+ self.waitUntilCallback = null;
+ }
+ self.failed++;
+ self.test.failed++;
+ self.done();
+ }
+
+ // We may already have registered a timeout callback
+ if (this.waitTimeout)
+ timer.clearTimeout(this.waitTimeout);
+
+ this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
+ },
+
+ startMany: function startMany(options) {
+ function runNextTest(self) {
+ let { tests, onDone } = options;
+
+ return tests.getNext().then((test) => {
+ if (options.stopOnError && self.test && self.test.failed) {
+ self.console.error("aborted: test failed and --stop-on-error was specified");
+ onDone(self);
+ }
+ else if (test) {
+ self.start({test: test, onDone: runNextTest});
+ }
+ else {
+ onDone(self);
+ }
+ });
+ }
+
+ return runNextTest(this).catch(console.exception);
+ },
+
+ start: function start(options) {
+ this.test = options.test;
+ this.test.passed = 0;
+ this.test.failed = 0;
+ this.test.errors = {};
+ this.test.last = 'START';
+ PromiseDebugging.clearUncaughtErrorObservers();
+ this._uncaughtErrorObserver = this._uncaughtErrorObserver.bind(this);
+ PromiseDebugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);
+
+ this.isDone = false;
+ this.onDone = function(self) {
+ if (cfxArgs.parseable)
+ self.console.print("TEST-END | " + self.test.name + "\n");
+ options.onDone(self);
+ }
+ this.waitTimeout = null;
+
+ try {
+ if (cfxArgs.parseable)
+ this.console.print("TEST-START | " + this.test.name + "\n");
+ else
+ this.console.info("executing '" + this.test.name + "'");
+
+ if(this.test.setup) {
+ this.test.setup(this);
+ }
+ this.test.testFunction(this);
+ } catch (e) {
+ this.exception(e);
+ }
+ if (this.waitTimeout === null)
+ this.done();
+ }
+};
+exports.TestRunner = TestRunner;
diff --git a/toolkit/jetpack/sdk/deprecated/window-utils.js b/toolkit/jetpack/sdk/deprecated/window-utils.js
new file mode 100644
index 000000000..93c0ab7b8
--- /dev/null
+++ b/toolkit/jetpack/sdk/deprecated/window-utils.js
@@ -0,0 +1,193 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+module.metadata = {
+ 'stability': 'deprecated'
+};
+
+const { Cc, Ci } = require('chrome');
+const events = require('../system/events');
+const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
+ getMostRecentBrowserWindow, getToplevelWindow, getMostRecentWindow } = require('../window/utils');
+const { deprecateFunction } = require('../util/deprecate');
+const { ignoreWindow } = require('sdk/private-browsing/utils');
+const { isPrivateBrowsingSupported } = require('../self');
+
+const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
+ getService(Ci.nsIWindowWatcher);
+const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
+ getService(Ci.nsIAppShellService);
+
+// Bug 834961: ignore private windows when they are not supported
+function getWindows() {
+ return windows(null, { includePrivate: isPrivateBrowsingSupported });
+}
+
+/**
+ * An iterator for XUL windows currently in the application.
+ *
+ * @return A generator that yields XUL windows exposing the
+ * nsIDOMWindow interface.
+ */
+function windowIterator() {
+ // Bug 752631: We only pass already loaded window in order to avoid
+ // breaking XUL windows DOM. DOM is broken when some JS code try
+ // to access DOM during "uninitialized" state of the related document.
+ let list = getWindows().filter(isDocumentLoaded);
+ for (let i = 0, l = list.length; i < l; i++) {
+ yield list[i];
+ }
+};
+exports.windowIterator = windowIterator;
+
+/**
+ * An iterator for browser windows currently open in the application.
+ * @returns {Function}
+ * A generator that yields browser windows exposing the `nsIDOMWindow`
+ * interface.
+ */
+function browserWindowIterator() {
+ for (let window of windowIterator()) {
+ if (isBrowser(window))
+ yield window;
+ }
+}
+exports.browserWindowIterator = browserWindowIterator;
+
+function WindowTracker(delegate) {
+ if (!(this instanceof WindowTracker)) {
+ return new WindowTracker(delegate);
+ }
+
+ this._delegate = delegate;
+
+ for (let window of getWindows())
+ this._regWindow(window);
+ windowWatcher.registerNotification(this);
+ this._onToplevelWindowReady = this._onToplevelWindowReady.bind(this);
+ events.on('toplevel-window-ready', this._onToplevelWindowReady);
+
+ require('../system/unload').ensure(this);
+
+ return this;
+};
+
+WindowTracker.prototype = {
+ _regLoadingWindow: function _regLoadingWindow(window) {
+ // Bug 834961: ignore private windows when they are not supported
+ if (ignoreWindow(window))
+ return;
+
+ window.addEventListener('load', this, true);
+ },
+
+ _unregLoadingWindow: function _unregLoadingWindow(window) {
+ // This may have no effect if we ignored the window in _regLoadingWindow().
+ window.removeEventListener('load', this, true);
+ },
+
+ _regWindow: function _regWindow(window) {
+ // Bug 834961: ignore private windows when they are not supported
+ if (ignoreWindow(window))
+ return;
+
+ if (window.document.readyState == 'complete') {
+ this._unregLoadingWindow(window);
+ this._delegate.onTrack(window);
+ } else
+ this._regLoadingWindow(window);
+ },
+
+ _unregWindow: function _unregWindow(window) {
+ if (window.document.readyState == 'complete') {
+ if (this._delegate.onUntrack)
+ this._delegate.onUntrack(window);
+ } else {
+ this._unregLoadingWindow(window);
+ }
+ },
+
+ unload: function unload() {
+ windowWatcher.unregisterNotification(this);
+ events.off('toplevel-window-ready', this._onToplevelWindowReady);
+ for (let window of getWindows())
+ this._unregWindow(window);
+ },
+
+ handleEvent: function handleEvent(event) {
+ try {
+ if (event.type == 'load' && event.target) {
+ var window = event.target.defaultView;
+ if (window)
+ this._regWindow(getToplevelWindow(window));
+ }
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ },
+
+ _onToplevelWindowReady: function _onToplevelWindowReady({subject}) {
+ let window = getToplevelWindow(subject);
+ // ignore private windows if they are not supported
+ if (ignoreWindow(window))
+ return;
+ this._regWindow(window);
+ },
+
+ observe: function observe(subject, topic, data) {
+ try {
+ var window = subject.QueryInterface(Ci.nsIDOMWindow);
+ // ignore private windows if they are not supported
+ if (ignoreWindow(window))
+ return;
+ if (topic == 'domwindowclosed')
+ this._unregWindow(window);
+ }
+ catch(e) {
+ console.exception(e);
+ }
+ }
+};
+exports.WindowTracker = WindowTracker;
+
+Object.defineProperties(exports, {
+ activeWindow: {
+ enumerable: true,
+ get: function() {
+ return getMostRecentWindow(null);
+ },
+ set: function(window) {
+ try {
+ window.focus();
+ } catch (e) {}
+ }
+ },
+ activeBrowserWindow: {
+ enumerable: true,
+ get: getMostRecentBrowserWindow
+ }
+});
+
+
+/**
+ * Returns the ID of the window's current inner window.
+ */
+exports.getInnerId = deprecateFunction(getInnerId,
+ 'require("window-utils").getInnerId is deprecated, ' +
+ 'please use require("sdk/window/utils").getInnerId instead'
+);
+
+exports.getOuterId = deprecateFunction(getOuterId,
+ 'require("window-utils").getOuterId is deprecated, ' +
+ 'please use require("sdk/window/utils").getOuterId instead'
+);
+
+exports.isBrowser = deprecateFunction(isBrowser,
+ 'require("window-utils").isBrowser is deprecated, ' +
+ 'please use require("sdk/window/utils").isBrowser instead'
+);
+
+exports.hiddenWindow = appShellService.hiddenDOMWindow;