Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Oct 2013 07:08:01 +0200
changeset 165739 5d5591687e676247c617f98f0a02c744250285d1
parent 165738 17a6af9a1c58efd858fb7573428665423cad12ba (current diff)
parent 165732 19fd3388c3729fbc8dd4a52376c964aa495a2a25 (diff)
child 165740 ce20f4b16d56b245848981ec472a1033bed7e82c
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.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
Merge mozilla-central to mozilla-inbound
browser/devtools/debugger/test/browser_dbg_blackboxing-07.js
dom/tests/mochitest/notification/create_notification.html
dom/tests/mochitest/notification/notification_common.js
dom/tests/mochitest/notification/test_basic_notification.html
dom/tests/mochitest/notification/test_basic_notification_click.html
dom/tests/mochitest/notification/test_leak_windowClose.html
dom/tests/mochitest/notification/test_notification_tag.html
dom/tests/mochitest/notification/test_system_principal.xul
dom/tests/mochitest/notification/test_web_notifications.html
netwerk/protocol/app/AppProtocolHandler.js
netwerk/protocol/app/AppProtocolHandler.manifest
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 918207 needed a clobber on every platform because we can't have nice things
+Bug 899574 needed a clobber on every platform because we can't have nice things
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -6,16 +6,17 @@
 
 Cu.import('resource://gre/modules/ContactService.jsm');
 Cu.import('resource://gre/modules/SettingsChangeNotifier.jsm');
 Cu.import('resource://gre/modules/DataStoreChangeNotifier.jsm');
 Cu.import('resource://gre/modules/AlarmService.jsm');
 Cu.import('resource://gre/modules/ActivitiesService.jsm');
 Cu.import('resource://gre/modules/PermissionPromptHelper.jsm');
 Cu.import('resource://gre/modules/ObjectWrapper.jsm');
+Cu.import('resource://gre/modules/NotificationDB.jsm');
 Cu.import('resource://gre/modules/accessibility/AccessFu.jsm');
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import('resource://gre/modules/UserAgentOverrides.jsm');
 Cu.import('resource://gre/modules/Keyboard.jsm');
 Cu.import('resource://gre/modules/ErrorPage.jsm');
 #ifdef MOZ_B2G_RIL
 Cu.import('resource://gre/modules/NetworkStatsService.jsm');
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "a57a913f1dd723afa191124f27b8d9fc4b0cb1c0", 
+    "revision": "20e3f42ccb6073c6d9bc9741de3a19a939a8a7d8", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -347,16 +347,18 @@
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
+@BINPATH@/components/NotificationStorage.js
+@BINPATH@/components/NotificationStorage.manifest
 @BINPATH@/components/PermissionSettings.js
 @BINPATH@/components/PermissionSettings.manifest
 @BINPATH@/components/PermissionPromptService.js
 @BINPATH@/components/PermissionPromptService.manifest
 @BINPATH@/components/AlarmsManager.js
 @BINPATH@/components/AlarmsManager.manifest
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
@@ -538,19 +540,16 @@
 @BINPATH@/components/ActivityWrapper.js
 @BINPATH@/components/ActivityMessageConfigurator.js
 
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPServerSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
-
 @BINPATH@/components/Payment.js
 @BINPATH@/components/PaymentFlowInfo.js
 @BINPATH@/components/PaymentRequestInfo.js
 @BINPATH@/components/Payment.manifest
 
 ; InputMethod API
 @BINPATH@/components/MozKeyboard.js
 @BINPATH@/components/InputMethod.manifest
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -360,22 +360,16 @@ pref("browser.download.manager.showWhenS
 pref("browser.download.manager.closeWhenDone", false);
 pref("browser.download.manager.focusWhenStarting", false);
 pref("browser.download.manager.flashCount", 2);
 pref("browser.download.manager.addToRecentDocs", true);
 pref("browser.download.manager.quitBehavior", 0);
 pref("browser.download.manager.scanWhenDone", true);
 pref("browser.download.manager.resumeOnWakeDelay", 10000);
 
-// Enables the asynchronous Downloads API in the Downloads Panel.
-pref("browser.download.useJSTransfer", true);
-
-// This allows disabling the Downloads Panel in favor of the old interface.
-pref("browser.download.useToolkitUI", false);
-
 // This allows disabling the animated notifications shown by
 // the Downloads Indicator when a download starts or completes.
 pref("browser.download.animateNotifications", true);
 
 // This records whether or not the panel has been shown at least once.
 pref("browser.download.panel.shown", false);
 
 // This records whether or not at least one session with the Downloads Panel
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2,16 +2,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/.
 
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 const nsIWebNavigation = Ci.nsIWebNavigation;
 
 var gCharsetMenu = null;
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -230,25 +230,22 @@ this.DownloadsCommon = {
     if (hours < 48) { // two days
       return DownloadsCommon.strings["shortTimeLeftHours"](hours);
     }
     let days = Math.round(hours / 24);
     return DownloadsCommon.strings["shortTimeLeftDays"](Math.min(days, 99));
   },
 
   /**
-   * Indicates whether we should show the full Download Manager window interface
-   * instead of the simplified panel interface.  The behavior of downloads
-   * across browsing session is consistent with the selected interface.
+   * Indicates that we should show the simplified panel interface instead of the
+   * full Download Manager window interface.  The code associated with the
+   * Download Manager window interface will be removed in bug 899110.
    */
   get useToolkitUI()
   {
-    try {
-      return Services.prefs.getBoolPref("browser.download.useToolkitUI");
-    } catch (ex) { }
     return false;
   },
 
   /**
    * Indicates whether we should show visual notification on the indicator
    * when a download event is triggered.
    */
   get animateNotifications()
@@ -565,27 +562,22 @@ XPCOMUtils.defineLazyGetter(DownloadsCom
   if (os != "WINNT") {
     return false;
   }
   let sysInfo = Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
   return parseFloat(sysInfo.getProperty("version")) >= 6;
 });
 
 /**
- * Returns true if we should hook the panel to the JavaScript API for downloads
- * instead of the nsIDownloadManager back-end.  In order for the logic to work
- * properly, this value never changes during the execution of the application,
- * even if the underlying preference value has changed.  A restart is required
- * for the change to take effect.
+ * Returns true to indicate that we should hook the panel to the JavaScript API
+ * for downloads instead of the nsIDownloadManager back-end.  The code
+ * associated with nsIDownloadManager will be removed in bug 899110.
  */
 XPCOMUtils.defineLazyGetter(DownloadsCommon, "useJSTransfer", function () {
-  try {
-    return Services.prefs.getBoolPref("browser.download.useJSTransfer");
-  } catch (ex) { }
-  return false;
+  return true;
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsData
 
 /**
  * Retrieves the list of past and completed downloads from the underlying
  * Download Manager data, and provides asynchronous notifications allowing to
--- a/browser/components/downloads/src/DownloadsStartup.js
+++ b/browser/components/downloads/src/DownloadsStartup.js
@@ -88,39 +88,23 @@ DownloadsStartup.prototype = {
       case "profile-after-change":
         // Override Toolkit's nsIDownloadManagerUI implementation with our own.
         // This must be done at application startup and not in the manifest to
         // ensure that our implementation overrides the original one.
         Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
                           .registerFactory(kDownloadsUICid, "",
                                            kDownloadsUIContractId, null);
 
-        // If the integration preference is enabled, override Toolkit's
-        // nsITransfer implementation with the one from the JavaScript API for
-        // downloads.  This should be used only by developers while testing new
-        // code that uses the JavaScript API, and will eventually be removed
-        // when nsIDownloadManager will not be available anymore (bug 851471).
-        let useJSTransfer = false;
-        try {
-          // For performance reasons, we don't want to load the DownloadsCommon
-          // module during startup, so we read the preference value directly.
-          useJSTransfer =
-            Services.prefs.getBoolPref("browser.download.useJSTransfer");
-        } catch (ex) { }
-        if (useJSTransfer) {
-          Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
-                            .registerFactory(kTransferCid, "",
-                                             kTransferContractId, null);
-        } else {
-          // The other notifications are handled internally by the JavaScript
-          // API for downloads, no need to observe when that API is enabled.
-          for (let topic of kObservedTopics) {
-            Services.obs.addObserver(this, topic, true);
-          }
-        }
+        // Override Toolkit's nsITransfer implementation with the one from the
+        // JavaScript API for downloads.  This will eventually be removed when
+        // nsIDownloadManager will not be available anymore (bug 851471).  The
+        // old code in this module will be removed in bug 899110.
+        Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+                          .registerFactory(kTransferCid, "",
+                                           kTransferContractId, null);
         break;
 
       case "sessionstore-windows-restored":
       case "sessionstore-browser-state-restored":
         // Unless there is no saved session, there is a chance that we are
         // starting up after a restart or a crash.  We should check the disk
         // database to see if there are completed downloads to recover and show
         // in the panel, in addition to in-progress downloads.
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1415,19 +1415,16 @@ BrowserGlue.prototype = {
           currentset = currentset.replace(/(^|,)home-button($|,)/,
                                           "$1downloads-button,home-button$2")
         } else {
           currentset = currentset.replace(/(^|,)window-controls($|,)/,
                                           "$1downloads-button,window-controls$2")
         }
         this._setPersist(toolbarResource, currentsetResource, currentset);
       }
-
-      Services.prefs.clearUserPref("browser.download.useToolkitUI");
-      Services.prefs.clearUserPref("browser.library.useNewDownloadsView");
     }
 
 #ifdef XP_WIN
     if (currentUIVersion < 10) {
       // For Windows systems with display set to > 96dpi (i.e. systemDefaultScale
       // will return a value > 1.0), we want to discard any saved full-zoom settings,
       // as we'll now be scaling the content according to the system resolution
       // scale factor (Windows "logical DPI" setting)
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -345,16 +345,20 @@ let SessionStoreInternal = {
 
   // Whether session has been initialized
   _sessionInitialized: false,
 
   // True if session store is disabled by multi-process browsing.
   // See bug 516755.
   _disabledForMultiProcess: false,
 
+  // Promise that is resolved when we're ready to initialize
+  // and restore the session.
+  _promiseReadyForInitialization: null,
+
   /**
    * A promise fulfilled once initialization is complete.
    */
   get promiseInitialized() {
     return this._deferredInitialized.promise;
   },
 
   /* ........ Public Getters .............. */
@@ -865,23 +869,43 @@ let SessionStoreInternal = {
         return;
       }
 
       if (this._sessionInitialized) {
         this.onLoad(aWindow);
         return;
       }
 
+      // The very first window that is opened creates a promise that is then
+      // re-used by all subsequent windows. The promise will be used to tell
+      // when we're ready for initialization.
+      if (!this._promiseReadyForInitialization) {
+        let deferred = Promise.defer();
+
+        // Wait for the given window's delayed startup to be finished.
+        Services.obs.addObserver(function obs(subject, topic) {
+          if (aWindow == subject) {
+            Services.obs.removeObserver(obs, topic);
+            deferred.resolve();
+          }
+        }, "browser-delayed-startup-finished", false);
+
+        // We are ready for initialization as soon as the session file has been
+        // read from disk and the initial window's delayed startup has finished.
+        this._promiseReadyForInitialization =
+          Promise.all([deferred.promise, gSessionStartup.onceInitialized]);
+      }
+
       // We can't call this.onLoad since initialization
       // hasn't completed, so we'll wait until it is done.
       // Even if additional windows are opened and wait
       // for initialization as well, the first opened
       // window should execute first, and this.onLoad
       // will be called with the initialState.
-      gSessionStartup.onceInitialized.then(() => {
+      this._promiseReadyForInitialization.then(() => {
         if (aWindow.closed) {
           return;
         }
 
         if (this._sessionInitialized) {
           this.onLoad(aWindow);
         } else {
           let initialState = this.initSession();
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -56,8 +56,10 @@ MOZ_APP_STATIC_INI=1
 MOZ_WEBAPP_RUNTIME=1
 MOZ_MEDIA_NAVIGATOR=1
 if test "$OS_TARGET" = "WINNT" -o "$OS_TARGET" = "Darwin"; then
   MOZ_FOLD_LIBS=1
 fi
 MOZ_WEBGL_CONFORMANT=1
 # Enable navigator.mozPay
 MOZ_PAY=1
+MOZ_JSDOWNLOADS=1
+
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -1151,18 +1151,23 @@ SourceScripts.prototype = {
   },
 
   /**
    * Handler for the debugger client's 'blackboxchange' notification.
    */
   _onBlackBoxChange: function (aEvent, { url, isBlackBoxed }) {
     const item = DebuggerView.Sources.getItemByValue(url);
     if (item) {
-      DebuggerView.Sources.callMethod("checkItem", item.target, !isBlackBoxed);
+      if (isBlackBoxed) {
+        item.target.classList.add("black-boxed");
+      } else {
+        item.target.classList.remove("black-boxed");
+      }
     }
+    DebuggerView.Sources.updateToolbarButtonsState();
     DebuggerView.maybeShowBlackBoxMessage();
   },
 
   /**
    * Set the black boxed status of the given source.
    *
    * @param Object aSource
    *        The source form.
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -14,60 +14,59 @@ function SourcesView() {
   this.togglePrettyPrint = this.togglePrettyPrint.bind(this);
   this._onEditorLoad = this._onEditorLoad.bind(this);
   this._onEditorUnload = this._onEditorUnload.bind(this);
   this._onEditorCursorActivity = this._onEditorCursorActivity.bind(this);
   this._onEditorContextMenu = this._onEditorContextMenu.bind(this);
   this._onSourceSelect = this._onSourceSelect.bind(this);
   this._onSourceClick = this._onSourceClick.bind(this);
   this._onBreakpointRemoved = this._onBreakpointRemoved.bind(this);
-  this._onSourceCheck = this._onSourceCheck.bind(this);
+  this.toggleBlackBoxing = this.toggleBlackBoxing.bind(this);
   this._onStopBlackBoxing = this._onStopBlackBoxing.bind(this);
   this._onBreakpointClick = this._onBreakpointClick.bind(this);
   this._onBreakpointCheckboxClick = this._onBreakpointCheckboxClick.bind(this);
   this._onConditionalPopupShowing = this._onConditionalPopupShowing.bind(this);
   this._onConditionalPopupShown = this._onConditionalPopupShown.bind(this);
   this._onConditionalPopupHiding = this._onConditionalPopupHiding.bind(this);
   this._onConditionalTextboxInput = this._onConditionalTextboxInput.bind(this);
   this._onConditionalTextboxKeyPress = this._onConditionalTextboxKeyPress.bind(this);
-  this._updatePrettyPrintButtonState = this._updatePrettyPrintButtonState.bind(this);
+  this.updateToolbarButtonsState = this.updateToolbarButtonsState.bind(this);
 }
 
 SourcesView.prototype = Heritage.extend(WidgetMethods, {
   /**
    * Initialization function, called when the debugger is started.
    */
   initialize: function() {
     dumpn("Initializing the SourcesView");
 
     this.widget = new SideMenuWidget(document.getElementById("sources"), {
-      showItemCheckboxes: true,
       showArrows: true
     });
 
     this.emptyText = L10N.getStr("noSourcesText");
     this._blackBoxCheckboxTooltip = L10N.getStr("blackBoxCheckboxTooltip");
 
     this._commandset = document.getElementById("debuggerCommands");
     this._popupset = document.getElementById("debuggerPopupset");
     this._cmPopup = document.getElementById("sourceEditorContextMenu");
     this._cbPanel = document.getElementById("conditional-breakpoint-panel");
     this._cbTextbox = document.getElementById("conditional-breakpoint-panel-textbox");
+    this._blackBoxButton = document.getElementById("black-box");
     this._stopBlackBoxButton = document.getElementById("black-boxed-message-button");
     this._prettyPrintButton = document.getElementById("pretty-print");
 
     if (Prefs.prettyPrintEnabled) {
       this._prettyPrintButton.removeAttribute("hidden");
     }
 
     window.on(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
     window.on(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
     this.widget.addEventListener("select", this._onSourceSelect, false);
     this.widget.addEventListener("click", this._onSourceClick, false);
-    this.widget.addEventListener("check", this._onSourceCheck, false);
     this._stopBlackBoxButton.addEventListener("click", this._onStopBlackBoxing, false);
     this._cbPanel.addEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.addEventListener("popupshown", this._onConditionalPopupShown, false);
     this._cbPanel.addEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.addEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.addEventListener("keypress", this._onConditionalTextboxKeyPress, false);
 
     this.autoFocusOnSelection = false;
@@ -81,17 +80,16 @@ SourcesView.prototype = Heritage.extend(
    */
   destroy: function() {
     dumpn("Destroying the SourcesView");
 
     window.off(EVENTS.EDITOR_LOADED, this._onEditorLoad, false);
     window.off(EVENTS.EDITOR_UNLOADED, this._onEditorUnload, false);
     this.widget.removeEventListener("select", this._onSourceSelect, false);
     this.widget.removeEventListener("click", this._onSourceClick, false);
-    this.widget.removeEventListener("check", this._onSourceCheck, false);
     this._stopBlackBoxButton.removeEventListener("click", this._onStopBlackBoxing, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShowing, false);
     this._cbPanel.removeEventListener("popupshowing", this._onConditionalPopupShown, false);
     this._cbPanel.removeEventListener("popuphiding", this._onConditionalPopupHiding, false);
     this._cbTextbox.removeEventListener("input", this._onConditionalTextboxInput, false);
     this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
   },
 
@@ -697,32 +695,33 @@ SourcesView.prototype = Heritage.extend(
     // The container is not empty and an actual item was selected.
     DebuggerView.setEditorLocation(sourceItem.value);
 
     // Set window title.
     let script = sourceItem.value.split(" -> ").pop();
     document.title = L10N.getFormatStr("DebuggerWindowScriptTitle", script);
 
     DebuggerView.maybeShowBlackBoxMessage();
-    this._updatePrettyPrintButtonState();
+    this.updateToolbarButtonsState();
   },
 
   /**
-   * Enable or disable the pretty print button depending on whether the selected
-   * source is black boxed or not and check or uncheck it depending on if the
-   * selected source is already pretty printed or not.
+   * Update the checked/unchecked and enabled/disabled states of the buttons in
+   * the sources toolbar based on the currently selected source's state.
    */
-  _updatePrettyPrintButtonState: function() {
+  updateToolbarButtonsState: function() {
     const { source } = this.selectedItem.attachment;
     const sourceClient = gThreadClient.source(source);
 
     if (sourceClient.isBlackBoxed) {
       this._prettyPrintButton.setAttribute("disabled", true);
+      this._blackBoxButton.setAttribute("checked", true);
     } else {
       this._prettyPrintButton.removeAttribute("disabled");
+      this._blackBoxButton.removeAttribute("checked");
     }
 
     if (sourceClient.isPrettyPrinted) {
       this._prettyPrintButton.setAttribute("checked", true);
     } else {
       this._prettyPrintButton.removeAttribute("checked");
     }
   },
@@ -731,44 +730,50 @@ SourcesView.prototype = Heritage.extend(
    * The click listener for the sources container.
    */
   _onSourceClick: function() {
     // Use this container as a filtering target.
     DebuggerView.Filtering.target = this;
   },
 
   /**
-   * The check listener for the sources container.
+   * Toggle the black boxed state of the selected source.
    */
-  _onSourceCheck: function({ detail: { checked }, target }) {
-    const shouldBlackBox = !checked;
+  toggleBlackBoxing: function() {
+    const { source } = this.selectedItem.attachment;
+    const sourceClient = gThreadClient.source(source);
+    const shouldBlackBox = !sourceClient.isBlackBoxed;
 
-    // Be optimistic that the (un-)black boxing will succeed and enable/disable
-    // the pretty print button immediately. Then, once we actually get the
-    // results from the server, make sure that it is in the correct state again
-    // by calling `_updatePrettyPrintButtonState`.
+    // Be optimistic that the (un-)black boxing will succeed, so enable/disable
+    // the pretty print button and check/uncheck the black box button
+    // immediately. Then, once we actually get the results from the server, make
+    // sure that it is in the correct state again by calling
+    // `updateToolbarButtonsState`.
 
     if (shouldBlackBox) {
       this._prettyPrintButton.setAttribute("disabled", true);
+      this._blackBoxButton.setAttribute("checked", true);
     } else {
       this._prettyPrintButton.removeAttribute("disabled");
+      this._blackBoxButton.removeAttribute("checked");
     }
 
-    const { source } = this.getItemForElement(target).attachment;
     DebuggerController.SourceScripts.blackBox(source, shouldBlackBox)
-      .then(this._updatePrettyPrintButtonState,
-            this._updatePrettyPrintButtonState);
+      .then(this.updateToolbarButtonsState,
+            this.updateToolbarButtonsState);
   },
 
   /**
    * The click listener for the "stop black boxing" button.
    */
   _onStopBlackBoxing: function() {
     let sourceForm = this.selectedItem.attachment.source;
-    DebuggerController.SourceScripts.blackBox(sourceForm, false);
+    DebuggerController.SourceScripts.blackBox(sourceForm, false)
+      .then(this.updateToolbarButtonsState,
+            this.updateToolbarButtonsState);
   },
 
   /**
    * The click listener for a breakpoint container.
    */
   _onBreakpointClick: function(e) {
     let sourceItem = this.getItemForElement(e.target);
     let breakpointItem = this.getItemForElement.call(sourceItem, e.target);
--- a/browser/devtools/debugger/debugger.xul
+++ b/browser/devtools/debugger/debugger.xul
@@ -27,16 +27,18 @@
   <script type="text/javascript" src="debugger-toolbar.js"/>
   <script type="text/javascript" src="debugger-panes.js"/>
 
   <commandset id="editMenuCommands"/>
 
   <commandset id="debuggerCommands">
     <command id="prettyPrintCommand"
              oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
+    <command id="blackBoxCommand"
+             oncommand="DebuggerView.Sources.toggleBlackBoxing()"/>
     <command id="unBlackBoxButton"
              oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
     <command id="nextSourceCommand"
              oncommand="DebuggerView.Sources.selectNextItem()"/>
     <command id="prevSourceCommand"
              oncommand="DebuggerView.Sources.selectPrevItem()"/>
     <command id="resumeCommand"
              oncommand="DebuggerView.Toolbar._onResumePressed()"/>
@@ -315,22 +317,28 @@
                      tabindex="0"/>
     </toolbar>
     <scrollbox id="globalsearch" orient="vertical" hidden="true"/>
     <splitter class="devtools-horizontal-splitter" hidden="true"/>
     <hbox id="debugger-widgets" flex="1">
       <vbox id="sources-pane">
         <vbox id="sources" flex="1"/>
         <toolbar id="sources-toolbar" class="devtools-toolbar">
-          <toolbarbutton id="pretty-print"
-                         label="{}"
-                         tooltiptext="&debuggerUI.sources.prettyPrint;"
-                         class="devtools-toolbarbutton devtools-monospace"
-                         command="prettyPrintCommand"
-                         hidden="true"/>
+          <hbox id="sources-controls">
+            <toolbarbutton id="black-box"
+                           tooltiptext="&debuggerUI.sources.blackBoxTooltip;"
+                           command="blackBoxCommand"
+                           class="devtools-toolbarbutton"/>
+            <toolbarbutton id="pretty-print"
+                           label="{}"
+                           tooltiptext="&debuggerUI.sources.prettyPrint;"
+                           class="devtools-toolbarbutton devtools-monospace"
+                           command="prettyPrintCommand"
+                           hidden="true"/>
+          </hbox>
         </toolbar>
       </vbox>
       <splitter id="sources-and-editor-splitter"
                 class="devtools-side-splitter"/>
       <deck id="editor-deck" flex="4">
         <vbox id="editor"/>
         <vbox id="black-boxed-message" align="center" pack="center">
           <label id="black-boxed-message-label">
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -60,17 +60,16 @@ support-files =
 [browser_dbg_aaa_run_first_leaktest.js]
 [browser_dbg_bfcache.js]
 [browser_dbg_blackboxing-01.js]
 [browser_dbg_blackboxing-02.js]
 [browser_dbg_blackboxing-03.js]
 [browser_dbg_blackboxing-04.js]
 [browser_dbg_blackboxing-05.js]
 [browser_dbg_blackboxing-06.js]
-[browser_dbg_blackboxing-07.js]
 [browser_dbg_file-reload.js]
 [browser_dbg_breadcrumbs-access.js]
 [browser_dbg_break-on-dom-01.js]
 [browser_dbg_break-on-dom-02.js]
 [browser_dbg_break-on-dom-03.js]
 [browser_dbg_break-on-dom-04.js]
 [browser_dbg_break-on-dom-05.js]
 [browser_dbg_break-on-dom-06.js]
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-01.js
@@ -22,34 +22,29 @@ function test() {
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
-  const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
-  ok(checkbox, "Should get the checkbox for black boxing the source.");
-  ok(checkbox.checked, "Should not be black boxed by default.");
+  const bbButton = getBlackBoxButton(gPanel);
+  ok(!bbButton.checked, "Should not be black boxed by default");
 
-  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+  return toggleBlackBoxing(gPanel).then(aSource => {
     ok(aSource.isBlackBoxed, "The source should be black boxed now.");
-    ok(!checkbox.checked, "The checkbox should no longer be checked.");
+    ok(bbButton.checked, "The checkbox should no longer be checked.");
   });
-
-  checkbox.click();
-  return finished;
 }
 
 function testBlackBoxReload() {
   return reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN).then(() => {
-    const checkbox = gDebugger.document.querySelector(".side-menu-widget-item-checkbox");
-    ok(checkbox, "Should get the checkbox for black boxing the source.");
-    ok(!checkbox.checked, "Should still be black boxed.");
+    const bbButton = getBlackBoxButton(gPanel);
+    ok(bbButton.checked, "Should still be black boxed.");
   });
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-02.js
@@ -26,43 +26,34 @@ function test() {
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
-  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+  return toggleBlackBoxing(gPanel).then(aSource => {
     ok(aSource.isBlackBoxed, "The source should be black boxed now.");
   });
-
-  getBlackBoxCheckbox(BLACKBOXME_URL).click();
-  return finished;
 }
 
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 3,
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
       "And one of them should be the combined black boxed frames.");
   });
 
   // Spin the event loop before causing the debuggee to pause, to allow
   // this function to return first.
   executeSoon(() => gDebuggee.runTest());
   return finished;
 }
 
-function getBlackBoxCheckbox(aUrl) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
-    ".side-menu-widget-item-checkbox");
-}
-
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-03.js
@@ -35,33 +35,24 @@ function test() {
 function testBlackBoxStack() {
   is(gFrames.itemCount, 6,
     "Should get 6 frames.");
   is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 0,
     "And none of them are black boxed.");
 }
 
 function testBlackBoxSource() {
-  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(aSource => {
+  return toggleBlackBoxing(gPanel, BLACKBOXME_URL).then(aSource => {
     ok(aSource.isBlackBoxed, "The source should be black boxed now.");
 
     is(gFrames.itemCount, 3,
       "Should only get 3 frames.");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 1,
       "And one of them should be the combined black boxed frames.");
   });
-
-  getBlackBoxCheckbox(BLACKBOXME_URL).click();
-  return finished;
-}
-
-function getBlackBoxCheckbox(aUrl) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
-    ".side-menu-widget-item-checkbox");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-04.js
@@ -27,19 +27,19 @@ function test() {
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function blackBoxSources() {
   let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
-  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_one.js").click();
-  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_two.js").click();
-  getBlackBoxCheckbox(EXAMPLE_URL + "code_blackboxing_three.js").click();
+  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_one.js");
+  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_two.js");
+  toggleBlackBoxing(gPanel, EXAMPLE_URL + "code_blackboxing_three.js");
   return finished;
 }
 
 function testBlackBoxStack() {
   let finished = waitForSourceAndCaretAndScopes(gPanel, ".html", 21).then(() => {
     is(gFrames.itemCount, 4,
       "Should get 4 frames (one -> two -> three -> doDebuggerStatement).");
     is(gDebugger.document.querySelectorAll(".dbg-stackframe-black-boxed").length, 3,
@@ -47,21 +47,15 @@ function testBlackBoxStack() {
   });
 
   // Spin the event loop before causing the debuggee to pause, to allow
   // this function to return first.
   executeSoon(() => gDebuggee.one());
   return finished;
 }
 
-function getBlackBoxCheckbox(aUrl) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
-    ".side-menu-widget-item-checkbox");
-}
-
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gFrames = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-05.js
@@ -16,59 +16,48 @@ function test() {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gDeck = gDebugger.document.getElementById("editor-deck");
 
     waitForSourceShown(gPanel, ".coffee")
       .then(testSourceEditorShown)
-      .then(blackBoxSource)
+      .then(toggleBlackBoxing.bind(null, gPanel))
       .then(testBlackBoxMessageShown)
       .then(clickStopBlackBoxingButton)
       .then(testSourceEditorShownAgain)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testSourceEditorShown() {
   is(gDeck.selectedIndex, "0",
     "The first item in the deck should be selected (the source editor).");
 }
 
-function blackBoxSource() {
-  let finished = waitForThreadEvents(gPanel, "blackboxchange");
-  getAnyBlackBoxCheckbox().click();
-  return finished;
-}
-
 function testBlackBoxMessageShown() {
   is(gDeck.selectedIndex, "1",
     "The second item in the deck should be selected (the black box message).");
 }
 
 function clickStopBlackBoxingButton() {
   let finished = waitForThreadEvents(gPanel, "blackboxchange");
   getEditorBlackboxMessageButton().click();
   return finished;
 }
 
 function testSourceEditorShownAgain() {
   is(gDeck.selectedIndex, "0",
     "The first item in the deck should be selected again (the source editor).");
 }
 
-function getAnyBlackBoxCheckbox() {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item .side-menu-widget-item-checkbox");
-}
-
 function getEditorBlackboxMessageButton() {
   return gDebugger.document.getElementById("black-boxed-message-button");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_blackboxing-06.js
@@ -1,56 +1,57 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Test that clicking the black box checkbox doesn't select that source.
+ * Test that clicking the black box checkbox when paused doesn't re-select the
+ * currently paused frame's source.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
 
 let gTab, gDebuggee, gPanel, gDebugger;
 let gSources;
 
 function test() {
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gTab = aTab;
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
-    waitForSourceShown(gPanel, ".js")
+    waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
       .then(testBlackBox)
-      .then(() => closeDebuggerAndFinish(gPanel))
+      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
+
+    gDebuggee.runTest();
   });
 }
 
 function testBlackBox() {
   const selectedUrl = gSources.selectedValue;
-  const checkbox = getDifferentBlackBoxCheckbox(selectedUrl);
-  ok(checkbox, "We should be able to grab a different checkbox.");
 
-  let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
-    is(selectedUrl, gSources.selectedValue,
-      "The same source should still be selected.");
+  let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
+    const newSelectedUrl = gSources.selectedValue;
+    isnot(selectedUrl, newSelectedUrl,
+      "Should not have the same url selected.");
+
+    return toggleBlackBoxing(gPanel).then(() => {
+      is(gSources.selectedValue, newSelectedUrl,
+        "The selected source did not change.");
+    });
   });
 
-  checkbox.click();
+  gSources.selectedIndex = 0;
   return finished;
 }
 
-function getDifferentBlackBoxCheckbox(aUrl) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item:not([tooltiptext=\"" + aUrl + "\"]) " +
-    ".side-menu-widget-item-checkbox");
-}
-
 registerCleanupFunction(function() {
   gTab = null;
   gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
 });
deleted file mode 100644
--- a/browser/devtools/debugger/test/browser_dbg_blackboxing-07.js
+++ /dev/null
@@ -1,66 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-/**
- * Test that clicking the black box checkbox when paused doesn't re-select the
- * currently paused frame's source.
- */
-
-const TAB_URL = EXAMPLE_URL + "doc_blackboxing.html";
-
-let gTab, gDebuggee, gPanel, gDebugger;
-let gSources;
-
-function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
-    gTab = aTab;
-    gDebuggee = aDebuggee;
-    gPanel = aPanel;
-    gDebugger = gPanel.panelWin;
-    gSources = gDebugger.DebuggerView.Sources;
-
-    waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
-      .then(testBlackBox)
-      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
-        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
-      });
-
-    gDebuggee.runTest();
-  });
-}
-
-function testBlackBox() {
-  const selectedUrl = gSources.selectedValue;
-
-  let finished = waitForSourceShown(gPanel, "blackboxme.js").then(() => {
-    const newSelectedUrl = gSources.selectedValue;
-    isnot(selectedUrl, newSelectedUrl,
-      "Should not have the same url selected.");
-
-    let finished = waitForThreadEvents(gPanel, "blackboxchange").then(() => {
-      is(gSources.selectedValue, newSelectedUrl,
-        "The selected source did not change.");
-    });
-
-    getBlackBoxCheckbox(newSelectedUrl).click()
-    return finished;
-  });
-
-  gSources.selectedIndex = 0;
-  return finished;
-}
-
-function getBlackBoxCheckbox(aUrl) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\"" + aUrl + "\"] " +
-    ".side-menu-widget-item-checkbox");
-}
-
-registerCleanupFunction(function() {
-  gTab = null;
-  gDebuggee = null;
-  gPanel = null;
-  gDebugger = null;
-  gSources = null;
-});
--- a/browser/devtools/debugger/test/browser_dbg_cmd-blackbox.js
+++ b/browser/devtools/debugger/test/browser_dbg_cmd-blackbox.js
@@ -41,79 +41,105 @@ function setupGlobals(aToolbox) {
   gThreadClient = gDebugger.gThreadClient;
 }
 
 function waitForDebuggerSources() {
   return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
 }
 
 function testBlackBoxSource() {
-  return cmd("dbg blackbox " + BLACKBOXME_URL).then(() => {
-    const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
-    ok(!checkbox.checked,
-      "Should be able to black box a specific source.");
+  return Task.spawn(function* () {
+    yield cmd("dbg blackbox " + BLACKBOXME_URL);
+
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
+    ok(bbButton.checked,
+       "Should be able to black box a specific source.");
   });
 }
 
 function testUnBlackBoxSource() {
-  return cmd("dbg unblackbox " + BLACKBOXME_URL).then(() => {
-    const checkbox = getBlackBoxCheckbox(BLACKBOXME_URL);
-    ok(checkbox.checked,
-      "Should be able to stop black boxing a specific source.");
+  return Task.spawn(function* () {
+    yield cmd("dbg unblackbox " + BLACKBOXME_URL);
+
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
+    ok(!bbButton.checked,
+       "Should be able to stop black boxing a specific source.");
   });
 }
 
 function testBlackBoxGlob() {
-  return cmd("dbg blackbox --glob *blackboxing_t*.js", 2,
-             [/blackboxing_three\.js/g, /blackboxing_two\.js/g]).then(() => {
-    ok(getBlackBoxCheckbox(BLACKBOXME_URL).checked,
-      "blackboxme should not be black boxed because it doesn't match the glob.");
-    ok(getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
-      "blackbox_one should not be black boxed because it doesn't match the glob.");
+  return Task.spawn(function* () {
+    yield cmd("dbg blackbox --glob *blackboxing_t*.js", 2,
+              [/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
 
-    ok(!getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
-      "blackbox_two should be black boxed because it matches the glob.");
-    ok(!getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
+    ok(!bbButton.checked,
+       "blackboxme should not be black boxed because it doesn't match the glob.");
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
+    ok(!bbButton.checked,
+       "blackbox_one should not be black boxed because it doesn't match the glob.");
+
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
+    ok(bbButton.checked,
+       "blackbox_two should be black boxed because it matches the glob.");
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
+    ok(bbButton.checked,
       "blackbox_three should be black boxed because it matches the glob.");
   });
 }
 
 function testUnBlackBoxGlob() {
-  return cmd("dbg unblackbox --glob *blackboxing_t*.js", 2).then(() => {
-    ok(getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
-      "blackbox_two should be un-black boxed because it matches the glob.");
-    ok(getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
+  return Task.spawn(function* () {
+    yield cmd("dbg unblackbox --glob *blackboxing_t*.js", 2);
+
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
+    ok(!bbButton.checked,
+       "blackbox_two should be un-black boxed because it matches the glob.");
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
+    ok(!bbButton.checked,
       "blackbox_three should be un-black boxed because it matches the glob.");
   });
 }
 
 function testBlackBoxInvert() {
-  return cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3,
-             [/blackboxing_three\.js/g, /blackboxing_two\.js/g]).then(() => {
-    ok(!getBlackBoxCheckbox(BLACKBOXME_URL).checked,
+  return Task.spawn(function* () {
+    yield cmd("dbg blackbox --invert --glob *blackboxing_t*.js", 3,
+              [/blackboxing_three\.js/g, /blackboxing_two\.js/g]);
+
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
+    ok(bbButton.checked,
       "blackboxme should be black boxed because it doesn't match the glob.");
-    ok(!getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
+    ok(bbButton.checked,
       "blackbox_one should be black boxed because it doesn't match the glob.");
-    ok(!getBlackBoxCheckbox(TEST_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, TEST_URL);
+    ok(bbButton.checked,
       "TEST_URL should be black boxed because it doesn't match the glob.");
 
-    ok(getBlackBoxCheckbox(BLACKBOXTWO_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTWO_URL);
+    ok(!bbButton.checked,
       "blackbox_two should not be black boxed because it matches the glob.");
-    ok(getBlackBoxCheckbox(BLACKBOXTHREE_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXTHREE_URL);
+    ok(!bbButton.checked,
       "blackbox_three should not be black boxed because it matches the glob.");
   });
 }
 
 function testUnBlackBoxInvert() {
-  return cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3).then(() => {
-    ok(getBlackBoxCheckbox(BLACKBOXME_URL).checked,
+  return Task.spawn(function* () {
+    yield cmd("dbg unblackbox --invert --glob *blackboxing_t*.js", 3);
+
+    let bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXME_URL);
+    ok(!bbButton.checked,
       "blackboxme should be un-black boxed because it does not match the glob.");
-    ok(getBlackBoxCheckbox(BLACKBOXONE_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, BLACKBOXONE_URL);
+    ok(!bbButton.checked,
       "blackbox_one should be un-black boxed because it does not match the glob.");
-    ok(getBlackBoxCheckbox(TEST_URL).checked,
+    bbButton = yield selectSourceAndGetBlackBoxButton(gPanel, TEST_URL);
+    ok(!bbButton.checked,
       "TEST_URL should be un-black boxed because it doesn't match the glob.");
   });
 }
 
 registerCleanupFunction(function() {
   gOptions = null;
   gPanel = null;
   gDebugger = null;
@@ -121,14 +147,8 @@ registerCleanupFunction(function() {
 });
 
 function cmd(aTyped, aEventRepeat = 1, aOutput = "") {
   return promise.all([
     waitForThreadEvents(gPanel, "blackboxchange", aEventRepeat),
     helpers.audit(gOptions, [{ setup: aTyped, output: aOutput, exec: {} }])
   ]);
 }
-
-function getBlackBoxCheckbox(url) {
-  return gDebugger.document.querySelector(
-    ".side-menu-widget-item[tooltiptext=\"" + url + "\"] " +
-    ".side-menu-widget-item-checkbox");
-}
--- a/browser/devtools/debugger/test/browser_dbg_pretty-print-10.js
+++ b/browser/devtools/debugger/test/browser_dbg_pretty-print-10.js
@@ -17,38 +17,31 @@ function test() {
     gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceShown(gPanel, "code_ugly.js")
       .then(testSourceIsUgly)
-      .then(blackBoxSource)
-      .then(waitForThreadEvents.bind(null, gPanel, "blackboxchange"))
+      .then(toggleBlackBoxing.bind(null, gPanel))
       .then(clickPrettyPrintButton)
       .then(testSourceIsStillUgly)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
       });
   });
 }
 
 function testSourceIsUgly() {
   ok(!gEditor.getText().contains("\n    "),
      "The source shouldn't be pretty printed yet.");
 }
 
-function blackBoxSource() {
-  const checkbox = gDebugger.document.querySelector(
-    ".selected .side-menu-widget-item-checkbox");
-  checkbox.click();
-}
-
 function clickPrettyPrintButton() {
   gDebugger.document.getElementById("pretty-print").click();
 }
 
 function testSourceIsStillUgly() {
   const { source } = gSources.selectedItem.attachment;
   return gDebugger.DebuggerController.SourceScripts.getText(source).then(([, text]) => {
     ok(!text.contains("\n    "));
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -518,8 +518,34 @@ function closeDebuggerAndFinish(aPanel, 
 }
 
 function resumeDebuggerThenCloseAndFinish(aPanel, aFlags = {}) {
   let deferred = promise.defer();
   let thread = aPanel.panelWin.gThreadClient;
   thread.resume(() => closeDebuggerAndFinish(aPanel, aFlags).then(deferred.resolve));
   return deferred.promise;
 }
+
+function getBlackBoxButton(aPanel) {
+  return aPanel.panelWin.document.getElementById("black-box");
+}
+
+function toggleBlackBoxing(aPanel, aSource = null) {
+  function clickBlackBoxButton() {
+    getBlackBoxButton(aPanel).click();
+  }
+
+  const blackBoxChanged = waitForThreadEvents(aPanel, "blackboxchange");
+
+  if (aSource) {
+    aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
+    ensureSourceIs(aPanel, aSource, true).then(clickBlackBoxButton);
+  } else {
+    clickBlackBoxButton();
+  }
+  return blackBoxChanged;
+}
+
+function selectSourceAndGetBlackBoxButton(aPanel, aSource) {
+  aPanel.panelWin.DebuggerView.Sources.selectedValue = aSource;
+  return ensureSourceIs(aPanel, aSource, true)
+    .then(getBlackBoxButton.bind(null, aPanel));
+}
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -49,17 +49,24 @@ function submit() {
   // Save the host/port values
   let host = document.getElementById("host").value;
   Services.prefs.setCharPref("devtools.debugger.remote-host", host);
 
   let port = document.getElementById("port").value;
   Services.prefs.setIntPref("devtools.debugger.remote-port", port);
 
   // Initiate the connection
-  let transport = debuggerSocketConnect(host, port);
+  let transport;
+  try {
+    transport = debuggerSocketConnect(host, port);
+  } catch(e) {
+    // Bug 921850: catch rare exception from debuggerSocketConnect
+    showError("unexpected");
+    return;
+  }
   gClient = new DebuggerClient(transport);
   let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
   gConnectionTimeout = setTimeout(handleConnectionTimeout, delay);
   gClient.connect(onConnectionReady);
 }
 
 /**
  * Connection is ready. List actors and build buttons.
--- a/browser/devtools/sourceeditor/codemirror/README
+++ b/browser/devtools/sourceeditor/codemirror/README
@@ -13,18 +13,19 @@ To confirm the functionality run mochite
 
  * sourceeditor
  * scratchpad
  * debugger
  * styleditor
 
 The sourceeditor component contains imported CodeMirror tests [3]. Some
 tests were commented out because we don't use that functionality within
-Firefox (for example Ruby editing mode). Other than that, we don't have
-any Mozilla-specific patches applied to CodeMirror itself.
+Firefox (for example Ruby editing mode). The search addon (search.js)
+was slightly modified to make search UI localizable. Other than that,
+we don't have any Mozilla-specific patches applied to CodeMirror itself.
 
 # Addons
 
 To install a new CodeMirror addon add it to the codemirror directory,
 jar.mn [4] file and editor.js [5]. Also, add it to the License section
 below.
 
 # License
--- a/browser/devtools/sourceeditor/codemirror/search/search.js
+++ b/browser/devtools/sourceeditor/codemirror/search/search.js
@@ -40,22 +40,26 @@
   function confirmDialog(cm, text, shortText, fs) {
     if (cm.openConfirm) cm.openConfirm(text, fs);
     else if (confirm(shortText)) fs[0]();
   }
   function parseQuery(query) {
     var isRE = query.match(/^\/(.*)\/([a-z]*)$/);
     return isRE ? new RegExp(isRE[1], isRE[2].indexOf("i") == -1 ? "" : "i") : query;
   }
-  var queryDialog =
-    'Search: <input type="text" style="width: 10em"/> <span style="color: #888">(Use /re/ syntax for regexp search)</span>';
+  var queryDialog;
   function doSearch(cm, rev) {
+    if (!queryDialog) {
+      queryDialog = cm.l10n('findCmd.promptMessage') +
+        ' <input type="text" style="width: 10em"/>';
+    }
+
     var state = getSearchState(cm);
     if (state.query) return findNext(cm, rev);
-    dialog(cm, queryDialog, "Search for:", function(query) {
+    dialog(cm, queryDialog, cm.l10n('findCmd.promptMessage'), function(query) {
       cm.operation(function() {
         if (!query || state.query) return;
         state.query = parseQuery(query);
         cm.removeOverlay(state.overlay);
         state.overlay = searchOverlay(state.query);
         cm.addOverlay(state.overlay);
         state.posFrom = state.posTo = cm.getCursor();
         findNext(cm, rev);
--- a/browser/devtools/sourceeditor/editor.js
+++ b/browser/devtools/sourceeditor/editor.js
@@ -202,16 +202,20 @@ Editor.prototype = {
         this.emit("contextMenu");
         this.showContextMenu(doc, ev.screenX, ev.screenY);
       }, false);
 
       cm.on("change", () => this.emit("change"));
       cm.on("gutterClick", (cm, line) => this.emit("gutterClick", line));
       cm.on("cursorActivity", (cm) => this.emit("cursorActivity"));
 
+      win.CodeMirror.defineExtension("l10n", (name) => {
+        return L10N.GetStringFromName(name);
+      });
+
       doc.defaultView.controllers.insertControllerAt(0, controller(this, doc.defaultView));
 
       this.container = env;
       editors.set(this, cm);
       def.resolve();
     };
 
     env.addEventListener("load", onLoad, true);
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -89,16 +89,19 @@ support-files =
   test-repeated-messages.html
   test-result-format-as-string.html
   test-webconsole-error-observer.html
   test_bug_770099_bad_policy_uri.html
   test_bug_770099_bad_policy_uri.html^headers^
   test_bug_770099_violation.html
   test_bug_770099_violation.html^headers^
   testscript.js
+  test-bug_923281_console_log_filter.html
+  test-bug_923281_test1.js
+  test-bug_923281_test2.js
 
 [browser_bug664688_sandbox_update_after_navigation.js]
 [browser_bug_638949_copy_link_location.js]
 [browser_bug_862916_console_dir_and_filter_off.js]
 [browser_bug_865288_repeat_different_objects.js]
 [browser_bug_865871_variables_view_close_on_esc_key.js]
 [browser_bug_869003_inspect_cross_domain_object.js]
 [browser_bug_871156_ctrlw_close_tab.js]
@@ -226,8 +229,9 @@ support-files =
 [browser_webconsole_netlogging.js]
 [browser_webconsole_network_panel.js]
 [browser_webconsole_notifications.js]
 [browser_webconsole_output_copy_newlines.js]
 [browser_webconsole_output_order.js]
 [browser_webconsole_property_provider.js]
 [browser_webconsole_scratchpad_panel_link.js]
 [browser_webconsole_view_source.js]
+[browser_webconsole_log_file_filter.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_log_file_filter.js
@@ -0,0 +1,80 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the text filter box works to filter based on filenames
+// where the logs were generated.
+
+const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html";
+
+let hud;
+
+function test() {
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    openConsole(null, consoleOpened);
+  }, true);
+}
+
+function consoleOpened(aHud) {
+  hud = aHud;
+  let console = content.console;
+  console.log("sentinel log");
+  waitForMessages({
+    webconsole: hud,
+    messages: [{
+      text: "sentinel log",
+      category: CATEGORY_WEBDEV,
+      severity: SEVERITY_LOG
+    }],
+  }).then(testLiveFilteringOnSearchStrings);
+}
+
+function testLiveFilteringOnSearchStrings() {
+  is(hud.outputNode.children.length, 4, "number of messages");
+
+  setStringFilter("random");
+  is(countMessageNodes(), 1, "the log nodes not containing string " +
+      "\"random\" are hidden");
+
+  setStringFilter("test2.js");
+  is(countMessageNodes(), 2, "show only log nodes containing string " +
+      "\"test2.js\" or log nodes created from files with filename " +
+      "containing \"test2.js\" as substring.");
+
+  setStringFilter("test1");
+  is(countMessageNodes(), 2, "show only log nodes containing string " +
+      "\"test1\" or log nodes created from files with filename " +
+      "containing \"test1\" as substring.");
+
+  setStringFilter("");
+  is(countMessageNodes(), 4, "show all log nodes on setting filter string " +
+      "as \"\".");
+
+  finishTest();
+}
+
+function countMessageNodes() {
+  let outputNode = hud.outputNode;
+
+  let messageNodes = outputNode.querySelectorAll(".message");
+  content.console.log(messageNodes.length);
+  let displayedMessageNodes = 0;
+  let view = hud.iframeWindow;
+  for (let i = 0; i < messageNodes.length; i++) {
+    let computedStyle = view.getComputedStyle(messageNodes[i], null);
+    if (computedStyle.display !== "none") {
+      displayedMessageNodes++;
+    }
+  }
+
+  return displayedMessageNodes;
+}
+
+function setStringFilter(aValue)
+{
+  hud.ui.filterBox.value = aValue;
+  hud.ui.adjustVisibilityOnSearchStringChange();
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_console_log_filter.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf-8">
+    <title>Console test</title>
+    <!-- Any copyright is dedicated to the Public Domain.
+       - http://creativecommons.org/publicdomain/zero/1.0/ -->
+    <script type="text/javascript" src="test-bug_923281_test1.js"></script>
+    <script type="text/javascript" src="test-bug_923281_test2.js"></script>
+  </head>
+  <body></body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_test1.js
@@ -0,0 +1,5 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("Sample log.");
+console.log("This log should be filtered when filtered for test2.js.");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-bug_923281_test2.js
@@ -0,0 +1,4 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("This is a random text.");
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -868,17 +868,17 @@ WebConsoleFrame.prototype = {
   {
     let nodes = this.outputNode.getElementsByClassName("message");
     let searchString = this.filterBox.value;
 
     for (let i = 0, n = nodes.length; i < n; ++i) {
       let node = nodes[i];
 
       // hide nodes that match the strings
-      let text = node.clipboardText;
+      let text = node.textContent;
 
       // if the text matches the words in aSearchString...
       if (this.stringMatchesFilters(text, searchString)) {
         node.classList.remove("filtered-by-string");
       }
       else {
         node.classList.add("filtered-by-string");
       }
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -504,29 +504,28 @@
 @BINPATH@/components/recording-cmdline.manifest
 
 @BINPATH@/components/PermissionSettings.js
 @BINPATH@/components/PermissionSettings.manifest
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
+@BINPATH@/components/NotificationStorage.js
+@BINPATH@/components/NotificationStorage.manifest
 @BINPATH@/components/AlarmsManager.js
 @BINPATH@/components/AlarmsManager.manifest
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 @BINPATH@/components/PushServiceLauncher.js
 @BINPATH@/components/TCPSocket.js
 @BINPATH@/components/TCPServerSocket.js
 @BINPATH@/components/TCPSocketParentIntermediary.js
 @BINPATH@/components/TCPSocket.manifest
 
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
-
 @BINPATH@/components/Payment.js
 @BINPATH@/components/PaymentFlowInfo.js
 @BINPATH@/components/PaymentRequestInfo.js
 @BINPATH@/components/Payment.manifest
 
 #ifdef MOZ_WEBRTC
 @BINPATH@/components/PeerConnection.js
 @BINPATH@/components/PeerConnection.manifest
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.dtd
@@ -28,18 +28,22 @@
   - the text displayed in the button to stop black boxing the currently selected
   - source. -->
 <!ENTITY debuggerUI.blackBoxMessage.unBlackBoxButton "Stop black boxing this source">
 
 <!-- LOCALIZATION NOTE (debuggerUI.optsButton.tooltip): This is the tooltip for
   -  the button that opens up an options context menu for the debugger UI. -->
 <!ENTITY debuggerUI.optsButton.tooltip  "Debugger Options">
 
+<!-- LOCALIZATION NOTE (debuggerUI.sources.blackBoxTooltip): This is the tooltip
+  -  for the button that black boxes the selected source. -->
+<!ENTITY debuggerUI.sources.blackBoxTooltip "Toggle Black Boxing">
+
 <!-- LOCALIZATION NOTE (debuggerUI.sources.prettyPrint): This is the tooltip for the
-button that pretty prints the selected source. -->
+  -  button that pretty prints the selected source. -->
 <!ENTITY debuggerUI.sources.prettyPrint "Prettify Source">
 
 <!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
   -  checkbox that toggles pausing on exceptions. -->
 <!ENTITY debuggerUI.pauseExceptions     "Pause on exceptions">
 <!ENTITY debuggerUI.pauseExceptions.key "E">
 
 <!-- LOCALIZATION NOTE (debuggerUI.pauseExceptions): This is the label for the
--- a/browser/metro/profile/metro.js
+++ b/browser/metro/profile/metro.js
@@ -31,25 +31,27 @@ pref("metro.debug.selection.dumpEvents",
 // Enable tab-modal prompts
 pref("prompts.tab_modal.enabled", true);
 
 
 // Enable off main thread compositing
 pref("layers.offmainthreadcomposition.enabled", true);
 pref("layers.async-pan-zoom.enabled", true);
 pref("layers.componentalpha.enabled", false);
-pref("gfx.azpc.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
-pref("gfx.azpc.pan_repaint_interval", "50");   // prefer 20 fps
-pref("gfx.azpc.fling_repaint_interval", "50"); // prefer 20 fps
-pref("gfx.axis.fling_friction", "0.002");
-pref("gfx.axis.fling_stopped_threshold", "0.2");
+
+// Prefs to control the async pan/zoom behaviour
+pref("apz.touch_start_tolerance", "0.1"); // dpi * tolerance = pixel threshold
+pref("apz.pan_repaint_interval", "50");   // prefer 20 fps
+pref("apz.fling_repaint_interval", "50"); // prefer 20 fps
+pref("apz.fling_friction", "0.002");
+pref("apz.fling_stopped_threshold", "0.2");
 
 // 0 = free, 1 = standard, 2 = sticky
-pref("apzc.axis_lock_mode", 2);
-pref("apzc.cross_slide.enabled", true);
+pref("apz.axis_lock_mode", 2);
+pref("apz.cross_slide.enabled", true);
 
 // Enable Microsoft TSF support by default for imes.
 pref("intl.enable_tsf_support", true);
 
 pref("general.autoScroll", true);
 pref("general.smoothScroll", true);
 pref("general.smoothScroll.durationToIntervalRatio", 200);
 pref("mousewheel.enable_pixel_scrolling", true);
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11a45b5d037853f7cfae2d266269382acb0cd558
GIT binary patch
literal 1005
zc$@+40}}j+P)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#k4Z#9RCwCV)mumuQ5XmCncbNkuj?$u
zqHB;^X@#wsiMk?0Jt$pK48!QX3zZK+1wGVb7bwc7u%Hmc&=4dlFhsgv)HD#%#Yk=2
z-E!TXy|~Wo?)3lMSrLnhdZ>lte0$FKJ9|9yof%eDRpwuQSiRBmZx`SV%>FT6W?A*u
z3*dg#>G<qSHi;R92+g!eeB2)=rrs9hua<vj(s%t&_3-Hk1tvbh9K2-PQXTv04!rIk
zOtu!TKhE=peS}dSq4)$$3NB$;o@?tL7`j?%D+~W<2Oe~H=;3qWv+-32AJx1bml*sf
z`0?HKjSCCWC4v{P-k6H5)>gfq+Z&b@Z%=RE`Mh;SjnpVcpTu!|L+R#C*W21Vla~G`
z_qy77_+oeqaupam>;N^2sSn-)U(|g0!ou$|KJ37urc;9JtM`V>6L{?O`1?A#A09?=
zu57*2RZ5Lw>Z38YC-B7Kp1z1aJryuDcpZEZd?j*h$jw22GOS3AV(OzY_)7To=!+i!
zn#kdrKI@(d^g6yu_c2M2wR~HCTW9x%8UO4{Y801LRTt10<$Uo5`X(H%qp&)f2)+Q`
zhOsi_RKTag7-6fZQ4F6(W0V7LgU=;PMaYFhvs@Z)2qIR{RFh~7DY7g?qA@S+5@N<A
zic}9&gmclD8kJ?mtQBzto`sK)rF{4(j2>QD<f$I$-pAlocwzoApcB%$N4<UHwR?7L
zOcDfvVVOu(MY*!Qw&9H$Q%9dYfBE?2nRE8ew%avn>1nH2hK>1rK4<mLx@$g33ebe`
zA@~e<9lQ#!!h7L^@E-Ueycs?Po`Ii({{SChw0kM;s6VjTKJmH5<?*+>XZ&}EM@H+i
z*Ay?emTfgtOno#)IW@KWOUP)J?j;W|VhnZQFF|f8OcuNWUZ9xzXpC~;Gsuu-rC^vb
zW#!RUW~?j7&u+eQY5Ur>`BuTei)?($c<gILRwB~an0=(_#OdzAq2USiOSJuo<4k$*
zN$_TPBRtQ>Z;1|nRConG0PlpK)vU@&q4^`tm@@NtTvE|QOID`w*pY)P@^W+NVZwf#
zoD8<yyfyWH)J`=)EimI8S6~*OL^@h@-g$TqJ_(-Fd;~rWuV{6as0U_Y0oo#H0)zGh
z6SN6gZM9T@lq}=NqgwuV4>oNfiN<REYN7zOrvB<>#A-jDcwj8Xzx|$BvVg_c|L1M^
bO@ILaIdut6%kstp00000NkvXXu0mjfrUB=;
--- a/browser/themes/linux/devtools/debugger.css
+++ b/browser/themes/linux/devtools/debugger.css
@@ -17,66 +17,39 @@
   -moz-border-start-color: transparent;
 }
 
 #pretty-print {
   min-width: 0;
   font-weight: bold;
 }
 
-#sources .side-menu-widget-item-checkbox {
-  -moz-appearance: none;
-  opacity: 0;
-  transition: opacity .15s ease 0s;
-}
-
-/* Only show the checkbox when the source is hovered over, is selected, or if it
- * is not checked. */
-#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item-checkbox:not([checked]) {
-  opacity: 1;
-  transition: opacity .15s ease 0s;
-}
-
-#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box {
-  -moz-appearance: none;
+#black-box {
+  list-style-image: url(debugger-blackbox.png);
+  -moz-image-region: rect(0px,16px,16px,0px);
 }
 
-#sources .side-menu-widget-item-checkbox > .checkbox-spacer-box > .checkbox-check {
-  -moz-appearance: none;
-  background: none;
-  background-image: url(itemToggle.png);
-  background-repeat: no-repeat;
-  background-clip: content-box;
-  background-size: 32px 16px;
-  background-position: -16px 0;
-  width: 16px;
-  height: 16px;
-  border: 0;
+#black-box[checked] {
+  -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#sources .side-menu-widget-item-checkbox[checked] > .checkbox-spacer-box > .checkbox-check {
-  background-position: 0 0;
-}
-
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
+#sources .black-boxed {
   color: #888;
 }
 
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
+#sources .black-boxed > .dbg-breakpoint {
   display: none;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: none;
   box-shadow: inset -1px 0 0 #222426;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
   background-image: none;
   box-shadow: inset 1px 0 0 #222426;
 }
 
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
@@ -142,17 +115,17 @@
 
 .dbg-stackframe-menuitem-details {
   -moz-padding-start: 16px;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
-  -moz-margin-start: -14px;
+  -moz-margin-start: 4px;
 }
 
 .dbg-breakpoint-line {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   -moz-margin-start: 10px !important;
@@ -397,30 +370,33 @@
 #step-in {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-in.png");
 }
 
 #step-out {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
 }
 
-#debugger-controls > toolbarbutton {
+#debugger-controls > toolbarbutton,
+#sources-controls > toolbarbutton {
   margin: 0;
   box-shadow: none;
   border-radius: 0;
   border-width: 0;
   -moz-border-end-width: 1px;
   outline-offset: -3px;
 }
 
-#debugger-controls > toolbarbutton:last-of-type {
+#debugger-controls > toolbarbutton:last-of-type,
+#sources-controls > toolbarbutton:last-of-type {
   -moz-border-end-width: 0;
 }
 
-#debugger-controls {
+#debugger-controls,
+#sources-controls {
   box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset,
               0 0 0 1px hsla(210,16%,76%,.15) inset,
               0 1px 0 hsla(210,16%,76%,.15);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: 3px;
   margin: 0 3px;
 }
 
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -190,16 +190,17 @@ browser.jar:
   skin/classic/browser/devtools/layoutview.css        (devtools/layoutview.css)
   skin/classic/browser/devtools/debugger-collapse.png  (devtools/debugger-collapse.png)
   skin/classic/browser/devtools/debugger-expand.png    (devtools/debugger-expand.png)
   skin/classic/browser/devtools/debugger-pause.png     (devtools/debugger-pause.png)
   skin/classic/browser/devtools/debugger-play.png      (devtools/debugger-play.png)
   skin/classic/browser/devtools/debugger-step-in.png   (devtools/debugger-step-in.png)
   skin/classic/browser/devtools/debugger-step-out.png  (devtools/debugger-step-out.png)
   skin/classic/browser/devtools/debugger-step-over.png (devtools/debugger-step-over.png)
+  skin/classic/browser/devtools/debugger-blackbox.png  (devtools/debugger-blackbox.png)
   skin/classic/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
   skin/classic/browser/devtools/responsive-background.png (devtools/responsive-background.png)
   skin/classic/browser/devtools/toggle-tools.png          (devtools/toggle-tools.png)
   skin/classic/browser/devtools/dock-bottom.png           (devtools/dock-bottom.png)
   skin/classic/browser/devtools/dock-side.png             (devtools/dock-side.png)
   skin/classic/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11a45b5d037853f7cfae2d266269382acb0cd558
GIT binary patch
literal 1005
zc$@+40}}j+P)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#k4Z#9RCwCV)mumuQ5XmCncbNkuj?$u
zqHB;^X@#wsiMk?0Jt$pK48!QX3zZK+1wGVb7bwc7u%Hmc&=4dlFhsgv)HD#%#Yk=2
z-E!TXy|~Wo?)3lMSrLnhdZ>lte0$FKJ9|9yof%eDRpwuQSiRBmZx`SV%>FT6W?A*u
z3*dg#>G<qSHi;R92+g!eeB2)=rrs9hua<vj(s%t&_3-Hk1tvbh9K2-PQXTv04!rIk
zOtu!TKhE=peS}dSq4)$$3NB$;o@?tL7`j?%D+~W<2Oe~H=;3qWv+-32AJx1bml*sf
z`0?HKjSCCWC4v{P-k6H5)>gfq+Z&b@Z%=RE`Mh;SjnpVcpTu!|L+R#C*W21Vla~G`
z_qy77_+oeqaupam>;N^2sSn-)U(|g0!ou$|KJ37urc;9JtM`V>6L{?O`1?A#A09?=
zu57*2RZ5Lw>Z38YC-B7Kp1z1aJryuDcpZEZd?j*h$jw22GOS3AV(OzY_)7To=!+i!
zn#kdrKI@(d^g6yu_c2M2wR~HCTW9x%8UO4{Y801LRTt10<$Uo5`X(H%qp&)f2)+Q`
zhOsi_RKTag7-6fZQ4F6(W0V7LgU=;PMaYFhvs@Z)2qIR{RFh~7DY7g?qA@S+5@N<A
zic}9&gmclD8kJ?mtQBzto`sK)rF{4(j2>QD<f$I$-pAlocwzoApcB%$N4<UHwR?7L
zOcDfvVVOu(MY*!Qw&9H$Q%9dYfBE?2nRE8ew%avn>1nH2hK>1rK4<mLx@$g33ebe`
zA@~e<9lQ#!!h7L^@E-Ueycs?Po`Ii({{SChw0kM;s6VjTKJmH5<?*+>XZ&}EM@H+i
z*Ay?emTfgtOno#)IW@KWOUP)J?j;W|VhnZQFF|f8OcuNWUZ9xzXpC~;Gsuu-rC^vb
zW#!RUW~?j7&u+eQY5Ur>`BuTei)?($c<gILRwB~an0=(_#OdzAq2USiOSJuo<4k$*
zN$_TPBRtQ>Z;1|nRConG0PlpK)vU@&q4^`tm@@NtTvE|QOID`w*pY)P@^W+NVZwf#
zoD8<yyfyWH)J`=)EimI8S6~*OL^@h@-g$TqJ_(-Fd;~rWuV{6as0U_Y0oo#H0)zGh
z6SN6gZM9T@lq}=NqgwuV4>oNfiN<REYN7zOrvB<>#A-jDcwj8Xzx|$BvVg_c|L1M^
bO@ILaIdut6%kstp00000NkvXXu0mjfrUB=;
--- a/browser/themes/osx/devtools/debugger.css
+++ b/browser/themes/osx/devtools/debugger.css
@@ -19,62 +19,39 @@
   -moz-border-start-color: transparent;
 }
 
 #pretty-print {
   min-width: 0;
   font-weight: bold;
 }
 
-#sources .side-menu-widget-item-checkbox {
-  -moz-appearance: none;
-  opacity: 0;
-  transition: opacity .15s ease-out 0s;
-}
-
-/* Only show the checkbox when the source is hovered over, is selected, or if it
- * is not checked. */
-#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item-checkbox:not([checked]) {
-  opacity: 1;
-  transition: opacity .15s ease-out 0s;
+#black-box {
+  list-style-image: url(debugger-blackbox.png);
+  -moz-image-region: rect(0px,16px,16px,0px);
 }
 
-#sources .side-menu-widget-item-checkbox > .checkbox-check {
-  -moz-appearance: none;
-  background: none;
-  background-image: url(itemToggle.png);
-  background-repeat: no-repeat;
-  background-clip: content-box;
-  background-size: 32px 16px;
-  background-position: -16px 0;
-  width: 16px;
-  height: 16px;
-  border: 0;
+#black-box[checked] {
+  -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
-  background-position: 0 0;
-}
-
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
+#sources .black-boxed {
   color: #888;
 }
 
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
+#sources .black-boxed > .dbg-breakpoint {
   display: none;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: none;
   box-shadow: inset -1px 0 0 #222426;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
   background-image: none;
   box-shadow: inset 1px 0 0 #222426;
 }
 
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
@@ -140,17 +117,17 @@
 
 .dbg-stackframe-menuitem-details {
   -moz-padding-start: 16px;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
-  -moz-margin-start: -14px;
+  -moz-margin-start: 4px;
 }
 
 .dbg-breakpoint-line {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   -moz-margin-start: 10px !important;
@@ -395,30 +372,33 @@
 #step-in {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-in.png");
 }
 
 #step-out {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
 }
 
-#debugger-controls > toolbarbutton {
+#debugger-controls > toolbarbutton,
+#sources-controls > toolbarbutton {
   margin: 0;
   box-shadow: none;
   border-radius: 0;
   border-width: 0;
   -moz-border-end-width: 1px;
   outline-offset: -3px;
 }
 
-#debugger-controls > toolbarbutton:last-of-type {
+#debugger-controls > toolbarbutton:last-of-type,
+#sources-controls > toolbarbutton:last-of-type {
   -moz-border-end-width: 0;
 }
 
-#debugger-controls {
+#debugger-controls,
+#sources-controls {
   box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset,
               0 0 0 1px hsla(210,16%,76%,.15) inset,
               0 1px 0 hsla(210,16%,76%,.15);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: @toolbarbuttonCornerRadius@;
   margin: 0 3px;
 }
 
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -282,16 +282,17 @@ browser.jar:
   skin/classic/browser/devtools/layoutview.css              (devtools/layoutview.css)
   skin/classic/browser/devtools/debugger-collapse.png       (devtools/debugger-collapse.png)
   skin/classic/browser/devtools/debugger-expand.png         (devtools/debugger-expand.png)
   skin/classic/browser/devtools/debugger-pause.png          (devtools/debugger-pause.png)
   skin/classic/browser/devtools/debugger-play.png           (devtools/debugger-play.png)
   skin/classic/browser/devtools/debugger-step-in.png        (devtools/debugger-step-in.png)
   skin/classic/browser/devtools/debugger-step-out.png       (devtools/debugger-step-out.png)
   skin/classic/browser/devtools/debugger-step-over.png      (devtools/debugger-step-over.png)
+  skin/classic/browser/devtools/debugger-blackbox.png       (devtools/debugger-blackbox.png)
   skin/classic/browser/devtools/floating-scrollbars.css     (devtools/floating-scrollbars.css)
   skin/classic/browser/devtools/floating-scrollbars-light.css (devtools/floating-scrollbars-light.css)
   skin/classic/browser/devtools/responsive-se-resizer.png   (devtools/responsive-se-resizer.png)
   skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
   skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
   skin/classic/browser/devtools/responsive-background.png   (devtools/responsive-background.png)
   skin/classic/browser/devtools/toggle-tools.png            (devtools/toggle-tools.png)
   skin/classic/browser/devtools/dock-bottom.png             (devtools/dock-bottom.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..11a45b5d037853f7cfae2d266269382acb0cd558
GIT binary patch
literal 1005
zc$@+40}}j+P)<h;3K|Lk000e1NJLTq001BW000mO1^@s6cL04^0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU#k4Z#9RCwCV)mumuQ5XmCncbNkuj?$u
zqHB;^X@#wsiMk?0Jt$pK48!QX3zZK+1wGVb7bwc7u%Hmc&=4dlFhsgv)HD#%#Yk=2
z-E!TXy|~Wo?)3lMSrLnhdZ>lte0$FKJ9|9yof%eDRpwuQSiRBmZx`SV%>FT6W?A*u
z3*dg#>G<qSHi;R92+g!eeB2)=rrs9hua<vj(s%t&_3-Hk1tvbh9K2-PQXTv04!rIk
zOtu!TKhE=peS}dSq4)$$3NB$;o@?tL7`j?%D+~W<2Oe~H=;3qWv+-32AJx1bml*sf
z`0?HKjSCCWC4v{P-k6H5)>gfq+Z&b@Z%=RE`Mh;SjnpVcpTu!|L+R#C*W21Vla~G`
z_qy77_+oeqaupam>;N^2sSn-)U(|g0!ou$|KJ37urc;9JtM`V>6L{?O`1?A#A09?=
zu57*2RZ5Lw>Z38YC-B7Kp1z1aJryuDcpZEZd?j*h$jw22GOS3AV(OzY_)7To=!+i!
zn#kdrKI@(d^g6yu_c2M2wR~HCTW9x%8UO4{Y801LRTt10<$Uo5`X(H%qp&)f2)+Q`
zhOsi_RKTag7-6fZQ4F6(W0V7LgU=;PMaYFhvs@Z)2qIR{RFh~7DY7g?qA@S+5@N<A
zic}9&gmclD8kJ?mtQBzto`sK)rF{4(j2>QD<f$I$-pAlocwzoApcB%$N4<UHwR?7L
zOcDfvVVOu(MY*!Qw&9H$Q%9dYfBE?2nRE8ew%avn>1nH2hK>1rK4<mLx@$g33ebe`
zA@~e<9lQ#!!h7L^@E-Ueycs?Po`Ii({{SChw0kM;s6VjTKJmH5<?*+>XZ&}EM@H+i
z*Ay?emTfgtOno#)IW@KWOUP)J?j;W|VhnZQFF|f8OcuNWUZ9xzXpC~;Gsuu-rC^vb
zW#!RUW~?j7&u+eQY5Ur>`BuTei)?($c<gILRwB~an0=(_#OdzAq2USiOSJuo<4k$*
zN$_TPBRtQ>Z;1|nRConG0PlpK)vU@&q4^`tm@@NtTvE|QOID`w*pY)P@^W+NVZwf#
zoD8<yyfyWH)J`=)EimI8S6~*OL^@h@-g$TqJ_(-Fd;~rWuV{6as0U_Y0oo#H0)zGh
z6SN6gZM9T@lq}=NqgwuV4>oNfiN<REYN7zOrvB<>#A-jDcwj8Xzx|$BvVg_c|L1M^
bO@ILaIdut6%kstp00000NkvXXu0mjfrUB=;
--- a/browser/themes/windows/devtools/debugger.css
+++ b/browser/themes/windows/devtools/debugger.css
@@ -17,64 +17,39 @@
   -moz-border-start-color: transparent;
 }
 
 #pretty-print {
   min-width: 0;
   font-weight: bold;
 }
 
-#sources .side-menu-widget-item-checkbox {
-  -moz-appearance: none;
-  -moz-margin-end: -6px;
-  padding: 0;
-  opacity: 0;
-  transition: opacity .15s ease 0s;
-}
-
-/* Only show the checkbox when the source is hovered over, is selected, or if it
- * is not checked. */
-#sources .side-menu-widget-item:hover > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox,
-#sources .side-menu-widget-item-checkbox:not([checked]) {
-  opacity: 1;
-  transition: opacity .15s ease-out 0s;
+#black-box {
+  list-style-image: url(debugger-blackbox.png);
+  -moz-image-region: rect(0px,16px,16px,0px);
 }
 
-#sources .side-menu-widget-item-checkbox > .checkbox-check {
-  -moz-appearance: none;
-  background: none;
-  background-image: url(itemToggle.png);
-  background-repeat: no-repeat;
-  background-clip: content-box;
-  background-size: 32px 16px;
-  background-position: -16px 0;
-  width: 16px;
-  height: 16px;
-  border: 0;
+#black-box[checked] {
+  -moz-image-region: rect(0px,32px,16px,16px);
 }
 
-#sources .side-menu-widget-item-checkbox[checked] > .checkbox-check {
-  background-position: 0 0;
-}
-
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents {
+#sources .black-boxed {
   color: #888;
 }
 
-#sources .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-contents > .dbg-breakpoint {
+#sources .black-boxed > .dbg-breakpoint {
   display: none;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(ltr) {
   background-image: none;
   box-shadow: inset -1px 0 0 #222426;
 }
 
-#sources .side-menu-widget-item.selected > .side-menu-widget-item-checkbox:not([checked]) ~ .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
+#sources .black-boxed + .side-menu-widget-item-arrow:-moz-locale-dir(rtl) {
   background-image: none;
   box-shadow: inset 1px 0 0 #222426;
 }
 
 /* Black box message and source progress meter */
 
 #black-boxed-message,
 #source-progress-container {
@@ -140,17 +115,17 @@
 
 .dbg-stackframe-menuitem-details {
   -moz-padding-start: 16px;
 }
 
 /* Sources and breakpoints view */
 
 .dbg-breakpoint {
-  -moz-margin-start: -14px;
+  -moz-margin-start: 4px;
 }
 
 .dbg-breakpoint-line {
   font-weight: 600;
 }
 
 .dbg-breakpoint-text {
   -moz-margin-start: 10px !important;
@@ -395,30 +370,33 @@
 #step-in {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-in.png");
 }
 
 #step-out {
   list-style-image: url("chrome://browser/skin/devtools/debugger-step-out.png");
 }
 
-#debugger-controls > toolbarbutton {
+#debugger-controls > toolbarbutton,
+#sources-controls > toolbarbutton {
   margin: 0;
   box-shadow: none;
   border-radius: 0;
   border-width: 0;
   -moz-border-end-width: 1px;
   outline-offset: -3px;
 }
 
-#debugger-controls > toolbarbutton:last-of-type {
+#debugger-controls > toolbarbutton:last-of-type,
+#sources-controls > toolbarbutton:last-of-type {
   -moz-border-end-width: 0;
 }
 
-#debugger-controls {
+#debugger-controls,
+#sources-controls {
   box-shadow: 0 1px 0 hsla(209,29%,72%,.15) inset,
               0 0 0 1px hsla(209,29%,72%,.1) inset,
               0 0 0 1px hsla(209,29%,72%,.1),
               0 1px 0 hsla(210,16%,76%,.1);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: 3px;
   margin: 0 3px;
 }
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -217,16 +217,17 @@ browser.jar:
         skin/classic/browser/devtools/layoutview.css                (devtools/layoutview.css)
         skin/classic/browser/devtools/debugger-collapse.png         (devtools/debugger-collapse.png)
         skin/classic/browser/devtools/debugger-expand.png           (devtools/debugger-expand.png)
         skin/classic/browser/devtools/debugger-pause.png            (devtools/debugger-pause.png)
         skin/classic/browser/devtools/debugger-play.png             (devtools/debugger-play.png)
         skin/classic/browser/devtools/debugger-step-in.png          (devtools/debugger-step-in.png)
         skin/classic/browser/devtools/debugger-step-out.png         (devtools/debugger-step-out.png)
         skin/classic/browser/devtools/debugger-step-over.png        (devtools/debugger-step-over.png)
+        skin/classic/browser/devtools/debugger-blackbox.png         (devtools/debugger-blackbox.png)
         skin/classic/browser/devtools/responsive-se-resizer.png     (devtools/responsive-se-resizer.png)
         skin/classic/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
         skin/classic/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
         skin/classic/browser/devtools/responsive-background.png     (devtools/responsive-background.png)
         skin/classic/browser/devtools/toggle-tools.png              (devtools/toggle-tools.png)
         skin/classic/browser/devtools/dock-bottom.png               (devtools/dock-bottom.png)
         skin/classic/browser/devtools/dock-side.png                 (devtools/dock-side.png)
         skin/classic/browser/devtools/floating-scrollbars.css       (devtools/floating-scrollbars.css)
@@ -492,16 +493,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/layoutview.css            (devtools/layoutview.css)
         skin/classic/aero/browser/devtools/debugger-collapse.png     (devtools/debugger-collapse.png)
         skin/classic/aero/browser/devtools/debugger-expand.png       (devtools/debugger-expand.png)
         skin/classic/aero/browser/devtools/debugger-pause.png        (devtools/debugger-pause.png)
         skin/classic/aero/browser/devtools/debugger-play.png         (devtools/debugger-play.png)
         skin/classic/aero/browser/devtools/debugger-step-in.png      (devtools/debugger-step-in.png)
         skin/classic/aero/browser/devtools/debugger-step-out.png     (devtools/debugger-step-out.png)
         skin/classic/aero/browser/devtools/debugger-step-over.png    (devtools/debugger-step-over.png)
+        skin/classic/aero/browser/devtools/debugger-blackbox.png     (devtools/debugger-blackbox.png)
         skin/classic/aero/browser/devtools/responsive-se-resizer.png (devtools/responsive-se-resizer.png)
         skin/classic/aero/browser/devtools/responsive-vertical-resizer.png (devtools/responsive-vertical-resizer.png)
         skin/classic/aero/browser/devtools/responsive-horizontal-resizer.png (devtools/responsive-horizontal-resizer.png)
         skin/classic/aero/browser/devtools/responsive-background.png (devtools/responsive-background.png)
         skin/classic/aero/browser/devtools/toggle-tools.png          (devtools/toggle-tools.png)
         skin/classic/aero/browser/devtools/dock-bottom.png           (devtools/dock-bottom.png)
         skin/classic/aero/browser/devtools/dock-side.png             (devtools/dock-side.png)
         skin/classic/aero/browser/devtools/floating-scrollbars.css   (devtools/floating-scrollbars.css)
--- a/build/autoconf/arch.m4
+++ b/build/autoconf/arch.m4
@@ -9,16 +9,17 @@ dnl ====================================
 dnl = ARM toolchain tweaks
 dnl ========================================================
 
 MOZ_THUMB=toolchain-default
 MOZ_THUMB_INTERWORK=toolchain-default
 MOZ_FPU=toolchain-default
 MOZ_FLOAT_ABI=toolchain-default
 MOZ_SOFT_FLOAT=toolchain-default
+MOZ_ALIGN=toolchain-default
 
 MOZ_ARG_WITH_STRING(arch,
 [  --with-arch=[[type|toolchain-default]]
                            Use specific CPU features (-march=type). Resets
                            thumb, fpu, float-abi, etc. defaults when set],
     if test -z "$GNU_CC"; then
         AC_MSG_ERROR([--with-arch is not supported on non-GNU toolchains])
     fi
@@ -27,16 +28,17 @@ MOZ_ARG_WITH_STRING(arch,
 if test -z "$MOZ_ARCH"; then
     dnl Defaults
     case "${CPU_ARCH}-${OS_TARGET}" in
     arm-Android)
         MOZ_THUMB=yes
         MOZ_ARCH=armv7-a
         MOZ_FPU=vfp
         MOZ_FLOAT_ABI=softfp
+        MOZ_ALIGN=no
         ;;
     arm-Darwin)
         MOZ_ARCH=toolchain-default
         MOZ_THUMB=yes
         ;;
     esac
 fi
 
@@ -154,18 +156,38 @@ yes)
 no)
     soft_float_flag="-mno-soft-float"
     ;;
 *) # toolchain-default
     soft_float_flag=""
     ;;
 esac
 
+case "$MOZ_ALIGN" in
+no)
+    align_flag="-mno-unaligned-access"
+    ;;
+yes)
+    align_flag="-munaligned-access"
+    ;;
+*)
+    align_flag=""
+    ;;
+esac
+
+if test -n "$align_flag"; then
+  _SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS $align_flag"
+  AC_MSG_CHECKING(whether alignment flag ($align_flag) is supported)
+  AC_TRY_COMPILE([],[],,align_flag="")
+  CFLAGS="$_SAVE_CFLAGS"
+fi
+
 dnl Use echo to avoid accumulating space characters
-all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag`
+all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag $align_flag`
 if test -n "$all_flags"; then
     _SAVE_CFLAGS="$CFLAGS"
     CFLAGS="$all_flags"
     AC_MSG_CHECKING(whether the chosen combination of compiler flags ($all_flags) works)
     AC_TRY_COMPILE([],[return 0;],
         AC_MSG_RESULT([yes]),
         AC_MSG_ERROR([no]))
 
--- a/configure.in
+++ b/configure.in
@@ -3981,17 +3981,17 @@ MOZ_UNIVERSALCHARDET=1
 MOZ_URL_CLASSIFIER=
 MOZ_XUL=1
 MOZ_ZIPWRITER=1
 NS_PRINTING=1
 MOZ_PDF_PRINTING=
 MOZ_DISABLE_CRYPTOLEGACY=
 NSS_DISABLE_DBM=
 NECKO_COOKIES=1
-NECKO_PROTOCOLS_DEFAULT="about data file ftp http res viewsource websocket wyciwyg device"
+NECKO_PROTOCOLS_DEFAULT="about app data file ftp http res viewsource websocket wyciwyg device"
 USE_ARM_KUSER=
 BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_ANDROID_BEAM=
 ACCESSIBILITY=1
 MOZ_TIME_MANAGER=
@@ -8337,16 +8337,21 @@ AC_SUBST(USE_DEPENDENT_LIBS)
 
 AC_SUBST(MOZ_BUILD_ROOT)
 AC_SUBST(MOZ_OS2_TOOLS)
 
 AC_SUBST(MOZ_POST_DSO_LIB_COMMAND)
 AC_SUBST(MOZ_POST_PROGRAM_COMMAND)
 AC_SUBST(MOZ_LINKER_EXTRACT)
 
+AC_SUBST(MOZ_JSDOWNLOADS)
+if test -n "$MOZ_JSDOWNLOADS"; then
+  AC_DEFINE(MOZ_JSDOWNLOADS)
+fi
+
 dnl ========================================================
 dnl = Mac bundle name prefix
 dnl ========================================================
 MOZ_ARG_WITH_STRING(macbundlename-prefix,
 [  --with-macbundlename-prefix=prefix
                           Prefix for MOZ_MACBUNDLE_NAME],
 [ MOZ_MACBUNDLE_NAME_PREFIX="$withval"])
 
--- a/dom/apps/src/OperatorApps.jsm
+++ b/dom/apps/src/OperatorApps.jsm
@@ -161,17 +161,18 @@ this.OperatorAppsRegistry = {
       app: {
         installOrigin: aMetadata.installOrigin,
         origin: aMetadata.origin,
         manifestURL: aMetadata.manifestURL,
         manifestHash: AppsUtils.computeHash(JSON.stringify(aManifest))
       },
       appId: undefined,
       isBrowser: false,
-      isPackage: isPackage
+      isPackage: isPackage,
+      forceSuccessAck: true
     };
 
     if (isPackage) {
       debug("aId:" + aId + ". Installing as packaged app.");
       let installPack = OS.Path.join(this.appsDir.path, aId, APPLICATION_ZIP);
       OS.File.exists(installPack).then(
         function(aExists) {
           if (!aExists) {
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -2150,16 +2150,30 @@ this.DOMApplicationRegistry = {
 
   // This function is called after we called the onsuccess callback on the
   // content side. This let the webpage the opportunity to set event handlers
   // on the app before we start firing progress events.
   queuedDownload: {},
   queuedPackageDownload: {},
 
   onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
+    // If we are offline, register to run when we'll be online.
+    if (Services.io.offline) {
+      let onlineWrapper = {
+        observe: function(aSubject, aTopic, aData) {
+          Services.obs.removeObserver(onlineWrapper,
+                                      "network:offline-status-changed");
+          DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
+        }
+      }
+      Services.obs.addObserver(onlineWrapper,
+                               "network:offline-status-changed", false);
+      return;
+    }
+
     let cacheDownload = this.queuedDownload[aManifestURL];
     if (cacheDownload) {
       this.startOfflineCacheDownload(cacheDownload.manifest,
                                      cacheDownload.app,
                                      cacheDownload.profileDir);
       delete this.queuedDownload[aManifestURL];
 
       return;
@@ -2366,22 +2380,22 @@ this.DOMApplicationRegistry = {
       // can't be used to resolve package paths.
       manifest = new ManifestHelper(jsonManifest, app.manifestURL);
 
       this.queuedPackageDownload[app.manifestURL] = {
         manifest: manifest,
         app: appObject,
         callback: aInstallSuccessCallback
       };
-
-      if (aData.app.localInstallPath) {
-        // if it's a local install, there's no content process so just
-        // ack the install
-        this.onInstallSuccessAck(app.manifestURL);
-      }
+    }
+
+    if (aData.forceSuccessAck) {
+      // If it's a local install, there's no content process so just
+      // ack the install.
+      this.onInstallSuccessAck(app.manifestURL);
     }
   },
 
   _nextLocalId: function() {
     let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
 
     while (this.getManifestURLByLocalId(id)) {
       id++;
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -757,20 +757,26 @@ DeviceStorageFile::GetRootDirectoryForTy
 #else
      f = sDirs->sdcard;
 #endif
   }
 
   // crash reports directory.
   else if (aStorageType.EqualsLiteral(DEVICESTORAGE_CRASHES)) {
     f = sDirs->crashes;
+  } else {
+    // Not a storage type that we recognize. Return null
+    return;
   }
 
-  // in testing, we default all device storage types to a temp directory
-  if (f && sDirs->temp) {
+  // In testing, we default all device storage types to a temp directory.
+  // sDirs->temp will only have been initialized (in InitDirs) if the
+  // preference device.storage.testing was set to true. We can't test the
+  // preference directly here, since we may not be on the main thread.
+  if (sDirs->temp) {
     f = sDirs->temp;
   }
 
   if (f) {
     f->Clone(aFile);
   }
 }
 
--- a/dom/interfaces/notification/moz.build
+++ b/dom/interfaces/notification/moz.build
@@ -1,14 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 XPIDL_SOURCES += [
     'nsIDOMDesktopNotification.idl',
+    'nsINotificationStorage.idl',
 ]
 
 XPIDL_MODULE = 'dom_notification'
 
 MODULE = 'dom'
 
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/notification/nsINotificationStorage.idl
@@ -0,0 +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/. */
+
+#include "domstubs.idl"
+
+[scriptable, uuid(fb089720-1c5c-11e3-b773-0800200c9a66)]
+interface nsINotificationStorageCallback : nsISupports
+{
+  /**
+   * Callback function used to pass single notification back
+   * into C++ land for Notification.get return data.
+   *
+   * @param id: a uuid for this notification
+   * @param title: the notification title
+   * @param dir: the notification direction,
+   *             possible values are "ltr", "rtl", "auto"
+   * @param lang: the notification language
+   * @param body: the notification body
+   * @param tag: the notification tag
+   */
+  [implicit_jscontext]
+  void handle(in DOMString id,
+              in DOMString title,
+              in DOMString dir,
+              in DOMString lang,
+              in DOMString body,
+              in DOMString tag,
+              in DOMString icon);
+
+  /**
+   * Callback function used to notify C++ the we have returned
+   * all notification objects for this Notification.get call.
+   */
+  [implicit_jscontext]
+  void done();
+};
+
+/**
+ * Interface for notification persistence layer.
+ */
+[scriptable, uuid(b177b080-2a23-11e3-8224-0800200c9a66)]
+interface nsINotificationStorage : nsISupports
+{
+
+  /**
+   * Add/replace a notification to the persistence layer.
+   *
+   * @param origin: the origin/app of this notification
+   * @param id: a uuid for this notification
+   * @param title: the notification title
+   * @param dir: the notification direction,
+   *             possible values are "ltr", "rtl", "auto"
+   * @param lang: the notification language
+   * @param body: the notification body
+   * @param tag: notification tag, will replace any existing
+   *             notifications with same origin/tag pair
+   */
+  void put(in DOMString origin,
+           in DOMString id,
+           in DOMString title,
+           in DOMString dir,
+           in DOMString lang,
+           in DOMString body,
+           in DOMString tag,
+           in DOMString icon);
+
+  /**
+   * Retrieve a list of notifications.
+   *
+   * @param origin: the origin/app for which to fetch notifications from
+   * @param tag: used to fetch only a specific tag
+   * @param callback: nsINotificationStorageCallback, used for
+   *                  returning notifications objects
+   */
+  void get(in DOMString origin,
+           in DOMString tag,
+           in nsINotificationStorageCallback aCallback);
+
+  /**
+   * Remove a notification from storage.
+   *
+   * @param origin: the origin/app to delete the notification from
+   * @param id: the uuid for the notification to delete
+   */
+  void delete(in DOMString origin,
+              in DOMString id);
+};
+
+%{C++
+#define NS_NOTIFICATION_STORAGE_CONTRACTID "@mozilla.org/notificationStorage;1"
+%}
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -184,16 +184,30 @@ Promise::EnabledForScope(JSContext* aCx,
     return workers::GetWorkerPrivateFromContext(aCx)->IsChromeWorker();
   }
 
   nsIPrincipal* prin = nsContentUtils::GetSubjectPrincipal();
   return nsContentUtils::IsSystemPrincipal(prin) ||
     prin->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
 }
 
+void
+Promise::MaybeResolve(JSContext* aCx,
+                      const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  MaybeResolveInternal(aCx, aValue);
+}
+
+void
+Promise::MaybeReject(JSContext* aCx,
+                     const Optional<JS::Handle<JS::Value> >& aValue)
+{
+  MaybeRejectInternal(aCx, aValue);
+}
+
 static void
 EnterCompartment(Maybe<JSAutoCompartment>& aAc, JSContext* aCx,
                  const Optional<JS::Handle<JS::Value> >& aValue)
 {
   // FIXME Bug 878849
   if (aValue.WasPassed() && aValue.Value().isObject()) {
     JS::Rooted<JSObject*> rooted(aCx, &aValue.Value().toObject());
     aAc.construct(aCx, rooted);
@@ -224,19 +238,19 @@ Promise::JSCallback(JSContext *aCx, unsi
   if (aArgc) {
     value.Value() = args[0];
   }
 
   v = js::GetFunctionNativeReserved(&args.callee(), SLOT_TASK);
   PromiseCallback::Task task = static_cast<PromiseCallback::Task>(v.toInt32());
 
   if (task == PromiseCallback::Resolve) {
-    promise->MaybeResolve(aCx, value);
+    promise->MaybeResolveInternal(aCx, value);
   } else {
-    promise->MaybeReject(aCx, value);
+    promise->MaybeRejectInternal(aCx, value);
   }
 
   return true;
 }
 
 /* static */ JSObject*
 Promise::CreateFunction(JSContext* aCx, JSObject* aParent, Promise* aPromise,
                         int32_t aTask)
@@ -295,17 +309,17 @@ Promise::Constructor(const GlobalObject&
   aRv.WouldReportJSException();
 
   if (aRv.IsJSException()) {
     Optional<JS::Handle<JS::Value> > value(cx);
     aRv.StealJSException(cx, &value.Value());
 
     Maybe<JSAutoCompartment> ac;
     EnterCompartment(ac, cx, value);
-    promise->MaybeReject(cx, value);
+    promise->MaybeRejectInternal(cx, value);
   }
 
   return promise.forget();
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Resolve(const GlobalObject& aGlobal, JSContext* aCx,
                  JS::Handle<JS::Value> aValue, ErrorResult& aRv)
@@ -314,34 +328,34 @@ Promise::Resolve(const GlobalObject& aGl
   if (!window) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = new Promise(window);
 
   Optional<JS::Handle<JS::Value> > value(aCx, aValue);
-  promise->MaybeResolve(aCx, value);
+  promise->MaybeResolveInternal(aCx, value);
   return promise.forget();
 }
 
 /* static */ already_AddRefed<Promise>
 Promise::Reject(const GlobalObject& aGlobal, JSContext* aCx,
                 JS::Handle<JS::Value> aValue, ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsRefPtr<Promise> promise = new Promise(window);
 
   Optional<JS::Handle<JS::Value> > value(aCx, aValue);
-  promise->MaybeReject(aCx, value);
+  promise->MaybeRejectInternal(aCx, value);
   return promise.forget();
 }
 
 already_AddRefed<Promise>
 Promise::Then(const Optional<OwningNonNull<AnyCallback> >& aResolveCallback,
               const Optional<OwningNonNull<AnyCallback> >& aRejectCallback)
 {
   nsRefPtr<Promise> promise = new Promise(GetParentObject());
@@ -436,31 +450,31 @@ Promise::MaybeReportRejected()
     new AsyncErrorReporter(JS_GetObjectRuntime(&mResult.toObject()),
                            report,
                            nullptr,
                            nsContentUtils::GetObjectPrincipal(&mResult.toObject()),
                            win));
 }
 
 void
-Promise::MaybeResolve(JSContext* aCx,
-                      const Optional<JS::Handle<JS::Value> >& aValue,
-                      PromiseTaskSync aAsynchronous)
+Promise::MaybeResolveInternal(JSContext* aCx,
+                              const Optional<JS::Handle<JS::Value> >& aValue,
+                              PromiseTaskSync aAsynchronous)
 {
   if (mResolvePending) {
     return;
   }
 
   ResolveInternal(aCx, aValue, aAsynchronous);
 }
 
 void
-Promise::MaybeReject(JSContext* aCx,
-                     const Optional<JS::Handle<JS::Value> >& aValue,
-                     PromiseTaskSync aAsynchronous)
+Promise::MaybeRejectInternal(JSContext* aCx,
+                             const Optional<JS::Handle<JS::Value> >& aValue,
+                             PromiseTaskSync aAsynchronous)
 {
   if (mResolvePending) {
     return;
   }
 
   RejectInternal(aCx, aValue, aAsynchronous);
 }
 
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -38,16 +38,21 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Promise)
 
   Promise(nsPIDOMWindow* aWindow);
   ~Promise();
 
   static bool PrefEnabled();
   static bool EnabledForScope(JSContext* aCx, JSObject* /* unused */);
 
+  void MaybeResolve(JSContext* aCx,
+                    const Optional<JS::Handle<JS::Value> >& aValue);
+  void MaybeReject(JSContext* aCx,
+                   const Optional<JS::Handle<JS::Value> >& aValue);
+
   // WebIDL
 
   nsPIDOMWindow* GetParentObject() const
   {
     return mWindow;
   }
 
   virtual JSObject*
@@ -109,22 +114,22 @@ private:
 
   void AppendCallbacks(PromiseCallback* aResolveCallback,
                        PromiseCallback* aRejectCallback);
 
   // If we have been rejected and our mResult is a JS exception,
   // report it to the error console.
   void MaybeReportRejected();
 
-  void MaybeResolve(JSContext* aCx,
-                    const Optional<JS::Handle<JS::Value> >& aValue,
-                    PromiseTaskSync aSync = AsyncTask);
-  void MaybeReject(JSContext* aCx,
-                   const Optional<JS::Handle<JS::Value> >& aValue,
-                   PromiseTaskSync aSync = AsyncTask);
+  void MaybeResolveInternal(JSContext* aCx,
+                            const Optional<JS::Handle<JS::Value> >& aValue,
+                            PromiseTaskSync aSync = AsyncTask);
+  void MaybeRejectInternal(JSContext* aCx,
+                           const Optional<JS::Handle<JS::Value> >& aValue,
+                           PromiseTaskSync aSync = AsyncTask);
 
   void ResolveInternal(JSContext* aCx,
                        const Optional<JS::Handle<JS::Value> >& aValue,
                        PromiseTaskSync aSync = AsyncTask);
 
   void RejectInternal(JSContext* aCx,
                       const Optional<JS::Handle<JS::Value> >& aValue,
                       PromiseTaskSync aSync = AsyncTask);
--- a/dom/src/notification/Notification.cpp
+++ b/dom/src/notification/Notification.cpp
@@ -1,37 +1,153 @@
 /* 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/. */
 
 #include "PCOMContentPermissionRequestChild.h"
 #include "mozilla/dom/Notification.h"
 #include "mozilla/dom/AppNotificationServiceOptionsBinding.h"
 #include "mozilla/dom/OwningNonNull.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/Preferences.h"
 #include "TabChild.h"
 #include "nsContentUtils.h"
 #include "nsDOMEvent.h"
 #include "nsIAlertsService.h"
+#include "nsIAppsService.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsIDocument.h"
+#include "nsINotificationStorage.h"
 #include "nsIPermissionManager.h"
+#include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsToolkitCompsCID.h"
 #include "nsGlobalWindow.h"
 #include "nsDOMJSUtils.h"
 #include "nsIScriptSecurityManager.h"
 #ifdef MOZ_B2G
 #include "nsIDOMDesktopNotification.h"
-#include "nsIAppsService.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 
+class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(NotificationStorageCallback)
+
+  NotificationStorageCallback(const GlobalObject& aGlobal, nsPIDOMWindow* aWindow, Promise* aPromise)
+    : mCount(0),
+      mGlobal(aGlobal.Get()),
+      mWindow(aWindow),
+      mPromise(aPromise)
+  {
+    MOZ_ASSERT(aWindow);
+    MOZ_ASSERT(aPromise);
+    JSContext* cx = aGlobal.GetContext();
+    JSAutoCompartment ac(cx, mGlobal);
+    mNotifications = JS_NewArrayObject(cx, 0, nullptr);
+    HoldData();
+  }
+
+  NS_IMETHOD Handle(const nsAString& aID,
+                    const nsAString& aTitle,
+                    const nsAString& aDir,
+                    const nsAString& aLang,
+                    const nsAString& aBody,
+                    const nsAString& aTag,
+                    const nsAString& aIcon,
+                    JSContext* aCx)
+  {
+    MOZ_ASSERT(!aID.IsEmpty());
+    MOZ_ASSERT(!aTitle.IsEmpty());
+
+    NotificationOptions options;
+    options.mDir = Notification::StringToDirection(nsString(aDir));
+    options.mLang = aLang;
+    options.mBody = aBody;
+    options.mTag = aTag;
+    options.mIcon = aIcon;
+    nsRefPtr<Notification> notification = Notification::CreateInternal(mWindow,
+                                                                       aID,
+                                                                       aTitle,
+                                                                       options);
+    JSAutoCompartment ac(aCx, mGlobal);
+    JS::RootedObject scope(aCx, mGlobal);
+    JS::RootedObject element(aCx, notification->WrapObject(aCx, scope));
+    NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
+
+    if (!JS_DefineElement(aCx, mNotifications, mCount++,
+                          JS::ObjectValue(*element), nullptr, nullptr, 0)) {
+      return NS_ERROR_FAILURE;
+    }
+    return NS_OK;
+  }
+
+  NS_IMETHOD Done(JSContext* aCx)
+  {
+    JSAutoCompartment ac(aCx, mGlobal);
+    Optional<JS::HandleValue> result(aCx, JS::ObjectValue(*mNotifications));
+    mPromise->MaybeResolve(aCx, result);
+    return NS_OK;
+  }
+
+private:
+  ~NotificationStorageCallback()
+  {
+    DropData();
+  }
+
+  void HoldData()
+  {
+    mozilla::HoldJSObjects(this);
+  }
+
+  void DropData()
+  {
+    mGlobal = nullptr;
+    mNotifications = nullptr;
+    mozilla::DropJSObjects(this);
+  }
+
+  uint32_t  mCount;
+  JS::Heap<JSObject *> mGlobal;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsRefPtr<Promise> mPromise;
+  JS::Heap<JSObject *> mNotifications;
+};
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(NotificationStorageCallback)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(NotificationStorageCallback)
+NS_IMPL_CYCLE_COLLECTION_CLASS(NotificationStorageCallback)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(NotificationStorageCallback)
+  NS_INTERFACE_MAP_ENTRY(nsINotificationStorageCallback)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(NotificationStorageCallback)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mNotifications)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(NotificationStorageCallback)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(NotificationStorageCallback)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise)
+  tmp->DropData();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
 class NotificationPermissionRequest : public nsIContentPermissionRequest,
                                       public PCOMContentPermissionRequestChild,
                                       public nsIRunnable
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSICONTENTPERMISSIONREQUEST
   NS_DECL_NSIRUNNABLE
@@ -252,22 +368,25 @@ NotificationPermissionRequest::Recv__del
 
 NS_IMPL_ISUPPORTS1(NotificationTask, nsIRunnable)
 
 NS_IMETHODIMP
 NotificationTask::Run()
 {
   switch (mAction) {
   case eShow:
-    return mNotification->ShowInternal();
+    mNotification->ShowInternal();
+    break;
   case eClose:
-    return mNotification->CloseInternal();
+    mNotification->CloseInternal();
+    break;
   default:
     MOZ_CRASH("Unexpected action for NotificationTask.");
   }
+  return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS1(NotificationObserver, nsIObserver)
 
 NS_IMETHODIMP
 NotificationObserver::Observe(nsISupports* aSubject, const char* aTopic,
                               const PRUnichar* aData)
 {
@@ -278,136 +397,195 @@ NotificationObserver::Observe(nsISupport
     mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("close"));
   } else if (!strcmp("alertshow", aTopic)) {
     mNotification->DispatchTrustedEvent(NS_LITERAL_STRING("show"));
   }
 
   return NS_OK;
 }
 
-Notification::Notification(const nsAString& aTitle, const nsAString& aBody,
+Notification::Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
                            NotificationDirection aDir, const nsAString& aLang,
                            const nsAString& aTag, const nsAString& aIconUrl)
-  : mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
+  : mID(aID), mTitle(aTitle), mBody(aBody), mDir(aDir), mLang(aLang),
     mTag(aTag), mIconUrl(aIconUrl), mIsClosed(false)
 {
   SetIsDOMBinding();
 }
 
+// static
 already_AddRefed<Notification>
 Notification::Constructor(const GlobalObject& aGlobal,
                           const nsAString& aTitle,
                           const NotificationOptions& aOptions,
                           ErrorResult& aRv)
 {
-  nsString tag;
-  if (aOptions.mTag.WasPassed()) {
-    tag.Append(NS_LITERAL_STRING("tag:"));
-    tag.Append(aOptions.mTag.Value());
-  } else {
-    tag.Append(NS_LITERAL_STRING("notag:"));
-    tag.AppendInt(sCount++);
-  }
-
-  nsRefPtr<Notification> notification = new Notification(aTitle,
-                                                         aOptions.mBody,
-                                                         aOptions.mDir,
-                                                         aOptions.mLang,
-                                                         tag,
-                                                         aOptions.mIcon);
-
+  MOZ_ASSERT(NS_IsMainThread());
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
   MOZ_ASSERT(window, "Window should not be null.");
-  notification->BindToOwner(window);
+  nsRefPtr<Notification> notification = CreateInternal(window,
+                                                       EmptyString(),
+                                                       aTitle,
+                                                       aOptions);
 
   // Queue a task to show the notification.
   nsCOMPtr<nsIRunnable> showNotificationTask =
     new NotificationTask(notification, NotificationTask::eShow);
-  NS_DispatchToMainThread(showNotificationTask);
+  NS_DispatchToCurrentThread(showNotificationTask);
+
+  // Persist the notification.
+  nsresult rv;
+  nsCOMPtr<nsINotificationStorage> notificationStorage =
+    do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  nsString origin;
+  aRv = GetOrigin(window, origin);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsString id;
+  notification->GetID(id);
+  aRv = notificationStorage->Put(origin,
+                                 id,
+                                 aTitle,
+                                 DirectionToString(aOptions.mDir),
+                                 aOptions.mLang,
+                                 aOptions.mBody,
+                                 aOptions.mTag,
+                                 aOptions.mIcon);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
 
   return notification.forget();
 }
 
-nsresult
+already_AddRefed<Notification>
+Notification::CreateInternal(nsPIDOMWindow* aWindow,
+                             const nsAString& aID,
+                             const nsAString& aTitle,
+                             const NotificationOptions& aOptions)
+{
+  nsString id;
+  if (!aID.IsEmpty()) {
+    id = aID;
+  } else {
+    nsCOMPtr<nsIUUIDGenerator> uuidgen =
+      do_GetService("@mozilla.org/uuid-generator;1");
+    NS_ENSURE_TRUE(uuidgen, nullptr);
+    nsID uuid;
+    nsresult rv = uuidgen->GenerateUUIDInPlace(&uuid);
+    NS_ENSURE_SUCCESS(rv, nullptr);
+
+    char buffer[NSID_LENGTH];
+    uuid.ToProvidedString(buffer);
+    NS_ConvertASCIItoUTF16 convertedID(buffer);
+    id = convertedID;
+  }
+
+  nsRefPtr<Notification> notification = new Notification(id,
+                                                         aTitle,
+                                                         aOptions.mBody,
+                                                         aOptions.mDir,
+                                                         aOptions.mLang,
+                                                         aOptions.mTag,
+                                                         aOptions.mIcon);
+
+  notification->BindToOwner(aWindow);
+  return notification.forget();
+}
+
+void
 Notification::ShowInternal()
 {
   nsCOMPtr<nsIAlertsService> alertService =
     do_GetService(NS_ALERTSERVICE_CONTRACTID);
 
   ErrorResult result;
   if (GetPermissionInternal(GetOwner(), result) !=
     NotificationPermission::Granted || !alertService) {
     // We do not have permission to show a notification or alert service
     // is not available.
-    return DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+    DispatchTrustedEvent(NS_LITERAL_STRING("error"));
+    return;
   }
 
   nsresult rv;
   nsAutoString absoluteUrl;
   if (mIconUrl.Length() > 0) {
     // Resolve image URL against document base URI.
     nsIDocument* doc = GetOwner()->GetExtantDoc();
-    NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
-    nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
-    NS_ENSURE_TRUE(baseUri, NS_ERROR_UNEXPECTED);
-    nsCOMPtr<nsIURI> srcUri;
-    rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
-                                                   mIconUrl, doc, baseUri);
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (srcUri) {
-      nsAutoCString src;
-      srcUri->GetSpec(src);
-      absoluteUrl = NS_ConvertUTF8toUTF16(src);
+    if (doc) {
+      nsCOMPtr<nsIURI> baseUri = doc->GetBaseURI();
+      if (baseUri) {
+        nsCOMPtr<nsIURI> srcUri;
+        rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(srcUri),
+                                                       mIconUrl, doc, baseUri);
+        if (NS_SUCCEEDED(rv)) {
+          nsAutoCString src;
+          srcUri->GetSpec(src);
+          absoluteUrl = NS_ConvertUTF8toUTF16(src);
+        }
+      }
+
     }
   }
 
   nsCOMPtr<nsIObserver> observer = new NotificationObserver(this);
 
   nsString alertName;
   rv = GetAlertName(alertName);
-  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_SUCCESS_VOID(rv);
 
 #ifdef MOZ_B2G
   nsCOMPtr<nsIAppNotificationService> appNotifier =
     do_GetService("@mozilla.org/system-alerts-service;1");
   if (appNotifier) {
     nsCOMPtr<nsPIDOMWindow> window = GetOwner();
     uint32_t appId = (window.get())->GetDoc()->NodePrincipal()->GetAppId();
 
     if (appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
       nsCOMPtr<nsIAppsService> appsService = do_GetService("@mozilla.org/AppsService;1");
       nsString manifestUrl = EmptyString();
-      appsService->GetManifestURLByLocalId(appId, manifestUrl);
-      mozilla::AutoSafeJSContext cx;
-      JS::RootedValue val(cx);
-      AppNotificationServiceOptions ops;
-      ops.mTextClickable = true;
-      ops.mManifestURL = manifestUrl;
-      ops.mId = alertName;
-      ops.mDir = DirectionToString(mDir);
-      ops.mLang = mLang;
+      rv = appsService->GetManifestURLByLocalId(appId, manifestUrl);
+      if (NS_SUCCEEDED(rv)) {
+        mozilla::AutoSafeJSContext cx;
+        JS::RootedValue val(cx);
+        AppNotificationServiceOptions ops;
+        ops.mTextClickable = true;
+        ops.mManifestURL = manifestUrl;
+        ops.mId = alertName;
+        ops.mDir = DirectionToString(mDir);
+        ops.mLang = mLang;
 
-      if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
-        NS_WARNING("Converting dict to object failed!");
-        return NS_ERROR_FAILURE;
+        if (!ops.ToObject(cx, JS::NullPtr(), &val)) {
+          NS_WARNING("Converting dict to object failed!");
+          return;
+        }
+
+        appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
+                                         observer, val);
+        return;
       }
-
-      return appNotifier->ShowAppNotification(mIconUrl, mTitle, mBody,
-                                              observer, val);
     }
   }
 #endif
 
   // In the case of IPC, the parent process uses the cookie to map to
   // nsIObserver. Thus the cookie must be unique to differentiate observers.
   nsString uniqueCookie = NS_LITERAL_STRING("notification:");
   uniqueCookie.AppendInt(sCount++);
-  return alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
-                                             uniqueCookie, observer, alertName,
-                                             DirectionToString(mDir), mLang);
+  alertService->ShowAlertNotification(absoluteUrl, mTitle, mBody, true,
+                                      uniqueCookie, observer, alertName,
+                                      DirectionToString(mDir), mLang);
 }
 
 void
 Notification::RequestPermission(const GlobalObject& aGlobal,
                                 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
                                 ErrorResult& aRv)
 {
   // Get principal from global to make permission request for notifications.
@@ -485,16 +663,57 @@ Notification::GetPermissionInternal(nsIS
     return NotificationPermission::Granted;
   case nsIPermissionManager::DENY_ACTION:
     return NotificationPermission::Denied;
   default:
     return NotificationPermission::Default;
   }
 }
 
+already_AddRefed<Promise>
+Notification::Get(const GlobalObject& aGlobal,
+                  const GetNotificationOptions& aFilter,
+                  ErrorResult& aRv)
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.GetAsSupports());
+  MOZ_ASSERT(window);
+  nsIDocument* doc = window->GetExtantDoc();
+  if (!doc) {
+    aRv.Throw(NS_ERROR_UNEXPECTED);
+    return nullptr;
+  }
+
+  nsString origin;
+  aRv = GetOrigin(window, origin);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsINotificationStorage> notificationStorage =
+    do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID, &rv);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  nsRefPtr<Promise> promise = new Promise(window);
+  nsCOMPtr<nsINotificationStorageCallback> callback =
+    new NotificationStorageCallback(aGlobal, window, promise);
+  nsString tag = aFilter.mTag.WasPassed() ?
+                 aFilter.mTag.Value() :
+                 EmptyString();
+  aRv = notificationStorage->Get(origin, tag, callback);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
 bool
 Notification::PrefEnabled()
 {
   return Preferences::GetBool("dom.webnotifications.enabled", false);
 }
 
 JSObject*
 Notification::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
@@ -506,51 +725,88 @@ void
 Notification::Close()
 {
   // Queue a task to close the notification.
   nsCOMPtr<nsIRunnable> showNotificationTask =
     new NotificationTask(this, NotificationTask::eClose);
   NS_DispatchToMainThread(showNotificationTask);
 }
 
-nsresult
+void
 Notification::CloseInternal()
 {
   if (!mIsClosed) {
+    nsresult rv;
+    // Don't bail out if notification storage fails, since we still
+    // want to send the close event through the alert service.
+    nsCOMPtr<nsINotificationStorage> notificationStorage =
+      do_GetService(NS_NOTIFICATION_STORAGE_CONTRACTID);
+    if (notificationStorage) {
+      nsString origin;
+      rv = GetOrigin(GetOwner(), origin);
+      if (NS_SUCCEEDED(rv)) {
+        notificationStorage->Delete(origin, mID);
+      }
+    }
+
     nsCOMPtr<nsIAlertsService> alertService =
       do_GetService(NS_ALERTSERVICE_CONTRACTID);
-
     if (alertService) {
       nsString alertName;
-      nsresult rv = GetAlertName(alertName);
-      NS_ENSURE_SUCCESS(rv, rv);
+      rv = GetAlertName(alertName);
+      if (NS_SUCCEEDED(rv)) {
+        alertService->CloseAlert(alertName);
+      }
+    }
+  }
+}
 
-      rv = alertService->CloseAlert(alertName);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
+nsresult
+Notification::GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin)
+{
+  MOZ_ASSERT(aWindow);
+  nsresult rv;
+  nsIDocument* doc = aWindow->GetExtantDoc();
+  NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
+  nsIPrincipal* principal = doc->NodePrincipal();
+  NS_ENSURE_TRUE(principal, NS_ERROR_UNEXPECTED);
+
+  uint16_t appStatus = principal->GetAppStatus();
+  uint32_t appId = principal->GetAppId();
+
+  if (appStatus == nsIPrincipal::APP_STATUS_NOT_INSTALLED ||
+      appId == nsIScriptSecurityManager::NO_APP_ID ||
+      appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    rv = nsContentUtils::GetUTFOrigin(principal, aOrigin);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    // If we are in "app code", use manifest URL as unique origin since
+    // multiple apps can share the same origin but not same notifications.
+    nsCOMPtr<nsIAppsService> appsService =
+      do_GetService("@mozilla.org/AppsService;1", &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
+    appsService->GetManifestURLByLocalId(appId, aOrigin);
   }
 
   return NS_OK;
 }
 
 nsresult
 Notification::GetAlertName(nsString& aAlertName)
 {
-  // Get the notification name that is unique per origin + tag.
-  // The name of the alert is of the form origin#tag
-
-  nsPIDOMWindow* owner = GetOwner();
-  NS_ENSURE_TRUE(owner, NS_ERROR_UNEXPECTED);
-
-  nsIDocument* doc = owner->GetExtantDoc();
-  NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
-
-  nsresult rv = nsContentUtils::GetUTFOrigin(doc->NodePrincipal(),
-                                             aAlertName);
+  // Get the notification name that is unique per origin + tag/ID.
+  // The name of the alert is of the form origin#tag/ID.
+  nsresult rv = GetOrigin(GetOwner(), aAlertName);
   NS_ENSURE_SUCCESS(rv, rv);
   aAlertName.AppendLiteral("#");
-  aAlertName.Append(mTag);
+  if (!mTag.IsEmpty()) {
+    aAlertName.Append(NS_LITERAL_STRING("tag:"));
+    aAlertName.Append(mTag);
+  } else {
+    aAlertName.Append(NS_LITERAL_STRING("notag:"));
+    aAlertName.Append(mID);
+  }
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/src/notification/Notification.h
+++ b/dom/src/notification/Notification.h
@@ -5,111 +5,142 @@
 #ifndef mozilla_dom_notification_h__
 #define mozilla_dom_notification_h__
 
 #include "mozilla/dom/NotificationBinding.h"
 
 #include "nsDOMEventTargetHelper.h"
 #include "nsIObserver.h"
 
+#include "nsCycleCollectionParticipant.h"
+
 namespace mozilla {
 namespace dom {
 
+
 class NotificationObserver;
+class Promise;
 
 class Notification : public nsDOMEventTargetHelper
 {
   friend class NotificationTask;
   friend class NotificationPermissionRequest;
   friend class NotificationObserver;
+  friend class NotificationStorageCallback;
+
 public:
   IMPL_EVENT_HANDLER(click)
   IMPL_EVENT_HANDLER(show)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(close)
 
-  Notification(const nsAString& aTitle, const nsAString& aBody,
-               NotificationDirection aDir, const nsAString& aLang,
-               const nsAString& aTag, const nsAString& aIconUrl);
-
   static already_AddRefed<Notification> Constructor(const GlobalObject& aGlobal,
                                                     const nsAString& aTitle,
                                                     const NotificationOptions& aOption,
                                                     ErrorResult& aRv);
-  void GetTitle(nsString& aRetval)
+  void GetID(nsAString& aRetval) {
+    aRetval = mID;
+  }
+
+  void GetTitle(nsAString& aRetval)
   {
     aRetval = mTitle;
   }
 
   NotificationDirection Dir()
   {
     return mDir;
   }
 
-  void GetLang(nsString& aRetval)
+  void GetLang(nsAString& aRetval)
   {
     aRetval = mLang;
   }
 
-  void GetBody(nsString& aRetval)
+  void GetBody(nsAString& aRetval)
   {
     aRetval = mBody;
   }
 
-  void GetTag(nsString& aRetval)
+  void GetTag(nsAString& aRetval)
   {
-    if (StringBeginsWith(mTag, NS_LITERAL_STRING("tag:"))) {
-      aRetval = Substring(mTag, 4);
-    }
+    aRetval = mTag;
   }
 
-  void GetIcon(nsString& aRetval)
+  void GetIcon(nsAString& aRetval)
   {
     aRetval = mIconUrl;
   }
 
   static void RequestPermission(const GlobalObject& aGlobal,
                                 const Optional<OwningNonNull<NotificationPermissionCallback> >& aCallback,
                                 ErrorResult& aRv);
 
   static NotificationPermission GetPermission(const GlobalObject& aGlobal,
                                               ErrorResult& aRv);
 
+  static already_AddRefed<Promise> Get(const GlobalObject& aGlobal,
+                                       const GetNotificationOptions& aFilter,
+                                       ErrorResult& aRv);
+
   void Close();
 
   static bool PrefEnabled();
 
   nsPIDOMWindow* GetParentObject()
   {
     return GetOwner();
   }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
 protected:
-  nsresult ShowInternal();
-  nsresult CloseInternal();
+  Notification(const nsAString& aID, const nsAString& aTitle, const nsAString& aBody,
+               NotificationDirection aDir, const nsAString& aLang,
+               const nsAString& aTag, const nsAString& aIconUrl);
+
+  static already_AddRefed<Notification> CreateInternal(nsPIDOMWindow* aWindow,
+                                                       const nsAString& aID,
+                                                       const nsAString& aTitle,
+                                                       const NotificationOptions& aOptions);
+
+  void ShowInternal();
+  void CloseInternal();
 
   static NotificationPermission GetPermissionInternal(nsISupports* aGlobal,
                                                       ErrorResult& rv);
 
   static const nsString DirectionToString(NotificationDirection aDirection)
   {
     switch (aDirection) {
     case NotificationDirection::Ltr:
       return NS_LITERAL_STRING("ltr");
     case NotificationDirection::Rtl:
       return NS_LITERAL_STRING("rtl");
     default:
       return NS_LITERAL_STRING("auto");
     }
   }
 
+  static const NotificationDirection StringToDirection(const nsAString& aDirection)
+  {
+    if (aDirection.EqualsLiteral("ltr")) {
+      return NotificationDirection::Ltr;
+    }
+    if (aDirection.EqualsLiteral("rtl")) {
+      return NotificationDirection::Rtl;
+    }
+    return NotificationDirection::Auto;
+  }
+
+  static nsresult GetOrigin(nsPIDOMWindow* aWindow, nsString& aOrigin);
+
   nsresult GetAlertName(nsString& aAlertName);
 
+  nsString mID;
   nsString mTitle;
   nsString mBody;
   NotificationDirection mDir;
   nsString mLang;
   nsString mTag;
   nsString mIconUrl;
 
   bool mIsClosed;
new file mode 100644
--- /dev/null
+++ b/dom/src/notification/NotificationDB.jsm
@@ -0,0 +1,270 @@
+/* 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";
+
+this.EXPORTED_SYMBOLS = [];
+
+const DEBUG = false;
+function debug(s) { dump("-*- NotificationDB component: " + s + "\n"); }
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+
+XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
+                                   "@mozilla.org/parentprocessmessagemanager;1",
+                                   "nsIMessageListenerManager");
+
+XPCOMUtils.defineLazyGetter(this, "gEncoder", function() {
+  return new TextEncoder();
+});
+
+XPCOMUtils.defineLazyGetter(this, "gDecoder", function() {
+  return new TextDecoder();
+});
+
+
+const NOTIFICATION_STORE_DIR = OS.Constants.Path.profileDir;
+const NOTIFICATION_STORE_PATH =
+        OS.Path.join(NOTIFICATION_STORE_DIR, "notificationstore.json");
+
+let NotificationDB = {
+  init: function() {
+    this.notifications = {};
+    this.byTag = {};
+    this.loaded = false;
+
+    this.tasks = []; // read/write operation queue
+    this.runningTask = false;
+
+    ppmm.addMessageListener("Notification:Save", this);
+    ppmm.addMessageListener("Notification:Delete", this);
+    ppmm.addMessageListener("Notification:GetAll", this);
+  },
+
+  // Attempt to read notification file, if it's not there we will create it.
+  load: function(callback) {
+    var promise = OS.File.read(NOTIFICATION_STORE_PATH);
+    promise.then(
+      function onSuccess(data) {
+        try {
+          this.notifications = JSON.parse(gDecoder.decode(data));
+        } catch (e) {
+          if (DEBUG) { debug("Unable to parse file data " + e); }
+        }
+        this.loaded = true;
+        callback && callback();
+      }.bind(this),
+
+      // If read failed, we assume we have no notifications to load.
+      function onFailure(reason) {
+        this.loaded = true;
+        this.createStore(callback);
+      }.bind(this)
+    );
+  },
+
+  // Creates the notification directory.
+  createStore: function(callback) {
+    var promise = OS.File.makeDir(NOTIFICATION_STORE_DIR, {
+      ignoreExisting: true
+    });
+    promise.then(
+      function onSuccess() {
+        this.createFile(callback);
+      }.bind(this),
+
+      function onFailure(reason) {
+        if (DEBUG) { debug("Directory creation failed:" + reason); }
+        callback && callback();
+      }
+    );
+  },
+
+  // Creates the notification file once the directory is created.
+  createFile: function(callback) {
+    var promise = OS.File.open(NOTIFICATION_STORE_PATH, {create: true});
+    promise.then(
+      function onSuccess(handle) {
+        callback && callback();
+      },
+      function onFailure(reason) {
+        if (DEBUG) { debug("File creation failed:" + reason); }
+        callback && callback();
+      }
+    );
+  },
+
+  // Save current notifications to the file.
+  save: function(callback) {
+    var data = gEncoder.encode(JSON.stringify(this.notifications));
+    var promise = OS.File.writeAtomic(NOTIFICATION_STORE_PATH, data);
+    promise.then(
+      function onSuccess() {
+        callback && callback();
+      },
+      function onFailure(reason) {
+        if (DEBUG) { debug("Save failed:" + reason); }
+        callback && callback();
+      }
+    );
+  },
+
+  // Helper function: callback will be called once file exists and/or is loaded.
+  ensureLoaded: function(callback) {
+    if (!this.loaded) {
+      this.load(callback);
+    } else {
+      callback();
+    }
+  },
+
+  receiveMessage: function(message) {
+    if (DEBUG) { debug("Received message:" + message.name); }
+
+    switch (message.name) {
+      case "Notification:GetAll":
+        this.queueTask("getall", message.data, function(notifications) {
+          message.target.sendAsyncMessage("Notification:GetAll:Return:OK", {
+            requestID: message.data.requestID,
+            notifications: notifications
+          });
+        });
+        break;
+
+      case "Notification:Save":
+        this.queueTask("save", message.data, function() {
+          message.target.sendAsyncMessage("Notification:Save:Return:OK", {
+            requestID: message.data.requestID
+          });
+        });
+        break;
+
+      case "Notification:Delete":
+        this.queueTask("delete", message.data, function() {
+          message.target.sendAsyncMessage("Notification:Delete:Return:OK", {
+            requestID: message.data.requestID
+          });
+        });
+        break;
+
+      default:
+        if (DEBUG) { debug("Invalid message name" + message.name); }
+    }
+  },
+
+  // We need to make sure any read/write operations are atomic,
+  // so use a queue to run each operation sequentially.
+  queueTask: function(operation, data, callback) {
+    if (DEBUG) { debug("Queueing task: " + operation); }
+    this.tasks.push({
+      operation: operation,
+      data: data,
+      callback: callback
+    });
+
+    // Only run immediately if we aren't currently running another task.
+    if (!this.runningTask) {
+      if (DEBUG) { dump("Task queue was not running, starting now..."); }
+      this.runNextTask();
+    }
+  },
+
+  runNextTask: function() {
+    if (this.tasks.length === 0) {
+      if (DEBUG) { dump("No more tasks to run, queue depleted"); }
+      this.runningTask = false;
+      return;
+    }
+    this.runningTask = true;
+
+    // Always make sure we are loaded before performing any read/write tasks.
+    this.ensureLoaded(function() {
+      var task = this.tasks.shift();
+
+      // Wrap the task callback to make sure we immediately
+      // run the next task after running the original callback.
+      var wrappedCallback = function() {
+        if (DEBUG) { debug("Finishing task: " + task.operation); }
+        task.callback.apply(this, arguments);
+        this.runNextTask();
+      }.bind(this);
+
+      switch (task.operation) {
+        case "getall":
+          this.taskGetAll(task.data, wrappedCallback);
+          break;
+
+        case "save":
+          this.taskSave(task.data, wrappedCallback);
+          break;
+
+        case "delete":
+          this.taskDelete(task.data, wrappedCallback);
+          break;
+      }
+    }.bind(this));
+  },
+
+  taskGetAll: function(data, callback) {
+    if (DEBUG) { debug("Task, getting all"); }
+    var origin = data.origin;
+    var notifications = [];
+    // Grab only the notifications for specified origin.
+    for (var i in this.notifications[origin]) {
+      notifications.push(this.notifications[origin][i]);
+    }
+    callback(notifications);
+  },
+
+  taskSave: function(data, callback) {
+    if (DEBUG) { debug("Task, saving"); }
+    var origin = data.origin;
+    var notification = data.notification;
+    if (!this.notifications[origin]) {
+      this.notifications[origin] = {};
+      this.byTag[origin] = {};
+    }
+
+    // We might have existing notification with this tag,
+    // if so we need to remove it before saving the new one.
+    if (notification.tag && this.byTag[origin][notification.tag]) {
+      var oldNotification = this.byTag[origin][notification.tag];
+      delete this.notifications[origin][oldNotification.id];
+      this.byTag[origin][notification.tag] = notification;
+    }
+
+    this.notifications[origin][notification.id] = notification;
+    this.save(callback);
+  },
+
+  taskDelete: function(data, callback) {
+    if (DEBUG) { debug("Task, deleting"); }
+    var origin = data.origin;
+    var id = data.id;
+    if (!this.notifications[origin]) {
+      if (DEBUG) { debug("No notifications found for origin: " + origin); }
+      return;
+    }
+
+    // Make sure we can find the notification to delete.
+    var oldNotification = this.notifications[origin][id];
+    if (!oldNotification) {
+      if (DEBUG) { debug("No notification found with id: " + id); }
+      return;
+    }
+
+    if (oldNotification.tag) {
+      delete this.byTag[origin][oldNotification.tag];
+    }
+    delete this.notifications[origin][id];
+    this.save(callback);
+  }
+};
+
+NotificationDB.init();
new file mode 100644
--- /dev/null
+++ b/dom/src/notification/NotificationStorage.js
@@ -0,0 +1,174 @@
+/* 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 DEBUG = false;
+function debug(s) { dump("-*- NotificationStorage.js: " + s + "\n"); }
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const NOTIFICATIONSTORAGE_CID = "{37f819b0-0b5c-11e3-8ffd-0800200c9a66}";
+const NOTIFICATIONSTORAGE_CONTRACTID = "@mozilla.org/notificationStorage;1";
+
+XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
+                                   "@mozilla.org/childprocessmessagemanager;1",
+                                   "nsIMessageSender");
+
+
+function NotificationStorage() {
+  // cache objects
+  this._notifications = {};
+  this._byTag = {};
+  this._cached = false;
+
+  this._requests = {};
+  this._requestCount = 0;
+
+  // Register for message listeners.
+  cpmm.addMessageListener("Notification:GetAll:Return:OK", this);
+}
+
+NotificationStorage.prototype = {
+
+  put: function(origin, id, title, dir, lang, body, tag, icon) {
+    if (DEBUG) { debug("PUT: " + id + ": " + title); }
+    var notification = {
+      id: id,
+      title: title,
+      dir: dir,
+      lang: lang,
+      body: body,
+      tag: tag,
+      icon: icon
+    };
+
+    this._notifications[id] = notification;
+    if (tag) {
+      // We might have existing notification with this tag,
+      // if so we need to remove it from our cache.
+      if (this._byTag[tag]) {
+        var oldNotification = this._byTag[tag];
+        delete this._notifications[oldNotification.id];
+      }
+
+      this._byTag[tag] = notification;
+    };
+
+    cpmm.sendAsyncMessage("Notification:Save", {
+      origin: origin,
+      notification: notification
+    });
+  },
+
+  get: function(origin, tag, callback) {
+    if (DEBUG) { debug("GET: " + tag); }
+    if (this._cached) {
+      this._fetchFromCache(tag, callback);
+    } else {
+      this._fetchFromDB(origin, tag, callback);
+    }
+  },
+
+  delete: function(origin, id) {
+    if (DEBUG) { debug("DELETE: " + id); }
+    var notification = this._notifications[id];
+    if (notification) {
+      if (notification.tag) {
+        delete this._byTag[notification.tag];
+      }
+      delete this._notifications[id];
+    }
+
+    cpmm.sendAsyncMessage("Notification:Delete", {
+      origin: origin,
+      id: id
+    });
+  },
+
+  receiveMessage: function(message) {
+    switch (message.name) {
+      case "Notification:GetAll:Return:OK":
+        var request = this._requests[message.data.requestID];
+        delete this._requests[message.data.requestID];
+        this._populateCache(message.data.notifications);
+        this._fetchFromCache(request.tag, request.callback);
+        break;
+
+      default:
+        if (DEBUG) debug("Unrecognized message: " + message.name);
+        break;
+    }
+  },
+
+  _fetchFromDB: function(origin, tag, callback) {
+    var request = {
+      origin: origin,
+      tag: tag,
+      callback: callback
+    };
+    var requestID = this._requestCount++;
+    this._requests[requestID] = request;
+    cpmm.sendAsyncMessage("Notification:GetAll", {
+      origin: origin,
+      requestID: requestID
+    });
+  },
+
+  _fetchFromCache: function(tag, callback) {
+    var notifications = [];
+    // If a tag was specified and we have a notification
+    // with this tag, return that. If no tag was specified
+    // simple return all stored notifications.
+    if (tag && this._byTag[tag]) {
+      notifications.push(this._byTag[tag]);
+    } else if (!tag) {
+      for (var id in this._notifications) {
+        notifications.push(this._notifications[id]);
+      }
+    }
+
+    // Pass each notification back separately.
+    notifications.forEach(function(notification) {
+      try {
+        callback.handle(notification.id,
+                        notification.title,
+                        notification.dir,
+                        notification.lang,
+                        notification.body,
+                        notification.tag,
+                        notification.icon);
+      } catch (e) {
+        if (DEBUG) { debug("Error calling callback handle: " + e); }
+      }
+    });
+    try {
+      callback.done();
+    } catch (e) {
+      if (DEBUG) { debug("Error calling callback done: " + e); }
+    }
+  },
+
+  _populateCache: function(notifications) {
+    notifications.forEach(function(notification) {
+      this._notifications[notification.id] = notification;
+      if (notification.tag) {
+        this._byTag[notification.tag] = notification;
+      }
+    }.bind(this));
+    this._cached = true;
+  },
+
+  classID : Components.ID(NOTIFICATIONSTORAGE_CID),
+  contractID : NOTIFICATIONSTORAGE_CONTRACTID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsINotificationStorage,
+                                         Ci.nsIMessageListener]),
+};
+
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([NotificationStorage]);
new file mode 100644
--- /dev/null
+++ b/dom/src/notification/NotificationStorage.manifest
@@ -0,0 +1,3 @@
+# NotificationStorage.js
+component {37f819b0-0b5c-11e3-8ffd-0800200c9a66} NotificationStorage.js
+contract @mozilla.org/notificationStorage;1 {37f819b0-0b5c-11e3-8ffd-0800200c9a66}
--- a/dom/src/notification/moz.build
+++ b/dom/src/notification/moz.build
@@ -1,16 +1,25 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 MODULE = 'dom'
 
+EXTRA_COMPONENTS += [
+    'NotificationStorage.js',
+    'NotificationStorage.manifest',
+]
+
+EXTRA_JS_MODULES += [
+    'NotificationDB.jsm'
+]
+
 EXPORTS.mozilla.dom += [
     'DesktopNotification.h',
     'Notification.h',
 ]
 
 CPP_SOURCES += [
     'DesktopNotification.cpp',
     'Notification.cpp',
--- a/dom/tests/mochitest/moz.build
+++ b/dom/tests/mochitest/moz.build
@@ -24,12 +24,8 @@ DIRS += [
     'notification',
     'webapps',
     'webcomponents',
 ]
 
 if CONFIG['MOZ_GAMEPAD']:
    DIRS += ['gamepad']
 
-#needs IPC support, also tests do not run successfully in Firefox for now
-#if CONFIG['MOZ_BUILD_APP'] != 'mobile':
-#    DIRS += ['notification']
-
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/notification/MockServices.js
@@ -0,0 +1,81 @@
+var MockServices = (function () {
+  "use strict";
+
+  const MOCK_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
+                          .ID("{48068bc2-40ab-4904-8afd-4cdfb3a385f3}");
+  const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
+
+  const MOCK_SYSTEM_ALERTS_CID = SpecialPowers.wrap(SpecialPowers.Components)
+                                 .ID("{e86d888c-e41b-4b78-9104-2f2742a532de}");
+  const SYSTEM_ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/system-alerts-service;1";
+
+  var registrar = SpecialPowers.wrap(SpecialPowers.Components).manager
+                  .QueryInterface(SpecialPowers.Ci.nsIComponentRegistrar);
+
+  var activeNotifications = Object.create(null);
+
+  var mockAlertsService = {
+    showAlertNotification: function(imageUrl, title, text, textClickable,
+                                    cookie, alertListener, name) {
+      var listener = SpecialPowers.wrap(alertListener);
+      activeNotifications[name] = {
+        listener: listener,
+        cookie: cookie
+      };
+
+      // fake async alert show event
+      setTimeout(function () {
+        listener.observe(null, "alertshow", cookie);
+      }, 100);
+
+      // ?? SpecialPowers.wrap(alertListener).observe(null, "alertclickcallback", cookie);
+    },
+
+    showAppNotification: function(imageUrl, title, text, textClickable,
+                                  manifestURL, alertListener, name) {
+      this.showAlertNotification(imageUrl, title, text, textClickable, "", alertListener, name);
+    },
+
+    closeAlert: function(name) {
+      var notification = activeNotifications[name];
+      if (notification) {
+        notification.listener.observe(null, "alertfinished", notification.cookie);
+        delete activeNotifications[name];
+      }
+    },
+
+    QueryInterface: function(aIID) {
+      if (SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsISupports) ||
+          SpecialPowers.wrap(aIID).equals(SpecialPowers.Ci.nsIAlertsService)) {
+        return this;
+      }
+      throw SpecialPowers.Components.results.NS_ERROR_NO_INTERFACE;
+    },
+
+    createInstance: function(aOuter, aIID) {
+      if (aOuter != null) {
+        throw SpecialPowers.Components.results.NS_ERROR_NO_AGGREGATION;
+      }
+      return this.QueryInterface(aIID);
+    }
+  };
+  mockAlertsService = SpecialPowers.wrapCallbackObject(mockAlertsService);
+
+  // MockServices API
+  return {
+    register: function () {
+      registrar.registerFactory(MOCK_ALERTS_CID, "alerts service",
+          ALERTS_SERVICE_CONTRACT_ID,
+          mockAlertsService);
+
+      registrar.registerFactory(MOCK_SYSTEM_ALERTS_CID, "system alerts service",
+          SYSTEM_ALERTS_SERVICE_CONTRACT_ID,
+          mockAlertsService);
+    },
+
+    unregister: function () {
+      registrar.unregisterFactory(MOCK_ALERTS_CID, mockAlertsService);
+      registrar.unregisterFactory(MOCK_SYSTEM_ALERTS_CID, mockAlertsService);
+    },
+  };
+})();
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/notification/NotificationTest.js
@@ -0,0 +1,73 @@
+var NotificationTest = (function () {
+  "use strict";
+
+  function info(msg, name) {
+    SimpleTest.info("::Notification Tests::" + (name || ""), msg);
+  }
+
+  function setup_testing_env() {
+    SimpleTest.waitForExplicitFinish();
+    // turn on testing pref (used by notification.cpp, and mock the alerts
+    SpecialPowers.setBoolPref("notification.prompt.testing", true);
+  }
+
+  function teardown_testing_env() {
+    SimpleTest.finish();
+  }
+
+  function executeTests(tests, callback) {
+    // context is `this` object in test functions
+    // it can be used to track data between tests
+    var context = {};
+
+    (function executeRemainingTests(remainingTests) {
+      if (!remainingTests.length) {
+        return callback();
+      }
+
+      var nextTest = remainingTests.shift();
+      var finishTest = executeRemainingTests.bind(null, remainingTests);
+      var startTest = nextTest.call.bind(nextTest, context, finishTest);
+
+      try {
+        startTest();
+        // if no callback was defined for test function,
+        // we must manually invoke finish to continue
+        if (nextTest.length === 0) {
+          finishTest();
+        }
+      } catch (e) {
+        ok(false, "Test threw exception!");
+        finishTest();
+      }
+    })(tests);
+  }
+
+  // NotificationTest API
+  return {
+    run: function (tests, callback) {
+      setup_testing_env();
+
+      addLoadEvent(function () {
+        executeTests(tests, function () {
+          teardown_testing_env();
+          callback && callback();
+        });
+      });
+    },
+
+    allowNotifications: function () {
+      SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
+    },
+
+    denyNotifications: function () {
+      SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
+    },
+
+    clickNotification: function (notification) {
+      // TODO: how??
+    },
+
+    info: info
+  };
+})();
rename from dom/tests/mochitest/notification/create_notification.html
rename to dom/tests/mochitest/notification/desktop-notification/create_notification.html
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/notification/desktop-notification/moz.build
@@ -0,0 +1,6 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
rename from dom/tests/mochitest/notification/notification_common.js
rename to dom/tests/mochitest/notification/desktop-notification/notification_common.js
rename from dom/tests/mochitest/notification/test_basic_notification.html
rename to dom/tests/mochitest/notification/desktop-notification/test_basic_notification.html
rename from dom/tests/mochitest/notification/test_basic_notification_click.html
rename to dom/tests/mochitest/notification/desktop-notification/test_basic_notification_click.html
rename from dom/tests/mochitest/notification/test_leak_windowClose.html
rename to dom/tests/mochitest/notification/desktop-notification/test_leak_windowClose.html
rename from dom/tests/mochitest/notification/test_notification_tag.html
rename to dom/tests/mochitest/notification/desktop-notification/test_notification_tag.html
rename from dom/tests/mochitest/notification/test_system_principal.xul
rename to dom/tests/mochitest/notification/desktop-notification/test_system_principal.xul
--- a/dom/tests/mochitest/notification/mochitest.ini
+++ b/dom/tests/mochitest/notification/mochitest.ini
@@ -1,10 +1,7 @@
 [DEFAULT]
 support-files =
-  create_notification.html
-  notification_common.js
+  MockServices.js
+  NotificationTest.js
 
-[test_basic_notification.html]
-[test_basic_notification_click.html]
-[test_leak_windowClose.html]
-[test_notification_tag.html]
-[test_web_notifications.html]
+[test_notification_basics.html]
+[test_notification_storage.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/notification/test_notification_basics.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Notification Basics</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="MockServices.js"></script>
+  <script type="text/javascript" src="NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="text/javascript">
+
+  var info = NotificationTest.info;
+
+  var steps = [
+    function () {
+      info("Test notification spec");
+      ok(Notification, "Notification constructor exists");
+      ok(Notification.permission, "Notification.permission exists");
+      ok(Notification.requestPermission, "Notification.requestPermission exists");
+      ok(Notification.get, "Notification.get exists");
+    },
+
+    function () {
+      info("Test blank requestPermission");
+      Notification.requestPermission();
+    },
+
+    function (done) {
+      info("Test requestPermission deny");
+      NotificationTest.denyNotifications();
+      Notification.requestPermission(function(perm) {
+        is(perm, "denied", "Permission should be denied.");
+        is(Notification.permission, "denied", "Permission should be denied.");
+        done();
+      });
+    },
+
+    function (done) {
+      info("Test requestPermission grant");
+      NotificationTest.allowNotifications();
+      Notification.requestPermission(function (perm) {
+        is(perm, "granted", "Permission should be granted.");
+        is(Notification.permission, "granted", "Permission should be granted");
+        done();
+      });
+    },
+
+    function () {
+      info("Test invalid requestPermission");
+      try {
+        Notification.requestPermission({});
+        ok(false, "Non callable arg to requestPermission should throw");
+      } catch (e) {
+        ok(true, "Non callable arg to requestPermission should throw");
+      }
+    },
+
+    function (done) {
+      info("Test create notification");
+
+      var options = {
+        dir: "auto",
+        lang: "",
+        body: "This is a notification body",
+        tag: "sometag",
+        icon: "icon.png"
+      };
+      var notification = new Notification("This is a title", options);
+
+      ok(notification, "Notification exists");
+      is(notification.onclick, null, "onclick() should be null");
+      is(notification.onshow, null, "onshow() should be null");
+      is(notification.onerror, null, "onerror() should be null");
+      is(notification.onclose, null, "onclose() should be null");
+      is(typeof notification.close, "function", "close() should exist");
+
+      is(notification.dir, options.dir, "auto should get set");
+      is(notification.lang, options.lang, "lang should get set");
+      is(notification.body, options.body, "body should get set");
+      is(notification.tag, options.tag, "tag should get set");
+      is(notification.icon, options.icon, "icon should get set");
+
+      // store notification in test context
+      this.notification = notification;
+
+      notification.onshow = function () {
+        ok(true, "onshow handler should be called");
+        done();
+      };
+    },
+
+    function (done) {
+      info("Test closing a notification");
+      var notification = this.notification;
+
+      notification.onclose = function () {
+        ok(true, "onclose handler should be called");
+        done();
+      };
+
+      notification.close();
+    },
+  ];
+
+  MockServices.register();
+  NotificationTest.run(steps, function () {
+    MockServices.unregister();
+  });
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/notification/test_notification_storage.html
@@ -0,0 +1,132 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Notification Basics</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="MockServices.js"></script>
+  <script type="text/javascript" src="NotificationTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script type="text/javascript">
+
+  function deleteAllNotifications() {
+    var promise = Notification.get();
+    promise.then(function (notifications) {
+      notifications.forEach(function(notification) {
+        notification.close();
+      });
+    });
+  }
+
+  var info = NotificationTest.info;
+
+  var steps = [
+    function (done) {
+      info("Test that Notifcation.get fulfills the promise");
+      var promise = Notification.get();
+      ok(promise.then, "should return a promise");
+
+      // Create a new notification to make sure
+      // Notification.get() works while creating
+      var notification = new Notification("this is a test");
+
+      promise.then(function () {
+        ok(true, "promise should be fulfilled");
+        done();
+      });
+    },
+
+    deleteAllNotifications,
+
+    function (done) {
+      info("Test adding a notification, and making sure get returns it");
+      NotificationTest.allowNotifications();
+      var options = {
+        dir: "auto",
+        lang: "",
+        body: "This is a notification body",
+        tag: "sometag",
+        icon: "icon.png"
+      };
+      var notification = new Notification("This is a title", options);
+      var promise = Notification.get();
+      promise.then(function (notifications) {
+        ok(notifications.length, "should return notifications");
+        for (var i = 0; i < notifications.length; i++) {
+          var notification = notifications[i];
+          if (notification.tag === options.tag) {
+            ok(true, "should contain newly created notification");
+            for (var key in options) {
+              is(notification[key], options[key], key + " property should match");
+            }
+            notification.close();
+            return;
+          }
+        }
+        ok(false, "should contain newly created notification");
+        notification.close();
+      });
+      notification.onclose = done;
+    },
+
+    function (done) {
+      info("Testing fetching notification by tag filter");
+      var n1 = new Notification("title1", {tag: "tag1"});
+      var n2 = new Notification("title2", {tag: "tag2"});
+      var n3 = new Notification("title3", {tag: "tag3"});
+      var promise = Notification.get({tag: "tag3"});
+      promise.then(function (notifications) {
+        var notification = notifications[0];
+        is(notifications.length, 1, "should return 1 notification");
+        is(notifications[0].title, "title3", "titles should match");
+        is(notifications[0].tag, "tag3", "tags should match");
+        var closeCount = 0;
+        var waitForAll = function () {
+          if (++closeCount >= 3) {
+            done();
+          }
+        };
+        n1.onclose = waitForAll;
+        n2.onclose = waitForAll;
+        n3.onclose = waitForAll;
+        n1.close();
+        n2.close();
+        n3.close();
+      });
+    },
+
+    deleteAllNotifications,
+
+    function (done) {
+      info("Testing fetching no notifications");
+      var promise = Notification.get();
+      promise.then(function (notifications) {
+        is(notifications.length, 0, "should return 0 notifications");
+        done();
+      });
+    },
+
+    function (done) {
+      info("Testing fetching multiple notifications");
+      var n1 = new Notification("title1");
+      var n2 = new Notification("title2");
+      var n3 = new Notification("title3");
+      var promise = Notification.get();
+      promise.then(function (notifications) {
+        is(notifications.length, 3, "should return 2 notifications");
+        done();
+      });
+    }
+  ];
+
+  MockServices.register();
+  NotificationTest.run(steps, function () {
+    MockServices.unregister();
+  });
+</script>
+</body>
+</html>
deleted file mode 100644
--- a/dom/tests/mochitest/notification/test_web_notifications.html
+++ /dev/null
@@ -1,100 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=782211
--->
-<head>
-  <title>Bug 782211</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="text/javascript" src="notification_common.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=782211">Bug 782211</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-</div>
-<pre id="test">
-</pre>
-<script type="text/javascript">
-  if (window.Notification) {
-    SimpleTest.waitForExplicitFinish();
-
-    function showNotifications() {
-      // Make sure callback is called.
-      Notification.requestPermission(function(perm) {
-        is(perm, "granted", "Permission should be granted.");
-        is(Notification.permission, "granted", "Permission should be granted.");
-        callbackCalled();
-      });
-
-      // Make sure nothing bad happens when requestPermission is called without a callback.
-      Notification.requestPermission();
-
-      try {
-        Notification.requestPermission({});
-        ok(false, "Non callable arugment to request permission should throw exception.");
-      } catch (ex) {
-        ok(true, "Non callable arugment to request permission should throw exception.");
-      }
-
-      var title = "This is a title";
-
-      var notification = new Notification(title);
-
-      is(notification.title, title, "Title should be set");
-      is(notification.dir, "auto", "Dir should default to 'auto'");
-      is(notification.lang, "", "Lang should not be set");
-      is(notification.body, "", "Body should not be set");
-      is(notification.tag, "", "Tag should not be set");
-
-      var options = {
-        dir: "auto",
-        lang: "",
-        body: "This is a notification body",
-        tag: "sometag"
-      };
-
-      var notification = new Notification(title, options);
-
-      is(notification.title, title, "Title should be set");
-      is(notification.dir, options.dir, "Dir should be set");
-      is(notification.lang, options.lang, "Lang should be set");
-      is(notification.body, options.body, "Body should be set");
-      is(notification.tag, options.tag, "Tag should be set");
-
-      notification.onclose = function() {
-        ok(true, "Notification should be closed.");
-        callbackCalled();
-      };
-
-      notification.onshow = function() {
-        ok(true, "Notification should be shown.");
-        notification.close();
-        callbackCalled();
-      };
-
-      notification.onerror = function() {
-        ok(false, "Failed to show notification.");
-        reset_notifications();
-        SimpleTest.finish();
-      };
-
-      var numCallbacksCalled = 0;
-
-      function callbackCalled() {
-        numCallbacksCalled++;
-        if (numCallbacksCalled == 3) {
-          reset_notifications();
-          SimpleTest.finish();
-        }
-      }
-    }
-
-    setup_notifications(true, true, showNotifications);
-  } else {
-    ok(true, "Notifications are not enabled on the platform.");
-  }
-</script>
-</body>
-</html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AppInfo.webidl
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+  * This dictionnary holds the parameters supporting the app:// protocol.
+  */
+dictionary AppInfo
+{
+  DOMString path = "";
+  boolean   isCoreApp = false;
+};
--- a/dom/webidl/DummyBinding.webidl
+++ b/dom/webidl/DummyBinding.webidl
@@ -23,13 +23,14 @@ interface DummyInterface : EventTarget {
   void MmsParameters(optional MmsParameters arg);
   void MmsAttachment(optional MmsAttachment arg);
   void AsyncScrollEventDetail(optional AsyncScrollEventDetail arg);
   void OpenWindowEventDetail(optional OpenWindowEventDetail arg);
   void DOMWindowResizeEventDetail(optional DOMWindowResizeEventDetail arg);
   void WifiOptions(optional WifiCommandOptions arg1,
                    optional WifiResultOptions arg2);
   void AppNotificationServiceOptions(optional AppNotificationServiceOptions arg);
+  void AppInfo(optional AppInfo arg1);
 };
 
 interface DummyInterfaceWorkers {
   BlobPropertyBag blobBag();
 };
--- a/dom/webidl/Notification.webidl
+++ b/dom/webidl/Notification.webidl
@@ -14,16 +14,19 @@
 [PrefControlled, Constructor(DOMString title, optional NotificationOptions options)]
 interface Notification : EventTarget {
   [GetterThrows]
   static readonly attribute NotificationPermission permission;
 
   [Throws]
   static void requestPermission(optional NotificationPermissionCallback permissionCallback);
 
+  [Throws]
+  static Promise get(optional GetNotificationOptions filter);
+
   attribute EventHandler onclick;
 
   attribute EventHandler onshow;
 
   attribute EventHandler onerror;
 
   attribute EventHandler onclose;
 
@@ -47,18 +50,22 @@ interface Notification : EventTarget {
 
   void close();
 };
 
 dictionary NotificationOptions {
   NotificationDirection dir = "auto";
   DOMString lang = "";
   DOMString body = "";
+  DOMString tag = "";
+  DOMString icon = "";
+};
+
+dictionary GetNotificationOptions {
   DOMString tag;
-  DOMString icon = "";
 };
 
 enum NotificationPermission {
   "default",
   "denied",
   "granted"
 };
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -14,16 +14,17 @@ PREPROCESSED_WEBIDL_FILES = [
     'Crypto.webidl',
     'Navigator.webidl',
 ]
 
 WEBIDL_FILES = [
     'AbstractWorker.webidl',
     'AnalyserNode.webidl',
     'AnimationEvent.webidl',
+    'AppInfo.webidl',
     'AppNotificationServiceOptions.webidl',
     'ArchiveReader.webidl',
     'ArchiveRequest.webidl',
     'Attr.webidl',
     'AudioBuffer.webidl',
     'AudioBufferSourceNode.webidl',
     'AudioContext.webidl',
     'AudioDestinationNode.webidl',
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -168,24 +168,24 @@ static float gYSkateSizeMultiplier = 3.5
  * case that a user is reading a page that scrolls up/down. Note that one,
  * both or neither of these may be used at any instant.
  */
 static float gXStationarySizeMultiplier = 1.5f;
 static float gYStationarySizeMultiplier = 2.5f;
 
 /**
  * The time period in ms that throttles mozbrowserasyncscroll event.
- * Default is 100ms if there is no "apzc.asyncscroll.throttle" in preference.
+ * Default is 100ms if there is no "apz.asyncscroll.throttle" in preference.
  */
 
 static int gAsyncScrollThrottleTime = 100;
 
 /**
  * The timeout in ms for mAsyncScrollTimeoutTask delay task.
- * Default is 300ms if there is no "apzc.asyncscroll.timeout" in preference.
+ * Default is 300ms if there is no "apz.asyncscroll.timeout" in preference.
  */
 static int gAsyncScrollTimeout = 300;
 
 /**
  * Temporary pref for disabling zoom in metrofx on aurora.
  */
 static bool gAsyncZoomDisabled = false;
 
@@ -231,31 +231,31 @@ AsyncPanZoomController::InitializeGlobal
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   static bool sInitialized = false;
   if (sInitialized)
     return;
   sInitialized = true;
 
-  Preferences::AddIntVarCache(&gPanRepaintInterval, "gfx.azpc.pan_repaint_interval", gPanRepaintInterval);
-  Preferences::AddIntVarCache(&gFlingRepaintInterval, "gfx.azpc.fling_repaint_interval", gFlingRepaintInterval);
-  Preferences::AddFloatVarCache(&gMinSkateSpeed, "gfx.azpc.min_skate_speed", gMinSkateSpeed);
-  Preferences::AddIntVarCache(&gTouchListenerTimeout, "gfx.azpc.touch_listener_timeout", gTouchListenerTimeout);
-  Preferences::AddIntVarCache(&gNumPaintDurationSamples, "gfx.azpc.num_paint_duration_samples", gNumPaintDurationSamples);
-  Preferences::AddFloatVarCache(&gTouchStartTolerance, "gfx.azpc.touch_start_tolerance", gTouchStartTolerance);
-  Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "gfx.azpc.x_skate_size_multiplier", gXSkateSizeMultiplier);
-  Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "gfx.azpc.y_skate_size_multiplier", gYSkateSizeMultiplier);
-  Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "gfx.azpc.x_stationary_size_multiplier", gXStationarySizeMultiplier);
-  Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "gfx.azpc.y_stationary_size_multiplier", gYStationarySizeMultiplier);
-  Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apzc.asyncscroll.throttle", gAsyncScrollThrottleTime);
-  Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apzc.asyncscroll.timeout", gAsyncScrollTimeout);
-  Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apzc.asynczoom.disabled", gAsyncZoomDisabled);
-  Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apzc.cross_slide.enabled", gCrossSlideEnabled);
-  Preferences::AddIntVarCache(&gAxisLockMode, "apzc.axis_lock_mode", gAxisLockMode);
+  Preferences::AddIntVarCache(&gPanRepaintInterval, "apz.pan_repaint_interval", gPanRepaintInterval);
+  Preferences::AddIntVarCache(&gFlingRepaintInterval, "apz.fling_repaint_interval", gFlingRepaintInterval);
+  Preferences::AddFloatVarCache(&gMinSkateSpeed, "apz.min_skate_speed", gMinSkateSpeed);
+  Preferences::AddIntVarCache(&gTouchListenerTimeout, "apz.touch_listener_timeout", gTouchListenerTimeout);
+  Preferences::AddIntVarCache(&gNumPaintDurationSamples, "apz.num_paint_duration_samples", gNumPaintDurationSamples);
+  Preferences::AddFloatVarCache(&gTouchStartTolerance, "apz.touch_start_tolerance", gTouchStartTolerance);
+  Preferences::AddFloatVarCache(&gXSkateSizeMultiplier, "apz.x_skate_size_multiplier", gXSkateSizeMultiplier);
+  Preferences::AddFloatVarCache(&gYSkateSizeMultiplier, "apz.y_skate_size_multiplier", gYSkateSizeMultiplier);
+  Preferences::AddFloatVarCache(&gXStationarySizeMultiplier, "apz.x_stationary_size_multiplier", gXStationarySizeMultiplier);
+  Preferences::AddFloatVarCache(&gYStationarySizeMultiplier, "apz.y_stationary_size_multiplier", gYStationarySizeMultiplier);
+  Preferences::AddIntVarCache(&gAsyncScrollThrottleTime, "apz.asyncscroll.throttle", gAsyncScrollThrottleTime);
+  Preferences::AddIntVarCache(&gAsyncScrollTimeout, "apz.asyncscroll.timeout", gAsyncScrollTimeout);
+  Preferences::AddBoolVarCache(&gAsyncZoomDisabled, "apz.asynczoom.disabled", gAsyncZoomDisabled);
+  Preferences::AddBoolVarCache(&gCrossSlideEnabled, "apz.cross_slide.enabled", gCrossSlideEnabled);
+  Preferences::AddIntVarCache(&gAxisLockMode, "apz.axis_lock_mode", gAxisLockMode);
 
   gComputedTimingFunction = new ComputedTimingFunction();
   gComputedTimingFunction->Init(
     nsTimingFunction(NS_STYLE_TRANSITION_TIMING_FUNCTION_EASE));
   ClearOnShutdown(&gComputedTimingFunction);
 }
 
 AsyncPanZoomController::AsyncPanZoomController(uint64_t aLayersId,
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -62,22 +62,22 @@ static float gFlingStoppedThreshold = 0.
  * Maximum size of velocity queue. The queue contains last N velocity records.
  * On touch end we calculate the average velocity in order to compensate
  * touch/mouse drivers misbehaviour.
  */
 static int gMaxVelocityQueueSize = 5;
 
 static void ReadAxisPrefs()
 {
-  Preferences::AddFloatVarCache(&gMaxEventAcceleration, "gfx.axis.max_event_acceleration", gMaxEventAcceleration);
-  Preferences::AddFloatVarCache(&gFlingFriction, "gfx.axis.fling_friction", gFlingFriction);
-  Preferences::AddFloatVarCache(&gVelocityThreshold, "gfx.axis.velocity_threshold", gVelocityThreshold);
-  Preferences::AddFloatVarCache(&gAccelerationMultiplier, "gfx.axis.acceleration_multiplier", gAccelerationMultiplier);
-  Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "gfx.axis.fling_stopped_threshold", gFlingStoppedThreshold);
-  Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "gfx.axis.max_velocity_queue_size", gMaxVelocityQueueSize);
+  Preferences::AddFloatVarCache(&gMaxEventAcceleration, "apz.max_event_acceleration", gMaxEventAcceleration);
+  Preferences::AddFloatVarCache(&gFlingFriction, "apz.fling_friction", gFlingFriction);
+  Preferences::AddFloatVarCache(&gVelocityThreshold, "apz.velocity_threshold", gVelocityThreshold);
+  Preferences::AddFloatVarCache(&gAccelerationMultiplier, "apz.acceleration_multiplier", gAccelerationMultiplier);
+  Preferences::AddFloatVarCache(&gFlingStoppedThreshold, "apz.fling_stopped_threshold", gFlingStoppedThreshold);
+  Preferences::AddIntVarCache(&gMaxVelocityQueueSize, "apz.max_velocity_queue_size", gMaxVelocityQueueSize);
 }
 
 class ReadAxisPref MOZ_FINAL : public nsRunnable {
 public:
   NS_IMETHOD Run()
   {
     ReadAxisPrefs();
     return NS_OK;
--- a/js/src/build/autoconf/arch.m4
+++ b/js/src/build/autoconf/arch.m4
@@ -9,16 +9,17 @@ dnl ====================================
 dnl = ARM toolchain tweaks
 dnl ========================================================
 
 MOZ_THUMB=toolchain-default
 MOZ_THUMB_INTERWORK=toolchain-default
 MOZ_FPU=toolchain-default
 MOZ_FLOAT_ABI=toolchain-default
 MOZ_SOFT_FLOAT=toolchain-default
+MOZ_ALIGN=toolchain-default
 
 MOZ_ARG_WITH_STRING(arch,
 [  --with-arch=[[type|toolchain-default]]
                            Use specific CPU features (-march=type). Resets
                            thumb, fpu, float-abi, etc. defaults when set],
     if test -z "$GNU_CC"; then
         AC_MSG_ERROR([--with-arch is not supported on non-GNU toolchains])
     fi
@@ -27,16 +28,17 @@ MOZ_ARG_WITH_STRING(arch,
 if test -z "$MOZ_ARCH"; then
     dnl Defaults
     case "${CPU_ARCH}-${OS_TARGET}" in
     arm-Android)
         MOZ_THUMB=yes
         MOZ_ARCH=armv7-a
         MOZ_FPU=vfp
         MOZ_FLOAT_ABI=softfp
+        MOZ_ALIGN=no
         ;;
     arm-Darwin)
         MOZ_ARCH=toolchain-default
         MOZ_THUMB=yes
         ;;
     esac
 fi
 
@@ -154,18 +156,38 @@ yes)
 no)
     soft_float_flag="-mno-soft-float"
     ;;
 *) # toolchain-default
     soft_float_flag=""
     ;;
 esac
 
+case "$MOZ_ALIGN" in
+no)
+    align_flag="-mno-unaligned-access"
+    ;;
+yes)
+    align_flag="-munaligned-access"
+    ;;
+*)
+    align_flag=""
+    ;;
+esac
+
+if test -n "$align_flag"; then
+  _SAVE_CFLAGS="$CFLAGS"
+  CFLAGS="$CFLAGS $align_flag"
+  AC_MSG_CHECKING(whether alignment flag ($align_flag) is supported)
+  AC_TRY_COMPILE([],[],,align_flag="")
+  CFLAGS="$_SAVE_CFLAGS"
+fi
+
 dnl Use echo to avoid accumulating space characters
-all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag`
+all_flags=`echo $arch_flag $thumb_flag $thumb_interwork_flag $fpu_flag $float_abi_flag $soft_float_flag $align_flag`
 if test -n "$all_flags"; then
     _SAVE_CFLAGS="$CFLAGS"
     CFLAGS="$all_flags"
     AC_MSG_CHECKING(whether the chosen combination of compiler flags ($all_flags) works)
     AC_TRY_COMPILE([],[return 0;],
         AC_MSG_RESULT([yes]),
         AC_MSG_ERROR([no]))
 
--- a/mobile/android/base/tests/testBookmark.java.in
+++ b/mobile/android/base/tests/testBookmark.java.in
@@ -8,26 +8,23 @@ import android.widget.ListView;
 
 import java.util.Arrays;
 import java.util.ArrayList;
 
 public class testBookmark extends AboutHomeTest {
     private static String BOOKMARK_URL;
     private static int WAIT_FOR_BOOKMARKED_TIMEOUT = 10000;
 
-    Navigation nav;
-
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
 
     public void testBookmark() {
         BOOKMARK_URL = getAbsoluteUrl(StringHelper.ROBOCOP_BLANK_PAGE_01_URL);
-        nav = new Navigation(mDevice);
         runAboutHomeTest();
         runMenuTest();
     }
 
     public void runMenuTest() {
         mAsserter.is(mDatabaseHelper.isBookmark(BOOKMARK_URL), false, "Page is not bookmarked initially");
         setUpBookmark(); // loads the page, taps the star button, and waits for the "Bookmark Added" message
         waitForBookmarked(true);
@@ -65,20 +62,20 @@ public class testBookmark extends AboutH
         }, WAIT_FOR_BOOKMARKED_TIMEOUT);
         mAsserter.is(bookmarked, true, BOOKMARK_URL + " was " + (isBookmarked ? "added as a bookmark" : "removed from bookmarks"));
     }
 
     private void setUpBookmark() {
         // Bookmark a page for the test
         loadUrl(BOOKMARK_URL);
         waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
-        nav.bookmark();
+        toggleBookmark();
         mAsserter.is(waitForText(StringHelper.BOOKMARK_ADDED_LABEL), true, "bookmark added successfully");
     }
 
     private void cleanUpBookmark() {
         // Go back to the page we bookmarked
         loadUrl(BOOKMARK_URL);
         waitForText(StringHelper.ROBOCOP_BLANK_PAGE_01_TITLE);
-        nav.bookmark();
+        toggleBookmark();
         mAsserter.is(waitForText(StringHelper.BOOKMARK_REMOVED_LABEL), true, "bookmark removed successfully");
     }
 }
--- a/mobile/android/chrome/content/aboutApps.js
+++ b/mobile/android/chrome/content/aboutApps.js
@@ -135,20 +135,20 @@ function addApplication(aApp) {
 }
 
 function onInstall(aEvent) {
   let node = document.getElementById("app-" + aEvent.application.origin);
   if (node)
     return;
 
   addApplication(aEvent.application);
-  document.getElementById("noapps").className = "hidden";
+  document.getElementById("main-container").classList.remove("hidden");
 }
 
 function onUninstall(aEvent) {
   let node = document.getElementById("app-" + aEvent.application.origin);
   if (node) {
     let parent = node.parentNode;
     parent.removeChild(node);
     if (!parent.firstChild)
-      document.getElementById("noapps").className = "";
+      document.getElementById("main-container").classList.add("hidden");
   }
 }
--- a/mobile/android/chrome/content/aboutApps.xhtml
+++ b/mobile/android/chrome/content/aboutApps.xhtml
@@ -31,22 +31,27 @@
 
     <menu type="context" id="appmenu">
       <menuitem label="&aboutApps.addToHomescreen;" onclick="ContextMenus.addToHomescreen();"></menuitem>
       <menuitem label="&aboutApps.uninstall;" onclick="ContextMenus.uninstall();"></menuitem>
     </menu>
   
     <div class="header">
       <div>&aboutApps.header;</div>
-      <div id="header-button" role="button" aria-label="&aboutApps.noApps.middle3;" pref="app.marketplaceURL" onclick="openLink(this);"/>
+      <div id="header-button" role="button" aria-label="&aboutApps.browseMarketplace;" pref="app.marketplaceURL" onclick="openLink(this);"/>
     </div>
-    <div id="main-container" class="list">
-      <div id="noapps" class="hidden">
-        &aboutApps.noApps.pre;<a id="marketplaceURL" pref="app.marketplaceURL">&aboutApps.noApps.middle3;</a>&aboutApps.noApps.post;
-      </div>
+
+    <div id="main-container" class="list hidden">
       <div>
         <div class="spacer" id="spacer1"> </div>
         <div id="appgrid"/>
         <div class="spacer" id="spacer1"> </div>
       </div>
     </div>
+
+    <div class="list-item" role="button" pref="app.marketplaceURL" onclick="openLink(this);">
+      <img class="icon" src="chrome://browser/skin/images/marketplace-logo.png" />
+      <div class="inner">
+        <div id="browse-title" class="title">&aboutApps.browseMarketplace;</div>
+      </div>
+    </div>
   </body>
 </html>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -13,16 +13,17 @@ let Cr = Components.results;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/JNI.jsm");
 Cu.import('resource://gre/modules/Payment.jsm');
 Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
 Cu.import("resource://gre/modules/ContactService.jsm");
+Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource://gre/modules/SpatialNavigation.jsm");
 
 #ifdef ACCESSIBILITY
 Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -277,16 +277,18 @@
 
 ; JavaScript components
 @BINPATH@/components/ConsoleAPI.manifest
 @BINPATH@/components/ConsoleAPI.js
 @BINPATH@/components/ContactManager.js
 @BINPATH@/components/ContactManager.manifest
 @BINPATH@/components/PhoneNumberService.js
 @BINPATH@/components/PhoneNumberService.manifest
+@BINPATH@/components/NotificationStorage.js
+@BINPATH@/components/NotificationStorage.manifest
 @BINPATH@/components/SettingsManager.js
 @BINPATH@/components/SettingsManager.manifest
 @BINPATH@/components/SettingsService.js
 @BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/BrowserElementParent.manifest
 @BINPATH@/components/BrowserElementParent.js
 @BINPATH@/components/FeedProcessor.manifest
 @BINPATH@/components/FeedProcessor.js
@@ -380,18 +382,16 @@
 @BINPATH@/components/nsPrompter.manifest
 @BINPATH@/components/nsPrompter.js
 @BINPATH@/components/TelemetryPing.js
 @BINPATH@/components/TelemetryPing.manifest
 @BINPATH@/components/Webapps.js
 @BINPATH@/components/Webapps.manifest
 @BINPATH@/components/AppsService.js
 @BINPATH@/components/AppsService.manifest
-@BINPATH@/components/AppProtocolHandler.js
-@BINPATH@/components/AppProtocolHandler.manifest
 
 @BINPATH@/components/Push.js
 @BINPATH@/components/Push.manifest
 
 @BINPATH@/components/SystemMessageInternal.js
 @BINPATH@/components/SystemMessageManager.js
 @BINPATH@/components/SystemMessageManager.manifest
 
--- a/mobile/android/locales/en-US/chrome/aboutApps.dtd
+++ b/mobile/android/locales/en-US/chrome/aboutApps.dtd
@@ -1,15 +1,10 @@
 <!-- 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/. -->
 
 <!ENTITY aboutApps.title2                          "Apps">
 <!ENTITY aboutApps.header                          "Your Apps">
 
-<!-- LOCALIZATION NOTE (aboutApps.noApps.pre): include a trailing space as needed -->
-<!-- LOCALIZATION NOTE (aboutApps.noApps.middle): avoid leading/trailing spaces, this text is a link -->
-<!-- LOCALIZATION NOTE (aboutApps.noApps.post): include a starting space as needed -->
-<!ENTITY aboutApps.noApps.pre "No web apps installed. Get some from the ">
-<!ENTITY aboutApps.noApps.middle3 "Firefox Marketplace">
-<!ENTITY aboutApps.noApps.post ".">
+<!ENTITY aboutApps.browseMarketplace "Browse the Firefox Marketplace">
 <!ENTITY aboutApps.uninstall "Uninstall">
 <!ENTITY aboutApps.addToHomescreen "Add to Home Screen">
--- a/mobile/android/themes/core/aboutAddons.css
+++ b/mobile/android/themes/core/aboutAddons.css
@@ -1,21 +1,12 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-.inner {
-  background-color: #eef2f5;
-  min-height: 3.8em;
-  padding: 0.5em;
-
-  /* make room for the favicon */
-  -moz-margin-start: 4.5em;
-}
-
 .details {
   width: 100%;
 }
 
 .details > div {
   display: inline;
 }
 
@@ -28,24 +19,16 @@
   float: right;
   margin-left: .67em;
 
   /* compensate for the title baseline */
   position: relative;
   bottom: -3px;
 }
 
-#browse-title {
-  margin-top: 1em;
-  background-image: url("chrome://browser/skin/images/chevron.png");
-  background-size: 8px 20px;
-  background-position: right;
-  background-repeat: no-repeat;
-}
-
 #header-button {
   background-image: url("chrome://browser/skin/images/amo-logo.png"), url("chrome://browser/skin/images/chevron.png");
   background-size: 20px 20px, 8px 20px;
   background-position: left, right 3px center;
 }
 
 .description {
   width: 100%;
--- a/mobile/android/themes/core/aboutApps.css
+++ b/mobile/android/themes/core/aboutApps.css
@@ -2,26 +2,30 @@
  * 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/. */
 
 .app:active {
   background-color: #febc2b;
 }
 
 #header-button {
-  background-image: url("chrome://browser/skin/images/marketplace-logo.png");
+  background-image: url("chrome://browser/skin/images/marketplace-logo.png"), url("chrome://browser/skin/images/chevron.png");
+  background-size: 32px 32px, 8px 20px;
+  background-position: left, right 0.5em center;
+  -moz-padding-start: 2.5em;
 }
 
 #main-container {
-  margin: 1em;
-  padding: 1em;
-  border-radius: 10px;
-  border: 1px solid grey;
-  background-color: white;
-  width: calc(100% - 4em);
+  padding: 2em;
+  background-color: #EEF2F5;
+  border-bottom: 1px solid #BAC2AC;
+}
+
+.hidden {
+  display: none;
 }
 
 .spacer {
   clear: both;
 }
 
 .app {
   float: left;
@@ -39,17 +43,8 @@
   width: 5em;
   height: 5em;
   pointer-events: none;
 }
 
 .app div {
   pointer-events: none;
 }
-
-#noapps {
-  padding: 1em;
-  text-align: center;
-}
-
-.hidden {
-  display: none;
-}
--- a/mobile/android/themes/core/aboutBase.css
+++ b/mobile/android/themes/core/aboutBase.css
@@ -60,16 +60,25 @@ body {
   color: #999999;
 }
 
 .list-item:active,
 .list-item:active > .inner {
   background-image: none;
 }
 
+.inner {
+  background-color: #eef2f5;
+  min-height: 3.8em;
+  padding: 0.5em;
+
+  /* make room for the favicon */
+  -moz-margin-start: 4.5em;
+}
+
 /* Icons */
 body[dir="ltr"] .icon {
   left: 1.35em;
 }
 
 body[dir="ltr"] .icon {
   right: 1.35em;
 }
@@ -127,8 +136,16 @@ body[dir="ltr"] .icon {
 }
 
 .title {
   font-size: 1.2em;
   font-weight: bold;
   overflow: hidden;
   flex: 1;
 }
+
+#browse-title {
+  margin-top: 1em;
+  background-image: url("chrome://browser/skin/images/chevron.png");
+  background-size: 8px 20px;
+  background-position: right;
+  background-repeat: no-repeat;
+}
--- a/mobile/locales/en-US/searchplugins/yahoo.xml
+++ b/mobile/locales/en-US/searchplugins/yahoo.xml
@@ -6,12 +6,12 @@
 <ShortName>Yahoo</ShortName>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
 <Url type="application/x-suggestions+json" method="GET"
      template="http://ff.search.yahoo.com/gossip?output=fxjson&amp;command={searchTerms}" />
 <Url type="text/html" method="GET" template="http://search.yahoo.com/search">
   <Param name="p" value="{searchTerms}" />
   <Param name="ei" value="UTF-8" />
-  <Param name="fr" value="mozilla_mobile_search" />
+  <MozParam name="fr" condition="top2" trueValue="mozilla_mobile_search" falseValue="" />
 </Url>
 <SearchForm>http://search.yahoo.com/</SearchForm>
 </SearchPlugin>
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -282,17 +282,17 @@ pref("media.video_stats.enabled", true);
 
 // Whether to enable the audio writing APIs on the audio element
 pref("media.audio_data.enabled", true);
 
 // Whether to lock touch scrolling to one axis at a time
 // 0 = FREE (No locking at all)
 // 1 = STANDARD (Once locked, remain locked until scrolling ends)
 // 2 = STICKY (Allow lock to be broken, with hysteresis)
-pref("apzc.axis_lock_mode", 0);
+pref("apz.axis_lock_mode", 0);
 
 #ifdef XP_MACOSX
 // Whether to run in native HiDPI mode on machines with "Retina"/HiDPI display;
 //   <= 0 : hidpi mode disabled, display will just use pixel-based upscaling
 //   == 1 : hidpi supported if all screens share the same backingScaleFactor
 //   >= 2 : hidpi supported even with mixed backingScaleFactors (somewhat broken)
 pref("gfx.hidpi.enabled", 2);
 #endif
--- a/netwerk/build/Makefile.in
+++ b/netwerk/build/Makefile.in
@@ -64,16 +64,17 @@ LOCAL_INCLUDES = \
   -I$(srcdir)/../base/src \
   -I$(srcdir)/../dns \
   -I$(srcdir)/../socket \
   -I$(srcdir)/../streamconv/src \
   -I$(srcdir)/../streamconv/converters \
   -I$(srcdir)/../mime \
   -I$(srcdir)/../cache \
   -I$(srcdir)/../protocol/about \
+  -I$(srcdir)/../protocol/app \
   -I../dns \
   $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \
     -I$(srcdir)/../protocol/$(d)) \
   $(NULL)
 
 ifeq ($(OS_ARCH),WINNT)
     LOCAL_INCLUDES += -I$(srcdir)/../system/win32
 endif
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -598,16 +598,28 @@
 { /* fbc81170-1f69-11d3-9344-00104ba0fd40 */         \
     0xfbc81170,                                      \
     0x1f69,                                          \
     0x11d3,                                          \
     {0x93, 0x44, 0x00, 0x10, 0x4b, 0xa0, 0xfd, 0x40} \
 }
 
 /******************************************************************************
+ * netwerk/protocol/app/ classes
+ */
+
+#define NS_APPPROTOCOLHANDLER_CID                    \
+{ /* {B6ED3030-9999-11d3-A178-0050041CAF44} */       \
+    0xb6ed3030,                                      \
+    0x9999,                                          \
+    0x11d3,                                          \
+    {0xa1, 0x78, 0x00, 0x50, 0x04, 0x1c, 0xaf, 0x44} \
+}
+
+/******************************************************************************
  * netwerk/protocol/data/ classes
  */
 
 #define NS_DATAPROTOCOLHANDLER_CID                   \
 { /* {B6ED3030-6183-11d3-A178-0050041CAF44} */       \
     0xb6ed3030,                                      \
     0x6183,                                          \
     0x11d3,                                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -238,16 +238,17 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsHttpDig
 #endif // !NECKO_PROTOCOL_http
 
 #include "mozilla/net/Dashboard.h"
 namespace mozilla {
 namespace net {
   NS_GENERIC_FACTORY_CONSTRUCTOR(Dashboard)
 }
 }
+#include "AppProtocolHandler.h"
 
 #ifdef NECKO_PROTOCOL_res
 // resource
 #include "nsResProtocolHandler.h"
 NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsResProtocolHandler, Init)
 NS_GENERIC_FACTORY_CONSTRUCTOR(nsResURL)
 #endif
 
@@ -767,16 +768,17 @@ NS_DEFINE_NAMED_CID(NS_ABOUT_CACHE_ENTRY
 #endif
 NS_DEFINE_NAMED_CID(NS_SOCKSSOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_SOCKS4SOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_UDPSOCKETPROVIDER_CID);
 NS_DEFINE_NAMED_CID(NS_CACHESERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHESERVICE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHENAMESPACE_CID);
 NS_DEFINE_NAMED_CID(NS_APPLICATIONCACHE_CID);
+NS_DEFINE_NAMED_CID(NS_APPPROTOCOLHANDLER_CID);
 #ifdef NECKO_COOKIES
 NS_DEFINE_NAMED_CID(NS_COOKIEMANAGER_CID);
 NS_DEFINE_NAMED_CID(NS_COOKIESERVICE_CID);
 #endif
 #ifdef NECKO_WIFI
 NS_DEFINE_NAMED_CID(NS_WIFI_MONITOR_COMPONENT_CID);
 #endif
 #ifdef NECKO_PROTOCOL_data
@@ -905,16 +907,17 @@ static const mozilla::Module::CIDEntry k
 #endif
     { &kNS_SOCKSSOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV5 },
     { &kNS_SOCKS4SOCKETPROVIDER_CID, false, nullptr, nsSOCKSSocketProvider::CreateV4 },
     { &kNS_UDPSOCKETPROVIDER_CID, false, nullptr, nsUDPSocketProviderConstructor },
     { &kNS_CACHESERVICE_CID, false, nullptr, nsCacheService::Create },
     { &kNS_APPLICATIONCACHESERVICE_CID, false, nullptr, nsApplicationCacheServiceConstructor },
     { &kNS_APPLICATIONCACHENAMESPACE_CID, false, nullptr, nsApplicationCacheNamespaceConstructor },
     { &kNS_APPLICATIONCACHE_CID, false, nullptr, nsApplicationCacheConstructor },
+    { &kNS_APPPROTOCOLHANDLER_CID, false, nullptr, AppProtocolHandler::Create },
 #ifdef NECKO_COOKIES
     { &kNS_COOKIEMANAGER_CID, false, nullptr, nsICookieServiceConstructor },
     { &kNS_COOKIESERVICE_CID, false, nullptr, nsICookieServiceConstructor },
 #endif
 #ifdef NECKO_WIFI
     { &kNS_WIFI_MONITOR_COMPONENT_CID, false, nullptr, nsWifiMonitorConstructor },
 #endif
 #ifdef NECKO_PROTOCOL_data
@@ -1050,16 +1053,17 @@ static const mozilla::Module::ContractID
 #endif
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks", &kNS_SOCKSSOCKETPROVIDER_CID },
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "socks4", &kNS_SOCKS4SOCKETPROVIDER_CID },
     { NS_NETWORK_SOCKET_CONTRACTID_PREFIX "udp", &kNS_UDPSOCKETPROVIDER_CID },
     { NS_CACHESERVICE_CONTRACTID, &kNS_CACHESERVICE_CID },
     { NS_APPLICATIONCACHESERVICE_CONTRACTID, &kNS_APPLICATIONCACHESERVICE_CID },
     { NS_APPLICATIONCACHENAMESPACE_CONTRACTID, &kNS_APPLICATIONCACHENAMESPACE_CID },
     { NS_APPLICATIONCACHE_CONTRACTID, &kNS_APPLICATIONCACHE_CID },
+    { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "app", &kNS_APPPROTOCOLHANDLER_CID },
 #ifdef NECKO_COOKIES
     { NS_COOKIEMANAGER_CONTRACTID, &kNS_COOKIEMANAGER_CID },
     { NS_COOKIESERVICE_CONTRACTID, &kNS_COOKIESERVICE_CID },
 #endif
 #ifdef NECKO_WIFI
     { NS_WIFI_MONITOR_CONTRACTID, &kNS_WIFI_MONITOR_COMPONENT_CID },
 #endif
 #ifdef NECKO_PROTOCOL_data
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/AppProtocolHandler.cpp
@@ -0,0 +1,424 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* 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/. */
+
+#include "AppProtocolHandler.h"
+#include "nsBaseChannel.h"
+#include "nsJARChannel.h"
+#include "nsNetCID.h"
+#include "nsIAppsService.h"
+#include "nsCxPusher.h"
+#include "nsXULAppAPI.h"
+
+/**
+  * This dummy channel implementation only provides enough functionality
+  * to return a fake 404 error when the caller asks for an app:// URL
+  * containing an unknown appId.
+  */
+class DummyChannel : public nsIJARChannel
+                          , nsRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIREQUEST
+  NS_DECL_NSICHANNEL
+  NS_DECL_NSIJARCHANNEL
+
+  DummyChannel();
+
+  NS_IMETHODIMP Run();
+
+private:
+  bool                        mPending;
+  uint32_t                    mSuspendCount;
+  nsCOMPtr<nsISupports>       mListenerContext;
+  nsCOMPtr<nsIStreamListener> mListener;
+  nsCOMPtr<nsILoadGroup>      mLoadGroup;
+  nsLoadFlags                 mLoadFlags;
+};
+
+NS_IMPL_ISUPPORTS3(DummyChannel, nsIRequest, nsIChannel, nsIJARChannel)
+
+DummyChannel::DummyChannel() : mPending(false)
+                             , mSuspendCount(0)
+                             , mLoadFlags(LOAD_NORMAL)
+{
+}
+
+NS_IMETHODIMP DummyChannel::GetName(nsACString &result)
+{
+  result = "dummy_app_channel";
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetStatus(nsresult *aStatus)
+{
+  *aStatus = NS_ERROR_FILE_NOT_FOUND;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::IsPending(bool *aResult)
+{
+  *aResult = mPending;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Suspend()
+{
+  mSuspendCount++;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Resume()
+{
+  if (mSuspendCount <= 0) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  if (--mSuspendCount == 0) {
+    NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Open(nsIInputStream**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::AsyncOpen(nsIStreamListener* aListener, nsISupports* aContext)
+{
+  mListener = aListener;
+  mListenerContext = aContext;
+  mPending = true;
+
+  if (mLoadGroup) {
+    mLoadGroup->AddRequest(this, aContext);
+  }
+
+  if (mSuspendCount == 0) {
+    NS_DispatchToMainThread(this, NS_DISPATCH_NORMAL);
+  }
+
+  return NS_OK;
+}
+
+// nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
+// a 404 error.
+NS_IMETHODIMP DummyChannel::GetIsUnsafe(bool *aResult)
+{
+  *aResult = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetAppURI(nsIURI *aURI)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::Run()
+{
+  nsresult rv = mListener->OnStartRequest(this, mListenerContext);
+  NS_ENSURE_SUCCESS(rv, rv);
+  mPending = false;
+  rv = mListener->OnStopRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (mLoadGroup) {
+    mLoadGroup->RemoveRequest(this, mListenerContext, NS_ERROR_FILE_NOT_FOUND);
+  }
+
+  mListener = nullptr;
+  mListenerContext = nullptr;
+  rv = SetNotificationCallbacks(nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::Cancel(nsresult)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
+{
+  *aLoadGroup = mLoadGroup;
+  NS_IF_ADDREF(*aLoadGroup);
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
+{
+  mLoadGroup = aLoadGroup;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
+{
+  *aLoadFlags = mLoadFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
+{
+  mLoadFlags = aLoadFlags;
+  return NS_OK;
+}
+
+NS_IMETHODIMP DummyChannel::GetOriginalURI(nsIURI**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetOriginalURI(nsIURI*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetOwner(nsISupports**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetOwner(nsISupports*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetNotificationCallbacks(nsIInterfaceRequestor**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetNotificationCallbacks(nsIInterfaceRequestor*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetSecurityInfo(nsISupports**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentType(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentType(const nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentCharset(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentCharset(const nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentLength(int64_t*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentLength(int64_t)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDisposition(uint32_t*)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentDisposition(uint32_t)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetURI(nsIURI**)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDispositionFilename(nsAString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::SetContentDispositionFilename(nsAString const &)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP DummyChannel::GetContentDispositionHeader(nsACString&)
+{
+  return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/**
+  * app:// protocol implementation.
+  */
+
+AppProtocolHandler::AppProtocolHandler() {
+}
+
+AppProtocolHandler::~AppProtocolHandler() {
+  mAppInfoCache.Clear();
+}
+
+NS_IMPL_ISUPPORTS1(AppProtocolHandler, nsIProtocolHandler)
+
+/* static */
+nsresult
+AppProtocolHandler::Create(nsISupports* aOuter,
+                           const nsIID& aIID,
+                           void* *aResult)
+{
+  AppProtocolHandler* ph = new AppProtocolHandler();
+  if (ph == nullptr) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  NS_ADDREF(ph);
+  nsresult rv = ph->QueryInterface(aIID, aResult);
+  NS_RELEASE(ph);
+  return rv;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetScheme(nsACString &aResult)
+{
+  aResult.AssignLiteral("app");
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetDefaultPort(int32_t *aResult)
+{
+  // No ports for the app protocol.
+  *aResult = -1;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::GetProtocolFlags(uint32_t *aResult)
+{
+  *aResult = URI_NOAUTH |
+             URI_DANGEROUS_TO_LOAD |
+             URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::NewURI(const nsACString &aSpec,
+                           const char *aCharset, // ignore charset info
+                           nsIURI *aBaseURI,
+                           nsIURI **result)
+{
+  nsresult rv;
+  nsCOMPtr<nsIStandardURL> surl(do_CreateInstance(NS_STANDARDURL_CONTRACTID, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = surl->Init(nsIStandardURL::URLTYPE_STANDARD, -1, aSpec, aCharset, aBaseURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURL> url(do_QueryInterface(surl, &rv));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  surl->SetMutable(false);
+  NS_ADDREF(*result = url);
+  return NS_OK;
+}
+
+// We map app://ABCDEF/path/to/file.ext to
+// jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
+NS_IMETHODIMP
+AppProtocolHandler::NewChannel(nsIURI* aUri, nsIChannel* *aResult)
+{
+  NS_ENSURE_ARG_POINTER(aUri);
+  nsJARChannel* channel = new nsJARChannel();
+  if (!channel) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  nsAutoCString host;
+  nsresult rv = aUri->GetHost(host);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsAutoCString fileSpec;
+  nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
+  rv = url->GetFilePath(fileSpec);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mozilla::dom::AppInfo appInfo;
+
+  if (!mAppInfoCache.Get(host, &appInfo)) {
+    nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+    if (!appsService) {
+      return NS_ERROR_FAILURE;
+    }
+
+    JS::Value jsInfo;
+    rv = appsService->GetAppInfo(NS_ConvertUTF8toUTF16(host), &jsInfo);
+    if (NS_FAILED(rv)) {
+      // Return a DummyChannel.
+      delete channel;
+      NS_IF_ADDREF(*aResult = new DummyChannel());
+      return NS_OK;
+    }
+
+    mozilla::AutoSafeJSContext cx;
+    if (!appInfo.Init(cx, JS::Handle<JS::Value>::fromMarkedLocation(&jsInfo)) ||
+        appInfo.mPath.IsEmpty()) {
+      printf_stderr("!! No appInfo for %s\n", host.get());
+      // Return a DummyChannel.
+      delete channel;
+      NS_IF_ADDREF(*aResult = new DummyChannel());
+      return NS_OK;
+    }
+
+    mAppInfoCache.Put(host, appInfo);
+  }
+
+  bool noRemote = (appInfo.mIsCoreApp ||
+                   XRE_GetProcessType() == GeckoProcessType_Default);
+
+  // In-parent and CoreApps can directly access files, so use jar:file://
+  nsAutoCString jarSpec(noRemote ? "jar:file://"
+                                 : "jar:remoteopenfile://");
+  jarSpec += NS_ConvertUTF16toUTF8(appInfo.mPath) +
+             NS_LITERAL_CSTRING("/application.zip!") +
+             fileSpec;
+
+  nsCOMPtr<nsIURI> jarURI;
+  rv = NS_NewURI(getter_AddRefs(jarURI),
+                 jarSpec, nullptr, nullptr);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->Init(jarURI);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->SetAppURI(aUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = channel->SetOriginalURI(aUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_ADDREF(*aResult = channel);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+AppProtocolHandler::AllowPort(int32_t aPort, const char *aScheme, bool *aRetval)
+{
+  // No port allowed for this scheme.
+  *aRetval = false;
+  return NS_OK;
+}
+
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/AppProtocolHandler.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim:set expandtab ts=4 sw=4 sts=4 cin: */
+/* 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/. */
+
+#ifndef AppProtocolHandler_
+#define AppProtocolHandler_
+
+#include "nsIProtocolHandler.h"
+#include "nsDataHashtable.h"
+#include "mozilla/dom/AppInfoBinding.h"
+
+class AppProtocolHandler : public nsIProtocolHandler
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  // nsIProtocolHandler methods:
+  NS_DECL_NSIPROTOCOLHANDLER
+
+  // AppProtocolHandler methods:
+  AppProtocolHandler();
+  virtual ~AppProtocolHandler();
+
+  // Define a Create method to be used with a factory:
+  static nsresult Create(nsISupports* aOuter,
+                         const nsIID& aIID,
+                         void* *aResult);
+
+private:
+  nsDataHashtable<nsCStringHashKey, mozilla::dom::AppInfo> mAppInfoCache;
+};
+
+#endif /* AppProtocolHandler_ */
deleted file mode 100644
--- a/netwerk/protocol/app/AppProtocolHandler.js
+++ /dev/null
@@ -1,197 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyServiceGetter(this, "appsService",
-                                   "@mozilla.org/AppsService;1",
-                                   "nsIAppsService");
-
-function AppProtocolHandler() {
-  this._appInfo = [];
-  this._runningInParent = Cc["@mozilla.org/xre/runtime;1"]
-                            .getService(Ci.nsIXULRuntime)
-                            .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-}
-
-AppProtocolHandler.prototype = {
-  classID: Components.ID("{b7ad6144-d344-4687-b2d0-b6b9dce1f07f}"),
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]),
-
-  scheme: "app",
-  defaultPort: -1,
-  // Don't allow loading from other protocols, and only from app:// if webapps is granted
-  protocolFlags: Ci.nsIProtocolHandler.URI_NOAUTH |
-                 Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
-                 Ci.nsIProtocolHandler.URI_CROSS_ORIGIN_NEEDS_WEBAPPS_PERM,
-
-  getAppInfo: function app_phGetAppInfo(aId) {
-
-    if (!this._appInfo[aId]) {
-      this._appInfo[aId] = appsService.getAppInfo(aId);
-    }
-    return this._appInfo[aId];
-  },
-
-  newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) {
-    let uri = Cc["@mozilla.org/network/standard-url;1"]
-              .createInstance(Ci.nsIStandardURL);
-    uri.init(Ci.nsIStandardURL.URLTYPE_STANDARD, -1, aSpec, aOriginCharset,
-             aBaseURI);
-    return uri.QueryInterface(Ci.nsIURI);
-  },
-
-  newChannel: function app_phNewChannel(aURI) {
-    // We map app://ABCDEF/path/to/file.ext to
-    // jar:file:///path/to/profile/webapps/ABCDEF/application.zip!/path/to/file.ext
-    let url = aURI.QueryInterface(Ci.nsIURL);
-    let appId = aURI.host;
-    let fileSpec = url.filePath;
-
-    // Build a jar channel and masquerade as an app:// URI.
-    let appInfo = this.getAppInfo(appId);
-    if (!appInfo) {
-      // That should not happen, so dump() inconditionnally.
-      // We create a dummy channel instead of throwing to let the
-      // downstream user get a 404 error.
-      dump("!! got no appInfo for " + appId + "\n");
-      return new DummyChannel();
-    }
-
-    let uri;
-    if (this._runningInParent || appInfo.isCoreApp) {
-      // In-parent and CoreApps can directly access files, so use jar:file://
-      uri = "jar:file://" + appInfo.path + "/application.zip!" + fileSpec;
-    } else {
-      // non-CoreApps in child need to ask parent for file handle, use jar:ipcfile://
-      uri = "jar:remoteopenfile://" + appInfo.path + "/application.zip!" + fileSpec;
-    }
-    let channel = Services.io.newChannel(uri, null, null);
-    channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI);
-    channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;
-
-    return channel;
-  },
-
-  allowPort: function app_phAllowPort(aPort, aScheme) {
-    return false;
-  }
-};
-
-/**
-  * This dummy channel implementation only provides enough functionality
-  * to return a fake 404 error when the caller asks for an app:// URL
-  * containing an unknown appId.
-  */
-function DummyChannel() {
-  this.originalURI = Services.io.newURI("app://unknown/nothing.html", null, null);
-  this.URI = Services.io.newURI("app://unknown/nothing.html", null, null);
-}
-
-DummyChannel.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIRequest,
-                                         Ci.nsIChannel,
-                                         Ci.nsIJARChannel]),
-
-  // nsIRequest
-  name: "dummy_app_channel",
-
-  isPending: function dc_isPending() {
-    return this._pending;
-  },
-
-  status: Cr.NS_ERROR_FILE_NOT_FOUND,
-
-  cancel: function dc_cancel() {
-  },
-
-  suspend: function dc_suspend() {
-    this._suspendCount++;
-  },
-
-  resume: function dc_resume() {
-    if (this._suspendCount <= 0)
-      throw Cr.NS_ERROR_UNEXPECTED;
-
-    if (--this._suspendCount == 0 && this._pending) {
-      this._dispatch();
-    }
-  },
-
-  loadGroup: null,
-  loadFlags: Ci.nsIRequest.LOAD_NORMAL,
-
-  // nsIChannel
-  owner: null,
-  notificationCallbacks: null,
-  securityInfo: null,
-  contentType: null,
-  contentCharset: null,
-  contentLength: 0,
-  contentDisposition: Ci.nsIChannel.DISPOSITION_INLINE,
-  contentDispositionFilename: "",
-
-  _pending: false,
-  _suspendCount: 0,
-  _listener: null,
-  _context: null,
-
-  open: function dc_open() {
-    return Cr.NS_ERROR_NOT_IMPLEMENTED;
-  },
-
-  _dispatch: function dc_dispatch() {
-    let request = this;
-
-    Services.tm.currentThread.dispatch(
-    {
-      run: function dc_run() {
-        request._listener.onStartRequest(request, request._context);
-        request._listener.onStopRequest(request, request._context,
-                                        Cr.NS_ERROR_FILE_NOT_FOUND);
-        if (request.loadGroup) {
-          request.loadGroup.removeRequest(request, request._context,
-                                          Cr.NS_ERROR_FILE_NOT_FOUND);
-        }
-        request._pending = false;
-        request.notificationCallbacks = null;
-        request._listener = null;
-        request._context = null;
-      }
-    },
-    Ci.nsIThread.DISPATCH_NORMAL);
-  },
-
-  asyncOpen: function dc_asyncopenfunction(aListener, aContext) {
-    if (this.loadGroup) {
-      this.loadGroup.addRequest(this, aContext);
-    }
-
-    this._listener = aListener;
-    this._context = aContext;
-    this._pending = true;
-
-    if (!this._suspended) {
-      this._dispatch();
-    }
-  },
-
-  // nsIJarChannel, needed for XHR to turn NS_ERROR_FILE_NOT_FOUND into
-  // a 404 error.
-  isUnsafe: false,
-
-  setAppURI: function(aURI) {
-    throw Cr.NS_ERROR_NOT_IMPLEMENTED;
-  }
-};
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppProtocolHandler]);
deleted file mode 100644
--- a/netwerk/protocol/app/AppProtocolHandler.manifest
+++ /dev/null
@@ -1,3 +0,0 @@
-# AppProtocolHander.js
-component {b7ad6144-d344-4687-b2d0-b6b9dce1f07f} AppProtocolHandler.js
-contract @mozilla.org/network/protocol;1?name=app {b7ad6144-d344-4687-b2d0-b6b9dce1f07f}
new file mode 100644
--- /dev/null
+++ b/netwerk/protocol/app/Makefile.in
@@ -0,0 +1,9 @@
+#
+# 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/.
+
+LOCAL_INCLUDES = \
+  -I$(srcdir)/../../base/src \
+  -I$(srcdir)/../../../modules/libjar \
+  $(NULL)
--- a/netwerk/protocol/app/moz.build
+++ b/netwerk/protocol/app/moz.build
@@ -1,10 +1,18 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-EXTRA_COMPONENTS += [
-    'AppProtocolHandler.js',
-    'AppProtocolHandler.manifest',
+MODULE = 'necko'
+
+CPP_SOURCES += [
+    'AppProtocolHandler.cpp',
 ]
+
+LIBRARY_NAME = 'nkapp_s'
+
+FAIL_ON_WARNINGS = True
+
+LIBXUL_LIBRARY = True
+
--- a/security/manager/ssl/tests/mochitest/mixedcontent/test_bug383369.html
+++ b/security/manager/ssl/tests/mochitest/mixedcontent/test_bug383369.html
@@ -41,18 +41,21 @@
     prefs.setIntPref("folderList", 2);
     prefs.setBoolPref("manager.closeWhenDone", true);
     prefs.setBoolPref("manager.showWhenStarting", false);
   
     var theWindow = window;
 
     var useJSTransfer = false;
     try {
-      useJSTransfer = prefs.getBoolPref("useJSTransfer");
-    } catch (ex) { }
+      // This method throws an exception if the old Download Manager is disabled.
+      Services.downloads.activeDownloadCount;
+    } catch (ex) {
+      useJSTransfer = true;
+    }
 
     if (useJSTransfer) {
       var Downloads = SpecialPowers.Cu.import("resource://gre/modules/Downloads.jsm").Downloads;
       Downloads.getList(Downloads.PUBLIC).then(list => {
         list = SpecialPowers.wrap(list);
         list.addView({
           onDownloadAdded: function (aDownload) {
             list.removeView(this);
--- a/storage/src/TelemetryVFS.cpp
+++ b/storage/src/TelemetryVFS.cpp
@@ -10,16 +10,22 @@
 #include "sqlite3.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Util.h"
 #include "mozilla/dom/quota/PersistenceType.h"
 #include "mozilla/dom/quota/QuotaManager.h"
 #include "mozilla/dom/quota/QuotaObject.h"
 #include "mozilla/IOInterposer.h"
 
+// The last VFS version for which this file has been updated.
+#define LAST_KNOWN_VFS_VERSION 3
+
+// The last io_methods version for which this file has been updated.
+#define LAST_KNOWN_IOMETHODS_VERSION 3
+
 /**
  * This preference is a workaround to allow users/sysadmins to identify
  * that the profile exists on an NFS share whose implementation
  * is incompatible with SQLite's default locking implementation.
  * Bug 433129 attempted to automatically identify such file-systems, 
  * but a reliable way was not found and it was determined that the fallback 
  * locking is slower than POSIX locking, so we do not want to do it by default.
 */
@@ -395,45 +401,50 @@ xOpen(sqlite3_vfs* vfs, const char *zNam
 
   rc = orig_vfs->xOpen(orig_vfs, zName, p->pReal, flags, pOutFlags);
   if( rc != SQLITE_OK )
     return rc;
   if( p->pReal->pMethods ){
     sqlite3_io_methods *pNew = new sqlite3_io_methods;
     const sqlite3_io_methods *pSub = p->pReal->pMethods;
     memset(pNew, 0, sizeof(*pNew));
-    // If you update this version number, you must add appropriate IO methods
-    // for any methods added in the version change.
-    pNew->iVersion = 3;
-    MOZ_ASSERT(pNew->iVersion >= pSub->iVersion);
+    // If the io_methods version is higher than the last known one, you should
+    // update this VFS adding appropriate IO methods for any methods added in
+    // the version change.
+    pNew->iVersion = pSub->iVersion;
+    MOZ_ASSERT(pNew->iVersion <= LAST_KNOWN_IOMETHODS_VERSION);
     pNew->xClose = xClose;
     pNew->xRead = xRead;
     pNew->xWrite = xWrite;
     pNew->xTruncate = xTruncate;
     pNew->xSync = xSync;
     pNew->xFileSize = xFileSize;
     pNew->xLock = xLock;
     pNew->xUnlock = xUnlock;
     pNew->xCheckReservedLock = xCheckReservedLock;
     pNew->xFileControl = xFileControl;
     pNew->xSectorSize = xSectorSize;
     pNew->xDeviceCharacteristics = xDeviceCharacteristics;
-    // Methods added in version 2.
-    pNew->xShmMap = pSub->xShmMap ? xShmMap : 0;
-    pNew->xShmLock = pSub->xShmLock ? xShmLock : 0;
-    pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0;
-    pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0;
-    // Methods added in version 3.
-    // SQLite 3.7.17 calls these methods without checking for nullptr first,
-    // so we always define them.  Verify that we're not going to call
-    // nullptrs, though.
-    MOZ_ASSERT(pSub->xFetch);
-    pNew->xFetch = xFetch;
-    MOZ_ASSERT(pSub->xUnfetch);
-    pNew->xUnfetch = xUnfetch;
+    if (pNew->iVersion >= 2) {
+      // Methods added in version 2.
+      pNew->xShmMap = pSub->xShmMap ? xShmMap : 0;
+      pNew->xShmLock = pSub->xShmLock ? xShmLock : 0;
+      pNew->xShmBarrier = pSub->xShmBarrier ? xShmBarrier : 0;
+      pNew->xShmUnmap = pSub->xShmUnmap ? xShmUnmap : 0;
+    }
+    if (pNew->iVersion >= 3) {
+      // Methods added in version 3.
+      // SQLite 3.7.17 calls these methods without checking for nullptr first,
+      // so we always define them.  Verify that we're not going to call
+      // nullptrs, though.
+      MOZ_ASSERT(pSub->xFetch);
+      pNew->xFetch = xFetch;
+      MOZ_ASSERT(pSub->xUnfetch);
+      pNew->xUnfetch = xUnfetch;
+    }
     pFile->pMethods = pNew;
   }
   return rc;
 }
 
 int
 xDelete(sqlite3_vfs* vfs, const char *zName, int syncDir)
 {
@@ -567,40 +578,44 @@ sqlite3_vfs* ConstructTelemetryVFS()
     expected_vfs = vfs->zName && !strcmp(vfs->zName, EXPECTED_VFS);
   }
   if (!expected_vfs) {
     return nullptr;
   }
 
   sqlite3_vfs *tvfs = new ::sqlite3_vfs;
   memset(tvfs, 0, sizeof(::sqlite3_vfs));
-  // If you update this version number, you must add appropriate VFS methods
-  // for any methods added in the version change.
-  tvfs->iVersion = 3;
-  MOZ_ASSERT(vfs->iVersion == tvfs->iVersion);
+  // If the VFS version is higher than the last known one, you should update
+  // this VFS adding appropriate methods for any methods added in the version
+  // change.
+  tvfs->iVersion = vfs->iVersion;
+  MOZ_ASSERT(vfs->iVersion <= LAST_KNOWN_VFS_VERSION);
   tvfs->szOsFile = sizeof(telemetry_file) - sizeof(sqlite3_file) + vfs->szOsFile;
   tvfs->mxPathname = vfs->mxPathname;
   tvfs->zName = "telemetry-vfs";
   tvfs->pAppData = vfs;
   tvfs->xOpen = xOpen;
   tvfs->xDelete = xDelete;
   tvfs->xAccess = xAccess;
   tvfs->xFullPathname = xFullPathname;
   tvfs->xDlOpen = xDlOpen;
   tvfs->xDlError = xDlError;
   tvfs->xDlSym = xDlSym;
   tvfs->xDlClose = xDlClose;
   tvfs->xRandomness = xRandomness;
   tvfs->xSleep = xSleep;
   tvfs->xCurrentTime = xCurrentTime;
   tvfs->xGetLastError = xGetLastError;
-  // Methods added in version 2.
-  tvfs->xCurrentTimeInt64 = xCurrentTimeInt64;
-  // Methods added in version 3.
-  tvfs->xSetSystemCall = xSetSystemCall;
-  tvfs->xGetSystemCall = xGetSystemCall;
-  tvfs->xNextSystemCall = xNextSystemCall;
-
+  if (tvfs->iVersion >= 2) {
+    // Methods added in version 2.
+    tvfs->xCurrentTimeInt64 = xCurrentTimeInt64;
+  }
+  if (tvfs->iVersion >= 3) {
+    // Methods added in version 3.
+    tvfs->xSetSystemCall = xSetSystemCall;
+    tvfs->xGetSystemCall = xGetSystemCall;
+    tvfs->xNextSystemCall = xNextSystemCall;
+  }
   return tvfs;
 }
 
 }
 }
--- a/toolkit/components/downloads/nsDownloadManager.cpp
+++ b/toolkit/components/downloads/nsDownloadManager.cpp
@@ -35,16 +35,17 @@
 #include "nsToolkitCompsCID.h"
 
 #include "SQLFunctions.h"
 
 #include "mozilla/Preferences.h"
 
 #ifdef XP_WIN
 #include <shlobj.h>
+#include "nsWindowsHelpers.h"
 #ifdef DOWNLOAD_SCANNER
 #include "nsDownloadScanner.h"
 #endif
 #endif
 
 #ifdef XP_MACOSX
 #include <CoreFoundation/CoreFoundation.h>
 #endif
@@ -932,17 +933,35 @@ nsDownloadManager::Init()
     mozilla::services::GetStringBundleService();
   if (!bundleService)
     return NS_ERROR_FAILURE;
 
   rv = bundleService->CreateBundle(DOWNLOAD_MANAGER_BUNDLE,
                                    getter_AddRefs(mBundle));
   NS_ENSURE_SUCCESS(rv, rv);
 
+#if defined(MOZ_JSDOWNLOADS) && !defined(XP_WIN)
+
+  // When MOZ_JSDOWNLOADS is defined on a non-Windows platform, this component
+  // is always disabled and we can safely omit the initialization code.
+  mUseJSTransfer = true;
+
+#else
+
+#if defined(MOZ_JSDOWNLOADS) && defined(XP_WIN)
+  // When MOZ_JSDOWNLOADS is defined on Windows, this component is disabled
+  // unless we are running in Windows Metro.  The conversion of Windows Metro
+  // to use the JavaScript API for downloads is tracked in bug 906042.
+  mUseJSTransfer = !IsRunningInWindowsMetro();
+#else
+  // When MOZ_JSDOWNLOADS is undefined, we still check the preference that can
+  // be used to enable the JavaScript API during the migration process.
   mUseJSTransfer = Preferences::GetBool(PREF_BD_USEJSTRANSFER, false);
+#endif
+
   if (mUseJSTransfer)
     return NS_OK;
 
   // Clean up any old downloads.rdf files from before Firefox 3
   {
     nsCOMPtr<nsIFile> oldDownloadsFile;
     bool fileExists;
     if (NS_SUCCEEDED(NS_GetSpecialDirectory(NS_APP_DOWNLOADS_50_FILE,
@@ -1003,16 +1022,18 @@ nsDownloadManager::Init()
   (void)mObserverService->AddObserver(this, NS_IOSERVICE_GOING_OFFLINE_TOPIC, true);
   (void)mObserverService->AddObserver(this, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, true);
   (void)mObserverService->AddObserver(this, "last-pb-context-exited", true);
   (void)mObserverService->AddObserver(this, "last-pb-context-exiting", true);
 
   if (history)
     (void)history->AddObserver(this, true);
 
+#endif // defined(MOZ_JSDOWNLOADS) && !defined(XP_WIN)
+
   return NS_OK;
 }
 
 int32_t
 nsDownloadManager::GetRetentionBehavior()
 {
   // We use 0 as the default, which is "remove when done"
   nsresult rv;
--- a/toolkit/components/downloads/test/moz.build
+++ b/toolkit/components/downloads/test/moz.build
@@ -1,11 +1,13 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
-TEST_DIRS += ['browser']
-
 MODULE = 'test_dm'
 
-XPCSHELL_TESTS_MANIFESTS += ['schema_migration/xpcshell.ini', 'unit/xpcshell.ini']
+if not CONFIG['MOZ_JSDOWNLOADS']:
+    TEST_DIRS += ['browser']
+    XPCSHELL_TESTS_MANIFESTS += ['schema_migration/xpcshell.ini']
+
+XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
--- a/toolkit/components/downloads/test/unit/head_download_manager.js
+++ b/toolkit/components/downloads/test/unit/head_download_manager.js
@@ -238,14 +238,15 @@ XPCOMUtils.defineLazyGetter(this, "Servi
 Services.prefs.setBoolPref("browser.download.manager.showAlertOnComplete", false);
 
 do_register_cleanup(function() {
   Services.obs.notifyObservers(null, "quit-application", null);
 });
 
 function oldDownloadManagerDisabled() {
   try {
-    if (Services.prefs.getBoolPref("browser.download.useJSTransfer")) {
-      return true;
-    }
-  } catch (ex) { }
+    // This method throws an exception if the old Download Manager is disabled.
+    Services.downloads.activeDownloadCount;
+  } catch (ex) {
+    return true;
+  }
   return false;
 }
\ No newline at end of file
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -904,17 +904,43 @@ EngineURL.prototype = {
 
   // Note: This method requires that aObj has a unique name or the previous MozParams entry with
   // that name will be overwritten.
   _addMozParam: function SRCH_EURL__addMozParam(aObj) {
     aObj.mozparam = true;
     this.mozparams[aObj.name] = aObj;
   },
 
+  reevalMozParams: function(engine) {
+    for (let param of this.params) {
+      let mozparam = this.mozparams[param.name];
+      if (mozparam && mozparam.positionDependent) {
+        // the condition is a string in the form of "topN", extract N as int
+        let positionStr = mozparam.condition.slice("top".length);
+        let position = parseInt(positionStr, 10);
+        let engines;
+        try {
+          // This will throw if we're not initialized yet (which shouldn't happen), just 
+          // ignore and move on with the false Value (checking isInitialized also throws)
+          // XXX
+          engines = Services.search.getVisibleEngines({});
+        } catch (ex) {
+          LOG("reevalMozParams called before search service initialization!?");
+          break;
+        }
+        let index = engines.map((e) => e.wrappedJSObject).indexOf(engine.wrappedJSObject);
+        let isTopN = index > -1 && (index + 1) <= position;
+        param.value = isTopN ? mozparam.trueValue : mozparam.falseValue;
+      }
+    }
+  },
+
   getSubmission: function SRCH_EURL_getSubmission(aSearchTerms, aEngine, aPurpose) {
+    this.reevalMozParams(aEngine);
+
     var url = ParamSubstitution(this.template, aSearchTerms, aEngine);
     // Default to an empty string if the purpose is not provided so that default purpose params
     // (purpose="") work consistently rather than having to define "null" and "" purposes.
     var purpose = aPurpose || "";
 
     // Create an application/x-www-form-urlencoded representation of our params
     // (name=value&name=value&name=value)
     var dataString = "";
@@ -1642,17 +1668,18 @@ Engine.prototype = {
         } catch (ex) {
           // Ignore failure
           LOG("_parseURL: Url element has an invalid param");
         }
       } else if (param.localName == "MozParam" &&
                  // We only support MozParams for default search engines
                  this._isDefault) {
         var value;
-        switch (param.getAttribute("condition")) {
+        let condition = param.getAttribute("condition");
+        switch (condition) {
           case "purpose":
             url.addParam(param.getAttribute("name"),
                          param.getAttribute("value"),
                          param.getAttribute("purpose"));
             // _addMozParam is not needed here since it can be serialized fine without. _addMozParam
             // also requires a unique "name" which is not normally the case when @purpose is used.
             break;
           case "defaultEngine":
@@ -1672,16 +1699,27 @@ Engine.prototype = {
             try {
               value = getMozParamPref(param.getAttribute("pref"), value);
               url.addParam(param.getAttribute("name"), value);
               url._addMozParam({"pref": param.getAttribute("pref"),
                                 "name": param.getAttribute("name"),
                                 "condition": "pref"});
             } catch (e) { }
             break;
+          default:
+            if (condition && condition.startsWith("top")) {
+              url.addParam(param.getAttribute("name"), param.getAttribute("falseValue"));
+              let mozparam = {"name": param.getAttribute("name"),
+                              "falseValue": param.getAttribute("falseValue"),
+                              "trueValue": param.getAttribute("trueValue"),
+                              "condition": condition,
+                              "positionDependent": true};
+              url._addMozParam(mozparam);
+            }
+          break;
         }
       }
     }
 
     this._urls.push(url);
   },
 
   _isDefaultEngine: function SRCH_ENG__isDefaultEngine() {
--- a/toolkit/devtools/apps/tests/Makefile.in
+++ b/toolkit/devtools/apps/tests/Makefile.in
@@ -9,12 +9,14 @@ MOCHITEST_FILES = \
     debugger-protocol-helper.js \
     redirect.sjs \
     $(NULL)
 
 MOCHITEST_DATA_FILES = \
     data/app-redirect.zip \
     data/app-updated.zip \
     data/app.zip \
+    data/app-certified.zip \
     $(NULL)
+
 MOCHITEST_DATA_DEST = $(MOCHITEST_DEST)/data
 INSTALL_TARGETS += MOCHITEST_DATA
 endif
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..3dbdedb8cafbc21377cecd74db25d8bea24cdda6
GIT binary patch
literal 501
zc$^FHW@Zs#U|`^2IFdTgi7D`)iaU@O3&dOuG7On{DXA5D86~+np&^_M%pVri2ZC^E
z1vdjD%L`@(1~9QX(C4JT-bx*h&=VRb&u)CYsWEV6r)HU%>P%J3m1SnX45V~UrB0qQ
zKPdC~(ghD@#7v4`5-=$$X3>O*fCa}>7xPbK)MA;<#1H^?N&{1X^TH1+W~Tvp^+3!I
zbV_bwUS?Wqafx1eYEoiB0oXkg_QwW+Fq(Tz&l++aHsEo%cz3zLEa%<nXGM33Uk%H4
zT*MKr%DJ@Mf9{2!_CMw`G<XRo^IH@YR&uE;7{7_~ib)nMp1HMCI$XT=;Gur`l@1r5
z7oD1TPwMmC6UCFmFCKZV`rBmQU2|)-kM;*Y@LC6WGcw6B;|e<o1`uEb;w_CJ7IH|k
oLP8SF)yU>y1^}{o-9Qo}I9MUUfng{s8%QG)5H1DMRv;4?03J-2=>Px#
--- a/toolkit/devtools/apps/tests/test_webapps_actor.html
+++ b/toolkit/devtools/apps/tests/test_webapps_actor.html
@@ -150,16 +150,36 @@ var steps = [
         }
         ok(!("error" in aResponse), "app installed without any error");
         is(aApp.manifest.name, "updated-name", "app name on update is correct");
         next();
       }
     );
   },
   function() {
+    ok(true, "== TEST == Install certified app");
+    let appId = "test-certified-id";
+    let url = SimpleTest.getTestFileURL("data/app-certified.zip");
+    installTestApp(url, appId,
+      function (aResponse, aApp) {
+        ok(true, "Installed");
+        is(aResponse.appId, appId, "Got same app id");
+        if ("error" in aResponse) {
+          ok(false, "Error: " + aResponse.error);
+        }
+        if ("message" in aResponse) {
+          ok(false, "Error message: " + aResponse.message);
+        }
+        ok(!("error" in aResponse), "app installed without any error");
+        is(aApp.manifest.name, "Certified app", "app name is correct");
+        next();
+      }
+    );
+  },
+  function() {
     ok(true, "all done!\n");
     mm.sendAsyncMessage("cleanup");
     SpecialPowers.popPrefEnv(finish);
   }
 ];
 
 addLoadEvent(start);
 
--- a/toolkit/devtools/client/connection-manager.js
+++ b/toolkit/devtools/client/connection-manager.js
@@ -216,17 +216,27 @@ Connection.prototype = {
     this._setStatus(Connection.Status.DESTROYED);
   },
 
   _clientConnect: function () {
     let transport;
     if (!this.host) {
       transport = DebuggerServer.connectPipe();
     } else {
-      transport = debuggerSocketConnect(this.host, this.port);
+      try {
+        transport = debuggerSocketConnect(this.host, this.port);
+      } catch (e) {
+        // In some cases, especially on Mac, the openOutputStream call in
+        // debuggerSocketConnect may throw NS_ERROR_NOT_INITIALIZED.
+        // It occurs when we connect agressively to the simulator,
+        // and keep trying to open a socket to the server being started in
+        // the simulator.
+        this._onDisconnected();
+        return;
+      }
     }
     this._client = new DebuggerClient(transport);
     this._client.addOneTimeListener("closed", this._onDisconnected);
     this._client.connect(this._onConnected);
   },
 
   get status() {
     return this._status
@@ -239,17 +249,17 @@ Connection.prototype = {
     this.emit(value);
     this.emit(Connection.Events.STATUS_CHANGED, value);
   },
 
   _onDisconnected: function() {
     this._client = null;
 
     if (this._status == Connection.Status.CONNECTING && this.keepConnecting) {
-      setTimeout(() => this._clientConnect(), 0);
+      setTimeout(() => this._clientConnect(), 100);
       return;
     }
 
     clearTimeout(this._timeoutID);
 
     switch (this.status) {
       case Connection.Status.CONNECTED:
         this.log("disconnected (unexpected)");
--- a/toolkit/devtools/client/dbg-client.jsm
+++ b/toolkit/devtools/client/dbg-client.jsm
@@ -2195,18 +2195,34 @@ eventSource(EnvironmentClient.prototype)
  * @param aHost string
  *        The host name or IP address of the debugger server.
  * @param aPort number
  *        The port number of the debugger server.
  */
 this.debuggerSocketConnect = function debuggerSocketConnect(aHost, aPort)
 {
   let s = socketTransportService.createTransport(null, 0, aHost, aPort, null);
-  let transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
-                                        s.openOutputStream(0, 0, 0));
+  // By default the CONNECT socket timeout is very long, 65535 seconds,
+  // so that if we race to be in CONNECT state while the server socket is still
+  // initializing, the connection is stuck in connecting state for 18.20 hours!
+  s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
+
+  // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
+  // where the nsISocketTransport gets shutdown in between its instantiation and
+  // the call to this method.
+  let transport;
+  try {
+    transport = new DebuggerTransport(s.openInputStream(0, 0, 0),
+                                      s.openOutputStream(0, 0, 0));
+  } catch(e) {
+    let msg = e + ": " + e.stack;
+    Cu.reportError(msg);
+    dumpn(msg);
+    throw e;
+  }
   return transport;
 }
 
 /**
  * Takes a pair of items and returns them as an array.
  */
 function pair(aItemOne, aItemTwo) {
   return [aItemOne, aItemTwo];
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -384,23 +384,16 @@ WebappsActor.prototype = {
           try {
             manifest = JSON.parse(jsonString);
           } catch(e) {
             self._sendError(deferred, "Error Parsing manifest.webapp: " + e, aId);
           }
 
           let appType = self._getAppType(manifest.type);
 
-          // In production builds, don't allow installation of certified apps.
-          if (!DOMApplicationRegistry.allowSideloadingCertified &&
-              appType == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
-            self._sendError(deferred, "Installing certified apps is not allowed.", aId);
-            return;
-          }
-
           // Privileged and certified packaged apps can setup a custom origin
           // via `origin` manifest property
           let id = aId;
           if (appType >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED &&
               manifest.origin !== undefined) {
             let uri;
             try {
               uri = Services.io.newURI(manifest.origin, null, null);
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -93,18 +93,21 @@ this.ForgetAboutSite = {
           // Ignore errors from the plugin
         }
       }
     }
 
     // Downloads
     let useJSTransfer = false;
     try {
-      useJSTransfer = Services.prefs.getBoolPref("browser.download.useJSTransfer");
-    } catch(ex) { }
+      // This method throws an exception if the old Download Manager is disabled.
+      Services.downloads.activeDownloadCount;
+    } catch (ex) {
+      useJSTransfer = true;
+    }
 
     if (useJSTransfer) {
       Task.spawn(function() {
         let list = yield Downloads.getList(Downloads.ALL);
         list.removeFinished(download => hasRootDomain(
              NetUtil.newURI(download.source.url).host, aDomain));
       }).then(null, Cu.reportError);
     }
--- a/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js
+++ b/toolkit/forgetaboutsite/test/unit/head_forgetaboutsite.js
@@ -25,8 +25,19 @@ function cleanUp()
   for (let i = 0; i < files.length; i++) {
     let file = dirSvc.get("ProfD", Ci.nsIFile);
     file.append(files[i]);
     if (file.exists())
       file.remove(false);
   }
 }
 cleanUp();
+
+function oldDownloadManagerDisabled()
+{
+  try {
+    // This method throws an exception if the old Download Manager is disabled.
+    Services.downloads.activeDownloadCount;
+  } catch (ex) {
+    return true;
+  }
+  return false;
+}
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain.js
@@ -440,32 +440,44 @@ function test_cookie_not_cleared_with_ur
   add_cookie(TEST_DOMAIN);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   check_cookie_exists(TEST_DOMAIN, true);
 }
 
 // Download Manager
 function test_download_history_cleared_with_direct_match()
 {
+  if (oldDownloadManagerDisabled()) {
+    return;
+  }
+
   const TEST_URI = "http://mozilla.org/foo";
   add_download(TEST_URI, false);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   check_downloaded(TEST_URI, false);
 }
 
 function test_download_history_cleared_with_subdomain()
 {
+  if (oldDownloadManagerDisabled()) {
+    return;
+  }
+
   const TEST_URI = "http://www.mozilla.org/foo";
   add_download(TEST_URI, false);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   check_downloaded(TEST_URI, false);
 }
 
 function test_download_history_not_cleared_with_active_direct_match()
 {
+  if (oldDownloadManagerDisabled()) {
+    return;
+  }
+
   // Tests that downloads marked as active in the db are not deleted from the db
   const TEST_URI = "http://mozilla.org/foo";
   add_download(TEST_URI, true);
   ForgetAboutSite.removeDataFromDomain("mozilla.org");
   check_downloaded(TEST_URI, true);
 
   // Reset state
   let db = Cc["@mozilla.org/download-manager;1"].
--- a/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain_activeDownloads.js
+++ b/toolkit/forgetaboutsite/test/unit/test_removeDataFromDomain_activeDownloads.js
@@ -77,16 +77,20 @@ function makeGUID() {
   let guid = "";
   for (var i = 0; i < 12; i++)
     guid += Math.floor(Math.random() * 10);
   return guid;
 }
 
 function run_test()
 {
+  if (oldDownloadManagerDisabled()) {
+    return;
+  }
+
   // We add this data to the database first, but we cannot instantiate the
   // download manager service, otherwise these downloads will not be placed in
   // the active downloads array.
 
   // Copy the empty downloads database to our profile directory
   let downloads = do_get_file("downloads.empty.sqlite");
   downloads.copyTo(dirSvc.get("ProfD", Ci.nsIFile), "downloads.sqlite");
 
--- a/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm
+++ b/toolkit/mozapps/downloads/DownloadTaskbarProgress.jsm
@@ -112,25 +112,16 @@ var DownloadTaskbarProgressUpdater =
    */
   _init: function DTPU_init()
   {
     if (this._initialized) {
       return; // Already initialized
     }
     this._initialized = true;
 
-    // Taskbar progress is disabled until this component is updated to use the
-    // asynchronous JavaScript API for downloads.
-    try {
-      if (Services.prefs.getBoolPref("browser.download.useJSTransfer")) {
-        DownloadTaskbarProgressUpdater = null;
-        return;
-      }
-    } catch (ex) { }
-
     if (kTaskbarIDWin in Cc) {
       this._taskbar = Cc[kTaskbarIDWin].getService(Ci.nsIWinTaskbar);
       if (!this._taskbar.available) {
         // The Windows version is probably too old
         DownloadTaskbarProgressUpdater = null;
         return;
       }
     } else if (kTaskbarIDMac in Cc) {
--- a/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_service.xul
+++ b/toolkit/mozapps/downloads/tests/chrome/test_taskbarprogress_service.xul
@@ -24,16 +24,22 @@ const DOWNLOAD_MANAGER_URL = "chrome://m
 const DLMGR_UI_DONE = "download-manager-ui-done";
 
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 let DownloadTaskbarProgress, TaskbarService, observerService, wwatch, chromeWindow;
 let gGen = null;
 
 function test() {
+  var dmui = getDMUI();
+  if (!dmui) {
+    todo(false, "skip test for toolkit download manager UI");
+    return;
+  }
+
   let isWin7OrHigher = false;
   try {
     let version = Cc["@mozilla.org/system-info;1"]
                     .getService(Ci.nsIPropertyBag2)
                     .getProperty("version");
     isWin7OrHigher = (parseFloat(version) >= 6.1);
   } catch (ex) { }
 
--- a/toolkit/mozapps/downloads/tests/chrome/utils.js
+++ b/toolkit/mozapps/downloads/tests/chrome/utils.js
@@ -18,20 +18,21 @@ Components.utils.import("resource://gre/
  * SeaMonkey doesn't package that version but an own implementation that calls
  * different UI), then returns false (see bug 483781).
  *
  * @returns toolkit's nsIDownloadManagerUI implementation or false if not found
  */
 function getDMUI()
 {
   try {
-    if (Services.prefs.getBoolPref("browser.download.useJSTransfer")) {
-      return false;
-    }
-  } catch (ex) { }
+    // This method throws an exception if the old Download Manager is disabled.
+    Services.downloads.activeDownloadCount;
+  } catch (ex) {
+    return false;
+  }
   if (Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"])
     return Components.classesByID["{7dfdf0d1-aff6-4a34-bad1-d0fe74601642}"].
            getService(Ci.nsIDownloadManagerUI);
   return false;
 }
 
 /**
  * Adds a live download to the download manager.
--- a/uriloader/exthandler/tests/moz.build
+++ b/uriloader/exthandler/tests/moz.build
@@ -9,17 +9,20 @@ DIRS += ['mochitest']
 FAIL_ON_WARNINGS = True
 
 MODULE = 'test_uriloader_exthandler'
 
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
 
 #FIXME/bug 575918: out-of-process xpcshell is broken on OS X
 if CONFIG['OS_ARCH'] != 'Darwin':
-    XPCSHELL_TESTS_MANIFESTS += ['unit_ipc/xpcshell.ini']
+    # The encoding test is already implemented in the Downloads API by a set of
+    # test cases with the string "content_encoding" in their names.
+    if not CONFIG['MOZ_JSDOWNLOADS']:
+        XPCSHELL_TESTS_MANIFESTS += ['unit_ipc/xpcshell.ini']
 
 sources = [
     'WriteArgument',
 ]
 
 CPP_SOURCES += [
     '%s.cpp' % s for s in sources
 ]