Bug 865135 - Uplift Add-on SDK integration branch to Firefox.
authorWes Kocher <wkocher@mozilla.com>
Fri, 26 Apr 2013 02:04:23 -0700
changeset 140987 ef735506cb13a73079602bb2c964ed980c9f8ff2
parent 140986 b66a03a1acf296af6bd48a8b1d75b84afdc57a00
child 140988 34413bab9ad5ea4515976a0b491810d5ea46c60d
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs865135
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 865135 - Uplift Add-on SDK integration branch to Firefox.
addon-sdk/source/app-extension/bootstrap.js
addon-sdk/source/doc/dev-guide-source/credits.md
addon-sdk/source/doc/module-source/sdk/lang/functional.md
addon-sdk/source/doc/module-source/sdk/lang/type.md
addon-sdk/source/doc/module-source/sdk/tabs.md
addon-sdk/source/lib/sdk/browser/events.js
addon-sdk/source/lib/sdk/console/plain-text.js
addon-sdk/source/lib/sdk/core/disposable.js
addon-sdk/source/lib/sdk/event/utils.js
addon-sdk/source/lib/sdk/lang/functional.js
addon-sdk/source/lib/sdk/loader/cuddlefish.js
addon-sdk/source/lib/sdk/net/xhr.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/tab/events.js
addon-sdk/source/lib/sdk/test/harness.js
addon-sdk/source/lib/sdk/test/loader.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
addon-sdk/source/test/private-browsing/helper.js
addon-sdk/source/test/tabs/test-fennec-tabs.js
addon-sdk/source/test/tabs/test-firefox-tabs.js
addon-sdk/source/test/test-context-menu.js
addon-sdk/source/test/test-disposable.js
addon-sdk/source/test/test-event-utils.js
addon-sdk/source/test/test-functional.js
addon-sdk/source/test/test-page-mod.js
addon-sdk/source/test/test-panel.js
addon-sdk/source/test/test-tabs-common.js
addon-sdk/source/test/test-window-utils.js
addon-sdk/source/test/test-xhr.js
addon-sdk/source/test/windows/test-firefox-windows.js
--- a/addon-sdk/source/app-extension/bootstrap.js
+++ b/addon-sdk/source/app-extension/bootstrap.js
@@ -230,16 +230,17 @@ function startup(data, reasonCode) {
         '@test/options': {
           allTestModules: options.allTestModules,
           iterations: options.iterations,
           filter: options.filter,
           profileMemory: options.profileMemory,
           stopOnError: options.stopOnError,
           verbose: options.verbose,
           parseable: options.parseable,
+          checkMemory: options.check_memory,
         }
       }
     });
 
     let module = cuddlefish.Module('sdk/loader/cuddlefish', cuddlefishURI);
     let require = cuddlefish.Require(loader, module);
 
     require('sdk/addon/runner').startup(reason, {
--- a/addon-sdk/source/doc/dev-guide-source/credits.md
+++ b/addon-sdk/source/doc/dev-guide-source/credits.md
@@ -71,16 +71,17 @@ We'd like to thank our many Jetpack proj
 
 * Eric H. Jung
 
 ### K ###
 
 * Hrishikesh Kale
 * Wes Kocher
 * Lajos Koszti
+* Kusanagi Kouichi
 * [Vladimir Kukushkin](https://github.com/kukushechkin)
 
 ### L ###
 
 * Edward Lee
 * Gregg Lind
 
 ### M ###
@@ -124,16 +125,17 @@ We'd like to thank our many Jetpack proj
 * Ayan Shah
 * [skratchdot](https://github.com/skratchdot)
 * Henrik Skupin
 * slash
 * Markus Stange
 * Dan Stevens
 * [J. Ryan Stinnett](https://github.com/jryans)
 * [Mihai Sucan](https://github.com/mihaisucan)
+* Sunny ([darkowlzz](https://github.com/darkowlzz))
 
 ### T ###
 
 * taku0
 * Clint Talbert
 * Tim Taubert
 * Shane Tomlinson
 * Dave Townsend
--- a/addon-sdk/source/doc/module-source/sdk/lang/functional.md
+++ b/addon-sdk/source/doc/module-source/sdk/lang/functional.md
@@ -89,36 +89,36 @@ Returns the value that is returned by `c
 @param params {Array}
   Parameters to be passed into `callee`.
 @param self {mixed}
   Object to be passed as the `this` context to `callee`.
 @returns {mixed}
   Returns the return value of `callee`.
 </api>
 
-<api name="curry">
+<api name="partial">
 @function
-[Curries](http://en.wikipedia.org/wiki/Currying) the given function with the arguments given.
+Takes a function and bind values to one or more arguments, returning a new function of smaller arity.
 
-    let { curry } = require("sdk/lang/functional");
+    let { partial } = require("sdk/lang/functional");
     let add = function add (x, y) { return x + y; }
-    let addOne = curry(add, 1);
+    let addOne = partial(add, 1);
 
     addOne(5); // 6
     addOne(10); // 11
-    curry(add, addOne(20))(2); // 23
+    partial(add, addOne(20))(2); // 23
 
 @param fn {function}
-  Function to be curried.
+  Function on which partial application is to be performed.
 
 @param arguments... {mixed}
   Additional arguments
 
 @returns {function}
-  The curried function.
+  The partial function.
 </api>
 
 <api name="compose">
 @function
 Returns the [composition](http://en.wikipedia.org/wiki/Function_composition_(computer_science)) of a list of functions, where each function consumes the
 return value of the function that follows. In math terms, composing the functions
 `f()`, `g()`, and `h()` produces `f(g(h()))`.
 
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/doc/module-source/sdk/lang/type.md
@@ -0,0 +1,346 @@
+<!-- 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 `lang/type` module provides simple helper functions for working with type
+detection.
+
+<api name="isUndefined">
+@function
+Returns `true` if `value` is [`undefined`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined), `false` otherwise.
+
+    let { isUndefined } = require('sdk/lang/type');
+
+    var foo;
+    isUndefined(foo); // true
+    isUndefined(0); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is `undefined`.
+</api>
+
+<api name="isNull">
+@function
+Returns `true` if `value` is [`null`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/null), `false` otherwise.
+
+    let { isNull } = require('sdk/lang/type');
+    
+    isNull(null); // true
+    isNull(false); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is `null`.
+</api>
+
+<api name="isString">
+@function
+Returns `true` if `value` is a [`String`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String),
+`false` otherwise. Uses [`typeof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/typeof)
+operator to check type, and will only properly detect string primitives:
+for example, a string created with `new String()` will always return false.
+
+    let { isString } = require('sdk/lang/type');
+    
+    isString('my string'); // true
+    isString(100); // false
+    isString('100'); // true
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a `String`.
+</api>
+
+<api name="isNumber">
+@function
+Returns `true` if `value` is a [`Number`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Number),
+`false` otherwise. Uses [`typeof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/typeof)
+operator to check type, and will only properly detect number primitives:
+for example, a number created with `new Number()` will always return false.
+
+    let { isNumber } = require('sdk/lang/type');
+    
+    isNumber(3.1415); // true
+    isNumber(100); // true
+    isNumber('100'); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a `Number`.
+</api>
+
+<api name="isRegExp">
+@function
+Returns `true` if `value` is a [`RegExp`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/RegExp), `false` otherwise.
+
+    let { isRegExp } = require('sdk/lang/type');
+    
+    isRegExp(/[^\.]*\.js$/); // true
+    isRegExp(new RegExp('substring')); // true
+    isRegExp(1000); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a `RegExp`.
+</api>
+
+<api name="isDate">
+@function
+Returns `true` if `value` is a [`Date`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Date), `false` otherwise.
+
+    let { isDate } = require('sdk/lang/type');
+    
+    isDate(new Date()); // true
+    isDate('3/1/2013'); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a `Date`.
+</api>
+
+<api name="isFunction">
+@function
+Returns `true` if `value` is a [`Function`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function), `false` otherwise.
+
+    let { isFunction } = require('sdk/lang/type');
+    
+    let fn = function () {};
+    isFunction(fn); // true;
+    isFunction(otherFn); // true;
+    isFunction(function () {}); // true;
+
+    function otherFn () {}
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a `Function`.
+</api>
+
+<api name="isObject">
+@function
+Returns `true` if `value` is an [`Object`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object) and not null, `false` otherwise.
+
+    let { isObject } = require('sdk/lang/type');
+
+    isObject({}); // true
+    isObject(new Class()); // true
+    isObject(null); // false
+    isObject(5); // false
+
+    function Class () {}
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is an `Object`.
+</api>
+
+<api name="isArray">
+@function
+Returns `true` if `value` is an [`Array`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array), `false` otherwise. Uses native
+[`Array.isArray`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray).
+
+    let { isArray } = require('sdk/lang/type');
+    
+    isArray([]); // true
+    isArray({}); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is an `Array`.
+</api>
+
+<api name="isArguments">
+@function
+Returns `true` if `value` is an array-like [`arguments`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Functions_and_function_scope/arguments) object,
+`false` otherwise.
+
+    let { isArguments } = require('sdk/lang/type');
+    
+    function run () {
+      isArguments(arguments); // true
+      isArguments([]); // false
+      isArguments(Array.slice(arguments)); // false
+    }
+    run(1, 2, 3);
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is an `arguments` object.
+</api>
+
+<api name="isPrimitive">
+@function
+Returns `true` if `value` is a primitive value: that is, any of [`null`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/null), [`undefined`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/undefined), [`number`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/number),
+[`boolean`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/boolean), or [`string`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/string). Returns `false` if `value` is not a primitive value.
+
+    let { isPrimitive } = require('sdk/lang/type');
+    
+    isPrimitive(3); // true
+    isPrimitive('foo'); // true
+    isPrimitive({}); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a primitive.
+</api>
+
+<api name="isFlat">
+@function
+Returns `true` if `value` is a direct descendant of `Object.prototype` or `null`.
+Similar to jQuery's [`isPlainObject`](http://api.jquery.com/jQuery.isPlainObject/).
+
+    let { isFlat } = require('sdk/lang/type');
+    
+    isFlat({}); // true
+    isFlat(new Type()); // false
+
+    function Type () {}
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is a direct descendant of `Object.prototype` or `null`.
+</api>
+
+<api name="isEmpty">
+@function
+Returns `true` if `value` is an [`Object`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object) with no properties and `false` otherwise.
+
+    let { isEmpty } = require('sdk/lang/type');
+    
+    isEmpty({}); // true
+    isEmpty({ init: false }); // false
+
+@param value {object}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is an `Object` with no properties.
+</api>
+
+<api name="isJSON">
+@function
+Returns `true` if `value` is a string, number, boolean, null, array of JSON-serializable values, or an object whose property values are themselves JSON-serializable. Returns `false` otherwise.
+
+    let { isJSON } = require('sdk/lang/type');
+    
+    isJSON({ value: 42 }); // true
+    isJSON({ fn: function () {} ); // false
+
+@param value {mixed}
+  The variable to check.
+
+@returns {boolean}
+  Boolean indicating if `value` is an `Array`/flat `Object` containing only
+  atomic values and other flat objects.
+</api>
+
+<api name="instanceOf">
+@function
+Returns `true` if `value` is an instance of a given `Type`. This is similar to the [`instanceof`](https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Operators/instanceof) operator.
+The difference is that the `Type` constructor can be from a scope that has
+a different top level object: for example, it could be from a different iframe,
+module or sandbox.
+
+    let { instanceOf } = require('sdk/lang/type');
+    
+    instanceOf(new Class(), Class); // true
+    function Class() {}
+
+@param value {object}
+  The variable to check.
+
+@param Type {object}
+  The constructor to compare to `value`
+
+@returns {boolean}
+  Boolean indicating if `value` is an instance of `Type`.
+</api>
+
+<api name="source">
+@function
+Returns the textual representation of `value`, containing property descriptors and types
+of properties contained within the object.
+
+    let { source } = require('sdk/lang/type');
+    
+    var obj = {
+      name: undefined,
+      twitter: '@horse_js',
+      tweets: [
+        { id: 100, text: 'What happens to you if you break the monad laws?' },
+        { id: 101, text: 'JAVASCRIPT DUBSTEP GENERATOR' }
+      ]
+    };
+
+    console.log(source(obj));
+    // Prints the below
+    /*
+    { // [object Object]
+        // writable configurable enumerable
+        name: undefined,
+        // writable configurable enumerable
+        twitter: "@horse_js",
+        // writable configurable enumerable
+        tweets: [
+            { // [object Object]
+                // writable configurable enumerable
+                id: 100,
+                // writable configurable enumerable
+                text: "What happens to you if you break the monad laws?",
+                "__proto__": { // [object Object]
+
+                }
+            },
+            { // [object Object]
+                // writable configurable enumerable
+                id: 101,
+                // writable configurable enumerable
+                text: "JAVASCRIPT DUBSTEP GENERATOR",
+                "__proto__": { // [object Object]
+
+                }
+            }
+        ],
+        "__proto__": { // [object Object]
+
+        }
+    }
+    */
+
+@param value {mixed}
+  The source object to create a textual representation of.
+
+@param indent {string}
+  Optional. `String` to be used as indentation in output. 4 spaces by default.
+
+@param limit {number}
+  Optional. Number of properties to display per object.
+
+@returns {string}
+  The textual representation of `value`.
+</api>
--- a/addon-sdk/source/doc/module-source/sdk/tabs.md
+++ b/addon-sdk/source/doc/module-source/sdk/tabs.md
@@ -187,16 +187,21 @@ This is an optional property.
 @class
 A `Tab` instance represents a single open tab. It contains various tab
 properties, several methods for manipulation, as well as per-tab event
 registration.
 
 Tabs emit all the events described in the Events section. Listeners are
 passed the `Tab` object that triggered the event.
 
+<api name="id">
+@property {string}
+The unique id for the tab. This property is read-only.
+</api>
+
 <api name="title">
 @property {string}
 The title of the tab (usually the title of the page currently loaded in the tab)
 This property can be set to change the tab title.
 </api>
 
 <api name="url">
 @property {String}
--- a/addon-sdk/source/lib/sdk/browser/events.js
+++ b/addon-sdk/source/lib/sdk/browser/events.js
@@ -12,9 +12,9 @@ const { filter } = require("../event/uti
 const { isBrowser } = require("../window/utils");
 
 // TODO: `isBrowser` detects weather window is a browser by checking
 // `windowtype` attribute, which means that all 'open' events will be
 // filtered out since document is not loaded yet. Maybe we can find a better
 // implementation for `isBrowser`. Either way it's not really needed yet
 // neither window tracker provides this event.
 
-exports.events = filter(function({target}) isBrowser(target), events);
+exports.events = filter(events, function({target}) isBrowser(target));
--- a/addon-sdk/source/lib/sdk/console/plain-text.js
+++ b/addon-sdk/source/lib/sdk/console/plain-text.js
@@ -8,17 +8,17 @@ module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci } = require("chrome");
 const self = require("../self");
 const traceback = require("./traceback")
 const prefs = require("../preferences/service");
 const { merge } = require("../util/object");
-const { curry } = require("../lang/functional");
+const { partial } = require("../lang/functional");
 
 const LEVELS = {
   "all": Number.MIN_VALUE,
   "debug": 20000,
   "info": 30000,
   "warn": 40000,
   "error": 50000,
   "off": Number.MAX_VALUE,
@@ -97,23 +97,23 @@ function PlainTextConsole(print) {
     // 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: curry(message, print, "info"),
-    info: curry(message, print, "info"),
-    warn: curry(message, print, "warn"),
-    error: curry(message, print, "error"),
-    debug: curry(message, print, "debug"),
-    exception: curry(errorMessage, print),
-    trace: curry(traceMessage, print),
+    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),
 
     dir: function dir() {},
     group: function group() {},
     groupCollapsed: function groupCollapsed() {},
     groupEnd: function groupEnd() {},
     time: function time() {},
     timeEnd: function timeEnd() {}
   });
--- a/addon-sdk/source/lib/sdk/core/disposable.js
+++ b/addon-sdk/source/lib/sdk/core/disposable.js
@@ -14,18 +14,17 @@ let { on, off } = require('../system/eve
 let unloadSubject = require('@loader/unload');
 
 let disposables = WeakMap();
 
 function initialize(instance) {
   // Create an event handler that will dispose instance on unload.
   function handler(event) {
     if (event.subject.wrappedJSObject === unloadSubject) {
-      dispose(instance);
-      instance.dispose();
+      instance.destroy();
     }
   }
 
   // Form weak reference between disposable instance and an unload event
   // handler associated with it. This will make sure that event handler can't
   // be garbage collected as long as instance is referenced. Also note that
   // system events intentionally hold weak reference to an event handler, this
   // will let GC claim both instance and an unload handler before actual add-on
@@ -61,13 +60,15 @@ let Disposable = Class({
   },
   dispose: function dispose() {
     // Implement your cleanup logic here.
   },
 
   destroy: function destroy() {
     // Destroying disposable removes unload handler so that attempt to dispose
     // won't be made at unload & delegates to dispose.
-    dispose(this);
-    this.dispose();
+    if (disposables.has(this)) {
+      dispose(this);
+      this.dispose();
+    }
   }
 });
 exports.Disposable = Disposable;
--- a/addon-sdk/source/lib/sdk/event/utils.js
+++ b/addon-sdk/source/lib/sdk/event/utils.js
@@ -20,17 +20,17 @@ let { emit, on, off } = require("./core"
 let refs = (function() {
   let refSets = new WeakMap();
   return function refs(target) {
     if (!refSets.has(target)) refSets.set(target, new Set());
     return refSets.get(target);
   }
 })();
 
-function transform(f, input) {
+function transform(input, f) {
   let output = {};
 
   // Since event listeners don't prevent `input` to be GC-ed we wanna presrve
   // it until `output` can be GC-ed. There for we add implicit reference which
   // is removed once `input` ends.
   refs(output).add(input);
 
   function next(data) emit(output, "data", data);
@@ -41,26 +41,26 @@ function transform(f, input) {
   });
   on(input, "data", function(data) f(data, next));
   return output;
 }
 
 // High order event transformation function that takes `input` event channel
 // and returns transformation containing only events on which `p` predicate
 // returns `true`.
-function filter(predicate, input) {
-  return transform(function(data, next) {
+function filter(input, predicate) {
+  return transform(input, function(data, next) {
     if (predicate(data)) next(data)
-  }, input);
+  });
 }
 exports.filter = filter;
 
 // High order function that takes `input` and returns input of it's values
 // mapped via given `f` function.
-function map(f, input) transform(function(data, next) next(f(data)), input)
+function map(input, f) transform(input, function(data, next) next(f(data)))
 exports.map = map;
 
 // High order function that takes `input` stream of streams and merges them
 // into single event stream. Like flatten but time based rather than order
 // based.
 function merge(inputs) {
   let output = {};
   let open = 1;
@@ -92,13 +92,13 @@ function merge(inputs) {
     on(inputs, "error", error);
     on(inputs, "data", forward);
   }
 
   return output;
 }
 exports.merge = merge;
 
-function expand(f, inputs) merge(map(f, inputs))
+function expand(inputs, f) merge(map(inputs, f))
 exports.expand = expand;
 
 function pipe(from, to) on(from, "*", emit.bind(emit, to))
 exports.pipe = pipe;
--- a/addon-sdk/source/lib/sdk/lang/functional.js
+++ b/addon-sdk/source/lib/sdk/lang/functional.js
@@ -8,16 +8,17 @@
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { 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) {
   return function method() {
@@ -50,32 +51,38 @@ exports.remit = defer;
  *    Arguments to invoke function with.
  * @param {Object} self
  *    Object to be passed as a `this` pseudo variable.
  */
 function invoke(callee, params, self) callee.apply(self, params);
 exports.invoke = invoke;
 
 /**
- * Curries a function with the arguments given.
+ * Takes a function and bind values to one or more arguments, returning a new
+ * function of smaller arity.
  *
  * @param {Function} fn
- *    The function to curry
+ *    The function to partial
  *
- * @returns The function curried
+ * @returns The new function with binded values
  */
-function curry(fn) {
+function partial(fn) {
   if (typeof fn !== "function")
     throw new TypeError(String(fn) + " is not a function");
 
   let args = Array.slice(arguments, 1);
 
   return function() fn.apply(this, args.concat(Array.slice(arguments)));
 }
-exports.curry = curry;
+exports.partial = partial;
+
+exports.curry = deprecateFunction(partial,
+  'curry is deprecated, ' +
+  'please use require("sdk/lang/functional").partial instead.'
+);
 
 /**
  * Returns the composition of a list of functions, where each function consumes
  * the return value of the function that follows. In math terms, composing the
  * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
  * @example
  *
  *   var greet = function(name) { return "hi: " + name; };
--- a/addon-sdk/source/lib/sdk/loader/cuddlefish.js
+++ b/addon-sdk/source/lib/sdk/loader/cuddlefish.js
@@ -58,19 +58,25 @@ function incompatibility(module) {
 
   let { engines } = metadata;
 
   if (engines === null || typeof(engines) !== "object")
     return new Error("Malformed engines' property in metadata");
 
   let applications = Object.keys(engines);
 
-  applications.forEach(xulappModule.is);
-
-  let versionRange = engines[xulappModule.name];
+  let versionRange;
+  applications.forEach(function(name) {
+    if (xulappModule.is(name)) {
+      versionRange = engines[name];
+      // Continue iteration. We want to ensure the module doesn't
+      // contain a typo in the applications' name or some unknown
+      // application - `is` function throws an exception in that case.
+    }
+  });
 
   if (typeof(versionRange) === "string") {
     if (xulappModule.satisfiesVersion(versionRange))
       return null;
 
     return new Error("Unsupported Application version: The module " + id +
             " currently supports only version " + versionRange + " of " +
             xulappModule.name + ".");
--- a/addon-sdk/source/lib/sdk/net/xhr.js
+++ b/addon-sdk/source/lib/sdk/net/xhr.js
@@ -1,12 +1,11 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci } = require("chrome");
 const memory = require('../deprecated/memory');
@@ -59,62 +58,57 @@ const DELEGATED_METHODS = ["abort", "get
                            "send", "sendAsBinary", "setRequestHeader",
                            "open"];
 
 var getRequestCount = exports.getRequestCount = function getRequestCount() {
   return requests.length;
 };
 
 var XMLHttpRequest = exports.XMLHttpRequest = function XMLHttpRequest() {
-  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+  let self = this;
+  let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
             .createInstance(Ci.nsIXMLHttpRequest);
   // For the sake of simplicity, don't tie this request to any UI.
   req.mozBackgroundRequest = true;
 
   memory.track(req, "XMLHttpRequest");
 
   this._req = req;
   this._orsc = null;
+  this._cleanup = this._cleanup.bind(this);
 
   requests.push(this);
 
-  var self = this;
-
-  this._boundCleanup = function _boundCleanup() {
-    self._cleanup();
-  };
-
-  TERMINATE_EVENTS.forEach(
-    function(name) {
-      self._req.addEventListener(name, self._boundCleanup, false);
-    });
+  TERMINATE_EVENTS.forEach(function(name) {
+    self._req.addEventListener(name, self._cleanup, false);
+  });
 };
 
 XMLHttpRequest.prototype = {
   _cleanup: function _cleanup() {
     this.onreadystatechange = null;
-    var index = requests.indexOf(this);
+
+    let index = requests.indexOf(this);
     if (index != -1) {
-      var self = this;
-      TERMINATE_EVENTS.forEach(
-        function(name) {
-          self._req.removeEventListener(name, self._boundCleanup, false);
-        });
+      let self = this;
+      TERMINATE_EVENTS.forEach(function(name) {
+        self._req.removeEventListener(name, self._cleanup, false);
+      });
       requests.splice(index, 1);
     }
   },
   _unload: function _unload() {
     this._req.abort();
     this._cleanup();
   },
-  addEventListener: function addEventListener() {
-    throw new Error("not implemented");
+  addEventListener: function addEventListener(name, func) {
+    this._req.addEventListener(name, func);
   },
-  removeEventListener: function removeEventListener() {
-    throw new Error("not implemented");
+  removeEventListener: function removeEventListener(name, func) {
+    this._req.removeEventListener(name, func);
   },
   set upload(newValue) {
     throw new Error("not implemented");
   },
   forceAllowThirdPartyCookie: function forceAllowThirdPartyCookie() {
     if (this._req.channel instanceof Ci.nsIHttpChannelInternal)
       this._req.channel.forceAllowThirdPartyCookie = true;
   },
@@ -123,22 +117,25 @@ XMLHttpRequest.prototype = {
   },
   set onreadystatechange(cb) {
     this._orsc = cb;
     if (cb) {
       var self = this;
       this._req.onreadystatechange = function() {
         try {
           self._orsc.apply(self, arguments);
-        } catch (e) {
+        }
+        catch (e) {
           console.exception(e);
         }
       };
-    } else
+    }
+    else {
       this._req.onreadystatechange = null;
+    }
   }
 };
 
 READ_ONLY_PROPS.forEach(
    function(name) {
      XMLHttpRequest.prototype.__defineGetter__(
        name,
        function() {
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -27,19 +27,16 @@ const { on, off, emit, setListeners } = 
 const { EventTarget } = require("./event/target");
 const domPanel = require("./panel/utils");
 const { events } = require("./panel/events");
 const systemEvents = require("./system/events");
 const { filter, pipe } = require("./event/utils");
 const { getNodeView, getActiveView } = require("./view/core");
 const { isNil, isObject } = require("./lang/type");
 
-if (isPrivateBrowsingSupported && isWindowPBSupported)
-  throw Error('The panel module cannot be used with per-window private browsing at the moment, see Bug 816257');
-
 let isArray = Array.isArray;
 let assetsURI = require("./self").data.url();
 
 function isAddonContent({ contentURL }) {
   return typeof(contentURL) === "string" && contentURL.indexOf(assetsURI) === 0;
 }
 
 function hasContentScript({ contentScript, contentScriptFile }) {
@@ -246,34 +243,34 @@ const Panel = Class({
     domPanel.resize(view, model.width, model.height);
 
     return this;
   }
 });
 exports.Panel = Panel;
 
 // Filter panel events to only panels that are create by this module.
-let panelEvents = filter(function({target}) panelFor(target), events);
+let panelEvents = filter(events, function({target}) panelFor(target));
 
 // Panel events emitted after panel has being shown.
-let shows = filter(function({type}) type === "sdk-panel-shown", panelEvents);
+let shows = filter(panelEvents, function({type}) type === "sdk-panel-shown");
 
 // Panel events emitted after panel became hidden.
-let hides = filter(function({type}) type === "sdk-panel-hidden", panelEvents);
+let hides = filter(panelEvents, function({type}) type === "sdk-panel-hidden");
 
 // Panel events emitted after content inside panel is ready. For different
 // panels ready may mean different state based on `contentScriptWhen` attribute.
 // Weather given event represents readyness is detected by `getAttachEventType`
 // helper function.
-let ready = filter(function({type, target})
-  getAttachEventType(modelFor(panelFor(target))) === type, panelEvents);
+let ready = filter(panelEvents, function({type, target})
+  getAttachEventType(modelFor(panelFor(target))) === type);
 
 // Panel events emitted after content document in the panel has changed.
-let change = filter(function({type}) type === "sdk-panel-content-changed",
-                    panelEvents);
+let change = filter(panelEvents, function({type})
+  type === "sdk-panel-content-changed");
 
 // Forward panel show / hide events to panel's own event listeners.
 on(shows, "data", function({target}) emit(panelFor(target), "show"));
 on(hides, "data", function({target}) emit(panelFor(target), "hide"));
 
 on(ready, "data", function({target}) {
   let worker = workerFor(panelFor(target));
   attach(worker, domPanel.getContentDocument(target).defaultView);
--- a/addon-sdk/source/lib/sdk/tab/events.js
+++ b/addon-sdk/source/lib/sdk/tab/events.js
@@ -34,23 +34,23 @@ function tabEventsFor(window) {
   // Map supported event types to a streams of those events on the given
   // `window` and than merge these streams into single form stream off
   // all events.
   let channels = TYPES.map(function(type) open(window, type));
   return merge(channels);
 }
 
 // Filter DOMContentLoaded events from all the browser events.
-let readyEvents = filter(function(e) e.type === "DOMContentLoaded", events);
+let readyEvents = filter(events, function(e) e.type === "DOMContentLoaded");
 // Map DOMContentLoaded events to it's target browser windows.
-let futureWindows = map(function(e) e.target, readyEvents);
+let futureWindows = map(readyEvents, function(e) e.target);
 // Expand all browsers that will become interactive to supported tab events
 // on these windows. Result will be a tab events from all tabs of all windows
 // that will become interactive.
-let eventsFromFuture = expand(tabEventsFor, futureWindows);
+let eventsFromFuture = expand(futureWindows, tabEventsFor);
 
 // Above covers only windows that will become interactive in a future, but some
 // windows may already be interactive so we pick those and expand to supported
 // tab events for them too.
 let interactiveWindows = windows("navigator:browser", { includePrivate: true }).
                          filter(isInteractive);
 let eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
 
--- a/addon-sdk/source/lib/sdk/test/harness.js
+++ b/addon-sdk/source/lib/sdk/test/harness.js
@@ -52,16 +52,19 @@ var findAndRunTests;
 
 // Combined information from all test runs.
 var results = {
   passed: 0,
   failed: 0,
   testRuns: []
 };
 
+// A list of the compartments and windows loaded after startup
+var startLeaks;
+
 // JSON serialization of last memory usage stats; we keep it stringified
 // so we don't actually change the memory usage stats (in terms of objects)
 // of the JSRuntime we're profiling.
 var lastMemoryUsage;
 
 function analyzeRawProfilingData(data) {
   var graph = data.graph;
   var shapes = {};
@@ -157,19 +160,42 @@ function reportMemoryUsage() {
                   for each (info in memory.getObjects())];
   weakrefs = [weakref for each (weakref in weakrefs) if (weakref)];
   print("Tracked memory objects in testing sandbox: " +
         weakrefs.length + "\n");
 }
 
 var gWeakrefInfo;
 
-function showResults() {
+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);
+      });
 
+      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 windowURLs)
+        console.warn("LEAKED", leaks.windows[url]);
+
+      showResults();
+    });
+  });
+}
+
+function showResults() {
   if (gWeakrefInfo) {
     gWeakrefInfo.forEach(
       function(info) {
         var ref = info.weakref.get();
         if (ref !== null) {
           var data = ref.__url__ ? ref.__url__ : ref;
           var warning = data == "[object Object]"
             ? "[object " + data.constructor.name + "(" +
@@ -222,17 +248,17 @@ function cleanup() {
 
     memory.gc();
   } catch (e) {
     results.failed++;
     console.error("unload.send() threw an exception.");
     console.exception(e);
   };
 
-  setTimeout(showResults, 1);
+  setTimeout(require('@test/options').checkMemory ? checkMemory : showResults, 1);
 
   // dump the coverobject
   if (Object.keys(coverObject).length){
     const self = require('self');
     const {pathFor} = require("sdk/system");
     let file = require('file');
     const {env} = require('sdk/system/environment');
     console.log("CWD:", env.PWD);
@@ -240,16 +266,133 @@ function cleanup() {
     console.log('coverstats:', out);
     let outfh = file.open(out,'w');
     outfh.write(JSON.stringify(coverObject,null,2));
     outfh.flush();
     outfh.close();
   }
 }
 
+function getPotentialLeaks() {
+  memory.gc();
+
+  // Things we can assume are part of the platform and so aren't leaks
+  let WHITELIST_BASE_URLS = [
+    "chrome://",
+    "resource:///",
+    "resource://app/",
+    "resource://gre/",
+    "resource://gre-resources/",
+    "resource://pdf.js/",
+    "resource://pdf.js.components/",
+    "resource://services-common/",
+    "resource://services-crypto/",
+    "resource://services-sync/"
+  ];
+
+  let ioService = Cc["@mozilla.org/network/io-service;1"].
+                 getService(Ci.nsIIOService);
+  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 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;
+
+    for (let whitelist of WHITELIST_BASE_URLS) {
+      if (item.location.substring(0, whitelist.length) == whitelist)
+        return false;
+    }
+
+    return true;
+  }
+
+  let compartments = {};
+  let windows = {};
+  function logReporter(process, path, kind, units, amount, description) {
+    let matches = compartmentRegexp.exec(path);
+    if (matches) {
+      if (matches[1] in compartments)
+        return;
+
+      let details = compartmentDetails.exec(matches[1]);
+      if (!details) {
+        console.error("Unable to parse compartment detail " + matches[1]);
+        return;
+      }
+ 
+      let item = {
+        path: matches[1],
+        principal: details[1],
+        location: details[2] ? details[2].replace("\\", "/", "g") : undefined,
+        source: details[3] ? details[3].split(" -> ").reverse() : undefined,
+        toString: function() this.location
+      };
+
+      if (!isPossibleLeak(item))
+        return;
+
+      compartments[matches[1]] = item;
+      return;
+    }
+
+    matches = windowRegexp.exec(path);
+    if (matches) {
+      if (matches[1] in windows)
+        return;
+
+      let details = windowDetails.exec(matches[1]);
+      if (!details) {
+        console.error("Unable to parse window detail " + matches[1]);
+        return;
+      }
+
+      let item = {
+        path: matches[1],
+        location: details[1].replace("\\", "/", "g"),
+        source: [details[1].replace("\\", "/", "g")],
+        toString: function() this.location
+      };
+
+      if (!isPossibleLeak(item))
+        return;
+
+      windows[matches[1]] = item;
+    }
+  }
+
+  let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
+            getService(Ci.nsIMemoryReporterManager);
+
+  let enm = mgr.enumerateReporters();
+  while (enm.hasMoreElements()) {
+    let reporter = enm.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    logReporter(reporter.process, reporter.path, reporter.kind, reporter.units,
+                reporter.amount, reporter.description);
+  }
+
+  let enm = mgr.enumerateMultiReporters();
+  while (enm.hasMoreElements()) {
+    let mr = enm.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
+    mr.collectReports(logReporter, null);
+  }
+
+  return { compartments: compartments, windows: windows };
+}
+
 function nextIteration(tests) {
   if (tests) {
     results.passed += tests.passed;
     results.failed += tests.failed;
 
     if (profileMemory)
       reportMemoryUsage();
 
@@ -435,16 +578,22 @@ var runTests = exports.runTests = functi
     else
       testConsole = new TestRunnerConsole(new PlainTextConsole(print), 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
+    require("../deprecated/unit-test");
+    require("../deprecated/unit-test-finder");
+    startLeaks = getPotentialLeaks();
+
     nextIteration();
   } catch (e) {
     let frames = fromException(e).reverse().reduce(function(frames, frame) {
       if (frame.fileName.split("/").pop() === "unit-test-finder.js")
         frames.done = true
       if (!frames.done) frames.push(frame)
 
       return frames
--- a/addon-sdk/source/lib/sdk/test/loader.js
+++ b/addon-sdk/source/lib/sdk/test/loader.js
@@ -1,40 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { Loader, resolveURI, Require,
         unload, override, descriptor  } = require('../loader/cuddlefish');
+const { ensure } = require('../system/unload');
 const addonWindow = require('../addon/window');
 const { PlainTextConsole } = require("sdk/console/plain-text");
 
 function CustomLoader(module, globals, packaging) {
   let options = packaging || require("@loader/options");
   options = override(options, {
     globals: override(require('../system/globals'), globals || {}),
     modules: override(options.modules || {}, {
       'sdk/addon/window': addonWindow
      })
   });
 
   let loader = Loader(options);
-  return Object.create(loader, descriptor({
+  let wrapper = Object.create(loader, descriptor({
     require: Require(loader, module),
     sandbox: function(id) {
       let requirement = loader.resolve(id, module.id);
       let uri = resolveURI(requirement, loader.mapping);
       return loader.sandboxes[uri];
     },
     unload: function(reason) {
       unload(loader, reason);
     }
   }));
+  ensure(wrapper);
+  return wrapper;
 };
 exports.Loader = CustomLoader;
 
 // Creates a custom loader instance whose console module is hooked in order
 // to avoid printing messages to the console, and instead, expose them in the
 // returned `messages` array attribute
 exports.LoaderWithHookedConsole = function (module, callback) {
   let messages = [];
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -223,16 +223,22 @@ parser_groups = (
                                   metavar=None,
                                   default=False,
                                   cmds=['test', 'testex', 'testpkgs'])),
         (("", "--override-version",), dict(dest="override_version",
                                   help="Pass in a version string to use in generated docs",
                                   metavar=None,
                                   default=False,
                                   cmds=['sdocs'])),
+        (("", "--check-memory",), dict(dest="check_memory",
+                                       help="attempts to detect leaked compartments after a test run",
+                                       action="store_true",
+                                       default=False,
+                                       cmds=['test', 'testpkgs', 'testaddons',
+                                             'testall'])),
         ]
      ),
 
     ("Internal Command-Specific Options", [
         (("", "--addons",), dict(dest="addons",
                                  help=("paths of addons to install, "
                                        "comma-separated"),
                                  metavar=None,
@@ -655,17 +661,17 @@ def run(arguments=sys.argv[1:], target_c
 
         target_cfg_json = os.path.join(options.pkgdir, 'package.json')
         target_cfg = packaging.get_config_in_dir(options.pkgdir)
 
     # At this point, we're either building an XPI or running Jetpack code in
     # a Mozilla application (which includes running tests).
 
     use_main = False
-    inherited_options = ['verbose', 'enable_e10s', 'parseable']
+    inherited_options = ['verbose', 'enable_e10s', 'parseable', 'check_memory']
     enforce_timeouts = False
 
     if command == "xpi":
         use_main = True
     elif command == "test":
         if 'tests' not in target_cfg:
             target_cfg['tests'] = []
         inherited_options.extend(['iterations', 'filter', 'profileMemory',
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-panel.js
@@ -1,16 +1,83 @@
 'use strict';
 
-const { isWindowPBSupported } = require('sdk/private-browsing/utils');
+const { open, focus, close } = require('sdk/window/helpers');
+const { isPrivate } = require('sdk/private-browsing');
+const { defer } = require('sdk/core/promise');
+
+const BROWSER = 'chrome://browser/content/browser.xul';
+
+exports.testRequirePanel = function(assert) {
+  require('panel');
+  assert.ok('the panel module should not throw an error');
+};
+
+exports.testShowPanelInPrivateWindow = function(assert, done) {
+  let panel = require('sdk/panel').Panel({
+    contentURL: "data:text/html;charset=utf-8,"
+  });
+
+  testShowPanel(assert, panel).
+    then(makeEmptyPrivateBrowserWindow).
+    then(focus).
+    then(function(window) {
+      assert.equal(isPrivate(window), true, 'opened window is private');
+      assert.pass('private window was focused');
+      return window;
+    }).
+    then(function(window) {
+      let { promise, resolve } = defer();
+
+      assert.ok(!panel.isShowing, 'the panel is not showing [1]');
 
-if (isWindowPBSupported) {
-  exports.testRequirePanel = function (assert) {
-    try {
-  	  require('panel');
+      panel.once('show', function() {
+        assert.ok(panel.isShowing, 'the panel is showing');
+
+        panel.once('hide', function() {
+          assert.ok(!panel.isShowing, 'the panel is not showing [2]');
+
+          resolve(window);
+        });
+
+        panel.hide();
+      });
+
+      panel.show();
+
+      return promise;
+    }).
+    then(close).
+    then(done, assert.fail.bind(assert));
+};
+
+
+function makeEmptyPrivateBrowserWindow(options) {
+  options = options || {};
+  return open(BROWSER, {
+    features: {
+      chrome: true,
+      toolbar: true,
+      private: true
     }
-    catch(e) {
-  	  assert.ok(e.message.match(/Bug 816257/), 'Bug 816257 is mentioned');
-      return;
-    }
-    assert.fail('the panel module should throw an error');
-  }
+  });
 }
+
+function testShowPanel(assert, panel) {
+  let { promise, resolve } = defer();
+
+  assert.ok(!panel.isShowing, 'the panel is not showing [1]');
+
+  panel.once('show', function() {
+    assert.ok(panel.isShowing, 'the panel is showing');
+
+    panel.once('hide', function() {
+      assert.ok(!panel.isShowing, 'the panel is not showing [2]');
+
+      resolve(null);
+    });
+
+    panel.hide();
+  })
+  panel.show();
+
+  return promise;
+}
--- a/addon-sdk/source/test/private-browsing/helper.js
+++ b/addon-sdk/source/test/private-browsing/helper.js
@@ -5,17 +5,17 @@
 
 const { Loader } = require('sdk/test/loader');
 
 const { loader } = LoaderWithHookedConsole(module);
 
 const pb = loader.require('sdk/private-browsing');
 const pbUtils = loader.require('sdk/private-browsing/utils');
 const xulApp = require("sdk/system/xul-app");
-const { openDialog, getMostRecentBrowserWindow } = require('sdk/window/utils');
+const { open: openWindow, getMostRecentBrowserWindow } = require('sdk/window/utils');
 const { openTab, getTabContentWindow, getActiveTab, setTabURL, closeTab } = require('sdk/tabs/utils');
 const promise = require("sdk/core/promise");
 const windowHelpers = require('sdk/window/helpers');
 const events = require("sdk/system/events");
 
 function LoaderWithHookedConsole(module) {
   let globals = {};
   let errors = [];
@@ -53,36 +53,40 @@ exports.pbUtils = pbUtils;
 exports.LoaderWithHookedConsole = LoaderWithHookedConsole;
 
 exports.openWebpage = function openWebpage(url, enablePrivate) {
   if (xulApp.is("Fennec")) {
     let chromeWindow = getMostRecentBrowserWindow();
     let rawTab = openTab(chromeWindow, url, {
       isPrivate: enablePrivate
     });
+
     return {
       ready: promise.resolve(getTabContentWindow(rawTab)),
       close: function () {
         closeTab(rawTab);
         // Returns a resolved promise as there is no need to wait
         return promise.resolve();
       }
     };
   }
   else {
-    let win = openDialog({
-      private: enablePrivate
+    let win = openWindow(null, {
+      features: {
+        private: enablePrivate
+      }
     });
     let deferred = promise.defer();
 
     // Wait for delayed startup code to be executed, in order to ensure
     // that the window is really ready
     events.on("browser-delayed-startup-finished", function onReady({subject}) {
       if (subject == win) {
-        events.off("browser-delayed-startup-finished", onReady, true);
+        events.off("browser-delayed-startup-finished", onReady);
+        deferred.resolve(win);
 
         let rawTab = getActiveTab(win);
         setTabURL(rawTab, url);
         deferred.resolve(getTabContentWindow(rawTab));
       }
     }, true);
 
     return {
--- a/addon-sdk/source/test/tabs/test-fennec-tabs.js
+++ b/addon-sdk/source/test/tabs/test-fennec-tabs.js
@@ -187,43 +187,16 @@ exports.testTabLocation = function(test)
   tabs.open({
     url: url1,
     onOpen: function(tab) {
       tab.url = url2;
     }
   });
 };
 
-// TEST: tab.reload()
-exports.testTabReload = function(test) {
-  test.waitUntilDone();
-
-  let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>";
-
-  tabs.open({
-    url: url,
-    onReady: function onReady(tab) {
-      tab.removeListener('ready', onReady);
-
-      tab.once(
-        'ready',
-        function onReload() {
-          test.pass("the tab was loaded again");
-          test.assertEqual(tab.url, url, "the tab has the same URL");
-
-          // end test
-          tab.close(function() test.done());
-        }
-      );
-
-      tab.reload();
-    }
-  });
-};
-
 // TEST: tab.move()
 exports.testTabMove = function(test) {
   test.waitUntilDone();
 
   let { loader, messages } = LoaderWithHookedConsole();
   let tabs = loader.require('sdk/tabs');
 
   let url = "data:text/html;charset=utf-8,testTabMove";
--- a/addon-sdk/source/test/tabs/test-firefox-tabs.js
+++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js
@@ -295,49 +295,16 @@ exports.testTabClose = function(test) {
       });
       test.assertNotEqual(tabs.activeTab.url, url, "tab is no longer the active tab");
     });
 
     tabs.open(url);
   });
 };
 
-// TEST: tab.reload()
-exports.testTabReload = function(test) {
-  test.waitUntilDone();
-  openBrowserWindow(function(window, browser) {
-    let tabs = require("sdk/tabs");
-    let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>";
-
-    tabs.open({ url: url, onReady: function onReady(tab) {
-      tab.removeListener("ready", onReady);
-
-      browser.addEventListener(
-        "load",
-        function onLoad() {
-          browser.removeEventListener("load", onLoad, true);
-
-          browser.addEventListener(
-            "load",
-            function onReload() {
-              browser.removeEventListener("load", onReload, true);
-              test.pass("the tab was loaded again");
-              test.assertEqual(tab.url, url, "the tab has the same URL");
-              closeBrowserWindow(window, function() test.done());
-            },
-            true
-          );
-          tab.reload();
-        },
-        true
-      );
-    }});
-  });
-};
-
 // TEST: tab.move()
 exports.testTabMove = function(test) {
   test.waitUntilDone();
   openBrowserWindow(function(window, browser) {
     let tabs = require("sdk/tabs");
     let url = "data:text/html;charset=utf-8,foo";
 
     tabs.open({
--- a/addon-sdk/source/test/test-context-menu.js
+++ b/addon-sdk/source/test/test-context-menu.js
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  'use strict';
 
 let { Cc, Ci } = require("chrome");
 
 const { Loader } = require('sdk/test/loader');
 const timer = require("sdk/timers");
+const { merge } = require("sdk/util/object");
 
 // These should match the same constants in the module.
 const ITEM_CLASS = "addon-context-menu-item";
 const SEPARATOR_CLASS = "addon-context-menu-separator";
 const OVERFLOW_THRESH_DEFAULT = 10;
 const OVERFLOW_THRESH_PREF =
   "extensions.addon-sdk.context-menu.overflowThreshold";
 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
@@ -1939,16 +1940,17 @@ exports.testParentMenu = function (test)
   test.assertEqual(menu.items[1], item, "Item should be in the sub menu");
 
   test.showMenu(null, function (popup) {
     test.checkMenu([menu], [], []);
     test.done();
   });
 };
 
+
 // Existing context menu modifications should apply to new windows.
 exports.testNewWindow = function (test) {
   test = new TestHelper(test);
   let loader = test.newLoader();
 
   let item = new loader.cm.Item({ label: "item" });
 
   test.withNewWindow(function () {
@@ -1976,16 +1978,83 @@ exports.testNewWindowMultipleModules = f
         test.checkMenu([item], [], [item]);
         test.done();
       });
     });
   });
 };
 
 
+// Existing context menu modifications should not apply to new private windows.
+exports.testNewPrivateWindow = function (test) {
+  test = new TestHelper(test);
+  let loader = test.newLoader();
+
+  let item = new loader.cm.Item({ label: "item" });
+
+  test.showMenu(null, function (popup) {
+    test.checkMenu([item], [], []);
+    popup.hidePopup();
+
+    test.withNewPrivateWindow(function () {
+      test.showMenu(null, function (popup) {
+        test.checkMenu([], [], []);
+        test.done();
+      });
+    });
+  });
+};
+
+
+// Existing context menu modifications should apply to new private windows when
+// private browsing support is enabled.
+exports.testNewPrivateEnabledWindow = function (test) {
+  test = new TestHelper(test);
+  let loader = test.newPrivateLoader();
+
+  let item = new loader.cm.Item({ label: "item" });
+
+  test.showMenu(null, function (popup) {
+    test.checkMenu([item], [], []);
+    popup.hidePopup();
+
+    test.withNewPrivateWindow(function () {
+      test.showMenu(null, function (popup) {
+        test.checkMenu([item], [], []);
+        test.done();
+      });
+    });
+  });
+};
+
+
+// Existing context menu modifications should apply to new private windows when
+// private browsing support is enabled unless unloaded.
+exports.testNewPrivateEnabledWindowUnloaded = function (test) {
+  test = new TestHelper(test);
+  let loader = test.newPrivateLoader();
+
+  let item = new loader.cm.Item({ label: "item" });
+
+  test.showMenu(null, function (popup) {
+    test.checkMenu([item], [], []);
+    popup.hidePopup();
+
+    loader.unload();
+
+    test.withNewPrivateWindow(function () {
+      test.showMenu(null, function (popup) {
+        test.checkMenu([], [], []);
+        test.done();
+      });
+    });
+  });
+};
+
+
 // Items in the context menu should be sorted according to locale.
 exports.testSorting = function (test) {
   test = new TestHelper(test);
   let loader = test.newLoader();
 
   // Make an unsorted items list.  It'll look like this:
   //   item 1, item 0, item 3, item 2, item 5, item 4, ...
   let items = [];
@@ -3334,16 +3403,47 @@ TestHelper.prototype = {
           throw new Error("Test error: tried to unload nonexistent loader");
         self.loaders.splice(idx, 1);
       }
     };
     this.loaders.push(wrapper);
     return wrapper;
   },
 
+  // As above but the loader has private-browsing support enabled.
+  newPrivateLoader: function() {
+    let base = require("@loader/options");
+
+    // Clone current loader's options adding the private-browsing permission
+    let options = merge({}, base, {
+      metadata: merge({}, base.metadata || {}, {
+        permissions: merge({}, base.metadata.permissions || {}, {
+          'private-browsing': true
+        })
+      })
+    });
+
+    const self = this;
+    let loader = Loader(module, null, options);
+    let wrapper = {
+      loader: loader,
+      cm: loader.require("sdk/context-menu"),
+      globalScope: loader.sandbox("sdk/context-menu"),
+      unload: function () {
+        loader.unload();
+        let idx = self.loaders.indexOf(wrapper);
+        if (idx < 0)
+          throw new Error("Test error: tried to unload nonexistent loader");
+        self.loaders.splice(idx, 1);
+      }
+    };
+    this.loaders.push(wrapper);
+    return wrapper;
+  },
+
   // Returns true if the count crosses the overflow threshold.
   shouldOverflow: function (count) {
     return count >
            (this.loaders.length ?
             this.loaders[0].loader.require("sdk/preferences/service").
               get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
             OVERFLOW_THRESH_DEFAULT);
   },
@@ -3400,16 +3500,25 @@ TestHelper.prototype = {
   // done() is called.
   withNewWindow: function (onloadCallback) {
     let win = this.browserWindow.OpenBrowserWindow();
     this.delayedEventListener(win, "load", onloadCallback, true);
     this.oldBrowserWindow = this.browserWindow;
     this.browserWindow = win;
   },
 
+  // Opens a new private browser window.  The window will be closed
+  // automatically when done() is called.
+  withNewPrivateWindow: function (onloadCallback) {
+    let win = this.browserWindow.OpenBrowserWindow({private: true});
+    this.delayedEventListener(win, "load", onloadCallback, true);
+    this.oldBrowserWindow = this.browserWindow;
+    this.browserWindow = win;
+  },
+
   // Opens a new tab with our test page in the current window.  The tab will
   // be closed automatically when done() is called.
   withTestDoc: function (onloadCallback) {
     this.oldSelectedTab = this.tabBrowser.selectedTab;
     this.tab = this.tabBrowser.addTab(TEST_DOC_URL);
     let browser = this.tabBrowser.getBrowserForTab(this.tab);
 
     this.delayedEventListener(browser, "load", function () {
--- a/addon-sdk/source/test/test-disposable.js
+++ b/addon-sdk/source/test/test-disposable.js
@@ -183,9 +183,43 @@ exports["test disposables that throw"] =
     let foo1 = Foo()
   }, /Boom/, "disposable constructors may throw");
 
   loader.unload();
 
   assert.equal(disposals, 0, "no disposal if constructor threw");
 }
 
+exports["test multiple destroy"] = function(assert) {
+  let loader = Loader(module);
+  let { Disposable } = loader.require("sdk/core/disposable");
+
+  let disposals = 0
+
+  let Foo = Class({
+    extends: Disposable,
+    dispose: function dispose() {
+      disposals = disposals + 1
+    }
+  })
+
+  let foo1 = Foo();
+  let foo2 = Foo();
+  let foo3 = Foo();
+
+  assert.equal(disposals, 0, "no disposals yet");
+
+  foo1.destroy();
+  assert.equal(disposals, 1, "disposed properly");
+  foo1.destroy();
+  assert.equal(disposals, 1, "didn't attempt to dispose twice");
+
+  foo2.destroy();
+  assert.equal(disposals, 2, "other instances still dispose fine");
+  foo2.destroy();
+  assert.equal(disposals, 2, "but not twice");
+
+  loader.unload();
+
+  assert.equal(disposals, 3, "unload only disposed the remaining instance");
+}
+
 require('test').run(exports);
--- a/addon-sdk/source/test/test-event-utils.js
+++ b/addon-sdk/source/test/test-event-utils.js
@@ -8,73 +8,73 @@ const { on, emit } = require("sdk/event/
 const { filter, map, merge, expand } = require("sdk/event/utils");
 const $ = require("./event/helpers");
 
 function isEven(x) !(x % 2)
 function inc(x) x + 1
 
 exports["test filter events"] = function(assert) {
   let input = {};
-  let evens = filter(isEven, input);
+  let evens = filter(input, isEven);
   let actual = [];
   on(evens, "data", function(e) actual.push(e));
 
   [1, 2, 3, 4, 5, 6, 7].forEach(function(x) emit(input, "data", x));
 
   assert.deepEqual(actual, [2, 4, 6], "only even numbers passed through");
 };
 
 exports["test filter emits"] = $.emits(function(input, assert) {
-  let output = filter(isEven, input);
+  let output = filter(input, isEven);
   assert(output,  [1, 2, 3, 4, 5], [2, 4], "this is `output` & evens passed");
 });;
 
 exports["test filter reg once"] = $.registerOnce(function(input, assert) {
-  assert(filter(isEven, input),  [1, 2, 3, 4, 5, 6], [2, 4, 6],
+  assert(filter(input, isEven),  [1, 2, 3, 4, 5, 6], [2, 4, 6],
          "listener can be registered only once");
 });
 
 exports["test filter ignores new"] = $.ignoreNew(function(input, assert) {
-  assert(filter(isEven, input), [1, 2, 3], [2],
+  assert(filter(input, isEven), [1, 2, 3], [2],
          "new listener is ignored")
 });
 
 exports["test filter is FIFO"] = $.FIFO(function(input, assert) {
-  assert(filter(isEven, input), [1, 2, 3, 4], [2, 4],
+  assert(filter(input, isEven), [1, 2, 3, 4], [2, 4],
          "listeners are invoked in fifo order")
 });
 
 exports["test map events"] = function(assert) {
   let input = {};
-  let incs = map(inc, input);
+  let incs = map(input, inc);
   let actual = [];
   on(incs, "data", function(e) actual.push(e));
 
   [1, 2, 3, 4].forEach(function(x) emit(input, "data", x));
 
   assert.deepEqual(actual, [2, 3, 4, 5], "all numbers were incremented");
 };
 
 exports["test map emits"] = $.emits(function(input, assert) {
-  let output = map(inc, input);
+  let output = map(input, inc);
   assert(output,  [1, 2, 3], [2, 3, 4], "this is `output` & evens passed");
 });;
 
 exports["test map reg once"] = $.registerOnce(function(input, assert) {
-  assert(map(inc, input),  [1, 2, 3], [2, 3, 4],
+  assert(map(input, inc),  [1, 2, 3], [2, 3, 4],
          "listener can be registered only once");
 });
 
 exports["test map ignores new"] = $.ignoreNew(function(input, assert) {
-  assert(map(inc, input), [1], [2],
+  assert(map(input, inc), [1], [2],
          "new listener is ignored")
 });
 
 exports["test map is FIFO"] = $.FIFO(function(input, assert) {
-  assert(map(inc, input), [1, 2, 3, 4], [2, 3, 4, 5],
+  assert(map(input, inc), [1, 2, 3, 4], [2, 3, 4, 5],
          "listeners are invoked in fifo order")
 });
 
 exports["test merge stream[stream]"] = function(assert) {
   let a = {}, b = {}, c = {};
   let inputs = {};
   let actual = [];
 
@@ -110,50 +110,50 @@ exports["test merge array[stream]"] = fu
   emit(b, "data", "b2");
   emit(a, "data", "a3");
 
   assert.deepEqual(actual, ["a1", "b1", "a2", "c1", "c2", "b2", "a3"],
                    "all inputs data merged into one");
 };
 
 exports["test merge emits"] = $.emits(function(input, assert) {
-  let evens = filter(isEven, input)
+  let evens = filter(input, isEven)
   let output = merge([evens, input]);
   assert(output, [1, 2, 3], [1, 2, 2, 3], "this is `output` & evens passed");
 });
 
 
 exports["test merge reg once"] = $.registerOnce(function(input, assert) {
-  let evens = filter(isEven, input)
+  let evens = filter(input, isEven)
   let output = merge([input, evens]);
   assert(output,  [1, 2, 3, 4], [1, 2, 2, 3, 4, 4],
          "listener can be registered only once");
 });
 
 exports["test merge ignores new"] = $.ignoreNew(function(input, assert) {
-  let evens = filter(isEven, input)
+  let evens = filter(input, isEven)
   let output = merge([input, evens])
   assert(output, [1], [1],
          "new listener is ignored")
 });
 
 exports["test marge is FIFO"] = $.FIFO(function(input, assert) {
-  let evens = filter(isEven, input)
+  let evens = filter(input, isEven)
   let output = merge([input, evens])
 
   assert(output, [1, 2, 3, 4], [1, 2, 2, 3, 4, 4],
          "listeners are invoked in fifo order")
 });
 
 exports["test expand"] = function(assert) {
   let a = {}, b = {}, c = {};
   let inputs = {};
   let actual = [];
 
-  on(expand(function($) $(), inputs), "data", function($) actual.push($))
+  on(expand(inputs, function($) $()), "data", function($) actual.push($))
 
   emit(inputs, "data", function() a);
   emit(a, "data", "a1");
   emit(inputs, "data", function() b);
   emit(b, "data", "b1");
   emit(a, "data", "a2");
   emit(inputs, "data", function() c);
   emit(c, "data", "c1");
--- a/addon-sdk/source/test/test-functional.js
+++ b/addon-sdk/source/test/test-functional.js
@@ -1,15 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+
 const { setTimeout } = require('sdk/timers');
 const utils = require('sdk/lang/functional');
-const { invoke, defer, curry, compose, memoize, once, delay, wrap } = utils;
+const { invoke, defer, partial, compose, memoize, once, delay, wrap } = utils;
+const { LoaderWithHookedConsole } = require('sdk/test/loader');
 
 exports['test forwardApply'] = function(assert) {
   function sum(b, c) this.a + b + c
   assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
                'passed arguments and pseoude-variable are used');
 
   assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
                'bounded `this` pseoudo variable is used');
@@ -24,27 +26,43 @@ exports['test deferred function'] = func
     done();
   }
 
   let fixture = { a: 1, method: defer(sum) }
   fixture.method(2, 3);
   nextTurn = true;
 };
 
+exports['test partial function'] = function(assert) {
+  function sum(b, c) this.a + b + c;
+
+  let foo = { a : 5 };
+
+  foo.sum7 = partial(sum, 7);
+  foo.sum8and4 = partial(sum, 8, 4);
+
+  assert.equal(foo.sum7(2), 14, 'partial one arguments works');
+
+  assert.equal(foo.sum8and4(), 17, 'partial both arguments works');
+};
+
 exports['test curry function'] = function(assert) {
+  let { loader, messages } = LoaderWithHookedConsole(module);
+  let { curry } = loader.require('sdk/lang/functional');
+
   function sum(b, c) this.a + b + c;
 
   let foo = { a : 5 };
 
   foo.sum7 = curry(sum, 7);
-  foo.sum8and4 = curry(sum, 8, 4);
 
-  assert.equal(foo.sum7(2), 14, 'curry one arguments works');
+  assert.equal(messages.length, 1, "only one error is dispatched");
+  assert.ok(messages[0].msg.indexOf('curry is deprecated') > -1);
 
-  assert.equal(foo.sum8and4(), 17, 'curry both arguments works');
+  loader.unload();
 };
 
 exports['test compose'] = function(assert) {
   let greet = function(name) { return 'hi: ' + name; };
   let exclaim = function(sentence) { return sentence + '!'; };
 
   assert.equal(compose(exclaim, greet)('moe'), 'hi: moe!',
                'can compose a function that takes another');
--- a/addon-sdk/source/test/test-page-mod.js
+++ b/addon-sdk/source/test/test-page-mod.js
@@ -1053,41 +1053,50 @@ exports.testEvents = function(test) {
       );
       done();
     }
   );
 };
 
 exports["test page-mod on private tab"] = function (test) {
   test.waitUntilDone();
+  let fail = test.fail.bind(test);
+
   let privateUri = "data:text/html;charset=utf-8," +
                    "<iframe src=\"data:text/html;charset=utf-8,frame\" />";
   let nonPrivateUri = "data:text/html;charset=utf-8,non-private";
 
   let pageMod = new PageMod({
     include: "data:*",
     onAttach: function(worker) {
       if (isTabPBSupported || isWindowPBSupported) {
         // When PB isn't supported, the page-mod will apply to all document
         // as all of them will be non-private
         test.assertEqual(worker.tab.url,
                          nonPrivateUri,
                          "page-mod should only attach to the non-private tab");
       }
+
       test.assert(!isPrivate(worker),
                   "The worker is really non-private");
       test.assert(!isPrivate(worker.tab),
                   "The document is really non-private");
       pageMod.destroy();
-      page1.close().then(page2.close).then(test.done.bind(test));
+
+      page1.close().
+        then(page2.close).
+        then(test.done.bind(test), fail);
     }
   });
 
-  let page1 = openWebpage(privateUri, true);
-  let page2 = openWebpage(nonPrivateUri, false);
+  let page1, page2;
+  page1 = openWebpage(privateUri, true);
+  page1.ready.then(function() {
+    page2 = openWebpage(nonPrivateUri, false);
+  }, fail);
 }
 
 exports["test page-mod on private tab in global pb"] = function (test) {
   test.waitUntilDone();
   if (!isGlobalPBSupported) {
     test.pass();
     return test.done();
   }
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -115,37 +115,40 @@ exports["test Show Hide Panel"] = functi
   });
 };
 
 exports["test Document Reload"] = function(assert, done) {
   const { Panel } = require('sdk/panel');
 
   let content =
     "<script>" +
-    "setTimeout(function () {" +
-    "  window.location = 'about:blank';" +
-    "}, 250);" +
+    "window.onload = function() {" +
+    "  setTimeout(function () {" +
+    "    window.location = 'about:blank';" +
+    "  }, 0);" +
+    "}" +
     "</script>";
   let messageCount = 0;
   let panel = Panel({
     // using URL here is intentional, see bug 859009
     contentURL: URL("data:text/html;charset=utf-8," + encodeURIComponent(content)),
     contentScript: "self.postMessage(window.location.href)",
     onMessage: function (message) {
       messageCount++;
       if (messageCount == 1) {
-        assert.ok(/data:text\/html/.test(message), "First document had a content script");
+        assert.ok(/data:text\/html/.test(message), "First document had a content script " + message);
       }
       else if (messageCount == 2) {
         assert.equal(message, "about:blank", "Second document too");
         panel.destroy();
         done();
       }
     }
   });
+  assert.pass('Panel was created');
 };
 
 exports["test Parent Resize Hack"] = function(assert, done) {
   const { Panel } = require('sdk/panel');
 
   let browserWindow = Cc["@mozilla.org/appshell/window-mediator;1"].
                       getService(Ci.nsIWindowMediator).
                       getMostRecentWindow("navigator:browser");
--- a/addon-sdk/source/test/test-tabs-common.js
+++ b/addon-sdk/source/test/test-tabs-common.js
@@ -376,8 +376,35 @@ exports.testImmediateClosing = function 
         setTimeout(function () {
           loader.unload();
           test.done();
         }, 0);
       });
     }
   });
 }
+
+// TEST: tab.reload()
+exports.testTabReload = function(test) {
+  test.waitUntilDone();
+
+  let url = "data:text/html;charset=utf-8,<!doctype%20html><title></title>";
+
+  tabs.open({
+    url: url,
+    onReady: function onReady(tab) {
+      tab.removeListener('ready', onReady);
+
+      tab.once(
+        'ready',
+        function onReload() {
+          test.pass("the tab was loaded again");
+          test.assertEqual(tab.url, url, "the tab has the same URL");
+
+          // end test
+          tab.close(function() test.done());
+        }
+      );
+
+      tab.reload();
+    }
+  });
+};
--- a/addon-sdk/source/test/test-window-utils.js
+++ b/addon-sdk/source/test/test-window-utils.js
@@ -203,40 +203,30 @@ exports['test window watcher unregs 4 lo
         done();
       }, false);
       myWindow.close();
     }, 0);
   }, false);
 }
 
 exports['test window watcher without untracker'] = function(assert, done) {
-  var myWindow;
-  var finished = false;
-
-  var delegate = {
+  let myWindow;
+  let wt = new windowUtils.WindowTracker({
     onTrack: function(window) {
       if (window == myWindow) {
         assert.pass("onTrack() called with our test window");
-        timer.setTimeout(function() {
-          myWindow.close();
 
-          if (!finished) {
-              finished = true;
-              myWindow = null;
-              wt.unload();
-              done();
-            } else {
-              assert.fail("onTrack() called multiple times.");
-            }
-        }, 1);
+        close(myWindow).then(function() {
+          wt.unload();
+          done();
+        }, assert.fail);
       }
     }
-  };
+  });
 
-  var wt = new windowUtils.WindowTracker(delegate);
   myWindow = makeEmptyWindow();
 };
 
 exports['test active window'] = function(assert, done) {
   let browserWindow = WM.getMostRecentWindow("navigator:browser");
   let continueAfterFocus = function(window) onFocus(window).then(nextTest);
 
   assert.equal(windowUtils.activeBrowserWindow, browserWindow,
--- a/addon-sdk/source/test/test-xhr.js
+++ b/addon-sdk/source/test/test-xhr.js
@@ -1,82 +1,92 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ 'use strict'
 
-var xhr = require("sdk/net/xhr");
-var timer = require("sdk/timers");
-var { Loader } = require("sdk/test/loader");
-var xulApp = require("sdk/system/xul-app");
+const xhr = require('sdk/net/xhr');
+const { Loader } = require('sdk/test/loader');
+const xulApp = require('sdk/system/xul-app');
 
+// TODO: rewrite test below
 /* Test is intentionally disabled until platform bug 707256 is fixed.
 exports.testAbortedXhr = function(test) {
   var req = new xhr.XMLHttpRequest();
   test.assertEqual(xhr.getRequestCount(), 1);
   req.abort();
   test.assertEqual(xhr.getRequestCount(), 0);
 };
 */
 
-exports.testLocalXhr = function(test) {
+exports.testLocalXhr = function(assert, done) {
   var req = new xhr.XMLHttpRequest();
-  req.overrideMimeType("text/plain");
-  req.open("GET", module.uri);
+  let ready = false;
+
+  req.overrideMimeType('text/plain');
+  req.open('GET', module.uri);
   req.onreadystatechange = function() {
     if (req.readyState == 4 && (req.status == 0 || req.status == 200)) {
-      test.assertMatches(req.responseText,
-                         /onreadystatechange/,
-                         "XMLHttpRequest should get local files");
-      timer.setTimeout(
-        function() { test.assertEqual(xhr.getRequestCount(), 0);
-                     test.done(); },
-        0
-      );
+      ready = true;
+      assert.ok(req.responseText.match(/onreadystatechange/i),
+                'XMLHttpRequest should get local files');
     }
   };
+  req.addEventListener('load', function onload() {
+    req.removeEventListener('load', onload);
+    assert.pass('addEventListener for load event worked');
+    assert.ok(ready, 'onreadystatechange listener worked');
+    assert.equal(xhr.getRequestCount(), 0, 'request count is 0');
+    done();
+  });
   req.send(null);
-  test.assertEqual(xhr.getRequestCount(), 1);
-  test.waitUntilDone(4000);
+
+  assert.equal(xhr.getRequestCount(), 1, 'request count is 1');
 };
 
-exports.testUnload = function(test) {
+exports.testUnload = function(assert) {
   var loader = Loader(module);
-  var sbxhr = loader.require("sdk/net/xhr");
+  var sbxhr = loader.require('sdk/net/xhr');
   var req = new sbxhr.XMLHttpRequest();
-  req.overrideMimeType("text/plain");
+
+  req.overrideMimeType('text/plain');
   req.open("GET", module.uri);
   req.send(null);
-  test.assertEqual(sbxhr.getRequestCount(), 1);
+
+  assert.equal(sbxhr.getRequestCount(), 1, 'request count is 1');
   loader.unload();
-  test.assertEqual(sbxhr.getRequestCount(), 0);
+  assert.equal(sbxhr.getRequestCount(), 0, 'request count is 0');
 };
 
-exports.testResponseHeaders = function(test) {
+exports.testResponseHeaders = function(assert, done) {
   var req = new xhr.XMLHttpRequest();
-  req.overrideMimeType("text/plain");
-  req.open("GET", module.uri);
+
+  req.overrideMimeType('text/plain');
+  req.open('GET', module.uri);
   req.onreadystatechange = function() {
     if (req.readyState == 4 && (req.status == 0 || req.status == 200)) {
       var headers = req.getAllResponseHeaders();
-      if (xulApp.versionInRange(xulApp.platformVersion, "13.0a1", "*")) {
+      if (xulApp.satisfiesVersion(xulApp.platformVersion, '>=13.0a1')) {
         headers = headers.split("\r\n");
-        if(headers.length == 1) {
+        if (headers.length == 1) {
           headers = headers[0].split("\n");
         }
-        for(let i in headers) {
-          if(headers[i] && headers[i].search("Content-Type") >= 0) {
-            test.assertEqual(headers[i], "Content-Type: text/plain",
-                             "XHR's headers are valid");
+        for (let i in headers) {
+          if (headers[i] && headers[i].search('Content-Type') >= 0) {
+            assert.equal(headers[i], 'Content-Type: text/plain',
+                         'XHR\'s headers are valid');
           }
         }
       }
       else {
-        test.assert(headers === null || headers === "",
-                    "XHR's headers are empty");
+        assert.ok(headers === null || headers === '',
+                  'XHR\'s headers are empty');
       }
-      test.done();
+
+      done();
     }
   };
   req.send(null);
-  test.assertEqual(xhr.getRequestCount(), 1);
-  test.waitUntilDone(4000);
+
+  assert.equal(xhr.getRequestCount(), 1, 'request count is 1');
 }
 
+require('test').run(exports);
--- a/addon-sdk/source/test/windows/test-firefox-windows.js
+++ b/addon-sdk/source/test/windows/test-firefox-windows.js
@@ -291,62 +291,67 @@ exports.testTrackWindows = function(test
   let actions = [];
 
   let expects = [
     "activate 0", "global activate 0", "deactivate 0", "global deactivate 0",
     "activate 1", "global activate 1", "deactivate 1", "global deactivate 1",
     "activate 2", "global activate 2"
   ];
 
-  function shutdown(window) {
-    if (this.length === 1) {
-      test.assertEqual(actions.join(), expects.join(),
-        "correct activate and deactivate sequence")
-
-      test.done();
-    }
-  }
-
   function openWindow() {
     windows.push(browserWindows.open({
       url: "data:text/html;charset=utf-8,<i>testTrackWindows</i>",
 
       onActivate: function(window) {
         let index = windows.indexOf(window);
 
+        test.assertEqual(actions.join(), expects.slice(0, index*4).join(), expects[index*4]);
         actions.push("activate " + index);
 
-        if (windows.length < 3)
+        if (windows.length < 3) {
           openWindow()
-        else
-          for each (let win in windows)
-            win.close(shutdown)
+        }
+        else {
+          let count = windows.length;
+          for each (let win in windows) {
+            win.close(function() {
+              if (--count == 0) {
+                test.done();
+              }
+            });
+          }
+        }
       },
 
       onDeactivate: function(window) {
         let index = windows.indexOf(window);
 
+        test.assertEqual(actions.join(), expects.slice(0, index*4 + 2).join(), expects[index*4 + 2]);
         actions.push("deactivate " + index)
       }
     }));
   }
 
   browserWindows.on("activate", function (window) {
     let index = windows.indexOf(window);
     // only concerned with windows opened for this test
     if (index < 0)
       return;
+
+    test.assertEqual(actions.join(), expects.slice(0, index*4 + 1).join(), expects[index*4 + 1]);
     actions.push("global activate " + index)
   })
 
   browserWindows.on("deactivate", function (window) {
     let index = windows.indexOf(window);
     // only concerned with windows opened for this test
     if (index < 0)
       return;
+
+    test.assertEqual(actions.join(), expects.slice(0, index*4 + 3).join(), expects[index*4 + 3]);
     actions.push("global deactivate " + index)
   })
 
   openWindow();
 }
 
 // test that it is not possible to open a private window by default
 exports.testWindowOpenPrivateDefault = function(test) {
@@ -356,19 +361,17 @@ exports.testWindowOpenPrivateDefault = f
     url: 'about:mozilla',
     isPrivate: true,
     onOpen: function(window) {
       let tab = window.tabs[0];
       tab.once('ready', function() {
         test.assertEqual(tab.url, 'about:mozilla', 'opened correct tab');
         test.assertEqual(isPrivate(tab), false, 'tab is not private');
 
-        window.close(function() {
-          test.done();
-        });
+        window.close(test.done.bind(test));
       });
     }
   });
 }
 
 // test that it is not possible to find a private window in
 // windows module's iterator
 exports.testWindowIteratorPrivateDefault = function(test) {