Bug 897683 - Uplift Addon-SDK to Firefox r=me
☠☠ backed out by 0f58c42394fe ☠ ☠
authorWes Kocher <wkocher@mozilla.com>
Wed, 24 Jul 2013 14:33:54 -0700
changeset 139880 489046125fa6eb98fe20d73460f4b0e641cfe037
parent 139879 fe1213d6035d8a48ad630a73072c1f9523e837fc
child 139881 0dbcf7b6347ad00e27c515b1bfaf9cc15ae95a16
push idunknown
push userunknown
push dateunknown
reviewersme
bugs897683
milestone25.0a1
Bug 897683 - Uplift Addon-SDK to Firefox r=me
addon-sdk/source/app-extension/bootstrap.js
addon-sdk/source/app-extension/install.rdf
addon-sdk/source/doc/dev-guide-source/cfx-tool.md
addon-sdk/source/doc/module-source/sdk/self.md
addon-sdk/source/doc/module-source/sdk/test/utils.md
addon-sdk/source/doc/module-source/sdk/util/array.md
addon-sdk/source/lib/sdk/console/plain-text.js
addon-sdk/source/lib/sdk/deprecated/unit-test.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/lang/functional.js
addon-sdk/source/lib/sdk/system.js
addon-sdk/source/lib/sdk/system/globals.js
addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
addon-sdk/source/lib/sdk/test/harness.js
addon-sdk/source/lib/sdk/timers.js
addon-sdk/source/lib/sdk/util/array.js
addon-sdk/source/lib/sdk/window/utils.js
addon-sdk/source/python-lib/cuddlefish/options_xul.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/python-lib/mozrunner/winprocess.py
addon-sdk/source/test/addons/content-permissions/main.js
addon-sdk/source/test/addons/main/main.js
addon-sdk/source/test/addons/main/package.json
addon-sdk/source/test/addons/private-browsing-supported/main.js
addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js
addon-sdk/source/test/addons/simple-prefs/lib/main.js
addon-sdk/source/test/addons/simple-prefs/package.json
addon-sdk/source/test/places-helper.js
addon-sdk/source/test/tabs/test-fennec-tabs.js
addon-sdk/source/test/test-array.js
addon-sdk/source/test/test-browser-events.js
addon-sdk/source/test/test-content-script.js
addon-sdk/source/test/test-fs.js
addon-sdk/source/test/test-observer-service.js
addon-sdk/source/test/test-page-mod.js
addon-sdk/source/test/test-page-worker.js
addon-sdk/source/test/test-places-bookmarks.js
addon-sdk/source/test/test-places-favicon.js
addon-sdk/source/test/test-places-history.js
addon-sdk/source/test/test-places-host.js
addon-sdk/source/test/test-plain-text-console.js
addon-sdk/source/test/test-system-events.js
addon-sdk/source/test/test-tab-utils.js
addon-sdk/source/test/test-timer.js
addon-sdk/source/test/test-window-events.js
addon-sdk/source/test/test-window-utils2.js
--- a/addon-sdk/source/app-extension/bootstrap.js
+++ b/addon-sdk/source/app-extension/bootstrap.js
@@ -215,18 +215,16 @@ function startup(data, reasonCode) {
       loadReason: reason,
 
       prefixURI: prefixURI,
       // Add-on URI.
       rootURI: rootURI,
       // options used by system module.
       // File to write 'OK' or 'FAIL' (exit code emulation).
       resultFile: options.resultFile,
-      // File to write stdout.
-      logFile: options.logFile,
       // Arguments passed as --static-args
       staticArgs: options.staticArgs,
 
       // Arguments related to test runner.
       modules: {
         '@test/options': {
           allTestModules: options.allTestModules,
           iterations: options.iterations,
--- a/addon-sdk/source/app-extension/install.rdf
+++ b/addon-sdk/source/app-extension/install.rdf
@@ -12,18 +12,18 @@
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:unpack>false</em:unpack>
 
     <!-- Firefox -->
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>19.0</em:minVersion>
-        <em:maxVersion>22.0a1</em:maxVersion>
+        <em:minVersion>21.0</em:minVersion>
+        <em:maxVersion>25.0a1</em:maxVersion>
       </Description>
     </em:targetApplication>
 
     <!-- Front End MetaData -->
     <em:name>Test App</em:name>
     <em:description>Harness for tests.</em:description>
     <em:creator>Mozilla Corporation</em:creator>
     <em:homepageURL></em:homepageURL>
--- a/addon-sdk/source/doc/dev-guide-source/cfx-tool.md
+++ b/addon-sdk/source/doc/dev-guide-source/cfx-tool.md
@@ -635,25 +635,24 @@ of `updateURL`.
 
 Note that as the [add-on documentation](https://developer.mozilla.org/en/extension_versioning,_update_and_compatibility#Securing_Updates)
 explains, you should make sure the update procedure for your add-on is secure,
 and this usually involves using HTTPS for the links.
 
 So if we run the following command:
 
 <pre>
-  cfx xpi --update-link https://example.com/addon/latest
-          --update-url https://example.com/addon/update_rdf
+  cfx xpi --update-link https://example.com/addon/latest/pluginName.xpi --update-url https://example.com/addon/update_rdf/pluginName.update.rdf
 </pre>
 
 `cfx` will create two files:
 
 * an XPI file which embeds
-`https://example.com/addon/update_rdf` as the value of `updateURL`
-* an RDF file which embeds `https://example.com/addon/latest` as the value of
+`https://example.com/addon/update_rdf/pluginName.update.rdf` as the value of `updateURL`
+* an RDF file which embeds `https://example.com/addon/latest/pluginName.xpi` as the value of
 `updateLink`.
 
 ### Supported Options ###
 
 As with `cfx run` you can point `cfx` at a different `package.json` file using
 the `--pkgdir` option. You can also embed arguments in the XPI using the
 `--static-args` option: if you do this the arguments will be passed to your
 add-on whenever it is run.
--- a/addon-sdk/source/doc/module-source/sdk/self.md
+++ b/addon-sdk/source/doc/module-source/sdk/self.md
@@ -8,16 +8,23 @@ The `self` module provides access to dat
 as a whole. It also provides access to the
 [Program ID](dev-guide/guides/program-id.html), a value which is
 unique for each add-on.
 
 Note that the `self` module is completely different from the global `self`
 object accessible to content scripts, which is used by a content script to
 [communicate with the add-on code](dev-guide/guides/content-scripts/using-port.html).
 
+<api name="uri">
+@property {string}
+This property represents an add-on associated unique URI string.
+This URI can be used for APIs which require a valid URI string, such as the
+[passwords](modules/sdk/passwords.html) module.
+</api>
+
 <api name="id">
 @property {string}
 This property is a printable string that is unique for each add-on. It comes
 from the `id` property set in the `package.json` file in the main package
 (i.e. the package in which you run `cfx xpi`). While not generally of use to
 add-on code directly, it can be used by internal API code to index local
 storage and other resources that are associated with a particular add-on.
 Eventually, this ID will be unspoofable (see
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/doc/module-source/sdk/test/utils.md
@@ -0,0 +1,90 @@
+<!-- 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 `test/utils` module provides additional helper methods to be used in
+the CommonJS Unit Testing test suite.
+
+## Before and After
+
+Helper functions `before()` and `after()` are available for running a function
+before or after each test in a suite. They're useful when you need to
+guarantee a particular state before running a test, and to clean up
+after your test.
+
+    let { before, after } = require('sdk/test/utils');
+    let { search } = require('sdk/places/bookmarks');
+
+    exports.testCountBookmarks = function (assert, done) {
+      search().on('end', function (results) {
+        assert.equal(results, 0, 'should be no bookmarks');
+        done();
+      });
+    };
+
+    before(exports, function (name, assert) {
+      removeAllBookmarks();
+    });
+
+    require('sdk/test').run(exports);
+
+Both `before` and `after` may be asynchronous. To make them asynchronous,
+pass a third argument `done`, which is a function to call when you have
+finished:
+
+    let { before, after } = require('sdk/test/utils');
+    let { search } = require('sdk/places/bookmarks');
+
+    exports.testCountBookmarks = function (assert, done) {
+      search().on('end', function (results) {
+        assert.equal(results, 0, 'should be no bookmarks');
+        done();
+      });
+    };
+
+    before(exports, function (name, assert, done) {
+      removeAllBookmarksAsync(function () {
+        done();
+      });
+    });
+
+    require('sdk/test').run(exports);
+
+<api name="before">
+@function
+  Runs `beforeFn` before each test in the file. May be asynchronous
+  if `beforeFn` accepts a third argument, which is a callback.
+
+ @param exports {Object}
+    A test file's `exports` object
+ @param beforeFn {Function}
+    The function to be called before each test. It has two arguments,
+    or three if it is asynchronous:
+
+   * the first argument is the test's name as a `String`.
+   * the second argument is the `assert` object for the test.
+   * the third, optional, argument is a callback. If the callback is
+    defined, then the `beforeFn` is considered asynchronous, and the
+    callback must be invoked before the test runs.
+
+</api>
+
+<api name="after">
+@function
+  Runs `afterFn` after each test in the file. May be asynchronous
+  if `afterFn` accepts a third argument, which is a callback.
+
+ @param exports {Object}
+    A test file's `exports` object
+ @param afterFn {Function}
+    The function to be called after each test. It has two arguments,
+    or three if it is asynchronous:
+
+   * the first argument is the test's name as a `String`.
+   * the second argument is the `assert` object for the test.
+   * the third, optional, argument is a callback. If the callback is
+    defined, then the `afterFn` is considered asynchronous, and the
+    callback must be invoked before the next test runs.
+
+</api>
+
--- a/addon-sdk/source/doc/module-source/sdk/util/array.md
+++ b/addon-sdk/source/doc/module-source/sdk/util/array.md
@@ -1,13 +1,13 @@
 <!-- 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 `util/array` module provides simple helper functions for working with 
+The `util/array` module provides simple helper functions for working with
 arrays.
 
 <api name="has">
 @function
 Returns `true` if the given [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains the element or `false` otherwise.
 A simplified version of `array.indexOf(element) >= 0`.
 
     let { has } = require('sdk/util/array');
@@ -24,17 +24,17 @@ A simplified version of `array.indexOf(e
   The element to search for in the array.
 
 @returns {boolean}
   A boolean indicating whether or not the element was found in the array.
 </api>
 
 <api name="hasAny">
 @function
-Returns `true` if the given [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains any of the elements in the 
+Returns `true` if the given [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains any of the elements in the
 `elements` array, or `false` otherwise.
 
     let { hasAny } = require('sdk/util/array');
     let a = ['rock', 'roll', 100];
 
     hasAny(a, ['rock', 'bright', 'light']); // true
     hasAny(a, ['rush', 'coil', 'jet']); // false
 
@@ -79,17 +79,17 @@ does not add the element and returns `fa
 If the given [array](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array) contains the given element, this function
 removes the element from the array and returns `true`. Otherwise, this function
 does not alter the array and returns `false`.
 
     let { remove } = require('sdk/util/array');
     let a = ['alice', 'bob', 'carol'];
 
     remove(a, 'dave'); // false
-    remove(a, 'bob'); // true 
+    remove(a, 'bob'); // true
     remove(a, 'bob'); // false
 
     console.log(a); // ['alice', 'carol']
 
 @param array {array}
   The array to remove the element from.
 
 @param element {*}
@@ -149,8 +149,29 @@ Iterates over an [iterator](https://deve
 
 @param iterator {iterator}
   The [`Iterator`](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Iterators_and_Generators#Iterators) object over which to iterate and place results into an array.
 
 @returns {array}
   The iterator's results in an array.
 </api>
 
+<api name="find">
+@function
+Iterates over given `array` and applies given `predicate` function until
+`predicate(element)` is `true`. If such element is found it's retured back
+otherwise third optional `fallback` argument is returned back. If fallback
+is not provided returns `undefined`.
+
+    let { find } = require('sdk/util/array');
+    let isOdd = (x) => x % 2;
+    find([2, 4, 5, 7, 8, 9], isOdd);   // => 5
+    find([2, 4, 6, 8], isOdd);         // => undefiend
+    find([2, 4, 6, 8], isOdd, null);   // => null
+
+    fromIterator(i) // ['otoro', 'unagi', 'keon']
+
+@param iterator {iterator}
+  The [`Iterator`](https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Iterators_and_Generators#Iterators) object over which to iterate and place results into an array.
+
+@returns {array}
+  The iterator's results in an array.
+</api>
--- a/addon-sdk/source/lib/sdk/console/plain-text.js
+++ b/addon-sdk/source/lib/sdk/console/plain-text.js
@@ -3,134 +3,77 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
-const { Cc, Ci } = require("chrome");
+const { Cc, Ci, Cu, Cr } = require("chrome");
 const self = require("../self");
-const traceback = require("./traceback")
 const prefs = require("../preferences/service");
 const { merge } = require("../util/object");
-const { partial } = require("../lang/functional");
+const { ConsoleAPI } = Cu.import("resource://gre/modules/devtools/Console.jsm");
 
-const LEVELS = {
-  "all": Number.MIN_VALUE,
-  "debug": 20000,
-  "info": 30000,
-  "warn": 40000,
-  "error": 50000,
-  "off": Number.MAX_VALUE,
-};
 const DEFAULT_LOG_LEVEL = "error";
 const ADDON_LOG_LEVEL_PREF = "extensions." + self.id + ".sdk.console.logLevel";
 const SDK_LOG_LEVEL_PREF = "extensions.sdk.console.logLevel";
 
-let logLevel;
-
+let logLevel = DEFAULT_LOG_LEVEL;
 function setLogLevel() {
-  logLevel = prefs.get(ADDON_LOG_LEVEL_PREF, prefs.get(SDK_LOG_LEVEL_PREF,
-                                                       DEFAULT_LOG_LEVEL));
+  logLevel = prefs.get(ADDON_LOG_LEVEL_PREF, 
+                           prefs.get(SDK_LOG_LEVEL_PREF,
+                                     DEFAULT_LOG_LEVEL));
 }
 setLogLevel();
 
 let logLevelObserver = {
+  QueryInterface: function(iid) {
+    if (!iid.equals(Ci.nsIObserver) &&
+        !iid.equals(Ci.nsISupportsWeakReference) &&
+        !iid.equals(Ci.nsISupports))
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    return this;
+  },
   observe: function(subject, topic, data) {
     setLogLevel();
   }
 };
 let branch = Cc["@mozilla.org/preferences-service;1"].
              getService(Ci.nsIPrefService).
              getBranch(null);
-branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, false);
-branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, false);
-
-function stringify(arg) {
-  try {
-    return String(arg);
-  }
-  catch(ex) {
-    return "<toString() error>";
-  }
-}
-
-function stringifyArgs(args) {
-  return Array.map(args, stringify).join(" ");
-}
-
-function message(print, level) {
-  if (LEVELS[level] < LEVELS[logLevel])
-    return;
-
-  let args = Array.slice(arguments, 2);
-
-  print(level + ": " + self.name + ": " + stringifyArgs(args) + "\n", level);
-}
-
-function errorMessage(print, e) {
-  // Some platform exception doesn't have name nor message but
-  // can be stringified to a meaningfull message
-  var fullString = ("An exception occurred.\n" +
-                   (e.name ? e.name + ": " : "") +
-                   (e.message ? e.message : e.toString()) + "\n" +
-                   (e.fileName ? traceback.sourceURI(e.fileName) + " " +
-                                 e.lineNumber + "\n"
-                               : "") +
-                   traceback.format(e));
-
-  message(print, "error", fullString);
-}
-
-function traceMessage(print) {
-  var stack = traceback.get();
-  stack.splice(-1, 1);
-
-  message(print, "info", traceback.format(stack));
-}
+branch.addObserver(ADDON_LOG_LEVEL_PREF, logLevelObserver, true);
+branch.addObserver(SDK_LOG_LEVEL_PREF, logLevelObserver, true);
 
 function PlainTextConsole(print) {
-  if (!print)
-    print = dump;
-
-  if (print === dump) {
-    // If we're just using dump(), auto-enable preferences so
-    // that the developer actually sees the console output.
-    var prefs = Cc["@mozilla.org/preferences-service;1"]
-                .getService(Ci.nsIPrefBranch);
-    prefs.setBoolPref("browser.dom.window.dump.enabled", true);
-  }
 
-  merge(this, {
-    log: partial(message, print, "info"),
-    info: partial(message, print, "info"),
-    warn: partial(message, print, "warn"),
-    error: partial(message, print, "error"),
-    debug: partial(message, print, "debug"),
-    exception: partial(errorMessage, print),
-    trace: partial(traceMessage, print),
+  let consoleOptions = {
+    prefix: self.name + ": ",
+    maxLogLevel: logLevel,
+    dump: print
+  };
+  let console = new ConsoleAPI(consoleOptions);
 
-    dir: function dir() {},
-    group: function group() {},
-    groupCollapsed: function groupCollapsed() {},
-    groupEnd: function groupEnd() {},
-    time: function time() {},
-    timeEnd: function timeEnd() {}
+  // As we freeze the console object, we can't modify this property afterward
+  Object.defineProperty(console, "maxLogLevel", {
+    get: function() {
+      return logLevel;
+    }
   });
 
   // We defined the `__exposedProps__` in our console chrome object.
   // Although it seems redundant, because we use `createObjectIn` too, in
   // worker.js, we are following what `ConsoleAPI` does. See:
   // http://mxr.mozilla.org/mozilla-central/source/dom/base/ConsoleAPI.js#132
   //
   // Meanwhile we're investigating with the platform team if `__exposedProps__`
   // are needed, or are just a left-over.
 
-  this.__exposedProps__ = Object.keys(this).reduce(function(exposed, prop) {
+  console.__exposedProps__ = Object.keys(ConsoleAPI.prototype).reduce(function(exposed, prop) {
     exposed[prop] = "r";
     return exposed;
   }, {});
 
-  Object.freeze(this);
+  Object.freeze(console);
+  return console;
 };
 exports.PlainTextConsole = PlainTextConsole;
--- a/addon-sdk/source/lib/sdk/deprecated/unit-test.js
+++ b/addon-sdk/source/lib/sdk/deprecated/unit-test.js
@@ -4,17 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 module.metadata = {
   "stability": "deprecated"
 };
 
-const memory = require('./memory');
+const { Cu } = require("chrome");
+const memory = require("./memory");
 var timer = require("../timers");
 var cfxArgs = require("@test/options");
 
 exports.findAndRunTests = function findAndRunTests(options) {
   var TestFinder = require("./unit-test-finder").TestFinder;
   var finder = new TestFinder({
     filter: options.filter,
     testInProcess: options.testInProcess,
@@ -438,27 +439,28 @@ TestRunner.prototype = {
     // We may already have registered a timeout callback
     if (this.waitTimeout)
       timer.clearTimeout(this.waitTimeout);
 
     this.waitTimeout = timer.setTimeout(tiredOfWaiting, ms);
   },
 
   startMany: function startMany(options) {
-    function runNextTest(self) {
+    let runNextTest = (self) => Cu.schedulePreciseGC(_ => {
       var test = options.tests.shift();
       if (options.stopOnError && self.test && self.test.failed) {
         self.console.error("aborted: test failed and --stop-on-error was specified");
         options.onDone(self);
       } else if (test) {
         self.start({test: test, onDone: runNextTest});
       } else {
         options.onDone(self);
       }
-    }
+    });
+
     runNextTest(this);
   },
 
   start: function start(options) {
     this.test = options.test;
     this.test.passed = 0;
     this.test.failed = 0;
     this.test.errors = {};
--- a/addon-sdk/source/lib/sdk/io/buffer.js
+++ b/addon-sdk/source/lib/sdk/io/buffer.js
@@ -4,79 +4,257 @@
  */
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
 
-const { Cc, Ci, CC } = require("chrome");
-const { Class } = require("../core/heritage");
+const { Cu } = require("chrome");
+const { TextEncoder, TextDecoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
 
-const Transcoder = CC("@mozilla.org/intl/scriptableunicodeconverter",
-                      "nsIScriptableUnicodeConverter");
+exports.TextEncoder = TextEncoder;
+exports.TextDecoder = TextDecoder;
+
 
-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));
+function Buffer(subject, encoding) {
+    var type = typeof(subject);
+    switch (type) {
+      case "number":
+        // Create typed array of the given size if number.
+        return Uint8Array(subject > 0 ? Math.floor(subject) : 0);
+      case "string":
+        // If string encode it and use buffer for the returned Uint8Array
+        // to create a local patched version that acts like node buffer.
+        encoding = encoding || "utf8";
+        return Uint8Array(TextEncoder(encoding).encode(subject).buffer);
+      case "object":
+        // If array or alike just make a copy with a local patched prototype.
+        return Uint8Array(subject);
+     default:
+        throw new TypeError("must start with number, buffer, array or string");
+    }
+}
+exports.Buffer = Buffer;
+
+// Tests if `value` is a Buffer.
+Buffer.isBuffer = value => value instanceof Buffer
+
+// Returns true if the encoding is a valid encoding argument & false otherwise
+Buffer.isEncoding = encoding => !!ENCODINGS[String(encoding).toLowerCase()]
+
+// Gives the actual byte length of a string. encoding defaults to 'utf8'.
+// This is not the same as String.prototype.length since that returns the
+// number of characters in a string.
+Buffer.byteLength = (value, encoding = "utf8") =>
+  TextEncoder(encoding).encode(value).byteLength
 
-    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
-    });
+// Direct copy of the nodejs's buffer implementation:
+// https://github.com/joyent/node/blob/b255f4c10a80343f9ce1cee56d0288361429e214/lib/buffer.js#L146-L177
+Buffer.concat = function(list, length) {
+  if (!Array.isArray(list))
+    throw new TypeError('Usage: Buffer.concat(list[, length])');
+
+  if (typeof length === 'undefined') {
+    length = 0;
+    for (var i = 0; i < list.length; i++)
+      length += list[i].length;
+  } else {
+    length = ~~length;
+  }
+
+  if (length < 0)
+    length = 0;
+
+  if (list.length === 0)
+    return new Buffer(0);
+  else if (list.length === 1)
+    return list[0];
+
+  if (length < 0)
+    throw new RangeError('length is not a positive number');
+
+  var buffer = new Buffer(length);
+  var pos = 0;
+  for (var i = 0; i < list.length; i++) {
+    var buf = list[i];
+    buf.copy(buffer, pos);
+    pos += buf.length;
+  }
+
+  return buffer;
+};
+
+// Node buffer is very much like Uint8Array although it has bunch of methods
+// that typically can be used in combination with `DataView` while preserving
+// access by index. Since in SDK echo module has it's own set of bult-ins it
+// ok to patch ours to make it nodejs Buffer compatible.
+Buffer.prototype = Uint8Array.prototype;
+Object.defineProperties(Buffer.prototype, {
+  view: {
+    get: function() this._view || (this._view = DataView(this.buffer))
   },
-  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);
+  toString: {
+    value: function(encoding, start, end) {
+      encoding = !!encoding ? (encoding + '').toLowerCase() : "utf8";
+      start = Math.max(0, ~~start);
+      end = Math.min(this.length, end === void(0) ? this.length : ~~end);
+      return TextDecoder(encoding).decode(this.subarray(start, end));
+    }
+  },
+  toJSON: {
+    value: function() ({ type: "Buffer", data: Array.slice(this, 0) })
   },
-  toJSON: function toJSON() {
-    return this.toString()
+  get: {
+    value: function(offset) this[offset]
+  },
+  set: {
+    value: function(offset, value) this[offset] = value
+  },
+  copy: {
+    value: function(target, offset, start, end)
+      Uint8Array.set(target, this.subarray(start, end), offset)
+  },
+  slice: {
+    value: Buffer.prototype.subarray
   },
-  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;
+  write: {
+    value: function(string, offset, length, encoding = "utf8") {
+      if (typeof(offset) === "string")
+        ([offset, length, encoding]) = [0, null, offset];
+      else if (typeof(length) === "string")
+        ([length, encoding]) = [null, length];
+
+      offset = ~~offset;
+      length = length || this.length - offset;
+      let buffer = TextEncoder(encoding).encode(string);
+      let result = Math.min(buffer.length, length);
+      if (buffer.length !== length)
+        buffer = buffer.subarray(0, length);
+      Uint8Array.set(this, buffer, offset);
+      return result;
+    }
   },
-  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));
+  fill: {
+    value: function fill(value, start, end) {
+      value = value || 0;
+      start = start || 0;
+      end = end || this.length;
+
+      if (typeof(value) === "string")
+        value = value.charCodeAt(0);
+      if (typeof(value) !== "number" || isNaN(value))
+        throw TypeError("value is not a number");
+      if (end < start)
+        throw new RangeError("end < start");
+
+      // Fill 0 bytes; we're done
+      if (end === start)
+        return 0;
+      if (this.length == 0)
+        return 0;
+
+      if (start < 0 || start >= this.length)
+        throw RangeError("start out of bounds");
+
+      if (end < 0 || end > this.length)
+        throw RangeError("end out of bounds");
+
+      let index = start;
+      while (index < end) this[index++] = value;
+    }
   }
 });
-Buffer.isBuffer = function isBuffer(buffer) {
-  return buffer instanceof Buffer
-};
-exports.Buffer = Buffer;
+
+// Define nodejs Buffer's getter and setter functions that just proxy
+// to internal DataView's equivalent methods.
+[["readUInt16LE", "getUint16", true],
+ ["readUInt16BE", "getUint16", false],
+ ["readInt16LE", "getInt16", true],
+ ["readInt16BE", "getInt16", false],
+ ["readUInt32LE", "getInt32", true],
+ ["readUInt32BE", "getInt32", false],
+ ["readInt32LE", "getInt32", true],
+ ["readInt32BE", "getInt32", false],
+ ["readFloatLE", "getFloat32", true],
+ ["readFloatBE", "getFloat32", false],
+ ["readDoubleLE", "getFloat64", true],
+ ["readDoubleBE", "getFloat64", false],
+ ["readUInt8", "getUint8"],
+ ["readInt8", "getInt8"]].forEach(([alias, name, littleEndian]) => {
+  Object.defineProperty(Buffer.prototype, alias, {
+    value: function(offset) this.view[name](offset, littleEndian)
+  });
+});
+
+[["writeUInt16LE", "setUint16", true],
+ ["writeUInt16BE", "setUint16", false],
+ ["writeInt16LE", "setInt16", true],
+ ["writeInt16BE", "setInt16", false],
+ ["writeUInt32LE", "setUint32", true],
+ ["writeUInt32BE", "setUint32", false],
+ ["writeInt32LE", "setInt32", true],
+ ["writeInt32BE", "setInt32", false],
+ ["writeFloatLE", "setFloat32", true],
+ ["writeFloatBE", "setFloat32", false],
+ ["writeDoubleLE", "setFloat64", true],
+ ["writeDoubleBE", "setFloat64", false],
+ ["writeUInt8", "setUint8"],
+ ["writeInt8", "setInt8"]].forEach(([alias, name, littleEndian]) => {
+  Object.defineProperty(Buffer.prototype, alias, {
+    value: function(value, offset) this.view[name](offset, value, littleEndian)
+  });
+});
+
+
+// List of supported encodings taken from:
+// http://mxr.mozilla.org/mozilla-central/source/dom/encoding/labelsencodings.properties
+const ENCODINGS = { "unicode-1-1-utf-8": 1, "utf-8": 1, "utf8": 1,
+  "866": 1, "cp866": 1, "csibm866": 1, "ibm866": 1, "csisolatin2": 1,
+  "iso-8859-2": 1, "iso-ir-101": 1, "iso8859-2": 1, "iso88592": 1,
+  "iso_8859-2": 1, "iso_8859-2:1987": 1, "l2": 1, "latin2": 1, "csisolatin3": 1,
+  "iso-8859-3": 1, "iso-ir-109": 1, "iso8859-3": 1, "iso88593": 1,
+  "iso_8859-3": 1, "iso_8859-3:1988": 1, "l3": 1, "latin3": 1, "csisolatin4": 1,
+  "iso-8859-4": 1, "iso-ir-110": 1, "iso8859-4": 1, "iso88594": 1,
+  "iso_8859-4": 1, "iso_8859-4:1988": 1, "l4": 1, "latin4": 1,
+  "csisolatincyrillic": 1, "cyrillic": 1, "iso-8859-5": 1, "iso-ir-144": 1,
+  "iso8859-5": 1, "iso88595": 1, "iso_8859-5": 1, "iso_8859-5:1988": 1,
+  "arabic": 1, "asmo-708": 1, "csiso88596e": 1, "csiso88596i": 1,
+  "csisolatinarabic": 1, "ecma-114": 1, "iso-8859-6": 1, "iso-8859-6-e": 1,
+  "iso-8859-6-i": 1, "iso-ir-127": 1, "iso8859-6": 1, "iso88596": 1,
+  "iso_8859-6": 1, "iso_8859-6:1987": 1, "csisolatingreek": 1, "ecma-118": 1,
+  "elot_928": 1, "greek": 1, "greek8": 1, "iso-8859-7": 1, "iso-ir-126": 1,
+  "iso8859-7": 1, "iso88597": 1, "iso_8859-7": 1, "iso_8859-7:1987": 1,
+  "sun_eu_greek": 1, "csiso88598e": 1, "csisolatinhebrew": 1, "hebrew": 1,
+  "iso-8859-8": 1, "iso-8859-8-e": 1, "iso-ir-138": 1, "iso8859-8": 1,
+  "iso88598": 1, "iso_8859-8": 1, "iso_8859-8:1988": 1, "visual": 1,
+  "csiso88598i": 1, "iso-8859-8-i": 1, "logical": 1, "csisolatin6": 1,
+  "iso-8859-10": 1, "iso-ir-157": 1, "iso8859-10": 1, "iso885910": 1,
+  "l6": 1, "latin6": 1, "iso-8859-13": 1, "iso8859-13": 1, "iso885913": 1,
+  "iso-8859-14": 1, "iso8859-14": 1, "iso885914": 1, "csisolatin9": 1,
+  "iso-8859-15": 1, "iso8859-15": 1, "iso885915": 1, "iso_8859-15": 1,
+  "l9": 1, "iso-8859-16": 1, "cskoi8r": 1, "koi": 1, "koi8": 1, "koi8-r": 1,
+  "koi8_r": 1, "koi8-u": 1, "csmacintosh": 1, "mac": 1, "macintosh": 1,
+  "x-mac-roman": 1, "dos-874": 1, "iso-8859-11": 1, "iso8859-11": 1,
+  "iso885911": 1, "tis-620": 1, "windows-874": 1, "cp1250": 1,
+  "windows-1250": 1, "x-cp1250": 1, "cp1251": 1, "windows-1251": 1,
+  "x-cp1251": 1, "ansi_x3.4-1968": 1, "ascii": 1, "cp1252": 1, "cp819": 1,
+  "csisolatin1": 1, "ibm819": 1, "iso-8859-1": 1, "iso-ir-100": 1,
+  "iso8859-1": 1, "iso88591": 1, "iso_8859-1": 1, "iso_8859-1:1987": 1,
+  "l1": 1, "latin1": 1, "us-ascii": 1, "windows-1252": 1, "x-cp1252": 1,
+  "cp1253": 1, "windows-1253": 1, "x-cp1253": 1, "cp1254": 1, "csisolatin5": 1,
+  "iso-8859-9": 1, "iso-ir-148": 1, "iso8859-9": 1, "iso88599": 1,
+  "iso_8859-9": 1, "iso_8859-9:1989": 1, "l5": 1, "latin5": 1,
+  "windows-1254": 1, "x-cp1254": 1, "cp1255": 1, "windows-1255": 1,
+  "x-cp1255": 1, "cp1256": 1, "windows-1256": 1, "x-cp1256": 1, "cp1257": 1,
+  "windows-1257": 1, "x-cp1257": 1, "cp1258": 1, "windows-1258": 1,
+  "x-cp1258": 1, "x-mac-cyrillic": 1, "x-mac-ukrainian": 1, "chinese": 1,
+  "csgb2312": 1, "csiso58gb231280": 1, "gb2312": 1, "gb_2312": 1,
+  "gb_2312-80": 1, "gbk": 1, "iso-ir-58": 1, "x-gbk": 1, "gb18030": 1,
+  "hz-gb-2312": 1, "big5": 1, "big5-hkscs": 1, "cn-big5": 1, "csbig5": 1,
+  "x-x-big5": 1, "cseucpkdfmtjapanese": 1, "euc-jp": 1, "x-euc-jp": 1,
+  "csiso2022jp": 1, "iso-2022-jp": 1, "csshiftjis": 1, "ms_kanji": 1,
+  "shift-jis": 1, "shift_jis": 1, "sjis": 1, "windows-31j": 1, "x-sjis": 1,
+  "cseuckr": 1, "csksc56011987": 1, "euc-kr": 1, "iso-ir-149": 1, "korean": 1,
+  "ks_c_5601-1987": 1, "ks_c_5601-1989": 1, "ksc5601": 1, "ksc_5601": 1,
+  "windows-949": 1, "csiso2022kr": 1, "iso-2022-kr": 1, "utf-16": 1,
+  "utf-16le": 1, "utf-16be": 1, "x-user-defined": 1 };
--- a/addon-sdk/source/lib/sdk/io/fs.js
+++ b/addon-sdk/source/lib/sdk/io/fs.js
@@ -7,20 +7,22 @@
 module.metadata = {
   "stability": "experimental"
 };
 
 const { Cc, Ci, CC } = require("chrome");
 
 const { setTimeout } = require("../timers");
 const { Stream, InputStream, OutputStream } = require("./stream");
+const { emit, on } = require("../event/core");
 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");
@@ -28,16 +30,18 @@ const BinaryOutputStream = CC("@mozilla.
                               "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 { OPEN_UNBUFFERED } = Ci.nsITransport;
+
 
 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:
@@ -152,24 +156,26 @@ const ReadStream = Class({
       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, {
+      asyncInputStream: transport.openInputStream(null, 0, 0)
+    });
 
-    InputStream.prototype.initialize.call(this, {
-      input: binaryInputStream, pump: pump
+    // Close file descriptor on end and destroy the stream.
+    on(this, "end", _ => {
+      this.destroy();
+      emit(this, "close");
     });
+
     this.read();
   },
   destroy: function() {
     closeSync(this.fd);
     InputStream.prototype.destroy.call(this);
   }
 });
 exports.ReadStream = ReadStream;
@@ -206,31 +212,30 @@ const WriteStream = Class({
       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);
+    OutputStream.prototype.initialize.call(this, {
+      asyncOutputStream: transport.openOutputStream(OPEN_UNBUFFERED, 0, 0),
+      output: output
+    });
 
-    // Storing output stream so that it can beaccessed later.
-    OutputStream.prototype.initialize.call(this, {
-      output: binaryOutputStream,
-      asyncOutputStream: asyncOutputStream
+    // For write streams "finish" basically means close.
+    on(this, "finish", _ => {
+       this.destroy();
+       emit(this, "close");
     });
   },
   destroy: function() {
+    OutputStream.prototype.destroy.call(this);
     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({
@@ -360,17 +365,17 @@ 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) {
+function ftruncateSync(fd, length = 0) {
   writeSync(fd, new Buffer(length), 0, length, 0);
 }
 exports.ftruncateSync = ftruncateSync;
 
 function chownSync(path, uid, gid) {
   throw Error("Not implemented yet!!");
 }
 exports.chownSync = chownSync;
@@ -629,16 +634,18 @@ exports.close = close;
 
 /**
  * Synchronous open(2).
  */
 function openSync(path, flags, mode) {
   let [ fd, flags, mode, file ] =
       [ { path: path }, Flags(flags), Mode(mode), nsILocalFile(path) ];
 
+  nsIFile(fd, file);
+
   // 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);
@@ -670,17 +677,19 @@ function writeSync(fd, buffer, offset, l
   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);
+
+  let output = BinaryOutputStream(nsIFileOutputStream(fd));
+  nsIBinaryOutputStream(fd, output);
   // 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;
 
 /**
@@ -731,20 +740,24 @@ function readSync(fd, buffer, offset, le
   // 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);
+  if (offset === 0) binaryInputStream.readArrayBuffer(count, buffer.buffer);
+  else {
+    let chunk = new Buffer(count);
+    binaryInputStream.readArrayBuffer(count, chunk.buffer);
+    chunk.copy(buffer, offset);
+  }
 
-  return bytes;
+  return buffer.slice(offset, offset + count);
 };
 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.
@@ -754,19 +767,19 @@ exports.readSync = readSync;
  * `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("data", function onData(data) {
+      data.copy(buffer, offset + bytesRead);
+      bytesRead += data.length;
   });
   readStream.on("end", function onEnd() {
     callback(null, bytesRead, buffer);
     readStream.destroy();
   });
 };
 exports.read = read;
 
@@ -776,44 +789,52 @@ exports.read = read;
  * contents of the file.
  */
 function readFile(path, encoding, callback) {
   if (isFunction(encoding)) {
     callback = encoding
     encoding = null
   }
 
-  let buffer = new Buffer();
+  let buffer = null;
   try {
     let readStream = new ReadStream(path);
-    readStream.on("data", function(chunck) {
-      chunck.copy(buffer, buffer.length);
+    readStream.on("data", function(data) {
+      if (!buffer) buffer = data;
+      else {
+        let bufferred = buffer
+        buffer = new Buffer(buffer.length + data.length);
+        bufferred.copy(buffer, 0);
+        data.copy(buffer, bufferred.length);
+      }
     });
     readStream.on("error", function onError(error) {
       callback(error);
-      readStream.destroy();
     });
     readStream.on("end", function onEnd() {
+      // Note: Need to destroy before invoking a callback
+      // so that file descriptor is released.
+      readStream.destroy();
       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");
+  let size = fstatSync(fd).size;
+  let buffer = new Buffer(size);
   try {
     readSync(fd, buffer, 0, ALL, 0);
   }
   finally {
     closeSync(fd);
   }
   return buffer;
 };
@@ -828,23 +849,26 @@ function writeFile(path, content, encodi
     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) {
+    let error = null;
+
+    writeStream.end(content, function() {
+      writeStream.destroy();
       callback(error);
-      writeStream.destroy();
     });
-    writeStream.write(content, function onDrain() {
+
+    writeStream.on("error", function onError(reason) {
+      error = reason;
       writeStream.destroy();
-      callback(null);
     });
   } catch (error) {
     callback(error);
   }
 };
 exports.writeFile = writeFile;
 
 /**
--- a/addon-sdk/source/lib/sdk/io/stream.js
+++ b/addon-sdk/source/lib/sdk/io/stream.js
@@ -3,109 +3,55 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
+const { CC, Cc, Ci, Cu, Cr, components } = require("chrome");
 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");
+
+
+const MultiplexInputStream = CC("@mozilla.org/io/multiplex-input-stream;1",
+                                "nsIMultiplexInputStream");
+const AsyncStreamCopier = CC("@mozilla.org/network/async-stream-copier;1",
+                             "nsIAsyncStreamCopier", "init");
+const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
+                             "nsIStringInputStream");
+const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
+                                  "nsIArrayBufferInputStream");
 
-function isFunction(value) typeof value === "function"
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+                             "nsIBinaryInputStream", "setInputStream");
+const InputStreamPump = CC("@mozilla.org/network/input-stream-pump;1",
+                           "nsIInputStreamPump", "init");
+
+const threadManager = Cc["@mozilla.org/thread-manager;1"].
+                      getService(Ci.nsIThreadManager);
+
+const eventTarget = Cc["@mozilla.org/network/socket-transport-service;1"].
+                    getService(Ci.nsIEventTarget);
+
+let 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);
+  return function(target, value) {
+    if (value)
+      map.set(target, value);
+    return map.get(target);
   }
 }
 
-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) {
@@ -115,17 +61,18 @@ const Stream = Class({
     let source = this;
     function onData(chunk) {
       if (target.writable) {
         if (false === target.write(chunk))
           source.pause();
       }
     }
     function onDrain() {
-      if (source.readable) source.resume();
+      if (source.readable)
+        source.resume();
     }
     function onEnd() {
       target.end();
     }
     function onPause() {
       source.pause();
     }
     function onResume() {
@@ -171,154 +118,320 @@ const Stream = Class({
     emit(this, "resume");
   },
   destroySoon: function destroySoon() {
     this.destroy();
   }
 });
 exports.Stream = Stream;
 
+
+let nsIStreamListener = accessor();
+let nsIInputStreamPump = accessor();
+let nsIAsyncInputStream = accessor();
+let nsIBinaryInputStream = accessor();
+
+const StreamListener = Class({
+  initialize: function(stream) {
+    this.stream = stream;
+  },
+
+  // Next three methods are part of `nsIStreamListener` interface and are
+  // invoked by `nsIInputStreamPump.asyncRead`.
+  onDataAvailable: function(request, context, input, offset, count) {
+    let stream = this.stream;
+    let buffer = new ArrayBuffer(count);
+    nsIBinaryInputStream(stream).readArrayBuffer(count, buffer);
+    emit(stream, "data", new Buffer(buffer, stream.encoding));
+  },
+
+  // Next two methods implement `nsIRequestObserver` interface and are invoked
+  // by `nsIInputStreamPump.asyncRead`.
+  onStartRequest: function() {},
+  // Called to signify the end of an asynchronous request. We only care to
+  // discover errors.
+  onStopRequest: function(request, context, status) {
+    let stream = this.stream;
+    stream.readable = false;
+    if (!components.isSuccessCode(status))
+      emit(stream, "error", status);
+    else
+      emit(stream, "end");
+  }
+});
+
+
 const InputStream = Class({
   extends: Stream,
+  readable: false,
+  paused: false,
   initialize: function initialize(options) {
-    let { input, pump } = options;
+    let { asyncInputStream } = options;
 
     this.readable = true;
-    this.paused = false;
-    nsIInputStream(this, input);
-    nsIInputStreamPump(this, pump);
+
+    let binaryInputStream = new BinaryInputStream(asyncInputStream);
+    let inputStreamPump = new InputStreamPump(asyncInputStream,
+                                              -1, -1, 0, 0, false);
+    let streamListener = new StreamListener(this);
+
+    nsIAsyncInputStream(this, asyncInputStream);
+    nsIInputStreamPump(this, inputStreamPump);
+    nsIBinaryInputStream(this, binaryInputStream);
+    nsIStreamListener(this, streamListener);
+
+    this.asyncInputStream = asyncInputStream;
+    this.inputStreamPump = inputStreamPump;
+    this.binaryInputStream = binaryInputStream;
   },
   get status() nsIInputStreamPump(this).status,
-  read: function() pump(this),
+  read: function() {
+    nsIInputStreamPump(this).asyncRead(nsIStreamListener(this), null);
+  },
   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() {
+  close: function close() {
     this.readable = false;
-    try {
-      emit(this, "close", null);
-      nsIInputStreamPump(this).cancel(null);
-      nsIInputStreamPump(this, null);
+    nsIInputStreamPump(this).cancel(Cr.NS_OK);
+    nsIBinaryInputStream(this).close();
+    nsIAsyncInputStream(this).close();
+  },
+  destroy: function destroy() {
+    this.close();
 
-      nsIInputStream(this).close();
-      nsIInputStream(this, null);
-    } catch (error) {
-      emit(this, "error", error);
-    }
+    nsIInputStreamPump(this);
+    nsIAsyncInputStream(this);
+    nsIBinaryInputStream(this);
+    nsIStreamListener(this);
   }
 });
 exports.InputStream = InputStream;
 
+
+
+let nsIRequestObserver = accessor();
+let nsIAsyncOutputStream = accessor();
+let nsIAsyncStreamCopier = accessor();
+let nsIMultiplexInputStream = accessor();
+
+const RequestObserver = Class({
+  initialize: function(stream) {
+    this.stream = stream;
+  },
+  // Method is part of `nsIRequestObserver` interface that is
+  // invoked by `nsIAsyncStreamCopier.asyncCopy`.
+  onStartRequest: function() {},
+  // Method is part of `nsIRequestObserver` interface that is
+  // invoked by `nsIAsyncStreamCopier.asyncCopy`.
+  onStopRequest: function(request, context, status) {
+    let stream = this.stream;
+    stream.drained = true;
+
+    // Remove copied chunk.
+    let multiplexInputStream = nsIMultiplexInputStream(stream);
+    multiplexInputStream.removeStream(0);
+
+    // If there was an error report.
+    if (!components.isSuccessCode(status))
+      emit(stream, "error", status);
+
+    // If there more chunks in queue then flush them.
+    else if (multiplexInputStream.count)
+      stream.flush();
+
+    // If stream is still writable notify that queue has drained.
+    else if (stream.writable)
+      emit(stream, "drain");
+
+    // If stream is no longer writable close it.
+    else {
+      nsIAsyncStreamCopier(stream).cancel(Cr.NS_OK);
+      nsIMultiplexInputStream(stream).close();
+      nsIAsyncOutputStream(stream).close();
+      nsIAsyncOutputStream(stream).flush();
+    }
+  }
+});
+
+const OutputStreamCallback = Class({
+  initialize: function(stream) {
+    this.stream = stream;
+  },
+  // Method is part of `nsIOutputStreamCallback` interface that
+  // is invoked by `nsIAsyncOutputStream.asyncWait`. It is registered
+  // with `WAIT_CLOSURE_ONLY` flag that overrides the default behavior,
+  // causing the `onOutputStreamReady` notification to be suppressed until
+  // the stream becomes closed.
+  onOutputStreamReady: function(nsIAsyncOutputStream) {
+    emit(this.stream, "finish");
+  }
+});
+
 const OutputStream = Class({
   extends: Stream,
+  writable: false,
+  drained: true,
+  get bufferSize() {
+    let multiplexInputStream = nsIMultiplexInputStream(this);
+    return multiplexInputStream && multiplexInputStream.available();
+  },
   initialize: function initialize(options) {
-    let { output, asyncOutputStream } = options;
+    let { asyncOutputStream, output } = options;
+    this.writable = true;
+
+    // Ensure that `nsIAsyncOutputStream` was provided.
+    asyncOutputStream.QueryInterface(Ci.nsIAsyncOutputStream);
 
-    this.writable = true;
-    nsIOutputStream(this, output);
+    // Create a `nsIMultiplexInputStream` and `nsIAsyncStreamCopier`. Former
+    // is used to queue written data chunks that `asyncStreamCopier` will
+    // asynchronously drain into `asyncOutputStream`.
+    let multiplexInputStream = MultiplexInputStream();
+    let asyncStreamCopier = AsyncStreamCopier(multiplexInputStream,
+                                              output || asyncOutputStream,
+                                              eventTarget,
+                                              // nsIMultiplexInputStream
+                                              // implemnts .readSegments()
+                                              true,
+                                              // nsIOutputStream may or
+                                              // may not implemnet
+                                              // .writeSegments().
+                                              false,
+                                              // Use default buffer size.
+                                              null,
+                                              // Should not close an input.
+                                              false,
+                                              // Should not close an output.
+                                              false);
+
+    // Create `requestObserver` implementing `nsIRequestObserver` interface
+    // in the constructor that's gonna be reused across several flushes.
+    let requestObserver = RequestObserver(this);
+
+
+    // Create observer that implements `nsIOutputStreamCallback` and register
+    // using `WAIT_CLOSURE_ONLY` flag. That way it will be notfied once
+    // `nsIAsyncOutputStream` is closed.
+    asyncOutputStream.asyncWait(OutputStreamCallback(this),
+                                asyncOutputStream.WAIT_CLOSURE_ONLY,
+                                0,
+                                threadManager.currentThread);
+
+    nsIRequestObserver(this, requestObserver);
     nsIAsyncOutputStream(this, asyncOutputStream);
+    nsIMultiplexInputStream(this, multiplexInputStream);
+    nsIAsyncStreamCopier(this, asyncStreamCopier);
+
+    this.asyncOutputStream = asyncOutputStream;
+    this.multiplexInputStream = multiplexInputStream;
+    this.asyncStreamCopier = asyncStreamCopier;
   },
   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");
+    if (!this.writable) throw Error("stream is not writable");
+
+    let chunk = null;
 
-    try {
-      // If content is not a buffer then we create one out of it.
-      if (!Buffer.isBuffer(content))
-        content = new Buffer(content, encoding);
+    // If content is not a buffer then we create one out of it.
+    if (Buffer.isBuffer(content)) {
+      chunk = new ArrayBufferInputStream();
+      chunk.setData(content.buffer, 0, content.length);
+    }
+    else {
+      chunk = new StringInputStream();
+      chunk.setData(content, content.length);
+    }
 
-      // 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);
+
+    // Queue up chunk to be copied to output sync.
+    nsIMultiplexInputStream(this).appendStream(chunk);
+    this.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);
+    return this.drained;
+  },
+  flush: function() {
+    if (this.drained) {
+      this.drained = false;
+      nsIAsyncStreamCopier(this).asyncCopy(nsIRequestObserver(this), null);
     }
   },
-  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.
+    // Setting a listener to "finish" event if passed.
     if (isFunction(callback))
-      this.once("close", callback);
+      this.once("finish", 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();
+      this.write(content, encoding);
+    this.writable = false;
+
+    // Close `asyncOutputStream` only if output has drained. If it's
+    // not drained than `asyncStreamCopier` is busy writing, so let
+    // it finish. Note that since `this.writable` is false copier will
+    // close `asyncOutputStream` once output drains.
+    if (this.drained)
+      nsIAsyncOutputStream(this).close();
   },
-  destroy: function destroy(callback) {
-    try {
-      this.end(callback);
-      nsIOutputStream(this, null);
-      nsIAsyncOutputStream(this, null);
-    } catch (error) {
-      emit(this, "error", error);
-    }
+  destroy: function destroy() {
+    nsIAsyncOutputStream(this).close();
+    nsIAsyncOutputStream(this);
+    nsIMultiplexInputStream(this);
+    nsIAsyncStreamCopier(this);
+    nsIRequestObserver(this);
   }
 });
 exports.OutputStream = OutputStream;
 
 const DuplexStream = Class({
   extends: Stream,
+  implements: [InputStream, OutputStream],
+  allowHalfOpen: true,
   initialize: function initialize(options) {
-    let { input, output, pump } = options;
+    options = options || {};
+    let { readable, writable, allowHalfOpen } = options;
+
+    InputStream.prototype.initialize.call(this, options);
+    OutputStream.prototype.initialize.call(this, options);
+
+    if (readable === false)
+      this.readable = false;
 
-    this.writable = true;
-    this.readable = true;
-    this.encoding = null;
+    if (writable === false)
+      this.writable = false;
+
+    if (allowHalfOpen === false)
+      this.allowHalfOpen = false;
 
-    nsIInputStream(this, input);
-    nsIOutputStream(this, output);
-    nsIInputStreamPump(this, pump);
+    // If in a half open state and it's disabled enforce end.
+    this.once("end", () => {
+      if (!this.allowHalfOpen && (!this.readable || !this.writable))
+        this.end();
+    });
   },
-  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/lang/functional.js
+++ b/addon-sdk/source/lib/sdk/lang/functional.js
@@ -7,17 +7,17 @@
 // those goes to him.
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
-const { setTimeout } = require("../timers");
+const { setImmediate, setTimeout } = require("../timers");
 const { deprecateFunction } = require("../util/deprecate");
 
 /**
  * Takes `lambda` function and returns a method. When returned method is
  * invoked it calls wrapped `lambda` and passes `this` as a first argument
  * and given argument as rest.
  */
 function method(lambda) {
@@ -25,23 +25,22 @@ function method(lambda) {
     return lambda.apply(null, [this].concat(Array.slice(arguments)));
   }
 }
 exports.method = method;
 
 /**
  * Takes a function and returns a wrapped one instead, calling which will call
  * original function in the next turn of event loop. This is basically utility
- * to do `setTimeout(function() { ... }, 0)`, with a difference that returned
+ * to do `setImmediate(function() { ... })`, with a difference that returned
  * function is reused, instead of creating a new one each time. This also allows
  * to use this functions as event listeners.
  */
 function defer(f) {
-  return function deferred()
-    setTimeout(invoke, 0, f, arguments, this);
+  return function deferred() setImmediate(invoke, f, arguments, this);
 }
 exports.defer = defer;
 // Exporting `remit` alias as `defer` may conflict with promises.
 exports.remit = defer;
 
 /*
  * Takes a funtion and returns a wrapped function that returns `this`
  */
--- a/addon-sdk/source/lib/sdk/system.js
+++ b/addon-sdk/source/lib/sdk/system.js
@@ -63,29 +63,21 @@ exports.exit = function exit(code) {
     stream.write(status, status.length);
     stream.flush();
     stream.close();
   }
 
   appStartup.quit(code ? E_ATTEMPT : E_FORCE);
 };
 
-exports.stdout = new function() {
-  let write = dump
-  if ('logFile' in options && options.logFile) {
-    let mode = PR_WRONLY | PR_CREATE_FILE | PR_APPEND;
-    let stream = openFile(options.logFile, mode);
-    write = function write(data) {
-      let text = String(data);
-      stream.write(text, text.length);
-      stream.flush();
-    }
-  }
-  return Object.freeze({ write: write });
-};
+// Adapter for nodejs's stdout & stderr:
+// http://nodejs.org/api/process.html#process_process_stdout
+let stdout = Object.freeze({ write: dump, end: dump });
+exports.stdout = stdout;
+exports.stderr = stdout;
 
 /**
  * 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-US/docs/Code_snippets/File_I_O#Getting_files_in_special_directories
  * http://mxr.mozilla.org/mozilla-central/source/xpcom/io/nsAppDirectoryServiceDefs.h
  * @example
  *
@@ -129,17 +121,17 @@ exports.build = appInfo.appBuildID;
  * The XUL application's UUID.
  * This has traditionally been in the form
  * `{AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE}` but for some applications it may
  * be: "appname@vendor.tld".
  */
 exports.id = appInfo.ID;
 
 /**
- * The name of the application. 
+ * The name of the application.
  */
 exports.name = appInfo.name;
 
 /**
  * The XUL application's version, for example "0.8.0+" or "3.7a1pre".
  */
 exports.version = appInfo.version;
 
--- a/addon-sdk/source/lib/sdk/system/globals.js
+++ b/addon-sdk/source/lib/sdk/system/globals.js
@@ -17,30 +17,17 @@ let consoleService = Cc['@mozilla.org/co
                      QueryInterface(Ci.nsIConsoleService);
 
 // On windows dump does not writes into stdout so cfx can't read thous dumps.
 // To workaround this issue we write to a special file from which cfx will
 // read and print to the console.
 // For more details see: bug-673383
 exports.dump = stdout.write;
 
-// Bug 718230: We need to send console messages to stdout and JS Console
-function forsakenConsoleDump(msg, level) {
-  stdout.write(msg);
-
-  if (level === 'error') {
-    let error = ScriptError();
-    msg = msg.replace(/^error: /, '');
-    error.init(msg, null, null, 0, 0, 0, 'Add-on SDK');
-    consoleService.logMessage(error);
-  }
-  else
-    consoleService.logStringMessage(msg);
-};
-exports.console = new PlainTextConsole(forsakenConsoleDump);
+exports.console = new PlainTextConsole();
 
 // Provide CommonJS `define` to allow authoring modules in a format that can be
 // loaded both into jetpack and into browser via AMD loaders.
 Object.defineProperty(exports, 'define', {
   // `define` is provided as a lazy getter that binds below defined `define`
   // function to the module scope, so that require, exports and module
   // variables remain accessible.
   configurable: true,
--- a/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
+++ b/addon-sdk/source/lib/sdk/tabs/tabs-firefox.js
@@ -21,20 +21,19 @@ function newTabWindow(options) {
 }
 
 Object.defineProperties(tabs, {
   open: { value: function open(options) {
     if (options.inNewWindow) {
         newTabWindow(options);
         return undefined;
     }
-    // Open in active window if new window was not required.
 
     let activeWindow = windows.activeWindow;
-    let privateState = !!options.isPrivate;
+    let privateState = (supportPrivateTabs && (options.isPrivate || isPrivate(activeWindow))) || false;
 
     // if the active window is in the state that we need then use it
     if (activeWindow && (!supportPrivateTabs || privateState === isPrivate(activeWindow))) {
       activeWindow.tabs.open(options);
     }
     else {
       // find a window in the state that we need
       let window = getWindow(privateState);
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
-const { Cc,Ci } = require("chrome");
+const { Cc, Ci, Cu } = require("chrome");
 const { Loader } = require('./loader');
 const { serializeStack, parseStack  } = require("toolkit/loader");
 const { setTimeout } = require('../timers');
 const memory = require('../deprecated/memory');
 const { PlainTextConsole } = require("../console/plain-text");
 const { when: unload } = require("../system/unload");
 const { format, fromException }  = require("../console/traceback");
 const system = require("../system");
@@ -142,19 +142,21 @@ function dictDiff(last, curr) {
   return diff;
 }
 
 function reportMemoryUsage() {
   memory.gc();
 
   var mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
             .getService(Ci.nsIMemoryReporterManager);
+
   var reporters = mgr.enumerateReporters();
   if (reporters.hasMoreElements())
     print("\n");
+
   while (reporters.hasMoreElements()) {
     var reporter = reporters.getNext();
     reporter.QueryInterface(Ci.nsIMemoryReporter);
     print(reporter.description + ": " + reporter.memoryUsed + "\n");
   }
 
   var weakrefs = [info.weakref.get()
                   for each (info in memory.getObjects())];
@@ -162,36 +164,34 @@ function reportMemoryUsage() {
   print("Tracked memory objects in testing sandbox: " +
         weakrefs.length + "\n");
 }
 
 var gWeakrefInfo;
 
 function checkMemory() {
   memory.gc();
-  setTimeout(function () {
-    memory.gc();
-    setTimeout(function () {
-      let leaks = getPotentialLeaks();
-      let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
-        return !(url in startLeaks.compartments);
-      });
+  Cu.schedulePreciseGC(function () {
+    let leaks = getPotentialLeaks();
+
+    let compartmentURLs = Object.keys(leaks.compartments).filter(function(url) {
+      return !(url in startLeaks.compartments);
+    });
 
-      let windowURLs = Object.keys(leaks.windows).filter(function(url) {
-        return !(url in startLeaks.windows);
-      });
+    let windowURLs = Object.keys(leaks.windows).filter(function(url) {
+      return !(url in startLeaks.windows);
+    });
 
-      for (let url of compartmentURLs)
-        console.warn("LEAKED", leaks.compartments[url]);
+    for (let url of compartmentURLs)
+      console.warn("LEAKED", leaks.compartments[url]);
 
-      for (let url of windowURLs)
-        console.warn("LEAKED", leaks.windows[url]);
+    for (let url of windowURLs)
+      console.warn("LEAKED", leaks.windows[url]);
 
-      showResults();
-    });
+    showResults();
   });
 }
 
 function showResults() {
   if (gWeakrefInfo) {
     gWeakrefInfo.forEach(
       function(info) {
         var ref = info.weakref.get();
@@ -293,16 +293,17 @@ function getPotentialLeaks() {
   let uri = ioService.newURI("chrome://global/content/", "UTF-8", null);
   let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
                   getService(Ci.nsIChromeRegistry);
   uri = chromeReg.convertChromeURL(uri);
   let spec = uri.spec;
   let pos = spec.indexOf("!/");
   WHITELIST_BASE_URLS.push(spec.substring(0, pos + 2));
 
+  let zoneRegExp = new RegExp("^explicit/js-non-window/zones/zone[^/]+/compartment\\((.+)\\)");
   let compartmentRegexp = new RegExp("^explicit/js-non-window/compartments/non-window-global/compartment\\((.+)\\)/");
   let compartmentDetails = new RegExp("^([^,]+)(?:, (.+?))?(?: \\(from: (.*)\\))?$");
   let windowRegexp = new RegExp("^explicit/window-objects/top\\((.*)\\)/active");
   let windowDetails = new RegExp("^(.*), id=.*$");
 
   function isPossibleLeak(item) {
     if (!item.location)
       return false;
@@ -313,18 +314,19 @@ function getPotentialLeaks() {
     }
 
     return true;
   }
 
   let compartments = {};
   let windows = {};
   function logReporter(process, path, kind, units, amount, description) {
-    let matches = compartmentRegexp.exec(path);
-    if (matches) {
+    let matches;
+
+    if ((matches = compartmentRegexp.exec(path)) || (matches = zoneRegExp.exec(path))) {
       if (matches[1] in compartments)
         return;
 
       let details = compartmentDetails.exec(matches[1]);
       if (!details) {
         console.error("Unable to parse compartment detail " + matches[1]);
         return;
       }
@@ -571,17 +573,17 @@ var runTests = exports.runTests = functi
     print("Running tests on " + system.name + " " + system.version +
           "/Gecko " + system.platformVersion + " (" +
           system.id + ") under " +
           system.platform + "/" + system.architecture + ".\n");
 
     if (options.parseable)
       testConsole = new TestRunnerTinderboxConsole(options);
     else
-      testConsole = new TestRunnerConsole(new PlainTextConsole(print), options);
+      testConsole = new TestRunnerConsole(new PlainTextConsole(), options);
 
     loader = Loader(module, {
       console: testConsole,
       global: {} // useful for storing things like coverage testing.
     });
 
     // Load these before getting initial leak stats as they will still be in
     // memory when we check later
--- a/addon-sdk/source/lib/sdk/timers.js
+++ b/addon-sdk/source/lib/sdk/timers.js
@@ -2,52 +2,104 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 module.metadata = {
   "stability": "stable"
 };
 
-const { CC, Ci } = require('chrome');
-const { when: unload } = require('./system/unload');
+const { CC, Cc, Ci } = require("chrome");
+const { when: unload } = require("./system/unload");
 
 const { TYPE_ONE_SHOT, TYPE_REPEATING_SLACK } = Ci.nsITimer;
-const Timer = CC('@mozilla.org/timer;1', 'nsITimer');
+const Timer = CC("@mozilla.org/timer;1", "nsITimer");
 const timers = Object.create(null);
+const threadManager = Cc["@mozilla.org/thread-manager;1"].
+                      getService(Ci.nsIThreadManager);
+const prefBranch = Cc["@mozilla.org/preferences-service;1"].
+                    getService(Ci.nsIPrefService).
+                    QueryInterface(Ci.nsIPrefBranch);
+
+let MIN_DELAY = 4;
+// Try to get min timeout delay used by browser.
+try { MIN_DELAY = prefBranch.getIntPref("dom.min_timeout_value"); } finally {}
+
 
 // Last timer id.
 let lastID = 0;
 
 // Sets typer either by timeout or by interval
 // depending on a given type.
-function setTimer(type, callback, delay) {
+function setTimer(type, callback, delay, ...args) {
   let id = ++ lastID;
   let timer = timers[id] = Timer();
-  let args = Array.slice(arguments, 3);
   timer.initWithCallback({
     notify: function notify() {
       try {
         if (type === TYPE_ONE_SHOT)
           delete timers[id];
         callback.apply(null, args);
       }
       catch(error) {
         console.exception(error);
       }
     }
-  }, delay || 0, type);
+  }, Math.max(delay || MIN_DELAY), type);
   return id;
 }
 
 function unsetTimer(id) {
   let timer = timers[id];
   delete timers[id];
-  if (timer)
-    timer.cancel();
+  if (timer) timer.cancel();
 }
 
+let immediates = new Map();
+
+let dispatcher = _ => {
+  // Allow scheduling of a new dispatch loop.
+  dispatcher.scheduled = false;
+  // Take a snapshot of timer `id`'s that have being present before
+  // starting a dispatch loop, in order to ignore timers registered
+  // in side effect to dispatch while also skipping immediates that
+  // were removed in side effect.
+  let ids = [id for ([id] of immediates)];
+  for (let id of ids) {
+    let immediate = immediates.get(id);
+    if (immediate) {
+      immediates.delete(id);
+      try { immediate(); }
+      catch (error) { console.exception(error); }
+    }
+  }
+}
+
+function setImmediate(callback, ...params) {
+  let id = ++ lastID;
+  // register new immediate timer with curried params.
+  immediates.set(id, _ => callback.apply(callback, params));
+  // if dispatch loop is not scheduled schedule one. Own scheduler
+  if (!dispatcher.scheduled) {
+    dispatcher.scheduled = true;
+    threadManager.currentThread.dispatch(dispatcher,
+                                         Ci.nsIThread.DISPATCH_NORMAL);
+  }
+  return id;
+}
+
+function clearImmediate(id) {
+  immediates.delete(id);
+}
+
+// Bind timers so that toString-ing them looks same as on native timers.
+exports.setImmediate = setImmediate.bind(null);
+exports.clearImmediate = clearImmediate.bind(null);
 exports.setTimeout = setTimer.bind(null, TYPE_ONE_SHOT);
 exports.setInterval = setTimer.bind(null, TYPE_REPEATING_SLACK);
 exports.clearTimeout = unsetTimer.bind(null);
 exports.clearInterval = unsetTimer.bind(null);
 
-unload(function() { Object.keys(timers).forEach(unsetTimer) });
+// all timers are cleared out on unload.
+unload(function() {
+  immediates.clear();
+  Object.keys(timers).forEach(unsetTimer)
+});
--- a/addon-sdk/source/lib/sdk/util/array.js
+++ b/addon-sdk/source/lib/sdk/util/array.js
@@ -96,18 +96,19 @@ function fromIterator(iterator) {
   else {
     for (let item of iterator)
       array.push(item);
   }
   return array;
 }
 exports.fromIterator = fromIterator;
 
-function find(array, predicate) {
+function find(array, predicate, fallback) {
   var index = 0;
   var count = array.length;
   while (index < count) {
     var value = array[index];
     if (predicate(value)) return value;
     else index = index + 1;
   }
+  return fallback;
 }
 exports.find = find;
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -13,16 +13,18 @@ const observers = require('../deprecated
 const { defer } = require('sdk/core/promise');
 
 const windowWatcher = Cc['@mozilla.org/embedcomp/window-watcher;1'].
                        getService(Ci.nsIWindowWatcher);
 const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                         getService(Ci.nsIAppShellService);
 const WM = Cc['@mozilla.org/appshell/window-mediator;1'].
            getService(Ci.nsIWindowMediator);
+const io = Cc['@mozilla.org/network/io-service;1'].
+           getService(Ci.nsIIOService);
 
 const BROWSER = 'navigator:browser',
       URI_BROWSER = 'chrome://browser/content/browser.xul',
       NAME = '_blank',
       FEATURES = 'chrome,all,dialog=no,non-private';
 
 function isWindowPrivate(win) {
   if (!win)
@@ -179,28 +181,31 @@ function serializeFeatures(options) {
  * @params {nsIDOMWindow} options.parent
  *    Used as parent for the created window.
  * @params {String} options.name
  *    Optional name that is assigned to the window.
  * @params {Object} options.features
  *    Map of key, values like: `{ width: 10, height: 15, chrome: true, private: true }`.
  */
 function open(uri, options) {
-  options = options || {};
+  uri = uri || URI_BROWSER;
+  options = options || {}
+
+  if (['chrome', 'resource', 'data'].indexOf(io.newURI(uri, null, null).scheme) < 0)
+    throw new Error('only chrome, resource and data uris are allowed');
+
   let newWindow = windowWatcher.
     openWindow(options.parent || null,
-               uri || URI_BROWSER,
+               uri,
                options.name || null,
                serializeFeatures(options.features || {}),
                options.args || null);
 
   return newWindow;
 }
-
-
 exports.open = open;
 
 function onFocus(window) {
   let { resolve, promise } = defer();
 
   if (isFocused(window)) {
     resolve(window);
   }
--- a/addon-sdk/source/python-lib/cuddlefish/options_xul.py
+++ b/addon-sdk/source/python-lib/cuddlefish/options_xul.py
@@ -47,16 +47,19 @@ def validate_prefs(options):
 
 def parse_options(options, jetpack_id):
     doc = Document()
     root = doc.createElement("vbox")
     root.setAttribute("xmlns", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")
     doc.appendChild(root)
 
     for pref in options:
+        if ("hidden" in pref and pref["hidden"] == True):
+            continue;
+
         setting = doc.createElement("setting")
         setting.setAttribute("pref-name", pref["name"])
         setting.setAttribute("data-jetpack-id", jetpack_id)
         setting.setAttribute("pref", "extensions." + jetpack_id + "." + pref["name"])
         setting.setAttribute("type", pref["type"])
         setting.setAttribute("title", pref["title"])
 
         if ("description" in pref):
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -25,17 +25,17 @@ FILTER_ONLY_CONSOLE_FROM_ADB = re.compil
 
 # Used to detect the currently running test
 PARSEABLE_TEST_NAME = re.compile(r'TEST-START \| ([^\n]+)\n')
 
 # Maximum time we'll wait for tests to finish, in seconds.
 # The purpose of this timeout is to recover from infinite loops.  It should be
 # longer than the amount of time any test run takes, including those on slow
 # machines running slow (debug) versions of Firefox.
-RUN_TIMEOUT = 45 * 60 # 45 minutes
+RUN_TIMEOUT = 1.5 * 60 * 60 # 1.5 Hour
 
 # Maximum time we'll wait for tests to emit output, in seconds.
 # The purpose of this timeout is to recover from hangs.  It should be longer
 # than the amount of time any test takes to report results.
 OUTPUT_TIMEOUT = 60 # one minute
 
 def follow_file(filename):
     """
@@ -491,19 +491,16 @@ def run_app(harness_root_dir, manifest_r
         fileno,logfile = tempfile.mkstemp(prefix="harness-log-")
         os.close(fileno)
     logfile_tail = follow_file(logfile)
     atexit.register(maybe_remove_logfile)
 
     logfile = os.path.abspath(os.path.expanduser(logfile))
     maybe_remove_logfile()
 
-    if app_type != "fennec-on-device":
-        harness_options['logFile'] = logfile
-
     env = {}
     env.update(os.environ)
     env['MOZ_NO_REMOTE'] = '1'
     env['XPCOM_DEBUG_BREAK'] = 'stack'
     env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
     env.update(extra_environment)
     if norun:
         cmdargs.append("-no-remote")
--- a/addon-sdk/source/python-lib/mozrunner/winprocess.py
+++ b/addon-sdk/source/python-lib/mozrunner/winprocess.py
@@ -325,23 +325,19 @@ GetExitCodeProcessProto = WINFUNCTYPE(BO
 GetExitCodeProcessFlags = ((1, "hProcess"),
                            (2, "lpExitCode"))
 GetExitCodeProcess = GetExitCodeProcessProto(
     ("GetExitCodeProcess", windll.kernel32),
     GetExitCodeProcessFlags)
 GetExitCodeProcess.errcheck = ErrCheckBool
 
 def CanCreateJobObject():
-    currentProc = GetCurrentProcess()
-    if IsProcessInJob(currentProc):
-        jobinfo = QueryInformationJobObject(HANDLE(0), 'JobObjectExtendedLimitInformation')
-        limitflags = jobinfo['BasicLimitInformation']['LimitFlags']
-        return bool(limitflags & JOB_OBJECT_LIMIT_BREAKAWAY_OK) or bool(limitflags & JOB_OBJECT_LIMIT_SILENT_BREAKAWAY_OK)
-    else:
-        return True
+    # Running firefox in a job (from cfx) hangs on sites using flash plugin
+    # so job creation is turned off for now. (see Bug 768651).
+    return False
 
 ### testing functions
 
 def parent():
     print 'Starting parent'
     currentProc = GetCurrentProcess()
     if IsProcessInJob(currentProc):
         print >> sys.stderr, "You should not be in a job object to test"
--- a/addon-sdk/source/test/addons/content-permissions/main.js
+++ b/addon-sdk/source/test/addons/content-permissions/main.js
@@ -1,20 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
-const xulApp = require("sdk/system/xul-app");
 const { PageMod } = require("sdk/page-mod");
 const tabs = require("sdk/tabs");
+const { startServerAsync } = require("sdk/test/httpd");
+
+const serverPort = 8099;
 
 exports.testCrossDomainIframe = function(assert, done) {
-  let serverPort = 8099;
-  let server = require("sdk/test/httpd").startServerAsync(serverPort);
+  let server = startServerAsync(serverPort);
   server.registerPathHandler("/iframe", function handle(request, response) {
     response.write("<html><body>foo</body></html>");
   });
 
   let pageMod = PageMod({
     include: "about:*",
     contentScript: "new " + function ContentScriptScope() {
       self.on("message", function (url) {
@@ -26,29 +27,33 @@ exports.testCrossDomainIframe = function
         iframe.setAttribute("src", url);
         document.documentElement.appendChild(iframe);
       });
     },
     onAttach: function(w) {
       w.on("message", function (body) {
         assert.equal(body, "foo", "received iframe html content");
         pageMod.destroy();
-        w.tab.close();
-        server.stop(done);
+        w.tab.close(function() {
+          server.stop(done);
+        });
       });
+
       w.postMessage("http://localhost:8099/iframe");
     }
   });
 
-  tabs.open("about:credits");
+  tabs.open({
+    url: "about:home",
+    inBackground: true
+  });
 };
 
 exports.testCrossDomainXHR = function(assert, done) {
-  let serverPort = 8099;
-  let server = require("sdk/test/httpd").startServerAsync(serverPort);
+  let server = startServerAsync(serverPort);
   server.registerPathHandler("/xhr", function handle(request, response) {
     response.write("foo");
   });
 
   let pageMod = PageMod({
     include: "about:*",
     contentScript: "new " + function ContentScriptScope() {
       self.on("message", function (url) {
@@ -60,27 +65,24 @@ exports.testCrossDomainXHR = function(as
         };
         request.send(null);
       });
     },
     onAttach: function(w) {
       w.on("message", function (body) {
         assert.equal(body, "foo", "received XHR content");
         pageMod.destroy();
-        w.tab.close();
-        server.stop(done);
+        w.tab.close(function() {
+          server.stop(done);
+        });
       });
+
       w.postMessage("http://localhost:8099/xhr");
     }
   });
 
-  tabs.open("about:credits");
+  tabs.open({
+    url: "about:home",
+    inBackground: true
+  });
 };
 
-if (!xulApp.versionInRange(xulApp.platformVersion, "17.0a2", "*")) {
-  module.exports = {
-    "test Unsupported Application": function Unsupported (assert) {
-      assert.pass("This firefox version doesn't support cross-domain-content permission.");
-    }
-  };
-}
-
 require("sdk/test/runner").runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/main/main.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 { setTimeout } = require('sdk/timers');
+
+let mainStarted = false;
+
+exports.main = function main(options, callbacks) {
+  mainStarted = true;
+
+  let tests = {};
+
+  tests.testMainArguments = function(assert) {
+  	assert.ok(!!options, 'options argument provided to main');
+    assert.ok('loadReason' in options, 'loadReason is in options provided by main');
+    assert.equal(typeof callbacks.print, 'function', 'callbacks.print is a function');
+    assert.equal(typeof callbacks.quit, 'function', 'callbacks.quit is a function');
+    assert.equal(options.loadReason, 'install', 'options.loadReason is install');
+  }
+
+  require('sdk/test/runner').runTestsFromModule({exports: tests});
+}
+
+// this causes a fail if main does not start
+setTimeout(function() {
+  if (mainStarted)
+  	return;
+
+  // main didn't start, fail..
+  require("sdk/test/runner").runTestsFromModule({exports: {
+  	testFail: function(assert) assert.fail('Main did not start..')
+  }});
+}, 500);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/main/package.json
@@ -0,0 +1,3 @@
+{
+  "id": "test-main"
+}
--- a/addon-sdk/source/test/addons/private-browsing-supported/main.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/main.js
@@ -1,24 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
 const { merge } = require('sdk/util/object');
-const app = require("sdk/system/xul-app");
+const app = require('sdk/system/xul-app');
 const { isGlobalPBSupported } = require('sdk/private-browsing/utils');
 
 merge(module.exports,
   require('./test-tabs'),
   require('./test-page-mod'),
   require('./test-selection'),
   require('./test-panel'),
   require('./test-private-browsing'),
   isGlobalPBSupported ? require('./test-global-private-browsing') : {}
 );
 
 // Doesn't make sense to test window-utils and windows on fennec,
 // as there is only one window which is never private
-if (!app.is("Fennec"))
+if (!app.is('Fennec'))
   merge(module.exports, require('./test-windows'));
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-tabs.js
@@ -1,15 +1,17 @@
 'use strict';
 
 const tabs = require('sdk/tabs');
 const { is } = require('sdk/system/xul-app');
 const { isPrivate } = require('sdk/private-browsing');
 const pbUtils = require('sdk/private-browsing/utils');
 const { getOwnerWindow } = require('sdk/private-browsing/window/utils');
+const { promise: windowPromise, close, focus } = require('sdk/window/helpers');
+const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 
 exports.testPrivateTabsAreListed = function (assert, done) {
   let originalTabCount = tabs.length;
 
   tabs.open({
     url: 'about:blank',
     isPrivate: true,
     onOpen: function(tab) {
@@ -21,12 +23,91 @@ exports.testPrivateTabsAreListed = funct
                      'New private window\'s tab are visible in tabs list');
       }
       else {
       // Global case, openDialog didn't opened a private window/tab
         assert.ok(!isPrivate(tab), "tab isn't private");
         assert.equal(tabs.length, originalTabCount + 1,
                      'New non-private window\'s tab is visible in tabs list');
       }
+
       tab.close(done);
     }
   });
 }
+
+exports.testOpenTabWithPrivateActiveWindowNoIsPrivateOption = function(assert, done) {
+  let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true });
+
+  windowPromise(window, 'load').then(focus).then(function (window) {
+    assert.ok(isPrivate(window), 'new window is private');
+
+    tabs.open({
+      url: 'about:blank',
+      onOpen: function(tab) {
+        assert.ok(isPrivate(tab), 'new tab is private');
+        assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private');
+        assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same');
+
+        close(window).then(done, assert.fail);
+      }
+    })
+  }, assert.fail).then(null, assert.fail);
+}
+
+exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) {
+  let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false });
+
+  windowPromise(window, 'load').then(focus).then(function (window) {
+    assert.equal(isPrivate(window), false, 'new window is not private');
+
+    tabs.open({
+      url: 'about:blank',
+      onOpen: function(tab) {
+        assert.equal(isPrivate(tab), false, 'new tab is not private');
+        assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private');
+        assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same');
+
+        close(window).then(done, assert.fail);
+      }
+    })
+  }, assert.fail).then(null, assert.fail);
+}
+
+exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) {
+  let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true });
+
+  windowPromise(window, 'load').then(focus).then(function (window) {
+    assert.ok(isPrivate(window), 'new window is private');
+
+    tabs.open({
+      url: 'about:blank',
+      isPrivate: true,
+      onOpen: function(tab) {
+        assert.ok(isPrivate(tab), 'new tab is private');
+        assert.ok(isPrivate(getOwnerWindow(tab)), 'new tab window is private');
+        assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the private window are the same');
+
+        close(window).then(done, assert.fail);
+      }
+    })
+  }, assert.fail).then(null, assert.fail);
+}
+
+exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) {
+  let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false });
+
+  windowPromise(window, 'load').then(focus).then(function (window) {
+    assert.equal(isPrivate(window), false, 'new window is not private');
+
+    tabs.open({
+      url: 'about:blank',
+      isPrivate: false,
+      onOpen: function(tab) {
+        assert.equal(isPrivate(tab), false, 'new tab is not private');
+        assert.equal(isPrivate(getOwnerWindow(tab)), false, 'new tab window is not private');
+        assert.strictEqual(getOwnerWindow(tab), window, 'the tab window and the new window are the same');
+
+        close(window).then(done, assert.fail);
+      }
+    })
+  }, assert.fail).then(null, assert.fail);
+}
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/simple-prefs/lib/main.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';
+
+const { Cu } = require('chrome');
+const sp = require('sdk/simple-prefs');
+const app = require('sdk/system/xul-app');
+const self = require('sdk/self');
+const tabs = require('sdk/tabs');
+
+const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
+
+exports.testDefaultValues = function (assert) {
+  assert.equal(sp.prefs.myHiddenInt, 5, 'myHiddenInt default is 5');
+  assert.equal(sp.prefs.myInteger, 8, 'myInteger default is 8');
+  assert.equal(sp.prefs.somePreference, 'TEST', 'somePreference default is correct');
+}
+
+exports.testOptionsType = function(assert, done) {
+  AddonManager.getAddonByID(self.id, function(aAddon) {
+    assert.equal(aAddon.optionsType, AddonManager.OPTIONS_TYPE_INLINE, 'options type is inline');
+    done();
+  });
+}
+
+if (app.is('Firefox')) {
+  exports.testAOM = function(assert, done) {
+      tabs.open({
+      	url: 'about:addons',
+      	onReady: function(tab) {
+          tab.attach({
+          	contentScript: 'AddonManager.getAddonByID("' + self.id + '", function(aAddon) {\n' +
+          		             'unsafeWindow.gViewController.viewObjects.detail.node.addEventListener("ViewChanged", function whenViewChanges() {\n' +
+          		               'unsafeWindow.gViewController.viewObjects.detail.node.removeEventListener("ViewChanged", whenViewChanges, false);\n' +
+          		               'setTimeout(function() {\n' + // TODO: figure out why this is necessary..
+                                 'self.postMessage({\n' +
+                                   'somePreference: getAttributes(unsafeWindow.document.querySelector("setting[title=\'some-title\']")),\n' +
+                                   'myInteger: getAttributes(unsafeWindow.document.querySelector("setting[title=\'my-int\']")),\n' +
+                                   'myHiddenInt: getAttributes(unsafeWindow.document.querySelector("setting[title=\'hidden-int\']"))\n' +
+                                 '});\n' +
+          		               '}, 250);\n' +
+          		             '}, false);\n' +
+                             'unsafeWindow.gViewController.commands.cmd_showItemDetails.doCommand(aAddon, true);\n' +
+                           '});\n' + 
+                           'function getAttributes(ele) {\n' +
+                             'if (!ele) return {};\n' +
+                             'return {\n' +
+                               'pref: ele.getAttribute("pref"),\n' +
+                               'type: ele.getAttribute("type"),\n' +
+                               'title: ele.getAttribute("title"),\n' +
+                               'desc: ele.getAttribute("desc")\n' +
+                             '}\n' +
+                           '}\n',
+            onMessage: function(msg) {
+              // test somePreference
+              assert.equal(msg.somePreference.type, 'string', 'some pref is a string');
+              assert.equal(msg.somePreference.pref, 'extensions.'+self.id+'.somePreference', 'somePreference path is correct');
+              assert.equal(msg.somePreference.title, 'some-title', 'somePreference title is correct');
+              assert.equal(msg.somePreference.desc, 'Some short description for the preference', 'somePreference description is correct');
+
+              // test myInteger
+              assert.equal(msg.myInteger.type, 'integer', 'myInteger is a int');
+              assert.equal(msg.myInteger.pref, 'extensions.'+self.id+'.myInteger', 'extensions.test-simple-prefs.myInteger');
+              assert.equal(msg.myInteger.title, 'my-int', 'myInteger title is correct');
+              assert.equal(msg.myInteger.desc, 'How many of them we have.', 'myInteger desc is correct');
+
+              // test myHiddenInt
+              assert.equal(msg.myHiddenInt.type, undefined, 'myHiddenInt was not displayed');
+              assert.equal(msg.myHiddenInt.pref, undefined, 'myHiddenInt was not displayed');
+              assert.equal(msg.myHiddenInt.title, undefined, 'myHiddenInt was not displayed');
+              assert.equal(msg.myHiddenInt.desc, undefined, 'myHiddenInt was not displayed');
+
+              tab.close(done);
+            }
+          });
+      	}
+      });
+  }
+}
+
+require('sdk/test/runner').runTestsFromModule(module);
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/addons/simple-prefs/package.json
@@ -0,0 +1,23 @@
+{
+  "id": "test-simple-prefs",
+    "preferences": [{
+        "name": "somePreference",
+        "title": "some-title",
+        "description": "Some short description for the preference",
+        "type": "string",
+        "value": "TEST"
+    },
+    {
+        "description": "How many of them we have.",
+        "name": "myInteger",
+        "type": "integer",
+        "value": 8,
+        "title": "my-int"
+    }, {
+        "name": "myHiddenInt",
+        "type": "integer",
+        "hidden": true,
+        "value": 5,
+        "title": "hidden-int"
+    }]
+}
--- a/addon-sdk/source/test/places-helper.js
+++ b/addon-sdk/source/test/places-helper.js
@@ -14,16 +14,17 @@ const tagsrv = Cc['@mozilla.org/browser/
               getService(Ci.nsITaggingService);
 const asyncHistory = Cc['@mozilla.org/browser/history;1'].
               getService(Ci.mozIAsyncHistory);
 const { send } = require('sdk/addon/events');
 const { setTimeout } = require('sdk/timers');
 const { newURI } = require('sdk/url/utils');
 const { defer, all } = require('sdk/core/promise');
 const { once } = require('sdk/system/events');
+const { set } = require('sdk/preferences/service');
 const {
   Bookmark, Group, Separator,
   save, search,
   MENU, TOOLBAR, UNSORTED
 } = require('sdk/places/bookmarks');
 
 function invalidResolve (assert) {
   return function (e) {
@@ -40,22 +41,35 @@ function invalidReject (assert) {
 exports.invalidReject = invalidReject;
 
 // Removes all children of group
 function clearBookmarks (group) {
   group
    ? bmsrv.removeFolderChildren(group.id)
    : clearAllBookmarks();
 }
-exports.clearBookmarks = clearBookmarks;
 
 function clearAllBookmarks () {
   [MENU, TOOLBAR, UNSORTED].forEach(clearBookmarks);
 }
-exports.clearAllBookmarks = clearAllBookmarks;
+
+function clearHistory (done) {
+  hsrv.removeAllPages();
+  once('places-expiration-finished', done);
+}
+
+// Cleans bookmarks and history and disables maintanance
+function resetPlaces (done) {
+  // Set last maintenance to current time to prevent
+  // Places DB maintenance occuring and locking DB
+  set('places.database.lastMaintenance', Math.floor(Date.now() / 1000));
+  clearAllBookmarks();
+  clearHistory(done);
+}
+exports.resetPlaces = resetPlaces;
 
 function compareWithHost (assert, item) {
   let id = item.id;
   let type = item.type === 'group' ? bmsrv.TYPE_FOLDER : bmsrv['TYPE_' + item.type.toUpperCase()];
   let url = item.url && !item.url.endsWith('/') ? item.url + '/' : item.url;
 
   if (type === bmsrv.TYPE_BOOKMARK) {
     assert.equal(url, bmsrv.getBookmarkURI(id).spec.toString(), 'Matches host url');
@@ -100,22 +114,16 @@ function createVisit (url) {
   place.visits = [{
     transitionType: hsrv.TRANSITION_LINK,
     visitDate: +(new Date()) * 1000,
     referredURI: undefined
   }];
   return place;
 }
 
-function clearHistory (done) {
-  hsrv.removeAllPages();
-  once('places-expiration-finished', done);
-}
-exports.clearHistory = clearHistory;
-
 function createBookmark (data) {
   data = data || {};
   let item = {
     title: data.title || 'Moz',
     url: data.url || (!data.type || data.type === 'bookmark' ?
       'http://moz.com/' :
       undefined),
     tags: data.tags || (!data.type || data.type === 'bookmark' ?
--- a/addon-sdk/source/test/tabs/test-fennec-tabs.js
+++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js
@@ -106,22 +106,21 @@ exports.testTabProperties = function(tes
   let tabsLen = tabs.length;
   tabs.open({
     url: url,
     onReady: function(tab) {
       test.assertEqual(tab.title, "foo", "title of the new tab matches");
       test.assertEqual(tab.url, url, "URL of the new tab matches");
       test.assert(tab.favicon, "favicon of the new tab is not empty");
       // TODO: remove need for this test by implementing the favicon feature
-      // Poors man deepEqual with JSON.stringify...
-      test.assertEqual(JSON.stringify(messages),
-                       JSON.stringify(['tab.favicon is deprecated, and ' +
-                          'currently favicon helpers are not yet supported ' +
-                          'by Fennec']),
-                       "favicon logs an error for now");
+      test.assertEqual(messages[0].msg,
+        "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();
 
--- a/addon-sdk/source/test/test-array.js
+++ b/addon-sdk/source/test/test-array.js
@@ -80,8 +80,16 @@ exports.testUnique = function(test) {
 
   function compareArray (a, b) {
     test.assertEqual(a.length, b.length);
     for (let i = 0; i < a.length; i++) {
       test.assertEqual(a[i], b[i]);
     }
   }
 };
+
+exports.testFind = function(test) {
+  let isOdd = (x) => x % 2;
+  test.assertEqual(array.find([2, 4, 5, 7, 8, 9], isOdd), 5);
+  test.assertEqual(array.find([2, 4, 6, 8], isOdd), undefined);
+  test.assertEqual(array.find([2, 4, 6, 8], isOdd, null), null);
+};
+
--- a/addon-sdk/source/test/test-browser-events.js
+++ b/addon-sdk/source/test/test-browser-events.js
@@ -87,19 +87,9 @@ exports["test browser events ignore othe
       done();
     }
   });
 
   // Open window and close it to trigger observers.
   let window = open("data:text/html,not a browser");
 };
 
-if (require("sdk/system/xul-app").is("Fennec")) {
-  module.exports = {
-    "test Unsupported Test": function UnsupportedTest (assert) {
-        assert.pass(
-          "Skipping this test until Fennec support is implemented." +
-          "See bug 793071");
-    }
-  }
-}
-
 require("test").run(exports);
--- a/addon-sdk/source/test/test-content-script.js
+++ b/addon-sdk/source/test/test-content-script.js
@@ -1,57 +1,71 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const hiddenFrames = require("sdk/frame/hidden-frame");
-
+const { create: makeFrame } = require("sdk/frame/utils");
+const { window } = require("sdk/addon/window");
 const { Loader } = require('sdk/test/loader');
+const { URL } = require("sdk/url");
+const testURI = require("sdk/self").data.url("test.html");
+const testHost = URL(testURI).scheme + '://' + URL(testURI).host;
 
 /*
  * Utility function that allow to easily run a proxy test with a clean
  * new HTML document. See first unit test for usage.
  */
 function createProxyTest(html, callback) {
   return function (assert, done) {
-    let url = 'data:text/html;charset=utf-8,' + encodeURI(html);
-
-    let hiddenFrame = hiddenFrames.add(hiddenFrames.HiddenFrame({
-      onReady: function () {
+    let url = 'data:text/html;charset=utf-8,' + encodeURIComponent(html);
+    let principalLoaded = false;
 
-        function onDOMReady() {
-          hiddenFrame.element.removeEventListener("DOMContentLoaded", onDOMReady,
-                                                  false);
+    let element = makeFrame(window.document, {
+      nodeName: "iframe",
+      type: "content",
+      allowJavascript: true,
+      allowPlugins: true,
+      allowAuth: true,
+      uri: testURI
+    });
 
-          let xrayWindow = hiddenFrame.element.contentWindow;
-          let rawWindow = xrayWindow.wrappedJSObject;
+    element.addEventListener("DOMContentLoaded", onDOMReady, false);
+
+    function onDOMReady() {
+      // Reload frame after getting principal from `testURI`
+      if (!principalLoaded) {
+        element.setAttribute("src", url);
+        principalLoaded = true;
+        return;
+      }
 
-          let isDone = false;
-          let helper = {
-            xrayWindow: xrayWindow,
-            rawWindow: rawWindow,
-            createWorker: function (contentScript) {
-              return createWorker(assert, xrayWindow, contentScript, helper.done);
-            },
-            done: function () {
-              if (isDone)
-                return;
-              isDone = true;
-              hiddenFrames.remove(hiddenFrame);
-              done();
-            }
-          }
-          callback(helper, assert);
+      assert.equal(element.getAttribute("src"), url, "correct URL loaded");
+      element.removeEventListener("DOMContentLoaded", onDOMReady,
+                                                  false);
+      let xrayWindow = element.contentWindow;
+      let rawWindow = xrayWindow.wrappedJSObject;
+
+      let isDone = false;
+      let helper = {
+        xrayWindow: xrayWindow,
+        rawWindow: rawWindow,
+        createWorker: function (contentScript) {
+          return createWorker(assert, xrayWindow, contentScript, helper.done);
+        },
+        done: function () {
+          if (isDone)
+            return;
+          isDone = true;
+          element.parentNode.removeChild(element);
+          done();
         }
-
-        hiddenFrame.element.addEventListener("DOMContentLoaded", onDOMReady, false);
-        hiddenFrame.element.setAttribute("src", url);
-
-      }
-    }));
+      };
+      callback(helper, assert);
+    }
   };
 }
 
 function createWorker(assert, xrayWindow, contentScript, done) {
   let loader = Loader(module);
   let Worker = loader.require("sdk/content/worker").Worker;
   let worker = Worker({
     window: xrayWindow,
@@ -160,19 +174,19 @@ exports["test postMessage"] = createProx
   // Listen without proxies, to check that it will work in regular case
   // simulate listening from a web document.
   ifWindow.addEventListener("message", function listener(event) {
     ifWindow.removeEventListener("message", listener, false);
     // As we are in system principal, event is an XrayWrapper
     // xrays use current compartments when calling postMessage method.
     // Whereas js proxies was using postMessage method compartment,
     // not the caller one.
-    assert.equal(event.source, helper.xrayWindow,
-                 "event.source is the top window");
-    assert.equal(event.origin, "null", "origin is null");
+    assert.strictEqual(event.source, helper.xrayWindow,
+                      "event.source is the top window");
+    assert.equal(event.origin, testHost, "origin matches testHost");
 
     assert.equal(event.data, "{\"foo\":\"bar\\n \\\"escaped\\\".\"}",
                      "message data is correct");
 
     helper.done();
   }, false);
 
   helper.createWorker(
@@ -211,38 +225,40 @@ exports["test Object Listener"] = create
     }
   );
 
 });
 
 exports["test Object Listener 2"] = createProxyTest("", function (helper) {
 
   helper.createWorker(
-    'new ' + function ContentScriptScope() {
+    ('new ' + function ContentScriptScope() {
+      // variable replaced with `testHost`
+      let testHost = "TOKEN";
       // Verify object as DOM event listener
       let myMessageListener = {
         called: false,
         handleEvent: function(event) {
           window.removeEventListener("message", myMessageListener, true);
 
           assert(this == myMessageListener, "`this` is the original object");
           assert(!this.called, "called only once");
           this.called = true;
           assert(event.target == document.defaultView, "event.target is the wrapped window");
           assert(event.source == document.defaultView, "event.source is the wrapped window");
-          assert(event.origin == "null", "origin is null");
+          assert(event.origin == testHost, "origin matches testHost");
           assert(event.data == "ok", "message data is correct");
           done();
         }
       };
 
       window.addEventListener("message", myMessageListener, true);
       document.defaultView.postMessage("ok", '*');
     }
-  );
+  ).replace("TOKEN", testHost));
 
 });
 
 let html = '<input id="input" type="text" /><input id="input3" type="checkbox" />' +
              '<input id="input2" type="checkbox" />';
 
 /* Disable test to keep tree green until Bug 756214 is fixed.
 exports.testStringOverload = createProxyTest(html, function (helper, test) {
--- a/addon-sdk/source/test/test-fs.js
+++ b/addon-sdk/source/test/test-fs.js
@@ -4,16 +4,17 @@
 
 "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");
+const { is } = require("sdk/system/xul-app");
 
 // 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");
@@ -21,29 +22,28 @@ const writePath = path.join(profilePath,
 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) {
+exports["test readdir"] = 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");
+              "got at least number of entries we expect");
     assert.ok(profileEntries.every(function(entry) {
                 return entries.indexOf(entry) >= 0;
               }), "all profiles are present");
     end();
   });
 
   async = true;
 };
@@ -62,23 +62,23 @@ exports["test readdir error"] = function
 
   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");
+            "got at 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) {
+exports["test readdirSync 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");
@@ -87,16 +87,17 @@ exports["test readirSync error"] = funct
   }
 };
 
 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;
 };
@@ -333,17 +334,16 @@ exports["test fs.truncateSync fs.unlinkS
 
 
 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;
   }
@@ -454,9 +454,33 @@ exports["test fs.writeFile"] = function(
     fs.unlinkSync(path);
     assert.ok(!fs.exists(path), "file was removed");
 
     end();
   });
   async = true;
 };
 
+exports["test fs.writeFile (with large files)"] = function(assert, end) {
+  let path = writePath;
+  let content = "";
+
+  for (var i = 0; i < 100000; i++) {
+    content += "buffer\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);
--- a/addon-sdk/source/test/test-observer-service.js
+++ b/addon-sdk/source/test/test-observer-service.js
@@ -17,23 +17,23 @@ exports.testUnloadAndErrorLogging = func
   var badCb = function(subject, data) {
     throw new Error("foo");
   };
   sbobsvc.add("blarg", cb);
   observers.notify("blarg", "yo yo");
   test.assertEqual(timesCalled, 1);
   sbobsvc.add("narg", badCb);
   observers.notify("narg", "yo yo");
-  var lines = messages[0].split("\n");
-  test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
-  test.assertEqual(lines[0], "error: " + require("sdk/self").name + ": An exception occurred.");
-  test.assertEqual(lines[1], "Error: foo");
+
+  test.assertEqual(messages[0], "console.error: " + require("sdk/self").name + ": \n");
+  var lines = messages[1].split("\n");
+  test.assertEqual(lines[0], "  Message: Error: foo");
+  test.assertEqual(lines[1], "  Stack:");
   // Keep in mind to update "18" to the line of "throw new Error("foo")"
-  test.assertEqual(lines[2], module.uri + " 18");
-  test.assertEqual(lines[3], "Traceback (most recent call last):");
+  test.assert(lines[2].indexOf(module.uri + ":18") != -1);
 
   loader.unload();
   observers.notify("blarg", "yo yo");
   test.assertEqual(timesCalled, 1);
 };
 
 exports.testObserverService = function(test) {
   var ios = Cc['@mozilla.org/network/io-service;1']
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -14,16 +14,18 @@ const windowUtils = require('sdk/depreca
 const { getTabContentWindow, getActiveTab, setTabURL, openTab, closeTab } = require('sdk/tabs/utils');
 const xulApp = require("sdk/system/xul-app");
 const { data, isPrivateBrowsingSupported } = require('sdk/self');
 const { isPrivate } = require('sdk/private-browsing');
 const { openWebpage } = require('./private-browsing/helper');
 const { isTabPBSupported, isWindowPBSupported, isGlobalPBSupported } = require('sdk/private-browsing/utils');
 const promise = require("sdk/core/promise");
 const { pb } = require('./private-browsing/helper');
+const { URL } = require("sdk/url");
+const testPageURI = require("sdk/self").data.url("test.html");
 
 /* XXX This can be used to delay closing the test Firefox instance for interactive
  * testing or visual inspection. This test is registered first so that it runs
  * the last. */
 exports.delay = function(test) {
   if (false) {
     test.waitUntilDone(60000);
     timer.setTimeout(function() {test.done();}, 4000);
@@ -114,26 +116,26 @@ exports.testPageModIncludes = function(t
       // so we attach it on 'start'.
       contentScriptWhen: 'start',
       onAttach: function(worker) {
         worker.postMessage(this.include[0]);
       }
     };
   }
 
-  testPageMod(test, "about:buildconfig", [
+  testPageMod(test, testPageURI, [
       createPageModTest("*", false),
       createPageModTest("*.google.com", false),
-      createPageModTest("about:*", true),
-      createPageModTest("about:", false),
-      createPageModTest("about:buildconfig", true)
+      createPageModTest("resource:*", true),
+      createPageModTest("resource:", false),
+      createPageModTest(testPageURI, true)
     ],
     function (win, done) {
-      test.waitUntil(function () win.localStorage["about:buildconfig"],
-                     "about:buildconfig page-mod to be executed")
+      test.waitUntil(function () win.localStorage[testPageURI],
+                     testPageURI + " page-mod to be executed")
           .then(function () {
             asserts.forEach(function(fn) {
               fn(test, win);
             });
             done();
           });
     }
     );
--- a/addon-sdk/source/test/test-page-worker.js
+++ b/addon-sdk/source/test/test-page-worker.js
@@ -2,16 +2,18 @@
  * 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 Pages = require("sdk/page-worker");
 const Page = Pages.Page;
+const { URL } = require("sdk/url");
+const testURI = require("sdk/self").data.url("test.html");
 
 const ERR_DESTROYED =
   "Couldn't find the worker to receive this message. " +
   "The script may not be initialized yet, or may already have been unloaded.";
 
 exports.testSimplePageCreation = function(assert, done) {
   let page = new Page({
     contentScript: "self.postMessage(window.location.href)",
@@ -151,24 +153,33 @@ exports.testValidateOptions = function(a
     "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>";
+
+  // Load up the page with testURI initially for the resource:// principal,
+  // then load the actual data:* content, as data:* URIs no longer
+  // have localStorage
   let page = Page({
-    contentURL: content,
-    contentScript: "self.postMessage(window.localStorage.allowScript)",
+    contentURL: testURI,
+    contentScript: "if (window.location.href==='"+testURI+"')" +
+      "  self.postMessage('reload');" +
+      "else " +
+      "  self.postMessage(window.localStorage.allowScript)",
     contentScriptWhen: "end",
     onMessage: step0
   });
 
   function step0(message) {
+    if (message === 'reload')
+      return page.contentURL = content;
     assert.equal(message, "3",
                      "Correct value expected for allowScript - 3");
     assert.equal(page.contentURL, content,
                      "Correct content expected");
     page.removeListener('message', step0);
     page.on('message', step1);
     page.allow = { script: false };
     page.contentURL = content =
--- a/addon-sdk/source/test/test-places-bookmarks.js
+++ b/addon-sdk/source/test/test-places-bookmarks.js
@@ -20,19 +20,19 @@ const { defer: async } = require('sdk/la
 const { before, after } = require('sdk/test/utils');
 
 const {
   Bookmark, Group, Separator,
   save, search, remove,
   MENU, TOOLBAR, UNSORTED
 } = require('sdk/places/bookmarks');
 const {
-  invalidResolve, invalidReject, clearBookmarks, createTree,
-  compareWithHost, clearAllBookmarks, createBookmark, createBookmarkItem,
-  createBookmarkTree, addVisits
+  invalidResolve, invalidReject, createTree,
+  compareWithHost, createBookmark, createBookmarkItem,
+  createBookmarkTree, addVisits, resetPlaces
 } = require('./places-helper');
 const { promisedEmitter } = require('sdk/places/utils');
 const bmsrv = Cc['@mozilla.org/browser/nav-bookmarks-service;1'].
                     getService(Ci.nsINavBookmarksService);
 const tagsrv = Cc['@mozilla.org/browser/tagging-service;1'].
                     getService(Ci.nsITaggingService);
 
 exports.testDefaultFolders = function (assert) {
@@ -936,23 +936,18 @@ exports.testCheckSaveOrder = function (a
   saveP(bookmarks).then(results => {
     for (let i = 0; i < bookmarks.length; i++)
       assert.equal(results[i].url, bookmarks[i].url,
         'correct ordering of bookmark results');
     done();
   });
 };
 
-before(exports, name => {
-  clearAllBookmarks();
-});
-
-after(exports, name => {
-  clearAllBookmarks();
-});
+before(exports, (name, assert, done) => resetPlaces(done));
+after(exports, (name, assert, done) => resetPlaces(done));
 
 function saveP () {
   return promisedEmitter(save.apply(null, Array.slice(arguments)));
 }
 
 function searchP () {
   return promisedEmitter(search.apply(null, Array.slice(arguments)));
 }
--- a/addon-sdk/source/test/test-places-favicon.js
+++ b/addon-sdk/source/test/test-places-favicon.js
@@ -14,17 +14,17 @@ const { Cc, Ci, Cu } = require('chrome')
 const { getFavicon } = require('sdk/places/favicon');
 const tabs = require('sdk/tabs');
 const open = tabs.open;
 const port = 8099;
 const host = 'http://localhost:' + port;
 const { onFaviconChange, serve, binFavicon } = require('./favicon-helpers');
 const { once } = require('sdk/system/events');
 const { defer } = require('sdk/core/promise');
-const { clearHistory } = require('./places-helper');
+const { resetPlaces } = require('./places-helper');
 const faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
                          getService(Ci.nsIFaviconService);
 
 exports.testStringGetFaviconCallbackSuccess = function (assert, done) {
   let name = 'callbacksuccess'
   let srv = makeServer(name);
   let url = host + '/' + name + '.html';
   let favicon = host + '/' + name + '.ico';
@@ -176,15 +176,15 @@ function waitAndExpire (url) {
     });
     faviconService.expireAllFavicons();
   });
   return deferred.promise;
 }
 
 function complete(tab, srv, done) {
   tab.close(function () {
-    clearHistory(() => {
+    resetPlaces(() => {
       srv.stop(done);
     });
   });
 }
 
 require("test").run(exports);
--- a/addon-sdk/source/test/test-places-history.js
+++ b/addon-sdk/source/test/test-places-history.js
@@ -9,26 +9,25 @@ module.metadata = {
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const { defer, all } = require('sdk/core/promise');
 const { has } = require('sdk/util/array');
 const { setTimeout } = require('sdk/timers');
 const { before, after } = require('sdk/test/utils');
+const { set } = require('sdk/preferences/service');
 const {
   search 
 } = require('sdk/places/history');
 const {
-  invalidResolve, invalidReject, clearBookmarks, createTree,
-  compareWithHost, clearAllBookmarks, addVisits, clearHistory
+  invalidResolve, invalidReject, createTree,
+  compareWithHost, addVisits, resetPlaces
 } = require('./places-helper');
 const { promisedEmitter } = require('sdk/places/utils');
-const hsrv = Cc['@mozilla.org/browser/nav-history-service;1'].
-              getService(Ci.nsINavHistoryService);
 
 exports.testEmptyQuery = function (assert, done) {
   let within = toBeWithin();
   addVisits([
     'http://simplequery-1.com', 'http://simplequery-2.com'
   ]).then(searchP).then(results => {
     assert.equal(results.length, 2, 'Correct number of entries returned');
     assert.equal(results[0].url, 'http://simplequery-1.com/',
@@ -234,21 +233,16 @@ exports.testEmitters = function (assert,
 function toBeWithin (range) {
   range = range || 2000;
   var current = new Date() * 1000; // convert to microseconds
   return compared => { 
     return compared - current < range;
   };
 }
 
-function clear (done) {
-  clearAllBookmarks();
-  clearHistory(done);
-}
-
 function searchP () {
   return promisedEmitter(search.apply(null, Array.slice(arguments)));
 }
 
-before(exports, (name, assert, done) => clear(done));
-after(exports, (name, assert, done) => clear(done));
+before(exports, (name, assert, done) => resetPlaces(done));
+after(exports, (name, assert, done) => resetPlaces(done));
 
 require('test').run(exports);
--- a/addon-sdk/source/test/test-places-host.js
+++ b/addon-sdk/source/test/test-places-host.js
@@ -9,32 +9,33 @@ module.metadata = {
   }
 };
 
 const { Cc, Ci } = require('chrome');
 const { defer, all } = require('sdk/core/promise');
 const { setTimeout } = require('sdk/timers');
 const { newURI } = require('sdk/url/utils');
 const { send } = require('sdk/addon/events');
+const { set } = require('sdk/preferences/service');
+const { before, after } = require('sdk/test/utils');
 
 require('sdk/places/host/host-bookmarks');
 require('sdk/places/host/host-tags');
 require('sdk/places/host/host-query');
 const {
-  invalidResolve, invalidReject, clearBookmarks, createTree,
-  compareWithHost, clearAllBookmarks, createBookmark, createBookmarkTree
+  invalidResolve, invalidReject, createTree,
+  compareWithHost, createBookmark, createBookmarkTree, resetPlaces
 } = require('./places-helper');
 
 const bmsrv = Cc['@mozilla.org/browser/nav-bookmarks-service;1'].
                     getService(Ci.nsINavBookmarksService);
 const hsrv = Cc['@mozilla.org/browser/nav-history-service;1'].
               getService(Ci.nsINavHistoryService);
 const tagsrv = Cc['@mozilla.org/browser/tagging-service;1'].
               getService(Ci.nsITaggingService);
-clearAllBookmarks();
 
 exports.testBookmarksCreate = function (assert, done) {
   let items = [{
     title: 'my title',
     url: 'http://moz.com',
     tags: ['some', 'tags', 'yeah'],
     type: 'bookmark'
   }, {
@@ -46,17 +47,16 @@ exports.testBookmarksCreate = function (
     group: bmsrv.unfiledBookmarksFolder
   }];
   
   all(items.map(function (item) {
     return send('sdk-places-bookmarks-create', item).then(function (data) {
       compareWithHost(assert, data);
     }, invalidReject(assert));
   })).then(function () {
-    clearAllBookmarks();
     done();
   }, invalidReject(assert));
 };
 
 exports.testBookmarksCreateFail = function (assert, done) {
   let items = [{
     title: 'my title',
     url: 'not-a-url',
@@ -67,17 +67,16 @@ exports.testBookmarksCreateFail = functi
   }, {
     group: bmsrv.unfiledBookmarksFolder
   }];
   all(items.map(function (item) {
     return send('sdk-places-bookmarks-create', item).then(null, function (reason) {
       assert.ok(reason, 'bookmark create should fail');
     });
   })).then(function () {
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testBookmarkLastUpdated = function (assert, done) {
   let timestamp;
   let item;
   createBookmark().then(function (data) {
@@ -89,33 +88,31 @@ exports.testBookmarkLastUpdated = functi
     item.title = 'updated mozilla';
     return send('sdk-places-bookmarks-save', item).then(function (data) {
       let deferred = defer();
       setTimeout(function () deferred.resolve(data), 100);
       return deferred.promise;
     });
   }).then(function (data) {
     assert.ok(data.updated > timestamp, 'time has elapsed and updated the updated property');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testBookmarkRemove = function (assert, done) {
   let id;
   createBookmark().then(function (data) {
     id = data.id;
     compareWithHost(assert, data); // ensure bookmark exists
     bmsrv.getItemTitle(id); // does not throw an error
     return send('sdk-places-bookmarks-remove', data);
   }).then(function () {
     assert.throws(function () {
       bmsrv.getItemTitle(id);
     }, 'item should no longer exist');
-    clearAllBookmarks();
     done();
   }, console.error);
 };
 
 exports.testBookmarkGet = function (assert, done) {
   let bookmark;
   createBookmark().then(function (data) {
     bookmark = data;
@@ -128,17 +125,16 @@ exports.testBookmarkGet = function (asse
             'correctly fetched tag ' + tag);
         }
         assert.equal(bookmark.tags.length, data.tags.length,
           'same amount of tags');
       }
       else
         assert.equal(bookmark[prop], data[prop], 'correctly fetched ' + prop);
     });
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testTagsTag = function (assert, done) {
   let url;
   createBookmark().then(function (data) {
     url = data.url;
@@ -146,17 +142,16 @@ exports.testTagsTag = function (assert, 
       url: data.url, tags: ['mozzerella', 'foxfire']
     });
   }).then(function () {
     let tags = tagsrv.getTagsForURI(newURI(url));
     assert.ok(~tags.indexOf('mozzerella'), 'first tag found');
     assert.ok(~tags.indexOf('foxfire'), 'second tag found');
     assert.ok(~tags.indexOf('firefox'), 'default tag found');
     assert.equal(tags.length, 3, 'no extra tags');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testTagsUntag = function (assert, done) {
   let item;
   createBookmark({tags: ['tag1', 'tag2', 'tag3']}).then(function (data) {
     item = data;
@@ -166,49 +161,46 @@ exports.testTagsUntag = function (assert
     });
   }).then(function () {
     let tags = tagsrv.getTagsForURI(newURI(item.url));
     assert.ok(~tags.indexOf('tag1'), 'first tag persisted');
     assert.ok(~tags.indexOf('tag3'), 'second tag persisted');
     assert.ok(!~tags.indexOf('firefox'), 'first tag removed');
     assert.ok(!~tags.indexOf('tag2'), 'second tag removed');
     assert.equal(tags.length, 2, 'no extra tags');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testTagsGetURLsByTag = function (assert, done) {
   let item;
   createBookmark().then(function (data) {
     item = data;
     return send('sdk-places-tags-get-urls-by-tag', {
       tag: 'firefox'
     });
   }).then(function(urls) {
     assert.equal(item.url, urls[0], 'returned correct url');
     assert.equal(urls.length, 1, 'returned only one url');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testTagsGetTagsByURL = function (assert, done) {
   let item;
   createBookmark({ tags: ['firefox', 'mozilla', 'metal']}).then(function (data) {
     item = data;
     return send('sdk-places-tags-get-tags-by-url', {
       url: data.url,
     });
   }).then(function(tags) {
     assert.ok(~tags.indexOf('firefox'), 'returned first tag');
     assert.ok(~tags.indexOf('mozilla'), 'returned second tag');
     assert.ok(~tags.indexOf('metal'), 'returned third tag');
     assert.equal(tags.length, 3, 'returned all tags');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testHostQuery = function (assert, done) {
   all([
     createBookmark({ url: 'http://firefox.com', tags: ['firefox', 'mozilla'] }),
     createBookmark({ url: 'http://mozilla.com', tags: ['mozilla'] }),
@@ -223,17 +215,16 @@ exports.testHostQuery = function (assert
     assert.equal(results[0].url, 'http://mozilla.com/', 'is sorted by URI asc');
     return send('sdk-places-query', {
       queries: { tags: ['mozilla'] }, 
       options: { sortingMode: 5, queryType: 1 } // sort by URI descending, bookmarks only
     });
   }).then(results => {
     assert.equal(results.length, 2, 'should only return two');
     assert.equal(results[0].url, 'http://firefox.com/', 'is sorted by URI desc');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testHostMultiQuery = function (assert, done) {
   all([
     createBookmark({ url: 'http://firefox.com', tags: ['firefox', 'mozilla'] }),
     createBookmark({ url: 'http://mozilla.com', tags: ['mozilla'] }),
@@ -248,37 +239,38 @@ exports.testHostMultiQuery = function (a
     assert.equal(results[0].url, 'http://firefox.com/', 'should match URL or tag');
     assert.equal(results[1].url, 'http://thunderbird.com/', 'should match URL or tag');
     return send('sdk-places-query', {
       queries: [{ tags: ['firefox'], url: 'http://mozilla.com/' }],
       options: { sortingMode: 5, queryType: 1 } // sort by URI descending, bookmarks only
     });
   }).then(results => {
     assert.equal(results.length, 0, 'query props should be AND\'d');
-    clearAllBookmarks();
     done();
   });
 };
 
 exports.testGetAllBookmarks = function (assert, done) {
   createBookmarkTree().then(() => { 
     return send('sdk-places-bookmarks-get-all', {});
   }).then(res => {
     assert.equal(res.length, 8, 'all bookmarks returned');
-    clearAllBookmarks();
     done();
   }, console.error);
 };
 
 exports.testGetAllChildren = function (assert, done) {
   createBookmarkTree().then(results => {
     return send('sdk-places-bookmarks-get-children', {
       id: results.filter(({title}) => title === 'mozgroup')[0].id
     });
   }).then(results => {
     assert.equal(results.length, 5,
       'should return all children and folders at a single depth');
-    clearAllBookmarks();
     done();
   });
 };
 
+
+before(exports, (name, assert, done) => resetPlaces(done));
+after(exports, (name, assert, done) => resetPlaces(done));
+
 require('test').run(exports);
--- a/addon-sdk/source/test/test-plain-text-console.js
+++ b/addon-sdk/source/test/test-plain-text-console.js
@@ -31,121 +31,134 @@ exports.testPlainTextConsole = function(
   prefs.reset(ADDON_LOG_LEVEL_PREF);
 
   var Console = require("sdk/console/plain-text").PlainTextConsole;
   var con = new Console(print);
 
   test.pass("PlainTextConsole instantiates");
 
   con.log('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "info: " + name + ": testing 1 2,3,4\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, 1, Array [2,3,4]\n",
                    "PlainTextConsole.log() must work.");
 
   con.info('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "info: " + name + ": testing 1 2,3,4\n",
+  test.assertEqual(lastPrint(), "console.info: " + name + ": testing, 1, Array [2,3,4]\n",
                    "PlainTextConsole.info() must work.");
 
   con.warn('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "warn: " + name + ": testing 1 2,3,4\n",
+  test.assertEqual(lastPrint(), "console.warn: " + name + ": testing, 1, Array [2,3,4]\n",
                    "PlainTextConsole.warn() must work.");
 
   con.error('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "error: " + name + ": testing 1 2,3,4\n",
+  test.assertEqual(prints[0], "console.error: " + name + ": \n",
                    "PlainTextConsole.error() must work.");
+  test.assertEqual(prints[1], "  testing\n")
+  test.assertEqual(prints[2], "  1\n")
+  test.assertEqual(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
+  prints = [];
 
   con.debug('testing', 1, [2, 3, 4]);
-  test.assertEqual(lastPrint(), "debug: " + name + ": testing 1 2,3,4\n",
+  test.assertEqual(prints[0], "console.debug: " + name + ": \n",
                    "PlainTextConsole.debug() must work.");
+  test.assertEqual(prints[1], "  testing\n")
+  test.assertEqual(prints[2], "  1\n")
+  test.assertEqual(prints[3], "Array\n    - 0 = 2\n    - 1 = 3\n    - 2 = 4\n    - length = 3\n");
+  prints = [];
 
   con.log('testing', undefined);
-  test.assertEqual(lastPrint(), "info: " + name + ": testing undefined\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, undefined\n",
                    "PlainTextConsole.log() must stringify undefined.");
 
   con.log('testing', null);
-  test.assertEqual(lastPrint(), "info: " + name + ": testing null\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, null\n",
                    "PlainTextConsole.log() must stringify null.");
 
+  // TODO: Fix console.jsm to detect custom toString.
   con.log("testing", { toString: function() "obj.toString()" });
-  test.assertEqual(lastPrint(), "info: " + name + ": testing obj.toString()\n",
-                   "PlainTextConsole.log() must stringify custom toString.");
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n",
+                   "PlainTextConsole.log() doesn't printify custom toString.");
 
   con.log("testing", { toString: function() { throw "fail!"; } });
-  test.assertEqual(lastPrint(), "info: " + name + ": testing <toString() error>\n",
+  test.assertEqual(lastPrint(), "console.log: " + name + ": testing, {}\n",
                    "PlainTextConsole.log() must stringify custom bad toString.");
 
+  
   con.exception(new Error("blah"));
 
-  var tbLines = prints[0].split("\n");
-  test.assertEqual(tbLines[0], "error: " + name + ": An exception occurred.");
-  test.assertEqual(tbLines[1], "Error: blah");
-  test.assertEqual(tbLines[2], module.uri + " 74");
-  test.assertEqual(tbLines[3], "Traceback (most recent call last):");
+  
+  test.assertEqual(prints[0], "console.error: " + name + ": \n");
+  let tbLines = prints[1].split("\n");
+  test.assertEqual(tbLines[0], "  Message: Error: blah");
+  test.assertEqual(tbLines[1], "  Stack:");
+  test.assert(prints[1].indexOf(module.uri + ":84") !== -1);
+  prints = []
 
-  prints = [];
   try {
     loadSubScript("invalid-url", {});
     test.fail("successed in calling loadSubScript with invalid-url");
   }
   catch(e) {
     con.exception(e);
   }
-  var tbLines = prints[0].split("\n");
-  test.assertEqual(tbLines[0], "error: " + name + ": An exception occurred.");
-  test.assertEqual(tbLines[1], "Error creating URI (invalid URL scheme?)");
-  test.assertEqual(tbLines[2], "Traceback (most recent call last):");
+  test.assertEqual(prints[0], "console.error: " + name + ": \n");
+  test.assertEqual(prints[1], "  Error creating URI (invalid URL scheme?)\n");
+  prints = [];
 
+  con.trace();
+  let tbLines = prints[0].split("\n");
+  test.assertEqual(tbLines[0], "console.trace: " + name + ": ");
+  test.assert(tbLines[1].indexOf("_ain-text-console.js 105") == 0);
   prints = [];
-  con.trace();
-  tbLines = prints[0].split("\n");
-  test.assertEqual(tbLines[0], "info: " + name + ": Traceback (most recent call last):");
-  test.assertEqual(tbLines[tbLines.length - 4].trim(), "con.trace();");
 
   // Whether or not console methods should print at the various log levels,
   // structured as a hash of levels, each of which contains a hash of methods,
   // each of whose value is whether or not it should print, i.e.:
   // { [level]: { [method]: [prints?], ... }, ... }.
   let levels = {
     all:   { debug: true,  log: true,  info: true,  warn: true,  error: true  },
     debug: { debug: true,  log: true,  info: true,  warn: true,  error: true  },
     info:  { debug: false, log: true,  info: true,  warn: true,  error: true  },
     warn:  { debug: false, log: false, info: false, warn: true,  error: true  },
     error: { debug: false, log: false, info: false, warn: false, error: true  },
     off:   { debug: false, log: false, info: false, warn: false, error: false },
   };
 
   // The messages we use to test the various methods, as a hash of methods.
   let messages = {
-    debug: "debug: " + name + ": \n",
-    log: "info: " + name + ": \n",
-    info: "info: " + name + ": \n",
-    warn: "warn: " + name + ": \n",
-    error: "error: " + name + ": \n",
+    debug: "console.debug: " + name + ": \n  \n",
+    log: "console.log: " + name + ": \n",
+    info: "console.info: " + name + ": \n",
+    warn: "console.warn: " + name + ": \n",
+    error: "console.error: " + name + ": \n  \n",
   };
 
   for (let level in levels) {
     let methods = levels[level];
     for (let method in methods) {
       // We have to reset the log level pref each time we run the test
       // because the test runner relies on the console to print test output,
       // and test results would not get printed to the console for some
       // values of the pref.
       prefs.set(SDK_LOG_LEVEL_PREF, level);
       con[method]("");
       prefs.set(SDK_LOG_LEVEL_PREF, "all");
-      test.assertEqual(lastPrint(), (methods[method] ? messages[method] : null),
+      test.assertEqual(prints.join(""), 
+                       (methods[method] ? messages[method] : ""),
                        "at log level '" + level + "', " + method + "() " +
                        (methods[method] ? "prints" : "doesn't print"));
+      prints = [];
     }
   }
 
   prefs.set(SDK_LOG_LEVEL_PREF, "off");
   prefs.set(ADDON_LOG_LEVEL_PREF, "all");
   con.debug("");
-  test.assertEqual(lastPrint(), messages["debug"],
+  test.assertEqual(prints.join(""), messages["debug"],
                    "addon log level 'all' overrides SDK log level 'off'");
+  prints = [];
 
   prefs.set(SDK_LOG_LEVEL_PREF, "all");
   prefs.set(ADDON_LOG_LEVEL_PREF, "off");
   con.error("");
   prefs.reset(ADDON_LOG_LEVEL_PREF);
   test.assertEqual(lastPrint(), null,
                    "addon log level 'off' overrides SDK log level 'all'");
 
--- a/addon-sdk/source/test/test-system-events.js
+++ b/addon-sdk/source/test/test-system-events.js
@@ -5,16 +5,18 @@
 const events = require("sdk/system/events");
 const self = require("sdk/self");
 const { Cc, Ci, Cu } = require("chrome");
 const { setTimeout } = require("sdk/timers");
 const { Loader, LoaderWithHookedConsole2 } = require("sdk/test/loader");
 const nsIObserverService = Cc["@mozilla.org/observer-service;1"].
                            getService(Ci.nsIObserverService);
 
+let isConsoleEvent = (topic) =>
+  !!~["console-api-log-event", "console-storage-cache-event"].indexOf(topic)
 
 exports["test basic"] = function(assert) {
   let type = Date.now().toString(32);
 
   let timesCalled = 0;
   function handler(subject, data) { timesCalled++; };
 
   events.on(type, handler);
@@ -43,20 +45,20 @@ exports["test error reporting"] = functi
   let lineNumber;
   try { brokenHandler() } catch (error) { lineNumber = error.lineNumber }
 
   let errorType = Date.now().toString(32);
 
   events.on(errorType, brokenHandler);
   events.emit(errorType, { data: "yo yo" });
 
-  assert.equal(messages.length, 1, "Got an exception");
-  let text = messages[0];
-  assert.ok(text.indexOf(self.name + ": An exception occurred.") >= 0,
-            "error is logged");
+  assert.equal(messages.length, 2, "Got an exception");
+  assert.equal(messages[0], "console.error: " + self.name + ": \n",
+               "error is logged");
+  let text = messages[1];
   assert.ok(text.indexOf("Error: foo") >= 0, "error message is logged");
   assert.ok(text.indexOf(module.uri) >= 0, "module uri is logged");
   assert.ok(text.indexOf(lineNumber) >= 0, "error line is logged");
 
   events.off(errorType, brokenHandler);
 
   loader.unload();
 };
@@ -99,16 +101,19 @@ exports["test handle nsIObserverService 
 
   let type = Date.now().toString(32);
   let timesCalled = 0;
   let lastSubject = null;
   let lastData = null;
   let lastType = null;
 
   function handler({ subject, data, type }) {
+    // Ignores internal console events
+    if (isConsoleEvent(type))
+      return;
     timesCalled++;
     lastSubject = subject;
     lastData = data;
     lastType = type;
   };
 
   events.on(type, handler);
   nsIObserverService.notifyObservers(uri, type, "some data");
@@ -163,33 +168,35 @@ exports["test emit to nsIObserverService
   let lastTopic = null;
 
   var topic = Date.now().toString(32)
   let nsIObserver = {
     QueryInterface: function() {
       return nsIObserver;
     },
     observe: function(subject, topic, data) {
+      // Ignores internal console events
+      if (isConsoleEvent(topic))
+        return;
       timesCalled = timesCalled + 1;
       lastSubject = subject;
       lastData = data;
       lastTopic = topic;
     }
   };
 
   nsIObserverService.addObserver(nsIObserver, topic, false);
 
   events.emit(topic, { subject: uri, data: "some data" });
 
   assert.equal(timesCalled, 1, "emit notifies observers");
   assert.equal(lastTopic, topic, "event type is notification topic");
   assert.equal(lastSubject.wrappedJSObject.object, uri,
                "event.subject is notification subject");
   assert.equal(lastData, "some data", "event.data is notification data");
-
   function customSubject() {}
   function customData() {}
   events.emit(topic, { subject: customSubject, data: customData });
 
   assert.equal(timesCalled, 2, "emit notifies observers");
   assert.equal(lastTopic, topic, "event.type is notification");
   assert.equal(lastSubject.wrappedJSObject.object, customSubject,
                "event.subject is notification subject");
@@ -201,20 +208,21 @@ exports["test emit to nsIObserverService
 
   assert.equal(timesCalled, 2, "removed observers no longer invoked");
 
   nsIObserverService.addObserver(nsIObserver, "*", false);
 
   events.emit(topic, { data: "data again" });
 
   assert.equal(timesCalled, 3, "emit notifies * observers");
+
   assert.equal(lastTopic, topic, "event.type is notification");
   assert.equal(lastSubject, null,
                "event.subject is notification subject");
   assert.equal(lastData, "data again", "event.data is notification data");
 
   nsIObserverService.removeObserver(nsIObserver, "*");
-
+  
   events.emit(topic, { data: "last data" });
   assert.equal(timesCalled, 3, "removed observers no longer invoked");
 }
 
 require("test").run(exports);
--- a/addon-sdk/source/test/test-tab-utils.js
+++ b/addon-sdk/source/test/test-tab-utils.js
@@ -1,57 +1,50 @@
 'use strict';
 
 const { getTabs } = require('sdk/tabs/utils');
 const { isGlobalPBSupported, isWindowPBSupported, isTabPBSupported } = require('sdk/private-browsing/utils');
 const { browserWindows } = require('sdk/windows');
 const tabs = require('sdk/tabs');
 const { pb } = require('./private-browsing/helper');
 const { isPrivate } = require('sdk/private-browsing');
-const { openTab, closeTab, getTabContentWindow } = require('sdk/tabs/utils');
+const { openTab, closeTab, getTabContentWindow, getOwnerWindow } = require('sdk/tabs/utils');
 const { open, close } = require('sdk/window/helpers');
 const { windows } = require('sdk/window/utils');
 const { getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { fromIterator } = require('sdk/util/array');
 
-if (isGlobalPBSupported) {
+if (isWindowPBSupported) {
   exports.testGetTabs = function(assert, done) {
-    pb.once('start', function() {
-      tabs.open({
-        url: 'about:blank',
-        inNewWindow: true,
-        onOpen: function(tab) {
-          assert.equal(getTabs().length, 2, 'there are two tabs');
-          assert.equal(browserWindows.length, 2, 'there are two windows');
-          pb.once('stop', function() {
-            done();
-          });
-          pb.deactivate();
-        }
-      });
-    });
-    pb.activate();
-  };
-}
-else if (isWindowPBSupported) {
-  exports.testGetTabs = function(assert, done) {
+    let tabCount = getTabs().length;
+    let windowCount = browserWindows.length;
+
     open(null, {
         features: {
         private: true,
         toolbar: true,
         chrome: true
       }
     }).then(function(window) {
       assert.ok(isPrivate(window), 'new tab is private');
-      assert.equal(getTabs().length, 1, 'there is one tab found');
-      assert.equal(browserWindows.length, 1, 'there is one window found');
+
+      assert.equal(getTabs().length, tabCount, 'there are no new tabs found');
+      getTabs().forEach(function(tab) {
+        assert.equal(isPrivate(tab), false, 'all found tabs are not private');
+        assert.equal(isPrivate(getOwnerWindow(tab)), false, 'all found tabs are not private');
+        assert.equal(isPrivate(getTabContentWindow(tab)), false, 'all found tabs are not private');
+      });
+
+      assert.equal(browserWindows.length, windowCount, 'there are no new windows found');
       fromIterator(browserWindows).forEach(function(window) {
-        assert.ok(!isPrivate(window), 'all found windows are not private');
+        assert.equal(isPrivate(window), false, 'all found windows are not private');
       });
+
       assert.equal(windows(null, {includePrivate: true}).length, 2, 'there are really two windows');
+
       close(window).then(done);
     });
   };
 }
 else if (isTabPBSupported) {
   exports.testGetTabs = function(assert, done) {
     let startTabCount = getTabs().length;
     let tab = openTab(getMostRecentBrowserWindow(), 'about:blank', {
@@ -66,12 +59,9 @@ else if (isTabPBSupported) {
                  'the last tab is the opened tab');
     assert.equal(browserWindows.length, 1, 'there is only one window');
     closeTab(tab);
 
     done();
   };
 }
 
-// Test disabled because of bug 855771
-module.exports = {};
-
 require('test').run(exports);
--- a/addon-sdk/source/test/test-timer.js
+++ b/addon-sdk/source/test/test-timer.js
@@ -1,131 +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/. */
 
 var timer = require("sdk/timers");
 const { Loader } = require("sdk/test/loader");
 
-exports.testSetTimeout = function(test) {
+exports.testSetTimeout = function(assert, end) {
   timer.setTimeout(function() {
-    test.pass("testSetTimeout passed");
-    test.done();
+    assert.pass("testSetTimeout passed");
+    end();
   }, 1);
-  test.waitUntilDone();
 };
 
-exports.testParamedSetTimeout = function(test) {
+exports.testParamedSetTimeout = function(assert, end) {
   let params = [1, 'foo', { bar: 'test' }, null, undefined];
   timer.setTimeout.apply(null, [function() {
-    test.assertEqual(arguments.length, params.length);
+    assert.equal(arguments.length, params.length);
     for (let i = 0, ii = params.length; i < ii; i++)
-      test.assertEqual(params[i], arguments[i]);
-    test.done();
+      assert.equal(params[i], arguments[i]);
+    end();
   }, 1].concat(params));
-  test.waitUntilDone();
 };
 
-exports.testClearTimeout = function(test) {
+exports.testClearTimeout = function(assert, end) {
   var myFunc = function myFunc() {
-    test.fail("myFunc() should not be called in testClearTimeout");
+    assert.fail("myFunc() should not be called in testClearTimeout");
   };
   var id = timer.setTimeout(myFunc, 1);
   timer.setTimeout(function() {
-    test.pass("testClearTimeout passed");
-    test.done();
+    assert.pass("testClearTimeout passed");
+    end();
   }, 2);
   timer.clearTimeout(id);
-  test.waitUntilDone();
 };
 
-exports.testParamedClearTimeout = function(test) {
+exports.testParamedClearTimeout = function(assert, end) {
   let params = [1, 'foo', { bar: 'test' }, null, undefined];
   var myFunc = function myFunc() {
-    test.fail("myFunc() should not be called in testClearTimeout");
+    assert.fail("myFunc() should not be called in testClearTimeout");
   };
   var id = timer.setTimeout(myFunc, 1);
   timer.setTimeout.apply(null, [function() {
-    test.assertEqual(arguments.length, params.length);
+    assert.equal(arguments.length, params.length);
     for (let i = 0, ii = params.length; i < ii; i++)
-      test.assertEqual(params[i], arguments[i]);
-    test.done();
+      assert.equal(params[i], arguments[i]);
+    end();
   }, 1].concat(params));
   timer.clearTimeout(id);
-  test.waitUntilDone();
 };
 
-exports.testSetInterval = function (test) {
+exports.testSetInterval = function (assert, end) {
   var count = 0;
   var id = timer.setInterval(function () {
     count++;
     if (count >= 5) {
       timer.clearInterval(id);
-      test.pass("testSetInterval passed");
-      test.done();
+      assert.pass("testSetInterval passed");
+      end();
     }
   }, 1);
-  test.waitUntilDone();
 };
 
-exports.testParamedSetInerval = function(test) {
+exports.testParamedSetInerval = function(assert, end) {
   let params = [1, 'foo', { bar: 'test' }, null, undefined];
   let count = 0;
   let id = timer.setInterval.apply(null, [function() {
     count ++;
     if (count < 5) {
-      test.assertEqual(arguments.length, params.length);
+      assert.equal(arguments.length, params.length);
       for (let i = 0, ii = params.length; i < ii; i++)
-        test.assertEqual(params[i], arguments[i]);
+        assert.equal(params[i], arguments[i]);
     } else {
       timer.clearInterval(id);
-      test.done();
+      end();
     }
   }, 1].concat(params));
-  test.waitUntilDone();
 };
 
-exports.testClearInterval = function (test) {
+exports.testClearInterval = function (assert, end) {
   timer.clearInterval(timer.setInterval(function () {
-    test.fail("setInterval callback should not be called");
+    assert.fail("setInterval callback should not be called");
   }, 1));
   var id = timer.setInterval(function () {
     timer.clearInterval(id);
-    test.pass("testClearInterval passed");
-    test.done();
+    assert.pass("testClearInterval passed");
+    end();
   }, 2);
-  test.waitUntilDone();
 };
 
-exports.testParamedClearInterval = function(test) {
+exports.testParamedClearInterval = function(assert, end) {
   timer.clearInterval(timer.setInterval(function () {
-    test.fail("setInterval callback should not be called");
+    assert.fail("setInterval callback should not be called");
   }, 1, timer, {}, null));
 
   let id = timer.setInterval(function() {
     timer.clearInterval(id);
-    test.assertEqual(3, arguments.length);
-    test.done();
+    assert.equal(3, arguments.length);
+    end();
   }, 2, undefined, 'test', {});
-  test.waitUntilDone();
 };
 
 
-exports.testUnload = function(test) {
+exports.testImmediate = function(assert, end) {
+  let actual = [];
+  let ticks = 0;
+  timer.setImmediate(function(...params) {
+    actual.push(params);
+    assert.equal(ticks, 1, "is a next tick");
+    assert.deepEqual(actual, [["start", "immediates"]]);
+  }, "start", "immediates");
+
+  timer.setImmediate(function(...params) {
+    actual.push(params);
+    assert.deepEqual(actual, [["start", "immediates"],
+                                  ["added"]]);
+    assert.equal(ticks, 1, "is a next tick");
+    timer.setImmediate(function(...params) {
+      actual.push(params);
+      assert.equal(ticks, 2, "is second tick");
+      assert.deepEqual(actual, [["start", "immediates"],
+                                    ["added"],
+                                    [],
+                                    ["last", "immediate", "handler"],
+                                    ["side-effect"]]);
+      end();
+    }, "side-effect");
+  }, "added");
+
+  timer.setImmediate(function(...params) {
+    actual.push(params);
+    assert.equal(ticks, 1, "is a next tick");
+    assert.deepEqual(actual, [["start", "immediates"],
+                              ["added"],
+                              []]);
+    timer.clearImmediate(removeID);
+  });
+
+  function removed() {
+    assert.fail("should be removed");
+  }
+  let removeID = timer.setImmediate(removed);
+
+  timer.setImmediate(function(...params) {
+    actual.push(params);
+    assert.equal(ticks, 1, "is a next tick");
+    assert.deepEqual(actual, [["start", "immediates"],
+                              ["added"],
+                              [],
+                              ["last", "immediate", "handler"]]);
+    ticks = ticks + 1;
+  }, "last", "immediate", "handler");
+
+
+  ticks = ticks + 1;
+};
+
+exports.testUnload = function(assert, end) {
   var loader = Loader(module);
   var sbtimer = loader.require("sdk/timers");
 
   var myFunc = function myFunc() {
-    test.fail("myFunc() should not be called in testUnload");
+    assert.fail("myFunc() should not be called in testUnload");
   };
 
   sbtimer.setTimeout(myFunc, 1);
   sbtimer.setTimeout(myFunc, 1, 'foo', 4, {}, undefined);
   sbtimer.setInterval(myFunc, 1);
   sbtimer.setInterval(myFunc, 1, {}, null, 'bar', undefined, 87);
   loader.unload();
   timer.setTimeout(function() {
-    test.pass("timer testUnload passed");
-    test.done();
+    assert.pass("timer testUnload passed");
+    end();
   }, 2);
-  test.waitUntilDone();
 };
 
+require("test").run(exports);
\ No newline at end of file
--- a/addon-sdk/source/test/test-window-events.js
+++ b/addon-sdk/source/test/test-window-events.js
@@ -1,12 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
 "use strict";
 
 const { Loader } = require("sdk/test/loader");
 const { open, getMostRecentBrowserWindow, getOuterId } = require("sdk/window/utils");
 
 exports["test browser events"] = function(assert, done) {
   let loader = Loader(module);
   let { events } = loader.require("sdk/window/events");
@@ -48,9 +47,9 @@ if (require("sdk/system/xul-app").is("Fe
     "test Unsupported Test": function UnsupportedTest (assert) {
         assert.pass(
           "Skipping this test until Fennec support is implemented." +
           "See bug 793071");
     }
   }
 }
 
-require("test").run(exports);
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/test-window-utils2.js
+++ b/addon-sdk/source/test/test-window-utils2.js
@@ -56,16 +56,44 @@ exports['test new top window with option
   assert.equal(window.innerHeight, 100, 'height is set');
   assert.equal(window.innerWidth, 200, 'height is set');
   assert.equal(window.toolbar.visible, true, 'toolbar was set');
 
   // Wait for the window unload before ending test
   close(window).then(done);
 };
 
+exports['test new top window with various URIs'] = function(assert, done) {
+  let msg = 'only chrome, resource and data uris are allowed';
+  assert.throws(function () {
+    open('foo');
+  }, msg);
+  assert.throws(function () {
+    open('http://foo');
+  }, msg);
+  assert.throws(function () {
+    open('https://foo');
+  }, msg); 
+  assert.throws(function () {
+    open('ftp://foo');
+  }, msg);
+  assert.throws(function () {
+    open('//foo');
+  }, msg);
+
+  let chromeWindow = open('chrome://foo/content/');
+  assert.ok(~windows().indexOf(chromeWindow), 'chrome URI works');
+  
+  let resourceWindow = open('resource://foo');
+  assert.ok(~windows().indexOf(resourceWindow), 'resource URI works');
+
+  // Wait for the window unload before ending test
+  close(chromeWindow).then(close.bind(null, resourceWindow)).then(done);
+};
+
 exports.testBackgroundify = function(assert, done) {
   let window = open('data:text/html;charset=utf-8,backgroundy');
   assert.ok(~windows().indexOf(window),
             'window is in the list of windows');
   let backgroundy = backgroundify(window);
   assert.equal(backgroundy, window, 'backgroundify returs give window back');
   assert.ok(!~windows().indexOf(window),
             'backgroundifyied window is in the list of windows');