From ac46df8daea09899ce30dc8fd70986e258c746bf Mon Sep 17 00:00:00 2001
From: "Matt A. Tobin" <email@mattatobin.com>
Date: Fri, 9 Feb 2018 06:46:43 -0500
Subject: Move Add-on SDK source to toolkit/jetpack

---
 toolkit/jetpack/sdk/deprecated/api-utils.js        | 197 +++++++
 toolkit/jetpack/sdk/deprecated/events/assembler.js |  54 ++
 toolkit/jetpack/sdk/deprecated/sync-worker.js      | 288 ++++++++++
 toolkit/jetpack/sdk/deprecated/unit-test-finder.js | 199 +++++++
 toolkit/jetpack/sdk/deprecated/unit-test.js        | 584 +++++++++++++++++++++
 toolkit/jetpack/sdk/deprecated/window-utils.js     | 193 +++++++
 6 files changed, 1515 insertions(+)
 create mode 100644 toolkit/jetpack/sdk/deprecated/api-utils.js
 create mode 100644 toolkit/jetpack/sdk/deprecated/events/assembler.js
 create mode 100644 toolkit/jetpack/sdk/deprecated/sync-worker.js
 create mode 100644 toolkit/jetpack/sdk/deprecated/unit-test-finder.js
 create mode 100644 toolkit/jetpack/sdk/deprecated/unit-test.js
 create mode 100644 toolkit/jetpack/sdk/deprecated/window-utils.js

(limited to 'toolkit/jetpack/sdk/deprecated')

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;
-- 
cgit v1.2.3