Merge m-c to b2ginbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 05 May 2015 14:55:41 -0700
changeset 273804 ac5405073f524b617aa6ef3c440ee7bc7839e869
parent 273803 994278cdd2459657a24307bc29435e3cd02c8be3 (current diff)
parent 273755 5907a8eca521ae88ca9caa7cba13346a8d0c16b7 (diff)
child 273805 ba44099cbd079063a4793f8ebccde0b074c2cd5a
child 273806 1bdc569c9617679d68c2a9ac1f5bce18b7f09900
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.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 m-c to b2ginbound a=merge
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -261,16 +261,20 @@
       <menuitem id="context-sharepage"
                 label="&sharePageCmd.label;"
                 accesskey="&sharePageCmd.accesskey;"
                 oncommand="SocialShare.sharePage();"/>
       <menuitem id="context-savepage"
                 label="&savePageCmd.label;"
                 accesskey="&savePageCmd.accesskey2;"
                 oncommand="gContextMenu.savePageAs();"/>
+      <menuitem id="context-pocket"
+                label="&saveToPocketCmd.label;"
+                accesskey="&saveToPocketCmd.accesskey;"
+                oncommand="gContextMenu.saveToPocket();"/>
       <menu id="context-markpageMenu" label="&social.markpageMenu.label;"
             accesskey="&social.markpageMenu.accesskey;">
         <menupopup/>
       </menu>
       <menuseparator id="context-sep-viewbgimage"/>
       <menuitem id="context-viewbgimage"
                 label="&viewBGImageCmd.label;"
                 accesskey="&viewBGImageCmd.accesskey;"
--- a/browser/base/content/browser-doctype.inc
+++ b/browser/base/content/browser-doctype.inc
@@ -1,13 +1,15 @@
 <!DOCTYPE window [
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
 %brandDTD;
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd" >
 %browserDTD;
+<!ENTITY % browserPocketDTD SYSTEM "chrome://browser/content/browser-pocket.dtd" >
+%browserPocketDTD;
 <!ENTITY % baseMenuDTD SYSTEM "chrome://browser/locale/baseMenuOverlay.dtd" >
 %baseMenuDTD;
 <!ENTITY % charsetDTD SYSTEM "chrome://global/locale/charsetMenu.dtd" >
 %charsetDTD;
 <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd" >
 %textcontextDTD;
 <!ENTITY % customizeToolbarDTD SYSTEM "chrome://global/locale/customizeToolbar.dtd">
   %customizeToolbarDTD;
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -382,16 +382,17 @@
 #ifndef XP_MACOSX
                placespopup="true"
 #endif
                context="placesContext"
                openInTabs="children"
                oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
                onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                onpopupshowing="BookmarkingUI.onMainMenuPopupShowing(event);
+                               BookmarkingUI.updatePocketItemVisibility('menu_');
                                if (!this.parentNode._placesView)
                                  new PlacesMenu(event, 'place:folder=BOOKMARKS_MENU');"
                tooltip="bhTooltip" popupsinherittooltip="true">
       <menuitem id="bookmarksShowAll"
                 label="&showAllBookmarks2.label;"
                 command="Browser:ShowAllBookmarks"
                 key="manBookmarkKb"/>
       <menuseparator id="organizeBookmarksSeparator"/>
@@ -455,16 +456,19 @@
           <menuitem id="viewReadingListSidebar" class="subviewbutton"
                     oncommand="SidebarUI.toggle('readingListSidebar');"
                     label="&readingList.showSidebar.label;">
             <observes element="readingListSidebar" attribute="checked"/>
           </menuitem>
         </menupopup>
       </menu>
 #endif
+      <menuseparator id="menu_pocketSeparator"/>
+      <menuitem id="menu_pocket" label="&pocketMenuitem.label;"
+                oncommand="openUILink(Pocket.listURL, event);"/>
       <menuseparator id="bookmarksMenuItemsSeparator"/>
       <!-- Bookmarks menu items -->
       <menuseparator builder="end"
                      class="hide-if-empty-places-result"/>
       <menuitem id="menu_unsortedBookmarks"
                 label="&unsortedBookmarksCmd.label;"
                 oncommand="PlacesCommandHook.showPlacesOrganizer('UnfiledBookmarks');"/>
     </menupopup>
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1560,16 +1560,22 @@ let BookmarkingUI = {
     this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
   },
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     this._updateBookmarkPageMenuItem();
     PlacesCommandHook.updateBookmarkAllTabsCommand();
   },
 
+  updatePocketItemVisibility: function BUI_updatePocketItemVisibility(prefix) {
+    let hidden = !CustomizableUI.getPlacementOfWidget("pocket-button");
+    document.getElementById(prefix + "pocket").hidden = hidden;
+    document.getElementById(prefix + "pocketSeparator").hidden = hidden;
+  },
+
   _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
     function getCenteringTransformForRects(rectToPosition, referenceRect) {
       let topDiff = referenceRect.top - rectToPosition.top;
       let leftDiff = referenceRect.left - rectToPosition.left;
       let heightDiff = referenceRect.height - rectToPosition.height;
       let widthDiff = referenceRect.width - rectToPosition.width;
       return [(leftDiff + .5 * widthDiff) + "px", (topDiff + .5 * heightDiff) + "px"];
     }
@@ -1677,16 +1683,17 @@ let BookmarkingUI = {
       case "ViewHiding":
         this.onPanelMenuViewHiding(aEvent);
         break;
     }
   },
 
   onPanelMenuViewShowing: function BUI_onViewShowing(aEvent) {
     this._updateBookmarkPageMenuItem();
+    this.updatePocketItemVisibility("panelMenu_");
     // Update checked status of the toolbar toggle.
     let viewToolbar = document.getElementById("panelMenu_viewBookmarksToolbar");
     let personalToolbar = document.getElementById("PersonalToolbar");
     if (personalToolbar.collapsed)
       viewToolbar.removeAttribute("checked");
     else
       viewToolbar.setAttribute("checked", "true");
     // Get all statically placed buttons to supply them with keyboard shortcuts.
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-pocket.dtd
@@ -0,0 +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/. -->
+
+<!-- This is a temporary file and not meant for localization; later versions
+   - of Firefox include these strings in browser.dtd -->
+
+<!ENTITY saveToPocketCmd.label     "Save Page to Pocket">
+<!ENTITY saveToPocketCmd.accesskey "k">
+<!ENTITY pocketMenuitem.label      "View Pocket List">
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -878,16 +878,17 @@
           <menupopup id="BMB_bookmarksPopup"
                      class="cui-widget-panel cui-widget-panelview cui-widget-panelWithFooter PanelUI-subView"
                      placespopup="true"
                      context="placesContext"
                      openInTabs="children"
                      oncommand="BookmarksEventHandler.onCommand(event, this.parentNode._placesView);"
                      onclick="BookmarksEventHandler.onClick(event, this.parentNode._placesView);"
                      onpopupshowing="BookmarkingUI.onPopupShowing(event);
+                                     BookmarkingUI.updatePocketItemVisibility('BMB_');
                                      BookmarkingUI.attachPlacesView(event, this);"
                      tooltip="bhTooltip" popupsinherittooltip="true">
             <menuitem id="BMB_viewBookmarksSidebar"
                       class="subviewbutton"
                       label="&viewBookmarksSidebar2.label;"
                       type="checkbox"
                       oncommand="SidebarUI.toggle('viewBookmarksSidebar');">
               <observes element="viewBookmarksSidebar" attribute="checked"/>
@@ -965,16 +966,21 @@
               <menupopup id="BMB_readingListPopup"
                          placespopup="true"
                          onpopupshowing="ReadingListUI.onReadingListPopupShowing(this);">
                 <menuitem id="BMB_viewReadingListSidebar" class="subviewbutton"
                           oncommand="SidebarUI.show('readingListSidebar');"
                           label="&readingList.showSidebar.label;"/>
               </menupopup>
             </menu>
+            <menuseparator id="BMB_pocketSeparator"/>
+            <menuitem id="BMB_pocket"
+                      class="menuitem-iconic bookmark-item subviewbutton"
+                      label="&pocketMenuitem.label;"
+                      oncommand="openUILink(Pocket.listURL, event);"/>
             <menuseparator/>
             <!-- Bookmarks menu items will go here -->
             <menuitem id="BMB_bookmarksShowAll"
                       class="subviewbutton panel-subview-footer"
                       label="&showAllBookmarks2.label;"
                       command="Browser:ShowAllBookmarks"
                       key="manBookmarkKb"/>
           </menupopup>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -172,16 +172,27 @@ nsContextMenu.prototype = {
     // SimpleServiceDiscovery.services), but SimpleServiceDiscovery is guaranteed
     // to be already loaded, since we load it on startup in nsBrowserGlue,
     // and CastingApps isn't, so check SimpleServiceDiscovery.services first
     // to avoid needing to load CastingApps.jsm if we don't need to.
     shouldShowCast = shouldShowCast && this.mediaURL &&
                      SimpleServiceDiscovery.services.length > 0 &&
                      CastingApps.getServicesForVideo(this.target).length > 0;
     this.setItemAttr("context-castvideo", "disabled", !shouldShowCast);
+
+    let canPocket = false;
+    if (shouldShow && window.gBrowser &&
+        this.browser.getTabBrowser() == window.gBrowser) {
+      let uri = this.browser.currentURI;
+      canPocket =
+        CustomizableUI.getPlacementOfWidget("pocket-button") &&
+        (uri.schemeIs("http") || uri.schemeIs("https") ||
+         (uri.schemeIs("about") && ReaderMode.getOriginalUrl(uri.spec)));
+    }
+    this.showItem("context-pocket", canPocket && window.Pocket && Pocket.isLoggedIn);
   },
 
   initViewItems: function CM_initViewItems() {
     // View source is always OK, unless in directory listing.
     this.showItem("context-viewpartialsource-selection",
                   this.isContentSelected);
     this.showItem("context-viewpartialsource-mathml",
                   this.onMathML && !this.isContentSelected);
@@ -1616,16 +1627,32 @@ nsContextMenu.prototype = {
   shareSelect: function CM_shareSelect() {
     SocialShare.sharePage(null, { url: this.browser.currentURI.spec, text: this.textSelected }, this.target);
   },
 
   savePageAs: function CM_savePageAs() {
     saveDocument(this.browser.contentDocumentAsCPOW);
   },
 
+  saveToPocket: function CM_saveToPocket() {
+    let pocketWidget = document.getElementById("pocket-button");
+    let placement = CustomizableUI.getPlacementOfWidget("pocket-button");
+    if (!placement)
+      return;
+
+    if (placement.area == CustomizableUI.AREA_PANEL) {
+      PanelUI.show().then(function() {
+        pocketWidget = document.getElementById("pocket-button");
+        pocketWidget.doCommand();
+      });
+    } else {
+      pocketWidget.doCommand();
+    }
+  },
+
   printFrame: function CM_printFrame() {
     PrintUtils.print(this.target.ownerDocument.defaultView, this.browser);
   },
 
   switchPageDirection: function CM_switchPageDirection() {
     this.browser.messageManager.sendAsyncMessage("SwitchDocumentDirection");
   },
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1499,22 +1499,18 @@
             let index = tab._tPos;
             let filter = this.mTabFilters[index];
             aBrowser.webProgress.removeProgressListener(filter);
             // Make sure the browser is destroyed so it unregisters from observer notifications
             aBrowser.destroy();
 
             // Change the "remote" attribute.
             let parent = aBrowser.parentNode;
-            let permanentKey = aBrowser.permanentKey;
             parent.removeChild(aBrowser);
             aBrowser.setAttribute("remote", aShouldBeRemote ? "true" : "false");
-            // Tearing down the browser gives a new permanentKey but we want to
-            // keep the old one. Re-set it explicitly after unbinding from DOM.
-            aBrowser._permanentKey = permanentKey;
             parent.appendChild(aBrowser);
 
             // Restore the progress listener.
             aBrowser.webProgress.addProgressListener(filter, Ci.nsIWebProgress.NOTIFY_ALL);
 
             if (aShouldBeRemote) {
               // Switching the browser to be remote will connect to a new child
               // process so the browser can no longer be considered to be
@@ -1623,16 +1619,17 @@
           <![CDATA[
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
             let remote = aParams && aParams.remote;
             let uriIsAboutBlank = aParams && aParams.uriIsAboutBlank;
             let isPreloadBrowser = aParams && aParams.isPreloadBrowser;
 
             let b = document.createElementNS(NS_XUL, "browser");
+            b.permanentKey = {};
             b.setAttribute("type", "content-targetable");
             b.setAttribute("message", "true");
             b.setAttribute("messagemanagergroup", "browsers");
             b.setAttribute("contextmenu", this.getAttribute("contentcontextmenu"));
             b.setAttribute("tooltip", this.getAttribute("contenttooltip"));
 
             if (remote)
               b.setAttribute("remote", "true");
@@ -2486,16 +2483,21 @@
             filter.removeProgressListener(tabListener);
 
             // Make sure to unregister any open URIs.
             this._swapRegisteredOpenURIs(ourBrowser, aOtherBrowser);
 
             // Swap the docshells
             ourBrowser.swapDocShells(aOtherBrowser);
 
+            // Swap permanentKey properties.
+            let ourPermanentKey = ourBrowser.permanentKey;
+            ourBrowser.permanentKey = aOtherBrowser.permanentKey;
+            aOtherBrowser.permanentKey = ourPermanentKey;
+
             // Restore the progress listener
             this.mTabListeners[index] = tabListener =
               this.mTabProgressListener(aOurTab, ourBrowser, false);
 
             const notifyAll = Ci.nsIWebProgress.NOTIFY_ALL;
             filter.addProgressListener(tabListener, notifyAll);
             ourBrowser.webProgress.addProgressListener(filter, notifyAll);
           ]]>
@@ -3871,16 +3873,17 @@
           }
         ]]></body>
       </method>
 
       <constructor>
         <![CDATA[
           let browserStack = document.getAnonymousElementByAttribute(this, "anonid", "browserStack");
           this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
+          this.mCurrentBrowser.permanentKey = {};
 
           this.mCurrentTab = this.tabContainer.firstChild;
           const nsIEventListenerService =
             Components.interfaces.nsIEventListenerService;
           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                               .getService(nsIEventListenerService);
           els.addSystemEventListener(document, "keydown", this, false);
           if (this.AppConstants.platform == "macosx") {
--- a/browser/base/content/test/general/browser_bug580956.js
+++ b/browser/base/content/test/general/browser_bug580956.js
@@ -14,14 +14,14 @@ function test() {
   is(numClosedTabs(), 0, "There should be 0 closed tabs.");
   ok(!isUndoCloseEnabled(), "Undo Close Tab should be disabled.");
 
   var tab = gBrowser.addTab("http://mochi.test:8888/");
   var browser = gBrowser.getBrowserForTab(tab);
   browser.addEventListener("load", function() {
     browser.removeEventListener("load", arguments.callee, true);
 
-    gBrowser.removeTab(tab);
-    ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
-
-    finish();
+    BrowserTestUtils.removeTab(tab).then(() => {
+      ok(isUndoCloseEnabled(), "Undo Close Tab should be enabled.");
+      finish();
+    });
   }, true);
 }
--- a/browser/base/content/test/general/browser_bug817947.js
+++ b/browser/base/content/test/general/browser_bug817947.js
@@ -29,24 +29,25 @@ function test() {
     });
   });
 }
 
 function preparePendingTab(aCallback) {
   let tab = gBrowser.addTab(URL);
 
   whenLoaded(tab.linkedBrowser, function () {
-    gBrowser.removeTab(tab);
-    let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
+    BrowserTestUtils.removeTab(tab).then(() => {
+      let [{state}] = JSON.parse(SessionStore.getClosedTabData(window));
 
-    tab = gBrowser.addTab("about:blank");
-    whenLoaded(tab.linkedBrowser, function () {
-      SessionStore.setTabState(tab, JSON.stringify(state));
-      ok(tab.hasAttribute("pending"), "tab should be pending");
-      aCallback(tab);
+      tab = gBrowser.addTab("about:blank");
+      whenLoaded(tab.linkedBrowser, function () {
+        SessionStore.setTabState(tab, JSON.stringify(state));
+        ok(tab.hasAttribute("pending"), "tab should be pending");
+        aCallback(tab);
+      });
     });
   });
 }
 
 function whenLoaded(aElement, aCallback) {
   aElement.addEventListener("load", function onLoad() {
     aElement.removeEventListener("load", onLoad, true);
     executeSoon(aCallback);
--- a/browser/base/content/test/general/browser_tab_detach_restore.js
+++ b/browser/base/content/test/general/browser_tab_detach_restore.js
@@ -5,16 +5,17 @@ add_task(function*() {
 
   // Clear out the closed windows set to start
   while (SessionStore.getClosedWindowCount() > 0)
     SessionStore.forgetClosedWindow(0);
 
   let tab = gBrowser.addTab();
   tab.linkedBrowser.loadURI(uri);
   yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+  TabState.flush(tab.linkedBrowser);
 
   let key = tab.linkedBrowser.permanentKey;
   let win = gBrowser.replaceTabWithWindow(tab);
   yield new Promise(resolve => whenDelayedStartupFinished(win, resolve));
 
   is(win.gBrowser.selectedBrowser.permanentKey, key, "Should have properly copied the permanentKey");
   yield promiseWindowClosed(win);
 
--- a/browser/base/content/web-panels.xul
+++ b/browser/base/content/web-panels.xul
@@ -7,16 +7,18 @@
 
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?> 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 <!DOCTYPE page [
 <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd">
 %browserDTD;
+<!ENTITY % browserPocketDTD SYSTEM "chrome://browser/content/browser-pocket.dtd">
+%browserPocketDTD;
 <!ENTITY % textcontextDTD SYSTEM "chrome://global/locale/textcontext.dtd">
 %textcontextDTD;
 ]>
 
 <page id="webpanels-window"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="load()" onunload="unload()">
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -71,16 +71,17 @@ browser.jar:
         content/browser/aboutProviderDirectory.xhtml  (content/aboutProviderDirectory.xhtml)
         content/browser/aboutTabCrashed.css           (content/aboutTabCrashed.css)
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
         content/browser/browser-pocket.properties     (content/browser-pocket.properties)
+        content/browser/browser-pocket.dtd            (content/browser-pocket.dtd)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/chatWindow.xul                (content/chatWindow.xul)
         content/browser/tab-content.js                (content/tab-content.js)
         content/browser/content.js                    (content/content.js)
         content/browser/defaultthemes/1.footer.jpg    (content/defaultthemes/1.footer.jpg)
         content/browser/defaultthemes/1.header.jpg    (content/defaultthemes/1.header.jpg)
         content/browser/defaultthemes/1.icon.jpg      (content/defaultthemes/1.icon.jpg)
         content/browser/defaultthemes/1.preview.jpg   (content/defaultthemes/1.preview.jpg)
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -132,16 +132,20 @@
         <toolbarbutton id="panelMenu_viewReadingListSidebar"
                        label="&readingList.showSidebar.label;"
                        class="subviewbutton"
                        key="key_readingListSidebar"
                        oncommand="SidebarUI.toggle('readingListSidebar'); PanelUI.hide();">
           <observes element="readingListSidebar" attribute="checked"/>
           <observes element="readingListSidebar" attribute="hidden"/>
         </toolbarbutton>
+        <toolbarseparator id="panelMenu_pocketSeparator"/>
+        <toolbarbutton id="panelMenu_pocket" label="&pocketMenuitem.label;"
+                       class="subviewbutton cui-withicon"
+                       oncommand="openUILink(Pocket.listURL, event);"/>
         <toolbarseparator class="small-separator"/>
         <toolbaritem id="panelMenu_bookmarksMenu"
                      orient="vertical"
                      smoothscroll="false"
                      onclick="if (event.button == 1) BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
                      oncommand="BookmarkingUI.onPanelMenuViewCommand(event, this._placesView);"
                      flatList="true"
                      tooltip="bhTooltip">
--- a/browser/components/loop/build-jsx
+++ b/browser/components/loop/build-jsx
@@ -1,49 +1,105 @@
 #! /usr/bin/env python
 
 import os
+import sys
 import re
 from distutils import spawn
 import subprocess
 from threading import Thread
 import argparse
 
+
 def find_react_version(lib_dir):
     "Finds the React library version number currently used."
     for filename in os.listdir(lib_dir):
         match = re.match(r"react-(.*)-prod\.js", filename)
         if (match and match.group(1)):
             return match.group(1)
     print 'Unable to find the current react version used in content.'
     print 'Please checked the %s directory.' % lib_dir
     exit(1)
 
+
+def append_arguments(array1, array2):
+    "Appends array2 onto the end of array1"
+    result = array1[:]
+    result.extend(array2)
+    return result
+
+
+def check_jsx(jsx_path):
+    "Checks to see if jsx is installed or not"
+    if jsx_path is None:
+        print 'You do not have the react-tools installed'
+        print 'Please do $ npm install -g react-tools and make sure it is available in PATH'
+        exit(1)
+
+
+def find_react_command():
+    "Searches for a jsx location and forms a runnable command"
+    if sys.platform != 'win32':
+        jsx_path = spawn.find_executable('jsx')
+        check_jsx(jsx_path)
+        return [jsx_path]
+
+    # Else windows.
+    def find_excutable_no_extension(fileName):
+        """
+            spawn.find_executable assumes a '.exe' extension on windows
+            something which jsx doesn't have...
+        """
+        paths = os.environ['PATH'].split(os.pathsep)
+        for path in paths:
+            file = os.path.join(path, fileName)
+            if os.path.isfile(file):
+                return path
+        return None
+
+    # jsx isn't a true windows executable, so the standard spawn
+    # processes get upset. Hence, we have to use node to run the
+    # jsx file direct.
+    node = spawn.find_executable('node')
+    if node is None:
+        print 'You do not have node installed, or it is not in your PATH'
+        exit(1)
+
+    # We need the jsx path to make node happy
+    jsx_path = find_excutable_no_extension('jsx')
+    check_jsx(jsx_path)
+
+    # This is what node really wants to run.
+    jsx_path = os.path.join(jsx_path,
+                            "node_modules", "react-tools", "bin", "jsx")
+
+    return [node, jsx_path]
+
+
 SHARED_LIBS_DIR=os.path.join(os.path.dirname(__file__), "content", "shared", "libs")
 REACT_VERSION=find_react_version(SHARED_LIBS_DIR)
 
 src_files = []  # files to be compiled
 
-# search for react-tools install
-jsx_path = spawn.find_executable('jsx')
-if jsx_path is None:
-    print 'You do not have the react-tools installed'
-    print 'Please do $ npm install -g react-tools'
-    exit(1)
+run_command = find_react_command()
 
-p = subprocess.Popen([jsx_path, '-V'],
-                     stdout=subprocess.PIPE,
-                     stderr=subprocess.STDOUT)
-for line in iter(p.stdout.readline, b''):
-    info = line.rstrip()
+if sys.platform == 'win32':
+    print 'Please ensure you are running react-tools version %s' % REACT_VERSION
+    print 'You may be already, but we are not currently able to detect it'
+else:
+    p = subprocess.Popen(append_arguments(run_command, ['-V']),
+                         stdout=subprocess.PIPE,
+                         stderr=subprocess.STDOUT)
+    for line in iter(p.stdout.readline, b''):
+        info = line.rstrip()
 
-if not info == REACT_VERSION:
-    print 'You have the wrong version of react-tools installed'
-    print 'Please use version %s' % REACT_VERSION
-    exit(1)
+    if not info == REACT_VERSION:
+        print 'You have the wrong version of react-tools installed'
+        print 'Please use version %s' % REACT_VERSION
+        exit(1)
 
 # parse the CLI arguments
 description = 'Loop build tool for JSX files. ' + \
               'Will scan entire loop directory and compile them in place. ' + \
               'Must be executed from browser/components/loop directory.'
 
 parser = argparse.ArgumentParser(description=description)
 parser.add_argument('--watch', '-w', action='store_true', help='continuous' +
@@ -58,17 +114,17 @@ for dirname, dirnames, filenames in os.w
     for filename in filenames:
         if '.jsx' == os.path.splitext(filename)[1]:  # (root, ext)
             src_files.append(filename)
             if dirname not in unique_jsx_dirs:
                 unique_jsx_dirs.append(dirname)
 
 
 def jsx_run_watcher(path):
-    subprocess.call(['jsx', '-w', '-x', 'jsx', path, path])
+    subprocess.call(append_arguments(run_command, ['-w', '-x', 'jsx', path, path]))
 
 
 def start_jsx_watcher_threads(dirs):
     """
         starts a thread with a jsx watch process
         for every dir in the dirs argument
     """
     threads = []
@@ -106,9 +162,9 @@ def check_file_packaging(srcs):
             print f + ' not in jar.mn file'
 
 check_file_packaging(src_files)
 
 if args.watch:
     start_jsx_watcher_threads(unique_jsx_dirs)
 else:
     for d in unique_jsx_dirs:
-        out = subprocess.call(['jsx', '-x', 'jsx', d, d])
+        out = subprocess.call(append_arguments(run_command, ['-x', 'jsx', d, d]))
--- a/browser/components/loop/standalone/package.json
+++ b/browser/components/loop/standalone/package.json
@@ -9,16 +9,16 @@
   "engines": {
     "node": "0.10.x",
     "npm": "1.3.x"
   },
   "dependencies": {},
   "devDependencies": {
     "eslint": "0.20.x",
     "eslint-plugin-react": "2.2.x",
-    "express": "3.x"
+    "express": "4.x"
   },
   "scripts": {
     "test": "make test",
     "start": "make runserver"
   },
   "license": "MPL-2.0"
 }
--- a/browser/components/loop/standalone/server.js
+++ b/browser/components/loop/standalone/server.js
@@ -8,29 +8,35 @@
  * writing, we're just bootstrapping the linting infrastructure.
  */
 /* eslint-disable no-path-concat,no-process-exit */
 
 var express = require('express');
 var app = express();
 
 var port = process.env.PORT || 3000;
-var loopServerPort = process.env.LOOP_SERVER_PORT || 5000;
 var feedbackApiUrl = process.env.LOOP_FEEDBACK_API_URL ||
                      "https://input.allizom.org/api/v1/feedback";
 var feedbackProductName = process.env.LOOP_FEEDBACK_PRODUCT_NAME || "Loop";
+var loopServerUrl = process.env.LOOP_SERVER_URL || "http://localhost:5000";
+
+// Remove trailing slashes as double slashes in the url can confuse the server
+// responses.
+if (loopServerUrl[loopServerUrl.length - 1] === "/") {
+  loopServerUrl = loopServerUrl.slice(0, -1);
+}
 
 function getConfigFile(req, res) {
   "use strict";
 
   res.set('Content-Type', 'text/javascript');
   res.send([
     "var loop = loop || {};",
     "loop.config = loop.config || {};",
-    "loop.config.serverUrl = 'http://localhost:" + loopServerPort + "/v0';",
+    "loop.config.serverUrl = '" + loopServerUrl + "/v0';",
     "loop.config.feedbackApiUrl = '" + feedbackApiUrl + "';",
     "loop.config.feedbackProductName = '" + feedbackProductName + "';",
     // XXX Update with the real marketplace url once the FxOS Loop app is
     //     uploaded to the marketplace bug 1053424
     "loop.config.marketplaceUrl = 'http://fake-market.herokuapp.com/iframe-install.html'",
     "loop.config.downloadFirefoxUrl = 'https://www.mozilla.org/firefox/new/?scene=2&utm_source=hello.firefox.com&utm_medium=referral&utm_campaign=non-webrtc-browser#download-fx';",
     "loop.config.privacyWebsiteUrl = 'https://www.mozilla.org/privacy/firefox-hello/';",
     "loop.config.learnMoreUrl = 'https://www.mozilla.org/hello/';",
@@ -49,33 +55,36 @@ function getConfigFile(req, res) {
 app.get('/content/config.js', getConfigFile);
 app.get('/content/c/config.js', getConfigFile);
 
 // Various mappings to let us end up with:
 // /test - for the test files
 // /ui - for the ui showcase
 // /content - for the standalone files.
 
-app.use('/test', express.static(__dirname + '/../test'));
 app.use('/ui', express.static(__dirname + '/../ui'));
 
 // This exists exclusively for the unit tests. They are served the
 // whole loop/ directory structure and expect some files in the standalone directory.
 app.use('/standalone/content', express.static(__dirname + '/content'));
 
 // We load /content this from  both /content *and* /../content. The first one
 // does what we need for running in the github loop-client context, the second one
 // handles running in the hg repo under mozilla-central and is used so that the shared
 // files are in the right location.
 app.use('/content', express.static(__dirname + '/content'));
 app.use('/content', express.static(__dirname + '/../content'));
 // These two are based on the above, but handle call urls, that have a /c/ in them.
 app.use('/content/c', express.static(__dirname + '/content'));
 app.use('/content/c', express.static(__dirname + '/../content'));
 
+// Two lines for the same reason as /content above.
+app.use('/test', express.static(__dirname + '/test'));
+app.use('/test', express.static(__dirname + '/../test'));
+
 // As we don't have hashes on the urls, the best way to serve the index files
 // appears to be to be to closely filter the url and match appropriately.
 function serveIndex(req, res) {
   return res.sendfile(__dirname + '/content/index.html');
 }
 
 app.get(/^\/content\/[\w\-]+$/, serveIndex);
 app.get(/^\/content\/c\/[\w\-]+$/, serveIndex);
--- a/browser/components/loop/test/functional/config.py
+++ b/browser/components/loop/test/functional/config.py
@@ -1,13 +1,14 @@
 # Loop server configuration
 CONTENT_SERVER_PORT = 3001
 LOOP_SERVER_PORT = 5001
+LOOP_SERVER_URL = "http://localhost:" + str(LOOP_SERVER_PORT)
 FIREFOX_PREFERENCES = {
-    "loop.server": "http://localhost:" + str(LOOP_SERVER_PORT),
+    "loop.server": LOOP_SERVER_URL + "/v0",
     "browser.dom.window.dump.enabled": True,
     # Some more changes might be necesarry to have this working in offline mode
     "media.peerconnection.default_iceservers": "[]",
     "media.peerconnection.use_document_iceservers": False,
     "media.peerconnection.ice.loopback": True,
     "devtools.chrome.enabled": True,
     "devtools.debugger.prompt-connection": False,
     "devtools.debugger.remote-enabled": True,
--- a/browser/components/loop/test/functional/serversetup.py
+++ b/browser/components/loop/test/functional/serversetup.py
@@ -16,28 +16,28 @@ sys.path.append(os.path.dirname(__file__
 import hanging_threads
 from config import *
 
 CONTENT_SERVER_COMMAND = ["make", "runserver"]
 CONTENT_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 CONTENT_SERVER_ENV.update({"PORT": str(CONTENT_SERVER_PORT),
-                           "LOOP_SERVER_PORT": str(LOOP_SERVER_PORT)})
+                           "LOOP_SERVER_URL": LOOP_SERVER_URL})
 
 ROOMS_WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
   "/content/{token}"
 
 LOOP_SERVER_COMMAND = ["make", "runserver"]
 LOOP_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 LOOP_SERVER_ENV.update({"NODE_ENV": "dev",
                         "PORT": str(LOOP_SERVER_PORT),
-                        "SERVER_ADDRESS": "localhost:" + str(LOOP_SERVER_PORT),
+                        "SERVER_ADDRESS": LOOP_SERVER_URL,
                         "ROOMS_WEB_APP_URL": ROOMS_WEB_APP_URL})
 
 
 class LoopTestServers:
     def __init__(self):
         loop_server_location = os.environ.get('LOOP_SERVER')
         if loop_server_location.startswith("http"):
             FIREFOX_PREFERENCES["loop.server"] = loop_server_location
--- a/browser/components/pocket/Pocket.jsm
+++ b/browser/components/pocket/Pocket.jsm
@@ -24,16 +24,18 @@ let Pocket = {
   get isLoggedIn() {
     return !!this._accessToken;
   },
 
   prefBranch: Services.prefs.getBranch("browser.pocket.settings."),
 
   get hostname() Services.prefs.getCharPref("browser.pocket.hostname"),
 
+  get listURL() { return "https://" + Pocket.hostname; },
+
   get _accessToken() {
     let sessionId, accessToken;
     let cookies = Services.cookies.getCookiesFromHost(this.hostname);
     while (cookies.hasMoreElements()) {
       let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
       if (cookie.name == "ftv1")
         accessToken = cookie.value;
       else if (cookie.name == "fsv1")
--- a/browser/components/pocket/jar.mn
+++ b/browser/components/pocket/jar.mn
@@ -11,16 +11,18 @@ browser.jar:
   content/browser/pocket/panels/css/firasans.css        (panels/css/firasans.css)
   content/browser/pocket/panels/css/saved.css           (panels/css/saved.css)
   content/browser/pocket/panels/css/signup.css           (panels/css/signup.css)
   content/browser/pocket/panels/fonts/FiraSans-Regular.woff (panels/fonts/FiraSans-Regular.woff)
   content/browser/pocket/panels/img/pocketlogo@1x.png              (panels/img/pocketlogo@1x.png)
   content/browser/pocket/panels/img/pocketlogo@2x.png              (panels/img/pocketlogo@2x.png)
   content/browser/pocket/panels/img/pocketlogosolo@1x.png          (panels/img/pocketlogosolo@1x.png)
   content/browser/pocket/panels/img/pocketlogosolo@2x.png          (panels/img/pocketlogosolo@2x.png)
+  content/browser/pocket/panels/img/pocketmenuitem16.png           (panels/img/pocketmenuitem16.png)
+  content/browser/pocket/panels/img/pocketmenuitem16@2x.png        (panels/img/pocketmenuitem16@2x.png)
   content/browser/pocket/panels/img/pocketmultidevices@1x.png      (panels/img/pocketmultidevices@1x.png)
   content/browser/pocket/panels/img/pocketmultidevices@2x.png      (panels/img/pocketmultidevices@2x.png)
   content/browser/pocket/panels/img/signup_firefoxlogo@1x.png      (panels/img/signup_firefoxlogo@1x.png)
   content/browser/pocket/panels/img/signup_firefoxlogo@2x.png      (panels/img/signup_firefoxlogo@2x.png)
   content/browser/pocket/panels/img/signup_help@1x.png             (panels/img/signup_help@1x.png)
   content/browser/pocket/panels/img/signup_help@2x.png             (panels/img/signup_help@2x.png)
   content/browser/pocket/panels/img/tag_close@1x.png               (panels/img/tag_close@1x.png)
   content/browser/pocket/panels/img/tag_close@2x.png               (panels/img/tag_close@2x.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..80816e1b049b3a2ad0b96df00f5f74bd4cef5eb3
GIT binary patch
literal 278
zc%17D@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`oCO|{#S9GG!XV7ZFl&wkP*AeO
zHKHUqKdq!Zu_%?nF(p4KRlzeiF+DXXH8G{K@MNkDP|;#f7sn8b)5$;n|F>sWo$+w4
z^9>e-fBtIo4mdQX8OYVJ$uK-(Q~1~5`jLIb9mX0qABI+)31JM6xIG^6H{4**GH;w9
zF0_D)bwOLh28Ixw#v2A24n{1?(`GP=7f5Vkm=@uXBa@}qpu+v3#Yp3T*@KDt4L1uK
zdo=bi_?-%1<|%NI)%(LJrp@E=&qU#ps(8YzKpqDp4My9I3a#!;Zh1>gteF|&UUkMQ
TuG9GgbS{IZtDnm{r-UW|?o3yS
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..ee341860adc7356379cb1c9a5353e2bca982ff75
GIT binary patch
literal 422
zc%17D@N?(olHy`uVBq!ia0vp^iXhCv1|-9u9Lfh$oCO|{#S9GG!XV7ZFl&wkP*AeO
zHKHUqKdq!Zu_%?nF(p4KRlzeiF+DXXH8G{K@MNkD0|TS6r;B4q#NoHo&k8mx2()S*
z6w+uE(m5D>Zb|~@v<X0#kcOgafMXjUhlO{WK!{k+5lQ~{4|;ajDfj<XjLHs*ev+>+
z@9kQ{M{b83U)>XU)+nW^9P7BaSwfr9qEyT&q)kGvgZt4GfkWDQ!bU&uc289mXpWB)
zG;L)sOfv6a5fEj1!)SO^ito=O35M=Hx2MRpv+0{md}71cp!#L$liMF77&RGZ{B1Uj
z7D;)yLt>HfcC!WUF}r;QW{X%#1-P%BsF1_hpj_-(ziHBm2m10U3};x+w!bhI(&$JI
zkS+Ey<4yO8Oy2T)#<sUJN);MD@%kB6JAaJXxvRY4cRl9;^Xc**+AX*eN}06_{!C)u
zwZ>FnRs74flG|75c)nqksdVA8^DB?<NK7rUUd{33*v@ZzqAPRvGDy07nZ4#y#w=hM
OGkCiCxvX<aXaWF)$(v;W
--- a/browser/components/preferences/in-content/search.js
+++ b/browser/components/preferences/in-content/search.js
@@ -5,29 +5,31 @@
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
 
 const ENGINE_FLAVOR = "text/x-moz-search-engine";
 
+document.addEventListener("Initialized", () => {
+  if (Services.prefs.getBoolPref("browser.search.showOneOffButtons"))
+    return;
+
+  document.getElementById("category-search").hidden = true;
+  if (document.location.hash == "#search")
+    document.location.hash = "";
+});
+
 var gEngineView = null;
 
 var gSearchPane = {
 
   init: function ()
   {
-    if (!Services.prefs.getBoolPref("browser.search.showOneOffButtons")) {
-      document.getElementById("category-search").hidden = true;
-      if (document.location.hash == "#search")
-        document.location.hash = "";
-      return;
-    }
-
     gEngineView = new EngineView(new EngineStore());
     document.getElementById("engineList").view = gEngineView;
     this.buildDefaultEngineDropDown();
 
     window.addEventListener("click", this, false);
     window.addEventListener("command", this, false);
     window.addEventListener("dragstart", this, false);
     window.addEventListener("keypress", this, false);
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -87,16 +87,19 @@ const NOTAB_MESSAGES = new Set([
 ]);
 
 // The list of messages we want to receive even during the short period after a
 // frame has been removed from the DOM and before its frame script has finished
 // unloading.
 const CLOSED_MESSAGES = new Set([
   // For a description see above.
   "SessionStore:crashedTabRevived",
+
+  // For a description see above.
+  "SessionStore:update",
 ]);
 
 // These are tab events that we listen to.
 const TAB_EVENTS = [
   "TabOpen", "TabClose", "TabSelect", "TabShow", "TabHide", "TabPinned",
   "TabUnpinned"
 ];
 
@@ -333,16 +336,25 @@ let SessionStoreInternal = {
   // For each <browser> element being restored, records the current epoch.
   _browserEpochs: new WeakMap(),
 
   // Any browsers that fires the oop-browser-crashed event gets stored in
   // here - that way we know which browsers to ignore messages from (until
   // they get restored).
   _crashedBrowsers: new WeakSet(),
 
+  // A map (xul:browser -> nsIFrameLoader) that maps a browser to the last
+  // associated frameLoader we heard about.
+  _lastKnownFrameLoader: new WeakMap(),
+
+  // A map (xul:browser -> object) that maps a browser associated with a
+  // recently closed tab to all its necessary state information we need to
+  // properly handle final update message.
+  _closedTabs: new WeakMap(),
+
   // whether a setBrowserState call is in progress
   _browserSetState: false,
 
   // time in milliseconds when the session was started (saved across sessions),
   // defaults to now if no session was restored or timestamp doesn't exist
   _sessionStartTime: Date.now(),
 
   // states for all currently opened windows
@@ -607,24 +619,74 @@ let SessionStoreInternal = {
                       `from a browser that has no tab`);
     }
 
     switch (aMessage.name) {
       case "SessionStore:setupSyncHandler":
         TabState.setSyncHandler(browser, aMessage.objects.handler);
         break;
       case "SessionStore:update":
+        // Ignore messages from <browser> elements that have crashed
+        // and not yet been revived.
         if (this._crashedBrowsers.has(browser.permanentKey)) {
-          // Ignore messages from <browser> elements that have crashed
-          // and not yet been revived.
           return;
         }
+
+        // |browser.frameLoader| might be empty if the browser was already
+        // destroyed and its tab removed. In that case we still have the last
+        // frameLoader we know about to compare.
+        let frameLoader = browser.frameLoader ||
+                          this._lastKnownFrameLoader.get(browser.permanentKey);
+
+        // If the message isn't targeting the latest frameLoader discard it.
+        if (frameLoader != aMessage.targetFrameLoader) {
+          return;
+        }
+
+        // Record telemetry measurements done in the child and update the tab's
+        // cached state. Mark the window as dirty and trigger a delayed write.
         this.recordTelemetry(aMessage.data.telemetry);
         TabState.update(browser, aMessage.data);
         this.saveStateDelayed(win);
+
+        // Handle any updates sent by the child after the tab was closed. This
+        // might be the final update as sent by the "unload" handler but also
+        // any async update message that was sent before the child unloaded.
+        if (this._closedTabs.has(browser.permanentKey)) {
+          let {closedTabs, tabData} = this._closedTabs.get(browser.permanentKey);
+
+          // Update the closed tab's state. This will be reflected in its
+          // window's list of closed tabs as that refers to the same object.
+          TabState.copyFromCache({linkedBrowser: browser}, tabData.state);
+
+          // Is this the tab's final message?
+          if (aMessage.data.isFinal) {
+            // We expect no further updates.
+            this._closedTabs.delete(browser.permanentKey);
+            // The tab state no longer needs this reference.
+            delete tabData.permanentKey;
+
+            // Determine whether the tab state is worth saving.
+            let shouldSave = this._shouldSaveTabState(tabData.state);
+            let index = closedTabs.indexOf(tabData);
+
+            if (shouldSave && index == -1) {
+              // If the tab state is worth saving and we didn't push it onto
+              // the list of closed tabs when it was closed (because we deemed
+              // the state not worth saving) then add it to the window's list
+              // of closed tabs now.
+              this.saveClosedTabData(closedTabs, tabData);
+            } else if (!shouldSave && index > -1) {
+              // Remove from the list of closed tabs. The update messages sent
+              // after the tab was closed changed enough state so that we no
+              // longer consider its data interesting enough to keep around.
+              this.removeClosedTabData(closedTabs, index);
+            }
+          }
+        }
         break;
       case "SessionStore:restoreHistoryComplete":
         if (this.isCurrentEpoch(browser, aMessage.data.epoch)) {
           // Notify the tabbrowser that the tab chrome has been restored.
           let tabData = browser.__SS_data;
 
           // wall-paper fix for bug 439675: make sure that the URL to be loaded
           // is always visible in the address bar
@@ -717,44 +779,52 @@ let SessionStoreInternal = {
   },
 
   /* ........ Window Event Handlers .............. */
 
   /**
    * Implement nsIDOMEventListener for handling various window and tab events
    */
   handleEvent: function ssi_handleEvent(aEvent) {
-    var win = aEvent.currentTarget.ownerDocument.defaultView;
+    let win = aEvent.currentTarget.ownerDocument.defaultView;
+    let target = aEvent.originalTarget;
     switch (aEvent.type) {
       case "TabOpen":
-        this.onTabAdd(win, aEvent.originalTarget);
+        this.onTabAdd(win, target);
         break;
       case "TabClose":
         // aEvent.detail determines if the tab was closed by moving to a different window
         if (!aEvent.detail)
-          this.onTabClose(win, aEvent.originalTarget);
-        this.onTabRemove(win, aEvent.originalTarget);
+          this.onTabClose(win, target);
+        this.onTabRemove(win, target);
         break;
       case "TabSelect":
         this.onTabSelect(win);
         break;
       case "TabShow":
-        this.onTabShow(win, aEvent.originalTarget);
+        this.onTabShow(win, target);
         break;
       case "TabHide":
-        this.onTabHide(win, aEvent.originalTarget);
+        this.onTabHide(win, target);
         break;
       case "TabPinned":
       case "TabUnpinned":
       case "SwapDocShells":
         this.saveStateDelayed(win);
         break;
       case "oop-browser-crashed":
-        this.onBrowserCrashed(win, aEvent.originalTarget);
+        this.onBrowserCrashed(win, target);
         break;
+      case "XULFrameLoaderCreated":
+        if (target.tagName == "browser" && target.frameLoader) {
+          this._lastKnownFrameLoader.set(target.permanentKey, target.frameLoader);
+        }
+        break;
+      default:
+        throw new Error(`unhandled event ${aEvent.type}?`);
     }
     this._clearRestoringWindows();
   },
 
   /**
    * Generate a unique window identifier
    * @return string
    *         A unique string to identify a window
@@ -938,16 +1008,19 @@ let SessionStoreInternal = {
     // add tab change listeners to all already existing tabs
     for (let i = 0; i < tabbrowser.tabs.length; i++) {
       this.onTabAdd(aWindow, tabbrowser.tabs[i], true);
     }
     // notification of tab add/remove/selection/show/hide
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.addEventListener(aEvent, this, true);
     }, this);
+
+    // Keep track of a browser's latest frameLoader.
+    aWindow.gBrowser.addEventListener("XULFrameLoaderCreated", this);
   },
 
   /**
    * Called right before a new browser window is shown.
    * @param aWindow
    *        Window reference
    */
   onBeforeBrowserWindowShown: function (aWindow) {
@@ -1041,16 +1114,18 @@ let SessionStoreInternal = {
     }
 
     var tabbrowser = aWindow.gBrowser;
 
     TAB_EVENTS.forEach(function(aEvent) {
       tabbrowser.tabContainer.removeEventListener(aEvent, this, true);
     }, this);
 
+    aWindow.gBrowser.removeEventListener("XULFrameLoaderCreated", this);
+
     let winData = this._windows[aWindow.__SSi];
 
     // Collect window data only when *not* closed during shutdown.
     if (RunState.isRunning) {
       // Flush all data queued in the content script before the window is gone.
       TabState.flushWindow(aWindow);
 
       // update all window data for a last time
@@ -1306,16 +1381,21 @@ let SessionStoreInternal = {
    *        Tab reference
    * @param aNoNotification
    *        bool Do not save state if we're updating an existing tab
    */
   onTabAdd: function ssi_onTabAdd(aWindow, aTab, aNoNotification) {
     let browser = aTab.linkedBrowser;
     browser.addEventListener("SwapDocShells", this);
     browser.addEventListener("oop-browser-crashed", this);
+
+    if (browser.frameLoader) {
+      this._lastKnownFrameLoader.set(browser.permanentKey, browser.frameLoader);
+    }
+
     if (!aNoNotification) {
       this.saveStateDelayed(aWindow);
     }
   },
 
   /**
    * remove listeners for a tab
    * @param aWindow
@@ -1360,45 +1440,115 @@ let SessionStoreInternal = {
     event.initEvent("SSTabClosing", true, false);
     aTab.dispatchEvent(event);
 
     // don't update our internal state if we don't have to
     if (this._max_tabs_undo == 0) {
       return;
     }
 
-    // Flush all data queued in the content script before the tab is gone.
-    TabState.flush(aTab.linkedBrowser);
-
     // Get the latest data for this tab (generally, from the cache)
     let tabState = TabState.collect(aTab);
 
     // Don't save private tabs
     let isPrivateWindow = PrivateBrowsingUtils.isWindowPrivate(aWindow);
     if (!isPrivateWindow && tabState.isPrivate) {
       return;
     }
 
-    // store closed-tab data for undo
+    // Store closed-tab data for undo.
+    let tabbrowser = aWindow.gBrowser;
+    let tabTitle = this._replaceLoadingTitle(aTab.label, tabbrowser, aTab);
+    let {permanentKey} = aTab.linkedBrowser;
+
+    let tabData = {
+      permanentKey,
+      state: tabState,
+      title: tabTitle,
+      image: tabbrowser.getIcon(aTab),
+      pos: aTab._tPos,
+      closedAt: Date.now()
+    };
+
+    let closedTabs = this._windows[aWindow.__SSi]._closedTabs;
+
+    // Determine whether the tab contains any information worth saving. Note
+    // that there might be pending state changes queued in the child that
+    // didn't reach the parent yet. If a tab is emptied before closing then we
+    // might still remove it from the list of closed tabs later.
     if (this._shouldSaveTabState(tabState)) {
-      let tabTitle = aTab.label;
-      let tabbrowser = aWindow.gBrowser;
-      tabTitle = this._replaceLoadingTitle(tabTitle, tabbrowser, aTab);
-
-      this._windows[aWindow.__SSi]._closedTabs.unshift({
-        state: tabState,
-        title: tabTitle,
-        image: tabbrowser.getIcon(aTab),
-        pos: aTab._tPos,
-        closedAt: Date.now()
-      });
-      var length = this._windows[aWindow.__SSi]._closedTabs.length;
-      if (length > this._max_tabs_undo)
-        this._windows[aWindow.__SSi]._closedTabs.splice(this._max_tabs_undo, length - this._max_tabs_undo);
+      // Save the tab state, for now. We might push a valid tab out
+      // of the list but those cases should be extremely rare and
+      // do probably never occur when using the browser normally.
+      // (Tests or add-ons might do weird things though.)
+      this.saveClosedTabData(closedTabs, tabData);
+    }
+
+    // Remember the closed tab to properly handle any last updates included in
+    // the final "update" message sent by the frame script's unload handler.
+    this._closedTabs.set(permanentKey, {closedTabs, tabData});
+  },
+
+  /**
+   * Insert a given |tabData| object into the list of |closedTabs|. We will
+   * determine the right insertion point based on the .closedAt properties of
+   * all tabs already in the list. The list will be truncated to contain a
+   * maximum of |this._max_tabs_undo| entries.
+   *
+   * @param closedTabs (array)
+   *        The list of closed tabs for a window.
+   * @param tabData (object)
+   *        The tabData to be inserted.
+   */
+  saveClosedTabData(closedTabs, tabData) {
+    // Find the index of the first tab in the list
+    // of closed tabs that was closed before our tab.
+    let index = closedTabs.findIndex(tab => {
+      return tab.closedAt < tabData.closedAt;
+    });
+
+    // If we found no tab closed before our
+    // tab then just append it to the list.
+    if (index == -1) {
+      index = closedTabs.length;
     }
+
+    // Insert tabData at the right position.
+    closedTabs.splice(index, 0, tabData);
+
+    // Truncate the list of closed tabs, if needed.
+    if (closedTabs.length > this._max_tabs_undo) {
+      closedTabs.splice(this._max_tabs_undo, closedTabs.length);
+    }
+  },
+
+  /**
+   * Remove the closed tab data at |index| from the list of |closedTabs|. If
+   * the tab's final message is still pending we will simply discard it when
+   * it arrives so that the tab doesn't reappear in the list.
+   *
+   * @param closedTabs (array)
+   *        The list of closed tabs for a window.
+   * @param index (uint)
+   *        The index of the tab to remove.
+   */
+  removeClosedTabData(closedTabs, index) {
+    // Remove the given index from the list.
+    let [closedTab] = closedTabs.splice(index, 1);
+
+    // If the closed tab's state still has a .permanentKey property then we
+    // haven't seen its final update message yet. Remove it from the map of
+    // closed tabs so that we will simply discard its last messages and will
+    // not add it back to the list of closed tabs again.
+    if (closedTab.permanentKey) {
+      this._closedTabs.delete(closedTab.permanentKey);
+      delete closedTab.permanentKey;
+    }
+
+    return closedTab;
   },
 
   /**
    * When a tab is selected, save session data
    * @param aWindow
    *        Window reference
    */
   onTabSelect: function ssi_onTabSelect(aWindow) {
@@ -1691,28 +1841,27 @@ let SessionStoreInternal = {
 
     // default to the most-recently closed tab
     aIndex = aIndex || 0;
     if (!(aIndex in closedTabs)) {
       throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // fetch the data of closed tab, while removing it from the array
-    let [closedTab] = closedTabs.splice(aIndex, 1);
-    let closedTabState = closedTab.state;
+    let {state, pos} = this.removeClosedTabData(closedTabs, aIndex);
 
     // create a new tab
     let tabbrowser = aWindow.gBrowser;
     let tab = tabbrowser.selectedTab = tabbrowser.addTab();
 
     // restore tab content
-    this.restoreTab(tab, closedTabState);
+    this.restoreTab(tab, state);
 
     // restore the tab's position
-    tabbrowser.moveTabTo(tab, closedTab.pos);
+    tabbrowser.moveTabTo(tab, pos);
 
     // focus the tab's content area (bug 342432)
     tab.linkedBrowser.focus();
 
     return tab;
   },
 
   forgetClosedTab: function ssi_forgetClosedTab(aWindow, aIndex) {
@@ -1724,17 +1873,17 @@ let SessionStoreInternal = {
 
     // default to the most-recently closed tab
     aIndex = aIndex || 0;
     if (!(aIndex in closedTabs)) {
       throw Components.Exception("Invalid index: not in the closed tabs", Cr.NS_ERROR_INVALID_ARG);
     }
 
     // remove closed tab from the array
-    closedTabs.splice(aIndex, 1);
+    this.removeClosedTabData(closedTabs, aIndex);
   },
 
   getClosedWindowCount: function ssi_getClosedWindowCount() {
     return this._closedWindows.length;
   },
 
   getClosedWindowData: function ssi_getClosedWindowData() {
     return JSON.stringify(this._closedWindows);
--- a/browser/components/sessionstore/TabState.jsm
+++ b/browser/components/sessionstore/TabState.jsm
@@ -48,16 +48,20 @@ this.TabState = Object.freeze({
   },
 
   collect: function (tab) {
     return TabStateInternal.collect(tab);
   },
 
   clone: function (tab) {
     return TabStateInternal.clone(tab);
+  },
+
+  copyFromCache: function (tab, tabData, options) {
+    TabStateInternal.copyFromCache(tab, tabData, options);
   }
 });
 
 let TabStateInternal = {
   // A map (xul:browser -> handler) that maps a tab to the
   // synchronous collection handler object for that tab.
   // See SyncHandler in content-sessionStore.js.
   _syncHandlers: new WeakMap(),
@@ -213,32 +217,32 @@ let TabStateInternal = {
 
     if (tab.__SS_extdata)
       tabData.extData = tab.__SS_extdata;
     else if (tabData.extData)
       delete tabData.extData;
 
     // Copy data from the tab state cache only if the tab has fully finished
     // restoring. We don't want to overwrite data contained in __SS_data.
-    this._copyFromCache(tab, tabData, options);
+    this.copyFromCache(tab, tabData, options);
 
     return tabData;
   },
 
   /**
    * Copy tab data for the given |tab| from the cache to |tabData|.
    *
    * @param tab (xul:tab)
    *        The tab belonging to the given |tabData| object.
    * @param tabData (object)
    *        The tab data belonging to the given |tab|.
    * @param options (object)
    *        {includePrivateData: true} to always include private data
    */
-  _copyFromCache: function (tab, tabData, options = {}) {
+  copyFromCache: function (tab, tabData, options = {}) {
     let data = TabStateCache.get(tab.linkedBrowser);
     if (!data) {
       return;
     }
 
     // The caller may explicitly request to omit privacy checks.
     let includePrivateData = options && options.includePrivateData;
 
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -111,16 +111,22 @@ let MessageListener = {
     "SessionStore:resetRestore",
   ],
 
   init: function () {
     this.MESSAGES.forEach(m => addMessageListener(m, this));
   },
 
   receiveMessage: function ({name, data}) {
+    // The docShell might be gone. Don't process messages,
+    // that will just lead to errors anyway.
+    if (!docShell) {
+      return;
+    }
+
     switch (name) {
       case "SessionStore:restoreHistory":
         this.restoreHistory(data);
         break;
       case "SessionStore:restoreTabContent":
         this.restoreTabContent(data);
         break;
       case "SessionStore:resetRestore":
@@ -217,17 +223,21 @@ let SyncHandler = {
 
   /**
    * DO NOT USE - DEBUGGING / TESTING ONLY
    *
    * This function is used to simulate certain situations where race conditions
    * can occur by sending data shortly before flushing synchronously.
    */
   flushAsync: function () {
-    MessageQueue.flushAsync();
+    if (!Services.prefs.getBoolPref("browser.sessionstore.debug")) {
+      throw new Error("flushAsync() must be used for testing, only.");
+    }
+
+    MessageQueue.send();
   }
 };
 
 /**
  * Listens for changes to the session history. Whenever the user navigates
  * we will collect URLs and everything belonging to session history.
  *
  * Causes a SessionStore:update message to be sent that contains the current
@@ -675,19 +685,18 @@ let MessageQueue = {
 
     durationMs = Date.now() - durationMs;
     let telemetry = {
       FX_SESSION_RESTORE_CONTENT_COLLECT_DATA_LONGEST_OP_MS: durationMs
     }
 
     // Send all data to the parent process.
     sendMessage("SessionStore:update", {
-      id: this._id,
-      data: data,
-      telemetry: telemetry
+      id: this._id, data, telemetry,
+      isFinal: options.isFinal || false
     });
 
     // Increase our unique message ID.
     this._id++;
   },
 
   /**
    * This function is used to make the message queue flush all queue data that
@@ -701,30 +710,16 @@ let MessageQueue = {
   flush: function (id) {
     // It's important to always send data, even if there is nothing to flush.
     // The update message will be received by the parent process that can then
     // update its last received update ID to ignore stale messages.
     this.send({id: id + 1, sync: true});
 
     this._data.clear();
     this._lastUpdated.clear();
-  },
-
-  /**
-   * DO NOT USE - DEBUGGING / TESTING ONLY
-   *
-   * This function is used to simulate certain situations where race conditions
-   * can occur by sending data shortly before flushing synchronously.
-   */
-  flushAsync: function () {
-    if (!Services.prefs.getBoolPref("browser.sessionstore.debug")) {
-      throw new Error("flushAsync() must be used for testing, only.");
-    }
-
-    this.send();
   }
 };
 
 EventListener.init();
 MessageListener.init();
 FormDataListener.init();
 SyncHandler.init();
 PageStyleListener.init();
@@ -754,16 +749,20 @@ function handleRevivedTab() {
   }
 }
 
 // If we're browsing from the tab crashed UI to a blacklisted URI that keeps
 // this browser non-remote, we'll handle that in a pagehide event.
 addEventListener("pagehide", handleRevivedTab);
 
 addEventListener("unload", () => {
+  // Upon frameLoader destruction, send a final update message to
+  // the parent and flush all data currently held in the child.
+  MessageQueue.send({isFinal: true});
+
   // If we're browsing from the tab crashed UI to a URI that causes the tab
   // to go remote again, we catch this in the unload event handler, because
   // swapping out the non-remote browser for a remote one in
   // tabbrowser.xml's updateBrowserRemoteness doesn't cause the pagehide
   // event to be fired.
   handleRevivedTab();
 
   // Remove all registered nsIObservers.
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -60,16 +60,18 @@ support-files =
 
 
 #disabled-for-intermittent-failures--bug-766044, browser_459906_empty.html
 #disabled-for-intermittent-failures--bug-766044, browser_459906_sample.html
 #disabled-for-intermittent-failures--bug-765389, browser_461743_sample.html
 
 [browser_aboutPrivateBrowsing.js]
 [browser_aboutSessionRestore.js]
+[browser_async_remove_tab.js]
+run-if = e10s
 [browser_attributes.js]
 [browser_backup_recovery.js]
 [browser_broadcast.js]
 [browser_capabilities.js]
 [browser_cleaner.js]
 [browser_cookies.js]
 [browser_crashedTabs.js]
 skip-if = !e10s || !crashreporter
--- a/browser/components/sessionstore/test/browser_350525.js
+++ b/browser/components/sessionstore/test/browser_350525.js
@@ -1,21 +1,21 @@
-function test() {
+"use strict";
+
+add_task(function* () {
   /** Test for Bug 350525 **/
 
   function test(aLambda) {
     try {
       return aLambda() || true;
     }
     catch (ex) { }
     return false;
   }
 
-  waitForExplicitFinish();
-
   ////////////////////////////
   // setWindowValue, et al. //
   ////////////////////////////
   let key = "Unique name: " + Date.now();
   let value = "Unique value: " + Math.random();
 
   // test adding
   ok(test(function() ss.setWindowValue(window, key, value)), "set a window value");
@@ -51,49 +51,46 @@ function test() {
 
   // value should not exist post-delete
   is(ss.getTabValue(tab, key), "", "tab value was deleted");
 
   // test deleting a non-existent value
   ok(test(function() ss.deleteTabValue(tab, key)), "delete non-existent tab value");
 
   // clean up
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   /////////////////////////////////////
   // getClosedTabCount, undoCloseTab //
   /////////////////////////////////////
 
   // get closed tab count
   let count = ss.getClosedTabCount(window);
   let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
   ok(0 <= count && count <= max_tabs_undo,
      "getClosedTabCount returns zero or at most max_tabs_undo");
 
   // create a new tab
   let testURL = "about:";
   tab = gBrowser.addTab(testURL);
-  promiseBrowserLoaded(tab.linkedBrowser).then(() => {
-    // make sure that the next closed tab will increase getClosedTabCount
-    gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
+  yield promiseBrowserLoaded(tab.linkedBrowser);
 
-    // remove tab
-    gBrowser.removeTab(tab);
+  // make sure that the next closed tab will increase getClosedTabCount
+  gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
+  registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo"));
 
-    // getClosedTabCount
-    var newcount = ss.getClosedTabCount(window);
-    ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented");
+  // remove tab
+  yield promiseRemoveTab(tab);
 
-    // undoCloseTab
-    tab = test(function() ss.undoCloseTab(window, 0));
-    ok(tab, "undoCloseTab doesn't throw")
-
-    promiseTabRestored(tab).then(() => {
-      is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened");
+  // getClosedTabCount
+  let newcount = ss.getClosedTabCount(window);
+  ok(newcount > count, "after closing a tab, getClosedTabCount has been incremented");
 
-      // clean up
-      if (gPrefService.prefHasUserValue("browser.sessionstore.max_tabs_undo"))
-        gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
-      gBrowser.removeTab(tab);
-      finish();
-    });
-  });
-}
+  // undoCloseTab
+  tab = test(function() ss.undoCloseTab(window, 0));
+  ok(tab, "undoCloseTab doesn't throw")
+
+  yield promiseTabRestored(tab);
+  is(tab.linkedBrowser.currentURI.spec, testURL, "correct tab was reopened");
+
+  // clean up
+  gBrowser.removeTab(tab);
+});
--- a/browser/components/sessionstore/test/browser_367052.js
+++ b/browser/components/sessionstore/test/browser_367052.js
@@ -1,37 +1,41 @@
 /* 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/. */
 
-function test() {
-  /** Test for Bug 367052 **/
+"use strict";
 
-  waitForExplicitFinish();
-
+add_task(function* () {
   // make sure that the next closed tab will increase getClosedTabCount
   let max_tabs_undo = gPrefService.getIntPref("browser.sessionstore.max_tabs_undo");
   gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", max_tabs_undo + 1);
-  let closedTabCount = ss.getClosedTabCount(window);
+  registerCleanupFunction(() => gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo"));
+
+  // Empty the list of closed tabs.
+  while (ss.getClosedTabCount(window)) {
+    ss.forgetClosedTab(window, 0);
+  }
 
   // restore a blank tab
   let tab = gBrowser.addTab("about:");
-  promiseBrowserLoaded(tab.linkedBrowser).then(() => {
-    let history = tab.linkedBrowser.webNavigation.sessionHistory;
-    ok(history.count >= 1, "the new tab does have at least one history entry");
+  yield promiseBrowserLoaded(tab.linkedBrowser);
 
-    promiseTabState(tab, {entries: []}).then(() => {
-      // We may have a different sessionHistory object if the tab
-      // switched from non-remote to remote.
-      history = tab.linkedBrowser.webNavigation.sessionHistory;
-      ok(history.count == 0, "the tab was restored without any history whatsoever");
+  let count = yield promiseSHistoryCount(tab.linkedBrowser);
+  ok(count >= 1, "the new tab does have at least one history entry");
+
+  yield promiseTabState(tab, {entries: []});
 
-      gBrowser.removeTab(tab);
-      ok(ss.getClosedTabCount(window) == closedTabCount,
-         "The closed blank tab wasn't added to Recently Closed Tabs");
+  // We may have a different sessionHistory object if the tab
+  // switched from non-remote to remote.
+  count = yield promiseSHistoryCount(tab.linkedBrowser);
+  is(count, 0, "the tab was restored without any history whatsoever");
 
-      // clean up
-      if (gPrefService.prefHasUserValue("browser.sessionstore.max_tabs_undo"))
-        gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
-      finish();
-    });
+  yield promiseRemoveTab(tab);
+  is(ss.getClosedTabCount(window), 0,
+     "The closed blank tab wasn't added to Recently Closed Tabs");
+});
+
+function promiseSHistoryCount(browser) {
+  return ContentTask.spawn(browser, null, function* () {
+    return docShell.QueryInterface(Ci.nsIWebNavigation).sessionHistory.count;
   });
 }
--- a/browser/components/sessionstore/test/browser_394759_perwindowpb.js
+++ b/browser/components/sessionstore/test/browser_394759_perwindowpb.js
@@ -1,25 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/** Private Browsing Test for Bug 394759 **/
-
-let closedWindowCount = 0;
-// Prevent VM timers issues, cache now and increment it manually.
-let now = Date.now();
+"use strict";
 
 const TESTS = [
   { url: "about:config",
     key: "bug 394759 Non-PB",
-    value: "uniq" + (++now) },
+    value: "uniq" + r() },
   { url: "about:mozilla",
     key: "bug 394759 PB",
-    value: "uniq" + (++now) },
+    value: "uniq" + r() },
 ];
 
 function promiseTestOpenCloseWindow(aIsPrivate, aTest) {
   return Task.spawn(function*() {
     let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
     win.gBrowser.selectedBrowser.loadURI(aTest.url);
     yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
     yield Promise.resolve();
@@ -28,63 +24,21 @@ function promiseTestOpenCloseWindow(aIsP
     // Close.
     yield promiseWindowClosed(win);
   });
 }
 
 function promiseTestOnWindow(aIsPrivate, aValue) {
   return Task.spawn(function*() {
     let win = yield promiseNewWindowLoaded({ "private": aIsPrivate });
-    yield promiseCheckClosedWindows(aIsPrivate, aValue);
-    registerCleanupFunction(() => promiseWindowClosed(win));
-  });
-}
-
-function promiseCheckClosedWindows(aIsPrivate, aValue) {
-  return Task.spawn(function*() {
     let data = JSON.parse(ss.getClosedWindowData())[0];
     is(ss.getClosedWindowCount(), 1, "Check that the closed window count hasn't changed");
     ok(JSON.stringify(data).indexOf(aValue) > -1,
        "Check the closed window data was stored correctly");
-  });
-}
-
-function promiseBlankState() {
-  return Task.spawn(function*() {
-    // Set interval to a large time so state won't be written while we setup
-    // environment.
-    Services.prefs.setIntPref("browser.sessionstore.interval", 100000);
-    registerCleanupFunction(() =>  Services.prefs.clearUserPref("browser.sessionstore.interval"));
-
-    // Set up the browser in a blank state. Popup windows in previous tests
-    // result in different states on different platforms.
-    let blankState = JSON.stringify({
-      windows: [{
-        tabs: [{ entries: [{ url: "about:blank" }] }],
-        _closedTabs: []
-      }],
-      _closedWindows: []
-    });
-
-    ss.setBrowserState(blankState);
-
-    // Wait for the sessionstore.js file to be written before going on.
-    // Note: we don't wait for the complete event, since if asyncCopy fails we
-    // would timeout.
-
-    yield forceSaveState();
-    closedWindowCount = ss.getClosedWindowCount();
-    is(closedWindowCount, 0, "Correctly set window count");
-
-    // Remove the sessionstore.js file before setting the interval to 0
-    yield SessionFile.wipe();
-
-    // Make sure that sessionstore.js can be forced to be created by setting
-    // the interval pref to 0.
-    yield forceSaveState();
+    registerCleanupFunction(() => promiseWindowClosed(win));
   });
 }
 
 add_task(function* init() {
   forgetClosedWindows();
   while (ss.getClosedTabCount(window) > 0) {
     ss.forgetClosedTab(window, 0);
   }
--- a/browser/components/sessionstore/test/browser_454908.js
+++ b/browser/components/sessionstore/test/browser_454908.js
@@ -23,17 +23,17 @@ add_task(function* test_dont_save_passwo
   yield promiseBrowserLoaded(browser);
 
   // Fill in some values.
   let usernameValue = "User " + Math.random();
   yield setInputValue(browser, {id: "username", value: usernameValue});
   yield setInputValue(browser, {id: "passwd", value: PASS});
 
   // Close and restore the tab.
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   tab = ss.undoCloseTab(window, 0);
   browser = tab.linkedBrowser;
   yield promiseTabRestored(tab);
 
   // Check that password fields aren't saved/restored.
   let username = yield getInputValue(browser, {id: "username"});
   is(username, usernameValue, "username was saved/restored");
   let passwd = yield getInputValue(browser, {id: "passwd"});
@@ -41,12 +41,11 @@ add_task(function* test_dont_save_passwo
 
   // Write to disk and read our file.
   yield forceSaveState();
   yield promiseForEachSessionRestoreFile((state, key) =>
     // Ensure that we have not saved our password.
     ok(!state.includes(PASS), "password has not been written to file " + key)
   );
 
-
   // Cleanup.
   gBrowser.removeTab(tab);
 });
--- a/browser/components/sessionstore/test/browser_456342.js
+++ b/browser/components/sessionstore/test/browser_456342.js
@@ -14,17 +14,17 @@ add_task(function test_restore_nonstanda
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   // Fill in form values.
   let expectedValue = Math.random();
   yield setFormElementValues(browser, {value: expectedValue});
 
   // Remove tab and check collected form data.
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   let undoItems = JSON.parse(ss.getClosedTabData(window));
   let savedFormData = undoItems[0].state.formdata;
 
   let countGood = 0, countBad = 0;
   for (let id of Object.keys(savedFormData.id)) {
     if (savedFormData.id[id] == expectedValue) {
       countGood++;
     } else {
--- a/browser/components/sessionstore/test/browser_579879.js
+++ b/browser/components/sessionstore/test/browser_579879.js
@@ -1,21 +1,20 @@
-function test() {
-  waitForExplicitFinish();
+"use strict";
 
-  var tab1 = gBrowser.addTab("data:text/plain;charset=utf-8,foo");
+add_task(function* () {
+  let tab1 = gBrowser.addTab("data:text/plain;charset=utf-8,foo");
   gBrowser.pinTab(tab1);
 
-  promiseBrowserLoaded(tab1.linkedBrowser).then(() => {
-    var tab2 = gBrowser.addTab();
-    gBrowser.pinTab(tab2);
+  yield promiseBrowserLoaded(tab1.linkedBrowser);
+  let tab2 = gBrowser.addTab();
+  gBrowser.pinTab(tab2);
+
+  is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 is at the first position");
+  yield promiseRemoveTab(tab1);
 
-    is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 is at the first position");
-    gBrowser.removeTab(tab1);
-    tab1 = undoCloseTab();
-    ok(tab1.pinned, "pinned tab 1 has been restored as a pinned tab");
-    is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 has been restored to the first position");
+  tab1 = undoCloseTab();
+  ok(tab1.pinned, "pinned tab 1 has been restored as a pinned tab");
+  is(Array.indexOf(gBrowser.tabs, tab1), 0, "pinned tab 1 has been restored to the first position");
 
-    gBrowser.removeTab(tab1);
-    gBrowser.removeTab(tab2);
-    finish();
-  });
-}
+  gBrowser.removeTab(tab1);
+  gBrowser.removeTab(tab2);
+});
--- a/browser/components/sessionstore/test/browser_581937.js
+++ b/browser/components/sessionstore/test/browser_581937.js
@@ -1,36 +1,19 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
 // Tests that an about:blank tab with no history will not be saved into
 // session store and thus, it will not show up in Recently Closed Tabs.
 
-let tab;
-function test() {
-  waitForExplicitFinish();
-
-  gPrefService.setIntPref("browser.sessionstore.max_tabs_undo", 0);
-  gPrefService.clearUserPref("browser.sessionstore.max_tabs_undo");
+"use strict";
 
-  is(ss.getClosedTabCount(window), 0, "should be no closed tabs");
-
-  gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
-
-  tab = gBrowser.addTab();
-}
+add_task(function* () {
+  let tab = gBrowser.addTab("about:blank");
+  yield promiseBrowserLoaded(tab.linkedBrowser);
 
-function onTabOpen(aEvent) {
-  gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
-
-  // Let other listeners react to the TabOpen event before removing the tab.
-  executeSoon(function() {
-    is(gBrowser.browsers[1].currentURI.spec, "about:blank",
-       "we will be removing an about:blank tab");
+  is(tab.linkedBrowser.currentURI.spec, "about:blank",
+     "we will be removing an about:blank tab");
 
-    gBrowser.removeTab(tab);
-
-    is(ss.getClosedTabCount(window), 0, "should still be no closed tabs");
+  let r = `rand-${Math.random()}`;
+  ss.setTabValue(tab, "foobar", r);
 
-    executeSoon(finish);
-  });
-}
+  yield promiseRemoveTab(tab);
+  let closedTabData = ss.getClosedTabData(window);
+  ok(!closedTabData.contains(r), "tab not stored in _closedTabs");
+});
--- a/browser/components/sessionstore/test/browser_628270.js
+++ b/browser/components/sessionstore/test/browser_628270.js
@@ -25,25 +25,26 @@ function test() {
   whenTabIsLoaded(tab, function () {
     // hide the newly created tab
     assertNumberOfVisibleTabs(2, "there are two visible tabs");
     gBrowser.showOnlyTheseTabs([gBrowser.tabs[0]]);
     assertNumberOfVisibleTabs(1, "there is one visible tab");
     ok(tab.hidden, "newly created tab is now hidden");
 
     // close and restore hidden tab
-    gBrowser.removeTab(tab);
-    tab = ss.undoCloseTab(window, 0);
+    promiseRemoveTab(tab).then(() => {
+      tab = ss.undoCloseTab(window, 0);
 
-    // check that everything was restored correctly, clean up and finish
-    whenTabIsLoaded(tab, function () {
-      is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "restored tab has correct url");
+      // check that everything was restored correctly, clean up and finish
+      whenTabIsLoaded(tab, function () {
+        is(tab.linkedBrowser.currentURI.spec, "about:mozilla", "restored tab has correct url");
 
-      gBrowser.removeTab(tab);
-      finish();
+        gBrowser.removeTab(tab);
+        finish();
+      });
     });
   });
 }
 
 function whenTabIsLoaded(tab, callback) {
   tab.linkedBrowser.addEventListener("load", function onLoad() {
     tab.linkedBrowser.removeEventListener("load", onLoad, true);
     callback();
--- a/browser/components/sessionstore/test/browser_911547.js
+++ b/browser/components/sessionstore/test/browser_911547.js
@@ -24,17 +24,17 @@ add_task(function* test() {
   // origin document) and navigate to the data URI in the link.
   browser.contentDocument.getElementById("test_data_link").click();
   yield promiseBrowserLoaded(browser);
 
   is(browser.contentDocument.getElementById("test_id2").value, "ok",
      "CSP should block the script loaded by the clicked data URI");
 
   // close the tab
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   // open new tab and recover the state
   tab = ss.undoCloseTab(window, 0);
   yield promiseTabRestored(tab);
   browser = tab.linkedBrowser;
 
   is(browser.contentDocument.getElementById("test_id2").value, "ok",
      "CSP should block the script loaded by the clicked data URI after restore");
--- a/browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js
+++ b/browser/components/sessionstore/test/browser_aboutPrivateBrowsing.js
@@ -1,25 +1,21 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
 
 // Tests that an about:privatebrowsing tab with no history will not
 // be saved into session store and thus, it will not show up in
 // Recently Closed Tabs.
 
 add_task(function* () {
-  while (ss.getClosedTabCount(window)) {
-    ss.forgetClosedTab(window, 0);
-  }
-
-  is(ss.getClosedTabCount(window), 0, "should be no closed tabs");
-
   let tab = gBrowser.addTab("about:privatebrowsing");
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   is(gBrowser.browsers[1].currentURI.spec, "about:privatebrowsing",
      "we will be removing an about:privatebrowsing tab");
 
-  gBrowser.removeTab(tab);
-  is(ss.getClosedTabCount(window), 0, "should still be no closed tabs");
+  let r = `rand-${Math.random()}`;
+  ss.setTabValue(tab, "foobar", r);
+
+  yield promiseRemoveTab(tab);
+  let closedTabData = ss.getClosedTabData(window);
+  ok(!closedTabData.contains(r), "tab not stored in _closedTabs");
 });
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_async_remove_tab.js
@@ -0,0 +1,242 @@
+"use strict";
+
+function* createTabWithRandomValue(url) {
+  let tab = gBrowser.addTab(url);
+  let browser = tab.linkedBrowser;
+  yield promiseBrowserLoaded(browser);
+
+  // Set a random value.
+  let r = `rand-${Math.random()}`;
+  ss.setTabValue(tab, "foobar", r);
+
+  // Flush to ensure there are no scheduled messages.
+  TabState.flush(browser);
+
+  return {tab, r};
+}
+
+function isValueInClosedData(rval) {
+  return ss.getClosedTabData(window).includes(rval);
+}
+
+function restoreClosedTabWithValue(rval) {
+  let closedTabData = JSON.parse(ss.getClosedTabData(window));
+  let index = closedTabData.findIndex(function (data) {
+    return (data.state.extData && data.state.extData.foobar) == rval;
+  });
+
+  if (index == -1) {
+    throw new Error("no closed tab found for given rval");
+  }
+
+  return ss.undoCloseTab(window, index);
+}
+
+function promiseNewLocationAndHistoryEntryReplaced(browser, snippet) {
+  return ContentTask.spawn(browser, snippet, function* (snippet) {
+    let webNavigation = docShell.QueryInterface(Ci.nsIWebNavigation);
+    let shistory = webNavigation.sessionHistory;
+
+    // Evaluate the snippet that the changes the location.
+    eval(snippet);
+
+    return new Promise(resolve => {
+      let listener = {
+        OnHistoryReplaceEntry() {
+          shistory.removeSHistoryListener(this);
+          resolve();
+        },
+
+        QueryInterface: XPCOMUtils.generateQI([
+          Ci.nsISHistoryListener,
+          Ci.nsISupportsWeakReference
+        ])
+      };
+
+      shistory.addSHistoryListener(listener);
+
+      /* Keep the weak shistory listener alive. */
+      addEventListener("unload", function () {
+        try {
+          shistory.removeSHistoryListener(listener);
+        } catch (e) { /* Will most likely fail. */ }
+      });
+    });
+  });
+}
+
+function promiseHistoryEntryReplacedNonRemote(browser) {
+  let {listeners} = promiseHistoryEntryReplacedNonRemote;
+
+  return new Promise(resolve => {
+    let shistory = browser.webNavigation.sessionHistory;
+
+    let listener = {
+      OnHistoryReplaceEntry() {
+        shistory.removeSHistoryListener(this);
+        resolve();
+      },
+
+      QueryInterface: XPCOMUtils.generateQI([
+        Ci.nsISHistoryListener,
+        Ci.nsISupportsWeakReference
+      ])
+    };
+
+    shistory.addSHistoryListener(listener);
+    listeners.set(browser, listener);
+  });
+}
+promiseHistoryEntryReplacedNonRemote.listeners = new WeakMap();
+
+add_task(function* dont_save_empty_tabs() {
+  let {tab, r} = yield createTabWithRandomValue("about:blank");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // No tab state worth saving.
+  ok(!isValueInClosedData(r), "closed tab not saved");
+  yield promise;
+
+  // Still no tab state worth saving.
+  ok(!isValueInClosedData(r), "closed tab not saved");
+});
+
+add_task(function* save_worthy_tabs_remote() {
+  let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+  ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // Tab state deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+  yield promise;
+
+  // Tab state still deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_nonremote() {
+  let {tab, r} = yield createTabWithRandomValue("about:robots");
+  ok(!tab.linkedBrowser.isRemoteBrowser, "browser is not remote");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // Tab state deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+  yield promise;
+
+  // Tab state still deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_remote_final() {
+  let {tab, r} = yield createTabWithRandomValue("about:blank");
+  let browser = tab.linkedBrowser;
+  ok(browser.isRemoteBrowser, "browser is remote");
+
+  // Replace about:blank with a new remote page.
+  let snippet = 'webNavigation.loadURI("https://example.com/", null, null, null, null)';
+  yield promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
+
+  // Remotness shouldn't have changed.
+  ok(browser.isRemoteBrowser, "browser is still remote");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // No tab state worth saving (that we know about yet).
+  ok(!isValueInClosedData(r), "closed tab not saved");
+  yield promise;
+
+  // Turns out there is a tab state worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* save_worthy_tabs_nonremote_final() {
+  let {tab, r} = yield createTabWithRandomValue("about:blank");
+  let browser = tab.linkedBrowser;
+  ok(browser.isRemoteBrowser, "browser is remote");
+
+  // Replace about:blank with a non-remote entry.
+  browser.loadURI("about:robots");
+  ok(!browser.isRemoteBrowser, "browser is not remote anymore");
+
+  // Wait until the new entry replaces about:blank.
+  yield promiseHistoryEntryReplacedNonRemote(browser);
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // No tab state worth saving (that we know about yet).
+  ok(!isValueInClosedData(r), "closed tab not saved");
+  yield promise;
+
+  // Turns out there is a tab state worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+});
+
+add_task(function* dont_save_empty_tabs_final() {
+  let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+  let browser = tab.linkedBrowser;
+
+  // Replace the current page with an about:blank entry.
+  let snippet = 'content.location.replace("about:blank")';
+  yield promiseNewLocationAndHistoryEntryReplaced(browser, snippet);
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // Tab state deemed worth saving (yet).
+  ok(isValueInClosedData(r), "closed tab saved");
+  yield promise;
+
+  // Turns out we don't want to save the tab state.
+  ok(!isValueInClosedData(r), "closed tab not saved");
+});
+
+add_task(function* undo_worthy_tabs() {
+  let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+  ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // Tab state deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+
+  // Restore the closed tab before receiving its final message.
+  tab = restoreClosedTabWithValue(r);
+
+  // Wait for the final update message.
+  yield promise;
+
+  // Check we didn't add the tab back to the closed list.
+  ok(!isValueInClosedData(r), "tab no longer closed");
+
+  // Cleanup.
+  yield promiseRemoveTab(tab);
+});
+
+add_task(function* forget_worthy_tabs_remote() {
+  let {tab, r} = yield createTabWithRandomValue("https://example.com/");
+  ok(tab.linkedBrowser.isRemoteBrowser, "browser is remote");
+
+  // Remove the tab before the update arrives.
+  let promise = promiseRemoveTab(tab);
+
+  // Tab state deemed worth saving.
+  ok(isValueInClosedData(r), "closed tab saved");
+
+  // Forget the closed tab.
+  ss.forgetClosedTab(window, 0);
+
+  // Wait for the final update message.
+  yield promise;
+
+  // Check we didn't add the tab back to the closed list.
+  ok(!isValueInClosedData(r), "we forgot about the tab");
+});
--- a/browser/components/sessionstore/test/browser_broadcast.js
+++ b/browser/components/sessionstore/test/browser_broadcast.js
@@ -9,17 +9,17 @@ const INITIAL_VALUE = "browser_broadcast
  * This test ensures we won't lose tab data queued in the content script when
  * closing a tab.
  */
 add_task(function flush_on_tabclose() {
   let tab = yield createTabWithStorageData(["http://example.com"]);
   let browser = tab.linkedBrowser;
 
   yield modifySessionStorage(browser, {test: "on-tab-close"});
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://example.com"].test, "on-tab-close",
     "sessionStorage data has been flushed on TabClose");
 });
 
 /**
  * This test ensures we won't lose tab data queued in the content script when
@@ -54,17 +54,17 @@ add_task(function flush_on_duplicate() {
 
   yield modifySessionStorage(browser, {test: "on-duplicate"});
   let tab2 = ss.duplicateTab(window, tab);
   let {storage} = JSON.parse(ss.getTabState(tab2));
   is(storage["http://example.com"].test, "on-duplicate",
     "sessionStorage data has been flushed when duplicating tabs");
 
   yield promiseTabRestored(tab2);
-  gBrowser.removeTab(tab2);
+  yield promiseRemoveTab(tab2);
   [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://example.com"].test, "on-duplicate",
     "sessionStorage data has been flushed when duplicating tabs");
 
   gBrowser.removeTab(tab);
 });
 
 /**
@@ -123,17 +123,17 @@ add_task(function flush_on_tabclose_racy
   // Flush to make sure we start with an empty queue.
   TabState.flush(browser);
 
   yield modifySessionStorage(browser, {test: "on-tab-close-racy"});
 
   // Flush all data contained in the content script but send it using
   // asynchronous messages.
   TabState.flushAsync(browser);
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://example.com"].test, "on-tab-close-racy",
     "sessionStorage data has been merged correctly to prevent data loss");
 });
 
 function promiseNewWindow() {
   let deferred = Promise.defer();
--- a/browser/components/sessionstore/test/browser_cleaner.js
+++ b/browser/components/sessionstore/test/browser_cleaner.js
@@ -66,18 +66,18 @@ add_task(function* test_open_and_close()
   is(state.windows[0].tabs[1].closedAt || false, false, "1. Second tab doesn't have closedAt");
 
 
 
   info("2. Making sure that after closing, we have closedAt");
 
   // Now close stuff, this should add closeAt
   yield promiseWindowClosed(newWin);
-  gBrowser.removeTab(newTab1);
-  gBrowser.removeTab(newTab2);
+  yield promiseRemoveTab(newTab1);
+  yield promiseRemoveTab(newTab2);
 
   state = CLOSED_STATE = JSON.parse(ss.getBrowserState());
 
   is(state.windows[0].closedAt || false, false, "2. Main window doesn't have closedAt");
   ok(isRecent(state._closedWindows[0].closedAt), "2. Second window was closed recently");
   ok(isRecent(state.windows[0]._closedTabs[0].closedAt), "2. First tab was closed recently");
   ok(isRecent(state.windows[0]._closedTabs[1].closedAt), "2. Second tab was closed recently");
 });
--- a/browser/components/sessionstore/test/browser_dying_cache.js
+++ b/browser/components/sessionstore/test/browser_dying_cache.js
@@ -15,17 +15,17 @@ add_task(function* test() {
   let flags = Ci.nsIWebNavigation.LOAD_FLAGS_REPLACE_HISTORY;
   win.gBrowser.selectedBrowser.loadURIWithFlags("about:robots", flags);
   yield promiseBrowserLoaded(win.gBrowser.selectedBrowser);
 
   // Open a second tab and close the first one.
   let tab = win.gBrowser.addTab("about:mozilla");
   yield promiseBrowserLoaded(tab.linkedBrowser);
   TabState.flush(tab.linkedBrowser);
-  win.gBrowser.removeTab(win.gBrowser.tabs[0]);
+  yield promiseRemoveTab(win.gBrowser.tabs[0]);
 
   // Make sure our window is still tracked by sessionstore
   // and the window state is as expected.
   ok("__SSi" in win, "window is being tracked by sessionstore");
   ss.setWindowValue(win, "foo", "bar");
   checkWindowState(win);
 
   let state = ss.getWindowState(win);
--- a/browser/components/sessionstore/test/browser_formdata.js
+++ b/browser/components/sessionstore/test/browser_formdata.js
@@ -25,17 +25,17 @@ add_task(function test_formdata() {
       let browser = tab.linkedBrowser;
       yield promiseBrowserLoaded(browser);
 
       // Modify form data.
       yield setInputValue(browser, {id: "txt", value: OUTER_VALUE});
       yield setInputValue(browser, {id: "txt", value: INNER_VALUE, frame: 0});
 
       // Remove the tab.
-      gBrowser.removeTab(tab);
+      yield promiseRemoveTab(tab);
     });
   }
 
   yield createAndRemoveTab();
   let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
   is(formdata.id.txt, OUTER_VALUE, "outer value is correct");
   is(formdata.children[0].id.txt, INNER_VALUE, "inner value is correct");
 
@@ -114,17 +114,17 @@ add_task(function test_nested() {
   let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   // Modify the input field's value.
   yield sendMessage(browser, "ss-test:sendKeyEvent", {key: "m", frame: 0});
 
   // Remove the tab and check that we stored form data correctly.
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   let [{state: {formdata}}] = JSON.parse(ss.getClosedTabData(window));
   is(JSON.stringify(formdata), JSON.stringify(FORM_DATA),
     "formdata for iframe stored correctly");
 
   // Restore the closed tab.
   tab = ss.undoCloseTab(window, 0);
   browser = tab.linkedBrowser;
   yield promiseTabRestored(tab);
@@ -151,28 +151,28 @@ add_task(function test_design_mode() {
   let tab = gBrowser.selectedTab = gBrowser.addTab(URL);
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   // Modify the document content.
   yield sendMessage(browser, "ss-test:sendKeyEvent", {key: "m"});
 
   // Close and restore the tab.
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   tab = ss.undoCloseTab(window, 0);
   browser = tab.linkedBrowser;
   yield promiseTabRestored(tab);
 
   // Check that the innerHTML value was restored.
   let html = yield getInnerHTML(browser);
   let expected = "<h1>Mmozilla</h1><script>document.designMode='on'</script>";
   is(html, expected, "editable document has been restored correctly");
 
   // Close and restore the tab.
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   tab = ss.undoCloseTab(window, 0);
   browser = tab.linkedBrowser;
   yield promiseTabRestored(tab);
 
   // Check that the innerHTML value was restored.
   html = yield getInnerHTML(browser);
   expected = "<h1>Mmozilla</h1><script>document.designMode='on'</script>";
   is(html, expected, "editable document has been restored correctly");
@@ -227,17 +227,17 @@ add_task(function test_ccNumbers() {
     let tab = gBrowser.addTab(URL);
     let browser = tab.linkedBrowser;
     yield promiseBrowserLoaded(browser);
 
     // Set form value.
     yield setInputValue(browser, {id: "txt", value: formValue});
 
     // Remove the tab.
-    gBrowser.removeTab(tab);
+    yield promiseRemoveTab(tab);
   }
 
   // Test that valid CC numbers are not collected.
   for (let number of validCCNumbers) {
     yield createAndRemoveTab(number);
     let [{state}] = JSON.parse(ss.getClosedTabData(window));
     ok(!("formdata" in state), "valid CC numbers are not collected");
   }
--- a/browser/components/sessionstore/test/browser_frame_history.js
+++ b/browser/components/sessionstore/test/browser_frame_history.js
@@ -27,17 +27,17 @@ add_task(function() {
   yield promise;
 
   info("Clicking on link 2, 1 load should take place");
   promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
   EventUtils.sendMouseEvent({type:"click"}, links[1], browser_b.contentWindow);
   yield promise;
 
   info("Close then un-close page, 4 loads should take place");
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   let newTab = ss.undoCloseTab(window, 0);
   yield waitForLoadsInBrowser(newTab.linkedBrowser, 4);
 
   info("Go back in time, 1 load should take place");
   gBrowser.goBack();
   yield waitForLoadsInBrowser(newTab.linkedBrowser, 1);
 
   let expectedURLEnds = ["a.html", "b.html", "c1.html"];
@@ -72,17 +72,17 @@ add_task(function() {
   yield promise;
 
   info("iframe: Clicking on link 2, 1 load should take place");
   promise = waitForLoadsInBrowser(tab.linkedBrowser, 1);
   EventUtils.sendMouseEvent({type:"click"}, links[1], browser_b.contentWindow);
   yield promise;
 
   info("iframe: Close then un-close page, 5 loads should take place");
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
   let newTab = ss.undoCloseTab(window, 0);
   yield waitForLoadsInBrowser(newTab.linkedBrowser, 5);
 
   info("iframe: Go back in time, 1 load should take place");
   gBrowser.goBack();
   yield waitForLoadsInBrowser(newTab.linkedBrowser, 1);
 
   let expectedURLEnds = ["a.html", "b.html", "c1.html"];
--- a/browser/components/sessionstore/test/browser_pageStyle.js
+++ b/browser/components/sessionstore/test/browser_pageStyle.js
@@ -54,17 +54,17 @@ add_task(function page_style() {
  * received and the page style is persisted correctly.
  */
 add_task(function nested_page_style() {
   let tab = gBrowser.addTab(URL_NESTED);
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   yield enableSubDocumentStyleSheetsForSet(browser, "alternate");
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   let [{state: {pageStyle}}] = JSON.parse(ss.getClosedTabData(window));
   let expected = JSON.stringify({children: [{pageStyle: "alternate"}]});
   is(JSON.stringify(pageStyle), expected, "correct pageStyle persisted");
 });
 
 function getStyleSheets(browser) {
   return sendMessage(browser, "ss-test:getStyleSheets");
--- a/browser/components/sessionstore/test/browser_sessionHistory.js
+++ b/browser/components/sessionstore/test/browser_sessionHistory.js
@@ -10,17 +10,17 @@ add_task(function test_load_start() {
   // Create a new tab.
   let tab = gBrowser.addTab("about:blank");
   let browser = tab.linkedBrowser;
   yield promiseBrowserLoaded(browser);
 
   // Load a new URI but remove the tab before it has finished loading.
   browser.loadURI("about:mozilla");
   yield promiseContentMessage(browser, "ss-test:OnHistoryReplaceEntry");
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   // Undo close the tab.
   tab = ss.undoCloseTab(window, 0);
   browser = tab.linkedBrowser;
   yield promiseTabRestored(tab);
 
   // Check that the correct URL was restored.
   is(browser.currentURI.spec, "about:mozilla", "url is correct");
--- a/browser/components/sessionstore/test/browser_sessionStorage.js
+++ b/browser/components/sessionstore/test/browser_sessionStorage.js
@@ -124,54 +124,54 @@ add_task(function purge_domain() {
 
 /**
  * This test ensures that collecting sessionStorage data respects the privacy
  * levels as set by the user.
  */
 add_task(function respect_privacy_level() {
   let tab = gBrowser.addTab(URL + "&secure");
   yield promiseBrowserLoaded(tab.linkedBrowser);
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
     "http sessionStorage data has been saved");
   is(storage["https://example.com"].test, INNER_VALUE,
     "https sessionStorage data has been saved");
 
   // Disable saving data for encrypted sites.
   Services.prefs.setIntPref("browser.sessionstore.privacy_level", 1);
 
   tab = gBrowser.addTab(URL + "&secure");
   yield promiseBrowserLoaded(tab.linkedBrowser);
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
     "http sessionStorage data has been saved");
   ok(!storage["https://example.com"],
     "https sessionStorage data has *not* been saved");
 
   // Disable saving data for any site.
   Services.prefs.setIntPref("browser.sessionstore.privacy_level", 2);
 
   // Check that duplicating a tab copies all private data.
   tab = gBrowser.addTab(URL + "&secure");
   yield promiseBrowserLoaded(tab.linkedBrowser);
   let tab2 = gBrowser.duplicateTab(tab);
   yield promiseTabRestored(tab2);
-  gBrowser.removeTab(tab);
+  yield promiseRemoveTab(tab);
 
   // With privacy_level=2 the |tab| shouldn't have any sessionStorage data.
   [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   ok(!storage, "sessionStorage data has *not* been saved");
 
   // Restore the default privacy level and close the duplicated tab.
   Services.prefs.clearUserPref("browser.sessionstore.privacy_level");
-  gBrowser.removeTab(tab2);
+  yield promiseRemoveTab(tab2);
 
   // With privacy_level=0 the duplicated |tab2| should persist all data.
   [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window));
   is(storage["http://mochi.test:8888"].test, OUTER_VALUE,
     "http sessionStorage data has been saved");
   is(storage["https://example.com"].test, INNER_VALUE,
     "https sessionStorage data has been saved");
 });
--- a/browser/components/sessionstore/test/browser_telemetry.js
+++ b/browser/components/sessionstore/test/browser_telemetry.js
@@ -71,49 +71,49 @@ add_task(function history() {
     gt(statistics2[KEY], statistics[KEY], "The total size of HISTORY has increased");
 
 // Almost nothing else should
     for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
       is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
     }
   } finally {
     if (tab) {
-      gBrowser.removeTab(tab);
+      yield promiseRemoveTab(tab);
     }
   }
 });
 
 /**
  * Test CLOSED_TABS_IN_OPEN_WINDOWS key.
  */
 add_task(function close_tab() {
   let KEY = Keys.CLOSED_TABS_IN_OPEN_WINDOWS;
   let tab = gBrowser.addTab("http://example.org:80/?close_tab");
   yield promiseBrowserLoaded(tab.linkedBrowser);
   try {
     TabState.flush(tab.linkedBrowser);
     let statistics = yield promiseStats();
 
     info("Now closing a tab");
-    gBrowser.removeTab(tab);
+    yield promiseRemoveTab(tab);
     tab = null;
     let statistics2 = yield promiseStats();
 
     isnot(statistics[KEY], undefined, "Key was defined");
     isnot(statistics2[KEY], undefined, "Key is still defined");
     gt(statistics2[KEY], statistics[KEY], "The total size of CLOSED_TABS_IN_OPEN_WINDOWS has increased");
 
     // Almost nothing else should change
     for (let k of ["FORMDATA", "DOM_STORAGE", "CLOSED_WINDOWS"]) {
       is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
     }
 
   } finally {
     if (tab) {
-      gBrowser.removeTab(tab);
+      yield promiseRemoveTab(tab);
     }
   }
 });
 
 /**
  * Test OPEN_WINDOWS key.
  */
 add_task(function open_window() {
@@ -197,17 +197,17 @@ add_task(function dom_storage() {
 
     // Almost nothing else should change
     for (let k of ["CLOSED_TABS_IN_OPEN_WINDOWS", "FORMDATA", "CLOSED_WINDOWS"]) {
       is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
     }
 
   } finally {
     if (tab) {
-      gBrowser.removeTab(tab);
+      yield promiseRemoveTab(tab);
     }
   }
 });
 
 /**
  * Test FORMDATA key.
  */
 add_task(function formdata() {
@@ -230,17 +230,17 @@ add_task(function formdata() {
     gt(statistics2[KEY], statistics[KEY], "The total size of FORMDATA has increased");
 
     // Almost nothing else should
     for (let k of ["DOM_STORAGE", "CLOSED_WINDOWS", "CLOSED_TABS_IN_OPEN_WINDOWS"]) {
       is(statistics2[Keys[k]], statistics[Keys[k]], "The total size of " + k + " has not increased");
     }
   } finally {
     if (tab) {
-      gBrowser.removeTab(tab);
+      yield promiseRemoveTab(tab);
     }
   }
 });
 
 add_task(function* test_sessionRestoreInit() {
    let info = Cc["@mozilla.org/toolkit/app-startup;1"].
      getService(Ci.nsIAppStartup).
      getStartupInfo();
--- a/browser/components/sessionstore/test/content.js
+++ b/browser/components/sessionstore/test/content.js
@@ -60,18 +60,20 @@ let historyListener = {
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsISHistoryListener,
     Ci.nsISupportsWeakReference
   ])
 };
 
-docShell.QueryInterface(Ci.nsIWebNavigation).
+let {sessionHistory} = docShell.QueryInterface(Ci.nsIWebNavigation);
+if (sessionHistory) {
   sessionHistory.addSHistoryListener(historyListener);
+}
 
 /**
  * This frame script is only loaded for sessionstore mochitests. It enables us
  * to modify and query docShell data when running with multiple processes.
  */
 
 addEventListener("hashchange", function () {
   sendAsyncMessage("ss-test:hashchange");
--- a/browser/components/sessionstore/test/head.js
+++ b/browser/components/sessionstore/test/head.js
@@ -522,8 +522,12 @@ const FORM_HELPERS = [
   "getMultipleSelected", "setMultipleSelected",
   "getFileNameArray", "setFileNameArray",
 ];
 
 for (let name of FORM_HELPERS) {
   let msg = "ss-test:" + name;
   this[name] = (browser, data) => sendMessage(browser, msg, data);
 }
+
+function promiseRemoveTab(tab) {
+  return BrowserTestUtils.removeTab(tab);
+}
--- a/browser/components/tabview/content.js
+++ b/browser/components/tabview/content.js
@@ -56,27 +56,29 @@ addEventListener("MozAfterPaint", Window
 // WindowMessageHandler
 //
 // Handles messages sent by the chrome process.
 let WindowMessageHandler = {
   // ----------
   // Function: isDocumentLoaded
   // Checks if the currently active document is loaded.
   isDocumentLoaded: function WMH_isDocumentLoaded(cx) {
-    let isLoaded = (content.document.readyState != "uninitialized" &&
+    let isLoaded = (content &&
+                    content.document.readyState != "uninitialized" &&
                     !webProgress.isLoadingDocument);
 
     sendAsyncMessage(cx.name, {isLoaded: isLoaded});
   },
 
   // ----------
   // Function: isImageDocument
   // Checks if the currently active document is an image document or not.
   isImageDocument: function WMH_isImageDocument(cx) {
-    let isImageDocument = (content.document instanceof Ci.nsIImageDocument);
+    let isImageDocument = (content &&
+                           content.document instanceof Ci.nsIImageDocument);
 
     sendAsyncMessage(cx.name, {isImageDocument: isImageDocument});
   },
 
   waitForDocumentLoad: function WMH_waitForDocumentLoad() {
     addEventListener("load", function listener() {
       removeEventListener("load", listener, true);
       sendAsyncMessage("Panorama:documentLoaded");
--- a/browser/components/tabview/favicons.js
+++ b/browser/components/tabview/favicons.js
@@ -84,17 +84,19 @@ let FavIcons = {
 
     // If the tab image's url starts with http(s), fetch icon from favicon
     // service via the moz-anno protocol.
     if (/^https?:/.test(tabImage)) {
       let tabImageURI = gWindow.makeURI(tabImage);
       tabImage = this._favIconService.getFaviconLinkForIcon(tabImageURI).spec;
     }
 
-    tabImage = PlacesUtils.getImageURLForResolution(window, tabImage);
+    if (tabImage) {
+      tabImage = PlacesUtils.getImageURLForResolution(window, tabImage);
+    }
 
     callback(tabImage);
   },
 
   // ----------
   // Function: _getFavIconForHttpDocument
   // Retrieves the favicon for tab containg a http(s) document.
   _getFavIconForHttpDocument:
--- a/browser/components/tabview/test/browser_tabview_bug608037.js
+++ b/browser/components/tabview/test/browser_tabview_bug608037.js
@@ -19,24 +19,25 @@ function test() {
 }
 
 function onTabViewWindowLoaded() {
   let contentWindow = TabView.getContentWindow();
   let groupItems = contentWindow.GroupItems.groupItems;
   is(groupItems.length, 1, "There is only one group");
   is(groupItems[0].getChildren().length, 3, "The group has three tab items");
 
-  gBrowser.removeTab(tabTwo);
-  ok(TabView.isVisible(), "Tab View is still visible after removing a tab");
-  is(groupItems[0].getChildren().length, 2, "The group has two tab items");
+  BrowserTestUtils.removeTab(tabTwo).then(() => {
+    ok(TabView.isVisible(), "Tab View is still visible after removing a tab");
+    is(groupItems[0].getChildren().length, 2, "The group has two tab items");
 
-  restoreTab(function (tabTwo) {
-    ok(TabView.isVisible(), "Tab View is still visible after restoring a tab");
-    is(groupItems[0].getChildren().length, 3, "The group still has three tab items");
+    restoreTab(function (tabTwo) {
+      ok(TabView.isVisible(), "Tab View is still visible after restoring a tab");
+      is(groupItems[0].getChildren().length, 3, "The group still has three tab items");
 
-    // clean up and finish
-    hideTabView(function () {
-      gBrowser.removeTab(tabOne);
-      gBrowser.removeTab(tabTwo);
-      finish();
+      // clean up and finish
+      hideTabView(function () {
+        gBrowser.removeTab(tabOne);
+        gBrowser.removeTab(tabTwo);
+        finish();
+      });
     });
   });
 }
--- a/browser/components/tabview/test/browser_tabview_bug624847.js
+++ b/browser/components/tabview/test/browser_tabview_bug624847.js
@@ -51,28 +51,29 @@ function test() {
   }
 
   let testUndoCloseWithSelectedBlankTab = function () {
     prefix = 'unpinned';
     let tab = createTab();
     assertNumberOfTabs(2);
 
     afterAllTabsLoaded(function () {
-      win.gBrowser.removeTab(tab);
-      assertNumberOfTabs(1);
-      assertNumberOfPinnedTabs(0);
+      BrowserTestUtils.removeTab(tab).then(() => {
+        assertNumberOfTabs(1);
+        assertNumberOfPinnedTabs(0);
 
-      restoreTab(function () {
-        prefix = 'unpinned-restored';
-        assertValidPrerequisites();
-        assertGroupItemPreserved();
+        restoreTab(function () {
+          prefix = 'unpinned-restored';
+          assertValidPrerequisites();
+          assertGroupItemPreserved();
 
-        createBlankTab();
-        afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab, win);
-      }, 0, win);
+          createBlankTab();
+          afterAllTabsLoaded(testUndoCloseWithSelectedBlankPinnedTab, win);
+        }, 0, win);
+      });
     }, win);
   }
 
   let testUndoCloseWithSelectedBlankPinnedTab = function () {
     prefix = 'pinned';
     assertNumberOfTabs(2);
 
     afterAllTabsLoaded(function () {
--- a/browser/components/tabview/test/browser_tabview_bug628270.js
+++ b/browser/components/tabview/test/browser_tabview_bug628270.js
@@ -65,27 +65,34 @@ function test() {
 
   let testRestoreTabFromInactiveGroup = function () {
     prefix = 'restore';
     activateFirstGroupItem();
 
     let groupItem = getGroupItem(1);
     let tabItem = groupItem.getChild(0);
 
+    // Wait until the tab has been removed but close it ourselves.
+    let promise = BrowserTestUtils.removeTab(tabItem.tab, {dontRemove: true});
+
+    // Close the tab.
     EventUtils.synthesizeMouseAtCenter(
       tabItem.$close[0], {}, TabView.getContentWindow());
-    assertNumberOfTabsInGroup(groupItem, 1);
 
-    restoreTab(function () {
-      assertNumberOfTabsInGroup(groupItem, 2);
+    promise.then(() => {
+      assertNumberOfTabsInGroup(groupItem, 1);
 
-      activateFirstGroupItem();
-      gBrowser.removeTab(gBrowser.tabs[1]);
-      gBrowser.removeTab(gBrowser.tabs[1]);
-      hideTabView(finishTest);
+      restoreTab(function () {
+        assertNumberOfTabsInGroup(groupItem, 2);
+
+        activateFirstGroupItem();
+        gBrowser.removeTab(gBrowser.tabs[1]);
+        gBrowser.removeTab(gBrowser.tabs[1]);
+        hideTabView(finishTest);
+      });
     });
   }
 
   waitForExplicitFinish();
   assertTabViewIsHidden();
   registerCleanupFunction(function () TabView.hide());
 
   showTabView(function () {
--- a/browser/modules/BrowserUITelemetry.jsm
+++ b/browser/modules/BrowserUITelemetry.jsm
@@ -656,17 +656,17 @@ this.BrowserUITelemetry = {
     "media-playbackrate-050x", "media-playbackrate-100x",
     "media-playbackrate-150x", "media-playbackrate-200x",
     "media-showcontrols", "media-hidecontrols", "video-showstats",
     "video-hidestats", "video-fullscreen", "leave-dom-fullscreen",
     "reloadimage", "viewimage", "viewvideo", "copyimage-contents", "copyimage",
     "copyvideourl", "copyaudiourl", "saveimage", "shareimage", "sendimage",
     "setDesktopBackground", "viewimageinfo", "viewimagedesc", "savevideo",
     "sharevideo", "saveaudio", "video-saveimage", "sendvideo", "sendaudio",
-    "ctp-play", "ctp-hide", "sharepage", "savepage", "markpageMenu",
+    "ctp-play", "ctp-hide", "sharepage", "savepage", "pocket", "markpageMenu",
     "viewbgimage", "undo", "cut", "copy", "paste", "delete", "selectall",
     "keywordfield", "searchselect", "shareselect", "frame", "showonlythisframe",
     "openframeintab", "openframe", "reloadframe", "bookmarkframe", "saveframe",
     "printframe", "viewframesource", "viewframeinfo",
     "viewpartialsource-selection", "viewpartialsource-mathml",
     "viewsource", "viewinfo", "spell-check-enabled",
     "spell-add-dictionaries-main", "spell-dictionaries",
     "spell-dictionaries-menu", "spell-add-dictionaries",
--- a/browser/modules/CustomizationTabPreloader.jsm
+++ b/browser/modules/CustomizationTabPreloader.jsm
@@ -150,16 +150,17 @@ HiddenBrowser.prototype = {
   _createBrowser: function () {
     if (!this._hiddenFrame) {
       this._hiddenFrame = new HiddenFrame();
     }
 
     this._hiddenFrame.get().then(aFrame => {
       let doc = aFrame.document;
       this._browser = doc.createElementNS(XUL_NS, "browser");
+      this._browser.permanentKey = {};
       this._browser.setAttribute("type", "content");
       this._browser.setAttribute("src", CUSTOMIZATION_URL);
       this._browser.style.width = "400px";
       this._browser.style.height = "400px";
       doc.getElementById("win").appendChild(this._browser);
     });
   },
 
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -518,16 +518,22 @@ menuitem:not([type]):not(.menuitem-toolt
   list-style-image: url("chrome://browser/skin/places/unsortedBookmarks.png");
 }
 
 #menu_readingList,
 #BMB_readingList {
   list-style-image: url("chrome://browser/skin/readinglist/readinglist-icon.svg");
 }
 
+#panelMenu_pocket,
+#menu_pocket,
+#BMB_pocket {
+  list-style-image: url("chrome://browser/content/pocket/panels/img/pocketmenuitem16.png");
+}
+
 #menu_openDownloads {
   list-style-image: url("chrome://browser/skin/Toolbar-small.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 #menu_openAddons {
   list-style-image: url("chrome://mozapps/skin/extensions/extensionGeneric-16.png");
 }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -563,16 +563,34 @@ toolbarpaletteitem[place="palette"] > #p
   }
 }
 
 /* #menu_readingList, svg icons don't work in the mac native menubar */
 #BMB_readingList {
   list-style-image: url("chrome://browser/skin/readinglist/readinglist-icon.svg");
 }
 
+#panelMenu_pocket,
+#menu_pocket,
+#BMB_pocket {
+  list-style-image: url("chrome://browser/content/pocket/panels/img/pocketmenuitem16.png");
+}
+
+@media (min-resolution: 2dppx) {
+  #panelMenu_pocket,
+  #menu_pocket,
+  #BMB_pocket {
+    list-style-image: url("chrome://browser/content/pocket/panels/img/pocketmenuitem16@2x.png");
+  }
+
+  #panelMenu_pocket > .toolbarbutton-icon {
+    width: 16px;
+  }
+}
+
 /* ----- PRIMARY TOOLBAR BUTTONS ----- */
 
 toolbar .toolbarbutton-1:not([type="menu-button"]),
 .toolbarbutton-1 > .toolbarbutton-menubutton-button,
 .toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker {
   -moz-box-orient: vertical;
   height: 24px;
   padding: 0;
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -131,18 +131,8 @@ div#certificateErrorReportingPanel:-moz-
 
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
 }
-
-#reportSendingMessage {
-  /* adjust the line-height to match the link */
-  line-height: 22px;
-}
-
-#reportSentMessage {
-  /* adjust the line-height to match the link */
-  line-height: 22px;
-}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2292,16 +2292,22 @@ notification[value="loop-sharing-notific
 }
 
 #menu_readingList,
 #BMB_readingList {
   list-style-image: url("chrome://browser/skin/readinglist/readinglist-icon.svg");
   -moz-image-region: auto;
 }
 
+#panelMenu_pocket,
+#menu_pocket,
+#BMB_pocket {
+  list-style-image: url("chrome://browser/content/pocket/panels/img/pocketmenuitem16.png");
+}
+
 /* ::::: Keyboard UI Panel ::::: */
 
 .KUI-panel {
   -moz-appearance: none;
   background: rgba(27%,27%,27%,.9) url(KUI-background.png) repeat-x;
   color: white;
   border-style: none;
   border-radius: 20px;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -27,16 +27,17 @@
 #include "nsICookiePermission.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
+#include "mozilla/dom/DeviceStorageAreaListener.h"
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
 #include "mozilla/dom/MobileMessageManager.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
@@ -205,16 +206,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCachedResolveResults)
 #ifdef MOZ_EME
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
 #endif
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
 
 void
 Navigator::Invalidate()
 {
@@ -329,16 +331,20 @@ Navigator::Invalidate()
   mServiceWorkerContainer = nullptr;
 
 #ifdef MOZ_EME
   if (mMediaKeySystemAccessManager) {
     mMediaKeySystemAccessManager->Shutdown();
     mMediaKeySystemAccessManager = nullptr;
   }
 #endif
+
+  if (mDeviceStorageAreaListener) {
+    mDeviceStorageAreaListener = nullptr;
+  }
 }
 
 //*****************************************************************************
 //    Navigator::nsIDOMNavigator
 //*****************************************************************************
 
 NS_IMETHODIMP
 Navigator::GetUserAgent(nsAString& aUserAgent)
@@ -909,16 +915,30 @@ Navigator::RegisterProtocolHandler(const
   if (!registrar) {
     return;
   }
 
   aRv = registrar->RegisterProtocolHandler(aProtocol, aURI, aTitle,
                                            mWindow->GetOuterWindow());
 }
 
+DeviceStorageAreaListener*
+Navigator::GetDeviceStorageAreaListener(ErrorResult& aRv)
+{
+  if (!mDeviceStorageAreaListener) {
+    if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+    mDeviceStorageAreaListener = new DeviceStorageAreaListener(mWindow);
+  }
+
+  return mDeviceStorageAreaListener;
+}
+
 nsDOMDeviceStorage*
 Navigator::GetDeviceStorage(const nsAString& aType, ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
@@ -944,16 +964,38 @@ Navigator::GetDeviceStorages(const nsASt
     return;
   }
 
   nsDOMDeviceStorage::CreateDeviceStoragesFor(mWindow, aType, aStores);
 
   mDeviceStorageStores.AppendElements(aStores);
 }
 
+nsDOMDeviceStorage*
+Navigator::GetDeviceStorageByNameAndType(const nsAString& aName,
+                                         const nsAString& aType,
+                                         ErrorResult& aRv)
+{
+  if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsDOMDeviceStorage> storage;
+  nsDOMDeviceStorage::CreateDeviceStorageByNameAndType(mWindow, aName, aType,
+                                                       getter_AddRefs(storage));
+
+  if (!storage) {
+    return nullptr;
+  }
+
+  mDeviceStorageStores.AppendElement(storage);
+  return storage;
+}
+
 Geolocation*
 Navigator::GetGeolocation(ErrorResult& aRv)
 {
   if (mGeolocation) {
     return mGeolocation;
   }
 
   if (!mWindow || !mWindow->GetOuterWindow() || !mWindow->GetDocShell()) {
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -89,16 +89,17 @@ class MobileConnectionArray;
 
 class PowerManager;
 class CellBroadcast;
 class IccManager;
 class Telephony;
 class Voicemail;
 class TVManager;
 class InputPortManager;
+class DeviceStorageAreaListener;
 
 namespace time {
 class TimeManager;
 } // namespace time
 
 namespace system {
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 class AudioChannelManager;
@@ -209,21 +210,25 @@ public:
   bool TaintEnabled()
   {
     return false;
   }
   void AddIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
   void RemoveIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
   already_AddRefed<WakeLock> RequestWakeLock(const nsAString &aTopic,
                                              ErrorResult& aRv);
+  DeviceStorageAreaListener* GetDeviceStorageAreaListener(ErrorResult& aRv);
   nsDOMDeviceStorage* GetDeviceStorage(const nsAString& aType,
                                        ErrorResult& aRv);
   void GetDeviceStorages(const nsAString& aType,
                          nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores,
                          ErrorResult& aRv);
+  nsDOMDeviceStorage* GetDeviceStorageByNameAndType(const nsAString& aName,
+                                                    const nsAString& aType,
+                                                    ErrorResult& aRv);
   DesktopNotificationCenter* GetMozNotification(ErrorResult& aRv);
   CellBroadcast* GetMozCellBroadcast(ErrorResult& aRv);
   IccManager* GetMozIccManager(ErrorResult& aRv);
   MobileMessageManager* GetMozMobileMessage();
   Telephony* GetMozTelephony(ErrorResult& aRv);
   Voicemail* GetMozVoicemail(ErrorResult& aRv);
   TVManager* GetTv();
   InputPortManager* GetInputPortManager(ErrorResult& aRv);
@@ -374,16 +379,17 @@ private:
 #endif
   nsRefPtr<nsDOMCameraManager> mCameraManager;
   nsRefPtr<MediaDevices> mMediaDevices;
   nsCOMPtr<nsIDOMNavigatorSystemMessages> mMessagesManager;
   nsTArray<nsRefPtr<nsDOMDeviceStorage> > mDeviceStorageStores;
   nsRefPtr<time::TimeManager> mTimeManager;
   nsRefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsRefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
 
   // Hashtable for saving cached objects DoResolve created, so we don't create
   // the object twice if asked for it twice, whether due to use of "delete" or
   // due to Xrays.  We could probably use a nsJSThingHashtable here, but then
   // we'd need to figure out exactly how to trace that, and that seems to be
   // rocket science.  :(
   nsInterfaceHashtable<nsStringHashKey, nsISupports> mCachedResolveResults;
 };
--- a/dom/cache/Cache.cpp
+++ b/dom/cache/Cache.cpp
@@ -507,17 +507,17 @@ Cache::AssertOwningThread() const
 #endif
 
 CachePushStreamChild*
 Cache::CreatePushStream(nsIAsyncInputStream* aStream)
 {
   NS_ASSERT_OWNINGTHREAD(Cache);
   MOZ_ASSERT(mActor);
   MOZ_ASSERT(aStream);
-  return mActor->CreatePushStream(aStream);
+  return mActor->CreatePushStream(this, aStream);
 }
 
 Cache::~Cache()
 {
   NS_ASSERT_OWNINGTHREAD(Cache);
   if (mActor) {
     mActor->StartDestroyFromListener();
     // DestroyInternal() is called synchronously by StartDestroyFromListener().
--- a/dom/cache/CacheChild.cpp
+++ b/dom/cache/CacheChild.cpp
@@ -68,21 +68,21 @@ CacheChild::ExecuteOp(nsIGlobalObject* a
                       nsISupports* aParent, const CacheOpArgs& aArgs)
 {
   mNumChildActors += 1;
   MOZ_ALWAYS_TRUE(SendPCacheOpConstructor(
     new CacheOpChild(GetFeature(), aGlobal, aParent, aPromise), aArgs));
 }
 
 CachePushStreamChild*
-CacheChild::CreatePushStream(nsIAsyncInputStream* aStream)
+CacheChild::CreatePushStream(nsISupports* aParent, nsIAsyncInputStream* aStream)
 {
   mNumChildActors += 1;
   auto actor = SendPCachePushStreamConstructor(
-    new CachePushStreamChild(GetFeature(), aStream));
+    new CachePushStreamChild(GetFeature(), aParent, aStream));
   MOZ_ASSERT(actor);
   return static_cast<CachePushStreamChild*>(actor);
 }
 
 void
 CacheChild::StartDestroyFromListener()
 {
   NS_ASSERT_OWNINGTHREAD(CacheChild);
--- a/dom/cache/CacheChild.h
+++ b/dom/cache/CacheChild.h
@@ -38,17 +38,17 @@ public:
   // its destructor to trigger ActorDestroy() if it has not been called yet.
   void ClearListener();
 
   void
   ExecuteOp(nsIGlobalObject* aGlobal, Promise* aPromise,
             nsISupports* aParent, const CacheOpArgs& aArgs);
 
   CachePushStreamChild*
-  CreatePushStream(nsIAsyncInputStream* aStream);
+  CreatePushStream(nsISupports* aParent, nsIAsyncInputStream* aStream);
 
   // Our parent Listener object has gone out of scope and is being destroyed.
   void StartDestroyFromListener();
 
 private:
   // ActorChild methods
 
   // Feature is trying to destroy due to worker shutdown.
--- a/dom/cache/CachePushStreamChild.cpp
+++ b/dom/cache/CachePushStreamChild.cpp
@@ -88,20 +88,23 @@ private:
   NS_DECL_THREADSAFE_ISUPPORTS
 };
 
 NS_IMPL_ISUPPORTS(CachePushStreamChild::Callback, nsIInputStreamCallback,
                                                   nsIRunnable,
                                                   nsICancelableRunnable);
 
 CachePushStreamChild::CachePushStreamChild(Feature* aFeature,
+                                           nsISupports* aParent,
                                            nsIAsyncInputStream* aStream)
-  : mStream(aStream)
+  : mParent(aParent)
+  , mStream(aStream)
   , mClosed(false)
 {
+  MOZ_ASSERT(mParent);
   MOZ_ASSERT(mStream);
   MOZ_ASSERT_IF(!NS_IsMainThread(), aFeature);
   SetFeature(aFeature);
 }
 
 CachePushStreamChild::~CachePushStreamChild()
 {
   NS_ASSERT_OWNINGTHREAD(CachePushStreamChild);
--- a/dom/cache/CachePushStreamChild.h
+++ b/dom/cache/CachePushStreamChild.h
@@ -26,31 +26,33 @@ public:
   void Start();
 
   virtual void StartDestroy() override;
 
 private:
   class Callback;
 
   // This class must be constructed using CacheChild::CreatePushStream()
-  CachePushStreamChild(Feature* aFeature, nsIAsyncInputStream* aStream);
+  CachePushStreamChild(Feature* aFeature, nsISupports* aParent,
+                       nsIAsyncInputStream* aStream);
   ~CachePushStreamChild();
 
   // PCachePushStreamChild methods
   virtual void
   ActorDestroy(ActorDestroyReason aReason) override;
 
   void DoRead();
 
   void Wait();
 
   void OnStreamReady(Callback* aCallback);
 
   void OnEnd(nsresult aRv);
 
+  nsCOMPtr<nsISupports> mParent;
   nsCOMPtr<nsIAsyncInputStream> mStream;
   nsRefPtr<Callback> mCallback;
   bool mClosed;
 
   NS_DECL_OWNINGTHREAD
 };
 
 } // namespace cache
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -287,16 +287,22 @@ public:
                          const nsAString& aType,
                          nsDOMDeviceStorage** aStore);
 
   static void
   CreateDeviceStoragesFor(nsPIDOMWindow* aWin,
                           const nsAString& aType,
                           nsTArray<nsRefPtr<nsDOMDeviceStorage> >& aStores);
 
+  static void
+  CreateDeviceStorageByNameAndType(nsPIDOMWindow* aWin,
+                                   const nsAString& aName,
+                                   const nsAString& aType,
+                                   nsDOMDeviceStorage** aStore);
+
   void Shutdown();
 
   static void GetOrderedVolumeNames(nsTArray<nsString>& aVolumeNames);
 
   static void GetDefaultStorageName(const nsAString& aStorageType,
                                     nsAString &aStorageName);
 
   static bool ParseFullPath(const nsAString& aFullPath,
@@ -329,16 +335,21 @@ private:
   bool mIsShareable;
   bool mIsRemovable;
 
   already_AddRefed<nsDOMDeviceStorage> GetStorage(const nsAString& aFullPath,
                                                   nsAString& aOutStoragePath);
   already_AddRefed<nsDOMDeviceStorage>
     GetStorageByName(const nsAString &aStorageName);
 
+  static already_AddRefed<nsDOMDeviceStorage>
+    GetStorageByNameAndType(nsPIDOMWindow* aWin,
+                            const nsAString& aStorageName,
+                            const nsAString& aType);
+
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   bool mIsWatchingFile;
   bool mAllowedToWatchFile;
   bool mIsDefaultLocation;
   void DispatchDefaultChangeEvent();
 
   nsresult Notify(const char* aReason, class DeviceStorageFile* aFile);
new file mode 100755
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageAreaListener.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 "mozilla/dom/DeviceStorageAreaListener.h"
+#include "mozilla/dom/DeviceStorageAreaListenerBinding.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Services.h"
+#include "nsIObserverService.h"
+#ifdef MOZ_WIDGET_GONK
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#endif
+
+namespace mozilla {
+namespace dom {
+
+class VolumeStateObserver final : public nsIObserver
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  explicit VolumeStateObserver(DeviceStorageAreaListener* aListener)
+    : mDeviceStorageAreaListener(aListener) {}
+  void ForgetListener() { mDeviceStorageAreaListener = nullptr; }
+
+private:
+  ~VolumeStateObserver() {};
+
+  // This reference is non-owning and it's cleared by
+  // DeviceStorageAreaListener's destructor.
+  DeviceStorageAreaListener* MOZ_NON_OWNING_REF mDeviceStorageAreaListener;
+};
+
+NS_IMPL_ISUPPORTS(VolumeStateObserver, nsIObserver)
+
+NS_IMETHODIMP
+VolumeStateObserver::Observe(nsISupports *aSubject,
+                             const char *aTopic,
+                             const char16_t *aData)
+{
+  if (!mDeviceStorageAreaListener) {
+    return NS_OK;
+  }
+
+#ifdef MOZ_WIDGET_GONK
+  if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
+    nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+    MOZ_ASSERT(vol);
+
+    int32_t state;
+    nsresult rv = vol->GetState(&state);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsString volName;
+    vol->GetName(volName);
+
+    switch (state) {
+      case nsIVolume::STATE_MOUNTED:
+        mDeviceStorageAreaListener->DispatchStorageAreaChangedEvent(
+          volName,
+          DeviceStorageAreaChangedEventOperation::Added);
+        break;
+      default:
+        mDeviceStorageAreaListener->DispatchStorageAreaChangedEvent(
+          volName,
+          DeviceStorageAreaChangedEventOperation::Removed);
+        break;
+    }
+  }
+#endif
+  return NS_OK;
+}
+
+NS_IMPL_ADDREF_INHERITED(DeviceStorageAreaListener, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(DeviceStorageAreaListener, DOMEventTargetHelper)
+
+NS_INTERFACE_MAP_BEGIN(DeviceStorageAreaListener)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+DeviceStorageAreaListener::DeviceStorageAreaListener(nsPIDOMWindow* aWindow)
+  : DOMEventTargetHelper(aWindow)
+{
+  MOZ_ASSERT(aWindow);
+
+  mVolumeStateObserver = new VolumeStateObserver(this);
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->AddObserver(mVolumeStateObserver, NS_VOLUME_STATE_CHANGED, false);
+  }
+#endif
+}
+
+DeviceStorageAreaListener::~DeviceStorageAreaListener()
+{
+#ifdef MOZ_WIDGET_GONK
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(mVolumeStateObserver, NS_VOLUME_STATE_CHANGED);
+  }
+#endif
+  mVolumeStateObserver->ForgetListener();
+}
+
+JSObject*
+DeviceStorageAreaListener::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return DeviceStorageAreaListenerBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+DeviceStorageAreaListener::DispatchStorageAreaChangedEvent(
+  const nsString& aStorageName,
+  DeviceStorageAreaChangedEventOperation aOperation)
+{
+  StateMapType::const_iterator iter = mStorageAreaStateMap.find(aStorageName);
+  if (iter == mStorageAreaStateMap.end() &&
+      aOperation != DeviceStorageAreaChangedEventOperation::Added) {
+    // The operation of the first event to dispatch should be "Added".
+    return;
+  }
+  if (iter != mStorageAreaStateMap.end() &&
+      iter->second == aOperation) {
+    // No need to disptach the event if the state is unchanged.
+    return;
+  }
+
+  DeviceStorageAreaChangedEventInit init;
+  init.mOperation = aOperation;
+  init.mStorageName = aStorageName;
+
+  nsRefPtr<DeviceStorageAreaChangedEvent> event =
+    DeviceStorageAreaChangedEvent::Constructor(this,
+                                               NS_LITERAL_STRING("storageareachanged"),
+                                               init);
+  event->SetTrusted(true);
+
+  bool ignore;
+  DOMEventTargetHelper::DispatchEvent(event, &ignore);
+
+  mStorageAreaStateMap[aStorageName] = aOperation;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100755
--- /dev/null
+++ b/dom/devicestorage/DeviceStorageAreaListener.h
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 mozilla_dom_DeviceStorageAreaListener_h
+#define mozilla_dom_DeviceStorageAreaListener_h
+
+#include <map>
+#include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/dom/DeviceStorageAreaChangedEvent.h"
+
+namespace mozilla {
+namespace dom {
+
+class VolumeStateObserver;
+
+class DeviceStorageAreaListener final : public DOMEventTargetHelper
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+  IMPL_EVENT_HANDLER(storageareachanged)
+
+  explicit DeviceStorageAreaListener(nsPIDOMWindow* aWindow);
+
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+private:
+  friend class VolumeStateObserver;
+
+  typedef std::map<nsString, DeviceStorageAreaChangedEventOperation> StateMapType;
+  StateMapType mStorageAreaStateMap;
+
+  nsRefPtr<VolumeStateObserver> mVolumeStateObserver;
+
+  ~DeviceStorageAreaListener();
+
+  void DispatchStorageAreaChangedEvent(
+    const nsString& aStorageName,
+    DeviceStorageAreaChangedEventOperation aOperation);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_DeviceStorageAreaListener_h
--- a/dom/devicestorage/ipc/ipc.json
+++ b/dom/devicestorage/ipc/ipc.json
@@ -1,7 +1,8 @@
 {
 "runtests":{
 },
 "excludetests":{
-    "dom/devicestorage/test/test_dirs.html":"excluded"
+    "dom/devicestorage/test/test_dirs.html":"excluded",
+    "dom/devicestorage/test/test_storageAreaListener.html":"excluded"
    }
 }
--- a/dom/devicestorage/moz.build
+++ b/dom/devicestorage/moz.build
@@ -5,22 +5,27 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'DeviceStorage.h',
     'DeviceStorageFileDescriptor.h',
     'nsDeviceStorage.h',
 ]
 
+EXPORTS.mozilla.dom += [
+    'DeviceStorageAreaListener.h',
+]
+
 EXPORTS.mozilla.dom.devicestorage += [
     'DeviceStorageRequestChild.h',
     'DeviceStorageRequestParent.h',
 ]
 
 UNIFIED_SOURCES += [
+    'DeviceStorageAreaListener.cpp',
     'DeviceStorageRequestChild.cpp',
     'DeviceStorageRequestParent.cpp',
     'nsDeviceStorage.cpp',
 ]
 
 IPDL_SOURCES += [
     'PDeviceStorageRequest.ipdl',
 ]
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -3575,16 +3575,40 @@ nsDOMDeviceStorage::CreateDeviceStorages
     if (NS_FAILED(rv)) {
       break;
     }
     aStores.AppendElement(storage);
   }
 }
 
 // static
+void
+nsDOMDeviceStorage::CreateDeviceStorageByNameAndType(
+  nsPIDOMWindow* aWin,
+  const nsAString& aName,
+  const nsAString& aType,
+  nsDOMDeviceStorage** aStore)
+{
+  if (!DeviceStorageTypeChecker::IsVolumeBased(aType)) {
+    nsRefPtr<nsDOMDeviceStorage> storage = new nsDOMDeviceStorage(aWin);
+    if (NS_FAILED(storage->Init(aWin, aType, EmptyString()))) {
+      *aStore = nullptr;
+      return;
+    }
+    NS_ADDREF(*aStore = storage.get());
+    return;
+  }
+
+  nsRefPtr<nsDOMDeviceStorage> storage = GetStorageByNameAndType(aWin,
+                                                                 aName,
+                                                                 aType);
+  NS_ADDREF(*aStore = storage.get());
+}
+
+// static
 bool
 nsDOMDeviceStorage::ParseFullPath(const nsAString& aFullPath,
                                   nsAString& aOutStorageName,
                                   nsAString& aOutStoragePath)
 {
   aOutStorageName.Truncate();
   aOutStoragePath.Truncate();
 
@@ -3633,24 +3657,38 @@ nsDOMDeviceStorage::GetStorageByName(con
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<nsDOMDeviceStorage> ds;
 
   if (mStorageName.Equals(aStorageName)) {
     ds = this;
     return ds.forget();
   }
+
+  return GetStorageByNameAndType(GetOwner(), aStorageName, mStorageType);
+}
+
+// static
+already_AddRefed<nsDOMDeviceStorage>
+nsDOMDeviceStorage::GetStorageByNameAndType(nsPIDOMWindow* aWin,
+                                            const nsAString& aStorageName,
+                                            const nsAString& aType)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsRefPtr<nsDOMDeviceStorage> ds;
+
   VolumeNameArray volNames;
   GetOrderedVolumeNames(volNames);
   VolumeNameArray::size_type numVolumes = volNames.Length();
   VolumeNameArray::index_type i;
   for (i = 0; i < numVolumes; i++) {
     if (volNames[i].Equals(aStorageName)) {
-      ds = new nsDOMDeviceStorage(GetOwner());
-      nsresult rv = ds->Init(GetOwner(), mStorageType, aStorageName);
+      ds = new nsDOMDeviceStorage(aWin);
+      nsresult rv = ds->Init(aWin, aType, aStorageName);
       if (NS_FAILED(rv)) {
         return nullptr;
       }
       return ds.forget();
     }
   }
   return nullptr;
 }
--- a/dom/devicestorage/test/mochitest.ini
+++ b/dom/devicestorage/test/mochitest.ini
@@ -19,16 +19,18 @@ support-files = devicestorage_common.js
 [test_freeSpace.html]
 [test_lastModificationFilter.html]
 [test_overrideDir.html]
 [test_overwrite.html]
 [test_sanity.html]
 [test_usedSpace.html]
 [test_watch.html]
 [test_watchOther.html]
+[test_storageAreaListener.html]
+skip-if = toolkit != 'gonk'
 
 # FileSystem API tests
 [test_fs_basic.html]
 [test_fs_createDirectory.html]
 [test_fs_get.html]
 [test_fs_remove.html]
 [test_fs_createFile.html]
 [test_fs_appendFile.html]
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_storageAreaListener.html
@@ -0,0 +1,61 @@
+<!--
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1126694
+-->
+<head>
+<title>Test for device storage area listener API </title>
+<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+<script type="text/javascript" src="devicestorage_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=1126694">Mozilla Bug 1126684</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+	devicestorage_setup()
+
+	var XPCOMUtils = SpecialPowers.Cu.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+	var Ci = SpecialPowers.Ci;
+
+	var volumeService = SpecialPowers.Cc["@mozilla.org/telephony/volume-service;1"].getService(Ci.nsIVolumeService);
+
+	var volName = "dummy-volume";
+	var mountPoint = "/data/local/tmp/dummy";
+
+	var storage;
+	if (navigator.deviceStorageAreaListener) {
+		ok (true, "got deviceStorageAreaListener")
+	}
+
+	navigator.deviceStorageAreaListener.addEventListener("storageareachanged", function (e) {
+		info("got storageareachanged event name:" + e.storageName + "\n");
+		info("operation:" + e.operation + "\n");
+
+		if (e.operation == "added") {
+			storage = navigator.getDeviceStorageByNameAndType(e.storageName, "sdcard");
+			ok (storage, "got storage");
+			volumeService.removeFakeVolume(volName);
+		}
+		else if (e.operation == "removed") {
+			ok (true, "got removal event");
+			devicestorage_cleanup();
+		}
+	});
+
+	volumeService.createFakeVolume(volName, mountPoint);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2357,16 +2357,31 @@ ContentChild::RecvFileSystemUpdate(const
     unused << aIsUnmounting;
     unused << aIsRemovable;
     unused << aIsHotSwappable;
 #endif
     return true;
 }
 
 bool
+ContentChild::RecvVolumeRemoved(const nsString& aFsName)
+{
+#ifdef MOZ_WIDGET_GONK
+    nsRefPtr<nsVolumeService> vs = nsVolumeService::GetSingleton();
+    if (vs) {
+        vs->RemoveVolumeByName(aFsName);
+    }
+#else
+    // Remove warnings about unused arguments
+    unused << aFsName;
+#endif
+    return true;
+}
+
+bool
 ContentChild::RecvNotifyProcessPriorityChanged(
     const hal::ProcessPriority& aPriority)
 {
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     NS_ENSURE_TRUE(os, true);
 
     nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
     props->SetPropertyAsInt32(NS_LITERAL_STRING("priority"),
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -355,16 +355,17 @@ public:
                                       const int32_t& aMountGeneration,
                                       const bool& aIsMediaPresent,
                                       const bool& aIsSharing,
                                       const bool& aIsFormatting,
                                       const bool& aIsFake,
                                       const bool& aIsUnmounting,
                                       const bool& aIsRemovable,
                                       const bool& aIsHotSwappable) override;
+    virtual bool RecvVolumeRemoved(const nsString& aFsName) override;
 
     virtual bool RecvNuwaFork() override;
 
     virtual bool
     RecvNotifyProcessPriorityChanged(const hal::ProcessPriority& aPriority) override;
     virtual bool RecvMinimizeMemoryUsage() override;
 
     virtual bool RecvLoadAndRegisterSheet(const URIParams& aURI,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -649,16 +649,17 @@ static const char* sObserverTopics[] = {
     "memory-pressure",
     "child-gc-request",
     "child-cc-request",
     "child-mmu-request",
     "last-pb-context-exited",
     "file-watcher-update",
 #ifdef MOZ_WIDGET_GONK
     NS_VOLUME_STATE_CHANGED,
+    NS_VOLUME_REMOVED,
     "phone-state-changed",
 #endif
 #ifdef ACCESSIBILITY
     "a11y-init-or-shutdown",
 #endif
     "app-theme-changed",
 #ifdef MOZ_ENABLE_PROFILER_SPS
     "profiler-started",
@@ -3161,16 +3162,25 @@ ContentParent::Observe(nsISupports* aSub
                                            mountGeneration, isMediaPresent,
                                            isSharing, isFormatting, isFake,
                                            isUnmounting, isRemovable, isHotSwappable);
         }
     } else if (!strcmp(aTopic, "phone-state-changed")) {
         nsString state(aData);
         unused << SendNotifyPhoneStateChange(state);
     }
+    else if(!strcmp(aTopic, NS_VOLUME_REMOVED)) {
+#ifdef MOZ_NUWA_PROCESS
+        if (!(IsNuwaReady() && IsNuwaProcess()))
+#endif
+        {
+            nsString volName(aData);
+            unused << SendVolumeRemoved(volName);
+        }
+    }
 #endif
 #ifdef ACCESSIBILITY
     // Make sure accessibility is running in content process when accessibility
     // gets initiated in chrome process.
     else if (aData && (*aData == '1') &&
              !strcmp(aTopic, "a11y-init-or-shutdown")) {
         unused << SendActivateA11y();
     }
@@ -4557,16 +4567,32 @@ ContentParent::RecvSetFakeVolumeState(co
     return true;
 #else
     NS_WARNING("ContentParent::RecvSetFakeVolumeState shouldn't be called when MOZ_WIDGET_GONK is not defined");
     return false;
 #endif
 }
 
 bool
+ContentParent::RecvRemoveFakeVolume(const nsString& fsName)
+{
+#ifdef MOZ_WIDGET_GONK
+    nsresult rv;
+    nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID, &rv);
+    if (vs) {
+        vs->RemoveFakeVolume(fsName);
+    }
+    return true;
+#else
+    NS_WARNING("ContentParent::RecvRemoveFakeVolume shouldn't be called when MOZ_WIDGET_GONK is not defined");
+    return false;
+#endif
+}
+
+bool
 ContentParent::RecvKeywordToURI(const nsCString& aKeyword,
                                 nsString* aProviderName,
                                 OptionalInputStreamParams* aPostData,
                                 OptionalURIParams* aURI)
 {
     nsCOMPtr<nsIURIFixup> fixup = do_GetService(NS_URIFIXUP_CONTRACTID);
     if (!fixup) {
         return true;
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -782,16 +782,18 @@ private:
 
     virtual bool RecvAddNewProcess(const uint32_t& aPid,
                                    InfallibleTArray<ProtocolFdMapping>&& aFds) override;
 
     virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) override;
 
     virtual bool RecvSetFakeVolumeState(const nsString& fsName, const int32_t& fsState) override;
 
+    virtual bool RecvRemoveFakeVolume(const nsString& fsName) override;
+
     virtual bool RecvKeywordToURI(const nsCString& aKeyword,
                                   nsString* aProviderName,
                                   OptionalInputStreamParams* aPostData,
                                   OptionalURIParams* aURI) override;
 
     virtual bool RecvNotifyKeywordSearchLoading(const nsString &aProvider,
                                                 const nsString &aKeyword) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -569,16 +569,19 @@ child:
 
     // Note: Any changes to this structure should also be changed in
     // VolumeInfo above.
     FileSystemUpdate(nsString fsName, nsString mountPoint, int32_t fsState,
                      int32_t mountGeneration, bool isMediaPresent,
                      bool isSharing, bool isFormatting, bool isFake,
                      bool isUnmounting, bool isRemovable, bool isHotSwappable);
 
+    // Notify volume is removed.
+    VolumeRemoved(nsString fsName);
+
     // Ask the Nuwa process to create a new child process.
     NuwaFork();
 
     NotifyProcessPriorityChanged(ProcessPriority priority);
     MinimizeMemoryUsage();
 
     /**
      * Used to manage nsIStyleSheetService across processes.
@@ -893,16 +896,17 @@ parent:
     // parent's signal to make it freeze.
     NuwaWaitForFreeze();
 
     sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds);
 
     // called by the child (test code only) to propagate volume changes to the parent
     async CreateFakeVolume(nsString fsName, nsString mountPoint);
     async SetFakeVolumeState(nsString fsName, int32_t fsState);
+    async RemoveFakeVolume(nsString fsName);
 
     sync KeywordToURI(nsCString keyword)
         returns (nsString providerName, OptionalInputStreamParams postData, OptionalURIParams uri);
 
     sync NotifyKeywordSearchLoading(nsString providerName, nsString keyword);
 
     async CopyFavicon(URIParams oldURI, URIParams newURI, bool isPrivate);
 
--- a/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp
+++ b/dom/media/fmp4/gonk/GonkAudioDecoderManager.cpp
@@ -33,25 +33,23 @@ PRLogModuleInfo* GetDemuxerLog();
 #endif
 #define READ_OUTPUT_BUFFER_TIMEOUT_US  3000
 
 using namespace android;
 typedef android::MediaCodecProxy MediaCodecProxy;
 
 namespace mozilla {
 
-GonkAudioDecoderManager::GonkAudioDecoderManager(
-  MediaTaskQueue* aTaskQueue,
-  const AudioInfo& aConfig)
-  : GonkDecoderManager(aTaskQueue)
-  , mAudioChannels(aConfig.mChannels)
+GonkAudioDecoderManager::GonkAudioDecoderManager(const AudioInfo& aConfig)
+  : mAudioChannels(aConfig.mChannels)
   , mAudioRate(aConfig.mRate)
   , mAudioProfile(aConfig.mProfile)
   , mUseAdts(true)
   , mAudioBuffer(nullptr)
+  , mMonitor("GonkAudioDecoderManager")
 {
   MOZ_COUNT_CTOR(GonkAudioDecoderManager);
   MOZ_ASSERT(mAudioChannels);
   mCodecSpecificData = aConfig.mCodecSpecificConfig;
   mMimeType = aConfig.mMimeType;
 
   // Pass through mp3 without applying an ADTS header.
   if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
@@ -106,23 +104,63 @@ GonkAudioDecoderManager::Init(MediaDataD
   if (rv == OK) {
     return mDecoder;
   } else {
     GADM_LOG("Failed to input codec specific data!");
     return nullptr;
   }
 }
 
-status_t
-GonkAudioDecoderManager::SendSampleToOMX(MediaRawData* aSample)
+bool
+GonkAudioDecoderManager::HasQueuedSample()
+{
+    MonitorAutoLock mon(mMonitor);
+    return mQueueSample.Length();
+}
+
+nsresult
+GonkAudioDecoderManager::Input(MediaRawData* aSample)
 {
-  return mDecoder->Input(reinterpret_cast<const uint8_t*>(aSample->mData),
-                         aSample->mSize,
-                         aSample->mTime,
-                         0);
+  MonitorAutoLock mon(mMonitor);
+  nsRefPtr<MediaRawData> sample;
+
+  if (aSample) {
+    sample = aSample;
+    if (!PerformFormatSpecificProcess(sample)) {
+      return NS_ERROR_FAILURE;
+    }
+  } else {
+    // It means EOS with empty sample.
+    sample = new MediaRawData();
+  }
+
+  mQueueSample.AppendElement(sample);
+
+  status_t rv;
+  while (mQueueSample.Length()) {
+    nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0);
+    {
+      MonitorAutoUnlock mon_exit(mMonitor);
+      rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->mData),
+                           data->mSize,
+                           data->mTime,
+                           0);
+    }
+    if (rv == OK) {
+      mQueueSample.RemoveElementAt(0);
+    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
+      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
+      // buffer on time.
+      return NS_OK;
+    } else {
+      return NS_ERROR_UNEXPECTED;
+    }
+  }
+
+  return NS_OK;
 }
 
 bool
 GonkAudioDecoderManager::PerformFormatSpecificProcess(MediaRawData* aSample)
 {
   if (aSample && mUseAdts) {
     int8_t frequency_index =
         mp4_demuxer::Adts::GetFrequencyIndex(mAudioRate);
@@ -178,16 +216,31 @@ GonkAudioDecoderManager::CreateAudioData
                                                 mAudioChannels,
                                                 mAudioRate);
   ReleaseAudioBuffer();
   audioData.forget(v);
   return NS_OK;
 }
 
 nsresult
+GonkAudioDecoderManager::Flush()
+{
+  {
+    MonitorAutoLock mon(mMonitor);
+    mQueueSample.Clear();
+  }
+
+  if (mDecoder->flush() != OK) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
+nsresult
 GonkAudioDecoderManager::Output(int64_t aStreamOffset,
                                 nsRefPtr<MediaData>& aOutData)
 {
   aOutData = nullptr;
   status_t err;
   err = mDecoder->Output(&mAudioBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US);
 
   switch (err) {
@@ -253,20 +306,9 @@ GonkAudioDecoderManager::Output(int64_t 
 }
 
 void GonkAudioDecoderManager::ReleaseAudioBuffer() {
   if (mAudioBuffer) {
     mDecoder->ReleaseMediaBuffer(mAudioBuffer);
     mAudioBuffer = nullptr;
   }
 }
-
-nsresult
-GonkAudioDecoderManager::Flush()
-{
-  GonkDecoderManager::Flush();
-  status_t err = mDecoder->flush();
-  if (err != OK) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
 } // namespace mozilla
--- a/dom/media/fmp4/gonk/GonkAudioDecoderManager.h
+++ b/dom/media/fmp4/gonk/GonkAudioDecoderManager.h
@@ -18,46 +18,55 @@ struct MOZ_EXPORT ALooper;
 class MOZ_EXPORT MediaBuffer;
 } // namespace android
 
 namespace mozilla {
 
 class GonkAudioDecoderManager : public GonkDecoderManager {
 typedef android::MediaCodecProxy MediaCodecProxy;
 public:
-  GonkAudioDecoderManager(MediaTaskQueue* aTaskQueue,
-                          const AudioInfo& aConfig);
-  ~GonkAudioDecoderManager();
+  GonkAudioDecoderManager(const AudioInfo& aConfig);
+
+  virtual ~GonkAudioDecoderManager() override;
 
   virtual android::sp<MediaCodecProxy> Init(MediaDataDecoderCallback* aCallback) override;
 
+  virtual nsresult Input(MediaRawData* aSample) override;
+
   virtual nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) override;
 
   virtual nsresult Flush() override;
 
-protected:
-  virtual bool PerformFormatSpecificProcess(MediaRawData* aSample) override;
-
-  virtual status_t SendSampleToOMX(MediaRawData* aSample) override;
+  virtual bool HasQueuedSample() override;
 
 private:
+  bool PerformFormatSpecificProcess(MediaRawData* aSample);
 
   nsresult CreateAudioData(int64_t aStreamOffset,
                               AudioData** aOutData);
 
   void ReleaseAudioBuffer();
-  // MediaCodedc's wrapper that performs the decoding.
-  android::sp<MediaCodecProxy> mDecoder;
 
   const uint32_t mAudioChannels;
   const uint32_t mAudioRate;
   const uint32_t mAudioProfile;
   bool mUseAdts;
 
   MediaDataDecoderCallback*  mReaderCallback;
   android::MediaBuffer* mAudioBuffer;
   android::sp<ALooper> mLooper;
+
+  // MediaCodedc's wrapper that performs the decoding.
+  android::sp<android::MediaCodecProxy> mDecoder;
+
+  // This monitor protects mQueueSample.
+  Monitor mMonitor;
+
+  // An queue with the MP4 samples which are waiting to be sent into OMX.
+  // If an element is an empty MP4Sample, that menas EOS. There should not
+  // any sample be queued after EOS.
+  nsTArray<nsRefPtr<MediaRawData>> mQueueSample;
 };
 
 } // namespace mozilla
 
 #endif // GonkAudioDecoderManager_h_
--- a/dom/media/fmp4/gonk/GonkDecoderModule.cpp
+++ b/dom/media/fmp4/gonk/GonkDecoderModule.cpp
@@ -29,29 +29,28 @@ GonkDecoderModule::Init()
 already_AddRefed<MediaDataDecoder>
 GonkDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
                                      mozilla::layers::LayersBackend aLayersBackend,
                                      mozilla::layers::ImageContainer* aImageContainer,
                                      FlushableMediaTaskQueue* aVideoTaskQueue,
                                      MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder =
-  new GonkMediaDataDecoder(new GonkVideoDecoderManager(aVideoTaskQueue,
-                                                       aImageContainer, aConfig),
+  new GonkMediaDataDecoder(new GonkVideoDecoderManager(aImageContainer, aConfig),
                            aVideoTaskQueue, aCallback);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 GonkDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
                                       FlushableMediaTaskQueue* aAudioTaskQueue,
                                       MediaDataDecoderCallback* aCallback)
 {
   nsRefPtr<MediaDataDecoder> decoder =
-  new GonkMediaDataDecoder(new GonkAudioDecoderManager(aAudioTaskQueue, aConfig),
+  new GonkMediaDataDecoder(new GonkAudioDecoderManager(aConfig),
                            aAudioTaskQueue, aCallback);
   return decoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 GonkDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo()) {
--- a/dom/media/fmp4/gonk/GonkMediaDataDecoder.cpp
+++ b/dom/media/fmp4/gonk/GonkMediaDataDecoder.cpp
@@ -20,68 +20,16 @@ PRLogModuleInfo* GetDemuxerLog();
 #else
 #define LOG(...)
 #endif
 
 using namespace android;
 
 namespace mozilla {
 
-GonkDecoderManager::GonkDecoderManager(MediaTaskQueue* aTaskQueue)
-  : mMonitor("GonkDecoderManager")
-{
-}
-
-nsresult
-GonkDecoderManager::Input(MediaRawData* aSample)
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  nsRefPtr<MediaRawData> sample;
-
-  if (!aSample) {
-    // It means EOS with empty sample.
-    sample = new MediaRawData();
-  } else {
-    sample = aSample;
-    if (!PerformFormatSpecificProcess(sample)) {
-      return NS_ERROR_FAILURE;
-    }
-  }
-
-  mQueueSample.AppendElement(sample);
-
-  status_t rv;
-  while (mQueueSample.Length()) {
-    nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0);
-    {
-      ReentrantMonitorAutoExit mon_exit(mMonitor);
-      rv = SendSampleToOMX(data);
-    }
-    if (rv == OK) {
-      mQueueSample.RemoveElementAt(0);
-    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
-      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
-      // buffer on time.
-      return NS_OK;
-    } else {
-      return NS_ERROR_UNEXPECTED;
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult
-GonkDecoderManager::Flush()
-{
-  ReentrantMonitorAutoEnter mon(mMonitor);
-  mQueueSample.Clear();
-  return NS_OK;
-}
-
 GonkMediaDataDecoder::GonkMediaDataDecoder(GonkDecoderManager* aManager,
                                            FlushableMediaTaskQueue* aTaskQueue,
                                            MediaDataDecoderCallback* aCallback)
   : mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mManager(aManager)
   , mSignaledEOS(false)
   , mDrainComplete(false)
--- a/dom/media/fmp4/gonk/GonkMediaDataDecoder.h
+++ b/dom/media/fmp4/gonk/GonkMediaDataDecoder.h
@@ -14,63 +14,41 @@ class MediaCodecProxy;
 } // namespace android
 
 namespace mozilla {
 class MediaRawData;
 
 // Manage the data flow from inputting encoded data and outputting decode data.
 class GonkDecoderManager {
 public:
-  GonkDecoderManager(MediaTaskQueue* aTaskQueue);
-
   virtual ~GonkDecoderManager() {}
 
   // Creates and initializs the GonkDecoder.
   // Returns nullptr on failure.
   virtual android::sp<android::MediaCodecProxy> Init(MediaDataDecoderCallback* aCallback) = 0;
 
   // Add samples into OMX decoder or queue them if decoder is out of input buffer.
-  virtual nsresult Input(MediaRawData* aSample);
+  virtual nsresult Input(MediaRawData* aSample) = 0;
 
   // Produces decoded output, it blocks until output can be produced or a timeout
   // is expired or until EOS. Returns NS_OK on success, or NS_ERROR_NOT_AVAILABLE
   // if there's not enough data to produce more output. If this returns a failure
   // code other than NS_ERROR_NOT_AVAILABLE, an error will be reported to the
   // MP4Reader.
   // The overrided class should follow the same behaviour.
   virtual nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) = 0;
 
   // Flush the queued sample.
-  // It this function is overrided by subclass, this functino should be called
-  // in the overrided function.
-  virtual nsresult Flush();
+  virtual nsresult Flush() = 0;
 
-  // It should be called in MediaTask thread.
-  bool HasQueuedSample() {
-    ReentrantMonitorAutoEnter mon(mMonitor);
-    return mQueueSample.Length();
-  }
+  // True if sample is queued.
+  virtual bool HasQueuedSample() = 0;
 
 protected:
-  // It performs special operation to MP4 sample, the real action is depended on
-  // the codec type.
-  virtual bool PerformFormatSpecificProcess(MediaRawData* aSample) { return true; }
-
-  // It sends MP4Sample to OMX layer. It must be overrided by subclass.
-  virtual android::status_t SendSampleToOMX(MediaRawData* aSample) = 0;
-
-  // This monitor protects mQueueSample.
-  ReentrantMonitor mMonitor;
-
-  // An queue with the MP4 samples which are waiting to be sent into OMX.
-  // If an element is an empty MP4Sample, that menas EOS. There should not
-  // any sample be queued after EOS.
-  nsTArray<nsRefPtr<MediaRawData>> mQueueSample;
-
   nsRefPtr<MediaByteBuffer> mCodecSpecificData;
 
   nsAutoCString mMimeType;
 };
 
 // Samples are decoded using the GonkDecoder (MediaCodec)
 // created by the GonkDecoderManager. This class implements
 // the higher-level logic that drives mapping the Gonk to the async
--- a/dom/media/fmp4/gonk/GonkVideoDecoderManager.cpp
+++ b/dom/media/fmp4/gonk/GonkVideoDecoderManager.cpp
@@ -39,26 +39,25 @@ PRLogModuleInfo* GetDemuxerLog();
 #endif
 using namespace mozilla::layers;
 using namespace android;
 typedef android::MediaCodecProxy MediaCodecProxy;
 
 namespace mozilla {
 
 GonkVideoDecoderManager::GonkVideoDecoderManager(
-  MediaTaskQueue* aTaskQueue,
   mozilla::layers::ImageContainer* aImageContainer,
   const VideoInfo& aConfig)
-  : GonkDecoderManager(aTaskQueue)
-  , mImageContainer(aImageContainer)
+  : mImageContainer(aImageContainer)
   , mReaderCallback(nullptr)
   , mLastDecodedTime(0)
   , mColorConverterBufferSize(0)
   , mNativeWindow(nullptr)
   , mPendingVideoBuffersLock("GonkVideoDecoderManager::mPendingVideoBuffersLock")
+  , mMonitor("GonkVideoDecoderManager")
 {
   MOZ_COUNT_CTOR(GonkVideoDecoderManager);
   mMimeType = aConfig.mMimeType;
   mVideoWidth  = aConfig.mDisplay.width;
   mVideoHeight = aConfig.mDisplay.height;
   mDisplayWidth = aConfig.mDisplay.width;
   mDisplayHeight = aConfig.mDisplay.height;
   mInfo.mVideo = aConfig;
@@ -113,16 +112,62 @@ GonkVideoDecoderManager::Init(MediaDataD
       MediaCodecProxy::kCanExposeGraphicBuffer)) {
     mNativeWindow = new GonkNativeWindow();
   }
 
   return mDecoder;
 }
 
 nsresult
+GonkVideoDecoderManager::Input(MediaRawData* aSample)
+{
+  MonitorAutoLock mon(mMonitor);
+  nsRefPtr<MediaRawData> sample;
+
+  if (!aSample) {
+    // It means EOS with empty sample.
+    sample = new MediaRawData();
+  } else {
+    sample = aSample;
+  }
+
+  mQueueSample.AppendElement(sample);
+
+  status_t rv;
+  while (mQueueSample.Length()) {
+    nsRefPtr<MediaRawData> data = mQueueSample.ElementAt(0);
+    {
+      MonitorAutoUnlock mon_unlock(mMonitor);
+      rv = mDecoder->Input(reinterpret_cast<const uint8_t*>(data->mData),
+                           data->mSize,
+                           data->mTime,
+                           0);
+    }
+    if (rv == OK) {
+      mQueueSample.RemoveElementAt(0);
+    } else if (rv == -EAGAIN || rv == -ETIMEDOUT) {
+      // In most cases, EAGAIN or ETIMEOUT are safe because OMX can't fill
+      // buffer on time.
+      return NS_OK;
+    } else {
+      return NS_ERROR_UNEXPECTED;
+    }
+  }
+
+  return NS_OK;
+}
+
+bool
+GonkVideoDecoderManager::HasQueuedSample()
+{
+    MonitorAutoLock mon(mMonitor);
+    return mQueueSample.Length();
+}
+
+nsresult
 GonkVideoDecoderManager::CreateVideoData(int64_t aStreamOffset, VideoData **v)
 {
   *v = nullptr;
   nsRefPtr<VideoData> data;
   int64_t timeUs;
   int32_t keyFrame;
 
   if (mVideoBuffer == nullptr) {
@@ -300,16 +345,33 @@ GonkVideoDecoderManager::SetVideoFormat(
       return false;
     }
     return true;
   }
   GVDM_LOG("Fail to get output format");
   return false;
 }
 
+nsresult
+GonkVideoDecoderManager::Flush()
+{
+  {
+    MonitorAutoLock mon(mMonitor);
+    mQueueSample.Clear();
+  }
+
+ mLastDecodedTime = 0;
+
+  if (mDecoder->flush() != OK) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return NS_OK;
+}
+
 // Blocks until decoded sample is produced by the deoder.
 nsresult
 GonkVideoDecoderManager::Output(int64_t aStreamOffset,
                                 nsRefPtr<MediaData>& aOutData)
 {
   aOutData = nullptr;
   status_t err;
   if (mDecoder == nullptr) {
@@ -388,37 +450,16 @@ GonkVideoDecoderManager::Output(int64_t 
 
 void GonkVideoDecoderManager::ReleaseVideoBuffer() {
   if (mVideoBuffer) {
     mDecoder->ReleaseMediaBuffer(mVideoBuffer);
     mVideoBuffer = nullptr;
   }
 }
 
-status_t
-GonkVideoDecoderManager::SendSampleToOMX(MediaRawData* aSample)
-{
-  return mDecoder->Input(reinterpret_cast<const uint8_t*>(aSample->mData),
-                         aSample->mSize,
-                         aSample->mTime,
-                         0);
-}
-
-nsresult
-GonkVideoDecoderManager::Flush()
-{
-  GonkDecoderManager::Flush();
-  mLastDecodedTime = 0;
-  status_t err = mDecoder->flush();
-  if (err != OK) {
-    return NS_ERROR_FAILURE;
-  }
-  return NS_OK;
-}
-
 void
 GonkVideoDecoderManager::codecReserved()
 {
   GVDM_LOG("codecReserved");
   sp<AMessage> format = new AMessage;
   sp<Surface> surface;
   status_t rv = OK;
   // Fixed values
@@ -569,17 +610,9 @@ void GonkVideoDecoderManager::ReleaseAll
     android::MediaBuffer *buffer;
     buffer = releasingVideoBuffers[i];
     mDecoder->ReleaseMediaBuffer(buffer);
     buffer = nullptr;
   }
   releasingVideoBuffers.clear();
 }
 
-void GonkVideoDecoderManager::ReleaseMediaResources() {
-  GVDM_LOG("ReleseMediaResources");
-  if (mDecoder == nullptr) {
-    return;
-  }
-  ReleaseAllPendingVideoBuffers();
-  mDecoder->ReleaseMediaResources();
-}
 } // namespace mozilla
--- a/dom/media/fmp4/gonk/GonkVideoDecoderManager.h
+++ b/dom/media/fmp4/gonk/GonkVideoDecoderManager.h
@@ -33,36 +33,34 @@ namespace layers {
 class TextureClient;
 } // namespace mozilla::layers
 
 class GonkVideoDecoderManager : public GonkDecoderManager {
 typedef android::MediaCodecProxy MediaCodecProxy;
 typedef mozilla::layers::TextureClient TextureClient;
 
 public:
-  GonkVideoDecoderManager(MediaTaskQueue* aTaskQueue,
-                          mozilla::layers::ImageContainer* aImageContainer,
+  GonkVideoDecoderManager(mozilla::layers::ImageContainer* aImageContainer,
                           const VideoInfo& aConfig);
 
-  ~GonkVideoDecoderManager();
+  virtual ~GonkVideoDecoderManager() override;
 
   virtual android::sp<MediaCodecProxy> Init(MediaDataDecoderCallback* aCallback) override;
 
+  virtual nsresult Input(MediaRawData* aSample) override;
+
   virtual nsresult Output(int64_t aStreamOffset,
                           nsRefPtr<MediaData>& aOutput) override;
 
   virtual nsresult Flush() override;
 
-  virtual void ReleaseMediaResources();
+  virtual bool HasQueuedSample() override;
 
   static void RecycleCallback(TextureClient* aClient, void* aClosure);
 
-protected:
-  virtual android::status_t SendSampleToOMX(MediaRawData* aSample) override;
-
 private:
   struct FrameInfo
   {
     int32_t mWidth = 0;
     int32_t mHeight = 0;
     int32_t mStride = 0;
     int32_t mSliceHeight = 0;
     int32_t mColorFormat = 0;
@@ -124,17 +122,16 @@ private:
 
   uint32_t mVideoWidth;
   uint32_t mVideoHeight;
   uint32_t mDisplayWidth;
   uint32_t mDisplayHeight;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
 
-  android::sp<MediaCodecProxy> mDecoder;
   nsRefPtr<layers::ImageContainer> mImageContainer;
 
   android::MediaBuffer* mVideoBuffer;
 
   MediaDataDecoderCallback*  mReaderCallback;
   MediaInfo mInfo;
   android::sp<VideoResourceListener> mVideoListener;
   android::sp<MessageHandler> mHandler;
@@ -155,13 +152,23 @@ private:
   };
 
   // Hold video's MediaBuffers that are released.
   // The holded MediaBuffers are released soon after flush.
   Vector<android::MediaBuffer*> mPendingVideoBuffers;
   // The lock protects mPendingVideoBuffers.
   Mutex mPendingVideoBuffersLock;
 
+  // MediaCodedc's wrapper that performs the decoding.
+  android::sp<android::MediaCodecProxy> mDecoder;
+
+  // This monitor protects mQueueSample.
+  Monitor mMonitor;
+
+  // An queue with the MP4 samples which are waiting to be sent into OMX.
+  // If an element is an empty MP4Sample, that menas EOS. There should not
+  // any sample be queued after EOS.
+  nsTArray<nsRefPtr<MediaRawData>> mQueueSample;
 };
 
 } // namespace mozilla
 
 #endif // GonkVideoDecoderManager_h_
--- a/dom/system/gonk/nsIVolume.idl
+++ b/dom/system/gonk/nsIVolume.idl
@@ -96,16 +96,17 @@ interface nsIVolume : nsISupports
   // Whether this is a hot-swappable volume
   readonly attribute boolean isHotSwappable;
 
 };
 
 %{C++
 // For use with the ObserverService
 #define NS_VOLUME_STATE_CHANGED  "volume-state-changed"
+#define NS_VOLUME_REMOVED        "volume-removed"
 
 namespace mozilla {
 namespace system {
 
 // Convert a state into a loggable/printable string.
 const char* NS_VolumeStateStr(int32_t aState);
 
 } // system
--- a/dom/system/gonk/nsIVolumeService.idl
+++ b/dom/system/gonk/nsIVolumeService.idl
@@ -3,31 +3,34 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 #include "nsIVolume.idl"
 #include "nsIVolumeMountLock.idl"
 
 interface nsIArray;
 
-[scriptable, uuid(879874c6-5532-437a-bf76-703d0c2e7e77)]
+[scriptable, uuid(cfbf9880-cba5-11e4-8830-0800200c9a66)]
 interface nsIVolumeService : nsISupports
 {
     nsIVolume getVolumeByName(in DOMString volName);
     nsIVolume getVolumeByPath(in DOMString path);
     nsIVolume createOrGetVolumeByPath(in DOMString path);
 
     nsIVolumeMountLock createMountLock(in DOMString volName);
 
     nsIArray getVolumeNames();
 
     void Dump(in DOMString label);
 
     /* for test case only to simulate sdcard insertion/removal */
     void createFakeVolume(in DOMString name, in DOMString path);
     void SetFakeVolumeState(in DOMString name, in long state);
+
+    /* for test case only to test removal of storage area */
+    void removeFakeVolume(in DOMString name);
 };
 
 %{C++
 #define NS_VOLUMESERVICE_CID \
   {0x7c179fb7, 0x67a0, 0x43a3, {0x93, 0x37, 0x29, 0x4e, 0x03, 0x60, 0xb8, 0x58}}
 #define NS_VOLUMESERVICE_CONTRACTID "@mozilla.org/telephony/volume-service;1"
 %}
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -434,17 +434,17 @@ nsVolumeService::UpdateVolume(nsIVolume*
   NS_ConvertUTF8toUTF16 stateStr(vol->StateStr());
   obs->NotifyObservers(vol, NS_VOLUME_STATE_CHANGED, stateStr.get());
 }
 
 NS_IMETHODIMP
 nsVolumeService::CreateFakeVolume(const nsAString& name, const nsAString& path)
 {
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
-    nsRefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_INIT,
+    nsRefPtr<nsVolume> vol = new nsVolume(name, path, nsIVolume::STATE_MOUNTED,
                                           -1    /* mountGeneration */,
                                           true  /* isMediaPresent */,
                                           false /* isSharing */,
                                           false /* isFormatting */,
                                           true  /* isFake */,
                                           false /* isUnmounting */,
                                           false /* isRemovable */,
                                           false /* isHotSwappable */);
@@ -464,26 +464,67 @@ nsVolumeService::SetFakeVolumeState(cons
     nsRefPtr<nsVolume> vol;
     {
       MonitorAutoLock autoLock(mArrayMonitor);
       vol = FindVolumeByName(name);
     }
     if (!vol || !vol->IsFake()) {
       return NS_ERROR_NOT_AVAILABLE;
     }
-    vol->SetState(state);
-    vol->LogState();
-    UpdateVolume(vol.get());
+
+    // UpdateVolume expects the volume passed in to NOT be the
+    // same pointer as what CreateOrFindVolumeByName would return,
+    // which is why we allocate a temporary volume here.
+    nsRefPtr<nsVolume> volume = new nsVolume(name);
+    volume->Set(vol);
+    volume->SetState(state);
+    volume->LogState();
+    UpdateVolume(volume.get());
     return NS_OK;
   }
 
   ContentChild::GetSingleton()->SendSetFakeVolumeState(nsString(name), state);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsVolumeService::RemoveFakeVolume(const nsAString& name)
+{
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    SetFakeVolumeState(name, nsIVolume::STATE_NOMEDIA);
+    RemoveVolumeByName(name);
+    return NS_OK;
+  }
+
+  ContentChild::GetSingleton()->SendRemoveFakeVolume(nsString(name));
+  return NS_OK;
+}
+
+void
+nsVolumeService::RemoveVolumeByName(const nsAString& aName)
+{
+  nsRefPtr<nsVolume> vol;
+  {
+    MonitorAutoLock autoLock(mArrayMonitor);
+    vol = FindVolumeByName(aName);
+  }
+  if (!vol) {
+    return;
+  }
+  mVolumeArray.RemoveElement(vol);
+
+  if (XRE_GetProcessType() == GeckoProcessType_Default) {
+    nsCOMPtr<nsIObserverService> obs = GetObserverService();
+    if (!obs) {
+      return;
+    }
+    obs->NotifyObservers(nullptr, NS_VOLUME_REMOVED, nsString(aName).get());
+  }
+}
+
 /***************************************************************************
 * The UpdateVolumeRunnable creates an nsVolume and updates the main thread
 * data structure while running on the main thread.
 */
 class UpdateVolumeRunnable : public nsRunnable
 {
 public:
   UpdateVolumeRunnable(nsVolumeService* aVolumeService, const Volume* aVolume)
--- a/dom/system/gonk/nsVolumeService.h
+++ b/dom/system/gonk/nsVolumeService.h
@@ -41,22 +41,25 @@ public:
   nsVolumeService();
 
   static already_AddRefed<nsVolumeService> GetSingleton();
   //static nsVolumeService* GetSingleton();
   static void Shutdown();
 
   void DumpNoLock(const char* aLabel);
 
+  // To use this function, you have to create a new volume and pass it in.
   void UpdateVolume(nsIVolume* aVolume, bool aNotifyObservers = true);
   void UpdateVolumeIOThread(const Volume* aVolume);
 
   void RecvVolumesFromParent(const nsTArray<dom::VolumeInfo>& aVolumes);
   void GetVolumesForIPC(nsTArray<dom::VolumeInfo>* aResult);
 
+  void RemoveVolumeByName(const nsAString& aName);
+
 private:
   ~nsVolumeService();
 
   void CheckMountLock(const nsAString& aMountLockName,
                       const nsAString& aMountLockState);
   already_AddRefed<nsVolume> FindVolumeByMountLockName(const nsAString& aMountLockName);
   already_AddRefed<nsVolume> FindVolumeByName(const nsAString& aName);
   already_AddRefed<nsVolume> CreateOrFindVolumeByName(const nsAString& aName, bool aIsFake = false);
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -338,16 +338,18 @@ var interfaceNamesInGlobalScope =
     "DeviceMotionEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceOrientationEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "DeviceProximityEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DeviceStorageAreaChangedEvent", desktop: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "DeviceStorageAreaListener", desktop: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DeviceStorage", desktop: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "DeviceStorageChangeEvent", desktop: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "DisplayPortInputPort", b2g: true, permission: ["inputport"]},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Document",
 // IMPORTANT: Do not change this list without review from a DOM peer!
new file mode 100644
--- /dev/null
+++ b/dom/webidl/DeviceStorageAreaListener.webidl
@@ -0,0 +1,10 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+[Pref="device.storage.enabled"]
+interface DeviceStorageAreaListener : EventTarget {
+  // Fired when a storage area is added or removed.
+  attribute EventHandler onstorageareachanged;
+};
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -240,22 +240,29 @@ partial interface Navigator {
    * automatically when its associated window is unloaded.
    *
    * @param aTopic resource name
    */
   [Throws, Pref="dom.wakelock.enabled", Func="Navigator::HasWakeLockSupport", UnsafeInPrerendering]
   MozWakeLock requestWakeLock(DOMString aTopic);
 };
 
+partial interface Navigator {
+  [Throws, Pref="device.storage.enabled"]
+  readonly attribute DeviceStorageAreaListener deviceStorageAreaListener;
+};
+
 // nsIDOMNavigatorDeviceStorage
 partial interface Navigator {
   [Throws, Pref="device.storage.enabled"]
   DeviceStorage? getDeviceStorage(DOMString type);
   [Throws, Pref="device.storage.enabled"]
   sequence<DeviceStorage> getDeviceStorages(DOMString type);
+  [Throws, Pref="device.storage.enabled"]
+  DeviceStorage? getDeviceStorageByNameAndType(DOMString name, DOMString type);
 };
 
 // nsIDOMNavigatorDesktopNotification
 partial interface Navigator {
   [Throws, Pref="notification.feature.enabled", UnsafeInPrerendering]
   readonly attribute DesktopNotificationCenter mozNotification;
 };
 
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -98,16 +98,17 @@ WEBIDL_FILES = [
     'DataStore.webidl',
     'DataStoreImpl.webidl',
     'DataTransfer.webidl',
     'DedicatedWorkerGlobalScope.webidl',
     'DelayNode.webidl',
     'DesktopNotification.webidl',
     'DeviceMotionEvent.webidl',
     'DeviceStorage.webidl',
+    'DeviceStorageAreaListener.webidl',
     'Directory.webidl',
     'DisplayPortInputPort.webidl',
     'Document.webidl',
     'DocumentFragment.webidl',
     'DocumentTimeline.webidl',
     'DocumentType.webidl',
     'DOMCursor.webidl',
     'DOMError.webidl',
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -219,16 +219,17 @@ public:
 };
 
 void
 RespondWithHandler::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue)
 {
   AutoCancel autoCancel(this);
 
   if (!aValue.isObject()) {
+    NS_WARNING("FetchEvent::RespondWith was passed a promise resolved to a non-Object value");
     return;
   }
 
   nsRefPtr<Response> response;
   nsresult rv = UNWRAP_OBJECT(Response, &aValue.toObject(), response);
   if (NS_FAILED(rv)) {
     return;
   }
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -29,16 +29,17 @@
 #include "nsIDOMEventListener.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULElement.h"
 #include "nsIDOMElementCSSInlineStyle.h"
 #include "nsIDOMXULSelectCntrlItemEl.h"
 #include "nsIDocument.h"
 #include "nsLayoutStylesheetCache.h"
+#include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "nsFocusManager.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsIJSRuntimeService.h"
 #include "nsNameSpaceManager.h"
 #include "nsIObjectInputStream.h"
@@ -1596,16 +1597,20 @@ nsXULElement::LoadSrc()
     if (!slots->mFrameLoader) {
         // false as the last parameter so that xul:iframe/browser/editor
         // session history handling works like dynamic html:iframes.
         // Usually xul elements are used in chrome, which doesn't have
         // session history at all.
         slots->mFrameLoader = nsFrameLoader::Create(this, false);
         NS_ENSURE_TRUE(slots->mFrameLoader, NS_OK);
 
+        (new AsyncEventDispatcher(this,
+                                  NS_LITERAL_STRING("XULFrameLoaderCreated"),
+                                  /* aBubbles */ true))->RunDOMEventWhenSafe();
+
         if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::prerendered,
                         NS_LITERAL_STRING("true"), eIgnoreCase)) {
             nsresult rv = slots->mFrameLoader->SetIsPrerendered();
             NS_ENSURE_SUCCESS(rv,rv);
         }
     }
 
     return slots->mFrameLoader->LoadFrame();
--- a/gfx/layers/D3D11ShareHandleImage.cpp
+++ b/gfx/layers/D3D11ShareHandleImage.cpp
@@ -38,22 +38,21 @@ D3D11ShareHandleImage::GetSize()
 {
   return mSize;
 }
 
 TextureClient*
 D3D11ShareHandleImage::GetTextureClient(CompositableClient* aClient)
 {
   if (!mTextureClient) {
-    RefPtr<TextureClientD3D11> textureClient =
-      new TextureClientD3D11(aClient->GetForwarder(),
-                             mFormat,
-                             TextureFlags::DEFAULT);
-    textureClient->InitWith(mTexture, mSize);
-    mTextureClient = textureClient;
+    mTextureClient = TextureClientD3D11::Create(aClient->GetForwarder(),
+                                                mFormat,
+                                                TextureFlags::DEFAULT,
+                                                mTexture,
+                                                mSize);
   }
   return mTextureClient;
 }
 
 TemporaryRef<gfx::SourceSurface>
 D3D11ShareHandleImage::GetAsSourceSurface()
 {
   if (!mTexture) {
--- a/gfx/layers/D3D9SurfaceImage.cpp
+++ b/gfx/layers/D3D9SurfaceImage.cpp
@@ -197,22 +197,22 @@ D3D9SurfaceImage::GetSize()
   return mSize;
 }
 
 TextureClient*
 D3D9SurfaceImage::GetTextureClient(CompositableClient* aClient)
 {
   EnsureSynchronized();
   if (!mTextureClient) {
-    RefPtr<SharedTextureClientD3D9> textureClient =
-      new SharedTextureClientD3D9(aClient->GetForwarder(),
-                                  gfx::SurfaceFormat::B8G8R8X8,
-                                  TextureFlags::DEFAULT);
-    textureClient->InitWith(mTexture, mShareHandle, mDesc);
-    mTextureClient = textureClient;
+    mTextureClient = SharedTextureClientD3D9::Create(aClient->GetForwarder(),
+                                                     gfx::SurfaceFormat::B8G8R8X8,
+                                                     TextureFlags::DEFAULT,
+                                                     mTexture,
+                                                     mShareHandle,
+                                                     mDesc);
   }
   return mTextureClient;
 }
 
 TemporaryRef<gfx::SourceSurface>
 D3D9SurfaceImage::GetAsSourceSurface()
 {
   NS_ENSURE_TRUE(mTexture, nullptr);
--- a/gfx/layers/IMFYCbCrImage.cpp
+++ b/gfx/layers/IMFYCbCrImage.cpp
@@ -185,22 +185,27 @@ IMFYCbCrImage::GetD3D9TextureClient(Comp
     }
     break;
   }
 
   if (!valid) {
     return nullptr;
   }
 
-  RefPtr<DXGIYCbCrTextureClient> texClient =
-    new DXGIYCbCrTextureClient(aClient->GetForwarder(), TextureFlags::DEFAULT);
-  texClient->InitWith(textureY, textureCb, textureCr,
-                      shareHandleY, shareHandleCb, shareHandleCr,
-                      GetSize(), mData.mYSize, mData.mCbCrSize);
-  mTextureClient = texClient;
+  mTextureClient = DXGIYCbCrTextureClient::Create(aClient->GetForwarder(),
+                                                  TextureFlags::DEFAULT,
+                                                  textureY,
+                                                  textureCb,
+                                                  textureCr,
+                                                  shareHandleY,
+                                                  shareHandleCb,
+                                                  shareHandleCr,
+                                                  GetSize(),
+                                                  mData.mYSize,
+                                                  mData.mCbCrSize);
 
   return mTextureClient;
 }
 
 TextureClient*
 IMFYCbCrImage::GetTextureClient(CompositableClient* aClient)
 {
   ID3D11Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D11MediaDevice();
@@ -263,21 +268,26 @@ IMFYCbCrImage::GetTextureClient(Composit
   HANDLE shareHandleCb;
   textureCb->QueryInterface((IDXGIResource**)byRef(resource));
   hr = resource->GetSharedHandle(&shareHandleCb);
 
   HANDLE shareHandleCr;
   textureCr->QueryInterface((IDXGIResource**)byRef(resource));
   hr = resource->GetSharedHandle(&shareHandleCr);
 
-  RefPtr<DXGIYCbCrTextureClient> texClient =
-    new DXGIYCbCrTextureClient(aClient->GetForwarder(), TextureFlags::DEFAULT);
-  texClient->InitWith(textureY, textureCb, textureCr,
-                      shareHandleY, shareHandleCb, shareHandleCr,
-                      GetSize(), mData.mYSize, mData.mCbCrSize);
-  mTextureClient = texClient;
+  mTextureClient = DXGIYCbCrTextureClient::Create(aClient->GetForwarder(),
+                                                  TextureFlags::DEFAULT,
+                                                  textureY,
+                                                  textureCb,
+                                                  textureCr,
+                                                  shareHandleY,
+                                                  shareHandleCb,
+                                                  shareHandleCr,
+                                                  GetSize(),
+                                                  mData.mYSize,
+                                                  mData.mCbCrSize);
 
   return mTextureClient;
 }
 
 
 } /* layers */
 } /* mozilla */
--- a/gfx/layers/MacIOSurfaceImage.cpp
+++ b/gfx/layers/MacIOSurfaceImage.cpp
@@ -10,20 +10,19 @@
 
 using namespace mozilla;
 using namespace mozilla::layers;
 
 TextureClient*
 MacIOSurfaceImage::GetTextureClient(CompositableClient* aClient)
 {
   if (!mTextureClient) {
-    RefPtr<MacIOSurfaceTextureClientOGL> buffer =
-      new MacIOSurfaceTextureClientOGL(aClient->GetForwarder(), TextureFlags::DEFAULT);
-    buffer->InitWith(mSurface);
-    mTextureClient = buffer;
+    mTextureClient = MacIOSurfaceTextureClientOGL::Create(aClient->GetForwarder(),
+                                                          TextureFlags::DEFAULT,
+                                                          mSurface);
   }
   return mTextureClient;
 }
 
 TemporaryRef<gfx::SourceSurface>
 MacIOSurfaceImage::GetAsSourceSurface()
 {
   mSurface->Lock();
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1313,16 +1313,20 @@ APZCTreeManager::BuildOverscrollHandoffC
       if (!apzc->IsRootForLayersId()) {
         // This probably indicates a bug or missed case in layout code
         NS_WARNING("Found a non-root APZ with no handoff parent");
       }
       apzc = apzc->GetParent();
       continue;
     }
 
+    // Guard against a possible infinite-loop condition. If we hit this, the
+    // layout code that generates the handoff parents did something wrong.
+    MOZ_ASSERT(apzc->GetScrollHandoffParentId() != apzc->GetGuid().mScrollId);
+
     // Find the AsyncPanZoomController instance with a matching layersId and
     // the scroll id that matches apzc->GetScrollHandoffParentId(). To do this
     // search the subtree with the same layersId for the apzc with the specified
     // scroll id.
     AsyncPanZoomController* scrollParent = nullptr;
     AsyncPanZoomController* parent = apzc;
     while (!parent->IsRootForLayersId()) {
       parent = parent->GetParent();
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -207,16 +207,32 @@ TextureClientD3D11::~TextureClientD3D11(
       UnlockD3DTexture(mTexture.get());
     } else {
       UnlockD3DTexture(mTexture10.get());
     }
   }
 #endif
 }
 
+// static
+TemporaryRef<TextureClientD3D11>
+TextureClientD3D11::Create(ISurfaceAllocator* aAllocator,
+                           gfx::SurfaceFormat aFormat,
+                           TextureFlags aFlags,
+                           ID3D11Texture2D* aTexture,
+                           gfx::IntSize aSize)
+{
+  RefPtr<TextureClientD3D11> texture = new TextureClientD3D11(aAllocator,
+                                                             aFormat,
+                                                             aFlags);
+  texture->mTexture = aTexture;
+  texture->mSize = aSize;
+  return texture;
+}
+
 TemporaryRef<TextureClient>
 TextureClientD3D11::CreateSimilar(TextureFlags aFlags,
                                   TextureAllocationFlags aAllocFlags) const
 {
   RefPtr<TextureClient> tex = new TextureClientD3D11(mAllocator, mFormat,
                                                      mFlags | aFlags);
 
   if (!tex->AllocateForSurface(mSize, aAllocFlags)) {
@@ -529,16 +545,44 @@ protected:
 DXGIYCbCrTextureClient::~DXGIYCbCrTextureClient()
 {
   if (mHoldRefs[0] && mActor) {
     KeepUntilFullDeallocation(MakeUnique<YCbCrKeepAliveD3D11>(mHoldRefs), true);
   }
   MOZ_COUNT_DTOR(DXGIYCbCrTextureClient);
 }
 
+// static
+TemporaryRef<DXGIYCbCrTextureClient>
+DXGIYCbCrTextureClient::Create(ISurfaceAllocator* aAllocator,
+                               TextureFlags aFlags,
+                               IUnknown* aTextureY,
+                               IUnknown* aTextureCb,
+                               IUnknown* aTextureCr,
+                               HANDLE aHandleY,
+                               HANDLE aHandleCb,
+                               HANDLE aHandleCr,
+                               const gfx::IntSize& aSize,
+                               const gfx::IntSize& aSizeY,
+                               const gfx::IntSize& aSizeCbCr)
+{
+  RefPtr<DXGIYCbCrTextureClient> texture =
+    new DXGIYCbCrTextureClient(aAllocator, aFlags);
+  texture->mHandles[0] = aHandleY;
+  texture->mHandles[1] = aHandleCb;
+  texture->mHandles[2] = aHandleCr;
+  texture->mHoldRefs[0] = aTextureY;
+  texture->mHoldRefs[1] = aTextureCb;
+  texture->mHoldRefs[2] = aTextureCr;
+  texture->mSize = aSize;
+  texture->mSizeY = aSizeY;
+  texture->mSizeCbCr = aSizeCbCr;
+  return texture;
+}
+
 bool
 DXGIYCbCrTextureClient::Lock(OpenMode)
 {
   MOZ_ASSERT(!mIsLocked);
   if (!IsValid()) {
     return false;
   }
   mIsLocked = true;
--- a/gfx/layers/d3d11/TextureD3D11.h
+++ b/gfx/layers/d3d11/TextureD3D11.h
@@ -27,21 +27,23 @@ class TextureClientD3D11 : public Textur
 {
 public:
   TextureClientD3D11(ISurfaceAllocator* aAllocator,
                      gfx::SurfaceFormat aFormat,
                      TextureFlags aFlags);
 
   virtual ~TextureClientD3D11();
 
-  void InitWith(ID3D11Texture2D* aTexture, const gfx::IntSize& aSize)
-  {
-    mTexture = aTexture;
-    mSize = aSize;
-  }
+  // Creates a TextureClient and init width.
+  static TemporaryRef<TextureClientD3D11>
+  Create(ISurfaceAllocator* aAllocator,
+         gfx::SurfaceFormat aFormat,
+         TextureFlags aFlags,
+         ID3D11Texture2D* aTexture,
+         gfx::IntSize aSize);
 
   // TextureClient
 
   virtual bool IsAllocated() const override { return mTexture || mTexture10; }
 
   virtual bool Lock(OpenMode aOpenMode) override;
 
   virtual void Unlock() override;
@@ -85,49 +87,42 @@ protected:
 class DXGIYCbCrTextureClient : public TextureClient
 {
 public:
   DXGIYCbCrTextureClient(ISurfaceAllocator* aAllocator,
                          TextureFlags aFlags);
 
   virtual ~DXGIYCbCrTextureClient();
 
+  // Creates a TextureClient and init width.
+  static TemporaryRef<DXGIYCbCrTextureClient>
+  Create(ISurfaceAllocator* aAllocator,
+         TextureFlags aFlags,
+         IUnknown* aTextureY,
+         IUnknown* aTextureCb,
+         IUnknown* aTextureCr,
+         HANDLE aHandleY,
+         HANDLE aHandleCb,
+         HANDLE aHandleCr,
+         const gfx::IntSize& aSize,
+         const gfx::IntSize& aSizeY,
+         const gfx::IntSize& aSizeCbCr);
+
   // TextureClient
 
   virtual bool IsAllocated() const override{ return !!mHoldRefs[0]; }
 
   virtual bool Lock(OpenMode aOpenMode) override;
 
   virtual void Unlock() override;
 
   virtual bool IsLocked() const override{ return mIsLocked; }
 
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) override;
 
-  void InitWith(IUnknown* aTextureY,
-                IUnknown* aTextureCb,
-                IUnknown* aTextureCr,
-                HANDLE aHandleY,
-                HANDLE aHandleCb,
-                HANDLE aHandleCr,
-                const gfx::IntSize& aSize,
-                const gfx::IntSize& aSizeY,
-                const gfx::IntSize& aSizeCbCr)
-  {
-    mHandles[0] = aHandleY;
-    mHandles[1] = aHandleCb;
-    mHandles[2] = aHandleCr;
-    mHoldRefs[0] = aTextureY;
-    mHoldRefs[1] = aTextureCb;
-    mHoldRefs[2] = aTextureCr;
-    mSize = aSize;
-    mSizeY = aSizeY;
-    mSizeCbCr = aSizeCbCr;
-  }
-
   virtual gfx::IntSize GetSize() const
   {
     return mSize;
   }
 
   virtual bool HasInternalBuffer() const override{ return true; }
 
     // This TextureClient should not be used in a context where we use CreateSimilar
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -757,16 +757,39 @@ SharedTextureClientD3D9::~SharedTextureC
     KeepUntilFullDeallocation(MakeUnique<TKeepAlive<IDirect3DTexture9>>(mTexture));
   }
   if (mTexture) {
     gfxWindowsPlatform::sD3D9SharedTextureUsed -= mDesc.Width * mDesc.Height * 4;
   }
   MOZ_COUNT_DTOR(SharedTextureClientD3D9);
 }
 
+// static
+TemporaryRef<SharedTextureClientD3D9>
+SharedTextureClientD3D9::Create(ISurfaceAllocator* aAllocator,
+                                gfx::SurfaceFormat aFormat,
+                                TextureFlags aFlags,
+                                IDirect3DTexture9* aTexture,
+                                HANDLE aSharedHandle,
+                                D3DSURFACE_DESC aDesc)
+{
+  RefPtr<SharedTextureClientD3D9> texture =
+    new SharedTextureClientD3D9(aAllocator,
+                                aFormat,
+                                aFlags);
+  MOZ_ASSERT(!texture->mTexture);
+  texture->mTexture = aTexture;
+  texture->mHandle = aSharedHandle;
+  texture->mDesc = aDesc;
+  if (texture->mTexture) {
+    gfxWindowsPlatform::sD3D9SharedTextureUsed += texture->mDesc.Width * texture->mDesc.Height * 4;
+  }
+  return texture;
+}
+
 bool
 SharedTextureClientD3D9::Lock(OpenMode)
 {
   MOZ_ASSERT(!mIsLocked);
   if (!IsValid()) {
     return false;
   }
   mIsLocked = true;
--- a/gfx/layers/d3d9/TextureD3D9.h
+++ b/gfx/layers/d3d9/TextureD3D9.h
@@ -243,39 +243,37 @@ class SharedTextureClientD3D9 : public T
 {
 public:
   SharedTextureClientD3D9(ISurfaceAllocator* aAllocator,
                           gfx::SurfaceFormat aFormat,
                           TextureFlags aFlags);
 
   virtual ~SharedTextureClientD3D9();
 
+  // Creates a TextureClient and init width.
+  static TemporaryRef<SharedTextureClientD3D9>
+  Create(ISurfaceAllocator* aAllocator,
+         gfx::SurfaceFormat aFormat,
+         TextureFlags aFlags,
+         IDirect3DTexture9* aTexture,
+         HANDLE aSharedHandle,
+         D3DSURFACE_DESC aDesc);
+
   // TextureClient
 
   virtual bool IsAllocated() const override { return !!mTexture; }
 
   virtual bool Lock(OpenMode aOpenMode) override;
 
   virtual void Unlock() override;
 
   virtual bool IsLocked() const override { return mIsLocked; }
 
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) override;
 
-  void InitWith(IDirect3DTexture9* aTexture, HANDLE aSharedHandle, D3DSURFACE_DESC aDesc)
-  {
-    MOZ_ASSERT(!mTexture);
-    mTexture = aTexture;
-    mHandle = aSharedHandle;
-    mDesc = aDesc;
-    if (mTexture) {
-      gfxWindowsPlatform::sD3D9SharedTextureUsed += mDesc.Width * mDesc.Height * 4;
-    }
-  }
-
   virtual gfx::IntSize GetSize() const
   {
     return gfx::IntSize(mDesc.Width, mDesc.Height);
   }
 
   virtual bool HasInternalBuffer() const override { return true; }
 
   // This TextureClient should not be used in a context where we use CreateSimilar
--- a/gfx/layers/opengl/GrallocTextureClient.cpp
+++ b/gfx/layers/opengl/GrallocTextureClient.cpp
@@ -54,26 +54,16 @@ GrallocTextureClientOGL::CreateSimilar(T
 
   if (!tex->AllocateForSurface(mSize, aAllocFlags)) {
     return nullptr;
   }
 
   return tex;
 }
 
-void
-GrallocTextureClientOGL::InitWith(MaybeMagicGrallocBufferHandle aHandle, gfx::IntSize aSize)
-{
-  MOZ_ASSERT(!IsAllocated());
-  MOZ_ASSERT(IsValid());
-  mGrallocHandle = aHandle;
-  mGraphicBuffer = GetGraphicBufferFrom(aHandle);
-  mSize = aSize;
-}
-
 bool
 GrallocTextureClientOGL::ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor)
 {
   MOZ_ASSERT(IsValid());
   if (!IsAllocated()) {
     return false;
   }
 
--- a/gfx/layers/opengl/GrallocTextureClient.h
+++ b/gfx/layers/opengl/GrallocTextureClient.h
@@ -55,18 +55,16 @@ public:
   virtual bool IsAllocated() const override;
 
   virtual bool ToSurfaceDescriptor(SurfaceDescriptor& aOutDescriptor) override;
 
   virtual void SetRemoveFromCompositableTracker(AsyncTransactionTracker* aTracker) override;
 
   virtual void WaitForBufferOwnership(bool aWaitReleaseFence = true) override;
 
-  void InitWith(MaybeMagicGrallocBufferHandle aDesc, gfx::IntSize aSize);
-
   void SetTextureFlags(TextureFlags aFlags) { AddFlags(aFlags); }
 
   gfx::IntSize GetSize() const override { return mSize; }
 
   android::sp<android::GraphicBuffer> GetGraphicBuffer()
   {
     return mGraphicBuffer;
   }
--- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.cpp
@@ -17,22 +17,28 @@ MacIOSurfaceTextureClientOGL::MacIOSurfa
 
 MacIOSurfaceTextureClientOGL::~MacIOSurfaceTextureClientOGL()
 {
   if (mActor && mSurface) {
     KeepUntilFullDeallocation(MakeUnique<TKeepAlive<MacIOSurface>>(mSurface));
   }
 }
 
-void
-MacIOSurfaceTextureClientOGL::InitWith(MacIOSurface* aSurface)
+// static
+TemporaryRef<MacIOSurfaceTextureClientOGL>
+MacIOSurfaceTextureClientOGL::Create(ISurfaceAllocator* aAllocator,
+                                     TextureFlags aFlags,
+                                     MacIOSurface* aSurface)
 {
-  MOZ_ASSERT(IsValid());
-  MOZ_ASSERT(!IsAllocated());
-  mSurface = aSurface;
+  RefPtr<MacIOSurfaceTextureClientOGL> texture =
+      new MacIOSurfaceTextureClientOGL(aAllocator, aFlags);
+  MOZ_ASSERT(texture->IsValid());
+  MOZ_ASSERT(!texture->IsAllocated());
+  texture->mSurface = aSurface;
+  return texture;
 }
 
 bool
 MacIOSurfaceTextureClientOGL::Lock(OpenMode aMode)
 {
   MOZ_ASSERT(!mIsLocked);
   mIsLocked = true;
   return IsValid() && IsAllocated();
--- a/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h
+++ b/gfx/layers/opengl/MacIOSurfaceTextureClientOGL.h
@@ -16,17 +16,21 @@ namespace layers {
 class MacIOSurfaceTextureClientOGL : public TextureClient
 {
 public:
   explicit MacIOSurfaceTextureClientOGL(ISurfaceAllocator* aAllcator,
                                         TextureFlags aFlags);
 
   virtual ~MacIOSurfaceTextureClientOGL();
 
-  void InitWith(MacIOSurface* aSurface);
+  // Creates a TextureClient and init width.
+  static TemporaryRef<MacIOSurfaceTextureClientOGL>
+  Create(ISurfaceAllocator* aAllocator,
+         TextureFlags aFlags,
+         MacIOSurface* aSurface);
 
   virtual bool Lock(OpenMode aMode) override;
 
   virtual void Unlock() override;
 
   virtual bool IsLocked() const override;
 
   virtual bool IsAllocated() const override { return !!mSurface; }
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8181,16 +8181,20 @@ nsLayoutUtils::ComputeFrameMetrics(nsIFr
 
     if (!aScrollFrame->GetParent() ||
         EventStateManager::CanVerticallyScrollFrameWithWheel(aScrollFrame->GetParent()))
     {
       metrics.SetAllowVerticalScrollWithWheel();
     }
   }
 
+  // If we have the scrollparent being the same as the scroll id, the
+  // compositor-side code could get into an infinite loop while building the
+  // overscroll handoff chain.
+  MOZ_ASSERT(aScrollParentId == FrameMetrics::NULL_SCROLL_ID || scrollId != aScrollParentId);
   metrics.SetScrollId(scrollId);
   metrics.SetIsRoot(aIsRoot);
   metrics.SetScrollParentId(aScrollParentId);
 
   // Only the root scrollable frame for a given presShell should pick up
   // the presShell's resolution. All the other frames are 1.0.
   if (aScrollFrame == presShell->GetRootScrollFrame()) {
     metrics.SetPresShellResolution(presShell->GetResolution());
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -480,20 +480,21 @@ FontFace::ParseDescriptor(nsCSSFontDesc 
                           nsCSSValue& aResult)
 {
   nsCSSParser parser;
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mParent);
   nsCOMPtr<nsIPrincipal> principal = global->PrincipalOrNull();
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mParent);
+  nsCOMPtr<nsIURI> docURI = window->GetDocumentURI();
   nsCOMPtr<nsIURI> base = window->GetDocBaseURI();
 
   if (!parser.ParseFontFaceDescriptor(aDescID, aString,
-                                      nullptr, // aSheetURL
+                                      docURI, // aSheetURL
                                       base,
                                       principal,
                                       aResult)) {
     aResult.Reset();
     return false;
   }
 
   return true;
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1161366-1.html
@@ -0,0 +1,7 @@
+<script>
+var f = new FontFace("x", "url(x.ttf)", { unicodeRange: "U+0041" });
+f.load();
+document.fonts.add(f);
+f = new FontFace("x", "url(x.ttf)", { unicodeRange: "U+0042" });
+f.load();
+</script>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -108,10 +108,11 @@ load 989965-1.html
 load 992333-1.html
 pref(dom.webcomponents.enabled,true) load 1017798-1.html
 load 1028514-1.html
 load 1066089-1.html
 load 1074651-1.html
 pref(dom.webcomponents.enabled,true) load 1089463-1.html
 pref(layout.css.expensive-style-struct-assertions.enabled,true) load 1136010-1.html
 load 1153693-1.html
+load 1161366-1.html
 load large_border_image_width.html
 load border-image-visited-link.html
--- a/layout/style/forms.css
+++ b/layout/style/forms.css
@@ -822,34 +822,34 @@ meter {
   background: linear-gradient(#f77, #f77, #fcc 20%, #d44 45%, #d44 55%);
 }
 
 input[type=range] {
   -moz-appearance: range;
   display: inline-block;
   inline-size: 12em;
   block-size: 1.3em;
-  margin-inline-start: 0.7em;
-  margin-inline-end: 0.7em;
+  -moz-margin-start: 0.7em;
+  -moz-margin-end: 0.7em;
   margin-block-start: 0;
   margin-block-end: 0;
   /* Override some rules that apply on all input types: */
   cursor: default;
   background: none;
   border: none;
   -moz-binding: none; /* we don't want any of platformHTMLBindings.xml#inputFields */
   /* Prevent nsFrame::HandlePress setting mouse capture to this element. */
   -moz-user-select: none ! important;
 }
 
 input[type=range][orient=block] {
   inline-size: 1.3em;
   block-size: 12em;
-  margin-inline-start: 0;
-  margin-inline-end: 0;
+  -moz-margin-start: 0;
+  -moz-margin-end: 0;
   margin-block-start: 0.7em;
   margin-block-end: 0.7em;
 }
 
 input[type=range][orient=horizontal] {
   width: 12em;
   height: 1.3em;
   margin: 0 0.7em;
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -2606,17 +2606,22 @@ nsRuleNode::SetDefaultOnRoot(const nsSty
                          aRuleDetail != eRuleNone))) {                        \
     if (parentContext) {                                                      \
       parentdata_ = parentContext->Style##type_();                            \
     } else {                                                                  \
       maybeFakeParentData.emplace ctorargs_;                                  \
       parentdata_ = maybeFakeParentData.ptr();                                \
     }                                                                         \
   }                                                                           \
-  if (aStartStruct)                                                           \
+  if (eStyleStruct_##type_ == eStyleStruct_Variables)                         \
+    /* no need to copy construct an nsStyleVariables, as we will copy */      \
+    /* inherited variables (and set canStoreInRuleTree to false) in */        \
+    /* ComputeVariablesData */                                                \
+    data_ = new (mPresContext) nsStyle##type_ ctorargs_;                      \
+  else if (aStartStruct)                                                      \
     /* We only need to compute the delta between this computed data and */    \
     /* our computed data. */                                                  \
     data_ = new (mPresContext)                                                \
             nsStyle##type_(*static_cast<nsStyle##type_*>(aStartStruct));      \
   else {                                                                      \
     if (aRuleDetail != eRuleFullMixed && aRuleDetail != eRuleFullReset) {     \
       /* No question. We will have to inherit. Go ahead and init */           \
       /* with inherited vals from parent. */                                  \
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -3638,16 +3638,17 @@ nsChangeHint nsStyleUIReset::CalcDiffere
 nsStyleVariables::nsStyleVariables()
 {
   MOZ_COUNT_CTOR(nsStyleVariables);
 }
 
 nsStyleVariables::nsStyleVariables(const nsStyleVariables& aSource)
 {
   MOZ_COUNT_CTOR(nsStyleVariables);
+  mVariables = aSource.mVariables;
 }
 
 nsStyleVariables::~nsStyleVariables(void)
 {
   MOZ_COUNT_DTOR(nsStyleVariables);
 }
 
 nsChangeHint
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -1295,17 +1295,17 @@ int WebrtcVideoConduit::SendRTCPPacket(i
   ReentrantMonitorAutoEnter enter(mTransportMonitor);
   if(mReceiverTransport &&
      mReceiverTransport->SendRtcpPacket(data, len) == NS_OK)
   {
     // Might be a sender report, might be a receiver report, we don't know.
     CSFLogDebug(logTag, "%s Sent RTCP Packet ", __FUNCTION__);
     return len;
   } else if(mTransmitterTransport &&
-            (mTransmitterTransport->SendRtpPacket(data, len) == NS_OK)) {
+            (mTransmitterTransport->SendRtcpPacket(data, len) == NS_OK)) {
       CSFLogDebug(logTag, "%s Sent RTCP Packet (sender report) ", __FUNCTION__);
       return len;
   } else {
     CSFLogError(logTag, "%s RTCP Packet Send Failed ", __FUNCTION__);
     return -1;
   }
 }
 
index df814faba2b7c2970645db530f6257a2d96ab729..d6e11f8f8a63f2ae431dcb524c0d9e4cd51b3879
GIT binary patch
literal 681
zc$@*L0#^NrP)<h;3K|Lk000e1NJLTq001xm001Ni1^@s6&qcWk0007TNkl<Zc-qyM
z&#O&Q6vwyx3Q1w2WMIm~OerO?*E-jGJRC~c>zvC;^wQ&%BHWnE%s-%%+H2p79=ZcE
zcmutG;tdExQc97$=ht<&$sxMu*S+V*w^p6eI-j%7+IxMMD6{G<+@LhHiRA~<@<sRg
za%0+EAgUnW$bE|CH>G9guV9$;Tf<8$2p<M9JPK|PEiNTIF}NXouYmT=jyt!C@L?D{
zT^e{#<KEctVJl5n6F!Vjg~Kc3>`4wE-bj^&x8WPaA`k5DEbxl)LUH%Jh7r>8Yg%~H
z@_d!y&lcA<26ttGXFMa~sPUjNZ~XqDC1ry57{{aC-#vFZF<kG)XT|cT((sJf*;ahz
z;gpC{pyevX@bbS23|{JVI&)GDDV94k!5eu}MCk;NtgzLNsDe(~R=$SeYzG!cMU+M-
za3L`~j`u6&ldg?jGiZ3*zNF!J%>IOn36sB5E!@;IXA_5%hS}8cERV$>=M2x2zQn?L
z?0AEKR|bQmBmHCgaSm7hTq-<vc4UKxdKypiFd})>xwsp&-DO$eNy85^Kq)tWXR}R^
z=_53}$^!2*9u`?-6A;6UO_*=MchA7V{)~E!8%e|4JmIP791#7~5sj-A%c)!{-^Bf5
z5E`3w7ZJmaLBZQ4h8SEiXeq{X_G;@lyj>$jUb05aZvOp%x3<DAGa_%*xwgIw@H%79
z&)Q;$qSQ5TEA7Bb3vca(C|bc(FrOH&C0s;is0ofJldR3PhcQOae<5~2lu>@Gwnedg
z-wWP1xLuT4{+-%d#q#Mt@YdZL*ea?ZyJ8aKRmJe;_jSv1;^-~8E>DJEP_aXj*M5U0
P00000NkvXXu0mjfQy)$H
index fe4a03dabda5caf5cbd656404dee41172a80f3f8..b97f1262427d6275bb60c0507eb39b4282bfea88
GIT binary patch
literal 493
zc$@+40TTX+P)<h;3K|Lk000e1NJLTq001BW000;W1^@s6n^XTZ0005BNkl<Zc-qC5
zze@u#6vu<Ve-4hWqM)Pyfm_m^?c|MeX_W&D4ubACxVQ-J;vk4{HfigX;xDX-6bDB|
zK}RQ1P#i_;%VG-sajDH+zYuPDd7qc$o0mgj$iA_=ElxHm!*B3?O0dWr0)+|BSqzu|
z2FE`%Lue>)%1Ktj<)Dmw!`EmFTo!@vgM%}i5fGmO-rED0Uf2o3a9j;eO37S&SofJT
z6*w61N-NwcI$g?ePA*eLbEqoGLM}cIm2E?SOL>$81ix54o*%3Mk4y_kRt7`2+HlHF
zhcBGrv<e)?x$VUUs*+G|b^=!ZQpcs9JgaIr80kQ9<!3eFLKY*ZjT4aDWZoG2KOCQ|
zl+1oqPZ0E}a9>}cch6LuxvS{*BU+?=sF8##6`RHvBuuHoA-=F&i1Z>qEplTDw)t61
z(lwySyo4*rW_T3be60n}$f0v*_Mm1ZDu&6sEv`JP0{8R8ns}?5E`Sp*+A-20nk7+4
z_@Tb-I4y1m(kbf2CL#Hw!nV5-u31ZYW28}|0{FgLHak&}5`Y9JTQ#<QPr^fx_?3ng
jKZu@@;CNj=RITY3n)Cab<R#|}00000NkvXXu0mjfBtqHZ
index b9c6f1ad5ffee5f487e213700bb7e7126872a8bc..f34adda6bbd4a58db2fefc39b08cf34da7517665
GIT binary patch
literal 931
zc$@*F16=%xP)<h;3K|Lk000e1NJLTq002M$001xu1^@s6p-X%p000AONkl<Zc-rNf
zNoW*76oyMhQQY@kQBlMVQPhKq=)rn5lSBi~NwlkbCPPdR6})+gCqeY$Rm6h_Ay98-
zvY~hg;u6%DsM$~yjZ1LX;1U#d?59FQAd;S`ndzSS;n6UMX}*51>Md0)-&B>X8Y*M_
zfK>dNfzWF|yX@!bsNyqOzAKU_nJy8U48@cEO)CCQ#vzc`@CNcuf*si4n-vqHFyWm@
z=rd7l%eS!aAUGHH*+SUld-!rJo#w#1HF&<iL5%Y?ybaM#FsYhEI9agpWD<F+@8Hb_
zG1Y-j+dlaZ9(kJ{crs`ySa>p1@e_p#PuA^=ED)_No+1(MX@y7VHRFK|1qm+||L7Zd
zL&06G@W?+%1?vhD-avS7#DZ%*4_}F;lXb&WD{Lyh#uxAs`5nEU=Wk^!u-bi>rNkVG
z5RwWiOcYyWqa5i!cz6>@z$$kmrH;@DiE!Ko|Jg5$9$1Os)(pb8>J58%E#rZe%#99R
z4~;Suzx<zY`emRvq7y!RAv8%hyn*~xSq)$AZs%=|4mXfD46=<pPan&R@Dlk;GcK07
zpQIvGyKE$GmDd){P6wWv5zTA9N7fcEWo{*7a4OXohGRwRy#OzfzbM1N64&rXLTse9
zokouBGf?bigVesd;6#aVTPHk?w`6V6VwQ@ef_gn3fMc|jvJQ{%EE*GwSgMl9pK44!
zHCCczY?k23x~;n4LY9uGQqr7)+9EZ@3;M&`zc*{n^G}@jq#-Bu=`BC0di=Y=?wp_<
z9^rAgJ}{r@kfoqhE4;HgewRudq;#p-^N;LqaURntso*UAx#tbxp&8#Xm+4k@GBnJo
z1-xn0i#UZ>hGZ%pq0sf9=kMFuWfseZWSnoNPRc``@5=h+2HIhr-aLnfyi?W(X0Tl7
z@Wnl&C4$P`2Y_}u`ewOQGMOA=B0uV)SI|~W(P0SlicBSTNrW$1^88(z3Nx>X28&dC
zXA++F9U6~$`$mR>s~Pfq3(X0bm*osi`5Db?zL^>(<|qBD^M7b)Y;ID+#QZ2LCbrXg
zTN<9f5xo=|&-^SJMNzeug6Er@^Ld|(yy#}GK=*iQa+G(Ml<CUJAR3!16^)@WEMH}z
z*lQ^GI?&9}IZ(-Cg)z9oKsaYx4^@zVPJfVBQj{={={H&T8rnIhlPCZH002ovPDHLk
FV1mKoyD0zw
index 03ba871f61efbab70f5ffaf4662be3b96d989b0c..b4bb48d31927d21fd58a3347de8e2b5a63e2ed21
GIT binary patch
literal 1339
zc$_U$2~ZPP7+wx7f}lgi6GfD!95tw*MXf+M+-xoqBnYBrcY~=%t8wrEZ7JZvh~mJA
zgi#BFS_-t;O+chb+E5fk3KNDR2#6Am2y%p~fQ3f--pbC~xBvT(@B9Au&z={HP)GZv
z_6UMFV%)Gu@WsK)wX*?xU=<=k5UU}vASxUN)LfiGWfYfLD6k0)hO1Gz7L!3gN>Tz`
z!vh|W33A}hELd1BB(<O%BE)OCGMc}a;(-vU<;f}j-ZE4Ms>%frR?RGm8&C0Ja+xq?
zIt7P}7~E-yOlbIWt%#s!lmJ7~q#%Hw`bYu&<ZV8K29LxYOh#is8=;0oFiHc>7^&ss
zG!NI~N9v{VYKTv2ML3NS8gA*d6dd<a5*dxcDjG%!ikG5KAz<&Y5rT2fbjt9OKqNiW
z0@MjGX{Z_3V4&IQ>1l!j26{j*C25qYMHS^j8O@Vxd7ukI&6WQq1uQ@w%w&>*fos5}
zi2x=^aZ?^iaWy8V1)y9}4Md_0FPGFXs?_i{02hG$cohm7g5Zo(UcrM6G7}**5l{f5
z6Bys#!Dfc5M*p%CuLtwKc{EbA9eHIQ860@&J-lw{gYTNj*L@rQ``6<6`TKshA8XwI
z3!k^2O-+ox@p|rVJ$5Q4RJ++@*P(;4vyR9fmHx9TzxSDoV(0p&FSaFf*4oW+ja({5
zZFCEd$0sBbwh^~lzG*G0sBhe}`y_qse6U=2&gv+CZuIYeuh<@#^tH193yFIdv~^M4
zSF-~)KAu;Zxhbr<_&d{rn!MG<m2Ga0Wj)K&yPwoL-R(@zwO)lF)^S)ECn~Y4qE#9f
z$60XJ=)y^}ol{qsm{!d%4E7TBFwgFGx8d*=`ObNMXkXJQz5ZH-vF!Do*%1xp2hFbU
z_I1@4Q?~tC#uvJbZOgy+ogRFc(w(8()u6gj8BtVK9r}mrLK1Im{Nd=%iOIl>UsL(f
zlb#z(s|;#oXpP2@ykqjbf6mP+LzrjWn!$w1zChm)3D0@2H+yinJT~D6mp0SD$ds4V
zVM7Val6iG%FluUlP8YJFj%9Ibwk#};J=FTlDZ4qlX43^rK%<8&E%<PO%JfU9PutY#
z`2o&kj(!EpSui>1u1veG>b%ir=(0`au#akXw;QfZY;hN>>K5xSrWdatFrTiuvU1T9
z?y+`*&MEcY%Uw6(9l~;xOqPvC$xow;#=Fg`AI}AkIb{sHR(1!?%ig@S(=T&z=bL1u
zeaI*KJOfu36|l{QV8QX|!1yY+&Gt6#jjmfR6<a3-z4EwtUs7s}y6xs`mYps1%h|Hb
zO6+Y`8ERvEgztUx#vBSbBzcCFR`DiTxb+q_tK^nqF01H3ac{ja&;HCn%)>P?$|<Mi
zr8)=x$k?W#FKbIL3A#EG+}blw?%z>o^H?&PHJ*3@H{5)1`<`lEbzgqV-TR1Ze^X&N
z=A|^1r02eW+uLGF*g1d4+y|^ZEm4Q8qjZ9z>p$BcQ1Co2=Q~PE?)fj2A;;)vEe+gH
zpR|0)C5<PP*uf>Uo}#^1wI1wsqgL$KmWJ?iL4QgM^lR%o4l85FPOwMi&LhjbO*(3S
qf7ZlhNsqqpLE_tj54HN9a%I1pxPwF6&3--!{t5&O7llzFc-sGd_j+*v
--- a/mobile/android/base/resources/layout/tab_queue_prompt.xml
+++ b/mobile/android/base/resources/layout/tab_queue_prompt.xml
@@ -67,19 +67,19 @@
             android:id="@+id/bottom_container"
             android:layout_width="match_parent"
             android:layout_height="52dp"
             android:layout_gravity="center"
             android:layout_marginBottom="40dp">
 
             <ImageView
                 android:id="@+id/enabled_confirmation"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent"
-                android:gravity="center"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
                 android:src="@drawable/img_check"
                 android:visibility="gone" />
 
             <LinearLayout
                 android:id="@+id/button_container"
                 android:layout_width="match_parent"
                 android:layout_height="match_parent"
                 android:gravity="center_horizontal"
new file mode 100644
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -334,10 +334,32 @@ this.BrowserTestUtils = {
   /**
    *  Version of synthesizeMouse that uses a client point within the child
    *  window instead of a target as the offset. Otherwise, the arguments and
    *  return value are the same as synthesizeMouse.
    */
   synthesizeMouseAtPoint(offsetX, offsetY, event, browser)
   {
     return BrowserTestUtils.synthesizeMouse(null, offsetX, offsetY, event, browser);
+  },
+
+  /**
+   * Removes the given tab from its parent tabbrowser and
+   * waits until its final message has reached the parent.
+   */
+  removeTab(tab, options = {}) {
+    let dontRemove = options && options.dontRemove;
+
+    return new Promise(resolve => {
+      let {messageManager: mm, frameLoader} = tab.linkedBrowser;
+      mm.addMessageListener("SessionStore:update", function onMessage(msg) {
+        if (msg.targetFrameLoader == frameLoader && msg.data.isFinal) {
+          mm.removeMessageListener("SessionStore:update", onMessage);
+          resolve();
+        }
+      }, true);
+
+      if (!dontRemove && !tab.closing) {
+        tab.ownerDocument.defaultView.gBrowser.removeTab(tab);
+      }
+    });
   }
 };
--- a/toolkit/components/places/PlacesUtils.jsm
+++ b/toolkit/components/places/PlacesUtils.jsm
@@ -875,16 +875,18 @@ this.PlacesUtils = {
            aItemId == PlacesUtils.tagsFolderId ||
            aItemId == PlacesUtils.placesRootId;
   },
 
   /**
    * Set the POST data associated with a bookmark, if any.
    * Used by POST keywords.
    *   @param aBookmarkId
+   *
+   * @deprecated Use PlacesUtils.keywords.insert() API instead.
    */
   setPostDataForBookmark(aBookmarkId, aPostData) {
     if (!aPostData)
       throw new Error("Must provide valid POST data");
     // For now we don't have a unified API to create a keyword with postData,
     // thus here we can just try to complete a keyword that should already exist
     // without any post data.
     let stmt = PlacesUtils.history.DBConnection.createStatement(
@@ -918,16 +920,18 @@ this.PlacesUtils = {
       }
     }).catch(Cu.reportError);
   },
 
   /**
    * Get the POST data associated with a bookmark, if any.
    * @param aBookmarkId
    * @returns string of POST data if set for aBookmarkId. null otherwise.
+   *
+   * @deprecated Use PlacesUtils.keywords.fetch() API instead.
    */
   getPostDataForBookmark(aBookmarkId) {
     let stmt = PlacesUtils.history.DBConnection.createStatement(
       `SELECT k.post_data
        FROM moz_keywords k
        JOIN moz_places h ON h.id = k.place_id
        JOIN moz_bookmarks b ON b.fk = h.id
        WHERE b.id = :item_id`);
--- a/toolkit/components/places/nsINavBookmarksService.idl
+++ b/toolkit/components/places/nsINavBookmarksService.idl
@@ -528,28 +528,34 @@ interface nsINavBookmarksService : nsISu
   void getBookmarkIdsForURI(in nsIURI aURI, [optional] out unsigned long count,
                             [array, retval, size_is(count)] out long long bookmarks);
 
   /**
    * Associates the given keyword with the given bookmark.
    *
    * Use an empty keyword to clear the keyword associated with the URI.
    * In both of these cases, succeeds but does nothing if the URL/keyword is not found.
+   *
+   * @deprecated Use PlacesUtils.keywords.insert() API instead.
    */
   void setKeywordForBookmark(in long long aItemId, in AString aKeyword);
 
   /**
    * Retrieves the keyword for the given bookmark. Will be void string
    * (null in JS) if no such keyword is found.
+   *
+   * @deprecated Use PlacesUtils.keywords.fetch() API instead.
    */
   AString getKeywordForBookmark(in long long aItemId);
 
   /**
    * Returns the URI associated with the given keyword. Empty if no such
    * keyword is found.
+   *
+   * @deprecated Use PlacesUtils.keywords.fetch() API instead.
    */
   nsIURI getURIForKeyword(in AString keyword);
 
   /**
    * Adds a bookmark observer. If ownsWeak is false, the bookmark service will
    * keep an owning reference to the observer.  If ownsWeak is true, then
    * aObserver must implement nsISupportsWeakReference, and the bookmark
    * service will keep a weak reference to the observer.
--- a/toolkit/components/places/nsNavBookmarks.cpp
+++ b/toolkit/components/places/nsNavBookmarks.cpp
@@ -2440,16 +2440,18 @@ nsNavBookmarks::GetKeywordForBookmark(in
 NS_IMETHODIMP
 nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
                                  nsIURI** aURI)
 {
   NS_ENSURE_ARG_POINTER(aURI);
   NS_ENSURE_TRUE(!aUserCasedKeyword.IsEmpty(), NS_ERROR_INVALID_ARG);
   *aURI = nullptr;
 
+  PLACES_WARN_DEPRECATED();
+
   // Shortcuts are always lowercased internally.
   nsAutoString keyword(aUserCasedKeyword);
   ToLowerCase(keyword);
 
   nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
     "SELECT h.url "
     "FROM moz_places h "
     "JOIN moz_keywords k ON k.place_id = h.id "
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -397,21 +397,16 @@
             this._fastFind = Components.classes["@mozilla.org/typeaheadfind;1"]
                                        .createInstance(Components.interfaces.nsITypeAheadFind);
             this._fastFind.init(this.docShell);
           }
           return this._fastFind;
         ]]></getter>
       </property>
 
-      <field name="_permanentKey">({})</field>
-
-      <property name="permanentKey" readonly="true"
-                onget="return this._permanentKey;"/>
-
       <property name="outerWindowID" readonly="true">
         <getter><![CDATA[
           return this.contentWindow
                      .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                      .getInterface(Components.interfaces.nsIDOMWindowUtils)
                      .outerWindowID;
         ]]></getter>
       </property>
@@ -1091,18 +1086,17 @@
           // Fields which are built as a result of notifactions (pageshow/hide,
           // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
           // because these notifications are dispatched again once the docshells
           // are swapped.
           var fieldsToSwap = [
             "_docShell",
             "_webBrowserFind",
             "_contentWindow",
-            "_webNavigation",
-            "_permanentKey"
+            "_webNavigation"
           ];
 
           if (this.isRemoteBrowser) {
             fieldsToSwap.push(...[
               "_remoteWebNavigation",
               "_remoteWebProgressManager",
               "_remoteWebProgress",
               "_remoteFinder",
--- a/toolkit/content/widgets/editor.xml
+++ b/toolkit/content/widgets/editor.xml
@@ -129,19 +129,22 @@
       <property name="editortype"
                 onget="return this.getAttribute('editortype');"
                 onset="this.setAttribute('editortype', val); return val;"/>
       <property name="webNavigation"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
                 readonly="true"/>
       <property name="contentDocument" readonly="true"
                 onget="return this.webNavigation.document;"/>
-      <property name="docShell"
-                onget="return this.boxObject.docShell;"
-                readonly="true"/>
+      <property name="docShell" readonly="true">
+        <getter><![CDATA[
+          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+          return frameLoader ? frameLoader.docShell : null;
+        ]]></getter>
+      </property>
       <property name="currentURI"
                 readonly="true"
                 onget="return this.webNavigation.currentURI;"/>
       <property name="contentWindow"
                 readonly="true"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
       <property name="contentWindowAsCPOW"
                 readonly="true"
--- a/toolkit/content/widgets/general.xml
+++ b/toolkit/content/widgets/general.xml
@@ -80,19 +80,22 @@
           this._lightweightTheme = null;
         }
       ]]></destructor>
     </implementation>
   </binding>
 
   <binding id="iframe" role="outerdoc">
     <implementation>
-      <property name="docShell"
-                readonly="true"
-                onget="return this.boxObject.docShell"/>
+      <property name="docShell" readonly="true">
+        <getter><![CDATA[
+          let frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+          return frameLoader ? frameLoader.docShell : null;
+        ]]></getter>
+      </property>
       <property name="contentWindow"
                 readonly="true"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
       <property name="webNavigation"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
                 readonly="true"/>
       <property name="contentDocument" readonly="true"
                 onget="return this.webNavigation.document;"/>
--- a/toolkit/devtools/server/actors/script.js
+++ b/toolkit/devtools/server/actors/script.js
@@ -4741,16 +4741,20 @@ function BreakpointActor(aThreadActor, a
   this.condition = null;
   this.isPending = true;
 }
 
 BreakpointActor.prototype = {
   actorPrefix: "breakpoint",
   condition: null,
 
+  disconnect: function () {
+    this.removeScripts();
+  },
+
   hasScript: function (aScript) {
     return this.scripts.has(aScript);
   },
 
   /**
    * Called when this same breakpoint is added to another Debugger.Script
    * instance.
    *
--- a/toolkit/modules/tests/browser/browser_RemotePageManager.js
+++ b/toolkit/modules/tests/browser/browser_RemotePageManager.js
@@ -44,16 +44,26 @@ function waitForPage(pages) {
       waitForMessage(target, "RemotePage:Load").then(() => resolve(target));
     }
 
     pages.addMessageListener("RemotePage:Init", listener);
     gBrowser.selectedTab = gBrowser.addTab(TEST_URL);
   });
 }
 
+function swapDocShells(browser1, browser2) {
+  // Swap frameLoaders.
+  browser1.swapDocShells(browser2);
+
+  // Swap permanentKeys.
+  let tmp = browser1.permanentKey;
+  browser1.permanentKey = browser2.permanentKey;
+  browser2.permanentKey = tmp;
+}
+
 // Test that opening a page creates a port, sends the load event and then
 // navigating to a new page sends the unload event. Going back should create a
 // new port
 add_task(function* init_navigate() {
   let port = yield waitForPort(TEST_URL);
   is(port.browser, gBrowser.selectedBrowser, "Port is for the correct browser");
 
   let loaded = new Promise(resolve => {
@@ -188,34 +198,34 @@ add_task(function* browser_switch() {
   is(message.data.value, "om nom", "Should have the right cookie");
 
   port1.addMessageListener("Cookie", failOnMessage);
   port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 });
   message = yield waitForMessage(port2, "Cookie");
   port1.removeMessageListener("Cookie", failOnMessage);
   is(message.data.value, "om nom nom", "Should have the right cookie");
 
-  browser1.swapDocShells(browser2);
+  swapDocShells(browser1, browser2);
   is(port1.browser, browser2, "Should have noticed the swap");
   is(port2.browser, browser1, "Should have noticed the swap");
 
   // Cookies should have stayed the same
   port2.addMessageListener("Cookie", failOnMessage);
   port1.sendAsyncMessage("GetCookie");
   message = yield waitForMessage(port1, "Cookie");
   port2.removeMessageListener("Cookie", failOnMessage);
   is(message.data.value, "om nom", "Should have the right cookie");
 
   port1.addMessageListener("Cookie", failOnMessage);
   port2.sendAsyncMessage("GetCookie", { str: "foobaz", counter: 5 });
   message = yield waitForMessage(port2, "Cookie");
   port1.removeMessageListener("Cookie", failOnMessage);
   is(message.data.value, "om nom nom", "Should have the right cookie");
 
-  browser1.swapDocShells(browser2);
+  swapDocShells(browser1, browser2);
   is(port1.browser, browser1, "Should have noticed the swap");
   is(port2.browser, browser2, "Should have noticed the swap");
 
   // Cookies should have stayed the same
   port2.addMessageListener("Cookie", failOnMessage);
   port1.sendAsyncMessage("GetCookie");
   message = yield waitForMessage(port1, "Cookie");
   port2.removeMessageListener("Cookie", failOnMessage);
--- a/toolkit/themes/shared/in-content/common.inc.css
+++ b/toolkit/themes/shared/in-content/common.inc.css
@@ -396,17 +396,16 @@ xul|textbox[disabled="true"] {
   opacity: 0.5;
 }
 
 /* Links */
 
 html|a,
 .text-link,
 .inline-link {
-  line-height: 22px;
   color: #0095dd;
   text-decoration: none;
 }
 
 html|a:hover,
 .text-link:hover,
 .inline-link:hover {
   color: #178ce5;
--- a/xpcom/io/nsLinebreakConverter.cpp
+++ b/xpcom/io/nsLinebreakConverter.cpp
@@ -95,31 +95,31 @@ ConvertBreaks(const T* aInSrc, int32_t& 
               const char* aDestBreak)
 {
   NS_ASSERTION(aInSrc && aSrcBreak && aDestBreak, "Got a null string");
 
   T* resultString = nullptr;
 
   // handle the no conversion case
   if (nsCRT::strcmp(aSrcBreak, aDestBreak) == 0) {
-    resultString = (T*)moz_xmalloc(sizeof(T) * aIoLen);
+    resultString = (T*)malloc(sizeof(T) * aIoLen);
     if (!resultString) {
       return nullptr;
     }
     memcpy(resultString, aInSrc, sizeof(T) * aIoLen); // includes the null, if any
     return resultString;
   }
 
   int32_t srcBreakLen = strlen(aSrcBreak);
   int32_t destBreakLen = strlen(aDestBreak);
 
   // handle the easy case, where the string length does not change, and the
   // breaks are only 1 char long, i.e. CR <-> LF
   if (srcBreakLen == destBreakLen && srcBreakLen == 1) {
-    resultString = (T*)moz_xmalloc(sizeof(T) * aIoLen);
+    resultString = (T*)malloc(sizeof(T) * aIoLen);
     if (!resultString) {
       return nullptr;
     }
 
     const T* src = aInSrc;
     const T* srcEnd = aInSrc + aIoLen;  // includes null, if any
     T* dst = resultString;
 
@@ -139,17 +139,17 @@ ConvertBreaks(const T* aInSrc, int32_t& 
   } else {
     // src and dest termination is different length. Do it a slower way.
 
     // count linebreaks in src. Assumes that chars in 2-char linebreaks are unique.
     int32_t numLinebreaks = CountLinebreaks(aInSrc, aIoLen, aSrcBreak);
 
     int32_t newBufLen =
       aIoLen - (numLinebreaks * srcBreakLen) + (numLinebreaks * destBreakLen);
-    resultString = (T*)moz_xmalloc(sizeof(T) * newBufLen);
+    resultString = (T*)malloc(sizeof(T) * newBufLen);
     if (!resultString) {
       return nullptr;
     }
 
     const T* src = aInSrc;
     const T* srcEnd = aInSrc + aIoLen;  // includes null, if any
     T* dst = resultString;
 
@@ -230,17 +230,17 @@ ConvertUnknownBreaks(const T* aInSrc, in
       // Lone LF
       finalLen += destBreakLen;
     } else {
       finalLen++;
     }
     src++;
   }
 
-  T* resultString = (T*)moz_xmalloc(sizeof(T) * finalLen);
+  T* resultString = (T*)malloc(sizeof(T) * finalLen);
   if (!resultString) {
     return nullptr;
   }
 
   src = aInSrc;
   srcEnd = aInSrc + aIoLen;  // includes null, if any
 
   T* dst = resultString;
--- a/xpcom/io/nsStreamUtils.cpp
+++ b/xpcom/io/nsStreamUtils.cpp
@@ -848,16 +848,20 @@ NS_FillArray(FallibleTArray<char>& aDest
   MOZ_ASSERT(aDest.Length() <= aDest.Capacity(), "buffer overflow");
   return rv;
 }
 
 nsresult
 NS_CloneInputStream(nsIInputStream* aSource, nsIInputStream** aCloneOut,
                     nsIInputStream** aReplacementOut)
 {
+  if (NS_WARN_IF(!aSource)) {
+    return NS_ERROR_FAILURE;
+  }
+
   // Attempt to perform the clone directly on the source stream
   nsCOMPtr<nsICloneableInputStream> cloneable = do_QueryInterface(aSource);
   if (cloneable && cloneable->GetCloneable()) {
     if (aReplacementOut) {
       *aReplacementOut = nullptr;
     }
     return cloneable->Clone(aCloneOut);
   }
--- a/xpcom/tests/gtest/TestCloneInputStream.cpp
+++ b/xpcom/tests/gtest/TestCloneInputStream.cpp
@@ -7,16 +7,24 @@
 #include "gtest/gtest.h"
 #include "Helpers.h"
 #include "mozilla/unused.h"
 #include "nsICloneableInputStream.h"
 #include "nsNetUtil.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
 
+TEST(CloneInputStream, InvalidInput)
+{
+  nsCOMPtr<nsIInputStream> clone;
+  nsresult rv = NS_CloneInputStream(nullptr, getter_AddRefs(clone));
+  ASSERT_TRUE(NS_FAILED(rv));
+  ASSERT_FALSE(clone);
+}
+
 TEST(CloneInputStream, CloneableInput)
 {
   nsTArray<char> inputData;
   testing::CreateData(4 * 1024, inputData);
   nsDependentCSubstring inputString(inputData.Elements(), inputData.Length());
 
   nsCOMPtr<nsIInputStream> stream;
   nsresult rv = NS_NewCStringInputStream(getter_AddRefs(stream), inputString);