Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 02 Oct 2015 13:33:20 -0700
changeset 265831 2308353160d2c7b75d6471874af62c965159e2ee
parent 265801 58acf8a0176e5cc381e33814140750fb781b72be (current diff)
parent 265713 0333102b6bf511ba51d2e8a80db74de6ac9c3e74 (diff)
child 265832 0010c0cb259e28faf764949df54687e3a21a2d0a
push id66032
push userkwierso@gmail.com
push dateFri, 02 Oct 2015 20:43:24 +0000
treeherdermozilla-inbound@03e8acfc181f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone44.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 inbound to central, a=merge
browser/base/content/tabbrowser.xml
dom/html/test/file_fullscreen-ancestor-stacking-context.html
dom/media/apple/AppleDecoder.cpp
dom/media/apple/AppleDecoder.h
dom/media/apple/AppleMP3Reader.cpp
dom/media/apple/AppleMP3Reader.h
dom/media/apple/moz.build
layout/style/full-screen-override.css
testing/web-platform/meta/content-security-policy/media-src/media-src-7_2.html.ini
--- a/accessible/ipc/ProxyAccessible.h
+++ b/accessible/ipc/ProxyAccessible.h
@@ -213,20 +213,35 @@ public:
 
   uint32_t StartOffset(bool* aOk);
 
   uint32_t EndOffset(bool* aOk);
 
   bool IsLinkValid();
 
   // XXX checking mRole alone may not result in same behavior as Accessibles
-  // due to ARIA roles
-  inline bool IsTable() const { return mRole == roles::TABLE; }
-  inline bool IsTableRow() const { return mRole == roles::ROW; }
-  inline bool IsTableCell() const { return mRole == roles::CELL; }
+  // due to ARIA roles. See bug 1210477.
+  inline bool IsTable() const
+  {
+    return mRole == roles::TABLE || mRole == roles::MATHML_TABLE;
+  }
+  inline bool IsTableRow() const
+  {
+    return (mRole == roles::ROW ||
+            mRole == roles::MATHML_TABLE_ROW ||
+            mRole == roles::MATHML_LABELED_ROW);
+  }
+  inline bool IsTableCell() const
+  {
+    return (mRole == roles::CELL ||
+            mRole == roles::COLUMNHEADER ||
+            mRole == roles::ROWHEADER ||
+            mRole == roles::GRID_CELL ||
+            mRole == roles::MATHML_CELL);
+  }
 
   uint32_t AnchorCount(bool* aOk);
 
   void AnchorURIAt(uint32_t aIndex, nsCString& aURI, bool* aOk);
 
   ProxyAccessible* AnchorAt(uint32_t aIndex);
 
   uint32_t LinkCount();
--- a/b2g/components/FxAccountsMgmtService.jsm
+++ b/b2g/components/FxAccountsMgmtService.jsm
@@ -96,17 +96,17 @@ this.FxAccountsMgmtService = {
     // Bug 1202450 dirty hack because Gaia is sending getAccounts.
     if (data.method == "getAccounts") {
       data.method = "getAccount";
     }
 
     switch(data.method) {
       case "getAssertion":
         let principal = Services.scriptSecurityManager.getSystemPrincipal();
-        let audience = msg.audience || principal.originNoSuffix;
+        let audience = data.audience || principal.originNoSuffix;
         FxAccountsManager.getAssertion(audience, principal, {
           silent: msg.silent || false
         }).then(result => {
           self._onFulfill(msg.id, result);
         }, reason => {
           self._onReject(msg.id, reason);
         });
         break;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4955,16 +4955,20 @@ nsBrowserAccess.prototype = {
       return browser.QueryInterface(Ci.nsIFrameLoaderOwner);
 
     return null;
   },
 
   isTabContentWindow: function (aWindow) {
     return gBrowser.browsers.some(browser => browser.contentWindow == aWindow);
   },
+
+  canClose() {
+    return CanCloseWindow();
+  },
 }
 
 function getTogglableToolbars() {
   let toolbarNodes = Array.slice(gNavToolbox.childNodes);
   toolbarNodes = toolbarNodes.concat(gNavToolbox.externalToolbars);
   toolbarNodes = toolbarNodes.filter(node => node.getAttribute("toolbarname"));
   return toolbarNodes;
 }
@@ -6419,47 +6423,59 @@ var IndexedDBPromptHelper = {
 
     // Set the timeoutId after the popup has been created, and use the long
     // timeout value. If the user doesn't notice the popup after this amount of
     // time then it is most likely not visible and we want to alert the page.
     timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
   }
 };
 
+function CanCloseWindow()
+{
+  // Avoid redundant calls to canClose from showing multiple
+  // PermitUnload dialogs.
+  if (window.skipNextCanClose) {
+    return true;
+  }
+
+  for (let browser of gBrowser.browsers) {
+    let {permitUnload, timedOut} = browser.permitUnload();
+    if (timedOut) {
+      return true;
+    }
+    if (!permitUnload) {
+      return false;
+    }
+  }
+  return true;
+}
+
 function WindowIsClosing()
 {
   if (TabView.isVisible()) {
     TabView.hide();
     return false;
   }
 
   if (!closeWindow(false, warnAboutClosingWindow))
     return false;
 
-  // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process
-  if (gMultiProcessBrowser)
+  // In theory we should exit here and the Window's internal Close
+  // method should trigger canClose on nsBrowserAccess. However, by
+  // that point it's too late to be able to show a prompt for
+  // PermitUnload. So we do it here, when we still can.
+  if (CanCloseWindow()) {
+    // This flag ensures that the later canClose call does nothing.
+    // It's only needed to make tests pass, since they detect the
+    // prompt even when it's not actually shown.
+    window.skipNextCanClose = true;
     return true;
-
-  for (let browser of gBrowser.browsers) {
-    let ds = browser.docShell;
-    // Passing true to permitUnload indicates we plan on closing the window.
-    // This means that once unload is permitted, all further calls to
-    // permitUnload will be ignored. This avoids getting multiple prompts
-    // to unload the page.
-    if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) {
-      // ... however, if the user aborts closing, we need to undo that,
-      // to ensure they get prompted again when we next try to close the window.
-      // We do this on the window's toplevel docshell instead of on the tab, so
-      // that all tabs we iterated before will get this reset.
-      window.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
-      return false;
-    }
-  }
-
-  return true;
+  }
+
+  return false;
 }
 
 /**
  * Checks if this is the last full *browser* window around. If it is, this will
  * be communicated like quitting. Otherwise, we warn about closing multiple tabs.
  * @returns true if closing can proceed, false if it got cancelled.
  */
 function warnAboutClosingWindow() {
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -114,17 +114,17 @@
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple-visible"
                 oncommand="gBrowser.reloadAllTabs();"/>
       <menuitem id="context_bookmarkAllTabs"
                 label="&bookmarkAllTabs.label;"
                 accesskey="&bookmarkAllTabs.accesskey;"
                 command="Browser:BookmarkAllTabs"/>
       <menuitem id="context_closeTabsToTheEnd" label="&closeTabsToTheEnd.label;" accesskey="&closeTabsToTheEnd.accesskey;"
-                oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab);"/>
+                oncommand="gBrowser.removeTabsToTheEndFrom(TabContextMenu.contextTab, {animate: true});"/>
       <menuitem id="context_closeOtherTabs" label="&closeOtherTabs.label;" accesskey="&closeOtherTabs.accesskey;"
                 oncommand="gBrowser.removeAllTabsBut(TabContextMenu.contextTab);"/>
       <menuseparator/>
       <menuitem id="context_undoCloseTab"
                 label="&undoCloseTab.label;"
                 accesskey="&undoCloseTab.accesskey;"
                 observes="History:UndoCloseTab"/>
       <menuitem id="context_closeTab" label="&closeTab.label;" accesskey="&closeTab.accesskey;"
--- a/browser/base/content/chatWindow.xul
+++ b/browser/base/content/chatWindow.xul
@@ -126,16 +126,21 @@ chatBrowserAccess.prototype = {
   openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
     let browser = this._openURIInNewTab(aURI, aWhere);
     return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
   },
 
   isTabContentWindow: function (aWindow) {
     return this.contentWindow == aWindow;
   },
+
+  canClose() {
+    let {BrowserUtils} = Cu.import("resource://gre/modules/BrowserUtils.jsm", {});
+    return BrowserUtils.canCloseWindow(window);
+  },
 };
 
 </script>
 
 #include browser-sets.inc
 
 #ifdef XP_MACOSX
 #include browser-menubar.inc
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -1,9 +1,9 @@
-// -*- indent-tabs-mode: nil; js-indent-level: 4 -*-
+// -*- indent-tabs-mode: nil; js-indent-level: 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/. */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
@@ -562,38 +562,27 @@ Sanitizer.prototype = {
       get canClear()
       {
         return true;
       }
     },
     openWindows: {
       privateStateForNewWindow: "non-private",
       _canCloseWindow: function(aWindow) {
-        // Bug 967873 - Proxy nsDocumentViewer::PermitUnload to the child process
-        if (!aWindow.gMultiProcessBrowser) {
-          // Cargo-culted out of browser.js' WindowIsClosing because we don't care
-          // about TabView or the regular 'warn me before closing windows with N tabs'
-          // stuff here, and more importantly, we want to set aCallerClosesWindow to true
-          // when calling into permitUnload:
-          for (let browser of aWindow.gBrowser.browsers) {
-            let ds = browser.docShell;
-            // 'true' here means we will be closing the window soon, so please don't dispatch
-            // another onbeforeunload event when we do so. If unload is *not* permitted somewhere,
-            // we will reset the flag that this triggers everywhere so that we don't interfere
-            // with the browser after all:
-            if (ds.contentViewer && !ds.contentViewer.permitUnload(true)) {
-              return false;
-            }
-          }
+        if (aWindow.CanCloseWindow()) {
+          // We already showed PermitUnload for the window, so let's
+          // make sure we don't do it again when we actually close the
+          // window.
+          aWindow.skipNextCanClose = true;
+          return true;
         }
-        return true;
       },
       _resetAllWindowClosures: function(aWindowList) {
         for (let win of aWindowList) {
-          win.getInterface(Ci.nsIDocShell).contentViewer.resetCloseWindow();
+          win.skipNextCanClose = false;
         }
       },
       clear: Task.async(function*() {
         // NB: this closes all *browser* windows, not other windows like the library, about window,
         // browser console, etc.
 
         // Keep track of the time in case we get stuck in la-la-land because of onbeforeunload
         // dialogs
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1185,35 +1185,39 @@
                 }
               });
               this.mCurrentTab.dispatchEvent(event);
 
               this._tabAttrModified(oldTab, ["selected"]);
               this._tabAttrModified(this.mCurrentTab, ["selected"]);
 
               if (oldBrowser != newBrowser &&
-                  oldBrowser.docShell &&
-                  oldBrowser.docShell.contentViewer.inPermitUnload) {
-                // Since the user is switching away from a tab that has
-                // a beforeunload prompt active, we remove the prompt.
-                // This prevents confusing user flows like the following:
-                //   1. User attempts to close Firefox
-                //   2. User switches tabs (ingoring a beforeunload prompt)
-                //   3. User returns to tab, presses "Leave page"
-                let promptBox = this.getTabModalPromptBox(oldBrowser);
-                let prompts = promptBox.listPrompts();
-                // There might not be any prompts here if the tab was closed
-                // while in an onbeforeunload prompt, which will have
-                // destroyed aforementioned prompt already, so check there's
-                // something to remove, first:
-                if (prompts.length) {
-                  // NB: This code assumes that the beforeunload prompt
-                  //     is the top-most prompt on the tab.
-                  prompts[prompts.length - 1].abortPrompt();
-                }
+                  oldBrowser.getInPermitUnload) {
+                oldBrowser.getInPermitUnload(inPermitUnload => {
+                  if (!inPermitUnload) {
+                    return;
+                  }
+                  // Since the user is switching away from a tab that has
+                  // a beforeunload prompt active, we remove the prompt.
+                  // This prevents confusing user flows like the following:
+                  //   1. User attempts to close Firefox
+                  //   2. User switches tabs (ingoring a beforeunload prompt)
+                  //   3. User returns to tab, presses "Leave page"
+                  let promptBox = this.getTabModalPromptBox(oldBrowser);
+                  let prompts = promptBox.listPrompts();
+                  // There might not be any prompts here if the tab was closed
+                  // while in an onbeforeunload prompt, which will have
+                  // destroyed aforementioned prompt already, so check there's
+                  // something to remove, first:
+                  if (prompts.length) {
+                    // NB: This code assumes that the beforeunload prompt
+                    //     is the top-most prompt on the tab.
+                    prompts[prompts.length - 1].abortPrompt();
+                  }
+                });
               }
 
               oldBrowser._urlbarFocused = (gURLBar && gURLBar.focused);
               if (this.isFindBarInitialized(oldTab)) {
                 let findBar = this.getFindBar(oldTab);
                 oldTab._findBarFocused = (!findBar.hidden &&
                   findBar._findField.getAttribute("focused") == "true");
               }
@@ -2072,22 +2076,23 @@
             }
             return tabsToEnd.reverse();
           ]]>
         </body>
       </method>
 
       <method name="removeTabsToTheEndFrom">
         <parameter name="aTab"/>
+        <parameter name="aParams"/>
         <body>
           <![CDATA[
             if (this.warnAboutClosingTabs(this.closingTabsEnum.TO_END, aTab)) {
               let tabs = this.getTabsToTheEndFrom(aTab);
               for (let i = tabs.length - 1; i >= 0; --i) {
-                this.removeTab(tabs[i], {animate: true});
+                this.removeTab(tabs[i], aParams);
               }
             }
           ]]>
         </body>
       </method>
 
       <method name="removeAllTabsBut">
         <parameter name="aTab"/>
@@ -2125,29 +2130,30 @@
       <method name="removeTab">
         <parameter name="aTab"/>
         <parameter name="aParams"/>
         <body>
           <![CDATA[
             if (aParams) {
               var animate = aParams.animate;
               var byMouse = aParams.byMouse;
+              var skipPermitUnload = aParams.skipPermitUnload;
             }
 
             // Handle requests for synchronously removing an already
             // asynchronously closing tab.
             if (!animate &&
                 aTab.closing) {
               this._endRemoveTab(aTab);
               return;
             }
 
             var isLastTab = (this.tabs.length - this._removingTabs.length == 1);
 
-            if (!this._beginRemoveTab(aTab, false, null, true))
+            if (!this._beginRemoveTab(aTab, false, null, true, skipPermitUnload))
               return;
 
             if (!aTab.pinned && !aTab.hidden && aTab._fullyOpen && byMouse)
               this.tabContainer._lockTabSizing(aTab);
             else
               this.tabContainer._unlockTabSizing();
 
             if (!animate /* the caller didn't opt in */ ||
@@ -2185,16 +2191,17 @@
         false
       </field>
 
       <method name="_beginRemoveTab">
         <parameter name="aTab"/>
         <parameter name="aTabWillBeMoved"/>
         <parameter name="aCloseWindowWithLastTab"/>
         <parameter name="aCloseWindowFastpath"/>
+        <parameter name="aSkipPermitUnload"/>
         <body>
           <![CDATA[
             if (aTab.closing ||
                 this._windowIsClosing)
               return false;
 
             var browser = this.getBrowserForTab(aTab);
 
@@ -2215,33 +2222,30 @@
                 // cancels the operation.  We are finished here in both cases.
                 this._windowIsClosing = window.closeWindow(true, window.warnAboutClosingWindow);
                 return null;
               }
 
               newTab = true;
             }
 
-            if (!aTab._pendingPermitUnload && !aTabWillBeMoved) {
-              let ds = browser.docShell;
-              if (ds && ds.contentViewer) {
-                // We need to block while calling permitUnload() because it
-                // processes the event queue and may lead to another removeTab()
-                // call before permitUnload() returns.
-                aTab._pendingPermitUnload = true;
-                let permitUnload = ds.contentViewer.permitUnload();
-                delete aTab._pendingPermitUnload;
-                // If we were closed during onbeforeunload, we return false now
-                // so we don't (try to) close the same tab again. Of course, we
-                // also stop if the unload was cancelled by the user:
-                if (aTab.closing || !permitUnload) {
-                  // NB: deliberately keep the _closedDuringPermitUnload set to
-                  // true so we keep exiting early in case of multiple calls.
-                  return false;
-                }
+            if (!aTab._pendingPermitUnload && !aTabWillBeMoved && !aSkipPermitUnload) {
+              // We need to block while calling permitUnload() because it
+              // processes the event queue and may lead to another removeTab()
+              // call before permitUnload() returns.
+              aTab._pendingPermitUnload = true;
+              let {permitUnload} = browser.permitUnload();
+              delete aTab._pendingPermitUnload;
+              // If we were closed during onbeforeunload, we return false now
+              // so we don't (try to) close the same tab again. Of course, we
+              // also stop if the unload was cancelled by the user:
+              if (aTab.closing || !permitUnload) {
+                // NB: deliberately keep the _closedDuringPermitUnload set to
+                // true so we keep exiting early in case of multiple calls.
+                return false;
               }
             }
 
             aTab.closing = true;
             this._removingTabs.push(aTab);
             this._visibleTabs = null; // invalidate cache
 
             // Invalidate hovered tab state tracking for this closing tab.
@@ -4049,23 +4053,29 @@
                 return;
               let titleChanged = this.setTabTitle(tab);
               if (titleChanged && !tab.selected && !tab.hasAttribute("busy"))
                 tab.setAttribute("titlechanged", "true");
               break;
             }
             case "DOMWindowClose": {
               if (this.tabs.length == 1) {
+                // We already did PermitUnload in the content process
+                // for this tab (the only one in the window). So we don't
+                // need to do it again for any tabs.
+                window.skipNextCanClose = true;
                 window.close();
                 return;
               }
 
               let tab = this.getTabForBrowser(browser);
               if (tab) {
-                this.removeTab(tab);
+                // Skip running PermitUnload since it already happened in
+                // the content process.
+                this.removeTab(tab, {skipPermitUnload: true});
               }
               break;
             }
             case "contextmenu": {
               let spellInfo = aMessage.data.spellInfo;
               if (spellInfo)
                 spellInfo.target = aMessage.target.messageManager;
               let documentURIObject = makeURI(aMessage.data.docLocation,
@@ -4339,22 +4349,28 @@
     </implementation>
 
     <handlers>
       <handler event="DOMWindowClose" phase="capturing">
         <![CDATA[
           if (!event.isTrusted)
             return;
 
-          if (this.tabs.length == 1)
+          if (this.tabs.length == 1) {
+            // We already did PermitUnload in nsGlobalWindow::Close
+            // for this tab. There are no other tabs we need to do
+            // PermitUnload for.
+            window.skipNextCanClose = true;
             return;
+          }
 
           var tab = this._getTabForContentWindow(event.target);
           if (tab) {
-            this.removeTab(tab);
+            // Skip running PermitUnload since it already happened.
+            this.removeTab(tab, {skipPermitUnload: true});
             event.preventDefault();
           }
         ]]>
       </handler>
       <handler event="DOMWillOpenModalDialog" phase="capturing">
         <![CDATA[
           if (!event.isTrusted)
             return;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -143,17 +143,16 @@ skip-if = e10s # Bug 1101993 - times out
 [browser_autocomplete_enter_race.js]
 [browser_autocomplete_no_title.js]
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
 [browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
-skip-if = e10s # bug 967873 means permitUnload doesn't work in e10s mode
 [browser_blob-channelname.js]
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
 [browser_bug331772_xul_tooltiptext_in_html.js]
--- a/browser/base/content/test/general/browser_bug406216.js
+++ b/browser/base/content/test/general/browser_bug406216.js
@@ -17,17 +17,17 @@ const URIS = ["about:config",
 function test() {
   waitForExplicitFinish();
   URIS.forEach(addTab);
 }
 
 function addTab(aURI, aIndex) {
   var tab = gBrowser.addTab(aURI);
   if (aIndex == 0)
-    gBrowser.removeTab(gBrowser.tabs[0]);
+    gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
 
   tab.linkedBrowser.addEventListener("load", function (event) {
     event.currentTarget.removeEventListener("load", arguments.callee, true);
     if (++count == URIS.length)
       executeSoon(doTabsTest);
   }, true);
 }
 
@@ -36,19 +36,19 @@ function doTabsTest() {
 
   // sample of "close related tabs" feature
   gBrowser.tabContainer.addEventListener("TabClose", function (event) {
     event.currentTarget.removeEventListener("TabClose", arguments.callee, true);
     var closedTab = event.originalTarget;
     var scheme = closedTab.linkedBrowser.currentURI.scheme;
     Array.slice(gBrowser.tabs).forEach(function (aTab) {
       if (aTab != closedTab && aTab.linkedBrowser.currentURI.scheme == scheme)
-        gBrowser.removeTab(aTab);
+        gBrowser.removeTab(aTab, {skipPermitUnload: true});
     });
   }, true);
 
-  gBrowser.removeTab(gBrowser.tabs[0]);
+  gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
   is(gBrowser.tabs.length, 1, "Related tabs are not closed unexpectedly");
 
   gBrowser.addTab("about:blank");
-  gBrowser.removeTab(gBrowser.tabs[0]);
+  gBrowser.removeTab(gBrowser.tabs[0], {skipPermitUnload: true});
   finish();
 }
--- a/browser/base/content/test/general/browser_bug432599.js
+++ b/browser/base/content/test/general/browser_bug432599.js
@@ -94,17 +94,17 @@ function checkBookmarksPanel(invoker, ph
       popupElement.removeEventListener("popuphidden", arguments.callee, false);
       if (phase < 4) {
         checkBookmarksPanel(invoker, phase + 1);
       } else {
         ++currentInvoker;
         if (currentInvoker < invokers.length) {
           checkBookmarksPanel(invokers[currentInvoker], 1);
         } else {
-          gBrowser.removeCurrentTab();
+          gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
           PlacesUtils.bookmarks.removeItem(bookmarkId);
           executeSoon(finish);
         }
       }
     }
   };
 
   switch (phase) {
--- a/browser/base/content/test/general/browser_bug455852.js
+++ b/browser/base/content/test/general/browser_bug455852.js
@@ -1,16 +1,20 @@
-function test() {
+add_task(function*() {
   is(gBrowser.tabs.length, 1, "one tab is open");
 
   gBrowser.selectedBrowser.focus();
   isnot(document.activeElement, gURLBar.inputField, "location bar is not focused");
 
   var tab = gBrowser.selectedTab;
   gPrefService.setBoolPref("browser.tabs.closeWindowWithLastTab", false);
+
+  let tabClosedPromise = BrowserTestUtils.removeTab(tab, {dontRemove: true});
   EventUtils.synthesizeKey("w", { accelKey: true });
+  yield tabClosedPromise;
+
   is(tab.parentNode, null, "ctrl+w removes the tab");
   is(gBrowser.tabs.length, 1, "a new tab has been opened");
   is(document.activeElement, gURLBar.inputField, "location bar is focused for the new tab");
 
   if (gPrefService.prefHasUserValue("browser.tabs.closeWindowWithLastTab"))
     gPrefService.clearUserPref("browser.tabs.closeWindowWithLastTab");
-}
+});
--- a/browser/base/content/test/general/browser_bug521216.js
+++ b/browser/base/content/test/general/browser_bug521216.js
@@ -13,20 +13,23 @@ function test() {
 
 function record(aName) {
   info("got " + aName);
   if (actual.indexOf(aName) == -1)
     actual.push(aName);
   if (actual.length == expected.length) {
     is(actual.toString(), expected.toString(),
        "got events and progress notifications in expected order");
-    gBrowser.removeTab(tab);
-    gBrowser.removeTabsProgressListener(progressListener);
-    gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
-    finish();
+
+    executeSoon(function(tab) {
+      gBrowser.removeTab(tab);
+      gBrowser.removeTabsProgressListener(progressListener);
+      gBrowser.tabContainer.removeEventListener("TabOpen", TabOpen, false);
+      finish();
+    }.bind(null, tab));
   }
 }
 
 function TabOpen(aEvent) {
   if (aEvent.target == tab)
     record(arguments.callee.name);
 }
 
--- a/browser/base/content/test/general/browser_bug533232.js
+++ b/browser/base/content/test/general/browser_bug533232.js
@@ -1,36 +1,36 @@
 function test() {
   var tab1 = gBrowser.selectedTab;
   var tab2 = gBrowser.addTab();
   var childTab1;
   var childTab2;
 
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab1),
      "closing a tab next to its parent selects the parent");
 
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = tab2;
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab2),
      "closing a tab next to its parent doesn't select the parent if another tab had been selected ad interim");
 
   gBrowser.selectedTab = tab1;
   childTab1 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   childTab2 = gBrowser.addTab("about:blank", { relatedToCurrent: true });
   gBrowser.selectedTab = childTab1;
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(childTab2),
      "closing a tab next to its parent selects the next tab with the same parent");
-  gBrowser.removeCurrentTab();
+  gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
   is(idx(gBrowser.selectedTab), idx(tab2),
      "closing the last tab in a set of child tabs doesn't go back to the parent");
 
-  gBrowser.removeTab(tab2);
+  gBrowser.removeTab(tab2, { skipPermitUnload: true });
 }
 
 function idx(tab) {
   return Array.indexOf(gBrowser.tabs, tab);
 }
--- a/browser/base/content/test/general/browser_bug585558.js
+++ b/browser/base/content/test/general/browser_bug585558.js
@@ -9,16 +9,23 @@ function addTab(aURL) {
 }
 
 function testAttrib(elem, attrib, attribValue, msg) {
   is(elem.hasAttribute(attrib), attribValue, msg);
 }
 
 function test() {
   waitForExplicitFinish();
+
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   // Add several new tabs in sequence, hiding some, to ensure that the
   // correct attributes get set
 
   addTab("http://mochi.test:8888/#0");
   addTab("http://mochi.test:8888/#1");
   addTab("http://mochi.test:8888/#2");
--- a/browser/base/content/test/general/browser_bug597218.js
+++ b/browser/base/content/test/general/browser_bug597218.js
@@ -1,15 +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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // establish initial state
   is(gBrowser.tabs.length, 1, "we start with one tab");
   
   // create a tab
   let tab = gBrowser.loadOneTab("about:blank");
   ok(!tab.hidden, "tab starts out not hidden");
   is(gBrowser.tabs.length, 2, "we now have two tabs");
 
--- a/browser/base/content/test/general/browser_bug676619.js
+++ b/browser/base/content/test/general/browser_bug676619.js
@@ -1,9 +1,10 @@
 function test () {
+  requestLongerTimeout(2);
   waitForExplicitFinish();
 
   var isHTTPS = false;
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function () {
     if (isHTTPS) {
       gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
--- a/browser/base/content/test/general/browser_hide_removing.js
+++ b/browser/base/content/test/general/browser_hide_removing.js
@@ -2,16 +2,22 @@
  * 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/. */
 
 // Bug 587922: tabs don't get removed if they're hidden
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // Add a tab that will get removed and hidden
   let testTab = gBrowser.addTab("about:blank", {skipAnimation: true});
   is(gBrowser.visibleTabs.length, 2, "just added a tab, so 2 tabs");
   gBrowser.selectedTab = testTab;
 
   let numVisBeforeHide, numVisAfterHide;
   gBrowser.tabContainer.addEventListener("TabSelect", function() {
     gBrowser.tabContainer.removeEventListener("TabSelect", arguments.callee, false);
--- a/browser/base/content/test/general/browser_relatedTabs.js
+++ b/browser/base/content/test/general/browser_relatedTabs.js
@@ -1,49 +1,52 @@
 /* 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() {
+add_task(function*() {
   is(gBrowser.tabs.length, 1, "one tab is open initially");
 
   // Add several new tabs in sequence, interrupted by selecting a
   // different tab, moving a tab around and closing a tab,
   // returning a list of opened tabs for verifying the expected order.
   // The new tab behaviour is documented in bug 465673
   let tabs = [];
+  let promises = [];
   function addTab(aURL, aReferrer) {
-    tabs.push(gBrowser.addTab(aURL, {referrerURI: aReferrer}));
+    let tab = gBrowser.addTab(aURL, {referrerURI: aReferrer});
+    tabs.push(tab);
+    return BrowserTestUtils.browserLoaded(tab.linkedBrowser);
   }
 
-  addTab("http://mochi.test:8888/#0");
+  yield addTab("http://mochi.test:8888/#0");
   gBrowser.selectedTab = tabs[0];
-  addTab("http://mochi.test:8888/#1");
-  addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
-  addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#1");
+  yield addTab("http://mochi.test:8888/#2", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#3", gBrowser.currentURI);
   gBrowser.selectedTab = tabs[tabs.length - 1];
   gBrowser.selectedTab = tabs[0];
-  addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#4", gBrowser.currentURI);
   gBrowser.selectedTab = tabs[3];
-  addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
+  yield addTab("http://mochi.test:8888/#5", gBrowser.currentURI);
   gBrowser.removeTab(tabs.pop());
-  addTab("about:blank", gBrowser.currentURI);
+  yield addTab("about:blank", gBrowser.currentURI);
   gBrowser.moveTabTo(gBrowser.selectedTab, 1);
-  addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
-  addTab();
-  addTab("http://mochi.test:8888/#7");
+  yield addTab("http://mochi.test:8888/#6", gBrowser.currentURI);
+  yield addTab();
+  yield addTab("http://mochi.test:8888/#7");
 
   function testPosition(tabNum, expectedPosition, msg) {
     is(Array.indexOf(gBrowser.tabs, tabs[tabNum]), expectedPosition, msg);
   }
 
   testPosition(0, 3, "tab without referrer was opened to the far right");
   testPosition(1, 7, "tab without referrer was opened to the far right");
   testPosition(2, 5, "tab with referrer opened immediately to the right");
   testPosition(3, 1, "next tab with referrer opened further to the right");
   testPosition(4, 4, "tab selection changed, tab opens immediately to the right");
   testPosition(5, 6, "blank tab with referrer opens to the right of 3rd original tab where removed tab was");
   testPosition(6, 2, "tab has moved, new tab opens immediately to the right");
   testPosition(7, 8, "blank tab without referrer opens at the end");
   testPosition(8, 9, "tab without referrer opens at the end");
 
   tabs.forEach(gBrowser.removeTab, gBrowser);
-}
+});
--- a/browser/base/content/test/general/browser_selectTabAtIndex.js
+++ b/browser/base/content/test/general/browser_selectTabAtIndex.js
@@ -13,10 +13,10 @@ function test() {
        (isLinux ? "Alt" : "Accel") + "+" + i + " selects expected tab");
   }
 
   gBrowser.selectTabAtIndex(-3);
   is(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length - 3,
      "gBrowser.selectTabAtIndex(-3) selects expected tab");
 
   for (let i = 0; i < 9; i++)
-    gBrowser.removeCurrentTab();
+    gBrowser.removeTab(gBrowser.selectedTab, {skipPermitUnload: true});
 }
--- a/browser/base/content/test/general/browser_visibleTabs.js
+++ b/browser/base/content/test/general/browser_visibleTabs.js
@@ -1,13 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function test() {
+add_task(function* () {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
 
   // Add a tab that will get pinned
   let pinned = gBrowser.addTab();
   gBrowser.pinTab(pinned);
 
   let testTab = gBrowser.addTab();
@@ -95,9 +99,10 @@ function test() {
   gBrowser.removeTab(testTab);
   is(gBrowser.visibleTabs.length, 1, "only orig is left and visible");
   is(gBrowser.tabs.length, 1, "sanity check that it matches");
   is(gBrowser.selectedTab, origTab, "got the orig tab");
   is(origTab.hidden, false, "and it's not hidden -- visible!");
 
   if (tabViewWindow)
     tabViewWindow.GroupItems.groupItems[0].close();
-}
+});
+
--- a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllPages.js
@@ -1,15 +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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   let tabOne = gBrowser.addTab("about:blank");
   let tabTwo = gBrowser.addTab("http://mochi.test:8888/");
   gBrowser.selectedTab = tabTwo;
 
   let browser = gBrowser.getBrowserForTab(tabTwo);
   let onLoad = function() {
     browser.removeEventListener("load", onLoad, true);
 
--- a/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
+++ b/browser/base/content/test/general/browser_visibleTabs_bookmarkAllTabs.js
@@ -1,15 +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/. */
 
 function test() {
   waitForExplicitFinish();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
   is(gBrowser.visibleTabs.length, 1, "1 tab should be open");  
   is(Disabled(), true, "Bookmark All Tabs should be disabled");
 
   // Add a tab
   let testTab1 = gBrowser.addTab();
   is(gBrowser.visibleTabs.length, 2, "2 tabs should be open");
--- a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
+++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
@@ -1,13 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function test() {
+add_task(function* test() {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // There should be one tab when we start the test
   let [origTab] = gBrowser.visibleTabs;
   is(gBrowser.visibleTabs.length, 1, "there is one visible tab");
   let testTab = gBrowser.addTab();
   is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
 
   // Check the context menu with two tabs
   updateTabContextMenu(origTab);
@@ -46,9 +50,10 @@ function test() {
   
   // Check the context menu of the original tab
   // Close Tabs To The End should now be enabled
   updateTabContextMenu(origTab);
   is(document.getElementById("context_closeTabsToTheEnd").disabled, false, "Close Tabs To The End is enabled");
 
   gBrowser.removeTab(testTab);
   gBrowser.removeTab(pinned);
-}
+});
+
--- a/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
+++ b/browser/base/content/test/general/browser_visibleTabs_tabPreview.js
@@ -1,13 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-function test() {
+add_task(function* test() {
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   gPrefService.setBoolPref("browser.ctrlTab.previews", true);
 
   let [origTab] = gBrowser.visibleTabs;
   let tabOne = gBrowser.addTab();
   let tabTwo = gBrowser.addTab();
 
   // test the ctrlTab.tabList
   pressCtrlTab();
@@ -25,17 +29,17 @@ function test() {
   releaseCtrl();
 
   // cleanup
   gBrowser.removeTab(tabOne);
   gBrowser.removeTab(tabTwo);
 
   if (gPrefService.prefHasUserValue("browser.ctrlTab.previews"))
     gPrefService.clearUserPref("browser.ctrlTab.previews");
-}
+});
 
 function pressCtrlTab(aShiftKey) {
   EventUtils.synthesizeKey("VK_TAB", { ctrlKey: true, shiftKey: !!aShiftKey });
 }
 
 function releaseCtrl() {
   EventUtils.synthesizeKey("VK_CONTROL", { type: "keyup" });
 }
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -411,17 +411,17 @@ function loadIntoTab(tab, url, callback)
     tab.linkedBrowser.removeEventListener("load", tabLoad, true);
     executeSoon(function() {callback(tab)});
   }, true);
   tab.linkedBrowser.loadURI(url);
 }
 
 function ensureBrowserTabClosed(tab) {
   let promise = ensureEventFired(gBrowser.tabContainer, "TabClose");
-  gBrowser.removeTab(tab);
+  gBrowser.removeTab(tab, {skipPermitUnload: true});
   return promise;
 }
 
 function ensureFrameLoaded(frame) {
   let deferred = Promise.defer();
   if (frame.contentDocument && frame.contentDocument.readyState == "complete") {
     deferred.resolve();
   } else {
--- a/browser/components/sessionstore/test/browser_607016.js
+++ b/browser/components/sessionstore/test/browser_607016.js
@@ -1,16 +1,20 @@
 "use strict";
 
 var stateBackup = ss.getBrowserState();
 
 add_task(function* () {
   /** Bug 607016 - If a tab is never restored, attributes (eg. hidden) aren't updated correctly **/
   ignoreAllUncaughtExceptions();
 
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  yield new Promise(resolve => TabView._initFrame(resolve));
+
   // Set the pref to true so we know exactly how many tabs should be restoring at
   // any given time. This guarantees that a finishing load won't start another.
   Services.prefs.setBoolPref("browser.sessionstore.restore_on_demand", true);
 
   let state = { windows: [{ tabs: [
     { entries: [{ url: "http://example.org#1" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org#2" }], extData: { "uniq": r() } }, // overwriting
     { entries: [{ url: "http://example.org#3" }], extData: { "uniq": r() } }, // hiding
--- a/browser/components/sessionstore/test/browser_635418.js
+++ b/browser/components/sessionstore/test/browser_635418.js
@@ -2,16 +2,22 @@
  * 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 tests that hiding/showing a tab, on its own, eventually triggers a
 // session store.
 
 function test() {
   waitForExplicitFinish();
+  // Ensure TabView has been initialized already. Otherwise it could
+  // activate at an unexpected time and show/hide tabs.
+  TabView._initFrame(runTest);
+}
+
+function runTest() {
   // We speed up the interval between session saves to ensure that the test
   // runs quickly.
   Services.prefs.setIntPref("browser.sessionstore.interval", 2000);
 
   // Loading a tab causes a save state and this is meant to catch that event.
   waitForSaveState(testBug635418_1);
 
   // Assumption: Only one window is open and it has one tab open.
--- a/browser/modules/RemotePrompt.jsm
+++ b/browser/modules/RemotePrompt.jsm
@@ -34,21 +34,28 @@ var RemotePrompt = {
     }
   },
 
   openTabPrompt: function(args, browser) {
     let window = browser.ownerDocument.defaultView;
     let tabPrompt = window.gBrowser.getTabModalPromptBox(browser)
     let callbackInvoked = false;
     let newPrompt;
+    let needRemove = false;
     let promptId = args._remoteId;
 
     function onPromptClose(forceCleanup) {
+      // It's possible that we removed the prompt during the
+      // appendPrompt call below. In that case, newPrompt will be
+      // undefined. We set the needRemove flag to remember to remove
+      // it right after we've finished adding it.
       if (newPrompt)
         tabPrompt.removePrompt(newPrompt);
+      else
+        needRemove = true;
 
       PromptUtils.fireDialogEvent(window, "DOMModalDialogClosed", browser);
       browser.messageManager.sendAsyncMessage("Prompt:Close", args);
     }
 
     browser.messageManager.addMessageListener("Prompt:ForceClose", function listener(message) {
       // If this was for another prompt in the same tab, ignore it.
       if (message.data._remoteId !== promptId) {
@@ -64,16 +71,20 @@ var RemotePrompt = {
 
     try {
       PromptUtils.fireDialogEvent(window, "DOMWillOpenModalDialog", browser);
 
       args.promptActive = true;
 
       newPrompt = tabPrompt.appendPrompt(args, onPromptClose);
 
+      if (needRemove) {
+        tabPrompt.removePrompt(newPrompt);
+      }
+
       // TODO since we don't actually open a window, need to check if
       // there's other stuff in nsWindowWatcher::OpenWindowInternal
       // that we might need to do here as well.
     } catch (ex) {
       onPromptClose(true);
     }
   },
 
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -102,17 +102,18 @@ function addTab(aUrl, aWindow) {
 }
 
 function removeTab(aTab, aWindow) {
   info("Removing tab.");
 
   let targetWindow = aWindow || window;
   let targetBrowser = targetWindow.gBrowser;
 
-  targetBrowser.removeTab(aTab);
+  // browser_net_pane-toggle.js relies on synchronous removeTab behavior.
+  targetBrowser.removeTab(aTab, {skipPermitUnload: true});
 }
 
 function waitForNavigation(aTarget) {
   let deferred = promise.defer();
   aTarget.once("will-navigate", () => {
     aTarget.once("navigate", () => {
       deferred.resolve();
     });
--- a/devtools/client/sourceeditor/test/helper_codemirror_runner.js
+++ b/devtools/client/sourceeditor/test/helper_codemirror_runner.js
@@ -23,13 +23,13 @@ function runCodeMirrorTest(browser) {
   //     setting a timeout to check again if not.
   mm.loadFrameScript('data:,' +
     'content.wrappedJSObject.mozilla_setStatus = function(statusMsg, type, customMsg) {' +
     '  sendSyncMessage("setStatus", {statusMsg: statusMsg, type: type, customMsg: customMsg});' +
     '};' +
     'function check() { ' +
     '  var doc = content.document; var out = doc.getElementById("status"); ' +
     '  if (!out || !out.classList.contains("done")) { return setTimeout(check, 100); }' +
-    '  sendSyncMessage("done", { failed: content.wrappedJSObject.failed });' +
+    '  sendAsyncMessage("done", { failed: content.wrappedJSObject.failed });' +
     '}' +
     'check();'
   , true);
-}
\ No newline at end of file
+}
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -7759,17 +7759,17 @@ nsDocShell::CreateAboutBlankContentViewe
     // with about:blank. And also ensure we fire the unload events
     // in the current document.
 
     // Unload gets fired first for
     // document loaded from the session history.
     mTiming->NotifyBeforeUnload();
 
     bool okToUnload;
-    rv = mContentViewer->PermitUnload(false, &okToUnload);
+    rv = mContentViewer->PermitUnload(&okToUnload);
 
     if (NS_SUCCEEDED(rv) && !okToUnload) {
       // The user chose not to unload the page, interrupt the load.
       return NS_ERROR_FAILURE;
     }
 
     mSavingOldViewer = aTryToSaveOldPresentation &&
                        CanSavePresentation(LOAD_NORMAL, nullptr, nullptr);
@@ -10092,17 +10092,17 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   bool timeBeforeUnload = aFileName.IsVoid();
   if (mTiming && timeBeforeUnload) {
     mTiming->NotifyBeforeUnload();
   }
   // Check if the page doesn't want to be unloaded. The javascript:
   // protocol handler deals with this for javascript: URLs.
   if (!isJavaScript && aFileName.IsVoid() && mContentViewer) {
     bool okToUnload;
-    rv = mContentViewer->PermitUnload(false, &okToUnload);
+    rv = mContentViewer->PermitUnload(&okToUnload);
 
     if (NS_SUCCEEDED(rv) && !okToUnload) {
       // The user chose not to unload the page, interrupt the
       // load.
       return NS_OK;
     }
   }
 
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -26,67 +26,52 @@ class nsDOMNavigationTiming;
 [ptr] native nsIWidgetPtr(nsIWidget);
 [ref] native nsIntRectRef(nsIntRect);
 [ptr] native nsIPresShellPtr(nsIPresShell);
 [ptr] native nsPresContextPtr(nsPresContext);
 [ptr] native nsViewPtr(nsView);
 [ptr] native nsDOMNavigationTimingPtr(nsDOMNavigationTiming);
 [ref] native nsIContentViewerTArray(nsTArray<nsCOMPtr<nsIContentViewer> >);
 
-[scriptable, builtinclass, uuid(702e0a92-7d63-490e-b5ee-d247e6bd4588)]
+[scriptable, builtinclass, uuid(91b6c1f3-fc5f-43a9-88f4-9286bd19387f)]
 interface nsIContentViewer : nsISupports
 {
 
   [noscript] void init(in nsIWidgetPtr aParentWidget,
                        [const] in nsIntRectRef aBounds);
 
   attribute nsIDocShell container;
 
   [noscript,notxpcom,nostdcall] void loadStart(in nsIDocument aDoc);
   void loadComplete(in nsresult aStatus);
 
   /**
    * Checks if the document wants to prevent unloading by firing beforeunload on
    * the document, and if it does, prompts the user. The result is returned.
-   *
-   * @param aCallerClosesWindow indicates that the current caller will close the
-   *        window. If the method returns true, all subsequent calls will be
-   *        ignored.
    */
-  boolean permitUnload([optional] in boolean aCallerClosesWindow);
+  boolean permitUnload();
 
   /**
    * Exposes whether we're blocked in a call to permitUnload.
    */
   readonly attribute boolean inPermitUnload;
 
   /**
    * As above, but this passes around the aShouldPrompt argument to keep
    * track of whether the user has responded to a prompt.
    * Used internally by the scriptable version to ensure we only prompt once.
    */
-  [noscript,nostdcall] boolean permitUnloadInternal(in boolean aCallerClosesWindow,
-                                                    inout boolean aShouldPrompt);
+  [noscript,nostdcall] boolean permitUnloadInternal(inout boolean aShouldPrompt);
 
   /**
    * Exposes whether we're in the process of firing the beforeunload event.
    * In this case, the corresponding docshell will not allow navigation.
    */
   readonly attribute boolean beforeUnloadFiring;
 
-  /**
-   * Works in tandem with permitUnload, if the caller decides not to close the
-   * window it indicated it will, it is the caller's responsibility to reset
-   * that with this method.
-   *
-   * @Note this method is only meant to be called on documents for which the
-   *  caller has indicated that it will close the window. If that is not the case
-   *  the behavior of this method is undefined.
-   */
-  void resetCloseWindow();
   void pageHide(in boolean isUnload);
 
   /**
    * All users of a content viewer are responsible for calling both
    * close() and destroy(), in that order. 
    *
    * close() should be called when the load of a new page for the next
    * content viewer begins, and destroy() should be called when the next
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -98,15 +98,14 @@ skip-if = e10s # Bug ?????? - event hand
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_uriFixupIntegration.js]
 [browser_loadDisallowInherit.js]
 skip-if = e10s
 [browser_loadURI.js]
 skip-if = e10s # Bug ?????? - event handler checks event.target is the content document and test e10s-utils doesn't do that.
 [browser_multiple_pushState.js]
 [browser_onbeforeunload_navigation.js]
-skip-if = e10s
 [browser_search_notification.js]
 [browser_timelineMarkers-01.js]
 [browser_timelineMarkers-02.js]
 [browser_timelineMarkers-03.js]
 [browser_timelineMarkers-04.js]
 [browser_timelineMarkers-05.js]
--- a/docshell/test/browser/browser_onbeforeunload_navigation.js
+++ b/docshell/test/browser/browser_onbeforeunload_navigation.js
@@ -5,16 +5,17 @@ var stayingOnPage = true;
 
 var TEST_PAGE = "http://mochi.test:8888/browser/docshell/test/browser/file_bug1046022.html";
 var TARGETED_PAGE = "data:text/html," + encodeURIComponent("<body>Shouldn't be seeing this</body>");
 
 SpecialPowers.pushPrefEnv({"set": [["dom.require_user_interaction_for_beforeunload", false]]});
 
 var loadExpected = TEST_PAGE;
 var testTab;
+var testsLength;
 
 var loadStarted = false;
 var tabStateListener = {
   onStateChange: function(webprogress, request, stateFlags, status) {
     let startDocumentFlags = Ci.nsIWebProgressListener.STATE_START |
                              Ci.nsIWebProgressListener.STATE_IS_DOCUMENT;
     if ((stateFlags & startDocumentFlags) == startDocumentFlags) {
       loadStarted = true;
@@ -35,16 +36,20 @@ function onTabLoaded(event) {
     return;
   }
 
   if (!loadExpected) {
     ok(false, "Expected no page loads, but loaded " + loadedPage + " instead!");
     return;
   }
 
+  if (!testsLength) {
+    testsLength = testTab.linkedBrowser.contentWindow.wrappedJSObject.testFns.length;
+  }
+
   is(loadedPage, loadExpected, "Loaded the expected page");
   if (contentWindow) {
     is(contentWindow.document, event.target, "Same doc");
   }
   if (onAfterPageLoad) {
     onAfterPageLoad();
   }
 }
@@ -97,57 +102,19 @@ function onTabModalDialogLoaded(node) {
   // ... and then actually make the dialog go away
   info("Clicking button: " + button.label);
   EventUtils.synthesizeMouseAtCenter(button, {});
 }
 
 // Listen for the dialog being created
 Services.obs.addObserver(onTabModalDialogLoaded, "tabmodal-dialog-loaded", false);
 
-var testFns = [
-  function(e) {
-    e.target.location.href = 'otherpage-href-set.html';
-    return "stop";
-  },
-  function(e) {
-    e.target.location.reload();
-    return "stop";
-  },
-  function(e) {
-    e.target.location.replace('otherpage-location-replaced.html');
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = "otherpage.html";
-    e.target.body.appendChild(link);
-    link.click();
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = "otherpage.html";
-    link.setAttribute("target", "_blank");
-    e.target.body.appendChild(link);
-    link.click();
-    return "stop";
-  },
-  function(e) {
-    var link = e.target.createElement('a');
-    link.href = e.target.location.href;
-    e.target.body.appendChild(link);
-    link.setAttribute("target", "somearbitrarywindow");
-    link.click();
-    return "stop";
-  },
-];
-
 function runNextTest() {
   currentTest++;
-  if (currentTest >= testFns.length) {
+  if (currentTest >= testsLength) {
     if (!stayingOnPage) {
       finish();
       return;
     }
     // Run the same tests again, but this time let the navigation happen:
     stayingOnPage = false;
     currentTest = 0;
   }
@@ -166,20 +133,20 @@ function runNextTest() {
 
 function runCurrentTest() {
   // Reset things so we're sure the previous tests failings don't influence this one:
   contentWindow = testTab.linkedBrowser.contentWindow;
   contentWindow.mySuperSpecialMark = 42;
   contentWindow.dialogWasInvoked = false;
   originalLocation = contentWindow.location.href;
   // And run this test:
-  info("Running test with onbeforeunload " + testFns[currentTest].toSource());
-  contentWindow.onbeforeunload = testFns[currentTest];
+  info("Running test with onbeforeunload " + contentWindow.wrappedJSObject.testFns[currentTest].toSource());
+  contentWindow.onbeforeunload = contentWindow.wrappedJSObject.testFns[currentTest];
   loadStarted = false;
-  contentWindow.location.href = TARGETED_PAGE;
+  testTab.linkedBrowser.loadURI(TARGETED_PAGE);
 }
 
 var onAfterPageLoad = runNextTest;
 
 function test() {
   waitForExplicitFinish();
   gBrowser.addProgressListener(tabStateListener);
 
--- a/docshell/test/browser/file_bug1046022.html
+++ b/docshell/test/browser/file_bug1046022.html
@@ -2,9 +2,49 @@
 <html>
   <head>
     <meta charset="utf-8">
     <title>Bug 1046022 - test navigating inside onbeforeunload</title>
   </head>
   <body>
     Waiting for onbeforeunload to hit...
   </body>
+
+  <script>
+var testFns = [
+  function(e) {
+    e.target.location.href = 'otherpage-href-set.html';
+    return "stop";
+  },
+  function(e) {
+    e.target.location.reload();
+    return "stop";
+  },
+  function(e) {
+    e.target.location.replace('otherpage-location-replaced.html');
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = "otherpage.html";
+    e.target.body.appendChild(link);
+    link.click();
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = "otherpage.html";
+    link.setAttribute("target", "_blank");
+    e.target.body.appendChild(link);
+    link.click();
+    return "stop";
+  },
+  function(e) {
+    var link = e.target.createElement('a');
+    link.href = e.target.location.href;
+    e.target.body.appendChild(link);
+    link.setAttribute("target", "somearbitrarywindow");
+    link.click();
+    return "stop";
+  },
+];
+  </script>
 </html>
--- a/docshell/test/browser/head.js
+++ b/docshell/test/browser/head.js
@@ -26,18 +26,18 @@ function makeTimelineTest(frameScriptNam
     // content process can forward their results to the main process.
     mm.addMessageListener("browser:test:ok", function(message) {
       ok(message.data.value, message.data.message);
     });
     mm.addMessageListener("browser:test:info", function(message) {
       info(message.data.message);
     });
     mm.addMessageListener("browser:test:finish", function(ignore) {
+      gBrowser.removeCurrentTab();
       finish();
-      gBrowser.removeCurrentTab();
     });
   });
 }
 
 /* Open a URL for a timeline test.  */
 function timelineTestOpenUrl(url) {
   window.focus();
 
--- a/dom/base/nsContentPolicyUtils.h
+++ b/dom/base/nsContentPolicyUtils.h
@@ -9,16 +9,17 @@
  *
  * XXXbz it would be nice if some of this stuff could be out-of-lined in
  * nsContentUtils.  That would work for almost all the callers...
  */
 
 #ifndef __nsContentPolicyUtils_h__
 #define __nsContentPolicyUtils_h__
 
+#include "nsContentUtils.h"
 #include "nsIContentPolicy.h"
 #include "nsIContent.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIURI.h"
 #include "nsServiceManagerUtils.h"
 
 //XXXtw sadly, this makes consumers of nsContentPolicyUtils depend on widget
 #include "nsIDocument.h"
@@ -189,17 +190,19 @@ NS_CP_ContentTypeName(uint32_t contentTy
               if (n) {                                                        \
                   nsIDocument* d = n->OwnerDoc();                             \
                   if (d->IsLoadedAsData() || d->IsBeingUsedAsImage() ||       \
                       d->IsResourceDoc()) {                                   \
                       nsCOMPtr<nsIContentPolicy> dataPolicy =                 \
                           do_GetService(                                      \
                               "@mozilla.org/data-document-content-policy;1"); \
                       if (dataPolicy) {                                       \
-                          dataPolicy-> action (contentType, contentLocation,  \
+                          nsContentPolicyType externalType =                  \
+                              nsContentUtils::InternalContentPolicyTypeToExternal(contentType);\
+                          dataPolicy-> action (externalType, contentLocation, \
                                                requestOrigin, context,        \
                                                mimeType, extra,               \
                                                originPrincipal, decision);    \
                       }                                                       \
                   }                                                           \
               }                                                               \
               return NS_OK;                                                   \
           }                                                                   \
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -8749,30 +8749,41 @@ public:
 
 };
 
 bool
 nsGlobalWindow::CanClose()
 {
   MOZ_ASSERT(IsOuterWindow());
 
+  if (mIsChrome) {
+    nsCOMPtr<nsIBrowserDOMWindow> bwin;
+    nsIDOMChromeWindow* chromeWin = static_cast<nsGlobalChromeWindow*>(this);
+    chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+    bool canClose = true;
+    if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
+      return canClose;
+    }
+  }
+
   if (!mDocShell) {
     return true;
   }
 
   // Ask the content viewer whether the toplevel window can close.
   // If the content viewer returns false, it is responsible for calling
   // Close() as soon as it is possible for the window to close.
   // This allows us to not close the window while printing is happening.
 
   nsCOMPtr<nsIContentViewer> cv;
   mDocShell->GetContentViewer(getter_AddRefs(cv));
   if (cv) {
     bool canClose;
-    nsresult rv = cv->PermitUnload(false, &canClose);
+    nsresult rv = cv->PermitUnload(&canClose);
     if (NS_SUCCEEDED(rv) && !canClose)
       return false;
 
     rv = cv->RequestWindowClose(&canClose);
     if (NS_SUCCEEDED(rv) && !canClose)
       return false;
   }
 
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1467,29 +1467,27 @@ CanvasRenderingContext2D::EnsureTarget(R
   } else {
     EnsureErrorTarget();
     mTarget = sErrorTarget;
   }
 
   return mode;
 }
 
-#ifdef DEBUG
 int32_t
 CanvasRenderingContext2D::GetWidth() const
 {
   return mWidth;
 }
 
 int32_t
 CanvasRenderingContext2D::GetHeight() const
 {
   return mHeight;
 }
-#endif
 
 NS_IMETHODIMP
 CanvasRenderingContext2D::SetDimensions(int32_t width, int32_t height)
 {
   ClearTarget();
 
   // Zero sized surfaces can cause problems.
   mZero = false;
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -413,20 +413,19 @@ public:
 
   bool SwitchRenderingMode(RenderingMode aRenderingMode);
 
   // Eventually this should be deprecated. Keeping for now to keep the binding functional.
   void Demote();
 
   nsresult Redraw();
 
-#ifdef DEBUG
-    virtual int32_t GetWidth() const override;
-    virtual int32_t GetHeight() const override;
-#endif
+  virtual int32_t GetWidth() const override;
+  virtual int32_t GetHeight() const override;
+
   // nsICanvasRenderingContextInternal
   /**
     * Gets the pres shell from either the canvas element or the doc shell
     */
   virtual nsIPresShell *GetPresShell() override {
     if (mCanvasElement) {
       return mCanvasElement->OwnerDoc()->GetShell();
     }
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -475,29 +475,27 @@ WebGLContext::SetContextOptions(JSContex
         // aren't the same as what they were originally.
         return NS_ERROR_FAILURE;
     }
 
     mOptions = newOpts;
     return NS_OK;
 }
 
-#ifdef DEBUG
 int32_t
 WebGLContext::GetWidth() const
 {
     return mWidth;
 }
 
 int32_t
 WebGLContext::GetHeight() const
 {
     return mHeight;
 }
-#endif
 
 /* So there are a number of points of failure here. We might fail based
  * on EGL vs. WGL, or we might fail to alloc a too-large size, or we
  * might not be able to create a context with a certain combo of context
  * creation attribs.
  *
  * We don't want to test the complete fallback matrix. (for now, at
  * least) Instead, attempt creation in this order:
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -210,20 +210,19 @@ public:
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(WebGLContext,
                                                            nsIDOMWebGLRenderingContext)
 
     virtual JSObject* WrapObject(JSContext* cx, JS::Handle<JSObject*> givenProto) override = 0;
 
     NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT
 
     // nsICanvasRenderingContextInternal
-#ifdef DEBUG
     virtual int32_t GetWidth() const override;
     virtual int32_t GetHeight() const override;
-#endif
+
     NS_IMETHOD SetDimensions(int32_t width, int32_t height) override;
     NS_IMETHOD InitializeWithSurface(nsIDocShell*, gfxASurface*, int32_t,
                                      int32_t) override
     {
         return NS_ERROR_NOT_IMPLEMENTED;
     }
 
     NS_IMETHOD Reset() override {
--- a/dom/canvas/nsICanvasRenderingContextInternal.h
+++ b/dom/canvas/nsICanvasRenderingContextInternal.h
@@ -75,21 +75,19 @@ public:
     mRefreshDriver->AddPostRefreshObserver(this);
   }
 
   mozilla::dom::HTMLCanvasElement* GetParentObject() const
   {
     return mCanvasElement;
   }
 
-#ifdef DEBUG
-    // Useful for testing
-    virtual int32_t GetWidth() const = 0;
-    virtual int32_t GetHeight() const = 0;
-#endif
+  // Dimensions of the canvas, in pixels.
+  virtual int32_t GetWidth() const = 0;
+  virtual int32_t GetHeight() const = 0;
 
   // Sets the dimensions of the canvas, in pixels.  Called
   // whenever the size of the element changes.
   NS_IMETHOD SetDimensions(int32_t width, int32_t height) = 0;
 
   NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) = 0;
 
   // Creates an image buffer. Returns null on failure.
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -664,28 +664,29 @@ HTMLCanvasElement::ToBlob(JSContext* aCx
 
   nsAutoString params;
   bool usingCustomParseOptions;
   aRv = ParseParams(aCx, type, aParams, params, &usingCustomParseOptions);
   if (aRv.Failed()) {
     return;
   }
 
-#ifdef DEBUG
   if (mCurrentContext) {
     // We disallow canvases of width or height zero, and set them to 1, so
     // we will have a discrepancy with the sizes of the canvas and the context.
     // That discrepancy is OK, the rest are not.
     nsIntSize elementSize = GetWidthHeight();
-    MOZ_ASSERT(elementSize.width == mCurrentContext->GetWidth() ||
-               (elementSize.width == 0 && mCurrentContext->GetWidth() == 1));
-    MOZ_ASSERT(elementSize.height == mCurrentContext->GetHeight() ||
-               (elementSize.height == 0 && mCurrentContext->GetHeight() == 1));
+    if ((elementSize.width != mCurrentContext->GetWidth() &&
+         (elementSize.width != 0 || mCurrentContext->GetWidth() != 1)) ||
+        (elementSize.height != mCurrentContext->GetHeight() &&
+         (elementSize.height != 0 || mCurrentContext->GetHeight() != 1))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+    }
   }
-#endif
 
   uint8_t* imageBuffer = nullptr;
   int32_t format = 0;
   if (mCurrentContext) {
     mCurrentContext->GetImageBuffer(&imageBuffer, &format);
   }
 
   // Encoder callback when encoding is complete.
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1514,17 +1514,17 @@ nsHTMLDocument::Open(JSContext* cx,
 
   // Stop current loads targeted at the window this document is in.
   if (mScriptGlobalObject) {
     nsCOMPtr<nsIContentViewer> cv;
     shell->GetContentViewer(getter_AddRefs(cv));
 
     if (cv) {
       bool okToUnload;
-      if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) && !okToUnload) {
+      if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) && !okToUnload) {
         // We don't want to unload, so stop here, but don't throw an
         // exception.
         nsCOMPtr<nsIDocument> ret = this;
         return ret.forget();
       }
     }
 
     nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(shell));
--- a/dom/html/test/test_bug448166.html
+++ b/dom/html/test/test_bug448166.html
@@ -18,17 +18,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 /** Test for Bug 448166 **/
 isnot($("test").href, "http://www.mozilla.org/",
       "Should notice unpaired surrogate");
-is($("test").href, "http://www.moz\ufffdilla.org/",
+is($("test").href, "http://www.xn--mozilla-2e14b.org/",
       "Should replace unpaired surrogate with replacement char");
 is($("control").href, "http://www.mozilla.org/",
       "Just making sure .href works");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/interfaces/base/nsIBrowserDOMWindow.idl
+++ b/dom/interfaces/base/nsIBrowserDOMWindow.idl
@@ -12,17 +12,17 @@ interface nsIFrameLoaderOwner;
 [scriptable, uuid(e774db14-79ac-4156-a7a3-aa3fd0a22c10)]
 
 interface nsIOpenURIInFrameParams : nsISupports
 {
   attribute DOMString referrer;
   attribute boolean isPrivate;
 };
 
-[scriptable, uuid(99f5a347-722c-4337-bd38-f14ec94801b3)]
+[scriptable, uuid(31da1ce2-aec4-4c26-ac66-d622935c3bf4)]
 
 /**
  * The C++ source has access to the browser script source through
  * nsIBrowserDOMWindow. It is intended to be attached to the chrome DOMWindow
  * of a toplevel browser window (a XUL window). A DOMWindow that does not
  * happen to be a browser chrome window will simply have no access to any such
  * interface.
  */
@@ -94,11 +94,19 @@ interface nsIBrowserDOMWindow : nsISuppo
   nsIFrameLoaderOwner openURIInFrame(in nsIURI aURI, in nsIOpenURIInFrameParams params,
                                      in short aWhere, in short aContext);
 
   /**
    * @param  aWindow the window to test.
    * @return whether the window is the main content window for any
    *         currently open tab in this toplevel browser window.
    */
-  boolean      isTabContentWindow(in nsIDOMWindow aWindow);
+  boolean isTabContentWindow(in nsIDOMWindow aWindow);
+
+  /**
+   * This function is responsible for calling
+   * nsIContentViewer::PermitUnload on each frame in the window. It
+   * returns true if closing the window is allowed. See canClose() in
+   * BrowserUtils.jsm for a simple implementation of this method.
+   */
+  boolean canClose();
 };
 
--- a/dom/jsurl/nsJSProtocolHandler.cpp
+++ b/dom/jsurl/nsJSProtocolHandler.cpp
@@ -744,17 +744,17 @@ nsJSChannel::EvaluateScript()
         NS_QueryNotificationCallbacks(mStreamChannel, docShell);
         if (docShell) {
             nsCOMPtr<nsIContentViewer> cv;
             docShell->GetContentViewer(getter_AddRefs(cv));
 
             if (cv) {
                 bool okToUnload;
 
-                if (NS_SUCCEEDED(cv->PermitUnload(false, &okToUnload)) &&
+                if (NS_SUCCEEDED(cv->PermitUnload(&okToUnload)) &&
                     !okToUnload) {
                     // The user didn't want to unload the current
                     // page, translate this into an undefined
                     // return from the javascript: URL...
                     mStatus = NS_ERROR_DOM_RETVAL_UNDEFINED;
                 }
             }
         }
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -195,20 +195,20 @@ DecoderTraits::IsWebMType(const nsACStri
 #endif
   return false;
 }
 
 #ifdef MOZ_GSTREAMER
 static bool
 IsGStreamerSupportedType(const nsACString& aMimeType)
 {
-  if (!MediaDecoder::IsGStreamerEnabled())
+  if (DecoderTraits::IsWebMType(aMimeType))
     return false;
 
-  if (DecoderTraits::IsWebMType(aMimeType) && !Preferences::GetBool("media.prefer-gstreamer", false))
+  if (!MediaDecoder::IsGStreamerEnabled())
     return false;
 
   if (IsOggType(aMimeType) && !Preferences::GetBool("media.prefer-gstreamer", false))
     return false;
 
   return GStreamerDecoder::CanHandleMediaType(aMimeType, nullptr);
 }
 #endif
@@ -361,17 +361,17 @@ DecoderTraits::IsMP4Type(const nsACStrin
 #endif
   return false;
 }
 
 static bool
 IsMP3SupportedType(const nsACString& aType,
                    const nsAString& aCodecs = EmptyString())
 {
-  return aType.EqualsASCII("audio/mpeg") && MP3Decoder::IsEnabled();
+  return MP3Decoder::CanHandleMediaType(aType, aCodecs);
 }
 
 #ifdef MOZ_APPLEMEDIA
 static const char * const gAppleMP3Types[] = {
   "audio/mp3",
   "audio/mpeg",
   nullptr,
 };
--- a/dom/media/MP3Decoder.cpp
+++ b/dom/media/MP3Decoder.cpp
@@ -5,20 +5,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MP3Decoder.h"
 #include "MediaDecoderStateMachine.h"
 #include "MediaFormatReader.h"
 #include "MP3Demuxer.h"
 #include "mozilla/Preferences.h"
-
-#ifdef MOZ_WIDGET_ANDROID
-#include "AndroidBridge.h"
-#endif
+#include "PlatformDecoderModule.h"
 
 namespace mozilla {
 
 MediaDecoder*
 MP3Decoder::Clone() {
   if (!IsEnabled()) {
     return nullptr;
   }
@@ -27,19 +24,66 @@ MP3Decoder::Clone() {
 
 MediaDecoderStateMachine*
 MP3Decoder::CreateStateMachine() {
   nsRefPtr<MediaDecoderReader> reader =
       new MediaFormatReader(this, new mp3::MP3Demuxer(GetResource()));
   return new MediaDecoderStateMachine(this, reader);
 }
 
+static already_AddRefed<MediaDataDecoder>
+CreateTestMP3Decoder(AudioInfo& aConfig)
+{
+  PlatformDecoderModule::Init();
+
+  nsRefPtr<PlatformDecoderModule> platform = PlatformDecoderModule::Create();
+  if (!platform || !platform->SupportsMimeType(aConfig.mMimeType)) {
+    return nullptr;
+  }
+
+  nsRefPtr<MediaDataDecoder> decoder(
+    platform->CreateDecoder(aConfig, nullptr, nullptr));
+  if (!decoder) {
+    return nullptr;
+  }
+
+  return decoder.forget();
+}
+
+static bool
+CanCreateMP3Decoder()
+{
+  static bool haveCachedResult = false;
+  static bool result = false;
+  if (haveCachedResult) {
+    return result;
+  }
+  AudioInfo config;
+  config.mMimeType = "audio/mpeg";
+  config.mRate = 48000;
+  config.mChannels = 2;
+  config.mBitDepth = 16;
+  nsRefPtr<MediaDataDecoder> decoder(CreateTestMP3Decoder(config));
+  if (decoder) {
+    result = true;
+  }
+  haveCachedResult = true;
+  return result;
+}
+
+/* static */
 bool
 MP3Decoder::IsEnabled() {
-#ifdef MOZ_WIDGET_ANDROID
-  // We need android.media.MediaCodec which exists in API level 16 and higher.
-  return AndroidBridge::Bridge()->GetAPIVersion() >= 16;
-#else
-  return Preferences::GetBool("media.mp3.enabled");
-#endif
+  return CanCreateMP3Decoder();
+}
+
+/* static */
+bool MP3Decoder::CanHandleMediaType(const nsACString& aType,
+                                    const nsAString& aCodecs)
+{
+  if (aType.EqualsASCII("audio/mp3") || aType.EqualsASCII("audio/mpeg")) {
+    return CanCreateMP3Decoder() &&
+      (aCodecs.IsEmpty() || aCodecs.EqualsASCII("mp3"));
+  }
+  return false;
 }
 
 } // namespace mozilla
--- a/dom/media/MP3Decoder.h
+++ b/dom/media/MP3Decoder.h
@@ -14,13 +14,15 @@ class MP3Decoder : public MediaDecoder {
 public:
   // MediaDecoder interface.
   MediaDecoder* Clone() override;
   MediaDecoderStateMachine* CreateStateMachine() override;
 
   // Returns true if the MP3 backend is preffed on, and we're running on a
   // platform that is likely to have decoders for the format.
   static bool IsEnabled();
+  static bool CanHandleMediaType(const nsACString& aType,
+                                 const nsAString& aCodecs);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MP3Demuxer.cpp
+++ b/dom/media/MP3Demuxer.cpp
@@ -313,20 +313,23 @@ MP3TrackDemuxer::SkipToNextRandomAccessP
 
 int64_t
 MP3TrackDemuxer::GetResourceOffset() const {
   return mOffset;
 }
 
 TimeIntervals
 MP3TrackDemuxer::GetBuffered() {
-  // TODO: bug 1169485.
-  MP3DEMUXER_LOG("MP3TrackDemuxer::GetBuffered()");
-  NS_WARNING("Unimplemented function GetBuffered");
-  return TimeIntervals();
+  TimeUnit duration = Duration();
+
+  if (duration <= TimeUnit()) {
+    return TimeIntervals();
+  }
+  AutoPinned<MediaResource> stream(mSource.GetResource());
+  return GetEstimatedBufferedTimeRanges(stream, duration.ToMicroseconds());
 }
 
 int64_t
 MP3TrackDemuxer::StreamLength() const {
   return mSource.GetLength();
 }
 
 TimeUnit
@@ -453,16 +456,18 @@ MP3TrackDemuxer::GetNextFrame(const Medi
     MP3DEMUXER_LOG("GetNext() Exit read=%u frame->Size()=%u", read, frame->Size());
     return nullptr;
   }
 
   UpdateState(aRange);
 
   frame->mTime = Duration(mFrameIndex - 1).ToMicroseconds();
   frame->mDuration = Duration(1).ToMicroseconds();
+  frame->mTimecode = frame->mTime;
+  frame->mKeyframe = true;
 
   MOZ_ASSERT(frame->mTime >= 0);
   MOZ_ASSERT(frame->mDuration > 0);
 
   if (mNumParsedFrames == 1) {
     // First frame parsed, let's read VBR info if available.
     // TODO: read info that helps with seeking (bug 1163667).
     mParser.ParseVBRHeader(frame->Data(), frame->Data() + frame->Size());
--- a/dom/media/MP3Demuxer.h
+++ b/dom/media/MP3Demuxer.h
@@ -21,16 +21,21 @@ public:
   nsRefPtr<InitPromise> Init() override;
   bool HasTrackType(TrackInfo::TrackType aType) const override;
   uint32_t GetNumberTracks(TrackInfo::TrackType aType) const override;
   already_AddRefed<MediaTrackDemuxer> GetTrackDemuxer(
       TrackInfo::TrackType aType, uint32_t aTrackNumber) override;
   bool IsSeekable() const override;
   void NotifyDataArrived(uint32_t aLength, int64_t aOffset) override;
   void NotifyDataRemoved() override;
+  // Do not shift the calculated buffered range by the start time of the first
+  // decoded frame. The mac MP3 decoder will buffer some samples and the first
+  // frame returned has typically a start time that is non-zero, causing our
+  // buffered range to have a negative start time.
+  bool ShouldComputeStartTime() const override { return false; }
 
 private:
   // Synchronous initialization.
   bool InitInternal();
 
   nsRefPtr<MediaResource> mSource;
   nsRefPtr<MP3TrackDemuxer> mTrackDemuxer;
 };
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -382,18 +382,19 @@ RTCPeerConnection.prototype = {
       }
     } else {
       // This gets executed in the typical case when iceServers
       // are passed in through the web page.
       this._mustValidateRTCConfiguration(rtcConfig,
         "RTCPeerConnection constructor passed invalid RTCConfiguration");
     }
     // Save the appId
-    this._appId = Cu.getWebIDLCallerPrincipal().appId;
-    this._https = this._win.document.documentURIObject.schemeIs("https");
+    var principal = Cu.getWebIDLCallerPrincipal();
+    this._appId = principal.appId;
+    this._isChrome = Services.scriptSecurityManager.isSystemPrincipal(principal);
 
     // Get the offline status for this appId
     let appOffline = false;
     if (this._appId != Ci.nsIScriptSecurityManager.NO_APP_ID &&
         this._appId != Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
       let ios = Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
       appOffline = ios.isAppOffline(this._appId);
     }
@@ -773,17 +774,18 @@ RTCPeerConnection.prototype = {
       });
     });
   },
 
   getPermission: function() {
     if (this._havePermission) {
       return this._havePermission;
     }
-    if (AppConstants.MOZ_B2G ||
+    if (this._isChrome ||
+        AppConstants.MOZ_B2G ||
         Services.prefs.getBoolPref("media.navigator.permission.disabled")) {
       return this._havePermission = Promise.resolve();
     }
     return this._havePermission = new Promise((resolve, reject) => {
       this._settlePermission = { allow: resolve, deny: reject };
       let outerId = this._win.QueryInterface(Ci.nsIInterfaceRequestor).
           getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
 
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -71,17 +71,17 @@ var gProgressTests = [
 ];
 
 // Used by test_played.html
 var gPlayedTests = [
   { name:"big.wav", type:"audio/x-wav", duration:9.0 },
   { name:"seek.ogv", type:"video/ogg", duration:3.966 },
   { name:"seek.webm", type:"video/webm", duration:3.966 },
   { name:"gizmo.mp4", type:"video/mp4", duration:5.56 },
-  { name:"owl.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
   // Disable vbr.mp3 to see if it reduces the error of AUDCLNT_E_CPUUSAGE_EXCEEDED.
   // See bug 1110922 comment 26.
   //{ name:"vbr.mp3", type:"audio/mpeg", duration:10.0 },
   { name:"bug495794.ogg", type:"audio/ogg", duration:0.3 }
 ];
 
 // Used by test_mozLoadFrom.  Need one test file per decoder backend, plus
 // anything for testing clone-specific bugs.
@@ -232,23 +232,23 @@ var gPlayTests = [
 
   { name:"gizmo.mp4", type:"video/mp4", duration:5.56 },
   // Test playback of a MP4 file with a non-zero start time (and audio starting
   // a second later).
   { name:"bipbop-lateaudio.mp4", type:"video/mp4" },
 
   { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 },
   { name:"small-shot.mp3", type:"audio/mpeg", duration:0.27 },
-  { name:"owl.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
   // owl.mp3 as above, but with something funny going on in the ID3v2 tag
   // that causes DirectShow to fail.
-  { name:"owl-funny-id3.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl-funny-id3.mp3", type:"audio/mpeg", duration:3.343 },
   // owl.mp3 as above, but with something even funnier going on in the ID3v2 tag
   // that causes DirectShow to fail.
-  { name:"owl-funnier-id3.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl-funnier-id3.mp3", type:"audio/mpeg", duration:3.343 },
   // One second of silence with ~140KB of ID3 tags. Usually when the first MP3
   // frame is at such a high offset into the file, MP3FrameParser will give up
   // and report that the stream is not MP3. However, it does not count ID3 tags
   // in that offset. This test case makes sure that ID3 exclusion holds.
   { name:"huge-id3.mp3", type:"audio/mpeg", duration:1.00 },
   // A truncated VBR MP3 with just enough frames to keep most decoders happy.
   // The Xing header reports the length of the file to be around 10 seconds, but
   // there is really only one second worth of data. We want MP3FrameParser to
@@ -464,17 +464,17 @@ var gSeekTests = [
   { name:"seek.ogv", type:"video/ogg", duration:3.966 },
   { name:"320x240.ogv", type:"video/ogg", duration:0.266 },
   { name:"seek.webm", type:"video/webm", duration:3.966 },
   { name:"sine.webm", type:"audio/webm", duration:4.001 },
   { name:"bug516323.indexed.ogv", type:"video/ogg", duration:4.208333 },
   { name:"split.webm", type:"video/webm", duration:1.967 },
   { name:"detodos.opus", type:"audio/ogg; codecs=opus", duration:2.9135 },
   { name:"gizmo.mp4", type:"video/mp4", duration:5.56 },
-  { name:"owl.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
   { name:"bogus.duh", type:"bogus/duh", duration:123 }
 ];
 
 var gFastSeekTests = [
   { name:"gizmo.mp4", type:"video/mp4", keyframes:[0, 1.0, 2.0, 3.0, 4.0, 5.0 ] },
   // Note: Not all keyframes in the file are actually referenced in the Cues in this file.
   { name:"seek.webm", type:"video/webm", keyframes:[0, 0.8, 1.6, 2.4, 3.2]},
   // Note: the sync points are the points on both the audio and video streams
@@ -518,17 +518,17 @@ if (getAndroidVersion() >= 18) {
     { name:"street.mp4", type:"video/mp4" }
   ]);
 }
 
 // These are files suitable for using with a "new Audio" constructor.
 var gAudioTests = [
   { name:"r11025_s16_c1.wav", type:"audio/x-wav", duration:1.0 },
   { name:"sound.ogg", type:"audio/ogg" },
-  { name:"owl.mp3", type:"audio/mpeg", duration:3.29 },
+  { name:"owl.mp3", type:"audio/mpeg", duration:3.343 },
   { name:"small-shot.m4a", type:"audio/mp4", duration:0.29 },
   { name:"bogus.duh", type:"bogus/duh", duration:123 }
 ];
 
 // These files ensure our handling of 404 errors is consistent across the
 // various backends.
 var g404Tests = [
   { name:"404.wav", type:"audio/x-wav" },
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -575,18 +575,18 @@ public:
 AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mLoopStart(0.0)
   , mLoopEnd(0.0)
   // mOffset and mDuration are initialized in Start().
-  , mPlaybackRate(new AudioParam(this, SendPlaybackRateToStream, 1.0f, "playbackRate"))
-  , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
+  , mPlaybackRate(new AudioParam(this, PLAYBACKRATE, 1.0f, "playbackRate"))
+  , mDetune(new AudioParam(this, DETUNE, 0.0f, "detune"))
   , mLoop(false)
   , mStartCalled(false)
 {
   AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NEED_MAIN_THREAD_FINISHED);
   engine->SetSourceStream(mStream);
   mStream->AddMainThreadListener(this);
@@ -780,38 +780,16 @@ AudioBufferSourceNode::NotifyMainThreadS
   NS_DispatchToMainThread(new EndedEventDispatcher(this));
 
   // Drop the playing reference
   // Warning: The below line might delete this.
   MarkInactive();
 }
 
 void
-AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode,
-                                                const AudioTimelineEvent& aEvent)
-{
-  AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
-  if (!This->mStream) {
-    return;
-  }
-  SendTimelineEventToStream(This, PLAYBACKRATE, aEvent);
-}
-
-void
-AudioBufferSourceNode::SendDetuneToStream(AudioNode* aNode,
-                                          const AudioTimelineEvent& aEvent)
-{
-  AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
-  if (!This->mStream) {
-    return;
-  }
-  SendTimelineEventToStream(This, DETUNE, aEvent);
-}
-
-void
 AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift)
 {
   MOZ_ASSERT(mStream, "Should have disconnected panner if no stream");
   SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift);
 }
 
 void
 AudioBufferSourceNode::SendLoopParametersToStream()
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -124,20 +124,16 @@ private:
     PLAYBACKRATE,
     DETUNE,
     DOPPLERSHIFT
   };
 
   void SendLoopParametersToStream();
   void SendBufferParameterToStream(JSContext* aCx);
   void SendOffsetAndDurationParametersToStream(AudioNodeStream* aStream);
-  static void SendPlaybackRateToStream(AudioNode* aNode,
-                                       const AudioTimelineEvent& aEvent);
-  static void SendDetuneToStream(AudioNode* aNode,
-                                 const AudioTimelineEvent& aEvent);
 
 private:
   double mLoopStart;
   double mLoopEnd;
   double mOffset;
   double mDuration;
   nsRefPtr<AudioBuffer> mBuffer;
   nsRefPtr<AudioParam> mPlaybackRate;
--- a/dom/media/webaudio/AudioNode.cpp
+++ b/dom/media/webaudio/AudioNode.cpp
@@ -299,25 +299,16 @@ AudioNode::SendChannelMixingParametersTo
 {
   if (mStream) {
     mStream->SetChannelMixingParameters(mChannelCount, mChannelCountMode,
                                         mChannelInterpretation);
   }
 }
 
 void
-AudioNode::SendTimelineEventToStream(AudioNode* aNode, uint32_t aIndex,
-                                     const AudioTimelineEvent& aEvent)
-{
-  AudioNodeStream* ns = aNode->mStream;
-  MOZ_ASSERT(ns, "How come we don't have a stream here?");
-  ns->SendTimelineEvent(aIndex, aEvent);
-}
-
-void
 AudioNode::Disconnect(uint32_t aOutput, ErrorResult& aRv)
 {
   if (aOutput >= NumberOfOutputs()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   // An upstream node may be starting to play on the graph thread, and the
--- a/dom/media/webaudio/AudioNode.h
+++ b/dom/media/webaudio/AudioNode.h
@@ -23,17 +23,16 @@ namespace mozilla {
 
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
 class AudioParamTimeline;
 struct ThreeDPoint;
-struct AudioTimelineEvent;
 
 /**
  * The DOM object representing a Web Audio AudioNode.
  *
  * Each AudioNode has a MediaStream representing the actual
  * real-time processing and output of this AudioNode.
  *
  * We track the incoming and outgoing connections to other AudioNodes.
@@ -207,30 +206,25 @@ public:
   void MarkInactive() { Context()->UnregisterActiveNode(this); }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
 
   virtual const char* NodeType() const = 0;
 
 private:
-  friend class AudioBufferSourceNode;
   // This could possibly delete 'this'.
   void DisconnectFromGraph();
 
 protected:
-  static void Callback(AudioNode* aNode) { /* not implemented */ }
-
   // Helpers for sending different value types to streams
   void SendDoubleParameterToStream(uint32_t aIndex, double aValue);
   void SendInt32ParameterToStream(uint32_t aIndex, int32_t aValue);
   void SendThreeDPointParameterToStream(uint32_t aIndex, const ThreeDPoint& aValue);
   void SendChannelMixingParametersToStream();
-  static void SendTimelineEventToStream(AudioNode* aNode, uint32_t aIndex,
-                                        const dom::AudioTimelineEvent& aEvent);
 
 private:
   nsRefPtr<AudioContext> mContext;
 
 protected:
   // Must be set in the constructor. Must not be null unless finished.
   nsRefPtr<AudioNodeStream> mStream;
 
--- a/dom/media/webaudio/AudioNodeStream.cpp
+++ b/dom/media/webaudio/AudioNodeStream.cpp
@@ -488,30 +488,28 @@ AudioNodeStream::AccumulateInputChunk(ui
 }
 
 void
 AudioNodeStream::UpMixDownMixChunk(const AudioBlock* aChunk,
                                    uint32_t aOutputChannelCount,
                                    nsTArray<const float*>& aOutputChannels,
                                    nsTArray<float>& aDownmixBuffer)
 {
-  static const float silenceChannel[WEBAUDIO_BLOCK_SIZE] = {0.f};
-
   for (uint32_t i = 0; i < aChunk->ChannelCount(); i++) {
     aOutputChannels.AppendElement(static_cast<const float*>(aChunk->mChannelData[i]));
   }
   if (aOutputChannels.Length() < aOutputChannelCount) {
     if (mChannelInterpretation == ChannelInterpretation::Speakers) {
-      AudioChannelsUpMix(&aOutputChannels, aOutputChannelCount, SilentChannel::ZeroChannel<float>());
+      AudioChannelsUpMix<float>(&aOutputChannels, aOutputChannelCount, nullptr);
       NS_ASSERTION(aOutputChannelCount == aOutputChannels.Length(),
                    "We called GetAudioChannelsSuperset to avoid this");
     } else {
       // Fill up the remaining aOutputChannels by zeros
       for (uint32_t j = aOutputChannels.Length(); j < aOutputChannelCount; ++j) {
-        aOutputChannels.AppendElement(silenceChannel);
+        aOutputChannels.AppendElement(nullptr);
       }
     }
   } else if (aOutputChannels.Length() > aOutputChannelCount) {
     if (mChannelInterpretation == ChannelInterpretation::Speakers) {
       nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannels;
       outputChannels.SetLength(aOutputChannelCount);
       aDownmixBuffer.SetLength(aOutputChannelCount * WEBAUDIO_BLOCK_SIZE);
       for (uint32_t j = 0; j < aOutputChannelCount; ++j) {
--- a/dom/media/webaudio/AudioParam.cpp
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -39,24 +39,24 @@ AudioParam::Release()
   }
   NS_IMPL_CC_NATIVE_RELEASE_BODY(AudioParam)
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release)
 
 AudioParam::AudioParam(AudioNode* aNode,
-                       AudioParam::CallbackType aCallback,
+                       uint32_t aIndex,
                        float aDefaultValue,
                        const char* aName)
   : AudioParamTimeline(aDefaultValue)
   , mNode(aNode)
-  , mCallback(aCallback)
+  , mName(aName)
+  , mIndex(aIndex)
   , mDefaultValue(aDefaultValue)
-  , mName(aName)
 {
 }
 
 AudioParam::~AudioParam()
 {
   MOZ_ASSERT(mInputNodes.IsEmpty());
 }
 
@@ -115,22 +115,30 @@ AudioParam::Stream()
   AudioNodeStream* nodeStream = mNode->GetStream();
   if (nodeStream) {
     mNodeStreamPort =
       nodeStream->AllocateInputPort(mStream, AudioNodeStream::AUDIO_TRACK);
   }
 
   // Send the stream to the timeline on the MSG side.
   AudioTimelineEvent event(mStream);
-
-  mCallback(mNode, event);
+  SendEventToEngine(event);
 
   return mStream;
 }
 
+void
+AudioParam::SendEventToEngine(const AudioTimelineEvent& aEvent)
+{
+  AudioNodeStream* stream = mNode->GetStream();
+  if (stream) {
+    stream->SendTimelineEvent(mIndex, aEvent);
+  }
+}
+
 float
 AudioParamTimeline::AudioNodeInputValue(size_t aCounter) const
 {
   MOZ_ASSERT(mStream);
 
   // If we have a chunk produced by the AudioNode inputs to the AudioParam,
   // get its value now.  We use aCounter to tell us which frame of the last
   // AudioChunk to look at.
--- a/dom/media/webaudio/AudioParam.h
+++ b/dom/media/webaudio/AudioParam.h
@@ -21,20 +21,18 @@ namespace mozilla {
 namespace dom {
 
 class AudioParam final : public nsWrapperCache,
                          public AudioParamTimeline
 {
   virtual ~AudioParam();
 
 public:
-  typedef void (*CallbackType)(AudioNode* aNode, const AudioTimelineEvent&);
-
   AudioParam(AudioNode* aNode,
-             CallbackType aCallback,
+             uint32_t aIndex,
              float aDefaultValue,
              const char* aName);
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
   NS_IMETHOD_(MozExternalRefCountType) Release(void);
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioParam)
 
   AudioContext* GetParentObject() const
@@ -67,17 +65,17 @@ public:
     if (!ValidateEvent(event, rv)) {
       MOZ_ASSERT(false, "This should not happen, "
                         "setting the value should always work");
       return;
     }
 
     AudioParamTimeline::SetValue(aValue);
 
-    mCallback(mNode, event);
+    SendEventToEngine(event);
   }
 
   void SetValueAtTime(float aValue, double aStartTime, ErrorResult& aRv)
   {
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
@@ -124,17 +122,17 @@ public:
       return;
     }
 
     // Remove some events on the main thread copy.
     AudioEventTimeline::CancelScheduledValues(aStartTime);
 
     AudioTimelineEvent event(AudioTimelineEvent::Cancel, aStartTime, 0.0f);
 
-    mCallback(mNode, event);
+    SendEventToEngine(event);
   }
 
   uint32_t ParentNodeId()
   {
     return mNode->Id();
   }
 
   void GetName(nsAString& aName)
@@ -142,21 +140,16 @@ public:
     aName.AssignASCII(mName);
   }
 
   float DefaultValue() const
   {
     return mDefaultValue;
   }
 
-  AudioNode* Node() const
-  {
-    return mNode;
-  }
-
   const nsTArray<AudioNode::InputNode>& InputNodes() const
   {
     return mInputNodes;
   }
 
   void RemoveInputNode(uint32_t aIndex)
   {
     mInputNodes.RemoveElementAt(aIndex);
@@ -188,20 +181,16 @@ public:
     return amount;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
-protected:
-  nsCycleCollectingAutoRefCnt mRefCnt;
-  NS_DECL_OWNINGTHREAD
-
 private:
   void EventInsertionHelper(ErrorResult& aRv,
                             AudioTimelineEvent::Type aType,
                             double aTime, float aValue,
                             double aTimeConstant = 0.0,
                             float aDuration = 0.0,
                             const float* aCurve = nullptr,
                             uint32_t aCurveLength = 0)
@@ -210,27 +199,31 @@ private:
                              aTimeConstant, aDuration, aCurve, aCurveLength);
 
     if (!ValidateEvent(event, aRv)) {
       return;
     }
 
     AudioEventTimeline::InsertEvent<double>(event);
 
-    mCallback(mNode, event);
+    SendEventToEngine(event);
   }
 
+  void SendEventToEngine(const AudioTimelineEvent& aEvent);
+
+  nsCycleCollectingAutoRefCnt mRefCnt;
+  NS_DECL_OWNINGTHREAD
   nsRefPtr<AudioNode> mNode;
   // For every InputNode, there is a corresponding entry in mOutputParams of the
   // InputNode's mInputNode.
   nsTArray<AudioNode::InputNode> mInputNodes;
-  CallbackType mCallback;
-  const float mDefaultValue;
   const char* mName;
   // The input port used to connect the AudioParam's stream to its node's stream
   nsRefPtr<MediaInputPort> mNodeStreamPort;
+  const uint32_t mIndex;
+  const float mDefaultValue;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -237,20 +237,21 @@ private:
 };
 
 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(BiquadFilterType::Lowpass)
-  , mFrequency(new AudioParam(this, SendFrequencyToStream, 350.f, "frequency"))
-  , mDetune(new AudioParam(this, SendDetuneToStream, 0.f, "detune"))
-  , mQ(new AudioParam(this, SendQToStream, 1.f, "Q"))
-  , mGain(new AudioParam(this, SendGainToStream, 0.f, "gain"))
+  , mFrequency(new AudioParam(this, BiquadFilterNodeEngine::FREQUENCY,
+                              350.f, "frequency"))
+  , mDetune(new AudioParam(this, BiquadFilterNodeEngine::DETUNE, 0.f, "detune"))
+  , mQ(new AudioParam(this, BiquadFilterNodeEngine::Q, 1.f, "Q"))
+  , mGain(new AudioParam(this, BiquadFilterNodeEngine::GAIN, 0.f, "gain"))
 {
   BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 BiquadFilterNode::~BiquadFilterNode()
 {
@@ -331,38 +332,10 @@ BiquadFilterNode::GetFrequencyResponse(c
   double gain = mGain->GetValueAtTime(currentTime);
   double detune = mDetune->GetValueAtTime(currentTime);
 
   WebCore::Biquad biquad;
   SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain, detune);
   biquad.getFrequencyResponse(int(length), frequencies, aMagResponse.Data(), aPhaseResponse.Data());
 }
 
-void
-BiquadFilterNode::SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineEventToStream(This, BiquadFilterNodeEngine::FREQUENCY, aEvent);
-}
-
-void
-BiquadFilterNode::SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineEventToStream(This, BiquadFilterNodeEngine::DETUNE, aEvent);
-}
-
-void
-BiquadFilterNode::SendQToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineEventToStream(This, BiquadFilterNodeEngine::Q, aEvent);
-}
-
-void
-BiquadFilterNode::SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  BiquadFilterNode* This = static_cast<BiquadFilterNode*>(aNode);
-  SendTimelineEventToStream(This, BiquadFilterNodeEngine::GAIN, aEvent);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/BiquadFilterNode.h
+++ b/dom/media/webaudio/BiquadFilterNode.h
@@ -10,17 +10,16 @@
 #include "AudioNode.h"
 #include "AudioParam.h"
 #include "mozilla/dom/BiquadFilterNodeBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 class AudioContext;
-struct AudioTimelineEvent;
 
 class BiquadFilterNode final : public AudioNode
 {
 public:
   explicit BiquadFilterNode(AudioContext* aContext);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(BiquadFilterNode, AudioNode)
@@ -64,26 +63,16 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~BiquadFilterNode();
 
 private:
-  static void SendFrequencyToStream(AudioNode* aNode,
-                                    const AudioTimelineEvent& aEvent);
-  static void SendDetuneToStream(AudioNode* aNode,
-                                const AudioTimelineEvent& aEvente);
-  static void SendQToStream(AudioNode* aNode,
-                            const AudioTimelineEvent& aEvent);
-  static void SendGainToStream(AudioNode* aNode,
-                               const AudioTimelineEvent& aEvent);
-
-private:
   BiquadFilterType mType;
   nsRefPtr<AudioParam> mFrequency;
   nsRefPtr<AudioParam> mDetune;
   nsRefPtr<AudioParam> mQ;
   nsRefPtr<AudioParam> mGain;
 };
 
 } // namespace dom
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -191,17 +191,17 @@ public:
   int32_t mLeftOverData;
 };
 
 DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
-  , mDelay(new AudioParam(this, SendDelayToStream, 0.0f, "delayTime"))
+  , mDelay(new AudioParam(this, DelayNodeEngine::DELAY, 0.0f, "delayTime"))
 {
   DelayNodeEngine* engine =
     new DelayNodeEngine(this, aContext->Destination(),
                         aContext->SampleRate() * aMaxDelay);
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
@@ -224,17 +224,10 @@ DelayNode::SizeOfIncludingThis(MallocSiz
 }
 
 JSObject*
 DelayNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DelayNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
-void
-DelayNode::SendDelayToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  DelayNode* This = static_cast<DelayNode*>(aNode);
-  SendTimelineEventToStream(This, DelayNodeEngine::DELAY, aEvent);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DelayNode.h
+++ b/dom/media/webaudio/DelayNode.h
@@ -37,18 +37,16 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~DelayNode();
 
 private:
-  static void SendDelayToStream(AudioNode* aNode,
-                                const AudioTimelineEvent& aEvent);
   friend class DelayNodeEngine;
 
 private:
   nsRefPtr<AudioParam> mDelay;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DynamicsCompressorNode.cpp
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -181,22 +181,27 @@ private:
   nsAutoPtr<DynamicsCompressor> mCompressor;
 };
 
 DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
-  , mThreshold(new AudioParam(this, SendThresholdToStream, -24.f, "threshold"))
-  , mKnee(new AudioParam(this, SendKneeToStream, 30.f, "knee"))
-  , mRatio(new AudioParam(this, SendRatioToStream, 12.f, "ratio"))
+  , mThreshold(new AudioParam(this, DynamicsCompressorNodeEngine::THRESHOLD,
+                              -24.f, "threshold"))
+  , mKnee(new AudioParam(this, DynamicsCompressorNodeEngine::KNEE,
+                         30.f, "knee"))
+  , mRatio(new AudioParam(this, DynamicsCompressorNodeEngine::RATIO,
+                          12.f, "ratio"))
   , mReduction(0)
-  , mAttack(new AudioParam(this, SendAttackToStream, 0.003f, "attack"))
-  , mRelease(new AudioParam(this, SendReleaseToStream, 0.25f, "release"))
+  , mAttack(new AudioParam(this, DynamicsCompressorNodeEngine::ATTACK,
+                           0.003f, "attack"))
+  , mRelease(new AudioParam(this, DynamicsCompressorNodeEngine::RELEASE,
+                            0.25f, "release"))
 {
   DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 DynamicsCompressorNode::~DynamicsCompressorNode()
 {
@@ -221,50 +226,10 @@ DynamicsCompressorNode::SizeOfIncludingT
 }
 
 JSObject*
 DynamicsCompressorNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return DynamicsCompressorNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
-void
-DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode,
-                                              const AudioTimelineEvent& aEvent)
-{
-  DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::THRESHOLD, aEvent);
-}
-
-void
-DynamicsCompressorNode::SendKneeToStream(AudioNode* aNode,
-                                         const AudioTimelineEvent& aEvent)
-{
-  DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::KNEE, aEvent);
-}
-
-void
-DynamicsCompressorNode::SendRatioToStream(AudioNode* aNode,
-                                          const AudioTimelineEvent& aEvent)
-{
-  DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::RATIO, aEvent);
-}
-
-void
-DynamicsCompressorNode::SendAttackToStream(AudioNode* aNode,
-                                          const AudioTimelineEvent& aEvent)
-{
-  DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::ATTACK, aEvent);
-}
-
-void
-DynamicsCompressorNode::SendReleaseToStream(AudioNode* aNode,
-                                            const AudioTimelineEvent& aEvent)
-{
-  DynamicsCompressorNode* This = static_cast<DynamicsCompressorNode*>(aNode);
-  SendTimelineEventToStream(This, DynamicsCompressorNodeEngine::RELEASE, aEvent);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/DynamicsCompressorNode.h
+++ b/dom/media/webaudio/DynamicsCompressorNode.h
@@ -69,28 +69,16 @@ public:
     MOZ_ASSERT(NS_IsMainThread());
     mReduction = aReduction;
   }
 
 protected:
   virtual ~DynamicsCompressorNode();
 
 private:
-  static void SendThresholdToStream(AudioNode* aNode,
-                                    const AudioTimelineEvent& aEvent);
-  static void SendKneeToStream(AudioNode* aNode,
-                               const AudioTimelineEvent& aEvent);
-  static void SendRatioToStream(AudioNode* aNode,
-                                const AudioTimelineEvent& aEvent);
-  static void SendAttackToStream(AudioNode* aNode,
-                                 const AudioTimelineEvent& aEvent);
-  static void SendReleaseToStream(AudioNode* aNode,
-                                  const AudioTimelineEvent& aEvent);
-
-private:
   nsRefPtr<AudioParam> mThreshold;
   nsRefPtr<AudioParam> mKnee;
   nsRefPtr<AudioParam> mRatio;
   float mReduction;
   nsRefPtr<AudioParam> mAttack;
   nsRefPtr<AudioParam> mRelease;
 };
 
--- a/dom/media/webaudio/GainNode.cpp
+++ b/dom/media/webaudio/GainNode.cpp
@@ -112,17 +112,17 @@ public:
   AudioParamTimeline mGain;
 };
 
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
-  , mGain(new AudioParam(this, SendGainToStream, 1.0f, "gain"))
+  , mGain(new AudioParam(this, GainNodeEngine::GAIN, 1.0f, "gain"))
 {
   GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 GainNode::~GainNode()
 {
@@ -143,17 +143,10 @@ GainNode::SizeOfIncludingThis(MallocSize
 }
 
 JSObject*
 GainNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return GainNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
-void
-GainNode::SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  GainNode* This = static_cast<GainNode*>(aNode);
-  SendTimelineEventToStream(This, GainNodeEngine::GAIN, aEvent);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/GainNode.h
+++ b/dom/media/webaudio/GainNode.h
@@ -37,19 +37,16 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~GainNode();
 
 private:
-  static void SendGainToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
-
-private:
   nsRefPtr<AudioParam> mGain;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -397,18 +397,19 @@ public:
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(OscillatorType::Sine)
-  , mFrequency(new AudioParam(this, SendFrequencyToStream, 440.0f, "frequency"))
-  , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
+  , mFrequency(new AudioParam(this, OscillatorNodeEngine::FREQUENCY,
+                              440.0f, "frequency"))
+  , mDetune(new AudioParam(this, OscillatorNodeEngine::DETUNE, 0.0f, "detune"))
   , mStartCalled(false)
 {
   OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NEED_MAIN_THREAD_FINISHED);
   engine->SetSourceStream(mStream);
   mStream->AddMainThreadListener(this);
 }
@@ -448,36 +449,16 @@ OscillatorNode::DestroyMediaStream()
 {
   if (mStream) {
     mStream->RemoveMainThreadListener(this);
   }
   AudioNode::DestroyMediaStream();
 }
 
 void
-OscillatorNode::SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
-  if (!This->mStream) {
-    return;
-  }
-  SendTimelineEventToStream(This, OscillatorNodeEngine::FREQUENCY, aEvent);
-}
-
-void
-OscillatorNode::SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  OscillatorNode* This = static_cast<OscillatorNode*>(aNode);
-  if (!This->mStream) {
-    return;
-  }
-  SendTimelineEventToStream(This, OscillatorNodeEngine::DETUNE, aEvent);
-}
-
-void
 OscillatorNode::SendTypeToStream()
 {
   if (!mStream) {
     return;
   }
   if (mType == OscillatorType::Custom) {
     // The engine assumes we'll send the custom data before updating the type.
     SendPeriodicWaveToStream();
--- a/dom/media/webaudio/OscillatorNode.h
+++ b/dom/media/webaudio/OscillatorNode.h
@@ -82,18 +82,16 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~OscillatorNode();
 
 private:
-  static void SendFrequencyToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
-  static void SendDetuneToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent);
   void SendTypeToStream();
   void SendPeriodicWaveToStream();
 
 private:
   OscillatorType mType;
   nsRefPtr<PeriodicWave> mPeriodicWave;
   nsRefPtr<AudioParam> mFrequency;
   nsRefPtr<AudioParam> mDetune;
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -166,17 +166,17 @@ public:
   AudioParamTimeline mPan;
 };
 
 StereoPannerNode::StereoPannerNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
-  , mPan(new AudioParam(this, SendPanToStream, 0.f, "pan"))
+  , mPan(new AudioParam(this, StereoPannerNodeEngine::PAN, 0.f, "pan"))
 {
   StereoPannerNodeEngine* engine = new StereoPannerNodeEngine(this, aContext->Destination());
   mStream = AudioNodeStream::Create(aContext, engine,
                                     AudioNodeStream::NO_STREAM_FLAGS);
 }
 
 StereoPannerNode::~StereoPannerNode()
 {
@@ -197,17 +197,10 @@ StereoPannerNode::SizeOfIncludingThis(Ma
 }
 
 JSObject*
 StereoPannerNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return StereoPannerNodeBinding::Wrap(aCx, this, aGivenProto);
 }
 
-void
-StereoPannerNode::SendPanToStream(AudioNode* aNode, const AudioTimelineEvent& aEvent)
-{
-  StereoPannerNode* This = static_cast<StereoPannerNode*>(aNode);
-  SendTimelineEventToStream(This, StereoPannerNodeEngine::PAN, aEvent);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/StereoPannerNode.h
+++ b/dom/media/webaudio/StereoPannerNode.h
@@ -55,18 +55,16 @@ public:
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override;
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
 protected:
   virtual ~StereoPannerNode();
 
 private:
-  static void SendPanToStream(AudioNode* aNode,
-                              const AudioTimelineEvent& aEvent);
   nsRefPtr<AudioParam> mPan;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
 
--- a/dom/svg/nsSVGPathGeometryElement.cpp
+++ b/dom/svg/nsSVGPathGeometryElement.cpp
@@ -76,17 +76,17 @@ nsSVGPathGeometryElement::GetMarkPoints(
 }
 
 already_AddRefed<Path>
 nsSVGPathGeometryElement::GetOrBuildPath(const DrawTarget& aDrawTarget,
                                          FillRule aFillRule)
 {
   // We only cache the path if it matches the backend used for screen painting:
   bool cacheable  = aDrawTarget.GetBackendType() ==
-                      gfxPlatform::GetPlatform()->GetContentBackend();
+                    gfxPlatform::GetPlatform()->GetDefaultContentBackend();
 
   // Checking for and returning mCachedPath before checking the pref means
   // that the pref is only live on page reload (or app restart for SVG in
   // chrome). The benefit is that we avoid causing a CPU memory cache miss by
   // looking at the global variable that the pref's stored in.
   if (cacheable && mCachedPath) {
     if (aDrawTarget.GetBackendType() == mCachedPath->GetBackendType()) {
       RefPtr<Path> path(mCachedPath);
--- a/dom/workers/test/serviceworkers/browser_force_refresh.js
+++ b/dom/workers/test/serviceworkers/browser_force_refresh.js
@@ -24,16 +24,21 @@ function test() {
     var tab = gBrowser.addTab(url);
     gBrowser.selectedTab = tab;
 
     var cachedLoad = false;
 
     function eventHandler(event) {
       if (event.type === 'base-load') {
         if (cachedLoad) {
+          removeEventListener('base-load', eventHandler, true);
+          removeEventListener('base-register', eventHandler, true);
+          removeEventListener('base-sw-ready', eventHandler, true);
+          removeEventListener('cached-load', eventHandler, true);
+
           gBrowser.removeTab(tab);
           executeSoon(finish);
         }
       } else if (event.type === 'base-register') {
         ok(!cachedLoad, 'cached load should not occur before base register');
         refresh();
       } else if (event.type === 'base-sw-ready') {
         ok(!cachedLoad, 'cached load should not occur before base ready');
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -69,17 +69,17 @@ public:
 
 BasicCompositor::BasicCompositor(nsIWidget *aWidget)
   : mWidget(aWidget)
 {
   MOZ_COUNT_CTOR(BasicCompositor);
   SetBackend(LayersBackend::LAYERS_BASIC);
 
   mMaxTextureSize =
-    Factory::GetMaxSurfaceSize(gfxPlatform::GetPlatform()->GetContentBackend());
+    Factory::GetMaxSurfaceSize(gfxPlatform::GetPlatform()->GetContentBackendFor(LayersBackend::LAYERS_BASIC));
 }
 
 BasicCompositor::~BasicCompositor()
 {
   MOZ_COUNT_DTOR(BasicCompositor);
 }
 
 bool
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -338,48 +338,48 @@ CreateBufferTextureClient(ISurfaceAlloca
   }
   RefPtr<BufferTextureClient> result = new ShmemTextureClient(aAllocator, aFormat,
                                                               aMoz2DBackend,
                                                               aTextureFlags);
   return result.forget();
 }
 
 static inline gfx::BackendType
-BackendTypeForBackendSelector(BackendSelector aSelector)
+BackendTypeForBackendSelector(LayersBackend aLayersBackend, BackendSelector aSelector)
 {
   switch (aSelector) {
     case BackendSelector::Canvas:
       return gfxPlatform::GetPlatform()->GetPreferredCanvasBackend();
     case BackendSelector::Content:
-      return gfxPlatform::GetPlatform()->GetContentBackend();
+      return gfxPlatform::GetPlatform()->GetContentBackendFor(aLayersBackend);
     default:
       MOZ_ASSERT_UNREACHABLE("Unknown backend selector");
       return gfx::BackendType::NONE;
   }
 };
 
 // static
 already_AddRefed<TextureClient>
 TextureClient::CreateForDrawing(ISurfaceAllocator* aAllocator,
                                 gfx::SurfaceFormat aFormat,
                                 gfx::IntSize aSize,
                                 BackendSelector aSelector,
                                 TextureFlags aTextureFlags,
                                 TextureAllocationFlags aAllocFlags)
 {
-  gfx::BackendType moz2DBackend = BackendTypeForBackendSelector(aSelector);
+  LayersBackend parentBackend = aAllocator->GetCompositorBackendType();
+  gfx::BackendType moz2DBackend = BackendTypeForBackendSelector(parentBackend, aSelector);
 
   RefPtr<TextureClient> texture;
 
 #if defined(MOZ_WIDGET_GONK) || defined(XP_WIN)
   int32_t maxTextureSize = aAllocator->GetMaxTextureSize();
 #endif
 
 #ifdef XP_WIN
-  LayersBackend parentBackend = aAllocator->GetCompositorBackendType();
   if (parentBackend == LayersBackend::LAYERS_D3D11 &&
       (moz2DBackend == gfx::BackendType::DIRECT2D ||
        moz2DBackend == gfx::BackendType::DIRECT2D1_1) &&
       aSize.width <= maxTextureSize &&
       aSize.height <= maxTextureSize)
   {
     texture = new TextureClientD3D11(aAllocator, aFormat, aTextureFlags);
   }
@@ -402,17 +402,16 @@ TextureClient::CreateForDrawing(ISurface
       texture = new TextureClientMemoryDIB(aAllocator, aFormat, aTextureFlags);
     } else {
       texture = new TextureClientShmemDIB(aAllocator, aFormat, aTextureFlags);
     }
   }
 #endif
 
 #ifdef MOZ_X11
-  LayersBackend parentBackend = aAllocator->GetCompositorBackendType();
   gfxSurfaceType type =
     gfxPlatform::GetPlatform()->ScreenReferenceSurface()->GetType();
 
   if (parentBackend == LayersBackend::LAYERS_BASIC &&
       moz2DBackend == gfx::BackendType::CAIRO &&
       type == gfxSurfaceType::Xlib)
   {
     texture = new TextureClientX11(aAllocator, aFormat, aTextureFlags);
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -574,17 +574,18 @@ TextureClientD3D11::AllocateForSurface(g
     return false;
   }
 
   gfxWindowsPlatform* windowsPlatform = gfxWindowsPlatform::GetPlatform();
   ID3D11Device* d3d11device = windowsPlatform->GetD3D11DeviceForCurrentThread();
 
   // When we're not on the main thread we're not going to be using Direct2D
   // to access the contents of this texture client so we will always use D3D11.
-  bool haveD3d11Backend = windowsPlatform->GetContentBackend() == BackendType::DIRECT2D1_1 || !NS_IsMainThread();
+  BackendType backend = windowsPlatform->GetContentBackendFor(LayersBackend::LAYERS_D3D11);
+  bool haveD3d11Backend = (backend == BackendType::DIRECT2D1_1) || !NS_IsMainThread();
 
   if (haveD3d11Backend) {
     if (!AllocateD3D11Surface(d3d11device, aSize)) {
       return false;
     }
   } else {
     ID3D10Device* device = gfxWindowsPlatform::GetPlatform()->GetD3D10Device();
 
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -266,17 +266,26 @@ public:
     virtual void GetAzureBackendInfo(mozilla::widget::InfoObject &aObj) {
       aObj.DefineProperty("AzureCanvasBackend", GetBackendName(mPreferredCanvasBackend));
       aObj.DefineProperty("AzureSkiaAccelerated", UseAcceleratedSkiaCanvas());
       aObj.DefineProperty("AzureFallbackCanvasBackend", GetBackendName(mFallbackCanvasBackend));
       aObj.DefineProperty("AzureContentBackend", GetBackendName(mContentBackend));
     }
     void GetApzSupportInfo(mozilla::widget::InfoObject& aObj);
 
-    mozilla::gfx::BackendType GetContentBackend() {
+    // Get the default content backend that will be used with the default
+    // compositor. If the compositor is known when calling this function,
+    // GetContentBackendFor() should be called instead.
+    mozilla::gfx::BackendType GetDefaultContentBackend() {
+      return mContentBackend;
+    }
+
+    // Return the best content backend available that is compatible with the
+    // given layers backend.
+    virtual mozilla::gfx::BackendType GetContentBackendFor(mozilla::layers::LayersBackend aLayers) {
       return mContentBackend;
     }
 
     mozilla::gfx::BackendType GetPreferredCanvasBackend() {
       return mPreferredCanvasBackend;
     }
     mozilla::gfx::BackendType GetFallbackCanvasBackend() {
       return mFallbackCanvasBackend;
--- a/gfx/thebes/gfxPlatformGtk.h
+++ b/gfx/thebes/gfxPlatformGtk.h
@@ -94,18 +94,18 @@ public:
     static GdkDrawable *GetGdkDrawable(cairo_surface_t *target);
 #endif
 
     static int32_t GetDPI();
     static double  GetDPIScale();
 
     bool UseXRender() {
 #if defined(MOZ_X11)
-        if (GetContentBackend() != mozilla::gfx::BackendType::NONE &&
-            GetContentBackend() != mozilla::gfx::BackendType::CAIRO)
+        if (GetDefaultContentBackend() != mozilla::gfx::BackendType::NONE &&
+            GetDefaultContentBackend() != mozilla::gfx::BackendType::CAIRO)
             return false;
 
         return sUseXRender;
 #else
         return false;
 #endif
     }
 
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -507,22 +507,24 @@ gfxWindowsPlatform::HandleDeviceReset()
 
   // Since we got a device reset, we must ask the parent process for an updated
   // list of which devices to create.
   UpdateDeviceInitData();
   InitializeDevices();
   return true;
 }
 
+static const BackendType SOFTWARE_BACKEND = BackendType::CAIRO;
+
 void
 gfxWindowsPlatform::UpdateBackendPrefs()
 {
-  uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO);
-  uint32_t contentMask = BackendTypeBit(BackendType::CAIRO);
-  BackendType defaultBackend = BackendType::CAIRO;
+  uint32_t canvasMask = BackendTypeBit(SOFTWARE_BACKEND);
+  uint32_t contentMask = BackendTypeBit(SOFTWARE_BACKEND);
+  BackendType defaultBackend = SOFTWARE_BACKEND;
   if (GetD2DStatus() == FeatureStatus::Available) {
     mRenderMode = RENDER_DIRECT2D;
     canvasMask |= BackendTypeBit(BackendType::DIRECT2D);
     contentMask |= BackendTypeBit(BackendType::DIRECT2D);
     if (GetD2D1Status() == FeatureStatus::Available) {
       contentMask |= BackendTypeBit(BackendType::DIRECT2D1_1);
       canvasMask |= BackendTypeBit(BackendType::DIRECT2D1_1);
       defaultBackend = BackendType::DIRECT2D1_1;
@@ -545,16 +547,27 @@ gfxWindowsPlatform::UpdateRenderMode()
   UpdateBackendPrefs();
 
   if (didReset) {
     mScreenReferenceDrawTarget =
       CreateOffscreenContentDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8);
   }
 }
 
+mozilla::gfx::BackendType
+gfxWindowsPlatform::GetContentBackendFor(mozilla::layers::LayersBackend aLayers)
+{
+  if (aLayers == LayersBackend::LAYERS_D3D11) {
+    return gfxPlatform::GetDefaultContentBackend();
+  }
+
+  // If we're not accelerated with D3D11, never use D2D.
+  return SOFTWARE_BACKEND;
+}
+
 #ifdef CAIRO_HAS_D2D_SURFACE
 HRESULT
 gfxWindowsPlatform::CreateDevice(nsRefPtr<IDXGIAdapter1> &adapter1,
                                  int featureLevelIndex)
 {
   nsModuleHandle d3d10module(LoadLibrarySystem32(L"d3d10_1.dll"));
   if (!d3d10module)
     return E_FAIL;
--- a/gfx/thebes/gfxWindowsPlatform.h
+++ b/gfx/thebes/gfxWindowsPlatform.h
@@ -209,16 +209,18 @@ public:
 
     /**
      * Check whether format is supported on a platform or not (if unclear, returns true)
      */
     virtual bool IsFontFormatSupported(nsIURI *aFontURI, uint32_t aFormatFlags);
 
     virtual bool DidRenderingDeviceReset(DeviceResetReason* aResetReason = nullptr);
 
+    mozilla::gfx::BackendType GetContentBackendFor(mozilla::layers::LayersBackend aLayers) override;
+
     // ClearType is not always enabled even when available (e.g. Windows XP)
     // if either of these prefs are enabled and apply, use ClearType rendering
     bool UseClearTypeForDownloadableFonts();
     bool UseClearTypeAlways();
 
     static void GetDLLVersion(char16ptr_t aDLLPath, nsAString& aVersion);
 
     // returns ClearType tuning information for each display
@@ -328,17 +330,17 @@ private:
 
     mozilla::gfx::FeatureStatus AttemptWARPDeviceCreation();
     bool AttemptWARPDeviceCreationHelper(
         mozilla::ScopedGfxFeatureReporter& aReporterWARP, HRESULT& aResOut);
 
     mozilla::gfx::FeatureStatus AttemptD3D11ImageBridgeDeviceCreation();
 
     mozilla::gfx::FeatureStatus AttemptD3D11ContentDeviceCreation();
-    bool gfxWindowsPlatform::AttemptD3D11ContentDeviceCreationHelper(
+    bool AttemptD3D11ContentDeviceCreationHelper(
         IDXGIAdapter1* aAdapter, HRESULT& aResOut);
 
     bool CanUseD3D11ImageBridge();
     bool ContentAdapterIsParentAdapter(ID3D11Device* device);
 
     void DisableD3D11AfterCrash();
     void ResetD3D11Devices();
 
--- a/image/Downscaler.h
+++ b/image/Downscaler.h
@@ -151,17 +151,17 @@ public:
   {
     MOZ_RELEASE_ASSERT(false, "Skia is not enabled");
   }
 
   const nsIntSize& OriginalSize() const { return nsIntSize(); }
   const nsIntSize& TargetSize() const { return nsIntSize(); }
   const gfxSize& Scale() const { return gfxSize(1.0, 1.0); }
 
-  nsresult BeginFrame(const nsIntSize&, uint8_t*, bool, bool = false)
+  nsresult BeginFrame(const nsIntSize&, const Maybe<nsIntRect>&, uint8_t*, bool, bool = false)
   {
     return NS_ERROR_FAILURE;
   }
 
   uint8_t* RowBuffer() { return nullptr; }
   void ClearRow(uint32_t = 0) { }
   void CommitRow() { }
   bool HasInvalidation() const { return false; }
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -148,17 +148,18 @@ void StopAllOffThreadCompilations(Zone* 
 void StopAllOffThreadCompilations(JSCompartment* comp);
 
 void LazyLink(JSContext* cx, HandleScript calleescript);
 uint8_t* LazyLinkTopActivation(JSContext* cx);
 
 static inline bool
 IsIonEnabled(JSContext* cx)
 {
-#ifdef JS_CODEGEN_NONE
+    // The ARM64 Ion engine is not yet implemented.
+#if defined(JS_CODEGEN_NONE) || defined(JS_CODEGEN_ARM64)
     return false;
 #else
     return cx->runtime()->options().ion() &&
            cx->runtime()->options().baseline() &&
            cx->runtime()->jitSupportsFloatingPoint;
 #endif
 }
 
--- a/js/src/jit/arm64/AtomicOperations-arm64.h
+++ b/js/src/jit/arm64/AtomicOperations-arm64.h
@@ -99,16 +99,44 @@ js::jit::AtomicOperations::fetchOrSeqCst
 template<typename T>
 inline T
 js::jit::AtomicOperations::fetchXorSeqCst(T* addr, T val)
 {
     static_assert(sizeof(T) <= 4, "not available for 8-byte values yet");
     return __atomic_fetch_xor(addr, val, __ATOMIC_SEQ_CST);
 }
 
+template <typename T>
+inline T
+js::jit::AtomicOperations::loadSafeWhenRacy(T* addr)
+{
+    return *addr; // FIXME (1208663): not yet safe
+}
+
+template <typename T>
+inline void
+js::jit::AtomicOperations::storeSafeWhenRacy(T* addr, T val)
+{
+    *addr = val; // FIXME (1208663): not yet safe
+}
+
+inline void
+js::jit::AtomicOperations::memcpySafeWhenRacy(void* dest, const void* src,
+                                              size_t nbytes)
+{
+    memcpy(dest, src, nbytes); // FIXME (1208663): not yet safe
+}
+
+inline void
+js::jit::AtomicOperations::memmoveSafeWhenRacy(void* dest, const void* src,
+                                               size_t nbytes)
+{
+    memmove(dest, src, nbytes); // FIXME (1208663): not yet safe
+}
+
 template<size_t nbytes>
 inline void
 js::jit::RegionLock::acquire(void* addr)
 {
     uint32_t zero = 0;
     uint32_t one = 1;
     while (!__atomic_compare_exchange(&spinlock, &zero, &one, false, __ATOMIC_ACQUIRE, __ATOMIC_ACQUIRE)) {
         zero = 0;
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -233,18 +233,23 @@ MacroAssemblerCompat::branchPtrInNursery
 
 void
 MacroAssemblerCompat::branchValueIsNurseryObject(Condition cond, ValueOperand value, Register temp,
                                                  Label* label)
 {
     MOZ_ASSERT(cond == Assembler::Equal || cond == Assembler::NotEqual);
     MOZ_ASSERT(temp != ScratchReg && temp != ScratchReg2); // Both may be used internally.
 
+    const Nursery& nursery = GetJitContext()->runtime->gcNursery();
+
+    // Avoid creating a bogus ObjectValue below.
+    if (!nursery.exists())
+        return;
+
     // 'Value' representing the start of the nursery tagged as a JSObject
-    const Nursery& nursery = GetJitContext()->runtime->gcNursery();
     Value start = ObjectValue(*reinterpret_cast<JSObject*>(nursery.start()));
 
     movePtr(ImmWord(-ptrdiff_t(start.asRawBits())), temp);
     addPtr(value.valueReg(), temp);
     branchPtr(cond == Assembler::Equal ? Assembler::Below : Assembler::AboveOrEqual,
               temp, ImmWord(nursery.nurserySize()), label);
 }
 
--- a/js/src/jit/arm64/vixl/Instrument-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Instrument-vixl.cpp
@@ -131,18 +131,18 @@ Instrument::Instrument(const char* dataf
     sizeof(kCounterList) / sizeof(CounterDescriptor);
 
   // Dump an instrumentation description comment at the top of the file.
   fprintf(output_stream_, "# counters=%d\n", num_counters);
   fprintf(output_stream_, "# sample_period=%" PRIu64 "\n", sample_period_);
 
   // Construct Counter objects from counter description array.
   for (int i = 0; i < num_counters; i++) {
-    Counter* counter = js_new<Counter>(kCounterList[i].name, kCounterList[i].type);
-    counters_.append(counter);
+    if (Counter* counter = js_new<Counter>(kCounterList[i].name, kCounterList[i].type))
+      counters_.append(counter);
   }
 
   DumpCounterNames();
 }
 
 
 Instrument::~Instrument() {
   // Dump any remaining instruction data to the output file.
--- a/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/MozSimulator-vixl.cpp
@@ -32,36 +32,29 @@
 #include "vm/Runtime.h"
 
 namespace vixl {
 
 
 using mozilla::DebugOnly;
 using js::jit::ABIFunctionType;
 
-
-Simulator::Simulator() {
-  decoder_ = js_new<Decoder>();
-  if (!decoder_) {
-    MOZ_ReportAssertionFailure("[unhandlable oom] Decoder", __FILE__, __LINE__);
-    MOZ_CRASH();
-  }
-
-  // FIXME: This just leaks the Decoder object for now, which is probably OK.
-  // FIXME: We should free it at some point.
-  // FIXME: Note that it can't be stored in the SimulatorRuntime due to lifetime conflicts.
-  this->init(decoder_, stdout);
+Simulator::Simulator(Decoder* decoder, FILE* stream)
+  : stream_(nullptr)
+  , print_disasm_(nullptr)
+  , instrumentation_(nullptr)
+  , stack_(nullptr)
+  , stack_limit_(nullptr)
+  , decoder_(nullptr)
+  , oom_(false)
+  , lock_(nullptr)
+{
+    this->init(decoder, stream);
 }
 
-
-Simulator::Simulator(Decoder* decoder, FILE* stream) {
-  this->init(decoder, stream);
-}
-
-
 void Simulator::ResetState() {
   // Reset the system registers.
   nzcv_ = SimSystemRegister::DefaultValueFor(NZCV);
   fpcr_ = SimSystemRegister::DefaultValueFor(FPCR);
 
   // Reset registers to 0.
   pc_ = NULL;
   pc_modified_ = false;
@@ -87,81 +80,89 @@ void Simulator::init(Decoder* decoder, F
   VIXL_ASSERT((static_cast<uint32_t>(-1) >> 1) == 0x7FFFFFFF);
 
   // Set up the decoder.
   decoder_ = decoder;
   decoder_->AppendVisitor(this);
 
   stream_ = stream;
   print_disasm_ = js_new<PrintDisassembler>(stream_);
+  if (!print_disasm_) {
+    oom_ = true;
+    return;
+  }
   set_coloured_trace(false);
   trace_parameters_ = LOG_NONE;
 
   ResetState();
 
   // Allocate and set up the simulator stack.
   stack_ = (byte*)js_malloc(stack_size_);
+  if (!stack_) {
+    oom_ = true;
+    return;
+  }
   stack_limit_ = stack_ + stack_protection_size_;
   // Configure the starting stack pointer.
   //  - Find the top of the stack.
   byte * tos = stack_ + stack_size_;
   //  - There's a protection region at both ends of the stack.
   tos -= stack_protection_size_;
   //  - The stack pointer must be 16-byte aligned.
   tos = AlignDown(tos, 16);
   set_sp(tos);
 
   // Set the sample period to 10, as the VIXL examples and tests are short.
   instrumentation_ = js_new<Instrument>("vixl_stats.csv", 10);
+  if (!instrumentation_) {
+    oom_ = true;
+    return;
+  }
 
   // Print a warning about exclusive-access instructions, but only the first
   // time they are encountered. This warning can be silenced using
   // SilenceExclusiveAccessWarning().
   print_exclusive_access_warning_ = true;
 
   lock_ = PR_NewLock();
-  if (!lock_)
-    MOZ_CRASH("Could not allocate simulator lock.");
+  if (!lock_) {
+    oom_ = true;
+    return;
+  }
 #ifdef DEBUG
   lockOwner_ = nullptr;
 #endif
   redirection_ = nullptr;
 }
 
 
 Simulator* Simulator::Current() {
   return js::TlsPerThreadData.get()->simulator();
 }
 
 
 Simulator* Simulator::Create() {
-  Decoder* decoder = js_new<vixl::Decoder>();
-  if (!decoder) {
-    MOZ_ReportAssertionFailure("[unhandlable oom] Decoder", __FILE__, __LINE__);
-    MOZ_CRASH();
-  }
+  Decoder *decoder = js_new<vixl::Decoder>();
+  if (!decoder)
+    return nullptr;
 
   // FIXME: This just leaks the Decoder object for now, which is probably OK.
   // FIXME: We should free it at some point.
   // FIXME: Note that it can't be stored in the SimulatorRuntime due to lifetime conflicts.
-  if (getenv("USE_DEBUGGER") != nullptr) {
-    Debugger* debugger = js_new<Debugger>(decoder, stdout);
-    if (!debugger) {
-      MOZ_ReportAssertionFailure("[unhandlable oom] Decoder", __FILE__, __LINE__);
-      MOZ_CRASH();
-    }
-    return debugger;
-  }
+  Simulator *sim;
+  if (getenv("USE_DEBUGGER") != nullptr)
+    sim = js_new<Debugger>(decoder, stdout);
+  else
+    sim = js_new<Simulator>(decoder, stdout);
 
-  Simulator* sim = js_new<Simulator>();
-  if (!sim) {
-    MOZ_CRASH("NEED SIMULATOR");
+  // Check if Simulator:init ran out of memory.
+  if (sim && sim->oom()) {
+    js_delete(sim);
     return nullptr;
   }
-  sim->init(decoder, stdout);
 
   return sim;
 }
 
 
 void Simulator::Destroy(Simulator* sim) {
   js_delete(sim);
 }
--- a/js/src/jit/arm64/vixl/Simulator-vixl.cpp
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.cpp
@@ -65,21 +65,25 @@ SimSystemRegister SimSystemRegister::Def
       return SimSystemRegister();
   }
 }
 
 
 Simulator::~Simulator() {
   js_free(stack_);
   // The decoder may outlive the simulator.
-  decoder_->RemoveVisitor(print_disasm_);
-  js_delete(print_disasm_);
-
-  decoder_->RemoveVisitor(instrumentation_);
-  js_delete(instrumentation_);
+  if (print_disasm_) {
+    decoder_->RemoveVisitor(print_disasm_);
+    js_delete(print_disasm_);
+  }
+
+  if (instrumentation_) {
+    decoder_->RemoveVisitor(instrumentation_);
+    js_delete(instrumentation_);
+  }
 }
 
 
 void Simulator::Run() {
   pc_modified_ = false;
   while (pc_ != kEndOfSimAddress) {
     ExecuteInstruction();
   }
--- a/js/src/jit/arm64/vixl/Simulator-vixl.h
+++ b/js/src/jit/arm64/vixl/Simulator-vixl.h
@@ -317,17 +317,16 @@ class Redirection;
 class Simulator : public DecoderVisitor {
   friend class AutoLockSimulatorCache;
 
  public:
   explicit Simulator(Decoder* decoder, FILE* stream = stdout);
   ~Simulator();
 
   // Moz changes.
-  explicit Simulator();
   void init(Decoder* decoder, FILE* stream);
   static Simulator* Current();
   static Simulator* Create();
   static void Destroy(Simulator* sim);
   uintptr_t stackLimit() const;
   uintptr_t* addressOfStackLimit();
   bool overRecursed(uintptr_t newsp = 0) const;
   bool overRecursedWithExtra(uint32_t extra) const;
@@ -971,16 +970,24 @@ class Simulator : public DecoderVisitor 
 
   // Indicates whether the instruction instrumentation is active.
   bool instruction_stats_;
 
   // Indicates whether the exclusive-access warning has been printed.
   bool print_exclusive_access_warning_;
   void PrintExclusiveAccessWarning();
 
+  // Indicates that the simulator ran out of memory at some point.
+  // Data structures may not be fully allocated.
+  bool oom_;
+
+ public:
+  // True if the simulator ran out of memory during or after construction.
+  bool oom() const { return oom_; }
+
  protected:
   // Moz: Synchronizes access between main thread and compilation threads.
   PRLock* lock_;
 #ifdef DEBUG
   PRThread* lockOwner_;
 #endif
   Redirection* redirection_;
   mozilla::Vector<int64_t, 0, js::SystemAllocPolicy> spStack_;
--- a/js/src/jsapi-tests/testPreserveJitCode.cpp
+++ b/js/src/jsapi-tests/testPreserveJitCode.cpp
@@ -1,12 +1,15 @@
 /* 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/. */
 
+// For js::jit::IsIonEnabled().
+#include "jit/Ion.h"
+
 #include "jsapi-tests/tests.h"
 
 using namespace JS;
 
 static void
 ScriptCallback(JSRuntime* rt, void* data, JSScript* script)
 {
     unsigned& count = *static_cast<unsigned*>(data);
@@ -35,16 +38,25 @@ testPreserveJitCode(bool preserveJitCode
     rt->options().setBaseline(true);
     rt->options().setIon(true);
     rt->setOffthreadIonCompilationEnabled(false);
 
     RootedObject global(cx, createTestGlobal(preserveJitCode));
     CHECK(global);
     JSAutoCompartment ac(cx, global);
 
+#ifdef JS_CODEGEN_ARM64
+    // The ARM64 Ion JIT is not yet enabled, so this test will fail with
+    // countIonScripts(global) == 0. Once Ion is enabled for ARM64, this test
+    // should be passing again, and this code can be deleted.
+    // Bug 1208526 - ARM64: Reenable jsapi-tests/testPreserveJitCode once Ion is enabled
+    if (!js::jit::IsIonEnabled(cx))
+        knownFail = true;
+#endif
+
     CHECK_EQUAL(countIonScripts(global), 0u);
 
     const char* source =
         "var i = 0;\n"
         "var sum = 0;\n"
         "while (i < 10) {\n"
         "    sum += i;\n"
         "    ++i;\n"
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -4890,17 +4890,17 @@ class BytecodeRangeWithPosition : privat
          * and including the current offset.
          */
         while (!SN_IS_TERMINATOR(sn) && snpc <= frontPC()) {
             SrcNoteType type = (SrcNoteType) SN_TYPE(sn);
             if (type == SRC_COLSPAN) {
                 ptrdiff_t colspan = SN_OFFSET_TO_COLSPAN(GetSrcNoteOffset(sn, 0));
                 MOZ_ASSERT(ptrdiff_t(column) + colspan >= 0);
                 column += colspan;
-            } if (type == SRC_SETLINE) {
+            } else if (type == SRC_SETLINE) {
                 lineno = size_t(GetSrcNoteOffset(sn, 0));
                 column = 0;
             } else if (type == SRC_NEWLINE) {
                 lineno++;
                 column = 0;
             }
 
             sn = SN_NEXT(sn);
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -625,19 +625,20 @@ RestyleManager::StyleChangeReflow(nsIFra
                                                 rootHandling);
     aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
   } while (aFrame);
 }
 
 void
 RestyleManager::AddSubtreeToOverflowTracker(nsIFrame* aFrame) 
 {
-  mOverflowChangedTracker.AddFrame(
-      aFrame,
-      OverflowChangedTracker::CHILDREN_CHANGED);
+  if (aFrame->FrameMaintainsOverflow()) {
+    mOverflowChangedTracker.AddFrame(aFrame,
+                                     OverflowChangedTracker::CHILDREN_CHANGED);
+  }
   nsIFrame::ChildListIterator lists(aFrame);
   for (; !lists.IsDone(); lists.Next()) {
     for (nsIFrame* child : lists.CurrentList()) {
       AddSubtreeToOverflowTracker(child);
     }
   }
 }
 
@@ -813,18 +814,17 @@ RestyleManager::ProcessRestyledFrames(ns
       // We could also have problems with triggering of CSS transitions
       // on elements whose frames are reconstructed, since we depend on
       // the reconstruction happening synchronously.
       FrameConstructor()->RecreateFramesForContent(content, false,
         nsCSSFrameConstructor::REMOVE_FOR_RECONSTRUCTION, nullptr);
     } else {
       NS_ASSERTION(frame, "This shouldn't happen");
 
-      if ((frame->GetStateBits() & NS_FRAME_SVG_LAYOUT) &&
-          (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+      if (!frame->FrameMaintainsOverflow()) {
         // frame does not maintain overflow rects, so avoid calling
         // FinishAndStoreOverflow on it:
         hint = NS_SubtractHint(hint,
                  NS_CombineHint(
                    NS_CombineHint(nsChangeHint_UpdateOverflow,
                                   nsChangeHint_ChildrenOnlyTransform),
                    NS_CombineHint(nsChangeHint_UpdatePostTransformOverflow,
                                   nsChangeHint_UpdateParentOverflow)));
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -405,17 +405,16 @@ protected:
 #endif // NS_PRINTING
 
   /* character set member data */
   int32_t mHintCharsetSource;
   nsCString mHintCharset;
   nsCString mForceCharacterSet;
   
   bool mIsPageMode;
-  bool mCallerIsClosingWindow;
   bool mInitializedForPrintPreview;
   bool mHidden;
 };
 
 class nsPrintEventDispatcher
 {
 public:
   explicit nsPrintEventDispatcher(nsIDocument* aTop) : mTop(aTop)
@@ -456,17 +455,16 @@ NS_NewContentViewer()
 }
 
 void nsDocumentViewer::PrepareToStartLoad()
 {
   mStopped          = false;
   mLoaded           = false;
   mAttachedToParent = false;
   mDeferredWindowClose = false;
-  mCallerIsClosingWindow = false;
 
 #ifdef NS_PRINTING
   mPrintIsPending        = false;
   mPrintDocIsFullyLoaded = false;
   mClosingWhilePrinting  = false;
 
   // Make sure we have destroyed it and cleared the data member
   if (mPrintEngine) {
@@ -1044,37 +1042,33 @@ nsDocumentViewer::LoadComplete(nsresult 
     mCachedPrintWebProgressListner = nullptr;
   }
 #endif
 
   return rv;
 }
 
 NS_IMETHODIMP
-nsDocumentViewer::PermitUnload(bool aCallerClosesWindow,
-                               bool *aPermitUnload)
+nsDocumentViewer::PermitUnload(bool *aPermitUnload)
 {
   bool shouldPrompt = true;
-  return PermitUnloadInternal(aCallerClosesWindow, &shouldPrompt,
-                              aPermitUnload);
+  return PermitUnloadInternal(&shouldPrompt, aPermitUnload);
 }
 
 
 nsresult
-nsDocumentViewer::PermitUnloadInternal(bool aCallerClosesWindow,
-                                       bool *aShouldPrompt,
+nsDocumentViewer::PermitUnloadInternal(bool *aShouldPrompt,
                                        bool *aPermitUnload)
 {
   AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
 
   *aPermitUnload = true;
 
   if (!mDocument
    || mInPermitUnload
-   || mCallerIsClosingWindow
    || mInPermitUnloadPrompt) {
     return NS_OK;
   }
 
   static bool sIsBeforeUnloadDisabled;
   static bool sBeforeUnloadRequiresInteraction;
   static bool sBeforeUnloadPrefsCached = false;
 
@@ -1240,26 +1234,22 @@ nsDocumentViewer::PermitUnloadInternal(b
 
       nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
 
       if (docShell) {
         nsCOMPtr<nsIContentViewer> cv;
         docShell->GetContentViewer(getter_AddRefs(cv));
 
         if (cv) {
-          cv->PermitUnloadInternal(aCallerClosesWindow, aShouldPrompt,
-                                   aPermitUnload);
+          cv->PermitUnloadInternal(aShouldPrompt, aPermitUnload);
         }
       }
     }
   }
 
-  if (aCallerClosesWindow && *aPermitUnload)
-    mCallerIsClosingWindow = true;
-
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent)
 {
   *aInEvent = mInPermitUnload;
   return NS_OK;
@@ -1268,45 +1258,16 @@ nsDocumentViewer::GetBeforeUnloadFiring(
 NS_IMETHODIMP
 nsDocumentViewer::GetInPermitUnload(bool* aInEvent)
 {
   *aInEvent = mInPermitUnloadPrompt;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocumentViewer::ResetCloseWindow()
-{
-  mCallerIsClosingWindow = false;
-
-  nsCOMPtr<nsIDocShell> docShell(mContainer);
-  if (docShell) {
-    int32_t childCount;
-    docShell->GetChildCount(&childCount);
-
-    for (int32_t i = 0; i < childCount; ++i) {
-      nsCOMPtr<nsIDocShellTreeItem> item;
-      docShell->GetChildAt(i, getter_AddRefs(item));
-
-      nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(item));
-
-      if (docShell) {
-        nsCOMPtr<nsIContentViewer> cv;
-        docShell->GetContentViewer(getter_AddRefs(cv));
-
-        if (cv) {
-          cv->ResetCloseWindow();
-        }
-      }
-    }
-  }
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsDocumentViewer::PageHide(bool aIsUnload)
 {
   AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
 
   mHidden = true;
 
   if (!mDocument) {
     return NS_ERROR_NULL_POINTER;
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1032613-1.svg
@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" onload="tweak()">
+    <pattern>
+        <rect id="r" />
+    </pattern>
+    <script>
+      function tweak() {
+        document.getElementById("r").style.textDecoration = "underline";
+      }
+    </script>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1032613-2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+  <script>
+    function tweak() {
+      document.getElementById("c").style.textShadow = "3px 3px gray";
+    }
+  </script>
+  <body onload="tweak()">
+    <div id="c">hello
+      <svg height="0">
+        <clipPath>
+          <path d=""/>
+        </clipPath>
+      </svg>
+    </div>
+  </body>
+</html>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -561,16 +561,18 @@ load 1001233.html
 load 1001258-1.html
 load 1003441.xul
 pref(layout.css.grid.enabled,true) load 1015562.html
 asserts(1-2) load 1015563-1.html
 asserts(1-2) load 1015563-2.html
 asserts(0-300) load 1015844.html # bug 574889
 load outline-on-frameset.xhtml
 pref(font.size.inflation.minTwips,200) load 1032450.html
+load 1032613-1.svg
+load 1032613-2.html
 load 1037903.html
 load 1039454-1.html
 load 1042489.html
 load 1054010-1.html
 load 1058954-1.html
 load 1134531.html
 load 1134667.html
 load 1137723-1.html
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -5550,28 +5550,20 @@ nsIFrame::GetVisualOverflowRectRelativeT
 nsRect
 nsIFrame::GetPreEffectsVisualOverflowRect() const
 {
   nsRect* r = static_cast<nsRect*>
     (Properties().Get(nsIFrame::PreEffectsBBoxProperty()));
   return r ? *r : GetVisualOverflowRectRelativeToSelf();
 }
 
-inline static bool
-FrameMaintainsOverflow(nsIFrame* aFrame)
-{
-  return (aFrame->GetStateBits() &
-          (NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY)) !=
-         (NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
-}
-
 /* virtual */ bool
 nsFrame::UpdateOverflow()
 {
-  MOZ_ASSERT(FrameMaintainsOverflow(this),
+  MOZ_ASSERT(FrameMaintainsOverflow(),
              "Non-display SVG do not maintain visual overflow rects");
 
   nsRect rect(nsPoint(0, 0), GetSize());
   nsOverflowAreas overflowAreas(rect, rect);
 
   if (!DoesClipChildren() &&
       !(IsCollapsed() && (IsBoxFrame() || ::IsBoxWrapped(this)))) {
     nsLayoutUtils::UnionChildOverflow(this, overflowAreas);
@@ -7581,17 +7573,17 @@ ComputeAndIncludeOutlineArea(nsIFrame* a
   nsRect& vo = aOverflowAreas.VisualOverflow();
   vo.UnionRectEdges(vo, innerRect.Union(outerRect));
 }
 
 bool
 nsIFrame::FinishAndStoreOverflow(nsOverflowAreas& aOverflowAreas,
                                  nsSize aNewSize, nsSize* aOldSize)
 {
-  NS_ASSERTION(FrameMaintainsOverflow(this),
+  NS_ASSERTION(FrameMaintainsOverflow(),
                "Don't call - overflow rects not maintained on these SVG frames");
 
   nsRect bounds(nsPoint(0, 0), aNewSize);
   // Store the passed in overflow area if we are a preserve-3d frame or we have
   // a transform, and it's not just the frame bounds.
   if (Combines3DTransformWithAncestors() || IsTransformed()) {
     if (!aOverflowAreas.VisualOverflow().IsEqualEdges(bounds) ||
         !aOverflowAreas.ScrollableOverflow().IsEqualEdges(bounds)) {
@@ -7739,17 +7731,17 @@ nsIFrame::RecomputePerspectiveChildrenOv
   if (aBounds) {
     SetSize(aBounds->Size());
   }
   nsIFrame::ChildListIterator lists(this);
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* child = childFrames.get();
-      if (!FrameMaintainsOverflow(child)) {
+      if (!child->FrameMaintainsOverflow()) {
         continue; // frame does not maintain overflow rects
       }
       if (child->HasPerspective()) {
         nsOverflowAreas* overflow = 
           static_cast<nsOverflowAreas*>(child->Properties().Get(nsIFrame::InitialOverflowProperty()));
         nsRect bounds(nsPoint(0, 0), child->GetSize());
         if (overflow) {
           nsOverflowAreas overflowCopy = *overflow;
@@ -7787,17 +7779,17 @@ RecomputePreserve3DChildrenOverflow(nsIF
   if (aBounds) {
     aFrame->SetSize(aBounds->Size());
   }
   nsIFrame::ChildListIterator lists(aFrame);
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
     for (; !childFrames.AtEnd(); childFrames.Next()) {
       nsIFrame* child = childFrames.get();
-      if (!FrameMaintainsOverflow(child)) {
+      if (!child->FrameMaintainsOverflow()) {
         continue; // frame does not maintain overflow rects
       }
       if (child->Extend3DContext()) {
         RecomputePreserve3DChildrenOverflow(child, nullptr);
       } else if (child->Combines3DTransformWithAncestors()) {
         nsOverflowAreas* overflow = 
           static_cast<nsOverflowAreas*>(child->Properties().Get(nsIFrame::InitialOverflowProperty()));
         nsRect bounds(nsPoint(0, 0), child->GetSize());
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -3023,16 +3023,25 @@ NS_PTR_TO_INT32(frame->Properties().Get(
    * Return whether this is a frame whose width is used when computing
    * the font size inflation of its descendants.
    */
   bool IsContainerForFontSizeInflation() const {
     return GetStateBits() & NS_FRAME_FONT_INFLATION_CONTAINER;
   }
 
   /**
+   * Return whether this frame keeps track of overflow areas. (Frames for
+   * non-display SVG elements -- e.g. <clipPath> -- do not maintain overflow
+   * areas, because they're never painted.)
+   */
+  bool FrameMaintainsOverflow() const {
+    return !HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY);
+  }
+
+  /**
    * Returns the content node within the anonymous content that this frame
    * generated and which corresponds to the specified pseudo-element type,
    * or nullptr if there is no such anonymous content.
    */
   virtual mozilla::dom::Element* GetPseudoElement(nsCSSPseudoElements::Type aType);
 
   bool BackfaceIsHidden() {
     return StyleDisplay()->BackfaceIsHidden();
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -85,18 +85,18 @@ static bool EqualImages(imgIRequest *aIm
 static int safe_strcmp(const char16_t* a, const char16_t* b)
 {
   if (!a || !b) {
     return (int)(a - b);
   }
   return NS_strcmp(a, b);
 }
 
-static nsChangeHint CalcShadowDifference(nsCSSShadowArray* lhs,
-                                         nsCSSShadowArray* rhs);
+static bool AreShadowArraysEqual(nsCSSShadowArray* lhs,
+                                 nsCSSShadowArray* rhs);
 
 // --------------------
 // nsStyleFont
 //
 nsStyleFont::nsStyleFont(const nsFont& aFont, nsPresContext *aPresContext)
   : mFont(aFont)
   , mGenericID(kGenericFont_NONE)
   , mExplicitLanguage(false)
@@ -514,45 +514,57 @@ nsStyleBorder::Destroy(nsPresContext* aC
   UntrackImage(aContext);
   this->~nsStyleBorder();
   aContext->PresShell()->
     FreeByObjectID(eArenaObjectID_nsStyleBorder, this);
 }
 
 nsChangeHint nsStyleBorder::CalcDifference(const nsStyleBorder& aOther) const
 {
-  nsChangeHint shadowDifference =
-    CalcShadowDifference(mBoxShadow, aOther.mBoxShadow);
-  MOZ_ASSERT(shadowDifference == unsigned(NS_STYLE_HINT_REFLOW) ||
-             shadowDifference == unsigned(NS_STYLE_HINT_VISUAL) ||
-             shadowDifference == unsigned(NS_STYLE_HINT_NONE),
-             "should do more with shadowDifference");
-
   // XXXbz we should be able to return a more specific change hint for
   // at least GetComputedBorder() differences...
   if (mTwipsPerPixel != aOther.mTwipsPerPixel ||
       GetComputedBorder() != aOther.GetComputedBorder() ||
       mFloatEdge != aOther.mFloatEdge ||
       mBorderImageOutset != aOther.mBorderImageOutset ||
-      (shadowDifference & nsChangeHint_NeedReflow) ||
       mBoxDecorationBreak != aOther.mBoxDecorationBreak)
     return NS_STYLE_HINT_REFLOW;
 
+  nsChangeHint boxShadowHint = nsChangeHint(0);
+  if (!AreShadowArraysEqual(mBoxShadow, aOther.mBoxShadow)) {
+    // Update overflow regions & trigger DLBI to be sure it's noticed:
+    NS_UpdateHint(boxShadowHint, nsChangeHint_UpdateOverflow);
+    NS_UpdateHint(boxShadowHint, nsChangeHint_SchedulePaint);
+    // Also request a repaint, since it's possible that only the color
+    // of the shadow is changing (and UpdateOverflow/SchedulePaint won't
+    // repaint for that, since they won't know what needs invalidating.)
+    NS_UpdateHint(boxShadowHint, nsChangeHint_RepaintFrame);
+    // Don't return yet; we may also need nsChangeHint_BorderStyleNoneChange.
+  }
+
   NS_FOR_CSS_SIDES(ix) {
     // See the explanation in nsChangeHint.h of
     // nsChangeHint_BorderStyleNoneChange .
     // Furthermore, even though we know *this* side is 0 width, just
     // assume a repaint hint for some other change rather than bother
     // tracking this result through the rest of the function.
     if (HasVisibleStyle(ix) != aOther.HasVisibleStyle(ix)) {
-      return NS_CombineHint(nsChangeHint_RepaintFrame,
+      return NS_CombineHint(boxShadowHint,
+                            nsChangeHint_RepaintFrame |
                             nsChangeHint_BorderStyleNoneChange);
     }
   }
 
+  if (boxShadowHint) {
+    // NOTE: This hint (UpdateOverflow + SchedulePaint + RepaintFrame) is
+    // expected to subsume all hints returned after this point. (Hence, we're
+    // OK to return early.)
+    return boxShadowHint;
+  }
+
   // Note that mBorderStyle stores not only the border style but also
   // color-related flags.  Given that we've already done an mComputedBorder
   // comparison, border-style differences can only lead to a repaint hint.  So
   // it's OK to just compare the values directly -- if either the actual
   // style or the color flags differ we want to repaint.
   NS_FOR_CSS_SIDES(ix) {
     if (mBorderStyle[ix] != aOther.mBorderStyle[ix] || 
         mBorderColor[ix] != aOther.mBorderColor[ix])
@@ -579,20 +591,16 @@ nsChangeHint nsStyleBorder::CalcDifferen
   if (mBorderColors) {
     NS_FOR_CSS_SIDES(ix) {
       if (!nsBorderColors::Equal(mBorderColors[ix],
                                  aOther.mBorderColors[ix]))
         return nsChangeHint_RepaintFrame;
     }
   }
 
-  if (shadowDifference) {
-    return shadowDifference;
-  }
-
   // mBorder is the specified border value.  Changes to this don't
   // need any change processing, since we operate on the computed
   // border values instead.
   if (mBorder != aOther.mBorder) {
     return nsChangeHint_NeutralChange;
   }
 
   return NS_STYLE_HINT_NONE;
@@ -3376,35 +3384,32 @@ nsChangeHint nsStyleTextReset::CalcDiffe
     if (mTextOverflow != aOther.mTextOverflow) {
       return nsChangeHint_RepaintFrame;
     }
     return NS_STYLE_HINT_NONE;
   }
   return NS_STYLE_HINT_REFLOW;
 }
 
-// Allowed to return one of NS_STYLE_HINT_NONE, NS_STYLE_HINT_REFLOW
-// or NS_STYLE_HINT_VISUAL. Currently we just return NONE or REFLOW, though.
-// XXXbz can this not return a more specific hint?  If that's ever
-// changed, nsStyleBorder::CalcDifference will need changing too.
-static nsChangeHint
-CalcShadowDifference(nsCSSShadowArray* lhs,
+// Returns true if the given shadow-arrays are equal.
+static bool
+AreShadowArraysEqual(nsCSSShadowArray* lhs,
                      nsCSSShadowArray* rhs)
 {
   if (lhs == rhs)
-    return NS_STYLE_HINT_NONE;
+    return true;
 
   if (!lhs || !rhs || lhs->Length() != rhs->Length())
-    return NS_STYLE_HINT_REFLOW;
+    return false;
 
   for (uint32_t i = 0; i < lhs->Length(); ++i) {
     if (*lhs->ShadowAt(i) != *rhs->ShadowAt(i))
-      return NS_STYLE_HINT_REFLOW;
+      return false;
   }
-  return NS_STYLE_HINT_NONE;
+  return true;
 }
 
 // --------------------
 // nsStyleText
 //
 
 nsStyleText::nsStyleText(void)
 { 
@@ -3490,17 +3495,22 @@ nsChangeHint nsStyleText::CalcDifference
       (mTextSizeAdjust != aOther.mTextSizeAdjust) ||
       (mLetterSpacing != aOther.mLetterSpacing) ||
       (mLineHeight != aOther.mLineHeight) ||
       (mTextIndent != aOther.mTextIndent) ||
       (mWordSpacing != aOther.mWordSpacing) ||
       (mTabSize != aOther.mTabSize))
     return NS_STYLE_HINT_REFLOW;
 
-  return CalcShadowDifference(mTextShadow, aOther.mTextShadow);
+  if (!AreShadowArraysEqual(mTextShadow, aOther.mTextShadow)) {
+    return nsChangeHint_UpdateSubtreeOverflow |
+           nsChangeHint_SchedulePaint |
+           nsChangeHint_RepaintFrame;
+  }
+  return NS_STYLE_HINT_NONE;
 }
 
 //-----------------------
 // nsStyleUserInterface
 //
 
 nsCursorImage::nsCursorImage()
   : mHaveHotspot(false)
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -859,19 +859,20 @@ struct nsStyleBorder {
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
     return aContext->PresShell()->
       AllocateByObjectID(mozilla::eArenaObjectID_nsStyleBorder, sz);
   }
   void Destroy(nsPresContext* aContext);
 
   nsChangeHint CalcDifference(const nsStyleBorder& aOther) const;
   static nsChangeHint MaxDifference() {
-    return NS_CombineHint(NS_STYLE_HINT_REFLOW,
-                          NS_CombineHint(nsChangeHint_BorderStyleNoneChange,
-                                         nsChangeHint_NeutralChange));
+    return NS_STYLE_HINT_REFLOW |
+           nsChangeHint_UpdateOverflow |
+           nsChangeHint_BorderStyleNoneChange |
+           nsChangeHint_NeutralChange;
   }
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference never returns the reflow hints that are sometimes
     // handled for descendants as hints not handled for descendants.
     return nsChangeHint_NeedReflow |
            nsChangeHint_ReflowChangesSizeOrPosition |
            nsChangeHint_ClearAncestorIntrinsics;
   }
@@ -1647,17 +1648,18 @@ struct nsStyleText {
   void Destroy(nsPresContext* aContext) {
     this->~nsStyleText();
     aContext->PresShell()->
       FreeByObjectID(mozilla::eArenaObjectID_nsStyleText, this);
   }
 
   nsChangeHint CalcDifference(const nsStyleText& aOther) const;
   static nsChangeHint MaxDifference() {
-    return NS_STYLE_HINT_FRAMECHANGE;
+    return NS_STYLE_HINT_FRAMECHANGE |
+           nsChangeHint_UpdateSubtreeOverflow;
   }
   static nsChangeHint DifferenceAlwaysHandledForDescendants() {
     // CalcDifference never returns the reflow hints that are sometimes
     // handled for descendants as hints not handled for descendants.
     return nsChangeHint_NeedReflow |
            nsChangeHint_ReflowChangesSizeOrPosition |
            nsChangeHint_ClearAncestorIntrinsics;
   }
--- a/layout/style/test/test_dynamic_change_causing_reflow.html
+++ b/layout/style/test/test_dynamic_change_causing_reflow.html
@@ -43,22 +43,52 @@ https://bugzilla.mozilla.org/show_bug.cg
 const gTestcases = [
   // Things that shouldn't cause reflow:
   // -----------------------------------
   // * Adding an outline (e.g. for focus ring).
   {
     afterStyle:  "outline: 1px dotted black",
   },
 
-  // * Changing between completely-different outlines.
+  // * Changing between completely different outlines.
   {
     beforeStyle: "outline: 2px solid black",
     afterStyle:  "outline: 6px dashed yellow",
   },
 
+  // * Adding a box-shadow.
+  {
+    afterStyle: "box-shadow: inset 3px 3px gray",
+  },
+  {
+    afterStyle: "box-shadow: 0px 0px 10px 30px blue"
+  },
+
+  // * Changing between completely different box-shadow values,
+  // e.g. from an upper-left shadow to a bottom-right shadow:
+  {
+    beforeStyle: "box-shadow: -15px -20px teal",
+    afterStyle:  "box-shadow:  30px  40px yellow",
+  },
+
+  // * Adding a text-shadow.
+  {
+    afterStyle: "text-shadow: 3px 3px gray",
+  },
+  {
+    afterStyle: "text-shadow: 0px 0px 10px blue"
+  },
+
+  // * Changing between completely different text-shadow values,
+  // e.g. from an upper-left shadow to a bottom-right shadow:
+  {
+    beforeStyle: "text-shadow: -15px -20px teal",
+    afterStyle:  "text-shadow:  30px  40px yellow",
+  },
+
   // Things that *should* cause reflow:
   // ----------------------------------
   // (e.g. to make sure our counts are actually measuring something)
 
   // * Changing 'height' should cause reflow, but not frame construction.
   {
     beforeStyle: "height: 10px",
     afterStyle:  "height: 15px",
--- a/media/libstagefright/binding/Box.cpp
+++ b/media/libstagefright/binding/Box.cpp
@@ -35,16 +35,21 @@ BoxOffset(AtomType aType)
 
   return 0;
 }
 
 Box::Box(BoxContext* aContext, uint64_t aOffset, const Box* aParent)
   : mContext(aContext), mParent(aParent)
 {
   uint8_t header[8];
+
+  if (aOffset > INT64_MAX - sizeof(header)) {
+    return;
+  }
+
   MediaByteRange headerRange(aOffset, aOffset + sizeof(header));
   if (mParent && !mParent->mRange.Contains(headerRange)) {
     return;
   }
 
   const MediaByteRange* byteRange;
   for (int i = 0; ; i++) {
     if (i == mContext->mByteRanges.Length()) {
@@ -62,39 +67,51 @@ Box::Box(BoxContext* aContext, uint64_t 
                                        &bytes) ||
       bytes != sizeof(header)) {
     return;
   }
 
   uint64_t size = BigEndian::readUint32(header);
   if (size == 1) {
     uint8_t bigLength[8];
+    if (aOffset > INT64_MAX - sizeof(header) - sizeof(bigLength)) {
+      return;
+    }
     MediaByteRange bigLengthRange(headerRange.mEnd,
                                   headerRange.mEnd + sizeof(bigLength));
     if ((mParent && !mParent->mRange.Contains(bigLengthRange)) ||
         !byteRange->Contains(bigLengthRange) ||
-        !mContext->mSource->CachedReadAt(aOffset + 8, bigLength,
+        !mContext->mSource->CachedReadAt(aOffset + sizeof(header), bigLength,
                                          sizeof(bigLength), &bytes) ||
         bytes != sizeof(bigLength)) {
       return;
     }
     size = BigEndian::readUint64(bigLength);
     mBodyOffset = bigLengthRange.mEnd;
   } else if (size == 0) {
     // box extends to end of file.
     size = mContext->mByteRanges.LastElement().mEnd - aOffset;
     mBodyOffset = headerRange.mEnd;
   } else {
     mBodyOffset = headerRange.mEnd;
   }
 
+  if (size > INT64_MAX) {
+    return;
+  }
+  int64_t end = static_cast<int64_t>(aOffset) + static_cast<int64_t>(size);
+  if (end < static_cast<int64_t>(aOffset)) {
+    // Overflowed.
+    return;
+  }
+
   mType = BigEndian::readUint32(&header[4]);
   mChildOffset = mBodyOffset + BoxOffset(mType);
 
-  MediaByteRange boxRange(aOffset, aOffset + size);
+  MediaByteRange boxRange(aOffset, end);
   if (mChildOffset > boxRange.mEnd ||
       (mParent && !mParent->mRange.Contains(boxRange)) ||
       !byteRange->Contains(boxRange)) {
     return;
   }
 
   mRange = boxRange;
 }
--- a/media/libstagefright/gtest/TestParser.cpp
+++ b/media/libstagefright/gtest/TestParser.cpp
@@ -160,17 +160,18 @@ struct TestFileData
 {
   const char* mFilename;
   uint32_t mNumberVideoTracks;
   int32_t mWidth;
   int32_t mHeight;
 };
 static const TestFileData testFiles[] = {
   { "test_case_1187067.mp4", 1, 160, 90 },
-  { "test_case_1200326.mp4", 0, 0, 0 }
+  { "test_case_1200326.mp4", 0, 0, 0 },
+  { "test_case_1204580.mp4", 1, 320, 180 }
 };
 
 TEST(stagefright_MPEG4Metadata, test_case_mp4)
 {
   for (size_t test = 0; test < ArrayLength(testFiles); ++test) {
     nsTArray<uint8_t> buffer = ReadTestFile(testFiles[test].mFilename);
     ASSERT_FALSE(buffer.IsEmpty());
     nsRefPtr<Stream> stream = new TestStream(buffer.Elements(), buffer.Length());
--- a/media/libstagefright/gtest/moz.build
+++ b/media/libstagefright/gtest/moz.build
@@ -9,16 +9,17 @@ Library('stagefright_gtest')
 SOURCES += [
     'TestInterval.cpp',
     'TestParser.cpp',
 ]
 
 TEST_HARNESS_FILES.gtest += [
     'test_case_1187067.mp4',
     'test_case_1200326.mp4',
+    'test_case_1204580.mp4',
 ]
 
 if CONFIG['MOZ_RUST']:
     UNIFIED_SOURCES += ['TestMP4Rust.cpp',]
     TEST_HARNESS_FILES.gtest += [
         '../../../dom/media/test/street.mp4',
     ]
 
new file mode 100644
index 0000000000000000000000000000000000000000..4e55b05719230440851fd678096967f6def883d5
GIT binary patch
literal 5833
zc$|$m2UJtpwh1NDq$@>~P()B#5;_D7Qk5n}K)?`E07*zlLI**S;)o!ilqe!7y*Fu6
z6hsk47y&7wqNpHUkS68d;Ji1!`Ro1l&${RCz0d9?H^~KoKu`ibnBq+%lRzLA5EB9$
zjtRiRNE9^~2n6CFQ1N&WNN|aS!_WblatQm*PVrmgspc=kW_78GL(pNFx%nWth8k1}
ziX~I=P?(wqlnPfzXhC6c7(!bE3oy)-0Ra_D8)H)?H7M%L2|yBuXEIKZDZxZMfewYM
zsv?x&sxWon(2Gu|=%}a!1_mkzc;oP7BF0ylO!ZV@dQtYGlZb!}nL_s_`_govSd0e-
zi-bX`cmfgu#o;}OWULPorlYE(3dQ(hh`}^GQZ)#nqZ$N*A)q8Y(hDC1rTKdRm?o4$
z3kFnyJDQ3^!jx43CEx}nc?aQfXr@INpn;}hd_D0<m<AN<MJ1CkXh0PPrBm@lqBjk|
zw1Tv7SUP}U{YXevAOs99gzSq)!eNRq7?gma(a{u|k2i%W@ymc81x+RpXm~nO2@a)u
zQ2`CW5FAP*lYKB=0EPbdA(Te+#^V1zP=)$Zcb#CpNf<ga5N}^Po=U_3LcqBP(VvP5
zMq|k&3Wg5QSfEC9D#qIvFaZcsG06K+4+0fK!qb3id7vr50Pc-L!T}tE!%&!Mc%VJJ
zF-%W5Zz_H_u|T}Hrx)D=z{nK5FWQq#0l2>oDS)aEJ{Yi#gsZFmg`!E`z5ofOVe!6r
ztUn#8rV8El&a5dFPxAuQsaW*C(`E9hSR|H8fRa3b!ZOnW2qZ#XSrzKXOa`f{tO1}D
zrr}+P2|{XU0|<?dry$j!-V~rqz(fE|1Z-jafB|5(6BG#ov0CHfE`wUPL+99eoX^3;
zN{mwMF!eF|JtwGQok}Z#pI3WwF7Gcd&-r;kGywzsURqSRKV-6=K#)87I$gwds|RB7
zN^b9|zDNyoT65_w_hDr249^2S9yf!VkVoG}FV(hNHnrm#E)29ua4yf6^o#%TtR$sp
zUH))k@ueW>7>P|<8%q;;wee@HqtEI^?z1AiH>Lb84n}jbO+`DEqi6zCJt1gbcleuJ
zmPvLcPilVyeoC;c>bv1k+q?1a@}}oyv)_G2hD%G7aTwQLvi)gvJRQWfkyf{7IT~8m
zwBjtZ@Z$lE6CvJyW90JM{T^ch3H$m_2KSyV8;`C9En>l?&LLj{9_WlLYZ^B~=5*EY
zP-Do}e6<wMIFFpYOf|%`4jZ@PbY#DK6eGFaQO`d|e5KtEUWG89Wb2c7+A<NiO{>Ni
zwEwAbZ}RlazQ#{oXt$cRgH^o;C1FCmgV;odQyZ~J>Xe~#x5W!De#FPXM@4jV#<0Fn
z5?<TudF8rt?BL+4%KaE6D@B{HRLY3YS7ZJvPx+%dNy@r9_c-w-`QuNf7({V&JrvRT
zLdfCp!Soi{$$VD9c0!QmO6S&%(xQQyN@Y}bam|H7Q~<Xpx9an6nOtUhl-D-$)7FmP
z3sOsvaZ<1kjl5#jjfD81>c*i%Uo(CN*9m}cyNKU-Zeq!+erIHk`X)(};J-XF(prVp
zJh&NsW`1z*mhF|hw>A*_^Xa&)wV?#TCBM5TlHeiFG^&g`<xBhBcXSmxCGMaQCsNs7
z-aSBn<?xs>bu8cV>)WfUIer&sPKx@3q#q<YL~U?)u|dnd+=@jU%?sJ>!_K~~GlI!N
zSf`!24~E8!8cq9qd!|(%-`9BZZHE0;LwJPs<<nXe&r?QngdYZ)-CU9e$;*36`{x9`
z(m0;2BEI8QsyR8wOuyEKCphFA4Hg&0uHdA7!GrwI(2IY@#HV~Ni9sK`Ize+Rxb9P)
z5kLD))HYsNJ|jOPryqGqsY@U%X2HWc#aUR&_**0&?AmD1sca{DsIhIX8eB`$;dY;X
zbBgvbl%{u+1N=HycacQ<>9BWNsY&DKq5iZ4J)5?p%{Tq6_lX&bUNd^yJ~=G2{Pm>x
z*2qWyp~4FsoWQq&B#I$Li~z01IEm??J__U8sf$akP7|-kqmJ{-&g!oLB6%o=p`KKV
zLhKHUz||M|;HI7Kjw~p+#UK+O_^n=}M&+*M68!ACzzt<l$+*UmmHzmTdZ76*)9dZ?
z*;YRiP3^Ut9R++K492knp<7qjvg5x$IZ+=V$*$hRB480(vdy7Y?!E6xqPug6PXOU`
z!m=K!;tY$0<otE~^pl|5ty8WhIBTEmX6cfS=Jcmm(yHr%=<f9|5-rL!me0KadxEW|
z1>@OixgZc2fnwmy$NA*!*!5sL+vC$P<?#Id4Vr3Efow1ICw^GnYk7|L$@!K@Ohn&1
zY#wD<F(8;<cF}Dr{x1Jpo|oya!G)m@j2d<tC!7jkhYX|A4ccbR=RQIVI_<dU67YvY
z8=P;R@6h<P3DHz`lTjBsARTlmPiy?zW4=Q>aGf8sDK#MTF`xAu&Y3sSTSZ5@hi3N_
zKTTN>kBdvp@_VlAW7_-Wanz<lg2iQ>IH}M?lP>PQVn36O_hJH_7tAQhyjD8hubvB=
z@~s{3=WFqBe3LT5N-6Dv#BFnoG<#i~Y#0S^rFb|OA3vUAko*3dBlm583ClFMx+6P!
z@D~2Agy}PA!gQ<D1((}%+?4svuFRS4Vh90#U-`rEIkp1*M@{$LApvQ#oT#qK12-af
z)EK$-EONkO6%+)%6LmgfZB)q`mvQuSxj|^*UY3wcx95euIv)CNse0-3w7dAnZyV+t
z74R$iF7dmZ>9RLTb3R~?aPFcgWN1-m-%i@)XC8Z%PPm5*m>n%C`^^4w&a+@`Gy2GR
zrF|ZKB1@^fN9w~|0^iAIr*MfRyKukHxwsG{0JQ7?iV?fpGM3ia>q)e`iIhlCZd@bv
zNas;Ccgqgp^byudyB~8|u=32w3oAcg^t$s;S%T{)*TvCpvTn&At{rY_h?#m}@m=Ez
zG+k3PSjp0fyIEo#Vaw~7tew5&mYrJbwPi0Eksq~XMtJzpY%Jy&rwm!p_HKqJvZB>I
zBd52d%O><5+Gv0Nz)SZ{)7seUd=(S{P3tPB16>WYabeB~Uxt9A|LN4q*s}gF@8+}n
zxI9Jl{{Z))w9P902!jICo_3e`pFauRdF!XM^1YuH#K+sES3epU`?Q?>@}pS9302WS
zW%iBRKQIjw-yq}iXyLlT=a?OZxbzpgXOaWTn=B)S^ICiN^QrLa?7N&&eh<%RmDzts
z>>~zfO$zExo?+_`cY<1u#OuqIN88h`<(sABD$(bNPybxitvm?<Yv&Dkf9~z?Ev6I|
zXK5@FLvcw~7tyb37VL*pU8s?JlJ5`D_0OFx{W7GT7LnY=I}tYA%@%*G2R$dly0Y$G
za;tIPFRS8gv1@>hm<U4L{+M;eK$f3C!I7)25-+wGybs<xO!v&zeN*Ttd*~J+PV581
z$^hSa;?1AG-As~k#vR-`Q1ZCj1XaOTQ7X1~sc2Fz<D41f<k@ys#e0L(gJq~Ml+3SJ
zl@J@41O3-VIQecElsI^j5~S@OtLGyA*eCQ*Q@)4qVyM|LkI06{xQ$`6Vy(xlk@|TP
z1ee8&#MT!ql*?PTVg}QeN*ahy9QB4_C;jDiT**eZRzIN6f*)^hv(+_U;pA?A`pwf&
z8yG2l6yuqB0m>`Gf^Vmr??LaLCi{92NDkvfh{L9p^IfYL$rKA{Hh8eyKf$e>(w8qN
zIH?l5`G>*BA%B}{$_4AzQ=<N<^j6dDp?%wKf=d!%W~Zk5)D+U{&HA999{Z~O5D5()
z5ZSroN)T~jG-dFUhKO;EduZ{q_0a+iRr&{BUHNn<T{EL}->ja&yQ1)b$or%!jfL60
z=?lemfq@R$7KVJcM{&Wr^3tLXN?5{S1H6Mw!kY#aq~oXR<-p_dxcG)Ikg<#swp5og
zEqmSf)2`Ggyo*b(q2lQT+&8DD>zi6*m6loE$;6IhN9NyJHl&svcr00wUvd>15j6JH
zi7fvW<bFtJ2CkpNs@m!8rxI^;`esaXdmDZ&FD`i1^TE)EspcW)1lG@L#tILu=;_T|
z{GK_F5m>|(*RSw4pn`W%j4AVRE?d0nv7NaFpIbEu!8@>e<1WyR#S1FwZr3uighNG4
z?l)g=2`f8kR2i}Tp&*OhVHPFrTU&ZbKde!_=~0Dt8oOer8v30JU)Tr!_f^Y`hp#Xs
z1t)lIlh}IR3Yxt7o&z+e)X>^rXYQAfyuokDekhP7h85b`=Owxp7Hv~m8Yc)vyT(Yx
zSG}}ttX24wlQdRVm4M|CqG$2+)8V3@6gZ68lW5qk5uHu-s%eqdpM7JKOUJH!$_xK~
za-Nbc`bIojXVV#ZIb2e|cX^2`zW7y>xK09_J`46xtY6c1mU3|S2UDfYRVjwno{Xa>
z<;JpS<zRIru1`UULUw7TQ5*|9JB2I<T35S2<{5+B<?ocZFOBoMi8QCK=>-%`XtiC<
zuA9jfI@278ve2KIG|o+MJ3b`3@FX;H!?vRYsqvEq7O`v<La=_+=ylgcX5*w3olWxy
z)$I`5G?^WivYv9r@tTc^U4c@kL#UCNda}F9``YLO>6Ha-9peqEO&{iU2Tp#OLE8MO
zrp}XZm5M}i1(U(j`4i${NkETMP`9|v$9b>Zcw{Mb<edK3FB4{-M>11N5uCB%XC1EX
zS?yp|(epD_4uz<F#7%e<N~-0(z0{V7Q0QNcnk$W@d`aEB`N&P>e$T}WF1=Q5!#<1;
zLC1zO`bJq#U0V90Q8}aP(xoM^&(GN*ltP=Y=crrLlai1dY?9H@#PfvVfbC_eS9)_x
zI%Tg&6*3Ddc7+<3XU{h%ye@a({?zm!SU4tpqhrDDahqO(+Oecgob9Z|>Hal8@cSYm
z!ht!6-jAArjp5h5Q8|ktk$D#bR*rBA?n!vbne}|pF!6mHa#FW9opUt(rP+&BwNAd8
zauJ70yuVymmXGp)^yw^U*-abKZ!Lr62f1O7T^a}MZc05{9^G1+gGlAQs;YC9l-;<_
z<!aD>Z5)NtK6=+jr1pZ4s5HBvA*(|5pcRBKIOQ|)_3+uAqb@zCbSAWTrnUl+V+rUs
zlYZOVvM$o$hFab=JdZD~a;c{;+8h4}FigFgaQCSfEhFB+#DEx+^u~22pgys*?akEB
zj`oPakg@zhz2?@9Y2e9t3!HWIy!lSO-ScaD7Wib*(8~fn_VeHS?$2TA5sz1Jk}nGi
z=Rcmk*gfaem)+}=g3VD>Yk6?twMX6kam6!mggGX%<!48XFIm6ALlP@+RYe}vvr?rH
zX7Ld&Y@Vq2-MjkfJFyGnV=KvwLM1W!4%LGlbIxV4VK#fv+4UUB<n4f<&sRomLXcOI
zo1qe6uh+@6s+((NQE?Np?!eT9p>8St^$SU9XS*NyWM@a=ihUAS0Z0X0WG*NbzLa_0
zzv+Vzqd;Z~U$%}=b0Lqx^%@7%-tCz=gLapK$Z~3(S<7DamaAE%micBmjZR;238YIl
zZzlxtOCr_v%);LA+|Fc(ONu_j-{r=lZ1PE+==IHa)=h=ZpeJ5j+yWto9^7u8b9?fB
z;oRO7DfO9$N}Pt8r(H*lzRl$5I74--E`{H@Me8xyyWh>dbGGf~K)iW_<&jsqKhqUO
zy5)(gj&!&8hLxHt5ZTF(>+wG_pAoH;DpaWE5v}DnJD>7V9E5$}S=3W9ue88>nzN#X
zNAjM|yv^&f9FpGJbH1*xboK0;c@yZ!pZbG5DuMl^jqscs#|lKP?kNm`8~qrCb5AN(
zqtrIj9&F>>v>BlSnZr4~na*LQQ}eOh-=nQ^Y!AY!uN~Ky>&n(RE$EZQdv9RiC_&N*
zN~vplpUQ}6J(+ZTu~cL8-Iow+mE2z1C4TAdQH#k&i4{{-U_4%fvn<Y^kX)OCOhDhE
zs$|N!@oN(67;CA@?Zf-}1!O~EJS{wrw7RIIpeN8JMhQeZ!RPgYV7Gp0cywCLDRrk_
zCsGT!h1{oU87~U11Q$BE+GNiPK6OCfZHPV2JCcKRDH=Tws;;H@9)npSO<Bj{V&Kwk
zv!ymH?@;U&7JocnR!b#0X=`h3Ej#+mzNs^Htew|5u0X5{q2m{&)oJpN5NmAi)(iO=
z4kz}6x1kkpI&@TC7+K_b$q^LThJ@1poTr0^$PXE|hsJn0tgYVbGT$xTLfx`DHO_J3
znGATdSMmuc#$X+4ag^d^s5vEGAeWOn_kq}F-Qk;Va?LU4&==m0(7b3GAJ{KAvThXj
zL0=bnyz@$n#dk<i!g*yYL9QFp`-gRHMJlb>P2MH$d)?vCR_1f5pRK<}wXUsW#z5=~
zpL~(DlKOT+ua=JZr!pZ8-&<nZT3k6_dp#!_<m@(^Pfh2hY3LaVC>J_hz6b)bmXXNh
z03b{vDZmQ{`j5pr34AJ6L0}N``g?)@Q|h1eanS#{Sb%GYPQ~~DxGUXf*C*@$wg&k9
z=lXy61v76oyQC~0N2dW$0#BsT|JGpIWmEm7%VI;qc{6`WLP@xP=B^(J{QvXX_jH#~
z^1>0R|Kc!T*8MJdhWYZ&4C9L<;+ZnwpCoTz0)Pkykams!9;Phr7f%95#S?x@gU6pr
zg#Hy^6OHac1n@K(ore2OCYJfW^1qaVnRpGz3_#5Oo5cTGqB}dl9R^rE0mgy$?u>&(
zJpouUj6!8)f%$jK1lj;C8fLhIK@hL$4>zh5nQAopZe9Q7gb%2rC^KCk+~3ccn}YpY
zmIt)kXQojm4`TKU?&<=N!~GZh7k_uL{CD|((z|jJKn&P_#K89#v;lY_zoieHLm&W@
z0d^4}q6xq-07^1T59~|;Hw5;5U^D3~07e4v%`cppFP8k9FQ$JFe;l1TPiIN^UC3Ng
vzt0XZZ&VD0Lj1iCO5VQ)^l%}a%rtlqhuKx$ErJCmfS|6Sj6f*ERAK)EQw&X7
--- a/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_candidate.c
@@ -268,30 +268,59 @@ int nr_ice_peer_peer_rflx_candidate_crea
     _status=0;
   abort:
     if (_status){
       nr_ice_candidate_destroy(&cand);
     }
     return(_status);
   }
 
+static void nr_ice_candidate_mark_done(nr_ice_candidate *cand, int state)
+  {
+    if (!cand || !cand->done_cb) {
+      assert(0);
+      return;
+    }
+
+    /* If this is a relay candidate, there's likely to be a srflx that is
+     * piggybacking on it. Make sure it is marked done too. */
+    if ((cand->type == RELAYED) && cand->u.relayed.srvflx_candidate) {
+      nr_ice_candidate *srflx=cand->u.relayed.srvflx_candidate;
+      /* Calling done_cb can destroy this, make sure it doesn't dangle. */
+      cand->u.relayed.srvflx_candidate=0;
+      if (state == NR_ICE_CAND_STATE_INITIALIZED &&
+          nr_turn_client_get_mapped_address(cand->u.relayed.turn,
+                                            &srflx->addr)) {
+        r_log(LOG_ICE, LOG_WARNING, "ICE(%s)/CAND(%s): Failed to get mapped address from TURN allocate response, srflx failed.", cand->ctx->label, cand->label);
+        nr_ice_candidate_mark_done(srflx, NR_ICE_CAND_STATE_FAILED);
+      } else {
+        nr_ice_candidate_mark_done(srflx, state);
+      }
+    }
+
+    NR_async_cb done_cb=cand->done_cb;
+    cand->done_cb=0;
+    cand->state=state;
+    /* This might destroy cand! */
+    done_cb(0,0,cand->cb_arg);
+  }
+
 int nr_ice_candidate_destroy(nr_ice_candidate **candp)
   {
     nr_ice_candidate *cand=0;
 
     if(!candp || !*candp)
       return(0);
 
     cand=*candp;
 
     if (cand->state == NR_ICE_CAND_STATE_INITIALIZING) {
       /* Make sure the ICE ctx isn't still waiting around for this candidate
        * to init. */
-      cand->state=NR_ICE_CAND_STATE_FAILED;
-      cand->done_cb(0,0,cand->cb_arg);
+      nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
     }
 
     switch(cand->type){
       case HOST:
         break;
 #ifdef USE_TURN
       case RELAYED:
         if (cand->u.relayed.turn_handle)
@@ -527,38 +556,34 @@ static void nr_ice_candidate_fire_ready_
   }
 
 int nr_ice_candidate_initialize(nr_ice_candidate *cand, NR_async_cb ready_cb, void *cb_arg)
   {
     int r,_status;
     int protocol=NR_RESOLVE_PROTOCOL_STUN;
     cand->done_cb=ready_cb;
     cand->cb_arg=cb_arg;
+    cand->state=NR_ICE_CAND_STATE_INITIALIZING;
 
     switch(cand->type){
       case HOST:
         if(r=nr_socket_getaddr(cand->isock->sock,&cand->addr))
           ABORT(r);
         cand->osock=cand->isock->sock;
-        // This is actually ready, but we set this anyway to prevent it from
-        // being paired twice.
-        cand->state=NR_ICE_CAND_STATE_INITIALIZING;
         // Post this so that it doesn't happen in-line
         cand->ready_cb = ready_cb;
         cand->ready_cb_arg = cb_arg;
         NR_ASYNC_TIMER_SET(0, nr_ice_candidate_fire_ready_cb, (void *)cand, &cand->ready_cb_timer);
         break;
 #ifdef USE_TURN
       case RELAYED:
         protocol=NR_RESOLVE_PROTOCOL_TURN;
         /* Fall through */
 #endif
       case SERVER_REFLEXIVE:
-        cand->state=NR_ICE_CAND_STATE_INITIALIZING;
-
         if(cand->stun_server->type == NR_ICE_STUN_SERVER_TYPE_ADDR) {
           if(nr_transport_addr_cmp(&cand->base,&cand->stun_server->u.addr,NR_TRANSPORT_ADDR_CMP_MODE_PROTOCOL)) {
             r_log(LOG_ICE, LOG_INFO, "ICE-CANDIDATE(%s): Skipping srflx/relayed candidate because of IP version/transport mis-match with STUN/TURN server (%u/%s - %u/%s).", cand->label,cand->base.ip_version,cand->base.protocol==IPPROTO_UDP?"UDP":"TCP",cand->stun_server->u.addr.ip_version,cand->stun_server->u.addr.protocol==IPPROTO_UDP?"UDP":"TCP");
             ABORT(R_NOT_FOUND); /* Same error code when DNS lookup fails */
           }
 
           /* Just copy the address */
           if (r=nr_transport_addr_copy(&cand->stun_server_addr,
@@ -608,17 +633,17 @@ int nr_ice_candidate_initialize(nr_ice_c
         ABORT(R_INTERNAL);
     }
 
     nr_ice_candidate_compute_codeword(cand);
 
     _status=0;
   abort:
     if(_status && _status!=R_WOULDBLOCK)
-      cand->state=NR_ICE_CAND_STATE_FAILED;
+      nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
     return(_status);
   }
 
 
 static int nr_ice_candidate_resolved_cb(void *cb_arg, nr_transport_addr *addr)
   {
     nr_ice_candidate *cand=cb_arg;
     int r,_status;
@@ -646,18 +671,17 @@ static int nr_ice_candidate_resolved_cb(
 
     /* Now start initializing */
     if(r=nr_ice_candidate_initialize2(cand))
       ABORT(r);
 
     _status=0;
   abort:
     if(_status && _status!=R_WOULDBLOCK) {
-      cand->state=NR_ICE_CAND_STATE_FAILED;
-      cand->done_cb(0,0,cand->cb_arg);
+      nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
     }
     return(_status);
   }
 
 static int nr_ice_candidate_initialize2(nr_ice_candidate *cand)
   {
     int r,_status;
 
@@ -681,18 +705,16 @@ static int nr_ice_candidate_initialize2(
         ABORT(R_WOULDBLOCK);
         break;
       default:
         ABORT(R_INTERNAL);
     }
 
     _status=0;
   abort:
-    if(_status && _status!=R_WOULDBLOCK)
-      cand->state=NR_ICE_CAND_STATE_FAILED;
     return(_status);
   }
 
 static void nr_ice_srvrflx_start_stun_timer_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     nr_ice_candidate *cand=cb_arg;
     int r,_status;
 
@@ -707,20 +729,19 @@ static void nr_ice_srvrflx_start_stun_ti
     if(r=nr_ice_ctx_remember_id(cand->ctx, cand->u.srvrflx.stun->request))
       ABORT(r);
 
     if(r=nr_ice_socket_register_stun_client(cand->isock,cand->u.srvrflx.stun,&cand->u.srvrflx.stun_handle))
       ABORT(r);
 
     _status=0;
   abort:
-    if(_status){
-      cand->state=NR_ICE_CAND_STATE_FAILED;
+    if (_status && (cand->u.srvrflx.stun->state==NR_STUN_CLIENT_STATE_RUNNING)) {
+      nr_stun_client_failed(cand->u.srvrflx.stun);
     }
-
     return;
   }
 
 static int nr_ice_srvrflx_start_stun(nr_ice_candidate *cand)
   {
     int r,_status;
 
     assert(!cand->delay_timer);
@@ -729,19 +750,16 @@ static int nr_ice_srvrflx_start_stun(nr_
       &cand->u.srvrflx.stun))
       ABORT(r);
 
     NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_srvrflx_start_stun_timer_cb,cand,&cand->delay_timer);
     cand->stream->ctx->stun_delay += cand->stream->ctx->Ta;
 
     _status=0;
   abort:
-    if(_status){
-      cand->state=NR_ICE_CAND_STATE_FAILED;
-    }
     return(_status);
   }
 
 #ifdef USE_TURN
 static void nr_ice_start_relay_turn_timer_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     nr_ice_candidate *cand=cb_arg;
     int r,_status;
@@ -752,18 +770,18 @@ static void nr_ice_start_relay_turn_time
       ABORT(r);
 
     if(r=nr_ice_socket_register_turn_client(cand->isock, cand->u.relayed.turn,
                                             cand->osock, &cand->u.relayed.turn_handle))
       ABORT(r);
 
     _status=0;
   abort:
-    if(_status){
-      cand->state=NR_ICE_CAND_STATE_FAILED;
+    if(_status && (cand->u.relayed.turn->state==NR_TURN_CLIENT_STATE_ALLOCATING)){
+      nr_turn_client_failed(cand->u.relayed.turn);
     }
     return;
   }
 
 static int nr_ice_start_relay_turn(nr_ice_candidate *cand)
   {
     int r,_status;
     assert(!cand->delay_timer);
@@ -777,19 +795,16 @@ static int nr_ice_start_relay_turn(nr_ic
     if(r=nr_socket_turn_set_ctx(cand->osock, cand->u.relayed.turn))
       ABORT(r);
 
     NR_ASYNC_TIMER_SET(cand->stream->ctx->stun_delay,nr_ice_start_relay_turn_timer_cb,cand,&cand->delay_timer);
     cand->stream->ctx->stun_delay += cand->stream->ctx->Ta;
 
     _status=0;
   abort:
-    if(_status){
-      cand->state=NR_ICE_CAND_STATE_FAILED;
-    }
     return(_status);
   }
 #endif /* USE_TURN */
 
 static void nr_ice_srvrflx_stun_finished_cb(NR_SOCKET sock, int how, void *cb_arg)
   {
     int _status;
     nr_ice_candidate *cand=cb_arg;
@@ -805,33 +820,31 @@ static void nr_ice_srvrflx_stun_finished
     switch(cand->u.srvrflx.stun->state){
       /* OK, we should have a mapped address */
       case NR_STUN_CLIENT_STATE_DONE:
         /* Copy the address */
         nr_transport_addr_copy(&cand->addr, &cand->u.srvrflx.stun->results.stun_binding_response.mapped_addr);
         cand->addr.protocol=cand->base.protocol;
         nr_transport_addr_fmt_addr_string(&cand->addr);
         nr_stun_client_ctx_destroy(&cand->u.srvrflx.stun);
-        cand->state=NR_ICE_CAND_STATE_INITIALIZED;
-        /* Execute the ready callback */
-        cand->done_cb(0,0,cand->cb_arg);
+        nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED);
+        cand=0;
         break;
 
       /* This failed, so go to the next STUN server if there is one */
       case NR_STUN_CLIENT_STATE_FAILED:
         ABORT(R_NOT_FOUND);
         break;
       default:
         ABORT(R_INTERNAL);
     }
     _status = 0;
   abort:
     if(_status){
-      cand->state=NR_ICE_CAND_STATE_FAILED;
-      cand->done_cb(0,0,cand->cb_arg);
+      nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
     }
   }
 
 #ifdef USE_TURN
 static void nr_ice_turn_allocated_cb(NR_SOCKET s, int how, void *cb_arg)
   {
     int r,_status;
     nr_ice_candidate *cand=cb_arg;
@@ -858,31 +871,17 @@ static void nr_ice_turn_allocated_cb(NR_
           ABORT(r);
         if (r=nr_transport_addr_copy_keep_ifname(&cand->base, &relay_addr))  /* Need to keep interface for priority calculation */
           ABORT(r);
 
         r_log(LOG_ICE,LOG_DEBUG,"ICE(%s)/CAND(%s): new relay base=%s addr=%s", cand->ctx->label, cand->label, cand->base.as_string, cand->addr.as_string);
 
         RFREE(cand->label);
         cand->label=label;
-        cand->state=NR_ICE_CAND_STATE_INITIALIZED;
-
-        /* We also need to activate the associated STUN candidate */
-        if(cand->u.relayed.srvflx_candidate){
-          nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate;
-
-          if (r=nr_turn_client_get_mapped_address(cand->u.relayed.turn, &cand2->addr))
-            ABORT(r);
-
-          cand2->state=NR_ICE_CAND_STATE_INITIALIZED;
-          cand2->done_cb(0,0,cand2->cb_arg);
-        }
-
-        /* Execute the ready callback */
-        cand->done_cb(0,0,cand->cb_arg);
+        nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_INITIALIZED);
         cand = 0;
 
         break;
 
     case NR_TURN_CLIENT_STATE_FAILED:
     case NR_TURN_CLIENT_STATE_CANCELLED:
       r_log(NR_LOG_TURN, LOG_WARNING,
             "ICE-CANDIDATE(%s): nr_turn_allocated_cb called with state %d",
@@ -896,25 +895,17 @@ static void nr_ice_turn_allocated_cb(NR_
     }
 
     _status=0;
   abort:
     if(_status){
       if (cand) {
         r_log(NR_LOG_TURN, LOG_WARNING,
               "ICE-CANDIDATE(%s): nr_turn_allocated_cb failed", cand->label);
-        cand->state=NR_ICE_CAND_STATE_FAILED;
-        cand->done_cb(0,0,cand->cb_arg);
-
-        if(cand->u.relayed.srvflx_candidate){
-          nr_ice_candidate *cand2=cand->u.relayed.srvflx_candidate;
-
-          cand2->state=NR_ICE_CAND_STATE_FAILED;
-          cand2->done_cb(0,0,cand2->cb_arg);
-        }
+        nr_ice_candidate_mark_done(cand, NR_ICE_CAND_STATE_FAILED);
       }
     }
   }
 #endif /* USE_TURN */
 
 /* Format the candidate attribute as per ICE S 15.1 */
 int nr_ice_format_candidate_attribute(nr_ice_candidate *cand, char *attr, int maxlen)
   {
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -619,22 +619,17 @@ int nr_ice_component_initialize(struct n
       ctx->uninitialized_candidates++;
       cand=TAILQ_NEXT(cand,entry_comp);
     }
 
     /* Now initialize all the candidates */
     cand=TAILQ_FIRST(&component->candidates);
     while(cand){
       if(cand->state!=NR_ICE_CAND_STATE_INITIALIZING){
-        if(r=nr_ice_candidate_initialize(cand,nr_ice_gather_finished_cb,cand)){
-          if(r!=R_WOULDBLOCK){
-            ctx->uninitialized_candidates--;
-            cand->state=NR_ICE_CAND_STATE_FAILED;
-          }
-        }
+        nr_ice_candidate_initialize(cand,nr_ice_gather_finished_cb,cand);
       }
       cand=TAILQ_NEXT(cand,entry_comp);
     }
     _status=0;
  abort:
     return(_status);
   }
 
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.c
@@ -108,16 +108,28 @@ int nr_stun_client_ctx_create(char *labe
     _status=0;
   abort:
     if(_status){
       nr_stun_client_ctx_destroy(&ctx);
     }
     return(_status);
   }
 
+static void nr_stun_client_fire_finished_cb(nr_stun_client_ctx *ctx)
+  {
+    if (ctx->finished_cb) {
+      NR_async_cb finished_cb = ctx->finished_cb;
+      ctx->finished_cb = 0;  /* prevent 2nd call */
+      /* finished_cb call must be absolutely last thing in function
+       * because as a side effect this ctx may be operated on in the
+       * callback */
+      finished_cb(0,0,ctx->cb_arg);
+    }
+  }
+
 int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg)
   {
     int r,_status;
 
     if (ctx->state != NR_STUN_CLIENT_STATE_INITTED)
         ABORT(R_NOT_PERMITTED);
 
     ctx->mode=mode;
@@ -129,21 +141,17 @@ int nr_stun_client_start(nr_stun_client_
     if(mode!=NR_STUN_CLIENT_MODE_KEEPALIVE){
       if(r=nr_stun_client_send_request(ctx))
         ABORT(r);
     }
 
     _status=0;
   abort:
    if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
-        ctx->finished_cb = 0;  /* prevent 2nd call */
-        /* finished_cb call must be absolutely last thing in function
-         * because as a side effect this ctx may be operated on in the
-         * callback */
-        finished_cb(0,0,cb_arg);
+     nr_stun_client_fire_finished_cb(ctx);
     }
 
     return(_status);
   }
 
 int nr_stun_client_restart(nr_stun_client_ctx *ctx)
   {
     int r,_status;
@@ -249,24 +257,17 @@ static void nr_stun_client_timer_expired
   abort:
     if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
         /* Cancel the timer firing */
         if (ctx->timer_handle){
             NR_async_timer_cancel(ctx->timer_handle);
             ctx->timer_handle=0;
         }
 
-        if (ctx->finished_cb) {
-            NR_async_cb finished_cb = ctx->finished_cb;
-            ctx->finished_cb = 0;  /* prevent 2nd call */
-            /* finished_cb call must be absolutely last thing in function
-             * because as a side effect this ctx may be operated on in the
-             * callback */
-            finished_cb(0,0,ctx->cb_arg);
-        }
+        nr_stun_client_fire_finished_cb(ctx);
     }
     return;
   }
 
 int nr_stun_client_force_retransmit(nr_stun_client_ctx *ctx)
   {
     int r,_status;
 
@@ -405,17 +406,17 @@ static int nr_stun_client_send_request(n
         gettimeofday(&ctx->timer_set, 0);
 
         NR_ASYNC_TIMER_SET(ctx->timeout_ms, nr_stun_client_timer_expired_cb, ctx, &ctx->timer_handle);
     }
 
     _status=0;
   abort:
     if (_status) {
-      ctx->state=NR_STUN_CLIENT_STATE_FAILED;
+      nr_stun_client_failed(ctx);
     }
     return(_status);
   }
 
 static int nr_stun_client_get_password(void *arg, nr_stun_message *msg, Data **password)
 {
     *password = (Data*)arg;
     if (!arg)
@@ -743,25 +744,17 @@ int nr_stun_client_process_response(nr_s
 
     if (ctx->state != NR_STUN_CLIENT_STATE_RUNNING) {
         /* Cancel the timer firing */
         if (ctx->timer_handle) {
             NR_async_timer_cancel(ctx->timer_handle);
             ctx->timer_handle = 0;
         }
 
-        /* Fire the callback */
-        if (ctx->finished_cb) {
-            NR_async_cb finished_cb = ctx->finished_cb;
-            ctx->finished_cb = 0;  /* prevent 2nd call */
-            /* finished_cb call must be absolutely last thing in function
-             * because as a side effect this ctx may be operated on in the
-             * callback */
-            finished_cb(0,0,ctx->cb_arg);
-        }
+        nr_stun_client_fire_finished_cb(ctx);
     }
 
     return(_status);
   }
 
 int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp)
   {
     nr_stun_client_ctx *ctx;
@@ -789,11 +782,19 @@ int nr_stun_client_cancel(nr_stun_client
     /* Cancel the timer firing */
     if (ctx->timer_handle){
       NR_async_timer_cancel(ctx->timer_handle);
       ctx->timer_handle=0;
     }
 
     /* Mark cancelled so we ignore any returned messsages */
     ctx->state=NR_STUN_CLIENT_STATE_CANCELLED;
+    return(0);
+}
 
+
+int nr_stun_client_failed(nr_stun_client_ctx *ctx)
+  {
+    nr_stun_client_cancel(ctx);
+    ctx->state=NR_STUN_CLIENT_STATE_FAILED;
+    nr_stun_client_fire_finished_cb(ctx);
     return(0);
   }
--- a/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/stun/stun_client_ctx.h
@@ -186,11 +186,12 @@ int nr_stun_client_ctx_create(char *labe
 int nr_stun_client_start(nr_stun_client_ctx *ctx, int mode, NR_async_cb finished_cb, void *cb_arg);
 int nr_stun_client_restart(nr_stun_client_ctx *ctx);
 int nr_stun_client_force_retransmit(nr_stun_client_ctx *ctx);
 int nr_stun_client_reset(nr_stun_client_ctx *ctx);
 int nr_stun_client_ctx_destroy(nr_stun_client_ctx **ctxp);
 int nr_stun_transport_addr_check(nr_transport_addr* addr, UINT4 mask);
 int nr_stun_client_process_response(nr_stun_client_ctx *ctx, UCHAR *msg, int len, nr_transport_addr *peer_addr);
 int nr_stun_client_cancel(nr_stun_client_ctx *ctx);
+int nr_stun_client_failed(nr_stun_client_ctx *ctx);
 
 #endif
 
--- a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c
@@ -74,17 +74,16 @@ static int nr_turn_stun_ctx_create(nr_tu
                                    nr_turn_stun_ctx **ctxp);
 static int nr_turn_stun_ctx_destroy(nr_turn_stun_ctx **ctxp);
 static void nr_turn_stun_ctx_cb(NR_SOCKET s, int how, void *arg);
 static int nr_turn_stun_set_auth_params(nr_turn_stun_ctx *ctx,
                                         char *realm, char *nonce);
 static void nr_turn_client_refresh_timer_cb(NR_SOCKET s, int how, void *arg);
 static int nr_turn_client_refresh_setup(nr_turn_client_ctx *ctx,
                                         nr_turn_stun_ctx **sctx);
-static int nr_turn_client_failed(nr_turn_client_ctx *ctx);
 static int nr_turn_client_start_refresh_timer(nr_turn_client_ctx *ctx,
                                               nr_turn_stun_ctx *sctx,
                                               UINT4 lifetime);
 static int nr_turn_permission_create(nr_turn_client_ctx *ctx,
                                      nr_transport_addr *addr,
                                      nr_turn_permission **permp);
 static int nr_turn_permission_find(nr_turn_client_ctx *ctx,
                                    nr_transport_addr *addr,
@@ -492,28 +491,35 @@ int nr_turn_client_deallocate(nr_turn_cl
   ctx->state = NR_TURN_CLIENT_STATE_DEALLOCATING;
 
   _status=0;
 abort:
   nr_stun_message_destroy(&aloc);
   return(_status);
 }
 
-static int nr_turn_client_failed(nr_turn_client_ctx *ctx)
+static void nr_turn_client_fire_finished_cb(nr_turn_client_ctx *ctx)
+  {
+    if (ctx->finished_cb) {
+      NR_async_cb finished_cb=ctx->finished_cb;
+      ctx->finished_cb=0;
+      finished_cb(0, 0, ctx->cb_arg);
+    }
+  }
+
+int nr_turn_client_failed(nr_turn_client_ctx *ctx)
 {
   if (ctx->state == NR_TURN_CLIENT_STATE_FAILED ||
       ctx->state == NR_TURN_CLIENT_STATE_CANCELLED)
     return(0);
 
   r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s) failed", ctx->label);
   nr_turn_client_cancel(ctx);
   ctx->state = NR_TURN_CLIENT_STATE_FAILED;
-  if (ctx->finished_cb) {
-    ctx->finished_cb(0, 0, ctx->cb_arg);
-  }
+  nr_turn_client_fire_finished_cb(ctx);
 
   return(0);
 }
 
 int nr_turn_client_get_relayed_address(nr_turn_client_ctx *ctx,
                                        nr_transport_addr *relayed_address)
 {
   int r, _status;
@@ -544,17 +550,16 @@ int nr_turn_client_get_mapped_address(nr
 abort:
   return(_status);
 }
 
 static void nr_turn_client_allocate_cb(NR_SOCKET s, int how, void *arg)
 {
   nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
   nr_turn_stun_ctx *refresh_ctx;
-  NR_async_cb tmp_finished_cb;
   int r,_status;
 
   ctx->tctx->state = NR_TURN_CLIENT_STATE_ALLOCATED;
 
   if ((r=nr_transport_addr_copy(
           &ctx->tctx->relay_addr,
           &ctx->stun->results.allocate_response.relay_addr)))
     ABORT(r);
@@ -573,24 +578,22 @@ static void nr_turn_client_allocate_cb(N
     ABORT(r);
 
   r_log(NR_LOG_TURN, LOG_INFO,
         "TURN(%s): Succesfully allocated addr %s lifetime=%u",
         ctx->tctx->label,
         ctx->tctx->relay_addr.as_string,
         ctx->stun->results.allocate_response.lifetime_secs);
 
+  nr_turn_client_fire_finished_cb(ctx->tctx);
   _status=0;
 abort:
   if (_status) {
     nr_turn_client_failed(ctx->tctx);
   }
-  tmp_finished_cb = ctx->tctx->finished_cb;
-  ctx->tctx->finished_cb = 0;  /* So we don't call it again */
-  tmp_finished_cb(0, 0, ctx->tctx->cb_arg);
 }
 
 static void nr_turn_client_error_cb(NR_SOCKET s, int how, void *arg)
 {
   nr_turn_stun_ctx *ctx = (nr_turn_stun_ctx *)arg;
 
   r_log(NR_LOG_TURN, LOG_WARNING, "TURN(%s): mode %d, %s",
         ctx->tctx->label, ctx->mode, __FUNCTION__);
--- a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h
+++ b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.h
@@ -115,16 +115,17 @@ int nr_turn_client_allocate(nr_turn_clie
 int nr_turn_client_get_relayed_address(nr_turn_client_ctx *ctx,
                                        nr_transport_addr *relayed_address);
 int nr_turn_client_get_mapped_address(nr_turn_client_ctx *ctx,
                                       nr_transport_addr *mapped_address);
 int nr_turn_client_process_response(nr_turn_client_ctx *ctx,
                                     UCHAR *msg, int len,
                                     nr_transport_addr *turn_server_addr);
 int nr_turn_client_cancel(nr_turn_client_ctx *ctx);
+int nr_turn_client_failed(nr_turn_client_ctx *ctx);
 int nr_turn_client_deallocate(nr_turn_client_ctx *ctx);
 int nr_turn_client_send_indication(nr_turn_client_ctx *ctx,
                                    const UCHAR *msg, size_t len,
                                    int flags, nr_transport_addr *remote_addr);
 int nr_turn_client_parse_data_indication(nr_turn_client_ctx *ctx,
                                          nr_transport_addr *source_addr,
                                          UCHAR *msg, size_t len,
                                          UCHAR *newmsg, size_t *newlen,
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -56,16 +56,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
 
 if (AppConstants.MOZ_SAFE_BROWSING) {
   XPCOMUtils.defineLazyModuleGetter(this, "SafeBrowsing",
                                     "resource://gre/modules/SafeBrowsing.jsm");
 }
 
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
+                                  "resource://gre/modules/BrowserUtils.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Sanitizer",
                                   "resource://gre/modules/Sanitizer.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
                                   "resource://gre/modules/Prompt.jsm");
@@ -3391,16 +3394,20 @@ nsBrowserAccess.prototype = {
   openURIInFrame: function browser_openURIInFrame(aURI, aParams, aWhere, aContext) {
     let browser = this._getBrowser(aURI, null, aWhere, aContext);
     return browser ? browser.QueryInterface(Ci.nsIFrameLoaderOwner) : null;
   },
 
   isTabContentWindow: function(aWindow) {
     return BrowserApp.getBrowserForWindow(aWindow) != null;
   },
+
+  canClose() {
+    return BrowserUtils.canCloseWindow(window);
+  },
 };
 
 
 // track the last known screen size so that new tabs
 // get created with the right size rather than being 1x1
 var gScreenWidth = 1;
 var gScreenHeight = 1;
 
--- a/netwerk/dns/nsIDNService.cpp
+++ b/netwerk/dns/nsIDNService.cpp
@@ -132,82 +132,70 @@ nsIDNService::nsIDNService()
 
 nsIDNService::~nsIDNService()
 {
   idn_nameprep_destroy(mNamePrepHandle);
 }
 
 NS_IMETHODIMP nsIDNService::ConvertUTF8toACE(const nsACString & input, nsACString & ace)
 {
-  return UTF8toACE(input, ace, true, true);
+  return UTF8toACE(input, ace, eStringPrepForDNS);
 }
 
-nsresult nsIDNService::SelectiveUTF8toACE(const nsACString& input, nsACString& ace)
-{
-  return UTF8toACE(input, ace, true, false);
-}
-
-nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace, bool allowUnassigned, bool convertAllLabels)
+nsresult nsIDNService::UTF8toACE(const nsACString & input, nsACString & ace,
+                                 stringPrepFlag flag)
 {
   nsresult rv;
   NS_ConvertUTF8toUTF16 ustr(input);
 
   // map ideographic period to ASCII period etc.
   normalizeFullStops(ustr);
 
-
   uint32_t len, offset;
   len = 0;
   offset = 0;
   nsAutoCString encodedBuf;
 
   nsAString::const_iterator start, end;
-  ustr.BeginReading(start); 
-  ustr.EndReading(end); 
+  ustr.BeginReading(start);
+  ustr.EndReading(end);
   ace.Truncate();
 
   // encode nodes if non ASCII
   while (start != end) {
     len++;
     if (*start++ == (char16_t)'.') {
-      rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf,
-                            allowUnassigned, convertAllLabels);
+      rv = stringPrepAndACE(Substring(ustr, offset, len - 1), encodedBuf, flag);
       NS_ENSURE_SUCCESS(rv, rv);
 
       ace.Append(encodedBuf);
       ace.Append('.');
       offset += len;
       len = 0;
     }
   }
 
   // encode the last node if non ASCII
   if (len) {
-    rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf,
-                          allowUnassigned, convertAllLabels);
+    rv = stringPrepAndACE(Substring(ustr, offset, len), encodedBuf, flag);
     NS_ENSURE_SUCCESS(rv, rv);
 
     ace.Append(encodedBuf);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIDNService::ConvertACEtoUTF8(const nsACString & input, nsACString & _retval)
 {
-  return ACEtoUTF8(input, _retval, true, true);
-}
-
-nsresult nsIDNService::SelectiveACEtoUTF8(const nsACString& input, nsACString& _retval)
-{
-  return ACEtoUTF8(input, _retval, false, false);
+  return ACEtoUTF8(input, _retval, eStringPrepForDNS);
 }
 
 nsresult nsIDNService::ACEtoUTF8(const nsACString & input, nsACString & _retval,
-                                 bool allowUnassigned, bool convertAllLabels)
+                                 stringPrepFlag flag)
 {
   // RFC 3490 - 4.2 ToUnicode
   // ToUnicode never fails.  If any step fails, then the original input
   // sequence is returned immediately in that step.
 
   uint32_t len = 0, offset = 0;
   nsAutoCString decodedBuf;
 
@@ -216,31 +204,31 @@ nsresult nsIDNService::ACEtoUTF8(const n
   input.EndReading(end); 
   _retval.Truncate();
 
   // loop and decode nodes
   while (start != end) {
     len++;
     if (*start++ == '.') {
       if (NS_FAILED(decodeACE(Substring(input, offset, len - 1), decodedBuf,
-                              allowUnassigned, convertAllLabels))) {
+                              flag))) {
         _retval.Assign(input);
         return NS_OK;
       }
 
       _retval.Append(decodedBuf);
       _retval.Append('.');
       offset += len;
       len = 0;
     }
   }
   // decode the last node
   if (len) {
     if (NS_FAILED(decodeACE(Substring(input, offset, len), decodedBuf,
-                            allowUnassigned, convertAllLabels)))
+                            flag)))
       _retval.Assign(input);
     else
       _retval.Append(decodedBuf);
   }
 
   return NS_OK;
 }
 
@@ -257,17 +245,18 @@ NS_IMETHODIMP nsIDNService::IsACE(const 
   // example: "www.xn--ENCODED.com"
 
   const char *p = PL_strncasestr(data, kACEPrefix, dataLen);
 
   *_retval = p && (p == data || *(p - 1) == '.');
   return NS_OK;
 }
 
-NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input, nsACString & output)
+NS_IMETHODIMP nsIDNService::Normalize(const nsACString & input,
+                                      nsACString & output)
 {
   // protect against bogus input
   NS_ENSURE_TRUE(IsUTF8(input), NS_ERROR_UNEXPECTED);
 
   NS_ConvertUTF8toUTF16 inUTF16(input);
   normalizeFullStops(inUTF16);
 
   // pass the domain name to stringprep label by label
@@ -277,36 +266,35 @@ NS_IMETHODIMP nsIDNService::Normalize(co
   nsresult rv;
   nsAString::const_iterator start, end;
   inUTF16.BeginReading(start);
   inUTF16.EndReading(end);
 
   while (start != end) {
     len++;
     if (*start++ == char16_t('.')) {
-      rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel, true);
+      rv = stringPrep(Substring(inUTF16, offset, len - 1), outLabel,
+                      eStringPrepIgnoreErrors);
       NS_ENSURE_SUCCESS(rv, rv);
    
       outUTF16.Append(outLabel);
       outUTF16.Append(char16_t('.'));
       offset += len;
       len = 0;
     }
   }
   if (len) {
-    rv = stringPrep(Substring(inUTF16, offset, len), outLabel, true);
+    rv = stringPrep(Substring(inUTF16, offset, len), outLabel,
+                    eStringPrepIgnoreErrors);
     NS_ENSURE_SUCCESS(rv, rv);
 
     outUTF16.Append(outLabel);
   }
 
   CopyUTF16toUTF8(outUTF16, output);
-  if (!isOnlySafeChars(outUTF16, mIDNBlacklist))
-    return ConvertUTF8toACE(output, output);
-
   return NS_OK;
 }
 
 NS_IMETHODIMP nsIDNService::ConvertToDisplayIDN(const nsACString & input, bool * _isASCII, nsACString & _retval)
 {
   // If host is ACE, then convert to UTF-8 if the host is in the IDN whitelist.
   // Else, if host is already UTF-8, then make sure it is normalized per IDN.
 
@@ -320,58 +308,56 @@ NS_IMETHODIMP nsIDNService::ConvertToDis
   if (IsASCII(input)) {
     // first, canonicalize the host to lowercase, for whitelist lookup
     _retval = input;
     ToLowerCase(_retval);
 
     if (isACE && !mShowPunycode) {
       // ACEtoUTF8() can't fail, but might return the original ACE string
       nsAutoCString temp(_retval);
-      if (isInWhitelist(temp)) {
-        // If the domain is in the whitelist, return the host in UTF-8
-        ACEtoUTF8(temp, _retval, false, true);
-      } else {
-        // Otherwise convert from ACE to UTF8 only those labels which are
-        // considered safe for display
-        SelectiveACEtoUTF8(temp, _retval);
-      }
+      // If the domain is in the whitelist, return the host in UTF-8.
+      // Otherwise convert from ACE to UTF8 only those labels which are
+      // considered safe for display
+      ACEtoUTF8(temp, _retval, isInWhitelist(temp) ?
+                                 eStringPrepIgnoreErrors : eStringPrepForUI);
       *_isASCII = IsASCII(_retval);
     } else {
       *_isASCII = true;
     }
   } else {
     // We have to normalize the hostname before testing against the domain
     // whitelist (see bug 315411), and to ensure the entire string gets
     // normalized.
     //
     // Normalization and the tests for safe display below, assume that the
     // input is Unicode, so first convert any ACE labels to UTF8
     if (isACE) {
       nsAutoCString temp;
-      ACEtoUTF8(input, temp, false, true);
+      ACEtoUTF8(input, temp, eStringPrepIgnoreErrors);
       rv = Normalize(temp, _retval);
     } else {
       rv = Normalize(input, _retval);
     }
     if (NS_FAILED(rv)) return rv;
 
-    if (mShowPunycode && NS_SUCCEEDED(ConvertUTF8toACE(_retval, _retval))) {
+    if (mShowPunycode && NS_SUCCEEDED(UTF8toACE(_retval, _retval,
+                                                eStringPrepIgnoreErrors))) {
       *_isASCII = true;
       return NS_OK;
     }
 
     // normalization could result in an ASCII-only hostname. alternatively, if
     // the host is converted to ACE by the normalizer, then the host may contain
     // unsafe characters, so leave it ACE encoded. see bug 283016, bug 301694, and bug 309311.
     *_isASCII = IsASCII(_retval);
     if (!*_isASCII && !isInWhitelist(_retval)) {
-      // SelectiveUTF8toACE may return a domain name where some labels are in UTF-8
-      // and some are in ACE, depending on whether they are considered safe for
-      // display
-      rv = SelectiveUTF8toACE(_retval, _retval);
+      // UTF8toACE with eStringPrepForUI may return a domain name where
+      // some labels are in UTF-8 and some are in ACE, depending on
+      // whether they are considered safe for display
+      rv = UTF8toACE(_retval, _retval, eStringPrepForUI);
       *_isASCII = IsASCII(_retval);
       return rv;
     }
   }
 
   return NS_OK;
 }
 
@@ -469,17 +455,17 @@ static nsresult punycode(const nsAString
 // for bidirectional strings. If the string does not satisfy the requirements
 // for bidirectional strings, return an error. This is described in section 6.
 //
 // 5) Check unassigned code points -- If allowUnassigned is false, check for
 // any unassigned Unicode points and if any are found return an error.
 // This is described in section 7.
 //
 nsresult nsIDNService::stringPrep(const nsAString& in, nsAString& out,
-                                  bool allowUnassigned)
+                                  stringPrepFlag flag)
 {
   if (!mNamePrepHandle || !mNormalizer)
     return NS_ERROR_FAILURE;
 
   uint32_t ucs4Buf[kMaxDNSNodeLen + 1];
   uint32_t ucs4Len;
   nsresult rv = utf16ToUcs4(in, ucs4Buf, kMaxDNSNodeLen, &ucs4Len);
   NS_ENSURE_SUCCESS(rv, rv);
@@ -498,79 +484,96 @@ nsresult nsIDNService::stringPrep(const 
     return NS_ERROR_FAILURE;
 
   // normalize
   nsAutoString normlizedStr;
   rv = mNormalizer->NormalizeUnicodeNFKC(namePrepStr, normlizedStr);
   if (normlizedStr.Length() >= kMaxDNSNodeLen)
     return NS_ERROR_FAILURE;
 
+  // set the result string
+  out.Assign(normlizedStr);
+
+  if (flag == eStringPrepIgnoreErrors) {
+    return NS_OK;
+  }
+
   // prohibit
   const uint32_t *found = nullptr;
-  idn_err = idn_nameprep_isprohibited(mNamePrepHandle, 
+  idn_err = idn_nameprep_isprohibited(mNamePrepHandle,
                                       (const uint32_t *) ucs4Buf, &found);
-  if (idn_err != idn_success || found)
-    return NS_ERROR_FAILURE;
-
-  // check bidi
-  idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle, 
-                                     (const uint32_t *) ucs4Buf, &found);
-  if (idn_err != idn_success || found)
-    return NS_ERROR_FAILURE;
-
-  if (!allowUnassigned) {
-    // check unassigned code points
-    idn_err = idn_nameprep_isunassigned(mNamePrepHandle,
-                                        (const uint32_t *) ucs4Buf, &found);
-    if (idn_err != idn_success || found)
-      return NS_ERROR_FAILURE;
+  if (idn_err != idn_success || found) {
+    rv = NS_ERROR_FAILURE;
+  } else {
+    // check bidi
+    idn_err = idn_nameprep_isvalidbidi(mNamePrepHandle,
+                                       (const uint32_t *) ucs4Buf, &found);
+    if (idn_err != idn_success || found) {
+      rv = NS_ERROR_FAILURE;
+    } else  if (flag == eStringPrepForUI) {
+      // check unassigned code points
+      idn_err = idn_nameprep_isunassigned(mNamePrepHandle,
+                                          (const uint32_t *) ucs4Buf, &found);
+      if (idn_err != idn_success || found) {
+        rv = NS_ERROR_FAILURE;
+      }
+    }
   }
 
-  // set the result string
-  out.Assign(normlizedStr);
+  if (flag == eStringPrepForDNS && NS_FAILED(rv)) {
+    out.Truncate();
+  }
 
   return rv;
 }
 
 nsresult nsIDNService::stringPrepAndACE(const nsAString& in, nsACString& out,
-                                        bool allowUnassigned,
-                                        bool convertAllLabels)
+                                        stringPrepFlag flag)
 {
   nsresult rv = NS_OK;
 
   out.Truncate();
 
   if (in.Length() > kMaxDNSNodeLen) {
     NS_WARNING("IDN node too large");
     return NS_ERROR_FAILURE;
   }
 
-  if (IsASCII(in))
+  if (IsASCII(in)) {
     LossyCopyUTF16toASCII(in, out);
-  else if (!convertAllLabels && isLabelSafe(in))
-    CopyUTF16toUTF8(in, out);
-  else {
-    nsAutoString strPrep;
-    rv = stringPrep(in, strPrep, allowUnassigned);
-    if (NS_SUCCEEDED(rv)) {
-      if (IsASCII(strPrep))
-        LossyCopyUTF16toASCII(strPrep, out);
-      else
-        rv = punycode(strPrep, out);
-    }
-    // Check that the encoded output isn't larger than the maximum length of an
-    // DNS node per RFC 1034.
-    // This test isn't necessary in the code paths above where the input is
-    // ASCII (since the output will be the same length as the input) or where
-    // we convert to UTF-8 (since the output is only used for display in the
-    // UI and not passed to DNS and can legitimately be longer than the limit).
-    if (out.Length() > kMaxDNSNodeLen) {
-      NS_WARNING("IDN node too large");
-      return NS_ERROR_FAILURE;
-    }
+    return NS_OK;
+  }
+
+  nsAutoString strPrep;
+  rv = stringPrep(in, strPrep, flag);
+  if (flag == eStringPrepForDNS) {
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  if (IsASCII(strPrep)) {
+    LossyCopyUTF16toASCII(strPrep, out);
+    return NS_OK;
+  }
+
+  if (flag == eStringPrepForUI && NS_SUCCEEDED(rv) && isLabelSafe(in)) {
+    CopyUTF16toUTF8(strPrep, out);
+    return NS_OK;
+  }
+
+  rv = punycode(strPrep, out);
+  // Check that the encoded output isn't larger than the maximum length
+  // of a DNS node per RFC 1034.
+  // This test isn't necessary in the code paths above where the input
+  // is ASCII (since the output will be the same length as the input) or
+  // where we convert to UTF-8 (since the output is only used for
+  // display in the UI and not passed to DNS and can legitimately be
+  // longer than the limit).
+  if (out.Length() > kMaxDNSNodeLen) {
+    NS_WARNING("IDN node too large");
+    return NS_ERROR_FAILURE;
   }
 
   return rv;
 }
 
 // RFC 3490
 // 1) Whenever dots are used as label separators, the following characters
 //    MUST be recognized as dots: U+002E (full stop), U+3002 (ideographic full
@@ -595,17 +598,17 @@ void nsIDNService::normalizeFullStops(ns
         break;
     }
     start++;
     index++;
   }
 }
 
 nsresult nsIDNService::decodeACE(const nsACString& in, nsACString& out,
-                                 bool allowUnassigned, bool convertAllLabels)
+                                 stringPrepFlag flag)
 {
   bool isAce;
   IsACE(in, &isAce);
   if (!isAce) {
     out.Assign(in);
     return NS_OK;
   }
 
@@ -625,42 +628,43 @@ nsresult nsIDNService::decodeACE(const n
     return NS_ERROR_FAILURE;
   }
 
   // UCS4 -> UTF8
   output[output_length] = 0;
   nsAutoString utf16;
   ucs4toUtf16(output, utf16);
   delete [] output;
-  if (!convertAllLabels && !isLabelSafe(utf16)) {
+  if (flag != eStringPrepForUI || isLabelSafe(utf16)) {
+    CopyUTF16toUTF8(utf16, out);
+  } else {
     out.Assign(in);
     return NS_OK;
   }
-  if (!isOnlySafeChars(utf16, mIDNBlacklist))
-    return NS_ERROR_FAILURE;
-  CopyUTF16toUTF8(utf16, out);
 
   // Validation: encode back to ACE and compare the strings
   nsAutoCString ace;
-  nsresult rv = UTF8toACE(out, ace, allowUnassigned, true);
+  nsresult rv = UTF8toACE(out, ace, flag);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!ace.Equals(in, nsCaseInsensitiveCStringComparator()))
+  if (flag == eStringPrepForDNS &&
+      !ace.Equals(in, nsCaseInsensitiveCStringComparator())) {
     return NS_ERROR_FAILURE;
+  }
 
   return NS_OK;
 }
 
 bool nsIDNService::isInWhitelist(const nsACString &host)
 {
   if (mIDNUseWhitelist && mIDNWhitelistPrefBranch) {
     nsAutoCString tld(host);
     // make sure the host is ACE for lookup and check that there are no
     // unassigned codepoints
-    if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, false, true))) {
+    if (!IsASCII(tld) && NS_FAILED(UTF8toACE(tld, tld, eStringPrepForDNS))) {
       return false;
     }
 
     // truncate trailing dots first
     tld.Trim(".");
     int32_t pos = tld.RFind(".");
     if (pos == kNotFound)
       return false;
@@ -672,16 +676,20 @@ bool nsIDNService::isInWhitelist(const n
       return safe;
   }
 
   return false;
 }
 
 bool nsIDNService::isLabelSafe(const nsAString &label)
 {
+  if (!isOnlySafeChars(PromiseFlatString(label), mIDNBlacklist)) {
+    return false;
+  }
+
   // We should never get here if the label is ASCII
   NS_ASSERTION(!IsASCII(label), "ASCII label in IDN checking");
   if (mRestrictionProfile == eASCIIOnlyProfile) {
     return false;
   }
 
   nsAString::const_iterator current, end;
   label.BeginReading(current);
--- a/netwerk/dns/nsIDNService.h
+++ b/netwerk/dns/nsIDNService.h
@@ -32,39 +32,139 @@ public:
   nsIDNService();
 
   nsresult Init();
 
 protected:
   virtual ~nsIDNService();
 
 private:
+  enum stringPrepFlag {
+    eStringPrepForDNS,
+    eStringPrepForUI,
+    eStringPrepIgnoreErrors
+  };
+
+  /**
+   * Convert the following characters that must be recognized as label
+   *  separators per RFC 3490 to ASCII full stop characters
+   *
+   * U+3002 (ideographic full stop)
+   * U+FF0E (fullwidth full stop)
+   * U+FF61 (halfwidth ideographic full stop)
+   */
   void normalizeFullStops(nsAString& s);
+
+  /**
+   * Convert and encode a DNS label in ACE/punycode.
+   * @param flag
+   *        if eStringPrepIgnoreErrors, all non-ASCII labels are
+   *           converted to punycode.
+   *        if eStringPrepForUI, only labels that are considered safe
+   *           for display are converted.
+   *           @see isLabelSafe
+   *        if eStringPrepForDNS and stringPrep finds an illegal
+   *           character, returns NS_FAILURE and out is empty
+   */
   nsresult stringPrepAndACE(const nsAString& in, nsACString& out,
-                            bool allowUnassigned, bool convertAllLabels);
-  nsresult stringPrep(const nsAString& in, nsAString& out,
-                      bool allowUnassigned);
+                            stringPrepFlag flag);
+
+  /**
+   * Convert a DNS label using the stringprep profile defined in RFC 3454
+   */
+  nsresult stringPrep(const nsAString& in, nsAString& out, stringPrepFlag flag);
+
+  /**
+   * Decode an ACE-encoded DNS label to UTF-8
+   *
+   * @param flag
+   *        if eStringPrepForUI and the label is not considered safe to
+   *           display, the output is the same as the input
+   *        @see isLabelSafe
+   */
   nsresult decodeACE(const nsACString& in, nsACString& out,
-                     bool allowUnassigned, bool convertAllLabels);
-  nsresult SelectiveUTF8toACE(const nsACString& input, nsACString& ace);
-  nsresult SelectiveACEtoUTF8(const nsACString& input, nsACString& _retval);
+                     stringPrepFlag flag);
+
+  /**
+   * Convert complete domain names between UTF8 and ACE and vice versa
+   *
+   * @param flag is passed to decodeACE or stringPrepAndACE for each
+   *  label individually, so the output may contain some labels in
+   *  punycode and some in UTF-8
+   */
   nsresult UTF8toACE(const nsACString& input, nsACString& ace,
-                     bool allowUnassigned, bool convertAllLabels);
+                     stringPrepFlag flag);
   nsresult ACEtoUTF8(const nsACString& input, nsACString& _retval,
-                     bool allowUnassigned, bool convertAllLabels);
+                     stringPrepFlag flag);
+
   bool isInWhitelist(const nsACString &host);
   void prefsChanged(nsIPrefBranch *prefBranch, const char16_t *pref);
+
+  /**
+   * Determine whether a label is considered safe to display to the user
+   * according to the algorithm defined in UTR 39 and the profile
+   * selected in mRestrictionProfile.
+   *
+   * For the ASCII-only profile, returns false for all labels containing
+   * non-ASCII characters.
+   *
+   * For the other profiles, returns false for labels containing any of
+   * the following:
+   *
+   *  Characters in scripts other than the "recommended scripts" and
+   *   "aspirational scripts" defined in
+   *   http://www.unicode.org/reports/tr31/#Table_Recommended_Scripts
+   *   and http://www.unicode.org/reports/tr31/#Aspirational_Use_Scripts
+   *  This includes codepoints that are not defined as Unicode
+   *   characters
+   *
+   *  Illegal combinations of scripts (@see illegalScriptCombo)
+   *
+   *  Numbers from more than one different numbering system
+   *
+   *  Sequences of the same non-spacing mark
+   *
+   *  Both simplified-only and traditional-only Chinese characters
+   *   XXX this test was disabled by bug 857481
+   */
   bool isLabelSafe(const nsAString &label);
+
+  /**
+   * Determine whether a combination of scripts in a single label is
+   * permitted according to the algorithm defined in UTR 39 and the
+   * profile selected in mRestrictionProfile.
+   *
+   * For the "Highly restrictive" profile, all characters in each
+   * identifier must be from a single script, or from the combinations:
+   *  Latin + Han + Hiragana + Katakana;
+   *  Latin + Han + Bopomofo; or
+   *  Latin + Han + Hangul
+   *
+   * For the "Moderately restrictive" profile, Latin is also allowed
+   *  with other scripts except Cyrillic and Greek
+   */
   bool illegalScriptCombo(int32_t script, int32_t& savedScript);
 
   idn_nameprep_t mNamePrepHandle;
   nsCOMPtr<nsIUnicodeNormalizer> mNormalizer;
   nsXPIDLString mIDNBlacklist;
+
+  /**
+   * Flag set by the pref network.IDN_show_punycode. When it is true,
+   * IDNs containing non-ASCII characters are always displayed to the
+   * user in punycode
+   */
   bool mShowPunycode;
-  enum restrictionProfile {
+
+  /**
+   * Restriction-level Detection profiles defined in UTR 39
+   * http://www.unicode.org/reports/tr39/#Restriction_Level_Detection,
+   * and selected by the pref network.IDN.restriction_profile
+   */
+   enum restrictionProfile {
     eASCIIOnlyProfile,
     eHighlyRestrictiveProfile,
     eModeratelyRestrictiveProfile
   };
   restrictionProfile mRestrictionProfile;
   nsCOMPtr<nsIPrefBranch> mIDNWhitelistPrefBranch;
   bool mIDNUseWhitelist;
 };
--- a/netwerk/protocol/http/nsHttpHandler.cpp
+++ b/netwerk/protocol/http/nsHttpHandler.cpp
@@ -360,16 +360,18 @@ nsHttpHandler::Init()
     if (obsService) {
         // register the handler object as a weak callback as we don't need to worry
         // about shutdown ordering.
         obsService->AddObserver(this, "profile-change-net-teardown", true);
         obsService->AddObserver(this, "profile-change-net-restore", true);
         obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, true);
         obsService->AddObserver(this, "net:clear-active-logins", true);
         obsService->AddObserver(this, "net:prune-dead-connections", true);
+        // Sent by the TorButton add-on in the Tor Browser
+        obsService->AddObserver(this, "net:prune-all-connections", true);
         obsService->AddObserver(this, "net:failed-to-process-uri-content", true);
         obsService->AddObserver(this, "last-pb-context-exited", true);
         obsService->AddObserver(this, "webapps-clear-data", true);
         obsService->AddObserver(this, "browser:purge-session-history", true);
         obsService->AddObserver(this, NS_NETWORK_LINK_TOPIC, true);
         obsService->AddObserver(this, "application-background", true);
     }
 
@@ -2039,16 +2041,21 @@ nsHttpHandler::Observe(nsISupports *subj
         InitConnectionMgr();
     } else if (!strcmp(topic, "net:clear-active-logins")) {
         mAuthCache.ClearAll();
         mPrivateAuthCache.ClearAll();
     } else if (!strcmp(topic, "net:prune-dead-connections")) {
         if (mConnMgr) {
             mConnMgr->PruneDeadConnections();
         }
+    } else if (!strcmp(topic, "net:prune-all-connections")) {
+        if (mConnMgr) {
+            mConnMgr->DoShiftReloadConnectionCleanup(nullptr);
+            mConnMgr->PruneDeadConnections();
+        }
     } else if (!strcmp(topic, "net:failed-to-process-uri-content")) {
         nsCOMPtr<nsIURI> uri = do_QueryInterface(subject);
         if (uri && mConnMgr) {
             mConnMgr->ReportFailedToProcess(uri);
         }
     } else if (!strcmp(topic, "last-pb-context-exited")) {
         mPrivateAuthCache.ClearAll();
         if (mConnMgr) {
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_idn_blacklist.js
@@ -0,0 +1,164 @@
+// Test that URLs containing characters in the IDN blacklist are
+// always displayed as punycode
+const testcases = [
+    //  Original  Punycode or
+    //            normalized form
+    //
+    ["\u00BC", "xn--14-c6t"],
+    ["\u00BD", "xn--12-c6t"],
+    ["\u00BE", "xn--34-c6t"],
+    ["\u01C3", "xn--ija"],
+    ["\u02D0", "xn--6qa"],
+    ["\u0337", "xn--4ta"],
+    ["\u0338", "xn--5ta"],
+    ["\u0589", "xn--3bb"],
+    ["\u05C3", "xn--rdb"],
+    ["\u05F4", "xn--5eb"],
+    ["\u0609", "xn--rfb"],
+    ["\u060A", "xn--sfb"],
+    ["\u066A", "xn--jib"],
+    ["\u06D4", "xn--klb"],
+    ["\u0701", "xn--umb"],
+    ["\u0702", "xn--vmb"],
+    ["\u0703", "xn--wmb"],
+    ["\u0704", "xn--xmb"],
+    ["\u115F", "xn--osd"],
+    ["\u1160", "xn--psd"],
+    ["\u1735", "xn--d0e"],
+    ["\u2027", "xn--svg"],
+    ["\u2028", "xn--tvg"],
+    ["\u2029", "xn--uvg"],
+    ["\u2039", "xn--bwg"],
+    ["\u203A", "xn--cwg"],
+    ["\u2041", "xn--jwg"],
+    ["\u2044", "xn--mwg"],
+    ["\u2052", "xn--0wg"],
+    ["\u2153", "xn--13-c6t"],
+    ["\u2154", "xn--23-c6t"],
+    ["\u2155", "xn--15-c6t"],
+    ["\u2156", "xn--25-c6t"],
+    ["\u2157", "xn--35-c6t"],
+    ["\u2158", "xn--45-c6t"],
+    ["\u2159", "xn--16-c6t"],
+    ["\u215A", "xn--56-c6t"],
+    ["\u215B", "xn--18-c6t"],
+    ["\u215C", "xn--38-c6t"],
+    ["\u215D", "xn--58-c6t"],
+    ["\u215E", "xn--78-c6t"],
+    ["\u215F", "xn--1-zjn"],
+    ["\u2215", "xn--w9g"],
+    ["\u2236", "xn--ubh"],
+    ["\u23AE", "xn--lmh"],
+    ["\u2571", "xn--hzh"],
+    ["\u29F6", "xn--jxi"],
+    ["\u29F8", "xn--lxi"],
+    ["\u2AFB", "xn--z4i"],
+    ["\u2AFD", "xn--14i"],
+    ["\u2FF0", "xn--85j"],
+    ["\u2FF1", "xn--95j"],
+    ["\u2FF2", "xn--b6j"],
+    ["\u2FF3", "xn--c6j"],
+    ["\u2FF4", "xn--d6j"],
+    ["\u2FF5", "xn--e6j"],
+    ["\u2FF6", "xn--f6j"],
+    ["\u2FF7", "xn--g6j"],
+    ["\u2FF8", "xn--h6j"],
+    ["\u2FF9", "xn--i6j"],
+    ["\u2FFA", "xn--j6j"],
+    ["\u2FFB", "xn--k6j"],
+    ["\u3014", "xn--96j"],
+    ["\u3015", "xn--b7j"],
+    ["\u3033", "xn--57j"],
+    ["\u3164", "xn--psd"],
+    ["\u321D", "xn--()-357j35d"],
+    ["\u321E", "xn--()-357jf36c"],
+    ["\u33AE", "xn--rads-id9a"],
+    ["\u33AF", "xn--rads2-4d6b"],
+    ["\u33C6", "xn--ckg-tc2a"],
+    ["\u33DF", "xn--am-6bv"],
+    ["\uA789", "xn--058a"],
+    ["\uFE3F", "xn--x6j"],
+    ["\uFE5D", "xn--96j"],
+    ["\uFE5E", "xn--b7j"],
+    ["\uFFA0", "xn--psd"],
+    ["\uFFF9", "xn--vn7c"],
+    ["\uFFFA", "xn--wn7c"],
+    ["\uFFFB", "xn--xn7c"],
+    ["\uFFFC", "xn--yn7c"],
+    ["\uFFFD", "xn--zn7c"],
+
+    // Characters from the IDN blacklist that normalize to ASCII
+    // If we start using STD3ASCIIRules these will be blocked (bug 316444)
+    ["\u00A0", " "],
+    ["\u2000", " "],
+    ["\u2001", " "],
+    ["\u2002", " "],
+    ["\u2003", " "],
+    ["\u2004", " "],
+    ["\u2005", " "],
+    ["\u2006", " "],
+    ["\u2007", " "],
+    ["\u2008", " "],
+    ["\u2009", " "],
+    ["\u200A", " "],
+    ["\u2024", "."],
+    ["\u202F", " "],
+    ["\u205F", " "],
+    ["\u3000", " "],
+    ["\u3002", "."],
+    ["\uFE14", ";"],
+    ["\uFE15", "!"],
+    ["\uFF0E", "."],
+    ["\uFF0F", "/"],
+    ["\uFF61", "."],
+
+    // Characters from the IDN blacklist that are stripped by Nameprep
+    ["\u200B", ""],
+    ["\uFEFF", ""],
+];
+
+
+function run_test() {
+    var pbi = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+    var oldProfile = pbi.getCharPref("network.IDN.restriction_profile", "moderate");
+    var oldWhiteListCom;
+    try {
+        oldWhitelistCom = pbi.getBoolPref("network.IDN.whitelist.com");
+    } catch(e) {
+        oldWhitelistCom = false;
+    }
+    var idnService = Cc["@mozilla.org/network/idn-service;1"].getService(Ci.nsIIDNService);
+
+    pbi.setCharPref("network.IDN.restriction_profile", "moderate");
+    pbi.setBoolPref("network.IDN.whitelist.com", false);
+
+    for (var j = 0; j < testcases.length; ++j) {
+        var test = testcases[j];
+        var URL = test[0] + ".com";
+        var punycodeURL = test[1] + ".com";
+        var isASCII = {};
+
+	var result;
+	try {
+	    result = idnService.convertToDisplayIDN(URL, isASCII);
+	} catch(e) {
+	    result = ".com";
+	}
+        if (punycodeURL.substr(0, 4) == "xn--") {
+            // test convertToDisplayIDN with a Unicode URL and with a
+            //  Punycode URL if we have one
+            do_check_eq(escape(result), escape(punycodeURL));
+
+            result = idnService.convertToDisplayIDN(punycodeURL, isASCII);
+            do_check_eq(escape(result), escape(punycodeURL));
+        } else {
+            // The "punycode" URL isn't punycode. This happens in testcases
+            // where the Unicode URL has become normalized to an ASCII URL,
+            // so, even though expectedUnicode is true, the expected result
+            // is equal to punycodeURL
+            do_check_eq(escape(result), escape(punycodeURL));
+        }
+    }
+    pbi.setBoolPref("network.IDN.whitelist.com", oldWhitelistCom);
+    pbi.setCharPref("network.IDN.restriction_profile", oldProfile);
+}
--- a/netwerk/test/unit/test_idn_urls.js
+++ b/netwerk/test/unit/test_idn_urls.js
@@ -149,17 +149,18 @@ const testcases = [
     // Han from Plane 2
     ["𠀀𠀁𠀂", "xn--j50icd",                         false, true,  true],
 
     // Han from Plane 2 with js (UTF-16) escapes
     ["\uD840\uDC00\uD840\uDC01\uD840\uDC02",
             "xn--j50icd",                            false, true,  true],
 
     // Same with a lone high surrogate at the end
-    ["\uD840\uDC00\uD840\uDC01\uD840", "",           false, false, false],
+    ["\uD840\uDC00\uD840\uDC01\uD840",
+            "xn--zn7c0336bda",                       false, false, false],
 
     // Latin text and Bengali digits
     ["super৪",   "xn--super-k2l",                    false, false, true],
 
     // Bengali digits and Latin text
     ["৫ab",   "xn--ab-x5f",                          false, false, true],
 
     // Bengali text and Latin digits
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -208,16 +208,17 @@ skip-if = bits != 32
 [test_header_Accept-Language_case.js]
 [test_headers.js]
 [test_http_headers.js]
 [test_httpauth.js]
 [test_httpcancel.js]
 [test_httpResponseTimeout.js]
 [test_httpsuspend.js]
 [test_idnservice.js]
+[test_idn_blacklist.js]
 [test_idn_urls.js]
 [test_invalidport.js]
 [test_localstreams.js]
 [test_mismatch_last-modified.js]
 [test_MIME_params.js]
 [test_mozTXTToHTMLConv.js]
 [test_multipart_byteranges.js]
 [test_multipart_streamconv.js]
--- a/services/fxaccounts/FxAccountsOAuthClient.jsm
+++ b/services/fxaccounts/FxAccountsOAuthClient.jsm
@@ -190,16 +190,35 @@ this.FxAccountsOAuthClient.prototype = {
               err = new Error("OAuth flow failed. Keys were not returned");
             } else {
               result = {
                 code: data.code,
                 state: data.state
               };
             }
 
+            // if the message asked to close the tab
+            if (data.closeWindow && target) {
+              // for e10s reasons the best way is to use the TabBrowser to close the tab.
+              let tabbrowser = target.getTabBrowser();
+
+              if (tabbrowser) {
+                let tab = tabbrowser.getTabForBrowser(target);
+
+                if (tab) {
+                  tabbrowser.removeTab(tab);
+                  log.debug("OAuth flow closed the tab.");
+                } else {
+                  log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser.");
+                }
+              } else {
+                log.debug("OAuth flow failed to close the tab. TabBrowser not found.");
+              }
+            }
+
             if (err) {
               log.debug(err.message);
               if (this.onError) {
                 this.onError(err);
               }
             } else {
               log.debug("OAuth flow completed.");
               if (this.onComplete) {
@@ -209,35 +228,16 @@ this.FxAccountsOAuthClient.prototype = {
                   this.onComplete(result);
                 }
               }
             }
 
             // onComplete will be called for this client only once
             // calling onComplete again will result in a failure of the OAuth flow
             this.tearDown();
-
-            // if the message asked to close the tab
-            if (data.closeWindow && target) {
-              // for e10s reasons the best way is to use the TabBrowser to close the tab.
-              let tabbrowser = target.getTabBrowser();
-
-              if (tabbrowser) {
-                let tab = tabbrowser.getTabForBrowser(target);
-
-                if (tab) {
-                  tabbrowser.removeTab(tab);
-                  log.debug("OAuth flow closed the tab.");
-                } else {
-                  log.debug("OAuth flow failed to close the tab. Tab not found in TabBrowser.");
-                }
-              } else {
-                log.debug("OAuth flow failed to close the tab. TabBrowser not found.");
-              }
-            }
             break;
         }
       }
     };
 
     this._channelCallback = listener.bind(this);
     this._channel = new WebChannel(this._webChannelId, this._webChannelOrigin);
     this._channel.listen(this._channelCallback);
--- a/testing/marionette/elements.js
+++ b/testing/marionette/elements.js
@@ -73,16 +73,20 @@ Accessibility.prototype = {
     'checkbutton',
     'combobox',
     'key',
     'link',
     'menuitem',
     'check menu item',
     'radio menu item',
     'option',
+    'listbox option',
+    'listbox rich option',
+    'check rich option',
+    'combobox option',
     'radiobutton',
     'rowheader',
     'switch',
     'slider',
     'spinbutton',
     'pagetab',
     'entry',
     'outlineitem'
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -283,17 +283,17 @@ Tester.prototype = {
         this.currentTest.addResult(new testResult(false, msg, "", false));
         gBrowser.removeTab(lastTab);
       }
     }
 
     // Replace the last tab with a fresh one
     if (window.gBrowser) {
       gBrowser.addTab("about:blank", { skipAnimation: true });
-      gBrowser.removeCurrentTab();
+      gBrowser.removeTab(gBrowser.selectedTab, { skipPermitUnload: true });
       gBrowser.stop();
     }
 
     // Remove stale windows
     this.dumper.structuredLogger.info("checking window state");
     let windowsEnum = Services.wm.getEnumerator(null);
     while (windowsEnum.hasMoreElements()) {
       let win = windowsEnum.getNext();
--- a/testing/tools/autotry/autotry.py
+++ b/testing/tools/autotry/autotry.py
@@ -318,16 +318,62 @@ class AutoTry(object):
     def _git_push_to_try(self, msg):
         self._run_git('commit', '--allow-empty', '-m', msg)
         try:
             self._run_git('push', 'hg::ssh://hg.mozilla.org/try',
                           '+HEAD:refs/heads/branches/default/tip')
         finally:
             self._run_git('reset', 'HEAD~')
 
+    def _git_find_changed_files(self):
+        # This finds the files changed on the current branch based on the
+        # diff of the current branch its merge-base base with other branches.
+        try:
+            args = ['git', 'rev-parse', 'HEAD']
+            current_branch = subprocess.check_output(args).strip()
+            args = ['git', 'for-each-ref', 'refs/heads', 'refs/remotes',
+                    '--format=%(objectname)']
+            all_branches = subprocess.check_output(args).splitlines()
+            other_branches = set(all_branches) - set([current_branch])
+            args = ['git', 'merge-base', 'HEAD'] + list(other_branches)
+            base_commit = subprocess.check_output(args).strip()
+            args = ['git', 'diff', '--name-only', '-z', 'HEAD', base_commit]
+            return subprocess.check_output(args).strip('\0').split('\0')
+        except subprocess.CalledProcessError as e:
+            print('Failed while determining files changed on this branch')
+            print('Failed whle running: %s' % args)
+            print(e.output)
+            sys.exit(1)
+
+    def _hg_find_changed_files(self):
+        hg_args = [
+            'hg', 'log', '-r',
+            '::. and not public()',
+            '--template',
+            '{join(files, "\n")}\n',
+        ]
+        try:
+            return subprocess.check_output(hg_args).splitlines()
+        except subprocess.CalledProcessError as e:
+            print('Failed while finding files changed since the last '
+                  'public ancestor')
+            print('Failed whle running: %s' % hg_args)
+            print(e.output)
+            sys.exit(1)
+
+    def find_changed_files(self):
+        """Finds files changed in a local source tree.
+
+        For hg, changes since the last public ancestor of '.' are
+        considered. For git, changes in the current branch are considered.
+        """
+        if self._use_git:
+            return self._git_find_changed_files()
+        return self._hg_find_changed_files()
+
     def push_to_try(self, msg, verbose):
         if not self._use_git:
             try:
                 hg_args = ['hg', 'push-to-try', '-m', msg]
                 subprocess.check_call(hg_args, stderr=subprocess.STDOUT)
             except subprocess.CalledProcessError as e:
                 print('ERROR hg command %s returned %s' % (hg_args, e.returncode))
                 print('The "push-to-try" hg extension is required to push from '
@@ -378,30 +424,8 @@ class AutoTry(object):
             if verbose:
                 if paths:
                     print("Pushing tests based on the following patterns:\n\t%s" %
                           "\n\t".join(paths))
                 if tags:
                     print("Pushing tests based on the following tags:\n\t%s" %
                           "\n\t".join(tags))
         return paths, tags
-
-
-    def find_changed_files(self):
-        """Finds files changed in a local source tree (hg only for now)."""
-        if self._use_git:
-            # Getting changed files on the current branch with rev-list and contains
-            # will not work: subsequent commits on mozilla-central frequently have
-            # non-increasing dates, breaking both.
-            # (see http://thread.gmane.org/gmane.comp.version-control.git/269560/)
-            # Git support will be added in bug 1203686.
-            return []
-
-        hg_args = [
-            'hg', 'log', '-r',
-            # Include everything from the current commit to the last
-            # public ancestor.
-            '::. and not public()',
-            '--template',
-            '{join(files, "\n")}\n',
-        ]
-
-        return subprocess.check_output(hg_args).splitlines()
--- a/testing/web-platform/harness/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/harness/wptrunner/browsers/firefox.py
@@ -48,18 +48,24 @@ def browser_kwargs(**kwargs):
             "e10s": kwargs["gecko_e10s"]}
 
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     executor_kwargs = base_executor_kwargs(test_type, server_config,
                                            cache_manager, **kwargs)
     executor_kwargs["close_after_done"] = True
-    if run_info_data["debug"] and kwargs["timeout_multiplier"] is None:
-        executor_kwargs["timeout_multiplier"] = 3
+    if kwargs["timeout_multiplier"] is None:
+        if kwargs["gecko_e10s"] and test_type == "reftest":
+            if run_info_data["debug"]:
+                executor_kwargs["timeout_multiplier"] = 4
+            else:
+                executor_kwargs["timeout_multiplier"] = 2
+        elif run_info_data["debug"]:
+            executor_kwargs["timeout_multiplier"] = 3
     return executor_kwargs
 
 
 def env_options():
     return {"host": "127.0.0.1",
             "external_host": "web-platform.test",
             "bind_hostname": "false",
             "certificate_domain": "web-platform.test",
--- a/testing/web-platform/harness/wptrunner/browsers/servo.py
+++ b/testing/web-platform/harness/wptrunner/browsers/servo.py
@@ -32,18 +32,20 @@ def browser_kwargs(**kwargs):
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data,
                     **kwargs):
     rv = base_executor_kwargs(test_type, server_config,
                               cache_manager, **kwargs)
     rv["pause_after_test"] = kwargs["pause_after_test"]
     return rv
 
+
 def env_options():
-    return {"host": "localhost",
+    return {"host": "127.0.0.1",
+            "external_host": "web-platform.test",
             "bind_hostname": "true",
             "testharnessreport": "testharnessreport-servo.js",
             "supports_debugger": True}
 
 
 class ServoBrowser(NullBrowser):
     def __init__(self, logger, binary, debug_info=None, user_stylesheets=None):
         NullBrowser.__init__(self, logger)
--- a/testing/web-platform/harness/wptrunner/browsers/servodriver.py
+++ b/testing/web-platform/harness/wptrunner/browsers/servodriver.py
@@ -44,17 +44,18 @@ def browser_kwargs(**kwargs):
 
 def executor_kwargs(test_type, server_config, cache_manager, run_info_data, **kwargs):
     rv = base_executor_kwargs(test_type, server_config,
                               cache_manager, **kwargs)
     return rv
 
 
 def env_options():
-    return {"host": "web-platform.test",
+    return {"host": "127.0.0.1",
+            "external_host": "web-platform.test",
             "bind_hostname": "true",
             "testharnessreport": "testharnessreport-servodriver.js",
             "supports_debugger": True}
 
 
 def make_hosts_file():
     hosts_fd, hosts_path = tempfile.mkstemp()
     with os.fdopen(hosts_fd, "w") as f:
--- a/testing/web-platform/harness/wptrunner/executors/executorservodriver.py
+++ b/testing/web-platform/harness/wptrunner/executors/executorservodriver.py
@@ -31,17 +31,18 @@ class ServoWebDriverProtocol(Protocol):
         self.session = None
 
     def setup(self, runner):
         """Connect to browser via WebDriver."""
         self.runner = runner
 
         session_started = False
         try:
-            self.session = webdriver.Session(self.host, self.port)
+            self.session = webdriver.Session(self.host, self.port,
+                                             extension=webdriver.ServoExtensions)
             self.session.start()
         except:
             self.logger.warning(
                 "Connecting with WebDriver failed:\n%s" % traceback.format_exc())
         else:
             self.logger.debug("session started")
             session_started = True
 
@@ -77,16 +78,21 @@ class ServoWebDriverProtocol(Protocol):
             except webdriver.TimeoutException:
                 pass
             except (socket.timeout, IOError):
                 break
             except Exception as e:
                 self.logger.error(traceback.format_exc(e))
                 break
 
+    def on_environment_change(self, old_environment, new_environment):
+        #Unset all the old prefs
+        self.session.extension.reset_prefs(*old_environment.get("prefs", {}).keys())
+        self.session.extension.set_prefs(new_environment.get("prefs", {}))
+
 
 class ServoWebDriverRun(object):
     def __init__(self, func, session, url, timeout, current_timeout=None):
         self.func = func
         self.result = None
         self.session = session
         self.url = url
         self.timeout = timeout
--- a/testing/web-platform/harness/wptrunner/executors/webdriver.py
+++ b/testing/web-platform/harness/wptrunner/executors/webdriver.py
@@ -341,46 +341,52 @@ class Find(object):
             rv = [self.session._element(item) for item in data]
         else:
             rv = self.session._element(data)
 
         return rv
 
 
 class Session(object):
-    def __init__(self, host, port, url_prefix="", desired_capabilities=None, port_timeout=60):
+    def __init__(self, host, port, url_prefix="", desired_capabilities=None, port_timeout=60,
+                 extension=None):
         self.transport = Transport(host, port, url_prefix, port_timeout)
         self.desired_capabilities = desired_capabilities
         self.session_id = None
         self.timeouts = None
         self.window = None
         self.find = None
         self._element_cache = {}
+        self.extension = None
+        self.extension_cls = extension
 
     def start(self):
         desired_capabilities = self.desired_capabilities if self.desired_capabilities else {}
         body = {"capabilities": {"desiredCapabilites": desired_capabilities}}
 
         rv = self.transport.send("POST", "session", body=body)
         self.session_id = rv["sessionId"]
 
         self.timeouts = Timeouts(self)
         self.window = Window(self)
         self.find = Find(self)
+        if self.extension_cls:
+            self.extension = self.extension_cls(self)
 
         return rv["value"]
 
     @command
     def end(self):
         url = "session/%s" % self.session_id
         self.transport.send("DELETE", url)
         self.session_id = None
         self.timeouts = None
         self.window = None
         self.find = None
+        self.extension = None
         self.transport.close_connection()
 
     def __enter__(self):
         resp = self.start()
         if resp.error:
             raise Exception(resp)
         return self
 
@@ -574,14 +580,42 @@ class Element(object):
 
         body = {"value": keys}
 
         return self.session.send_command("POST", self.url("value"), body)
 
     @property
     @command
     def text(self):
-        return self.session.send_command("GET", self.url("text"), key="value")
+        return self.session.send_command("GET", self.url("text"))
 
     @property
     @command
     def name(self):
-        return self.session.send_command("GET", self.url("name"), key="value")
+        return self.session.send_command("GET", self.url("name"))
+
+    @command
+    def css(self, property_name):
+        return self.session.send_command("GET", self.url("css/%s" % property_name))
+
+    @property
+    @command
+    def rect(self):
+        return self.session.send_command("GET", self.url("rect"))
+
+class ServoExtensions(object):
+    def __init__(self, session):
+        self.session = session
+
+    @command
+    def get_prefs(self, *prefs):
+        body = {"prefs": list(prefs)}
+        return self.session.send_command("POST", "servo/prefs/get", body)
+
+    @command
+    def set_prefs(self, prefs):
+        body = {"prefs": prefs}
+        return self.session.send_command("POST", "servo/prefs/set", body)
+
+    @command
+    def reset_prefs(self, *prefs):
+        body = {"prefs": list(prefs)}
+        return self.session.send_command("POST", "servo/prefs/reset", body)
--- a/testing/web-platform/harness/wptrunner/update/tree.py
+++ b/testing/web-platform/harness/wptrunner/update/tree.py
@@ -269,16 +269,18 @@ class GitTree(object):
 
     def checkout(self, rev, branch=None, force=False):
         """Checkout a particular revision, optionally into a named branch.
 
         :param rev: Revision identifier (e.g. SHA1) to checkout
         :param branch: Branch name to use
         :param force: Force-checkout
         """
+        assert rev is not None
+
         args = []
         if branch:
             branches = [ref[len("refs/heads/"):] for sha1, ref in self.list_refs()
                         if ref.startswith("refs/heads/")]
             branch = get_unique_name(branches, branch)
 
             args += ["-b", branch]
 
--- a/testing/web-platform/meta/content-security-policy/child-src/child-src-cross-origin-load.sub.html.ini
+++ b/testing/web-platform/meta/content-security-policy/child-src/child-src-cross-origin-load.sub.html.ini
@@ -1,10 +1,12 @@
 [child-src-cross-origin-load.sub.html]
   type: testharness
+  disabled:
+    if e10s: https://bugzilla.mozilla.org/show_bug.cgi?id=1209756
   [Expecting alerts: ["PASS","PASS"\]]
     expected:
       if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): FAIL
       if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): FAIL
       if not debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): FAIL
       if debug and not e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): FAIL
       if not debug and e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86_64") and (bits == 64): FAIL
 
deleted file mode 100644
--- a/testing/web-platform/meta/content-security-policy/media-src/media-src-7_2.html.ini
+++ /dev/null
@@ -1,10 +0,0 @@
-[media-src-7_2.html]
-  type: testharness
-  [In-policy audio src]
-    expected:
-      if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): FAIL
-
-  [In-policy audio source element]
-    expected:
-      if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): FAIL
-
--- a/testing/web-platform/meta/content-security-policy/media-src/media-src-7_3.html.ini
+++ b/testing/web-platform/meta/content-security-policy/media-src/media-src-7_3.html.ini
@@ -1,12 +1,10 @@
 [media-src-7_3.html]
   type: testharness
   expected:
     if not debug and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
     if debug and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
   [In-policy track element]
     expected:
       if not debug and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): NOTRUN
       if debug and (os == "win") and (version == "5.1.2600") and (processor == "x86") and (bits == 32): NOTRUN
-      if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): NOTRUN
 
--- a/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html.ini
+++ b/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/document.open-02.html.ini
@@ -1,5 +1,4 @@
 [document.open-02.html]
   type: testharness
-  expected:
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): TIMEOUT
-    if debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
+  disabled:
+    if e10s and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1210716
--- a/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html.ini
+++ b/testing/web-platform/meta/html/dom/dynamic-markup-insertion/opening-the-input-stream/document.open-03.html.ini
@@ -1,7 +1,7 @@
 [document.open-03.html]
   type: testharness
+  disabled:
+    if e10s and (os == "linux"): https://bugzilla.mozilla.org/show_bug.cgi?id=1210716
   expected:
-    if not debug and e10s and (os == "mac") and (version == "OS X 10.10.2") and (processor == "x86") and (bits == 32): TIMEOUT
+    if e10s and (os == "mac") and (version == "OS X 10.10.2"): TIMEOUT
     if not debug and e10s and (os == "win") and (version == "6.1.7601") and (processor == "x86") and (bits == 32): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86_64") and (bits == 64): TIMEOUT
-    if not debug and e10s and (os == "linux") and (version == "Ubuntu 12.04") and (processor == "x86") and (bits == 32): TIMEOUT
--- a/testing/web-platform/meta/webstorage/event_setattribute.html.ini
+++ b/testing/web-platform/meta/webstorage/event_setattribute.html.ini
@@ -1,5 +1,7 @@
 [event_setattribute.html]
   type: testharness
+  disabled:
+    if e10s and os == "linux": https://bugzilla.mozilla.org/show_bug.cgi?id=1210717
   [localStorage mutations fire StorageEvents that are caught by the event listener attached via setattribute.]
     expected: FAIL
 
index 92e0019d6c7e9ce8cb479628fd1332ddd9d8406f..4b5ca3c1aca0464554485690c0736a6befe6d644
GIT binary patch
literal 2134
zc$^FHW@Zs#U|`^2INy=$rZnI3@+u|<hNr9y3_J`n3`zO<CB-F0i3NID#i1db49soS
z%%L4VD?&>vxEUB(UNAE-fQjBI`T4gD1diQ*9z17)+ES6EUDH$Nx?L_?Wx~}Iwr9nv
zQx<AFir<{5cgQ_je&3QMD#QGn+N8I?f6CdHi(hJ)SH-sMbh`4Voy>`fk7p)a`N%Ve
z&HTB+mtakam={f4Q(YJg%GcSbwn+NTjVM^w?W8?BWAfxpT<2Crd^*&trtaD9Fxf>=
z(O{XYrsbI}jVmvDpFW$ce|pQTfaA~ZsQ8<8N!=^jaE{}d)Lo6Fip|a8?_0JkpYTh}
zz;o@>#Dh@^Qy!<eMQ#<-wrCC!Um6x;xqE3o`{CDLKK{uonauazV9CQBrMXf@`qyXP
z4$cp`{i<nu<V(Z%?ga*xKChmNy^ZfXzIXm~_6H4fxK!^<+b#XKYLC=c0e|=Fhi^A*
z+<EZz#SgM3F>VtIe}otrZ+S3HGSJGz@W-@YE(-*Y9pyc|_FG%4bkSkYg~s|jRg?3?
zn0lQTWNg`R<&a$I|K{CGzkSye_I#%>y~-<fzr5FhchdG;f0dRj2@PL!K49TeOZ^px
z*1w!*BDZ1Yq&d5zJm++|+<g7d>do{GKTPs|z5Xzv&1ANqNn%XVRL2c6a}M-xcY4~e
z^ysrAZnG<o&bh$llv-eJ*A(s}V$v0p**v$F?K}V7s`FM^GVfv%&Z~RrKVE#c^T1ud
z2%YClyUpsl8_IfXn<x7%-|WXQ`}O;y9}R07*Yls~KCph#)%c^_tP5@JrrOQ8a_jTw
zx4XK_L(B5F8FYRMT4Wo&>(aB+QunS!`sX(3n#_1~M|VeOc74?D&lj5{r%nsklW}05
zeC=Mxo`a61cLf{oXlMO7ADQ8pSZwh-?)I;LD=Qk3=2;Y*-RxMQv+hftv6t`W)ds?k
zv(z5#U0F4GXI1{~JG*`Kucq&ljC;XqpK#axyxmc|TieuK9`rYOMZP}p|5@)b&%<)Z
z?TeO`JMFcS_`@CmPw&aS*=`<c4!dj_85nYaX`UOH-ZS%xOA>Q(^oml_z{y?eAw%dl
z%;a7=>9pTr0|D3XKe<ldRaEt3=9W^`o2q5k@LXtT=WVG!9d9%>Yi#>IXK*W7pP%!1
zXY$erxv4Jm?^q?4bUyd&61+ZjW$V4EKYqTn+jrDk%Jt~sUviZpyM^8}&YCk}Tkhm<
zR)%Hn&$^E`Es;FA>BhC&H)=Bzjz{d4><jqLYWV!sq0nd19w!g6M$Wn?u+FNwZRPYX
z)AFgJygwp3a?db&)oiemUlNnS?sF=_P<PuM#|e9mh3aLUdUfl?G{>#a&DiemlWq@n
z6J64-mUv^{ucyxWHvS9-yB}}JlG4;-3x2KiBtS~I!SIvwoM*pO^`CuPAoMF$M($R6
zYjKb4@mHb!bGe*_KB;>#{P(;UyUUPGCgNJa26nj@!J4xS&WbGW_c5Kcc<siFWZ&02
zYNJd2|J<@K{PeC#{)XRQ(d7#N-Y>k!kB9-^kX*MAxj8!&fjng(<_E?=az;^pZmM2x
zVqRuiYH<lT78c1fV~YjvbLX`^&uRyH_;{W?ukEkndCo)klCGz&UZ6+Yi5JC;3?Q4h
z7(n1z_XjryB)|c(DL=0yHLpY;Y{hw?@gFg*K)4lTg4degO$NZ!asY_MkW5G`O3Y0y
zPA<wUD1nueAoFHnnitydn0?4Wp!NN4ZMjA#5nq=VuCEtHaIr3R;$GU(el~4VPUr4T
z)|ne7{lDjXb(-9?#QS@`pY^dilD*V6F6XgGVrKE7#ii9OXU%W2YqCWbxF!@;^GE-w
zx%SQb`qm@MPG+rTX^Y5oJ$9L6KIgKNDhGoZIaB$M-#?Sq8dfE7`oz}s<(u0y-B<7A
zVtZTdv+(tSitW5pOg2b*%XTSxxE-yTvGnCX8|#hhe3*@SFJEFg9MZAkV~gAxi&@-(
zCT<(Iu|+;UQ}p?d7=M9~gRe}?B}1`_2|k@0b8TkCTkSHr*1A>f#=EMuYY%yK{O^&7
zzf`_suFpA-nB?2uzF9`8J6^M0j&=AOUY(<ErD!wr&pr9xsL9_qEwM=YWHYTY&sRG#
z^vSVT3AbmO%~~wz7h7>Xd)J0f<^kS}OmfV)>L&?sTmmD5VM!y1g<4y&LTW3F7)6+f
zSpp!N_yH(_QXa5E$^$$GVkT&01B+M~Fq1bcBza>s6j$1nfU*P_-a6g^846FytdNw4
g$BCHf7}<$(tPD6)H7>JR*+BYPfp8Jfka=Jp05<4I<NyEw
--- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
@@ -283,10 +283,10 @@ add_task(function* test() {
       break;
     } else {
       info(`Not enough CPU time detected: ${parent.jank.totalUserTime}`);
     }
   }
   isShuttingDown = true;
 
   // Cleanup
-  gBrowser.removeTab(newTab);
+  gBrowser.removeTab(newTab, {skipPermitUnload: true});
 });
--- a/toolkit/components/startup/tests/browser/browser.ini
+++ b/toolkit/components/startup/tests/browser/browser.ini
@@ -1,10 +1,8 @@
 [DEFAULT]
 support-files =
   head.js
   beforeunload.html
 
 [browser_bug511456.js]
-skip-if = e10s # Bug ?????? - test touches content (uses a WindowWatcher in the parent process to try and observe content created alerts etc)
 [browser_bug537449.js]
-skip-if = e10s # Bug ?????? - test touches content (uses a WindowWatcher in the parent process to try and observe content created alerts etc)
 [browser_crash_detection.js]
--- a/toolkit/components/startup/tests/browser/head.js
+++ b/toolkit/components/startup/tests/browser/head.js
@@ -10,17 +10,23 @@ function whenBrowserLoaded(browser, call
     if (event.target == browser.contentDocument) {
       browser.removeEventListener("load", onLoad, true);
       executeSoon(callback);
     }
   }, true);
 }
 
 function waitForOnBeforeUnloadDialog(browser, callback) {
-  browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog() {
+  browser.addEventListener("DOMWillOpenModalDialog", function onModalDialog(event) {
+    if (Cu.isCrossProcessWrapper(event.target)) {
+      // This event fires in both the content and chrome processes. We
+      // want to ignore the one in the content process.
+      return;
+    }
+
     browser.removeEventListener("DOMWillOpenModalDialog", onModalDialog, true);
 
     executeSoon(() => {
       let stack = browser.parentNode;
       let dialogs = stack.getElementsByTagNameNS(XUL_NS, "tabmodalprompt");
       let {button0, button1} = dialogs[0].ui;
       callback(button0, button1);
     });
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -572,16 +572,32 @@ var AutoCompletePopup = {
   selectBy: function(reverse, page) {
     this._index = sendSyncMessage("FormAutoComplete:SelectBy", {
       reverse: reverse,
       page: page
     });
   }
 }
 
+addMessageListener("InPermitUnload", msg => {
+  let inPermitUnload = docShell.contentViewer && docShell.contentViewer.inPermitUnload;
+  sendAsyncMessage("InPermitUnload", {id: msg.data.id, inPermitUnload});
+});
+
+addMessageListener("PermitUnload", msg => {
+  sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "start"});
+
+  let permitUnload = true;
+  if (docShell && docShell.contentViewer) {
+    permitUnload = docShell.contentViewer.permitUnload();
+  }
+
+  sendAsyncMessage("PermitUnload", {id: msg.data.id, kind: "end", permitUnload});
+});
+
 // We may not get any responses to Browser:Init if the browser element
 // is torn down too quickly.
 var outerWindowID = content.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils)
                            .outerWindowID;
 var initData = sendSyncMessage("Browser:Init", {outerWindowID: outerWindowID});
 if (initData.length) {
   docShell.useGlobalHistory = initData[0].useGlobalHistory;
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -1068,18 +1068,18 @@
             window.addEventListener("mousemove", this, true);
             window.addEventListener("mousedown", this, true);
             window.addEventListener("mouseup", this, true);
             window.addEventListener("contextmenu", this, true);
             window.addEventListener("keydown", this, true);
             window.addEventListener("keypress", this, true);
             window.addEventListener("keyup", this, true);
          ]]>
-       </body>
-     </method>
+        </body>
+      </method>
 
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body>
         <![CDATA[
           if (this._scrolling) {
             switch(aEvent.type) {
               case "mousemove": {
@@ -1222,16 +1222,40 @@
               this._remoteFinder.swapBrowser(this);
             if (aOtherBrowser._remoteFinder)
               aOtherBrowser._remoteFinder.swapBrowser(aOtherBrowser);
           }
         ]]>
         </body>
       </method>
 
+      <method name="getInPermitUnload">
+        <parameter name="aCallback"/>
+        <body>
+        <![CDATA[
+          if (!this.docShell || !this.docShell.contentViewer) {
+            aCallback(false);
+            return;
+          }
+          aCallback(this.docShell.contentViewer.inPermitUnload);
+        ]]>
+        </body>
+      </method>
+
+      <method name="permitUnload">
+        <body>
+        <![CDATA[
+          if (!this.docShell || !this.docShell.contentViewer) {
+            return true;
+          }
+          return {permitUnload: this.docShell.contentViewer.permitUnload(), timedOut: false};
+        ]]>
+        </body>
+      </method>
+
       <!-- This will go away if the binding has been removed for some reason. -->
       <field name="_alive">true</field>
     </implementation>
 
     <handlers>
       <handler event="keypress" keycode="VK_F7" group="system">
         <![CDATA[
           if (event.defaultPrevented || !event.isTrusted)
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -229,16 +229,99 @@
             frameLoader.tabParent.docShellIsActive = val;
             return val;
           ]]>
         </setter>
       </property>
 
       <field name="mDestroyed">false</field>
 
+      <field name="_permitUnloadId">0</field>
+
+      <method name="getInPermitUnload">
+        <parameter name="aCallback"/>
+        <body>
+        <![CDATA[
+          let id = this._permitUnloadId++;
+          let mm = this.messageManager;
+          mm.sendAsyncMessage("InPermitUnload", {id});
+          mm.addMessageListener("InPermitUnload", function listener(msg) {
+            if (msg.data.id != id) {
+              return;
+            }
+	    aCallback(msg.data.inPermitUnload);
+          });
+        ]]>
+        </body>
+      </method>
+
+      <method name="permitUnload">
+        <body>
+        <![CDATA[
+          const Cc = Components.classes;
+          const Ci = Components.interfaces;
+
+          const kTimeout = 5000;
+
+          let finished = false;
+          let responded = false;
+          let permitUnload;
+          let id = this._permitUnloadId++;
+          let mm = this.messageManager;
+
+          let msgListener = msg => {
+            if (msg.data.id != id) {
+              return;
+            }
+            if (msg.data.kind == "start") {
+              responded = true;
+              return;
+            }
+            done(msg.data.permitUnload);
+          };
+
+          let observer = subject => {
+            if (subject == mm) {
+              done(true);
+            }
+          };
+
+          function done(result) {
+            finished = true;
+            permitUnload = result;
+            mm.removeMessageListener("PermitUnload", msgListener);
+            Services.obs.removeObserver(observer, "message-manager-close");
+          }
+
+          mm.sendAsyncMessage("PermitUnload", {id});
+          mm.addMessageListener("PermitUnload", msgListener);
+          Services.obs.addObserver(observer, "message-manager-close", false);
+
+          let timedOut = false;
+          function timeout() {
+            if (!responded) {
+              timedOut = true;
+            }
+
+            // Dispatch something to ensure that the main thread wakes up.
+            Services.tm.mainThread.dispatch(function() {}, Ci.nsIThread.DISPATCH_NORMAL);
+          }
+
+          let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+          timer.initWithCallback(timeout, kTimeout, timer.TYPE_ONE_SHOT);
+
+          while (!finished && !timedOut) {
+            Services.tm.currentThread.processNextEvent(true);
+          }
+
+          return {permitUnload, timedOut};
+        ]]>
+        </body>
+      </method>
+
       <constructor>
         <![CDATA[
           /*
            * Don't try to send messages from this function. The message manager for
            * the <browser> element may not be initialized yet.
            */
 
           let jsm = "resource://gre/modules/RemoteWebNavigation.jsm";
--- a/toolkit/modules/BrowserUtils.jsm
+++ b/toolkit/modules/BrowserUtils.jsm
@@ -396,10 +396,26 @@ this.BrowserUtils = {
     }
 
     if (url && !url.host) {
       url = null;
     }
 
     return { text: selectionStr, docSelectionIsCollapsed: collapsed,
              linkURL: url ? url.spec : null, linkText: url ? linkText : "" };
-  }
+  },
+
+  // Iterates through every docshell in the window and calls PermitUnload.
+  canCloseWindow(window) {
+    let docShell = window.QueryInterface(Ci.nsIInterfaceRequestor)
+                         .getInterface(Ci.nsIWebNavigation);
+    let node = docShell.QueryInterface(Ci.nsIDocShellTreeItem);
+    for (let i = 0; i < node.childCount; ++i) {
+      let docShell = node.getChildAt(i).QueryInterface(Ci.nsIDocShell);
+      let contentViewer = docShell.contentViewer;
+      if (contentViewer && !contentViewer.permitUnload()) {
+        return false;
+      }
+    }
+
+    return true;
+  },
 };
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -169,17 +169,17 @@ var Harness = {
     this.downloadEndedCallback = null;
     this.installStartedCallback = null;
     this.installFailedCallback = null;
     this.installEndedCallback = null;
     this.installsCompletedCallback = null;
     this.runningInstalls = null;
 
     if (callback)
-      callback(count);
+      executeSoon(() => callback(count));
   },
 
   // Window open handling
   windowReady: function(window) {
     if (window.document.location.href == XPINSTALL_URL) {
       if (this.installBlockedCallback)
         ok(false, "Should have been blocked by the whitelist");
       this.pendingCount = window.document.getElementById("itemList").childNodes.length;
--- a/widget/windows/TSFTextStore.cpp
+++ b/widget/windows/TSFTextStore.cpp
@@ -3935,20 +3935,32 @@ TSFTextStore::InsertTextAtSelectionInter
   TS_SELECTION_ACP oldSelection = lockedContent.Selection().ACP();
   if (!mComposition.IsComposing()) {
     // Use a temporary composition to contain the text
     PendingAction* compositionStart = mPendingActions.AppendElement();
     compositionStart->mType = PendingAction::COMPOSITION_START;
     compositionStart->mSelectionStart = oldSelection.acpStart;
     compositionStart->mSelectionLength =
       oldSelection.acpEnd - oldSelection.acpStart;
+    compositionStart->mAdjustSelection = false;
 
     PendingAction* compositionEnd = mPendingActions.AppendElement();
     compositionEnd->mType = PendingAction::COMPOSITION_END;
     compositionEnd->mData = aInsertStr;
+
+    MOZ_LOG(sTextStoreLog, LogLevel::Debug,
+            ("TSF: 0x%p   TSFTextStore::InsertTextAtSelectionInternal() "
+             "appending pending compositionstart and compositionend... "
+             "PendingCompositionStart={ mSelectionStart=%d, "
+             "mSelectionLength=%d }, PendingCompositionEnd={ mData=\"%s\" "
+             "(Length()=%u) }",
+             this, compositionStart->mSelectionStart,
+             compositionStart->mSelectionLength,
+             NS_ConvertUTF16toUTF8(compositionEnd->mData).get(),
+             compositionEnd->mData.Length()));
   }
 
   lockedContent.ReplaceSelectedTextWith(aInsertStr);
 
   if (aTextChange) {
     aTextChange->acpStart = oldSelection.acpStart;
     aTextChange->acpOldEnd = oldSelection.acpEnd;
     aTextChange->acpNewEnd = lockedContent.Selection().EndOffset();
@@ -4023,16 +4035,44 @@ TSFTextStore::RecordCompositionStartActi
   if (!lockedContent.IsInitialized()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
            ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() FAILED "
             "due to LockedContent() failure", this));
     return E_FAIL;
   }
 
   CompleteLastActionIfStillIncomplete();
+
+  // TIP may have inserted text at selection before calling
+  // OnStartComposition().  In this case, we've already created a pair of
+  // pending compositionstart and pending compositionend.  If the pending
+  // compositionstart occurred same range as this composition, it was the
+  // start of this composition.  In such case, we should cancel the pending
+  // compositionend and start composition normally.
+  if (!aPreserveSelection &&
+      WasTextInsertedWithoutCompositionAt(aStart, aLength)) {
+    const PendingAction& pendingCompositionEnd = mPendingActions.LastElement();
+    const PendingAction& pendingCompositionStart =
+      mPendingActions[mPendingActions.Length() - 2];
+    lockedContent.RestoreCommittedComposition(
+      aComposition, pendingCompositionStart, pendingCompositionEnd);
+    mPendingActions.RemoveElementAt(mPendingActions.Length() - 1);
+    MOZ_LOG(sTextStoreLog, LogLevel::Info,
+           ("TSF: 0x%p   TSFTextStore::RecordCompositionStartAction() "
+            "succeeded: restoring the committed string as composing string, "
+            "mComposition={ mStart=%ld, mString.Length()=%ld, "
+            "mSelection={ acpStart=%ld, acpEnd=%ld, style.ase=%s, "
+            "style.fInterimChar=%s } }",
+            this, mComposition.mStart, mComposition.mString.Length(),
+            mSelection.StartOffset(), mSelection.EndOffset(),
+            GetActiveSelEndName(mSelection.ActiveSelEnd()),
+            GetBoolName(mSelection.IsInterimChar())));
+    return S_OK;
+  }
+
   PendingAction* action = mPendingActions.AppendElement();
   action->mType = PendingAction::COMPOSITION_START;
   action->mSelectionStart = aStart;
   action->mSelectionLength = aLength;
 
   Selection& currentSel = CurrentSelection();
   if (currentSel.IsDirty()) {
     MOZ_LOG(sTextStoreLog, LogLevel::Error,
@@ -5539,16 +5579,40 @@ TSFTextStore::Content::StartComposition(
     // XXX Do we need to set a new writing-mode here when setting a new
     // selection? Currently, we just preserve the existing value.
     mSelection.SetSelection(mComposition.mStart, mComposition.mString.Length(),
                             false, mSelection.GetWritingMode());
   }
 }
 
 void
+TSFTextStore::Content::RestoreCommittedComposition(
+                         ITfCompositionView* aCompositionView,
+                         const PendingAction& aPendingCompositionStart,
+                         const PendingAction& aCanceledCompositionEnd)
+{
+  MOZ_ASSERT(mInitialized);
+  MOZ_ASSERT(aCompositionView);
+  MOZ_ASSERT(!mComposition.mView);
+  MOZ_ASSERT(aPendingCompositionStart.mType ==
+               PendingAction::COMPOSITION_START);
+  MOZ_ASSERT(aCanceledCompositionEnd.mType ==
+               PendingAction::COMPOSITION_END);
+  MOZ_ASSERT(GetSubstring(
+               static_cast<uint32_t>(aPendingCompositionStart.mSelectionStart),
+               static_cast<uint32_t>(aCanceledCompositionEnd.mData.Length())) ==
+               aCanceledCompositionEnd.mData);
+
+  // Restore the committed string as composing string.
+  mComposition.Start(aCompositionView,
+                     aPendingCompositionStart.mSelectionStart,
+                     aCanceledCompositionEnd.mData);
+}
+
+void
 TSFTextStore::Content::EndComposition(const PendingAction& aCompEnd)
 {
   MOZ_ASSERT(mInitialized);
   MOZ_ASSERT(mComposition.mView);
   MOZ_ASSERT(aCompEnd.mType == PendingAction::COMPOSITION_END);
 
   mSelection.CollapseAt(mComposition.mStart + aCompEnd.mData.Length());
   mComposition.End();
--- a/widget/windows/TSFTextStore.h
+++ b/widget/windows/TSFTextStore.h
@@ -544,16 +544,42 @@ protected:
     }
     PendingAction* newAction = mPendingActions.AppendElement();
     newAction->mType = PendingAction::COMPOSITION_UPDATE;
     newAction->mRanges = new TextRangeArray();
     newAction->mIncomplete = true;
     return newAction;
   }
 
+  /**
+   * WasTextInsertedWithoutCompositionAt() checks if text was inserted without
+   * composition immediately before (e.g., see InsertTextAtSelectionInternal()).
+   *
+   * @param aStart              The inserted offset you expected.
+   * @param aLength             The inserted text length you expected.
+   * @return                    true if the last pending actions are
+   *                            COMPOSITION_START and COMPOSITION_END and
+   *                            aStart and aLength match their information.
+   */
+  bool WasTextInsertedWithoutCompositionAt(LONG aStart, LONG aLength) const
+  {
+    if (mPendingActions.Length() < 2) {
+      return false;
+    }
+    const PendingAction& pendingLastAction = mPendingActions.LastElement();
+    if (pendingLastAction.mType != PendingAction::COMPOSITION_END ||
+        pendingLastAction.mData.Length() != aLength) {
+      return false;
+    }
+    const PendingAction& pendingPreLastAction =
+      mPendingActions[mPendingActions.Length() - 2];
+    return pendingPreLastAction.mType == PendingAction::COMPOSITION_START &&
+           pendingPreLastAction.mSelectionStart == aStart;
+  }
+
   bool IsPendingCompositionUpdateIncomplete() const
   {
     if (mPendingActions.IsEmpty()) {
       return false;
     }
     const PendingAction& lastAction = mPendingActions.LastElement();
     return lastAction.mType == PendingAction::COMPOSITION_UPDATE &&
            lastAction.mIncomplete;
@@ -637,16 +663,33 @@ protected:
     const nsDependentSubstring GetSubstring(uint32_t aStart,
                                             uint32_t aLength) const;
     void ReplaceSelectedTextWith(const nsAString& aString);
     void ReplaceTextWith(LONG aStart, LONG aLength, const nsAString& aString);
 
     void StartComposition(ITfCompositionView* aCompositionView,
                           const PendingAction& aCompStart,
                           bool aPreserveSelection);
+    /**
+     * RestoreCommittedComposition() restores the committed string as
+     * composing string.  If InsertTextAtSelection() or something is called
+     * before a call of OnStartComposition(), there is a pending
+     * compositionstart and a pending compositionend.  In this case, we
+     * need to cancel the pending compositionend and continue the composition.
+     *
+     * @param aCompositionView          The composition view.
+     * @param aPendingCompositionStart  The pending compositionstart which
+     *                                  started the committed composition.
+     * @param aCanceledCompositionEnd   The pending compositionend which is
+     *                                  canceled for restarting the composition.
+     */
+    void RestoreCommittedComposition(
+                         ITfCompositionView* aCompositionView,
+                         const PendingAction& aPendingCompositionStart,
+                         const PendingAction& aCanceledCompositionEnd);
     void EndComposition(const PendingAction& aCompEnd);
 
     const nsString& Text() const
     {
       MOZ_ASSERT(mInitialized);
       return mText;
     }
     const nsString& LastCompositionString() const