Bug 982379 - Uplift Add-on SDK to Firefox
☠☠ backed out by 34941dd46be6 ☠ ☠
authorErik Vold <evold@mozilla.com>
Tue, 11 Mar 2014 20:43:42 -0700
changeset 191366 bd0463063293e9e2fdfcea2dce80bc849316af0f
parent 191365 9fe5610363b3f8e35f248bb2ce726c8e2665a8a1
child 191367 31f9a148b33cb6f6eb509df29a339b5de4f65254
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs982379
milestone30.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 982379 - Uplift Add-on SDK to Firefox
addon-sdk/source/lib/sdk/input/customizable-ui.js
addon-sdk/source/lib/sdk/input/system.js
addon-sdk/source/lib/sdk/panel.js
addon-sdk/source/lib/sdk/panel/utils.js
addon-sdk/source/lib/sdk/system.js
addon-sdk/source/lib/sdk/test/runner.js
addon-sdk/source/lib/sdk/ui/button/action.js
addon-sdk/source/lib/sdk/ui/button/toggle.js
addon-sdk/source/lib/sdk/ui/button/view.js
addon-sdk/source/lib/sdk/ui/frame/model.js
addon-sdk/source/lib/sdk/ui/state.js
addon-sdk/source/lib/sdk/ui/toolbar/view.js
addon-sdk/source/lib/sdk/widget.js
addon-sdk/source/lib/sdk/window/utils.js
addon-sdk/source/python-lib/cuddlefish/__init__.py
addon-sdk/source/python-lib/cuddlefish/runner.py
addon-sdk/source/test/addons/addon-page/main.js
addon-sdk/source/test/addons/child_process/index.js
addon-sdk/source/test/addons/places/main.js
addon-sdk/source/test/addons/simple-prefs-l10n/main.js
addon-sdk/source/test/test-functional.js
addon-sdk/source/test/test-ui-frame.js
addon-sdk/source/test/test-ui-toggle-button.js
addon-sdk/source/test/test-ui-toolbar.js
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/input/customizable-ui.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cu } = require("chrome");
+
+// Because Firefox Holly, we still need to check if `CustomizableUI` is
+// available. Once Australis will officially land, we can safely remove it.
+// See Bug 959142
+try {
+  Cu.import("resource:///modules/CustomizableUI.jsm", {});
+}
+catch (e) {
+  throw Error("Unsupported Application: The module"  + module.id +
+              " does not support this application.");
+}
+
+const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
+const { receive } = require("../event/utils");
+const { InputPort } = require("./system");
+const { object} = require("../util/sequence");
+const { getOuterId } = require("../window/utils");
+
+const Input = function() {};
+Input.prototype = Object.create(InputPort.prototype);
+
+Input.prototype.onCustomizeStart = function (window) {
+  receive(this, object([getOuterId(window), true]));
+}
+
+Input.prototype.onCustomizeEnd = function (window) {
+  receive(this, object([getOuterId(window), null]));
+}
+
+Input.prototype.addListener = input => CustomizableUI.addListener(input);
+
+Input.prototype.removeListener = input => CustomizableUI.removeListener(input);
+
+exports.CustomizationInput = Input;
--- a/addon-sdk/source/lib/sdk/input/system.js
+++ b/addon-sdk/source/lib/sdk/input/system.js
@@ -43,33 +43,39 @@ const InputPort = function InputPort({id
 
 // InputPort type implements `Input` signal interface.
 InputPort.prototype = new Input();
 InputPort.prototype.constructor = InputPort;
 
 // When port is started (which is when it's subgraph get's
 // first subscriber) actual observer is registered.
 InputPort.start = input => {
-  addObserver(input, input.topic, false);
+  input.addListener(input);
   // Also register add-on unload observer to end this signal
   // when that happens.
   addObserver(input, addonUnloadTopic, false);
 };
 InputPort.prototype[start] = InputPort.start;
 
+InputPort.addListener = input => addObserver(input, input.topic, false);
+InputPort.prototype.addListener = InputPort.addListener;
+
 // When port is stopped (which is when it's subgraph has no
 // no subcribers left) an actual observer unregistered.
 // Note that port stopped once it ends as well (which is when
 // add-on is unloaded).
 InputPort.stop = input => {
-  removeObserver(input, input.topic);
+  input.removeListener(input);
   removeObserver(input, addonUnloadTopic);
 };
 InputPort.prototype[stop] = InputPort.stop;
 
+InputPort.removeListener = input => removeObserver(input, input.topic);
+InputPort.prototype.removeListener = InputPort.removeListener;
+
 // `InputPort` also implements `nsIObserver` interface and
 // `nsISupportsWeakReference` interfaces as it's going to be used as such.
 InputPort.prototype.QueryInterface = function(iid) {
   if (!iid.equals(Ci.nsIObserver) && !iid.equals(Ci.nsISupportsWeakReference))
     throw Cr.NS_ERROR_NO_INTERFACE;
 
   return this;
 };
--- a/addon-sdk/source/lib/sdk/panel.js
+++ b/addon-sdk/source/lib/sdk/panel.js
@@ -8,17 +8,16 @@
 module.metadata = {
   "stability": "stable",
   "engines": {
     "Firefox": "*"
   }
 };
 
 const { Ci } = require("chrome");
-const { validateOptions: valid } = require('./deprecated/api-utils');
 const { setTimeout } = require('./timers');
 const { isPrivateBrowsingSupported } = require('./self');
 const { isWindowPBSupported } = require('./private-browsing/utils');
 const { Class } = require("./core/heritage");
 const { merge } = require("./util/object");
 const { WorkerHost, detach, attach, destroy } = require("./content/utils");
 const { Worker } = require("./content/worker");
 const { Disposable } = require("./core/disposable");
@@ -26,39 +25,46 @@ const { contract: loaderContract } = req
 const { contract } = require("./util/contract");
 const { on, off, emit, setListeners } = require("./event/core");
 const { EventTarget } = require("./event/target");
 const domPanel = require("./panel/utils");
 const { events } = require("./panel/events");
 const systemEvents = require("./system/events");
 const { filter, pipe, stripListeners } = require("./event/utils");
 const { getNodeView, getActiveView } = require("./view/core");
-const { isNil, isObject } = require("./lang/type");
+const { isNil, isObject, isNumber } = require("./lang/type");
 const { getAttachEventType } = require("./content/utils");
+const { number, boolean, object } = require('./deprecated/api-utils');
 
-let number = { is: ['number', 'undefined', 'null'] };
-let boolean = { is: ['boolean', 'undefined', 'null'] };
+let isRect = ({top, right, bottom, left}) => [top, right, bottom, left].
+  some(value => isNumber(value) && !isNaN(value));
+
+let isSDKObj = obj => obj instanceof Class;
 
 let rectContract = contract({
   top: number,
   right: number,
   bottom: number,
   left: number
 });
 
-let rect = {
-  is: ['object', 'undefined', 'null'],
-  map: function(v) isNil(v) || !isObject(v) ? v : rectContract(v)
+let position = {
+  is: object,
+  map: v => (isNil(v) || isSDKObj(v) || !isObject(v)) ? v : rectContract(v),
+  ok: v => isNil(v) || isSDKObj(v) || (isObject(v) && isRect(v)),
+  msg: 'The option "position" must be a SDK object registered as anchor; ' +
+        'or an object with one or more of the following keys set to numeric ' +
+        'values: top, right, bottom, left.'
 }
 
 let displayContract = contract({
   width: number,
   height: number,
   focus: boolean,
-  position: rect
+  position: position
 });
 
 let panelContract = contract(merge({}, displayContract.rules, loaderContract.rules));
 
 
 function isDisposed(panel) !views.has(panel);
 
 let panels = new WeakMap();
@@ -171,32 +177,32 @@ const Panel = Class({
     // reatached once panel content is ready.
     detach(workerFor(this));
   },
 
   /* Public API: Panel.isShowing */
   get isShowing() !isDisposed(this) && domPanel.isOpen(viewFor(this)),
 
   /* Public API: Panel.show */
-  show: function show(options, anchor) {
+  show: function show(options={}, anchor) {
     if (options instanceof Ci.nsIDOMElement) {
       [anchor, options] = [options, null];
     }
 
     if (anchor instanceof Ci.nsIDOMElement) {
       console.warn(
         "Passing a DOM node to Panel.show() method is an unsupported " +
         "feature that will be soon replaced. " +
         "See: https://bugzilla.mozilla.org/show_bug.cgi?id=878877"
       );
     }
 
     let model = modelFor(this);
     let view = viewFor(this);
-    let anchorView = getNodeView(anchor);
+    let anchorView = getNodeView(anchor || options.position);
 
     options = merge({
       position: model.position,
       width: model.width,
       height: model.height,
       defaultWidth: model.defaultWidth,
       defaultHeight: model.defaultHeight,
       focus: model.focus
@@ -234,31 +240,32 @@ const Panel = Class({
   }
 });
 exports.Panel = Panel;
 
 // Note must be defined only after value to `Panel` is assigned.
 getActiveView.define(Panel, viewFor);
 
 // Filter panel events to only panels that are create by this module.
-let panelEvents = filter(events, function({target}) panelFor(target));
+let panelEvents = filter(events, ({target}) => panelFor(target));
 
 // Panel events emitted after panel has being shown.
-let shows = filter(panelEvents, function({type}) type === "popupshown");
+let shows = filter(panelEvents, ({type}) => type === "popupshown");
 
 // Panel events emitted after panel became hidden.
-let hides = filter(panelEvents, function({type}) type === "popuphidden");
+let hides = filter(panelEvents, ({type}) => type === "popuphidden");
 
 // Panel events emitted after content inside panel is ready. For different
 // panels ready may mean different state based on `contentScriptWhen` attribute.
 // Weather given event represents readyness is detected by `getAttachEventType`
 // helper function.
-let ready = filter(panelEvents, function({type, target})
+let ready = filter(panelEvents, ({type, target}) =>
   getAttachEventType(modelFor(panelFor(target))) === type);
 
 // 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(shows, "data", ({target}) => emit(panelFor(target), "show"));
+
+on(hides, "data", ({target}) => emit(panelFor(target), "hide"));
 
 on(ready, "data", function({target}) {
   let worker = workerFor(panelFor(target));
   attach(worker, domPanel.getContentDocument(target).defaultView);
 });
--- a/addon-sdk/source/lib/sdk/panel/utils.js
+++ b/addon-sdk/source/lib/sdk/panel/utils.js
@@ -18,16 +18,18 @@ const { create: createFrame, swapFrameLo
 const { window: addonWindow } = require("../addon/window");
 const { isNil } = require("../lang/type");
 const events = require("../system/events");
 
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function calculateRegion({ position, width, height, defaultWidth, defaultHeight }, rect) {
+  position = position || {};
+
   let x, y;
 
   let hasTop = !isNil(position.top);
   let hasRight = !isNil(position.right);
   let hasBottom = !isNil(position.bottom);
   let hasLeft = !isNil(position.left);
   let hasWidth = !isNil(width);
   let hasHeight = !isNil(height);
@@ -122,24 +124,42 @@ function display(panel, options, anchor)
     // in order to, be positioned properly
     panel.style.margin = "0";
 
     let viewportRect = document.defaultView.gBrowser.getBoundingClientRect();
 
     ({x, y, width, height}) = calculateRegion(options, viewportRect);
   }
   else {
+    // The XUL Panel has an arrow, so the margin needs to be reset
+    // to the default value.
+    panel.style.margin = "";
+    let { CustomizableUI, window } = anchor.ownerDocument.defaultView;
+
+    // In Australis, widgets may be positioned in an overflow panel or the
+    // menu panel.
+    // In such cases clicking this widget will hide the overflow/menu panel,
+    // and the widget's panel will show instead.
+    if (CustomizableUI) {
+      let node = anchor;
+      ({anchor}) = CustomizableUI.getWidget(anchor.id).forWindow(window);
+
+      // if `node` is not the `anchor` itself, it means the widget is
+      // positioned in a panel, therefore we have to hide it before show
+      // the widget's panel in the same anchor
+      if (node !== anchor)
+        CustomizableUI.hidePanelForNode(anchor);
+    }
+
     width = width || defaultWidth;
     height = height || defaultHeight;
 
     // Open the popup by the anchor.
     let rect = anchor.getBoundingClientRect();
 
-    let window = anchor.ownerDocument.defaultView;
-
     let zoom = getScreenPixelsPerCSSPixel(window);
     let screenX = rect.left + window.mozInnerScreenX * zoom;
     let screenY = rect.top + window.mozInnerScreenY * zoom;
 
     // Set up the vertical position of the popup relative to the anchor
     // (always display the arrow on anchor center)
     let horizontal, vertical;
     if (screenY > window.screen.availHeight / 2 + height)
--- a/addon-sdk/source/lib/sdk/system.js
+++ b/addon-sdk/source/lib/sdk/system.js
@@ -7,17 +7,16 @@
 module.metadata = {
   "stability": "unstable"
 };
 
 const { Cc, Ci, CC } = require('chrome');
 const options = require('@loader/options');
 const file = require('./io/file');
 const runtime = require("./system/runtime");
-var cfxArgs = require("@test/options");
 
 const appStartup = Cc['@mozilla.org/toolkit/app-startup;1'].
                    getService(Ci.nsIAppStartup);
 const appInfo = Cc["@mozilla.org/xre/app-info;1"].
                 getService(Ci.nsIXULAppInfo);
 const directoryService = Cc['@mozilla.org/file/directory_service;1'].
                          getService(Ci.nsIProperties);
 
@@ -65,23 +64,22 @@ exports.exit = function exit(code) {
   // This is used by 'cfx' to find out exit code.
   if ('resultFile' in options && options.resultFile) {
     let mode = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
     let stream = openFile(options.resultFile, mode);
     let status = code ? 'FAIL' : 'OK';
     stream.write(status, status.length);
     stream.flush();
     stream.close();
-    if (cfxArgs.parseable) {
-      console.log('wrote to resultFile');
-    }
   }
 
-  forcedExit = true;
-  appStartup.quit(E_FORCE);
+  if (code == 0) {
+    forcedExit = true;
+  }
+  appStartup.quit(code ? E_ATTEMPT : E_FORCE);
 };
 
 // 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;
 
--- a/addon-sdk/source/lib/sdk/test/runner.js
+++ b/addon-sdk/source/lib/sdk/test/runner.js
@@ -2,39 +2,31 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 module.metadata = {
   "stability": "experimental"
 };
 
-var { setTimeout } = require("../timers");
 var { exit, stdout } = require("../system");
 var cfxArgs = require("@test/options");
 
 function runTests(findAndRunTests) {
   var harness = require("./harness");
 
   function onDone(tests) {
     stdout.write("\n");
     var total = tests.passed + tests.failed;
     stdout.write(tests.passed + " of " + total + " tests passed.\n");
 
     if (tests.failed == 0) {
-      if (tests.passed === 0) {
+      if (tests.passed === 0)
         stdout.write("No tests were run\n");
-      }
-
-      setTimeout(function() {
-        if (cfxArgs.parseable) {
-          console.log('calling exit(0)');
-        }
-        exit(0);
-      }, 0);
+      exit(0);
     } else {
       if (cfxArgs.verbose || cfxArgs.parseable)
         printFailedTests(tests, stdout.write);
       exit(1);
     }
   };
 
   // We may have to run test on next cycle, otherwise XPCOM components
--- a/addon-sdk/source/lib/sdk/ui/button/action.js
+++ b/addon-sdk/source/lib/sdk/ui/button/action.js
@@ -21,16 +21,17 @@ catch (e) {
               ' does not support this application.');
 }
 
 const { Class } = require('../../core/heritage');
 const { merge } = require('../../util/object');
 const { Disposable } = require('../../core/disposable');
 const { on, off, emit, setListeners } = require('../../event/core');
 const { EventTarget } = require('../../event/target');
+const { getNodeView } = require('../../view/core');
 
 const view = require('./view');
 const { buttonContract, stateContract } = require('./contract');
 const { properties, render, state, register, unregister,
         getDerivedStateFor } = require('../state');
 const { events: stateEvents } = require('../state/events');
 const { events: viewEvents } = require('./view/events');
 const events = require('../../event/utils');
@@ -84,16 +85,20 @@ const ActionButton = Class({
   get id() this.state().id,
 
   click: function click() { view.click(toWidgetId(this.id)) }
 });
 exports.ActionButton = ActionButton;
 
 identify.define(ActionButton, ({id}) => toWidgetId(id));
 
+getNodeView.define(ActionButton, button =>
+  view.nodeFor(toWidgetId(button.id))
+);
+
 let actionButtonStateEvents = events.filter(stateEvents,
   e => e.target instanceof ActionButton);
 
 let actionButtonViewEvents = events.filter(viewEvents,
   e => buttons.has(e.target));
 
 let clickEvents = events.filter(actionButtonViewEvents, e => e.type === 'click');
 let updateEvents = events.filter(actionButtonViewEvents, e => e.type === 'update');
--- a/addon-sdk/source/lib/sdk/ui/button/toggle.js
+++ b/addon-sdk/source/lib/sdk/ui/button/toggle.js
@@ -21,16 +21,17 @@ catch (e) {
               ' does not support this application.');
 }
 
 const { Class } = require('../../core/heritage');
 const { merge } = require('../../util/object');
 const { Disposable } = require('../../core/disposable');
 const { on, off, emit, setListeners } = require('../../event/core');
 const { EventTarget } = require('../../event/target');
+const { getNodeView } = require('../../view/core');
 
 const view = require('./view');
 const { toggleButtonContract, toggleStateContract } = require('./contract');
 const { properties, render, state, register, unregister,
   setStateFor, getStateFor, getDerivedStateFor } = require('../state');
 const { events: stateEvents } = require('../state/events');
 const { events: viewEvents } = require('./view/events');
 const events = require('../../event/utils');
@@ -85,16 +86,20 @@ const ToggleButton = Class({
   get id() this.state().id,
 
   click: function click() view.click(toWidgetId(this.id))
 });
 exports.ToggleButton = ToggleButton;
 
 identify.define(ToggleButton, ({id}) => toWidgetId(id));
 
+getNodeView.define(ToggleButton, button =>
+  view.nodeFor(toWidgetId(button.id))
+);
+
 let toggleButtonStateEvents = events.filter(stateEvents,
   e => e.target instanceof ToggleButton);
 
 let toggleButtonViewEvents = events.filter(viewEvents,
   e => buttons.has(e.target));
 
 let clickEvents = events.filter(toggleButtonViewEvents, e => e.type === 'click');
 let updateEvents = events.filter(toggleButtonViewEvents, e => e.type === 'update');
--- a/addon-sdk/source/lib/sdk/ui/button/view.js
+++ b/addon-sdk/source/lib/sdk/ui/button/view.js
@@ -103,16 +103,21 @@ function getImage(icon, isInToolbar, pix
   }
 
   if (image.indexOf('./') === 0)
     return data.url(image.substr(2));
 
   return image;
 }
 
+function nodeFor(id, window=getMostRecentBrowserWindow()) {
+  return customizedWindows.has(window) ? null : getNode(id, window);
+};
+exports.nodeFor = nodeFor;
+
 function create(options) {
   let { id, label, icon, type } = options;
 
   if (views.has(id))
     throw new Error('The ID "' + id + '" seems already used.');
 
   CustomizableUI.createWidget({
     id: id,
@@ -178,41 +183,40 @@ function setIcon(id, window, icon) {
     let image = getImage(icon, isInToolbar(id), window.devicePixelRatio);
 
     node.setAttribute('image', image);
   }
 }
 exports.setIcon = setIcon;
 
 function setLabel(id, window, label) {
-  let node = customizedWindows.has(window) ? null : getNode(id, window);
+  let node = nodeFor(id, window);
 
   if (node) {
     node.setAttribute('label', label);
     node.setAttribute('tooltiptext', label);
   }
 }
 exports.setLabel = setLabel;
 
 function setDisabled(id, window, disabled) {
-  let node = customizedWindows.has(window) ? null : getNode(id, window);
+  let node = nodeFor(id, window);
 
   if (node)
     node.disabled = disabled;
 }
 exports.setDisabled = setDisabled;
 
 function setChecked(id, window, checked) {
-  let node = customizedWindows.has(window) ? null : getNode(id, window);
+  let node = nodeFor(id, window);
 
   if (node)
     node.checked = checked;
 }
 exports.setChecked = setChecked;
 
 function click(id) {
-  let window = getMostRecentBrowserWindow();
-  let node = customizedWindows.has(window) ? null : getNode(id, window);
+  let node = nodeFor(id);
 
   if (node)
     node.click();
 }
 exports.click = click;
--- a/addon-sdk/source/lib/sdk/ui/frame/model.js
+++ b/addon-sdk/source/lib/sdk/ui/frame/model.js
@@ -27,27 +27,27 @@ const { id: addonID, data: { url: resolv
 const { Frames } = require("../../input/frame");
 
 
 const output = new OutputPort({ id: "frame-change" });
 const mailbox = new OutputPort({ id: "frame-mailbox" });
 const input = Frames;
 
 
-const urlToId = url =>
+const makeID = url =>
   ("frame-" + addonID + "-" + url).
     split("/").join("-").
     split(".").join("-").
     replace(/[^A-Za-z0-9_\-]/g, "");
 
 const validate = contract({
-  id: {
+  name: {
     is: ["string", "undefined"],
     ok: x => /^[a-z][a-z0-9-_]+$/i.test(x),
-    msg: "The `option.id` must be a valid alphanumeric string (hyphens and " +
+    msg: "The `option.name` must be a valid alphanumeric string (hyphens and " +
          "underscores are allowed) starting with letter."
   },
   url: {
     map: x => x.toString(),
     is: ["string"],
     ok: x => isLocalURL(x),
     msg: "The `options.url` must be a valid local URI."
   }
@@ -83,17 +83,17 @@ const Message = function({type, data, so
 const frames = new Map();
 const sources = new Map();
 
 const Frame = Class({
   extends: EventTarget,
   implements: [Disposable, Source],
   initialize: function(params={}) {
     const options = validate(params);
-    const id = options.id || urlToId(options.url);
+    const id = makeID(options.name || options.url);
 
     if (frames.has(id))
       throw Error("Frame with this id already exists: " + id);
 
     const initial = { id: id, url: resolve(options.url) };
     this.id = id;
 
     setListeners(this, params);
--- a/addon-sdk/source/lib/sdk/ui/state.js
+++ b/addon-sdk/source/lib/sdk/ui/state.js
@@ -14,17 +14,17 @@ module.metadata = {
 
 const { Ci } = require('chrome');
 
 const events = require('../event/utils');
 const { events: browserEvents } = require('../browser/events');
 const { events: tabEvents } = require('../tab/events');
 const { events: stateEvents } = require('./state/events');
 
-const { windows, isInteractive, getMostRecentBrowserWindow } = require('../window/utils');
+const { windows, isInteractive, getFocusedBrowser } = require('../window/utils');
 const { getActiveTab, getOwnerWindow } = require('../tabs/utils');
 
 const { ignoreWindow } = require('../private-browsing/utils');
 
 const { freeze } = Object;
 const { merge } = require('../util/object');
 const { on, off, emit } = require('../event/core');
 
@@ -42,17 +42,17 @@ const ERR_INVALID_TARGET = 'The state ca
   'Only window, tab and registered component are valid targets.';
 
 const isWindow = thing => thing instanceof Ci.nsIDOMWindow;
 const isTab = thing => thing.tagName && thing.tagName.toLowerCase() === 'tab';
 const isActiveTab = thing => isTab(thing) && thing === getActiveTab(getOwnerWindow(thing));
 const isEnumerable = window => !ignoreWindow(window);
 const browsers = _ =>
   windows('navigator:browser', { includePrivate: true }).filter(isInteractive);
-const getMostRecentTab = _ => getActiveTab(getMostRecentBrowserWindow());
+const getMostRecentTab = _ => getActiveTab(getFocusedBrowser());
 
 function getStateFor(component, target) {
   if (!isRegistered(component))
     throw new Error(ERR_UNREGISTERED);
 
   if (!components.has(component))
     return null;
 
@@ -167,17 +167,17 @@ function properties(contract) {
 
   return Object.create(Object.prototype, descriptor);
 }
 exports.properties = properties;
 
 function state(contract) {
   return {
     state: function state(target, state) {
-      let nativeTarget = target === 'window' ? getMostRecentBrowserWindow()
+      let nativeTarget = target === 'window' ? getFocusedBrowser()
                           : target === 'tab' ? getMostRecentTab()
                           : viewFor(target);
 
       if (!nativeTarget && target !== this && !isNil(target))
         throw new Error('target not allowed.');
 
       target = nativeTarget || target;
 
--- a/addon-sdk/source/lib/sdk/ui/toolbar/view.js
+++ b/addon-sdk/source/lib/sdk/ui/toolbar/view.js
@@ -11,21 +11,23 @@ module.metadata = {
 };
 
 const { Cu } = require("chrome");
 const { CustomizableUI } = Cu.import('resource:///modules/CustomizableUI.jsm', {});
 const { subscribe, send, Reactor, foldp, lift, merges } = require("../../event/utils");
 const { InputPort } = require("../../input/system");
 const { OutputPort } = require("../../output/system");
 const { Interactive } = require("../../input/browser");
+const { CustomizationInput } = require("../../input/customizable-ui");
 const { pairs, map, isEmpty, object,
         each, keys, values } = require("../../util/sequence");
 const { curry, flip } = require("../../lang/functional");
 const { patch, diff } = require("diffpatcher/index");
 const prefs = require("../../preferences/service");
+const { getByOuterId } = require("../../window/utils");
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const PREF_ROOT = "extensions.sdk-toolbar-collapsed.";
 
 
 // There are two output ports one for publishing changes that occured
 // and the other for change requests. Later is synchronous and is only
 // consumed here. Note: it needs to be synchronous to avoid race conditions
@@ -33,18 +35,19 @@ const PREF_ROOT = "extensions.sdk-toolba
 // toolbar is destroyed between the ticks.
 const output = new OutputPort({ id: "toolbar-changed" });
 const syncoutput = new OutputPort({ id: "toolbar-change", sync: true });
 
 // Merge disptached changes and recevied changes from models to keep state up to
 // date.
 const Toolbars = foldp(patch, {}, merges([new InputPort({ id: "toolbar-changed" }),
                                           new InputPort({ id: "toolbar-change" })]));
-const State = lift((toolbars, windows) => ({windows: windows, toolbars: toolbars}),
-                   Toolbars, Interactive);
+const State = lift((toolbars, windows, customizable) =>
+  ({windows: windows, toolbars: toolbars, customizable: customizable}),
+  Toolbars, Interactive, new CustomizationInput());
 
 // Shared event handler that makes `event.target.parent` collapsed.
 // Used as toolbar's close buttons click handler.
 const collapseToolbar = event => {
   const toolbar = event.target.parentNode;
   toolbar.collapsed = true;
 };
 
@@ -83,77 +86,111 @@ const attributesChanged = mutations => {
 // it back. In addition it set's up a listener and observer to communicate
 // state changes.
 const addView = curry((options, {document}) => {
   let view = document.createElementNS(XUL_NS, "toolbar");
   view.setAttribute("id", options.id);
   view.setAttribute("collapsed", options.collapsed);
   view.setAttribute("toolbarname", options.title);
   view.setAttribute("pack", "end");
-  view.setAttribute("defaultset", options.items.join(","));
-  view.setAttribute("customizable", true);
-  view.setAttribute("style", "max-height: 40px;");
+  view.setAttribute("customizable", "false");
+  view.setAttribute("style", "padding: 2px 0; max-height: 40px;");
   view.setAttribute("mode", "icons");
   view.setAttribute("iconsize", "small");
   view.setAttribute("context", "toolbar-context-menu");
+  view.setAttribute("class", "toolbar-primary chromeclass-toolbar");
+
+  let label = document.createElementNS(XUL_NS, "label");
+  label.setAttribute("value", options.title);
+  label.setAttribute("collapsed", "true");
+  view.appendChild(label);
 
   let closeButton = document.createElementNS(XUL_NS, "toolbarbutton");
   closeButton.setAttribute("id", "close-" + options.id);
   closeButton.setAttribute("class", "close-icon");
   closeButton.setAttribute("customizable", false);
   closeButton.addEventListener("command", collapseToolbar);
 
   view.appendChild(closeButton);
 
+  // In order to have a close button not costumizable, aligned on the right,
+  // leaving the customizable capabilities of Australis, we need to create
+  // a toolbar inside a toolbar.
+  // This is should be a temporary hack, we should have a proper XBL for toolbar
+  // instead. See:
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=982005
+  let toolbar = document.createElementNS(XUL_NS, "toolbar");
+  toolbar.setAttribute("id", "inner-" + options.id);
+  toolbar.setAttribute("defaultset", options.items.join(","));
+  toolbar.setAttribute("customizable", "true");
+  toolbar.setAttribute("style", "-moz-appearance: none; overflow: hidden");
+  toolbar.setAttribute("mode", "icons");
+  toolbar.setAttribute("iconsize", "small");
+  toolbar.setAttribute("context", "toolbar-context-menu");
+  toolbar.setAttribute("flex", "1");
+
+  view.insertBefore(toolbar, closeButton);
+
   const observer = new document.defaultView.MutationObserver(attributesChanged);
   observer.observe(view, { attributes: true,
                            attributeFilter: ["collapsed", "toolbarname"] });
 
   const toolbox = document.getElementById("navigator-toolbox");
   toolbox.appendChild(view);
 });
 const viewAdd = curry(flip(addView));
 
 const removeView = curry((id, {document}) => {
   const view = document.getElementById(id);
   if (view) view.remove();
 });
 
-const updateView = curry((id, {title, collapsed}, {document}) => {
+const updateView = curry((id, {title, collapsed, isCustomizing}, {document}) => {
   const view = document.getElementById(id);
-  if (view && title)
+
+  if (!view)
+    return;
+
+  if (title)
     view.setAttribute("toolbarname", title);
-  if (view && collapsed !== void(0))
+
+  if (collapsed !== void(0))
     view.setAttribute("collapsed", Boolean(collapsed));
+
+  if (isCustomizing !== void(0)) {
+    view.querySelector("label").collapsed = !isCustomizing;
+    view.querySelector("toolbar").style.visibility = isCustomizing
+                                                ? "hidden" : "visible";
+  }
 });
 
-
+const viewUpdate = curry(flip(updateView));
 
 // Utility function used to register toolbar into CustomizableUI.
 const registerToolbar = state => {
   // If it's first additon register toolbar as customizableUI component.
-  CustomizableUI.registerArea(state.id, {
+  CustomizableUI.registerArea("inner-" + state.id, {
     type: CustomizableUI.TYPE_TOOLBAR,
     legacy: true,
-    defaultPlacements: [...state.items, "close-" + state.id]
+    defaultPlacements: [...state.items]
   });
 };
 // Utility function used to unregister toolbar from the CustomizableUI.
 const unregisterToolbar = CustomizableUI.unregisterArea;
 
 const reactor = new Reactor({
   onStep: (present, past) => {
     const delta = diff(past, present);
 
     each(([id, update]) => {
       // If update is `null` toolbar is removed, in such case
       // we unregister toolbar and remove it from each window
       // it was added to.
       if (update === null) {
-        unregisterToolbar(id);
+        unregisterToolbar("inner-" + id);
         each(removeView(id), values(past.windows));
 
         send(output, object([id, null]));
       }
       else if (past.toolbars[id]) {
         // If `collapsed` state for toolbar was updated, persist
         // it for a future sessions.
         if (update.collapsed !== void(0))
@@ -185,17 +222,23 @@ const reactor = new Reactor({
       }
     }, pairs(delta.toolbars));
 
     // Add views to every window that was added.
     each(window => {
       if (window)
         each(viewAdd(window), values(past.toolbars));
     }, values(delta.windows));
+
+    each(([id, isCustomizing]) => {
+      each(viewUpdate(getByOuterId(id), {isCustomizing: !!isCustomizing}),
+        keys(present.toolbars));
+
+    }, pairs(delta.customizable))
   },
   onEnd: state => {
     each(id => {
-      unregisterToolbar(id);
+      unregisterToolbar("inner-" + id);
       each(removeView(id), values(state.windows));
     }, keys(state.toolbars));
   }
 });
 reactor.run(State);
--- a/addon-sdk/source/lib/sdk/widget.js
+++ b/addon-sdk/source/lib/sdk/widget.js
@@ -439,38 +439,20 @@ const WidgetViewTrait = LightTrait.compo
     if ("click" == type || type.indexOf("mouse") == 0)
       this._baseWidget._onEvent(type, this._public);
     else
       this._baseWidget._onEvent(type, eventData);
 
     // Special case for click events: if the widget doesn't have a click
     // handler, but it does have a panel, display the panel.
     if ("click" == type && !this._listeners("click").length && this.panel) {
-      // In Australis, widgets may be positioned in an overflow panel or the
-      // menu panel.
-      // In such cases clicking this widget will hide the overflow/menu panel,
-      // and the widget's panel will show instead.
-
-      let anchor = domNode;
-      let { CustomizableUI, window } = domNode.ownerDocument.defaultView;
-
-      if (CustomizableUI) {
-        ({anchor}) = CustomizableUI.getWidget(domNode.id).forWindow(window);
-
-        // if `anchor` is not the `domNode` itself, it means the widget is
-        // positioned in a panel, therefore we have to hide it before show
-        // the widget's panel in the same anchor
-        if (anchor !== domNode)
-          CustomizableUI.hidePanelForNode(domNode);
-      }
-
       // This kind of ugly workaround, instead we should implement
       // `getNodeView` for the `Widget` class itself, but that's kind of
       // hard without cleaning things up.
-      this.panel.show(null, getNodeView.implement({}, () => anchor));
+      this.panel.show(null, getNodeView.implement({}, () => domNode));
     }
   },
 
   _isInWindow: function WidgetView__isInWindow(window) {
     return windowsAPI.BrowserWindow({
       window: this._chrome.window
     }) == window;
   },
@@ -790,38 +772,49 @@ WidgetChrome.prototype._createNode = fun
 
   node.style.minWidth = this._widget.width + "px";
 
   this.node = node;
 }
 
 // Initial population of a widget's content.
 WidgetChrome.prototype.fill = function WC_fill() {
+  let { node, _doc: document } = this;
+
   // Create element
-  var iframe = this._doc.createElement("iframe");
+  let iframe = document.createElement("iframe");
   iframe.setAttribute("type", "content");
   iframe.setAttribute("transparent", "transparent");
   iframe.style.overflow = "hidden";
   iframe.style.height = "16px";
   iframe.style.maxHeight = "16px";
   iframe.style.width = this._widget.width + "px";
   iframe.setAttribute("flex", "1");
   iframe.style.border = "none";
   iframe.style.padding = "0px";
 
   // Do this early, because things like contentWindow are null
   // until the node is attached to a document.
-  this.node.appendChild(iframe);
+  node.appendChild(iframe);
 
-  var label = this._doc.createElement("label");
+  let label = document.createElement("label");
   label.setAttribute("value", this._widget.label);
   label.className = "toolbarbutton-text";
   label.setAttribute("crop", "right");
   label.setAttribute("flex", "1");
-  this.node.appendChild(label);
+  node.appendChild(label);
+
+  // This toolbarbutton is created to provide a more consistent user experience
+  // during customization, see:
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=959640
+  let button = document.createElement("toolbarbutton");
+  button.setAttribute("label", this._widget.label);
+  button.setAttribute("crop", "right");
+  button.className = "toolbarbutton-1 chromeclass-toolbar-additional";
+  node.appendChild(button);
 
   // add event handlers
   this.addEventHandlers();
 
   // set content
   this.setContent();
 }
 
--- a/addon-sdk/source/lib/sdk/window/utils.js
+++ b/addon-sdk/source/lib/sdk/window/utils.js
@@ -14,16 +14,18 @@ const { defer } = require('sdk/core/prom
 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 FM = Cc["@mozilla.org/focus-manager;1"].
+              getService(Ci.nsIFocusManager);
 
 const XUL_NS = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
 
 const BROWSER = 'navigator:browser',
       URI_BROWSER = 'chrome://browser/content/browser.xul',
       NAME = '_blank',
       FEATURES = 'chrome,all,dialog=no,non-private';
 
@@ -352,16 +354,28 @@ exports.isXULBrowser = isXULBrowser;
 function getFocusedWindow() {
   let window = WM.getMostRecentWindow(BROWSER);
 
   return window ? window.document.commandDispatcher.focusedWindow : null;
 }
 exports.getFocusedWindow = getFocusedWindow;
 
 /**
+ * Returns the focused browser window if any, or the most recent one.
+ * Opening new window, updates most recent window, but focus window
+ * changes later; so most recent window and focused window are not always
+ * the same.
+ */
+function getFocusedBrowser() {
+  let window = FM.activeWindow;
+  return isBrowser(window) ? window : getMostRecentBrowserWindow()
+}
+exports.getFocusedBrowser = getFocusedBrowser;
+
+/**
  * Returns the focused element in the most recent focused window
  */
 function getFocusedElement() {
   let window = WM.getMostRecentWindow(BROWSER);
 
   return window ? window.document.commandDispatcher.focusedElement : null;
 }
 exports.getFocusedElement = getFocusedElement;
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -538,33 +538,29 @@ def initializer(env_root, args, out=sys.
             print >>out, '*', args[1], 'package directory created'
         except OSError:
             print >>out, '*', args[1], 'already exists, testing if directory is empty'
     # avoid clobbering existing files, but we tolerate things like .git
     existing = [fn for fn in os.listdir(path) if not fn.startswith(".")]
     if existing:
         print >>err, 'This command must be run in an empty directory.'
         return {"result":1}
-    for d in ['lib','data','test','doc']:
+    for d in ['lib','data','test']:
         os.mkdir(os.path.join(path,d))
         print >>out, '*', d, 'directory created'
-    open(os.path.join(path,'README.md'),'w').write('')
-    print >>out, '* README.md written'
     jid = create_jid()
     print >>out, '* generated jID automatically:', jid
     open(os.path.join(path,'package.json'),'w').write(PACKAGE_JSON % {'name':addon.lower(),
                                                    'title':addon,
                                                    'id':jid })
     print >>out, '* package.json written'
     open(os.path.join(path,'test','test-main.js'),'w').write(TEST_MAIN_JS)
     print >>out, '* test/test-main.js written'
     open(os.path.join(path,'lib','main.js'),'w').write('')
     print >>out, '* lib/main.js written'
-    open(os.path.join(path,'doc','main.md'),'w').write('')
-    print >>out, '* doc/main.md written'
     if len(args) == 1:
         print >>out, '\nYour sample add-on is now ready.'
         print >>out, 'Do "cfx test" to test it and "cfx run" to try it.  Have fun!'
     else:
         print >>out, '\nYour sample add-on is now ready in the \'' + args[1] +  '\' directory.'
         print >>out, 'Change to that directory, then do "cfx test" to test it, \nand "cfx run" to try it.  Have fun!'
     return {"result":0, "jid":jid}
 
--- a/addon-sdk/source/python-lib/cuddlefish/runner.py
+++ b/addon-sdk/source/python-lib/cuddlefish/runner.py
@@ -731,17 +731,16 @@ def run_app(harness_root_dir, manifest_r
                         sys.stderr.flush()
                         if is_running_tests and parseable:
                             match = PARSEABLE_TEST_NAME.search(new_chars)
                             if match:
                                 test_name = match.group(1)
             if os.path.exists(resultfile):
                 result = open(resultfile).read()
                 if result:
-                    sys.stderr.write("resultfile contained " + "'" + result + "'\n")
                     if result in ['OK', 'FAIL']:
                         done = True
                     else:
                         sys.stderr.write("Hrm, resultfile (%s) contained something weird (%d bytes)\n" % (resultfile, len(result)))
                         sys.stderr.write("'"+result+"'\n")
             if enforce_timeouts:
                 if time.time() - last_output_time > OUTPUT_TIMEOUT:
                     raise Timeout("Test output exceeded timeout (%ds)." %
@@ -750,19 +749,17 @@ def run_app(harness_root_dir, manifest_r
                     raise Timeout("Test run exceeded timeout (%ds)." %
                                   RUN_TIMEOUT, test_name, parseable)
     except:
         runner.stop()
         raise
     else:
         runner.wait(10)
     finally:
-        sys.stderr.write("Done.\n")
         outf.close()
-        sys.stderr.write("Clean the profile.\n")
         if profile:
             profile.cleanup()
 
     print >>sys.stderr, "Total time: %f seconds" % (time.time() - starttime)
 
     if result == 'OK':
         print >>sys.stderr, "Program terminated successfully."
         return 0
--- a/addon-sdk/source/test/addons/addon-page/main.js
+++ b/addon-sdk/source/test/addons/addon-page/main.js
@@ -1,25 +1,19 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 'use strict';
 
-module.metadata = {
-  engines: {
-    'Firefox': '*'
-  }
-};
-
 const { isTabOpen, activateTab, openTab,
         closeTab, getTabURL, getWindowHoldingTab } = require('sdk/tabs/utils');
 const windows = require('sdk/deprecated/window-utils');
 const { LoaderWithHookedConsole } = require('sdk/test/loader');
 const { setTimeout } = require('sdk/timers');
-const { is } = require('sdk/system/xul-app');
+const app = require("sdk/system/xul-app");
 const tabs = require('sdk/tabs');
 const isAustralis = "gCustomizeMode" in windows.activeBrowserWindow;
 const { set: setPref } = require("sdk/preferences/service");
 const { defer } = require('sdk/core/promise');
 
 const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
 
 let uri = require('sdk/self').data.url('index.html');
@@ -39,16 +33,20 @@ function closeTabPromise(tab) {
   return promise;
 }
 
 function isChromeVisible(window) {
   let x = window.document.documentElement.getAttribute('disablechrome')
   return x !== 'true';
 }
 
+// Once Bug 903018 is resolved, just move the application testing to
+// module.metadata.engines
+if (app.is('Firefox')) {
+
 exports['test add-on page deprecation message'] = function(assert) {
   let { loader, messages } = LoaderWithHookedConsole(module);
   loader.require('sdk/addon-page');
   setPref(DEPRECATE_PREF, true);
 
   assert.equal(messages.length, 1, "only one error is dispatched");
   assert.equal(messages[0].type, "error", "the console message is an error");
 
@@ -69,17 +67,17 @@ exports['test that add-on page has no ch
 
   assert.ok(isChromeVisible(window), 'chrome is visible for non addon page');
 
   // need to do this in another turn to make sure event listener
   // that sets property has time to do that.
   setTimeout(function() {
     activateTab(tab);
 
-    assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
+    assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTabPromise(tab).then(function() {
       assert.ok(isChromeVisible(window), 'chrome is visible again');
       loader.unload();
       assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
       done();
     }).then(null, assert.fail);
@@ -95,17 +93,17 @@ exports['test that add-on page with hash
 
   assert.ok(isChromeVisible(window), 'chrome is visible for non addon page');
 
   // need to do this in another turn to make sure event listener
   // that sets property has time to do that.
   setTimeout(function() {
     activateTab(tab);
 
-    assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
+    assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTabPromise(tab).then(function() {
       assert.ok(isChromeVisible(window), 'chrome is visible again');
       loader.unload();
       assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
       done();
     }).then(null, assert.fail);
@@ -121,17 +119,17 @@ exports['test that add-on page with quer
 
   assert.ok(isChromeVisible(window), 'chrome is visible for non addon page');
 
   // need to do this in another turn to make sure event listener
   // that sets property has time to do that.
   setTimeout(function() {
     activateTab(tab);
 
-    assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
+    assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTabPromise(tab).then(function() {
       assert.ok(isChromeVisible(window), 'chrome is visible again');
       loader.unload();
       assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
       done();
     }).then(null, assert.fail);
@@ -147,17 +145,17 @@ exports['test that add-on page with hash
 
   assert.ok(isChromeVisible(window), 'chrome is visible for non addon page');
 
   // need to do this in another turn to make sure event listener
   // that sets property has time to do that.
   setTimeout(function() {
     activateTab(tab);
 
-    assert.equal(isChromeVisible(window), is('Fennec') || isAustralis,
+    assert.equal(isChromeVisible(window), app.is('Fennec') || isAustralis,
       'chrome is not visible for addon page');
 
     closeTabPromise(tab).then(function() {
       assert.ok(isChromeVisible(window), 'chrome is visible again');
       loader.unload();
       assert.ok(!isTabOpen(tab), 'add-on page tab is closed on unload');
       done();
     }).then(null, assert.fail);
@@ -180,9 +178,13 @@ exports['test that malformed uri is not 
 
     closeTabPromise(tab).then(function() {
       loader.unload();
       done();
     }).then(null, assert.fail);
   });
 };
 
+} else {
+  exports['test unsupported'] = (assert) => assert.pass('This application is unsupported.');
+}
+
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/child_process/index.js
+++ b/addon-sdk/source/test/addons/child_process/index.js
@@ -1,32 +1,39 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { exec } = require("sdk/system/child_process");
-const { platform, pathFor } = require("sdk/system");
-const PROFILE_DIR = pathFor("ProfD");
-const isWindows = platform.toLowerCase().indexOf("win") === 0;
-
 /**
  * Ensures using child_process and underlying subprocess.jsm
  * works within an addon
  */
-exports["test child_process in an addon"] = (assert, done) => {
-  exec(isWindows ? "DIR /A-D" : "ls -al", {
-    cwd: PROFILE_DIR
-  }, (err, stdout, stderr) => {
-    assert.ok(!err, "no errors");
-    assert.equal(stderr, "", "stderr is empty");
-    assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files");
+
+const { exec } = require("sdk/system/child_process");
+const { platform, pathFor } = require("sdk/system");
+const PROFILE_DIR = pathFor("ProfD");
+const isWindows = platform.toLowerCase().indexOf("win") === 0;
+const app = require("sdk/system/xul-app");
 
-    if (isWindows)
-      assert.ok(!/<DIR>/.test(stdout), "passing args works");
-    else
-      assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works");
-    done();
-  });
-};
+// Once Bug 903018 is resolved, just move the application testing to
+// module.metadata.engines
+if (app.is("Firefox")) {
+  exports["test child_process in an addon"] = (assert, done) => {
+    exec(isWindows ? "DIR /A-D" : "ls -al", {
+      cwd: PROFILE_DIR
+    }, (err, stdout, stderr) => {
+      assert.ok(!err, "no errors");
+      assert.equal(stderr, "", "stderr is empty");
+      assert.ok(/extensions\.ini/.test(stdout), "stdout output of `ls -al` finds files");
 
+      if (isWindows)
+        assert.ok(!/<DIR>/.test(stdout), "passing args works");
+      else
+        assert.ok(/d(r[-|w][-|x]){3}/.test(stdout), "passing args works");
+      done();
+    });
+  };
+} else {
+  exports["test unsupported"] = (assert) => assert.pass("This application is unsupported.");
+}
 require("sdk/test/runner").runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/places/main.js
+++ b/addon-sdk/source/test/addons/places/main.js
@@ -1,24 +1,27 @@
 /* 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 = {
-  'engines': {
-    'Firefox': '*'
-  }
-};
-
 const { safeMerge: merge } = require('sdk/util/object');
+const app = require("sdk/system/xul-app");
 
-merge(module.exports,
-  require('./tests/test-places-bookmarks'),
-  require('./tests/test-places-events'),
-  require('./tests/test-places-favicon'),
-  require('./tests/test-places-history'),
-  require('./tests/test-places-host'),
-  require('./tests/test-places-utils')
-);
+// Once Bug 903018 is resolved, just move the application testing to
+// module.metadata.engines
+if (app.is('Firefox')) {
+  merge(module.exports,
+    require('./tests/test-places-bookmarks'),
+    require('./tests/test-places-events'),
+    require('./tests/test-places-favicon'),
+    require('./tests/test-places-history'),
+    require('./tests/test-places-host'),
+    require('./tests/test-places-utils')
+  );
+} else {
+  exports['test unsupported'] = (assert) => {
+    assert.pass('This application is unsupported.');
+  };
+}
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/addons/simple-prefs-l10n/main.js
+++ b/addon-sdk/source/test/addons/simple-prefs-l10n/main.js
@@ -7,51 +7,59 @@ 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 { preferencesBranch } = require('sdk/self');
 
 const { AddonManager } = Cu.import('resource://gre/modules/AddonManager.jsm', {});
 
-exports.testAOMLocalization = function(assert, done) {
-    tabs.open({
-    	url: 'about:addons',
-    	onReady: function(tab) {
-        tab.attach({
-          contentScriptWhen: 'end',
-        	contentScript: 'function onLoad() {\n' +
-                           'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
-                           '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[data-jetpack-id=\'' + self.id + '\']"))\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' +
-                               'title: ele.getAttribute("title")\n' +
+// Once Bug 903018 is resolved, just move the application testing to
+// module.metadata.engines
+//
+// This should work in Fennec, needs to be refactored to work, via bug 979645
+if (app.is('Firefox')) {
+  exports.testAOMLocalization = function(assert, done) {
+      tabs.open({
+        url: 'about:addons',
+        onReady: function(tab) {
+          tab.attach({
+            contentScriptWhen: 'end',
+            contentScript: 'function onLoad() {\n' +
+                             'unsafeWindow.removeEventListener("load", onLoad, false);\n' +
+                             '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[data-jetpack-id=\'' + self.id + '\']"))\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' +
+                                 'title: ele.getAttribute("title")\n' +
+                               '}\n' +
                              '}\n' +
                            '}\n' +
-                         '}\n' +
-                         // Wait for the load event ?
-                         'if (document.readyState == "complete") {\n' +
-                           'onLoad()\n' +
-                         '} else {\n' +
-                           'unsafeWindow.addEventListener("load", onLoad, false);\n' +
-                         '}\n',
-          onMessage: function(msg) {
-            // test somePreference
-            assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct');
-            tab.close(done);
-          }
-        });
-    	}
-    });
+                           // Wait for the load event ?
+                           'if (document.readyState == "complete") {\n' +
+                             'onLoad()\n' +
+                           '} else {\n' +
+                             'unsafeWindow.addEventListener("load", onLoad, false);\n' +
+                           '}\n',
+            onMessage: function(msg) {
+              // test somePreference
+              assert.equal(msg.somePreference.title, 'A', 'somePreference title is correct');
+              tab.close(done);
+            }
+          });
+        }
+      });
+  }
+} else {
+  exports['test unsupported'] = (assert) => assert.pass('This test is unsupported.');
 }
 
 require('sdk/test/runner').runTestsFromModule(module);
--- a/addon-sdk/source/test/test-functional.js
+++ b/addon-sdk/source/test/test-functional.js
@@ -433,30 +433,38 @@ exports["test debounce"] = (assert, done
     setTimeout(() => {
       assert.equal(counter, 2, "function able to be called again after wait");
       done();
     }, 150);
   }, 200);
 };
 
 exports["test throttle"] = (assert, done) => {
-  let called = 0;
-  let attempt = 0;
-  let throttledFn = throttle(() => called++, 100);
-  let fn = () => ++attempt && throttledFn();
+  let counter = 0;
+  let incr = function(){ counter++; };
+  let throttledIncr = throttle(incr, 32);
+  throttledIncr();
+  throttledIncr();
+  throttledIncr();
+  throttledIncr();
 
-  new Array(11).join(0).split("").forEach((_, i) => {
-    setTimeout(fn, 20 * (i+1));
-  });
-
+  assert.equal(counter, 1, 'incr was called immediately');
   setTimeout(() => {
-    assert.equal(called, 1, "function called atleast once during first throttle period");
-    assert.ok(attempt >= 2, "function attempted to be called several times during first period");
-  }, 50);
+    assert.equal(counter, 2, 'incr was throttled');
+    done();
+  }, 64);
+};
 
+exports["test throttle args"] = (assert, done) => {
+  let value = 0;
+  let update = function(val){ value = val; };
+  let throttledUpdate = throttle(update, 32);
+  throttledUpdate(1);
+  throttledUpdate(2);
+  setTimeout(() => throttledUpdate(3), 64);
+  assert.equal(value, 1, 'updated to latest value');
   setTimeout(() => {
-    assert.equal(called, 3, "function called again during second throttle period");
-    assert.equal(attempt, 10, "function attempted to be called several times during second period");
+    assert.equal(value, 3, 'updated to latest value');
     done();
-  }, 300);
+  }, 96);
 };
 
 require('test').run(exports);
--- a/addon-sdk/source/test/test-ui-frame.js
+++ b/addon-sdk/source/test/test-ui-frame.js
@@ -41,36 +41,36 @@ exports["test frame API"] = function*(as
   assert.throws(() => new Frame(),
                 /The `options.url`/,
                 "must provide url");
 
   assert.throws(() => new Frame({ url: "http://mozilla.org/" }),
                 /The `options.url`/,
                 "options.url must be local url");
 
-  assert.throws(() => new Frame({url: url, id: "4you" }),
-                /The `option.id` must be a valid/,
-                "can only take valid ID's");
+  assert.throws(() => new Frame({url: url, name: "4you" }),
+                /The `option.name` must be a valid/,
+                "can only take valid names");
 
   const f1 = new Frame({ url: url });
 
   assert.ok(f1.id, "frame has an id");
   assert.equal(f1.url, void(0), "frame has no url until it's loaded");
   assert.equal(typeof(f1.postMessage), "function",
                "frames can postMessages");
 
   const p1 = wait(f1, "register");
 
   assert.throws(() => new Frame({ url: url }),
                 /Frame with this id already exists/,
                 "can't have two identical frames");
 
 
-  const f2 = new Frame({ id: "frame-2", url: url });
-  assert.pass("can create frame with same url but diff id");
+  const f2 = new Frame({ name: "frame-2", url: url });
+  assert.pass("can create frame with same url but diff name");
   const p2 = wait(f2, "register");
 
   yield p1;
   assert.pass("frame#1 was registered");
   assert.equal(f1.url, url, "once registered it get's url");
 
   yield p2;
   assert.pass("frame#2 was registered");
@@ -140,17 +140,17 @@ exports["test frame in toolbar"] = funct
 
 exports["test host to content messaging"] = function*(assert) {
   const url = "data:text/html,<script>new " + function() {
     window.addEventListener("message", (event) => {
       if (event.data === "ping!")
         event.source.postMessage("pong!", event.origin);
     });
   } + "</script>";
-  const f1 = new Frame({ id: "mailbox", url: url });
+  const f1 = new Frame({ name: "mailbox", url: url });
   const t1 = new Toolbar({ title: "mailbox", items: [f1] });
 
   const e1 = yield wait(f1, "ready");
   e1.source.postMessage("ping!", e1.origin);
 
   const pong = yield wait(f1, "message");
   assert.equal(pong.data, "pong!", "received ping back");
   t1.destroy();
@@ -164,17 +164,17 @@ exports["test content to host messaging"
     window.addEventListener("message", (event) => {
       if (event.data === "pong!")
         event.source.postMessage("end", event.origin);
     });
 
     window.parent.postMessage("ping!", "*");
   } + "</script>";
 
-  const f1 = new Frame({ id: "inbox", url: url });
+  const f1 = new Frame({ name: "inbox", url: url });
   const t1 = new Toolbar({ title: "inbox", items: [f1] });
 
   const e1 = yield wait(f1, "message");
   assert.equal(e1.data, "ping!", "received ping from content");
 
   e1.source.postMessage("pong!", e1.origin);
 
   const e2 = yield wait(f1, "message");
@@ -192,17 +192,17 @@ exports["test direct messaging"] = funct
       if (event.data === "inc")
         n = n + 1;
       if (event.data === "print")
         event.source.postMessage({ n: n }, event.origin);
     });
   } + "</script>";
 
   const w1 = getMostRecentBrowserWindow();
-  const f1 = new Frame({ url: url, id: "mail-cluster" });
+  const f1 = new Frame({ url: url, name: "mail-cluster" });
   const t1 = new Toolbar({ title: "claster", items: [f1] });
 
   yield wait(f1, "ready");
   assert.pass("document loaded in window#1");
 
   const w2 = open();
 
   yield wait(f1, "ready");
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -1015,16 +1015,54 @@ exports['test button click do not messin
   button.state(mainWindow, null);
 
   assert.equal(button.state(activeTab).icon, './icon.png',
     'icon property for tab state, properly derived from window state');
 
   loader.unload();
 }
 
+exports['test buttons can have anchored panels'] = function(assert, done) {
+  let loader = Loader(module);
+  let { ToggleButton } = loader.require('sdk/ui');
+  let { Panel } = loader.require('sdk/panel');
+  let { identify } = loader.require('sdk/ui/id');
+  let { getActiveView } = loader.require('sdk/view/core');
+
+  let button = ToggleButton({
+    id: 'my-button-22',
+    label: 'my button',
+    icon: './icon.png',
+    onChange: ({checked}) => checked && panel.show({position: button})
+  });
+
+  let panel = Panel();
+
+  panel.once('show', () => {
+    let { document } = getMostRecentBrowserWindow();
+    let buttonNode = document.getElementById(identify(button));
+    let panelNode = getActiveView(panel);
+
+    assert.ok(button.state('window').checked,
+      'button is checked');
+
+    assert.equal(panelNode.getAttribute('type'), 'arrow',
+      'the panel is a arrow type');
+
+    assert.strictEqual(buttonNode, panelNode.anchorNode,
+      'the panel is anchored properly to the button');
+
+    loader.unload();
+
+    done();
+  });
+
+  button.click();
+}
+
 // If the module doesn't support the app we're being run in, require() will
 // throw.  In that case, remove all tests above from exports, and add one dummy
 // test that passes.
 try {
   require('sdk/ui/button/toggle');
 }
 catch (err) {
   if (!/^Unsupported Application/.test(err.message))
--- a/addon-sdk/source/test/test-ui-toolbar.js
+++ b/addon-sdk/source/test/test-ui-toolbar.js
@@ -7,21 +7,22 @@ module.metadata = {
   "engines": {
     "Firefox": "*"
   }
 };
 
 const { Toolbar } = require("sdk/ui/toolbar");
 const { Loader } = require("sdk/test/loader");
 const { identify } = require("sdk/ui/id");
-const { getMostRecentBrowserWindow, open } = require("sdk/window/utils");
+const { getMostRecentBrowserWindow, open, getOuterId } = require("sdk/window/utils");
 const { ready, close } = require("sdk/window/helpers");
 const { defer } = require("sdk/core/promise");
-const { send } = require("sdk/event/utils");
+const { send, stop, Reactor } = require("sdk/event/utils");
 const { object } = require("sdk/util/sequence");
+const { CustomizationInput } = require("sdk/input/customizable-ui");
 const { OutputPort } = require("sdk/output/system");
 const output = new OutputPort({ id: "toolbar-change" });
 
 const wait = (toolbar, event) => {
   let { promise, resolve } = defer();
   toolbar.once(event, resolve);
   return promise;
 };
@@ -352,9 +353,125 @@ exports["test title change"] = function*
   assert.equal(readTitle(t1, w2), "second title",
                "title updated in second window");
 
   t1.destroy();
   yield wait(t1, "detach");
   yield close(w2);
 };
 
+exports["test toolbar is not customizable"] = function*(assert, done) {
+  const { window, document, gCustomizeMode } = getMostRecentBrowserWindow();
+  const outerId = getOuterId(window);
+  const input = new CustomizationInput();
+  const customized = defer();
+  const customizedEnd = defer();
+
+  new Reactor({ onStep: value => {
+    if (value[outerId] === true)
+      customized.resolve();
+    if (value[outerId] === null)
+      customizedEnd.resolve();
+  }}).run(input);
+
+  const toolbar = new Toolbar({ title: "foo" });
+
+  yield wait(toolbar, "attach");
+
+  let view = document.getElementById(toolbar.id);
+  let label = view.querySelector("label");
+  let inner = view.querySelector("toolbar");
+
+  assert.equal(view.getAttribute("customizable"), "false",
+    "The outer toolbar is not customizable.");
+
+  assert.ok(label.collapsed,
+    "The label is not displayed.")
+
+  assert.equal(inner.getAttribute("customizable"), "true",
+    "The inner toolbar is customizable.");
+
+  assert.equal(window.getComputedStyle(inner).visibility, "visible",
+    "The inner toolbar is visible.");
+
+  // Enter in customization mode
+  gCustomizeMode.toggle();
+
+  yield customized.promise;
+
+  assert.equal(view.getAttribute("customizable"), "false",
+    "The outer toolbar is not customizable.");
+
+  assert.equal(label.collapsed, false,
+    "The label is displayed.")
+
+  assert.equal(inner.getAttribute("customizable"), "true",
+    "The inner toolbar is customizable.");
+
+  assert.equal(window.getComputedStyle(inner).visibility, "hidden",
+    "The inner toolbar is hidden.");
+
+  // Exit from customization mode
+  gCustomizeMode.toggle();
+
+  yield customizedEnd.promise;
+
+  assert.equal(view.getAttribute("customizable"), "false",
+    "The outer toolbar is not customizable.");
+
+  assert.ok(label.collapsed,
+    "The label is not displayed.")
+
+  assert.equal(inner.getAttribute("customizable"), "true",
+    "The inner toolbar is customizable.");
+
+  assert.equal(window.getComputedStyle(inner).visibility, "visible",
+    "The inner toolbar is visible.");
+
+  toolbar.destroy();
+};
+
+exports["test button are attached to toolbar"] = function*(assert) {
+  const { document } = getMostRecentBrowserWindow();
+  const { ActionButton, ToggleButton } = require("sdk/ui");
+  const { identify } = require("sdk/ui/id");
+
+  let action = ActionButton({
+    id: "btn-1",
+    label: "action",
+    icon: "./placeholder.png"
+  });
+
+  let toggle = ToggleButton({
+    id: "btn-2",
+    label: "toggle",
+    icon: "./placeholder.png"
+  });
+
+  const toolbar = new Toolbar({
+    title: "foo",
+    items: [action, toggle]
+  });
+
+  yield wait(toolbar, "attach");
+
+  let actionNode = document.getElementById(identify(action));
+  let toggleNode = document.getElementById(identify(toggle));
+
+  assert.notEqual(actionNode, null,
+    "action button exists in the document");
+
+  assert.notEqual(actionNode, null,
+    "action button exists in the document");
+
+  assert.notEqual(toggleNode, null,
+    "toggle button exists in the document");
+
+  assert.equal(actionNode.nextElementSibling, toggleNode,
+    "action button is placed before toggle button");
+
+  assert.equal(actionNode.parentNode.parentNode.id, toolbar.id,
+    "buttons are placed in the correct toolbar");
+
+  toolbar.destroy();
+};
+
 require("sdk/test").run(exports);