Bug 852338: Uplift Add-on SDK changeset 78a193e577555d2915206a235dbd6c6f65ed2782. a=bbajaj
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 20 Mar 2013 14:24:03 -0700
changeset 128663 4dd735a81d7f72013a78cce3bdec278db846d204
parent 128662 bb20a650077843bcb8e5ed59f1da2668ff91b763
child 128664 f5d00a1b6731b94efab1887b9a586505fa72b594
push id3536
push userdtownsend@mozilla.com
push dateWed, 20 Mar 2013 21:24:19 +0000
treeherdermozilla-aurora@4dd735a81d7f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbajaj
bugs852338
milestone21.0a2
Bug 852338: Uplift Add-on SDK changeset 78a193e577555d2915206a235dbd6c6f65ed2782. a=bbajaj
addon-sdk/source/app-extension/bootstrap.js
addon-sdk/source/data/test-iframe.js
addon-sdk/source/doc/dev-guide-source/package-spec.md
addon-sdk/source/doc/module-source/sdk/private-browsing.md
addon-sdk/source/doc/module-source/sdk/self.md
addon-sdk/source/doc/module-source/sdk/tabs.md
addon-sdk/source/doc/module-source/sdk/windows.md
addon-sdk/source/lib/sdk/content/symbiont.js
addon-sdk/source/lib/sdk/deprecated/unit-test.js
addon-sdk/source/lib/sdk/deprecated/window-utils.js
addon-sdk/source/lib/sdk/page-mod.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/panel/window.js
addon-sdk/source/lib/sdk/private-browsing.js
addon-sdk/source/lib/sdk/private-browsing/utils.js
addon-sdk/source/lib/sdk/private-browsing/window/utils.js
addon-sdk/source/lib/sdk/selection.js
addon-sdk/source/lib/sdk/self.js
addon-sdk/source/lib/sdk/system/xul-app.js
addon-sdk/source/lib/sdk/tabs/common.js
addon-sdk/source/lib/sdk/tabs/helpers.js
addon-sdk/source/lib/sdk/tabs/namespace.js
addon-sdk/source/lib/sdk/tabs/tab-fennec.js
addon-sdk/source/lib/sdk/tabs/tab-firefox.js
addon-sdk/source/lib/sdk/tabs/tab.js
addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
addon-sdk/source/lib/sdk/tabs/tabs.js
addon-sdk/source/lib/sdk/tabs/utils.js
addon-sdk/source/lib/sdk/test/assert.js
addon-sdk/source/lib/sdk/test/loader.js
addon-sdk/source/lib/sdk/util/array.js
addon-sdk/source/lib/sdk/window/browser.js
addon-sdk/source/lib/sdk/window/helpers.js
addon-sdk/source/lib/sdk/window/utils.js
addon-sdk/source/lib/sdk/windows.js
addon-sdk/source/lib/sdk/windows/dom.js
addon-sdk/source/lib/sdk/windows/firefox.js
addon-sdk/source/lib/sdk/windows/loader.js
addon-sdk/source/lib/sdk/windows/tabs-fennec.js
addon-sdk/source/lib/sdk/windows/tabs-firefox.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/python-lib/cuddlefish/manifest.py
addon-sdk/source/python-lib/cuddlefish/packaging.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/python-lib/cuddlefish/templates.py
addon-sdk/source/python-lib/cuddlefish/xpi.py
addon-sdk/source/test/addons/private-browsing-supported/main.js
addon-sdk/source/test/addons/private-browsing-supported/package.json
addon-sdk/source/test/addons/private-browsing-supported/test-page-mod.js
addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
addon-sdk/source/test/addons/private-browsing-supported/test-selection.js
addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js
addon-sdk/source/test/addons/private-browsing-supported/test-windows.js
addon-sdk/source/test/pagemod-test-helpers.js
addon-sdk/source/test/private-browsing/global.js
addon-sdk/source/test/private-browsing/helper.js
addon-sdk/source/test/private-browsing/tabs.js
addon-sdk/source/test/private-browsing/windows.js
addon-sdk/source/test/tabs/test-fennec-tabs.js
addon-sdk/source/test/test-app-strings.js
addon-sdk/source/test/test-content-worker.js
addon-sdk/source/test/test-deprecate.js
addon-sdk/source/test/test-event-core.js
addon-sdk/source/test/test-events.js
addon-sdk/source/test/test-observer-service.js
addon-sdk/source/test/test-packaging.js
addon-sdk/source/test/test-page-mod.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-private-browsing.js
addon-sdk/source/test/test-selection.js
addon-sdk/source/test/test-self.js
addon-sdk/source/test/test-system-events.js
addon-sdk/source/test/test-tabs-common.js
addon-sdk/source/test/test-test-loader.js
addon-sdk/source/test/test-unload.js
addon-sdk/source/test/test-window-utils-private-browsing.js
addon-sdk/source/test/test-window-utils.js
addon-sdk/source/test/test-window-utils2.js
addon-sdk/source/test/windows/test-firefox-windows.js
--- a/addon-sdk/source/app-extension/bootstrap.js
+++ b/addon-sdk/source/app-extension/bootstrap.js
@@ -76,16 +76,25 @@ function startup(data, reasonCode) {
     let rootURI = data.resourceURI.spec;
 
     // TODO: Maybe we should perform read harness-options.json asynchronously,
     // since we can't do anything until 'sessionstore-windows-restored' anyway.
     let options = JSON.parse(readURI(rootURI + './harness-options.json'));
 
     let id = options.jetpackID;
     let name = options.name;
+
+    // Clean the metadata
+    options.metadata[name]['permissions'] = options.metadata[name]['permissions'] || {};
+
+    // freeze the permissionss
+    Object.freeze(options.metadata[name]['permissions']);
+    // freeze the metadata
+    Object.freeze(options.metadata[name]);
+
     // Register a new resource 'domain' for this addon which is mapping to
     // XPI's `resources` folder.
     // Generate the domain name by using jetpack ID, which is the extension ID
     // by stripping common characters that doesn't work as a domain name:
     let uuidRe =
       /^\{([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\}$/;
 
     let domain = id.
@@ -137,16 +146,21 @@ function startup(data, reasonCode) {
     let branch = prefService.getBranch('extensions.modules.' + id + '.path');
     paths = branch.getChildList('', {}).reduce(function (result, name) {
       // Allows overloading of any sub folder by replacing . by / in pref name
       let path = name.substr(1).split('.').join('/');
       // Only accept overloading folder by ensuring always ending with `/`
       if (path) path += '/';
       let fileURI = branch.getCharPref(name);
 
+      // On mobile, file URI has to end with a `/` otherwise, setSubstitution
+      // takes the parent folder instead.
+      if (fileURI[fileURI.length-1] !== '/')
+        fileURI += '/';
+
       // Maps the given file:// URI to a resource:// in order to avoid various
       // failure that happens with file:// URI and be close to production env
       let resourcesURI = ioService.newURI(fileURI, null, null);
       let resName = 'extensions.modules.' + domain + '.commonjs.path' + name;
       resourceHandler.setSubstitution(resName, resourcesURI);
 
       result[path] = 'resource://' + resName + '/';
       return result;
--- a/addon-sdk/source/data/test-iframe.js
+++ b/addon-sdk/source/data/test-iframe.js
@@ -1,11 +1,13 @@
 
-var count = 0
+var count = 0;
 
 setTimeout(function() {
   window.addEventListener("message", function(msg) {
-    if (++count > 1) self.postMessage(msg.data);
+    if (++count > 1) {
+    	self.postMessage(msg.data);
+    }
     else msg.source.postMessage(msg.data, '*');
   });
 
   document.getElementById('inner').src = iframePath;
 }, 0);
--- a/addon-sdk/source/doc/dev-guide-source/package-spec.md
+++ b/addon-sdk/source/doc/dev-guide-source/package-spec.md
@@ -88,17 +88,21 @@ called `package.json`. This file is also
 * `main` - a String representing the name of a program module that is
   located in one of the top-level module directories specified by
   `lib`. Defaults to `"main"`.
 
 * `harnessClassID` - a String in the GUID format:
   `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where `x` represents a single
   hexadecimal digit. It is used as a `classID` (CID) of the "harness service"
   XPCOM component. Defaults to a random GUID generated by `cfx`.
-  
+
+* `permissions` - a set of permissions that the add-on needs.
+    * `private-browsing` - A Boolean indicating whether or not the
+      package supports private browsing.  If this value is not `true`
+      then the package will not see private windows.
 
 ## Documentation ##
 
 A package may optionally contain a
 [Markdown](http://daringfireball.net/projects/markdown/)-formatted file
 called `README.md` in its root directory. Package-browsing tools may display
 this file to developers.
 
--- a/addon-sdk/source/doc/module-source/sdk/private-browsing.md
+++ b/addon-sdk/source/doc/module-source/sdk/private-browsing.md
@@ -22,24 +22,33 @@ and <a href="modules/sdk/private-browsin
 events are all
 now deprecated due to per-window private browsing. They will continue to work
 until version 1.13 of the SDK. From version 1.13 onwards they will still exist
 but will have no effect when called.
 </div>
 
 <api name="isActive">
 @property {boolean}
-  This read-only boolean is true if global private browsing mode is turned on.
+  This read-only boolean is `true` if global private browsing mode is turned on.
 
   <div class="warning">
   This property is deprecated. It will continue to work until version 1.13 of the SDK.
-  From version 1.13 onwards it will always return false.
+  From version 1.13 onwards it will always return `false`.
   </div>
 </api>
 
+<api name="isPrivate">
+@function
+  Returns `true` if the argument is a private window or tab.
+@param [thing] {any}
+  The thing to check if it is private, only handles windows and tabs at the moment.
+  Everything else returns `false` automatically.
+  In global private browsing mode, this method returns the same value as `isActive`.
+</api>
+
 <api name="activate">
 @function
   Turns on global private browsing mode.
 
   <div class="warning">
   This function is deprecated. It will continue to work until version 1.13 of the SDK.
   From version 1.13 onwards it will still exist but will have no effect when called.
   </div>
--- a/addon-sdk/source/doc/module-source/sdk/self.md
+++ b/addon-sdk/source/doc/module-source/sdk/self.md
@@ -46,16 +46,22 @@ install
 enable
 startup
 upgrade
 downgrade
 </pre>
 
 </api>
 
+<api name="isPrivateBrowsingSupported">
+@property {boolean}
+This property indicates add-on's support for private browsing. It comes from the
+`private-browsing` property set in the `package.json` file in the main package.
+</api>
+
 <api name="data">
 @property {object}
 The `data` object is used to access data that was bundled with the add-on.
 This data lives in the main package's `data/` directory, immediately below
 the `package.json` file. All files in this directory will be copied into the
 XPI and made available through the `data` object.
 
 The [Package Specification](dev-guide/package-spec.html)
@@ -87,8 +93,10 @@ as the Panel:
     myPanel.show();
 
 @param name {string} The filename to be read, relative to the
   package's `data` directory. Each package that uses the `self` module
   will see its own `data` directory.
 @returns {String}
 </api>
 </api>
+
+
--- a/addon-sdk/source/doc/module-source/sdk/tabs.md
+++ b/addon-sdk/source/doc/module-source/sdk/tabs.md
@@ -134,16 +134,21 @@ This is a required property.
 @prop [inNewWindow] {boolean}
 If present and true, a new browser window will be opened and the URL will be
 opened in the first tab in that window. This is an optional property.
 
 @prop [inBackground] {boolean}
 If present and true, the new tab will be opened to the right of the active tab
 and will not be active. This is an optional property.
 
+@prop isPrivate {boolean}
+Boolean which will determine if a private tab should be opened.
+Private browsing mode must be supported in order to do this.
+See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information.
+
 @prop [isPinned] {boolean}
 If present and true, then the new tab will be pinned as an
 [app tab](http://support.mozilla.com/en-US/kb/what-are-app-tabs).
 
 @prop [onOpen] {function}
 A callback function that will be registered for 'open' event.
 This is an optional property.
 @prop [onClose] {function}
--- a/addon-sdk/source/doc/module-source/sdk/windows.md
+++ b/addon-sdk/source/doc/module-source/sdk/windows.md
@@ -113,16 +113,21 @@ as well as a callback for being notified
 
 If the only option being used is `url`, then a bare string URL can be passed to
 `open` instead of specifying it as a property of the `options` object.
 
 @prop url {string}
 String URL to be opened in the new window.
 This is a required property.
 
+@prop isPrivate {boolean}
+Boolean which will determine if a private window should be opened.
+Private browsing mode must be supported in order to do this.
+See the [private-browsing](modules/sdk/private-browsing.html) documentation for more information.
+
 @prop [onOpen] {function}
 A callback function that is called when the window has opened. This does not
 mean that the URL content has loaded, only that the window itself is fully
 functional and its properties can be accessed. This is an optional property.
 
 @prop [onClose] {function}
 A callback function that is called when the window will be called.
 This is an optional property.
@@ -172,16 +177,23 @@ This property is read-only.
 A live list of tabs in this window. This object has the same interface as the
 [`tabs` API](modules/sdk/tabs.html), except it contains only the
 tabs in this window, not all tabs in all windows. This property is read-only.
 </api>
 
 <api name="isPrivateBrowsing">
 @property {boolean}
 Returns `true` if the window is in private browsing mode, and `false` otherwise.
+
+<div class="warning">
+  This property is deprecated.
+  From version 1.14, please consider using following code instead:<br/>
+  <code>require("private-browsing").isPrivate(browserWindow)</code>
+</div>
+
 </api>
 
 <api name="activate">
 @method
 Makes window active, which will focus that window and bring it to the
 foreground.
 </api>
 
--- a/addon-sdk/source/lib/sdk/content/symbiont.js
+++ b/addon-sdk/source/lib/sdk/content/symbiont.js
@@ -10,16 +10,17 @@ module.metadata = {
 };
 
 const { Worker } = require('./worker');
 const { Loader } = require('./loader');
 const hiddenFrames = require('../frame/hidden-frame');
 const observers = require('../deprecated/observer-service');
 const unload = require('../system/unload');
 const { getDocShell } = require("../frame/utils");
+const { ignoreWindow } = require('../private-browsing/utils');
 
 const assetsURI = require('../self').data.url();
 
 /**
  * This trait is layered on top of `Worker` and in contrast to symbiont
  * Worker constructor requires `content` option that represents content
  * that will be loaded in the provided frame, if frame is not provided
  * Worker will create hidden one.
@@ -102,16 +103,17 @@ const Symbiont = Worker.resolve({
    * Listener to the `'frameReady"` event (emitted when `iframe` is ready).
    * Removes listener, sets right permissions to the frame and loads content.
    */
   _initFrame: function _initFrame(frame) {
     if (this._loadListener)
       this._unregisterListener();
     
     this._frame = frame;
+
     getDocShell(frame).allowJavascript = this.allow.script;
     frame.setAttribute("src", this._contentURL);
 
     // Inject `addon` object in document if we load a document from
     // one of our addon folder and if no content script are defined. bug 612726
     let isDataResource =
       typeof this._contentURL == "string" &&
       this._contentURL.indexOf(assetsURI) == 0;
@@ -137,18 +139,22 @@ const Symbiont = Worker.resolve({
     }
     
     let self = this;
     
     if ('start' == this.contentScriptWhen) {
       this._loadEvent = 'start';
       observers.add('document-element-inserted', 
         this._loadListener = function onStart(doc) {
-          
           let window = doc.defaultView;
+
+          if (ignoreWindow(window)) {
+            return;
+          }
+
           if (window && window == frame.contentWindow) {
             self._unregisterListener();
             self._onInit();
           }
           
         });
       return;
     }
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -104,16 +104,18 @@ TestRunner.prototype = {
   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)
--- a/addon-sdk/source/lib/sdk/deprecated/window-utils.js
+++ b/addon-sdk/source/lib/sdk/deprecated/window-utils.js
@@ -7,36 +7,41 @@ module.metadata = {
   'stability': 'deprecated'
 };
 
 const { Cc, Ci } = require('chrome');
 const { EventEmitter } = require('../deprecated/events');
 const { Trait } = require('../deprecated/traits');
 const { when } = require('../system/unload');
 const { getInnerId, getOuterId, windows, isDocumentLoaded, isBrowser,
-        getMostRecentBrowserWindow } = require('../window/utils');
+        getMostRecentBrowserWindow, getMostRecentWindow } = require('../window/utils');
 const errors = require('../deprecated/errors');
 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() 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 = windows().filter(isDocumentLoaded);
+  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.
@@ -55,41 +60,49 @@ exports.browserWindowIterator = browserW
 function WindowTracker(delegate) {
    if (!(this instanceof WindowTracker)) {
      return new WindowTracker(delegate);
    }
 
   this._delegate = delegate;
   this._loadingWindows = [];
 
-  for each (let window in windows())
+  for each (let window in getWindows())
     this._regWindow(window);
   windowWatcher.registerNotification(this);
 
   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;
+
     this._loadingWindows.push(window);
     window.addEventListener('load', this, true);
   },
 
   _unregLoadingWindow: function _unregLoadingWindow(window) {
     var index = this._loadingWindows.indexOf(window);
 
     if (index != -1) {
       this._loadingWindows.splice(index, 1);
       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) {
@@ -98,30 +111,33 @@ WindowTracker.prototype = {
         this._delegate.onUntrack(window);
     } else {
       this._unregLoadingWindow(window);
     }
   },
 
   unload: function unload() {
     windowWatcher.unregisterNotification(this);
-    for each (let window in windows())
+    for each (let window in getWindows())
       this._unregWindow(window);
   },
 
   handleEvent: errors.catchAndLog(function handleEvent(event) {
     if (event.type == 'load' && event.target) {
       var window = event.target.defaultView;
       if (window)
         this._regWindow(window);
     }
   }),
 
   observe: errors.catchAndLog(function observe(subject, topic, data) {
     var window = subject.QueryInterface(Ci.nsIDOMWindow);
+    // ignore private windows if they are not supported
+    if (ignoreWindow(window))
+      return;
     if (topic == 'domwindowopened')
       this._regWindow(window);
     else
       this._unregWindow(window);
   })
 };
 exports.WindowTracker = WindowTracker;
 
@@ -154,22 +170,22 @@ exports.closeOnUnload = function closeOn
   window.addEventListener('unload', onDocUnload, false);
   gDocsToClose.push(window.document);
 };
 
 Object.defineProperties(exports, {
   activeWindow: {
     enumerable: true,
     get: function() {
-      return Cc['@mozilla.org/appshell/window-mediator;1']
-        .getService(Ci.nsIWindowMediator)
-        .getMostRecentWindow(null);
+      return getMostRecentWindow(null);
     },
     set: function(window) {
-      try { window.focus(); } catch (e) { }
+      try {
+        window.focus();
+      } catch (e) {}
     }
   },
   activeBrowserWindow: {
     enumerable: true,
     get: getMostRecentBrowserWindow
   }
 });
 
--- a/addon-sdk/source/lib/sdk/page-mod.js
+++ b/addon-sdk/source/lib/sdk/page-mod.js
@@ -20,16 +20,17 @@ const { validateOptions : validate } = r
 const { Cc, Ci } = require('chrome');
 const { merge } = require('./util/object');
 const { readURISync } = require('./net/url');
 const { windowIterator } = require('./deprecated/window-utils');
 const { isBrowser, getFrames } = require('./window/utils');
 const { getTabs, getTabContentWindow, getTabForContentWindow,
         getURI: getTabURI } = require('./tabs/utils');
 const { has, hasAny } = require('./util/array');
+const { ignoreWindow } = require('sdk/private-browsing/utils');
 
 const styleSheetService = Cc["@mozilla.org/content/style-sheet-service;1"].
                             getService(Ci.nsIStyleSheetService);
 
 const USER_SHEET = styleSheetService.USER_SHEET;
 
 const io = Cc['@mozilla.org/network/io-service;1'].
               getService(Ci.nsIIOService);
@@ -348,16 +349,22 @@ const PageModManager = Registry.resolve(
     let window = document.defaultView;
     // XML documents don't have windows, and we don't yet support them.
     if (!window)
       return;
     // We apply only on documents in tabs of Firefox
     if (!getTabForContentWindow(window))
       return;
 
+    // When the tab is private, only addons with 'private-browsing' flag in
+    // their package.json can apply content script to private documents
+    if (ignoreWindow(window)) {
+      return;
+    }
+
     for (let rule in RULES)
       if (RULES[rule].test(document.URL))
         this._emit(rule, window);
   },
   off: function off(topic, listener) {
     this.removeListener(topic, listener);
     if (!this._listeners(topic).length)
       delete RULES[topic];
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -1,41 +1,42 @@
 /* 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";
 
 // The panel module currently supports only Firefox.
 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=jetpack-panel-apps
 module.metadata = {
   "stability": "stable",
   "engines": {
     "Firefox": "*"
   }
 };
 
-const { Cc, Ci } = require("chrome");
-
+const { Ci } = require("chrome");
 const { validateOptions: valid } = require('./deprecated/api-utils');
 const { Symbiont } = require('./content/content');
 const { EventEmitter } = require('./deprecated/events');
-const timer = require('./timers');
+const { setTimeout } = require('./timers');
 const runtime = require('./system/runtime');
-const { getMostRecentBrowserWindow } = require('./window/utils');
 const { getDocShell } = require("./frame/utils");
-
-const windowMediator = Cc['@mozilla.org/appshell/window-mediator;1'].
-                       getService(Ci.nsIWindowMediator);
+const { getWindow } = require('./panel/window');
+const { isPrivateBrowsingSupported } = require('./self');
+const { isWindowPBSupported } = require('./private-browsing/utils');
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
       ON_SHOW = 'popupshown',
       ON_HIDE = 'popuphidden',
       validNumber = { is: ['number', 'undefined', 'null'] };
 
+if (isPrivateBrowsingSupported && isWindowPBSupported) {
+  throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257');
+}
+
 /**
  * Emits show and hide events.
  */
 const Panel = Symbiont.resolve({
   constructor: '_init',
   _onInit: '_onSymbiontInit',
   destroy: '_symbiontDestructor',
   _documentUnload: '_workerDocumentUnload'
@@ -110,17 +111,25 @@ const Panel = Symbiont.resolve({
   _height: 240,
 
   /* Public API: Panel.isShowing */
   get isShowing() !!this._xulPanel && this._xulPanel.state == "open",
 
   /* Public API: Panel.show */
   show: function show(anchor) {
     anchor = anchor || null;
-    let document = getWindow(anchor).document;
+    let anchorWindow = getWindow(anchor);
+
+    // If there is no open window, or the anchor is in a private window
+    // then we will not be able to display the panel
+    if (!anchorWindow) {
+      return;
+    }
+
+    let document = anchorWindow.document;
     let xulPanel = this._xulPanel;
     if (!xulPanel) {
       xulPanel = this._xulPanel = document.createElementNS(XUL_NS, 'panel');
       xulPanel.setAttribute("type", "arrow");
 
       // One anonymous node has a big padding that doesn't work well with
       // Jetpack, as we would like to display an iframe that completely fills
       // the panel.
@@ -138,16 +147,17 @@ const Panel = Symbiont.resolve({
       '</bindings>';
       xulPanel.style.MozBinding = 'url("data:text/xml;charset=utf-8,' +
         document.defaultView.encodeURIComponent(binding) + '")';
 
       let frame = document.createElementNS(XUL_NS, 'iframe');
       frame.setAttribute('type', 'content');
       frame.setAttribute('flex', '1');
       frame.setAttribute('transparent', 'transparent');
+
       if (runtime.OS === "Darwin") {
         frame.style.borderRadius = "6px";
         frame.style.padding = "1px";
       }
 
       // Load an empty document in order to have an immediatly loaded iframe,
       // so swapFrameLoaders is going to work without having to wait for load.
       frame.setAttribute("src","data:;charset=utf-8,");
@@ -198,17 +208,17 @@ const Panel = Symbiont.resolve({
     // Resize the iframe instead of using panel.sizeTo
     // because sizeTo doesn't work with arrow panels
     xulPanel.firstChild.style.width = width + "px";
     xulPanel.firstChild.style.height = height + "px";
 
     // Wait for the XBL binding to be constructed
     function waitForBinding() {
       if (!xulPanel.openPopup) {
-        timer.setTimeout(waitForBinding, 50);
+        setTimeout(waitForBinding, 50);
         return;
       }
       xulPanel.openPopup(anchor, position, x, y);
     }
     waitForBinding();
 
     return this._public;
   },
@@ -358,45 +368,8 @@ const Panel = Symbiont.resolve({
       // document
       this._workerCleanup();
       this._initFrame(this._frame);
     }
   }
 });
 exports.Panel = function(options) Panel(options)
 exports.Panel.prototype = Panel.prototype;
-
-function getWindow(anchor) {
-  let window;
-
-  if (anchor) {
-    let anchorWindow = anchor.ownerDocument.defaultView.top;
-    let anchorDocument = anchorWindow.document;
-
-    let enumerator = windowMediator.getEnumerator("navigator:browser");
-    while (enumerator.hasMoreElements()) {
-      let enumWindow = enumerator.getNext();
-
-      // Check if the anchor is in this browser window.
-      if (enumWindow == anchorWindow) {
-        window = anchorWindow;
-        break;
-      }
-
-      // Check if the anchor is in a browser tab in this browser window.
-      let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
-      if (browser) {
-        window = enumWindow;
-        break;
-      }
-
-      // Look in other subdocuments (sidebar, etc.)?
-    }
-  }
-
-  // If we didn't find the anchor's window (or we have no anchor),
-  // return the most recent browser window.
-  if (!window)
-    window = getMostRecentBrowserWindow();
-
-  return window;
-}
-
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/panel/window.js
@@ -0,0 +1,51 @@
+/* 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 { getMostRecentBrowserWindow, windows: getWindows } = require('../window/utils');
+const { ignoreWindow } = require('../private-browsing/utils');
+const { isPrivateBrowsingSupported } = require('../self');
+
+function getWindow(anchor) {
+  let window;
+  let windows = getWindows("navigator:browser", {
+    includePrivate: isPrivateBrowsingSupported
+  });
+
+  if (anchor) {
+    let anchorWindow = anchor.ownerDocument.defaultView.top;
+    let anchorDocument = anchorWindow.document;
+
+    // loop thru supported windows
+    for each(let enumWindow in windows) {
+      // Check if the anchor is in this browser window.
+      if (enumWindow == anchorWindow) {
+        window = anchorWindow;
+        break;
+      }
+
+      // Check if the anchor is in a browser tab in this browser window.
+      let browser = enumWindow.gBrowser.getBrowserForDocument(anchorDocument);
+      if (browser) {
+        window = enumWindow;
+        break;
+      }
+
+      // Look in other subdocuments (sidebar, etc.)?
+    }
+  }
+
+  // If we didn't find the anchor's window (or we have no anchor),
+  // return the most recent browser window.
+  if (!window)
+    window = getMostRecentBrowserWindow();
+
+  // if the window is not supported, then it should be ignored
+  if (ignoreWindow(window)) {
+  	return null;
+  }
+
+  return window;
+}
+exports.getWindow = getWindow;
--- a/addon-sdk/source/lib/sdk/private-browsing.js
+++ b/addon-sdk/source/lib/sdk/private-browsing.js
@@ -3,44 +3,79 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 module.metadata = {
   "stability": "stable"
 };
 
 const { setMode, getMode, on: onStateChange } = require('./private-browsing/utils');
+const { isWindowPrivate } = require('./window/utils');
 const { emit, on, once, off } = require('./event/core');
 const { when: unload } = require('./system/unload');
 const { deprecateUsage, deprecateFunction, deprecateEvent } = require('./util/deprecate');
+const { getOwnerWindow } = require('./private-browsing/window/utils');
 
 onStateChange('start', function onStart() {
   emit(exports, 'start');
 });
 
 onStateChange('stop', function onStop() {
   emit(exports, 'stop');
 });
 
 Object.defineProperty(exports, "isActive", {
-	get: deprecateFunction(getMode, 'require("private-browsing").isActive is deprecated.')
+  get: deprecateFunction(getMode, 'require("private-browsing").isActive is deprecated.')
 });
 
 exports.activate = function activate() setMode(true);
 exports.deactivate = function deactivate() setMode(false);
 
 exports.on = deprecateEvents(on.bind(null, exports));
 exports.once = deprecateEvents(once.bind(null, exports));
 exports.removeListener = deprecateEvents(function removeListener(type, listener) {
   // Note: We can't just bind `off` as we do it for other methods cause skipping
   // a listener argument will remove all listeners for the given event type
   // causing misbehavior. This way we make sure all arguments are passed.
   off(exports, type, listener);
 });
 
+exports.isPrivate = function(thing) {
+  // if thing is defined, and we can find a window for it
+  // then check if the window is private
+  if (!!thing) {
+    // if the thing is a window, and the window is private
+    // then return true
+    if (isWindowPrivate(thing)) {
+      return true;
+    }
+
+    // does the thing have an associated tab?
+    // page-mod instances do..
+    if (thing.tab) {
+      let tabWindow = getOwnerWindow(thing.tab);
+      if (tabWindow) {
+        let isThingPrivate = isWindowPrivate(tabWindow);
+        if (isThingPrivate)
+          return isThingPrivate;
+      }
+    }
+
+    // can we find an associated window?
+    let window = getOwnerWindow(thing);
+    if (window)
+      return isWindowPrivate(window);
+  }
+
+  // if we get here, and global private browsing
+  // is available, and it is true, then return
+  // true otherwise false is returned here
+  return getMode();
+};
+
 function deprecateEvents(func) deprecateEvent(
   func,
    'The require("private-browsing") module\'s "start" and "stop" events are deprecated.',
   ['start', 'stop']
 );
 
 // Make sure listeners are cleaned up.
 unload(function() off(exports));
--- a/addon-sdk/source/lib/sdk/private-browsing/utils.js
+++ b/addon-sdk/source/lib/sdk/private-browsing/utils.js
@@ -6,60 +6,62 @@
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci, Cu } = require('chrome');
 const { defer } = require('../lang/functional');
 const { emit, on, once, off } = require('../event/core');
 const { when: unload } = require('../system/unload');
-const { getWindowLoadingContext, windows } = require('../window/utils');
-const { WindowTracker } = require("../deprecated/window-utils");
 const events = require('../system/events');
 const { deprecateFunction } = require('../util/deprecate');
+const { isOneOf, is, satisfiesVersion, version } = require('../system/xul-app');
+const { isWindowPrivate } = require('../window/utils');
+const { isPrivateBrowsingSupported } = require('../self');
 
 let deferredEmit = defer(emit);
 let pbService;
 let PrivateBrowsingUtils;
 
 // Private browsing is only supported in Fx
-if (require("../system/xul-app").is("Firefox")) {
+if (isOneOf(['Firefox', 'Fennec'])) {
   // get the nsIPrivateBrowsingService if it exists
   try {
     pbService = Cc["@mozilla.org/privatebrowsing;1"].
                 getService(Ci.nsIPrivateBrowsingService);
 
     // a dummy service exists for the moment (Fx20 atleast), but will be removed eventually
     // ie: the service will exist, but it won't do anything and the global private browing
     //     feature is not really active.  See Bug 818800 and Bug 826037
     if (!('privateBrowsingEnabled' in pbService))
       pbService = undefined;
-  } catch(e) { /* Private Browsing Service has been removed (Bug 818800) */ }
+  }
+  catch(e) { /* Private Browsing Service has been removed (Bug 818800) */ }
 
   try {
     PrivateBrowsingUtils = Cu.import('resource://gre/modules/PrivateBrowsingUtils.jsm', {}).PrivateBrowsingUtils;
   }
   catch(e) { /* if this file DNE then an error will be thrown */ }
 }
 
-function isWindowPrivate(win) {
-  // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
-  // and the app is Firefox, then assume per-window private browsing is
-  // enabled.
-  return win instanceof Ci.nsIDOMWindow &&
-         isWindowPBSupported &&
-         PrivateBrowsingUtils.isWindowPrivate(win);
-}
-exports.isWindowPrivate = isWindowPrivate;
-
 // checks that global private browsing is implemented
-let isGlobalPBSupported = exports.isGlobalPBSupported =  !!pbService;
+let isGlobalPBSupported = exports.isGlobalPBSupported = !!pbService && is('Firefox');
 
 // checks that per-window private browsing is implemented
-let isWindowPBSupported = exports.isWindowPBSupported = !isGlobalPBSupported && !!PrivateBrowsingUtils;
+let isWindowPBSupported = exports.isWindowPBSupported =
+                          !pbService && !!PrivateBrowsingUtils && is('Firefox');
+
+// checks that per-tab private browsing is implemented
+let isTabPBSupported = exports.isTabPBSupported =
+                       !pbService && !!PrivateBrowsingUtils && is('Fennec') && satisfiesVersion(version, '>=20.0*');
+                       
+function ignoreWindow(window) {
+  return !isPrivateBrowsingSupported && isWindowPrivate(window);
+}
+exports.ignoreWindow = ignoreWindow;
 
 function onChange() {
   // Emit event with in next turn of event loop.
   deferredEmit(exports, pbService.privateBrowsingEnabled ? 'start' : 'stop');
 }
 
 // Currently, only Firefox implements the private browsing service.
 if (isGlobalPBSupported) {
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/private-browsing/window/utils.js
@@ -0,0 +1,33 @@
+/* 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': 'unstable'
+};
+
+const privateNS = require('../../core/namespace').ns();
+
+function getOwnerWindow(thing) {
+  try {
+    // check for and return associated window
+    let fn = (privateNS(thing.prototype) || privateNS(thing) || {}).getOwnerWindow;
+
+    if (fn)
+      return fn.apply(fn, [thing].concat(arguments));
+  }
+  // stuff like numbers and strings throw errors with namespaces
+  catch(e) {}
+  // default
+  return undefined;
+}
+getOwnerWindow.define = function(Type, fn) {
+  privateNS(Type.prototype).getOwnerWindow = fn;
+}
+
+getOwnerWindow.implement = function(instance, fn) {
+  privateNS(instance).getOwnerWindow = fn;
+}
+
+exports.getOwnerWindow = getOwnerWindow;
--- a/addon-sdk/source/lib/sdk/selection.js
+++ b/addon-sdk/source/lib/sdk/selection.js
@@ -13,20 +13,20 @@ module.metadata = {
 
 const { Ci, Cc } = require("chrome"),
     { setTimeout } = require("./timers"),
     { emit, off } = require("./event/core"),
     { Class, obscure } = require("./core/heritage"),
     { EventTarget } = require("./event/target"),
     { ns } = require("./core/namespace"),
     { when: unload } = require("./system/unload"),
+    { ignoreWindow } = require('./private-browsing/utils'),
     { getTabs, getTabContentWindow, getTabForContentWindow,
       getAllTabContentWindows } = require('./tabs/utils'),
-    { getMostRecentBrowserWindow,
-      windows, getFocusedWindow, getFocusedElement } = require("./window/utils"),
+    winUtils = require("./window/utils"),
     events = require("./system/events");
 
 // The selection types
 const HTML = 0x01,
       TEXT = 0x02,
       DOM  = 0x03; // internal use only
 
 // A more developer-friendly message than the caught exception when is not
@@ -96,32 +96,62 @@ const selectionListener = {
  * Empty selections are skipped - see `safeGetRange` for further details.
  *
  * If discontiguous selections are in a text field, only the first one
  * is returned because the text field selection APIs doesn't support
  * multiple selections.
  */
 function iterator() {
     let selection = getSelection(DOM);
-    let count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);
+    let count = 0;
+
+    if (selection)
+      count = selection.rangeCount || (getElementWithSelection() ? 1 : 0);
 
     for (let i = 0; i < count; i++) {
       let sel = Selection(i);
 
       if (sel.text)
         yield Selection(i);
     }
 }
 
 const selectionIterator = obscure({
   __iterator__: iterator, // for...in; for each...in
   iterator: iterator // for....of
 });
 
 /**
+ * Returns the most recent focused window.
+ * if private browsing window is most recent and not supported,
+ * then ignore it and return `null`, because the focused window
+ * can't be targeted.
+ */
+function getFocusedWindow() {
+  let window = winUtils.getFocusedWindow();
+
+  return ignoreWindow(window) ? null : window;
+}
+
+/**
+ * Returns the focused element in the most recent focused window
+ * if private browsing window is most recent and not supported,
+ * then ignore it and return `null`, because the focused element
+ * can't be targeted.
+ */
+function getFocusedElement() {
+  let element = winUtils.getFocusedElement();
+
+  if (!element || ignoreWindow(element.ownerDocument.defaultView))
+    return null;
+
+  return element;
+}
+
+/**
  * Returns the current selection from most recent content window. Depending on
  * the specified |type|, the value returned can be a string of text, stringified
  * HTML, or a DOM selection object as described at
  * https://developer.mozilla.org/en/DOM/Selection.
  *
  * @param type
  *        Specifies the return type of the selection. Valid values are the one
  *        of the constants HTML, TEXT, or DOM.
@@ -343,20 +373,21 @@ function removeSelectionListener(window)
   window.removeEventListener("select", selectionListener.onSelect, true);
 
   delete selections(window).selection;
 };
 
 function onContent(event) {
   let window = event.subject.defaultView;
 
-  // We are not interested in documents without valid defaultView (e.g. XML), or
-  // not in a tab (e.g. Panel).
-   if (window && getTabForContentWindow(window))
+  // We are not interested in documents without valid defaultView (e.g. XML)
+  // that aren't in a tab (e.g. Panel); or in private windows
+   if (window && getTabForContentWindow(window) && !ignoreWindow(window)) {
     addSelectionListener(window);
+  }
 }
 
 // Adds Selection listener to new documents
 // Note that strong reference is needed for documents that are loading slowly or
 // where the server didn't close the connection (e.g. "comet").
 events.on("document-element-inserted", onContent, true);
 
 // Adds Selection listeners to existing documents
--- a/addon-sdk/source/lib/sdk/self.js
+++ b/addon-sdk/source/lib/sdk/self.js
@@ -1,22 +1,20 @@
 /* vim:st=2:sts=2:sw=2:
  * 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": "stable"
 };
 
-
 const { CC } = require('chrome');
-const { id, name, prefixURI, rootURI,
+const { id, name, prefixURI, rootURI, metadata,
         version, loadReason } = require('@loader/options');
 
 const { readURISync } = require('./net/url');
 
 const addonDataURI = prefixURI + name + '/data/';
 
 function uri(path) {
   return addonDataURI + (path || '');
@@ -34,8 +32,10 @@ exports.version = version;
 // If `rootURI` is jar:file://...!/ than add-on is packed.
 exports.packed = rootURI.indexOf('jar:') === 0
 exports.data = Object.freeze({
   url: uri,
   load: function read(path) {
     return readURISync(uri(path));
   }
 });
+exports.isPrivateBrowsingSupported = ((metadata.permissions || {})['private-browsing'] === true) ?
+                                     true : false;
--- a/addon-sdk/source/lib/sdk/system/xul-app.js
+++ b/addon-sdk/source/lib/sdk/system/xul-app.js
@@ -1,12 +1,11 @@
 /* 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": "experimental"
 };
 
 var { Cc, Ci } = require("chrome");
 
@@ -35,28 +34,30 @@ var ids = exports.ids = {
   Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
   Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
   Sunbird: "{718e30fb-e89b-41dd-9da7-e25a45638b28}",
   SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
   Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
   Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
 };
 
-var is = exports.is = function is(name) {
+function is(name) {
   if (!(name in ids))
     throw new Error("Unkown Mozilla Application: " + name);
   return ID == ids[name];
 };
+exports.is = is;
 
-var isOneOf = exports.isOneOf = function isOneOf(names) {
+function isOneOf(names) {
   for (var i = 0; i < names.length; i++)
     if (is(names[i]))
       return true;
   return false;
 };
+exports.isOneOf = isOneOf;
 
 /**
  * Use this to check whether the given version (e.g. xulApp.platformVersion)
  * is in the given range. Versions must be in version comparator-compatible
  * format. See MDC for details:
  * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIVersionComparator
  */
 var versionInRange = exports.versionInRange =
@@ -98,17 +99,16 @@ function normalizeRange(range) {
  *
  * @param {String} comparison
  *  The comparison operator
  *
  * @param {String} compareVersion
  *  A version to compare
  */
 function compareVersion(version, comparison, compareVersion) {
-
   let hasWildcard = compareVersion.indexOf("*") !== -1;
 
   comparison = comparison || "=";
 
   if (hasWildcard) {
     switch (comparison) {
       case "=":
         let zeroVersion = compareVersion.replace(reSubInfinity, ".0");
@@ -176,10 +176,9 @@ function satisfiesVersion(version, versi
 
     let [, lowMod, lowVer, highMod, highVer] = matches;
 
     return compareVersion(version, lowMod, lowVer) && (highVer !== undefined
       ? compareVersion(version, highMod, highVer)
       : true);
   });
 }
-
 exports.satisfiesVersion = satisfiesVersion;
--- a/addon-sdk/source/lib/sdk/tabs/common.js
+++ b/addon-sdk/source/lib/sdk/tabs/common.js
@@ -6,18 +6,22 @@
 const { validateOptions } = require('../deprecated/api-utils');
 
 function Options(options) {
   if ('string' === typeof options)
     options = { url: options };
 
   return validateOptions(options, {
     url: { is: ["string"] },
-    inBackground: { is: ["undefined", "boolean"] },
+    inBackground: {
+      map: function(v) !!v,
+      is: ["undefined", "boolean"]
+    },
     isPinned: { is: ["undefined", "boolean"] },
+    isPrivate: { is: ["undefined", "boolean"] },
     onOpen: { is: ["undefined", "function"] },
     onClose: { is: ["undefined", "function"] },
     onReady: { is: ["undefined", "function"] },
     onActivate: { is: ["undefined", "function"] },
     onDeactivate: { is: ["undefined", "function"] }
   });
 }
 exports.Options = Options;
--- a/addon-sdk/source/lib/sdk/tabs/helpers.js
+++ b/addon-sdk/source/lib/sdk/tabs/helpers.js
@@ -1,17 +1,41 @@
 /* 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 { getTabForContentWindow } = require('./utils');
+module.metadata = {
+  'stability': 'unstable'
+};
+
+
+// NOTE: This file should only export Tab instances
+
+
+const { getTabForContentWindow, getTabForBrowser: getRawTabForBrowser } = require('./utils');
 const { Tab } = require('./tab');
+const { rawTabNS } = require('./namespace');
 
 function getTabForWindow(win) {
   let tab = getTabForContentWindow(win);
   // We were unable to find the related tab!
   if (!tab)
     return null;
 
-  return Tab({ tab: tab });
+  return getTabForRawTab(tab) || Tab({ tab: tab });
 }
 exports.getTabForWindow = getTabForWindow;
+
+// only works on fennec atm
+function getTabForRawTab(rawTab) {
+  let tab = rawTabNS(rawTab).tab;
+  if (tab) {
+    return tab;
+  }
+  return null;
+}
+exports.getTabForRawTab = getTabForRawTab;
+
+function getTabForBrowser(browser) {
+  return getTabForRawTab(getRawTabForBrowser(browser));
+}
+exports.getTabForBrowser = getTabForBrowser;
--- a/addon-sdk/source/lib/sdk/tabs/namespace.js
+++ b/addon-sdk/source/lib/sdk/tabs/namespace.js
@@ -2,8 +2,9 @@
  * 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';
 
 let { ns } = require('../core/namespace');
 
 exports.tabsNS = ns();
 exports.tabNS = ns();
+exports.rawTabNS = ns();
--- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js
+++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js
@@ -1,35 +1,49 @@
 /* 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 { Cc, Ci } = require('chrome');
 const { Class } = require('../core/heritage');
-const { tabNS } = require('./namespace');
+const { tabNS, rawTabNS } = require('./namespace');
 const { EventTarget } = require('../event/target');
-const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL,
+const { activateTab, getTabTitle, setTabTitle, closeTab, getTabURL, getContentWindowForTab,
+        getTabForBrowser,
         setTabURL, getOwnerWindow, getTabContentType, getTabId } = require('./utils');
 const { emit } = require('../event/core');
+const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
 const { when: unload } = require('../system/unload');
+const { EVENTS } = require('./events');
 
-const { EVENTS } = require('./events');
 const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
 
 const Tab = Class({
   extends: EventTarget,
   initialize: function initialize(options) {
     options = options.tab ? options : { tab: options };
+    let tab = options.tab;
 
     EventTarget.prototype.initialize.call(this, options);
     let tabInternals = tabNS(this);
+    rawTabNS(tab).tab = this;
 
-    tabInternals.window = options.window || getOwnerWindow(options.tab);
-    tabInternals.tab = options.tab;
+    let window = tabInternals.window = options.window || getOwnerWindow(tab);
+    tabInternals.tab = tab;
+
+    // TabReady
+    let onReady = tabInternals.onReady = onTabReady.bind(this);
+    tab.browser.addEventListener(EVENTS.ready.dom, onReady, false);
+
+    // TabClose
+    let onClose = tabInternals.onClose = onTabClose.bind(this);
+    window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onClose, false);
+
+    unload(cleanupTab.bind(null, this));
   },
 
   /**
    * The title of the page currently loaded in the tab.
    * Changing this property changes an actual title.
    * @type {String}
    */
   get title() getTabTitle(tabNS(this).tab),
@@ -138,8 +152,47 @@ const Tab = Class({
   /**
    * Reload the tab
    */
   reload: function reload() {
     tabNS(this).tab.browser.reload();
   }
 });
 exports.Tab = Tab;
+
+function cleanupTab(tab) {
+  let tabInternals = tabNS(tab);
+  if (!tabInternals.tab)
+    return;
+
+  if (tabInternals.tab.browser) {
+    tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false);
+  }
+  tabInternals.onReady = null;
+  tabInternals.window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, tabInternals.onClose, false);
+  tabInternals.onClose = null;
+  rawTabNS(tabInternals.tab).tab = null;
+  tabInternals.tab = null;
+  tabInternals.window = null;
+}
+
+function onTabReady(event) {
+  let win = event.target.defaultView;
+
+  // ignore frames
+  if (win === win.top) {
+    emit(this, 'ready', this);
+  }
+}
+
+// TabClose
+function onTabClose(event) {
+  let rawTab = getTabForBrowser(event.target);
+  if (tabNS(this).tab !== rawTab)
+    return;
+
+  emit(this, EVENTS.close.name, this);
+  cleanupTab(this);
+};
+
+getPBOwnerWindow.define(Tab, function(tab) {
+  return getContentWindowForTab(tabNS(tab).tab);
+});
--- a/addon-sdk/source/lib/sdk/tabs/tab-firefox.js
+++ b/addon-sdk/source/lib/sdk/tabs/tab-firefox.js
@@ -6,16 +6,18 @@
 const { Trait } = require("../deprecated/traits");
 const { EventEmitter } = require("../deprecated/events");
 const { defer } = require("../lang/functional");
 const { EVENTS } = require("./events");
 const { getThumbnailURIForWindow } = require("../content/thumbnail");
 const { getFaviconURIForLocation } = require("../io/data");
 const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle,
         getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils');
+const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
+const viewNS = require('sdk/core/namespace').ns();
 
 // Array of the inner instances of all the wrapped tabs.
 const TABS = [];
 
 /**
  * Trait used to create tab wrappers.
  */
 const TabTrait = Trait.compose(EventEmitter, {
@@ -45,25 +47,31 @@ const TabTrait = Trait.compose(EventEmit
     }
 
     this.on(EVENTS.close.name, this.destroy.bind(this));
     this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
 
     if (options.isPinned)
       this.pin();
 
+    viewNS(this._public).tab = this._tab;
+    getPBOwnerWindow.implement(this._public, getChromeTab);
+
     // Since we will have to identify tabs by a DOM elements facade function
     // is used as constructor that collects all the instances and makes sure
     // that they more then one wrapper is not created per tab.
     return this;
   },
   destroy: function destroy() {
     this._removeAllListeners();
     if (this._tab) {
-      this._browser.removeEventListener(EVENTS.ready.dom, this._onReady, true);
+      let browser = this._browser;
+      // The tab may already be removed from DOM -or- not yet added
+      if (browser)
+        browser.removeEventListener(EVENTS.ready.dom, this._onReady, true);
       this._tab = null;
       TABS.splice(TABS.indexOf(this), 1);
     }
   },
 
   /**
    * Internal listener that emits public event 'ready' when the page of this
    * tab is loaded.
@@ -207,20 +215,29 @@ const TabTrait = Trait.compose(EventEmit
    */
   reload: function reload() {
     if (!this._tab)
       return;
     this._window.gBrowser.reloadTab(this._tab);
   }
 });
 
-function Tab(options) {
+function getChromeTab(tab) {
+  return getOwnerWindow(viewNS(tab).tab);
+}
+
+function Tab(options, existingOnly) {
   let chromeTab = options.tab;
   for each (let tab in TABS) {
     if (chromeTab == tab._tab)
       return tab._public;
   }
+  // If called asked to return only existing wrapper,
+  // we should return null here as no matching Tab object has been found
+  if (existingOnly)
+    return null;
+
   let tab = TabTrait(options);
   TABS.push(tab);
   return tab._public;
 }
 Tab.prototype = TabTrait.prototype;
 exports.Tab = Tab;
--- a/addon-sdk/source/lib/sdk/tabs/tab.js
+++ b/addon-sdk/source/lib/sdk/tabs/tab.js
@@ -6,14 +6,14 @@
 module.metadata = {
   'stability': 'unstable',
   'engines': {
     'Firefox': '*',
     'Fennec': '*'
   }
 };
 
-if (require('../system/xul-app').is('Firefox')) {
+if (require('../system/xul-app').name == 'Fennec') {
+  module.exports = require('./tab-fennec');
+}
+else {
   module.exports = require('./tab-firefox');
 }
-else if (require('../system/xul-app').is('Fennec')) {
-  module.exports = require('./tab-fennec');
-}
--- a/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
+++ b/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
@@ -1,26 +1,67 @@
 /* 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';
 
 // TODO: BUG 792670 - remove dependency below
-const { browserWindows } = require('../windows');
+const { browserWindows: windows } = require('../windows');
 const { tabs } = require('../windows/tabs-firefox');
+const { isPrivate } = require('../private-browsing');
+const { isWindowPBSupported } = require('../private-browsing/utils')
+const { isPrivateBrowsingSupported } = require('sdk/self');
+
+const supportPrivateTabs = isPrivateBrowsingSupported && isWindowPBSupported;
 
 Object.defineProperties(tabs, {
   open: { value: function open(options) {
-    if (options.inNewWindow)
+    if (options.inNewWindow) {
         // `tabs` option is under review and may be removed.
-        return browserWindows.open({ tabs: [ options ] });
+        windows.open({
+          tabs: [ options ],
+          isPrivate: options.isPrivate
+        });
+        return undefined;
+    }
     // Open in active window if new window was not required.
-    return browserWindows.activeWindow.tabs.open(options);
+
+    let activeWindow = windows.activeWindow;
+    let privateState = !!options.isPrivate;
+    // if the active window is in the state that we need then use it
+    if (!supportPrivateTabs || privateState === isPrivate(activeWindow)) {
+      activeWindow.tabs.open(options);
+    }
+    else {
+      // find a window in the state that we need
+      let window = getWindow(privateState);
+      if (window) {
+        window.tabs.open(options);
+      }
+      // open a window in the state that we need
+      else {
+        windows.open({
+          tabs: [ options ],
+          isPrivate: options.isPrivate
+        });
+      }
+    }
+
+    return undefined;
   }}
 });
 
+function getWindow(privateState) {
+  for each (let window in windows) {
+    if (privateState === isPrivate(window)) {
+      return window;
+    }
+  }
+  return null;
+}
+
 // Workaround for bug 674195. Freezing objects from other compartments fail,
 // so we use `Object.freeze` from the same component as objects
 // `hasOwnProperty`. Since `hasOwnProperty` here will be from other component
 // we override it to support our workaround.
 module.exports = Object.create(tabs, {
   isPrototypeOf: { value: Object.prototype.isPrototypeOf }
 });
--- a/addon-sdk/source/lib/sdk/tabs/tabs.js
+++ b/addon-sdk/source/lib/sdk/tabs/tabs.js
@@ -6,14 +6,14 @@
 module.metadata = {
   'stability': 'unstable',
   'engines': {
     'Firefox': '*',
     'Fennec': '*'
   }
 };
 
-if (require('../system/xul-app').is('Firefox')) {
+if (require('../system/xul-app').name == 'Fennec') {
+  module.exports = require('../windows/tabs-fennec').tabs;
+}
+else {
   module.exports = require('./tabs-firefox');
 }
-else if (require('../system/xul-app').is('Fennec')) {
-  module.exports = require('../windows/tabs-fennec').tabs;
-}
--- a/addon-sdk/source/lib/sdk/tabs/utils.js
+++ b/addon-sdk/source/lib/sdk/tabs/utils.js
@@ -3,19 +3,27 @@
  * 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': 'unstable'
 };
 
+
+// NOTE: This file should only deal with xul/native tabs
+
+
+const { Ci } = require('chrome');
 const { defer } = require("../lang/functional");
 const { windows, isBrowser } = require('../window/utils');
-const { Ci } = require('chrome');
+const { isPrivateBrowsingSupported } = require('../self');
+
+// Bug 834961: ignore private windows when they are not supported
+function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported });
 
 function activateTab(tab, window) {
   let gBrowser = getTabBrowserForTab(tab);
 
   // normal case
   if (gBrowser) {
     gBrowser.selectedTab = tab;
   }
@@ -43,49 +51,50 @@ exports.getTabContainer = getTabContaine
  *
  * @param {nsIWindow} [window]
  *    A reference to a window
  *
  * @returns {Array} an array of Tab objects
  */
 function getTabs(window) {
   if (arguments.length === 0) {
-    return windows().filter(isBrowser).reduce(function(tabs, window) {
+    return getWindows().filter(isBrowser).reduce(function(tabs, window) {
       return tabs.concat(getTabs(window))
     }, []);
   }
 
   // fennec
   if (window.BrowserApp)
     return window.BrowserApp.tabs;
 
   // firefox - default
   return Array.slice(getTabContainer(window).children);
 }
 exports.getTabs = getTabs;
 
 function getActiveTab(window) {
-  return window.gBrowser.selectedTab;
+  return getSelectedTab(window);
 }
 exports.getActiveTab = getActiveTab;
 
 function getOwnerWindow(tab) {
   // normal case
   if (tab.ownerDocument)
     return tab.ownerDocument.defaultView;
 
   // try fennec case
   return getWindowHoldingTab(tab);
 }
 exports.getOwnerWindow = getOwnerWindow;
 
 // fennec
 function getWindowHoldingTab(rawTab) {
-  for each (let window in windows()) {
-    // this function may be called when not using fennec
+  for each (let window in getWindows()) {
+    // this function may be called when not using fennec,
+    // but BrowserApp is only defined on Fennec
     if (!window.BrowserApp)
       continue;
 
     for each (let tab in window.BrowserApp.tabs) {
       if (tab === rawTab)
         return window;
     }
   }
@@ -95,20 +104,27 @@ function getWindowHoldingTab(rawTab) {
 
 function openTab(window, url, options) {
   options = options || {};
 
   // fennec?
   if (window.BrowserApp) {
     return window.BrowserApp.addTab(url, {
       selected: options.inBackground ? false : true,
-      pinned: options.isPinned || false
+      pinned: options.isPinned || false,
+      isPrivate: options.isPrivate || false
     });
   }
-  return window.gBrowser.addTab(url);
+
+  // firefox
+  let newTab = window.gBrowser.addTab(url);
+  if (!options.inBackground) {
+    activateTab(newTab);
+  }
+  return newTab;
 };
 exports.openTab = openTab;
 
 function isTabOpen(tab) {
   // try normal case then fennec case
   return !!((tab.linkedBrowser) || getWindowHoldingTab(tab));
 }
 exports.isTabOpen = isTabOpen;
@@ -145,25 +161,30 @@ exports.getTabBrowserForTab = getTabBrow
 function getBrowserForTab(tab) {
   if (tab.browser) // fennec
     return tab.browser;
 
   return tab.linkedBrowser;
 }
 exports.getBrowserForTab = getBrowserForTab;
 
+
+function getContentWindowForTab(tab) {
+  return getBrowserForTab(tab).contentWindow;
+}
+exports.getContentWindowForTab = getContentWindowForTab;
+
 function getTabId(tab) {
   if (tab.browser) // fennec
     return tab.id
 
   return String.split(tab.linkedPanel, 'panel').pop();
 }
 exports.getTabId = getTabId;
 
-
 function getTabTitle(tab) {
   return getBrowserForTab(tab).contentDocument.title || tab.label || "";
 }
 exports.getTabTitle = getTabTitle;
 
 function setTabTitle(tab, title) {
   title = String(title);
   if (tab.browser)
@@ -180,51 +201,69 @@ exports.getTabContentWindow = getTabCont
 /**
  * Returns all tabs' content windows across all the browsers' windows
  */
 function getAllTabContentWindows() {
   return getTabs().map(getTabContentWindow);
 }
 exports.getAllTabContentWindows = getAllTabContentWindows;
 
+// gets the tab containing the provided window
 function getTabForContentWindow(window) {
   // Retrieve the topmost frame container. It can be either <xul:browser>,
   // <xul:iframe/> or <html:iframe/>. But in our case, it should be xul:browser.
   let browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShell)
                    .chromeEventHandler;
+
   // Is null for toplevel documents
-  if (!browser)
+  if (!browser) {
     return false;
+  }
+
   // Retrieve the owner window, should be browser.xul one
   let chromeWindow = browser.ownerDocument.defaultView;
 
   // Ensure that it is top-level browser window.
   // We need extra checks because of Mac hidden window that has a broken
   // `gBrowser` global attribute.
   if ('gBrowser' in chromeWindow && chromeWindow.gBrowser &&
       'browsers' in chromeWindow.gBrowser) {
     // Looks like we are on Firefox Desktop
     // Then search for the position in tabbrowser in order to get the tab object
     let browsers = chromeWindow.gBrowser.browsers;
     let i = browsers.indexOf(browser);
     if (i !== -1)
       return chromeWindow.gBrowser.tabs[i];
     return null;
   }
+  // Fennec
   else if ('BrowserApp' in chromeWindow) {
-    // Looks like we are on Firefox Mobile
-    return chromeWindow.BrowserApp.getTabForWindow(window)
+    return getTabForWindow(window);
   }
 
   return null;
 }
 exports.getTabForContentWindow = getTabForContentWindow;
 
+// used on fennec
+function getTabForWindow(window) {
+  for each (let { BrowserApp } in getWindows()) {
+    if (!BrowserApp)
+      continue;
+
+    for each (let tab in BrowserApp.tabs) {
+      if (tab.browser.contentWindow == window.top)
+        return tab;
+    }
+  }
+  return null; 
+}
+
 function getTabURL(tab) {
   if (tab.browser) // fennec
     return String(tab.browser.currentURI.spec);
   return String(getBrowserForTab(tab).currentURI.spec);
 }
 exports.getTabURL = getTabURL;
 
 function setTabURL(tab, url) {
@@ -247,8 +286,24 @@ exports.getTabContentType = getTabConten
 function getSelectedTab(window) {
   if (window.BrowserApp) // fennec?
     return window.BrowserApp.selectedTab;
   if (window.gBrowser)
     return window.gBrowser.selectedTab;
   return null;
 }
 exports.getSelectedTab = getSelectedTab;
+
+
+function getTabForBrowser(browser) {
+  for each (let window in getWindows()) {
+    // this function may be called when not using fennec
+    if (!window.BrowserApp)
+      continue;
+
+    for each (let tab in window.BrowserApp.tabs) {
+      if (tab.browser === browser)
+        return tab;
+    }
+  }
+  return null;
+}
+exports.getTabForBrowser = getTabForBrowser;
--- a/addon-sdk/source/lib/sdk/test/assert.js
+++ b/addon-sdk/source/lib/sdk/test/assert.js
@@ -55,33 +55,42 @@ AssertionError.prototype = Object.create
       ].join(" ");
     }
     return value;
   }}
 });
 exports.AssertionError = AssertionError;
 
 function Assert(logger) {
-  return Object.create(Assert.prototype, { _log: { value: logger }});
+  let assert = Object.create(Assert.prototype, { _log: { value: logger }});
+
+  assert.fail = assert.fail.bind(assert);
+  assert.pass = assert.pass.bind(assert);
+
+  return assert;
 }
+
 Assert.prototype = {
   fail: function fail(e) {
     if (!e || typeof(e) !== 'object') {
       this._log.fail(e);
       return;
     }
     let message = e.message;
-    if ('operator' in e) {
-      message += [
-        " -",
-        source(e.expected),
-        e.operator,
-        source(e.actual)
-      ].join(" ");
+    try {
+      if ('operator' in e) {
+        message += [
+          " -",
+          source(e.expected),
+          e.operator,
+          source(e.actual)
+        ].join(" ");
+      }
     }
+    catch(e) {}
     this._log.fail(message);
   },
   pass: function pass(message) {
     this._log.pass(message);
   },
   error: function error(e) {
     this._log.exception(e);
   },
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -1,18 +1,19 @@
 /* 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 { Loader, resolveURI, Require,
         unload, override, descriptor  } = require('../loader/cuddlefish');
+const { PlainTextConsole } = require("sdk/console/plain-text");
 
-exports.Loader = function(module, globals, packaging) {
+function CustomLoader(module, globals, packaging) {
   let options = packaging || require("@loader/options");
   options = override(options, {
     globals: override(require('../system/globals'), globals || {})
   });
 
   let loader = Loader(options);
   return Object.create(loader, descriptor({
     require: Require(loader, module),
@@ -21,8 +22,50 @@ exports.Loader = function(module, global
       let uri = resolveURI(requirement, loader.mapping);
       return loader.sandboxes[uri];
     },
     unload: function(reason) {
       unload(loader, reason);
     }
   }));
 };
+exports.Loader = CustomLoader;
+
+// Creates a custom loader instance whose console module is hooked in order
+// to avoid printing messages to the console, and instead, expose them in the
+// returned `messages` array attribute
+exports.LoaderWithHookedConsole = function (module, callback) {
+  let messages = [];
+  function hook(msg) {
+    messages.push({type: this, msg: msg});
+    if (callback)
+      callback(this, msg);
+  }
+  return {
+    loader: CustomLoader(module, {
+      console: {
+        log: hook.bind("log"),
+        info: hook.bind("info"),
+        warn: hook.bind("warn"),
+        error: hook.bind("error"),
+        debug: hook.bind("debug"),
+        exception: hook.bind("exception")
+      }
+    }),
+    messages: messages
+  };
+}
+
+// Same than LoaderWithHookedConsole with lower level, instead we get what is
+// actually printed to the command line console
+exports.LoaderWithHookedConsole2 = function (module, callback) {
+  let messages = [];
+  return {
+    loader: CustomLoader(module, {
+      console: new PlainTextConsole(function (msg) {
+        messages.push(msg);
+        if (callback)
+          callback(msg);
+      })
+    }),
+    messages: messages
+  };
+}
--- a/addon-sdk/source/lib/sdk/util/array.js
+++ b/addon-sdk/source/lib/sdk/util/array.js
@@ -82,8 +82,16 @@ exports.unique = function unique(array) 
 
 exports.flatten = function flatten(array){
    var flat = [];
    for (var i = 0, l = array.length; i < l; i++) {
     flat = flat.concat(Array.isArray(array[i]) ? flatten(array[i]) : array[i]);
    }
    return flat;
 };
+
+function fromIterator(iterator) {
+  let array = [];
+  for each (let item in iterator)
+    array.push(item);
+  return array;
+}
+exports.fromIterator = fromIterator;
--- a/addon-sdk/source/lib/sdk/window/browser.js
+++ b/addon-sdk/source/lib/sdk/window/browser.js
@@ -4,18 +4,20 @@
 'use strict';
 
 const { Class } = require('../core/heritage');
 const { windowNS } = require('./namespace');
 const { on, off, once } = require('../event/core');
 const { method } = require('../lang/functional');
 const { getWindowTitle } = require('./utils');
 const unload = require('../system/unload');
-const { getMode } = require('../private-browsing/utils');
+const { isWindowPrivate } = require('../private-browsing/utils');
 const { EventTarget } = require('../event/target');
+const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
+const { deprecateUsage } = require('../util/deprecate');
 
 const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec, consider using require("tabs") instead';
 
 const BrowserWindow = Class({
   initialize: function initialize(options) {
     EventTarget.prototype.initialize.call(this, options);
     windowNS(this).window = options.window;
   },
@@ -31,11 +33,21 @@ const BrowserWindow = Class({
   // NOTE: Fennec only has one window, which is assumed below
   // TODO: remove assumption below
   // NOTE: tabs requires windows
   get tabs() require('../tabs'),
   get activeTab() require('../tabs').activeTab,
   on: method(on),
   removeListener: method(off),
   once: method(once),
-  get isPrivateBrowsing() getMode(windowNS(this).window),
+  get isPrivateBrowsing() {
+    deprecateUsage('`browserWindow.isPrivateBrowsing` is deprecated, please ' +
+                 'consider using ' +
+                 '`require("private-browsing").isPrivate(browserWindow)` ' +
+                 'instead.');
+    return isWindowPrivate(windowNS(this).window);
+  }
 });
 exports.BrowserWindow = BrowserWindow;
+
+getPBOwnerWindow.define(BrowserWindow, function(window) {
+  return windowNS(window).window;
+});
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/window/helpers.js
@@ -0,0 +1,41 @@
+/* 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 { defer } = require('../core/promise');
+const { open: openWindow, onFocus } = require('./utils');
+
+function open(uri, options) {
+  return promise(openWindow.apply(null, arguments), 'load');
+}
+exports.open = open;
+
+function close(window) {
+  // unload event could happen so fast that it is not resolved
+  // if we listen to unload after calling close()
+  let p = promise(window, 'unload');
+  window.close();
+  return p;
+}
+exports.close = close;
+
+function focus(window) {
+  let p = onFocus(window);
+  window.focus();
+  return p;
+}
+exports.focus = focus;
+
+function promise(target, evt, capture) {
+  let deferred = defer();
+  capture = !!capture;
+
+  target.addEventListener(evt, function eventHandler() {
+    target.removeEventListener(evt, eventHandler, capture);
+    deferred.resolve(target);
+  }, capture);
+
+  return deferred.promise;
+}
+exports.promise = promise;
\ No newline at end of file
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -4,35 +4,66 @@
 'use strict';
 
 module.metadata = {
   'stability': 'unstable'
 };
 
 const { Cc, Ci } = require('chrome');
 const array = require('../util/array');
+const observers = require('../deprecated/observer-service');
+const { defer } = require('sdk/core/promise');
 
 const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                        getService(Ci.nsIWindowWatcher);
 const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                         getService(Ci.nsIAppShellService);
-const observers = require('../deprecated/observer-service');
 const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
            getService(Ci.nsIWindowMediator);
 
 const BROWSER = 'navigator:browser',
       URI_BROWSER = 'chrome://browser/content/browser.xul',
       NAME = '_blank',
       FEATURES = 'chrome,all,dialog=no';
 
+function isWindowPrivate(win) {
+  if (!win)
+    return false;
+
+  // if the pbService is undefined, the PrivateBrowsingUtils.jsm is available,
+  // and the app is Firefox, then assume per-window private browsing is
+  // enabled.
+  try {
+    return win.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIWebNavigation)
+                  .QueryInterface(Ci.nsILoadContext)
+                  .usePrivateBrowsing;
+  }
+  catch(e) {}
+
+  // Sometimes the input is not a nsIDOMWindow.. but it is still a winodw.
+  try {
+    return !!win.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing;
+  }
+  catch (e) {}
+
+  return false;
+}
+exports.isWindowPrivate = isWindowPrivate;
+
 function getMostRecentBrowserWindow() {
-  return WM.getMostRecentWindow(BROWSER);
+  return getMostRecentWindow(BROWSER);
 }
 exports.getMostRecentBrowserWindow = getMostRecentBrowserWindow;
 
+function getMostRecentWindow(type) {
+  return WM.getMostRecentWindow(type);
+}
+exports.getMostRecentWindow = getMostRecentWindow;
+
 /**
  * Returns the ID of the window's current inner window.
  */
 function getInnerId(window) {
   return window.QueryInterface(Ci.nsIInterfaceRequestor).
                 getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
 };
 exports.getInnerId = getInnerId;
@@ -102,81 +133,137 @@ exports.backgroundify = backgroundify;
 /**
  * Takes hash of options and serializes it to a features string that
  * can be used passed to `window.open`. For more details on features string see:
  * https://developer.mozilla.org/en/DOM/window.open#Position_and_size_features
  */
 function serializeFeatures(options) {
   return Object.keys(options).reduce(function(result, name) {
     let value = options[name];
+
+    // the chrome and private features are special
+    if ((name == 'private' || name == 'chrome'))
+      return result + ((value === true) ? ',' + name : '');
+
     return result + ',' + name + '=' +
            (value === true ? 'yes' : value === false ? 'no' : value);
   }, '').substr(1);
 }
 
 /**
  * Opens a top level window and returns it's `nsIDOMWindow` representation.
  * @params {String} uri
  *    URI of the document to be loaded into window.
  * @params {nsIDOMWindow} options.parent
  *    Used as parent for the created window.
  * @params {String} options.name
  *    Optional name that is assigned to the window.
  * @params {Object} options.features
- *    Map of key, values like: `{ width: 10, height: 15, chrome: true }`.
+ *    Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
  */
 function open(uri, options) {
   options = options || {};
-  return windowWatcher.
+  let newWindow = windowWatcher.
     openWindow(options.parent || null,
-               uri,
+               uri || URI_BROWSER,
                options.name || null,
                serializeFeatures(options.features || {}),
                options.args || null);
+
+  return newWindow;
 }
+
+
 exports.open = open;
 
+function onFocus(window) {
+  let { resolve, promise } = defer();
+
+  if (isFocused(window)) {
+    resolve(window);
+  }
+  else {
+    window.addEventListener("focus", function focusListener() {
+      window.removeEventListener("focus", focusListener, true);
+      resolve(window);
+    }, true);
+  }
+
+  return promise;
+}
+exports.onFocus = onFocus;
+
+function isFocused(window) {
+  const FM = Cc["@mozilla.org/focus-manager;1"].
+                getService(Ci.nsIFocusManager);
+
+  let childTargetWindow = {};
+  FM.getFocusedElementForWindow(window, true, childTargetWindow);
+  childTargetWindow = childTargetWindow.value;
+
+  let focusedChildWindow = {};
+  if (FM.activeWindow) {
+    FM.getFocusedElementForWindow(FM.activeWindow, true, focusedChildWindow);
+    focusedChildWindow = focusedChildWindow.value;
+  }
+
+  return (focusedChildWindow === childTargetWindow);
+}
+exports.isFocused = isFocused;
+
 /**
  * Opens a top level window and returns it's `nsIDOMWindow` representation.
  * Same as `open` but with more features
  * @param {Object} options
  *
  */
 function openDialog(options) {
   options = options || {};
-  
+
   let features = options.features || FEATURES;
   if (!!options.private &&
       !array.has(features.toLowerCase().split(','), 'private')) {
     features = features.split(',').concat('private').join(',');
   }
 
-  let browser = WM.getMostRecentWindow(BROWSER);
-  return browser.openDialog.apply(
+  let browser = getMostRecentBrowserWindow();
+
+  // if there is no browser then do nothing
+  if (!browser)
+    return undefined;
+
+  let newWindow = browser.openDialog.apply(
       browser,
       array.flatten([
         options.url || URI_BROWSER,
         options.name || NAME,
         features,
         options.args || null
       ])
   );
+
+  return newWindow;
 }
 exports.openDialog = openDialog;
 
 /**
  * Returns an array of all currently opened windows.
  * Note that these windows may still be loading.
  */
-function windows() {
+function windows(type, options) {
+  options = options || {};
   let list = [];
-  let winEnum = windowWatcher.getWindowEnumerator();
+  let winEnum = WM.getEnumerator(type);
   while (winEnum.hasMoreElements()) {
     let window = winEnum.getNext().QueryInterface(Ci.nsIDOMWindow);
-    list.push(window);
+    // Only add non-private windows when pb permission isn't set,
+    // unless an option forces the addition of them.
+    if (options.includePrivate || !isWindowPrivate(window)) {
+      list.push(window);
+    }
   }
   return list;
 }
 exports.windows = windows;
 
 /**
  * Check if the given window is completely loaded.
  * i.e. if its "load" event has already been fired and all possible DOM content
@@ -184,17 +271,21 @@ exports.windows = windows;
  * @params {nsIDOMWindow} window
  */
 function isDocumentLoaded(window) {
   return window.document.readyState == "complete";
 }
 exports.isDocumentLoaded = isDocumentLoaded;
 
 function isBrowser(window) {
-  return window.document.documentElement.getAttribute("windowtype") === BROWSER;
+  try {
+    return window.document.documentElement.getAttribute("windowtype") === BROWSER;
+  }
+  catch (e) {}
+  return false;
 };
 exports.isBrowser = isBrowser;
 
 function getWindowTitle(window) {
   return window && window.document ? window.document.title : null;
 }
 exports.getWindowTitle = getWindowTitle;
 
@@ -202,26 +293,28 @@ function isXULBrowser(window) {
   return !!(isBrowser(window) && window.XULBrowserWindow);
 }
 exports.isXULBrowser = isXULBrowser;
 
 /**
  * Returns the most recent focused window
  */
 function getFocusedWindow() {
-  let window = getMostRecentBrowserWindow();
+  let window = WM.getMostRecentWindow(BROWSER);
+
   return window ? window.document.commandDispatcher.focusedWindow : null;
 }
 exports.getFocusedWindow = getFocusedWindow;
 
 /**
  * Returns the focused element in the most recent focused window
  */
 function getFocusedElement() {
-  let window = getMostRecentBrowserWindow();
+  let window = WM.getMostRecentWindow(BROWSER);
+
   return window ? window.document.commandDispatcher.focusedElement : null;
 }
 exports.getFocusedElement = getFocusedElement;
 
 function getFrames(window) {
   return Array.slice(window.frames).reduce(function(frames, frame) {
     return frames.concat(frame, getFrames(frame))
   }, [])
--- a/addon-sdk/source/lib/sdk/windows.js
+++ b/addon-sdk/source/lib/sdk/windows.js
@@ -6,14 +6,14 @@
 module.metadata = {
   'stability': 'stable',
   'engines': {
     'Firefox': '*',
     'Fennec': '*'
   }
 };
 
-if (require('./system/xul-app').is('Firefox')) {
+if (require('./system/xul-app').is('Fennec')) {
+  module.exports = require('./windows/fennec');
+}
+else {
   module.exports = require('./windows/firefox');
 }
-else if (require('./system/xul-app').is('Fennec')) {
-  module.exports = require('./windows/fennec');
-}
--- a/addon-sdk/source/lib/sdk/windows/dom.js
+++ b/addon-sdk/source/lib/sdk/windows/dom.js
@@ -1,16 +1,16 @@
 /* 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 { Trait } = require('../deprecated/traits');
-const { getWindowTitle } = require('../window/utils');
-const { getMode } = require('../private-browsing/utils');
+const { isWindowPrivate, getWindowTitle } = require('../window/utils');
+const { deprecateUsage } = require('../util/deprecate');
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const WindowDom = Trait.compose({
   _window: Trait.required,
   get title() {
@@ -22,12 +22,16 @@ const WindowDom = Trait.compose({
     return this._public;
   },
   activate: function activate() {
     let window = this._window;
     if (window) window.focus();
     return this._public;
   },
   get isPrivateBrowsing() {
-    return getMode(this._window);
+    deprecateUsage('`browserWindow.isPrivateBrowsing` is deprecated, please ' +
+                   'consider using ' +
+                   '`require("private-browsing").isPrivate(browserWindow)` ' +
+                   'instead.');
+    return isWindowPrivate(this._window);
   }
 });
 exports.WindowDom = WindowDom;
--- a/addon-sdk/source/lib/sdk/windows/firefox.js
+++ b/addon-sdk/source/lib/sdk/windows/firefox.js
@@ -5,24 +5,28 @@
 
 const { Cc, Ci, Cr } = require('chrome'),
       { Trait } = require('../deprecated/traits'),
       { List } = require('../deprecated/list'),
       { EventEmitter } = require('../deprecated/events'),
       { WindowTabs, WindowTabTracker } = require('./tabs-firefox'),
       { WindowDom } = require('./dom'),
       { WindowLoader } = require('./loader'),
-      { isBrowser, getWindowDocShell } = require('../window/utils'),
+      { isBrowser, getWindowDocShell, windows: windowIterator } = require('../window/utils'),
       { Options } = require('../tabs/common'),
       apiUtils = require('../deprecated/api-utils'),
       unload = require('../system/unload'),
       windowUtils = require('../deprecated/window-utils'),
       { WindowTrackerTrait } = windowUtils,
       { ns } = require('../core/namespace'),
-      { observer: windowObserver } = require('./observer');
+      { observer: windowObserver } = require('./observer'),
+      { getOwnerWindow } = require('../private-browsing/window/utils'),
+      viewNS = require('../core/namespace').ns(),
+      { isPrivateBrowsingSupported } = require('../self');
+const { ignoreWindow } = require('sdk/private-browsing/utils');
 
 /**
  * Window trait composes safe wrappers for browser window that are E10S
  * compatible.
  */
 const BrowserWindowTrait = Trait.compose(
   EventEmitter,
   WindowDom.resolve({ close: '_close' }),
@@ -61,19 +65,23 @@ const BrowserWindowTrait = Trait.compose
         this._tabOptions = Array.isArray(options.tabs) ?
                            options.tabs.map(Options) :
                            [ Options(options.tabs) ];
       }
       else if ('url' in options) {
         this._tabOptions = [ Options(options.url) ];
       }
 
-      this._private = !!options.private;
+      this._isPrivate = isPrivateBrowsingSupported && !!options.isPrivate;
 
       this._load();
+
+      viewNS(this._public).window = this._window;
+      getOwnerWindow.implement(this._public, getChromeWindow);
+
       return this;
     },
     destroy: function () this._onUnload(),
     _tabOptions: [],
     _onLoad: function() {
       try {
         this._initWindowTabTracker();
       }
@@ -195,36 +203,44 @@ const browserWindows = Trait.resolve({ t
     },
     /**
      * This property represents currently active window.
      * Property is non-enumerable, in order to preserve array like enumeration.
      * @type {Window|null}
      */
     get activeWindow() {
       let window = windowUtils.activeBrowserWindow;
+      // Bug 834961: ignore private windows when they are not supported
+      if (ignoreWindow(window))
+        window = windowIterator()[0];
       return window ? BrowserWindow({window: window}) : null;
     },
     open: function open(options) {
-      if (typeof options === "string")
+      if (typeof options === "string") {
         // `tabs` option is under review and may be removed.
-        options = { tabs: [Options(options)] };
+        options = {
+          tabs: [Options(options)],
+          isPrivate: isPrivateBrowsingSupported && options.isPrivate
+        };
+      }
       return BrowserWindow(options);
     },
 
      /**
       * Internal listener which is called whenever new window gets open.
       * Creates wrapper and adds to this list.
       * @param {nsIWindow} chromeWindow
       */
     _onTrack: function _onTrack(chromeWindow) {
       if (!isBrowser(chromeWindow)) return;
       let window = BrowserWindow({ window: chromeWindow });
       this._add(window);
       this._emit('open', window);
     },
+
     /**
      * Internal listener which is called whenever window gets closed.
      * Cleans up references and removes wrapper from this list.
      * @param {nsIWindow} window
      */
     _onUntrack: function _onUntrack(chromeWindow) {
       if (!isBrowser(chromeWindow)) return;
       let window = BrowserWindow({ window: chromeWindow });
@@ -234,9 +250,13 @@ const browserWindows = Trait.resolve({ t
       // Bug 724404: do not leak this module and linked windows:
       // We have to do it on untrack and not only when `_onUnload` is called
       // when windows are closed, otherwise, we will leak on addon disabling.
       window.destroy();
     }
   }).resolve({ toString: null })
 )();
 
+function getChromeWindow(window) {
+  return viewNS(window).window;
+}
+
 exports.browserWindows = browserWindows;
--- a/addon-sdk/source/lib/sdk/windows/loader.js
+++ b/addon-sdk/source/lib/sdk/windows/loader.js
@@ -36,17 +36,17 @@ const WindowLoader = Trait.compose({
    * is dispatched. Please note that this trait will not handle exceptions that
    * may be thrown by this method so method itself should take care of
    * handling them.
    */
   _onUnload: Trait.required,
   _load: function _load() {
     if (this.__window) return;
     this._window = openDialog({
-      private: this._private,
+      private: this._isPrivate,
       args: this._tabOptions.map(function(options) options.url).join("|")
     });
   },
   /**
    * Private window who's load event is being tracked. Once window is loaded
    * `_onLoad` is called.
    * @type {nsIWindow}
    */
--- a/addon-sdk/source/lib/sdk/windows/tabs-fennec.js
+++ b/addon-sdk/source/lib/sdk/windows/tabs-fennec.js
@@ -3,62 +3,65 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 const { Class } = require('../core/heritage');
 const { Tab } = require('../tabs/tab');
 const { browserWindows } = require('./fennec');
 const { windowNS } = require('../window/namespace');
 const { tabsNS, tabNS } = require('../tabs/namespace');
-const { openTab, getTabs, getSelectedTab } = require('../tabs/utils');
+const { openTab, getTabs, getSelectedTab, getTabForBrowser: getRawTabForBrowser } = require('../tabs/utils');
 const { Options } = require('../tabs/common');
+const { getTabForBrowser, getTabForRawTab } = require('../tabs/helpers');
 const { on, once, off, emit } = require('../event/core');
 const { method } = require('../lang/functional');
 const { EVENTS } = require('../tabs/events');
 const { EventTarget } = require('../event/target');
 const { when: unload } = require('../system/unload');
 const { windowIterator } = require('../deprecated/window-utils');
 const { List, addListItem, removeListItem } = require('../util/list');
+const { isPrivateBrowsingSupported } = require('sdk/self');
+const { isTabPBSupported } = require('sdk/private-browsing/utils');
 
 const mainWindow = windowNS(browserWindows.activeWindow).window;
 
 const ERR_FENNEC_MSG = 'This method is not yet supported by Fennec';
 
+const supportPrivateTabs = isPrivateBrowsingSupported && isTabPBSupported;
+
 const Tabs = Class({
   implements: [ List ],
   extends: EventTarget,
   initialize: function initialize(options) {
     let tabsInternals = tabsNS(this);
     let window = tabsNS(this).window = options.window || mainWindow;
 
     EventTarget.prototype.initialize.call(this, options);
     List.prototype.initialize.apply(this, getTabs(window).map(Tab));
 
     // TabOpen event
     window.BrowserApp.deck.addEventListener(EVENTS.open.dom, onTabOpen, false);
 
     // TabSelect
     window.BrowserApp.deck.addEventListener(EVENTS.activate.dom, onTabSelect, false);
-
-    // TabClose
-    window.BrowserApp.deck.addEventListener(EVENTS.close.dom, onTabClose, false);
   },
   get activeTab() {
     return getTabForRawTab(getSelectedTab(tabsNS(this).window));
   },
   open: function(options) {
     options = Options(options);
     let activeWin = browserWindows.activeWindow;
 
     if (options.isPinned) {
       console.error(ERR_FENNEC_MSG); // TODO
     }
 
     let rawTab = openTab(windowNS(activeWin).window, options.url, {
-      inBackground: options.inBackground
+      inBackground: options.inBackground,
+      isPrivate: supportPrivateTabs && options.isPrivate
     });
 
     // by now the tab has been created
     let tab = getTabForRawTab(rawTab);
 
     if (options.onClose)
       tab.on('close', options.onClose);
 
@@ -82,17 +85,16 @@ const Tabs = Class({
 let gTabs = exports.tabs = Tabs(mainWindow);
 
 function tabsUnloader(event, window) {
   window = window || (event && event.target);
   if (!(window && window.BrowserApp))
     return;
   window.BrowserApp.deck.removeEventListener(EVENTS.open.dom, onTabOpen, false);
   window.BrowserApp.deck.removeEventListener(EVENTS.activate.dom, onTabSelect, false);
-  window.BrowserApp.deck.removeEventListener(EVENTS.close.dom, onTabClose, false);
 }
 
 // unload handler
 unload(function() {
   for (let window in windowIterator()) {
     tabsUnloader(null, window);
   }
 });
@@ -102,89 +104,48 @@ function addTab(tab) {
   return tab;
 }
 
 function removeTab(tab) {
   removeListItem(gTabs, tab);
   return tab;
 }
 
-function getTabForBrowser(browser) {
-  return getTabForRawTab(getRawTabForBrowser(browser));
-}
-
-function getRawTabForBrowser(browser) {
-  let tabs = mainWindow.BrowserApp.tabs;
-  for (let i = 0; i < tabs.length; i++) {
-    let tab = tabs[i];
-    if (tab.browser === browser)
-      return tab
-  }
-  return null;
-}
-
 // TabOpen
 function onTabOpen(event) {
   let browser = event.target;
 
   let tab = getTabForBrowser(browser);
   if (tab === null) {
     let rawTab = getRawTabForBrowser(browser);
 
     // create a Tab instance for this new tab
     tab = addTab(Tab(rawTab));
   }
 
   tabNS(tab).opened = true;
 
-    // TabReady
-  let onReady = tabNS(tab).onReady = onTabReady.bind(tab);
-  browser.addEventListener(EVENTS.ready.dom, onReady, false);
+  tab.on('ready', function() emit(gTabs, 'ready', tab));
+  tab.once('close', onTabClose);
 
   emit(tab, 'open', tab);
   emit(gTabs, 'open', tab);
 };
 
-function onTabReady() {
-  emit(this, 'ready', this);
-  emit(gTabs, 'ready', this);
-}
-
 // TabSelect
 function onTabSelect(event) {
   // Set value whenever new tab becomes active.
   let tab = getTabForBrowser(event.target);
   emit(tab, 'activate', tab);
   emit(gTabs, 'activate', tab);
 
   for each (let t in gTabs) {
     if (t === tab) continue;
     emit(t, 'deactivate', t);
     emit(gTabs, 'deactivate', t);
   }
 };
 
 // TabClose
-function onTabClose(event) {
-  let tab = getTabForBrowser(event.target);
+function onTabClose(tab) {
   removeTab(tab);
-
-  emit(gTabs, 'close', tab);
-  emit(tab, 'close', tab);
+  emit(gTabs, EVENTS.close.name, tab);
 };
-
-function getTabForRawTab(rawTab) {
-  for each (let tab in gTabs) {
-    if (tabNS(tab).tab === rawTab)
-      return tab;
-  }
-  return null;
-}
-
-unload(function() {
-  for each (let tab in gTabs) {
-    let tabInternals = tabNS(tab);
-    tabInternals.tab.browser.removeEventListener(EVENTS.ready.dom, tabInternals.onReady, false);
-    tabInternals.onReady = null;
-    tabInternals.tab = null;
-    tabInternals.window = null;
-  }
-});
--- a/addon-sdk/source/lib/sdk/windows/tabs-firefox.js
+++ b/addon-sdk/source/lib/sdk/windows/tabs-firefox.js
@@ -8,19 +8,20 @@ module.metadata = {
 };
 
 const { Trait } = require("../deprecated/traits");
 const { List } = require("../deprecated/list");
 const { Tab } = require("../tabs/tab");
 const { EventEmitter } = require("../deprecated/events");
 const { EVENTS } = require("../tabs/events");
 const { getOwnerWindow, getActiveTab, getTabs,
-        openTab, activateTab } = require("../tabs/utils");
+        openTab } = require("../tabs/utils");
 const { Options } = require("../tabs/common");
 const { observer: tabsObserver } = require("../tabs/observer");
+const { ignoreWindow, isWindowPrivate } = require("../private-browsing/utils");
 
 const TAB_BROWSER = "tabbrowser";
 
 /**
  * This is a trait that is used in composition of window wrapper. Trait tracks
  * tab related events of the wrapped window in order to keep track of open
  * tabs and maintain their wrappers. Every new tab gets wrapped and jetpack
  * type event is emitted.
@@ -85,23 +86,32 @@ const WindowTabTracker = Trait.compose({
     this._tabs._clear();
 
     tabsObserver.removeListener("open", this._onTabOpen);
     tabsObserver.removeListener("close", this._onTabClose);
     tabsObserver.removeListener("activate", this._onTabActivate);
     tabsObserver.removeListener("deactivate", this._onTabDeactivate);
   },
   _onTabEvent: function _onTabEvent(type, tab) {
-    if (this._window === getOwnerWindow(tab)) {
+    // Accept only tabs for the watched window, and ignore private tabs
+    // if addon doesn't have private permission
+    if (this._window === getOwnerWindow(tab) && !ignoreWindow(this._window)) {
       let options = this._tabOptions.shift() || {};
       options.tab = tab;
       options.window = this._public;
 
-      // creating tab wrapper and adding listener to "ready" events.
-      let wrappedTab = Tab(options);
+      // Ignore zombie tabs on open that have already been removed
+      if (type == "open" && !tab.linkedBrowser)
+        return;
+
+      // Create a tab wrapper on open event, otherwise, just fetch existing
+      // tab object
+      let wrappedTab = Tab(options, type !== "open");
+      if (!wrappedTab)
+        return;
 
       // Setting up an event listener for ready events.
       if (type === "open")
         wrappedTab.on("ready", this._onTabReady);
 
       this._emitEvent(type, wrappedTab);
     }
   },
@@ -147,21 +157,24 @@ const TabList = List.resolve({ construct
       // This list is not going to emit any events, object holding this list
       // will do it instead, to make that possible we return a private API.
       return this;
     },
     get activeTab() this._activeTab,
     _activeTab: null,
 
     open: function open(options) {
+      let window = this._window;
+      let chromeWindow = window._window;
       options = Options(options);
-      this._window._tabOptions.push(options);
-      let tab = openTab(this._window._window, options.url);
-      if (!options.inBackground)
-        activateTab(tab);
+
+      // save the tab options
+      window._tabOptions.push(options);
+      // open the tab
+      let tab = openTab(chromeWindow, options.url, options);
     }
   // This is ugly, but necessary. Will be removed by #596248
   }).resolve({ toString: null })
 );
 
 /**
  * Combined list of all open tabs on all the windows.
  * type {TabList}
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -820,18 +820,18 @@ def run(arguments=sys.argv[1:], target_c
         app_extension_dir = os.path.abspath(options.templatedir)
     else:
         mydir = os.path.dirname(os.path.abspath(__file__))
         app_extension_dir = os.path.join(mydir, "../../app-extension")
 
     if target_cfg.get('preferences'):
         harness_options['preferences'] = target_cfg.get('preferences')
 
-    harness_options['manifest'] = \
-        manifest.get_harness_options_manifest(options.bundle_sdk)
+    # Do not add entries for SDK modules
+    harness_options['manifest'] = manifest.get_harness_options_manifest(False)
 
     # Gives an hint to tell if sdk modules are bundled or not
     harness_options['is-sdk-bundled'] = options.bundle_sdk
 
     if options.force_use_bundled_sdk:
         if not options.bundle_sdk:
             print >>sys.stderr, ("--force-use-bundled-sdk and --strip-sdk "
                                  "can't be used at the same time.")
@@ -875,20 +875,22 @@ def run(arguments=sys.argv[1:], target_c
     if command == "xpi":
         used_files = set(manifest.get_used_files(options.bundle_sdk))
 
     if options.no_strip_xpi:
         used_files = None # disables the filter, includes all files
 
     if command == 'xpi':
         from cuddlefish.xpi import build_xpi
+        # Generate extra options
         extra_harness_options = {}
         for kv in options.extra_harness_option_args:
             key,value = kv.split("=", 1)
             extra_harness_options[key] = value
+        # Generate xpi filepath
         xpi_path = XPI_FILENAME % target_cfg.name
         print >>stdout, "Exporting extension to %s." % xpi_path
         build_xpi(template_root_dir=app_extension_dir,
                   manifest=manifest_rdf,
                   xpi_path=xpi_path,
                   harness_options=harness_options,
                   limit_to=used_files,
                   extra_harness_options=extra_harness_options)
@@ -905,16 +907,17 @@ def run(arguments=sys.argv[1:], target_c
         try:
             retval = run_app(harness_root_dir=app_extension_dir,
                              manifest_rdf=manifest_rdf,
                              harness_options=harness_options,
                              app_type=options.app,
                              binary=options.binary,
                              profiledir=options.profiledir,
                              verbose=options.verbose,
+                             parseable=options.parseable,
                              enforce_timeouts=enforce_timeouts,
                              logfile=options.logfile,
                              addons=options.addons,
                              args=options.cmdargs,
                              extra_environment=extra_environment,
                              norun=options.no_run,
                              used_files=used_files,
                              enable_mobile=options.enable_mobile,
--- a/addon-sdk/source/python-lib/cuddlefish/manifest.py
+++ b/addon-sdk/source/python-lib/cuddlefish/manifest.py
@@ -264,30 +264,30 @@ class ManifestBuilder:
         # returns all .js files that we reference, plus data/ files. You will
         # need to add the loader, off-manifest files that it needs, and
         # generated metadata.
         for datamap in self.datamaps.values():
             for (zipname, absname) in datamap.files_to_copy:
                 yield absname
 
         for me in self.get_module_entries():
-            # Do not add manifest entries for system modules,
-            # so that we won't ship SDK files.
+            # Only ship SDK files if we are told to do so
             if me.packageName != "addon-sdk" or bundle_sdk_modules:
                 yield me.js_filename
 
     def get_all_test_modules(self):
         return self.test_modules
 
     def get_harness_options_manifest(self, bundle_sdk_modules):
         manifest = {}
         for me in self.get_module_entries():
             path = me.get_path()
-            # Do not add manifest entries for system modules,
-            # so that we won't ship SDK files.
+            # Do not add manifest entries for system modules.
+            # Doesn't prevent from shipping modules.
+            # Shipping modules is decided in `get_used_files`.
             if me.packageName != "addon-sdk" or bundle_sdk_modules:
                 manifest[path] = me.get_entry_for_manifest()
         return manifest
 
     def get_manifest_entry(self, package, section, module):
         index = (package, section, module)
         if index not in self.manifest:
             m = self.manifest[index] = ManifestEntry()
--- a/addon-sdk/source/python-lib/cuddlefish/packaging.py
+++ b/addon-sdk/source/python-lib/cuddlefish/packaging.py
@@ -18,17 +18,17 @@ env_root = os.environ.get('CUDDLEFISH_RO
 
 DEFAULT_PROGRAM_MODULE = 'main'
 
 DEFAULT_ICON = 'icon.png'
 DEFAULT_ICON64 = 'icon64.png'
 
 METADATA_PROPS = ['name', 'description', 'keywords', 'author', 'version',
                   'translators', 'contributors', 'license', 'homepage', 'icon',
-                  'icon64', 'main', 'directories']
+                  'icon64', 'main', 'directories', 'permissions']
 
 RESOURCE_HOSTNAME_RE = re.compile(r'^[a-z0-9_\-]+$')
 
 class Error(Exception):
     pass
 
 class MalformedPackageError(Error):
     pass
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -5,28 +5,32 @@
 import os
 import sys
 import time
 import tempfile
 import atexit
 import shlex
 import subprocess
 import re
+import shutil
 
 import mozrunner
 from cuddlefish.prefs import DEFAULT_COMMON_PREFS
 from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS
 from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS
 from cuddlefish.prefs import DEFAULT_FENNEC_PREFS
 
 # Used to remove noise from ADB output
 CLEANUP_ADB = re.compile(r'^(I|E)/(stdout|stderr|GeckoConsole)\s*\(\s*\d+\):\s*(.*)$')
 # Used to filter only messages send by `console` module
 FILTER_ONLY_CONSOLE_FROM_ADB = re.compile(r'^I/(stdout|stderr)\s*\(\s*\d+\):\s*((info|warning|error|debug): .*)$')
 
+# Used to detect the currently running test
+PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n')
+
 # Maximum time we'll wait for tests to finish, in seconds.
 # The purpose of this timeout is to recover from infinite loops.  It should be
 # longer than the amount of time any test run takes, including those on slow
 # machines running slow (debug) versions of Firefox.
 RUN_TIMEOUT = 30 * 60 # 30 minutes
 
 # Maximum time we'll wait for tests to emit output, in seconds.
 # The purpose of this timeout is to recover from hangs.  It should be longer
@@ -118,23 +122,23 @@ class FennecRunner(mozrunner.Runner):
     def find_binary(self):
         if not self.__real_binary:
             if sys.platform == 'darwin':
                 if os.path.exists(self.__DARWIN_PATH):
                     return self.__DARWIN_PATH
             self.__real_binary = mozrunner.Runner.find_binary(self)
         return self.__real_binary
 
+FENNEC_REMOTE_PATH = '/mnt/sdcard/jetpack-profile'
 
 class RemoteFennecRunner(mozrunner.Runner):
     profile_class = FennecProfile
 
     names = ['fennec']
 
-    _REMOTE_PATH = '/mnt/sdcard/jetpack-profile'
     _INTENT_PREFIX = 'org.mozilla.'
 
     _adb_path = None
 
     def __init__(self, binary=None, **kwargs):
         # Check that we have a binary set
         if not binary:
             raise ValueError("You have to define `--binary` option set to the "
@@ -193,23 +197,23 @@ class RemoteFennecRunner(mozrunner.Runne
             if self.getProcessPID(self._intent_name) != None:
                 raise Exception("Unable to automatically kill running Firefox" +
                                 " instance. Please close it manually before " +
                                 "executing cfx.")
 
         print "Pushing the addon to your device"
 
         # Create a clean empty profile on the sd card
-        subprocess.call([self._adb_path, "shell", "rm -r " + self._REMOTE_PATH])
-        subprocess.call([self._adb_path, "shell", "mkdir " + self._REMOTE_PATH])
+        subprocess.call([self._adb_path, "shell", "rm -r " + FENNEC_REMOTE_PATH])
+        subprocess.call([self._adb_path, "shell", "mkdir " + FENNEC_REMOTE_PATH])
 
         # Push the profile folder created by mozrunner to the device
         # (we can't simply use `adb push` as it doesn't copy empty folders)
         localDir = self.profile.profile
-        remoteDir = self._REMOTE_PATH
+        remoteDir = FENNEC_REMOTE_PATH
         for root, dirs, files in os.walk(localDir, followlinks='true'):
             relRoot = os.path.relpath(root, localDir)
             # Note about os.path usage below:
             # Local files may be using Windows `\` separators but
             # remote are always `/`, so we need to convert local ones to `/`
             for file in files:
                 localFile = os.path.join(root, file)
                 remoteFile = remoteDir.replace("/", os.sep)
@@ -231,17 +235,17 @@ class RemoteFennecRunner(mozrunner.Runne
     @property
     def command(self):
         """Returns the command list to run."""
         return [self._adb_path,
             "shell",
             "am start " +
                 "-a android.activity.MAIN " +
                 "-n " + self._intent_name + "/" + self._intent_name + ".App " +
-                "--es args \"-profile " + self._REMOTE_PATH + "\""
+                "--es args \"-profile " + FENNEC_REMOTE_PATH + "\""
         ]
 
     def start(self):
         subprocess.call(self.command)
 
     def getProcessPID(self, processName):
         p = subprocess.Popen([self._adb_path, "shell", "ps"],
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
@@ -369,19 +373,43 @@ class XulrunnerAppRunner(mozrunner.Runne
             self.__real_binary = self.__find_xulrunner_binary()
             if not self.__real_binary:
                 dummy_profile = {}
                 runner = mozrunner.FirefoxRunner(profile=dummy_profile)
                 self.__real_binary = runner.find_binary()
                 self.names = runner.names
         return self.__real_binary
 
+def set_overloaded_modules(env_root, app_type, addon_id, preferences, overloads):
+    # win32 file scheme needs 3 slashes
+    desktop_file_scheme = "file://"
+    if not env_root.startswith("/"):
+      desktop_file_scheme = desktop_file_scheme + "/"
+
+    pref_prefix = "extensions.modules." + addon_id + ".path"
+
+    # Set preferences that will map require prefix to a given path
+    for name, path in overloads.items():
+        if len(name) == 0:
+            prefName = pref_prefix
+        else:
+            prefName = pref_prefix + "." + name
+        if app_type == "fennec-on-device":
+            # For testing on device, we have to copy overloaded files from fs
+            # to the device and use device path instead of local fs path.
+            # Actual copy of files if done after the call to Profile constructor
+            preferences[prefName] = "file://" + \
+                FENNEC_REMOTE_PATH + "/overloads/" + name
+        else:
+            preferences[prefName] = desktop_file_scheme + \
+                path.replace("\\", "/") + "/"
+
 def run_app(harness_root_dir, manifest_rdf, harness_options,
             app_type, binary=None, profiledir=None, verbose=False,
-            enforce_timeouts=False,
+            parseable=False, enforce_timeouts=False,
             logfile=None, addons=None, args=None, extra_environment={},
             norun=None,
             used_files=None, enable_mobile=False,
             mobile_app_name=None,
             env_root=None,
             is_running_tests=False,
             overload_modules=False,
             bundle_sdk=True):
@@ -391,32 +419,16 @@ def run_app(harness_root_dir, manifest_r
     if addons is None:
         addons = []
     else:
         addons = list(addons)
 
     cmdargs = []
     preferences = dict(DEFAULT_COMMON_PREFS)
 
-    # Overload global commonjs path with lib/ folders
-    file_scheme = "file://"
-    # win32 file scheme needs 3 slashes
-    if not env_root.startswith("/"):
-      file_scheme = file_scheme + "/"
-    addon_id = harness_options["jetpackID"]
-    pref_prefix = "extensions.modules." + addon_id + ".path"
-    if overload_modules:
-        preferences[pref_prefix] = file_scheme + \
-            os.path.join(env_root, "lib").replace("\\", "/") + "/"
-
-    # Overload tests/ mapping with test/ folder, only when running test
-    if is_running_tests:
-        preferences[pref_prefix + ".tests"] = file_scheme + \
-            os.path.join(env_root, "test").replace("\\", "/") + "/"
-
     # For now, only allow running on Mobile with --force-mobile argument
     if app_type in ["fennec", "fennec-on-device"] and not enable_mobile:
         print """
   WARNING: Firefox Mobile support is still experimental.
   If you would like to run an addon on this platform, use --force-mobile flag:
 
     cfx --force-mobile"""
         return 0
@@ -528,24 +540,47 @@ def run_app(harness_root_dir, manifest_r
 
     if app_type == "fennec-on-device":
         # Install a special addon when we run firefox on mobile device
         # in order to be able to kill it
         mydir = os.path.dirname(os.path.abspath(__file__))
         addon_dir = os.path.join(mydir, "mobile-utils")
         addons.append(addon_dir)
 
+    # Overload addon-specific commonjs modules path with lib/ folder
+    overloads = dict()
+    if overload_modules:
+        overloads[""] = os.path.join(env_root, "lib")
+
+    # Overload tests/ mapping with test/ folder, only when running test
+    if is_running_tests:
+        overloads["tests"] = os.path.join(env_root, "test")
+
+    set_overloaded_modules(env_root, app_type, harness_options["jetpackID"], \
+                           preferences, overloads)
+
     # the XPI file is copied into the profile here
     profile = profile_class(addons=addons,
                             profile=profiledir,
                             preferences=preferences)
 
     # Delete the temporary xpi file
     os.remove(xpi_path)
 
+    # Copy overloaded files registered in set_overloaded_modules
+    # For testing on device, we have to copy overloaded files from fs
+    # to the device and use device path instead of local fs path.
+    # (has to be done after the call to profile_class() which eventualy creates
+    #  profile folder)
+    if app_type == "fennec-on-device":
+        profile_path = profile.profile
+        for name, path in overloads.items():
+            shutil.copytree(path, \
+                os.path.join(profile_path, "overloads", name))
+
     runner = runner_class(profile=profile,
                           binary=binary,
                           env=env,
                           cmdargs=cmdargs,
                           kp_kwargs=popen_kwargs)
 
     sys.stdout.flush(); sys.stderr.flush()
 
@@ -667,41 +702,53 @@ def run_app(harness_root_dir, manifest_r
         print "To launch the application, enter the following command:"
         print " ".join(runner.command) + " " + (" ".join(runner.cmdargs))
         return 0
 
     runner.start()
 
     done = False
     result = None
+    test_name = "unknown"
+
+    def Timeout(message, test_name, parseable):
+        if parseable:
+            sys.stderr.write("TEST-UNEXPECTED-FAIL | %s | %s\n" % (test_name, message))
+            sys.stderr.flush()
+        return Exception(message)
+
     try:
         while not done:
             time.sleep(0.05)
             for tail in (logfile_tail, outfile_tail):
                 if tail:
                     new_chars = tail.next()
                     if new_chars:
                         last_output_time = time.time()
                         sys.stderr.write(new_chars)
                         sys.stderr.flush()
+                        if is_running_tests and parseable:
+                            match = PARSEABLE_TEST_NAME.search(new_chars)
+                            if match:
+                                test_name = match.group(1)
             if os.path.exists(resultfile):
                 result = open(resultfile).read()
                 if result:
                     if result in ['OK', 'FAIL']:
                         done = True
                     else:
                         sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result)))
                         sys.stderr.write("'"+result+"'\n")
             if enforce_timeouts:
                 if time.time() - last_output_time > OUTPUT_TIMEOUT:
-                    raise Exception("Test output exceeded timeout (%ds)." %
-                                    OUTPUT_TIMEOUT)
+                    raise Timeout("Test output exceeded timeout (%ds)." %
+                                  OUTPUT_TIMEOUT, test_name, parseable)
                 if time.time() - starttime > RUN_TIMEOUT:
-                    raise Exception("Test run exceeded timeout (%ds)." %
-                                    RUN_TIMEOUT)
+                    raise Timeout("Test run exceeded timeout (%ds)." %
+                                  RUN_TIMEOUT, test_name, parseable)
     except:
         runner.stop()
         raise
     else:
         runner.wait(10)
     finally:
         outf.close()
         if profile:
--- a/addon-sdk/source/python-lib/cuddlefish/templates.py
+++ b/addon-sdk/source/python-lib/cuddlefish/templates.py
@@ -1,26 +1,26 @@
 # 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/.
 
 #Template used by test-main.js
 TEST_MAIN_JS = '''\
-var main = require("main");
+var main = require("./main");
 
 exports["test main"] = function(assert) {
   assert.pass("Unit test running!");
 };
 
 exports["test main async"] = function(assert, done) {
   assert.pass("async Unit test running!");
   done();
 };
 
-require("test").run(exports);
+require("sdk/test").run(exports);
 '''
 
 #Template used by package.json
 PACKAGE_JSON = '''\
 {
   "name": "%(name)s",
   "fullName": "%(fullName)s",
   "id": "%(id)s",
--- a/addon-sdk/source/python-lib/cuddlefish/xpi.py
+++ b/addon-sdk/source/python-lib/cuddlefish/xpi.py
@@ -25,24 +25,26 @@ def build_xpi(template_root_dir, manifes
               harness_options, limit_to=None, extra_harness_options={},
               bundle_sdk=True):
     zf = zipfile.ZipFile(xpi_path, "w", zipfile.ZIP_DEFLATED)
 
     open('.install.rdf', 'w').write(str(manifest))
     zf.write('.install.rdf', 'install.rdf')
     os.remove('.install.rdf')
 
+    # Handle add-on icon
     if 'icon' in harness_options:
         zf.write(str(harness_options['icon']), 'icon.png')
         del harness_options['icon']
 
     if 'icon64' in harness_options:
         zf.write(str(harness_options['icon64']), 'icon64.png')
         del harness_options['icon64']
 
+    # Handle simple-prefs
     if 'preferences' in harness_options:
         from options_xul import parse_options, validate_prefs
 
         validate_prefs(harness_options["preferences"])
 
         opts_xul = parse_options(harness_options["preferences"],
                                  harness_options["jetpackID"])
         open('.options.xul', 'wb').write(opts_xul.encode("utf-8"))
@@ -133,28 +135,31 @@ def build_xpi(template_root_dir, manifes
 
     # now figure out which directories we need: all retained files parents
     for arcpath in files_to_copy:
         bits = arcpath.split("/")
         for i in range(1,len(bits)):
             parentpath = ZIPSEP.join(bits[0:i])
             dirs_to_create.add(parentpath)
 
-    # create zipfile in alphabetical order, with each directory before its
+    # Create zipfile in alphabetical order, with each directory before its
     # files
     for name in sorted(dirs_to_create.union(set(files_to_copy))):
         if name in dirs_to_create:
             mkzipdir(zf, name+"/")
         if name in files_to_copy:
             zf.write(files_to_copy[name], name)
 
+    # Add extra harness options
     harness_options = harness_options.copy()
     for key,value in extra_harness_options.items():
         if key in harness_options:
             msg = "Can't use --harness-option for existing key '%s'" % key
             raise HarnessOptionAlreadyDefinedError(msg)
         harness_options[key] = value
+
+    # Write harness-options.json
     open('.options.json', 'w').write(json.dumps(harness_options, indent=1,
                                                 sort_keys=True))
     zf.write('.options.json', 'harness-options.json')
     os.remove('.options.json')
 
     zf.close()
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js
@@ -0,0 +1,175 @@
+/* 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 { Ci } = require('chrome');
+const { isPrivateBrowsingSupported } = require('sdk/self');
+const tabs = require('sdk/tabs');
+const { browserWindows: windows } = require('sdk/windows');
+const { isPrivate } = require('sdk/private-browsing');
+const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+const { is } = require('sdk/system/xul-app');
+const { isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
+const { merge } = require('sdk/util/object');
+
+const TAB_URL = 'data:text/html;charset=utf-8,TEST-TAB';
+
+exports.testIsPrivateBrowsingTrue = function(assert) {
+  assert.ok(isPrivateBrowsingSupported,
+            'isPrivateBrowsingSupported property is true');
+};
+
+// test tab.open with isPrivate: true
+// test isPrivate on a tab
+// test getOwnerWindow on windows and tabs
+exports.testGetOwnerWindow = function(assert, done) {
+  let window = windows.activeWindow;
+  let chromeWindow = getOwnerWindow(window);
+  assert.ok(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
+
+  tabs.open({
+    url: 'about:blank',
+    isPrivate: true,
+    onOpen: function(tab) {
+      // test that getOwnerWindow works as expected
+      if (is('Fennec')) {
+        assert.notStrictEqual(chromeWindow, getOwnerWindow(tab));
+        assert.ok(getOwnerWindow(tab) instanceof Ci.nsIDOMWindow);
+      }
+      else {
+        if (isWindowPBSupported) {
+          assert.notStrictEqual(chromeWindow,
+                                getOwnerWindow(tab),
+                                'associated window is not the same for window and window\'s tab');
+        }
+        else {
+          assert.strictEqual(chromeWindow,
+                            getOwnerWindow(tab),
+                            'associated window is the same for window and window\'s tab');
+        }
+      }
+
+      let pbSupported = isTabPBSupported || isWindowPBSupported;
+
+      // test that the tab is private if it should be
+      assert.equal(isPrivate(tab), pbSupported);
+      assert.equal(isPrivate(getOwnerWindow(tab)), pbSupported);
+
+      tab.close(function() done());
+    }
+  });
+};
+
+// test that it is possible to open a private tab
+exports.testTabOpenPrivate = function(assert, done) {
+  tabs.open({
+    url: TAB_URL,
+    isPrivate: true,
+    onReady: function(tab) {
+      assert.equal(tab.url, TAB_URL, 'opened correct tab');
+      assert.equal(isPrivate(tab), (isWindowPBSupported || isTabPBSupported));
+
+      tab.close(function() {
+        done();
+      });
+    }
+  });
+}
+
+
+// test that it is possible to open a non private tab
+exports.testTabOpenPrivateDefault = function(assert, done) {
+  tabs.open({
+    url: TAB_URL,
+    onReady: function(tab) {
+      assert.equal(tab.url, TAB_URL, 'opened correct tab');
+      assert.equal(isPrivate(tab), false);
+
+      tab.close(function() {
+        done();
+      });
+    }
+  });
+}
+
+// test that it is possible to open a non private tab in explicit case
+exports.testTabOpenPrivateOffExplicit = function(assert, done) {
+  tabs.open({
+    url: TAB_URL,
+    isPrivate: false,
+    onReady: function(tab) {
+      assert.equal(tab.url, TAB_URL, 'opened correct tab');
+      assert.equal(isPrivate(tab), false);
+
+      tab.close(function() {
+        done();
+      });
+    }
+  });
+}
+
+// test windows.open with isPrivate: true
+// test isPrivate on a window
+if (!is('Fennec')) {
+  // test that it is possible to open a private window
+  exports.testWindowOpenPrivate = function(assert, done) {
+    windows.open({
+      url: TAB_URL,
+      isPrivate: true,
+      onOpen: function(window) {
+        let tab = window.tabs[0];
+        tab.once('ready', function() {
+          assert.equal(tab.url, TAB_URL, 'opened correct tab');
+          assert.equal(isPrivate(tab), isWindowPBSupported, 'tab is private');
+
+          window.close(function() {
+            done();
+          });
+        });
+      }
+    });
+  };
+
+  exports.testIsPrivateOnWindowOn = function(assert, done) {
+    windows.open({
+      isPrivate: true,
+      onOpen: function(window) {
+        assert.equal(isPrivate(window), isWindowPBSupported, 'isPrivate for a window is true when it should be');
+        assert.equal(isPrivate(window.tabs[0]), isWindowPBSupported, 'isPrivate for a tab is false when it should be');
+        window.close(done);
+      }
+    });
+  };
+
+  exports.testIsPrivateOnWindowOffImplicit = function(assert, done) {
+    windows.open({
+      onOpen: function(window) {
+        assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
+        assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
+        window.close(done);
+      }
+    })
+  }
+
+  exports.testIsPrivateOnWindowOffExplicit = function(assert, done) {
+    windows.open({
+      isPrivate: false,
+      onOpen: function(window) {
+        assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
+        assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
+        window.close(done);
+      }
+    })
+  }
+}
+
+merge(module.exports,
+  require('./test-windows'),
+  require('./test-tabs'),
+  require('./test-page-mod'),
+  require('./test-selection'),
+  require('./test-panel')
+);
+
+require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/package.json
@@ -0,0 +1,6 @@
+{
+	"id": "private-browsing-mode-test@jetpack",
+	"permissions": {
+		"private-browsing": true
+	}
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-page-mod.js
@@ -0,0 +1,111 @@
+const { getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { PageMod } = require("sdk/page-mod");
+const { getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
+const xulApp = require('sdk/system/xul-app');
+const windowHelpers = require('sdk/window/helpers');
+const { defer } = require("sdk/core/promise");
+const { isPrivate } = require('sdk/private-browsing');
+const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
+
+function openWebpage(url, enablePrivate) {
+  let { promise, resolve, reject } = defer();
+
+  if (xulApp.is("Fennec")) {
+    let chromeWindow = getMostRecentBrowserWindow();
+    let rawTab = openTab(chromeWindow, url, {
+      isPrivate: enablePrivate
+    });
+
+    resolve(function() {
+      closeTab(rawTab)
+    });
+
+    return promise;
+  }
+  else {
+    windowHelpers.open("", {
+      features: {
+        toolbar: true,
+        private: enablePrivate
+      }
+    }).then(function(chromeWindow) {
+      if (isPrivate(chromeWindow) !== !!enablePrivate)
+        reject(new Error("Window should have Private set to " + !!enablePrivate));
+
+      let tab = getActiveTab(chromeWindow);
+      setTabURL(tab, url);
+
+      resolve(function(){
+        windowHelpers.close(chromeWindow);
+      });
+    });
+
+    return promise;
+  }
+}
+
+exports["test page-mod on private tab"] = function (assert, done) {
+  // Only set private mode when explicitely supported.
+  // (fennec 19 has some intermediate PB support where isTabPBSupported
+  // will be false, but isPrivate(worker.tab) will be true if we open a private
+  // tab)
+  let setPrivate = isTabPBSupported || isWindowPBSupported;
+
+  let id = Date.now().toString(36);
+  let frameUri = "data:text/html;charset=utf-8," + id;
+  let uri = "data:text/html;charset=utf-8," +
+            encodeURIComponent(id + "<iframe src='" + frameUri + "'><iframe>");
+
+  let count = 0;
+
+  openWebpage(uri, setPrivate).then(function(close) {
+    PageMod({
+      include: [uri, frameUri],
+
+      onAttach: function(worker) {
+        assert.ok(worker.tab.url == uri || worker.tab.url == frameUri,
+                  "Got a worker attached to the private window tab");
+
+        if (setPrivate) {
+          assert.ok(isPrivate(worker), "The worker is really private");
+          assert.ok(isPrivate(worker.tab), "The document is really private");
+        }
+        else {
+          assert.ok(!isPrivate(worker),
+                    "private browsing isn't supported, " +
+                    "so that the worker isn't private");
+          assert.ok(!isPrivate(worker.tab),
+                    "private browsing isn't supported, " +
+                    "so that the document isn't private");
+        }
+
+        if (++count == 2) {
+          this.destroy();
+          close();
+          done();
+        }
+      }
+    });
+  }).then(null, assert.fail);
+};
+
+exports["test page-mod on non-private tab"] = function (assert, done) {
+  let id = Date.now().toString(36);
+  let url = "data:text/html;charset=utf-8," + encodeURIComponent(id);
+
+  openWebpage(url, false).then(function(close){
+    PageMod({
+      include: url,
+      onAttach: function(worker) {
+        assert.equal(worker.tab.url, url,
+                     "Got a worker attached to the private window tab");
+        assert.ok(!isPrivate(worker), "The worker is really non-private");
+        assert.ok(!isPrivate(worker.tab), "The document is really non-private");
+
+        this.destroy();
+        close();
+        done();
+      }
+    });
+  }).then(null, assert.fail);
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
@@ -0,0 +1,16 @@
+'use strict';
+
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
+
+if (isWindowPBSupported) {
+  exports.testRequirePanel = function (assert) {
+    try {
+  	  require('panel');
+    }
+    catch(e) {
+  	  assert.ok(e.message.match(/Bug 816257/), 'Bug 816257 is mentioned');
+      return;
+    }
+    assert.fail('the panel module should throw an error');
+  }
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-selection.js
@@ -0,0 +1,458 @@
+/* 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 HTML = "<html>\
+  <body>\
+    <div>foo</div>\
+    <div>and</div>\
+    <textarea>noodles</textarea>\
+  </body>\
+</html>";
+
+const URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
+
+const FRAME_HTML = "<iframe src='" + URL + "'><iframe>";
+const FRAME_URL = "data:text/html;charset=utf-8," + encodeURIComponent(FRAME_HTML);
+
+const { defer } = require("sdk/core/promise");
+const { browserWindows } = require("sdk/windows");
+const tabs = require("sdk/tabs");
+const { setTabURL, getActiveTab, getTabContentWindow, closeTab, getTabs,
+  getTabTitle } = require("sdk/tabs/utils");
+const { getMostRecentBrowserWindow, isFocused } = require("sdk/window/utils");
+const { open: openNewWindow, close: closeWindow, focus } = require("sdk/window/helpers");
+const { Loader } = require("sdk/test/loader");
+const { merge } = require("sdk/util/object");
+const { isPrivate } = require("sdk/private-browsing");
+
+// General purpose utility functions
+
+/**
+ * Opens the url given and return a promise, that will be resolved with the
+ * content window when the document is ready.
+ *
+ * I believe this approach could be useful in most of our unit test, that
+ * requires to open a tab and need to access to its content.
+ */
+function open(url, options) {
+  let { promise, resolve } = defer();
+
+  if (options && typeof(options) === "object") {
+    openNewWindow("", {
+      features: merge({ toolbar: true }, options)
+    }).then(function(chromeWindow) {
+      if (isPrivate(chromeWindow) !== !!options.private)
+        throw new Error("Window should have Private set to " + !!options.private);
+
+      let tab = getActiveTab(chromeWindow);
+
+      tab.addEventListener("load", function ready(event) {
+        let { document } = getTabContentWindow(this);
+
+        if (document.readyState === "complete" && document.URL === url) {
+          this.removeEventListener(event.type, ready);
+
+          if (options.title)
+            document.title = options.title;
+
+          resolve(document.defaultView);
+        }
+      })
+
+      setTabURL(tab, url);
+    });
+
+    return promise;
+  };
+
+  tabs.open({
+    url: url,
+    onReady: function(tab) {
+      // Unfortunately there is no way to get a XUL Tab from SDK Tab on Firefox,
+      // only on Fennec. We should implement `tabNS` also on Firefox in order
+      // to have that.
+
+      // Here we assuming that the most recent browser window is the one we're
+      // doing the test, and the active tab is the one we just opened.
+      let window = getTabContentWindow(getActiveTab(getMostRecentBrowserWindow()));
+
+      resolve(window);
+    }
+  });
+
+  return promise;
+};
+
+/**
+ * Close the Active Tab
+ */
+function close(window) {
+  let { promise, resolve } = defer();
+
+  if (window && typeof(window.close) === "function") {
+    closeWindow(window).then(function() resolve());
+  }
+  else {
+    // Here we assuming that the most recent browser window is the one we're
+    // doing the test, and the active tab is the one we just opened.
+    closeTab(getActiveTab(getMostRecentBrowserWindow()));
+    resolve();
+  }
+
+  return promise;
+}
+
+/**
+ * Reload the window given and return a promise, that will be resolved with the
+ * content window after a small delay.
+ */
+function reload(window) {
+  let { promise, resolve } = defer();
+
+  // Here we assuming that the most recent browser window is the one we're
+  // doing the test, and the active tab is the one we just opened.
+  let tab = tabs.activeTab;
+
+  tab.once("ready", function () {
+    resolve(window);
+  });
+
+  window.location.reload(true);
+
+  return promise;
+}
+
+// Selection's unit test utility function
+
+/**
+ * Select the first div in the page, adding the range to the selection.
+ */
+function selectFirstDiv(window) {
+  let div = window.document.querySelector("div");
+  let selection = window.getSelection();
+  let range = window.document.createRange();
+
+  if (selection.rangeCount > 0)
+    selection.removeAllRanges();
+
+  range.selectNode(div);
+  selection.addRange(range);
+
+  return window;
+}
+
+/**
+ * Select all divs in the page, adding the ranges to the selection.
+ */
+function selectAllDivs(window) {
+  let divs = window.document.getElementsByTagName("div");
+  let selection = window.getSelection();
+
+  if (selection.rangeCount > 0)
+    selection.removeAllRanges();
+
+  for (let i = 0; i < divs.length; i++) {
+    let range = window.document.createRange();
+
+    range.selectNode(divs[i]);
+    selection.addRange(range);
+  }
+
+  return window;
+}
+
+/**
+ * Select the textarea content
+ */
+function selectTextarea(window) {
+  let selection = window.getSelection();
+  let textarea = window.document.querySelector("textarea");
+
+  if (selection.rangeCount > 0)
+    selection.removeAllRanges();
+
+  textarea.setSelectionRange(0, textarea.value.length);
+  textarea.focus();
+
+  return window;
+}
+
+/**
+ * Select the content of the first div
+ */
+function selectContentFirstDiv(window) {
+  let div = window.document.querySelector("div");
+  let selection = window.getSelection();
+  let range = window.document.createRange();
+
+  if (selection.rangeCount > 0)
+    selection.removeAllRanges();
+
+  range.selectNodeContents(div);
+  selection.addRange(range);
+
+  return window;
+}
+
+/**
+ * Dispatch the selection event for the selection listener added by
+ * `nsISelectionPrivate.addSelectionListener`
+ */
+function dispatchSelectionEvent(window) {
+  // We modify the selection in order to dispatch the selection's event, by
+  // contract the selection by one character. So if the text selected is "foo"
+  // will be "fo".
+  window.getSelection().modify("extend", "backward", "character");
+
+  return window;
+}
+
+/**
+ * Dispatch the selection event for the selection listener added by
+ * `window.onselect` / `window.addEventListener`
+ */
+function dispatchOnSelectEvent(window) {
+  let { document } = window;
+  let textarea = document.querySelector("textarea");
+  let event = document.createEvent("UIEvents");
+
+  event.initUIEvent("select", true, true, window, 1);
+
+  textarea.dispatchEvent(event);
+
+  return window;
+}
+
+// Test cases
+
+exports["test PWPB Selection Listener"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "PWPB Selection Listener"}).
+    then(function(window) {
+      selection.once("select", function() {
+        assert.equal(browserWindows.length, 2, "there should be only two windows open.");
+        assert.equal(getTabs().length, 2, "there should be only two tabs open: '" +
+                     getTabs().map(function(tab) getTabTitle(tab)).join("', '") +
+                     "'."
+        );
+
+        // window should be focused, but force the focus anyhow.. see bug 841823
+        focus(window).then(function() {
+          // check state of window
+          assert.ok(isFocused(window), "the window is focused");
+          assert.ok(isPrivate(window), "the window should be a private window");
+
+          assert.equal(selection.text, "fo");
+
+          close(window).
+            then(loader.unload).
+            then(done, assert.fail);
+        });
+      });
+      return window;
+    }).
+    then(selectContentFirstDiv).
+    then(dispatchSelectionEvent).
+    then(null, assert.fail);
+};
+
+exports["test PWPB Textarea OnSelect Listener"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "PWPB OnSelect Listener"}).
+    then(function(window) {
+      selection.once("select", function() {
+        assert.equal(browserWindows.length, 2, "there should be only two windows open.");
+        assert.equal(getTabs().length, 2, "there should be only two tabs open: '" +
+                     getTabs().map(function(tab) getTabTitle(tab)).join("', '") +
+                     "'."
+        );
+
+        // window should be focused, but force the focus anyhow.. see bug 841823
+        focus(window).then(function() {
+          assert.equal(selection.text, "noodles");
+
+          close(window).
+            then(loader.unload).
+            then(done, assert.fail);
+        });
+      });
+      return window;
+    }).
+    then(selectTextarea).
+    then(dispatchOnSelectEvent).
+    then(null, assert.fail);
+};
+
+exports["test PWPB Single DOM Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "PWPB Single DOM Selection"}).
+    then(selectFirstDiv).
+    then(focus).then(function() {
+      assert.equal(selection.isContiguous, true,
+        "selection.isContiguous with single DOM Selection works.");
+
+      assert.equal(selection.text, "foo",
+        "selection.text with single DOM Selection works.");
+
+      assert.equal(selection.html, "<div>foo</div>",
+        "selection.html with single DOM Selection works.");
+
+      let selectionCount = 0;
+      for each (let sel in selection) {
+        selectionCount++;
+
+        assert.equal(sel.text, "foo",
+          "iterable selection.text with single DOM Selection works.");
+
+        assert.equal(sel.html, "<div>foo</div>",
+          "iterable selection.html with single DOM Selection works.");
+      }
+
+      assert.equal(selectionCount, 1,
+        "One iterable selection");
+    }).then(close).then(loader.unload).then(done, assert.fail);
+}
+
+exports["test PWPB Textarea Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "PWPB Textarea Listener"}).
+    then(selectTextarea).
+    then(focus).
+    then(function() {
+
+      assert.equal(selection.isContiguous, true,
+        "selection.isContiguous with Textarea Selection works.");
+
+      assert.equal(selection.text, "noodles",
+        "selection.text with Textarea Selection works.");
+
+      assert.strictEqual(selection.html, null,
+        "selection.html with Textarea Selection works.");
+
+      let selectionCount = 0;
+      for each (let sel in selection) {
+        selectionCount++;
+
+        assert.equal(sel.text, "noodles",
+          "iterable selection.text with Textarea Selection works.");
+
+        assert.strictEqual(sel.html, null,
+          "iterable selection.html with Textarea Selection works.");
+      }
+
+      assert.equal(selectionCount, 1,
+        "One iterable selection");
+
+    }).then(close).then(loader.unload).then(done, assert.fail);
+};
+
+exports["test PWPB Set HTML in Multiple DOM Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "PWPB Set HTML in Multiple DOM Selection"}).
+    then(selectAllDivs).
+    then(focus).
+    then(function() {
+      let html = "<span>b<b>a</b>r</span>";
+
+      let expectedText = ["bar", "and"];
+      let expectedHTML = [html, "<div>and</div>"];
+
+      selection.html = html;
+
+      assert.equal(selection.text, expectedText[0],
+        "set selection.text with DOM Selection works.");
+
+      assert.equal(selection.html, expectedHTML[0],
+        "selection.html with DOM Selection works.");
+
+      let selectionCount = 0;
+      for each (let sel in selection) {
+
+        assert.equal(sel.text, expectedText[selectionCount],
+          "iterable selection.text with multiple DOM Selection works.");
+
+        assert.equal(sel.html, expectedHTML[selectionCount],
+          "iterable selection.html with multiple DOM Selection works.");
+
+        selectionCount++;
+      }
+
+      assert.equal(selectionCount, 2,
+        "Two iterable selections");
+    }).then(close).then(loader.unload).then(done, assert.fail);
+};
+
+exports["test PWPB Set Text in Textarea Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true, title: "test PWPB Set Text in Textarea Selection"}).
+    then(selectTextarea).
+    then(focus).
+    then(function() {
+
+      let text = "bar";
+
+      selection.text = text;
+
+      assert.equal(selection.text, text,
+        "set selection.text with Textarea Selection works.");
+
+      assert.strictEqual(selection.html, null,
+        "selection.html with Textarea Selection works.");
+
+      let selectionCount = 0;
+      for each (let sel in selection) {
+        selectionCount++;
+
+        assert.equal(sel.text, text,
+          "iterable selection.text with Textarea Selection works.");
+
+        assert.strictEqual(sel.html, null,
+          "iterable selection.html with Textarea Selection works.");
+      }
+
+      assert.equal(selectionCount, 1,
+        "One iterable selection");
+
+    }).then(close).then(loader.unload).then(done, assert.fail);
+};
+
+// If the platform doesn't support the PBPW, we're replacing PBPW tests
+if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
+  module.exports = {
+    "test PBPW Unsupported": function Unsupported (assert) {
+      assert.pass("Private Window Per Browsing is not supported on this platform.");
+    }
+  }
+}
+
+// If the module doesn't support the app we're being run in, require() will
+// throw.  In that case, remove all tests above from exports, and add one dummy
+// test that passes.
+try {
+  require("sdk/selection");
+}
+catch (err) {
+  if (!/^Unsupported Application/.test(err.message))
+    throw err;
+
+  module.exports = {
+    "test Unsupported Application": function Unsupported (assert) {
+      assert.pass(err.message);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js
@@ -0,0 +1,32 @@
+'use strict';
+
+const tabs = require('sdk/tabs');
+const { is } = require('sdk/system/xul-app');
+const { isPrivate } = require('sdk/private-browsing');
+const pbUtils = require('sdk/private-browsing/utils');
+const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+
+exports.testPrivateTabsAreListed = function (assert, done) {
+  let originalTabCount = tabs.length;
+
+  tabs.open({
+    url: 'about:blank',
+    isPrivate: true,
+    onOpen: function(tab) {
+      let win = getOwnerWindow(tab);
+      // PWPB case
+      if (pbUtils.isWindowPBSupported || pbUtils.isTabPBSupported) {
+        assert.ok(isPrivate(tab), "tab is private");
+        assert.equal(tabs.length, originalTabCount + 1,
+                     'New private window\'s tab are visible in tabs list');
+      }
+      else {
+      // Global case, openDialog didn't opened a private window/tab
+        assert.ok(!isPrivate(tab), "tab isn't private");
+        assert.equal(tabs.length, originalTabCount + 1,
+                     'New non-private window\'s tab is visible in tabs list');
+      }
+      tab.close(done);
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js
@@ -0,0 +1,255 @@
+/* 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 { Cc, Ci } = require('chrome');
+const { isPrivate } = require('sdk/private-browsing');
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
+const { onFocus, getMostRecentWindow, getWindowTitle,
+        getFrames, windows, open: openWindow, isWindowPrivate } = require('sdk/window/utils');
+const { open, close, focus, promise } = require('sdk/window/helpers');
+const { browserWindows } = require("sdk/windows");
+const winUtils = require("sdk/deprecated/window-utils");
+const { fromIterator: toArray } = require('sdk/util/array');
+const tabs = require('sdk/tabs');
+
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
+
+const BROWSER = 'chrome://browser/content/browser.xul';
+
+function makeEmptyBrowserWindow(options) {
+  options = options || {};
+  return open(BROWSER, {
+    features: {
+      chrome: true,
+      private: !!options.private
+    }
+  });
+}
+
+exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) {
+  var myNonPrivateWindow, myPrivateWindow;
+  var finished = false;
+  var privateWindow;
+  var privateWindowClosed = false;
+  var privateWindowOpened = false;
+
+  let wt = winUtils.WindowTracker({
+    onTrack: function(window) {
+      if (window === myPrivateWindow) {
+        assert.equal(isPrivate(window), isWindowPBSupported);
+        privateWindowOpened = true;
+      }
+    },
+    onUntrack: function(window) {
+      if (window === myPrivateWindow && isWindowPBSupported) {
+        privateWindowClosed = true;
+      }
+
+      if (window === myNonPrivateWindow) {
+        assert.equal(privateWindowClosed, isWindowPBSupported);
+        assert.ok(privateWindowOpened);
+        wt.unload();
+        done();
+      }
+    }
+  });
+
+  // make a new private window
+  myPrivateWindow = openWindow(BROWSER, {
+  	features: {
+      private: true
+    }
+  });
+  promise(myPrivateWindow, 'load').then(function(window) {
+    assert.equal(isPrivate(window), isWindowPBSupported, 'private window isPrivate');
+    assert.equal(isWindowPrivate(window), isWindowPBSupported);
+    assert.ok(getFrames(window).length > 1, 'there are frames for private window');
+    assert.equal(getWindowTitle(window), window.document.title,
+                 'getWindowTitle works');
+
+    close(myPrivateWindow).then(function() {
+      assert.pass('private window was closed');
+      makeEmptyBrowserWindow().then(function(window) {
+        myNonPrivateWindow = window;
+        assert.notDeepEqual(myPrivateWindow, myNonPrivateWindow);
+        assert.pass('opened new window');
+        close(myNonPrivateWindow).then(function() {
+          assert.pass('non private window was closed');
+        })
+      });
+    });
+  });
+};
+
+// Test setting activeWIndow and onFocus for private windows
+exports.testSettingActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
+  let browserWindow = WM.getMostRecentWindow("navigator:browser");
+  let testSteps;
+
+  assert.equal(winUtils.activeBrowserWindow, browserWindow,
+               "Browser window is the active browser window.");
+  assert.ok(!isPrivate(browserWindow), "Browser window is not private.");
+
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(focus).then(function(window) {
+    let continueAfterFocus = function(window) onFocus(window).then(nextTest);
+
+    // PWPB case
+    if (isWindowPBSupported) {
+      assert.ok(isPrivate(window), "window is private");
+      assert.notDeepEqual(winUtils.activeBrowserWindow, browserWindow);
+    }
+    // Global case
+    else {
+      assert.ok(!isPrivate(window), "window is not private");
+    }
+
+    assert.strictEqual(winUtils.activeBrowserWindow, window,
+                 "Correct active browser window pb supported");
+    assert.notStrictEqual(browserWindow, window,
+                 "The window is not the old browser window");
+
+    testSteps = [
+      function() {
+        // test setting a non private window
+        continueAfterFocus(winUtils.activeWindow = browserWindow);
+      },
+      function() {
+        assert.strictEqual(winUtils.activeWindow, browserWindow,
+                           "Correct active window [1]");
+        assert.strictEqual(winUtils.activeBrowserWindow, browserWindow,
+                           "Correct active browser window [1]");
+
+        // test focus(window)
+        focus(window).then(nextTest);
+      },
+      function(w) {
+        assert.strictEqual(w, window, 'require("sdk/window/helpers").focus on window works');
+        assert.strictEqual(winUtils.activeBrowserWindow, window,
+                           "Correct active browser window [2]");
+        assert.strictEqual(winUtils.activeWindow, window,
+                           "Correct active window [2]");
+
+        // test setting a private window
+        continueAfterFocus(winUtils.activeWindow = window);
+      },
+      function() {
+        assert.deepEqual(winUtils.activeBrowserWindow, window,
+                         "Correct active browser window [3]");
+        assert.deepEqual(winUtils.activeWindow, window,
+                         "Correct active window [3]");
+
+        // just to get back to original state
+        continueAfterFocus(winUtils.activeWindow = browserWindow);
+      },
+      function() {
+        assert.deepEqual(winUtils.activeBrowserWindow, browserWindow,
+                         "Correct active browser window when pb mode is supported [4]");
+        assert.deepEqual(winUtils.activeWindow, browserWindow,
+                         "Correct active window when pb mode is supported [4]");
+
+        close(window).then(done);
+      }
+    ];
+
+    function nextTest() {
+      let args = arguments;
+      if (testSteps.length) {
+        require('sdk/timers').setTimeout(function() {
+          (testSteps.shift()).apply(null, args);
+        }, 0);
+      }
+    }
+    nextTest();
+  });
+};
+
+exports.testActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(focus).then(function(window) {
+    // PWPB case
+    if (isWindowPBSupported) {
+      assert.equal(isPrivate(winUtils.activeWindow), true,
+                   "active window is private");
+      assert.equal(isPrivate(winUtils.activeBrowserWindow), true,
+                   "active browser window is private");
+      assert.ok(isWindowPrivate(window), "window is private");
+      assert.ok(isPrivate(window), "window is private");
+
+      // pb mode is supported
+      assert.ok(
+        isWindowPrivate(winUtils.activeWindow),
+        "active window is private when pb mode is supported");
+      assert.ok(
+        isWindowPrivate(winUtils.activeBrowserWindow),
+        "active browser window is private when pb mode is supported");
+      assert.ok(isPrivate(winUtils.activeWindow),
+                "active window is private when pb mode is supported");
+      assert.ok(isPrivate(winUtils.activeBrowserWindow),
+        "active browser window is private when pb mode is supported");
+    }
+    // Global case
+    else {
+      assert.equal(isPrivate(winUtils.activeWindow), false,
+                   "active window is not private");
+      assert.equal(isPrivate(winUtils.activeBrowserWindow), false,
+                   "active browser window is not private");
+      assert.equal(isWindowPrivate(window), false, "window is not private");
+      assert.equal(isPrivate(window), false, "window is not private");
+    }
+
+    close(window).then(done);
+  });
+}
+
+exports.testWindowIteratorIgnoresPrivateWindows = function(assert, done) {
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(focus).then(function(window) {
+    assert.equal(isWindowPrivate(window), isWindowPBSupported);
+    assert.ok(toArray(winUtils.windowIterator()).indexOf(window) > -1,
+              "window is in windowIterator()");
+
+    close(window).then(done);
+  });
+};
+
+// test that it is not possible to find a private window in
+// windows module's iterator
+exports.testWindowIteratorPrivateDefault = function(assert, done) {
+  // there should only be one window open here, if not give us the
+  // the urls
+  if (browserWindows.length > 1) {
+    for each (let tab in tabs) {
+      assert.fail("TAB URL: " + tab.url);
+    }
+  }
+  else {
+    assert.equal(browserWindows.length, 1, 'only one window open');
+  }
+
+  open('chrome://browser/content/browser.xul', {
+    features: {
+      private: true,
+      chrome: true
+    }
+  }).then(focus).then(function(window) {
+    // test that there is a private window opened
+    assert.equal(isPrivate(window), isWindowPBSupported, 'there is a private window open');
+    assert.equal(isPrivate(winUtils.activeWindow), isWindowPBSupported);
+    assert.equal(isPrivate(getMostRecentWindow()), isWindowPBSupported);
+    assert.equal(isPrivate(browserWindows.activeWindow), isWindowPBSupported);
+
+    assert.equal(browserWindows.length, 2, '2 windows open');
+    assert.equal(windows(null, { includePrivate: true }).length, 2);
+
+    close(window).then(done);
+  });
+};
--- a/addon-sdk/source/test/pagemod-test-helpers.js
+++ b/addon-sdk/source/test/pagemod-test-helpers.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc,Ci} = require("chrome");
 const timer = require("sdk/timers");
 const xulApp = require("sdk/system/xul-app");
 const { Loader } = require("sdk/test/loader");
+const { openTab, getBrowserForTab, closeTab } = require("sdk/tabs/utils");
 
 /**
  * A helper function that creates a PageMod, then opens the specified URL
  * and checks the effect of the page mod on 'onload' event via testCallback.
  */
 exports.testPageMod = function testPageMod(test, testURL, pageModOptions,
                                            testCallback, timeout) {
   if (!xulApp.versionInRange(xulApp.platformVersion, "1.9.3a3", "*") &&
@@ -35,33 +36,33 @@ exports.testPageMod = function testPageM
   else
     test.waitUntilDone();
 
   let loader = Loader(module);
   let pageMod = loader.require("sdk/page-mod");
 
   var pageMods = [new pageMod.PageMod(opts) for each(opts in pageModOptions)];
 
-  var tabBrowser = browserWindow.gBrowser;
-  var newTab = tabBrowser.addTab(testURL);
-  tabBrowser.selectedTab = newTab;
-  var b = tabBrowser.getBrowserForTab(newTab);
+  let newTab = openTab(browserWindow, testURL, {
+    inBackground: false
+  });
+  var b = getBrowserForTab(newTab);
 
   function onPageLoad() {
     b.removeEventListener("load", onPageLoad, true);
     // Delay callback execute as page-mod content scripts may be executed on
     // load event. So page-mod actions may not be already done.
     // If we delay even more contentScriptWhen:'end', we may want to modify
     // this code again.
     timer.setTimeout(testCallback, 0,
       b.contentWindow.wrappedJSObject, 
       function done() {
         pageMods.forEach(function(mod) mod.destroy());
         // XXX leaks reported if we don't close the tab?
-        tabBrowser.removeTab(newTab);
+        closeTab(newTab);
         loader.unload();
         test.done();
       }
     );
   }
   b.addEventListener("load", onPageLoad, true);
 
   return pageMods;
--- a/addon-sdk/source/test/private-browsing/global.js
+++ b/addon-sdk/source/test/private-browsing/global.js
@@ -1,16 +1,15 @@
 /* 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';
 
-let { Cc, Ci } = require("chrome");
 const timer = require("sdk/timers");
-const { LoaderWithHookedConsole, pb, pbUtils, activate, deactivate } = require("./helper");
+const { LoaderWithHookedConsole, deactivate, pb, pbUtils } = require("./helper");
 const tabs = require("sdk/tabs");
 
 exports["test activate private mode via handler"] = function(test) {
   test.waitUntilDone();
 
   function onReady(tab) {
     if (tab.url == "about:robots")
       tab.close(function() pb.activate());
@@ -42,55 +41,65 @@ exports["test activate private mode via 
 
 // tests that isActive has the same value as the private browsing service
 // expects
 exports.testGetIsActive = function (test) {
   test.waitUntilDone();
 
   test.assertEqual(pb.isActive, false,
                    "private-browsing.isActive is correct without modifying PB service");
+  test.assertEqual(pb.isPrivate(), false,
+                   "private-browsing.sPrivate() is correct without modifying PB service");
 
   pb.once("start", function() {
     test.assert(pb.isActive,
                   "private-browsing.isActive is correct after modifying PB service");
+    test.assert(pb.isPrivate(),
+                  "private-browsing.sPrivate() is correct after modifying PB service");
     // Switch back to normal mode.
     pb.deactivate();
   });
   pb.activate();
 
   pb.once("stop", function() {
     test.assert(!pb.isActive,
                 "private-browsing.isActive is correct after modifying PB service");
+    test.assert(!pb.isPrivate(),
+                "private-browsing.sPrivate() is correct after modifying PB service");
     test.done();
   });
 };
 
 exports.testStart = function(test) {
   test.waitUntilDone();
 
   pb.on("start", function onStart() {
     test.assertEqual(this, pb, "`this` should be private-browsing module");
     test.assert(pbUtils.getMode(),
                 'private mode is active when "start" event is emitted');
     test.assert(pb.isActive,
                 '`isActive` is `true` when "start" event is emitted');
+    test.assert(pb.isPrivate(),
+                '`isPrivate` is `true` when "start" event is emitted');
     pb.removeListener("start", onStart);
     deactivate(function() test.done());
   });
   pb.activate();
 };
 
 exports.testStop = function(test) {
   test.waitUntilDone();
   pb.once("stop", function onStop() {
     test.assertEqual(this, pb, "`this` should be private-browsing module");
     test.assertEqual(pbUtils.getMode(), false,
                      "private mode is disabled when stop event is emitted");
     test.assertEqual(pb.isActive, false,
                      "`isActive` is `false` when stop event is emitted");
+    test.assertEqual(pb.isPrivate(), false,
+                     "`isPrivate()` is `false` when stop event is emitted");
     test.done();
   });
   pb.activate();
   pb.once("start", function() {
     pb.deactivate();
   });
 };
 
@@ -101,97 +110,107 @@ exports.testBothListeners = function(tes
 
   function onStop() {
     test.assertEqual(stop, false,
                      "stop callback must be called only once");
     test.assertEqual(pbUtils.getMode(), false,
                      "private mode is disabled when stop event is emitted");
     test.assertEqual(pb.isActive, false,
                      "`isActive` is `false` when stop event is emitted");
+    test.assertEqual(pb.isPrivate(), false,
+                     "`isPrivate()` is `false` when stop event is emitted");
 
     pb.on("start", finish);
     pb.removeListener("start", onStart);
     pb.removeListener("start", onStart2);
     pb.activate();
     stop = true;
   }
 
   function onStart() {
     test.assertEqual(false, start,
                      "stop callback must be called only once");
     test.assert(pbUtils.getMode(),
                 "private mode is active when start event is emitted");
     test.assert(pb.isActive,
                 "`isActive` is `true` when start event is emitted");
+    test.assert(pb.isPrivate(),
+                "`isPrivate()` is `true` when start event is emitted");
 
     pb.on("stop", onStop);
     pb.deactivate();
     start = true;
   }
 
   function onStart2() {
     test.assert(start, "start listener must be called already");
     test.assertEqual(false, stop, "stop callback must not be called yet");
   }
 
   function finish() {
     test.assert(pbUtils.getMode(), true,
                 "private mode is active when start event is emitted");
     test.assert(pb.isActive,
                 "`isActive` is `true` when start event is emitted");
+    test.assert(pb.isPrivate(),
+                "`isPrivate()` is `true` when start event is emitted");
 
     pb.removeListener("start", finish);
     pb.removeListener("stop", onStop);
 
     pb.deactivate();
     pb.once("stop", function () {
       test.assertEqual(pbUtils.getMode(), false);
       test.assertEqual(pb.isActive, false);
+      test.assertEqual(pb.isPrivate(), false);
 
       test.done();
     });
   }
 
   pb.on("start", onStart);
   pb.on("start", onStart2);
   pb.activate();
 };
 
 exports.testAutomaticUnload = function(test) {
   test.waitUntilDone();
 
   // Create another private browsing instance and unload it
-  let { loader, errors } = LoaderWithHookedConsole();
+  let { loader, errors } = LoaderWithHookedConsole(module);
   let pb2 = loader.require("sdk/private-browsing");
   let called = false;
   pb2.on("start", function onStart() {
     called = true;
     test.fail("should not be called:x");
   });
   loader.unload();
 
   // Then switch to private mode in order to check that the previous instance
   // is correctly destroyed
   pb.once("start", function onStart() {
     timer.setTimeout(function () {
       test.assert(!called, 
         "First private browsing instance is destroyed and inactive");
       // Must reset to normal mode, so that next test starts with it.
-      deactivate(function() test.done());
+      deactivate(function() {
+        test.assert(errors.length, 0, "should have been 1 deprecation error");
+        test.done();
+      });
     }, 0);
   });
 
   pb.activate();
 };
 
 exports.testUnloadWhileActive = function(test) {
   test.waitUntilDone();
 
   let called = false;
-  let { loader, errors } = LoaderWithHookedConsole();
+  let { loader, errors } = LoaderWithHookedConsole(module);
   let pb2 = loader.require("sdk/private-browsing");
   let ul = loader.require("sdk/system/unload");
 
   let unloadHappened = false;
   ul.when(function() {
     unloadHappened = true;
     timer.setTimeout(function() {
       pb.deactivate();
@@ -201,14 +220,15 @@ exports.testUnloadWhileActive = function
     loader.unload();
   });
   pb2.once("stop", function() {
     called = true;
     test.assert(unloadHappened, "the unload event should have already occurred.");
     test.fail("stop should not have been fired");
   });
   pb.once("stop", function() {
-    test.assert(!called, "stop was not called on unload")
+    test.assert(!called, "stop was not called on unload");
+    test.assert(errors.length, 2, "should have been 2 deprecation errors");
     test.done();
   });
 
   pb.activate();
 };
--- a/addon-sdk/source/test/private-browsing/helper.js
+++ b/addon-sdk/source/test/private-browsing/helper.js
@@ -1,35 +1,37 @@
 /* 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';
 
-let { Cc,Ci } = require('chrome');
-const unload = require("sdk/system/unload");
 const { Loader } = require('sdk/test/loader');
-const { windows: windowsIterator } = require("sdk/window/utils");
-const windows = require("sdk/windows").browserWindows;
 
-let { loader } = LoaderWithHookedConsole();
+const { loader } = LoaderWithHookedConsole(module);
+
 const pb = loader.require('sdk/private-browsing');
 const pbUtils = loader.require('sdk/private-browsing/utils');
 
-function LoaderWithHookedConsole() {
+function LoaderWithHookedConsole(module) {
+  let globals = {};
   let errors = [];
-  let loader = Loader(module, {
-    console: Object.create(console, {
-      error: { value: function(e) {
+
+  globals.console = Object.create(console, {
+    error: {
+      value: function(e) {
+        errors.push(e);
         if (!/DEPRECATED:/.test(e)) {
           console.error(e);
         }
-      }}
-    })
+      }
+    }
   });
 
+  let loader = Loader(module, globals);
+
   return {
     loader: loader,
     errors: errors
   }
 }
 
 function deactivate(callback) {
   if (pbUtils.isGlobalPBSupported) {
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/private-browsing/tabs.js
@@ -0,0 +1,24 @@
+'use strict';
+
+const { Ci } = require('chrome');
+const { openTab, closeTab } = require('sdk/tabs/utils');
+const browserWindows = require('sdk/windows');
+const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+
+exports.testIsPrivateOnTab = function(test) {
+  let window = browserWindows.activeWindow;
+  let chromeWindow = getOwnerWindow(window);
+  test.assert(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
+  test.assert(!pb.isPrivate(chromeWindow), 'the top level window is not private');
+
+  let rawTab = openTab(chromeWindow, 'data:text/html,<h1>Hi!</h1>', {
+    isPrivate: true
+  });
+
+  // test that the tab is private
+  test.assert(rawTab.browser.docShell.QueryInterface(Ci.nsILoadContext).usePrivateBrowsing);
+  test.assert(pb.isPrivate(rawTab.browser.contentWindow));
+  test.assert(pb.isPrivate(rawTab.browser));
+
+  closeTab(rawTab);
+}
--- a/addon-sdk/source/test/private-browsing/windows.js
+++ b/addon-sdk/source/test/private-browsing/windows.js
@@ -1,30 +1,77 @@
 /* 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 { pb, pbUtils } = require('./helper');
-const { openDialog } = require('sdk/window/utils');
+const { openDialog, open } = require('sdk/window/utils');
+const { promise, close } = require('sdk/window/helpers');
+const { isPrivate } = require('sdk/private-browsing');
+const { browserWindows: windows } = require('sdk/windows');
 
-exports["test Per Window Private Browsing getter"] = function(assert, done) {
+// test openDialog() from window/utils with private option
+// test isActive state in pwpb case
+// test isPrivate on ChromeWindow
+exports.testPerWindowPrivateBrowsingGetter = function(assert, done) {
   let win = openDialog({
     private: true
   });
 
-  win.addEventListener('DOMContentLoaded', function onload() {
-    win.removeEventListener('DOMContentLoaded', onload, false);
-
+  promise(win, 'DOMContentLoaded').then(function onload() {
     assert.equal(pbUtils.getMode(win),
                  true, 'Newly opened window is in PB mode');
+    assert.ok(isPrivate(win), 'isPrivate(window) is true');
     assert.equal(pb.isActive, false, 'PB mode is not active');
 
-    win.addEventListener("unload", function onunload() {
-      win.removeEventListener('unload', onload, false);
+    close(win).then(function() {
       assert.equal(pb.isActive, false, 'PB mode is not active');
       done();
-    }, false);
-    win.close();
-  }, false);
+    });
+  });
+}
+
+// test open() from window/utils with private feature
+// test isActive state in pwpb case
+// test isPrivate on ChromeWindow
+exports.testPerWindowPrivateBrowsingGetter = function(assert, done) {
+  let win = open('chrome://browser/content/browser.xul', {
+    features: {
+      private: true
+    }
+  });
+
+  promise(win, 'DOMContentLoaded').then(function onload() {
+    assert.equal(pbUtils.getMode(win),
+                 true, 'Newly opened window is in PB mode');
+    assert.ok(isPrivate(win), 'isPrivate(window) is true');
+    assert.equal(pb.isActive, false, 'PB mode is not active');
+
+    close(win).then(function() {
+      assert.equal(pb.isActive, false, 'PB mode is not active');
+      done();
+    });
+  });
+}
+
+exports.testIsPrivateOnWindowOn = function(assert, done) {
+  windows.open({
+    isPrivate: true,
+    onOpen: function(window) {
+      assert.equal(isPrivate(window), false, 'isPrivate for a window is true when it should be');
+      assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
+      window.close(done);
+    }
+  });
+}
+
+exports.testIsPrivateOnWindowOff = function(assert, done) {
+  windows.open({
+    onOpen: function(window) {
+      assert.equal(isPrivate(window), false, 'isPrivate for a window is false when it should be');
+      assert.equal(isPrivate(window.tabs[0]), false, 'isPrivate for a tab is false when it should be');
+      window.close(done);
+    }
+  })
 }
 
 require("test").run(exports);
--- a/addon-sdk/source/test/tabs/test-fennec-tabs.js
+++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js
@@ -1,38 +1,28 @@
 /* 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 { Cc, Ci } = require('chrome');
-const { Loader } = require('sdk/test/loader');
+const { Loader, LoaderWithHookedConsole } = require('sdk/test/loader');
 const timer = require('sdk/timers');
 const tabs = require('sdk/tabs');
 const windows = require('sdk/windows');
 
 const tabsLen = tabs.length;
 const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
-const ERR_MSG = 'Error: This method is not yet supported by Fennec';
 
-function LoaderWithHookedConsole() {
-  let errors = [];
-  let loader = Loader(module, {
-    console: Object.create(console, {
-      error: { value: function(error) {
-        errors.push(error);
-      }}
-    })
-  });
-
-  return {
-    loader: loader,
-    errors: errors
-  }
-}
+// Fennec error message dispatched on all currently unimplement tab features,
+// that match LoaderWithHookedConsole messages object pattern
+const ERR_FENNEC_MSG = {
+  type: "error",
+  msg: "This method is not yet supported by Fennec"
+};
 
 // TEST: tab unloader
 exports.testAutomaticDestroy = function(test) {
   test.waitUntilDone();
 
   let called = false;
 
   let loader2 = Loader(module);
@@ -104,29 +94,32 @@ exports.testAutomaticDestroy = function(
   });
 
   tabs.open('data:text/html;charset=utf-8,foo');
 };
 
 // TEST: tab properties
 exports.testTabProperties = function(test) {
   test.waitUntilDone();
-  let { loader, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole();
   let tabs = loader.require('sdk/tabs');
 
   let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
   let tabsLen = tabs.length;
   tabs.open({
     url: url,
     onReady: function(tab) {
       test.assertEqual(tab.title, "foo", "title of the new tab matches");
       test.assertEqual(tab.url, url, "URL of the new tab matches");
       test.assert(tab.favicon, "favicon of the new tab is not empty");
       // TODO: remove need for this test by implementing the favicon feature
-      test.assertEqual(errors.length, 1, "favicon logs an error for now");
+      // Poors man deepEqual with JSON.stringify...
+      test.assertEqual(JSON.stringify(messages),
+                       JSON.stringify([ERR_FENNEC_MSG]),
+                       "favicon logs an error for now");
       test.assertEqual(tab.style, null, "style of the new tab matches");
       test.assertEqual(tab.index, tabsLen, "index of the new tab matches");
       test.assertNotEqual(tab.getThumbnail(), null, "thumbnail of the new tab matches");
       test.assertNotEqual(tab.id, null, "a tab object always has an id property");
 
       tab.close(function() {
         loader.unload();
 
@@ -225,113 +218,125 @@ exports.testTabReload = function(test) {
     }
   });
 };
 
 // TEST: tab.move()
 exports.testTabMove = function(test) {
   test.waitUntilDone();
 
-  let { loader, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole();
   let tabs = loader.require('sdk/tabs');
 
   let url = "data:text/html;charset=utf-8,testTabMove";
 
   tabs.open({
     url: url,
     onOpen: function(tab1) {
       test.assert(tab1.index >= 0, "opening a tab returns a tab w/ valid index");
 
       tabs.open({
         url: url,
         onOpen: function(tab) {
           let i = tab.index;
           test.assert(tab.index > tab1.index, "2nd tab has valid index");
           tab.index = 0;
           test.assertEqual(tab.index, i, "tab index after move matches");
-          test.assertEqual(errors.length, 1, "setting tab.index logs error");
-
+          test.assertEqual(JSON.stringify(messages),
+                           JSON.stringify([ERR_FENNEC_MSG]),
+                           "setting tab.index logs error");
           // end test
           tab1.close(function() tab.close(function() {
             loader.unload();
             test.done();
           }));
         }
       });
     }
   });
 };
 
 // TEST: open tab with default options
 exports.testTabsOpen_alt = function(test) {
   test.waitUntilDone();
 
-  let { loader, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole();
   let tabs = loader.require('sdk/tabs');
   let url = "data:text/html;charset=utf-8,default";
 
   tabs.open({
     url: url,
     onReady: function(tab) {
       test.assertEqual(tab.url, url, "URL of the new tab matches");
       test.assertEqual(tabs.activeTab, tab, "URL of active tab in the current window matches");
       test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
-      test.assertEqual(errors.length, 1, "isPinned logs error");
+      test.assertEqual(messages.length, 1, "isPinned logs error");
 
       // end test
       tab.close(function() {
         loader.unload();
         test.done();
       });
     }
   });
 };
 
 // TEST: open pinned tab
 exports.testOpenPinned_alt = function(test) {
     test.waitUntilDone();
 
-    let { loader, errors } = LoaderWithHookedConsole();
+    let { loader, messages } = LoaderWithHookedConsole();
     let tabs = loader.require('sdk/tabs');
     let url = "about:blank";
 
     tabs.open({
       url: url,
       isPinned: true,
       onOpen: function(tab) {
         test.assertEqual(tab.isPinned, false, "The new tab is pinned");
-        test.assertEqual(errors.length, 2, "isPinned logs error");
+        // We get two error message: one for tabs.open's isPinned argument
+        // and another one for tab.isPinned
+        test.assertEqual(JSON.stringify(messages),
+                         JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
+                         "isPinned logs error");
 
         // end test
         tab.close(function() {
           loader.unload();
           test.done();
         });
       }
     });
 };
 
 // TEST: pin/unpin opened tab
 exports.testPinUnpin_alt = function(test) {
     test.waitUntilDone();
 
-    let { loader, errors } = LoaderWithHookedConsole();
+    let { loader, messages } = LoaderWithHookedConsole();
     let tabs = loader.require('sdk/tabs');
     let url = "data:text/html;charset=utf-8,default";
 
     tabs.open({
       url: url,
       onOpen: function(tab) {
         tab.pin();
         test.assertEqual(tab.isPinned, false, "The tab was pinned correctly");
-        test.assertEqual(errors.length, 2, "tab.pin() logs error");
+        test.assertEqual(JSON.stringify(messages),
+                         JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
+                         "tab.pin() logs error");
+
+        // Clear console messages for the following test
+        messages.length = 0;
 
         tab.unpin();
         test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
-        test.assertEqual(errors.length, 4, "tab.unpin() logs error");
+        test.assertEqual(JSON.stringify(messages),
+                         JSON.stringify([ERR_FENNEC_MSG, ERR_FENNEC_MSG]),
+                         "tab.unpin() logs error");
 
         // end test
         tab.close(function() {
           loader.unload();
           test.done();
         });
       }
     });
--- a/addon-sdk/source/test/test-app-strings.js
+++ b/addon-sdk/source/test/test-app-strings.js
@@ -16,22 +16,22 @@ exports.testStringBundle = function(test
   let appLocale = Cc["@mozilla.org/intl/nslocaleservice;1"].
                   getService(Ci.nsILocaleService).
                   getApplicationLocale();
 
   let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"].
                      getService(Ci.nsIStringBundleService).
                      createBundle(url, appLocale);
 
-  let (name = "Yes") {
+  let (name = "CheckMessage") {
     test.assertEqual(strings.get(name), stringBundle.GetStringFromName(name),
                      "getting a string returns the string");
   }
 
-  let (name = "ExtensionCapability", args = ["foo"]) {
+  let (name = "CreateWrapperDenied", args = ["foo"]) {
     test.assertEqual(strings.get(name, args),
                      stringBundle.formatStringFromName(name, args, args.length),
                      "getting a formatted string returns the formatted string");
   }
 
   test.assertRaises(function () strings.get("nonexistentString"),
                     "String 'nonexistentString' could not be retrieved from " +
                     "the bundle due to an unknown error (it doesn't exist?).",
--- a/addon-sdk/source/test/test-content-worker.js
+++ b/addon-sdk/source/test/test-content-worker.js
@@ -1,17 +1,17 @@
 /* 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 stirct";
 
 const { Cc, Ci } = require("chrome");
 const { setTimeout } = require("sdk/timers");
-const { Loader, Require, override } = require("sdk/test/loader");
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
 const { Worker } = require("sdk/content/worker");
 
 const DEFAULT_CONTENT_URL = "data:text/html;charset=utf-8,foo";
 
 function makeWindow(contentURL) {
   let content =
     "<?xml version=\"1.0\"?>" +
     "<window " +
@@ -343,34 +343,22 @@ exports["test:nothing is leaked to conte
       }
     });
   }
 );
 
 exports["test:ensure console.xxx works in cs"] = WorkerTest(
   DEFAULT_CONTENT_URL,
   function(assert, browser, done) {
-
-    // Create a new module loader in order to be able to create a `console`
-    // module mockup:
-    let loader = Loader(module, {
-      console: {
-        log: hook.bind("log"),
-        info: hook.bind("info"),
-        warn: hook.bind("warn"),
-        error: hook.bind("error"),
-        debug: hook.bind("debug"),
-        exception: hook.bind("exception")
-      }
-    });
+    let { loader } = LoaderWithHookedConsole(module, onMessage);
 
     // Intercept all console method calls
     let calls = [];
-    function hook(msg) {
-      assert.equal(this, msg,
+    function onMessage(type, msg) {
+      assert.equal(type, msg,
                        "console.xxx(\"xxx\"), i.e. message is equal to the " +
                        "console method name we are calling");
       calls.push(msg);
     }
 
     // Finally, create a worker that will call all console methods
     let worker =  loader.require("sdk/content/worker").Worker({
       window: browser.contentWindow,
@@ -652,32 +640,21 @@ exports["test:check worker API with page
     });
 
   }
 );
 
 exports["test:global postMessage"] = WorkerTest(
   DEFAULT_CONTENT_URL,
   function(assert, browser, done) {
-    // Create a new module loader in order to be able to create a `console`
-    // module mockup:
-    let loader = Loader(module, {
-      console: {
-        log: hook.bind(null, "log"),
-        info: hook.bind(null, "info"),
-        warn: hook.bind(null, "warn"),
-        error: hook.bind(null, "error"),
-        debug: hook.bind(null, "debug"),
-        exception: hook.bind(null, "exception")
-      }
-    });
+    let { loader } = LoaderWithHookedConsole(module, onMessage);
 
     // Intercept all console method calls
     let seenMessages = 0;
-    function hook(type, message) {
+    function onMessage(type, message) {
       seenMessages++;
       assert.equal(type, "error", "Should be an error");
       assert.equal(message, "DEPRECATED: The global `postMessage()` function in " +
                             "content scripts is deprecated in favor of the " +
                             "`self.postMessage()` function, which works the same. " +
                             "Replace calls to `postMessage()` with calls to " +
                             "`self.postMessage()`." +
                             "For more info on `self.on`, see " +
--- a/addon-sdk/source/test/test-deprecate.js
+++ b/addon-sdk/source/test/test-deprecate.js
@@ -1,104 +1,94 @@
 /* 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/. */
 
 const deprecate = require("sdk/util/deprecate");
-var { Loader } = require("sdk/test/loader");
-
-function LoaderWithHookedConsole() {
-  let errors = [];
-  let loader = Loader(module, {
-    console: Object.create(console, {
-      error: { value: function(error) {
-        errors.push(error);
-      }}
-    })
-  });
-
-  return {
-    loader: loader,
-    deprecate: loader.require("sdk/util/deprecate"),
-    errors: errors
-  }
-}
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
 
 exports["test Deprecate Usage"] = function testDeprecateUsage(assert) {
-  let { loader, deprecate, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  let deprecate = loader.require("sdk/util/deprecate");
 
   function functionIsDeprecated() {
     deprecate.deprecateUsage("foo");
   }
 
   functionIsDeprecated();
 
-  assert.equal(errors.length, 1, "only one error is dispatched");
+  assert.equal(messages.length, 1, "only one error is dispatched");
+  assert.equal(messages[0].type, "error", "the console message is an error");
 
-  let msg = errors[0];
+  let msg = messages[0].msg;
 
   assert.ok(msg.indexOf("foo") !== -1,
             "message contains the given message");
   assert.ok(msg.indexOf("functionIsDeprecated") !== -1,
             "message contains name of the caller function");
   assert.ok(msg.indexOf(module.uri) !== -1,
              "message contains URI of the caller module");
 
   loader.unload();
 }
 
 exports["test Deprecate Function"] = function testDeprecateFunction(assert) {
-  let { loader, deprecate, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  let deprecate = loader.require("sdk/util/deprecate");
 
   let self = {};
   let arg1 = "foo";
   let arg2 = {};
 
   function originalFunction(a1, a2) {
     assert.equal(this, self);
     assert.equal(a1, arg1);
     assert.equal(a2, arg2);
   };
 
   let deprecateFunction = deprecate.deprecateFunction(originalFunction,
                                                        "bar");
 
   deprecateFunction.call(self, arg1, arg2);
 
-  assert.equal(errors.length, 1,
-                   "only one error is dispatched");
+  assert.equal(messages.length, 1, "only one error is dispatched");
+  assert.equal(messages[0].type, "error", "the console message is an error");
 
-  let msg = errors[0];
+  let msg = messages[0].msg;
   assert.ok(msg.indexOf("bar") !== -1, "message contains the given message");
   assert.ok(msg.indexOf("testDeprecateFunction") !== -1,
             "message contains name of the caller function");
   assert.ok(msg.indexOf(module.uri) !== -1,
             "message contains URI of the caller module");
 
   loader.unload();
 }
 
 exports.testDeprecateEvent = function(assert, done) {
-  let { loader, deprecate, errors } = LoaderWithHookedConsole();
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  let deprecate = loader.require("sdk/util/deprecate");
 
   let { on, emit } = loader.require('sdk/event/core');
   let testObj = {};
   testObj.on = deprecate.deprecateEvent(on.bind(null, testObj), 'BAD', ['fire']);
 
   testObj.on('fire', function() {
     testObj.on('water', function() {
-      assert.equal(errors.length, 1, "only one error is dispatched");
+      assert.equal(messages.length, 1, "only one error is dispatched");
       loader.unload();
       done();
     })
-    assert.equal(errors.length, 1, "only one error is dispatched");
+    assert.equal(messages.length, 1, "only one error is dispatched");
     emit(testObj, 'water');
   });
-  assert.equal(errors.length, 1, "only one error is dispatched");
-  let msg = errors[0];console.log(msg);
+
+  assert.equal(messages.length, 1, "only one error is dispatched");
+  assert.equal(messages[0].type, "error", "the console message is an error");
+  let msg = messages[0].msg;
+
   assert.ok(msg.indexOf("BAD") !== -1, "message contains the given message");
   assert.ok(msg.indexOf("deprecateEvent") !== -1,
             "message contains name of the caller function");
   assert.ok(msg.indexOf(module.uri) !== -1,
             "message contains URI of the caller module");
 
   emit(testObj, 'fire');
 }
--- a/addon-sdk/source/test/test-event-core.js
+++ b/addon-sdk/source/test/test-event-core.js
@@ -1,16 +1,16 @@
 /* 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 { on, once, off, emit, count, amass } = require('sdk/event/core');
-const { Loader } = require('sdk/test/loader');
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
 
 exports['test add a listener'] = function(assert) {
   let events = [ { name: 'event#1' }, 'event#2' ];
   let target = { name: 'target' };
 
   on(target, 'message', function(message) {
     assert.equal(this, target, 'this is a target object');
     assert.equal(message, events.shift(), 'message is emitted event');
@@ -154,37 +154,36 @@ exports['test error handling'] = functio
   on(target, 'error', function(boom) {
     assert.equal(boom, error, 'thrown exception causes error event');
   });
   emit(target, 'message');
 };
 
 exports['test unhandled errors'] = function(assert) {
   let exceptions = [];
-  let loader = Loader(module, {
-    console: Object.create(console, {
-      exception: { value: function(e) {
-        exceptions.push(e);
-      }}
-    })
-  });
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  
   let { emit, on } = loader.require('sdk/event/core');
   let target = {};
   let boom = Error('Boom!');
   let drax = Error('Draax!!');
 
   on(target, 'message', function() { throw boom; });
 
   emit(target, 'message');
-  assert.ok(~String(exceptions[0]).indexOf('Boom!'),
+  assert.equal(messages.length, 1, 'Got the first exception');
+  assert.equal(messages[0].type, 'exception', 'The console message is exception');
+  assert.ok(~String(messages[0].msg).indexOf('Boom!'),
             'unhandled exception is logged');
 
   on(target, 'error', function() { throw drax; });
   emit(target, 'message');
-  assert.ok(~String(exceptions[1]).indexOf('Draax!'),
+  assert.equal(messages.length, 2, 'Got the second exception');
+  assert.equal(messages[1].type, 'exception', 'The console message is exception');
+  assert.ok(~String(messages[1].msg).indexOf('Draax!'),
             'error in error handler is logged');
 };
 
 exports['test count'] = function(assert) {
   let target = {};
 
   assert.equal(count(target, 'foo'), 0, 'no listeners for "foo" events');
   on(target, 'foo', function() {});
--- a/addon-sdk/source/test/test-events.js
+++ b/addon-sdk/source/test/test-events.js
@@ -1,15 +1,15 @@
 /* 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 { Loader } = require('sdk/test/loader');
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
 
 // Exposing private methods as public in order to test
 const EventEmitter = require('sdk/deprecated/events').EventEmitter.compose({
   listeners: function(type) this._listeners(type),
   emit: function() this._emit.apply(this, arguments),
   emitOnObject: function() this._emitOnObject.apply(this, arguments),
   removeAllListeners: function(type) this._removeAllListeners(type)
 });
@@ -251,21 +251,17 @@ exports["test:removing once"] = function
   e._emit("foo", "bug-656684");
 };
 
 // Bug 726967: Ensure that `emit` doesn't do an infinite loop when `error`
 // listener throws an exception
 exports['test:emitLoop'] = function(test) {
   // Override the console for this test so it doesn't log the exception to the
   // test output
-  let loader = Loader(module, {
-    console: Object.create(console, {
-      exception: { value: function(e) { }}
-    })
-  });
+  let { loader } = LoaderWithHookedConsole(module);
 
   let EventEmitter = loader.require('sdk/deprecated/events').EventEmitter.compose({
     listeners: function(type) this._listeners(type),
     emit: function() this._emit.apply(this, arguments),
     emitOnObject: function() this._emitOnObject.apply(this, arguments),
     removeAllListeners: function(type) this._removeAllListeners(type)
   });
 
--- a/addon-sdk/source/test/test-observer-service.js
+++ b/addon-sdk/source/test/test-observer-service.js
@@ -1,42 +1,38 @@
 /* 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/. */
 
 const observers = require("sdk/deprecated/observer-service");
 const { Cc, Ci } = require("chrome");
-const { Loader } = require("sdk/test/loader");
-const { PlainTextConsole } = require("sdk/console/plain-text");
+const { LoaderWithHookedConsole2 } = require("sdk/test/loader");
 
 exports.testUnloadAndErrorLogging = function(test) {
-  var prints = [];
-  var loader = Loader(module, {
-    console: new PlainTextConsole(function(_) {
-      prints.push(_);
-    })
-  });
+  let { loader, messages } = LoaderWithHookedConsole2(module);
   var sbobsvc = loader.require("sdk/deprecated/observer-service");
 
   var timesCalled = 0;
   var cb = function(subject, data) {
     timesCalled++;
   };
   var badCb = function(subject, data) {
     throw new Error("foo");
   };
   sbobsvc.add("blarg", cb);
   observers.notify("blarg", "yo yo");
   test.assertEqual(timesCalled, 1);
   sbobsvc.add("narg", badCb);
   observers.notify("narg", "yo yo");
-  var lines = prints[0].split("\n");
+  var lines = messages[0].split("\n");
+  test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
   test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
   test.assertEqual(lines[1], "Error: foo");
-  test.assertEqual(lines[2], module.uri + " 24");
+  // Keep in mind to update "18" to the line of "throw new Error("foo")"
+  test.assertEqual(lines[2], module.uri + " 18");
   test.assertEqual(lines[3], "Traceback (most recent call last):");
 
   loader.unload();
   observers.notify("blarg", "yo yo");
   test.assertEqual(timesCalled, 1);
 };
 
 exports.testObserverService = function(test) {
--- a/addon-sdk/source/test/test-packaging.js
+++ b/addon-sdk/source/test/test-packaging.js
@@ -1,15 +1,44 @@
 /* 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";
 
-var url = require("sdk/url");
-var file = require("sdk/io/file");
-var {Cm,Ci} = require("chrome");
 var options = require("@loader/options");
 
 exports.testPackaging = function(test) {
+  test.assertEqual(options.metadata.description,
+                   "Add-on development made easy.",
+                   "packaging metadata should be available");
+  try {
+    options.metadata.description = 'new description';
+    test.fail('should not have been able to set options.metadata property');
+  }
+  catch (e) {}
 
   test.assertEqual(options.metadata.description,
                    "Add-on development made easy.",
-                   "packaging metadata should be available");
+                   "packaging metadata should be frozen");
+
+  test.assertEqual(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be undefined");
+  test.assertEqual(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+  test.assertEqual(options['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata['private-browsing'] = true;
+    test.fail('should not have been able to set options.metadata property');
+  }
+  catch(e) {}
+  test.assertEqual(options.metadata['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
+
+  try {
+    options.metadata.permissions['private-browsing'] = true;
+    test.fail('should not have been able to set options.metadata.permissions property');
+  }
+  catch (e) {}
+  test.assertEqual(options.metadata.permissions['private-browsing'], undefined,
+                   "private browsing metadata should be be frozen");
 };
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -1,33 +1,40 @@
 /* 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";
 
-var pageMod = require("sdk/page-mod");
-var testPageMod = require("./pagemod-test-helpers").testPageMod;
+const { PageMod } = require("sdk/page-mod");
+const testPageMod = require("./pagemod-test-helpers").testPageMod;
 const { Loader } = require('sdk/test/loader');
 const tabs = require("sdk/tabs");
 const timer = require("sdk/timers");
 const { Cc, Ci } = require("chrome");
-const { open, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { open, openDialog, getFrames, getMostRecentBrowserWindow } = require('sdk/window/utils');
 const windowUtils = require('sdk/deprecated/window-utils');
-const { getTabContentWindow, getActiveTab, openTab, closeTab } = require('sdk/tabs/utils');
+const windowHelpers = require('sdk/window/helpers');
+const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
+const xulApp = require("sdk/system/xul-app");
 const { data } = require('sdk/self');
+const { isPrivate } = require('sdk/private-browsing');
+const { isTabPBSupported, isWindowPBSupported } = require('sdk/private-browsing/utils');
+const promise = require("sdk/core/promise");
 
 /* XXX This can be used to delay closing the test Firefox instance for interactive
  * testing or visual inspection. This test is registered first so that it runs
  * the last. */
 exports.delay = function(test) {
   if (false) {
     test.waitUntilDone(60000);
     timer.setTimeout(function() {test.done();}, 4000);
-  } else
+  }
+  else {
     test.pass();
+  }
 }
 
 function Isolate(worker) {
   return "(" + worker + ")()";
 }
 
 /* Tests for the PageMod APIs */
 
@@ -128,17 +135,17 @@ exports.testPageModIncludes = function(t
             done();
           });
     }
     );
 };
 
 exports.testPageModErrorHandling = function(test) {
   test.assertRaises(function() {
-      new pageMod.PageMod();
+      new PageMod();
     },
     'pattern is undefined',
     "PageMod() throws when 'include' option is not specified.");
 };
 
 /* Tests for internal functions. */
 exports.testCommunication1 = function(test) {
   let workerDone = false,
@@ -331,17 +338,16 @@ exports.testHistory = function(test) {
     }
   );
 };
 
 exports.testRelatedTab = function(test) {
   test.waitUntilDone();
 
   let tab;
-  let { PageMod } = require("sdk/page-mod");
   let pageMod = new PageMod({
     include: "about:*",
     onAttach: function(worker) {
       test.assert(!!worker.tab, "Worker.tab exists");
       test.assertEqual(tab, worker.tab, "Worker.tab is valid");
       pageMod.destroy();
       tab.close(function() {
         test.done();
@@ -358,29 +364,30 @@ exports.testRelatedTab = function(test) 
 };
 
 exports.testRelatedTabNoOtherReqs = function(test) {
   test.waitUntilDone();
 
   let loader = Loader(module);
   let { PageMod } = loader.require("sdk/page-mod");
   let pageMod = new PageMod({
-    include: "about:*",
+    include: "about:blank?testRelatedTabNoOtherReqs",
     onAttach: function(worker) {
       test.assert(!!worker.tab, "Worker.tab exists");
       pageMod.destroy();
       worker.tab.close(function() {
+        worker.destroy();
         loader.unload();
         test.done();
       });
     }
   });
 
   tabs.open({
-    url: "about:"
+    url: "about:blank?testRelatedTabNoOtherReqs"
   });
 };
 
 exports.testWorksWithExistingTabs = function(test) {
   test.waitUntilDone();
 
   let url = "data:text/html;charset=utf-8," + encodeURI("Test unique document");
   let { PageMod } = require("sdk/page-mod");
@@ -408,17 +415,17 @@ exports.testWorksWithExistingTabs = func
         onAttach: function(worker) {
           test.fail("pageModOffExisting page-mod should not have attached to anything");
         }
       });
     }
   });
 };
 
-exports['test tab worker on message'] = function(test) {
+exports.testTabWorkerOnMessage = function(test) {
   test.waitUntilDone();
 
   let { browserWindows } = require("sdk/windows");
   let tabs = require("sdk/tabs");
   let { PageMod } = require("sdk/page-mod");
 
   let url1 = "data:text/html;charset=utf-8,<title>tab1</title><h1>worker1.tab</h1>";
   let url2 = "data:text/html;charset=utf-8,<title>tab2</title><h1>worker2.tab</h1>";
@@ -480,17 +487,17 @@ exports.testAutomaticDestroy = function(
       test.pass("check automatic destroy");
       tab.close();
       test.done();
     }
   });
 
 }
 
-exports['test attachment to tabs only'] = function(test) {
+exports.testAttachToTabsOnly = function(test) {
   test.waitUntilDone();
 
   let { PageMod } = require('sdk/page-mod');
   let openedTab = null; // Tab opened in openTabWithIframe()
   let workerCount = 0;
 
   let mod = PageMod({
     include: 'data:text/html*',
@@ -517,17 +524,23 @@ exports['test attachment to tabs only'] 
     console.info('Open iframe in hidden window');
     let hiddenFrames = require('sdk/frame/hidden-frame');
     let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
       onReady: function () {
         let element = this.element;
         element.addEventListener('DOMContentLoaded', function onload() {
           element.removeEventListener('DOMContentLoaded', onload, false);
           hiddenFrames.remove(hiddenFrame);
-          openToplevelWindow();
+
+          if (!xulApp.is("Fennec")) {
+            openToplevelWindow();
+          }
+          else {
+            openBrowserIframe(); 
+          }
         }, false);
         element.setAttribute('src', 'data:text/html;charset=utf-8,foo');
       }
     }));
   }
 
   function openToplevelWindow() {
     console.info('Open toplevel window');
@@ -553,17 +566,17 @@ exports['test attachment to tabs only'] 
     }, false);
     document.documentElement.appendChild(iframe);
   }
 
   // Only these three documents will be accepted by the page-mod
   function openTabWithIframes() {
     console.info('Open iframes in a tab');
     let subContent = '<iframe src="data:text/html;charset=utf-8,sub frame" />'
-    let content = '<iframe src="data:text/html,' +
+    let content = '<iframe src="data:text/html;charset=utf-8,' +
                   encodeURIComponent(subContent) + '" />';
     require('sdk/tabs').open({
       url: 'data:text/html;charset=utf-8,' + encodeURIComponent(content),
       onOpen: function onOpen(tab) {
         openedTab = tab;
       }
     });
   }
@@ -799,18 +812,18 @@ exports.testPageModCssAutomaticDestroy =
 
   tabs.open({
     url: "data:text/html;charset=utf-8,<div style='width:200px'>css test</div>",
 
     onReady: function onReady(tab) {
       let browserWindow = windowUtils.activeBrowserWindow;
       let win = getTabContentWindow(getActiveTab(browserWindow));
 
-      let div = win.document.querySelector("div"),
-          style = win.getComputedStyle(div);
+      let div = win.document.querySelector("div");
+      let style = win.getComputedStyle(div);
 
       test.assertEqual(
         style.width,
         "100px",
         "PageMod contentStyle worked"
       );
 
       loader.unload();
@@ -919,17 +932,17 @@ exports.testExistingOnFrames = function(
   let window = getTabContentWindow(tab);
 
   function wait4Iframes() {
     if (window.document.readyState != "complete" ||
         getFrames(window).length != 2) {
       return;
     }
 
-    let pagemodOnExisting = pageMod.PageMod({
+    let pagemodOnExisting = PageMod({
       include: ["*", "data:*"],
       attachTo: ["existing", "frame"],
       contentScriptWhen: 'ready',
       onAttach: function(worker) {
         // need to ignore urls that are not part of the test, because other
         // tests are not closing their tabs when they complete..
         if (urls.indexOf(worker.url) == -1)
           return;
@@ -954,39 +967,41 @@ exports.testExistingOnFrames = function(
             pagemodOffExisting.destroy();
             closeTab(tab);
             test.done();
           }, 0);
         }
       }
     });
 
-    let pagemodOffExisting = pageMod.PageMod({
+    let pagemodOffExisting = PageMod({
       include: ["*", "data:*"],
       attachTo: ["frame"],
       contentScriptWhen: 'ready',
       onAttach: function(mod) {
         test.fail('pagemodOffExisting page-mod should not have been attached');
       }
     });
   }
 
   window.addEventListener("load", wait4Iframes, false);
 };
 
 exports.testIFramePostMessage = function(test) {
   test.waitUntilDone();
+  let count = 0;
 
   tabs.open({
     url: data.url("test-iframe.html"),
     onReady: function(tab) {
       var worker = tab.attach({
         contentScriptFile: data.url('test-iframe.js'),
-        contentScript: ' var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'',
+        contentScript: 'var iframePath = \'' + data.url('test-iframe-postmessage.html') + '\'',
         onMessage: function(msg) {
+          test.assertEqual(++count, 1);
           test.assertEqual(msg.first, 'a string');
           test.assert(msg.second[1], "array");
           test.assertEqual(typeof msg.third, 'object');
 
           worker.destroy();
           tab.close(function() test.done());
         }
       });
@@ -1014,19 +1029,66 @@ exports.testEvents = function(test) {
         win.receivedEvent,
         "Content script sent an event and document received it"
       );
       done();
     }
   );
 };
 
-if (require("sdk/system/xul-app").is("Fennec")) {
-
-  module.exports = {
-    "test Unsupported Test": function UnsupportedTest (test) {
-        test.pass(
-          "Skipping this test until Fennec support is implemented." +
-          "See bug 784224");
-    }
+function openWebpage(url, enablePrivate) {
+  if (xulApp.is("Fennec")) {
+    let chromeWindow = getMostRecentBrowserWindow();
+    let rawTab = openTab(chromeWindow, url, {
+      isPrivate: enablePrivate
+    });
+    return {
+      close: function () {
+        closeTab(rawTab)
+        // Returns a resolved promise as there is no need to wait
+        return promise.resolve();
+      }
+    };
+  }
+  else {
+    let win = openDialog({
+      private: enablePrivate
+    });
+    win.addEventListener("load", function onLoad() {
+      win.removeEventListener("load", onLoad, false);
+      setTabURL(getActiveTab(win), url);
+    });
+    return {
+      close: function () {
+        return windowHelpers.close(win);
+      }
+    };
   }
 }
 
+exports["test page-mod on private tab"] = function (test) {
+  test.waitUntilDone();
+  let privateUri = "data:text/html;charset=utf-8," +
+                   "<iframe src=\"data:text/html;charset=utf-8,frame\" />";
+  let nonPrivateUri = "data:text/html;charset=utf-8,non-private";
+
+  let pageMod = new PageMod({
+    include: "data:*",
+    onAttach: function(worker) {
+      if (isTabPBSupported || isWindowPBSupported) {
+        // When PB isn't supported, the page-mod will apply to all document
+        // as all of them will be non-private
+        test.assertEqual(worker.tab.url,
+                         nonPrivateUri,
+                         "page-mod should only attach to the non-private tab");
+      }
+      test.assert(!isPrivate(worker),
+                  "The worker is really non-private");
+      test.assert(!isPrivate(worker.tab),
+                  "The document is really non-private");
+      pageMod.destroy();
+      page1.close().then(page2.close).then(test.done.bind(test));
+    }
+  });
+
+  let page1 = openWebpage(privateUri, true);
+  let page2 = openWebpage(nonPrivateUri, false);
+}
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -1,16 +1,36 @@
 /* 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';
 
-let { Cc, Ci } = require("chrome");
+const { Cc, Ci } = require("chrome");
 const { Loader } = require('sdk/test/loader');
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
 const timer = require("sdk/timers");
 const self = require('sdk/self');
+const { open, close, focus } = require('sdk/window/helpers');
+const { isPrivate } = require('sdk/private-browsing');
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
+const { defer } = require('sdk/core/promise');
+const { getMostRecentBrowserWindow } = require('sdk/window/utils');
+
+const SVG_URL = self.data.url('mofo_logo.SVG');
+
+function makeEmptyPrivateBrowserWindow(options) {
+  options = options || {};
+  return open('chrome://browser/content/browser.xul', {
+    features: {
+      chrome: true,
+      toolbar: true,
+      private: true
+    }
+  });
+}
 
 exports["test Panel"] = function(assert, done) {
   const { Panel } = require('sdk/panel');
 
   let panel = Panel({
     contentURL: "about:buildconfig",
     contentScript: "self.postMessage(1); self.on('message', function() self.postMessage(2));",
     onMessage: function (message) {
@@ -450,18 +470,16 @@ exports["test Content URL Option"] = fun
   }
 
   assert.throws(function () Panel({ contentURL: "foo" }),
                     /The `contentURL` option must be a valid URL./,
                     "Panel throws an exception if contentURL is not a URL.");
 };
 
 exports["test SVG Document"] = function(assert) {
-  let SVG_URL = self.data.url("mofo_logo.SVG");
-
   let panel = require("sdk/panel").Panel({ contentURL: SVG_URL });
 
   panel.show();
   panel.hide();
   panel.destroy();
 
   assert.pass("contentURL accepts a svg document");
   assert.equal(panel.contentURL, SVG_URL,
@@ -487,38 +505,164 @@ exports["test ContentScriptOptions Optio
 };
 
 exports["test console.log in Panel"] = function(assert, done) {
   let text = 'console.log() in Panel works!';
   let html = '<script>onload = function log(){\
                 console.log("' + text + '");\
               }</script>';
 
-  let panel;
-
-  let loader = Loader(module,  {
-    console: {
-      log: function (message) {
-        assert.equal(message, text, 'console.log() works');
-
-        panel.destroy();
-        done();
-      }
-    }
-  });
-
+  let { loader } = LoaderWithHookedConsole(module, onMessage);
   let { Panel } = loader.require('sdk/panel');
 
-  panel = Panel({
+  let panel = Panel({
     contentURL: 'data:text/html;charset=utf-8,' + encodeURIComponent(html)
   });
 
   panel.show();
+  
+  function onMessage(type, message) {
+    assert.equal(type, 'log', 'console.log() works');
+    assert.equal(message, text, 'console.log() works');
+    panel.destroy();
+    done();
+  }
 };
 
+if (isWindowPBSupported) {
+  exports.testPanelDoesNotShowInPrivateWindowNoAnchor = function(assert, done) {
+    let loader = Loader(module);
+    let { Panel } = loader.require("sdk/panel");
+    let browserWindow = getMostRecentBrowserWindow();
+
+    assert.equal(isPrivate(browserWindow), false, 'open window is not private');
+
+    let panel = Panel({
+      contentURL: SVG_URL
+    });
+
+    testShowPanel(assert, panel).
+      then(makeEmptyPrivateBrowserWindow).
+      then(focus).
+      then(function(window) {
+        assert.equal(isPrivate(window), true, 'opened window is private');
+        assert.pass('private window was focused');
+        return window;
+      }).
+      then(function(window) {
+        let { promise, resolve } = defer();
+        let showTries = 0;
+        let showCount = 0;
+
+        panel.on('show', function runTests() {
+          showCount++;
+
+          if (showTries == 2) {
+            panel.removeListener('show', runTests);
+            assert.equal(showCount, 1, 'show count is correct - 1');
+            resolve(window);
+          }
+        });
+        showTries++;
+        panel.show();
+        showTries++;
+        panel.show(browserWindow.gBrowser);
+
+        return promise;
+      }).
+      then(function(window) {
+        assert.equal(panel.isShowing, true, 'panel is still showing');
+        panel.hide();
+        assert.equal(panel.isShowing, false, 'panel is hidden');
+        return window;
+      }).
+      then(close).
+      then(function() {
+        assert.pass('private window was closed');
+      }).
+      then(testShowPanel.bind(null, assert, panel)).
+      then(done, assert.fail.bind(assert));
+  }
+
+  exports.testPanelDoesNotShowInPrivateWindowWithAnchor = function(assert, done) {
+    let loader = Loader(module);
+    let { Panel } = loader.require("sdk/panel");
+    let browserWindow = getMostRecentBrowserWindow();
+
+    assert.equal(isPrivate(browserWindow), false, 'open window is not private');
+
+    let panel = Panel({
+      contentURL: SVG_URL
+    });
+
+    testShowPanel(assert, panel).
+      then(makeEmptyPrivateBrowserWindow).
+      then(focus).
+      then(function(window) {
+        assert.equal(isPrivate(window), true, 'opened window is private');
+        assert.pass('private window was focused');
+        return window;
+      }).
+      then(function(window) {
+        let { promise, resolve } = defer();
+        let showTries = 0;
+        let showCount = 0;
+
+        panel.on('show', function runTests() {
+          showCount++;
+
+          if (showTries == 2) {
+            panel.removeListener('show', runTests);
+            assert.equal(showCount, 1, 'show count is correct - 1');
+            resolve(window);
+          }
+        });
+        showTries++;
+        panel.show(window.gBrowser);
+        showTries++;
+        panel.show(browserWindow.gBrowser);
+
+        return promise;
+      }).
+      then(function(window) {
+        assert.equal(panel.isShowing, true, 'panel is still showing');
+        panel.hide();
+        assert.equal(panel.isShowing, false, 'panel is hidden');
+        return window;
+      }).
+      then(close).
+      then(function() {
+        assert.pass('private window was closed');
+      }).
+      then(testShowPanel.bind(null, assert, panel)).
+      then(done, assert.fail.bind(assert));
+  }
+}
+
+function testShowPanel(assert, panel) {
+  let { promise, resolve } = defer();
+
+  assert.ok(!panel.isShowing, 'the panel is not showing [1]');
+
+  panel.once('show', function() {
+    assert.ok(panel.isShowing, 'the panel is showing');
+
+    panel.once('hide', function() {
+      assert.ok(!panel.isShowing, 'the panel is not showing [2]');
+
+      resolve(null);
+    });
+
+    panel.hide();
+  })
+  panel.show();
+
+  return promise;
+}
+
 try {
   require("sdk/panel");
 }
 catch (e) {
   if (!/^Unsupported Application/.test(e.message))
     throw e;
 
   module.exports = {
--- a/addon-sdk/source/test/test-private-browsing.js
+++ b/addon-sdk/source/test/test-private-browsing.js
@@ -1,31 +1,136 @@
 /* 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 { pb, pbUtils } = require('./private-browsing/helper');
+const { Ci } = require('chrome');
 const { merge } = require('sdk/util/object');
 const windows = require('sdk/windows').browserWindows;
+const tabs = require('sdk/tabs');
 const winUtils = require('sdk/window/utils');
+const { isWindowPrivate } = winUtils;
+const { isPrivateBrowsingSupported } = require('sdk/self');
+const { is } = require('sdk/system/xul-app');
+const { isPrivate } = require('sdk/private-browsing');
+const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+const { LoaderWithHookedConsole } = require("sdk/test/loader");
+const { getMode, isGlobalPBSupported,
+        isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
+const { pb } = require('./private-browsing/helper');
 
 // is global pb is enabled?
-if (pbUtils.isGlobalPBSupported) {
+if (isGlobalPBSupported) {
   merge(module.exports, require('./private-browsing/global'));
+
+  exports.testGlobalOnlyOnFirefox = function(test) {
+    test.assert(is("Firefox"), "isGlobalPBSupported is only true on Firefox");
+  }
 }
-else if (pbUtils.isWindowPBSupported) {
+else if (isWindowPBSupported) {
   merge(module.exports, require('./private-browsing/windows'));
+
+  exports.testPWOnlyOnFirefox = function(test) {
+    test.assert(is("Firefox"), "isWindowPBSupported is only true on Firefox");
+  }
+}
+// only on Fennec
+else if (isTabPBSupported) {
+  merge(module.exports, require('./private-browsing/tabs'));
+
+  exports.testPTOnlyOnFennec = function(test) {
+    test.assert(is("Fennec"), "isTabPBSupported is only true on Fennec");
+  }
 }
 
+exports.testIsPrivateDefaults = function(test) {
+  test.assertEqual(isPrivate(), false, 'undefined is not private');
+  test.assertEqual(isPrivate('test'), false, 'strings are not private');
+  test.assertEqual(isPrivate({}), false, 'random objects are not private');
+  test.assertEqual(isPrivate(4), false, 'numbers are not private');
+  test.assertEqual(isPrivate(/abc/), false, 'regex are not private');
+  test.assertEqual(isPrivate(function() {}), false, 'functions are not private');
+};
+
 exports.testWindowDefaults = function(test) {
-  test.assertEqual(windows.activeWindow.isPrivateBrowsing, false, 'window is not private browsing by default');
+  // Ensure that browserWindow still works while being deprecated
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  let windows = loader.require("sdk/windows").browserWindows;
+  test.assertEqual(windows.activeWindow.isPrivateBrowsing, false,
+                   'window is not private browsing by default');
+  test.assertMatches(messages[0].msg, /DEPRECATED.+isPrivateBrowsing/,
+                     'isPrivateBrowsing is deprecated');
+
   let chromeWin = winUtils.getMostRecentBrowserWindow();
-  test.assertEqual(pbUtils.getMode(chromeWin), false);
-  test.assertEqual(pbUtils.isWindowPrivate(chromeWin), false);
+  test.assertEqual(getMode(chromeWin), false);
+  test.assertEqual(isWindowPrivate(chromeWin), false);
 }
 
 // tests for the case where private browsing doesn't exist
 exports.testIsActiveDefault = function(test) {
   test.assertEqual(pb.isActive, false,
                    'pb.isActive returns false when private browsing isn\'t supported');
 };
 
+exports.testIsPrivateBrowsingFalseDefault = function(test) {
+  test.assertEqual(isPrivateBrowsingSupported, false,
+  	               'isPrivateBrowsingSupported property is false by default');
+};
+
+exports.testGetOwnerWindow = function(test) {
+  test.waitUntilDone();
+
+  let window = windows.activeWindow;
+  let chromeWindow = getOwnerWindow(window);
+  test.assert(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
+
+  tabs.open({
+    url: 'about:blank',
+    isPrivate: true,
+    onOpen: function(tab) {
+      // test that getOwnerWindow works as expected
+      if (is('Fennec')) {
+        test.assertNotStrictEqual(chromeWindow, getOwnerWindow(tab)); 
+        test.assert(getOwnerWindow(tab) instanceof Ci.nsIDOMWindow); 
+      }
+      else {
+        test.assertStrictEqual(chromeWindow, getOwnerWindow(tab), 'associated window is the same for window and window\'s tab');
+      }
+
+      // test that the tab is not private
+      // private flag should be ignored by default
+      test.assert(!isPrivate(tab));
+      test.assert(!isPrivate(getOwnerWindow(tab)));
+
+      tab.close(function() test.done());
+    }
+  });
+};
+
+exports.testGetOwnerWindow = function(test) {
+  test.waitUntilDone();
+
+  let window = windows.activeWindow;
+  let chromeWindow = getOwnerWindow(window);
+  test.assert(chromeWindow instanceof Ci.nsIDOMWindow, 'associated window is found');
+
+  window.tabs.open({
+    url: 'about:blank',
+    private: true, // should be ignored in this case
+    onOpen: function(tab) {
+      // test that getOwnerWindow works as expected
+      if (is('Fennec')) {
+        test.assertNotStrictEqual(chromeWindow, getOwnerWindow(tab)); 
+        test.assert(getOwnerWindow(tab) instanceof Ci.nsIDOMWindow); 
+      }
+      else {
+        test.assertStrictEqual(chromeWindow, getOwnerWindow(tab), 'associated window is the same for window and window\'s tab');
+      }
+
+      // test that the tab is not private
+      // private flag should be ignored by default
+      test.assert(!isPrivate(tab));
+
+      tab.close(function() test.done());
+    }
+  });
+}
--- a/addon-sdk/source/test/test-selection.js
+++ b/addon-sdk/source/test/test-selection.js
@@ -14,34 +14,63 @@ const HTML = "<html>\
 
 const URL = "data:text/html;charset=utf-8," + encodeURIComponent(HTML);
 
 const FRAME_HTML = "<iframe src='" + URL + "'><iframe>";
 const FRAME_URL = "data:text/html;charset=utf-8," + encodeURIComponent(FRAME_HTML);
 
 const { defer } = require("sdk/core/promise");
 const tabs = require("sdk/tabs");
+const { setTabURL } = require("sdk/tabs/utils");
 const { getActiveTab, getTabContentWindow, closeTab } = require("sdk/tabs/utils")
 const { getMostRecentBrowserWindow } = require("sdk/window/utils");
+const { open: openNewWindow } = require("sdk/window/helpers");
 const { Loader } = require("sdk/test/loader");
 const { setTimeout } = require("sdk/timers");
 const { Cu } = require("chrome");
+const { merge } = require("sdk/util/object");
+const { isPrivate } = require("sdk/private-browsing");
 
 // General purpose utility functions
 
 /**
  * Opens the url given and return a promise, that will be resolved with the
  * content window when the document is ready.
  *
  * I believe this approach could be useful in most of our unit test, that
  * requires to open a tab and need to access to its content.
  */
-function open(url) {
+function open(url, options) {
   let { promise, resolve } = defer();
 
+  if (options && typeof(options) === "object") {
+    openNewWindow("", {
+      features: merge({ toolbar: true }, options)
+    }).then(function(chromeWindow) {
+      if (isPrivate(chromeWindow) !== !!options.private)
+        throw new Error("Window should have Private set to " + !!options.private);
+
+      let tab = getActiveTab(chromeWindow);
+
+      tab.addEventListener("load", function ready(event) {
+        let { document } = getTabContentWindow(this);
+
+        if (document.readyState === "complete" && document.URL === url) {
+          this.removeEventListener(event.type, ready);
+
+          resolve(document.defaultView);
+        }
+      })
+
+      setTabURL(tab, url);
+    });
+
+    return promise;
+  };
+
   tabs.open({
     url: url,
     onReady: function(tab) {
       // Unfortunately there is no way to get a XUL Tab from SDK Tab on Firefox,
       // only on Fennec. We should implement `tabNS` also on Firefox in order
       // to have that.
 
       // Here we assuming that the most recent browser window is the one we're
@@ -53,20 +82,26 @@ function open(url) {
   });
 
   return promise;
 };
 
 /**
  * Close the Active Tab
  */
-function close() {
-  // Here we assuming that the most recent browser window is the one we're
-  // doing the test, and the active tab is the one we just opened.
-  closeTab(getActiveTab(getMostRecentBrowserWindow()));
+function close(window) {
+  if (window && window.top && typeof(window.top).close === "function") {
+    window.top.close();
+  } else {
+    // Here we assuming that the most recent browser window is the one we're
+    // doing the test, and the active tab is the one we just opened.
+    let tab = getActiveTab(getMostRecentBrowserWindow());
+
+    closeTab(tab);
+  }
 }
 
 /**
  * Reload the window given and return a promise, that will be resolved with the
  * content window after a small delay.
  */
 function reload(window) {
   let { promise, resolve } = defer();
@@ -209,30 +244,34 @@ function selectContentFirstDiv(window) {
  * Dispatch the selection event for the selection listener added by
  * `nsISelectionPrivate.addSelectionListener`
  */
 function dispatchSelectionEvent(window) {
   // We modify the selection in order to dispatch the selection's event, by
   // contract the selection by one character. So if the text selected is "foo"
   // will be "fo".
   window.getSelection().modify("extend", "backward", "character");
+
+  return window;
 }
 
 /**
  * Dispatch the selection event for the selection listener added by
  * `window.onselect` / `window.addEventListener`
  */
 function dispatchOnSelectEvent(window) {
   let { document } = window;
   let textarea = document.querySelector("textarea");
   let event = document.createEvent("UIEvents");
 
   event.initUIEvent("select", true, true, window, 1);
 
   textarea.dispatchEvent(event);
+
+  return window;
 }
 
 /**
  * Creates empty ranges and add them to selections
  */
 function createEmptySelections(window) {
   selectAllDivs(window);
 
@@ -261,17 +300,17 @@ exports["test No Selection"] = function(
 
     let selectionCount = 0;
     for each (let sel in selection)
       selectionCount++;
 
     assert.equal(selectionCount, 0,
       "No iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Single DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectFirstDiv).then(function() {
 
@@ -293,17 +332,17 @@ exports["test Single DOM Selection"] = f
 
       assert.equal(sel.html, "<div>foo</div>",
         "iterable selection.html with single DOM Selection works.");
     }
 
     assert.equal(selectionCount, 1,
       "One iterable selection");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Multiple DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectAllDivs).then(function() {
     let expectedText = ["foo", "and"];
@@ -327,17 +366,17 @@ exports["test Multiple DOM Selection"] =
         "iterable selection.text with multiple DOM Selection works.");
 
       selectionCount++;
     }
 
     assert.equal(selectionCount, 2,
       "Two iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Textarea Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectTextarea).then(function() {
 
@@ -359,17 +398,17 @@ exports["test Textarea Selection"] = fun
 
       assert.strictEqual(sel.html, null,
         "iterable selection.html with Textarea Selection works.");
     }
 
     assert.equal(selectionCount, 1,
       "One iterable selection");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Set Text in Multiple DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectAllDivs).then(function() {
     let expectedText = ["bar", "and"];
@@ -393,17 +432,17 @@ exports["test Set Text in Multiple DOM S
         "iterable selection.html with multiple DOM Selection works.");
 
       selectionCount++;
     }
 
     assert.equal(selectionCount, 2,
       "Two iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Set HTML in Multiple DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectAllDivs).then(function() {
     let html = "<span>b<b>a</b>r</span>";
@@ -429,17 +468,17 @@ exports["test Set HTML in Multiple DOM S
         "iterable selection.html with multiple DOM Selection works.");
 
       selectionCount++;
     }
 
     assert.equal(selectionCount, 2,
       "Two iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Set HTML as text in Multiple DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectAllDivs).then(function() {
     let text = "<span>b<b>a</b>r</span>";
@@ -466,17 +505,17 @@ exports["test Set HTML as text in Multip
         "iterable selection.html with multiple DOM Selection works.");
 
       selectionCount++;
     }
 
     assert.equal(selectionCount, 2,
       "Two iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Set Text in Textarea Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectTextarea).then(function() {
 
@@ -499,17 +538,17 @@ exports["test Set Text in Textarea Selec
 
       assert.strictEqual(sel.html, null,
         "iterable selection.html with Textarea Selection works.");
     }
 
     assert.equal(selectionCount, 1,
       "One iterable selection");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Set HTML in Textarea Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectTextarea).then(function() {
 
@@ -534,17 +573,17 @@ exports["test Set HTML in Textarea Selec
 
       assert.strictEqual(sel.html, null,
         "iterable selection.html with Textarea Selection works.");
     }
 
     assert.equal(selectionCount, 1,
       "One iterable selection");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test Empty Selections"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(createEmptySelections).then(function(){
     assert.equal(selection.isContiguous, false,
@@ -558,17 +597,17 @@ exports["test Empty Selections"] = funct
 
     let selectionCount = 0;
     for each (let sel in selection)
       selectionCount++;
 
     assert.equal(selectionCount, 0,
       "No iterable selections");
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 }
 
 
 exports["test No Selection Exception"] = function(assert, done) {
   const NO_SELECTION = /It isn't possible to change the selection/;
 
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
@@ -579,17 +618,17 @@ exports["test No Selection Exception"] =
     assert.throws(function() {
       selection.text = "bar";
     }, NO_SELECTION);
 
     assert.throws(function() {
       selection.html = "bar";
     }, NO_SELECTION);
 
-  }).then(close).then(loader.unload).then(done);
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 exports["test for...of without selections"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(function() {
     let selectionCount = 0;
@@ -602,17 +641,17 @@ exports["test for...of without selection
 
   }).then(close).then(loader.unload).then(null, function(error) {
     // iterable are not supported yet in Firefox 16, for example, but
     // they are in Firefox 17.
     if (error.message.indexOf("is not iterable") > -1)
       assert.pass("`iterable` method not supported in this application");
     else
       assert.fail(error);
-  }).then(done);
+  }).then(done, assert.fail);
 }
 
 exports["test for...of with selections"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL).then(selectAllDivs).then(function(){
     let expectedText = ["foo", "and"];
@@ -635,47 +674,47 @@ exports["test for...of with selections"]
 
   }).then(close).then(loader.unload).then(null, function(error) {
     // iterable are not supported yet in Firefox 16, for example, but
     // they are in Firefox 17.
     if (error.message.indexOf("is not iterable") > -1)
       assert.pass("`iterable` method not supported in this application");
     else
       assert.fail(error);
-  }).then(done)
+  }).then(done, assert.fail)
 }
 
 exports["test Selection Listener"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "fo");
     done();
   });
 
   open(URL).then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
     then(close).
-    then(loader.unload);
+    then(loader.unload, assert.fail);
 };
 
 exports["test Textarea OnSelect Listener"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "noodles");
     done();
   });
 
   open(URL).then(selectTextarea).
     then(dispatchOnSelectEvent).
     then(close).
-    then(loader.unload);
+    then(loader.unload, assert.fail);
 };
 
 exports["test Selection listener removed on unload"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.fail("Shouldn't be never called");
@@ -684,17 +723,17 @@ exports["test Selection listener removed
   loader.unload();
 
   assert.pass();
 
   open(URL).
     then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
     then(close).
-    then(done)
+    then(done, assert.fail);
 };
 
 exports["test Textarea onSelect Listener removed on unload"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.fail("Shouldn't be never called");
@@ -703,17 +742,17 @@ exports["test Textarea onSelect Listener
   loader.unload();
 
   assert.pass();
 
   open(URL).
     then(selectTextarea).
     then(dispatchOnSelectEvent).
     then(close).
-    then(done)
+    then(done, assert.fail);
 };
 
 
 exports["test Selection Listener on existing document"] = function(assert, done) {
   let loader = Loader(module);
 
   open(URL).then(function(window){
     let selection = loader.require("sdk/selection");
@@ -722,17 +761,17 @@ exports["test Selection Listener on exis
       assert.equal(selection.text, "fo");
       done();
     });
 
     return window;
   }).then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
     then(close).
-    then(loader.unload)
+    then(loader.unload, assert.fail);
 };
 
 
 exports["test Textarea OnSelect Listener on existing document"] = function(assert, done) {
   let loader = Loader(module);
 
   open(URL).then(function(window){
     let selection = loader.require("sdk/selection");
@@ -741,51 +780,51 @@ exports["test Textarea OnSelect Listener
       assert.equal(selection.text, "noodles");
       done();
     });
 
     return window;
   }).then(selectTextarea).
     then(dispatchOnSelectEvent).
     then(close).
-    then(loader.unload)
+    then(loader.unload, assert.fail);
 };
 
 exports["test Selection Listener on document reload"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "fo");
     done();
   });
 
   open(URL).
     then(reload).
     then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
     then(close).
-    then(loader.unload);
+    then(loader.unload, assert.fail);
 };
 
 exports["test Textarea OnSelect Listener on document reload"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "noodles");
     done();
   });
 
   open(URL).
     then(reload).
     then(selectTextarea).
     then(dispatchOnSelectEvent).
     then(close).
-    then(loader.unload);
+    then(loader.unload, assert.fail);
 };
 
 exports["test Selection Listener on frame"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "fo");
@@ -793,17 +832,17 @@ exports["test Selection Listener on fram
   });
 
   open(FRAME_URL).
     then(hideAndShowFrame).
     then(getFrameWindow).
     then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
     then(close).
-    then(loader.unload)
+    then(loader.unload, assert.fail);
 };
 
 exports["test Textarea onSelect Listener on frame"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   selection.once("select", function() {
     assert.equal(selection.text, "noodles");
@@ -811,17 +850,114 @@ exports["test Textarea onSelect Listener
   });
 
   open(FRAME_URL).
     then(hideAndShowFrame).
     then(getFrameWindow).
     then(selectTextarea).
     then(dispatchOnSelectEvent).
     then(close).
-    then(loader.unload)
+    then(loader.unload, assert.fail);
+};
+
+
+exports["test PBPW Selection Listener"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  selection.once("select", function() {
+    assert.fail("Shouldn't be never called");
+  });
+
+  assert.pass();
+
+  open(URL, {private: true}).
+    then(selectContentFirstDiv).
+    then(dispatchSelectionEvent).
+    then(close).
+    then(loader.unload).
+    then(done, assert.fail);
+};
+
+exports["test PBPW Textarea OnSelect Listener"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  selection.once("select", function() {
+    assert.fail("Shouldn't be never called");
+  });
+
+  assert.pass();
+
+  open(URL, {private: true}).
+    then(selectTextarea).
+    then(dispatchOnSelectEvent).
+    then(close).
+    then(loader.unload).
+    then(done, assert.fail);
+};
+
+
+exports["test PBPW Single DOM Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true}).then(selectFirstDiv).then(function(window) {
+
+    assert.equal(selection.isContiguous, false,
+      "selection.isContiguous with single DOM Selection in PBPW works.");
+
+    assert.equal(selection.text, null,
+      "selection.text with single DOM Selection in PBPW works.");
+
+    assert.equal(selection.html, null,
+      "selection.html with single DOM Selection in PBPW works.");
+
+    let selectionCount = 0;
+    for each (let sel in selection)
+      selectionCount++;
+
+    assert.equal(selectionCount, 0,
+      "No iterable selection in PBPW");
+
+    return window;
+  }).then(close).then(loader.unload).then(done, assert.fail);
+};
+
+exports["test PBPW Textarea Selection"] = function(assert, done) {
+  let loader = Loader(module);
+  let selection = loader.require("sdk/selection");
+
+  open(URL, {private: true}).then(selectTextarea).then(function(window) {
+
+    assert.equal(selection.isContiguous, false,
+      "selection.isContiguous with Textarea Selection in PBPW works.");
+
+    assert.equal(selection.text, null,
+      "selection.text with Textarea Selection in PBPW works.");
+
+    assert.strictEqual(selection.html, null,
+      "selection.html with Textarea Selection in PBPW works.");
+
+    let selectionCount = 0;
+    for each (let sel in selection) {
+      selectionCount++;
+
+      assert.equal(sel.text, null,
+        "iterable selection.text with Textarea Selection in PBPW works.");
+
+      assert.strictEqual(sel.html, null,
+        "iterable selection.html with Textarea Selection in PBPW works.");
+    }
+
+    assert.equal(selectionCount, 0,
+      "No iterable selection in PBPW");
+
+    return window;
+  }).then(close).then(loader.unload).then(done, assert.fail);
 };
 
 // TODO: test Selection Listener on long-held connection (Bug 661884)
 //
 //  I didn't find a way to do so with httpd, using `processAsync` I'm able to
 //  Keep the connection but not to flush the buffer to the client in two steps,
 //  that is what I need for this test (e.g. flush "Hello" to the client, makes
 //  selection when the connection is still hold, and check that the listener
@@ -830,16 +966,27 @@ exports["test Textarea onSelect Listener
 //  Because this test is needed to the refactoring of context-menu as well, I
 //  believe we will find a proper solution quickly.
 /*
 exports["test Selection Listener on long-held connection"] = function(assert, done) {
 
 };
 */
 
+// If the platform doesn't support the PBPW, we're replacing PBPW tests
+if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
+  Object.keys(module.exports).forEach(function(key) {
+    if (key.indexOf("test PBPW") === 0) {
+      module.exports[key] = function Unsupported (assert) {
+        assert.pass("Private Window Per Browsing is not supported on this platform.");
+      }
+    }
+  });
+}
+
 // If the module doesn't support the app we're being run in, require() will
 // throw.  In that case, remove all tests above from exports, and add one dummy
 // test that passes.
 try {
   require("sdk/selection");
 }
 catch (err) {
   if (!/^Unsupported Application/.test(err.message))
--- a/addon-sdk/source/test/test-self.js
+++ b/addon-sdk/source/test/test-self.js
@@ -27,16 +27,19 @@ exports.testSelf = function(test) {
 
   // When tests are run on just the api-utils package, self.name is
   // api-utils. When they're run as 'cfx testall', self.name is testpkgs.
   test.assert(self.name == "addon-sdk", "self.name is addon-sdk");
 
   // loadReason may change here, as we change the way tests addons are installed
   test.assertEqual(self.loadReason, "startup",
                    "self.loadReason is always `startup` on test runs");
+
+  test.assertEqual(self.isPrivateBrowsingSupported, false,
+                   'usePrivateBrowsing property is false by default');
 };
 
 exports.testSelfID = function(test) {
   test.waitUntilDone();
 
   var self = require("sdk/self");
   // We can't assert anything about the ID inside the unit test right now,
   // because the ID we get depends upon how the test was invoked. The idea
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-system-events.js
@@ -0,0 +1,216 @@
+/* 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/. */
+
+const events = require("sdk/system/events");
+const self = require("sdk/self");
+const { Cc, Ci, Cu } = require("chrome");
+const { setTimeout } = require("sdk/timers");
+const { LoaderWithHookedConsole2 } = require("sdk/test/loader");
+const nsIObserverService = Cc["@mozilla.org/observer-service;1"].
+                           getService(Ci.nsIObserverService);
+
+
+exports["test basic"] = function(assert) {
+  let type = Date.now().toString(32);
+
+  let timesCalled = 0;
+  function handler(subject, data) { timesCalled++; };
+
+  events.on(type, handler);
+  events.emit(type, { data: "yo yo" });
+
+  assert.equal(timesCalled, 1, "event handler was called");
+
+  events.off(type, handler);
+  events.emit(type, { data: "no way" });
+
+  assert.equal(timesCalled, 1, "removed handler is no longer called");
+
+  events.once(type, handler);
+  events.emit(type, { data: "and we meet again" });
+  events.emit(type, { data: "it's always hard to say bye" });
+
+  assert.equal(timesCalled, 2, "handlers added via once are triggered once");
+}
+
+exports["test error reporting"] = function(assert) {
+  let { loader, messages } = LoaderWithHookedConsole2(module);
+
+  let events = loader.require("sdk/system/events");
+  function brokenHandler(subject, data) { throw new Error("foo"); };
+
+  let lineNumber;
+  try { brokenHandler() } catch (error) { lineNumber = error.lineNumber }
+
+  let errorType = Date.now().toString(32);
+
+  events.on(errorType, brokenHandler);
+  events.emit(errorType, { data: "yo yo" });
+
+  assert.equal(messages.length, 1, "Got an exception");
+  let text = messages[0];
+  assert.ok(text.indexOf(self.name + ": An exception occurred.") >= 0,
+            "error is logged");
+  assert.ok(text.indexOf("Error: foo") >= 0, "error message is logged");
+  assert.ok(text.indexOf(module.uri) >= 0, "module uri is logged");
+  assert.ok(text.indexOf(lineNumber) >= 0, "error line is logged");
+
+  events.off(errorType, brokenHandler);
+
+  loader.unload();
+};
+
+exports["test listeners are GC-ed"] = function(assert, done) {
+  let receivedFromWeak = [];
+  let receivedFromStrong = [];
+  let type = Date.now().toString(32);
+  function handler(event) { receivedFromStrong.push(event); }
+  function weakHandler(event) { receivedFromWeak.push(event); }
+
+  events.on(type, handler, true);
+  events.on(type, weakHandler);
+
+  events.emit(type, { data: 1 });
+  assert.equal(receivedFromStrong.length, 1, "strong listener invoked");
+  assert.equal(receivedFromWeak.length, 1, "weak listener invoked");
+
+  handler = weakHandler = null;
+
+  Cu.forceGC();
+  setTimeout(function() {
+    Cu.forceGC();
+    events.emit(type, { data: 2 });
+    assert.equal(receivedFromWeak.length, 1, "weak listener was GC-ed");
+    assert.equal(receivedFromStrong.length, 2, "strong listener was invoked");
+    done();
+  }, 300);
+};
+
+exports["test handle nsIObserverService notifications"] = function(assert) {
+  let ios = Cc['@mozilla.org/network/io-service;1']
+            .getService(Ci.nsIIOService);
+
+  let uri = ios.newURI("http://www.foo.com", null, null);
+
+  let type = Date.now().toString(32);
+  let timesCalled = 0;
+  let lastSubject = null;
+  let lastData = null;
+  let lastType = null;
+
+  function handler({ subject, data, type }) {
+    timesCalled++;
+    lastSubject = subject;
+    lastData = data;
+    lastType = type;
+  };
+
+  events.on(type, handler);
+  nsIObserverService.notifyObservers(uri, type, "some data");
+
+  assert.equal(timesCalled, 1, "notification invokes handler");
+  assert.equal(lastType, type, "event.type is notification topic");
+  assert.equal(lastSubject, uri, "event.subject is notification subject");
+  assert.equal(lastData, "some data", "event.data is notification data");
+
+  function customSubject() {}
+  function customData() {}
+
+  events.emit(type, { data: customData, subject: customSubject });
+
+  assert.equal(timesCalled, 2, "notification invokes handler");
+  assert.equal(lastType, type, "event.type is notification topic");
+  assert.equal(lastSubject, customSubject,
+               "event.subject is wrapped & unwrapped");
+  assert.equal(lastData, customData, "event.data is wrapped & unwrapped");
+
+  events.off(type, handler);
+
+  nsIObserverService.notifyObservers(null, type, "some data");
+
+  assert.equal(timesCalled, 2, "event handler is removed");
+
+  events.on("*", handler);
+
+  nsIObserverService.notifyObservers(null, type, "more data");
+
+  assert.equal(timesCalled, 3, "notification invokes * handler");
+  assert.equal(lastType, type, "event.type is notification topic");
+  assert.equal(lastSubject, null,
+               "event.subject is notification subject");
+  assert.equal(lastData, "more data", "event.data is notification data");
+
+  events.off("*", handler);
+
+  nsIObserverService.notifyObservers(null, type, "last data");
+
+  assert.equal(timesCalled, 3, "* event handler is removed");
+};
+
+exports["test emit to nsIObserverService observers"] = function(assert) {
+  let ios = Cc['@mozilla.org/network/io-service;1']
+            .getService(Ci.nsIIOService);
+
+  let uri = ios.newURI("http://www.foo.com", null, null);
+  let timesCalled = 0;
+  let lastSubject = null;
+  let lastData = null;
+  let lastTopic = null;
+
+  var topic = Date.now().toString(32)
+  let nsIObserver = {
+    QueryInterface: function() {
+      return nsIObserver;
+    },
+    observe: function(subject, topic, data) {
+      timesCalled = timesCalled + 1;
+      lastSubject = subject;
+      lastData = data;
+      lastTopic = topic;
+    }
+  };
+
+  nsIObserverService.addObserver(nsIObserver, topic, false);
+
+  events.emit(topic, { subject: uri, data: "some data" });
+
+  assert.equal(timesCalled, 1, "emit notifies observers");
+  assert.equal(lastTopic, topic, "event type is notification topic");
+  assert.equal(lastSubject.wrappedJSObject.object, uri,
+               "event.subject is notification subject");
+  assert.equal(lastData, "some data", "event.data is notification data");
+
+  function customSubject() {}
+  function customData() {}
+  events.emit(topic, { subject: customSubject, data: customData });
+
+  assert.equal(timesCalled, 2, "emit notifies observers");
+  assert.equal(lastTopic, topic, "event.type is notification");
+  assert.equal(lastSubject.wrappedJSObject.object, customSubject,
+               "event.subject is notification subject");
+  assert.equal(lastData, customData, "event.data is notification data");
+
+  nsIObserverService.removeObserver(nsIObserver, topic, false);
+
+  events.emit(topic, { data: "more data" });
+
+  assert.equal(timesCalled, 2, "removed observers no longer invoked");
+
+  nsIObserverService.addObserver(nsIObserver, "*", false);
+
+  events.emit(topic, { data: "data again" });
+
+  assert.equal(timesCalled, 3, "emit notifies * observers");
+  assert.equal(lastTopic, topic, "event.type is notification");
+  assert.equal(lastSubject, null,
+               "event.subject is notification subject");
+  assert.equal(lastData, "data again", "event.data is notification data");
+
+  nsIObserverService.removeObserver(nsIObserver, "*");
+
+  events.emit(topic, { data: "last data" });
+  assert.equal(timesCalled, 3, "removed observers no longer invoked");
+}
+
+require("test").run(exports);
--- a/addon-sdk/source/test/test-tabs-common.js
+++ b/addon-sdk/source/test/test-tabs-common.js
@@ -1,16 +1,21 @@
 /* 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 { Loader } = require('sdk/test/loader');
+const { Loader, LoaderWithHookedConsole } = require("sdk/test/loader");
 const { browserWindows } = require('sdk/windows');
 const tabs = require('sdk/tabs');
+const { isPrivate } = require('sdk/private-browsing');
+const { openDialog } = require('sdk/window/utils');
+const pbUtils = require('sdk/private-browsing/utils');
+const { isWindowPrivate } = require('sdk/window/utils');
+const { setTimeout } = require('sdk/timers');
 
 const URL = 'data:text/html;charset=utf-8,<html><head><title>#title#</title></head></html>';
 
 // TEST: tab count
 exports.testTabCounts = function(test) {
   test.waitUntilDone();
 
   tabs.open({
@@ -284,13 +289,101 @@ exports.testTabContentTypeAndReload = fu
     url: url,
     onReady: function(tab) {
       if (tab.url === url) {
         test.assertEqual(tab.contentType, "text/html");
         tab.url = urlXML;
       }
       else {
         test.assertEqual(tab.contentType, "text/xml");
-        tab.close(function() test.done());
+        tab.close(function() {
+          test.done();
+        });
       }
     }
   });
 };
+
+// test that it isn't possible to open a private tab without the private permission
+exports.testTabOpenPrivate = function(test) {
+  test.waitUntilDone();
+
+  let url = 'about:blank';
+  tabs.open({
+    url: url,
+    isPrivate: true,
+    onReady: function(tab) {
+      test.assertEqual(tab.url, url, 'opened correct tab');
+      test.assertEqual(isPrivate(tab), false, 'private tabs are not supported by default');
+
+      tab.close(function() {
+        test.done();
+      });
+    }
+  });
+}
+
+// We need permission flag in order to see private window's tabs
+exports.testPrivateAreNotListed = function (test) {
+  test.waitUntilDone();
+  let originalTabCount = tabs.length;
+
+  let win = openDialog({
+    private: true
+  });
+
+  win.addEventListener("load", function onload() {
+    win.removeEventListener("load", onload);
+
+    // PWPB case
+    if (pbUtils.isWindowPBSupported) {
+      test.assert(isWindowPrivate(win), "window is private");
+      test.assertEqual(tabs.length, originalTabCount,
+                       'New private window\'s tab isn\'t visible in tabs list');
+    }
+    else {
+    // Global case, openDialog didn't opened a private window/tab
+      test.assert(!isWindowPrivate(win), "window is private");
+      test.assertEqual(tabs.length, originalTabCount + 1,
+                       'New non-private window\'s tab is visible in tabs list');
+    }
+
+    win.addEventListener("unload", function onunload() {
+      win.removeEventListener('unload', onunload);
+      test.done();
+    });
+    win.close();
+  });
+}
+
+// If we close the tab while being in `onOpen` listener,
+// we end up synchronously consuming TabOpen, closing the tab and still
+// synchronously consuming the related TabClose event before the second
+// loader have a change to process the first TabOpen event!
+exports.testImmediateClosing = function (test) {
+  test.waitUntilDone();
+  let { loader, messages } = LoaderWithHookedConsole(module, onMessage);
+  let concurrentTabs = loader.require("sdk/tabs");
+  concurrentTabs.on("open", function () {
+    test.fail("Concurrent loader manager receive a tabs `open` event");
+    // It shouldn't receive such event as the other loader will just open
+    // and destroy the tab without giving a change to other loader to even know
+    // about the existance of this tab.
+  });
+  function onMessage(type, msg) {
+    test.fail("Unexpected mesage on concurrent loader: " + msg);
+  }
+
+  tabs.open({
+    url: 'about:blank',
+    onOpen: function(tab) {
+      tab.close(function () {
+        test.pass("Tab succesfully removed");
+        // Let a chance to the concurrent loader to receive a TabOpen event
+        // on the next event loop turn
+        setTimeout(function () {
+          loader.unload();
+          test.done();
+        }, 0);
+      });
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-test-loader.js
@@ -0,0 +1,60 @@
+/* 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 { LoaderWithHookedConsole } = require("sdk/test/loader");
+
+exports["test LoaderWithHookedConsole"] = function (assert) {
+  let count = 0;
+  function onMessage(type, message) {
+    switch (count++) {
+      case 0:
+        assert.equal(type, "log", "got log type");
+        assert.equal(message, "1st", "got log msg");
+        break;
+      case 1:
+        assert.equal(type, "error", "got error type");
+        assert.equal(message, "2nd", "got error msg");
+        break;
+      case 2:
+        assert.equal(type, "warn", "got warn type");
+        assert.equal(message, "3rd", "got warn msg");
+        break;
+      case 3:
+        assert.equal(type, "info", "got info type");
+        assert.equal(message, "4th", "got info msg");
+        break;
+      case 4:
+        assert.equal(type, "debug", "got debug type");
+        assert.equal(message, "5th", "got debug msg");
+        break;
+      case 5:
+        assert.equal(type, "exception", "got exception type");
+        assert.equal(message, "6th", "got exception msg");
+        break;
+      default:
+        assert.fail("Got unexception message: " + i);
+    }
+  }
+
+  let { loader, messages } = LoaderWithHookedConsole(module, onMessage);
+  let console = loader.globals.console;
+  console.log("1st");
+  console.error("2nd");
+  console.warn("3rd");
+  console.info("4th");
+  console.debug("5th");
+  console.exception("6th");
+  assert.equal(messages.length, 6, "Got all console messages");
+  assert.deepEqual(messages[0], {type: "log", msg: "1st"}, "Got log");
+  assert.deepEqual(messages[1], {type: "error", msg: "2nd"}, "Got error");
+  assert.deepEqual(messages[2], {type: "warn", msg: "3rd"}, "Got warn");
+  assert.deepEqual(messages[3], {type: "info", msg: "4th"}, "Got info");
+  assert.deepEqual(messages[4], {type: "debug", msg: "5th"}, "Got debug");
+  assert.deepEqual(messages[5], {type: "exception", msg: "6th"}, "Got exception");
+  assert.equal(count, 6, "Called for all messages");
+};
+
+require("test").run(exports);
--- a/addon-sdk/source/test/test-unload.js
+++ b/addon-sdk/source/test/test-unload.js
@@ -1,44 +1,39 @@
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var unload = require("sdk/system/unload");
-var { Loader } = require("sdk/test/loader");
+var { Loader, LoaderWithHookedConsole } = require("sdk/test/loader");
 
 exports.testUnloading = function(test) {
-  var loader = Loader(module, {
-    console: Object.create(console, {
-      exception: { value: function(error) {
-        exceptions.push(error);
-      }}
-    })
-  });
-  var exceptions = [];
+  let { loader, messages } = LoaderWithHookedConsole(module);
   var ul = loader.require("sdk/system/unload");
   var unloadCalled = 0;
   function unload() {
     unloadCalled++;
     throw new Error("error");
   }
   ul.when(unload);
 
   // This should be ignored, as we already registered it
   ul.when(unload);
 
   function unload2() { unloadCalled++; }
   ul.when(unload2);
   loader.unload();
   test.assertEqual(unloadCalled, 2,
                    "Unloader functions are called on unload.");
-  test.assertEqual(exceptions.length, 1,
-                   "One unload handler threw exception");
+  test.assertEqual(messages.length, 1,
+                   "One unload handler threw exception 1/2");
+  test.assertEqual(messages[0].type, "exception",
+                   "One unload handler threw exception 2/2");
 };
 
 exports.testEnsure = function(test) {
   test.assertRaises(function() { unload.ensure({}); },
                     "object has no 'unload' property",
                     "passing obj with no unload prop should fail");
   test.assertRaises(function() { unload.ensure({}, "destroy"); },
                     "object has no 'destroy' property",
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-window-utils-private-browsing.js
@@ -0,0 +1,231 @@
+/* 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 windowUtils = require('sdk/deprecated/window-utils');
+const { Cc, Ci } = require('chrome');
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
+const { getFrames, getWindowTitle, onFocus, isWindowPrivate } = require('sdk/window/utils');
+const { open, close, focus } = require('sdk/window/helpers');
+const WM = Cc['@mozilla.org/appshell/window-mediator;1'].getService(Ci.nsIWindowMediator);
+const { isPrivate } = require('sdk/private-browsing');
+const { fromIterator: toArray } = require('sdk/util/array');
+
+function makeEmptyBrowserWindow(options) {
+  options = options || {};
+  return open('chrome://browser/content/browser.xul', {
+    features: {
+      chrome: true,
+      private: !!options.private
+    }
+  });
+}
+
+exports.testWindowTrackerIgnoresPrivateWindows = function(assert, done) {
+  var myNonPrivateWindow, myPrivateWindow;
+  var finished = false;
+  var privateWindow;
+  var privateWindowClosed = false;
+
+  let wt = windowUtils.WindowTracker({
+    onTrack: function(window) {
+      if (isWindowPrivate(window)) {
+        assert.fail('private window was tracked!');
+      }
+    },
+    onUntrack: function(window) {
+      if (isWindowPrivate(window)) {
+        assert.fail('private window was tracked!');
+      }
+      // PWPB case
+      if (window === myPrivateWindow && isWindowPBSupported) {
+        privateWindowClosed = true;
+      }
+      if (window === myNonPrivateWindow) {
+        assert.ok(!privateWindowClosed);
+        wt.unload();
+        done();
+      }
+    }
+  });
+
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(function(window) {
+    myPrivateWindow = window;
+
+    assert.equal(isWindowPrivate(window), isWindowPBSupported);
+    assert.ok(getFrames(window).length > 1, 'there are frames for private window');
+    assert.equal(getWindowTitle(window), window.document.title,
+                 'getWindowTitle works');
+
+    close(window).then(function() {
+      makeEmptyBrowserWindow().then(function(window) {
+        myNonPrivateWindow = window;
+        assert.pass('opened new window');
+        window.close();
+      });
+    });
+  });
+};
+
+// Test setting activeWIndow and onFocus for private windows
+exports.testSettingActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
+  let browserWindow = WM.getMostRecentWindow("navigator:browser");
+  let testSteps;
+
+  assert.equal(windowUtils.activeBrowserWindow, browserWindow,
+               "Browser window is the active browser window.");
+  assert.ok(!isPrivate(browserWindow), "Browser window is not private.");
+
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(focus).then(function(window) {
+    let continueAfterFocus = function(window) onFocus(window).then(nextTest);
+
+    // PWPB case
+    if (isWindowPBSupported) {
+      assert.ok(isPrivate(window), "window is private");
+      assert.notDeepEqual(windowUtils.activeBrowserWindow, browserWindow);
+    }
+    // Global case
+    else {
+      assert.ok(!isPrivate(window), "window is not private");
+    }
+
+    assert.strictEqual(windowUtils.activeBrowserWindow, window,
+                 "Correct active browser window pb supported");
+    assert.notStrictEqual(browserWindow, window,
+                 "The window is not the old browser window");
+
+    testSteps = [
+      function() {
+        // test setting a non private window
+        continueAfterFocus(windowUtils.activeWindow = browserWindow);
+      },
+      function() {
+        assert.strictEqual(windowUtils.activeWindow, browserWindow,
+                           "Correct active window [1]");
+        assert.strictEqual(windowUtils.activeBrowserWindow, browserWindow,
+                           "Correct active browser window [1]");
+
+        // test focus(window)
+        focus(window).then(nextTest);
+      },
+      function(w) {
+        assert.strictEqual(w, window, 'require("sdk/window/helpers").focus on window works');
+        assert.strictEqual(windowUtils.activeBrowserWindow, window,
+                           "Correct active browser window [2]");
+        assert.strictEqual(windowUtils.activeWindow, window,
+                           "Correct active window [2]");
+
+        // test setting a private window
+        continueAfterFocus(windowUtils.activeWindow = window);
+      },
+      function() {
+        assert.deepEqual(windowUtils.activeBrowserWindow, window,
+                         "Correct active browser window [3]");
+        assert.deepEqual(windowUtils.activeWindow, window,
+                         "Correct active window [3]");
+
+        // just to get back to original state
+        continueAfterFocus(windowUtils.activeWindow = browserWindow);
+      },
+      function() {
+        assert.deepEqual(windowUtils.activeBrowserWindow, browserWindow,
+                         "Correct active browser window when pb mode is supported [4]");
+        assert.deepEqual(windowUtils.activeWindow, browserWindow,
+                         "Correct active window when pb mode is supported [4]");
+
+        close(window).then(done);
+      }
+    ];
+
+    function nextTest() {
+      let args = arguments;
+      if (testSteps.length) {
+        require('sdk/timers').setTimeout(function() {
+          (testSteps.shift()).apply(null, args);
+        }, 0);
+      }
+    }
+    nextTest();
+  });
+};
+
+exports.testActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(function(window) {
+    // PWPB case
+    if (isWindowPBSupported) {
+      assert.equal(isPrivate(windowUtils.activeWindow), true,
+                   "active window is private");
+      assert.equal(isPrivate(windowUtils.activeBrowserWindow), true,
+                   "active browser window is private");
+      assert.ok(isWindowPrivate(window), "window is private");
+      assert.ok(isPrivate(window), "window is private");
+
+      // pb mode is supported
+      assert.ok(
+        isWindowPrivate(windowUtils.activeWindow),
+        "active window is private when pb mode is supported");
+      assert.ok(
+        isWindowPrivate(windowUtils.activeBrowserWindow),
+        "active browser window is private when pb mode is supported");
+      assert.ok(isPrivate(windowUtils.activeWindow),
+                "active window is private when pb mode is supported");
+      assert.ok(isPrivate(windowUtils.activeBrowserWindow),
+        "active browser window is private when pb mode is supported");
+    }
+    // Global case
+    else {
+      assert.equal(isPrivate(windowUtils.activeWindow), false,
+                   "active window is not private");
+      assert.equal(isPrivate(windowUtils.activeBrowserWindow), false,
+                   "active browser window is not private");
+      assert.equal(isWindowPrivate(window), false, "window is not private");
+      assert.equal(isPrivate(window), false, "window is not private");
+    }
+
+    close(window).then(done);
+  });
+}
+
+exports.testWindowIteratorIgnoresPrivateWindows = function(assert, done) {
+  // make a new private window
+  makeEmptyBrowserWindow({
+    private: true
+  }).then(function(window) {
+    // PWPB case
+    if (isWindowPBSupported) {
+      assert.ok(isWindowPrivate(window), "window is private");
+      assert.equal(toArray(windowUtils.windowIterator()).indexOf(window), -1,
+                   "window is not in windowIterator()");
+    }
+    // Global case
+    else {
+      assert.equal(isWindowPrivate(window), false, "window is not private");
+      assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
+                "window is in windowIterator()"); 
+    }
+
+    close(window).then(done);
+  });
+};
+
+if (require("sdk/system/xul-app").is("Fennec")) {
+  module.exports = {
+    "test Unsupported Test": function UnsupportedTest (assert) {
+        assert.pass(
+          "Skipping this test until Fennec support is implemented." +
+          "See bug 809412");
+    }
+  }
+}
+
+require("test").run(exports);
--- a/addon-sdk/source/test/test-window-utils.js
+++ b/addon-sdk/source/test/test-window-utils.js
@@ -1,39 +1,39 @@
 /* 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";
 
-var windowUtils = require("sdk/deprecated/window-utils");
-var timer = require("sdk/timers");
-var { Cc, Ci } = require("chrome");
-var { Loader, unload } = require("sdk/test/loader");
+const windowUtils = require("sdk/deprecated/window-utils");
+const timer = require("sdk/timers");
+const { Cc, Ci } = require("chrome");
+const { Loader } = require("sdk/test/loader");
+const { open, getFrames, getWindowTitle, onFocus } = require('sdk/window/utils');
+const { close } = require('sdk/window/helpers');
+const { fromIterator: toArray } = require('sdk/util/array');
 
-function toArray(iterator) {
-  let array = [];
-  for each (let item in iterator)
-    array.push(item);
-  return array;
-}
+const WM = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
 
-function makeEmptyWindow() {
+function makeEmptyWindow(options) {
+  options = options || {};
   var xulNs = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
   var blankXul = ('<?xml version="1.0"?>' +
                   '<?xml-stylesheet href="chrome://global/skin/" ' +
                   '                 type="text/css"?>' +
                   '<window xmlns="' + xulNs + '" windowtype="test:window">' +
                   '</window>');
-  var url = "data:application/vnd.mozilla.xul+xml;charset=utf-8," + escape(blankXul);
-  var features = ["chrome", "width=10", "height=10"];
 
-  var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
-           .getService(Ci.nsIWindowWatcher);
-  return ww.openWindow(null, url, null, features.join(","), null);
+  return open("data:application/vnd.mozilla.xul+xml;charset=utf-8," + escape(blankXul), {
+    features: {
+      chrome: true,
+      width: 10,
+      height: 10
+    }
+  });
 }
 
 exports['test close on unload'] = function(assert) {
   var timesClosed = 0;
   var fakeWindow = {
     _listeners: [],
     addEventListener: function(name, func, bool) {
       this._listeners.push(func);
@@ -78,39 +78,41 @@ exports['test close on unload'] = functi
                    "window closed when close() called.");
   assert.equal(fakeWindow._listeners.length, 0,
                    "unload event listener removed on window close");
   loader.unload();
   assert.equal(timesClosed, 1,
                    "window not closed again on module unload.");
 };
 
-exports['test window watcher'] = function(assert, done) {
+exports.testWindowTracker = function(assert, done) {
   var myWindow;
   var finished = false;
 
   var delegate = {
     onTrack: function(window) {
       if (window == myWindow) {
         assert.pass("onTrack() called with our test window");
-        timer.setTimeout(function() { myWindow.close(); }, 1);
+        timer.setTimeout(function() myWindow.close());
       }
     },
     onUntrack: function(window) {
       if (window == myWindow) {
         assert.pass("onUntrack() called with our test window");
         timer.setTimeout(function() {
-                           if (!finished) {
-                             finished = true;
-                             myWindow = null;
-                             wt.unload();
-                             done();
-                           } else
-                             assert.fail("finishTest() called multiple times.");
-                         }, 1);
+          if (!finished) {
+           finished = true;
+           myWindow = null;
+           wt.unload();
+           done();
+          }
+          else {
+           assert.fail("finishTest() called multiple times.");
+          }
+        });
       }
     }
   };
 
   // test bug 638007 (new is optional), using new
   var wt = new windowUtils.WindowTracker(delegate);
   myWindow = makeEmptyWindow();
 };
@@ -154,19 +156,17 @@ exports['test window watcher untracker']
   var wt = windowUtils.WindowTracker(delegate);
   myWindow = makeEmptyWindow();
 };
 
 // test that _unregWindow calls _unregLoadingWindow
 exports['test window watcher unregs 4 loading wins'] = function(assert, done) {
   var myWindow;
   var finished = false;
-  let browserWindow =  Cc["@mozilla.org/appshell/window-mediator;1"]
-      .getService(Ci.nsIWindowMediator)
-      .getMostRecentWindow("navigator:browser");
+  let browserWindow =  WM.getMostRecentWindow("navigator:browser");
   var counter = 0;
 
   var delegate = {
     onTrack: function(window) {
       var type = window.document.documentElement.getAttribute("windowtype");
       if (type == "test:window")
         assert.fail("onTrack shouldn't have been executed.");
     }
@@ -231,28 +231,26 @@ exports['test window watcher without unt
     }
   };
 
   var wt = new windowUtils.WindowTracker(delegate);
   myWindow = makeEmptyWindow();
 };
 
 exports['test active window'] = function(assert, done) {
-  let browserWindow =  Cc["@mozilla.org/appshell/window-mediator;1"]
-                      .getService(Ci.nsIWindowMediator)
-                      .getMostRecentWindow("navigator:browser");
+  let browserWindow = WM.getMostRecentWindow("navigator:browser");
+  let continueAfterFocus = function(window) onFocus(window).then(nextTest);
 
   assert.equal(windowUtils.activeBrowserWindow, browserWindow,
                "Browser window is the active browser window.");
 
 
   let testSteps = [
     function() {
-      windowUtils.activeWindow = browserWindow;
-      continueAfterFocus(browserWindow);
+      continueAfterFocus(windowUtils.activeWindow = browserWindow);
     },
     function() {
       assert.equal(windowUtils.activeWindow, browserWindow,
                        "Correct active window [1]");
       nextTest();
     },
     function() {
       assert.equal(windowUtils.activeBrowserWindow, browserWindow,
@@ -262,59 +260,28 @@ exports['test active window'] = function
     function() {
       assert.equal(windowUtils.activeWindow, browserWindow,
                        "Correct active window [3]");
       nextTest();
     },
     function() {
       assert.equal(windowUtils.activeBrowserWindow, browserWindow,
                        "Correct active browser window [4]");
-      browserWindow = null;
       done();
     }
   ];
 
-  let nextTest = function() {
-    let func = testSteps.shift();
-    if (func) {
-      func();
-    }
+  function nextTest() {
+    if (testSteps.length)
+      testSteps.shift()();
   }
-
-  function continueAfterFocus(targetWindow) {
-    // Based on SimpleTest.waitForFocus
-    var fm = Cc["@mozilla.org/focus-manager;1"].
-             getService(Ci.nsIFocusManager);
-
-    var childTargetWindow = {};
-    fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
-    childTargetWindow = childTargetWindow.value;
-
-    var focusedChildWindow = {};
-    if (fm.activeWindow) {
-      fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
-      focusedChildWindow = focusedChildWindow.value;
-    }
-
-    var focused = (focusedChildWindow == childTargetWindow);
-    if (focused) {
-      nextTest();
-    } else {
-      childTargetWindow.addEventListener("focus", function focusListener() {
-        childTargetWindow.removeEventListener("focus", focusListener, true);
-        nextTest();
-      }, true);
-    }
-
-  }
-
   nextTest();
 };
 
-exports['test windowIterator'] = function(assert, done) {
+exports.testWindowIterator = function(assert, done) {
   // make a new window
   let window = makeEmptyWindow();
 
   // make sure that the window hasn't loaded yet
   assert.notEqual(
       window.document.readyState,
       "complete",
       "window hasn't loaded yet.");
@@ -322,27 +289,22 @@ exports['test windowIterator'] = functio
   // this window should only appear in windowIterator() while its loading
   assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) === -1,
             "window isn't in windowIterator()");
 
   // Then it should be in windowIterator()
   window.addEventListener("load", function onload() {
     window.addEventListener("load", onload, false);
     assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) !== -1,
-              "window is now in windowIterator(false)");
+              "window is now in windowIterator()");
 
     // Wait for the window unload before ending test
-    window.addEventListener("unload", function onunload() {
-      window.addEventListener("unload", onunload, false);
-      done();
-    }, false);
-    window.close();
+    close(window).then(done);
   }, false);
-}
-
+};
 
 if (require("sdk/system/xul-app").is("Fennec")) {
   module.exports = {
     "test Unsupported Test": function UnsupportedTest (assert) {
         assert.pass(
           "Skipping this test until Fennec support is implemented." +
           "See bug 809412");
     }
--- a/addon-sdk/source/test/test-window-utils2.js
+++ b/addon-sdk/source/test/test-window-utils2.js
@@ -1,17 +1,18 @@
 /* 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 { Ci } = require('chrome');
-const { open, backgroundify, windows,
-        getXULWindow, getBaseWindow } = require('sdk/window/utils');
+const { open, backgroundify, windows, isBrowser,
+        getXULWindow, getBaseWindow, getMostRecentWindow,
+        getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { close } = require('sdk/window/helpers');
 const windowUtils = require('sdk/deprecated/window-utils');
 
 exports['test get nsIBaseWindow from nsIDomWindow'] = function(assert) {
   let active = windowUtils.activeBrowserWindow;
 
   assert.ok(!(active instanceof Ci.nsIBaseWindow),
             'active window is not nsIBaseWindow');
 
@@ -22,39 +23,58 @@ exports['test get nsIBaseWindow from nsI
 exports['test get nsIXULWindow from nsIDomWindow'] = function(assert) {
   let active = windowUtils.activeBrowserWindow;
   assert.ok(!(active instanceof Ci.nsIXULWindow),
             'active window is not nsIXULWindow');
   assert.ok(getXULWindow(active) instanceof Ci.nsIXULWindow,
             'base returns nsIXULWindow');
 };
 
-exports['test top window creation'] = function(assert) {
+exports['test top window creation'] = function(assert, done) {
   let window = open('data:text/html;charset=utf-8,Hello top window');
   assert.ok(~windows().indexOf(window), 'window was opened');
-  window.close();
+
+  // Wait for the window unload before ending test
+  close(window).then(done);
 };
 
-exports['test new top window with options'] = function(assert) {
+exports['test new top window with options'] = function(assert, done) {
   let window = open('data:text/html;charset=utf-8,Hi custom top window', {
     name: 'test',
     features: { height: 100, width: 200, toolbar: true }
   });
   assert.ok(~windows().indexOf(window), 'window was opened');
   assert.equal(window.name, 'test', 'name was set');
   assert.equal(window.innerHeight, 100, 'height is set');
   assert.equal(window.innerWidth, 200, 'height is set');
   assert.equal(window.toolbar.visible, true, 'toolbar was set');
-  window.close();
+
+  // Wait for the window unload before ending test
+  close(window).then(done);
 };
 
-exports['test backgroundify'] = function(assert) {
+exports.testBackgroundify = function(assert, done) {
   let window = open('data:text/html;charset=utf-8,backgroundy');
   assert.ok(~windows().indexOf(window),
             'window is in the list of windows');
   let backgroundy = backgroundify(window);
   assert.equal(backgroundy, window, 'backgroundify returs give window back');
   assert.ok(!~windows().indexOf(window),
             'backgroundifyied window is in the list of windows');
-  window.close();
+
+  // Wait for the window unload before ending test
+  close(window).then(done);
+};
+
+exports.testIsBrowser = function(assert) {
+  // dummy window, bad type
+  assert.equal(isBrowser({ document: { documentElement: { getAttribute: function() {
+    return 'navigator:browserx';
+  }}}}), false, 'dummy object with correct stucture and bad type does not pass');
+
+  assert.ok(isBrowser(getMostRecentBrowserWindow()), 'active browser window is a browser window');
+  assert.ok(!isBrowser({}), 'non window is not a browser window');
+  assert.ok(!isBrowser({ document: {} }), 'non window is not a browser window');
+  assert.ok(!isBrowser({ document: { documentElement: {} } }), 'non window is not a browser window');
+  assert.ok(!isBrowser(), 'no argument is not a browser window');
 };
 
 require('test').run(exports);
--- a/addon-sdk/source/test/windows/test-firefox-windows.js
+++ b/addon-sdk/source/test/windows/test-firefox-windows.js
@@ -1,22 +1,24 @@
 /* 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 { Cc, Ci } = require('chrome');
 const { setTimeout } = require('sdk/timers');
 const { Loader } = require('sdk/test/loader');
-const wm = Cc['@mozilla.org/appshell/window-mediator;1'].
-           getService(Ci.nsIWindowMediator);
-
+const { onFocus, getMostRecentWindow, windows } = require('sdk/window/utils');
+const { open, close, focus } = require('sdk/window/helpers');
 const { browserWindows } = require("sdk/windows");
 const tabs = require("sdk/tabs");
-const { WindowTracker } = require("sdk/deprecated/window-utils");
+const winUtils = require("sdk/deprecated/window-utils");
+const { WindowTracker } = winUtils;
+const { isPrivate } = require('sdk/private-browsing');
+const { isWindowPBSupported } = require('sdk/private-browsing/utils');
 
 // TEST: open & close window
 exports.testOpenAndCloseWindow = function(test) {
   test.waitUntilDone();
 
   test.assertEqual(browserWindows.length, 1, "Only one window open");
 
   browserWindows.open({
@@ -262,46 +264,21 @@ exports.testActiveWindow = function(test
             });
           }
         });
       });
     }
   });
 
   function nextStep() {
-    if (testSteps.length > 0)
+    if (testSteps.length)
       testSteps.shift()();
   }
 
-  function continueAfterFocus(targetWindow) {
-    // Based on SimpleTest.waitForFocus
-    var fm = Cc["@mozilla.org/focus-manager;1"].
-             getService(Ci.nsIFocusManager);
-
-    var childTargetWindow = {};
-    fm.getFocusedElementForWindow(targetWindow, true, childTargetWindow);
-    childTargetWindow = childTargetWindow.value;
-
-    var focusedChildWindow = {};
-    if (fm.activeWindow) {
-      fm.getFocusedElementForWindow(fm.activeWindow, true, focusedChildWindow);
-      focusedChildWindow = focusedChildWindow.value;
-    }
-
-    var focused = (focusedChildWindow == childTargetWindow);
-    if (focused) {
-      setTimeout(nextStep, 0);
-    } else {
-      childTargetWindow.addEventListener("focus", function focusListener() {
-        childTargetWindow.removeEventListener("focus", focusListener, true);
-        setTimeout(nextStep, 0);
-      }, true);
-    }
-
-  }
+  let continueAfterFocus = function(w) onFocus(w).then(nextStep);
 
   function finishTest() {
     window3.close(function() {
       window2.close(function() {
         test.done();
       });
     });
   }
@@ -365,8 +342,70 @@ exports.testTrackWindows = function(test
     // only concerned with windows opened for this test
     if (index < 0)
       return;
     actions.push("global deactivate " + index)
   })
 
   openWindow();
 }
+
+// test that it is not possible to open a private window by default
+exports.testWindowOpenPrivateDefault = function(test) {
+  test.waitUntilDone();
+
+  browserWindows.open({
+    url: 'about:mozilla',
+    isPrivate: true,
+    onOpen: function(window) {
+      test.assertEqual();
+
+      let tab = window.tabs[0];
+      tab.once('ready', function() {
+        test.assertEqual(tab.url, 'about:mozilla', 'opened correct tab');
+        test.assertEqual(isPrivate(tab), false, 'tab is not private');
+
+        window.close(function() {
+          test.done();
+        });
+      });
+    }
+  });
+}
+
+// test that it is not possible to find a private window in
+// windows module's iterator
+exports.testWindowIteratorPrivateDefault = function(test) {
+  test.waitUntilDone();
+
+  test.assertEqual(browserWindows.length, 1, 'only one window open');
+
+  open('chrome://browser/content/browser.xul', {
+    features: {
+      private: true,
+      chrome: true
+    }
+  }).then(function(window) {
+    // test that there is a private window opened
+    test.assertEqual(isPrivate(window), isWindowPBSupported, 'there is a private window open');
+    test.assertStrictEqual(window, winUtils.activeWindow);
+    test.assertStrictEqual(window, getMostRecentWindow());
+
+    test.assert(!isPrivate(browserWindows.activeWindow));
+
+    if (isWindowPBSupported) {
+      test.assertEqual(browserWindows.length, 1, 'only one window in browserWindows');
+      test.assertEqual(windows().length, 1, 'only one window in windows()');
+    }
+    else {
+      test.assertEqual(browserWindows.length, 2, 'two windows open');
+      test.assertEqual(windows().length, 2, 'two windows in windows()');
+    }
+    test.assertEqual(windows(null, { includePrivate: true }).length, 2);
+
+    for each(let window in browserWindows) {
+      // test that all windows in iterator are not private
+      test.assert(!isPrivate(window), 'no window in browserWindows is private');
+    }
+
+    close(window).then(test.done.bind(test));
+  });
+}