Bug 889157 - Uplift Add-on SDK to Firefox r=me
☠☠ backed out by d0ad91498ee5 ☠ ☠
authorWes Kocher <wkocher@mozilla.com>
Mon, 01 Jul 2013 17:03:43 -0700
changeset 137084 6d349e5ae0eddf527a64152222316e3135df15fc
parent 137083 2f349618cab1374b0ba12f282bd98682023e1e5c
child 137086 d62ccac259dffcd9483168b571b5eca4e28f6dbc
push id24909
push userkhuey@mozilla.com
push dateTue, 02 Jul 2013 16:45:38 +0000
treeherdermozilla-central@23ce4eab8fb1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersme
bugs889157
milestone25.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 889157 - Uplift Add-on SDK to Firefox r=me
addon-sdk/source/README
addon-sdk/source/doc/dev-guide-source/cfx-tool.md
addon-sdk/source/doc/dev-guide-source/console.md
addon-sdk/source/doc/dev-guide-source/guides/content-scripts/cross-domain.md
addon-sdk/source/doc/dev-guide-source/guides/content-scripts/index.md
addon-sdk/source/doc/dev-guide-source/guides/index.md
addon-sdk/source/doc/dev-guide-source/guides/xul-migration.md
addon-sdk/source/doc/dev-guide-source/package-spec.md
addon-sdk/source/doc/dev-guide-source/search.md
addon-sdk/source/doc/dev-guide-source/tutorials/adding-menus.md
addon-sdk/source/doc/dev-guide-source/tutorials/installation.md
addon-sdk/source/doc/dev-guide-source/tutorials/logging.md
addon-sdk/source/doc/dev-guide-source/tutorials/mobile.md
addon-sdk/source/doc/dev-guide-source/tutorials/modifying-web-pages-url.md
addon-sdk/source/doc/dev-guide-source/tutorials/reusable-modules.md
addon-sdk/source/doc/module-source/sdk/context-menu.md
addon-sdk/source/doc/module-source/sdk/page-mod.md
addon-sdk/source/doc/module-source/sdk/page-mod/match-pattern.md
addon-sdk/source/doc/module-source/sdk/panel.md
addon-sdk/source/doc/module-source/sdk/private-browsing.md
addon-sdk/source/doc/module-source/sdk/tabs.md
addon-sdk/source/doc/module-source/sdk/util/match-pattern.md
addon-sdk/source/doc/static-files/media/xul-migration-cs.png
addon-sdk/source/lib/sdk/addon-page.js
addon-sdk/source/lib/sdk/addon/events.js
addon-sdk/source/lib/sdk/addon/host.js
addon-sdk/source/lib/sdk/content/events.js
addon-sdk/source/lib/sdk/content/utils.js
addon-sdk/source/lib/sdk/context-menu.js
addon-sdk/source/lib/sdk/core/heritage.js
addon-sdk/source/lib/sdk/deprecated/events.js
addon-sdk/source/lib/sdk/fs/path.js
addon-sdk/source/lib/sdk/io/buffer.js
addon-sdk/source/lib/sdk/io/fs.js
addon-sdk/source/lib/sdk/io/stream.js
addon-sdk/source/lib/sdk/page-mod.js
addon-sdk/source/lib/sdk/page-mod/match-pattern.js
addon-sdk/source/lib/sdk/page-worker.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/panel/events.js
addon-sdk/source/lib/sdk/panel/utils.js
addon-sdk/source/lib/sdk/places/utils.js
addon-sdk/source/lib/sdk/private-browsing.js
addon-sdk/source/lib/sdk/private-browsing/utils.js
addon-sdk/source/lib/sdk/system.js
addon-sdk/source/lib/sdk/tabs/tab-fennec.js
addon-sdk/source/lib/sdk/test.js
addon-sdk/source/lib/sdk/util/array.js
addon-sdk/source/lib/sdk/util/match-pattern.js
addon-sdk/source/lib/sdk/util/object.js
addon-sdk/source/lib/sdk/util/rules.js
addon-sdk/source/lib/sdk/widget.js
addon-sdk/source/lib/sdk/window/utils.js
addon-sdk/source/mapping.json
addon-sdk/source/python-lib/cuddlefish/docs/renderapi.readme.md
addon-sdk/source/python-lib/jetpack_sdk_env.py
addon-sdk/source/test/addons/layout-change/main.js
addon-sdk/source/test/tabs/test-fennec-tabs.js
addon-sdk/source/test/tabs/test-firefox-tabs.js
addon-sdk/source/test/test-addon-page.js
addon-sdk/source/test/test-fs.js
addon-sdk/source/test/test-host-events.js
addon-sdk/source/test/test-match-pattern.js
addon-sdk/source/test/test-object.js
addon-sdk/source/test/test-page-mod.js
addon-sdk/source/test/test-page-worker.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-path.js
addon-sdk/source/test/test-places-utils.js
addon-sdk/source/test/test-private-browsing.js
addon-sdk/source/test/test-rules.js
addon-sdk/source/test/test-tabs-common.js
addon-sdk/source/test/test-url.js
addon-sdk/source/test/test-widget.js
addon-sdk/source/test/test-windows-common.js
addon-sdk/source/test/windows/test-firefox-windows.js
--- a/addon-sdk/source/README
+++ b/addon-sdk/source/README
@@ -17,22 +17,18 @@ is in (the SDK's root directory) using a
 or on Windows with MSYS, you can execute the following command:
 
   source bin/activate
 
 Windows users using cmd.exe should instead run:
 
   bin\activate.bat
 
-Then run:
-
-  cfx docs
-
-This should start a documentation server and open a web browser
-with further instructions.
+Then go to https://addons.mozilla.org/developers/docs/sdk/latest/dev-guide to
+browse the SDK documentation.
 
 If you get an error when running cfx or have any other problems getting
 started, see the "Troubleshooting" guide at:
 https://addons.mozilla.org/en-US/developers/docs/sdk/latest/dev-guide/tutorials/troubleshooting.html
 
 Bugs
 -------
 
--- a/addon-sdk/source/doc/dev-guide-source/cfx-tool.md
+++ b/addon-sdk/source/doc/dev-guide-source/cfx-tool.md
@@ -17,35 +17,26 @@ commands (for example `--help`). `cfx` s
 
 <pre>
   -h, --help        - show a help message and exit
   -v, --verbose     - enable lots of output
 </pre>
 
 "Command-specific options" are documented alongside the commands.
 
-There are five supported cfx commands:
+There are four supported cfx commands:
 
 <table>
   <colgroup>
     <col width="10%">
     <col width="90%">
   </colgroup>
 
   <tr>
     <td>
-      <a href="dev-guide/cfx-tool.html#cfx-docs"><code>cfx docs</code></a>
-    </td>
-    <td>
-      Display the documentation for the SDK.
-    </td>
-  </tr>
-
-  <tr>
-    <td>
       <a href="dev-guide/cfx-tool.html#cfx-init"><code>cfx init</code></a>
     </td>
     <td>
       Create a skeleton add-on as a starting point for your own add-on.
     </td>
   </tr>
 
   <tr>
@@ -77,37 +68,16 @@ There are five supported cfx commands:
   </tr>
 
 </table>
 
 There are also a number of
 [internal commands](dev-guide/cfx-tool.html#internal-commands),
 which are more likely to be useful to SDK developers than to add-on developers.
 
-## <a name="cfx-docs">cfx docs</a> ##
-
-This command displays the documentation for the SDK. The documentation is
-shipped with the SDK in [Markdown](http://daringfireball.net/projects/markdown/)
-format. The first time this command is executed, and any time after the
-Markdown files on disk have changed, `cfx docs` will generate a set of HTML
-pages from them and launch a web browser to display them. If the Markdown files
-haven't changed, `cfx docs` just launches a browser initialized to the set of
-generated pages.
-
-To regenerate the documentation associated with a single file, you can
-specify the file as an argument. For example:
-
-<pre>
-  cfx docs doc/dev-guide-source/addon-development/cfx-tool.md 
-</pre>
-
-This command will regenerate only the HTML page you're reading.
-This is useful if you're iteratively editing a single file, and don't want to wait for cfx to
-regenerate the complete documentation tree.
-
 ## <a name="cfx-init">cfx init</a> ##
 
 Create a new directory called "my-addon", change into it, and run `cfx init`.
 
 This command will create an skeleton add-on, as a starting point for your
 own add-on development, with the following file structure:
 
 <ul class="tree">
@@ -805,18 +775,17 @@ add-on whenever it is run.
 
 </table>
 
 ## <a name="internal-commands">Internal Commands</a> ##
 
 ### cfx sdocs ###
 
 Executing this command builds a static HTML version of the SDK documentation
-that can be hosted on a web server without the special application support
-required by `cfx docs`.
+that can be hosted on a web server.
 
 #### Options ####
 
 <table>
 <colgroup>
 <col width="50%">
 <col width="50%">
 </colgroup>
--- a/addon-sdk/source/doc/dev-guide-source/console.md
+++ b/addon-sdk/source/doc/dev-guide-source/console.md
@@ -1,46 +1,207 @@
 <!-- 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/. -->
 
 # console #
 
 The `console` object enables your add-on to log messages. If you have started
-the host application for your add-on from the command line (for example, by
-executing `cfx run` or `cfx test`) then these messages appear in the command
-shell you used. If the add-on has been installed in the host application, then
-the messages appear in the host application's
+Firefox for your add-on from the command line with `cfx run` or `cfx test`
+then these messages appear in the command shell you used. If the add-on has
+been installed in Firefox, then the messages appear in the host application's
 [Error Console](https://developer.mozilla.org/en/Error_Console).
 
-The `console` object has the following methods:
-
-<code>console.**log**(*object*[, *object*, ...])</code>
+If you're developing your add-on using the
+[Add-on Builder](https://builder.addons.mozilla.org/) or are using
+the [Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/),
+then the add-on is installed in Firefox, meaning that messages will appear in
+the Error Console. But see the discussion of
+[logging levels](dev-guide/console.html#Logging Levels): by default, messages
+logged using  `log()`, `info()`, `trace()`, or `warn()` won't be logged in
+these situations.
 
-Logs an informational message to the shell.
+## Console Methods ##
+
+All console methods except `exception()` and `trace()` accept one or
+more JavaScript objects as arguments and log them to the console.
 Depending on the console's underlying implementation and user interface,
-you may be able to introspect into the properties of non-primitive objects
+you may be able to examine the properties of non-primitive objects
 that are logged.
 
-<code>console.**info**(*object*[, *object*, ...])</code>
+### <code>console.log(*object*[, *object*, ...])</code> ###
+
+Logs the arguments to the console, preceded by "info:" and the name of your
+add-on:
+
+    console.log("This is an informational message");
+
+<pre>
+info: my-addon: This is an informational message
+</pre>
+
+### <code>console.info(*object*[, *object*, ...])</code> ###
 
 A synonym for `console.log()`.
 
-<code>console.**warn**(*object*[, *object*, ...])</code>
+### <code>console.warn(*object*[, *object*, ...])</code> ###
+
+Logs the arguments to the console, preceded by "warn:" and the name of your
+add-on:
+
+    console.warn("This is a warning message");
 
-Logs a warning message.
+<pre>
+warn: my-addon: This is a warning message
+</pre>
 
-<code>console.**error**(*object*[, *object*, ...])</code>
+### <code>console.error(*object*[, *object*, ...])</code> ###
+
+Logs the arguments to the console, preceded by "error:" and the name of your
+add-on:
 
-Logs an error message.
+    console.error("This is an error message");
 
-<code>console.**debug**(*object*[, *object*, ...])</code>
+<pre>
+error: my-addon: This is an error message
+</pre>
+
+### <code>console.debug(*object*[, *object*, ...])</code> ###
 
-Logs a debug message.
+Logs the arguments to the console, preceded by "debug:" and the name of your
+add-on:
+
+    console.error("This is a debug message");
 
-<code>console.**exception**(*exception*)</code>
+<pre>
+debug: my-addon: This is a debug message
+</pre>
+
+### <code>console.exception(*exception*)</code> ###
 
 Logs the given exception instance as an error, outputting information
 about the exception's stack traceback if one is available.
 
-<code>console.**trace**()</code>
+    try {
+       doThing();
+    } catch (e) {
+       console.exception(e);
+    }
+
+    function UserException(message) {
+       this.message = message;
+       this.name = "UserException";
+    }
+
+    function doThing() {
+      throw new UserException("Thing could not be done!");
+    }
+
+<pre>
+error: my-addon: An exception occurred.
+UserException: Thing could not be done!
+</pre>
+
+### <code>console.trace()</code> ###
+
+Logs a stack trace at the point the function is called.
+
+<h2 id="Logging Levels">Logging Levels</h2>
+
+Logging's useful, of course, especially during development. But the more
+logging there is, the more noise you see in the console output.
+Especially when debug logging shows up in a production environment, the
+noise can make it harder, not easier, to debug issues.
+
+This is the problem that logging levels are designed to fix. The console
+defines a number of logging levels, from "more verbose" to "less verbose",
+and a number of different logging functions that correspond to these levels,
+which are arranged in order of "severity" from informational
+messages, through warnings, to errors.
+
+At a given logging level, only calls to the corresponding functions and
+functions with a higher severity will have any effect.
+
+For example, if the logging level is set to "info", then calls to `info()`,
+`log()`, `warn()`, and `error()` will all result in output being written.
+But if the logging level is "warn" then only calls to `warn()` and `error()`
+have any effect, and calls to `info()` and `log()` are simply discarded.
+
+This means that the same code can be more verbose in a development
+environment than in a production environment - you just need to arrange for
+the appropriate logging level to be set.
+
+The complete set of logging levels is given in the table below, along
+with the set of functions that will result in output at each level:
+
+<table>
+  <colgroup>
+    <col width="10%">
+    <col width="90%">
+  </colgroup>
+
+  <tr>
+    <th>Level</th>
+    <th>Will log calls to:</th>
+  </tr>
 
-Logs a stack trace at the point this function is called.
+  <tr>
+    <td>all</td>
+    <td>Any console method</td>
+  </tr>
+
+  <tr>
+    <td>debug</td>
+    <td><code>debug()</code>, <code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
+  </tr>
+
+  <tr>
+    <td>info</td>
+    <td><code>log()</code>, <code>info()</code>, <code>trace()</code>, <code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
+  </tr>
+
+  <tr>
+    <td>warn</td>
+    <td><code>warn()</code>, <code>exception()</code>, <code>error()</code></td>
+  </tr>
+
+  <tr>
+    <td>error</td>
+    <td><code>exception()</code>, <code>error()</code></td>
+  </tr>
+
+  <tr>
+    <td>off</td>
+    <td>Nothing</td>
+  </tr>
+
+</table>
+
+### Setting the Logging Level ###
+
+The logging level defaults to "error".
+
+There are two system preferences that can be used to override this default:
+
+* **extensions.sdk.console.logLevel**: if set, this determines the logging
+level for all installed SDK-based add-ons.
+
+* **extensions.[extension-id].sdk.console.logLevel**: if set, this determines
+the logging level for the specified add-on. This overrides the global
+preference if both are set.
+
+Both these preferences can be set programmatically using the
+[`preferences/service`](modules/sdk/preferences/service.html) API, or manually
+using [about:config](http://kb.mozillazine.org/About:config). The value for each
+preference is the desired logging level, given as a string. 
+
+When you run your add-on using `cfx run` or `cfx test`, the global
+**extensions.sdk.console.logLevel** preference is automatically set to "info".
+This means that calls to `console.log()` will appear in the console output.
+
+When you install an add-on into Firefox, the logging level will be "error"
+by default (that is, unless you have set one of the two preferences). This
+means that messages written using `debug()`, `log()`, `info()`, `trace()`,
+and `warn()` will not appear in the console.
+
+This includes add-ons being developed using the
+[Add-on Builder](https://builder.addons.mozilla.org/) or the
+[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/cross-domain.md
@@ -0,0 +1,177 @@
+<!-- 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/. -->
+
+# Cross-domain Content Scripts #
+
+By default, content scripts don't have any cross-domain privileges.
+In particular, they can't:
+
+* [access content hosted in an `iframe`, if that content is served from a different domain](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain iframes)
+* [make cross-domain XMLHttpRequests](dev-guide/guides/content-scripts/cross-domain.html#Cross-domain XMLHttpRequest)
+
+However, you can enable these features for specific domains
+by adding them to your add-on's [package.json](dev-guide/package-spec.html)
+under the `"cross-domain-content"` key, which itself lives under the
+`"permissions"` key:
+
+<pre>
+"permissions": {
+    "cross-domain-content": ["http://example.org/", "http://example.com/"]
+}
+</pre>
+
+* The domains listed must include the scheme and fully qualified domain name,
+and these must exactly match the domains serving the content - so in the
+example above, the content script will not be allowed to access content
+served from `https://example.com/`.
+* Wildcards are not allowed.
+* This feature is currently only available for content scripts, not for page
+scripts included in HTML files shipped with your add-on.
+
+## Cross-domain iframes ##
+
+The following "main.js" creates a page-worker which loads a local HTML file
+called "page.html", attaches a content script called "page.js" to the
+page, waits for messages from the script, and logs the payload.
+
+    //main.js
+    var data = require("sdk/self").data;
+
+    var pageWorker = require("sdk/page-worker").Page({
+      contentURL: data.url("page.html"),
+      contentScriptFile: data.url("page-script.js")
+    });
+
+    pageWorker.on("message", function(message) {
+      console.log(message);
+    });
+
+The "page.html" file embeds an iframe whose content is
+served from "http://en.m.wikipedia.org/":
+
+<pre class="brush: html">
+    &lt;!doctype html&gt;
+    &lt;!-- page.html --&gt;
+    &lt;html&gt;
+      &lt;head>&lt;/head&gt;
+      &lt;body&gt;
+        &lt;iframe id="wikipedia" src="http://en.m.wikipedia.org/"&gt;&lt;/iframe&gt;
+      &lt;/body&gt;
+    &lt;/html&gt;
+</pre>
+
+The "page-script.js" file locates "Today's Featured Article" and sends its
+content to "main.js":
+
+    // page-script.js
+    var iframe = window.document.getElementById("wikipedia");
+    var todaysFeaturedArticle = iframe.contentWindow.document.getElementById("mp-tfa");
+    self.postMessage(todaysFeaturedArticle.textContent);
+
+For this to work, we need to add the `"cross-domain-content"` key to
+"package.json":
+
+<pre>
+"permissions": {
+  "cross-domain-content": ["http://en.m.wikipedia.org/"]
+}
+</pre>
+
+The add-on should successfully retrieve the iframe's content.
+
+## Cross-domain XMLHttpRequest ##
+
+The following add-on creates a panel whose content is the summary weather
+forecast for [Shetland](https://en.wikipedia.org/wiki/Shetland).
+If you want to try it out, you'll need to
+[register](http://www.metoffice.gov.uk/datapoint/support/API)
+and get an API key.
+
+The "main.js":
+
+* creates a panel whose content is supplied by "panel.html" and
+adds a content script "panel-script.js" to it
+* sends the panel a "show" message when it is shown
+* attaches the panel to a widget
+
+<!-- terminate Markdown list -->
+
+    // main.js
+    var data = require("sdk/self").data;
+
+    var forecast_panel = require("sdk/panel").Panel({
+      height: 50,
+      contentURL: data.url("panel.html"),
+      contentScriptFile: data.url("panel-script.js")
+    });
+
+    forecast_panel.on("show", function(){
+      forecast_panel.port.emit("show");
+    });
+
+    require("sdk/widget").Widget({
+      id: "forecast",
+      label: "Weather Forecast",
+      contentURL: "http://www.metoffice.gov.uk/favicon.ico",
+      panel: forecast_panel
+    });
+
+The "panel.html" just includes a `<div>` block for the forecast:
+
+<pre class="brush: html">
+&lt;!doctype HTML&gt;
+&lt;!-- panel.html --&gt;
+
+&lt;html&gt;
+  &lt;head&gt;&lt;/head&gt;
+  &lt;body&gt;
+    &lt;div id="forecast_summary">&lt;/div&gt;
+  &lt;/body&gt;
+&lt;/html&gt;
+</pre>
+
+The "panel-script.js" uses [XMLHttpRequest](https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest)
+to fetch the latest forecast:
+
+    // panel-script.js
+
+    var url = "http://datapoint.metoffice.gov.uk/public/data/txt/wxfcs/regionalforecast/json/500?key=YOUR-API-KEY";
+
+    self.port.on("show", function () {
+      var request = new XMLHttpRequest();
+      request.open("GET", url, true);
+      request.onload = function () {
+        var jsonResponse = JSON.parse(request.responseText);
+        var summary = getSummary(jsonResponse);
+        var element = document.getElementById("forecast_summary");
+        element.textContent = summary;
+      };
+      request.send();
+    });
+
+    function getSummary(forecast) {
+      return forecast.RegionalFcst.FcstPeriods.Period[0].Paragraph[0].$;
+    }
+
+
+Finally, we need to add the `"cross-domain-content"` key to "package.json":
+
+<pre>
+"permissions": {
+  "cross-domain-content": ["http://datapoint.metoffice.gov.uk"]
+}
+</pre>
+
+## Content Permissions and unsafeWindow ##
+
+If you use `"cross-domain-content"`, then JavaScript values in content
+scripts will not be available from pages. Suppose your content script includes
+a line like:
+
+    // content-script.js:
+    unsafeWindow.myCustomAPI = function () {};
+
+If you have included the `"cross-domain-content"` key, when the page script
+tries to access `myCustomAPI` this will result in a "permission denied"
+exception.
--- a/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/index.md
+++ b/addon-sdk/source/doc/dev-guide-source/guides/content-scripts/index.md
@@ -87,10 +87,12 @@ detail about the access content scripts 
 detail about how content scripts can communicate with "main.js", with other
 content scripts, and with scripts loaded by the web page itself
 * [Communicating Using <code>port</code>](dev-guide/guides/content-scripts/using-port.html):
 how to communicate between your add-on and its content scripts using the
 <code>port</code> object
 * [Communicating using <code>postMessage()</code>](dev-guide/guides/content-scripts/using-postmessage.html):
 how to communicate between your add-on and its content scripts using the
 <code>postMessage()</code> API
+* [Cross-domain Content Scripts](dev-guide/guides/content-scripts/cross-domain.html):
+how to enable a content script to interact with content served from other domains.
 * [Example](dev-guide/guides/content-scripts/reddit-example.html):
 a simple example add-on using content scripts
--- a/addon-sdk/source/doc/dev-guide-source/guides/index.md
+++ b/addon-sdk/source/doc/dev-guide-source/guides/index.md
@@ -139,16 +139,22 @@ This page lists more theoretical in-dept
       this technique and the <code>port</code> object.
     </td>
 
   </tr>
 
   <tr>
 
     <td>
+      <h4><a href="dev-guide/guides/content-scripts/cross-domain.html">Cross-domain content scripts</a></h4>
+      How to enable content scripts to interact with content served from different domains.
+    </td>
+
+
+    <td>
       <h4><a href="dev-guide/guides/content-scripts/reddit-example.html">Reddit example</a></h4>
       A simple add-on which uses content scripts.
     </td>
 
   </tr>
 
 </table>
 
--- a/addon-sdk/source/doc/dev-guide-source/guides/xul-migration.md
+++ b/addon-sdk/source/doc/dev-guide-source/guides/xul-migration.md
@@ -48,16 +48,87 @@ supported APIs, it's a good candidate fo
 APIs, or XPCOM, then the cost of migrating is high, and may not be worth
 it at this point.
 
 * If your add-on only needs a little help from those techniques, and can
 accomplish most of what it needs using the supported APIs, then it might
 still be worth migrating: we'll add more supported APIs in future releases
 to meet important use cases.
 
+## <a name="user-interface-components">User Interface Components</a>##
+
+XUL-based add-ons typically implement a user interface using a combination
+of two techniques: XUL overlays and XUL windows.
+
+### XUL Overlays ###
+
+XUL overlays are used to modify existing windows such as the main browser
+window. In this way an extension can integrate its user interface into the
+browser: for example, adding menu items, buttons, and toolbars.
+
+Because SDK-based extensions are restartless, they can't use XUL overlays. To
+add user interface components to the browser, there are a few different
+options. In order of complexity, the main options are:
+
+* the SDK includes modules that implement some basic user interface
+components including [buttons](modules/sdk/widget.html),
+[dialogs](modules/sdk/panel.html), and
+[context menu items](modules/sdk/context-menu.html).
+
+* there is a collection of
+[community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
+that includes various user interface components, including
+[toolbar buttons](https://github.com/voldsoftware/toolbarbutton-jplib) and
+[menu items](https://github.com/voldsoftware/menuitems-jplib).
+
+* by using the SDK's
+[low-level APIs](dev-guide/guides/xul-migration.html#Using the Low-level APIs)
+you can directly modify the browser chrome.
+
+### XUL Windows
+
+XUL windows are used to define completely new windows to host user interface
+elements specific to the add-on.
+
+The SDK generally expects you to specify your user interface using HTML, not
+XUL. However, you can include a
+[chrome.manifest file](https://developer.mozilla.org/en-US/docs/Chrome_Registration)
+in your add-on and it will be included in the generated XPI.
+
+<ul class="tree">
+  <li>my-addon
+    <ul>
+    <li class="highlight-tree-node">chrome
+      <ul><li>content</li>
+          <li>locale</li>
+          <li>skin</li></ul>
+    </li>
+    <li class="highlight-tree-node">chrome.manifest</li>
+    <li>data</li>
+    <li>lib</li>
+    <li>package.json</li>
+    </ul>
+  </li>
+</ul>
+
+There are limitations on what you can do in this manifest file: for example,
+you can't register overlays, `resource:` URIs, or components. However, you
+can register a `chrome:` URI, with a skin and locale, and this means you
+can include XUL windows in an SDK-based add-on.
+
+You can keep the "chrome.manifest" file in your add-on's root directory
+and create a directory there called "chrome". In that directory you can keep
+your "content", "locale", and "skin" subdirectories:
+
+This allows you to refer to objects in these directories from "chrome.manifest" using a relative path, like "chrome/content".
+
+This is provided only as a migration aid, and it's still a good idea to port XUL windows to HTML.
+
+<div style="clear:both"></div>
+
 ## <a name="content-scripts">Content Scripts</a> ##
 
 In a XUL-based add-on, code that uses XPCOM objects, code that manipulates
 the browser chrome, and code that interacts with web pages all runs in the
 same context. But the SDK makes a distinction between:
 
 * **add-on scripts**, which can use the SDK APIs, but are not able to interact
 with web pages
@@ -66,37 +137,18 @@ the SDK's APIs
 
 Content scripts and add-on scripts communicate by sending each other JSON
 messages: in fact, the ability to communicate with the add-on scripts is the
 only extra privilege a content script is granted over a normal remote web
 page script.
 
 A XUL-based add-on will need to be reorganized to respect this distinction.
 
-Suppose an add-on wants to make a cross-domain XMLHttpRequest based on some
-data extracted from a web page. In a XUL-based extension you would implement
-all this in a single script. An SDK-based equivalent would need to be
-structured like this:
-
-* the main add-on code (1) attaches a content script to the page, and (2)
-registers a listener function for messages from the content script
-* the content script (3) extracts the data from the page and (4) sends
-it to the main add-on code in a message
-* the main add-on code (5) receives the message and (6) sends the request,
-using the SDK's [`request`](modules/sdk/request.html) API
-
-<img class="image-center" src="static-files/media/xul-migration-cs.png"
-alt="Content script organization">
-
-There are two related reasons for this design. The first is security: it
-reduces the risk that a malicious web page will be able to access privileged
-APIs. The second is the need to be compatible with the multi-process architecture
-planned for Firefox: after this is implemented in Firefox, all add-ons will
-need to use a similar pattern, so it's likely that a XUL-based add-on will
-need to be rewritten anyway.
+The main reason for this design is security: it reduces the risk that a
+malicious web page will be able to access privileged APIs.
 
 There's much more information on content scripts in the
 [Working With Content Scripts](dev-guide/guides/content-scripts/index.html) guide.
 
 ## <a name="supported-apis">Using the Supported APIs</a> ##
 
 The SDK provides a set of high level APIs
 providing some basic user interface components and functionality commonly
--- a/addon-sdk/source/doc/dev-guide-source/package-spec.md
+++ b/addon-sdk/source/doc/dev-guide-source/package-spec.md
@@ -174,19 +174,23 @@ directory the first time you run
 
 <tr>
   <td id="permissions"><code>permissions</code></td>
   <td><p>A set of permissions that the add-on needs.</p>
     <p><strong><code>private-browsing</code></strong>: a boolean
   indicating whether or not the
   add-on supports private browsing. If this value is not <code>true</code>
   or is omitted, then the add-on will not see any private windows or
-objects, such as tabs, that are associated with private windows. See the
-documentation for the
-<a href="modules/sdk/private-browsing.html"><code>private-browsing</code> module</a>.</p>
+  objects, such as tabs, that are associated with private windows. See the
+  documentation for the
+  <a href="modules/sdk/private-browsing.html"><code>private-browsing</code> module</a>.</p>
+    <p><strong><code>cross-domain-content</code></strong>: a list of domains for
+  which content scripts are given cross-domain privileges to access content in
+  iframes or to make XMLHTTPRequests. See the documentation for
+<a href="dev-guide/guides/content-scripts/cross-domain.html">enabling cross-domain content scripts</a>.</p>
   </td>
 </tr>
 
 <tr>
   <td id="preferences"><code>preferences</code></td>
   <td><p>An array of JSON objects that use the following keys:
   <code>name</code>,<code>type</code>, <code>value</code>,
   <code>title</code>, and <code>description</code>.  These JSON objects will be used to
--- a/addon-sdk/source/doc/dev-guide-source/search.md
+++ b/addon-sdk/source/doc/dev-guide-source/search.md
@@ -1,14 +1,14 @@
 <!-- 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/. -->
 
 <div id="cse" style="width: 100%;">Loading</div>
-<script src="http://www.google.com/jsapi" type="text/javascript"></script>
+<script src="https://www.google.com/jsapi" type="text/javascript"></script>
 <script type="text/javascript">
 function parseQueryFromUrl () {
   var queryParamName = "q";
   var search = window.location.search.substr(1);
   var parts = search.split('&');
   for (var i = 0; i < parts.length; i++) {
     var keyvaluepair = parts[i].split('=');
     if (decodeURIComponent(keyvaluepair[0]) == queryParamName) {
@@ -33,17 +33,17 @@ google.setOnLoadCallback(function() {
     searchBox.value = queryFromUrl;
     searchBox.focus();
     searchBox.blur();
     customSearchControl.execute(queryFromUrl);
   }
 }, true);
 </script>
 
-<link rel="stylesheet" href="http://www.google.com/cse/style/look/default.css" type="text/css" />
+<link rel="stylesheet" href="https://www.google.com/cse/style/look/default.css" type="text/css" />
 
 <style type="text/css">
   #cse table, #cse tr, #cse td {
     border: none;
   }
 
   .gsc-above-wrapper-area, .gsc-result-info-container {
 	border: none;
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/adding-menus.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/adding-menus.md
@@ -16,49 +16,60 @@ But it's extensible by design, so anyone
 modules for add-on developers to use. Luckily, Erik Vold has written
 a [`menuitems`](https://github.com/erikvold/menuitems-jplib) module
 that enables us to add menu items.
 
 This tutorial does double-duty. It describes the general method for
 using an external, third-party module in your add-on, and it
 describes how to add a menu item using the `menuitems` module in particular.
 
+First, create a new add-on. Make a directory called "clickme" wherever you
+like, navigate to it and run `cfx init`.
+
+<pre>
+mkdir clickme
+cd clickme
+cfx init
+</pre>
+
+The usual directory structure will be created:
+
+<ul class="tree">
+  <li>clickme
+    <ul>
+    <li>data</li>
+    <li>docs
+      <ul><li>main.md</li></ul>
+    </li>
+    <li>lib
+      <ul><li>main.js</li></ul>
+    </li>
+    <li>package.json</li>
+    <li>README.md</li>
+    <li>tests
+      <ul><li>test-main.js</li></ul>
+    </li>
+    </ul>
+  </li>
+</ul>
+
+<div style="clear:both"></div>
+
 ## Installing `menuitems` ##
 
-First we'll download the `menuitems` package from
-[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176).
-
-Third-party packages like `menuitems` can be installed in three
-different places:
-
-* in the `packages` directory under the SDK root. If you do this the package
-is available to any other add-ons you're developing using that SDK instance,
-and the package's documentation is visible through `cfx docs`.
-* in a `packages` directory you create under your add-on's root: if you
-do this, the package is only available to that add-on.
-* in a directory indicated using the `packages` key in
-your add-on's [package.json](dev-guide/package-spec.html). If you
-do this, you may not keep any packages in your add-on's `packages`
-directory, or they will not be found.
-
-In this example we will install the package under the SDK root. From
-the SDK root directory, execute something like the following commands:
+Create a directory under "clickme" called "packages".
+Then download the `menuitems` package from
+[https://github.com/erikvold/menuitems-jplib](https://github.com/erikvold/menuitems-jplib/zipball/51080383cbb0fe2a05f8992a8aae890f4c014176) and extract it into the "packages" directory you just created:
 
 <pre>
+mkdir packages
 cd packages
 tar -xf ../erikvold-menuitems-jplib-d80630c.zip
 </pre>
 
-Now if you run `cfx docs` you'll see a new section appear in the sidebar
-labeled "Third-Party APIs", which lists the modules in the `menuitems`
-package: this package contains a single module, also
-called `menuitems`.
-
-Click on the module name and you'll see API documentation for the module.
-
 ## Module Dependencies ##
 
 If third-party modules only depend on SDK modules, you can use them right
 away, but if they depend on other third-party modules, you'll have to install
 those dependencies as well.
 
 In the package's main directory you'll find a file called "package.json".
 Open it up and look for an entry named "dependencies". The entry for the
@@ -70,33 +81,28 @@ Open it up and look for an entry named "
 
 This tells us that we need to install the `vold-utils` package,
 which we can do by downloading it from
 [https://github.com/erikvold/vold-utils-jplib](https://github.com/voldsoftware/vold-utils-jplib/zipball/1b2ad874c2d3b2070a1b0d43301aa3731233e84f)
 and adding it under the `packages` directory alongside `menuitems`.
 
 ## Using `menuitems` ##
 
-We can use the `menuitems` module in exactly the same way we use built-in
-modules.
-
-The documentation for the `menuitems` module tells us to we create a menu
-item using `MenuItem()`. Of the options accepted by `MenuItem()`, we'll use
-this minimal set:
+The [documentation for the `menuitems` module](https://github.com/erikvold/menuitems-jplib/blob/master/docs/menuitems.md)
+tells us to create a menu item using `MenuItem()`. Of the options
+accepted by `MenuItem()`, we'll use this minimal set:
 
 * `id`: identifier for this menu item
 * `label`: text the item displays
 * `command`: function called when the user selects the item
 * `menuid`: identifier for the item's parent element
 * `insertbefore`: identifier for the item before which we want our item to
 appear
 
-Next, create a new add-on. Make a directory called 'clickme' wherever you
-like, navigate to it and run `cfx init`. Open `lib/main.js` and add the
-following code:
+<!--comment to terminate Markdown list -->
 
     var menuitem = require("menuitems").Menuitem({
       id: "clickme",
       menuid: "menu_ToolsPopup",
       label: "Click Me!",
       onCommand: function() {
         console.log("clicked");
       },
@@ -121,22 +127,11 @@ like this:
 </pre>
 
 Now we're done. Run the add-on and you'll see the new item appear in the
 `Tools` menu: select it and you'll see `info: clicked` appear in the
 console.
 
 ## Caveats ##
 
-Eventually we expect the availability of a rich set of third party packages
-will be one of the most valuable aspects of the SDK. Right now they're a great
-way to use features not supported by the supported APIs without the
-complexity of using the low-level APIs, but there are some caveats you should
-be aware of:
-
-* our support for third party packages is still fairly immature. One
-consequence of this is that it's not always obvious where to find third-party
-packages, although the
-[Community Developed Modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules)
-page in the SDK's GitHub Wiki lists a number of packages.
-
-* because third party modules typically use low-level APIs, they may be broken
-by new releases of Firefox.
+Third-party modules are a great way to use features not directly supported by
+the SDK, but because third party modules typically use low-level APIs,
+they may be broken by new releases of Firefox.
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/installation.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/installation.md
@@ -138,21 +138,16 @@ many lines of usage information:
 <pre>
 Usage: cfx [options] [command]
 </pre>
 
 This is the `cfx` command-line program.  It's your primary interface to the
 Add-on SDK.  You use it to launch Firefox and test your add-on, package your
 add-on for distribution, view documentation, and run unit tests.
 
-## cfx docs ##
-
-If you're reading these documents online, try running `cfx docs`. This will
-build the documentation for the SDK and display it in a browser.
-
 ## Problems? ##
 
 Try the [Troubleshooting](dev-guide/tutorials/troubleshooting.html)
 page.
 
 ## Next Steps ##
 
 Next, take a look at the
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/logging.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/logging.md
@@ -56,12 +56,21 @@ script:
 If you are running your add-on from the command line (for example,
 executing `cfx run` or `cfx test`) then the console's messages appear
 in the command shell you used.
 
 If you've installed the add-on in Firefox, or you're running the
 add-on in the Add-on Builder, then the messages appear in Firefox's
 [Error Console](https://developer.mozilla.org/en/Error_Console).
 
+But note that **by default, calls to `console.log()` will not result
+in any output in the Error Console for any installed add-ons**: this
+includes add-ons installed using the Add-on Builder or using tools
+like the
+[Extension Auto-installer](https://addons.mozilla.org/en-US/firefox/addon/autoinstaller/).
+
+See ["Logging Levels"](dev-guide/console.html#Logging Levels)
+in the console reference documentation for more information on this.
+
 ## Learning More ##
 
 For the complete `console` API, see its
 [API reference](dev-guide/console.html).
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/mobile.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/mobile.md
@@ -307,17 +307,16 @@ Modules not yet supported in Firefox Mob
  - keyboard/observer
  - keyboard/utils
  - lang/functional
  - lang/type
  - [loader/cuddlefish](modules/sdk/loader/cuddlefish.html)
  - [loader/sandbox](modules/sdk/loader/sandbox.html)
  - [net/url](modules/sdk/net/url.html)
  - [net/xhr](modules/sdk/net/xhr.html)
- - [page-mod/match-pattern](modules/sdk/page-mod/match-pattern.html)
  - [platform/xpcom](modules/sdk/platform/xpcom.html)
  - [preferences/service](modules/sdk/preferences/service.html)
  - [system/environment](modules/sdk/system/environment.html)
  - [system/events](modules/sdk/system/events.html)
  - system/globals
  - [system/runtime](modules/sdk/system/runtime.html)
  - [system/unload](modules/sdk/system/unload.html)
  - [system/xul-app](modules/sdk/system/xul-app.html)
@@ -325,11 +324,12 @@ Modules not yet supported in Firefox Mob
  - [test/harness](modules/sdk/test/harness.html)
  - [test/httpd](modules/sdk/test/httpd.html)
  - [test/runner](modules/sdk/test/runner.html)
  - test/tmp-file
  - util/array
  - [util/collection](modules/sdk/util/collection.html)
  - [util/deprecate](modules/sdk/util/deprecate.html)
  - [util/list](modules/sdk/util/list.html)
+ - [util/match-pattern](modules/sdk/util/match-pattern.html)
  - util/registry
  - [util/uuid](modules/sdk/util/uuid.html)
  - [window/utils](modules/sdk/window/utils.html)
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/modifying-web-pages-url.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/modifying-web-pages-url.md
@@ -47,17 +47,17 @@ Try it out:
 This is what you should see:
 
 <img  class="image-center" src="static-files/media/screenshots/pagemod-ietf.png"
 alt="ietf.org eaten by page-mod" />
 
 ## Specifying the Match Pattern ##
 
 The match pattern uses the
-[`match-pattern`](modules/sdk/page-mod/match-pattern.html)
+[`match-pattern`](modules/sdk/util/match-pattern.html)
 syntax. You can pass a single match-pattern string, or an array.
 
 ## Keeping the Content Script in a Separate File ##
 
 In the example above we've passed in the content script as a string. Unless
 the script is extremely simple, you should instead maintain the script as a
 separate file. This makes the code easier to maintain, debug, and review.
 
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/reusable-modules.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/reusable-modules.md
@@ -366,37 +366,16 @@ to the add-on:
 ### Repackaging ###
 
 Next we'll repackage the geolocation module.
 
 * create a new directory called "geolocation", and run `cfx init` in it.
 * delete the "main.js" that `cfx` generated, and copy "geolocation.js"
 there instead.
 
-### Documentation ###
-
-If you document your modules, people who install your package and
-execute `cfx docs` will see the documentation
-integrated with the SDK's own documentation.
-
-You can document the geolocation module by creating a file called
-"geolocation.md" in your package's "doc" directory. This file is also
-written in Markdown, although you can optionally use some
-[extended syntax](https://wiki.mozilla.org/Jetpack/SDK/Writing_Documentation#APIDoc_Syntax)
-to document APIs.
-
-Try it:
-
-* add a "geolocation.md" under "doc"
-* copy your geolocation package under the "packages" directory in the SDK root
-* execute `cfx docs`
-
-Once `cfx docs` has finished, you should see a new entry appear in the
-sidebar called "Third-Party APIs", which lists the geolocation module.
-
 ### Editing "package.json" ###
 
 The "package.json" file in your package's root directory contains metadata
 for your package. See the
 [package specification](dev-guide/package-spec.html) for
 full details. If you intend to distribute the package, this is a good place
 to add your name as the author, choose a distribution license, and so on.
 
--- a/addon-sdk/source/doc/module-source/sdk/context-menu.md
+++ b/addon-sdk/source/doc/module-source/sdk/context-menu.md
@@ -116,17 +116,17 @@ exported by the `context-menu` module.
     <td>
       This context occurs when the menu is invoked on pages with particular
       URLs. <code>matchPattern</code> is a match pattern string or an array of
       match pattern strings. When <code>matchPattern</code> is an array, the
       context occurs when the menu is invoked on a page whose URL matches any of
       the patterns. These are the same match pattern strings that you use with
       the <a href="modules/sdk/page-mod.html"><code>page-mod</code></a>
       <code>include</code> property.
-      <a href="modules/sdk/page-mod/match-pattern.html">Read more about patterns</a>.
+      <a href="modules/sdk/util/match-pattern.html">Read more about patterns</a>.
     </td>
   </tr>
   <tr>
     <td>
       array
     </td>
     <td>
       An array of any of the other types. This context occurs when all contexts
@@ -798,12 +798,12 @@ top-level context menu.
 
 <api name="URLContext">
 @class
 <api name="URLContext">
 @constructor
   Creates a context that matches pages with particular URLs. See Specifying
   Contexts above.
 @param matchPattern {string,array}
-  A [match pattern](modules/sdk/page-mod/match-pattern.html) string, regexp or an
+  A [match pattern](modules/sdk/util/match-pattern.html) string, regexp or an
   array of match pattern strings or regexps.
 </api>
 </api>
--- a/addon-sdk/source/doc/module-source/sdk/page-mod.md
+++ b/addon-sdk/source/doc/module-source/sdk/page-mod.md
@@ -298,17 +298,17 @@ Creates a page-mod.
 
         var pageMod = require("sdk/page-mod");
         pageMod.PageMod({
           include: "*.mozilla.org",
           contentScript: 'window.alert("Page matches ruleset");'
         });
 
     You can specify a set of URLs using a
-    [regular expression](modules/sdk/page-mod/match-pattern.html#Regular Expressions).
+    [regular expression](modules/sdk/util/match-pattern.html#Regular Expressions).
     The pattern must match the entire URL, not just a subset, and has
     `global`, `ignoreCase`, and `multiline` disabled.
 
         var pageMod = require("sdk/page-mod");
         pageMod.PageMod({
           include: /.*developer.*/,
           contentScript: 'window.alert("Page matches ruleset");'
         });
@@ -316,17 +316,17 @@ Creates a page-mod.
   To specify multiple patterns, pass an array of match patterns:
 
       var pageMod = require("sdk/page-mod");
       pageMod.PageMod({
         include: ["*.developer.mozilla.org", "*.addons.mozilla.org"],
         contentScript: 'window.alert("Page matches ruleset");'
       });
 
-    See the [match-pattern](modules/sdk/page-mod/match-pattern.html) module for
+    See the [match-pattern](modules/sdk/util/match-pattern.html) module for
     a detailed description of match pattern syntax.
 
   @prop [contentScriptFile] {string,array}
     This option specifies one or more content scripts to attach to targeted
     documents.
 
     Each script is supplied as a separate file under your add-on's "data"
     directory, and is specified by a URL typically constructed using the
deleted file mode 100644
--- a/addon-sdk/source/doc/module-source/sdk/page-mod/match-pattern.md
+++ /dev/null
@@ -1,259 +0,0 @@
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-The `match-pattern` module can be used to test strings containing URLs
-against simple patterns.
-
-## Specifying Patterns ##
-
-There are three ways you can specify patterns:
-
-* as an exact match string
-* using a wildcard in a string
-* using a regular expression
-
-### Exact Matches ###
-
-**A URL** matches only that URL. The URL must start with a scheme, end with a
-slash, and contain no wildcards.
-
-<table>
-
-  <colgroup>
-    <col width="20%">
-    <col width="25%">
-    <col width="55%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-    <th>Example non-matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>"http://example.com/"</code></td>
-    <td><code>http://example.com/</code></td>
-    <td><code>http://example.com</code><br>
-        <code>http://example.com/foo</code><br>
-        <code>https://example.com/</code><br>
-        <code>http://foo.example.com/</code></td>
-  </tr>
-
-</table>
-
-### Wildcards ###
-
-**A single asterisk** matches any URL with an `http`, `https`, or `ftp`
-scheme. For other schemes like `file`, `resource`, or `data`, use a scheme
-followed by an asterisk, as below.
-
-<table>
-
-  <colgroup>
-    <col width="20%">
-    <col width="25%">
-    <col width="55%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-    <th>Example non-matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>"*"</code></td>
-    <td><code>http://example.com/</code><br>
-        <code>https://example.com/</code><br>
-        <code>ftp://example.com/</code><br>
-        <code>http://bar.com/foo.js</code><br>
-        <code>http://foo.com/</code></td>
-    <td><code>file://example.js</code><br>
-        <code>resource://me/my-addon/data/file.html</code><br>
-        <code>data:text/html,Hi there</code></td>
-  </tr>
-
-</table>
-
-**A domain name prefixed with an asterisk and dot** matches any URL of that
-domain or a subdomain, using any of `http`, `https`, `ftp`.
-
-<table>
-
-  <colgroup>
-    <col width="20%">
-    <col width="25%">
-    <col width="55%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-    <th>Example non-matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>"*.example.com"</code></td>
-    <td><code>http://example.com/</code><br>
-        <code>http://foo.example.com/</code><br>
-        <code>https://example.com/</code><br>
-        <code>http://example.com/foo</code><br>
-        <code>ftp://foo.example.com/</code></td>
-    <td><code>ldap://example.com</code><br>
-        <code>http://example.foo.com/</code></td>
-  </tr>
-
-</table>
-
-**A URL followed by an asterisk** matches that URL and any URL prefixed with
-the pattern.
-
-<table>
-
-  <colgroup>
-    <col width="20%">
-    <col width="25%">
-    <col width="55%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-    <th>Example non-matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>"https://foo.com/*"</code></td>
-    <td><code>https://foo.com/</code><br>
-        <code>https://foo.com/bar</code></td>
-    <td><code>http://foo.com/</code><br>
-        <code>https://foo.com</code><br>
-        <code>https://bar.foo.com/</code></td>
-  </tr>
-
-</table>
-
-**A scheme followed by an asterisk** matches all URLs with that scheme. To
-match local files, use `file://*`, and to match files loaded from your
-add-on's [data](modules/sdk/self.html#data) directory, use `resource://`.
-
-<table>
-
-  <colgroup>
-    <col width="20%">
-    <col width="80%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>"file://*"</code></td>
-    <td><code>file://C:/file.html</code><br>
-        <code>file:///home/file.png</code></td>
-  </tr>
-
-  <tr>
-    <td><code>"resource://*"</code></td>
-    <td><code>resource://my-addon-at-me-dot-org/my-addon/data/file.html</code></td>
-  </tr>
-
-  <tr>
-    <td><code>"data:*"</code></td>
-    <td><code>data:text/html,Hi there</code></td>
-  </tr>
-
-</table>
-
-### Regular Expressions ###
-
-You can specify patterns using a
-[regular expression](https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions):
-
-    var { MatchPattern } = require("sdk/page-mod/match-pattern");
-    var pattern = new MatchPattern(/.*example.*/);
-
-The regular expression is subject to restrictions based on those applied to the
-[HTML5 pattern attribute](http://dev.w3.org/html5/spec/common-input-element-attributes.html#attr-input-pattern). In particular:
-
-* The pattern must match the entire value, not just any subset. For example, the
-pattern `/moz.*/` will not match the URL `http://mozilla.org`.
-
-* The expression is compiled with the `global`, `ignoreCase`, and `multiline` flags
-  disabled. The `MatchPattern` constructor will throw an exception
-  if you try to set any of these flags.
-
-<table>
-
-  <colgroup>
-    <col width="30%">
-    <col width="35%">
-    <col width="35%">
-  </colgroup>
-
-  <tr>
-    <th>Example pattern</th>
-    <th>Example matching URLs</th>
-    <th>Example non-matching URLs</th>
-  </tr>
-
-  <tr>
-    <td><code>/.*moz.*/</code></td>
-    <td><code>http://foo.mozilla.org/</code><br>
-        <code>http://mozilla.org</code><br>
-        <code>https://mozilla.org</code><br>
-        <code>http://foo.com/mozilla</code><br>
-        <code>http://hemozoon.org</code><br>
-        <code>mozscheme://foo.org</code><br></td>
-    <td><code>http://foo.org</code><br>
-  </tr>
-
-  <tr>
-    <td><code>/http:\/\/moz.*/</code></td>
-    <td><code>http://mozilla.org</code><br>
-        <code>http://mozzarella.com</code></td>
-    <td><code>https://mozilla.org</code><br>
-        <code>http://foo.mozilla.org/</code><br>
-        <code>http://foo.com/moz</code></td>
-  </tr>
-
-  <tr>
-    <td><code>/http.*moz.*/</code><br></td>
-    <td><code>http://foo.mozilla.org/</code><br>
-        <code>http://mozilla.org</code><br>
-        <code>http://hemozoon.org/</code></td>
-        <td><code>ftp://http/mozilla.org</code></td>
-  </tr>
-
-</table>
-
-## Examples ##
-
-    var { MatchPattern } = require("sdk/page-mod/match-pattern");
-    var pattern = new MatchPattern("http://example.com/*");
-    console.log(pattern.test("http://example.com/"));       // true
-    console.log(pattern.test("http://example.com/foo"));    // true
-    console.log(pattern.test("http://foo.com/"));           // false!
-
-<api name="MatchPattern">
-@class
-<api name="MatchPattern">
-@constructor
-  This constructor creates match pattern objects that can be used to test URLs.
-@param pattern {string}
-  The pattern to use.  See Patterns above.
-</api>
-
-<api name="test">
-@method
-  Tests a URL against the match pattern.
-@param url {string}
-  The URL to test.
-@returns {boolean}
-  True if the URL matches the pattern and false otherwise.
-</api>
-</api>
--- a/addon-sdk/source/doc/module-source/sdk/panel.md
+++ b/addon-sdk/source/doc/module-source/sdk/panel.md
@@ -28,23 +28,16 @@ and the content remains loaded when a pa
 to keep a panel around in the background, updating its content as appropriate
 in preparation for the next time it is shown.
 
 Your add-on can receive notifications when a panel is shown or hidden by
 listening to its `show` and `hide` events.
 
 Opening a panel will close an already opened panel.
 
-<div class="warning">
-If your add-on has
-<a href="modules/sdk/private-browsing.html#Opting into private browsing">opted into private browsing</a>,
-then you can't use panels in your add-on. This is due to a platform bug which we expect to
-be fixed in Firefox 21.
-</div>
-
 ## Panel Content ##
 
 The panel's content is specified as HTML, which is loaded from the URL
 supplied in the `contentURL` option to the panel's constructor.
 
 You can load remote HTML into the panel:
 
     var panel = require("sdk/panel").Panel({
@@ -389,23 +382,19 @@ alt="OS X panel default style">
 This helps to ensure that the panel's style is consistent with the dialogs
 displayed by Firefox and other applications, but means you need to take care
 when applying your own styles. For example, if you set the panel's
 `background-color` property to `white` and do not set the `color` property,
 then the panel's text will be invisible on OS X although it looks fine on Ubuntu.
 
 ## Private Browsing ##
 
-If your add-on has
+If your add-on has not
 [opted into private browsing](modules/sdk/private-browsing.html#Opting into private browsing),
-then **you can't use panels in your add-on**. This is due to a platform bug which we expect to
-be fixed in Firefox 21.
-
-If your add-on has not opted into private browsing, and it calls `panel.show()`
-when the currently active window is a
+and it calls `panel.show()` when the currently active window is a
 [private window](modules/sdk/private-browsing.html#Per-window private browsing),
 then the panel will not be shown.
 
 <api name="Panel">
 @class
 The Panel object represents a floating modal dialog that can by an add-on to
 present user interface content.
 
--- a/addon-sdk/source/doc/module-source/sdk/private-browsing.md
+++ b/addon-sdk/source/doc/module-source/sdk/private-browsing.md
@@ -46,25 +46,20 @@ context menus that belong to private bro
 content scripts to documents belonging to private browser windows
 
 * any [`panel`](modules/sdk/panel.html) objects will not be shown if the
 active window is a private browser window
 
 * the [`selection`](modules/sdk/selection.html) module will not include
 any selections made in private browser windows
 
-Add-ons that have opted in:
-
-* will see private windows, so they will need to
+Add-ons that have opted in will see private windows, so they will need to
 use the `private-browsing` module to check whether objects are private,
 so as to avoid storing data derived from such objects.
 
-* will not be able to use panels in their code. This is due to a platform
-restriction which will be fixed in Firefox 21.
-
 Additionally, add-ons that use low-level modules such as
 [`window/utils`](modules/sdk/window/utils.html) may see private browser
 windows with certain functions, even if they have not explicitly opted
 into private browsing.
 
 ## Respecting private browsing ##
 
 The `private-browsing` module exports a single function
--- a/addon-sdk/source/doc/module-source/sdk/tabs.md
+++ b/addon-sdk/source/doc/module-source/sdk/tabs.md
@@ -2,62 +2,119 @@
    - 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/. -->
 
 <!-- contributed by Dietrich Ayala [dietrich@mozilla.com]  -->
 <!-- edited by Noelle Murata [fiveinchpixie@gmail.com]  -->
 
 The `tabs` module provides easy access to tabs and tab-related events.
 
-The module itself can be used like a basic list of all opened
-tabs across all windows. In particular, you can enumerate it:
-
-    var tabs = require('sdk/tabs');
-    for each (var tab in tabs)
-      console.log(tab.title);
+## Module-level Operations ##
 
-You can also access individual tabs by index:
-
-    var tabs = require('sdk/tabs');
-
-    tabs.on('ready', function () {
-      console.log('first: ' + tabs[0].title);
-      console.log('last: ' + tabs[tabs.length-1].title);
-    });
+### Open a Tab ###
 
 You can open a new tab, specifying various properties including location:
 
     var tabs = require("sdk/tabs");
     tabs.open("http://www.example.com");
 
+### Track Tabs ###
+
 You can register event listeners to be notified when tabs open, close, finish
 loading DOM content, or are made active or inactive:
 
     var tabs = require("sdk/tabs");
 
     // Listen for tab openings.
     tabs.on('open', function onOpen(tab) {
       myOpenTabs.push(tab);
     });
 
     // Listen for tab content loads.
     tabs.on('ready', function(tab) {
       console.log('tab is loaded', tab.title, tab.url)
     });
 
+### Access Tabs ###
+
+The module itself can be used as a list of all opened
+tabs across all windows. In particular, you can enumerate it:
+
+    var tabs = require('sdk/tabs');
+    for each (var tab in tabs)
+      console.log(tab.title);
+
+You can also access individual tabs by index:
+
+    var tabs = require('sdk/tabs');
+
+    tabs.on('ready', function () {
+      console.log('first: ' + tabs[0].title);
+      console.log('last: ' + tabs[tabs.length-1].title);
+    });
+
+You can access the currently active tab:
+
+    var tabs = require('sdk/tabs');
+
+    tabs.on('activate', function () {
+      console.log('active: ' + tabs.activeTab.url);
+    });
+
+## Tab-level Operations ##
+
+### Track a Tab ###
+
+Given a tab, you can register event listeners to be notified when the
+tab is closed, activated or deactivated, or when the page hosted by the
+tab is loaded or retrieved from the
+["back-forward cache"](https://developer.mozilla.org/en-US/docs/Working_with_BFCache):
+
+    var tabs = require("sdk/tabs");
+
+    function onOpen(tab) {
+      console.log(tab.url + " is open");
+      tab.on("pageshow", logShow);
+      tab.on("activate", logActivate);
+      tab.on("deactivate", logDeactivate);
+      tab.on("close", logClose);
+    }
+
+    function logShow(tab) {
+      console.log(tab.url + " is loaded");
+    }
+
+    function logActivate(tab) {
+      console.log(tab.url + " is activated");
+    }
+
+    function logDeactivate(tab) {
+      console.log(tab.url + " is deactivated");
+    }
+
+    function logClose(tab) {
+      console.log(tab.url + " is closed");
+    }
+
+    tabs.on('open', onOpen);
+
+### Manipulate a Tab ###
+
 You can get and set various properties of tabs (but note that properties
  relating to the tab's content, such as the URL, will not contain valid
 values until after the tab's `ready` event fires). By setting the `url`
 property you can load a new page in the tab:
 
     var tabs = require("sdk/tabs");
     tabs.on('activate', function(tab) {
       tab.url = "http://www.example.com";
     });
 
+### Run Scripts in a Tab ###
+
 You can attach a [content script](dev-guide/guides/content-scripts/index.html)
 to the page hosted in a tab, and use that to access and manipulate the page's
 content (see the
 [Modifying the Page Hosted by a Tab](dev-guide/tutorials/modifying-web-pages-tab.html) tutorial):
 
     var tabs = require("sdk/tabs");
 
     tabs.on('activate', function(tab) {
@@ -157,35 +214,35 @@ Boolean which will determine whether the
 If your add-on does not support private browsing this will have no effect.
 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.
+A callback function that will be registered for the 'open' event.
 This is an optional property.
 @prop [onClose] {function}
-A callback function that will be registered for 'close' event.
+A callback function that will be registered for the 'close' event.
 This is an optional property.
 @prop [onReady] {function}
-A callback function that will be registered for 'ready' event.
+A callback function that will be registered for the 'ready' event.
 This is an optional property.
 @prop [onLoad] {function}
-A callback function that will be registered for 'load' event.
+A callback function that will be registered for the 'load' event.
 This is an optional property.
 @prop [onPageShow] {function}
-A callback function that will be registered for 'pageshow' event.
+A callback function that will be registered for the 'pageshow' event.
 This is an optional property.
 @prop [onActivate] {function}
-A callback function that will be registered for 'activate' event.
+A callback function that will be registered for the 'activate' event.
 This is an optional property.
 @prop [onDeactivate] {function}
-A callback function that will be registered for 'deactivate' event.
+A callback function that will be registered for the 'deactivate' event.
 This is an optional property.
 </api>
 
 <api name="Tab">
 @class
 A `Tab` instance represents a single open tab. It contains various tab
 properties, several methods for manipulation, as well as per-tab event
 registration.
@@ -335,67 +392,80 @@ tab's window is closed.
 @argument {Tab}
 Listeners are passed the tab object.
 </api>
 
 <api name="ready">
 @event
 
 This event is emitted when the DOM for the tab's content is ready. It is
-equivalent to the `DOMContentLoaded` event for the given content page.
+equivalent to the
+[`DOMContentLoaded`](https://developer.mozilla.org/en-US/docs/Web/Reference/Events/DOMContentLoaded)
+event for the given content page.
 
 A single tab will emit this event every time the DOM is loaded: so it will be
 emitted again if the tab's location changes or the content is reloaded.
-
 After this event has been emitted, all properties relating to the tab's
 content can be used.
 
 @argument {Tab}
 Listeners are passed the tab object.
 </api>
 
 <api name="load">
 @event
 
 This event is emitted when the page for the tab's content is loaded. It is
-equivalent to the `load` event for the given content page.
+equivalent to the
+[`load`](https://developer.mozilla.org/en-US/docs/Web/Reference/Events/load)
+event for the given content page.
 
 A single tab will emit this event every time the page is loaded: so it will be
 emitted again if the tab's location changes or the content is reloaded.
+This event is similar to the [`ready`](modules/sdk/tabs.html#ready) event,
+except that it can be used for pages that do not have a `DOMContentLoaded`
+event, like images.
 
 After this event has been emitted, all properties relating to the tab's
-content can be used.
-
-This is fired after the `ready` event on DOM content pages and can be used
-for pages that do not have a `DOMContentLoaded` event, like images.
+content can be used. For pages that have a `DOMContentLoaded` event, `load`
+is fired after `ready`.
 
 @argument {Tab}
 Listeners are passed the tab object.
 </api>
 
 <api name="pageshow">
 @event
 
-This event is emitted when the page for the tab's content is potentially
-from the cache. It is equivilent to the [pageshow](https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/pageshow) event for the given
-content page.
+The `pageshow` event is emitted when the page for a tab's content is loaded.
+It is equivalent to the
+[`pageshow`](https://developer.mozilla.org/en-US/docs/DOM/Mozilla_event_reference/pageshow)
+event for the given content page.
+
+This event is similar to the [`load`](modules/sdk/tabs.html#load) and
+[`ready`](modules/sdk/tabs.html#ready) events, except unlike
+`load` and `ready`, `pageshow` is triggered if the page was retrieved from the
+[bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache).
+This means that if the user loads a page, loads a new page, then
+moves back to the previous page using the "Back" button,
+the `pageshow` event is emitted when the user moves back to the previous
+page, while the `load` and `ready` events are not.
+
+This event is *not* emitted when the tab is made the active tab: to get
+notified about that, you need to listen to the
+[`activate`](modules/sdk/tabs.html#activate) event.
 
 After this event has been emitted, all properties relating to the tab's
-content can be used.
-
-While the `ready` and `load` events will not be fired when a user uses the back
-or forward buttons to navigate history, the `pageshow` event will be fired.
-If the `persisted` argument is true, then the contents were loaded from the
-bfcache.
+content can be used. It is emitted after `load` and `ready`.
 
 @argument {Tab}
 Listeners are passed the tab object.
 @argument {persisted}
 Listeners are passed a boolean value indicating whether or not the page
-was loaded from the [bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache) or not.
+was loaded from the [bfcache](https://developer.mozilla.org/en-US/docs/Working_with_BFCache).
 </api>
 
 <api name="activate">
 @event
 
 This event is emitted when the tab is made active.
 
 @argument {Tab}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/doc/module-source/sdk/util/match-pattern.md
@@ -0,0 +1,259 @@
+<!-- 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/. -->
+
+The `match-pattern` module can be used to test strings containing URLs
+against simple patterns.
+
+## Specifying Patterns ##
+
+There are three ways you can specify patterns:
+
+* as an exact match string
+* using a wildcard in a string
+* using a regular expression
+
+### Exact Matches ###
+
+**A URL** matches only that URL. The URL must start with a scheme, end with a
+slash, and contain no wildcards.
+
+<table>
+
+  <colgroup>
+    <col width="20%">
+    <col width="25%">
+    <col width="55%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+    <th>Example non-matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>"http://example.com/"</code></td>
+    <td><code>http://example.com/</code></td>
+    <td><code>http://example.com</code><br>
+        <code>http://example.com/foo</code><br>
+        <code>https://example.com/</code><br>
+        <code>http://foo.example.com/</code></td>
+  </tr>
+
+</table>
+
+### Wildcards ###
+
+**A single asterisk** matches any URL with an `http`, `https`, or `ftp`
+scheme. For other schemes like `file`, `resource`, or `data`, use a scheme
+followed by an asterisk, as below.
+
+<table>
+
+  <colgroup>
+    <col width="20%">
+    <col width="25%">
+    <col width="55%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+    <th>Example non-matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>"*"</code></td>
+    <td><code>http://example.com/</code><br>
+        <code>https://example.com/</code><br>
+        <code>ftp://example.com/</code><br>
+        <code>http://bar.com/foo.js</code><br>
+        <code>http://foo.com/</code></td>
+    <td><code>file://example.js</code><br>
+        <code>resource://me/my-addon/data/file.html</code><br>
+        <code>data:text/html,Hi there</code></td>
+  </tr>
+
+</table>
+
+**A domain name prefixed with an asterisk and dot** matches any URL of that
+domain or a subdomain, using any of `http`, `https`, `ftp`.
+
+<table>
+
+  <colgroup>
+    <col width="20%">
+    <col width="25%">
+    <col width="55%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+    <th>Example non-matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>"*.example.com"</code></td>
+    <td><code>http://example.com/</code><br>
+        <code>http://foo.example.com/</code><br>
+        <code>https://example.com/</code><br>
+        <code>http://example.com/foo</code><br>
+        <code>ftp://foo.example.com/</code></td>
+    <td><code>ldap://example.com</code><br>
+        <code>http://example.foo.com/</code></td>
+  </tr>
+
+</table>
+
+**A URL followed by an asterisk** matches that URL and any URL prefixed with
+the pattern.
+
+<table>
+
+  <colgroup>
+    <col width="20%">
+    <col width="25%">
+    <col width="55%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+    <th>Example non-matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>"https://foo.com/*"</code></td>
+    <td><code>https://foo.com/</code><br>
+        <code>https://foo.com/bar</code></td>
+    <td><code>http://foo.com/</code><br>
+        <code>https://foo.com</code><br>
+        <code>https://bar.foo.com/</code></td>
+  </tr>
+
+</table>
+
+**A scheme followed by an asterisk** matches all URLs with that scheme. To
+match local files, use `file://*`, and to match files loaded from your
+add-on's [data](modules/sdk/self.html#data) directory, use `resource://`.
+
+<table>
+
+  <colgroup>
+    <col width="20%">
+    <col width="80%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>"file://*"</code></td>
+    <td><code>file://C:/file.html</code><br>
+        <code>file:///home/file.png</code></td>
+  </tr>
+
+  <tr>
+    <td><code>"resource://*"</code></td>
+    <td><code>resource://my-addon-at-me-dot-org/my-addon/data/file.html</code></td>
+  </tr>
+
+  <tr>
+    <td><code>"data:*"</code></td>
+    <td><code>data:text/html,Hi there</code></td>
+  </tr>
+
+</table>
+
+### Regular Expressions ###
+
+You can specify patterns using a
+[regular expression](https://developer.mozilla.org/en/JavaScript/Guide/Regular_Expressions):
+
+    var { MatchPattern } = require("sdk/util/match-pattern");
+    var pattern = new MatchPattern(/.*example.*/);
+
+The regular expression is subject to restrictions based on those applied to the
+[HTML5 pattern attribute](http://dev.w3.org/html5/spec/common-input-element-attributes.html#attr-input-pattern). In particular:
+
+* The pattern must match the entire value, not just any subset. For example, the
+pattern `/moz.*/` will not match the URL `http://mozilla.org`.
+
+* The expression is compiled with the `global`, `ignoreCase`, and `multiline` flags
+  disabled. The `MatchPattern` constructor will throw an exception
+  if you try to set any of these flags.
+
+<table>
+
+  <colgroup>
+    <col width="30%">
+    <col width="35%">
+    <col width="35%">
+  </colgroup>
+
+  <tr>
+    <th>Example pattern</th>
+    <th>Example matching URLs</th>
+    <th>Example non-matching URLs</th>
+  </tr>
+
+  <tr>
+    <td><code>/.*moz.*/</code></td>
+    <td><code>http://foo.mozilla.org/</code><br>
+        <code>http://mozilla.org</code><br>
+        <code>https://mozilla.org</code><br>
+        <code>http://foo.com/mozilla</code><br>
+        <code>http://hemozoon.org</code><br>
+        <code>mozscheme://foo.org</code><br></td>
+    <td><code>http://foo.org</code><br>
+  </tr>
+
+  <tr>
+    <td><code>/http:\/\/moz.*/</code></td>
+    <td><code>http://mozilla.org</code><br>
+        <code>http://mozzarella.com</code></td>
+    <td><code>https://mozilla.org</code><br>
+        <code>http://foo.mozilla.org/</code><br>
+        <code>http://foo.com/moz</code></td>
+  </tr>
+
+  <tr>
+    <td><code>/http.*moz.*/</code><br></td>
+    <td><code>http://foo.mozilla.org/</code><br>
+        <code>http://mozilla.org</code><br>
+        <code>http://hemozoon.org/</code></td>
+        <td><code>ftp://http/mozilla.org</code></td>
+  </tr>
+
+</table>
+
+## Examples ##
+
+    var { MatchPattern } = require("sdk/util/match-pattern");
+    var pattern = new MatchPattern("http://example.com/*");
+    console.log(pattern.test("http://example.com/"));       // true
+    console.log(pattern.test("http://example.com/foo"));    // true
+    console.log(pattern.test("http://foo.com/"));           // false!
+
+<api name="MatchPattern">
+@class
+<api name="MatchPattern">
+@constructor
+  This constructor creates match pattern objects that can be used to test URLs.
+@param pattern {string}
+  The pattern to use.  See Patterns above.
+</api>
+
+<api name="test">
+@method
+  Tests a URL against the match pattern.
+@param url {string}
+  The URL to test.
+@returns {boolean}
+  True if the URL matches the pattern and false otherwise.
+</api>
+</api>
deleted file mode 100644
index 639faaa2be35fbe53f23e4f21197d5fdd516ca3e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/addon-sdk/source/lib/sdk/addon-page.js
+++ b/addon-sdk/source/lib/sdk/addon-page.js
@@ -32,39 +32,42 @@ WindowTracker({
     let { XULBrowserWindow } = window;
     let { hideChromeForLocation } = XULBrowserWindow;
 
     windows(window).hideChromeForLocation = hideChromeForLocation;
 
     // Augmenting the behavior of `hideChromeForLocation` method, as
     // suggested by https://developer.mozilla.org/en-US/docs/Hiding_browser_chrome
     XULBrowserWindow.hideChromeForLocation = function(url) {
-      if (url.indexOf(addonURL) === 0) {
-        let rest = url.substr(addonURL.length);
-        return rest.length === 0 || ['#','?'].indexOf(rest.charAt(0)) > -1
-      }
-
-      return hideChromeForLocation.call(this, url);
+      return isAddonURL(url) || hideChromeForLocation.call(this, url);
     }
   },
 
   onUntrack: function onUntrack(window) {
     if (isXULBrowser(window))
       getTabs(window).filter(tabFilter).forEach(untrackTab.bind(null, window));
   }
 });
 
+function isAddonURL(url) {
+  if (url.indexOf(addonURL) === 0) {
+    let rest = url.substr(addonURL.length);
+    return ((rest.length === 0) || (['#','?'].indexOf(rest.charAt(0)) > -1));
+  }
+  return false;
+}
+
 function tabFilter(tab) {
-  return getURI(tab) === addonURL;
+  return isAddonURL(getURI(tab));
 }
 
 function untrackTab(window, tab) {
   // Note: `onUntrack` will be called for all windows on add-on unloads,
   // so we want to clean them up from these URLs.
   let { hideChromeForLocation } = windows(window);
 
   if (hideChromeForLocation) {
-    window.XULBrowserWindow.hideChromeForLocation = hideChromeForLocation;
+    window.XULBrowserWindow.hideChromeForLocation = hideChromeForLocation.bind(window.XULBrowserWindow);
     windows(window).hideChromeForLocation = null;
   }
 
   closeTab(tab);
 }
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/addon/events.js
@@ -0,0 +1,48 @@
+/* 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'
+};
+
+let { request: hostReq, response: hostRes } = require('./host');
+let { defer: async } = require('../lang/functional');
+let { defer } = require('../core/promise');
+let { emit: emitSync, on, off } = require('../event/core');
+let { uuid } = require('../util/uuid');
+let emit = async(emitSync);
+
+// Map of IDs to deferreds
+let requests = new Map();
+
+function receive ({data, id, error}) {
+  let request = requests.get(id);
+  if (request) {
+    if (error) request.reject(error);
+    else request.resolve(serialize(data));
+    requests.delete(id);
+  }
+}
+on(hostRes, 'data', receive);
+
+/*
+ * Send is a helper to be used in client APIs to send
+ * a request to host
+ */
+function send (eventName, data) {
+  let id = uuid();
+  let deferred = defer();
+  requests.set(id, deferred);
+  emit(hostReq, 'data', {
+    id: id,
+    data: serialize(data),
+    event: eventName
+  });
+  return deferred.promise;
+}
+exports.send = send;
+
+function serialize (obj) JSON.parse(JSON.stringify(obj))
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/addon/host.js
@@ -0,0 +1,12 @@
+/* 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"
+};
+
+exports.request = {};
+exports.response = {};
--- a/addon-sdk/source/lib/sdk/content/events.js
+++ b/addon-sdk/source/lib/sdk/content/events.js
@@ -25,33 +25,33 @@ let insert = observe("document-element-i
 let windowCreate = merge([
   observe("content-document-global-created"),
   observe("chrome-document-global-created")
 ]);
 let create = map(windowCreate, function({target, data, type}) {
   return { target: target.document, type: type, data: data }
 });
 
-function readStates({document}) {
+function streamEventsFrom({document}) {
   // Map supported event types to a streams of those events on the given
   // `window` for the inserted document and than merge these streams into
   // single form stream off all window state change events.
   let stateChanges = TYPES.map(function(type) {
     return open(document, type, { capture: true });
   });
 
   // Since load events on document occur for every loded resource
   return filter(merge(stateChanges), function({target}) {
     return target instanceof Ci.nsIDOMDocument
   })
 }
-
+exports.streamEventsFrom = streamEventsFrom;
 
 let opened = windows(null, { includePrivate: true });
-let state = merge(opened.map(readStates));
+let state = merge(opened.map(streamEventsFrom));
 
 
 let futureReady = filter(windowEvents, function({type})
                                         type === "DOMContentLoaded");
 let futureWindows = map(futureReady, function({target}) target);
-let futureState = expand(futureWindows, readStates);
+let futureState = expand(futureWindows, streamEventsFrom);
 
 exports.events = merge([insert, create, state, futureState]);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/content/utils.js
@@ -0,0 +1,43 @@
+/* -*- 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/. */
+"use strict";
+
+module.metadata = {
+  "stability": "unstable"
+};
+
+let assetsURI = require("../self").data.url();
+let isArray = Array.isArray;
+
+function isAddonContent({ contentURL }) {
+  return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
+}
+exports.isAddonContent = isAddonContent;
+
+function hasContentScript({ contentScript, contentScriptFile }) {
+  return (isArray(contentScript) ? contentScript.length > 0 :
+         !!contentScript) ||
+         (isArray(contentScriptFile) ? contentScriptFile.length > 0 :
+         !!contentScriptFile);
+}
+exports.hasContentScript = hasContentScript;
+
+function requiresAddonGlobal(model) {
+  return isAddonContent(model) && !hasContentScript(model);
+}
+exports.requiresAddonGlobal = requiresAddonGlobal;
+
+function getAttachEventType(model) {
+  if (!model) return null;
+  let when = model.contentScriptWhen;
+  return requiresAddonGlobal(model) ? "document-element-inserted" :
+         when === "start" ? "document-element-inserted" :
+         when === "end" ? "load" :
+         when === "ready" ? "DOMContentLoaded" :
+         null;
+}
+exports.getAttachEventType = getAttachEventType;
+
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ b/addon-sdk/source/lib/sdk/context-menu.js
@@ -10,17 +10,17 @@ module.metadata = {
 const { Class, mix } = require("./core/heritage");
 const { addCollectionProperty } = require("./util/collection");
 const { ns } = require("./core/namespace");
 const { validateOptions, getTypeOf } = require("./deprecated/api-utils");
 const { URL, isValidURI } = require("./url");
 const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
 const { isBrowser, getInnerId } = require("./window/utils");
 const { Ci } = require("chrome");
-const { MatchPattern } = require("./page-mod/match-pattern");
+const { MatchPattern } = require("./util/match-pattern");
 const { Worker } = require("./content/worker");
 const { EventTarget } = require("./event/target");
 const { emit } = require('./event/core');
 const { when } = require('./system/unload');
 
 // All user items we add have this class.
 const ITEM_CLASS = "addon-context-menu-item";
 
--- a/addon-sdk/source/lib/sdk/core/heritage.js
+++ b/addon-sdk/source/lib/sdk/core/heritage.js
@@ -155,18 +155,22 @@ var Class = new function() {
     var initialize = prototype.initialize;
 
     // Combine ancestor attributes with prototype's attributes so that
     // ancestors attributes also become initializeable.
     var attributes = mix(descriptor.extends.constructor.attributes || {},
                          getDataProperties(prototype));
 
     constructor.attributes = attributes;
-    constructor.prototype = prototype;
-    return freeze(constructor);
+    Object.defineProperty(constructor, 'prototype', {
+      configurable: false,
+      writable: false,
+      value: prototype
+    });
+    return constructor;
   };
 }
 Class.prototype = extend(null, obscure({
   constructor: function constructor() {
     this.initialize.apply(this, arguments);
     return this;
   },
   initialize: function initialize() {
--- a/addon-sdk/source/lib/sdk/deprecated/events.js
+++ b/addon-sdk/source/lib/sdk/deprecated/events.js
@@ -87,17 +87,17 @@ const eventEmitter =  {
   /**
    * Returns an array of listeners for the specified event `type`. This array
    * can be manipulated, e.g. to remove listeners.
    * @param {String} type
    *    The type of event.
    */
   _listeners: function listeners(type) {
     let events = this._events || (this._events = {});
-    return events[type] || (events[type] = []);
+    return (events.hasOwnProperty(type) && events[type]) || (events[type] = []);
   },
 
   /**
    * Execute each of the listeners in order with the supplied arguments.
    * Returns `true` if listener for this event was called, `false` if there are
    * no listeners for this event `type`.
    *
    * All the exceptions that are thrown by listeners during the emit
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/fs/path.js
@@ -0,0 +1,446 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+// Adapted version of:
+// https://github.com/joyent/node/blob/v0.9.1/lib/path.js
+
+
+var system = require('../system');
+var isWindows = system.platform.indexOf('win') === 0;
+
+
+// resolves . and .. elements in a path array with directory names there
+// must be no slashes, empty elements, or device names (c:\) in the array
+// (so also no leading and trailing slashes - it does not distinguish
+// relative and absolute paths)
+function normalizeArray(parts, allowAboveRoot) {
+  // if the path tries to go above the root, `up` ends up > 0
+  var up = 0;
+  for (var i = parts.length - 1; i >= 0; i--) {
+    var last = parts[i];
+    if (last === '.') {
+      parts.splice(i, 1);
+    } else if (last === '..') {
+      parts.splice(i, 1);
+      up++;
+    } else if (up) {
+      parts.splice(i, 1);
+      up--;
+    }
+  }
+
+  // if the path is allowed to go above the root, restore leading ..s
+  if (allowAboveRoot) {
+    for (; up--; up) {
+      parts.unshift('..');
+    }
+  }
+
+  return parts;
+}
+
+
+if (isWindows) {
+  // Regex to split a windows path into three parts: [*, device, slash,
+  // tail] windows-only
+  var splitDeviceRe =
+      /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?([\\\/])?([\s\S]*?)$/;
+
+  // Regex to split the tail part of the above into [*, dir, basename, ext]
+  var splitTailRe =
+      /^([\s\S]+[\\\/](?!$)|[\\\/])?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/\\]*)?)$/;
+
+  // Function to split a filename into [root, dir, basename, ext]
+  // windows version
+  var splitPath = function(filename) {
+    // Separate device+slash from tail
+    var result = splitDeviceRe.exec(filename),
+        device = (result[1] || '') + (result[2] || ''),
+        tail = result[3] || '';
+    // Split the tail into dir, basename and extension
+    var result2 = splitTailRe.exec(tail),
+        dir = result2[1] || '',
+        basename = result2[2] || '',
+        ext = result2[3] || '';
+    return [device, dir, basename, ext];
+  };
+
+  // path.resolve([from ...], to)
+  // windows version
+  exports.resolve = function() {
+    var resolvedDevice = '',
+        resolvedTail = '',
+        resolvedAbsolute = false;
+
+    for (var i = arguments.length - 1; i >= -1; i--) {
+      var path;
+      if (i >= 0) {
+        path = arguments[i];
+      } else if (!resolvedDevice) {
+        path = system.pathFor('CurProcD');
+      } else {
+        // Windows has the concept of drive-specific current working
+        // directories. If we've resolved a drive letter but not yet an
+        // absolute path, get cwd for that drive. We're sure the device is not
+        // an unc path at this points, because unc paths are always absolute.
+        path = system.env['=' + resolvedDevice];
+        // Verify that a drive-local cwd was found and that it actually points
+        // to our drive. If not, default to the drive's root.
+        if (!path || path.substr(0, 3).toLowerCase() !==
+            resolvedDevice.toLowerCase() + '\\') {
+          path = resolvedDevice + '\\';
+        }
+      }
+
+      // Skip empty and invalid entries
+      if (typeof path !== 'string' || !path) {
+        continue;
+      }
+
+      var result = splitDeviceRe.exec(path),
+          device = result[1] || '',
+          isUnc = device && device.charAt(1) !== ':',
+          isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
+          tail = result[3];
+
+      if (device &&
+          resolvedDevice &&
+          device.toLowerCase() !== resolvedDevice.toLowerCase()) {
+        // This path points to another device so it is not applicable
+        continue;
+      }
+
+      if (!resolvedDevice) {
+        resolvedDevice = device;
+      }
+      if (!resolvedAbsolute) {
+        resolvedTail = tail + '\\' + resolvedTail;
+        resolvedAbsolute = isAbsolute;
+      }
+
+      if (resolvedDevice && resolvedAbsolute) {
+        break;
+      }
+    }
+
+    // Replace slashes (in UNC share name) by backslashes
+    resolvedDevice = resolvedDevice.replace(/\//g, '\\');
+
+    // At this point the path should be resolved to a full absolute path,
+    // but handle relative paths to be safe (might happen when process.cwd()
+    // fails)
+
+    // Normalize the tail path
+
+    function f(p) {
+      return !!p;
+    }
+
+    resolvedTail = normalizeArray(resolvedTail.split(/[\\\/]+/).filter(f),
+                                  !resolvedAbsolute).join('\\');
+
+    return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) ||
+           '.';
+  };
+
+  // windows version
+  exports.normalize = function(path) {
+    var result = splitDeviceRe.exec(path),
+        device = result[1] || '',
+        isUnc = device && device.charAt(1) !== ':',
+        isAbsolute = !!result[2] || isUnc, // UNC paths are always absolute
+        tail = result[3],
+        trailingSlash = /[\\\/]$/.test(tail);
+
+    // Normalize the tail path
+    tail = normalizeArray(tail.split(/[\\\/]+/).filter(function(p) {
+      return !!p;
+    }), !isAbsolute).join('\\');
+
+    if (!tail && !isAbsolute) {
+      tail = '.';
+    }
+    if (tail && trailingSlash) {
+      tail += '\\';
+    }
+
+    // Convert slashes to backslashes when `device` points to an UNC root.
+    device = device.replace(/\//g, '\\');
+
+    return device + (isAbsolute ? '\\' : '') + tail;
+  };
+
+  // windows version
+  exports.join = function() {
+    function f(p) {
+      return p && typeof p === 'string';
+    }
+
+    var paths = Array.prototype.filter.call(arguments, f);
+    var joined = paths.join('\\');
+
+    // Make sure that the joined path doesn't start with two slashes
+    // - it will be mistaken for an unc path by normalize() -
+    // unless the paths[0] also starts with two slashes
+    if (/^[\\\/]{2}/.test(joined) && !/^[\\\/]{2}/.test(paths[0])) {
+      joined = joined.substr(1);
+    }
+
+    return exports.normalize(joined);
+  };
+
+  // path.relative(from, to)
+  // it will solve the relative path from 'from' to 'to', for instance:
+  // from = 'C:\\orandea\\test\\aaa'
+  // to = 'C:\\orandea\\impl\\bbb'
+  // The output of the function should be: '..\\..\\impl\\bbb'
+  // windows version
+  exports.relative = function(from, to) {
+    from = exports.resolve(from);
+    to = exports.resolve(to);
+
+    // windows is not case sensitive
+    var lowerFrom = from.toLowerCase();
+    var lowerTo = to.toLowerCase();
+
+    function trim(arr) {
+      var start = 0;
+      for (; start < arr.length; start++) {
+        if (arr[start] !== '') break;
+      }
+
+      var end = arr.length - 1;
+      for (; end >= 0; end--) {
+        if (arr[end] !== '') break;
+      }
+
+      if (start > end) return [];
+      return arr.slice(start, end - start + 1);
+    }
+
+    var toParts = trim(to.split('\\'));
+
+    var lowerFromParts = trim(lowerFrom.split('\\'));
+    var lowerToParts = trim(lowerTo.split('\\'));
+
+    var length = Math.min(lowerFromParts.length, lowerToParts.length);
+    var samePartsLength = length;
+    for (var i = 0; i < length; i++) {
+      if (lowerFromParts[i] !== lowerToParts[i]) {
+        samePartsLength = i;
+        break;
+      }
+    }
+
+    if (samePartsLength == 0) {
+      return to;
+    }
+
+    var outputParts = [];
+    for (var i = samePartsLength; i < lowerFromParts.length; i++) {
+      outputParts.push('..');
+    }
+
+    outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+    return outputParts.join('\\');
+  };
+
+  exports.sep = '\\';
+
+} else /* posix */ {
+
+  // Split a filename into [root, dir, basename, ext], unix version
+  // 'root' is just a slash, or nothing.
+  var splitPathRe =
+      /^(\/?)([\s\S]+\/(?!$)|\/)?((?:\.{1,2}$|[\s\S]+?)?(\.[^.\/]*)?)$/;
+  var splitPath = function(filename) {
+    var result = splitPathRe.exec(filename);
+    return [result[1] || '', result[2] || '', result[3] || '', result[4] || ''];
+  };
+
+  // path.resolve([from ...], to)
+  // posix version
+  exports.resolve = function() {
+    var resolvedPath = '',
+        resolvedAbsolute = false;
+
+    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
+      var path = (i >= 0) ? arguments[i] : system.pathFor('CurProcD');
+
+      // Skip empty and invalid entries
+      if (typeof path !== 'string' || !path) {
+        continue;
+      }
+
+      resolvedPath = path + '/' + resolvedPath;
+      resolvedAbsolute = path.charAt(0) === '/';
+    }
+
+    // At this point the path should be resolved to a full absolute path, but
+    // handle relative paths to be safe (might happen when process.cwd() fails)
+
+    // Normalize the path
+    resolvedPath = normalizeArray(resolvedPath.split('/').filter(function(p) {
+      return !!p;
+    }), !resolvedAbsolute).join('/');
+
+    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
+  };
+
+  // path.normalize(path)
+  // posix version
+  exports.normalize = function(path) {
+    var isAbsolute = path.charAt(0) === '/',
+        trailingSlash = path.substr(-1) === '/';
+
+    // Normalize the path
+    path = normalizeArray(path.split('/').filter(function(p) {
+      return !!p;
+    }), !isAbsolute).join('/');
+
+    if (!path && !isAbsolute) {
+      path = '.';
+    }
+    if (path && trailingSlash) {
+      path += '/';
+    }
+
+    return (isAbsolute ? '/' : '') + path;
+  };
+
+
+  // posix version
+  exports.join = function() {
+    var paths = Array.prototype.slice.call(arguments, 0);
+    return exports.normalize(paths.filter(function(p, index) {
+      return p && typeof p === 'string';
+    }).join('/'));
+  };
+
+
+  // path.relative(from, to)
+  // posix version
+  exports.relative = function(from, to) {
+    from = exports.resolve(from).substr(1);
+    to = exports.resolve(to).substr(1);
+
+    function trim(arr) {
+      var start = 0;
+      for (; start < arr.length; start++) {
+        if (arr[start] !== '') break;
+      }
+
+      var end = arr.length - 1;
+      for (; end >= 0; end--) {
+        if (arr[end] !== '') break;
+      }
+
+      if (start > end) return [];
+      return arr.slice(start, end - start + 1);
+    }
+
+    var fromParts = trim(from.split('/'));
+    var toParts = trim(to.split('/'));
+
+    var length = Math.min(fromParts.length, toParts.length);
+    var samePartsLength = length;
+    for (var i = 0; i < length; i++) {
+      if (fromParts[i] !== toParts[i]) {
+        samePartsLength = i;
+        break;
+      }
+    }
+
+    var outputParts = [];
+    for (var i = samePartsLength; i < fromParts.length; i++) {
+      outputParts.push('..');
+    }
+
+    outputParts = outputParts.concat(toParts.slice(samePartsLength));
+
+    return outputParts.join('/');
+  };
+
+  exports.sep = '/';
+}
+
+
+exports.dirname = function(path) {
+  var result = splitPath(path),
+      root = result[0],
+      dir = result[1];
+
+  if (!root && !dir) {
+    // No dirname whatsoever
+    return '.';
+  }
+
+  if (dir) {
+    // It has a dirname, strip trailing slash
+    dir = dir.substr(0, dir.length - 1);
+  }
+
+  return root + dir;
+};
+
+
+exports.basename = function(path, ext) {
+  var f = splitPath(path)[2];
+  // TODO: make this comparison case-insensitive on windows?
+  if (ext && f.substr(-1 * ext.length) === ext) {
+    f = f.substr(0, f.length - ext.length);
+  }
+  return f;
+};
+
+
+exports.extname = function(path) {
+  return splitPath(path)[3];
+};
+
+
+if (isWindows) {
+  exports._makeLong = function(path) {
+    path = '' + path;
+    if (!path) {
+      return '';
+    }
+
+    var resolvedPath = exports.resolve(path);
+
+    if (/^[a-zA-Z]\:\\/.test(resolvedPath)) {
+      // path is local filesystem path, which needs to be converted
+      // to long UNC path.
+      return '\\\\?\\' + resolvedPath;
+    } else if (/^\\\\[^?.]/.test(resolvedPath)) {
+      // path is network UNC path, which needs to be converted
+      // to long UNC path.
+      return '\\\\?\\UNC\\' + resolvedPath.substring(2);
+    }
+
+    return path;
+  };
+} else {
+  exports._makeLong = function(path) {
+    return path;
+  };
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -0,0 +1,82 @@
+/* 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"
+};
+
+
+const { Cc, Ci, CC } = require("chrome");
+const { Class } = require("../core/heritage");
+
+const Transcoder = CC("@mozilla.org/intl/scriptableunicodeconverter",
+                      "nsIScriptableUnicodeConverter");
+
+var Buffer = Class({
+  initialize: function initialize(subject, encoding) {
+    subject = subject ? subject.valueOf() : 0;
+    let length = typeof subject === "number" ? subject : 0;
+    this.encoding = encoding || "utf-8";
+    this.valueOf(Array.isArray(subject) ? subject : new Array(length));
+
+    if (typeof subject === "string") this.write(subject);
+  },
+  get length() {
+    return this.valueOf().length;
+  },
+  get: function get(index) {
+    return this.valueOf()[index];
+  },
+  set: function set(index, value) {
+    return this.valueOf()[index] = value;
+  },
+  valueOf: function valueOf(value) {
+    Object.defineProperty(this, "valueOf", {
+      value: Array.prototype.valueOf.bind(value),
+      configurable: false,
+      writable: false,
+      enumerable: false
+    });
+  },
+  toString: function toString(encoding, start, end) {
+    let bytes = this.valueOf().slice(start || 0, end || this.length);
+    let transcoder = Transcoder();
+    transcoder.charset = String(encoding || this.encoding).toUpperCase();
+    return transcoder.convertFromByteArray(bytes, this.length);
+  },
+  toJSON: function toJSON() {
+    return this.toString()
+  },
+  write: function write(string, offset, encoding) {
+    offset = Math.max(offset || 0, 0);
+    let value = this.valueOf();
+    let transcoder = Transcoder();
+    transcoder.charset = String(encoding || this.encoding).toUpperCase();
+    let bytes = transcoder.convertToByteArray(string, {});
+    value.splice.apply(value, [
+      offset,
+      Math.min(value.length - offset, bytes.length, bytes)
+    ].concat(bytes));
+    return bytes;
+  },
+  slice: function slice(start, end) {
+    return new Buffer(this.valueOf().slice(start, end));
+  },
+  copy: function copy(target, offset, start, end) {
+    offset = Math.max(offset || 0, 0);
+    target = target.valueOf();
+    let bytes = this.valueOf();
+    bytes.slice(Math.max(start || 0, 0), end);
+    target.splice.apply(target, [
+      offset,
+      Math.min(target.length - offset, bytes.length),
+    ].concat(bytes));
+  }
+});
+Buffer.isBuffer = function isBuffer(buffer) {
+  return buffer instanceof Buffer
+};
+exports.Buffer = Buffer;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/io/fs.js
@@ -0,0 +1,906 @@
+/* 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"
+};
+
+const { Cc, Ci, CC } = require("chrome");
+
+const { setTimeout } = require("../timers");
+const { Stream, InputStream, OutputStream } = require("./stream");
+const { Buffer } = require("./buffer");
+const { ns } = require("../core/namespace");
+const { Class } = require("../core/heritage");
+
+const nsILocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
+                        "initWithPath");
+const FileOutputStream = CC("@mozilla.org/network/file-output-stream;1",
+                            "nsIFileOutputStream", "init");
+const FileInputStream = CC("@mozilla.org/network/file-input-stream;1",
+                           "nsIFileInputStream", "init");
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream", "setInputStream");
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+                              "nsIBinaryOutputStream", "setOutputStream");
+const StreamPump = CC("@mozilla.org/network/input-stream-pump;1",
+                      "nsIInputStreamPump", "init");
+
+const { createOutputTransport, createInputTransport } =
+  Cc["@mozilla.org/network/stream-transport-service;1"].
+  getService(Ci.nsIStreamTransportService);
+
+
+const { REOPEN_ON_REWIND, DEFER_OPEN } = Ci.nsIFileInputStream;
+const { DIRECTORY_TYPE, NORMAL_FILE_TYPE } = Ci.nsIFile;
+const { NS_SEEK_SET, NS_SEEK_CUR, NS_SEEK_END } = Ci.nsISeekableStream;
+
+const FILE_PERMISSION = parseInt("0666", 8);
+const PR_UINT32_MAX = 0xfffffff;
+// Values taken from:
+// http://mxr.mozilla.org/mozilla-central/source/nsprpub/pr/include/prio.h#615
+const PR_RDONLY =       0x01;
+const PR_WRONLY =       0x02;
+const PR_RDWR =         0x04;
+const PR_CREATE_FILE =  0x08;
+const PR_APPEND =       0x10;
+const PR_TRUNCATE =     0x20;
+const PR_SYNC =         0x40;
+const PR_EXCL =         0x80;
+
+const FLAGS = {
+  "r":                  PR_RDONLY,
+  "r+":                 PR_RDWR,
+  "w":                  PR_CREATE_FILE | PR_TRUNCATE | PR_WRONLY,
+  "w+":                 PR_CREATE_FILE | PR_TRUNCATE | PR_RDWR,
+  "a":                  PR_APPEND | PR_CREATE_FILE | PR_WRONLY,
+  "a+":                 PR_APPEND | PR_CREATE_FILE | PR_RDWR
+};
+
+function accessor() {
+  let map = new WeakMap();
+  return function(fd, value) {
+    if (value === null) map.delete(fd);
+    if (value !== undefined) map.set(fd, value);
+    return map.get(fd);
+  }
+}
+
+let nsIFile = accessor();
+let nsIFileInputStream = accessor();
+let nsIFileOutputStream = accessor();
+let nsIBinaryInputStream = accessor();
+let nsIBinaryOutputStream = accessor();
+
+// Just a contstant object used to signal that all of the file
+// needs to be read.
+const ALL = new String("Read all of the file");
+
+function isWritable(mode) !!(mode & PR_WRONLY || mode & PR_RDWR)
+function isReadable(mode) !!(mode & PR_RDONLY || mode & PR_RDWR)
+
+function isString(value) typeof(value) === "string"
+function isFunction(value) typeof(value) === "function"
+
+function toArray(enumerator) {
+  let value = [];
+  while(enumerator.hasMoreElements())
+    value.push(enumerator.getNext())
+  return value
+}
+
+function getFileName(file) file.QueryInterface(Ci.nsIFile).leafName
+
+
+function remove(path, recursive) {
+  let fd = new nsILocalFile(path)
+  if (fd.exists()) {
+    fd.remove(recursive || false);
+  }
+  else {
+    throw FSError("remove", "ENOENT", 34, path);
+  }
+}
+
+function Mode(mode, fallback) {
+  return isString(mode) ? parseInt(mode) : mode || fallback;
+}
+function Flags(flag) {
+  return !isString(flag) ? flag :
+         FLAGS[flag] || Error("Unknown file open flag: " + flag);
+}
+
+
+function FSError(op, code, errno, path, file, line) {
+  let error = Error(code + ", " + op + " " + path, file, line);
+  error.code = code;
+  error.path = path;
+  error.errno = errno;
+  return error;
+}
+
+const ReadStream = Class({
+  extends: InputStream,
+  initialize: function initialize(path, options) {
+    this.position = -1;
+    this.length = -1;
+    this.flags = "r";
+    this.mode = FILE_PERMISSION;
+    this.bufferSize = 64 * 1024;
+
+    options = options || {};
+
+    if ("flags" in options && options.flags)
+      this.flags = options.flags;
+    if ("bufferSize" in options && options.bufferSize)
+      this.bufferSize = options.bufferSize;
+    if ("length" in options && options.length)
+      this.length = options.length;
+    if ("position" in options && options.position !== undefined)
+      this.position = options.position;
+
+    let { flags, mode, position, length } = this;
+    let fd = isString(path) ? openSync(path, flags, mode) : path;
+    this.fd = fd;
+
+    let input = nsIFileInputStream(fd);
+    // Setting a stream position, unless it"s `-1` which means current position.
+    if (position >= 0)
+      input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+    // We use `nsIStreamTransportService` service to transform blocking
+    // file input stream into a fully asynchronous stream that can be written
+    // without blocking the main thread.
+    let transport = createInputTransport(input, position, length, false);
+    // Open an input stream on a transport. We don"t pass flags to guarantee
+    // non-blocking stream semantics. Also we use defaults for segment size &
+    // count.
+    let asyncInputStream = transport.openInputStream(null, 0, 0);
+    let binaryInputStream = BinaryInputStream(asyncInputStream);
+    nsIBinaryInputStream(fd, binaryInputStream);
+    let pump = StreamPump(asyncInputStream, position, length, 0, 0, false);
+
+    InputStream.prototype.initialize.call(this, {
+      input: binaryInputStream, pump: pump
+    });
+    this.read();
+  },
+  destroy: function() {
+    closeSync(this.fd);
+    InputStream.prototype.destroy.call(this);
+  }
+});
+exports.ReadStream = ReadStream;
+exports.createReadStream = function createReadStream(path, options) {
+  return new ReadStream(path, options);
+};
+
+const WriteStream = Class({
+  extends: OutputStream,
+  initialize: function initialize(path, options) {
+    this.drainable = true;
+    this.flags = "w";
+    this.position = -1;
+    this.mode = FILE_PERMISSION;
+
+    options = options || {};
+
+    if ("flags" in options && options.flags)
+      this.flags = options.flags;
+    if ("mode" in options && options.mode)
+      this.mode = options.mode;
+    if ("position" in options && options.position !== undefined)
+      this.position = options.position;
+
+    let { position, flags, mode } = this;
+    // If pass was passed we create a file descriptor out of it. Otherwise
+    // we just use given file descriptor.
+    let fd = isString(path) ? openSync(path, flags, mode) : path;
+    this.fd = fd;
+
+    let output = nsIFileOutputStream(fd);
+    // Setting a stream position, unless it"s `-1` which means current position.
+    if (position >= 0)
+      output.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+    // We use `nsIStreamTransportService` service to transform blocking
+    // file output stream into a fully asynchronous stream that can be written
+    // without blocking the main thread.
+    let transport = createOutputTransport(output, position, -1, false);
+    // Open an output stream on a transport. We don"t pass flags to guarantee
+    // non-blocking stream semantics. Also we use defaults for segment size &
+    // count.
+    let asyncOutputStream = transport.openOutputStream(null, 0, 0);
+    // Finally we create a non-blocking binary output stream. This will allows
+    // us to write buffers as byte arrays without any further transcoding.
+    let binaryOutputStream = BinaryOutputStream(asyncOutputStream);
+    nsIBinaryOutputStream(fd, binaryOutputStream);
+
+    // Storing output stream so that it can beaccessed later.
+    OutputStream.prototype.initialize.call(this, {
+      output: binaryOutputStream,
+      asyncOutputStream: asyncOutputStream
+    });
+  },
+  destroy: function() {
+    closeSync(this.fd);
+    OutputStream.prototype.destroy.call(this);
+  }
+});
+exports.WriteStream = WriteStream;
+exports.createWriteStream = function createWriteStream(path, options) {
+  return new WriteStream(path, options);
+};
+
+const Stats = Class({
+  initialize: function initialize(path) {
+    let file = new nsILocalFile(path);
+    if (!file.exists()) throw FSError("stat", "ENOENT", 34, path);
+    nsIFile(this, file);
+  },
+  isDirectory: function() nsIFile(this).isDirectory(),
+  isFile: function() nsIFile(this).isFile(),
+  isSymbolicLink: function() nsIFile(this).isSymlink(),
+  get mode() nsIFile(this).permissions,
+  get size() nsIFile(this).fileSize,
+  get mtime() nsIFile(this).lastModifiedTime,
+  isBlockDevice: function() nsIFile(this).isSpecial(),
+  isCharacterDevice: function() nsIFile(this).isSpecial(),
+  isFIFO: function() nsIFile(this).isSpecial(),
+  isSocket: function() nsIFile(this).isSpecial(),
+  // non standard
+  get exists() nsIFile(this).exists(),
+  get hidden() nsIFile(this).isHidden(),
+  get writable() nsIFile(this).isWritable(),
+  get readable() nsIFile(this).isReadable()
+});
+exports.Stats = Stats;
+
+const LStats = Class({
+  extends: Stats,
+  get size() this.isSymbolicLink() ? nsIFile(this).fileSizeOfLink :
+                                     nsIFile(this).fileSize,
+  get mtime() this.isSymbolicLink() ? nsIFile(this).lastModifiedTimeOfLink :
+                                      nsIFile(this).lastModifiedTime,
+  // non standard
+  get permissions() this.isSymbolicLink() ? nsIFile(this).permissionsOfLink :
+                                            nsIFile(this).permissions
+});
+
+const FStat = Class({
+  extends: Stats,
+  initialize: function initialize(fd) {
+    nsIFile(this, nsIFile(fd));
+  }
+});
+
+function noop() {}
+function Async(wrapped) {
+  return function (path, callback) {
+    let args = Array.slice(arguments);
+    callback = args.pop();
+    // If node is not given a callback argument
+    // it just does not calls it.
+    if (typeof(callback) !== "function") {
+      args.push(callback);
+      callback = noop;
+    }
+    setTimeout(function() {
+      try {
+        var result = wrapped.apply(this, args);
+        if (result === undefined) callback(null);
+        else callback(null, result);
+      } catch (error) {
+        callback(error);
+      }
+    }, 0);
+  }
+}
+
+
+/**
+ * Synchronous rename(2)
+ */
+function renameSync(oldPath, newPath) {
+  let source = new nsILocalFile(oldPath);
+  let target = new nsILocalFile(newPath);
+  if (!source.exists()) throw FSError("rename", "ENOENT", 34, oldPath);
+  return source.moveTo(target.parent, target.leafName);
+};
+exports.renameSync = renameSync;
+
+/**
+ * Asynchronous rename(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let rename = Async(renameSync);
+exports.rename = rename;
+
+/**
+ * Test whether or not the given path exists by checking with the file system.
+ */
+function existsSync(path) {
+  return new nsILocalFile(path).exists();
+}
+exports.existsSync = existsSync;
+
+let exists = Async(existsSync);
+exports.exists = exists;
+
+/**
+ * Synchronous ftruncate(2).
+ */
+function truncateSync(path, length) {
+  let fd = openSync(path, "w");
+  ftruncateSync(fd, length);
+  closeSync(fd);
+}
+exports.truncateSync = truncateSync;
+
+/**
+ * Asynchronous ftruncate(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+function truncate(path, length, callback) {
+  open(path, "w", function(error, fd) {
+    if (error) return callback(error);
+    ftruncate(fd, length, function(error) {
+      if (error) {
+        closeSync(fd);
+        callback(error);
+      }
+      else {
+        close(fd, callback);
+      }
+    });
+  });
+}
+exports.truncate = truncate;
+
+function ftruncate(fd, length, callback) {
+  write(fd, new Buffer(length), 0, length, 0, function(error) {
+    callback(error);
+  });
+}
+exports.ftruncate = ftruncate;
+
+function ftruncateSync(fd, length) {
+  writeSync(fd, new Buffer(length), 0, length, 0);
+}
+exports.ftruncateSync = ftruncateSync;
+
+function chownSync(path, uid, gid) {
+  throw Error("Not implemented yet!!");
+}
+exports.chownSync = chownSync;
+
+let chown = Async(chownSync);
+exports.chown = chown;
+
+function lchownSync(path, uid, gid) {
+  throw Error("Not implemented yet!!");
+}
+exports.lchownSync = chownSync;
+
+let lchown = Async(lchown);
+exports.lchown = lchown;
+
+/**
+ * Synchronous chmod(2).
+ */
+function chmodSync (path, mode) {
+  throw Error("Not implemented yet!!");
+};
+exports.chmodSync = chmodSync;
+/**
+ * Asynchronous chmod(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let chmod = Async(chmodSync);
+exports.chmod = chmod;
+
+/**
+ * Synchronous chmod(2).
+ */
+function fchmodSync(fd, mode) {
+  throw Error("Not implemented yet!!");
+};
+exports.fchmodSync = fchmodSync;
+/**
+ * Asynchronous chmod(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let fchmod = Async(fchmodSync);
+exports.chmod = fchmod;
+
+
+/**
+ * Synchronous stat(2). Returns an instance of `fs.Stats`
+ */
+function statSync(path) {
+  return new Stats(path);
+};
+exports.statSync = statSync;
+
+/**
+ * Asynchronous stat(2). The callback gets two arguments (err, stats) where
+ * stats is a `fs.Stats` object. It looks like this:
+ */
+let stat = Async(statSync);
+exports.stat = stat;
+
+/**
+ * Synchronous lstat(2). Returns an instance of `fs.Stats`.
+ */
+function lstatSync(path) {
+  return new LStats(path);
+};
+exports.lstatSync = lstatSync;
+
+/**
+ * Asynchronous lstat(2). The callback gets two arguments (err, stats) where
+ * stats is a fs.Stats object. lstat() is identical to stat(), except that if
+ * path is a symbolic link, then the link itself is stat-ed, not the file that
+ * it refers to.
+ */
+let lstat = Async(lstatSync);
+exports.lstat = lstat;
+
+/**
+ * Synchronous fstat(2). Returns an instance of `fs.Stats`.
+ */
+function fstatSync(fd) {
+  return new FStat(fd);
+};
+exports.fstatSync = fstatSync;
+
+/**
+ * Asynchronous fstat(2). The callback gets two arguments (err, stats) where
+ * stats is a fs.Stats object.
+ */
+let fstat = Async(fstatSync);
+exports.fstat = fstat;
+
+/**
+ * Synchronous link(2).
+ */
+function linkSync(source, target) {
+  throw Error("Not implemented yet!!");
+};
+exports.linkSync = linkSync;
+
+/**
+ * Asynchronous link(2). No arguments other than a possible exception are given
+ * to the completion callback.
+ */
+let link = Async(linkSync);
+exports.link = link;
+
+/**
+ * Synchronous symlink(2).
+ */
+function symlinkSync(source, target) {
+  throw Error("Not implemented yet!!");
+};
+exports.symlinkSync = symlinkSync;
+
+/**
+ * Asynchronous symlink(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let symlink = Async(symlinkSync);
+exports.symlink = symlink;
+
+/**
+ * Synchronous readlink(2). Returns the resolved path.
+ */
+function readlinkSync(path) {
+  return new nsILocalFile(path).target;
+};
+exports.readlinkSync = readlinkSync;
+
+/**
+ * Asynchronous readlink(2). The callback gets two arguments
+ * `(error, resolvedPath)`.
+ */
+let readlink = Async(readlinkSync);
+exports.readlink = readlink;
+
+/**
+ * Synchronous realpath(2). Returns the resolved path.
+ */
+function realpathSync(path) {
+  return new nsILocalFile(path).path;
+};
+exports.realpathSync = realpathSync;
+
+/**
+ * Asynchronous realpath(2). The callback gets two arguments
+ * `(err, resolvedPath)`.
+ */
+let realpath = Async(realpathSync);
+exports.realpath = realpath;
+
+/**
+ * Synchronous unlink(2).
+ */
+let unlinkSync = remove;
+exports.unlinkSync = unlinkSync;
+
+/**
+ * Asynchronous unlink(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let unlink = Async(remove);
+exports.unlink = unlink;
+
+/**
+ * Synchronous rmdir(2).
+ */
+let rmdirSync = remove;
+exports.rmdirSync = rmdirSync;
+
+/**
+ * Asynchronous rmdir(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let rmdir = Async(rmdirSync);
+exports.rmdir = rmdir;
+
+/**
+ * Synchronous mkdir(2).
+ */
+function mkdirSync(path, mode) {
+  try {
+    return nsILocalFile(path).create(DIRECTORY_TYPE, Mode(mode));
+  } catch (error) {
+    // Adjust exception thorw to match ones thrown by node.
+    if (error.name === "NS_ERROR_FILE_ALREADY_EXISTS") {
+      let { fileName, lineNumber } = error;
+      error = FSError("mkdir", "EEXIST", 47, path, fileName, lineNumber);
+    }
+    throw error;
+  }
+};
+exports.mkdirSync = mkdirSync;
+
+/**
+ * Asynchronous mkdir(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let mkdir = Async(mkdirSync);
+exports.mkdir = mkdir;
+
+/**
+ * Synchronous readdir(3). Returns an array of filenames excluding `"."` and
+ * `".."`.
+ */
+function readdirSync(path) {
+  try {
+    return toArray(new nsILocalFile(path).directoryEntries).map(getFileName);
+  }
+  catch (error) {
+    // Adjust exception thorw to match ones thrown by node.
+    if (error.name === "NS_ERROR_FILE_TARGET_DOES_NOT_EXIST" ||
+        error.name === "NS_ERROR_FILE_NOT_FOUND")
+    {
+      let { fileName, lineNumber } = error;
+      error = FSError("readdir", "ENOENT", 34, path, fileName, lineNumber);
+    }
+    throw error;
+  }
+};
+exports.readdirSync = readdirSync;
+
+/**
+ * Asynchronous readdir(3). Reads the contents of a directory. The callback
+ * gets two arguments `(error, files)` where `files` is an array of the names
+ * of the files in the directory excluding `"."` and `".."`.
+ */
+let readdir = Async(readdirSync);
+exports.readdir = readdir;
+
+/**
+ * Synchronous close(2).
+ */
+ function closeSync(fd) {
+   let input = nsIFileInputStream(fd);
+   let output = nsIFileOutputStream(fd);
+
+   // Closing input stream and removing reference.
+   if (input) input.close();
+   // Closing output stream and removing reference.
+   if (output) output.close();
+
+   nsIFile(fd, null);
+   nsIFileInputStream(fd, null);
+   nsIFileOutputStream(fd, null);
+   nsIBinaryInputStream(fd, null);
+   nsIBinaryOutputStream(fd, null);
+};
+exports.closeSync = closeSync;
+/**
+ * Asynchronous close(2). No arguments other than a possible exception are
+ * given to the completion callback.
+ */
+let close = Async(closeSync);
+exports.close = close;
+
+/**
+ * Synchronous open(2).
+ */
+function openSync(path, flags, mode) {
+  let [ fd, flags, mode, file ] =
+      [ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ];
+
+  // If trying to open file for just read that does not exists
+  // need to throw exception as node does.
+  if (!file.exists() && !isWritable(flags))
+    throw FSError("open", "ENOENT", 34, path);
+
+  // If we want to open file in read mode we initialize input stream.
+  if (isReadable(flags)) {
+    let input = FileInputStream(file, flags, mode, DEFER_OPEN);
+    nsIFileInputStream(fd, input);
+  }
+
+  // If we want to open file in write mode we initialize output stream for it.
+  if (isWritable(flags)) {
+    let output = FileOutputStream(file, flags, mode, DEFER_OPEN);
+    nsIFileOutputStream(fd, output);
+  }
+
+  return fd;
+}
+exports.openSync = openSync;
+/**
+ * Asynchronous file open. See open(2). Flags can be
+ * `"r", "r+", "w", "w+", "a"`, or `"a+"`. mode defaults to `0666`.
+ * The callback gets two arguments `(error, fd).
+ */
+let open = Async(openSync);
+exports.open = open;
+
+/**
+ * Synchronous version of buffer-based fs.write(). Returns the number of bytes
+ * written.
+ */
+function writeSync(fd, buffer, offset, length, position) {
+  if (length + offset > buffer.length) {
+    throw Error("Length is extends beyond buffer");
+  }
+  else if (length + offset !== buffer.length) {
+    buffer = buffer.slice(offset, offset + length);
+  }
+  let writeStream = new WriteStream(fd, { position: position,
+                                          length: length });
+  let output = nsIBinaryOutputStream(fd);
+  // We write content as a byte array as this will avoid any transcoding
+  // if content was a buffer.
+  output.writeByteArray(buffer.valueOf(), buffer.length);
+  output.flush();
+};
+exports.writeSync = writeSync;
+
+/**
+ * Write buffer to the file specified by fd.
+ *
+ * `offset` and `length` determine the part of the buffer to be written.
+ *
+ * `position` refers to the offset from the beginning of the file where this
+ * data should be written. If `position` is `null`, the data will be written
+ * at the current position. See pwrite(2).
+ *
+ * The callback will be given three arguments `(error, written, buffer)` where
+ * written specifies how many bytes were written into buffer.
+ *
+ * Note that it is unsafe to use `fs.write` multiple times on the same file
+ * without waiting for the callback.
+ */
+function write(fd, buffer, offset, length, position, callback) {
+  if (!Buffer.isBuffer(buffer)) {
+    // (fd, data, position, encoding, callback)
+    let encoding = null;
+    [ position, encoding, callback ] = Array.slice(arguments, 1);
+    buffer = new Buffer(String(buffer), encoding);
+    offset = 0;
+  } else if (length + offset > buffer.length) {
+    throw Error("Length is extends beyond buffer");
+  } else if (length + offset !== buffer.length) {
+    buffer = buffer.slice(offset, offset + length);
+  }
+
+  let writeStream = new WriteStream(fd, { position: position,
+                                          length: length });
+  writeStream.on("error", callback);
+  writeStream.write(buffer, function onEnd() {
+    writeStream.destroy();
+    if (callback)
+      callback(null, buffer.length, buffer);
+  });
+};
+exports.write = write;
+
+/**
+ * Synchronous version of string-based fs.read. Returns the number of
+ * bytes read.
+ */
+function readSync(fd, buffer, offset, length, position) {
+  let input = nsIFileInputStream(fd);
+  // Setting a stream position, unless it"s `-1` which means current position.
+  if (position >= 0)
+    input.QueryInterface(Ci.nsISeekableStream).seek(NS_SEEK_SET, position);
+  // We use `nsIStreamTransportService` service to transform blocking
+  // file input stream into a fully asynchronous stream that can be written
+  // without blocking the main thread.
+  let binaryInputStream = BinaryInputStream(input);
+  let count = length === ALL ? binaryInputStream.available() : length;
+  var bytes = binaryInputStream.readByteArray(count);
+  buffer.copy.call(bytes, buffer, offset);
+
+  return bytes;
+};
+exports.readSync = readSync;
+
+/**
+ * Read data from the file specified by `fd`.
+ *
+ * `buffer` is the buffer that the data will be written to.
+ * `offset` is offset within the buffer where writing will start.
+ *
+ * `length` is an integer specifying the number of bytes to read.
+ *
+ * `position` is an integer specifying where to begin reading from in the file.
+ * If `position` is `null`, data will be read from the current file position.
+ *
+ * The callback is given the three arguments, `(error, bytesRead, buffer)`.
+ */
+function read(fd, buffer, offset, length, position, callback) {
+  let bytesRead = 0;
+  let readStream = new ReadStream(fd, { position: position, length: length });
+  readStream.on("data", function onData(chunck) {
+      chunck.copy(buffer, offset + bytesRead);
+      bytesRead += chunck.length;
+  });
+  readStream.on("end", function onEnd() {
+    callback(null, bytesRead, buffer);
+    readStream.destroy();
+  });
+};
+exports.read = read;
+
+/**
+ * Asynchronously reads the entire contents of a file.
+ * The callback is passed two arguments `(error, data)`, where data is the
+ * contents of the file.
+ */
+function readFile(path, encoding, callback) {
+  if (isFunction(encoding)) {
+    callback = encoding
+    encoding = null
+  }
+
+  let buffer = new Buffer();
+  try {
+    let readStream = new ReadStream(path);
+    readStream.on("data", function(chunck) {
+      chunck.copy(buffer, buffer.length);
+    });
+    readStream.on("error", function onError(error) {
+      callback(error);
+      readStream.destroy();
+    });
+    readStream.on("end", function onEnd() {
+      callback(null, buffer);
+      readStream.destroy();
+    });
+  } catch (error) {
+    setTimeout(callback, 0, error);
+  }
+};
+exports.readFile = readFile;
+
+/**
+ * Synchronous version of `fs.readFile`. Returns the contents of the path.
+ * If encoding is specified then this function returns a string.
+ * Otherwise it returns a buffer.
+ */
+function readFileSync(path, encoding) {
+  let buffer = new Buffer();
+  let fd = openSync(path, "r");
+  try {
+    readSync(fd, buffer, 0, ALL, 0);
+  }
+  finally {
+    closeSync(fd);
+  }
+  return buffer;
+};
+exports.readFileSync = readFileSync;
+
+/**
+ * Asynchronously writes data to a file, replacing the file if it already
+ * exists. data can be a string or a buffer.
+ */
+function writeFile(path, content, encoding, callback) {
+  try {
+    if (isFunction(encoding)) {
+      callback = encoding
+      encoding = null
+    }
+    if (isString(content))
+      content = new Buffer(content, encoding);
+
+    let writeStream = new WriteStream(path);
+    writeStream.on("error", function onError(error) {
+      callback(error);
+      writeStream.destroy();
+    });
+    writeStream.write(content, function onDrain() {
+      writeStream.destroy();
+      callback(null);
+    });
+  } catch (error) {
+    callback(error);
+  }
+};
+exports.writeFile = writeFile;
+
+/**
+ * The synchronous version of `fs.writeFile`.
+ */
+function writeFileSync(filename, data, encoding) {
+  throw Error("Not implemented");
+};
+exports.writeFileSync = writeFileSync;
+
+
+function utimesSync(path, atime, mtime) {
+  throw Error("Not implemented");
+}
+exports.utimesSync = utimesSync;
+
+let utimes = Async(utimesSync);
+exports.utimes = utimes;
+
+function futimesSync(fd, atime, mtime, callback) {
+  throw Error("Not implemented");
+}
+exports.futimesSync = futimesSync;
+
+let futimes = Async(futimesSync);
+exports.futimes = futimes;
+
+function fsyncSync(fd, atime, mtime, callback) {
+  throw Error("Not implemented");
+}
+exports.fsyncSync = fsyncSync;
+
+let fsync = Async(fsyncSync);
+exports.fsync = fsync;
+
+
+/**
+ * Watch for changes on filename. The callback listener will be called each
+ * time the file is accessed.
+ *
+ * The second argument is optional. The options if provided should be an object
+ * containing two members a boolean, persistent, and interval, a polling value
+ * in milliseconds. The default is { persistent: true, interval: 0 }.
+ */
+function watchFile(path, options, listener) {
+  throw Error("Not implemented");
+};
+exports.watchFile = watchFile;
+
+
+function unwatchFile(path, listener) {
+  throw Error("Not implemented");
+}
+exports.unwatchFile = unwatchFile;
+
+function watch(path, options, listener) {
+  throw Error("Not implemented");
+}
+exports.watch = watch;
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/io/stream.js
@@ -0,0 +1,324 @@
+/* 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"
+};
+
+const { EventTarget } = require("../event/target");
+const { emit } = require("../event/core");
+const { Buffer } = require("./buffer");
+const { Class } = require("../core/heritage");
+const { setTimeout } = require("../timers");
+const { ns } = require("../core/namespace");
+
+function isFunction(value) typeof value === "function"
+
+function accessor() {
+  let map = new WeakMap();
+  return function(fd, value) {
+    if (value === null) map.delete(fd);
+    if (value !== undefined) map.set(fd, value);
+    return map.get(fd);
+  }
+}
+
+let nsIInputStreamPump = accessor();
+let nsIAsyncOutputStream = accessor();
+let nsIInputStream = accessor();
+let nsIOutputStream = accessor();
+
+
+/**
+ * Utility function / hack that we use to figure if output stream is closed.
+ */
+function isClosed(stream) {
+  // We assume that stream is not closed.
+  let isClosed = false;
+  stream.asyncWait({
+    // If `onClose` callback is called before outer function returns
+    // (synchronously) `isClosed` will be set to `true` identifying
+    // that stream is closed.
+    onOutputStreamReady: function onClose() isClosed = true
+
+  // `WAIT_CLOSURE_ONLY` flag overrides the default behavior, causing the
+  // `onOutputStreamReady` notification to be suppressed until the stream
+  // becomes closed.
+  }, stream.WAIT_CLOSURE_ONLY, 0, null);
+  return isClosed;
+}
+/**
+ * Utility function takes output `stream`, `onDrain`, `onClose` callbacks and
+ * calls one of this callbacks depending on stream state. It is guaranteed
+ * that only one called will be called and it will be called asynchronously.
+ * @param {nsIAsyncOutputStream} stream
+ * @param {Function} onDrain
+ *    callback that is called when stream becomes writable.
+ * @param {Function} onClose
+ *    callback that is called when stream becomes closed.
+ */
+function onStateChange(stream, target) {
+  let isAsync = false;
+  stream.asyncWait({
+    onOutputStreamReady: function onOutputStreamReady() {
+      // If `isAsync` was not yet set to `true` by the last line we know that
+      // `onOutputStreamReady` was called synchronously. In such case we just
+      // defer execution until next turn of event loop.
+      if (!isAsync)
+        return setTimeout(onOutputStreamReady, 0);
+
+      // As it"s not clear what is a state of the stream (TODO: Is there really
+      // no better way ?) we employ hack (see details in `isClosed`) to verify
+      // if stream is closed.
+      emit(target, isClosed(stream) ? "close" : "drain");
+    }
+  }, 0, 0, null);
+  isAsync = true;
+}
+
+function pump(stream) {
+  let input = nsIInputStream(stream);
+  nsIInputStreamPump(stream).asyncRead({
+    onStartRequest: function onStartRequest() {
+      emit(stream, "start");
+    },
+    onDataAvailable: function onDataAvailable(req, c, is, offset, count) {
+      try {
+        let bytes = input.readByteArray(count);
+        emit(stream, "data", new Buffer(bytes, stream.encoding));
+      } catch (error) {
+        emit(stream, "error", error);
+        stream.readable = false;
+      }
+    },
+    onStopRequest: function onStopRequest() {
+      stream.readable = false;
+      emit(stream, "end");
+    }
+  }, null);
+}
+
+const Stream = Class({
+  extends: EventTarget,
+  initialize: function() {
+    this.readable = false;
+    this.writable = false;
+    this.encoding = null;
+  },
+  setEncoding: function setEncoding(encoding) {
+    this.encoding = String(encoding).toUpperCase();
+  },
+  pipe: function pipe(target, options) {
+    let source = this;
+    function onData(chunk) {
+      if (target.writable) {
+        if (false === target.write(chunk))
+          source.pause();
+      }
+    }
+    function onDrain() {
+      if (source.readable) source.resume();
+    }
+    function onEnd() {
+      target.end();
+    }
+    function onPause() {
+      source.pause();
+    }
+    function onResume() {
+      if (source.readable)
+        source.resume();
+    }
+
+    function cleanup() {
+      source.removeListener("data", onData);
+      target.removeListener("drain", onDrain);
+      source.removeListener("end", onEnd);
+
+      target.removeListener("pause", onPause);
+      target.removeListener("resume", onResume);
+
+      source.removeListener("end", cleanup);
+      source.removeListener("close", cleanup);
+
+      target.removeListener("end", cleanup);
+      target.removeListener("close", cleanup);
+    }
+
+    if (!options || options.end !== false)
+      target.on("end", onEnd);
+
+    source.on("data", onData);
+    target.on("drain", onDrain);
+    target.on("resume", onResume);
+    target.on("pause", onPause);
+
+    source.on("end", cleanup);
+    source.on("close", cleanup);
+
+    target.on("end", cleanup);
+    target.on("close", cleanup);
+
+    emit(target, "pipe", source);
+  },
+  pause: function pause() {
+    emit(this, "pause");
+  },
+  resume: function resume() {
+    emit(this, "resume");
+  },
+  destroySoon: function destroySoon() {
+    this.destroy();
+  }
+});
+exports.Stream = Stream;
+
+const InputStream = Class({
+  extends: Stream,
+  initialize: function initialize(options) {
+    let { input, pump } = options;
+
+    this.readable = true;
+    this.paused = false;
+    nsIInputStream(this, input);
+    nsIInputStreamPump(this, pump);
+  },
+  get status() nsIInputStreamPump(this).status,
+  read: function() pump(this),
+  pause: function pause() {
+    this.paused = true;
+    nsIInputStreamPump(this).suspend();
+    emit(this, "paused");
+  },
+  resume: function resume() {
+    this.paused = false;
+    nsIInputStreamPump(this).resume();
+    emit(this, "resume");
+  },
+  destroy: function destroy() {
+    this.readable = false;
+    try {
+      emit(this, "close", null);
+      nsIInputStreamPump(this).cancel(null);
+      nsIInputStreamPump(this, null);
+
+      nsIInputStream(this).close();
+      nsIInputStream(this, null);
+    } catch (error) {
+      emit(this, "error", error);
+    }
+  }
+});
+exports.InputStream = InputStream;
+
+const OutputStream = Class({
+  extends: Stream,
+  initialize: function initialize(options) {
+    let { output, asyncOutputStream } = options;
+
+    this.writable = true;
+    nsIOutputStream(this, output);
+    nsIAsyncOutputStream(this, asyncOutputStream);
+  },
+  write: function write(content, encoding, callback) {
+    let output = nsIOutputStream(this);
+    let asyncOutputStream = nsIAsyncOutputStream(this);
+
+    if (isFunction(encoding)) {
+      callback = encoding;
+      encoding = callback;
+    }
+
+    // Flag indicating whether or not content has been flushed to the kernel
+    // buffer.
+    let isWritten = false;
+    // If stream is not writable we throw an error.
+    if (!this.writable)
+      throw Error("stream not writable");
+
+    try {
+      // If content is not a buffer then we create one out of it.
+      if (!Buffer.isBuffer(content))
+        content = new Buffer(content, encoding);
+
+      // We write content as a byte array as this will avoid any transcoding
+      // if content was a buffer.
+      output.writeByteArray(content.valueOf(), content.length);
+      output.flush();
+
+      if (callback) this.once("drain", callback);
+      onStateChange(asyncOutputStream, this);
+      return true;
+    } catch (error) {
+      // If errors occur we emit appropriate event.
+      emit(this, "error", error);
+    }
+  },
+  flush: function flush() {
+    nsIOutputStream(this).flush();
+  },
+  end: function end(content, encoding, callback) {
+    if (isFunction(content)) {
+      callback = content
+      content = callback
+    }
+    if (isFunction(encoding)) {
+      callback = encoding
+      encoding = callback
+    }
+
+    // Setting a listener to "close" event if passed.
+    if (isFunction(callback))
+      this.once("close", callback);
+
+    // If content is passed then we defer closing until we finish with writing.
+    if (content)
+      this.write(content, encoding, end.bind(this));
+    // If we don"t write anything, then we close an outputStream.
+    else
+      nsIOutputStream(this).close();
+  },
+  destroy: function destroy(callback) {
+    try {
+      this.end(callback);
+      nsIOutputStream(this, null);
+      nsIAsyncOutputStream(this, null);
+    } catch (error) {
+      emit(this, "error", error);
+    }
+  }
+});
+exports.OutputStream = OutputStream;
+
+const DuplexStream = Class({
+  extends: Stream,
+  initialize: function initialize(options) {
+    let { input, output, pump } = options;
+
+    this.writable = true;
+    this.readable = true;
+    this.encoding = null;
+
+    nsIInputStream(this, input);
+    nsIOutputStream(this, output);
+    nsIInputStreamPump(this, pump);
+  },
+  read: InputStream.prototype.read,
+  pause: InputStream.prototype.pause,
+  resume: InputStream.prototype.resume,
+
+  write: OutputStream.prototype.write,
+  flush: OutputStream.prototype.flush,
+  end: OutputStream.prototype.end,
+
+  destroy: function destroy(error) {
+    if (error)
+      emit(this, "error", error);
+    InputStream.prototype.destroy.call(this);
+    OutputStream.prototype.destroy.call(this);
+  }
+});
+exports.DuplexStream = DuplexStream;
--- a/addon-sdk/source/lib/sdk/page-mod.js
+++ b/addon-sdk/source/lib/sdk/page-mod.js
@@ -7,68 +7,49 @@
 
 module.metadata = {
   "stability": "stable"
 };
 
 const observers = require('./deprecated/observer-service');
 const { Loader, validationAttributes } = require('./content/loader');
 const { Worker } = require('./content/worker');
+const { Registry } = require('./util/registry');
 const { EventEmitter } = require('./deprecated/events');
-const { List } = require('./deprecated/list');
-const { Registry } = require('./util/registry');
-const { MatchPattern } = require('./page-mod/match-pattern');
+const { on, emit } = require('./event/core');
 const { validateOptions : validate } = require('./deprecated/api-utils');
 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 { Style } = require("./stylesheet/style");
 const { attach, detach } = require("./content/mod");
+const { has, hasAny } = require("./util/array");
+const { Rules } = require("./util/rules");
 
 // Valid values for `attachTo` option
 const VALID_ATTACHTO_OPTIONS = ['existing', 'top', 'frame'];
 
+const mods = new WeakMap();
+
 // contentStyle* / contentScript* are sharing the same validation constraints,
 // so they can be mostly reused, except for the messages.
 const validStyleOptions = {
   contentStyle: merge(Object.create(validationAttributes.contentScript), {
     msg: 'The `contentStyle` option must be a string or an array of strings.'
   }),
   contentStyleFile: merge(Object.create(validationAttributes.contentScriptFile), {
     msg: 'The `contentStyleFile` option must be a local URL or an array of URLs'
   })
 };
 
-// rules registry
-const RULES = {};
-
-const Rules = EventEmitter.resolve({ toString: null }).compose(List, {
-  add: function() Array.slice(arguments).forEach(function onAdd(rule) {
-    if (this._has(rule))
-      return;
-    // registering rule to the rules registry
-    if (!(rule in RULES))
-      RULES[rule] = new MatchPattern(rule);
-    this._add(rule);
-    this._emit('add', rule);
-  }.bind(this)),
-  remove: function() Array.slice(arguments).forEach(function onRemove(rule) {
-    if (!this._has(rule))
-      return;
-    this._remove(rule);
-    this._emit('remove', rule);
-  }.bind(this)),
-});
-
 /**
  * PageMod constructor (exported below).
  * @constructor
  */
 const PageMod = Loader.compose(EventEmitter, {
   on: EventEmitter.required,
   _listeners: EventEmitter.required,
   attachTo: [],
@@ -116,61 +97,55 @@ const PageMod = Loader.compose(EventEmit
                         ' `top` or `frame` value');
     }
     else {
       this.attachTo = ["top", "frame"];
     }
 
     let include = options.include;
     let rules = this.include = Rules();
-    rules.on('add', this._onRuleAdd = this._onRuleAdd.bind(this));
-    rules.on('remove', this._onRuleRemove = this._onRuleRemove.bind(this));
+    
+    if (!include)
+      throw new Error('The `include` option must always contain atleast one rule');
 
-    if (Array.isArray(include))
-      rules.add.apply(null, include);
-    else
-      rules.add(include);
+    rules.add.apply(rules, [].concat(include));
 
     if (contentStyle || contentStyleFile) {
       this._style = Style({
         uri: contentStyleFile,
         source: contentStyle
       });
     }
 
     this.on('error', this._onUncaughtError = this._onUncaughtError.bind(this));
     pageModManager.add(this._public);
+    mods.set(this._public, this);
 
     // `_applyOnExistingDocuments` has to be called after `pageModManager.add()`
     // otherwise its calls to `_onContent` method won't do anything.
     if ('attachTo' in options && has(options.attachTo, 'existing'))
       this._applyOnExistingDocuments();
   },
 
   destroy: function destroy() {
-
     if (this._style)
       detach(this._style);
 
-    for each (let rule in this.include)
-      this.include.remove(rule);
+    for (let i in this.include)
+      this.include.remove(this.include[i]);
+
+    mods.delete(this._public);
     pageModManager.remove(this._public);
   },
 
   _applyOnExistingDocuments: function _applyOnExistingDocuments() {
     let mod = this;
     // Returns true if the tab match one rule
-    function isMatchingURI(uri) {
-      // Use Array.some as `include` isn't a native array
-      return Array.some(mod.include, function (rule) {
-        return RULES[rule].test(uri);
-      });
-    }
     let tabs = getAllTabs().filter(function (tab) {
-      return isMatchingURI(getTabURI(tab));
+      return mod.include.matchesAny(getTabURI(tab));
     });
 
     tabs.forEach(function (tab) {
       // Fake a newly created document
       let window = getTabContentWindow(tab);
       if (has(mod.attachTo, "top"))
         mod._onContent(window);
       if (has(mod.attachTo, "frame"))
@@ -225,22 +200,16 @@ const PageMod = Loader.compose(EventEmit
       onError: this._onUncaughtError
     });
     this._emit('attach', worker);
     let self = this;
     worker.once('detach', function detach() {
       worker.destroy();
     });
   },
-  _onRuleAdd: function _onRuleAdd(url) {
-    pageModManager.on(url, this._onContent);
-  },
-  _onRuleRemove: function _onRuleRemove(url) {
-    pageModManager.off(url, this._onContent);
-  },
   _onUncaughtError: function _onUncaughtError(e) {
     if (this._listeners('error').length == 1)
       console.exception(e);
   }
 });
 exports.PageMod = function(options) PageMod(options)
 exports.PageMod.prototype = PageMod.prototype;
 
@@ -253,19 +222,16 @@ const PageModManager = Registry.resolve(
     observers.add(
       'document-element-inserted',
       this._onContentWindow = this._onContentWindow.bind(this)
     );
   },
   _destructor: function _destructor() {
     observers.remove('document-element-inserted', this._onContentWindow);
     this._removeAllListeners();
-    for (let rule in RULES) {
-      delete RULES[rule];
-    }
 
     // We need to do some cleaning er PageMods, like unregistering any
     // `contentStyle*`
     this._registry.forEach(function(pageMod) {
       pageMod.destroy();
     });
 
     this._registryDestructor();
@@ -280,24 +246,23 @@ const PageModManager = Registry.resolve(
       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);
+    this._registry.forEach(function(mod) {
+      if (mod.include.matchesAny(document.URL))
+        mods.get(mod)._onContent(window);
+    });
   },
   off: function off(topic, listener) {
     this.removeListener(topic, listener);
-    if (!this._listeners(topic).length)
-      delete RULES[topic];
   }
 });
 const pageModManager = PageModManager();
 
 // Returns all tabs on all currently opened windows
 function getAllTabs() {
   let tabs = [];
   // Iterate over all chrome windows
--- a/addon-sdk/source/lib/sdk/page-mod/match-pattern.js
+++ b/addon-sdk/source/lib/sdk/page-mod/match-pattern.js
@@ -1,115 +1,5 @@
-/* -*- 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/. */
-
-"use strict";
-
-module.metadata = {
-  "stability": "unstable"
-};
-
-const { URL } = require("../url");
-
-exports.MatchPattern = MatchPattern;
-
-function MatchPattern(pattern) {
-  if (typeof pattern.test == "function") {
-
-    // For compatibility with -moz-document rules, we require the RegExp's
-    // global, ignoreCase, and multiline flags to be set to false.
-    if (pattern.global) {
-      throw new Error("A RegExp match pattern cannot be set to `global` " +
-                      "(i.e. //g).");
-    }
-    if (pattern.ignoreCase) {
-      throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
-                      "(i.e. //i).");
-    }
-    if (pattern.multiline) {
-      throw new Error("A RegExp match pattern cannot be set to `multiline` " +
-                      "(i.e. //m).");
-    }
-
-    this.regexp = pattern;
-  }
-  else {
-    let firstWildcardPosition = pattern.indexOf("*");
-    let lastWildcardPosition = pattern.lastIndexOf("*");
-    if (firstWildcardPosition != lastWildcardPosition)
-      throw new Error("There can be at most one '*' character in a wildcard.");
-
-    if (firstWildcardPosition == 0) {
-      if (pattern.length == 1)
-        this.anyWebPage = true;
-      else if (pattern[1] != ".")
-        throw new Error("Expected a *.<domain name> string, got: " + pattern);
-      else
-        this.domain = pattern.substr(2);
-    }
-    else {
-      if (pattern.indexOf(":") == -1) {
-        throw new Error("When not using *.example.org wildcard, the string " +
-                        "supplied is expected to be either an exact URL to " +
-                        "match or a URL prefix. The provided string ('" +
-                        pattern + "') is unlikely to match any pages.");
-      }
+let { deprecateUsage } = require("../util/deprecate");
 
-      if (firstWildcardPosition == -1)
-        this.exactURL = pattern;
-      else if (firstWildcardPosition == pattern.length - 1)
-        this.urlPrefix = pattern.substr(0, pattern.length - 1);
-      else {
-        throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
-                        "in an unexpected position. It is expected to be the " +
-                        "first or the last character in the wildcard.");
-      }
-    }
-  }
-}
-
-MatchPattern.prototype = {
-
-  test: function MatchPattern_test(urlStr) {
-    try {
-      var url = URL(urlStr);
-    }
-    catch (err) {
-      return false;
-    }
+deprecateUsage("Module 'sdk/page-mod/match-pattern' is deprecated use 'sdk/util/match-pattern' instead");
 
-    // Test the URL against a RegExp pattern.  For compatibility with
-    // -moz-document rules, we require the RegExp to match the entire URL,
-    // so we not only test for a match, we also make sure the matched string
-    // is the entire URL string.
-    //
-    // Assuming most URLs don't match most match patterns, we call `test` for
-    // speed when determining whether or not the URL matches, then call `exec`
-    // for the small subset that match to make sure the entire URL matches.
-    //
-    if (this.regexp && this.regexp.test(urlStr) &&
-        this.regexp.exec(urlStr)[0] == urlStr)
-      return true;
-
-    if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
-      return true;
-    if (this.exactURL && this.exactURL == urlStr)
-      return true;
-
-    // Tests the urlStr against domain and check if
-    // wildcard submitted (*.domain.com), it only allows
-    // subdomains (sub.domain.com) or from the root (http://domain.com)
-    // and reject non-matching domains (otherdomain.com)
-    // bug 856913
-    if (this.domain && url.host &&
-         (url.host === this.domain ||
-          url.host.slice(-this.domain.length - 1) === "." + this.domain))
-      return true;
-    if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
-      return true;
-
-    return false;
-  }
-
-};
+module.exports = require("../page-mod/match-pattern");
--- a/addon-sdk/source/lib/sdk/page-worker.js
+++ b/addon-sdk/source/lib/sdk/page-worker.js
@@ -4,59 +4,166 @@
  * 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 { Symbiont } = require("./content/symbiont");
-const { Trait } = require("./deprecated/traits");
+const { Class } = require('./core/heritage');
+const { on, emit, off, setListeners } = require('./event/core');
+const { filter, pipe, map, merge: streamMerge } = require('./event/utils');
+const { WorkerHost, Worker, detach, attach } = require('./worker/utils');
+const { Disposable } = require('./core/disposable');
+const { EventTarget } = require('./event/target');
+const { unload } = require('./system/unload');
+const { events, streamEventsFrom } = require('./content/events');
+const { getAttachEventType } = require('./content/utils');
+const { window } = require('./addon/window');
+const { getParentWindow } = require('./window/utils');
+const { create: makeFrame, getDocShell } = require('./frame/utils');
+const { contract } = require('./util/contract');
+const { contract: loaderContract } = require('./content/loader');
+const { has } = require('./util/array');
+const { Rules } = require('./util/rules');
+const { merge } = require('./util/object');
+
+const views = WeakMap();
+const workers = WeakMap();
+const pages = WeakMap();
+
+const readyEventNames = [
+  'DOMContentLoaded',
+  'document-element-inserted',
+  'load'
+];
+
+function workerFor(page) workers.get(page)
+function pageFor(view) pages.get(view)
+function viewFor(page) views.get(page)
+function isDisposed (page) !views.get(page, false)
 
-const Page = Trait.compose(
-  Symbiont.resolve({
-    constructor: '_initSymbiont'
-  }),
-  {
-    _frame: Trait.required,
-    _initFrame: Trait.required,
-    postMessage: Symbiont.required,
-    on: Symbiont.required,
-    destroy: Symbiont.required,
+let pageContract = contract(merge({
+  allow: {
+    is: ['object', 'undefined', 'null'],
+    map: function (allow) { return { script: !allow || allow.script !== false }}
+  },
+  onMessage: {
+    is: ['function', 'undefined']
+  },
+  include: {
+    is: ['string', 'array', 'undefined']
+  },
+  contentScriptWhen: {
+    is: ['string', 'undefined']
+  }
+}, loaderContract.rules));
+
+function enableScript (page) {
+  getDocShell(viewFor(page)).allowJavascript = true;
+}
 
-    constructor: function Page(options) {
-      options = options || {};
+function disableScript (page) {
+  getDocShell(viewFor(page)).allowJavascript = false;
+}
+
+function Allow (page) {
+  return {
+    get script() getDocShell(viewFor(page)).allowJavascript,
+    set script(value) value ? enableScript(page) : disableScript(page)
+  };
+}
+
+function injectWorker ({page}) {
+  let worker = workerFor(page);
+  let view = viewFor(page);
+  if (isValidURL(page, view.contentDocument.URL))
+    attach(worker, view.contentWindow);
+}
+
+function isValidURL(page, url) !page.rules || page.rules.matchesAny(url)
 
-      this.contentURL = 'contentURL' in options ? options.contentURL
-        : 'about:blank';
-      if ('contentScriptWhen' in options)
-        this.contentScriptWhen = options.contentScriptWhen;
-      if ('contentScriptFile' in options)
-        this.contentScriptFile = options.contentScriptFile;
-      if ('contentScriptOptions' in options)
-        this.contentScriptOptions = options.contentScriptOptions;
-      if ('contentScript' in options)
-        this.contentScript = options.contentScript;
-      if ('allow' in options)
-        this.allow = options.allow;
-      if ('onError' in options)
-        this.on('error', options.onError);
-      if ('onMessage' in options)
-        this.on('message', options.onMessage);
+const Page = Class({
+  implements: [
+    EventTarget,
+    Disposable
+  ],
+  extends: WorkerHost(workerFor),
+  setup: function Page(options) {
+    let page = this;
+    options = pageContract(options);
+    setListeners(this, options);
+    let view = makeFrame(window.document, {
+      nodeName: 'iframe',
+      type: 'content',
+      uri: options.contentURL,
+      allowJavascript: options.allow.script,
+      allowPlugins: true,
+      allowAuth: true
+    });
+
+    ['contentScriptFile', 'contentScript', 'contentScriptWhen']
+      .forEach(function (prop) page[prop] = options[prop]);
+
+    views.set(this, view);
+    pages.set(view, this);
+
+    let worker = new Worker(options);
+    workers.set(this, worker);
+    pipe(worker, this);
 
-      this.on('propertyChange', this._onChange.bind(this));
+    if (this.include || options.include) {
+      this.rules = Rules();
+      this.rules.add.apply(this.rules, [].concat(this.include || options.include));
+    }
+  },
+  get allow() Allow(this),
+  set allow(value) {
+    let allowJavascript = pageContract({ allow: value }).allow.script;
+    return allowJavascript ? enableScript(this) : disableScript(this);
+  },
+  get contentURL() { return viewFor(this).getAttribute('src'); },
+  set contentURL(value) {
+    if (!isValidURL(this, value)) return;
+    let view = viewFor(this);
+    let contentURL = pageContract({ contentURL: value }).contentURL;
+    view.setAttribute('src', contentURL);
+  },
+  dispose: function () {
+    if (isDisposed(this)) return;
+    let view = viewFor(this);
+    if (view.parentNode) view.parentNode.removeChild(view);
+    views.delete(this);
+    detach(workers.get(this));
+  },
+  toString: function () '[object Page]'
+});
 
-      this._initSymbiont();
-    },
-    
-    _onChange: function _onChange(e) {
-      if ('contentURL' in e && this._frame) {
-        // Cleanup the worker before injecting the content script in the new
-        // document
-        this._workerCleanup();
-        this._initFrame(this._frame);
-      }
-    }
-  }
-);
-exports.Page = function(options) Page(options);
-exports.Page.prototype = Page.prototype;
+exports.Page = Page;
+
+let pageEvents = streamMerge([events, streamEventsFrom(window)]);
+let readyEvents = filter(pageEvents, isReadyEvent);
+let formattedEvents = map(readyEvents, function({target, type}) {
+  return { type: type, page: pageFromDoc(target) };
+});
+let pageReadyEvents = filter(formattedEvents, function({page, type}) {
+  return getAttachEventType(page) === type});
+on(pageReadyEvents, 'data', injectWorker);
+
+function isReadyEvent ({type}) {
+  return has(readyEventNames, type);
+}
+
+/*
+ * Takes a document, finds its doc shell tree root and returns the
+ * matching Page instance if found
+ */
+function pageFromDoc(doc) {
+  let parentWindow = getParentWindow(doc.defaultView), page;
+  if (!parentWindow) return;
+
+  let frames = parentWindow.document.getElementsByTagName('iframe');
+  for (let i = frames.length; i--;)
+    if (frames[i].contentDocument === doc && (page = pageFor(frames[i])))
+      return page;
+  return null;
+}
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -27,26 +27,17 @@ const { contract } = require("./util/con
 const { on, off, emit, setListeners } = require("./event/core");
 const { EventTarget } = require("./event/target");
 const domPanel = require("./panel/utils");
 const { events } = require("./panel/events");
 const systemEvents = require("./system/events");
 const { filter, pipe } = require("./event/utils");
 const { getNodeView, getActiveView } = require("./view/core");
 const { isNil, isObject } = require("./lang/type");
-
-function getAttachEventType(model) {
-  let when = model.contentScriptWhen;
-  return requiresAddonGlobal(model) ? "sdk-panel-content-changed" :
-         when === "start" ? "sdk-panel-content-changed" :
-         when === "end" ? "sdk-panel-document-loaded" :
-         when === "ready" ? "sdk-panel-content-loaded" :
-         null;
-}
-
+const { getAttachEventType } = require("./content/utils");
 
 let number = { is: ['number', 'undefined', 'null'] };
 let boolean = { is: ['boolean', 'undefined', 'null'] };
 
 let rectContract = contract({
   top: number,
   right: number,
   bottom: number,
@@ -89,24 +80,24 @@ let setupAutoHide = new function() {
 
   return function setupAutoHide(panel) {
     // Create system event listener that reacts to any panel showing and
     // hides given `panel` if it's not the one being shown.
     function listener({subject}) {
       // It could be that listener is not GC-ed in the same cycle as
       // panel in such case we remove listener manually.
       let view = viewFor(panel);
-      if (!view) systemEvents.off("sdk-panel-show", listener);
+      if (!view) systemEvents.off("popupshowing", listener);
       else if (subject !== view) panel.hide();
     }
 
     // system event listener is intentionally weak this way we'll allow GC
     // to claim panel if it's no longer referenced by an add-on code. This also
     // helps minimizing cleanup required on unload.
-    systemEvents.on("sdk-panel-show", listener);
+    systemEvents.on("popupshowing", listener);
     // To make sure listener is not claimed by GC earlier than necessary we
     // associate it with `panel` it's associated with. This way it won't be
     // GC-ed earlier than `panel` itself.
     refs.set(panel, listener);
   }
 }
 
 const Panel = Class({
@@ -229,31 +220,31 @@ const Panel = Class({
   }
 });
 exports.Panel = Panel;
 
 // Filter panel events to only panels that are create by this module.
 let panelEvents = filter(events, function({target}) panelFor(target));
 
 // Panel events emitted after panel has being shown.
-let shows = filter(panelEvents, function({type}) type === "sdk-panel-shown");
+let shows = filter(panelEvents, function({type}) type === "popupshown");
 
 // Panel events emitted after panel became hidden.
-let hides = filter(panelEvents, function({type}) type === "sdk-panel-hidden");
+let hides = filter(panelEvents, function({type}) type === "popuphidden");
 
 // Panel events emitted after content inside panel is ready. For different
 // panels ready may mean different state based on `contentScriptWhen` attribute.
 // Weather given event represents readyness is detected by `getAttachEventType`
 // helper function.
 let ready = filter(panelEvents, function({type, target})
   getAttachEventType(modelFor(panelFor(target))) === type);
 
 // Panel events emitted after content document in the panel has changed.
 let change = filter(panelEvents, function({type})
-  type === "sdk-panel-content-changed");
+  type === "document-element-inserted");
 
 // Forward panel show / hide events to panel's own event listeners.
 on(shows, "data", function({target}) emit(panelFor(target), "show"));
 on(hides, "data", function({target}) emit(panelFor(target), "hide"));
 
 on(ready, "data", function({target}) {
   let worker = workerFor(panelFor(target));
   attach(worker, domPanel.getContentDocument(target).defaultView);
--- a/addon-sdk/source/lib/sdk/panel/events.js
+++ b/addon-sdk/source/lib/sdk/panel/events.js
@@ -14,14 +14,13 @@ module.metadata = {
 const events = require("../system/events");
 const { emit } = require("../event/core");
 
 let channel = {};
 
 function forward({ subject, type, data })
   emit(channel, "data", { target: subject, type: type, data: data });
 
-["sdk-panel-show", "sdk-panel-hide", "sdk-panel-shown",
- "sdk-panel-hidden", "sdk-panel-content-changed", "sdk-panel-content-loaded",
- "sdk-panel-document-loaded"
+["popupshowing", "popuphiding", "popupshown", "popuphidden",
+"document-element-inserted", "DOMContentLoaded", "load"
 ].forEach(function(type) events.on(type, forward));
 
 exports.events = channel;
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -200,26 +200,16 @@ function setupPanelFrame(frame) {
   frame.setAttribute("showcaret", true);
   frame.setAttribute("autocompleteenabled", true);
   if (platform === "darwin") {
     frame.style.borderRadius = "6px";
     frame.style.padding = "1px";
   }
 }
 
-let EVENT_NAMES = {
-  "popupshowing": "sdk-panel-show",
-  "popuphiding": "sdk-panel-hide",
-  "popupshown": "sdk-panel-shown",
-  "popuphidden": "sdk-panel-hidden",
-  "document-element-inserted": "sdk-panel-content-changed",
-  "DOMContentLoaded": "sdk-panel-content-loaded",
-  "load": "sdk-panel-document-loaded"
-};
-
 function make(document) {
   document = document || getMostRecentBrowserWindow().document;
   let panel = document.createElementNS(XUL_NS, "panel");
   panel.setAttribute("type", "arrow");
 
   // Note that panel is a parent of `viewFrame` who's `docShell` will be
   // configured at creation time. If `panel` and there for `viewFrame` won't
   // have an owner document attempt to access `docShell` will throw. There
@@ -244,39 +234,39 @@ function make(document) {
   setupPanelFrame(backgroundFrame);
 
   let viewFrame = createFrame(panel, frameOptions);
   setupPanelFrame(viewFrame);
 
   function onDisplayChange({type}) {
     try { swapFrameLoaders(backgroundFrame, viewFrame); }
     catch(error) { console.exception(error); }
-    events.emit(EVENT_NAMES[type], { subject: panel });
+    events.emit(type, { subject: panel });
   }
 
   function onContentReady({target, type}) {
     if (target === getContentDocument(panel)) {
       style(panel);
-      events.emit(EVENT_NAMES[type], { subject: panel });
+      events.emit(type, { subject: panel });
     }
   }
 
   function onContentLoad({target, type}) {
     if (target === getContentDocument(panel))
-      events.emit(EVENT_NAMES[type], { subject: panel });
+      events.emit(type, { subject: panel });
   }
 
   function onContentChange({subject, type}) {
     let document = subject;
     if (document === getContentDocument(panel) && document.defaultView)
-      events.emit(EVENT_NAMES[type], { subject: panel });
+      events.emit(type, { subject: panel });
   }
 
   function onPanelStateChange({type}) {
-    events.emit(EVENT_NAMES[type], { subject: panel })
+    events.emit(type, { subject: panel })
   }
 
   panel.addEventListener("popupshowing", onDisplayChange, false);
   panel.addEventListener("popuphiding", onDisplayChange, false);
   panel.addEventListener("popupshown", onPanelStateChange, false);
   panel.addEventListener("popuphidden", onPanelStateChange, false);
 
   // Panel content document can be either in panel `viewFrame` or in
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/places/utils.js
@@ -0,0 +1,64 @@
+/* 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 { Class } = require('../core/heritage');
+const { method } = require('../lang/functional');
+const { defer, promised, all } = require('../core/promise');
+
+/*
+ * TreeNodes are used to construct dependency trees
+ * for BookmarkItems
+ */
+let TreeNode = Class({
+  initialize: function (value) {
+    this.value = value;
+    this.children = [];
+  },
+  add: function (values) {
+    [].concat(values).forEach(value => {
+      this.children.push(value instanceof TreeNode ? value : TreeNode(value));
+    });
+  },
+  get length () {
+    let count = 0;
+    this.walk(() => count++);
+    // Do not count the current node
+    return --count;
+  },
+  get: method(get),
+  walk: method(walk),
+  toString: function () '[object TreeNode]'
+});
+exports.TreeNode = TreeNode;
+
+/*
+ * Descends down from `node` applying `fn` to each in order.
+ * Can be asynchronous if `fn` returns a promise. `fn` is passed 
+ * one argument, the current node, `curr`
+ */
+function walk (curr, fn) {
+  return promised(fn)(curr).then(val => {
+    return all(curr.children.map(child => walk(child, fn)));
+  });
+} 
+
+/*
+ * Descends from the TreeNode `node`, returning
+ * the node with value `value` if found or `null`
+ * otherwise
+ */
+function get (node, value) {
+  if (node.value === value) return node;
+  for (let child of node.children) {
+    let found = get(child, value);
+    if (found) return found;
+  }
+  return null;
+}
--- a/addon-sdk/source/lib/sdk/private-browsing.js
+++ b/addon-sdk/source/lib/sdk/private-browsing.js
@@ -2,17 +2,17 @@
  * 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 { setMode, getMode, on: onStateChange } = require('./private-browsing/utils');
+const { setMode, getMode, on: onStateChange, isPermanentPrivateBrowsing } = 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');
@@ -60,16 +60,20 @@ exports.isPrivate = function(thing) {
     }
 
     // can we find an associated window?
     let window = getOwnerWindow(thing);
     if (window)
       return isWindowPrivate(window);
   }
 
+  // check if the post pwpb, global pb service is enabled.
+  if (isPermanentPrivateBrowsing())
+    return true;
+
   // 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,
--- a/addon-sdk/source/lib/sdk/private-browsing/utils.js
+++ b/addon-sdk/source/lib/sdk/private-browsing/utils.js
@@ -47,16 +47,20 @@ let isGlobalPBSupported = exports.isGlob
 
 // checks that per-window private browsing is implemented
 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*');
+
+exports.isPermanentPrivateBrowsing = function() {
+ return !!(PrivateBrowsingUtils && PrivateBrowsingUtils.permanentPrivateBrowsing);
+}
                        
 function ignoreWindow(window) {
   return !isPrivateBrowsingSupported && isWindowPrivate(window) && !isGlobalPBSupported;
 }
 exports.ignoreWindow = ignoreWindow;
 
 function onChange() {
   // Emit event with in next turn of event loop.
--- a/addon-sdk/source/lib/sdk/system.js
+++ b/addon-sdk/source/lib/sdk/system.js
@@ -80,17 +80,17 @@ exports.stdout = new function() {
     }
   }
   return Object.freeze({ write: write });
 };
 
 /**
  * Returns a path of the system's or application's special directory / file
  * associated with a given `id`. For list of possible `id`s please see:
- * https://developer.mozilla.org/en/Code_snippets/File_I%2F%2FO#Getting_special_files
+ * https://developer.mozilla.org/en-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories
  * http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
  * @example
  *
  *    // get firefox profile path
  *    let profilePath = require('system').pathFor('ProfD');
  *    // get OS temp files directory (/tmp)
  *    let temps = require('system').pathFor('TmpD');
  *    // get OS desktop path for an active user (~/Desktop on linux
--- a/addon-sdk/source/lib/sdk/tabs/tab-fennec.js
+++ b/addon-sdk/source/lib/sdk/tabs/tab-fennec.js
@@ -57,18 +57,26 @@ const Tab = Class({
   get url() getTabURL(tabNS(this).tab),
   set url(url) setTabURL(tabNS(this).tab, url),
 
   /**
    * URI of the favicon for the page currently loaded in this tab.
    * @type {String}
    */
   get favicon() {
-    // TODO: provide the real favicon when it is available
-    console.error(ERR_FENNEC_MSG);
+    /*
+     * Synchronous favicon services were never supported on Fennec,
+     * and as of FF22, are now deprecated. When/if favicon services
+     * are supported for Fennec, this getter should reference
+     * `require('sdk/places/favicon').getFavicon`
+     */
+    console.error(
+      'tab.favicon is deprecated, and currently ' +
+      'favicon helpers are not yet supported by Fennec'
+    );
 
     // return 16x16 blank default
     return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAEklEQVQ4jWNgGAWjYBSMAggAAAQQAAF/TXiOAAAAAElFTkSuQmCC';
   },
 
   getThumbnail: function() {
     // TODO: implement!
     console.error(ERR_FENNEC_MSG);
--- a/addon-sdk/source/lib/sdk/test.js
+++ b/addon-sdk/source/lib/sdk/test.js
@@ -7,16 +7,18 @@
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const BaseAssert = require("sdk/test/assert").Assert;
 const { isFunction, isObject } = require("sdk/lang/type");
 
+exports.Assert = BaseAssert;
+
 function extend(target) {
   let descriptor = {}
   Array.slice(arguments, 1).forEach(function(source) {
     Object.getOwnPropertyNames(source).forEach(function onEach(name) {
       descriptor[name] = Object.getOwnPropertyDescriptor(source, name);
     });
   });
   return Object.create(target, descriptor);
--- a/addon-sdk/source/lib/sdk/util/array.js
+++ b/addon-sdk/source/lib/sdk/util/array.js
@@ -96,17 +96,16 @@ function fromIterator(iterator) {
   else {
     for (let item of iterator)
       array.push(item);
   }
   return array;
 }
 exports.fromIterator = fromIterator;
 
-
 function find(array, predicate) {
   var index = 0;
   var count = array.length;
   while (index < count) {
     var value = array[index];
     if (predicate(value)) return value;
     else index = index + 1;
   }
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/util/match-pattern.js
@@ -0,0 +1,122 @@
+/* -*- 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/. */
+
+"use strict";
+
+module.metadata = {
+  "stability": "unstable"
+};
+
+const { URL } = require('../url');
+const cache = {};
+
+function MatchPattern(pattern) {
+  if (cache[pattern]) return cache[pattern];
+
+  if (typeof pattern.test == "function") {
+
+    // For compatibility with -moz-document rules, we require the RegExp's
+    // global, ignoreCase, and multiline flags to be set to false.
+    if (pattern.global) {
+      throw new Error("A RegExp match pattern cannot be set to `global` " +
+                      "(i.e. //g).");
+    }
+    if (pattern.ignoreCase) {
+      throw new Error("A RegExp match pattern cannot be set to `ignoreCase` " +
+                      "(i.e. //i).");
+    }
+    if (pattern.multiline) {
+      throw new Error("A RegExp match pattern cannot be set to `multiline` " +
+                      "(i.e. //m).");
+    }
+
+    this.regexp = pattern;
+  }
+  else {
+    let firstWildcardPosition = pattern.indexOf("*");
+    let lastWildcardPosition = pattern.lastIndexOf("*");
+    if (firstWildcardPosition != lastWildcardPosition)
+      throw new Error("There can be at most one '*' character in a wildcard.");
+
+    if (firstWildcardPosition == 0) {
+      if (pattern.length == 1)
+        this.anyWebPage = true;
+      else if (pattern[1] != ".")
+        throw new Error("Expected a *.<domain name> string, got: " + pattern);
+      else
+        this.domain = pattern.substr(2);
+    }
+    else {
+      if (pattern.indexOf(":") == -1) {
+        throw new Error("When not using *.example.org wildcard, the string " +
+                        "supplied is expected to be either an exact URL to " +
+                        "match or a URL prefix. The provided string ('" +
+                        pattern + "') is unlikely to match any pages.");
+      }
+
+      if (firstWildcardPosition == -1)
+        this.exactURL = pattern;
+      else if (firstWildcardPosition == pattern.length - 1)
+        this.urlPrefix = pattern.substr(0, pattern.length - 1);
+      else {
+        throw new Error("The provided wildcard ('" + pattern + "') has a '*' " +
+                        "in an unexpected position. It is expected to be the " +
+                        "first or the last character in the wildcard.");
+      }
+    }
+  }
+
+  cache[pattern] = this;
+}
+
+MatchPattern.prototype = {
+
+  test: function MatchPattern_test(urlStr) {
+    try {
+      var url = URL(urlStr);
+    }
+    catch (err) {
+      return false;
+    }
+
+    // Test the URL against a RegExp pattern.  For compatibility with
+    // -moz-document rules, we require the RegExp to match the entire URL,
+    // so we not only test for a match, we also make sure the matched string
+    // is the entire URL string.
+    //
+    // Assuming most URLs don't match most match patterns, we call `test` for
+    // speed when determining whether or not the URL matches, then call `exec`
+    // for the small subset that match to make sure the entire URL matches.
+    //
+    if (this.regexp && this.regexp.test(urlStr) &&
+        this.regexp.exec(urlStr)[0] == urlStr)
+      return true;
+
+    if (this.anyWebPage && /^(https?|ftp)$/.test(url.scheme))
+      return true;
+    if (this.exactURL && this.exactURL == urlStr)
+      return true;
+
+    // Tests the urlStr against domain and check if
+    // wildcard submitted (*.domain.com), it only allows
+    // subdomains (sub.domain.com) or from the root (http://domain.com)
+    // and reject non-matching domains (otherdomain.com)
+    // bug 856913
+    if (this.domain && url.host &&
+         (url.host === this.domain ||
+          url.host.slice(-this.domain.length - 1) === "." + this.domain))
+      return true;
+    if (this.urlPrefix && 0 == urlStr.indexOf(this.urlPrefix))
+      return true;
+
+    return false;
+  },
+
+  toString: function () '[object MatchPattern]'
+
+};
+
+exports.MatchPattern = MatchPattern;
--- a/addon-sdk/source/lib/sdk/util/object.js
+++ b/addon-sdk/source/lib/sdk/util/object.js
@@ -49,9 +49,15 @@ exports.merge = merge;
  */
 function extend(source) {
   let rest = Array.slice(arguments, 1);
   rest.unshift(Object.create(source));
   return merge.apply(null, rest);
 }
 exports.extend = extend;
 
+function has(obj, key) obj.hasOwnProperty(key);
+exports.has = has;
 
+function each(obj, fn) {
+  for (let key in obj) has(obj, key) && fn(obj[key], key, obj);
+}
+exports.each = each;
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/util/rules.js
@@ -0,0 +1,52 @@
+/* -*- 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/. */
+
+"use strict";
+
+module.metadata = {
+  "stability": "unstable"
+};
+
+const { Class } = require('../core/heritage');
+const { MatchPattern } = require('./match-pattern');
+const { on, off, emit } = require('../event/core');
+const { method } = require('../lang/functional');
+const objectUtil = require('./object');
+const { EventTarget } = require('../event/target');
+const { List, addListItem, removeListItem } = require('./list');
+
+// Should deprecate usage of EventEmitter/compose
+const Rules = Class({
+  implements: [
+    EventTarget,
+    List
+  ],
+  add: function(...rules) [].concat(rules).forEach(function onAdd(rule) {
+    addListItem(this, rule);
+    emit(this, 'add', rule);
+  }, this),
+  remove: function(...rules) [].concat(rules).forEach(function onRemove(rule) {
+    removeListItem(this, rule);
+    emit(this, 'remove', rule);
+  }, this),
+  get: function(rule) {
+    let found = false;
+    for (let i in this) if (this[i] === rule) found = true;
+    return found;
+  },
+  // Returns true if uri matches atleast one stored rule
+  matchesAny: function(uri) !!filterMatches(this, uri).length,
+  toString: function() '[object Rules]'
+});
+exports.Rules = Rules;
+
+function filterMatches(instance, uri) {
+  let matches = [];
+  for (let i in instance) {
+    if (new MatchPattern(instance[i]).test(uri)) matches.push(instance[i]);
+  }
+  return matches;
+}
--- a/addon-sdk/source/lib/sdk/widget.js
+++ b/addon-sdk/source/lib/sdk/widget.js
@@ -599,16 +599,52 @@ BrowserWindow.prototype = {
   },
 
   _insertNodeInToolbar: function BW__insertNodeInToolbar(node) {
     // Add to the customization palette
     let toolbox = this.doc.getElementById("navigator-toolbox");
     let palette = toolbox.palette;
     palette.appendChild(node);
 
+    if (this.window.CustomizableUI) {
+      let placement = this.window.CustomizableUI.getPlacementOfWidget(node.id);
+      if (!placement) {
+        placement = {area: 'nav-bar', position: undefined};
+      }
+      this.window.CustomizableUI.addWidgetToArea(node.id, placement.area, placement.position);
+
+      // Depending on when this gets called, we might be in the right place now. In that case,
+      // don't run the following code.
+      if (node.parentNode != palette) {
+        return;
+      }
+      // Otherwise, insert:
+      let container = this.doc.getElementById(placement.area);
+      if (container.customizationTarget) {
+        container = container.customizationTarget;
+      }
+
+      if (placement.position !== undefined) {
+        // Find a position:
+        let items = this.window.CustomizableUI.getWidgetIdsInArea(placement.area);
+        let itemIndex = placement.position;
+        for (let l = items.length; itemIndex < l; itemIndex++) {
+          let realItems = container.getElementsByAttribute("id", items[itemIndex]);
+          if (realItems[0]) {
+            container.insertBefore(node, realItems[0]);
+            break;
+          }
+        }
+      }
+      if (node.parentNode != container) {
+        container.appendChild(node);
+      }
+      return;
+    }
+
     // Search for widget toolbar by reading toolbar's currentset attribute
     let container = null;
     let toolbars = this.doc.getElementsByTagName("toolbar");
     let id = node.getAttribute("id");
     for (let i = 0, l = toolbars.length; i < l; i++) {
       let toolbar = toolbars[i];
       if (toolbar.getAttribute("currentset").indexOf(id) == -1)
         continue;
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -384,8 +384,21 @@ function getOwnerBrowserWindow(node) {
   // contains browser for the anchored window document.
   let document = window.document;
   let browsers = windows("navigator:browser", { includePrivate: true });
   return array.find(browsers, function isTargetBrowser(window) {
     return !!window.gBrowser.getBrowserForDocument(document);
   });
 }
 exports.getOwnerBrowserWindow = getOwnerBrowserWindow;
+
+function getParentWindow(window) {
+  try {
+    return window.QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebNavigation)
+      .QueryInterface(Ci.nsIDocShellTreeItem).parent
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIDOMWindow);
+  }
+  catch (e) {}
+  return null;
+}
+exports.getParentWindow = getParentWindow;
--- a/addon-sdk/source/mapping.json
+++ b/addon-sdk/source/mapping.json
@@ -28,17 +28,17 @@
   "window-utils": "sdk/deprecated/window-utils",
   "window/utils": "sdk/window/utils",
   "windows/dom": "sdk/windows/dom",
   "windows/loader": "sdk/windows/loader",
   "xul-app": "sdk/system/xul-app",
   "url": "sdk/url",
   "traceback": "sdk/console/traceback",
   "xhr": "sdk/net/xhr",
-  "match-pattern": "sdk/page-mod/match-pattern",
+  "match-pattern": "sdk/util/match-pattern",
   "tab-browser": "sdk/deprecated/tab-browser",
   "file": "sdk/io/file",
   "runtime": "sdk/system/runtime",
   "xpcom": "sdk/platform/xpcom",
   "querystring": "sdk/querystring",
   "text-streams": "sdk/io/text-streams",
   "app-strings": "sdk/deprecated/app-strings",
   "light-traits": "sdk/deprecated/light-traits",
--- a/addon-sdk/source/python-lib/cuddlefish/docs/renderapi.readme.md
+++ b/addon-sdk/source/python-lib/cuddlefish/docs/renderapi.readme.md
@@ -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/. -->
 
 
 This document describes the structure of the HTML generated by the renderapi.py
-tool, both for use in the API docs shown by "cfx docs" and as exported by
-"cfx sdocs". The particular HTML id and class attributes embedded in the files,
+tool. The particular HTML id and class attributes embedded in the files,
 as well as their organization, represent the interface between the tool and any
 front-end code wanting to style the docs in some particular way.
 
 renderapi generates two sorts of files:
 
 - a file called "<module-name>.div": this is the contents of the parsed
 Markdown file rendered inside a well-defined DIV tag
 
--- a/addon-sdk/source/python-lib/jetpack_sdk_env.py
+++ b/addon-sdk/source/python-lib/jetpack_sdk_env.py
@@ -55,12 +55,12 @@ def welcome():
         # Python 2.x and 3.x, so we'll have to use the traceback module.
 
         import traceback
         _, e, _ = sys.exc_info()
         print ("Verification of Add-on SDK environment failed (%s)." % e)
         print ("Your SDK may not work properly.")
         return
 
-    print ("Welcome to the Add-on SDK. Run 'cfx docs' for assistance.")
+    print ("Welcome to the Add-on SDK. For the docs, visit https://addons.mozilla.org/en-US/developers/docs/sdk/latest/")
 
 if __name__ == '__main__':
     welcome()
--- a/addon-sdk/source/test/addons/layout-change/main.js
+++ b/addon-sdk/source/test/addons/layout-change/main.js
@@ -1,13 +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 { LoaderWithHookedConsole } = require('sdk/test/loader');
+const { loader } = LoaderWithHookedConsole(module);
 const app = require("sdk/system/xul-app");
 
 // This test makes sure that require statements used by all AMO hosted
 // add-ons will be able to use old require statements.
 // Tests are based on following usage data:
 // https://docs.google.com/spreadsheet/ccc?key=0ApEBy-GRnGxzdHlRMHJ5RXN1aWJ4RGhINkxSd0FCQXc#gid=0
 
 exports["test compatibility"] = function(assert) {
@@ -99,17 +102,17 @@ exports["test compatibility"] = function
 
   assert.equal(require("passwords"),
                require("sdk/passwords"), "sdk/passwords -> passwords");
 
   assert.equal(require("events"),
                require("sdk/deprecated/events"), "sdk/deprecated/events -> events");
 
   assert.equal(require("match-pattern"),
-               require("sdk/page-mod/match-pattern"), "sdk/page-mod/match-pattern -> match-pattern");
+               require("sdk/util/match-pattern"), "sdk/util/match-pattern -> match-pattern");
 
   if (app.is("Firefox")) {
     assert.equal(require("tab-browser"),
                  require("sdk/deprecated/tab-browser"), "sdk/deprecated/tab-browser -> tab-browser");
   }
 
   assert.equal(require("file"),
                require("sdk/io/file"), "sdk/io/file -> file");
@@ -136,18 +139,18 @@ exports["test compatibility"] = function
                require("sdk/keyboard/utils"), "sdk/keyboard/utils -> keyboard/utils");
 
   assert.equal(require("system"),
                require("sdk/system"), "sdk/system -> system");
 
   assert.equal(require("querystring"),
                require("sdk/querystring"), "sdk/querystring -> querystring");
 
-  assert.equal(require("addon-page"),
-               require("sdk/addon-page"), "sdk/addon-page -> addon-page");
+  assert.equal(loader.require("addon-page"),
+               loader.require("sdk/addon-page"), "sdk/addon-page -> addon-page");
 
   assert.equal(require("tabs/utils"),
                require("sdk/tabs/utils"), "sdk/tabs/utils -> tabs/utils");
 
   assert.equal(require("app-strings"),
                require("sdk/deprecated/app-strings"), "sdk/deprecated/app-strings -> app-strings");
 
   assert.equal(require("dom/events"),
--- a/addon-sdk/source/test/tabs/test-fennec-tabs.js
+++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js
@@ -108,17 +108,19 @@ exports.testTabProperties = function(tes
     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
       // Poors man deepEqual with JSON.stringify...
       test.assertEqual(JSON.stringify(messages),
-                       JSON.stringify([ERR_FENNEC_MSG]),
+                       JSON.stringify(['tab.favicon is deprecated, and ' +
+                          'currently favicon helpers are not yet supported ' +
+                          'by Fennec']),
                        "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();
@@ -571,39 +573,16 @@ exports.testPerTabEvents = function(test
 
         // end test
         tab.close(function() test.done());
       });
     }
   });
 };
 
-// TEST: tabs.activeTab getter
-exports.testActiveTab_getter_alt = function(test) {
-  test.waitUntilDone();
-
-  let url = URL.replace("#title#", "foo");
-  tabs.open({
-    url: url,
-    onActivate: function(tab) {
-      test.assertEqual(tabs.activeTab.url, tab.url, 'the active tab is correct');
-
-      tab.once('ready', function() {
-        test.assertEqual(tab.url, url);
-        test.assertEqual(tab.title, "foo");
-
-        tab.close(function() {
-          // end test
-          test.done();
-        });
-      });
-    }
-  });
-};
-
 exports.testUniqueTabIds = function(test) {
   test.waitUntilDone();
   var tabs = require('sdk/tabs');
   var tabIds = {};
   var steps = [
     function (index) {
       tabs.open({
         url: "data:text/html;charset=utf-8,foo",
--- a/addon-sdk/source/test/tabs/test-firefox-tabs.js
+++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js
@@ -1,220 +1,151 @@
 /* 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 timer = require('sdk/timers');
+const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+const { windows, onFocus, getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { open, focus } = require('sdk/window/helpers');
 const { StringBundle } = require('sdk/deprecated/app-strings');
+const tabs = require('sdk/tabs');
 
 const base64png = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYA" +
                   "AABzenr0AAAASUlEQVRYhe3O0QkAIAwD0eyqe3Q993AQ3cBSUKpygfsNTy" +
                   "N5ugbQpK0BAADgP0BRDWXWlwEAAAAAgPsA3rzDaAAAAHgPcGrpgAnzQ2FG" +
                   "bWRR9AAAAABJRU5ErkJggg%3D%3D";
 
-// TEST: tabs.activeTab getter
-exports.testActiveTab_getter = function(test) {
-  test.waitUntilDone();
-
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-
-    let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>";
-    require("sdk/deprecated/tab-browser").addTab(
-      url,
-      {
-        onLoad: function(e) {
-          test.assert(tabs.activeTab);
-          test.assertEqual(tabs.activeTab.url, url);
-          test.assertEqual(tabs.activeTab.title, "foo");
-          closeBrowserWindow(window, function() test.done());
-        }
-      }
-    );
-  });
-};
-
 // Bug 682681 - tab.title should never be empty
 exports.testBug682681_aboutURI = function(test) {
   test.waitUntilDone();
 
   let tabStrings = StringBundle('chrome://browser/locale/tabbrowser.properties');
 
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+  tabs.on('ready', function onReady(tab) {
+    tabs.removeListener('ready', onReady);
 
-    tabs.on('ready', function onReady(tab) {
-      tabs.removeListener('ready', onReady);
-
-      test.assertEqual(tab.title,
-                       tabStrings.get('tabs.emptyTabTitle'),
-                       "title of about: tab is not blank");
+    test.assertEqual(tab.title,
+                     tabStrings.get('tabs.emptyTabTitle'),
+                     "title of about: tab is not blank");
 
-      // end of test
-      closeBrowserWindow(window, function() test.done());
-    });
+    tab.close(function() test.done());
+  });
 
-    // open a about: url
-    tabs.open({
-      url: "about:blank",
-      inBackground: true
-    });
+  // open a about: url
+  tabs.open({
+    url: "about:blank",
+    inBackground: true
   });
 };
 
 // related to Bug 682681
 exports.testTitleForDataURI = function(test) {
   test.waitUntilDone();
 
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-
-    tabs.on('ready', function onReady(tab) {
-      tabs.removeListener('ready', onReady);
-
+  tabs.open({
+    url: "data:text/html;charset=utf-8,<title>tab</title>",
+    inBackground: true,
+    onReady: function(tab) {
       test.assertEqual(tab.title, "tab", "data: title is not Connecting...");
-
-      // end of test
-      closeBrowserWindow(window, function() test.done());
-    });
-
-    // open a about: url
-    tabs.open({
-      url: "data:text/html;charset=utf-8,<title>tab</title>",
-      inBackground: true
-    });
+      tab.close(function() test.done());
+    }
   });
 };
 
 // TEST: 'BrowserWindow' instance creation on tab 'activate' event
 // See bug 648244: there was a infinite loop.
 exports.testBrowserWindowCreationOnActivate = function(test) {
   test.waitUntilDone();
 
   let windows = require("sdk/windows").browserWindows;
-  let tabs = require("sdk/tabs");
-
   let gotActivate = false;
 
   tabs.once('activate', function onActivate(eventTab) {
     test.assert(windows.activeWindow, "Is able to fetch activeWindow");
     gotActivate = true;
   });
 
-  openBrowserWindow(function(window, browser) {
+  open().then(function(window) {
     test.assert(gotActivate, "Received activate event before openBrowserWindow's callback is called");
     closeBrowserWindow(window, function () test.done());
   });
 }
 
-// TEST: tab.activate()
-exports.testActiveTab_setter = function(test) {
-  test.waitUntilDone();
-
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-    let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head></html>";
-
-    tabs.on('ready', function onReady(tab) {
-      tabs.removeListener('ready', onReady);
-      test.assertEqual(tabs.activeTab.url, "about:blank", "activeTab url has not changed");
-      test.assertEqual(tab.url, url, "url of new background tab matches");
-      tabs.on('activate', function onActivate(eventTab) {
-        tabs.removeListener('activate', onActivate);
-        test.assertEqual(tabs.activeTab.url, url, "url after activeTab setter matches");
-        test.assertEqual(eventTab, tab, "event argument is the activated tab");
-        test.assertEqual(eventTab, tabs.activeTab, "the tab is the active one");
-        closeBrowserWindow(window, function() test.done());
-      });
-      tab.activate();
-    })
-
-    tabs.open({
-      url: url,
-      inBackground: true
-    });
-  });
-};
-
 // TEST: tab unloader
 exports.testAutomaticDestroy = function(test) {
   test.waitUntilDone();
 
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-
-    // Create a second tab instance that we will destroy
-    let called = false;
+  // Create a second tab instance that we will destroy
+  let called = false;
 
-    let loader = Loader(module);
-    let tabs2 = loader.require("sdk/tabs");
-    tabs2.on('open', function onOpen(tab) {
-      called = true;
-    });
+  let loader = Loader(module);
+  let tabs2 = loader.require("sdk/tabs");
+  tabs2.on('open', function onOpen(tab) {
+    called = true;
+  });
 
-    loader.unload();
+  loader.unload();
 
-    // Fire a tab event and ensure that the destroyed tab is inactive
-    tabs.once('open', function () {
-      timer.setTimeout(function () {
-        test.assert(!called, "Unloaded tab module is destroyed and inactive");
-        closeBrowserWindow(window, function() test.done());
-      }, 0);
-    });
+  // Fire a tab event and ensure that the destroyed tab is inactive
+  tabs.once('open', function (tab) {
+    timer.setTimeout(function () {
+      test.assert(!called, "Unloaded tab module is destroyed and inactive");
+      tab.close(test.done.bind(test));
+    }, 0);
+  });
 
-    tabs.open("data:text/html;charset=utf-8,foo");
-  });
+  tabs.open("data:text/html;charset=utf-8,foo");
 };
 
 // test tab properties
 exports.testTabProperties = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require('sdk/tabs');
-    let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
-    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");
-        test.assertEqual(tab.style, null, "style of the new tab matches");
-        test.assertEqual(tab.index, 1, "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.");
-        onReadyOrLoad(window);
-      },
-      onLoad: 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");
-        test.assertEqual(tab.style, null, "style of the new tab matches");
-        test.assertEqual(tab.index, 1, "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.");
-        onReadyOrLoad(window);
-      }
-    });
-  });
 
   let count = 0;
-  function onReadyOrLoad (window) {
-    if (count++)
-      closeBrowserWindow(window, function() test.done());
+  function onReadyOrLoad (tab) {
+    if (count++) {
+      tab.close(test.done.bind(test));
+    }
   }
+
+  let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
+  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");
+      test.assertEqual(tab.style, null, "style of the new tab matches");
+      test.assertEqual(tab.index, 1, "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.");
+      onReadyOrLoad(tab);
+    },
+    onLoad: 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");
+      test.assertEqual(tab.style, null, "style of the new tab matches");
+      test.assertEqual(tab.index, 1, "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.");
+      onReadyOrLoad(tab);
+    }
+  });
 };
 
 // TEST: tab properties
 exports.testTabContentTypeAndReload = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+
+  open().then(focus).then(function(window) {
     let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
     let urlXML = "data:text/xml;charset=utf-8,<foo>bar</foo>";
     tabs.open({
       url: url,
       onReady: function(tab) {
         if (tab.url === url) {
           test.assertEqual(tab.contentType, "text/html");
           tab.url = urlXML;
@@ -225,42 +156,44 @@ exports.testTabContentTypeAndReload = fu
       }
     });
   });
 };
 
 // TEST: tabs iterator and length property
 exports.testTabsIteratorAndLength = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+
+  open(null, { features: { chrome: true, toolbar: true } }).then(focus).then(function(window) {
     let startCount = 0;
     for each (let t in tabs) startCount++;
     test.assertEqual(startCount, tabs.length, "length property is correct");
     let url = "data:text/html;charset=utf-8,default";
+
     tabs.open(url);
     tabs.open(url);
     tabs.open({
       url: url,
       onOpen: function(tab) {
         let count = 0;
         for each (let t in tabs) count++;
         test.assertEqual(count, startCount + 3, "iterated tab count matches");
         test.assertEqual(startCount + 3, tabs.length, "iterated tab count matches length property");
+
         closeBrowserWindow(window, function() test.done());
       }
     });
   });
 };
 
 // TEST: tab.url setter
 exports.testTabLocation = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+
+  open().then(focus).then(function(window) {
     let url1 = "data:text/html;charset=utf-8,foo";
     let url2 = "data:text/html;charset=utf-8,bar";
 
     tabs.on('ready', function onReady(tab) {
       if (tab.url != url2)
         return;
       tabs.removeListener('ready', onReady);
       test.pass("tab.load() loaded the correct url");
@@ -274,55 +207,49 @@ exports.testTabLocation = function(test)
       }
     });
   });
 };
 
 // TEST: tab.close()
 exports.testTabClose = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-    let url = "data:text/html;charset=utf-8,foo";
+
+  let url = "data:text/html;charset=utf-8,foo";
+
+  test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
+  tabs.on('ready', function onReady(tab) {
+    tabs.removeListener('ready', onReady);
+    test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
+    let secondOnCloseCalled = false;
 
-    test.assertNotEqual(tabs.activeTab.url, url, "tab is not the active tab");
-    tabs.on('ready', function onReady(tab) {
-      tabs.removeListener('ready', onReady);
-      test.assertEqual(tabs.activeTab.url, tab.url, "tab is now the active tab");
-      let secondOnCloseCalled = false;
-      tab.close(function() {
-        closeBrowserWindow(window, function() {
-          test.assert(secondOnCloseCalled,
-            "The immediate second call to tab.close gots its callback fired");
-          test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
-          test.done()
-        });
+    // Bug 699450: Multiple calls to tab.close should not throw
+    tab.close(function() secondOnCloseCalled = true);
+    try {
+      tab.close(function () {
+        test.assert(secondOnCloseCalled,
+          "The immediate second call to tab.close gots its callback fired");
+        test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+        test.done();
       });
+    }
+    catch(e) {
+      test.fail("second call to tab.close() thrown an exception: " + e);
+    }
+    test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
+  });
 
-      // Bug 699450: Multiple calls to tab should not throw
-      try {
-        tab.close(function () {
-          secondOnCloseCalled = true;
-        });
-      }
-      catch(e) {
-        test.fail("second call to tab.close() thrown an exception: " + e);
-      }
-      test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
-    });
-
-    tabs.open(url);
-  });
+  tabs.open(url);
 };
 
 // TEST: tab.move()
 exports.testTabMove = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+
+  open().then(function(window) {
     let url = "data:text/html;charset=utf-8,foo";
 
     tabs.open({
       url: url,
       onOpen: function(tab) {
         test.assertEqual(tab.index, 1, "tab index before move matches");
         tab.index = 0;
         test.assertEqual(tab.index, 0, "tab index after move matches");
@@ -330,186 +257,141 @@ exports.testTabMove = function(test) {
       }
     });
   });
 };
 
 // TEST: open tab with default options
 exports.testOpen = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = 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(window.content.location, url, "URL of active tab in the current window matches");
-        test.assertEqual(tab.isPinned, false, "The new tab is not pinned");
 
-        closeBrowserWindow(window, function() test.done());
-      }
-    });
+  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(tab.isPinned, false, "The new tab is not pinned");
+
+      tab.close(function() test.done());
+    }
   });
 };
 
-// TEST: open pinned tab
+// TEST: opening a pinned tab
 exports.testOpenPinned = function(test) {
-  const xulApp = require("sdk/system/xul-app");
-  if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
-    // test tab pinning
-    test.waitUntilDone();
-    openBrowserWindow(function(window, browser) {
-      let tabs = require("sdk/tabs");
-      let url = "data:text/html;charset=utf-8,default";
-      tabs.open({
-        url: url,
-        isPinned: true,
-        onOpen: function(tab) {
-          test.assertEqual(tab.isPinned, true, "The new tab is pinned");
-          closeBrowserWindow(window, function() test.done());
-        }
-      });
-    });
-  }
-  else {
-    test.pass("Pinned tabs are not supported in this application.");
-  }
+  test.waitUntilDone();
+
+  let url = "data:text/html;charset=utf-8,default";
+  tabs.open({
+    url: url,
+    isPinned: true,
+    onOpen: function(tab) {
+      test.assertEqual(tab.isPinned, true, "The new tab is pinned");
+      tab.close(test.done.bind(test));
+    }
+  });
 };
 
 // TEST: pin/unpin opened tab
 exports.testPinUnpin = function(test) {
-  const xulApp = require("sdk/system/xul-app");
-  if (xulApp.versionInRange(xulApp.platformVersion, "2.0b2", "*")) {
-    test.waitUntilDone();
-    openBrowserWindow(function(window, browser) {
-      let tabs = 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, true, "The tab was pinned correctly");
-          tab.unpin();
-          test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
-          closeBrowserWindow(window, function() test.done());
-        }
-      });
-    });
-  }
-  else {
-    test.pass("Pinned tabs are not supported in this application.");
-  }
-};
+  test.waitUntilDone();
+
+  let url = "data:text/html;charset=utf-8,default";
+  tabs.open({
+    url: url,
+    inBackground: true,
+    onOpen: function(tab) {
+      tab.pin();
+      test.assertEqual(tab.isPinned, true, "The tab was pinned correctly");
+      tab.unpin();
+      test.assertEqual(tab.isPinned, false, "The tab was unpinned correctly");
+      tab.close(test.done.bind(test));
+    }
+  });
+}
 
 // TEST: open tab in background
 exports.testInBackground = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-    let activeUrl = tabs.activeTab.url;
-    let url = "data:text/html;charset=utf-8,background";
-    test.assertEqual(activeWindow, window, "activeWindow matches this window");
-    tabs.on('ready', function onReady(tab) {
-      tabs.removeListener('ready', onReady);
-      test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
-      test.assertEqual(tab.url, url, "URL of the new background tab matches");
-      test.assertEqual(activeWindow, window, "a new window was not opened");
-      test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
-      closeBrowserWindow(window, function() test.done());
-    });
-    tabs.open({
-      url: url,
-      inBackground: true
-    });
+
+  let window = getMostRecentBrowserWindow();
+  let activeUrl = tabs.activeTab.url;
+  let url = "data:text/html;charset=utf-8,background";
+  test.assertEqual(activeWindow, window, "activeWindow matches this window");
+  tabs.on('ready', function onReady(tab) {
+    tabs.removeListener('ready', onReady);
+    test.assertEqual(tabs.activeTab.url, activeUrl, "URL of active tab has not changed");
+    test.assertEqual(tab.url, url, "URL of the new background tab matches");
+    test.assertEqual(activeWindow, window, "a new window was not opened");
+    test.assertNotEqual(tabs.activeTab.url, url, "URL of active tab is not the new URL");
+    tab.close(test.done.bind(test));
   });
-};
+
+  tabs.open({
+    url: url,
+    inBackground: true
+  });
+}
 
 // TEST: open tab in new window
 exports.testOpenInNewWindow = function(test) {
   test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
+
+  let startWindowCount = windows().length;
 
-    let cache = [];
-    let windowUtils = require("sdk/deprecated/window-utils");
-    let wt = new windowUtils.WindowTracker({
-      onTrack: function(win) {
-        cache.push(win);
-      },
-      onUntrack: function(win) {
-        cache.splice(cache.indexOf(win), 1)
-      }
-    });
-    let startWindowCount = cache.length;
+  let url = "data:text/html;charset=utf-8,testOpenInNewWindow";
+  tabs.open({
+    url: url,
+    inNewWindow: true,
+    onReady: function(tab) {
+      let newWindow = getOwnerWindow(tab);
+      test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
 
-    let url = "data:text/html;charset=utf-8,newwindow";
-    tabs.open({
-      url: url,
-      inNewWindow: true,
-      onReady: function(tab) {
-        let newWindow = cache[cache.length - 1];
-        test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+      onFocus(newWindow).then(function() {
         test.assertEqual(activeWindow, newWindow, "new window is active");
         test.assertEqual(tab.url, url, "URL of the new tab matches");
         test.assertEqual(newWindow.content.location, url, "URL of new tab in new window matches");
         test.assertEqual(tabs.activeTab.url, url, "URL of activeTab matches");
-        for (let i in cache) cache[i] = null;
-        wt.unload();
-        closeBrowserWindow(newWindow, function() {
-          closeBrowserWindow(window, function() test.done());
-        });
-      }
-    });
+
+        closeBrowserWindow(newWindow, test.done.bind(test));
+      }, test.fail).then(null, test.fail);
+    }
   });
-};
+
+}
 
 // Test tab.open inNewWindow + onOpen combination
 exports.testOpenInNewWindowOnOpen = function(test) {
   test.waitUntilDone();
-  let tabs = require("sdk/tabs");
+
+  let startWindowCount = windows().length;
 
-  openBrowserWindow(function(window, browser) {
-    let cache = [];
-    let windowUtils = require("sdk/deprecated/window-utils");
-    let wt = new windowUtils.WindowTracker({
-      onTrack: function(win) {
-        cache.push(win);
-      },
-      onUntrack: function(win) {
-        cache.splice(cache.indexOf(win), 1)
-      }
-    });
-    let startWindowCount = cache.length;
+  let url = "data:text/html;charset=utf-8,newwindow";
+  tabs.open({
+    url: url,
+    inNewWindow: true,
+    onOpen: function(tab) {
+      let newWindow = getOwnerWindow(tab);
 
-    let url = "data:text/html;charset=utf-8,newwindow";
-    tabs.open({
-      url: url,
-      inNewWindow: true,
-      onOpen: function(tab) {
-        let newWindow = cache[cache.length - 1];
-        test.assertEqual(cache.length, startWindowCount + 1, "a new window was opened");
+      onFocus(newWindow).then(function() {
+        test.assertEqual(windows().length, startWindowCount + 1, "a new window was opened");
         test.assertEqual(activeWindow, newWindow, "new window is active");
 
-        for (let i in cache) cache[i] = null;
-        wt.unload();
-
         closeBrowserWindow(newWindow, function() {
-          closeBrowserWindow(window, function() test.done());
+          test.done();
         });
-      }
-    });
+      });
+    }
   });
 };
 
 // TEST: onOpen event handler
 exports.testTabsEvent_onOpen = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,1";
     let eventCount = 0;
 
     // add listener via property assignment
     function listener1(tab) {
       eventCount++;
     };
     tabs.on('open', listener1);
@@ -525,17 +407,16 @@ exports.testTabsEvent_onOpen = function(
     tabs.open(url);
   });
 };
 
 // TEST: onClose event handler
 exports.testTabsEvent_onClose = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,onclose";
     let eventCount = 0;
 
     // add listener via property assignment
     function listener1(tab) {
       eventCount++;
     }
     tabs.on('close', listener1);
@@ -557,18 +438,16 @@ exports.testTabsEvent_onClose = function
   });
 };
 
 // TEST: onClose event handler when a window is closed
 exports.testTabsEvent_onCloseWindow = function(test) {
   test.waitUntilDone();
 
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
-
     let closeCount = 0, individualCloseCount = 0;
     function listener() {
       closeCount++;
     }
     tabs.on('close', listener);
 
     // One tab is already open with the window
     let openTabs = 1;
@@ -611,17 +490,16 @@ exports.testTabsEvent_onCloseWindow = fu
 
   });
 }
 
 // TEST: onReady event handler
 exports.testTabsEvent_onReady = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,onready";
     let eventCount = 0;
 
     // add listener via property assignment
     function listener1(tab) {
       eventCount++;
     };
     tabs.on('ready', listener1);
@@ -637,17 +515,16 @@ exports.testTabsEvent_onReady = function
     tabs.open(url);
   });
 };
 
 // TEST: onActivate event handler
 exports.testTabsEvent_onActivate = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,onactivate";
     let eventCount = 0;
 
     // add listener via property assignment
     function listener1(tab) {
       eventCount++;
     };
     tabs.on('activate', listener1);
@@ -663,17 +540,16 @@ exports.testTabsEvent_onActivate = funct
     tabs.open(url);
   });
 };
 
 // onDeactivate event handler
 exports.testTabsEvent_onDeactivate = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,ondeactivate";
     let eventCount = 0;
 
     // add listener via property assignment
     function listener1(tab) {
       eventCount++;
     };
     tabs.on('deactivate', listener1);
@@ -694,17 +570,16 @@ exports.testTabsEvent_onDeactivate = fun
     tabs.open(url);
   });
 };
 
 // pinning
 exports.testTabsEvent_pinning = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,1";
 
     tabs.on('open', function onOpen(tab) {
       tabs.removeListener('open', onOpen);
       tab.pin();
     });
 
     tabs.on('pinned', function onPinned(tab) {
@@ -722,17 +597,16 @@ exports.testTabsEvent_pinning = function
     tabs.open(url);
   });
 };
 
 // TEST: per-tab event handlers
 exports.testPerTabEvents = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    var tabs = require("sdk/tabs");
     let eventCount = 0;
 
     tabs.open({
       url: "data:text/html;charset=utf-8,foo",
       onOpen: function(tab) {
         // add listener via property assignment
         function listener1() {
           eventCount++;
@@ -750,18 +624,16 @@ exports.testPerTabEvents = function(test
     });
   });
 };
 
 exports.testAttachOnOpen = function (test) {
   // Take care that attach has to be called on tab ready and not on tab open.
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-
     tabs.open({
       url: "data:text/html;charset=utf-8,foobar",
       onOpen: function (tab) {
         let worker = tab.attach({
           contentScript: 'self.postMessage(document.location.href); ',
           onMessage: function (msg) {
             test.assertEqual(msg, "about:blank",
               "Worker document url is about:blank on open");
@@ -774,17 +646,16 @@ exports.testAttachOnOpen = function (tes
 
   });
 }
 
 exports.testAttachOnMultipleDocuments = function (test) {
   // Example of attach that process multiple tab documents
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
     let firstLocation = "data:text/html;charset=utf-8,foobar";
     let secondLocation = "data:text/html;charset=utf-8,bar";
     let thirdLocation = "data:text/html;charset=utf-8,fox";
     let onReadyCount = 0;
     let worker1 = null;
     let worker2 = null;
     let detachEventCount = 0;
     tabs.open({
@@ -856,17 +727,16 @@ exports.testAttachOnMultipleDocuments = 
   });
 }
 
 
 exports.testAttachWrappers = function (test) {
   // Check that content script has access to wrapped values by default
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
     let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " +
                    "                       document.getElementById = 3;</script>";
     let count = 0;
 
     tabs.open({
       url: document,
       onReady: function (tab) {
         let worker = tab.attach({
@@ -890,17 +760,16 @@ exports.testAttachWrappers = function (t
 
 /*
 // We do not offer unwrapped access to DOM since bug 601295 landed
 // See 660780 to track progress of unwrap feature
 exports.testAttachUnwrapped = function (test) {
   // Check that content script has access to unwrapped values through unsafeWindow
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
     let document = "data:text/html;charset=utf-8,<script>var globalJSVar=true;</script>";
     let count = 0;
 
     tabs.open({
       url: document,
       onReady: function (tab) {
         let worker = tab.attach({
           contentScript: 'try {' +
@@ -919,17 +788,16 @@ exports.testAttachUnwrapped = function (
   });
 }
 */
 
 exports['test window focus changes active tab'] = function(test) {
   test.waitUntilDone();
   let win1 = openBrowserWindow(function() {
     let win2 = openBrowserWindow(function() {
-      let tabs = require("sdk/tabs");
       tabs.on("activate", function onActivate() {
         tabs.removeListener("activate", onActivate);
         test.pass("activate was called on windows focus change.");
         closeBrowserWindow(win1, function() {
           closeBrowserWindow(win2, function() { test.done(); });
         });
       });
       win1.focus();
@@ -988,17 +856,16 @@ exports['test unique tab ids'] = functio
   });
 }
 
 // related to Bug 671305
 exports.testOnLoadEventWithDOM = function(test) {
   test.waitUntilDone();
 
   openBrowserWindow(function(window, browser) {
-    let tabs = require('sdk/tabs');
     let count = 0;
     tabs.on('load', function onLoad(tab) {
       test.assertEqual(tab.title, 'tab', 'tab passed in as arg, load called');
       if (!count++) {
         tab.reload();
       }
       else {
         // end of test
@@ -1016,17 +883,16 @@ exports.testOnLoadEventWithDOM = functio
   });
 };
 
 // related to Bug 671305
 exports.testOnLoadEventWithImage = function(test) {
   test.waitUntilDone();
 
   openBrowserWindow(function(window, browser) {
-    let tabs = require('sdk/tabs');
     let count = 0;
     tabs.on('load', function onLoad(tab) {
       if (!count++) {
         tab.reload();
       }
       else {
         // end of test
         tabs.removeListener('load', onLoad);
@@ -1045,18 +911,16 @@ exports.testOnLoadEventWithImage = funct
 
 exports.testOnPageShowEvent = function (test) {
   test.waitUntilDone();
 
   let firstUrl = 'data:text/html;charset=utf-8,First';
   let secondUrl = 'data:text/html;charset=utf-8,Second';
 
   openBrowserWindow(function(window, browser) {
-    let tabs = require('sdk/tabs');
-
     let counter = 0;
     tabs.on('pageshow', function onPageShow(tab, persisted) {
       counter++;
       if (counter === 1) {
         test.assert(!persisted, 'page should not be cached on initial load');
         tab.url = secondUrl;
       }
       else if (counter === 2) {
@@ -1145,12 +1009,8 @@ function openBrowserWindow(callback, url
 // Helper for calling code at window close
 function closeBrowserWindow(window, callback) {
   window.addEventListener("unload", function unload() {
     window.removeEventListener("unload", unload, false);
     callback();
   }, false);
   window.close();
 }
-
-// Test disabled on Linux because of bug 882867
-if (require("sdk/system/runtime").OS == "Linux")
-  module.exports = {};
--- a/addon-sdk/source/test/test-addon-page.js
+++ b/addon-sdk/source/test/test-addon-page.js
@@ -50,16 +50,17 @@ exports['test that add-on page has no ch
     activateTab(tab);
 
     assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTab(tab);
     assert.ok(isChromeVisible(window), 'chrome is visible again');
     loader.unload();
+    assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
     done();
   });
 };
 
 exports['test that add-on page with hash has no chrome'] = function(assert, done) {
   let { loader } = LoaderWithHookedConsole(module);
   loader.require('sdk/addon-page');
 
@@ -74,16 +75,17 @@ exports['test that add-on page with hash
     activateTab(tab);
 
     assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTab(tab);
     assert.ok(isChromeVisible(window), 'chrome is visible again');
     loader.unload();
+    assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
     done();
   });
 };
 
 exports['test that add-on page with querystring has no chrome'] = function(assert, done) {
   let { loader } = LoaderWithHookedConsole(module);
   loader.require('sdk/addon-page');
 
@@ -98,16 +100,17 @@ exports['test that add-on page with quer
     activateTab(tab);
 
     assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTab(tab);
     assert.ok(isChromeVisible(window), 'chrome is visible again');
     loader.unload();
+    assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
     done();
   });
 };
 
 exports['test that add-on page with hash and querystring has no chrome'] = function(assert, done) {
   let { loader } = LoaderWithHookedConsole(module);
   loader.require('sdk/addon-page');
 
@@ -122,16 +125,17 @@ exports['test that add-on page with hash
     activateTab(tab);
 
     assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTab(tab);
     assert.ok(isChromeVisible(window), 'chrome is visible again');
     loader.unload();
+    assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
     done();
   });
 };
 
 exports['test that malformed uri is not an addon-page'] = function(assert, done) {
   let { loader } = LoaderWithHookedConsole(module);
   loader.require('sdk/addon-page');
 
@@ -146,24 +150,9 @@ exports['test that malformed uri is not 
     assert.ok(isChromeVisible(window), 'chrome is visible for malformed uri');
 
     closeTab(tab);
     loader.unload();
     done();
   });
 };
 
-exports['test that add-on pages are closed on unload'] = function(assert, done) {
-  let { loader } = LoaderWithHookedConsole(module);
-  loader.require('sdk/addon-page');
-
-  tabs.open({
-    url: uri,
-    onReady: function listener(tab) {
-      loader.unload();
-      assert.ok(!isTabOpen(tab), 'add-on page tabs are closed on unload');
-
-      done();
-    }
-  });
-};
-
 require('test').run(exports);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-fs.js
@@ -0,0 +1,462 @@
+/* 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 { pathFor } = require("sdk/system");
+const fs = require("sdk/io/fs");
+const url = require("sdk/url");
+const path = require("sdk/fs/path");
+const { Buffer } = require("sdk/io/buffer");
+
+// Use profile directory to list / read / write files.
+const profilePath = pathFor("ProfD");
+const fileNameInProfile = "compatibility.ini";
+const dirNameInProfile = "extensions";
+const filePathInProfile = path.join(profilePath, fileNameInProfile);
+const dirPathInProfile = path.join(profilePath, dirNameInProfile);
+const mkdirPath = path.join(profilePath, "sdk-fixture-mkdir");
+const writePath = path.join(profilePath, "sdk-fixture-writeFile");
+const unlinkPath = path.join(profilePath, "sdk-fixture-unlink");
+const truncatePath = path.join(profilePath, "sdk-fixture-truncate");
+const renameFromPath = path.join(profilePath, "sdk-fixture-rename-from");
+const renameToPath = path.join(profilePath, "sdk-fixture-rename-to");
+
+const profileEntries = [
+  "compatibility.ini",
+  "extensions",
+  "extensions.ini",
+  "prefs.js"
+  // There are likely to be a lot more files but we can't really
+  // on consistent list so we limit to this.
+];
+
+exports["test readir"] = function(assert, end) {
+  var async = false;
+  fs.readdir(profilePath, function(error, entries) {
+    assert.ok(async, "readdir is async");
+    assert.ok(!error, "there is no error when reading directory");
+    assert.ok(profileEntries.length <= entries.length,
+              "got et least number of entries we expect");
+    assert.ok(profileEntries.every(function(entry) {
+                return entries.indexOf(entry) >= 0;
+              }), "all profiles are present");
+    end();
+  });
+
+  async = true;
+};
+
+exports["test readdir error"] = function(assert, end) {
+  var async = false;
+  var path = profilePath + "-does-not-exists";
+  fs.readdir(path, function(error, entries) {
+    assert.ok(async, "readdir is async");
+    assert.equal(error.message, "ENOENT, readdir " + path);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+    end();
+  });
+
+  async = true;
+};
+
+exports["test readdirSync"] = function(assert) {
+  var async = false;
+  var entries = fs.readdirSync(profilePath);
+  assert.ok(profileEntries.length <= entries.length,
+            "got et least number of entries we expect");
+  assert.ok(profileEntries.every(function(entry) {
+    return entries.indexOf(entry) >= 0;
+  }), "all profiles are present");
+};
+
+exports["test readirSync error"] = function(assert) {
+  var async = false;
+  var path = profilePath + "-does-not-exists";
+  try {
+    fs.readdirSync(path);
+    assert.fail(Error("No error was thrown"));
+  } catch (error) {
+    assert.equal(error.message, "ENOENT, readdir " + path);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+  }
+};
+
+exports["test readFile"] = function(assert, end) {
+  let async = false;
+  fs.readFile(filePathInProfile, function(error, content) {
+    assert.ok(async, "readFile is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(Buffer.isBuffer(content), "readFile returns buffer");
+    assert.ok(typeof(content.length) === "number", "buffer has length");
+    assert.ok(content.toString().indexOf("[Compatibility]") >= 0,
+              "content contains expected data");
+    end();
+  });
+  async = true;
+};
+
+exports["test readFile error"] = function(assert, end) {
+  let async = false;
+  let path = filePathInProfile + "-does-not-exists";
+  fs.readFile(path, function(error, content) {
+    assert.ok(async, "readFile is async");
+    assert.equal(error.message, "ENOENT, open " + path);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+
+    end();
+  });
+  async = true;
+};
+
+exports["test readFileSync not implemented"] = function(assert) {
+  let buffer = fs.readFileSync(filePathInProfile);
+  assert.ok(buffer.toString().indexOf("[Compatibility]") >= 0,
+            "read content");
+};
+
+exports["test fs.stat file"] = function(assert, end) {
+  let async = false;
+  let path = filePathInProfile;
+  fs.stat(path, function(error, stat) {
+    assert.ok(async, "fs.stat is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!stat.isDirectory(), "not a dir");
+    assert.ok(stat.isFile(), "is a file");
+    assert.ok(!stat.isSymbolicLink(), "isn't a symlink");
+    assert.ok(typeof(stat.size) === "number", "size is a number");
+    assert.ok(stat.exists === true, "file exists");
+    assert.ok(typeof(stat.isBlockDevice()) === "boolean");
+    assert.ok(typeof(stat.isCharacterDevice()) === "boolean");
+    assert.ok(typeof(stat.isFIFO()) === "boolean");
+    assert.ok(typeof(stat.isSocket()) === "boolean");
+    assert.ok(typeof(stat.hidden) === "boolean");
+    assert.ok(typeof(stat.writable) === "boolean")
+    assert.ok(stat.readable === true);
+
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.stat dir"] = function(assert, end) {
+  let async = false;
+  let path = dirPathInProfile;
+  fs.stat(path, function(error, stat) {
+    assert.ok(async, "fs.stat is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(stat.isDirectory(), "is a dir");
+    assert.ok(!stat.isFile(), "not a file");
+    assert.ok(!stat.isSymbolicLink(), "isn't a symlink");
+    assert.ok(typeof(stat.size) === "number", "size is a number");
+    assert.ok(stat.exists === true, "file exists");
+    assert.ok(typeof(stat.isBlockDevice()) === "boolean");
+    assert.ok(typeof(stat.isCharacterDevice()) === "boolean");
+    assert.ok(typeof(stat.isFIFO()) === "boolean");
+    assert.ok(typeof(stat.isSocket()) === "boolean");
+    assert.ok(typeof(stat.hidden) === "boolean");
+    assert.ok(typeof(stat.writable) === "boolean")
+    assert.ok(typeof(stat.readable) === "boolean");
+
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.stat error"] = function(assert, end) {
+  let async = false;
+  let path = filePathInProfile + "-does-not-exists";
+  fs.stat(path, function(error, stat) {
+    assert.ok(async, "fs.stat is async");
+    assert.equal(error.message, "ENOENT, stat " + path);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.exists NO"] = function(assert, end) {
+  let async = false
+  let path = filePathInProfile + "-does-not-exists";
+  fs.exists(path, function(error, value) {
+    assert.ok(async, "fs.exists is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!value, "file does not exists");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.exists YES"] = function(assert, end) {
+  let async = false
+  let path = filePathInProfile
+  fs.exists(path, function(error, value) {
+    assert.ok(async, "fs.exists is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(value, "file exists");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.exists NO"] = function(assert, end) {
+  let async = false
+  let path = filePathInProfile + "-does-not-exists";
+  fs.exists(path, function(error, value) {
+    assert.ok(async, "fs.exists is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!value, "file does not exists");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.existsSync"] = function(assert) {
+  let path = filePathInProfile
+  assert.equal(fs.existsSync(path), true, "exists");
+  assert.equal(fs.existsSync(path + "-does-not-exists"), false, "exists");
+};
+
+exports["test fs.mkdirSync fs.rmdirSync"] = function(assert) {
+  let path = mkdirPath;
+
+  assert.equal(fs.existsSync(path), false, "does not exists");
+  fs.mkdirSync(path);
+  assert.equal(fs.existsSync(path), true, "dir was created");
+  try {
+    fs.mkdirSync(path);
+    assert.fail(Error("mkdir on existing should throw"));
+  } catch (error) {
+    assert.equal(error.message, "EEXIST, mkdir " + path);
+    assert.equal(error.code, "EEXIST", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 47, "error has a errno");
+  }
+  fs.rmdirSync(path);
+  assert.equal(fs.existsSync(path), false, "dir was removed");
+};
+
+exports["test fs.mkdir"] = function(assert, end) {
+  let path = mkdirPath;
+
+  if (!fs.existsSync(path)) {
+    let async = false;
+    fs.mkdir(path, function(error) {
+      assert.ok(async, "mkdir is async");
+      assert.ok(!error, "no error");
+      assert.equal(fs.existsSync(path), true, "dir was created");
+      fs.rmdirSync(path);
+      assert.equal(fs.existsSync(path), false, "dir was removed");
+      end();
+    });
+    async = true;
+  }
+};
+
+exports["test fs.mkdir error"] = function(assert, end) {
+  let path = mkdirPath;
+
+  if (!fs.existsSync(path)) {
+    fs.mkdirSync(path);
+    let async = false;
+    fs.mkdir(path, function(error) {
+      assert.ok(async, "mkdir is async");
+      assert.equal(error.message, "EEXIST, mkdir " + path);
+      assert.equal(error.code, "EEXIST", "error has a code");
+      assert.equal(error.path, path, "error has a path");
+      assert.equal(error.errno, 47, "error has a errno");
+      fs.rmdirSync(path);
+      assert.equal(fs.existsSync(path), false, "dir was removed");
+      end();
+    });
+    async = true;
+  }
+};
+
+exports["test fs.rmdir"] = function(assert, end) {
+  let path = mkdirPath;
+
+  if (!fs.existsSync(path)) {
+    fs.mkdirSync(path);
+    assert.equal(fs.existsSync(path), true, "dir exists");
+    let async = false;
+    fs.rmdir(path, function(error) {
+      assert.ok(async, "mkdir is async");
+      assert.ok(!error, "no error");
+      assert.equal(fs.existsSync(path), false, "dir was removed");
+      end();
+    });
+    async = true;
+  }
+};
+
+
+exports["test fs.rmdir error"] = function(assert, end) {
+  let path = mkdirPath;
+
+  if (!fs.existsSync(path)) {
+    assert.equal(fs.existsSync(path), false, "dir doesn't exists");
+    let async = false;
+    fs.rmdir(path, function(error) {
+      assert.ok(async, "mkdir is async");
+      assert.equal(error.message, "ENOENT, remove " + path);
+      assert.equal(error.code, "ENOENT", "error has a code");
+      assert.equal(error.path, path, "error has a path");
+      assert.equal(error.errno, 34, "error has a errno");
+      assert.equal(fs.existsSync(path), false, "dir is removed");
+      end();
+    });
+    async = true;
+  }
+};
+
+exports["test fs.truncateSync fs.unlinkSync"] = function(assert) {
+  let path = truncatePath;
+
+  assert.equal(fs.existsSync(path), false, "does not exists");
+  fs.truncateSync(path);
+  assert.equal(fs.existsSync(path), true, "file was created");
+  fs.truncateSync(path);
+  fs.unlinkSync(path);
+  assert.equal(fs.existsSync(path), false, "file was removed");
+};
+
+
+exports["test fs.truncate"] = function(assert, end) {
+  let path = truncatePath;
+  if (!fs.existsSync(path)) {
+    let async = false;
+    fs.truncate(path, 0, function(error) {
+      assert.ok(async, "truncate is async");
+      console.log(error);
+      assert.ok(!error, "no error");
+      assert.equal(fs.existsSync(path), true, "file was created");
+      fs.unlinkSync(path);
+      assert.equal(fs.existsSync(path), false, "file was removed");
+      end();
+    })
+    async = true;
+  }
+};
+
+exports["test fs.unlink"] = function(assert, end) {
+  let path = unlinkPath;
+  let async = false;
+  assert.ok(!fs.existsSync(path), "file doesn't exists yet");
+  fs.truncateSync(path, 0);
+  assert.ok(fs.existsSync(path), "file was created");
+  fs.unlink(path, function(error) {
+    assert.ok(async, "fs.unlink is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!fs.existsSync(path), "file was removed");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.unlink error"] = function(assert, end) {
+  let path = unlinkPath;
+  let async = false;
+  assert.ok(!fs.existsSync(path), "file doesn't exists yet");
+  fs.unlink(path, function(error) {
+    assert.ok(async, "fs.unlink is async");
+    assert.equal(error.message, "ENOENT, remove " + path);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, path, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.rename"] = function(assert, end) {
+  let fromPath = renameFromPath;
+  let toPath = renameToPath;
+
+  fs.truncateSync(fromPath);
+  assert.ok(fs.existsSync(fromPath), "source file exists");
+  assert.ok(!fs.existsSync(toPath), "destination doesn't exists");
+  var async = false;
+  fs.rename(fromPath, toPath, function(error) {
+    assert.ok(async, "fs.rename is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!fs.existsSync(fromPath), "source path no longer exists");
+    assert.ok(fs.existsSync(toPath), "destination file exists");
+    fs.unlinkSync(toPath);
+    assert.ok(!fs.existsSync(toPath), "cleaned up properly");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.rename (missing source file)"] = function(assert, end) {
+  let fromPath = renameFromPath;
+  let toPath = renameToPath;
+
+  assert.ok(!fs.existsSync(fromPath), "source file doesn't exists");
+  assert.ok(!fs.existsSync(toPath), "destination doesn't exists");
+  var async = false;
+  fs.rename(fromPath, toPath, function(error) {
+    assert.ok(async, "fs.rename is async");
+    assert.equal(error.message, "ENOENT, rename " + fromPath);
+    assert.equal(error.code, "ENOENT", "error has a code");
+    assert.equal(error.path, fromPath, "error has a path");
+    assert.equal(error.errno, 34, "error has a errno");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.rename (existing target file)"] = function(assert, end) {
+  let fromPath = renameFromPath;
+  let toPath = renameToPath;
+
+  fs.truncateSync(fromPath);
+  fs.truncateSync(toPath);
+  assert.ok(fs.existsSync(fromPath), "source file exists");
+  assert.ok(fs.existsSync(toPath), "destination file exists");
+  var async = false;
+  fs.rename(fromPath, toPath, function(error) {
+    assert.ok(async, "fs.rename is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(!fs.existsSync(fromPath), "source path no longer exists");
+    assert.ok(fs.existsSync(toPath), "destination file exists");
+    fs.unlinkSync(toPath);
+    assert.ok(!fs.existsSync(toPath), "cleaned up properly");
+    end();
+  });
+  async = true;
+};
+
+exports["test fs.writeFile"] = function(assert, end) {
+  let path = writePath;
+  let content = ["hello world",
+                 "this is some text"].join("\n");
+
+  var async = false;
+  fs.writeFile(path, content, function(error) {
+    assert.ok(async, "fs write is async");
+    assert.ok(!error, "error is falsy");
+    assert.ok(fs.existsSync(path), "file was created");
+    assert.equal(fs.readFileSync(path).toString(),
+                 content,
+                 "contet was written");
+    fs.unlinkSync(path);
+    assert.ok(!fs.exists(path), "file was removed");
+
+    end();
+  });
+  async = true;
+};
+
+require("test").run(exports);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-host-events.js
@@ -0,0 +1,99 @@
+/* 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 { defer, all } = require('sdk/core/promise');
+const { setTimeout } = require('sdk/timers');
+const { request, response } = require('sdk/addon/host');
+const { send } = require('sdk/addon/events');
+const { filter } = require('sdk/event/utils');
+const { on, emit, off } = require('sdk/event/core');
+
+let stream = filter(request, (data) => /sdk-x-test/.test(data.event));
+
+exports.testSend = function (assert, done) {
+  on(stream, 'data', handle);
+  send('sdk-x-test-simple', { title: 'my test data' }).then((data) => {
+    assert.equal(data.title, 'my response', 'response is handled');
+    off(stream, 'data', handle);
+    done();
+  }, (reason) => {
+    assert.fail('should not call reject');
+  });
+  function handle (e) {
+    assert.equal(e.event, 'sdk-x-test-simple', 'correct event name');
+    assert.ok(e.id != null, 'message has an ID');
+    assert.equal(e.data.title, 'my test data', 'serialized data passes');
+    e.data.title = 'my response';
+    emit(response, 'data', e);
+  }
+};
+
+exports.testSendError = function (assert, done) {
+  on(stream, 'data', handle);
+  send('sdk-x-test-error', { title: 'my test data' }).then((data) => {
+    assert.fail('should not call success');
+  }, (reason) => {
+    assert.equal(reason, 'ErrorInfo', 'should reject with error/reason');
+    off(stream, 'data', handle);
+    done();
+  });
+  function handle (e) {
+    e.error = 'ErrorInfo';
+    emit(response, 'data', e);
+  }
+};
+
+exports.testMultipleSends = function (assert, done) {
+  let count = 0;
+  let ids = [];
+  on(stream, 'data', handle);
+  ['firefox', 'thunderbird', 'rust'].map(data =>
+    send('sdk-x-test-multi', { data: data }).then(val => {
+    assert.ok(val === 'firefox' || val === 'rust', 'successful calls resolve correctly');
+    if (++count === 3) {
+      off(stream, 'data', handle);
+      done();
+    }
+  }, reason => {
+    assert.equal(reason.error, 'too blue', 'rejected calls are rejected');
+    if (++count === 3) {
+      off(stream, 'data', handle);
+      done();
+    }
+  }));
+
+  function handle (e) {
+    if (e.data !== 'firefox' || e.data !== 'rust')
+      e.error = { data: e.data, error: 'too blue' };
+    assert.ok(!~ids.indexOf(e.id), 'ID should be unique');
+    assert.equal(e.event, 'sdk-x-test-multi', 'has event name');
+    ids.push(e.id);
+    emit(response, 'data', e);
+  }
+};
+
+exports.testSerialization = function (assert, done) {
+  on(stream, 'data', handle);
+  let object = { title: 'my test data' };
+  let resObject;
+  send('sdk-x-test-serialize', object).then(data => {
+    data.title = 'another title';
+    assert.equal(object.title, 'my test data', 'original object not modified');
+    assert.equal(resObject.title, 'new title', 'object passed by value from host');
+    off(stream, 'data', handle);
+    done();
+  }, (reason) => {
+    assert.fail('should not call reject');
+  });
+  function handle (e) {
+    e.data.title = 'new title';
+    assert.equal(object.title, 'my test data', 'object passed by value to host');
+    resObject = e.data;
+    emit(response, 'data', e);
+  }
+};
+
+require('test').run(exports);
--- a/addon-sdk/source/test/test-match-pattern.js
+++ b/addon-sdk/source/test/test-match-pattern.js
@@ -1,16 +1,16 @@
 /* -*- 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/. */
 "use strict";
 
-const { MatchPattern } = require("sdk/page-mod/match-pattern");
+const { MatchPattern } = require("sdk/util/match-pattern");
 
 exports.testMatchPatternTestTrue = function(test) {
   function ok(pattern, url) {
     let mp = new MatchPattern(pattern);
     test.assert(mp.test(url), pattern + " should match " + url);
   }
 
   ok("*", "http://example.com");
@@ -30,16 +30,18 @@ exports.testMatchPatternTestTrue = funct
 
   ok("http://example.com", "http://example.com");
   ok("http://example.com/ice-cream", "http://example.com/ice-cream");
 
   ok(/.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
   ok(/https:.*zilla.*/, "https://bugzilla.redhat.com/show_bug.cgi?id=569753");
   ok('*.sample.com', 'http://ex.sample.com/foo.html');
   ok('*.amp.le.com', 'http://ex.amp.le.com');
+
+  ok('data:*', 'data:text/html;charset=utf-8,');
 };
 
 exports.testMatchPatternTestFalse = function(test) {
   function ok(pattern, url) {
     let mp = new MatchPattern(pattern);
     test.assert(!mp.test(url), pattern + " should not match " + url);
   }
 
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-object.js
@@ -0,0 +1,35 @@
+/* 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 { merge, extend, has, each } = require('sdk/util/object');
+
+let o = {
+  'paper': 0,
+  'rock': 1,
+  'scissors': 2
+}
+
+//exports.testMerge = function(test) {}
+//exports.testExtend = function(test) {}
+
+exports.testHas = function(test) {
+  test.assertEqual(has(o, 'paper'), true, 'has correctly finds key');
+  test.assertEqual(has(o, 'rock'), true, 'has correctly finds key');
+  test.assertEqual(has(o, 'scissors'), true, 'has correctly finds key');
+  test.assertEqual(has(o, 'nope'), false, 'has correctly does not find key');
+  test.assertEqual(has(o, '__proto__'), false, 'has correctly does not find key');
+  test.assertEqual(has(o, 'isPrototypeOf'), false, 'has correctly does not find key');
+};
+
+exports.testEach = function(test) {
+  var count = 0;
+  var keys = new Set();
+  each(o, function (value, key, object) {
+    keys.add(key);
+    test.assertEqual(o[key], value, 'Key and value pairs passed in');
+    test.assertEqual(o, object, 'Object passed in');
+  });
+  test.assertEqual(keys.size, 3, 'All keys have been iterated upon');
+}
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -138,17 +138,17 @@ exports.testPageModIncludes = function(t
     }
     );
 };
 
 exports.testPageModErrorHandling = function(test) {
   test.assertRaises(function() {
       new PageMod();
     },
-    'pattern is undefined',
+    'The `include` option must always contain atleast one rule',
     "PageMod() throws when 'include' option is not specified.");
 };
 
 /* Tests for internal functions. */
 exports.testCommunication1 = function(test) {
   let workerDone = false,
       callbackDone = null;
 
--- a/addon-sdk/source/test/test-page-worker.js
+++ b/addon-sdk/source/test/test-page-worker.js
@@ -142,17 +142,17 @@ exports.testValidateOptions = function(a
   assert.throws(
     function () Page({ contentURL: 'home' }),
     /The `contentURL` option must be a valid URL\./,
     "Validation correctly denied a non-URL contentURL"
   );
 
   assert.throws(
     function () Page({ onMessage: "This is not a function."}),
-    /The event listener must be a function\./,
+    /The option "onMessage" must be one of the following types: function/,
     "Validation correctly denied a non-function onMessage."
   );
 
   assert.pass("Options validation is working.");
 }
 
 exports.testContentAndAllowGettersAndSetters = function(assert, done) {
   let content = "data:text/html;charset=utf-8,<script>window.localStorage.allowScript=3;</script>";
@@ -297,16 +297,118 @@ exports.testPingPong = function(assert, 
       else {
         assert.ok(message, 'pong', 'Callback from contentScript');
         done();
       }
     }
   });
 };
 
+exports.testRedirect = function (assert, done) {
+  let page = Page({
+    contentURL: 'data:text/html;charset=utf-8,first-page',
+    contentScript: '(function () {' +
+      'if (/first-page/.test(document.location.href)) ' +
+      '  document.location.href = "data:text/html;charset=utf-8,redirect";' +
+      'else ' +
+      '  self.port.emit("redirect", document.location.href);' +
+      '})();'
+  });
+
+  page.port.on('redirect', function (url) {
+    assert.equal(url, 'data:text/html;charset=utf-8,redirect', 'Reinjects contentScript on reload');
+    done();
+  });
+};
+
+exports.testRedirectIncludeArrays = function (assert, done) {
+  let firstURL = 'data:text/html;charset=utf-8,first-page';
+  let page = Page({
+    contentURL: firstURL,
+    contentScript: '(function () {' +
+      'self.port.emit("load", document.location.href);' +
+      '  self.port.on("redirect", function (url) {' +
+      '   document.location.href = url;' +
+      '  })' +
+      '})();',
+    include: ['about:blank', 'data:*']
+  });
+
+  page.port.on('load', function (url) {
+    if (url === firstURL) {
+      page.port.emit('redirect', 'about:blank');
+    } else if (url === 'about:blank') {
+      page.port.emit('redirect', 'about:home');
+      assert.ok('`include` property handles arrays');
+      assert.equal(url, 'about:blank', 'Redirects work with accepted domains');
+      done();
+    } else if (url === 'about:home') {
+      assert.fail('Should not redirect to restricted domain');
+    }
+  });
+};
+
+exports.testRedirectFromWorker = function (assert, done) {
+  let firstURL = 'data:text/html;charset=utf-8,first-page';
+  let secondURL = 'data:text/html;charset=utf-8,second-page';
+  let thirdURL = 'data:text/html;charset=utf-8,third-page';
+  let page = Page({
+    contentURL: firstURL,
+    contentScript: '(function () {' +
+      'self.port.emit("load", document.location.href);' +
+      '  self.port.on("redirect", function (url) {' +
+      '   document.location.href = url;' +
+      '  })' +
+      '})();',
+    include: 'data:*'
+  });
+
+  page.port.on('load', function (url) {
+    if (url === firstURL) {
+      page.port.emit('redirect', secondURL);
+    } else if (url === secondURL) {
+      page.port.emit('redirect', thirdURL);
+    } else if (url === thirdURL) {
+      page.port.emit('redirect', 'about:home');
+      assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
+      done();
+    } else {
+      assert.fail('Should not redirect to unauthorized domains');
+    }
+  });
+};
+
+exports.testRedirectWithContentURL = function (assert, done) {
+  let firstURL = 'data:text/html;charset=utf-8,first-page';
+  let secondURL = 'data:text/html;charset=utf-8,second-page';
+  let thirdURL = 'data:text/html;charset=utf-8,third-page';
+  let page = Page({
+    contentURL: firstURL,
+    contentScript: '(function () {' +
+      'self.port.emit("load", document.location.href);' +
+      '})();',
+    include: 'data:*'
+  });
+
+  page.port.on('load', function (url) {
+    if (url === firstURL) {
+      page.contentURL = secondURL;
+    } else if (url === secondURL) {
+      page.contentURL = thirdURL;
+    } else if (url === thirdURL) {
+      page.contentURL = 'about:home';
+      assert.equal(url, thirdURL, 'Redirects work with accepted domains on include strings');
+      done();
+    } else {
+      assert.fail('Should not redirect to unauthorized domains');
+    }
+  });
+};
+
+
 exports.testMultipleDestroys = function(assert) {
   let page = Page();
   page.destroy();
   page.destroy();
   assert.pass("Multiple destroys should not cause an error");
 };
 
 exports.testContentScriptOptionsOption = function(assert, done) {
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -452,16 +452,34 @@ exports["test Panel Text Color"] = funct
   panel.port.on("color", function (color) {
     assert.equal(color, "rgb(255, 255, 0)",
       "The panel text color style is preserved when a style exists.");
     panel.destroy();
     done();
   });
 };
 
+// Bug 866333
+exports["test watch event name"] = function(assert, done) {
+  const { Panel } = require('sdk/panel');
+
+  let html = "<html><head><style>body {color: yellow}</style></head>" +
+             "<body><p>Foo</p></body></html>";
+
+  let panel = Panel({
+    contentURL: "data:text/html;charset=utf-8," + encodeURI(html),
+    contentScript: "self.port.emit('watch', 'test');"
+  });
+  panel.port.on("watch", function (msg) {
+    assert.equal(msg, "test", 'watch event name works');
+    panel.destroy();
+    done();
+  });
+}
+
 // Bug 696552: Ensure panel.contentURL modification support
 exports["test Change Content URL"] = function(assert, done) {
   const { Panel } = require('sdk/panel');
 
   let panel = Panel({
     contentURL: "about:blank",
     contentScript: "self.port.emit('ready', document.location.href);"
   });
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-path.js
@@ -0,0 +1,285 @@
+// Copyright Joyent, Inc. and other Node contributors.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a
+// copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to permit
+// persons to whom the Software is furnished to do so, subject to the
+// following conditions:
+//
+// The above copyright notice and this permission notice shall be included
+// in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+// USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+// Adapted version of:
+// https://github.com/joyent/node/blob/v0.9.1/test/simple/test-path.js
+
+exports['test path'] = function(assert) {
+
+var system = require('sdk/system');
+var path = require('sdk/fs/path');
+var isWindows = require('sdk/system').platform.indexOf('win') === 0;
+
+
+// POSIX filenames may include control characters
+// c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html
+if (!isWindows) {
+  var controlCharFilename = 'Icon' + String.fromCharCode(13);
+  assert.equal(path.basename('/a/b/' + controlCharFilename),
+               controlCharFilename);
+}
+
+
+assert.equal(path.dirname('/a/b/'), '/a');
+assert.equal(path.dirname('/a/b'), '/a');
+assert.equal(path.dirname('/a'), '/');
+assert.equal(path.dirname('/'), '/');
+
+if (isWindows) {
+  assert.equal(path.dirname('c:\\'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo\\'), 'c:\\');
+  assert.equal(path.dirname('c:\\foo\\bar'), 'c:\\foo');
+  assert.equal(path.dirname('c:\\foo\\bar\\'), 'c:\\foo');
+  assert.equal(path.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar');
+  assert.equal(path.dirname('\\'), '\\');
+  assert.equal(path.dirname('\\foo'), '\\');
+  assert.equal(path.dirname('\\foo\\'), '\\');
+  assert.equal(path.dirname('\\foo\\bar'), '\\foo');
+  assert.equal(path.dirname('\\foo\\bar\\'), '\\foo');
+  assert.equal(path.dirname('\\foo\\bar\\baz'), '\\foo\\bar');
+  assert.equal(path.dirname('c:'), 'c:');
+  assert.equal(path.dirname('c:foo'), 'c:');
+  assert.equal(path.dirname('c:foo\\'), 'c:');
+  assert.equal(path.dirname('c:foo\\bar'), 'c:foo');
+  assert.equal(path.dirname('c:foo\\bar\\'), 'c:foo');
+  assert.equal(path.dirname('c:foo\\bar\\baz'), 'c:foo\\bar');
+  assert.equal(path.dirname('\\\\unc\\share'), '\\\\unc\\share');
+  assert.equal(path.dirname('\\\\unc\\share\\foo'), '\\\\unc\\share\\');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\'), '\\\\unc\\share\\');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar'),
+               '\\\\unc\\share\\foo');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\'),
+               '\\\\unc\\share\\foo');
+  assert.equal(path.dirname('\\\\unc\\share\\foo\\bar\\baz'),
+               '\\\\unc\\share\\foo\\bar');
+}
+
+
+assert.equal(path.extname(''), '');
+assert.equal(path.extname('/path/to/file'), '');
+assert.equal(path.extname('/path/to/file.ext'), '.ext');
+assert.equal(path.extname('/path.to/file.ext'), '.ext');
+assert.equal(path.extname('/path.to/file'), '');
+assert.equal(path.extname('/path.to/.file'), '');
+assert.equal(path.extname('/path.to/.file.ext'), '.ext');
+assert.equal(path.extname('/path/to/f.ext'), '.ext');
+assert.equal(path.extname('/path/to/..ext'), '.ext');
+assert.equal(path.extname('file'), '');
+assert.equal(path.extname('file.ext'), '.ext');
+assert.equal(path.extname('.file'), '');
+assert.equal(path.extname('.file.ext'), '.ext');
+assert.equal(path.extname('/file'), '');
+assert.equal(path.extname('/file.ext'), '.ext');
+assert.equal(path.extname('/.file'), '');
+assert.equal(path.extname('/.file.ext'), '.ext');
+assert.equal(path.extname('.path/file.ext'), '.ext');
+assert.equal(path.extname('file.ext.ext'), '.ext');
+assert.equal(path.extname('file.'), '.');
+assert.equal(path.extname('.'), '');
+assert.equal(path.extname('./'), '');
+assert.equal(path.extname('.file.ext'), '.ext');
+assert.equal(path.extname('.file'), '');
+assert.equal(path.extname('.file.'), '.');
+assert.equal(path.extname('.file..'), '.');
+assert.equal(path.extname('..'), '');
+assert.equal(path.extname('../'), '');
+assert.equal(path.extname('..file.ext'), '.ext');
+assert.equal(path.extname('..file'), '.file');
+assert.equal(path.extname('..file.'), '.');
+assert.equal(path.extname('..file..'), '.');
+assert.equal(path.extname('...'), '.');
+assert.equal(path.extname('...ext'), '.ext');
+assert.equal(path.extname('....'), '.');
+assert.equal(path.extname('file.ext/'), '');
+
+if (isWindows) {
+  // On windows, backspace is a path separator.
+  assert.equal(path.extname('.\\'), '');
+  assert.equal(path.extname('..\\'), '');
+  assert.equal(path.extname('file.ext\\'), '');
+} else {
+  // On unix, backspace is a valid name component like any other character.
+  assert.equal(path.extname('.\\'), '');
+  assert.equal(path.extname('..\\'), '.\\');
+  assert.equal(path.extname('file.ext\\'), '.ext\\');
+}
+
+// path.join tests
+var failures = [];
+var joinTests =
+    // arguments                     result
+    [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'],
+     [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'],
+     [['/foo', '../../../bar'], '/bar'],
+     [['foo', '../../../bar'], '../../bar'],
+     [['foo/', '../../../bar'], '../../bar'],
+     [['foo/x', '../../../bar'], '../bar'],
+     [['foo/x', './bar'], 'foo/x/bar'],
+     [['foo/x/', './bar'], 'foo/x/bar'],
+     [['foo/x/', '.', 'bar'], 'foo/x/bar'],
+     [['./'], './'],
+     [['.', './'], './'],
+     [['.', '.', '.'], '.'],
+     [['.', './', '.'], '.'],
+     [['.', '/./', '.'], '.'],
+     [['.', '/////./', '.'], '.'],
+     [['.'], '.'],
+     [['', '.'], '.'],
+     [['', 'foo'], 'foo'],
+     [['foo', '/bar'], 'foo/bar'],
+     [['', '/foo'], '/foo'],
+     [['', '', '/foo'], '/foo'],
+     [['', '', 'foo'], 'foo'],
+     [['foo', ''], 'foo'],
+     [['foo/', ''], 'foo/'],
+     [['foo', '', '/bar'], 'foo/bar'],
+     [['./', '..', '/foo'], '../foo'],
+     [['./', '..', '..', '/foo'], '../../foo'],
+     [['.', '..', '..', '/foo'], '../../foo'],
+     [['', '..', '..', '/foo'], '../../foo'],
+     [['/'], '/'],
+     [['/', '.'], '/'],
+     [['/', '..'], '/'],
+     [['/', '..', '..'], '/'],
+     [[''], '.'],
+     [['', ''], '.'],
+     [[' /foo'], ' /foo'],
+     [[' ', 'foo'], ' /foo'],
+     [[' ', '.'], ' '],
+     [[' ', '/'], ' /'],
+     [[' ', ''], ' '],
+     // filtration of non-strings.
+     [['x', true, 7, 'y', null, {}], 'x/y']
+    ];
+joinTests.forEach(function(test) {
+  var actual = path.join.apply(path, test[0]);
+  var expected = isWindows ? test[1].replace(/\//g, '\\') : test[1];
+  var message = 'path.join(' + test[0].map(JSON.stringify).join(',') + ')' +
+                '\n  expect=' + JSON.stringify(expected) +
+                '\n  actual=' + JSON.stringify(actual);
+  if (actual !== expected) failures.push('\n' + message);
+  // assert.equal(actual, expected, message);
+});
+assert.equal(failures.length, 0, failures.join(''));
+
+// path normalize tests
+if (isWindows) {
+  assert.equal(path.normalize('./fixtures///b/../b/c.js'),
+               'fixtures\\b\\c.js');
+  assert.equal(path.normalize('/foo/../../../bar'), '\\bar');
+  assert.equal(path.normalize('a//b//../b'), 'a\\b');
+  assert.equal(path.normalize('a//b//./c'), 'a\\b\\c');
+  assert.equal(path.normalize('a//b//.'), 'a\\b');
+  assert.equal(path.normalize('//server/share/dir/file.ext'),
+               '\\\\server\\share\\dir\\file.ext');
+} else {
+  assert.equal(path.normalize('./fixtures///b/../b/c.js'),
+               'fixtures/b/c.js');
+  assert.equal(path.normalize('/foo/../../../bar'), '/bar');
+  assert.equal(path.normalize('a//b//../b'), 'a/b');
+  assert.equal(path.normalize('a//b//./c'), 'a/b/c');
+  assert.equal(path.normalize('a//b//.'), 'a/b');
+}
+
+// path.resolve tests
+if (isWindows) {
+  // windows
+  var resolveTests =
+      // arguments                                    result
+      [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'],
+       [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'],
+       [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'],
+       [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'],
+       [['.'], system.pathFor('CurProcD')],
+       [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative']];
+} else {
+  // Posix
+  var resolveTests =
+      // arguments                                    result
+      [[['/var/lib', '../', 'file/'], '/var/file'],
+       [['/var/lib', '/../', 'file/'], '/file'],
+       [['a/b/c/', '../../..'], system.pathFor('CurProcD')],
+       [['.'], system.pathFor('CurProcD')],
+       [['/some/dir', '.', '/absolute/'], '/absolute']];
+}
+var failures = [];
+resolveTests.forEach(function(test) {
+  var actual = path.resolve.apply(path, test[0]);
+  var expected = test[1];
+  var message = 'path.resolve(' + test[0].map(JSON.stringify).join(',') + ')' +
+                '\n  expect=' + JSON.stringify(expected) +
+                '\n  actual=' + JSON.stringify(actual);
+  if (actual !== expected) failures.push('\n' + message);
+  // assert.equal(actual, expected, message);
+});
+assert.equal(failures.length, 0, failures.join(''));
+
+// path.relative tests
+if (isWindows) {
+  // windows
+  var relativeTests =
+      // arguments                     result
+      [['c:/blah\\blah', 'd:/games', 'd:\\games'],
+       ['c:/aaaa/bbbb', 'c:/aaaa', '..'],
+       ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'],
+       ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''],
+       ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'],
+       ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'],
+       ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'],
+       ['c:/aaaa/bbbb', 'd:\\', 'd:\\']];
+} else {
+  // posix
+  var relativeTests =
+      // arguments                    result
+      [['/var/lib', '/var', '..'],
+       ['/var/lib', '/bin', '../../bin'],
+       ['/var/lib', '/var/lib', ''],
+       ['/var/lib', '/var/apache', '../apache'],
+       ['/var/', '/var/lib', 'lib'],
+       ['/', '/var/lib', 'var/lib']];
+}
+var failures = [];
+relativeTests.forEach(function(test) {
+  var actual = path.relative(test[0], test[1]);
+  var expected = test[2];
+  var message = 'path.relative(' +
+                test.slice(0, 2).map(JSON.stringify).join(',') +
+                ')' +
+                '\n  expect=' + JSON.stringify(expected) +
+                '\n  actual=' + JSON.stringify(actual);
+  if (actual !== expected) failures.push('\n' + message);
+});
+assert.equal(failures.length, 0, failures.join(''));
+
+// path.sep tests
+if (isWindows) {
+    // windows
+    assert.equal(path.sep, '\\');
+} else {
+    // posix
+    assert.equal(path.sep, '/');
+}
+
+};
+
+require('test').run(exports);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-places-utils.js
@@ -0,0 +1,75 @@
+/* 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, all } = require('sdk/core/promise');
+const { TreeNode } = require('sdk/places/utils');
+const { setTimeout } = require('sdk/timers');
+
+exports['test construct tree'] = function (assert) {
+  let tree = TreeNode(1);
+  tree.add([2, 3, 4]);
+  tree.get(2).add([2.1, 2.2, 2.3]);
+  let newTreeNode = TreeNode(4.3);
+  newTreeNode.add([4.31, 4.32]);
+  tree.get(4).add([4.1, 4.2, newTreeNode]);
+
+  assert.equal(tree.get(2).value, 2, 'get returns node with correct value');
+  assert.equal(tree.get(2.3).value, 2.3, 'get returns node with correct value');
+  assert.equal(tree.get(4.32).value, 4.32, 'get returns node even if created from nested node');
+  assert.equal(tree.get(4).children.length, 3, 'nodes have correct children length');
+  assert.equal(tree.get(3).children.length, 0, 'nodes have correct children length');
+
+  assert.equal(tree.get(4).get(4.32).value, 4.32, 'node.get descends from itself');
+  assert.equal(tree.get(4).get(2), null, 'node.get descends from itself fails if not descendant');
+};
+
+exports['test walk'] = function (assert) {
+  let resultsAll = [];
+  let tree = TreeNode(1);
+  tree.add([2, 3, 4]);
+  tree.get(2).add([2.1, 2.2]);
+
+  tree.walk(function (node) {
+    resultsAll.push(node.value);
+  });
+
+  [1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
+    assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
+  });
+
+  let resultsNode = [];
+  tree.get(2).walk(function (node) resultsNode.push(node.value));
+
+  [2, 2.1, 2.2].forEach(function (num) {
+    assert.ok(~resultsNode.indexOf(num), 'function applied to each node from node');
+  });
+};
+
+exports['test async walk'] = function (assert, done) {
+  let resultsAll = [];
+  let tree = TreeNode(1);
+  tree.add([2, 3, 4]);
+  tree.get(2).add([2.1, 2.2]);
+
+  tree.walk(function (node) {
+    let deferred = defer();
+    setTimeout(function () {
+      resultsAll.push(node.value);
+      deferred.resolve(node.value);
+    }, node.value === 2 ? 50 : 5);
+    return deferred.promise;
+  }).then(function () {
+    [1, 2, 2.1, 2.2, 3, 4].forEach(function (num) {
+      assert.ok(~resultsAll.indexOf(num), 'function applied to each node from root');
+    });
+    assert.ok(resultsAll.indexOf(2) < resultsAll.indexOf(2.1),
+      'child should wait for parent to complete');
+    assert.ok(resultsAll.indexOf(2) < resultsAll.indexOf(2.2),
+      'child should wait for parent to complete');
+    done();
+  });
+};
+
+require('test').run(exports);
--- a/addon-sdk/source/test/test-private-browsing.js
+++ b/addon-sdk/source/test/test-private-browsing.js
@@ -12,16 +12,19 @@ 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');
+const prefs = require('sdk/preferences/service');
+
+const kAutoStartPref = "browser.privatebrowsing.autostart";
 
 // is global pb is enabled?
 if (isGlobalPBSupported) {
   merge(module.exports, require('./private-browsing/global'));
 
   exports.testGlobalOnlyOnFirefox = function(test) {
     test.assert(is("Firefox"), "isGlobalPBSupported is only true on Firefox");
   }
@@ -100,8 +103,17 @@ exports.testGetOwnerWindow = function(te
       // private flag should be ignored by default
       test.assert(!isPrivate(tab));
       test.assert(!isPrivate(getOwnerWindow(tab)));
 
       tab.close(function() test.done());
     }
   });
 };
+
+exports.testNewGlobalPBService = function(test) {
+  test.assertEqual(isPrivate(), false, 'isPrivate() is false by default');
+  prefs.set(kAutoStartPref, true);
+  test.assertEqual(prefs.get(kAutoStartPref, false), true, kAutoStartPref + ' is true now');
+  test.assertEqual(isPrivate(), true, 'isPrivate() is true now');
+  prefs.set(kAutoStartPref, false);
+  test.assertEqual(isPrivate(), false, 'isPrivate() is false again');
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/test-rules.js
@@ -0,0 +1,79 @@
+/* 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 { Rules } = require('sdk/util/rules');
+const { on, off, emit } = require('sdk/event/core');
+
+exports.testAdd = function (test, done) {
+  let rules = Rules();
+  let urls = [
+    'http://www.firefox.com',
+    '*.mozilla.org',
+    '*.html5audio.org'
+  ];
+  let count = 0;
+  on(rules, 'add', function (rule) {
+    if (count < urls.length) {
+      test.ok(rules.get(rule), 'rule added to internal registry');
+      test.equal(rule, urls[count], 'add event fired with proper params');
+      if (++count < urls.length) rules.add(urls[count]);
+      else done();
+    }
+  });
+  rules.add(urls[0]);
+};
+
+exports.testRemove = function (test, done) {
+  let rules = Rules();
+  let urls = [
+    'http://www.firefox.com',
+    '*.mozilla.org',
+    '*.html5audio.org'
+  ];
+  let count = 0;
+  on(rules, 'remove', function (rule) {
+    if (count < urls.length) {
+      test.ok(!rules.get(rule), 'rule removed to internal registry');
+      test.equal(rule, urls[count], 'remove event fired with proper params');
+      if (++count < urls.length) rules.remove(urls[count]);
+      else done();
+    }
+  });
+  urls.forEach(function (url) rules.add(url));
+  rules.remove(urls[0]);
+};
+
+exports.testMatchesAny = function(test) {
+  let rules = Rules();
+  rules.add('*.mozilla.org');
+  rules.add('data:*');
+  matchTest('http://mozilla.org', true);
+  matchTest('http://www.mozilla.org', true);
+  matchTest('http://www.google.com', false);
+  matchTest('data:text/html;charset=utf-8,', true);
+
+  function matchTest(string, expected) {
+    test.equal(rules.matchesAny(string), expected,
+      'Expected to find ' + string + ' in rules');
+  }
+};
+
+exports.testIterable = function(test) {
+  let rules = Rules();
+  rules.add('*.mozilla.org');
+  rules.add('data:*');
+  rules.add('http://google.com');
+  rules.add('http://addons.mozilla.org');
+  rules.remove('http://google.com');
+
+  test.equal(rules.length, 3, 'has correct length of keys');
+  Array.forEach(rules, function (rule, i) {
+    test.equal(rule, ['*.mozilla.org', 'data:*', 'http://addons.mozilla.org'][i]);
+  });
+  for (let i in rules)
+    test.equal(rules[i], ['*.mozilla.org', 'data:*', 'http://addons.mozilla.org'][i]);
+};
+
+require('test').run(exports);
--- a/addon-sdk/source/test/test-tabs-common.js
+++ b/addon-sdk/source/test/test-tabs-common.js
@@ -37,21 +37,57 @@ exports.testTabCounts = function(test) {
       test.assertEqual(count2, tabs.length, 'tab count by iteration is correct');
 
       // end test
       tab.close(function() test.done());
     }
   });
 };
 
+
+// TEST: tabs.activeTab getter
+exports.testActiveTab_getter = function(test) {
+  test.waitUntilDone();
+  let evtCount = 0;
+  let activeTab = null;
+
+  function endTest(type, tab) {
+    if (type == 'activate') {
+      test.assertStrictEqual(tabs.activeTab, tab, 'the active tab is the opened tab');
+      activeTab = tabs.activeTab;
+    }
+    else {
+      test.assertEqual(tab.url, url, 'the opened tab has the correct url');
+    }
+
+    if (++evtCount != 2)
+      return;
+
+    test.assertStrictEqual(activeTab, tab, 'the active tab is the ready tab');
+    test.assertStrictEqual(tabs.activeTab, tab, 'the active tab is the ready tab');
+
+    tab.close(function() {
+      // end test
+      test.done();
+    });
+  }
+
+  let url = URL.replace("#title#", "testActiveTab_getter");
+  tabs.open({
+    url: url,
+    onReady: endTest.bind(null, 'ready'),
+    onActivate: endTest.bind(null, 'activate')
+  });
+};
+
 // TEST: tab.activate()
-exports.testActiveTab_setter_alt = function(test) {
+exports.testActiveTab_setter = function(test) {
   test.waitUntilDone();
 
-  let url = URL.replace("#title#", "testActiveTab_setter_alt");
+  let url = URL.replace("#title#", "testActiveTab_setter");
   let tab1URL = URL.replace("#title#", "tab1");
 
   tabs.open({
     url: tab1URL,
     onReady: function(activeTab) {
       let activeTabURL = tabs.activeTab.url;
 
       tabs.open({
--- a/addon-sdk/source/test/test-url.js
+++ b/addon-sdk/source/test/test-url.js
@@ -1,349 +1,342 @@
 /* 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 { Loader } = require("sdk/test/loader");
-var { pathFor } = require("sdk/system");
-var file = require("sdk/io/file");
-var loader = Loader(module);
-var httpd = loader.require("sdk/test/httpd");
-var port = 8099;
-var tabs = require("sdk/tabs");
+const { URL, toFilename, fromFilename, isValidURI, getTLD, DataURL } = require('sdk/url');
+const { pathFor } = require('sdk/system');
+const file = require('sdk/io/file');
+const tabs = require('sdk/tabs');
+const { decode } = require('sdk/base64');
+
+const httpd = require('sdk/test/httpd');
+const port = 8099;
 
-exports.testResolve = function(test) {
-  test.assertEqual(url.URL("bar", "http://www.foo.com/").toString(),
-                   "http://www.foo.com/bar");
+const defaultLocation = '{\'scheme\':\'about\',\'userPass\':null,\'host\':null,\'hostname\':null,\'port\':null,\'path\':\'addons\',\'pathname\':\'addons\',\'hash\':\'\',\'href\':\'about:addons\',\'origin\':\'about:\',\'protocol\':\'about:\',\'search\':\'\'}'.replace(/'/g, '"');
 
-  test.assertEqual(url.URL("bar", "http://www.foo.com"),
-                   "http://www.foo.com/bar");
+exports.testResolve = function(assert) {
+  assert.equal(URL('bar', 'http://www.foo.com/').toString(),
+                   'http://www.foo.com/bar');
+
+  assert.equal(URL('bar', 'http://www.foo.com'),
+                   'http://www.foo.com/bar');
 
-  test.assertEqual(url.URL("http://bar.com/", "http://foo.com/"),
-                   "http://bar.com/",
-                   "relative should override base");
+  assert.equal(URL('http://bar.com/', 'http://foo.com/'),
+                   'http://bar.com/',
+                   'relative should override base');
 
-  test.assertRaises(function() { url.URL("blah"); },
-                    "malformed URI: blah",
-                    "url.resolve() should throw malformed URI on base");
+  assert.throws(function() { URL('blah'); },
+                    /malformed URI: blah/i,
+                    'url.resolve() should throw malformed URI on base');
 
-  test.assertRaises(function() { url.URL("chrome://global"); },
-                    "invalid URI: chrome://global",
-                    "url.resolve() should throw invalid URI on base");
+  assert.throws(function() { URL('chrome://global'); },
+                    /invalid URI: chrome:\/\/global/i,
+                    'url.resolve() should throw invalid URI on base');
 
-  test.assertRaises(function() { url.URL("chrome://foo/bar"); },
-                    "invalid URI: chrome://foo/bar",
-                    "url.resolve() should throw on bad chrome URI");
+  assert.throws(function() { URL('chrome://foo/bar'); },
+                    /invalid URI: chrome:\/\/foo\/bar/i,
+                    'url.resolve() should throw on bad chrome URI');
 
-  test.assertEqual(url.URL("", "http://www.foo.com"),
-                   "http://www.foo.com/",
-                   "url.resolve() should add slash to end of domain");
+  assert.equal(URL('', 'http://www.foo.com'),
+                   'http://www.foo.com/',
+                   'url.resolve() should add slash to end of domain');
 };
 
-exports.testParseHttp = function(test) {
-  var aUrl = "http://sub.foo.com/bar?locale=en-US&otherArg=%20x%20#myhash"; 
-  var info = url.URL(aUrl);
-  test.assertEqual(info.scheme, "http");
-  test.assertEqual(info.protocol, "http:");
-  test.assertEqual(info.host, "sub.foo.com");
-  test.assertEqual(info.hostname, "sub.foo.com");
-  test.assertEqual(info.port, null);
-  test.assertEqual(info.userPass, null);
-  test.assertEqual(info.path, "/bar?locale=en-US&otherArg=%20x%20#myhash");
-  test.assertEqual(info.pathname, "/bar");
-  test.assertEqual(info.href, aUrl);
-  test.assertEqual(info.hash, "#myhash");
-  test.assertEqual(info.search, "?locale=en-US&otherArg=%20x%20");
+exports.testParseHttp = function(assert) {
+  var aUrl = 'http://sub.foo.com/bar?locale=en-US&otherArg=%20x%20#myhash';
+  var info = URL(aUrl);
+
+  assert.equal(info.scheme, 'http');
+  assert.equal(info.protocol, 'http:');
+  assert.equal(info.host, 'sub.foo.com');
+  assert.equal(info.hostname, 'sub.foo.com');
+  assert.equal(info.port, null);
+  assert.equal(info.userPass, null);
+  assert.equal(info.path, '/bar?locale=en-US&otherArg=%20x%20#myhash');
+  assert.equal(info.pathname, '/bar');
+  assert.equal(info.href, aUrl);
+  assert.equal(info.hash, '#myhash');
+  assert.equal(info.search, '?locale=en-US&otherArg=%20x%20');
 };
 
-exports.testParseHttpSearchAndHash = function (test) {
-  var info = url.URL("https://www.moz.com/some/page.html");
-  test.assertEqual(info.hash, "");
-  test.assertEqual(info.search, "");
+exports.testParseHttpSearchAndHash = function (assert) {
+  var info = URL('https://www.moz.com/some/page.html');
+  assert.equal(info.hash, '');
+  assert.equal(info.search, '');
   
-  var hashOnly = url.URL("https://www.sub.moz.com/page.html#justhash");
-  test.assertEqual(hashOnly.search, "");
-  test.assertEqual(hashOnly.hash, "#justhash");
+  var hashOnly = URL('https://www.sub.moz.com/page.html#justhash');
+  assert.equal(hashOnly.search, '');
+  assert.equal(hashOnly.hash, '#justhash');
   
-  var queryOnly = url.URL("https://www.sub.moz.com/page.html?my=query");
-  test.assertEqual(queryOnly.search, "?my=query");
-  test.assertEqual(queryOnly.hash, "");
+  var queryOnly = URL('https://www.sub.moz.com/page.html?my=query');
+  assert.equal(queryOnly.search, '?my=query');
+  assert.equal(queryOnly.hash, '');
 
-  var qMark = url.URL("http://www.moz.org?");
-  test.assertEqual(qMark.search, "");
-  test.assertEqual(qMark.hash, "");
+  var qMark = URL('http://www.moz.org?');
+  assert.equal(qMark.search, '');
+  assert.equal(qMark.hash, '');
   
-  var hash = url.URL("http://www.moz.org#");
-  test.assertEqual(hash.search, "");
-  test.assertEqual(hash.hash, "");
+  var hash = URL('http://www.moz.org#');
+  assert.equal(hash.search, '');
+  assert.equal(hash.hash, '');
   
-  var empty = url.URL("http://www.moz.org?#");
-  test.assertEqual(hash.search, "");
-  test.assertEqual(hash.hash, "");
+  var empty = URL('http://www.moz.org?#');
+  assert.equal(hash.search, '');
+  assert.equal(hash.hash, '');
 
-  var strange = url.URL("http://moz.org?test1#test2?test3");
-  test.assertEqual(strange.search, "?test1");
-  test.assertEqual(strange.hash, "#test2?test3");
+  var strange = URL('http://moz.org?test1#test2?test3');
+  assert.equal(strange.search, '?test1');
+  assert.equal(strange.hash, '#test2?test3');
 };
 
-exports.testParseHttpWithPort = function(test) {
-  var info = url.URL("http://foo.com:5/bar");
-  test.assertEqual(info.port, 5);
+exports.testParseHttpWithPort = function(assert) {
+  var info = URL('http://foo.com:5/bar');
+  assert.equal(info.port, 5);
 };
 
-exports.testParseChrome = function(test) {
-  var info = url.URL("chrome://global/content/blah");
-  test.assertEqual(info.scheme, "chrome");
-  test.assertEqual(info.host, "global");
-  test.assertEqual(info.port, null);
-  test.assertEqual(info.userPass, null);
-  test.assertEqual(info.path, "/content/blah");
+exports.testParseChrome = function(assert) {
+  var info = URL('chrome://global/content/blah');
+  assert.equal(info.scheme, 'chrome');
+  assert.equal(info.host, 'global');
+  assert.equal(info.port, null);
+  assert.equal(info.userPass, null);
+  assert.equal(info.path, '/content/blah');
 };
 
-exports.testParseAbout = function(test) {
-  var info = url.URL("about:boop");
-  test.assertEqual(info.scheme, "about");
-  test.assertEqual(info.host, null);
-  test.assertEqual(info.port, null);
-  test.assertEqual(info.userPass, null);
-  test.assertEqual(info.path, "boop");
+exports.testParseAbout = function(assert) {
+  var info = URL('about:boop');
+  assert.equal(info.scheme, 'about');
+  assert.equal(info.host, null);
+  assert.equal(info.port, null);
+  assert.equal(info.userPass, null);
+  assert.equal(info.path, 'boop');
 };
 
-exports.testParseFTP = function(test) {
-  var info = url.URL("ftp://1.2.3.4/foo");
-  test.assertEqual(info.scheme, "ftp");
-  test.assertEqual(info.host, "1.2.3.4");
-  test.assertEqual(info.port, null);
-  test.assertEqual(info.userPass, null);
-  test.assertEqual(info.path, "/foo");
+exports.testParseFTP = function(assert) {
+  var info = URL('ftp://1.2.3.4/foo');
+  assert.equal(info.scheme, 'ftp');
+  assert.equal(info.host, '1.2.3.4');
+  assert.equal(info.port, null);
+  assert.equal(info.userPass, null);
+  assert.equal(info.path, '/foo');
 };
 
-exports.testParseFTPWithUserPass = function(test) {
-  var info = url.URL("ftp://user:pass@1.2.3.4/foo");
-  test.assertEqual(info.userPass, "user:pass");
+exports.testParseFTPWithUserPass = function(assert) {
+  var info = URL('ftp://user:pass@1.2.3.4/foo');
+  assert.equal(info.userPass, 'user:pass');
 };
 
-exports.testToFilename = function(test) {
-  test.assertRaises(
-    function() { url.toFilename("resource://nonexistent"); },
-    "resource does not exist: resource://nonexistent/",
-    "url.toFilename() on nonexistent resources should throw"
+exports.testToFilename = function(assert) {
+  assert.throws(
+    function() { toFilename('resource://nonexistent'); },
+    /resource does not exist: resource:\/\/nonexistent\//i,
+    'toFilename() on nonexistent resources should throw'
   );
 
-  test.assertRaises(
-    function() { url.toFilename("http://foo.com/"); },
-    "cannot map to filename: http://foo.com/",
-    "url.toFilename() on http: URIs should raise error"
+  assert.throws(
+    function() { toFilename('http://foo.com/'); },
+    /cannot map to filename: http:\/\/foo.com\//i,
+    'toFilename() on http: URIs should raise error'
   );
 
   try {
-    test.assertMatches(
-      url.toFilename("chrome://global/content/console.xul"),
-      /.*console\.xul$/,
-      "url.toFilename() w/ console.xul works when it maps to filesystem"
+    assert.ok(
+      /.*console\.xul$/.test(toFilename('chrome://global/content/console.xul')),
+      'toFilename() w/ console.xul works when it maps to filesystem'
     );
-  } catch (e) {
+  }
+  catch (e) {
     if (/chrome url isn\'t on filesystem/.test(e.message))
-      test.pass("accessing console.xul in jar raises exception");
+      assert.pass('accessing console.xul in jar raises exception');
     else
-      test.fail("accessing console.xul raises " + e);
+      assert.fail('accessing console.xul raises ' + e);
   }
 
   // TODO: Are there any chrome URLs that we're certain exist on the
   // filesystem?
-  // test.assertMatches(url.toFilename("chrome://myapp/content/main.js"),
-  //                    /.*main\.js$/);
+  // assert.ok(/.*main\.js$/.test(toFilename('chrome://myapp/content/main.js')));
 };
 
-exports.testFromFilename = function(test) {
-  var profileDirName = require("sdk/system").pathFor("ProfD");
-  var fileUrl = url.fromFilename(profileDirName);
-  test.assertEqual(url.URL(fileUrl).scheme, 'file',
-                   'url.toFilename() should return a file: url');
-  test.assertEqual(url.fromFilename(url.toFilename(fileUrl)),
-                   fileUrl);
+exports.testFromFilename = function(assert) {
+  var profileDirName = require('sdk/system').pathFor('ProfD');
+  var fileUrl = fromFilename(profileDirName);
+  assert.equal(URL(fileUrl).scheme, 'file',
+                   'toFilename() should return a file: url');
+  assert.equal(fromFilename(toFilename(fileUrl)), fileUrl);
 };
 
-exports.testURL = function(test) {
-  let URL = url.URL;
-  test.assert(URL("h:foo") instanceof URL, "instance is of correct type");
-  test.assertRaises(function() URL(),
-                    "malformed URI: undefined",
-                    "url.URL should throw on undefined");
-  test.assertRaises(function() URL(""),
-                    "malformed URI: ",
-                    "url.URL should throw on empty string");
-  test.assertRaises(function() URL("foo"),
-                    "malformed URI: foo",
-                    "url.URL should throw on invalid URI");
-  test.assert(URL("h:foo").scheme, "has scheme");
-  test.assertEqual(URL("h:foo").toString(),
-                   "h:foo",
-                   "toString should roundtrip");
+exports.testURL = function(assert) {
+  assert.ok(URL('h:foo') instanceof URL, 'instance is of correct type');
+  assert.throws(function() URL(),
+                    /malformed URI: undefined/i,
+                    'url.URL should throw on undefined');
+  assert.throws(function() URL(''),
+                    /malformed URI: /i,
+                    'url.URL should throw on empty string');
+  assert.throws(function() URL('foo'),
+                    /malformed URI: foo/i,
+                    'url.URL should throw on invalid URI');
+  assert.ok(URL('h:foo').scheme, 'has scheme');
+  assert.equal(URL('h:foo').toString(),
+                   'h:foo',
+                   'toString should roundtrip');
   // test relative + base
-  test.assertEqual(URL("mypath", "http://foo").toString(),
-                   "http://foo/mypath",
-                   "relative URL resolved to base");
+  assert.equal(URL('mypath', 'http://foo').toString(),
+                   'http://foo/mypath',
+                   'relative URL resolved to base');
   // test relative + no base
-  test.assertRaises(function() URL("path").toString(),
-                    "malformed URI: path",
-                    "no base for relative URI should throw");
+  assert.throws(function() URL('path').toString(),
+                    /malformed URI: path/i,
+                    'no base for relative URI should throw');
 
-  let a = URL("h:foo");
+  let a = URL('h:foo');
   let b = URL(a);
-  test.assertEqual(b.toString(),
-                   "h:foo",
-                   "a URL can be initialized from another URL");
-  test.assertNotStrictEqual(a, b,
-                            "a URL initialized from another URL is not the same object");
-  test.assert(a == "h:foo",
-              "toString is implicit when a URL is compared to a string via ==");
-  test.assertStrictEqual(a + "", "h:foo",
-                         "toString is implicit when a URL is concatenated to a string");
+  assert.equal(b.toString(),
+                   'h:foo',
+                   'a URL can be initialized from another URL');
+  assert.notStrictEqual(a, b,
+                            'a URL initialized from another URL is not the same object');
+  assert.ok(a == 'h:foo',
+              'toString is implicit when a URL is compared to a string via ==');
+  assert.strictEqual(a + '', 'h:foo',
+                         'toString is implicit when a URL is concatenated to a string');
 };
 
-exports.testStringInterface = function(test) {
-  let URL = url.URL;
-  var EM = "about:addons";
+exports.testStringInterface = function(assert) {
+  var EM = 'about:addons';
   var a = URL(EM);
 
   // make sure the standard URL properties are enumerable and not the String interface bits
-  test.assertEqual(Object.keys(a),
-    "scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search",
-    "enumerable key list check for URL.");
-  test.assertEqual(
+  assert.equal(Object.keys(a),
+    'scheme,userPass,host,hostname,port,path,pathname,hash,href,origin,protocol,search',
+    'enumerable key list check for URL.');
+  assert.equal(
       JSON.stringify(a),
-      "{\"scheme\":\"about\",\"userPass\":null,\"host\":null,\"hostname\":null,\"port\":null,\"path\":\"addons\",\"pathname\":\"addons\",\"hash\":\"\",\"href\":\"about:addons\",\"origin\":\"about:\",\"protocol\":\"about:\",\"search\":\"\"}",
-      "JSON.stringify should return a object with correct props and vals.");
+      defaultLocation,
+      'JSON.stringify should return a object with correct props and vals.');
 
   // make sure that the String interface exists and works as expected
-  test.assertEqual(a.indexOf(":"), EM.indexOf(":"), "indexOf on URL works");
-  test.assertEqual(a.valueOf(), EM.valueOf(), "valueOf on URL works.");
-  test.assertEqual(a.toSource(), EM.toSource(), "toSource on URL works.");
-  test.assertEqual(a.lastIndexOf("a"), EM.lastIndexOf("a"), "lastIndexOf on URL works.");
-  test.assertEqual(a.match("t:").toString(), EM.match("t:").toString(), "match on URL works.");
-  test.assertEqual(a.toUpperCase(), EM.toUpperCase(), "toUpperCase on URL works.");
-  test.assertEqual(a.toLowerCase(), EM.toLowerCase(), "toLowerCase on URL works.");
-  test.assertEqual(a.split(":").toString(), EM.split(":").toString(), "split on URL works.");
-  test.assertEqual(a.charAt(2), EM.charAt(2), "charAt on URL works.");
-  test.assertEqual(a.charCodeAt(2), EM.charCodeAt(2), "charCodeAt on URL works.");
-  test.assertEqual(a.concat(EM), EM.concat(a), "concat on URL works.");
-  test.assertEqual(a.substr(2,3), EM.substr(2,3), "substr on URL works.");
-  test.assertEqual(a.substring(2,3), EM.substring(2,3), "substring on URL works.");
-  test.assertEqual(a.trim(), EM.trim(), "trim on URL works.");
-  test.assertEqual(a.trimRight(), EM.trimRight(), "trimRight on URL works.");
-  test.assertEqual(a.trimLeft(), EM.trimLeft(), "trimLeft on URL works.");
+  assert.equal(a.indexOf(':'), EM.indexOf(':'), 'indexOf on URL works');
+  assert.equal(a.valueOf(), EM.valueOf(), 'valueOf on URL works.');
+  assert.equal(a.toSource(), EM.toSource(), 'toSource on URL works.');
+  assert.equal(a.lastIndexOf('a'), EM.lastIndexOf('a'), 'lastIndexOf on URL works.');
+  assert.equal(a.match('t:').toString(), EM.match('t:').toString(), 'match on URL works.');
+  assert.equal(a.toUpperCase(), EM.toUpperCase(), 'toUpperCase on URL works.');
+  assert.equal(a.toLowerCase(), EM.toLowerCase(), 'toLowerCase on URL works.');
+  assert.equal(a.split(':').toString(), EM.split(':').toString(), 'split on URL works.');
+  assert.equal(a.charAt(2), EM.charAt(2), 'charAt on URL works.');
+  assert.equal(a.charCodeAt(2), EM.charCodeAt(2), 'charCodeAt on URL works.');
+  assert.equal(a.concat(EM), EM.concat(a), 'concat on URL works.');
+  assert.equal(a.substr(2,3), EM.substr(2,3), 'substr on URL works.');
+  assert.equal(a.substring(2,3), EM.substring(2,3), 'substring on URL works.');
+  assert.equal(a.trim(), EM.trim(), 'trim on URL works.');
+  assert.equal(a.trimRight(), EM.trimRight(), 'trimRight on URL works.');
+  assert.equal(a.trimLeft(), EM.trimLeft(), 'trimLeft on URL works.');
 }
 
-exports.testDataURLwithouthURI = function (test) {
-  const { DataURL } = url;
-
+exports.testDataURLwithouthURI = function (assert) {
   let dataURL = new DataURL();
 
-  test.assertEqual(dataURL.base64, false, "base64 is false for empty uri")
-  test.assertEqual(dataURL.data, "", "data is an empty string for empty uri")
-  test.assertEqual(dataURL.mimeType, "", "mimeType is an empty string for empty uri")
-  test.assertEqual(Object.keys(dataURL.parameters).length, 0, "parameters is an empty object for empty uri");
+  assert.equal(dataURL.base64, false, 'base64 is false for empty uri')
+  assert.equal(dataURL.data, '', 'data is an empty string for empty uri')
+  assert.equal(dataURL.mimeType, '', 'mimeType is an empty string for empty uri')
+  assert.equal(Object.keys(dataURL.parameters).length, 0, 'parameters is an empty object for empty uri');
 
-  test.assertEqual(dataURL.toString(), "data:,");
+  assert.equal(dataURL.toString(), 'data:,');
 }
 
-exports.testDataURLwithMalformedURI = function (test) {
-  const { DataURL } = url;
-
-  test.assertRaises(function() {
-      let dataURL = new DataURL("http://www.mozilla.com/");
+exports.testDataURLwithMalformedURI = function (assert) {
+  assert.throws(function() {
+      let dataURL = new DataURL('http://www.mozilla.com/');
     },
-    "Malformed Data URL: http://www.mozilla.com/",
-    "DataURL raises an exception for malformed data uri"
+    /Malformed Data URL: http:\/\/www.mozilla.com\//i,
+    'DataURL raises an exception for malformed data uri'
   );
 }
 
-exports.testDataURLparse = function (test) {
-  const { DataURL } = url;
-
-  let dataURL = new DataURL("data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
+exports.testDataURLparse = function (assert) {
+  let dataURL = new DataURL('data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E');
 
-  test.assertEqual(dataURL.base64, false, "base64 is false for non base64 data uri")
-  test.assertEqual(dataURL.data, "<h1>Hello!</h1>", "data is properly decoded")
-  test.assertEqual(dataURL.mimeType, "text/html", "mimeType is set properly")
-  test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
-  test.assertEqual(dataURL.parameters["charset"], "US-ASCII", "charset parsed");
+  assert.equal(dataURL.base64, false, 'base64 is false for non base64 data uri')
+  assert.equal(dataURL.data, '<h1>Hello!</h1>', 'data is properly decoded')
+  assert.equal(dataURL.mimeType, 'text/html', 'mimeType is set properly')
+  assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified');
+  assert.equal(dataURL.parameters['charset'], 'US-ASCII', 'charset parsed');
 
-  test.assertEqual(dataURL.toString(), "data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E");
+  assert.equal(dataURL.toString(), 'data:text/html;charset=US-ASCII,%3Ch1%3EHello!%3C%2Fh1%3E');
 }
 
-exports.testDataURLparseBase64 = function (test) {
-  const { DataURL } = url;
-  const { decode } = require("sdk/base64");
-
-  let text = "Awesome!";
-  let b64text = "QXdlc29tZSE=";
-  let dataURL = new DataURL("data:text/plain;base64," + b64text);
+exports.testDataURLparseBase64 = function (assert) {
+  let text = 'Awesome!';
+  let b64text = 'QXdlc29tZSE=';
+  let dataURL = new DataURL('data:text/plain;base64,' + b64text);
 
-  test.assertEqual(dataURL.base64, true, "base64 is true for base64 encoded data uri")
-  test.assertEqual(dataURL.data, text, "data is properly decoded")
-  test.assertEqual(dataURL.mimeType, "text/plain", "mimeType is set properly")
-  test.assertEqual(Object.keys(dataURL.parameters).length, 1, "one parameters specified");
-  test.assertEqual(dataURL.parameters["base64"], "", "parameter set without value");
-
-  test.assertEqual(dataURL.toString(), "data:text/plain;base64," + encodeURIComponent(b64text));
+  assert.equal(dataURL.base64, true, 'base64 is true for base64 encoded data uri')
+  assert.equal(dataURL.data, text, 'data is properly decoded')
+  assert.equal(dataURL.mimeType, 'text/plain', 'mimeType is set properly')
+  assert.equal(Object.keys(dataURL.parameters).length, 1, 'one parameters specified');
+  assert.equal(dataURL.parameters['base64'], '', 'parameter set without value');
+  assert.equal(dataURL.toString(), 'data:text/plain;base64,' + encodeURIComponent(b64text));
 }
 
-exports.testIsValidURI = function (test) {
+exports.testIsValidURI = function (assert) {
   validURIs().forEach(function (aUri) {
-    test.assertEqual(url.isValidURI(aUri), true, aUri + ' is a valid URL');
+    assert.equal(isValidURI(aUri), true, aUri + ' is a valid URL');
   });
 };
 
-exports.testIsInvalidURI = function (test) {
+exports.testIsInvalidURI = function (assert) {
   invalidURIs().forEach(function (aUri) {
-    test.assertEqual(url.isValidURI(aUri), false, aUri + ' is an invalid URL');
+    assert.equal(isValidURI(aUri), false, aUri + ' is an invalid URL');
   });
 };
 
-exports.testURLFromURL = function(test) {
-  let aURL = url.URL('http://mozilla.org');
-  let bURL = url.URL(aURL);
-  test.assertEqual(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
+exports.testURLFromURL = function(assert) {
+  let aURL = URL('http://mozilla.org');
+  let bURL = URL(aURL);
+  assert.equal(aURL.toString(), bURL.toString(), 'Making a URL from a URL works');
 };
 
-exports.testTLD = function(test) {
+exports.testTLD = function(assert) {
   let urls = [
     { url: 'http://my.sub.domains.mozilla.co.uk', tld: 'co.uk' },
     { url: 'http://my.mozilla.com', tld: 'com' },
     { url: 'http://my.domains.mozilla.org.hk', tld: 'org.hk' },
     { url: 'chrome://global/content/blah', tld: 'global' },
     { url: 'data:text/plain;base64,QXdlc29tZSE=', tld: null },
     { url: 'https://1.2.3.4', tld: null }
   ];
 
   urls.forEach(function (uri) {
-    test.assertEqual(url.getTLD(uri.url), uri.tld);
-    test.assertEqual(url.getTLD(url.URL(uri.url)), uri.tld);
+    assert.equal(getTLD(uri.url), uri.tld);
+    assert.equal(getTLD(URL(uri.url)), uri.tld);
   });
 }
 
-exports.testWindowLocationMatch = function (test) {
-  let srv = serve();
+exports.testWindowLocationMatch = function (assert, done) {
+  let server = httpd.startServerAsync(port);
+  server.registerPathHandler('/index.html', function (request, response) {
+    response.write('<html><head></head><body><h1>url tests</h1></body></html>');
+  });
+
   let aUrl = 'http://localhost:' + port + '/index.html?q=aQuery#somehash';
-  let urlObject = url.URL(aUrl);
-  test.waitUntilDone();
+  let urlObject = URL(aUrl);
 
   tabs.open({
     url: aUrl,
     onReady: function (tab) {
       tab.attach({
         onMessage: function (loc) {
           for (let prop in loc) {
-            test.assertEqual(urlObject[prop], loc[prop], prop + ' matches');
+            assert.equal(urlObject[prop], loc[prop], prop + ' matches');
           }
-          tab.close();
-          srv.stop(test.done.bind(test));
+
+          tab.close(function() server.stop(done));
         },
         contentScript: '(' + function () {
           let res = {};
           // `origin` is `null` in this context???
           let props = 'hostname,port,pathname,hash,href,protocol,search'.split(',');
           props.forEach(function (prop) {
             res[prop] = window.location[prop];
           });
@@ -441,20 +434,9 @@ function invalidURIs () {
 //  'http://.www.foo.bar/',
 //  'http://www.foo.bar./',
 //  'http://.www.foo.bar./',
 //  'http://10.1.1.1',
 //  'http://10.1.1.254'
   ];
 }
 
-function serve () {
-  let basePath = pathFor("ProfD");
-  let filePath = file.join(basePath, 'index.html');
-  let content = "<html><head></head><body><h1>url tests</h1></body></html>";
-  let fileStream = file.open(filePath, 'w');
-  fileStream.write(content);
-  fileStream.close();
-
-  let srv = httpd.startServerAsync(port, basePath);
-  return srv;
-}
-
+require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-widget.js
+++ b/addon-sdk/source/test/test-widget.js
@@ -1,32 +1,50 @@
 /* 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 url = require("sdk/url");
 const timer = require("sdk/timers");
 const self = require("sdk/self");
 const windowUtils = require("sdk/deprecated/window-utils");
+const { getMostRecentBrowserWindow } = require('sdk/window/utils');
+
+let jetpackID = "testID";
+try {
+  jetpackID = require("sdk/self").id;
+} catch(e) {}
+
+const australis = !!require("sdk/window/utils").getMostRecentBrowserWindow().CustomizableUI;
 
 exports.testConstructor = function(test) {
   test.waitUntilDone();
 
   let browserWindow = windowUtils.activeBrowserWindow;
   let doc = browserWindow.document;
-  let AddonsMgrListener = browserWindow.AddonsMgrListener;
+  let AddonsMgrListener;
+  if (australis) {
+    AddonsMgrListener = {
+      onInstalling: () => {},
+      onInstalled: () => {},
+      onUninstalling: () => {},
+      onUninstalled: () => {}
+    };
+  } else {
+    AddonsMgrListener = browserWindow.AddonsMgrListener;
+  }
 
-  function container() doc.getElementById("addon-bar");
-  function widgetCount() container() ? container().getElementsByTagName("toolbaritem").length : 0;
+  function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
+  function getWidgets() container() ? container().querySelectorAll('[id^="widget\:"]') : [];
+  function widgetCount() getWidgets().length;
   let widgetStartCount = widgetCount();
-  function widgetNode(index) container() ? container().getElementsByTagName("toolbaritem")[index] : null;
+  function widgetNode(index) getWidgets()[index];
 
   // Test basic construct/destroy
   AddonsMgrListener.onInstalling();
   let w = widgets.Widget({ id: "fooID", label: "foo", content: "bar" });
   AddonsMgrListener.onInstalled();
   test.assertEqual(widgetCount(), widgetStartCount + 1, "panel has correct number of child elements after widget construction");
 
   // test widget height
@@ -124,39 +142,40 @@ exports.testConstructor = function(test)
   // Cleanup this testcase
   AddonsMgrListener.onUninstalling();
   w1.destroy();
   w2.destroy();
   w3.destroy();
   AddonsMgrListener.onUninstalled();
 
   // Test concurrent widget module instances on addon-bar hiding
-  let loader = Loader(module);
-  let anotherWidgetsInstance = loader.require("sdk/widget");
-  test.assert(container().collapsed, "UI is hidden when no widgets");
-  AddonsMgrListener.onInstalling();
-  let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
-  // Ideally we would let AddonsMgrListener display the addon bar
-  // But, for now, addon bar is immediatly displayed by sdk code
-  // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
-  test.assert(!container().collapsed, "UI is already visible when we just added the widget");
-  AddonsMgrListener.onInstalled();
-  test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
-  let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
-  test.assert(!container().collapsed, "UI still visible when we add a second widget");
-  AddonsMgrListener.onUninstalling();
-  w1.destroy();
-  AddonsMgrListener.onUninstalled();
-  test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
-  AddonsMgrListener.onUninstalling();
-  w2.destroy();
-  test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
-  AddonsMgrListener.onUninstalled();
-  test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
-
+  if (!australis) {
+    let loader = Loader(module);
+    let anotherWidgetsInstance = loader.require("sdk/widget");
+    test.assert(container().collapsed, "UI is hidden when no widgets");
+    AddonsMgrListener.onInstalling();
+    let w1 = widgets.Widget({id: "foo", label: "foo", content: "bar"});
+    // Ideally we would let AddonsMgrListener display the addon bar
+    // But, for now, addon bar is immediatly displayed by sdk code
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=627484
+    test.assert(!container().collapsed, "UI is already visible when we just added the widget");
+    AddonsMgrListener.onInstalled();
+    test.assert(!container().collapsed, "UI become visible when we notify AddonsMgrListener about end of addon installation");
+    let w2 = anotherWidgetsInstance.Widget({id: "bar", label: "bar", content: "foo"});
+    test.assert(!container().collapsed, "UI still visible when we add a second widget");
+    AddonsMgrListener.onUninstalling();
+    w1.destroy();
+    AddonsMgrListener.onUninstalled();
+    test.assert(!container().collapsed, "UI still visible when we remove one of two widgets");
+    AddonsMgrListener.onUninstalling();
+    w2.destroy();
+    test.assert(!container().collapsed, "UI is still visible when we have removed all widget but still not called onUninstalled");
+    AddonsMgrListener.onUninstalled();
+    test.assert(container().collapsed, "UI is hidden when we have removed all widget and called onUninstalled");
+  }
   // Helper for testing a single widget.
   // Confirms proper addition and content setup.
   function testSingleWidget(widgetOptions) {
     // We have to display which test is being run, because here we do not
     // use the regular test framework but rather a custom one that iterates
     // the `tests` array.
     console.info("executing: " + widgetOptions.id);
 
@@ -485,18 +504,18 @@ exports.testConstructor = function(test)
 
   // test multiple windows
   tests.push(function testMultipleWindows() {
     const tabBrowser = require("sdk/deprecated/tab-browser");
 
     tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
       let browserWindow = e.target.defaultView;
       let doc = browserWindow.document;
-      function container() doc.getElementById("addon-bar");
-      function widgetCount2() container() ? container().childNodes.length : 0;
+      function container() australis ? doc.getElementById("nav-bar") : doc.getElementById("addon-bar");
+      function widgetCount2() container() ? container().querySelectorAll('[id^="widget\:"]').length : 0;
       let widgetStartCount2 = widgetCount2();
 
       let w1Opts = {id:"first", label: "first widget", content: "first content"};
       let w1 = testSingleWidget(w1Opts);
       test.assertEqual(widgetCount2(), widgetStartCount2 + 1, "2nd window has correct number of child elements after first widget");
 
       let w2Opts = {id:"second", label: "second widget", content: "second content"};
       let w2 = testSingleWidget(w2Opts);
@@ -565,55 +584,57 @@ exports.testConstructor = function(test)
         });
 
         // 5/ Destroy this window
         window.close();
       }
     });
   });
 
-  tests.push(function testAddonBarHide() {
-    const tabBrowser = require("sdk/deprecated/tab-browser");
+  if (!australis) {
+    tests.push(function testAddonBarHide() {
+      const tabBrowser = require("sdk/deprecated/tab-browser");
 
-    // Hide the addon-bar
-    browserWindow.setToolbarVisibility(container(), false);
-    test.assert(container().collapsed,
-                "1st window starts with an hidden addon-bar");
+      // Hide the addon-bar
+      browserWindow.setToolbarVisibility(container(), false);
+      test.assert(container().collapsed,
+                  "1st window starts with an hidden addon-bar");
 
-    // Then open a browser window and verify that the addon-bar remains hidden
-    tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
-      let browserWindow2 = e.target.defaultView;
-      let doc2 = browserWindow2.document;
-      function container2() doc2.getElementById("addon-bar");
-      function widgetCount2() container2() ? container2().childNodes.length : 0;
-      let widgetStartCount2 = widgetCount2();
-      test.assert(container2().collapsed,
-                  "2nd window starts with an hidden addon-bar");
+      // Then open a browser window and verify that the addon-bar remains hidden
+      tabBrowser.addTab("about:blank", { inNewWindow: true, onLoad: function(e) {
+        let browserWindow2 = e.target.defaultView;
+        let doc2 = browserWindow2.document;
+        function container2() doc2.getElementById("addon-bar");
+        function widgetCount2() container2() ? container2().childNodes.length : 0;
+        let widgetStartCount2 = widgetCount2();
+        test.assert(container2().collapsed,
+                    "2nd window starts with an hidden addon-bar");
 
-      let w1Opts = {id:"first", label: "first widget", content: "first content"};
-      let w1 = testSingleWidget(w1Opts);
-      test.assertEqual(widgetCount2(), widgetStartCount2 + 1,
-                       "2nd window has correct number of child elements after" +
-                       "widget creation");
-      w1.destroy();
-      test.assertEqual(widgetCount2(), widgetStartCount2,
-                       "2nd window has correct number of child elements after" +
-                       "widget destroy");
+        let w1Opts = {id:"first", label: "first widget", content: "first content"};
+        let w1 = testSingleWidget(w1Opts);
+        test.assertEqual(widgetCount2(), widgetStartCount2 + 1,
+                         "2nd window has correct number of child elements after" +
+                         "widget creation");
+        w1.destroy();
+        test.assertEqual(widgetCount2(), widgetStartCount2,
+                         "2nd window has correct number of child elements after" +
+                         "widget destroy");
 
-      test.assert(container().collapsed, "1st window has an hidden addon-bar");
-      test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
+        test.assert(container().collapsed, "1st window has an hidden addon-bar");
+        test.assert(container2().collapsed, "2nd window has an hidden addon-bar");
 
-      // Reset addon-bar visibility before exiting this test
-      browserWindow.setToolbarVisibility(container(), true);
+        // Reset addon-bar visibility before exiting this test
+        browserWindow.setToolbarVisibility(container(), true);
 
-      closeBrowserWindow(browserWindow2, function() {
-        doneTest();
-      });
-    }});
-  });
+        closeBrowserWindow(browserWindow2, function() {
+          doneTest();
+        });
+      }});
+    });
+  }
 
   // test widget.width
   tests.push(function testWidgetWidth() testSingleWidget({
     id: "text",
     label: "test widget.width",
     content: "test width",
     width: 200,
     contentScript: "self.postMessage(1)",
@@ -659,39 +680,46 @@ exports.testConstructor = function(test)
       doneTest();
     }
   }));
 
   // kick off test execution
   doneTest();
 };
 
-exports.testPanelWidget1 = function testPanelWidget1(test) {
+exports.testWidgetWithValidPanel = function(test) {
   const widgets = require("sdk/widget");
 
   let widget1 = widgets.Widget({
     id: "panel1",
     label: "panel widget 1",
     content: "<div id='me'>foo</div>",
     contentScript: "var evt = new MouseEvent('click', {button: 0});" +
                    "document.body.dispatchEvent(evt);",
     contentScriptWhen: "end",
     panel: require("sdk/panel").Panel({
       contentURL: "data:text/html;charset=utf-8,<body>Look ma, a panel!</body>",
       onShow: function() {
+        let { document } = getMostRecentBrowserWindow();
+        let widgetEle = document.getElementById("widget:" + jetpackID + "-" + widget1.id);
+        let panelEle = document.getElementById('mainPopupSet').lastChild;
+        // See bug https://bugzilla.mozilla.org/show_bug.cgi?id=859592
+        test.assertEqual(panelEle.getAttribute("type"), "arrow", 'the panel is a arrow type');
+        test.assertStrictEqual(panelEle.anchorNode, widgetEle, 'the panel is properly anchored to the widget');
+
         widget1.destroy();
         test.pass("panel displayed on click");
         test.done();
       }
     })
   });
   test.waitUntilDone();
 };
 
-exports.testPanelWidget2 = function testPanelWidget2(test) {
+exports.testWidgetWithInvalidPanel = function(test) {
   const widgets = require("sdk/widget");
   test.assertRaises(
     function() {
       widgets.Widget({
         id: "panel2",
         label: "panel widget 2",
         panel: {}
       });
@@ -1038,104 +1066,106 @@ exports.testSVGWidget = function(test) {
       widget.destroy();
       test.assertEqual(data.count, 1, 'only one image');
       test.assertEqual(data.src, SVG_URL, 'only one image');
       test.done();
     }
   });
 };
 
-exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) {
-  test.waitUntilDone();
+if (!australis) {
+  exports.testNavigationBarWidgets = function testNavigationBarWidgets(test) {
+    test.waitUntilDone();
 
-  let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
-  let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
-  let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
+    let w1 = widgets.Widget({id: "1st", label: "1st widget", content: "1"});
+    let w2 = widgets.Widget({id: "2nd", label: "2nd widget", content: "2"});
+    let w3 = widgets.Widget({id: "3rd", label: "3rd widget", content: "3"});
 
-  // First wait for all 3 widgets to be added to the current browser window
-  let firstAttachCount = 0;
-  function onAttachFirstWindow(widget) {
-    if (++firstAttachCount<3)
-      return;
-    onWidgetsReady();
-  }
-  w1.once("attach", onAttachFirstWindow);
-  w2.once("attach", onAttachFirstWindow);
-  w3.once("attach", onAttachFirstWindow);
+    // First wait for all 3 widgets to be added to the current browser window
+    let firstAttachCount = 0;
+    function onAttachFirstWindow(widget) {
+      if (++firstAttachCount<3)
+        return;
+      onWidgetsReady();
+    }
+    w1.once("attach", onAttachFirstWindow);
+    w2.once("attach", onAttachFirstWindow);
+    w3.once("attach", onAttachFirstWindow);
 
-  function getWidgetNode(toolbar, position) {
-    return toolbar.getElementsByTagName("toolbaritem")[position];
-  }
-  function openBrowserWindow() {
-    let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-             getService(Ci.nsIWindowWatcher);
-    let urlString = Cc["@mozilla.org/supports-string;1"].
-                    createInstance(Ci.nsISupportsString);
-    urlString.data = "about:blank";
-    return ww.openWindow(null, "chrome://browser/content/browser.xul",
-                               "_blank", "chrome,all,dialog=no", urlString);
-  }
+    function getWidgetNode(toolbar, position) {
+      return toolbar.getElementsByTagName("toolbaritem")[position];
+    }
+    function openBrowserWindow() {
+      let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+               getService(Ci.nsIWindowWatcher);
+      let urlString = Cc["@mozilla.org/supports-string;1"].
+                      createInstance(Ci.nsISupportsString);
+      urlString.data = "about:blank";
+      return ww.openWindow(null, "chrome://browser/content/browser.xul",
+                                 "_blank", "chrome,all,dialog=no", urlString);
+    }
 
-  // Then move them before openeing a new browser window
-  function onWidgetsReady() {
-    // Hack to move 2nd and 3rd widgets manually to the navigation bar right after
-    // the search box.
-    let browserWindow = windowUtils.activeBrowserWindow;
-    let doc = browserWindow.document;
-    let addonBar = doc.getElementById("addon-bar");
-    let w2ToolbarItem = getWidgetNode(addonBar, 1);
-    let w3ToolbarItem = getWidgetNode(addonBar, 2);
-    let navBar = doc.getElementById("nav-bar");
-    let searchBox = doc.getElementById("search-container");
-    // Insert 3rd at the right of search box by adding it before its right sibling
-    navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
-    // Then insert 2nd before 3rd
-    navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false);
-    // Widget and Firefox codes rely on this `currentset` attribute,
-    // so ensure it is correctly saved
-    navBar.setAttribute("currentset", navBar.currentSet);
-    doc.persist(navBar.id, "currentset");
-    // Update addonbar too as we removed widget from there.
-    // Otherwise, widgets may still be added to this toolbar.
-    addonBar.setAttribute("currentset", addonBar.currentSet);
-    doc.persist(addonBar.id, "currentset");
+    // Then move them before openeing a new browser window
+    function onWidgetsReady() {
+      // Hack to move 2nd and 3rd widgets manually to the navigation bar right after
+      // the search box.
+      let browserWindow = windowUtils.activeBrowserWindow;
+      let doc = browserWindow.document;
+      let addonBar = doc.getElementById("addon-bar");
+      let w2ToolbarItem = getWidgetNode(addonBar, 1);
+      let w3ToolbarItem = getWidgetNode(addonBar, 2);
+      let navBar = doc.getElementById("nav-bar");
+      let searchBox = doc.getElementById("search-container");
+      // Insert 3rd at the right of search box by adding it before its right sibling
+      navBar.insertItem(w3ToolbarItem.id, searchBox.nextSibling, null, false);
+      // Then insert 2nd before 3rd
+      navBar.insertItem(w2ToolbarItem.id, w3ToolbarItem, null, false);
+      // Widget and Firefox codes rely on this `currentset` attribute,
+      // so ensure it is correctly saved
+      navBar.setAttribute("currentset", navBar.currentSet);
+      doc.persist(navBar.id, "currentset");
+      // Update addonbar too as we removed widget from there.
+      // Otherwise, widgets may still be added to this toolbar.
+      addonBar.setAttribute("currentset", addonBar.currentSet);
+      doc.persist(addonBar.id, "currentset");
 
-    // Wait for all widget to be attached to this new window before checking
-    // their position
-    let attachCount = 0;
-    let browserWindow2;
-    function onAttach(widget) {
-      if (++attachCount < 3)
-        return;
-      let doc = browserWindow2.document;
-      let addonBar = doc.getElementById("addon-bar");
-      let searchBox = doc.getElementById("search-container");
+      // Wait for all widget to be attached to this new window before checking
+      // their position
+      let attachCount = 0;
+      let browserWindow2;
+      function onAttach(widget) {
+        if (++attachCount < 3)
+          return;
+        let doc = browserWindow2.document;
+        let addonBar = doc.getElementById("addon-bar");
+        let searchBox = doc.getElementById("search-container");
 
-      // Ensure that 1st is in addon bar
-      test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
-      // And that 2nd and 3rd keep their original positions in navigation bar,
-      // i.e. right after search box
-      test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label);
-      test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
+        // Ensure that 1st is in addon bar
+        test.assertEqual(getWidgetNode(addonBar, 0).getAttribute("label"), w1.label);
+        // And that 2nd and 3rd keep their original positions in navigation bar,
+        // i.e. right after search box
+        test.assertEqual(searchBox.nextSibling.getAttribute("label"), w2.label);
+        test.assertEqual(searchBox.nextSibling.nextSibling.getAttribute("label"), w3.label);
 
-      w1.destroy();
-      w2.destroy();
-      w3.destroy();
+        w1.destroy();
+        w2.destroy();
+        w3.destroy();
 
-      closeBrowserWindow(browserWindow2, function() {
-        test.done();
-      });
+        closeBrowserWindow(browserWindow2, function() {
+          test.done();
+        });
+      }
+      w1.on("attach", onAttach);
+      w2.on("attach", onAttach);
+      w3.on("attach", onAttach);
+
+      browserWindow2 = openBrowserWindow(browserWindow);
     }
-    w1.on("attach", onAttach);
-    w2.on("attach", onAttach);
-    w3.on("attach", onAttach);
-
-    browserWindow2 = openBrowserWindow(browserWindow);
-  }
-};
+  };
+}
 
 /******************* helpers *********************/
 
 // Helper for calling code at window close
 function closeBrowserWindow(window, callback) {
   timer.setTimeout(function() {
     window.addEventListener("unload", function onUnload() {
       window.removeEventListener("unload", onUnload, false);
--- a/addon-sdk/source/test/test-windows-common.js
+++ b/addon-sdk/source/test/test-windows-common.js
@@ -2,58 +2,57 @@
  * 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 { browserWindows } = require('sdk/windows');
 
 // TEST: browserWindows Iterator
-exports.testBrowserWindowsIterator = function(test) {
+exports.testBrowserWindowsIterator = function(assert) {
   let activeWindowCount = 0;
   let windows = [];
   let i = 0;
   for each (let window in browserWindows) {
     if (window === browserWindows.activeWindow)
       activeWindowCount++;
 
-    test.assertEqual(windows.indexOf(window), -1, 'window not already in iterator');
-    test.assertEqual(browserWindows[i++], window, 'browserWindows[x] works');
+    assert.equal(windows.indexOf(window), -1, 'window not already in iterator');
+    assert.equal(browserWindows[i++], window, 'browserWindows[x] works');
     windows.push(window);
   }
-  test.assertEqual(activeWindowCount, 1, 'activeWindow was found in the iterator');
+  assert.equal(activeWindowCount, 1, 'activeWindow was found in the iterator');
 
   i = 0;
   for (let j in browserWindows) {
-    test.assertEqual(j, i++, 'for (x in browserWindows) works');
+    assert.equal(j, i++, 'for (x in browserWindows) works');
   }
 };
 
-exports.testWindowTabsObject_alt = function(test) {
-  test.waitUntilDone();
-
+exports.testWindowTabsObject_alt = function(assert, done) {
   let window = browserWindows.activeWindow;
   window.tabs.open({
-    url: "data:text/html;charset=utf-8,<title>tab 2</title>",
+    url: 'data:text/html;charset=utf-8,<title>tab 2</title>',
     inBackground: true,
     onReady: function onReady(tab) {
-      test.assertEqual(tab.title, "tab 2", "Correct new tab title");
-      test.assertNotEqual(window.tabs.activeTab, tab, "Correct active tab");
+      assert.equal(tab.title, 'tab 2', 'Correct new tab title');
+      assert.notEqual(window.tabs.activeTab, tab, 'Correct active tab');
 
       // end test
-      tab.close(test.done.bind(test));
+      tab.close(done);
     }
   });
 };
 
 // TEST: browserWindows.activeWindow
-exports.testWindowActivateMethod_simple = function(test) {
+exports.testWindowActivateMethod_simple = function(assert) {
   let window = browserWindows.activeWindow;
   let tab = window.tabs.activeTab;
 
   window.activate();
 
-  test.assertEqual(browserWindows.activeWindow, window,
-                   "Active window is active after window.activate() call");
-  test.assertEqual(window.tabs.activeTab, tab,
-                   "Active tab is active after window.activate() call");
-  
+  assert.equal(browserWindows.activeWindow, window,
+               'Active window is active after window.activate() call');
+  assert.equal(window.tabs.activeTab, tab,
+               'Active tab is active after window.activate() call');
 };
+
+require('sdk/test').run(exports);
--- a/addon-sdk/source/test/windows/test-firefox-windows.js
+++ b/addon-sdk/source/test/windows/test-firefox-windows.js
@@ -179,22 +179,16 @@ exports.testOnOpenOnCloseListeners = fun
       window.close(verify);
     }
   });
 };
 
 /*
 Disabled due to all of the Win8 PGO bustage in bug 873007.
 exports.testActiveWindow = function(test) {
-  const xulApp = require("sdk/system/xul-app");
-  if (xulApp.versionInRange(xulApp.platformVersion, "1.9.2", "1.9.2.*")) {
-    test.pass("This test is disabled on 3.6. For more information, see bug 598525");
-    return;
-  }
-
   let windows = browserWindows;
 
   // API window objects
   let window2, window3;
 
   // Raw window objects
   let rawWindow2, rawWindow3;