merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 12 Nov 2015 12:00:55 +0100
changeset 272264 3cc3b1968524248450c465c4ea2ee5596ffa65f2
parent 272196 662a3871dc77c9fe85abbaba483c5537ce94100b (current diff)
parent 272263 ed48eac74e39a37bf9b1fc21e1ae16699965b19f (diff)
child 272284 40c1e96693fb0efc7850c3f8e550bf552982d191
child 272296 8921c18fecfc098d8dceec686a05345054e299e0
child 272332 955150f99a9eea6feeed04e3b87d0f0a2d155e42
push id29665
push usercbook@mozilla.com
push dateThu, 12 Nov 2015 11:01:10 +0000
treeherdermozilla-central@3cc3b1968524 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
3cc3b1968524 / 45.0a1 / 20151112030238 / files
nightly linux64
3cc3b1968524 / 45.0a1 / 20151112030238 / files
nightly mac
3cc3b1968524 / 45.0a1 / 20151112030238 / files
nightly win32
3cc3b1968524 / 45.0a1 / 20151112030238 / files
nightly win64
3cc3b1968524 / 45.0a1 / 20151112030238 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/base/nsDocument.cpp
dom/browser-element/mochitest/file_inputmethod.html
dom/security/test/csp/file_worker_redirect.html
dom/security/test/csp/file_worker_redirect.sjs
dom/security/test/csp/test_worker_redirect.html
layout/base/nsDocumentViewer.cpp
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1010,17 +1010,16 @@ pref("osfile.reset_worker_delay", 5000);
 
 // APZC preferences.
 #ifdef MOZ_WIDGET_GONK
 pref("apz.allow_zooming", true);
 #endif
 
 // Gaia relies heavily on scroll events for now, so lets fire them
 // more often than the default value (100).
-pref("apz.asyncscroll.throttle", 40);
 pref("apz.pan_repaint_interval", 16);
 
 // APZ physics settings, tuned by UX designers
 pref("apz.fling_curve_function_x1", "0.41");
 pref("apz.fling_curve_function_y1", "0.0");
 pref("apz.fling_curve_function_x2", "0.80");
 pref("apz.fling_curve_function_y2", "1.0");
 pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5004,16 +5004,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;
 }
@@ -6560,47 +6564,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/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
@@ -1151,35 +1151,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");
               }
@@ -2098,29 +2102,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 */ ||
@@ -2158,16 +2163,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);
 
@@ -2188,33 +2194,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.
@@ -3996,18 +3999,18 @@
             case "keydown":
               this._handleKeyDownEvent(aEvent);
               break;
             case "keypress":
               this._handleKeyPressEventMac(aEvent);
               break;
             case "sizemodechange":
               if (aEvent.target == window) {
-                this.mCurrentBrowser.docShellIsActive =
-                  (window.windowState != window.STATE_MINIMIZED);
+                this.mCurrentBrowser.setDocShellIsActiveAndForeground(
+                  window.windowState != window.STATE_MINIMIZED);
               }
               break;
           }
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage"/>
@@ -4022,23 +4025,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,
@@ -4312,22 +4321,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
@@ -144,17 +144,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/caps/nsNullPrincipal.h
+++ b/caps/nsNullPrincipal.h
@@ -66,12 +66,11 @@ public:
   bool SubsumesInternal(nsIPrincipal* aOther, DocumentDomainConsideration aConsideration) override
   {
     return aOther == this;
   }
 
   bool MayLoadInternal(nsIURI* aURI) override;
 
   nsCOMPtr<nsIURI> mURI;
-  nsCOMPtr<nsIContentSecurityPolicy> mCSP;
 };
 
 #endif // nsNullPrincipal_h__
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -20,16 +20,17 @@
 #include "nsIEffectiveTLDService.h"
 #include "nsIClassInfoImpl.h"
 #include "nsIProtocolHandler.h"
 #include "nsError.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsNetCID.h"
 #include "jswrapper.h"
 
+#include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/HashFunctions.h"
 
 #include "nsIAppsService.h"
 #include "mozIApplication.h"
 
 using namespace mozilla;
@@ -71,17 +72,22 @@ nsPrincipal::InitializeStatics()
 
 nsPrincipal::nsPrincipal()
   : mCodebaseImmutable(false)
   , mDomainImmutable(false)
   , mInitialized(false)
 { }
 
 nsPrincipal::~nsPrincipal()
-{ }
+{ 
+  // let's clear the principal within the csp to avoid a tangling pointer
+  if (mCSP) {
+    static_cast<nsCSPContext*>(mCSP.get())->clearLoadingPrincipal();
+  }
+}
 
 nsresult
 nsPrincipal::Init(nsIURI *aCodebase, const OriginAttributes& aOriginAttributes)
 {
   NS_ENSURE_STATE(!mInitialized);
   NS_ENSURE_ARG(aCodebase);
 
   mInitialized = true;
@@ -399,17 +405,17 @@ nsPrincipal::Read(nsIObjectInputStream* 
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = SetCsp(csp);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // need to link in the CSP context here (link in the URI of the protected
   // resource).
   if (csp) {
-    csp->SetRequestContext(codebase, nullptr, nullptr);
+    csp->SetRequestContext(nullptr, this);
   }
 
   SetDomain(domain);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -663,16 +663,17 @@ SSL_SetCanFalseStartCallback
 SSL_SetNextProtoNego
 SSL_SetPKCS11PinArg
 SSL_SetSockPeerID
 SSL_SetSRTPCiphers
 SSL_SetStapledOCSPResponses
 SSL_SetURL
 SSL_SNISocketConfigHook
 SSL_VersionRangeGet
+SSL_VersionRangeGetDefault
 SSL_VersionRangeGetSupported
 SSL_VersionRangeSet
 SSL_VersionRangeSetDefault
 UTIL_SetForkState
 VFY_Begin
 VFY_CreateContext
 VFY_DestroyContext
 VFY_End
--- a/config/faster/rules.mk
+++ b/config/faster/rules.mk
@@ -122,11 +122,13 @@ ACDEFINES += -DBUILD_FASTER
 
 # ============================================================================
 # Below is a set of additional dependencies and variables used to build things
 # that are not supported by data in moz.build.
 
 # Files to build with the recursive backend and simply copy
 $(TOPOBJDIR)/dist/bin/platform.ini: $(TOPOBJDIR)/toolkit/xre/platform.ini
 
+$(TOPOBJDIR)/toolkit/xre/platform.ini: $(TOPOBJDIR)/config/buildid
+
 # The xpidl target in config/makefiles/xpidl requires the install manifest for
 # dist/idl to have been processed.
 $(TOPOBJDIR)/config/makefiles/xpidl/xpidl: $(TOPOBJDIR)/install-dist_idl
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -5978,30 +5978,42 @@ nsDocShell::SetIsOffScreenBrowser(bool a
 NS_IMETHODIMP
 nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen)
 {
   *aIsOffScreen = mIsOffScreenBrowser;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDocShell::SetIsActiveAndForeground(bool aIsActive)
+{
+  return SetIsActiveInternal(aIsActive, false);
+}
+
+NS_IMETHODIMP
 nsDocShell::SetIsActive(bool aIsActive)
 {
+  return SetIsActiveInternal(aIsActive, true);
+}
+
+nsresult
+nsDocShell::SetIsActiveInternal(bool aIsActive, bool aIsHidden)
+{
   // We disallow setting active on chrome docshells.
   if (mItemType == nsIDocShellTreeItem::typeChrome) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // Keep track ourselves.
   mIsActive = aIsActive;
 
   // Tell the PresShell about it.
   nsCOMPtr<nsIPresShell> pshell = GetPresShell();
   if (pshell) {
-    pshell->SetIsActive(aIsActive);
+    pshell->SetIsActive(aIsActive, aIsHidden);
   }
 
   // Tell the window about it
   if (mScriptGlobal) {
     mScriptGlobal->SetIsBackground(!aIsActive);
     if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) {
       // Update orientation when the top-level browsing context becomes active.
       // We make an exception for apps because they currently rely on
@@ -6025,17 +6037,21 @@ nsDocShell::SetIsActive(bool aIsActive)
   nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
   while (iter.HasMore()) {
     nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
     if (!docshell) {
       continue;
     }
 
     if (!docshell->GetIsBrowserOrApp()) {
-      docshell->SetIsActive(aIsActive);
+      if (aIsHidden) {
+        docshell->SetIsActive(aIsActive);
+      } else {
+        docshell->SetIsActiveAndForeground(aIsActive);
+      }
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetIsActive(bool* aIsActive)
@@ -7784,17 +7800,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);
@@ -10122,17 +10138,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/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -480,16 +480,18 @@ protected:
 
   // overridden from nsDocLoader, this provides more information than the
   // normal OnStateChange with flags STATE_REDIRECTING
   virtual void OnRedirectStateChange(nsIChannel* aOldChannel,
                                      nsIChannel* aNewChannel,
                                      uint32_t aRedirectFlags,
                                      uint32_t aStateFlags) override;
 
+  nsresult SetIsActiveInternal(bool aIsActive, bool aIsHidden);
+
   /**
    * Helper function that determines if channel is an HTTP POST.
    *
    * @param aChannel
    *        The channel to test
    *
    * @return True iff channel is an HTTP post.
    */
--- a/docshell/base/nsIContentViewer.idl
+++ b/docshell/base/nsIContentViewer.idl
@@ -26,66 +26,51 @@ 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(fbd04c99-e149-473f-8a68-44f53d82f98b)]
+[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/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -38,17 +38,17 @@ interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 interface nsIScrollObserver;
 interface nsITabParent;
 
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(b1df6e41-c8dd-45c2-bc18-dd330d986214)]
+[scriptable, builtinclass, uuid(41b1cf17-b37b-4a62-9df8-5f67cfecab3f)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -624,16 +624,22 @@ interface nsIDocShell : nsIDocShellTreeI
   /**
    * Sets whether a docshell is active. An active docshell is one that is
    * visible, and thus is not a good candidate for certain optimizations
    * like image frame discarding. Docshells are active unless told otherwise.
    */
   attribute boolean isActive;
 
   /**
+   * Sets whether a docshell is active, as above, but ensuring it does
+   * not discard its layers
+   */
+  void setIsActiveAndForeground(in boolean aIsActive);
+
+  /**
    * Puts the docshell in prerendering mode. noscript because we want only
    * native code to be able to put a docshell in prerendering.
    */
   [noscript] void SetIsPrerendered(in boolean prerendered);
   [infallible] readonly attribute boolean isPrerendered;
 
   /**
    * The ID of the docshell in the session history.
--- a/docshell/test/browser/browser.ini
+++ b/docshell/test/browser/browser.ini
@@ -78,15 +78,14 @@ skip-if = e10s # Bug 1220927 - Test trie
 [browser_bug673467.js]
 [browser_bug852909.js]
 [browser_bug92473.js]
 [browser_uriFixupIntegration.js]
 [browser_loadDisallowInherit.js]
 [browser_loadURI.js]
 [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_bug134911.js
+++ b/docshell/test/browser/browser_bug134911.js
@@ -1,38 +1,41 @@
-/* The test text decoded correctly as Shift_JIS */
-const rightText="\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059";
+const TEXT = {
+  /* The test text decoded correctly as Shift_JIS */
+  rightText: "\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059",
 
-const enteredText1="The quick brown fox jumps over the lazy dog";
-const enteredText2="\u03BE\u03B5\u03C3\u03BA\u03B5\u03C0\u03AC\u03B6\u03C9\u0020\u03C4\u1F74\u03BD\u0020\u03C8\u03C5\u03C7\u03BF\u03C6\u03B8\u03CC\u03C1\u03B1\u0020\u03B2\u03B4\u03B5\u03BB\u03C5\u03B3\u03BC\u03AF\u03B1";
+  enteredText1: "The quick brown fox jumps over the lazy dog",
+  enteredText2: "\u03BE\u03B5\u03C3\u03BA\u03B5\u03C0\u03AC\u03B6\u03C9\u0020\u03C4\u1F74\u03BD\u0020\u03C8\u03C5\u03C7\u03BF\u03C6\u03B8\u03CC\u03C1\u03B1\u0020\u03B2\u03B4\u03B5\u03BB\u03C5\u03B3\u03BC\u03AF\u03B1",
+};
 
 function test() {
   waitForExplicitFinish();
 
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
   gBrowser.selectedTab = gBrowser.addTab(rootDir + "test-form_sjis.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
 }
 
 function afterOpen() {
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset);
 
-  gBrowser.contentDocument.getElementById("testtextarea").value = enteredText1;
-  gBrowser.contentDocument.getElementById("testinput").value = enteredText2;
+  ContentTask.spawn(gBrowser.selectedBrowser, TEXT, function(TEXT) {
+    content.document.getElementById("testtextarea").value = TEXT.enteredText1;
+    content.document.getElementById("testinput").value = TEXT.enteredText2;
+  }).then(() => {
+    /* Force the page encoding to Shift_JIS */
+    BrowserSetForcedCharacterSet("Shift_JIS");
+  });
+}
 
-  /* Force the page encoding to Shift_JIS */
-  BrowserSetForcedCharacterSet("Shift_JIS");
-}
-  
 function afterChangeCharset() {
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.getElementById("testpar").innerHTML, rightText,
-     "encoding successfully changed");
-  is(gBrowser.contentDocument.getElementById("testtextarea").value, enteredText1,
-     "text preserved in <textarea>");
-  is(gBrowser.contentDocument.getElementById("testinput").value, enteredText2,
-     "text preserved in <input>");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  ContentTask.spawn(gBrowser.selectedBrowser, TEXT, function(TEXT) {
+    is(content.document.getElementById("testpar").innerHTML, TEXT.rightText,
+       "encoding successfully changed");
+    is(content.document.getElementById("testtextarea").value, TEXT.enteredText1,
+       "text preserved in <textarea>");
+    is(content.document.getElementById("testinput").value, TEXT.enteredText2,
+       "text preserved in <input>");
+  }).then(() => {
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
--- a/docshell/test/browser/browser_bug234628-1.js
+++ b/docshell/test/browser/browser_bug234628-1.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-1.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-1.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 85, "Child doc should be windows-1252 initially");  
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 85, "Child doc should be windows-1252 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u0402'), 85, "Child doc should decode as windows-1251 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u0402'), 85, "Child doc should decode as windows-1251 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-10.js
+++ b/docshell/test/browser/browser_bug234628-10.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-10.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-10.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 151, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 151, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should be utf-8 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should be utf-8 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 151, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should decode as utf-8 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 151, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 71, "Child doc should decode as utf-8 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-11.js
+++ b/docshell/test/browser/browser_bug234628-11.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-11.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-11.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 193, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 193, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 193, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should decode as utf-8 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 193, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should decode as utf-8 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-2.js
+++ b/docshell/test/browser/browser_bug234628-2.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-2.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-2.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 129, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u00E2\u201A\u00AC'), 78, "Child doc should be windows-1252 initially");  
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u00E2\u201A\u00AC'), 78, "Child doc should be windows-1252 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 78, "Child doc should decode as windows-1251 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 129, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 78, "Child doc should decode as windows-1251 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-3.js
+++ b/docshell/test/browser/browser_bug234628-3.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-3.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-3.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 118, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 118, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 73, "Child doc should be utf-8 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 73, "Child doc should be utf-8 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 118, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 73, "Child doc should decode as windows-1251 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 118, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 73, "Child doc should decode as windows-1251 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-4.js
+++ b/docshell/test/browser/browser_bug234628-4.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-4.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-4.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 132, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 132, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should be utf-8 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should be utf-8 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 132, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should decode as utf-8 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 132, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 79, "Child doc should decode as utf-8 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "UTF-8", "Child doc should report UTF-8 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-5.js
+++ b/docshell/test/browser/browser_bug234628-5.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-5.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-5.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 146, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 146, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should be utf-16 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should be utf-16 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 146, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should decode as utf-16 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 146, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 87, "Child doc should decode as utf-16 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "UTF-16LE", "Child doc should report UTF-16LE subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "UTF-16LE", "Child doc should report UTF-16LE subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-6.js
+++ b/docshell/test/browser/browser_bug234628-6.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-6.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-6.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 190, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 190, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should be utf-16 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should be utf-16 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 190, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should decode as utf-16 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 190, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 109, "Child doc should decode as utf-16 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "UTF-16BE", "Child doc should report UTF-16 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "UTF-16BE", "Child doc should report UTF-16 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-7.js
+++ b/docshell/test/browser/browser_bug234628-7.js
@@ -1,39 +1,18 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-7.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-7.html", afterOpen, "windows-1251", afterChangeCharset);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 188, "Parent doc should be windows-1252 initially");
 
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
-
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 188, "Parent doc should be windows-1252 initially");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially");
-
-  BrowserSetForcedCharacterSet("windows-1251");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 107, "Child doc should be utf-8 initially");
 }
-  
-function afterChangeCharset(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 188, "Parent doc should decode as windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 107, "Child doc should decode as windows-1251 subsequently");  
+function afterChangeCharset() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 188, "Parent doc should decode as windows-1251 subsequently");
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u0432\u201A\u00AC'), 107, "Child doc should decode as windows-1251 subsequently");
 
-  is(gBrowser.contentDocument.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.document.characterSet, "windows-1251", "Parent doc should report windows-1251 subsequently");
+  is(content.frames[0].document.characterSet, "windows-1251", "Child doc should report windows-1251 subsequently");
 }
--- a/docshell/test/browser/browser_bug234628-8.js
+++ b/docshell/test/browser/browser_bug234628-8.js
@@ -1,23 +1,11 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-8.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-8.html", afterOpen);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u0402'), 156, "Parent doc should be windows-1251");
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u0402'), 156, "Parent doc should be windows-1251");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u0402'), 99, "Child doc should be windows-1251");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u0402'), 99, "Child doc should be windows-1251");
 }
 
--- a/docshell/test/browser/browser_bug234628-9.js
+++ b/docshell/test/browser/browser_bug234628-9.js
@@ -1,23 +1,11 @@
 function test() {
-  waitForExplicitFinish();
-
   var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
-  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug234628-9.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  runCharsetTest(rootDir + "file_bug234628-9.html", afterOpen);
 }
 
-function afterOpen(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
+function afterOpen() {
+  is(content.document.documentElement.textContent.indexOf('\u20AC'), 145, "Parent doc should be UTF-16");
 
-  is(gBrowser.contentDocument.documentElement.textContent.indexOf('\u20AC'), 145, "Parent doc should be UTF-16");
-
-  is(gBrowser.contentDocument.getElementsByTagName("iframe")[0].contentDocument.documentElement.textContent.indexOf('\u20AC'), 96, "Child doc should be windows-1252");
-
-  gBrowser.removeCurrentTab();
-  finish();
+  is(content.frames[0].document.documentElement.textContent.indexOf('\u20AC'), 96, "Child doc should be windows-1252");
 }
 
--- a/docshell/test/browser/browser_bug673467.js
+++ b/docshell/test/browser/browser_bug673467.js
@@ -17,33 +17,35 @@ var doc = "data:text/html,<html><body on
  "</body></html>"
 
 function test() {
   waitForExplicitFinish();
 
   let tab = gBrowser.addTab(doc);
   let tabBrowser = tab.linkedBrowser;
 
-  tabBrowser.addEventListener('load', function(aEvent) {
-    tabBrowser.removeEventListener('load', arguments.callee, true);
+  BrowserTestUtils.browserLoaded(tab.linkedBrowser).then(() => {
+    return ContentTask.spawn(tab.linkedBrowser, null, () => {
+      return new Promise(resolve => {
+        // The main page has loaded.  Now wait for the iframe to load.
+        let iframe = content.document.getElementById('iframe');
+        iframe.addEventListener('load', function listener(aEvent) {
 
-    // The main page has loaded.  Now wait for the iframe to load.
-    let iframe = tabBrowser.contentWindow.document.getElementById('iframe');
-    iframe.addEventListener('load', function(aEvent) {
-
-      // Wait for the iframe to load the new document, not about:blank.
-      if (!iframe.src)
-        return;
+          // Wait for the iframe to load the new document, not about:blank.
+          if (!iframe.src)
+            return;
 
-      iframe.removeEventListener('load', arguments.callee, true);
-      let shistory = tabBrowser.contentWindow
-                      .QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebNavigation)
-                      .sessionHistory;
+          iframe.removeEventListener('load', listener, true);
+          let shistory = content
+                          .QueryInterface(Ci.nsIInterfaceRequestor)
+                          .getInterface(Ci.nsIWebNavigation)
+                          .sessionHistory;
 
-      is(shistory.count, 1, 'shistory count should be 1.');
-
-      gBrowser.removeTab(tab);
-      finish();
-
-    }, true);
-  }, true);
+          is(shistory.count, 1, 'shistory count should be 1.');
+          resolve();
+        }, true);
+      });
+    });
+  }).then(() => {
+    gBrowser.removeTab(tab);
+    finish();
+  });
 }
--- a/docshell/test/browser/browser_bug852909.js
+++ b/docshell/test/browser/browser_bug852909.js
@@ -1,33 +1,23 @@
 var rootDir = "http://mochi.test:8888/browser/docshell/test/browser/";
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug852909.png");
-  gBrowser.selectedBrowser.addEventListener("load", image, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(image);
 }
 
 function image(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-  gBrowser.selectedBrowser.removeEventListener("load", image, true);
-
   ok(!gBrowser.selectedTab.mayEnableCharacterEncodingMenu, "Docshell should say the menu should be disabled for images.");
 
   gBrowser.removeCurrentTab();
   gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug852909.pdf");
-  gBrowser.selectedBrowser.addEventListener("load", pdf, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(pdf);
 }
 
 function pdf(event) {
-  if (event.target != gBrowser.contentDocument) {
-    return;
-  }
-  gBrowser.selectedBrowser.removeEventListener("load", pdf, true);
-
   ok(!gBrowser.selectedTab.mayEnableCharacterEncodingMenu, "Docshell should say the menu should be disabled for PDF.js.");
 
   gBrowser.removeCurrentTab();
   finish();
 }
--- a/docshell/test/browser/browser_bug92473.js
+++ b/docshell/test/browser/browser_bug92473.js
@@ -1,55 +1,54 @@
 /* The test text as octets for reference
  * %83%86%83%6a%83%52%81%5b%83%68%82%cd%81%41%82%b7%82%d7%82%c4%82%cc%95%b6%8e%9a%82%c9%8c%c5%97%4c%82%cc%94%d4%8d%86%82%f0%95%74%97%5e%82%b5%82%dc%82%b7
  */
 
-/* The test text decoded correctly as Shift_JIS */
-const rightText="\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059";
-
-/* The test text decoded incorrectly as Windows-1251. This is the "right" wrong
-   text; anything else is unexpected. */
-const wrongText="\u0453\u2020\u0453\u006A\u0453\u0052\u0403\u005B\u0453\u0068\u201A\u041D\u0403\u0041\u201A\u00B7\u201A\u0427\u201A\u0414\u201A\u041C\u2022\u00B6\u040B\u0459\u201A\u0419\u040A\u0415\u2014\u004C\u201A\u041C\u201D\u0424\u040C\u2020\u201A\u0440\u2022\u0074\u2014\u005E\u201A\u00B5\u201A\u042C\u201A\u00B7";
-
 function testContent(text) {
-  is(gBrowser.contentDocument.getElementById("testpar").innerHTML, text,
-     "<p> contains expected text");
-  is(gBrowser.contentDocument.getElementById("testtextarea").innerHTML, text,
-     "<textarea> contains expected text");
-  is(gBrowser.contentDocument.getElementById("testinput").value, text,
-     "<input> contains expected text");
+  return ContentTask.spawn(gBrowser.selectedBrowser, text, text => {
+    is(content.document.getElementById("testpar").innerHTML, text,
+       "<p> contains expected text");
+    is(content.document.getElementById("testtextarea").innerHTML, text,
+       "<textarea> contains expected text");
+    is(content.document.getElementById("testinput").value, text,
+       "<input> contains expected text");
+  });
 }
 
 function afterOpen() {
-  gBrowser.selectedBrowser.removeEventListener("load", afterOpen, true);
-  gBrowser.selectedBrowser.addEventListener("load", afterChangeCharset, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset);
+
+  /* The test text decoded incorrectly as Windows-1251. This is the "right" wrong
+   text; anything else is unexpected. */
+  const wrongText="\u0453\u2020\u0453\u006A\u0453\u0052\u0403\u005B\u0453\u0068\u201A\u041D\u0403\u0041\u201A\u00B7\u201A\u0427\u201A\u0414\u201A\u041C\u2022\u00B6\u040B\u0459\u201A\u0419\u040A\u0415\u2014\u004C\u201A\u041C\u201D\u0424\u040C\u2020\u201A\u0440\u2022\u0074\u2014\u005E\u201A\u00B5\u201A\u042C\u201A\u00B7";
 
   /* Test that the content on load is the expected wrong decoding */
-  testContent(wrongText);
+  testContent(wrongText).then(() => {
+    BrowserSetForcedCharacterSet("Shift_JIS");
+  });
+}
 
-  /* Force the page encoding to Shift_JIS */
-  BrowserSetForcedCharacterSet("Shift_JIS");
-}
-  
 function afterChangeCharset() {
-  gBrowser.selectedBrowser.removeEventListener("load", afterChangeCharset, true);
+  /* The test text decoded correctly as Shift_JIS */
+  const rightText="\u30E6\u30CB\u30B3\u30FC\u30C9\u306F\u3001\u3059\u3079\u3066\u306E\u6587\u5B57\u306B\u56FA\u6709\u306E\u756A\u53F7\u3092\u4ED8\u4E0E\u3057\u307E\u3059";
 
   /* test that the content is decoded correctly */
-  testContent(rightText);
-  gBrowser.removeCurrentTab();
-  finish();
+  testContent(rightText).then(() => {
+    gBrowser.removeCurrentTab();
+    finish();
+  });
 }
 
 function test() {
   waitForExplicitFinish();
 
   // Get the local directory. This needs to be a file: URI because chrome: URIs
   // are always UTF-8 (bug 617339) and we are testing decoding from other
   // charsets.
   var jar = getJar(getRootDirectory(gTestPath));
   var dir = jar ?
               extractJarToTmp(jar) :
               getChromeDir(getResolvedURI(gTestPath));
   var rootDir = Services.io.newFileURI(dir).spec;
 
   gBrowser.selectedTab = gBrowser.addTab(rootDir + "test-form_sjis.html");
-  gBrowser.selectedBrowser.addEventListener("load", afterOpen, true);
+  BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterOpen);
 }
--- a/docshell/test/browser/head.js
+++ b/docshell/test/browser/head.js
@@ -55,8 +55,45 @@ function timelineTestOpenUrl(url) {
     linkedBrowser.addEventListener("load", function onload() {
       linkedBrowser.removeEventListener("load", onload, true);
       resolve(tab);
     }, true);
   });
 
   return Promise.all([tabSwitchPromise, loadPromise]).then(([_, tab]) => tab);
 }
+
+/**
+ * Helper function for charset tests. It loads |url| in a new tab,
+ * runs |check1| in a ContentTask when the page is ready, switches the
+ * charset to |charset|, and then runs |check2| in a ContentTask when
+ * the page has finished reloading.
+ *
+ * |charset| and |check2| can be omitted, in which case the test
+ * finishes when |check1| completes.
+ */
+function runCharsetTest(url, check1, charset, check2) {
+  waitForExplicitFinish();
+
+  BrowserTestUtils.openNewForegroundTab(gBrowser, url, true).then(afterOpen);
+
+  function afterOpen() {
+    if (charset) {
+      BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser).then(afterChangeCharset);
+
+      ContentTask.spawn(gBrowser.selectedBrowser, null, check1).then(() => {
+        BrowserSetForcedCharacterSet(charset);
+      });
+    } else {
+      ContentTask.spawn(gBrowser.selectedBrowser, null, check1).then(() => {
+        gBrowser.removeCurrentTab();
+        finish();
+      });
+    }
+  }
+
+  function afterChangeCharset() {
+    ContentTask.spawn(gBrowser.selectedBrowser, null, check2).then(() => {
+      gBrowser.removeCurrentTab();
+      finish();
+    });
+  }
+}
--- a/dom/apps/tests/file_test_widget.js
+++ b/dom/apps/tests/file_test_widget.js
@@ -84,17 +84,16 @@ function testApp(isValidWidget) {
   if (!isValidWidget) {
     return;
   }
 
   [
     "mozbrowsertitlechange",
     "mozbrowseropenwindow",
     "mozbrowserscroll",
-    "mozbrowserasyncscroll"
   ].forEach( function(topic) {
     ifr.addEventListener(topic, function() {
       ok(false, topic + " should be hidden");
     }, false);
   });
 }
 
 function testLimitedBrowserAPI(ifr) {
@@ -168,17 +167,17 @@ function checkIsWidgetScript(testMozbrow
     finish();
   };
   request.onerror = onError;
 
   if (testMozbrowserEvent) {
     var win = content.window.open("about:blank"); /* test mozbrowseropenwindow */
     /*Close new window to avoid mochitest "unable to restore focus" failures.*/
     win.close();
-    content.window.scrollTo(4000, 4000); /* test mozbrowser(async)scroll */
+    content.window.scrollTo(4000, 4000); /* test mozbrowserscroll */
   }
 }
 
 var tests = [
   // Permissions
   function() {
     SpecialPowers.pushPermissions(
       [{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "context": document },
--- a/dom/archivereader/test/helpers.js
+++ b/dom/archivereader/test/helpers.js
@@ -1,37 +1,24 @@
 /**
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-var archiveReaderEnabled = false;
-
-var testGenerator = testSteps();
+var testGenerator;
 
 function runTest()
 {
-  enableArchiveReader();
+  SimpleTest.waitForExplicitFinish();
 
-  SimpleTest.waitForExplicitFinish();
-  testGenerator.next();
+  SpecialPowers.pushPrefEnv({'set': [ ["dom.archivereader.enabled", true] ]}, function() {
+    testGenerator = testSteps();
+    return testGenerator.next();
+  });
 }
 
 function finishTest()
 {
-  resetArchiveReader();
-
-  SimpleTest.executeSoon(function() {
+  SpecialPowers.popPrefEnv(function() {
     testGenerator.close();
     SimpleTest.finish();
   });
 }
-
-function enableArchiveReader()
-{
-  archiveReaderEnabled = SpecialPowers.getBoolPref("dom.archivereader.enabled");
-  SpecialPowers.setBoolPref("dom.archivereader.enabled", true);
-}
-
-function resetArchiveReader()
-{
-  SpecialPowers.setBoolPref("dom.archivereader.enabled", archiveReaderEnabled);
-}
--- a/dom/archivereader/test/test_nonUnicode.html
+++ b/dom/archivereader/test/test_nonUnicode.html
@@ -59,18 +59,18 @@
   {
     var binaryFile = createNonUnicodeData();
 
     try {
       new ArchiveReader(binaryFile, { encoding: "random stuff" });
       ok(false, "Should have thrown for bogus encoding label.");
     } catch (e) {
       ok(e instanceof RangeError, "Expected a RangeError");
-      finishTest();
     }
+    finishTest();
   }
 
   function testSteps()
   {
     test1();
     yield undefined;
   }
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2321,17 +2321,17 @@ nsDOMWindowUtils::GetAsyncPanZoomEnabled
   } else {
     *aResult = gfxPlatform::AsyncPanZoomEnabled();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SetAsyncScrollOffset(nsIDOMNode* aNode,
-                                       int32_t aX, int32_t aY)
+                                       float aX, float aY)
 {
   nsCOMPtr<Element> element = do_QueryInterface(aNode);
   if (!element) {
     return NS_ERROR_INVALID_ARG;
   }
   FrameMetrics::ViewID viewId;
   if (!nsLayoutUtils::FindIDFor(element, &viewId)) {
     return NS_ERROR_UNEXPECTED;
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -169,16 +169,17 @@
 #include "imgIContainer.h"
 #include "nsSVGUtils.h"
 #include "SVGElementFactory.h"
 
 #include "nsRefreshDriver.h"
 
 // FOR CSP (autogenerated by xpidl)
 #include "nsIContentSecurityPolicy.h"
+#include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/nsCSPService.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsHTMLCSSStyleSheet.h"
 #include "SVGAttrAnimationRuleProcessor.h"
 #include "mozilla/dom/DOMImplementation.h"
 #include "mozilla/dom/ShadowRoot.h"
 #include "mozilla/dom/Comment.h"
 #include "nsTextNode.h"
@@ -2581,34 +2582,16 @@ nsDocument::StartDocumentLoad(const char
     nsresult rv = InitCSP(aChannel);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return NS_OK;
 }
 
 void
-CSPErrorQueue::Add(const char* aMessageName)
-{
-  mErrors.AppendElement(aMessageName);
-}
-
-void
-CSPErrorQueue::Flush(nsIDocument* aDocument)
-{
-  for (uint32_t i = 0; i < mErrors.Length(); i++) {
-    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
-        NS_LITERAL_CSTRING("CSP"), aDocument,
-        nsContentUtils::eSECURITY_PROPERTIES,
-        mErrors[i]);
-  }
-  mErrors.Clear();
-}
-
-void
 nsDocument::SendToConsole(nsCOMArray<nsISecurityConsoleMessage>& aMessages)
 {
   for (uint32_t i = 0; i < aMessages.Length(); ++i) {
     nsAutoString messageTag;
     aMessages[i]->GetTag(messageTag);
 
     nsAutoString category;
     aMessages[i]->GetCategory(category);
@@ -2776,17 +2759,17 @@ nsDocument::InitCSP(nsIChannel* aChannel
     return rv;
   }
 
   // used as a "self" identifier for the CSP.
   nsCOMPtr<nsIURI> selfURI;
   aChannel->GetURI(getter_AddRefs(selfURI));
 
   // Store the request context for violation reports
-  csp->SetRequestContext(nullptr, nullptr, aChannel);
+  csp->SetRequestContext(this, nullptr);
 
   // ----- if the doc is an app and we want a default CSP, apply it.
   if (applyAppDefaultCSP) {
     csp->AppendPolicy(appDefaultCSP, false);
   }
 
   // ----- if the doc is an app and specifies a CSP in its manifest, apply it.
   if (applyAppManifestCSP) {
@@ -4595,17 +4578,22 @@ nsDocument::SetScriptGlobalObject(nsIScr
   // Remember the pointer to our window (or lack there of), to avoid
   // having to QI every time it's asked for.
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mScriptGlobalObject);
   mWindow = window;
 
   // Now that we know what our window is, we can flush the CSP errors to the
   // Web Console. We are flushing all messages that occured and were stored
   // in the queue prior to this point.
-  FlushCSPWebConsoleErrorQueue();
+  nsCOMPtr<nsIContentSecurityPolicy> csp;
+  NodePrincipal()->GetCsp(getter_AddRefs(csp));
+  if (csp) {
+    static_cast<nsCSPContext*>(csp.get())->flushConsoleMessages();
+  }
+
   nsCOMPtr<nsIHttpChannelInternal> internalChannel =
     do_QueryInterface(GetChannel());
   if (internalChannel) {
     nsCOMArray<nsISecurityConsoleMessage> messages;
     internalChannel->TakeAllSecurityMessages(messages);
     SendToConsole(messages);
   }
 
--- a/dom/base/nsDocument.h
+++ b/dom/base/nsDocument.h
@@ -656,39 +656,16 @@ protected:
                                nsILoadGroup* aLoadGroup,
                                nsIDocument* aDisplayDocument);
   
   nsClassHashtable<nsURIHashKey, ExternalResource> mMap;
   nsRefPtrHashtable<nsURIHashKey, PendingLoad> mPendingLoads;
   bool mHaveShutDown;
 };
 
-class CSPErrorQueue
-{
-  public:
-    /**
-     * Note this was designed to be passed string literals. If you give it
-     * a dynamically allocated string, it is your responsibility to make sure
-     * it never dies and is properly freed!
-     */
-    void Add(const char* aMessageName);
-    void Flush(nsIDocument* aDocument);
-
-    CSPErrorQueue()
-    {
-    }
-
-    ~CSPErrorQueue()
-    {
-    }
-
-  private:
-    nsAutoTArray<const char*,5> mErrors;
-};
-
 // Base class for our document implementations.
 //
 // Note that this class *implements* nsIDOMXMLDocument, but it's not
 // really an nsIDOMXMLDocument. The reason for implementing
 // nsIDOMXMLDocument on this class is to avoid having to duplicate all
 // its inherited methods on document classes that *are*
 // nsIDOMXMLDocument's. nsDocument's QI should *not* claim to support
 // nsIDOMXMLDocument unless someone writes a real implementation of
@@ -1735,21 +1712,16 @@ private:
 
   void PostUnblockOnloadEvent();
   void DoUnblockOnload();
 
   nsresult CheckFrameOptions();
   bool IsLoopDocument(nsIChannel* aChannel);
   nsresult InitCSP(nsIChannel* aChannel);
 
-  void FlushCSPWebConsoleErrorQueue()
-  {
-    mCSPWebConsoleErrorQueue.Flush(this);
-  }
-
   /**
    * Find the (non-anonymous) content in this document for aFrame. It will
    * be aFrame's content node if that content is in this document and not
    * anonymous. Otherwise, when aFrame is in a subdocument, we use the frame
    * element containing the subdocument containing aFrame, and/or find the
    * nearest non-anonymous ancestor in this document.
    * Returns null if there is no such element.
    */
@@ -1863,18 +1835,16 @@ private:
   mozilla::LayoutDeviceToScreenScale mScaleFloat;
   mozilla::CSSToLayoutDeviceScale mPixelRatio;
   bool mAutoSize, mAllowZoom, mAllowDoubleTapZoom, mValidScaleFloat, mValidMaxScale, mScaleStrEmpty, mWidthStrEmpty;
   mozilla::CSSSize mViewportSize;
 
   nsrefcnt mStackRefCnt;
   bool mNeedsReleaseAfterStackRefCntRelease;
 
-  CSPErrorQueue mCSPWebConsoleErrorQueue;
-
   nsCOMPtr<nsIDocument> mMasterDocument;
   RefPtr<mozilla::dom::ImportManager> mImportManager;
   nsTArray<nsCOMPtr<nsINode> > mSubImportLinks;
 
   // Set to true when the document is possibly controlled by the ServiceWorker.
   // Used to prevent multiple requests to ServiceWorkerManager.
   bool mMaybeServiceWorkerControlled;
 
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -7993,30 +7993,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/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -299,84 +299,9 @@ BrowserElementParent::OpenWindowInProces
   NS_ENSURE_TRUE(docshell, BrowserElementParent::OPEN_WINDOW_IGNORED);
 
   nsCOMPtr<nsIDOMWindow> window = docshell->GetWindow();
   window.forget(aReturnWindow);
 
   return !!*aReturnWindow ? opened : BrowserElementParent::OPEN_WINDOW_CANCELLED;
 }
 
-class DispatchAsyncScrollEventRunnable : public nsRunnable
-{
-public:
-  DispatchAsyncScrollEventRunnable(TabParent* aTabParent,
-                                   const CSSRect& aContentRect,
-                                   const CSSSize& aContentSize)
-    : mTabParent(aTabParent)
-    , mContentRect(aContentRect)
-    , mContentSize(aContentSize)
-  {}
-
-  NS_IMETHOD Run();
-
-private:
-  RefPtr<TabParent> mTabParent;
-  const CSSRect mContentRect;
-  const CSSSize mContentSize;
-};
-
-NS_IMETHODIMP DispatchAsyncScrollEventRunnable::Run()
-{
-  nsCOMPtr<Element> frameElement = mTabParent->GetOwnerElement();
-  NS_ENSURE_STATE(frameElement);
-  nsIDocument *doc = frameElement->OwnerDoc();
-  nsCOMPtr<nsIGlobalObject> globalObject = doc->GetScopeObject();
-  NS_ENSURE_TRUE(globalObject, NS_ERROR_UNEXPECTED);
-
-  // Create the event's detail object.
-  AsyncScrollEventDetail detail;
-  detail.mLeft = mContentRect.x;
-  detail.mTop = mContentRect.y;
-  detail.mWidth = mContentRect.width;
-  detail.mHeight = mContentRect.height;
-  detail.mScrollWidth = mContentRect.width;
-  detail.mScrollHeight = mContentRect.height;
-
-  AutoSafeJSContext cx;
-  JS::Rooted<JSObject*> globalJSObject(cx, globalObject->GetGlobalJSObject());
-  NS_ENSURE_TRUE(globalJSObject, NS_ERROR_UNEXPECTED);
-
-  JSAutoCompartment ac(cx, globalJSObject);
-  JS::Rooted<JS::Value> val(cx);
-
-  if (!ToJSValue(cx, detail, &val)) {
-    MOZ_CRASH("Failed to convert dictionary to JS::Value due to OOM.");
-    return NS_ERROR_FAILURE;
-  }
-
-  nsEventStatus status = nsEventStatus_eIgnore;
-  DispatchCustomDOMEvent(frameElement,
-                         NS_LITERAL_STRING("mozbrowserasyncscroll"),
-                         cx,
-                         val, &status);
-  return NS_OK;
-}
-
-bool
-BrowserElementParent::DispatchAsyncScrollEvent(TabParent* aTabParent,
-                                               const CSSRect& aContentRect,
-                                               const CSSSize& aContentSize)
-{
-  // Do not dispatch a mozbrowserasyncscroll event of a widget to its embedder
-  nsCOMPtr<Element> frameElement = aTabParent->GetOwnerElement();
-  NS_ENSURE_TRUE(frameElement, false);
-  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(frameElement);
-  if (browserFrame && browserFrame->GetReallyIsWidget()) {
-    return true;
-  }
-
-  RefPtr<DispatchAsyncScrollEventRunnable> runnable =
-    new DispatchAsyncScrollEventRunnable(aTabParent, aContentRect,
-                                         aContentSize);
-  return NS_SUCCEEDED(NS_DispatchToMainThread(runnable));
-}
-
 } // namespace mozilla
--- a/dom/browser-element/BrowserElementParent.h
+++ b/dom/browser-element/BrowserElementParent.h
@@ -104,36 +104,16 @@ public:
    */
   static OpenWindowResult
   OpenWindowInProcess(nsIDOMWindow* aOpenerWindow,
                       nsIURI* aURI,
                       const nsAString& aName,
                       const nsACString& aFeatures,
                       nsIDOMWindow** aReturnWindow);
 
-  /**
-   * Fire a mozbrowserasyncscroll CustomEvent on the given TabParent's frame element.
-   * This event's detail is an AsyncScrollEventDetail dictionary.
-   *
-   * @param aContentRect: The portion of the page which is currently visible
-   *                      onscreen in CSS pixels.
-   *
-   * @param aContentSize: The content width/height in CSS pixels.
-   *
-   * aContentRect.top + aContentRect.height may be larger than aContentSize.height.
-   * This indicates that the content is over-scrolled, which occurs when the
-   * page "rubber-bands" after being scrolled all the way to the bottom.
-   * Similarly, aContentRect.left + aContentRect.width may be greater than
-   * contentSize.width, and both left and top may be negative.
-   */
-  static bool
-  DispatchAsyncScrollEvent(dom::TabParent* aTabParent,
-                           const CSSRect& aContentRect,
-                           const CSSSize& aContentSize);
-
 private:
   static OpenWindowResult
   DispatchOpenWindowEvent(dom::Element* aOpenerFrameElement,
                           dom::Element* aPopupFrameElement,
                           const nsAString& aURL,
                           const nsAString& aName,
                           const nsAString& aFeatures);
 };
--- a/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
+++ b/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
@@ -4,16 +4,37 @@
 // Bug 905573 - Add setInputMethodActive to browser elements to allow gaia
 // system set the active IME app.
 'use strict';
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
+// We'll need to get the appId from the current document,
+// it's either SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID when
+// we are not running inside an app (e.g. Firefox Desktop),
+// or the appId of Mochitest app when we are running inside that app
+// (e.g. Emulator).
+var currentAppId = SpecialPowers.wrap(document).nodePrincipal.appId;
+var inApp =
+  currentAppId !== SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID;
+// We will also need the manifest URL and set it on iframes.
+var currentAppManifestURL;
+
+if (inApp) {
+  let appsService = SpecialPowers.Cc["@mozilla.org/AppsService;1"]
+                      .getService(SpecialPowers.Ci.nsIAppsService);
+
+  currentAppManifestURL = appsService.getManifestURLByLocalId(currentAppId);
+};
+
+info('appId=' + currentAppId);
+info('manifestURL=' + currentAppManifestURL);
+
 function setup() {
   let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1']
                 .getService(SpecialPowers.Ci.nsIXULAppInfo);
   if (appInfo.name != 'B2G') {
     SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", window);
   }
 
   SpecialPowers.setBoolPref("dom.mozInputMethod.enabled", true);
@@ -22,120 +43,144 @@ function setup() {
 
 function tearDown() {
   SpecialPowers.setBoolPref("dom.mozInputMethod.enabled", false);
   SpecialPowers.removePermission('input-manage', document);
   SimpleTest.finish();
 }
 
 function runTest() {
-  let path = location.pathname;
-  let imeUrl = location.protocol + '//' + location.host +
-               path.substring(0, path.lastIndexOf('/')) +
-               '/file_inputmethod.html';
-  SpecialPowers.pushPermissions([{
-    type: 'input',
-    allow: true,
-    context: {
-      url: imeUrl,
-      originAttributes: {inBrowser: true}
-    }
-  }], SimpleTest.waitForFocus.bind(SimpleTest, createFrames));
+  createFrames();
 }
 
-var gFrames = [];
+var gInputMethodFrames = [];
 var gInputFrame;
 
 function createFrames() {
   // Create two input method iframes.
   let loadendCount = 0;
   let countLoadend = function() {
-    if (this === gInputFrame) {
-      // The frame script running in the frame where the input is hosted.
-      let appFrameScript = function appFrameScript() {
-        let input = content.document.body.firstElementChild;
-        input.oninput = function() {
-          sendAsyncMessage('test:InputMethod:oninput', {
-            from: 'input',
-            value: this.value
-          });
-        };
+    loadendCount++;
+    if (loadendCount === 3) {
+      setPermissions();
+     }
+   };
+
+   // Create an input field to receive string from input method iframes.
+   gInputFrame = document.createElement('iframe');
+   gInputFrame.setAttribute('mozbrowser', 'true');
+   gInputFrame.src =
+     'data:text/html,<input autofocus value="hello" />' +
+     '<p>This is targetted mozbrowser frame.</p>';
+   document.body.appendChild(gInputFrame);
+   gInputFrame.addEventListener('mozbrowserloadend', countLoadend);
+
+   for (let i = 0; i < 2; i++) {
+    let frame = gInputMethodFrames[i] = document.createElement('iframe');
+    frame.setAttribute('mozbrowser', 'true');
+    if (currentAppManifestURL) {
+      frame.setAttribute('mozapp', currentAppManifestURL);
+    }
+    frame.src = 'file_empty.html#' + i;
+     document.body.appendChild(frame);
+     frame.addEventListener('mozbrowserloadend', countLoadend);
+   }
+ }
 
-        input.onblur = function() {
-          // "Expected" lost of focus since the test is finished.
-          if (input.value === '#0#1hello') {
-            return;
-          }
+function setPermissions() {
+  let permissions = [{
+    type: 'input',
+    allow: true,
+    context: {
+      url: SimpleTest.getTestFileURL('/file_empty.html'),
+      originAttributes: {
+        appId: currentAppId,
+        inBrowser: true
+      }
+    }
+  }];
+
+  if (inApp) {
+    // The current document would also need to be given access for IPC to
+    // recognize our permission (why)?
+    permissions.push({
+      type: 'input', allow: true, context: document });
+  }
 
-          sendAsyncMessage('test:InputMethod:oninput', {
-            from: 'input',
-            error: true,
-            value: 'Unexpected lost of focus on the input frame!'
-          });
-        };
+  SpecialPowers.pushPermissions(permissions,
+    SimpleTest.waitForFocus.bind(SimpleTest, startTest));
+}
 
-        input.focus();
+ function startTest() {
+  // The frame script running in the frame where the input is hosted.
+  let appFrameScript = function appFrameScript() {
+    let input = content.document.body.firstElementChild;
+    input.oninput = function() {
+      sendAsyncMessage('test:InputMethod:oninput', {
+        from: 'input',
+        value: this.value
+      });
+    };
+
+    input.onblur = function() {
+      // "Expected" lost of focus since the test is finished.
+      if (input.value === '#0#1hello') {
+        return;
       }
 
-      // Inject frame script to receive input.
-      let mm = SpecialPowers.getBrowserFrameMessageManager(gInputFrame);
-      mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
-      mm.addMessageListener("test:InputMethod:oninput", next);
-    } else {
-      ok(this.setInputMethodActive, 'Can access setInputMethodActive.');
+      sendAsyncMessage('test:InputMethod:oninput', {
+        from: 'input',
+        error: true,
+        value: 'Unexpected lost of focus on the input frame!'
+      });
+    };
+
+    input.focus();
+  };
 
-      // The frame script running in the input method frames.
+  // Inject frame script to receive input.
+  let mm = SpecialPowers.getBrowserFrameMessageManager(gInputFrame);
+  mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+  mm.addMessageListener("test:InputMethod:oninput", next);
+
+  gInputMethodFrames.forEach((frame) => {
+    ok(frame.setInputMethodActive, 'Can access setInputMethodActive.');
 
-      let appFrameScript = function appFrameScript() {
-        content.addEventListener("message", function(evt) {
+    // The frame script running in the input method frames.
+    let appFrameScript = function appFrameScript() {
+      let im = content.navigator.mozInputMethod;
+      im.oninputcontextchange = function() {
+        let ctx = im.inputcontext;
+        // Report back to parent frame on status of ctx gotten.
+        // (A setTimeout() here to ensure this always happens after
+        //  DOMRequest succeed.)
+        content.setTimeout(() => {
           sendAsyncMessage('test:InputMethod:imFrameMessage', {
             from: 'im',
-            value: evt.data
+            value: content.location.hash + !!ctx
           });
         });
-      }
 
-      // Inject frame script to receive message.
-      let mm = SpecialPowers.getBrowserFrameMessageManager(this);
-      mm.loadFrameScript('data:,(' + appFrameScript.toString() + ')();', false);
-      mm.addMessageListener("test:InputMethod:imFrameMessage", next);
-    }
-
-    loadendCount++;
-    if (loadendCount === 3) {
-      startTest();
-    }
-  };
+        // If there is a context, send out the hash.
+        if (ctx) {
+          ctx.replaceSurroundingText(content.location.hash);
+        }
+      };
+    };
 
-  // Create an input field to receive string from input method iframes.
-  gInputFrame = document.createElement('iframe');
-  gInputFrame.setAttribute('mozbrowser', 'true');
-  gInputFrame.src =
-    'data:text/html,<input autofocus value="hello" />' +
-    '<p>This is targetted mozbrowser frame.</p>';
-  document.body.appendChild(gInputFrame);
-  gInputFrame.addEventListener('mozbrowserloadend', countLoadend);
+    // Inject frame script to receive message.
+    let mm = SpecialPowers.getBrowserFrameMessageManager(frame);
+    mm.loadFrameScript('data:,(' + encodeURIComponent(appFrameScript.toString()) + ')();', false);
+    mm.addMessageListener("test:InputMethod:imFrameMessage", next);
+  });
 
-  for (let i = 0; i < 2; i++) {
-    let frame = gFrames[i] = document.createElement('iframe');
-    gFrames[i].setAttribute('mozbrowser', 'true');
-    // When the input method iframe is activated, it will send the URL
-    // hash to current focused element. We set different hash to each
-    // iframe so that iframes can be differentiated by their hash.
-    frame.src = 'file_inputmethod.html#' + i;
-    document.body.appendChild(frame);
-    frame.addEventListener('mozbrowserloadend', countLoadend);
-  }
-}
+   // Set focus to the input field and wait for input methods' inputting.
+   SpecialPowers.DOMWindowUtils.focus(gInputFrame);
 
-function startTest() {
-  // Set focus to the input field and wait for input methods' inputting.
-  SpecialPowers.DOMWindowUtils.focus(gInputFrame);
-
-  let req0 = gFrames[0].setInputMethodActive(true);
+  let req0 = gInputMethodFrames[0].setInputMethodActive(true);
   req0.onsuccess = function() {
     ok(true, 'setInputMethodActive succeeded (0).');
   };
 
   req0.onerror = function() {
     ok(false, 'setInputMethodActive failed (0): ' + this.error.name);
   };
 }
@@ -198,24 +243,24 @@ function next(msg) {
       if (gFrameMsgCounts.input !== 1 ||
           gFrameMsgCounts.im0 !== 1 ||
           gFrameMsgCounts.im1 !== 0) {
         return;
       }
 
       gCount++;
 
-      let req0 = gFrames[0].setInputMethodActive(false);
+      let req0 = gInputMethodFrames[0].setInputMethodActive(false);
       req0.onsuccess = function() {
         ok(true, 'setInputMethodActive succeeded (0).');
       };
       req0.onerror = function() {
         ok(false, 'setInputMethodActive failed (0): ' + this.error.name);
       };
-      let req1 = gFrames[1].setInputMethodActive(true);
+      let req1 = gInputMethodFrames[1].setInputMethodActive(true);
       req1.onsuccess = function() {
         ok(true, 'setInputMethodActive succeeded (1).');
       };
       req1.onerror = function() {
         ok(false, 'setInputMethodActive failed (1): ' + this.error.name);
       };
 
       break;
@@ -254,17 +299,17 @@ function next(msg) {
           gFrameMsgCounts.im1 !== 1) {
         return;
       }
 
       gCount++;
 
       // Receive the second input from the second iframe.
       // Deactive the second iframe.
-      let req3 = gFrames[1].setInputMethodActive(false);
+      let req3 = gInputMethodFrames[1].setInputMethodActive(false);
       req3.onsuccess = function() {
         ok(true, 'setInputMethodActive(false) succeeded (2).');
       };
       req3.onerror = function() {
         ok(false, 'setInputMethodActive(false) failed (2): ' + this.error.name);
       };
       break;
 
deleted file mode 100644
--- a/dom/browser-element/mochitest/file_inputmethod.html
+++ /dev/null
@@ -1,20 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<body>
-<script>
-  var im = navigator.mozInputMethod;
-  if (im) {
-    im.oninputcontextchange = function() {
-      var ctx = im.inputcontext;
-      // Report back to parent frame on status of ctx gotten.
-      window.postMessage(window.location.hash + !!ctx, '*');
-      // If there is a context, send out the hash.
-      if (ctx) {
-        ctx.replaceSurroundingText(location.hash);
-      }
-    };
-  }
-</script>
-<p>This frame represents the input method frame.</p>
-</body>
-</html>
--- a/dom/browser-element/mochitest/mochitest-oop.ini
+++ b/dom/browser-element/mochitest/mochitest-oop.ini
@@ -71,30 +71,32 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_OpenWindowRejected.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_Opensearch.html]
 [test_browserElement_oop_OpenTab.html]
 skip-if = (toolkit == 'gonk') # Disabled on emulator. See bug 1144015 comment 8
 [test_browserElement_oop_PrivateBrowsing.html]
 [test_browserElement_oop_PromptCheck.html]
 [test_browserElement_oop_PromptConfirm.html]
+# Disabled on B2G Emulator because permission cannot be asserted in content process,
+# need to fix either bug 1094055 or bug 1020135.
 [test_browserElement_oop_Proxy.html]
-skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
+skip-if = (toolkit == 'gonk')
 [test_browserElement_oop_PurgeHistory.html]
 [test_browserElement_oop_Reload.html]
 [test_browserElement_oop_ReloadPostRequest.html]
 [test_browserElement_oop_RemoveBrowserElement.html]
 [test_browserElement_oop_ScrollEvent.html]
 [test_browserElement_oop_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) #TIMED_OUT, bug 766586
 [test_browserElement_oop_SelectionStateBlur.html]
 skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
 [test_browserElement_oop_SendEvent.html]
 [test_browserElement_oop_SetInputMethodActive.html]
-skip-if = (os == "android") || (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
+skip-if = (os == "android")
 [test_browserElement_oop_SetVisible.html]
 [test_browserElement_oop_SetVisibleFrames.html]
 [test_browserElement_oop_SetVisibleFrames2.html]
 [test_browserElement_oop_Stop.html]
 [test_browserElement_oop_TargetBlank.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_oop_TargetTop.html]
 [test_browserElement_oop_Titlechange.html]
--- a/dom/browser-element/mochitest/mochitest.ini
+++ b/dom/browser-element/mochitest/mochitest.ini
@@ -124,22 +124,20 @@ support-files =
   file_bug709759.sjs
   file_bug741717.sjs
   file_download_bin.sjs
   file_empty.html
   file_empty_script.js
   file_focus.html
   file_http_401_response.sjs
   file_http_407_response.sjs
-  file_inputmethod.html
   file_microdata.html
   file_microdata_bad_itemref.html
   file_microdata_itemref.html
   file_microformats.html
-  file_inputmethod.html
   file_post_request.html
   file_wyciwyg.html
   file_audio.html
   iframe_file_audio.html
   file_web_manifest.html
   file_web_manifest.json
   file_illegal_web_manifest.html
 
@@ -208,30 +206,32 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenWindowInFrame.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_OpenWindowRejected.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_Opensearch.html]
 [test_browserElement_inproc_PrivateBrowsing.html]
 [test_browserElement_inproc_PromptCheck.html]
 [test_browserElement_inproc_PromptConfirm.html]
+# Disabled on B2G Emulator because permission cannot be asserted in content process,
+# need to fix either bug 1094055 or bug 1020135.
 [test_browserElement_inproc_Proxy.html]
-skip-if = (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
+skip-if = (toolkit == 'gonk')
 [test_browserElement_inproc_PurgeHistory.html]
 [test_browserElement_inproc_ReloadPostRequest.html]
 [test_browserElement_inproc_RemoveBrowserElement.html]
 [test_browserElement_inproc_ScrollEvent.html]
 [test_browserElement_inproc_SecurityChange.html]
 skip-if = toolkit == 'android' || (toolkit == 'gonk' && !debug) # android(TIMED_OUT, bug 766586) androidx86(TIMED_OUT, bug 766586)
 [test_browserElement_inproc_SelectionStateBlur.html]
 skip-if = (toolkit == 'gonk') # Disabled on b2g due to bug 1097419
 [test_browserElement_inproc_SendEvent.html]
 # The setInputMethodActive() tests will timed out on Android
 [test_browserElement_inproc_SetInputMethodActive.html]
-skip-if = (os == "android") || (toolkit == 'gonk') # Disabled on B2G Emulator bug 1198163
+skip-if = (os == "android")
 [test_browserElement_inproc_SetVisible.html]
 [test_browserElement_inproc_SetVisibleFrames.html]
 [test_browserElement_inproc_SetVisibleFrames2.html]
 [test_browserElement_inproc_Stop.html]
 [test_browserElement_inproc_TargetBlank.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_browserElement_inproc_TargetTop.html]
 [test_browserElement_inproc_Titlechange.html]
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4842,17 +4842,18 @@ CanvasRenderingContext2D::DrawWindow(nsG
     return;
   }
 
   RefPtr<gfxContext> thebes;
   RefPtr<DrawTarget> drawDT;
   // Rendering directly is faster and can be done if mTarget supports Azure
   // and does not need alpha blending.
   if (gfxPlatform::GetPlatform()->SupportsAzureContentForDrawTarget(mTarget) &&
-      GlobalAlpha() == 1.0f)
+      GlobalAlpha() == 1.0f &&
+      UsedOperation() == CompositionOp::OP_OVER)
   {
     thebes = new gfxContext(mTarget);
     thebes->SetMatrix(gfxMatrix(matrix._11, matrix._12, matrix._21,
                                 matrix._22, matrix._31, matrix._32));
   } else {
     drawDT =
       gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(IntSize(ceil(sw), ceil(sh)),
                                                                    SurfaceFormat::B8G8R8A8);
@@ -4887,17 +4888,17 @@ CanvasRenderingContext2D::DrawWindow(nsG
       error.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     gfx::Rect destRect(0, 0, w, h);
     gfx::Rect sourceRect(0, 0, sw, sh);
     mTarget->DrawSurface(source, destRect, sourceRect,
                          DrawSurfaceOptions(gfx::Filter::POINT),
-                         DrawOptions(GlobalAlpha(), CompositionOp::OP_OVER,
+                         DrawOptions(GlobalAlpha(), UsedOperation(),
                                      AntialiasMode::NONE));
     mTarget->Flush();
   } else {
     mTarget->SetTransform(matrix);
   }
 
   // note that x and y are coordinates in the document that
   // we're drawing; x and y are drawn to 0,0 in current user
--- a/dom/events/ContentEventHandler.cpp
+++ b/dom/events/ContentEventHandler.cpp
@@ -209,17 +209,17 @@ ContentEventHandler::QueryContentRect(ns
   // account for any additional frames
   while ((frame = frame->GetNextContinuation()) != nullptr) {
     nsRect frameRect(nsPoint(0, 0), frame->GetRect().Size());
     rv = ConvertToRootRelativeOffset(frame, frameRect);
     NS_ENSURE_SUCCESS(rv, rv);
     resultRect.UnionRect(resultRect, frameRect);
   }
 
-  aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
+  aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
       resultRect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
   aEvent->mSucceeded = true;
 
   return NS_OK;
 }
 
 // Editor places a bogus BR node under its root content if the editor doesn't
 // have any text. This happens even for single line editors.
@@ -1076,17 +1076,17 @@ ContentEventHandler::OnQueryTextRect(Wid
     frameRect.width -= lastFrame->GetRect().width - ptOffset.x - 1;
   }
 
   if (firstFrame == lastFrame) {
     rect.IntersectRect(rect, frameRect);
   } else {
     rect.UnionRect(rect, frameRect);
   }
-  aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
+  aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
   aEvent->mReply.mWritingMode = lastFrame->GetWritingMode();
   aEvent->mSucceeded = true;
   return NS_OK;
 }
 
 nsresult
 ContentEventHandler::OnQueryEditorRect(WidgetQueryContentEvent* aEvent)
@@ -1124,17 +1124,17 @@ ContentEventHandler::OnQueryCaretRect(Wi
       rv = GetFlatTextOffsetOfRange(mRootContent, mFirstSelectedRange, &offset,
                                     lineBreakType);
       NS_ENSURE_SUCCESS(rv, rv);
       if (offset == aEvent->mInput.mOffset) {
         rv = ConvertToRootRelativeOffset(caretFrame, caretRect);
         NS_ENSURE_SUCCESS(rv, rv);
         nscoord appUnitsPerDevPixel =
           caretFrame->PresContext()->AppUnitsPerDevPixel();
-        aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
+        aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
           caretRect.ToOutsidePixels(appUnitsPerDevPixel));
         aEvent->mReply.mWritingMode = caretFrame->GetWritingMode();
         aEvent->mReply.mOffset = aEvent->mInput.mOffset;
         aEvent->mSucceeded = true;
         return NS_OK;
       }
     }
   }
@@ -1184,17 +1184,17 @@ ContentEventHandler::OnQueryCaretRect(Wi
   } else {
     rect.width = caretRect.width;
     rect.height = fontHeight;
   }
 
   rv = ConvertToRootRelativeOffset(frame, rect);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  aEvent->mReply.mRect = LayoutDevicePixel::FromUntyped(
+  aEvent->mReply.mRect = LayoutDeviceIntRect::FromUnknownRect(
       rect.ToOutsidePixels(mPresContext->AppUnitsPerDevPixel()));
   // If the caret rect is empty, let's make it non-empty rect.
   if (!aEvent->mReply.mRect.width) {
     aEvent->mReply.mRect.width = 1;
   }
   if (!aEvent->mReply.mRect.height) {
     aEvent->mReply.mRect.height = 1;
   }
--- a/dom/events/IMEContentObserver.cpp
+++ b/dom/events/IMEContentObserver.cpp
@@ -802,19 +802,19 @@ IMEContentObserver::OnMouseButtonEvent(n
     charAtPt.refPoint += aMouseEvent->widget->WidgetToScreenOffset() -
       mWidget->WidgetToScreenOffset();
   }
 
   IMENotification notification(NOTIFY_IME_OF_MOUSE_BUTTON_EVENT);
   notification.mMouseButtonEventData.mEventMessage = aMouseEvent->mMessage;
   notification.mMouseButtonEventData.mOffset = charAtPt.mReply.mOffset;
   notification.mMouseButtonEventData.mCursorPos.Set(
-    LayoutDeviceIntPoint::ToUntyped(charAtPt.refPoint));
+    charAtPt.refPoint.ToUnknownPoint());
   notification.mMouseButtonEventData.mCharRect.Set(
-    LayoutDevicePixel::ToUntyped(charAtPt.mReply.mRect));
+    charAtPt.mReply.mRect.ToUnknownRect());
   notification.mMouseButtonEventData.mButton = aMouseEvent->button;
   notification.mMouseButtonEventData.mButtons = aMouseEvent->buttons;
   notification.mMouseButtonEventData.mModifiers = aMouseEvent->modifiers;
 
   nsresult rv = IMEStateManager::NotifyIME(notification, mWidget);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
--- a/dom/events/WheelHandlingHelper.cpp
+++ b/dom/events/WheelHandlingHelper.cpp
@@ -333,18 +333,18 @@ WheelTransaction::SetTimeout()
   NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "nsITimer::InitWithFuncCallback failed");
 }
 
 /* static */ nsIntPoint
 WheelTransaction::GetScreenPoint(WidgetGUIEvent* aEvent)
 {
   NS_ASSERTION(aEvent, "aEvent is null");
   NS_ASSERTION(aEvent->widget, "aEvent-widget is null");
-  return LayoutDeviceIntPoint::ToUntyped(aEvent->refPoint +
-           aEvent->widget->WidgetToScreenOffset());
+  return (aEvent->refPoint + aEvent->widget->WidgetToScreenOffset())
+      .ToUnknownPoint();
 }
 
 /* static */ uint32_t
 WheelTransaction::GetTimeoutTime()
 {
   return Preferences::GetUint("mousewheel.transaction.timeout", 1500);
 }
 
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1517,17 +1517,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/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -194,17 +194,17 @@ this.Keyboard = {
     if (permName) {
       mm = Utils.getMMFromMessage(msg);
       if (!mm) {
         dump("Keyboard.jsm: Message " + msg.name + " has no message manager.");
         return;
       }
       if (!Utils.checkPermissionForMM(mm, permName)) {
         dump("Keyboard.jsm: Message " + msg.name +
-          " from a content process with no '" + permName + "' privileges.");
+          " from a content process with no '" + permName + "' privileges.\n");
         return;
       }
     }
 
     // we don't process kb messages (other than register)
     // if they come from a kb that we're currently not regsitered for.
     // this decision is made with the kbID kept by us and kb app
     let kbID = null;
--- 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/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,17 +44,17 @@ interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 interface nsIObserver;
 
-[scriptable, uuid(a30a95ac-3b95-4251-88dc-8efa89ba9f9c)]
+[scriptable, uuid(3f3f2bf4-d411-44b2-b2f7-dee5948c4763)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1426,17 +1426,17 @@ interface nsIDOMWindowUtils : nsISupport
    */
   readonly attribute bool asyncPanZoomEnabled;
 
   /**
    * Set async scroll offset on an element. The next composite will render
    * with that offset if async scrolling is enabled, and then the offset
    * will be removed. Only call this while test-controlled refreshes is enabled.
    */
-  void setAsyncScrollOffset(in nsIDOMNode aNode, in int32_t aX, in int32_t aY);
+  void setAsyncScrollOffset(in nsIDOMNode aNode, in float aX, in float aY);
 
   /**
    * Set async zoom value. aRootElement should be the document element of our
    * document. The next composite will render with that zoom added to any
    * existing zoom if async scrolling is enabled, and then the zoom will be
    * removed. Only call this while test-controlled refreshes is enabled.
    */
   void setAsyncZoom(in nsIDOMNode aRootElement, in float aValue);
--- a/dom/interfaces/base/nsITabParent.idl
+++ b/dom/interfaces/base/nsITabParent.idl
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 #include "domstubs.idl"
 
-[scriptable, uuid(3dd203e4-66ec-40fd-acde-43f0b35c98e9)]
+[scriptable, uuid(7615408c-1fb3-4128-8dd5-a3e2f3fa8842)]
 interface nsITabParent : nsISupports
 {
   void injectTouchEvent(in AString aType,
                         [array, size_is(count)] in uint32_t aIdentifiers,
                         [array, size_is(count)] in int32_t aXs,
                         [array, size_is(count)] in int32_t aYs,
                         [array, size_is(count)] in uint32_t aRxs,
                         [array, size_is(count)] in uint32_t aRys,
@@ -24,16 +24,27 @@ interface nsITabParent : nsISupports
   readonly attribute boolean useAsyncPanZoom;
 
   /**
     * Manages the docshell active state of the remote browser.
     */
   attribute boolean docShellIsActive;
 
   /**
+    * As an optimisation, setting the docshell's active state to
+    * inactive also triggers a layer invalidation to free up some
+    * potentially unhelpful memory usage. This attribute should be
+    * used where callers would like to set the docshell's state
+    * without losing any layer data.
+    *
+    * Otherwise, this does the same as setting the attribute above.
+    */
+  void setDocShellIsActiveAndForeground(in boolean aIsActive);
+
+  /**
    * During interactions where painting performance
    * is more important than scrolling, we may temporarily
    * suppress the displayport. Each enable called must be matched
    * with a disable call.
    */
   void suppressDisplayport(in bool aEnabled);
 
   readonly attribute uint64_t tabId;
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -3,29 +3,30 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISerializable.idl"
 #include "nsIContentPolicy.idl"
 
 interface nsIURI;
 interface nsIChannel;
 interface nsIDocShell;
+interface nsIDOMDocument;
 interface nsIPrincipal;
 interface nsIURI;
 
 /**
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
 typedef unsigned short CSPDirective;
 
-[scriptable, uuid(36c6d419-24c2-40e8-9adb-11d0b1341770)]
+[scriptable, builtinclass, uuid(b9a029c3-9484-4bf7-826d-0c6b545790bc)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
   /**
    * Directives supported by Content Security Policy.  These are enums for
    * the CSPDirective type.
    * The NO_DIRECTIVE entry is  used for checking default permissions and
    * returning failure when asking CSP which directive to check.
    *
@@ -82,22 +83,16 @@ interface nsIContentSecurityPolicy : nsI
    * @param aPolicy
    *        The referrer policy to use for the protected resource.
    * @return
    *        true if a referrer policy is specified, false if it's unspecified.
    */
   bool getReferrerPolicy(out unsigned long policy);
 
   /**
-   * Remove a policy associated with this CSP context.
-   * @throws NS_ERROR_FAILURE if the index is out of bounds or invalid.
-   */
-  void removePolicy(in unsigned long index);
-
-  /**
    * Parse and install a CSP policy.
    * @param aPolicy
    *        String representation of the policy (e.g., header value)
    * @param reportOnly
    *        Should this policy affect content, script and style processing or
    *        just send reports if it is violated?
    */
   void appendPolicy(in AString policyString, in boolean reportOnly);
@@ -168,26 +163,22 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short VIOLATION_TYPE_INLINE_STYLE  = 3;
   const unsigned short VIOLATION_TYPE_NONCE_SCRIPT  = 4;
   const unsigned short VIOLATION_TYPE_NONCE_STYLE   = 5;
   const unsigned short VIOLATION_TYPE_HASH_SCRIPT   = 6;
   const unsigned short VIOLATION_TYPE_HASH_STYLE    = 7;
 
   /**
    * Called after the CSP object is created to fill in appropriate request
-   * context and give it a reference to its owning principal for violation
-   * report generation.
-   * This will use whatever data is available, choosing earlier arguments first
-   * if multiple are available.  Either way, it will attempt to obtain the URI,
-   * referrer and the principal from whatever is available.  If the channel is
-   * available, it'll also store that for processing policy-uri directives.
+   * context. Either use
+   *  * aDocument (preferred), or if no document is available, then provide
+   *  * aPrincipal
    */
-  void setRequestContext(in nsIURI selfURI,
-                         in nsIURI referrer,
-                         in nsIChannel aChannel);
+  void setRequestContext(in nsIDOMDocument aDocument,
+                         in nsIPrincipal aPrincipal);
 
   /**
    * Verifies ancestry as permitted by the policy.
    *
    * NOTE: Calls to this may trigger violation reports when queried, so this
    * value should not be cached.
    *
    * @param docShell
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2258,16 +2258,49 @@ ContentChild::RecvSystemMemoryAvailable(
 bool
 ContentChild::RecvPreferenceUpdate(const PrefSetting& aPref)
 {
     Preferences::SetPreference(aPref);
     return true;
 }
 
 bool
+ContentChild::RecvDataStoragePut(const nsString& aFilename,
+                                 const DataStorageItem& aItem)
+{
+    RefPtr<DataStorage> storage = DataStorage::GetIfExists(aFilename);
+    if (storage) {
+        storage->Put(aItem.key(), aItem.value(), aItem.type());
+    }
+    return true;
+}
+
+bool
+ContentChild::RecvDataStorageRemove(const nsString& aFilename,
+                                    const nsCString& aKey,
+                                    const DataStorageType& aType)
+{
+    RefPtr<DataStorage> storage = DataStorage::GetIfExists(aFilename);
+    if (storage) {
+        storage->Remove(aKey, aType);
+    }
+    return true;
+}
+
+bool
+ContentChild::RecvDataStorageClear(const nsString& aFilename)
+{
+    RefPtr<DataStorage> storage = DataStorage::GetIfExists(aFilename);
+    if (storage) {
+        storage->Clear();
+    }
+    return true;
+}
+
+bool
 ContentChild::RecvNotifyAlertsObserver(const nsCString& aType, const nsString& aData)
 {
     for (uint32_t i = 0; i < mAlertObservers.Length();
          /*we mutate the array during the loop; ++i iff no mutation*/) {
         AlertObserver* observer = mAlertObservers[i];
         if (observer->Observes(aData) && observer->Notify(aType)) {
             // if aType == alertfinished, this alert is done.  we can
             // remove the observer.
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -333,16 +333,23 @@ public:
     // auto remove when alertfinished is received.
     nsresult AddRemoteAlertObserver(const nsString& aData, nsIObserver* aObserver);
 
     virtual bool RecvSystemMemoryAvailable(const uint64_t& aGetterId,
                                            const uint32_t& aMemoryAvailable) override;
 
     virtual bool RecvPreferenceUpdate(const PrefSetting& aPref) override;
 
+    virtual bool RecvDataStoragePut(const nsString& aFilename,
+                                    const DataStorageItem& aItem) override;
+    virtual bool RecvDataStorageRemove(const nsString& aFilename,
+                                       const nsCString& aKey,
+                                       const DataStorageType& aType) override;
+    virtual bool RecvDataStorageClear(const nsString& aFilename) override;
+
     virtual bool RecvNotifyAlertsObserver(const nsCString& aType,
                                           const nsString& aData) override;
 
     virtual bool RecvLoadProcessScript(const nsString& aURL) override;
 
     virtual bool RecvAsyncMessage(const nsString& aMsg,
                                   const ClonedMessageData& aData,
                                   InfallibleTArray<CpowEntry>&& aCpows,
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -30,16 +30,17 @@
 #include "AudioChannelService.h"
 #include "BlobParent.h"
 #include "CrashReporterParent.h"
 #include "GMPServiceParent.h"
 #include "IHistory.h"
 #include "imgIContainer.h"
 #include "mozIApplication.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DataStorage.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
 #include "mozilla/docshell/OfflineCacheUpdateParent.h"
 #include "mozilla/dom/DataStoreService.h"
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
@@ -1217,20 +1218,22 @@ ContentParent::RecvGetBlocklistState(con
         return true;
     }
 
     return NS_SUCCEEDED(tag->GetBlocklistState(aState));
 }
 
 bool
 ContentParent::RecvFindPlugins(const uint32_t& aPluginEpoch,
+                               nsresult* aRv,
                                nsTArray<PluginTag>* aPlugins,
                                uint32_t* aNewPluginEpoch)
 {
-    return mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
+    *aRv = mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
+    return true;
 }
 
 /*static*/ TabParent*
 ContentParent::CreateBrowserOrApp(const TabContext& aContext,
                                   Element* aFrameElement,
                                   ContentParent* aOpenerContentParent)
 {
     if (!sCanLaunchSubprocesses) {
@@ -2666,16 +2669,25 @@ ContentParent::RecvReadFontList(Infallib
 {
 #ifdef ANDROID
     gfxAndroidPlatform::GetPlatform()->GetSystemFontList(retValue);
 #endif
     return true;
 }
 
 bool
+ContentParent::RecvReadDataStorageArray(const nsString& aFilename,
+                                        InfallibleTArray<DataStorageItem>* aValues)
+{
+    RefPtr<DataStorage> storage = DataStorage::Get(aFilename);
+    storage->GetAll(aValues);
+    return true;
+}
+
+bool
 ContentParent::RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions)
 {
 #ifdef MOZ_PERMISSIONS
     nsCOMPtr<nsIPermissionManager> permissionManagerIface =
         services::GetPermissionManager();
     nsPermissionManager* permissionManager =
         static_cast<nsPermissionManager*>(permissionManagerIface.get());
     MOZ_ASSERT(permissionManager,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -186,16 +186,17 @@ public:
                                         const nsCString& aVersion,
                                         bool* aIsPresent,
                                         nsCString* aMessage) override;
 
     virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID) override;
     virtual bool RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv) override;
     virtual bool RecvGetBlocklistState(const uint32_t& aPluginId, uint32_t* aIsBlocklisted) override;
     virtual bool RecvFindPlugins(const uint32_t& aPluginEpoch,
+                                 nsresult* aRv,
                                  nsTArray<PluginTag>* aPlugins,
                                  uint32_t* aNewPluginEpoch) override;
 
     virtual bool RecvUngrabPointer(const uint32_t& aTime) override;
 
     NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ContentParent, nsIObserver)
 
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
@@ -737,16 +738,19 @@ private:
     virtual bool RecvPSpeechSynthesisConstructor(PSpeechSynthesisParent* aActor) override;
 
     virtual PWebBrowserPersistDocumentParent* AllocPWebBrowserPersistDocumentParent(PBrowserParent* aBrowser, const uint64_t& aOuterWindowID) override;
     virtual bool DeallocPWebBrowserPersistDocumentParent(PWebBrowserPersistDocumentParent* aActor) override;
 
     virtual bool RecvReadPrefsArray(InfallibleTArray<PrefSetting>* aPrefs) override;
     virtual bool RecvReadFontList(InfallibleTArray<FontListEntry>* retValue) override;
 
+    virtual bool RecvReadDataStorageArray(const nsString& aFilename,
+                                          InfallibleTArray<DataStorageItem>* aValues) override;
+
     virtual bool RecvReadPermissions(InfallibleTArray<IPC::Permission>* aPermissions) override;
 
     virtual bool RecvSetClipboard(const IPCDataTransfer& aDataTransfer,
                                   const bool& aIsPrivateData,
                                   const int32_t& aWhichClipboard) override;
     virtual bool RecvGetClipboard(nsTArray<nsCString>&& aTypes,
                                   const int32_t& aWhichClipboard,
                                   IPCDataTransfer* aDataTransfer) override;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -690,17 +690,17 @@ child:
      * Tell the child side if it has to update it's touchable region
      * to the parent.
      */
     SetUpdateHitRegion(bool aEnabled);
 
     /**
      * Update the child side docShell active (resource use) state.
      */
-    SetDocShellIsActive(bool aIsActive);
+    SetDocShellIsActive(bool aIsActive, bool aIsHidden);
 
     /**
      * Notify the child that it shouldn't paint the offscreen displayport.
      * This is useful to speed up interactive operations over async
      * scrolling performance like resize, tabswitch, pageload.
      *
      * Each enable call must be matched with a disable call. The child
      * will remain in the suppress mode as long as there's
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -86,16 +86,17 @@ using struct mozilla::void_t from "ipc/I
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using mozilla::dom::quota::PersistenceType from "mozilla/dom/quota/PersistenceType.h";
 using mozilla::hal::ProcessPriority from "mozilla/HalTypes.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using struct LookAndFeelInt from "mozilla/widget/WidgetMessageUtils.h";
 using class mozilla::dom::ipc::StructuredCloneData from "ipc/IPCMessageUtils.h";
+using mozilla::DataStorageType from "ipc/DataStorageIPCUtils.h";
 
 union ChromeRegistryItem
 {
     ChromePackage;
     OverrideMapping;
     SubstitutionMapping;
 };
 
@@ -319,16 +320,22 @@ union MaybePrefValue {
 };
 
 struct PrefSetting {
   nsCString name;
   MaybePrefValue defaultValue;
   MaybePrefValue userValue;
 };
 
+struct DataStorageItem {
+  nsCString key;
+  nsCString value;
+  DataStorageType type;
+};
+
 struct DataStoreSetting {
   nsString name;
   nsString originURL;
   nsString manifestURL;
   bool readOnly;
   bool enabled;
 };
 
@@ -554,16 +561,20 @@ child:
     async SetConnectivity(bool connectivity);
 
     async NotifyVisited(URIParams uri);
 
     async SystemMemoryAvailable(uint64_t getterId, uint32_t memoryAvailable);
 
     PreferenceUpdate(PrefSetting pref);
 
+    DataStoragePut(nsString aFilename, DataStorageItem aItem);
+    DataStorageRemove(nsString aFilename, nsCString aKey, DataStorageType aType);
+    DataStorageClear(nsString aFilename);
+
     NotifyAlertsObserver(nsCString topic, nsString data);
 
     GeolocationUpdate(GeoPosition somewhere);
 
     GeolocationError(uint16_t errorCode);
 
     UpdateDictionaryList(nsString[] dictionaries);
 
@@ -758,17 +769,17 @@ parent:
      * epoch number every time the set of plugins changes. The content process
      * sends up the last epoch it observed. If the epochs are the same, the
      * chrome process returns no plugins. Otherwise it returns a complete list.
      *
      * |pluginEpoch| is the epoch last observed by the content
      * process. |newPluginEpoch| is the current epoch in the chrome process. If
      * |pluginEpoch == newPluginEpoch|, then |plugins| will be left empty.
      */
-    sync FindPlugins(uint32_t pluginEpoch) returns (PluginTag[] plugins, uint32_t newPluginEpoch);
+    sync FindPlugins(uint32_t pluginEpoch) returns (nsresult aResult, PluginTag[] plugins, uint32_t newPluginEpoch);
 
     async PJavaScript();
 
     PRemoteSpellcheckEngine();
     PDeviceStorageRequest(DeviceStorageParams params);
 
     PFileSystemRequest(FileSystemParams params);
 
@@ -834,16 +845,19 @@ parent:
 
     async LoadURIExternal(URIParams uri);
 
     // PrefService message
     sync ReadPrefsArray() returns (PrefSetting[] prefs);
 
     sync ReadFontList() returns (FontListEntry[] retValue);
 
+    sync ReadDataStorageArray(nsString aFilename)
+      returns (DataStorageItem[] retValue);
+
     sync SyncMessage(nsString aMessage, ClonedMessageData aData,
                      CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
     prio(high) sync RpcMessage(nsString aMessage, ClonedMessageData aData,
                                CpowEntry[] aCpows, Principal aPrincipal)
       returns (StructuredCloneData[] retval);
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -2294,21 +2294,25 @@ TabChild::RecvSetUpdateHitRegion(const b
     RefPtr<nsPresContext> presContext = presShell->GetPresContext();
     NS_ENSURE_TRUE(presContext, true);
     presContext->InvalidatePaintedLayers();
 
     return true;
 }
 
 bool
-TabChild::RecvSetDocShellIsActive(const bool& aIsActive)
+TabChild::RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden)
 {
     nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
     if (docShell) {
-      docShell->SetIsActive(aIsActive);
+      if (aIsHidden) {
+        docShell->SetIsActive(aIsActive);
+      } else {
+        docShell->SetIsActiveAndForeground(aIsActive);
+      }
     }
     return true;
 }
 
 bool
 TabChild::RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation)
 {
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -529,17 +529,17 @@ public:
 
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() override;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) override;
     virtual bool RecvDestroy() override;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) override;
-    virtual bool RecvSetDocShellIsActive(const bool& aIsActive) override;
+    virtual bool RecvSetDocShellIsActive(const bool& aIsActive, const bool& aIsHidden) override;
     virtual bool RecvNavigateByKey(const bool& aForward, const bool& aForDocumentNavigation) override;
 
     virtual bool RecvRequestNotifyAfterRemotePaint() override;
 
     virtual bool RecvSuppressDisplayport(const bool& aEnabled) override;
 
     virtual bool RecvParentActivated(const bool& aActivated) override;
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2904,28 +2904,36 @@ TabParent::GetUseAsyncPanZoom(bool* useA
   return NS_OK;
 }
 
 // defined in nsITabParent
 NS_IMETHODIMP
 TabParent::SetDocShellIsActive(bool isActive)
 {
   mDocShellIsActive = isActive;
-  Unused << SendSetDocShellIsActive(isActive);
+  Unused << SendSetDocShellIsActive(isActive, true);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TabParent::GetDocShellIsActive(bool* aIsActive)
 {
   *aIsActive = mDocShellIsActive;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+TabParent::SetDocShellIsActiveAndForeground(bool isActive)
+{
+  mDocShellIsActive = isActive;
+  Unused << SendSetDocShellIsActive(isActive, false);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 TabParent::SuppressDisplayport(bool aEnabled)
 {
   if (IsDestroyed()) {
     return NS_OK;
   }
 
   if (aEnabled) {
     mActiveSupressDisplayportCount++;
--- 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/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -243,17 +243,17 @@ GMPExToNsresult(GMPDOMException aDomExce
 }
 
 bool
 GMPDecryptorParent::RecvRejectPromise(const uint32_t& aPromiseId,
                                       const GMPDOMException& aException,
                                       const nsCString& aMessage)
 {
   LOGD(("GMPDecryptorParent[%p]::RecvRejectPromise(promiseId=%u, exception=%d, msg='%s')",
-        this, aException, aMessage.get()));
+        this, aPromiseId, aException, aMessage.get()));
 
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
   mCallback->RejectPromise(aPromiseId, GMPExToNsresult(aException), aMessage);
   return true;
 }
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -242,16 +242,20 @@ GMPVideoDecoder::Input(MediaRawData* aSa
   if (!mGMP) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
   mAdapter->SetLastStreamOffset(sample->mOffset);
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
+  if (!frame) {
+    mCallback->Error();
+    return NS_ERROR_FAILURE;
+  }
   nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
   nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
   if (NS_FAILED(rv)) {
     mCallback->Error();
     return rv;
   }
 
   return NS_OK;
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -2413,19 +2413,21 @@ nsresult nsPluginHost::LoadPlugins()
 }
 
 nsresult
 nsPluginHost::FindPluginsInContent(bool aCreatePluginList, bool* aPluginsChanged)
 {
   MOZ_ASSERT(XRE_IsContentProcess());
 
   dom::ContentChild* cp = dom::ContentChild::GetSingleton();
+  nsresult rv;
   nsTArray<PluginTag> plugins;
   uint32_t parentEpoch;
-  if (!cp->SendFindPlugins(ChromeEpochForContent(), &plugins, &parentEpoch)) {
+  if (!cp->SendFindPlugins(ChromeEpochForContent(), &rv, &plugins, &parentEpoch) ||
+      NS_FAILED(rv)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (parentEpoch != ChromeEpochForContent()) {
     SetChromeEpochForContent(parentEpoch);
     *aPluginsChanged = true;
     if (!aCreatePluginList) {
       return NS_OK;
@@ -2629,41 +2631,43 @@ nsresult nsPluginHost::FindPlugins(bool 
 
   // No more need for cached plugins. Clear it up.
   NS_ITERATIVE_UNREF_LIST(RefPtr<nsPluginTag>, mCachedPlugins, mNext);
   NS_ITERATIVE_UNREF_LIST(RefPtr<nsInvalidPluginTag>, mInvalidPlugins, mNext);
 
   return NS_OK;
 }
 
-bool
+nsresult
 mozilla::plugins::FindPluginsForContent(uint32_t aPluginEpoch,
                                         nsTArray<PluginTag>* aPlugins,
                                         uint32_t* aNewPluginEpoch)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
   RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
-  host->FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
-  return true;
+  return host->FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
 }
 
-void
+nsresult
 nsPluginHost::FindPluginsForContent(uint32_t aPluginEpoch,
                                     nsTArray<PluginTag>* aPlugins,
                                     uint32_t* aNewPluginEpoch)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
   // Load plugins so that the epoch is correct.
-  LoadPlugins();
+  nsresult rv = LoadPlugins();
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   *aNewPluginEpoch = ChromeEpoch();
   if (aPluginEpoch == ChromeEpoch()) {
-    return;
+    return NS_OK;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
   GetPlugins(plugins, true);
 
   for (size_t i = 0; i < plugins.Length(); i++) {
     nsCOMPtr<nsIInternalPluginTag> basetag = plugins[i];
 
@@ -2688,16 +2692,17 @@ nsPluginHost::FindPluginsForContent(uint
                                       tag->mIsJavaPlugin,
                                       tag->mIsFlashPlugin,
                                       tag->mSupportsAsyncInit,
                                       tag->FileName(),
                                       tag->Version(),
                                       tag->mLastModifiedTime,
                                       tag->IsFromExtension()));
   }
+  return NS_OK;
 }
 
 // This function is not relevant for fake plugins.
 void
 nsPluginHost::UpdatePluginInfo(nsPluginTag* aPluginTag)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
--- a/dom/plugins/base/nsPluginHost.h
+++ b/dom/plugins/base/nsPluginHost.h
@@ -113,19 +113,19 @@ public:
   // FIXME-jsplugins what if fake has different extensions
   bool HavePluginForExtension(const nsACString & aExtension,
                               /* out */ nsACString & aMimeType,
                               PluginFilter aFilter = eExcludeDisabled);
 
   void GetPlugins(nsTArray<nsCOMPtr<nsIInternalPluginTag>>& aPluginArray,
                   bool aIncludeDisabled = false);
 
-  void FindPluginsForContent(uint32_t aPluginEpoch,
-                             nsTArray<mozilla::plugins::PluginTag>* aPlugins,
-                             uint32_t* aNewPluginEpoch);
+  nsresult FindPluginsForContent(uint32_t aPluginEpoch,
+                                 nsTArray<mozilla::plugins::PluginTag>* aPlugins,
+                                 uint32_t* aNewPluginEpoch);
 
   nsresult GetURL(nsISupports* pluginInst,
                   const char* url,
                   const char* target,
                   nsNPAPIPluginStreamListener* streamListener,
                   const char* altHost,
                   const char* referrer,
                   bool forceJSEnabled);
--- a/dom/plugins/ipc/PluginBridge.h
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -14,17 +14,17 @@ class ContentParent;
 } // namespace dom
 
 namespace plugins {
 
 bool
 SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent,
             bool aForceBridgeNow, nsresult* aResult, uint32_t* aRunID);
 
-bool
+nsresult
 FindPluginsForContent(uint32_t aPluginEpoch,
                       nsTArray<PluginTag>* aPlugins,
                       uint32_t* aNewPluginEpoch);
 
 void
 TerminatePlugin(uint32_t aPluginId,
                 const nsCString& aMonitorDescription,
                 const nsAString& aBrowserDumpId);
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1339,40 +1339,20 @@ Promise::RejectInternal(JSContext* aCx,
   mResolvePending = true;
 
   MaybeSettle(aValue, Rejected);
 }
 
 void
 Promise::Settle(JS::Handle<JS::Value> aValue, PromiseState aState)
 {
-#ifdef MOZ_CRASHREPORTER
-  if (!mGlobal && mFullfillmentStack) {
-    AutoJSAPI jsapi;
-    jsapi.Init();
-    JSContext* cx = jsapi.cx();
-    JS::RootedObject stack(cx, mFullfillmentStack);
-    JSAutoCompartment ac(cx, stack);
-    JS::RootedString stackJSString(cx);
-    if (JS::BuildStackString(cx, stack, &stackJSString)) {
-      nsAutoJSString stackString;
-      if (stackString.init(cx, stackJSString)) {
-        // Put the string in the crash report here, since we're about to crash
-        CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("cced_promise_stack"),
-                                           NS_ConvertUTF16toUTF8(stackString));
-      } else {
-        JS_ClearPendingException(cx);
-      }
-    } else {
-      JS_ClearPendingException(cx);
-    }
-  }
-#endif
-
-  if (mGlobal->IsDying()) {
+  MOZ_ASSERT(mGlobal,
+             "We really should have a global here.  Except we sometimes don't "
+             "in the wild for some odd reason");
+  if (!mGlobal || mGlobal->IsDying()) {
     return;
   }
 
   mSettlementTimestamp = TimeStamp::Now();
 
   SetResult(aValue);
   SetState(aState);
 
--- a/dom/push/PushDB.jsm
+++ b/dom/push/PushDB.jsm
@@ -122,17 +122,20 @@ this.PushDB.prototype = {
     console.debug("delete()");
 
     return new Promise((resolve, reject) =>
       this.newTxn(
         "readwrite",
         this._dbStoreName,
         function txnCb(aTxn, aStore) {
           console.debug("delete: Removing record", aKeyID);
-          aStore.delete(aKeyID);
+          aStore.get(aKeyID).onsuccess = event => {
+            aTxn.result = event.target.result;
+            aStore.delete(aKeyID);
+          };
         },
         resolve,
         reject
       )
     );
   },
 
   // testFn(record) is called with a database record and should return true if
@@ -208,63 +211,49 @@ this.PushDB.prototype = {
         },
         resolve,
         reject
       )
     );
   },
 
   /**
-   * Updates all push registrations for an origin.
+   * Reduces all records associated with an origin to a single value.
    *
    * @param {String} origin The origin, matched as a prefix against the scope.
    * @param {String} originAttributes Additional origin attributes. Requires
    *  an exact match.
-   * @param {Function} updateFunc A function that receives the existing
-   *  registration record as its argument, and returns a new record. The record
-   *  will not be updated if the function returns `null`, `undefined`, or an
-   *  invalid record. If the function returns `false`, the record will be
-   *  dropped.
-   * @returns {Promise} A promise that resolves once all records have been
-   *  updated.
+   * @param {Function} callback A function with the signature `(result,
+   *  record, cursor)`, where `result` is the value returned by the previous
+   *  invocation, `record` is the registration, and `cursor` is an `IDBCursor`.
+   * @param {Object} [initialValue] The value to use for the first invocation.
+   * @returns {Promise} Resolves with the value of the last invocation.
    */
-  updateByOrigin: function(origin, originAttributes, updateFunc) {
-    console.debug("updateByOrigin()");
+  reduceByOrigin: function(origin, originAttributes, callback, initialValue) {
+    console.debug("forEachOrigin()");
 
     return new Promise((resolve, reject) =>
       this.newTxn(
         "readwrite",
         this._dbStoreName,
         (aTxn, aStore) => {
-          aTxn.result = [];
+          aTxn.result = initialValue;
 
           let index = aStore.index("identifiers");
           let range = IDBKeyRange.bound(
             [origin, originAttributes],
             [origin + "\x7f", originAttributes]
           );
           index.openCursor(range).onsuccess = event => {
             let cursor = event.target.result;
             if (!cursor) {
               return;
             }
             let record = this.toPushRecord(cursor.value);
-            let newRecord = updateFunc(record);
-            if (newRecord === false) {
-              console.debug("updateByOrigin: Removing record for key ID",
-                record.keyID);
-              cursor.delete();
-            } else if (this.isValidRecord(newRecord)) {
-              console.debug("updateByOrigin: Updating record for key ID",
-                record.keyID, newRecord);
-              cursor.update(newRecord);
-            } else {
-              console.error("updateByOrigin: Ignoring invalid update for record",
-                record.keyID, newRecord);
-            }
+            aTxn.result = callback(aTxn.result, record, cursor);
             cursor.continue();
           };
         },
         resolve,
         reject
       )
     );
   },
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -190,43 +190,39 @@ PushRecord.prototype = {
    */
   hasPermission() {
     let permission = Services.perms.testExactPermissionFromPrincipal(
       this.principal, "desktop-notification");
     return permission == Ci.nsIPermissionManager.ALLOW_ACTION;
   },
 
   quotaChanged() {
+    if (!this.hasPermission()) {
+      return Promise.resolve(false);
+    }
     return this.getLastVisit()
       .then(lastVisit => lastVisit > this.lastPush);
   },
 
   quotaApplies() {
     return Number.isFinite(this.quota);
   },
 
   isExpired() {
     return this.quota === 0;
   },
 
-  toRegistration() {
+  toSubscription() {
     return {
       pushEndpoint: this.pushEndpoint,
       lastPush: this.lastPush,
       pushCount: this.pushCount,
       p256dhKey: this.p256dhPublicKey,
     };
   },
-
-  toRegister() {
-    return {
-      pushEndpoint: this.pushEndpoint,
-      p256dhKey: this.p256dhPublicKey,
-    };
-  },
 };
 
 // Define lazy getters for the principal and scope URI. IndexedDB can't store
 // `nsIPrincipal` objects, so we keep them in a private weak map.
 var principals = new WeakMap();
 Object.defineProperties(PushRecord.prototype, {
   principal: {
     get() {
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -22,16 +22,20 @@ const {PushServiceHttp2} = Cu.import("re
 const {PushCrypto} = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 // Currently supported protocols: WebSocket.
 const CONNECTION_PROTOCOLS = [PushServiceWebSocket, PushServiceHttp2];
 
 XPCOMUtils.defineLazyModuleGetter(this, "AlarmService",
                                   "resource://gre/modules/AlarmService.jsm");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
+                                   "@mozilla.org/contentsecuritymanager;1",
+                                   "nsIContentSecurityManager");
+
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "PushService",
   });
@@ -277,64 +281,89 @@ this.PushService = {
           return;
         }
 
         var originAttributes =
           ChromeUtils.originAttributesToSuffix({ appId: data.appId,
                                                  inBrowser: data.browserOnly });
         this._db.getAllByOriginAttributes(originAttributes)
           .then(records => Promise.all(records.map(record =>
-            this._db.delete(record.keyID).then(
-              _ => this._backgroundUnregister(record),
-              err => {
+            this._db.delete(record.keyID)
+              .catch(err => {
                 console.error("webapps-clear-data: Error removing record",
                   record, err);
-
-                this._backgroundUnregister(record);
+                // This is the record we were unable to delete.
+                return record;
               })
+              .then(maybeDeleted => this._backgroundUnregister(maybeDeleted))
             )
           ));
 
         break;
     }
   },
 
+  /**
+   * Sends an unregister request to the server in the background. If the
+   * service is not connected, this function is a no-op.
+   *
+   * @param {PushRecord} record The record to unregister.
+   */
   _backgroundUnregister: function(record) {
-    if (this._service.isConnected()) {
-      // courtesy, but don't establish a connection
-      // just for it
-      console.debug("backgroundUnregister: Notifying server", record);
-      return this._sendUnregister(record)
-          .catch(function(e) {
-            console.error("backgroundUnregister: Error notifying server", e);
-          });
+    console.debug("backgroundUnregister()");
+
+    if (!this._service.isConnected() || !record) {
+      return;
     }
+
+    console.debug("backgroundUnregister: Notifying server", record);
+    this._sendUnregister(record).catch(e => {
+      console.error("backgroundUnregister: Error notifying server", e);
+    });
   },
 
   // utility function used to add/remove observers in startObservers() and
   // stopObservers()
   getNetworkStateChangeEventName: function() {
     try {
       Cc["@mozilla.org/network/manager;1"].getService(Ci.nsINetworkManager);
       return "network-active-changed";
     } catch (e) {
       return "network:offline-status-changed";
     }
   },
 
-  _findService: function(serverURI) {
-    var uri;
-    var service;
-    if (serverURI) {
-      for (let connProtocol of CONNECTION_PROTOCOLS) {
-        uri = connProtocol.checkServerURI(serverURI);
-        if (uri) {
-          service = connProtocol;
-          break;
-        }
+  _findService: function(serverURL) {
+    console.debug("findService()");
+
+    let uri;
+    let service;
+
+    if (!serverURL) {
+      console.warn("findService: No dom.push.serverURL found");
+      return [];
+    }
+
+    try {
+      uri = Services.io.newURI(serverURL, null, null);
+    } catch (e) {
+      console.warn("findService: Error creating valid URI from",
+        "dom.push.serverURL", serverURL);
+      return [];
+    }
+
+    if (!gContentSecurityManager.isURIPotentiallyTrustworthy(uri)) {
+      console.warn("findService: Untrusted server URI", uri.spec);
+      return [];
+    }
+
+    for (let connProtocol of CONNECTION_PROTOCOLS) {
+      if (connProtocol.validServerURI(uri)) {
+        service = connProtocol;
+        break;
       }
     }
     return [service, uri];
   },
 
   _changeServerURL: function(serverURI, event) {
     console.debug("changeServerURL()");
 
@@ -416,32 +445,17 @@ this.PushService = {
 
     this._setState(PUSH_SERVICE_ACTIVATING);
 
     Services.obs.addObserver(this, "xpcom-shutdown", false);
 
     if (options.serverURI) {
       // this is use for xpcshell test.
 
-      var uri;
-      var service;
-      if (!options.service) {
-        for (let connProtocol of CONNECTION_PROTOCOLS) {
-          uri = connProtocol.checkServerURI(options.serverURI);
-          if (uri) {
-            service = connProtocol;
-            break;
-          }
-        }
-      } else {
-        try {
-          uri  = Services.io.newURI(options.serverURI, null, null);
-          service = options.service;
-        } catch(e) {}
-      }
+      let [service, uri] = this._findService(options.serverURI);
       if (!service) {
         this._setState(PUSH_SERVICE_INIT);
         return;
       }
 
       // Start service.
       this._startService(service, uri, false, options).then(_ => {
         // Before completing the activation check prefs. This will first check
@@ -568,17 +582,17 @@ this.PushService = {
     }
     if (event == UNINIT_EVENT) {
       // If it is uninitialized just close db.
       this._db.close();
       this._db = null;
       return Promise.resolve();
     }
 
-    return this.dropRegistrations()
+    return this.dropUnexpiredRegistrations()
        .then(_ => {
          this._db.close();
          this._db = null;
        }, err => {
          this._db.close();
          this._db = null;
        });
   },
@@ -659,22 +673,45 @@ this.PushService = {
   stopAlarm: function() {
     if (this._alarmID !== null) {
       console.debug("stopAlarm: Stopped existing alarm", this._alarmID);
       AlarmService.remove(this._alarmID);
       this._alarmID = null;
     }
   },
 
-  dropRegistrations: function() {
-    return this._notifyAllAppsRegister()
-      .then(_ => this._db.drop());
+  /**
+   * Drops all active registrations and notifies the associated service
+   * workers. This function is called when the user switches Push servers,
+   * or when the server invalidates all existing registrations.
+   *
+   * We ignore expired registrations because they're already handled in other
+   * code paths. Registrations that expired after exceeding their quotas are
+   * evicted at startup, or on the next `idle-daily` event. Registrations that
+   * expired because the user revoked the notification permission are evicted
+   * once the permission is reinstated.
+   */
+  dropUnexpiredRegistrations: function() {
+    let subscriptionChanges = [];
+    return this._db.clearIf(record => {
+      if (record.isExpired()) {
+        return false;
+      }
+      subscriptionChanges.push(record);
+      return true;
+    }).then(() => {
+      this.notifySubscriptionChanges(subscriptionChanges);
+    });
   },
 
   _notifySubscriptionChangeObservers: function(record) {
+    if (!record) {
+      return;
+    }
+
     // Notify XPCOM observers.
     Services.obs.notifyObservers(
       null,
       "push-subscription-change",
       record.scope
     );
 
     let data = {
@@ -699,39 +736,59 @@ this.PushService = {
       }
     } else {
       let ppmm = Cc['@mozilla.org/parentprocessmessagemanager;1']
                    .getService(Ci.nsIMessageListenerManager);
       ppmm.broadcastAsyncMessage(name, data);
     }
   },
 
-  // Fires a push-register system message to all applications that have
-  // registration.
-  _notifyAllAppsRegister: function() {
-    console.debug("notifyAllAppsRegister()");
-    // records are objects describing the registration as stored in IndexedDB.
-    return this._db.getAllUnexpired().then(records => {
-      records.forEach(record => {
-        this._notifySubscriptionChangeObservers(record);
-      });
-    });
+  /**
+   * Drops a registration and notifies the associated service worker. If the
+   * registration does not exist, this function is a no-op.
+   *
+   * @param {String} keyID The registration ID to remove.
+   * @returns {Promise} Resolves once the worker has been notified.
+   */
+  dropRegistrationAndNotifyApp: function(aKeyID) {
+    return this._db.delete(aKeyID)
+      .then(record => this._notifySubscriptionChangeObservers(record));
   },
 
-  dropRegistrationAndNotifyApp: function(aKeyId) {
-    return this._db.getByKeyID(aKeyId).then(record => {
-      this._notifySubscriptionChangeObservers(record);
-      return this._db.delete(aKeyId);
-    });
+  /**
+   * Replaces an existing registration and notifies the associated service
+   * worker.
+   *
+   * @param {String} aOldKey The registration ID to replace.
+   * @param {PushRecord} aNewRecord The new record.
+   * @returns {Promise} Resolves once the worker has been notified.
+   */
+  updateRegistrationAndNotifyApp: function(aOldKey, aNewRecord) {
+    return this.updateRecordAndNotifyApp(aOldKey, _ => aNewRecord);
+  },
+  /**
+   * Updates a registration and notifies the associated service worker.
+   *
+   * @param {String} keyID The registration ID to update.
+   * @param {Function} updateFunc Returns the updated record.
+   * @returns {Promise} Resolves with the updated record once the worker
+   *  has been notified.
+   */
+  updateRecordAndNotifyApp: function(aKeyID, aUpdateFunc) {
+    return this._db.update(aKeyID, aUpdateFunc)
+      .then(record => {
+        this._notifySubscriptionChangeObservers(record);
+        return record;
+      });
   },
 
-  updateRegistrationAndNotifyApp: function(aOldKey, aRecord) {
-    return this._db.delete(aOldKey)
-      .then(_ => this._db.put(aRecord))
-      .then(record => this._notifySubscriptionChangeObservers(record));
+  notifySubscriptionChanges: function(records) {
+    records.forEach(record => {
+      this._notifySubscriptionChangeObservers(record);
+    });
   },
 
   ensureP256dhKey: function(record) {
     if (record.p256dhPublicKey && record.p256dhPrivateKey) {
       return Promise.resolve(record);
     }
     // We do not have a encryption key. so we need to generate it. This
     // is only going to happen on db upgrade from version 4 to higher.
@@ -743,29 +800,16 @@ this.PushService = {
           return record;
         });
       }, error => {
         return this.dropRegistrationAndNotifyApp(record.keyID).then(
           () => Promise.reject(error));
       });
   },
 
-  updateRecordAndNotifyApp: function(aKeyID, aUpdateFunc) {
-    return this._db.update(aKeyID, aUpdateFunc)
-      .then(record => {
-        this._notifySubscriptionChangeObservers(record);
-        return record;
-      });
-  },
-
-  dropRecordAndNotifyApp: function(aRecord) {
-    return this._db.delete(aRecord.keyID)
-      .then(_ => this._notifySubscriptionChangeObservers(aRecord));
-  },
-
   _recordDidNotNotify: function(reason) {
     Services.telemetry.
       getHistogramById("PUSH_API_NOTIFICATION_RECEIVED_BUT_DID_NOT_NOTIFY").
       add(reason);
   },
 
   /**
    * Dispatches an incoming message to a service worker, recalculating the
@@ -930,17 +974,17 @@ this.PushService = {
     console.debug("registerWithServer()", aPageRecord);
 
     Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_ATTEMPT").add();
     return this._sendRequest("register", aPageRecord)
       .then(record => this._onRegisterSuccess(record),
             err => this._onRegisterError(err))
       .then(record => {
         this._deletePendingRequest(aPageRecord);
-        return record.toRegister();
+        return record.toSubscription();
       }, err => {
         this._deletePendingRequest(aPageRecord);
         throw err;
      });
   },
 
   _sendUnregister: function(aRecord) {
     Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_ATTEMPT").add();
@@ -1071,22 +1115,22 @@ this.PushService = {
         if (!record) {
           return this._lookupOrPutPendingRequest(aPageRecord);
         }
         if (record.isExpired()) {
           return record.quotaChanged().then(isChanged => {
             if (isChanged) {
               // If the user revisited the site, drop the expired push
               // registration and re-register.
-              return this._db.delete(record.keyID);
+              return this.dropRegistrationAndNotifyApp(record.keyID);
             }
             throw new Error("Push subscription expired");
           }).then(_ => this._lookupOrPutPendingRequest(aPageRecord));
         }
-        return record.toRegister();
+        return record.toSubscription();
       });
   },
 
   /**
    * Called on message from the child process.
    *
    * Why is the record being deleted from the local database before the server
    * is told?
@@ -1189,35 +1233,35 @@ this.PushService = {
       .then(_ => this._db.getByIdentifiers(aPageRecord))
       .then(record => {
         if (!record) {
           return null;
         }
         if (record.isExpired()) {
           return record.quotaChanged().then(isChanged => {
             if (isChanged) {
-              return this._db.delete(record.keyID).then(_ => null);
+              return this.dropRegistrationAndNotifyApp(record.keyID).then(_ => null);
             }
             return null;
           });
         }
-        return record.toRegistration();
+        return record.toSubscription();
       });
   },
 
   _dropExpiredRegistrations: function() {
     console.debug("dropExpiredRegistrations()");
 
     return this._db.getAllExpired().then(records => {
       return Promise.all(records.map(record =>
         record.quotaChanged().then(isChanged => {
           if (isChanged) {
             // If the user revisited the site, drop the expired push
             // registration and notify the associated service worker.
-            return this.dropRecordAndNotifyApp(record);
+            return this.dropRegistrationAndNotifyApp(record.keyID);
           }
         }).catch(error => {
           console.error("dropExpiredRegistrations: Error dropping registration",
             record.keyID, error);
         })
       ));
     });
   },
@@ -1254,74 +1298,89 @@ this.PushService = {
     let isAllow = permission.capability ==
                   Ci.nsIPermissionManager.ALLOW_ACTION;
     let isChange = type == "added" || type == "changed";
 
     if (isAllow && isChange) {
       // Permission set to "allow". Drop all expired registrations for this
       // site, notify the associated service workers, and reset the quota
       // for active registrations.
-      return this._updateByPrincipal(
+      return this._reduceByPrincipal(
         permission.principal,
-        record => this._permissionAllowed(record)
-      );
+        (subscriptionChanges, record, cursor) => {
+          this._permissionAllowed(subscriptionChanges, record, cursor);
+          return subscriptionChanges;
+        },
+        []
+      ).then(subscriptionChanges => {
+        this.notifySubscriptionChanges(subscriptionChanges);
+      });
     } else if (isChange || (isAllow && type == "deleted")) {
       // Permission set to "block" or "always ask," or "allow" permission
       // removed. Expire all registrations for this site.
-      return this._updateByPrincipal(
+      return this._reduceByPrincipal(
         permission.principal,
-        record => this._permissionDenied(record)
+        (memo, record, cursor) => this._permissionDenied(record, cursor)
       );
     }
 
     return Promise.resolve();
   },
 
-  _updateByPrincipal: function(principal, updateFunc) {
-    return this._db.updateByOrigin(
+  _reduceByPrincipal: function(principal, callback, initialValue) {
+    return this._db.reduceByOrigin(
       principal.URI.prePath,
       ChromeUtils.originAttributesToSuffix(principal.originAttributes),
-      updateFunc
+      callback,
+      initialValue
     );
   },
 
   /**
-   * Expires all registrations if the push permission is revoked. We only
-   * expire the registration so we can notify the service worker as soon as
-   * the permission is reinstated. If we just deleted the registration, the
-   * worker wouldn't be notified until the next visit to the site.
+   * The update function called for each registration record if the push
+   * permission is revoked. We only expire the record so we can notify the
+   * service worker as soon as the permission is reinstated. If we just
+   * deleted the record, the worker wouldn't be notified until the next visit
+   * to the site.
    *
-   * @param {Array} A list of records to expire.
-   * @returns {Promise} A promise resolved with the expired records.
+   * @param {PushRecord} record The record to expire.
+   * @param {IDBCursor} cursor The IndexedDB cursor.
    */
-  _permissionDenied: function(record) {
+  _permissionDenied: function(record, cursor) {
+    console.debug("permissionDenied()");
+
     if (!record.quotaApplies() || record.isExpired()) {
       // Ignore already-expired records.
-      return null;
+      return;
     }
     // Drop the registration in the background.
     this._backgroundUnregister(record);
     record.setQuota(0);
-    return record;
+    cursor.update(record);
   },
 
   /**
-   * Drops all expired registrations, notifies the associated service
-   * workers, and resets the quota for active registrations if the push
-   * permission is granted.
+   * The update function called for each registration record if the push
+   * permission is granted. If the record has expired, it will be dropped;
+   * otherwise, its quota will be reset to the default value.
    *
-   * @param {Array} A list of records to refresh.
-   * @returns {Promise} A promise resolved with the refreshed records.
+   * @param {Array} subscriptionChanges A list of records whose associated
+   *  service workers should be notified once the transaction has committed.
+   * @param {PushRecord} record The record to update.
+   * @param {IDBCursor} cursor The IndexedDB cursor.
    */
-  _permissionAllowed: function(record) {
+  _permissionAllowed: function(subscriptionChanges, record, cursor) {
+    console.debug("permissionAllowed()");
+
     if (!record.quotaApplies()) {
-      return null;
+      return;
     }
     if (record.isExpired()) {
       // If the registration has expired, drop and notify the worker
       // unconditionally.
-      this._notifySubscriptionChangeObservers(record);
-      return false;
+      subscriptionChanges.push(record);
+      cursor.delete();
+      return;
     }
     record.resetQuota();
-    return record;
+    cursor.update(record);
   },
 };
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -436,36 +436,18 @@ this.PushServiceHttp2 = {
   serviceType: function() {
     return "http2";
   },
 
   hasmainPushService: function() {
     return this._mainPushService !== null;
   },
 
-  checkServerURI: function(serverURL) {
-    if (!serverURL) {
-      console.warn("checkServerURI: No dom.push.serverURL found");
-      return;
-    }
-
-    let uri;
-    try {
-      uri = Services.io.newURI(serverURL, null, null);
-    } catch(e) {
-      console.warn("checkServerURI: Error creating valid URI from",
-        "dom.push.serverURL", serverURL);
-      return null;
-    }
-
-    if (uri.scheme !== "https") {
-      console.warn("checkServerURI: Unsupported scheme", uri.scheme);
-      return null;
-    }
-    return uri;
+  validServerURI: function(serverURI) {
+    return serverURI.scheme == "http" || serverURI.scheme == "https";
   },
 
   connect: function(subscriptions) {
     this.startConnections(subscriptions);
   },
 
   isConnected: function() {
     return this._mainPushService != null;
@@ -853,19 +835,13 @@ function PushRecordHttp2(record) {
 PushRecordHttp2.prototype = Object.create(PushRecord.prototype, {
   keyID: {
     get() {
       return this.subscriptionUri;
     },
   },
 });
 
-PushRecordHttp2.prototype.toRegistration = function() {
-  let registration = PushRecord.prototype.toRegistration.call(this);
-  registration.pushReceiptEndpoint = this.pushReceiptEndpoint;
-  return registration;
+PushRecordHttp2.prototype.toSubscription = function() {
+  let subscription = PushRecord.prototype.toSubscription.call(this);
+  subscription.pushReceiptEndpoint = this.pushReceiptEndpoint;
+  return subscription;
 };
-
-PushRecordHttp2.prototype.toRegister = function() {
-  let register = PushRecord.prototype.toRegister.call(this);
-  register.pushReceiptEndpoint = this.pushReceiptEndpoint;
-  return register;
-};
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -198,36 +198,18 @@ this.PushServiceWebSocket = {
         if (requestTimedOut) {
           this._reconnect();
         }
       }
       break;
     }
   },
 
-  checkServerURI: function(serverURL) {
-    if (!serverURL) {
-      console.warn("checkServerURI: No dom.push.serverURL found");
-      return;
-    }
-
-    let uri;
-    try {
-      uri = Services.io.newURI(serverURL, null, null);
-    } catch(e) {
-      console.warn("checkServerURI: Error creating valid URI from",
-        "dom.push.serverURL", serverURL);
-      return null;
-    }
-
-    if (uri.scheme !== "wss") {
-      console.warn("checkServerURI: Unsupported websocket scheme", uri.scheme);
-      return null;
-    }
-    return uri;
+  validServerURI: function(serverURI) {
+    return serverURI.scheme == "ws" || serverURI.scheme == "wss";
   },
 
   get _UAID() {
     return prefs.get("userAgentID");
   },
 
   set _UAID(newID) {
     if (typeof(newID) !== "string") {
@@ -835,17 +817,17 @@ this.PushServiceWebSocket = {
     // accept.
     //
     // We unconditionally drop all existing registrations and notify service
     // workers if we receive a new UAID. This ensures we expunge all stale
     // registrations if the `userAgentID` pref is reset.
     if (this._UAID != reply.uaid) {
       console.debug("handleHelloReply: Received new UAID");
 
-      this._mainPushService.dropRegistrations()
+      this._mainPushService.dropUnexpiredRegistrations()
           .then(finishHandshake.bind(this));
 
       return;
     }
 
     // otherwise we are good to go
     finishHandshake.bind(this)();
   },
@@ -1467,13 +1449,13 @@ function PushRecordWebSocket(record) {
 PushRecordWebSocket.prototype = Object.create(PushRecord.prototype, {
   keyID: {
     get() {
       return this.channelID;
     },
   },
 });
 
-PushRecordWebSocket.prototype.toRegistration = function() {
-  let registration = PushRecord.prototype.toRegistration.call(this);
-  registration.version = this.version;
-  return registration;
+PushRecordWebSocket.prototype.toSubscription = function() {
+  let subscription = PushRecord.prototype.toSubscription.call(this);
+  subscription.version = this.version;
+  return subscription;
 };
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
+Cu.import('resource://gre/modules/Task.jsm');
 Cu.import('resource://gre/modules/Timer.jsm');
 Cu.import('resource://gre/modules/Promise.jsm');
 Cu.import('resource://gre/modules/Preferences.jsm');
 Cu.import('resource://gre/modules/PlacesUtils.jsm');
 
 const serviceExports = Cu.import('resource://gre/modules/PushService.jsm', {});
 const servicePrefs = new Preferences('dom.push.');
 
new file mode 100644
--- /dev/null
+++ b/dom/push/test/xpcshell/test_drop_expired.js
@@ -0,0 +1,153 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+'use strict';
+
+const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
+
+const userAgentID = '2c43af06-ab6e-476a-adc4-16cbda54fb89';
+
+var db;
+var quotaURI;
+var permURI;
+
+function visitURI(uri, timestamp) {
+  return addVisit({
+    uri: uri,
+    title: uri.spec,
+    visits: [{
+      visitDate: timestamp * 1000,
+      transitionType: Ci.nsINavHistoryService.TRANSITION_LINK,
+    }],
+  });
+}
+
+var putRecord = Task.async(function* ({scope, perm, quota, lastPush, lastVisit}) {
+  let uri = Services.io.newURI(scope, null, null);
+
+  Services.perms.add(uri, 'desktop-notification',
+    Ci.nsIPermissionManager[perm]);
+  do_register_cleanup(() => {
+    Services.perms.remove(uri, 'desktop-notification');
+  });
+
+  yield visitURI(uri, lastVisit);
+
+  yield db.put({
+    channelID: uri.path,
+    pushEndpoint: 'https://example.org/push' + uri.path,
+    scope: uri.spec,
+    pushCount: 0,
+    lastPush: lastPush,
+    version: null,
+    originAttributes: '',
+    quota: quota,
+  });
+
+  return uri;
+});
+
+function run_test() {
+  do_get_profile();
+  setPrefs({
+    userAgentID: userAgentID,
+  });
+
+  db = PushServiceWebSocket.newPushDB();
+  do_register_cleanup(() => {return db.drop().then(_ => db.close());});
+
+  run_next_test();
+}
+
+add_task(function* setUp() {
+  // An expired registration that should be evicted on startup. Permission is
+  // granted for this origin, and the last visit is more recent than the last
+  // push message.
+  yield putRecord({
+    scope: 'https://example.com/expired-quota-restored',
+    perm: 'ALLOW_ACTION',
+    quota: 0,
+    lastPush: Date.now() - 10,
+    lastVisit: Date.now(),
+  });
+
+  // An expired registration that we should evict when the origin is visited
+  // again.
+  quotaURI = yield putRecord({
+    scope: 'https://example.xyz/expired-quota-exceeded',
+    perm: 'ALLOW_ACTION',
+    quota: 0,
+    lastPush: Date.now() - 10,
+    lastVisit: Date.now() - 20,
+  });
+
+  // An expired registration that we should evict when permission is granted
+  // again.
+  permURI = yield putRecord({
+    scope: 'https://example.info/expired-perm-revoked',
+    perm: 'DENY_ACTION',
+    quota: 0,
+    lastPush: Date.now() - 10,
+    lastVisit: Date.now(),
+  });
+
+  // An active registration that we should leave alone.
+  yield putRecord({
+    scope: 'https://example.ninja/active',
+    perm: 'ALLOW_ACTION',
+    quota: 16,
+    lastPush: Date.now() - 10,
+    lastVisit: Date.now() - 20,
+  });
+
+  let subChangePromise = promiseObserverNotification(
+    'push-subscription-change',
+    (subject, data) => data == 'https://example.com/expired-quota-restored'
+  );
+
+  PushService.init({
+    serverURI: 'wss://push.example.org/',
+    networkInfo: new MockDesktopNetworkInfo(),
+    db,
+    makeWebSocket(uri) {
+      return new MockWebSocket(uri, {
+        onHello(request) {
+          this.serverSendMsg(JSON.stringify({
+            messageType: 'hello',
+            status: 200,
+            uaid: userAgentID,
+          }));
+        },
+      });
+    },
+  });
+
+  yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for subscription change event on startup');
+});
+
+add_task(function* test_site_visited() {
+  let subChangePromise = promiseObserverNotification(
+    'push-subscription-change',
+    (subject, data) => data == 'https://example.xyz/expired-quota-exceeded'
+  );
+
+  yield visitURI(quotaURI, Date.now());
+  PushService.observe(null, 'idle-daily', '');
+
+  yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for subscription change event after visit');
+});
+
+add_task(function* test_perm_restored() {
+  let subChangePromise = promiseObserverNotification(
+    'push-subscription-change',
+    (subject, data) => data == 'https://example.info/expired-perm-revoked'
+  );
+
+  Services.perms.add(permURI, 'desktop-notification',
+    Ci.nsIPermissionManager.ALLOW_ACTION);
+
+  yield waitForPromise(subChangePromise, DEFAULT_TIMEOUT,
+    'Timed out waiting for subscription change event after permission');
+});
--- a/dom/push/test/xpcshell/test_notification_incomplete.js
+++ b/dom/push/test/xpcshell/test_notification_incomplete.js
@@ -1,18 +1,22 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
 
+const userAgentID = '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90';
+
 function run_test() {
   do_get_profile();
-  setPrefs();
+  setPrefs({
+    userAgentID: userAgentID,
+  });
   disableServiceWorkerEvents(
     'https://example.com/page/1',
     'https://example.com/page/2',
     'https://example.com/page/3',
     'https://example.com/page/4'
   );
   run_next_test();
 }
@@ -69,17 +73,17 @@ add_task(function* test_notification_inc
     networkInfo: new MockDesktopNetworkInfo(),
     db,
     makeWebSocket(uri) {
       return new MockWebSocket(uri, {
         onHello(request) {
           this.serverSendMsg(JSON.stringify({
             messageType: 'hello',
             status: 200,
-            uaid: '1ca1cf66-eeb4-4df7-87c1-d5c92906ab90'
+            uaid: userAgentID,
           }));
           this.serverSendMsg(JSON.stringify({
             // Missing "updates" field; should ignore message.
             messageType: 'notification'
           }));
           this.serverSendMsg(JSON.stringify({
             messageType: 'notification',
             updates: [{
--- a/dom/push/test/xpcshell/test_quota_observer.js
+++ b/dom/push/test/xpcshell/test_quota_observer.js
@@ -2,42 +2,56 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
 
 const userAgentID = '28cd09e2-7506-42d8-9e50-b02785adc7ef';
 
+var db;
+
 function run_test() {
   do_get_profile();
   setPrefs({
     userAgentID,
   });
   run_next_test();
 }
 
+let putRecord = Task.async(function* (perm, record) {
+  let uri = Services.io.newURI(record.scope, null, null);
+
+  Services.perms.add(uri, 'desktop-notification',
+    Ci.nsIPermissionManager[perm]);
+  do_register_cleanup(() => {
+    Services.perms.remove(uri, 'desktop-notification');
+  });
+
+  yield db.put(record);
+});
+
 add_task(function* test_expiration_history_observer() {
-  let db = PushServiceWebSocket.newPushDB();
+  db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => db.drop().then(_ => db.close()));
 
   // A registration that we'll expire...
-  yield db.put({
+  yield putRecord('ALLOW_ACTION', {
     channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9',
     pushEndpoint: 'https://example.org/push/1',
     scope: 'https://example.com/deals',
     pushCount: 0,
     lastPush: 0,
     version: null,
     originAttributes: '',
     quota: 16,
   });
 
   // ...And a registration that we'll evict on startup.
-  yield db.put({
+  yield putRecord('ALLOW_ACTION', {
     channelID: '4cb6e454-37cf-41c4-a013-4e3a7fdd0bf1',
     pushEndpoint: 'https://example.org/push/3',
     scope: 'https://example.com/stuff',
     pushCount: 0,
     lastPush: 0,
     version: null,
     originAttributes: '',
     quota: 0,
@@ -96,17 +110,17 @@ add_task(function* test_expiration_histo
 
   let notifiedScopes = [];
   subChangePromise = promiseObserverNotification('push-subscription-change', (subject, data) => {
     notifiedScopes.push(data);
     return notifiedScopes.length == 2;
   });
 
   // Add an expired registration that we'll revive later.
-  yield db.put({
+  yield putRecord('ALLOW_ACTION', {
     channelID: 'eb33fc90-c883-4267-b5cb-613969e8e349',
     pushEndpoint: 'https://example.org/push/2',
     scope: 'https://example.com/auctions',
     pushCount: 0,
     lastPush: 0,
     version: null,
     originAttributes: '',
     quota: 0,
--- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js
@@ -74,17 +74,16 @@ add_task(function* test1() {
   do_test_pending();
   do_test_pending();
   do_test_pending();
 
   var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
 
   PushService.init({
     serverURI: serverURL + "/subscribe5xxCode",
-    service: PushServiceHttp2,
     db
   });
 
   let newRecord = yield PushNotificationService.register(
     'https://example.com/retry5xxCode',
     ChromeUtils.originAttributesToSuffix({ appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inBrowser: false })
   );
 
--- a/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_resubscribe_4xxCode_http2.js
@@ -78,13 +78,12 @@ add_task(function* test1() {
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL + "/subscribe",
-    service: PushServiceHttp2,
     db
   });
 
 });
--- a/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_resubscribe_5xxCode_http2.js
@@ -88,13 +88,12 @@ add_task(function* test1() {
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL + "/subscribe",
-    service: PushServiceHttp2,
     db
   });
 
 });
--- a/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js
+++ b/dom/push/test/xpcshell/test_resubscribe_listening_for_msg_error_http2.js
@@ -83,13 +83,12 @@ add_task(function* test1() {
   }];
 
   for (let record of records) {
     yield db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL + "/subscribe",
-    service: PushServiceHttp2,
     db
   });
 
 });
--- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_http2.js
@@ -61,17 +61,16 @@ add_task(function* test1() {
 
   yield db.put(record);
 
   let notifyPromise = promiseObserverNotification('push-subscription-change',
                                                   _ => true);
 
   PushService.init({
     serverURI: serverURL + "/subscribe",
-    service: PushServiceHttp2,
     db
   });
 
   yield waitForPromise(notifyPromise, DEFAULT_TIMEOUT,
     'Timed out waiting for notifications');
 
   let aRecord = yield db.getByKeyID(serverURL + '/subscriptionNoKey');
   ok(aRecord, 'The record should still be there');
--- a/dom/push/test/xpcshell/xpcshell.ini
+++ b/dom/push/test/xpcshell/xpcshell.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 head = head.js head-http2.js
 tail =
 # Push notifications and alarms are currently disabled on Android.
 skip-if = toolkit == 'android'
 
+[test_drop_expired.js]
 [test_notification_ack.js]
 [test_notification_data.js]
 [test_notification_duplicate.js]
 [test_notification_error.js]
 [test_notification_incomplete.js]
 [test_notification_version_string.js]
 
 [test_permissions.js]
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -26,21 +26,21 @@
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsIStringStream.h"
 #include "nsIUploadChannel.h"
 #include "nsIScriptError.h"
 #include "nsIWebNavigation.h"
 #include "nsMimeTypes.h"
 #include "nsNetUtil.h"
-#include "nsNullPrincipal.h"
 #include "nsIContentPolicy.h"
 #include "nsSupportsPrimitives.h"
 #include "nsThreadUtils.h"
 #include "nsString.h"
+#include "nsScriptSecurityManager.h"
 #include "nsStringStream.h"
 #include "mozilla/Logging.h"
 #include "mozilla/dom/CSPReportBinding.h"
 #include "mozilla/dom/CSPDictionariesBinding.h"
 #include "mozilla/net/ReferrerPolicy.h"
 #include "nsINetworkInterceptController.h"
 
 using namespace mozilla;
@@ -260,17 +260,20 @@ NS_IMPL_CLASSINFO(nsCSPContext,
                   nsIClassInfo::MAIN_THREAD_ONLY,
                   NS_CSPCONTEXT_CID)
 
 NS_IMPL_ISUPPORTS_CI(nsCSPContext,
                      nsIContentSecurityPolicy,
                      nsISerializable)
 
 nsCSPContext::nsCSPContext()
-  : mSelfURI(nullptr)
+  : mInnerWindowID(0)
+  , mLoadingContext(nullptr)
+  , mLoadingPrincipal(nullptr)
+  , mQueueUpMessages(true)
 {
   CSPCONTEXTLOG(("nsCSPContext::nsCSPContext"));
 }
 
 nsCSPContext::~nsCSPContext()
 {
   CSPCONTEXTLOG(("nsCSPContext::~nsCSPContext"));
   for (uint32_t i = 0; i < mPolicies.Length(); i++) {
@@ -333,37 +336,25 @@ nsCSPContext::GetReferrerPolicy(uint32_t
       *outIsSet = true;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsCSPContext::RemovePolicy(uint32_t aIndex)
-{
-  if (aIndex >= mPolicies.Length()) {
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
-  mPolicies.RemoveElementAt(aIndex);
-  // reset cache since effective policy changes
-  mShouldLoadCache.Clear();
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsCSPContext::AppendPolicy(const nsAString& aPolicyString,
                            bool aReportOnly)
 {
   CSPCONTEXTLOG(("nsCSPContext::AppendPolicy: %s",
                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
 
   // Use the mSelfURI from setRequestContext, see bug 991474
   NS_ASSERTION(mSelfURI, "mSelfURI required for AppendPolicy, but not set");
-  nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI, aReportOnly, mInnerWindowID);
+  nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(aPolicyString, mSelfURI, aReportOnly, this);
   if (policy) {
     mPolicies.AppendElement(policy);
     // reset cache since effective policy changes
     mShouldLoadCache.Clear();
   }
   return NS_OK;
 }
 
@@ -585,63 +576,102 @@ nsCSPContext::LogViolationDetails(uint16
     }
   }
   return NS_OK;
 }
 
 #undef CASE_CHECK_AND_REPORT
 
 NS_IMETHODIMP
-nsCSPContext::SetRequestContext(nsIURI* aSelfURI,
-                                nsIURI* aReferrer,
-                                nsIChannel* aChannel)
+nsCSPContext::SetRequestContext(nsIDOMDocument* aDOMDocument,
+                                nsIPrincipal* aPrincipal)
 {
-  NS_PRECONDITION(aSelfURI || aChannel, "Need aSelfURI or aChannel to set the context properly");
-  NS_ENSURE_ARG(aSelfURI || aChannel);
+  NS_PRECONDITION(aDOMDocument || aPrincipal,
+                  "Can't set context without doc or principal");
+  NS_ENSURE_ARG(aDOMDocument || aPrincipal);
 
-  // first use aSelfURI.  If that's not available get the URI from aChannel.
-  mSelfURI = aSelfURI;
-  if (!mSelfURI) {
-    nsresult rv = aChannel->GetURI(getter_AddRefs(mSelfURI));
-    NS_ENSURE_SUCCESS(rv, rv);
+  if (aDOMDocument) {
+    nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDOMDocument);
+    mLoadingContext = do_GetWeakReference(doc);
+    mSelfURI = doc->GetDocumentURI();
+    doc->GetReferrer(mReferrer);
+    mInnerWindowID = doc->InnerWindowID();
+    // the innerWindowID is not available for CSPs delivered through the
+    // header at the time setReqeustContext is called - let's queue up
+    // console messages until it becomes available, see flushConsoleMessages
+    mQueueUpMessages = !mInnerWindowID;
+    mCallingChannelLoadGroup = doc->GetDocumentLoadGroup();
+  }
+  else {
+    NS_WARNING("No Document in SetRequestContext; can not query loadgroup; sending reports may fail.");
+    mLoadingPrincipal = aPrincipal;
+    mLoadingPrincipal->GetURI(getter_AddRefs(mSelfURI));
+    // if no document is available, then it also does not make sense to queue console messages
+    // sending messages to the browser conolse instead of the web console in that case.
+    mQueueUpMessages = false;
   }
 
-  NS_ASSERTION(mSelfURI, "No aSelfURI and no URI available from channel in SetRequestContext, can not translate 'self' into actual URI");
+  NS_ASSERTION(mSelfURI, "mSelfURI not available, can not translate 'self' into actual URI");
+  return NS_OK;
+}
 
-  if (aChannel) {
-    mInnerWindowID = nsContentUtils::GetInnerWindowID(aChannel);
-    aChannel->GetLoadGroup(getter_AddRefs(mCallingChannelLoadGroup));
+struct ConsoleMsgQueueElem {
+  nsXPIDLString mMsg;
+  nsString      mSourceName;
+  nsString      mSourceLine;
+  uint32_t      mLineNumber;
+  uint32_t      mColumnNumber;
+  uint32_t      mSeverityFlag;
+};
 
-    // Storing the nsINode from the LoadInfo of the original channel,
-    // so we can reuse that information when sending reports.
-    nsCOMPtr<nsILoadInfo> loadInfo;
-    aChannel->GetLoadInfo(getter_AddRefs(loadInfo));
-    if (loadInfo) {
-      nsINode* loadingNode = loadInfo->LoadingNode();
-      if (loadingNode) {
-        mLoadingContext = do_GetWeakReference(loadingNode);
-      }
-    }
+void
+nsCSPContext::flushConsoleMessages()
+{
+  // should flush messages even if doc is not available
+  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
+  if (doc) {
+    mInnerWindowID = doc->InnerWindowID();
+  }
+  mQueueUpMessages = false;
+
+  for (uint32_t i = 0; i < mConsoleMsgQueue.Length(); i++) {
+    ConsoleMsgQueueElem &elem = mConsoleMsgQueue[i];
+    CSP_LogMessage(elem.mMsg, elem.mSourceName, elem.mSourceLine,
+                   elem.mLineNumber, elem.mColumnNumber,
+                   elem.mSeverityFlag, "CSP", mInnerWindowID);
   }
-  else {
-    NS_WARNING("Channel needed (but null) in SetRequestContext.  Cannot query loadgroup, which means report sending may fail.");
-  }
+  mConsoleMsgQueue.Clear();
+}
 
-  mReferrer = aReferrer;
-  if (!mReferrer) {
-    nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel));
-    if (httpChannel) {
-      httpChannel->GetReferrer(getter_AddRefs(mReferrer));
-    }
-    else {
-      NS_WARNING("Channel provided to SetRequestContext is not an nsIHttpChannel so referrer is not available for reporting." );
-    }
+void
+nsCSPContext::logToConsole(const char16_t* aName,
+                           const char16_t** aParams,
+                           uint32_t aParamsLength,
+                           const nsAString& aSourceName,
+                           const nsAString& aSourceLine,
+                           uint32_t aLineNumber,
+                           uint32_t aColumnNumber,
+                           uint32_t aSeverityFlag)
+{
+  // let's check if we have to queue up console messages
+  if (mQueueUpMessages) {
+    nsXPIDLString msg;
+    CSP_GetLocalizedStr(aName, aParams, aParamsLength, getter_Copies(msg));
+    ConsoleMsgQueueElem &elem = *mConsoleMsgQueue.AppendElement();
+    elem.mMsg = msg;
+    elem.mSourceName = PromiseFlatString(aSourceName);
+    elem.mSourceLine = PromiseFlatString(aSourceLine);
+    elem.mLineNumber = aLineNumber;
+    elem.mColumnNumber = aColumnNumber;
+    elem.mSeverityFlag = aSeverityFlag;
+    return;
   }
-
-  return NS_OK;
+  CSP_LogLocalizedStr(aName, aParams, aParamsLength, aSourceName,
+                      aSourceLine, aLineNumber, aColumnNumber,
+                      aSeverityFlag, "CSP", mInnerWindowID);
 }
 
 /**
  * Sends CSP violation reports to all sources listed under report-uri.
  *
  * @param aBlockedContentSource
  *        Either a CSP Source (like 'self', as string) or nsIURI: the source
  *        of the violation.
@@ -715,20 +745,18 @@ nsCSPContext::SendReports(nsISupports* a
 
   // original-policy
   nsAutoString originalPolicy;
   rv = this->GetPolicy(aViolatedPolicyIndex, originalPolicy);
   NS_ENSURE_SUCCESS(rv, rv);
   report.mCsp_report.mOriginal_policy = originalPolicy;
 
   // referrer
-  if (mReferrer) {
-    nsAutoCString referrerURI;
-    mReferrer->GetSpec(referrerURI);
-    report.mCsp_report.mReferrer = NS_ConvertUTF8toUTF16(referrerURI);
+  if (!mReferrer.IsEmpty()) {
+    report.mCsp_report.mReferrer = mReferrer;
   }
 
   // violated-directive
   report.mCsp_report.mViolated_directive = aViolatedDirective;
 
   // source-file
   if (!aSourceFile.IsEmpty()) {
     // if aSourceFile is a URI, we have to make sure to strip fragments
@@ -760,130 +788,92 @@ nsCSPContext::SendReports(nsISupports* a
     return NS_ERROR_FAILURE;
   }
 
   // ---------- Assembled, now send it to all the report URIs ----------- //
 
   nsTArray<nsString> reportURIs;
   mPolicies[aViolatedPolicyIndex]->getReportURIs(reportURIs);
 
+
+  nsCOMPtr<nsIDocument> doc = do_QueryReferent(mLoadingContext);
   nsCOMPtr<nsIURI> reportURI;
   nsCOMPtr<nsIChannel> reportChannel;
 
-  nsCOMPtr<nsIDOMNode> loadingContext = do_QueryReferent(mLoadingContext);
-  nsCOMPtr<nsINode> loadingNode = do_QueryInterface(loadingContext);
-
   for (uint32_t r = 0; r < reportURIs.Length(); r++) {
     nsAutoCString reportURICstring = NS_ConvertUTF16toUTF8(reportURIs[r]);
     // try to create a new uri from every report-uri string
     rv = NS_NewURI(getter_AddRefs(reportURI), reportURIs[r]);
     if (NS_FAILED(rv)) {
       const char16_t* params[] = { reportURIs[r].get() };
       CSPCONTEXTLOG(("Could not create nsIURI for report URI %s",
                      reportURICstring.get()));
-      CSP_LogLocalizedStr(MOZ_UTF16("triedToSendReport"),
-                          params, ArrayLength(params),
-                          aSourceFile, aScriptSample, aLineNum, 0,
-                          nsIScriptError::errorFlag, "CSP", mInnerWindowID);
+      logToConsole(MOZ_UTF16("triedToSendReport"), params, ArrayLength(params),
+                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
       continue; // don't return yet, there may be more URIs
     }
 
-    nsIDocShell* docShell = nullptr;
-
     // try to create a new channel for every report-uri
-    if (loadingNode) {
-      nsIDocument* doc = loadingNode->OwnerDoc();
-      if (doc) {
-        docShell = doc->GetDocShell();
-      }
+    if (doc) {
       rv = NS_NewChannel(getter_AddRefs(reportChannel),
                          reportURI,
-                         loadingNode,
-                         nsILoadInfo::SEC_NORMAL,
+                         doc,
+                         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                          nsIContentPolicy::TYPE_CSP_REPORT);
     }
     else {
-      nsCOMPtr<nsIPrincipal> nullPrincipal = nsNullPrincipal::Create();
-      NS_ENSURE_TRUE(nullPrincipal, NS_ERROR_FAILURE);
       rv = NS_NewChannel(getter_AddRefs(reportChannel),
                          reportURI,
-                         nullPrincipal,
-                         nsILoadInfo::SEC_NORMAL,
+                         mLoadingPrincipal,
+                         nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                          nsIContentPolicy::TYPE_CSP_REPORT);
     }
 
     if (NS_FAILED(rv)) {
       CSPCONTEXTLOG(("Could not create new channel for report URI %s",
                      reportURICstring.get()));
       continue; // don't return yet, there may be more URIs
     }
 
     // log a warning to console if scheme is not http or https
     bool isHttpScheme =
       (NS_SUCCEEDED(reportURI->SchemeIs("http", &isHttpScheme)) && isHttpScheme) ||
       (NS_SUCCEEDED(reportURI->SchemeIs("https", &isHttpScheme)) && isHttpScheme);
 
     if (!isHttpScheme) {
       const char16_t* params[] = { reportURIs[r].get() };
-      CSP_LogLocalizedStr(MOZ_UTF16("reportURInotHttpsOrHttp2"),
-                          params, ArrayLength(params),
-                          aSourceFile, aScriptSample, aLineNum, 0,
-                          nsIScriptError::errorFlag, "CSP", mInnerWindowID);
+      logToConsole(MOZ_UTF16("reportURInotHttpsOrHttp2"), params, ArrayLength(params),
+                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
     }
 
     // make sure this is an anonymous request (no cookies) so in case the
     // policy URI is injected, it can't be abused for CSRF.
     nsLoadFlags flags;
     rv = reportChannel->GetLoadFlags(&flags);
     NS_ENSURE_SUCCESS(rv, rv);
     flags |= nsIRequest::LOAD_ANONYMOUS;
     rv = reportChannel->SetLoadFlags(flags);
     NS_ENSURE_SUCCESS(rv, rv);
 
     // we need to set an nsIChannelEventSink on the channel object
     // so we can tell it to not follow redirects when posting the reports
     RefPtr<CSPReportRedirectSink> reportSink = new CSPReportRedirectSink();
-    if (docShell) {
-      nsCOMPtr<nsINetworkInterceptController> interceptController = do_QueryInterface(docShell);
+    if (doc && doc->GetDocShell()) {
+      nsCOMPtr<nsINetworkInterceptController> interceptController =
+        do_QueryInterface(doc->GetDocShell());
       reportSink->SetInterceptController(interceptController);
     }
     reportChannel->SetNotificationCallbacks(reportSink);
 
     // apply the loadgroup from the channel taken by setRequestContext.  If
     // there's no loadgroup, AsyncOpen will fail on process-split necko (since
     // the channel cannot query the iTabChild).
     rv = reportChannel->SetLoadGroup(mCallingChannelLoadGroup);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    // check content policy
-    int16_t shouldLoad = nsIContentPolicy::ACCEPT;
-    nsCOMPtr<nsIContentPolicy> cp = do_GetService(NS_CONTENTPOLICY_CONTRACTID);
-    if (!cp) {
-      return NS_ERROR_FAILURE;
-    }
-
-    rv = cp->ShouldLoad(nsIContentPolicy::TYPE_CSP_REPORT,
-                        reportURI,
-                        mSelfURI,
-                        nullptr,        // Context
-                        EmptyCString(), // mime type
-                        nullptr,        // Extra parameter
-                        nullptr,        // optional request principal
-                        &shouldLoad);
-
-    // refuse to load if we can't do a security check
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (NS_CP_REJECTED(shouldLoad)) {
-      // skip unauthorized URIs
-      CSPCONTEXTLOG(("nsIContentPolicy blocked sending report to %s",
-                     reportURICstring.get()));
-      continue; // don't return yet, there may be more URIs
-    }
-
     // wire in the string input stream to send the report
     nsCOMPtr<nsIStringInputStream> sis(do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID));
     NS_ASSERTION(sis, "nsIStringInputStream is needed but not available to send CSP violation reports");
     rv = sis->SetData(NS_ConvertUTF16toUTF8(csp_report).get(), csp_report.Length());
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(reportChannel));
     NS_ASSERTION(uploadChannel, "nsIUploadChannel is needed but not available to send CSP violation reports");
@@ -892,30 +882,28 @@ nsCSPContext::SendReports(nsISupports* a
 
     // if this is an HTTP channel, set the request method to post
     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(reportChannel));
     if (httpChannel) {
       httpChannel->SetRequestMethod(NS_LITERAL_CSTRING("POST"));
     }
 
     RefPtr<CSPViolationReportListener> listener = new CSPViolationReportListener();
-    rv = reportChannel->AsyncOpen(listener, nullptr);
+    rv = reportChannel->AsyncOpen2(listener);
 
     // AsyncOpen should not fail, but could if there's no load group (like if
     // SetRequestContext is not given a channel).  This should fail quietly and
     // not return an error since it's really ok if reports don't go out, but
     // it's good to log the error locally.
 
     if (NS_FAILED(rv)) {
       const char16_t* params[] = { reportURIs[r].get() };
       CSPCONTEXTLOG(("AsyncOpen failed for report URI %s", params[0]));
-      CSP_LogLocalizedStr(MOZ_UTF16("triedToSendReport"),
-                          params, ArrayLength(params),
-                          aSourceFile, aScriptSample, aLineNum, 0,
-                          nsIScriptError::errorFlag, "CSP", mInnerWindowID);
+      logToConsole(MOZ_UTF16("triedToSendReport"), params, ArrayLength(params),
+                   aSourceFile, aScriptSample, aLineNum, 0, nsIScriptError::errorFlag);
     } else {
       CSPCONTEXTLOG(("Sent violation report to URI %s", reportURICstring.get()));
     }
   }
   return NS_OK;
 }
 
 /**
@@ -928,27 +916,25 @@ class CSPReportSenderRunnable final : pu
                             nsIURI* aOriginalURI,
                             uint32_t aViolatedPolicyIndex,
                             bool aReportOnlyFlag,
                             const nsAString& aViolatedDirective,
                             const nsAString& aObserverSubject,
                             const nsAString& aSourceFile,
                             const nsAString& aScriptSample,
                             uint32_t aLineNum,
-                            uint64_t aInnerWindowID,
                             nsCSPContext* aCSPContext)
       : mBlockedContentSource(aBlockedContentSource)
       , mOriginalURI(aOriginalURI)
       , mViolatedPolicyIndex(aViolatedPolicyIndex)
       , mReportOnlyFlag(aReportOnlyFlag)
       , mViolatedDirective(aViolatedDirective)
       , mSourceFile(aSourceFile)
       , mScriptSample(aScriptSample)
       , mLineNum(aLineNum)
-      , mInnerWindowID(aInnerWindowID)
       , mCSPContext(aCSPContext)
     {
       NS_ASSERTION(!aViolatedDirective.IsEmpty(), "Can not send reports without a violated directive");
       // the observer subject is an nsISupports: either an nsISupportsCString
       // from the arg passed in directly, or if that's empty, it's the blocked
       // source.
       if (aObserverSubject.IsEmpty()) {
         mObserverSubject = aBlockedContentSource;
@@ -991,38 +977,35 @@ class CSPReportSenderRunnable final : pu
       } else if (blockedString) {
         blockedString->GetData(blockedDataStr);
       }
 
       if (blockedDataStr.Length() > 0) {
         nsString blockedDataChar16 = NS_ConvertUTF8toUTF16(blockedDataStr);
         const char16_t* params[] = { mViolatedDirective.get(),
                                      blockedDataChar16.get() };
-
-        CSP_LogLocalizedStr(mReportOnlyFlag ? MOZ_UTF16("CSPROViolationWithURI") :
-                                              MOZ_UTF16("CSPViolationWithURI"),
-                            params, ArrayLength(params),
-                            mSourceFile, mScriptSample, mLineNum, 0,
-                            nsIScriptError::errorFlag, "CSP", mInnerWindowID);
+        mCSPContext->logToConsole(mReportOnlyFlag ? MOZ_UTF16("CSPROViolationWithURI") :
+                                                    MOZ_UTF16("CSPViolationWithURI"),
+                                  params, ArrayLength(params), mSourceFile, mScriptSample,
+                                  mLineNum, 0, nsIScriptError::errorFlag);
       }
       return NS_OK;
     }
 
   private:
     nsCOMPtr<nsISupports>   mBlockedContentSource;
     nsCOMPtr<nsIURI>        mOriginalURI;
     uint32_t                mViolatedPolicyIndex;
     bool                    mReportOnlyFlag;
     nsString                mViolatedDirective;
     nsCOMPtr<nsISupports>   mObserverSubject;
     nsString                mSourceFile;
     nsString                mScriptSample;
     uint32_t                mLineNum;
-    uint64_t                mInnerWindowID;
-    RefPtr<nsCSPContext>  mCSPContext;
+    RefPtr<nsCSPContext>    mCSPContext;
 };
 
 /**
  * Asynchronously notifies any nsIObservers listening to the CSP violation
  * topic that a violation occurred.  Also triggers report sending and console
  * logging.  All asynchronous on the main thread.
  *
  * @param aBlockedContentSource
@@ -1061,17 +1044,16 @@ nsCSPContext::AsyncReportViolation(nsISu
                                                       aOriginalURI,
                                                       aViolatedPolicyIndex,
                                                       mPolicies[aViolatedPolicyIndex]->getReportOnlyFlag(),
                                                       aViolatedDirective,
                                                       aObserverSubject,
                                                       aSourceFile,
                                                       aScriptSample,
                                                       aLineNum,
-                                                      mInnerWindowID,
                                                       this));
    return NS_OK;
 }
 
 /**
  * Based on the given docshell, determines if this CSP context allows the
  * ancestry.
  *
@@ -1369,17 +1351,17 @@ nsCSPContext::Read(nsIObjectInputStream*
 
     bool reportOnly = false;
     rv = aStream->ReadBoolean(&reportOnly);
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCSPPolicy* policy = nsCSPParser::parseContentSecurityPolicy(policyString,
                                                                   mSelfURI,
                                                                   reportOnly,
-                                                                  mInnerWindowID);
+                                                                  this);
     if (policy) {
       mPolicies.AppendElement(policy);
     }
   }
 
   return NS_OK;
 }
 
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -21,30 +21,47 @@
 
 #define NS_CSPCONTEXT_CONTRACTID "@mozilla.org/cspcontext;1"
  // 09d9ed1a-e5d4-4004-bfe0-27ceb923d9ac
 #define NS_CSPCONTEXT_CID \
 { 0x09d9ed1a, 0xe5d4, 0x4004, \
   { 0xbf, 0xe0, 0x27, 0xce, 0xb9, 0x23, 0xd9, 0xac } }
 
 class nsINetworkInterceptController;
+struct ConsoleMsgQueueElem;
 
 class nsCSPContext : public nsIContentSecurityPolicy
 {
   public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSICONTENTSECURITYPOLICY
     NS_DECL_NSISERIALIZABLE
 
   protected:
     virtual ~nsCSPContext();
 
   public:
     nsCSPContext();
 
+    /**
+     * SetRequestContext() needs to be called before the innerWindowID
+     * is initialized on the document. Use this function to call back to
+     * flush queued up console messages and initalize the innerWindowID.
+     */
+    void flushConsoleMessages();
+
+    void logToConsole(const char16_t* aName,
+                      const char16_t** aParams,
+                      uint32_t aParamsLength,
+                      const nsAString& aSourceName,
+                      const nsAString& aSourceLine,
+                      uint32_t aLineNumber,
+                      uint32_t aColumnNumber,
+                      uint32_t aSeverityFlag);
+
     nsresult SendReports(nsISupports* aBlockedContentSource,
                          nsIURI* aOriginalURI,
                          nsAString& aViolatedDirective,
                          uint32_t aViolatedPolicyIndex,
                          nsAString& aSourceFile,
                          nsAString& aScriptSample,
                          uint32_t aLineNum);
 
@@ -52,16 +69,23 @@ class nsCSPContext : public nsIContentSe
                                   nsIURI* aOriginalURI,
                                   const nsAString& aViolatedDirective,
                                   uint32_t aViolatedPolicyIndex,
                                   const nsAString& aObserverSubject,
                                   const nsAString& aSourceFile,
                                   const nsAString& aScriptSample,
                                   uint32_t aLineNum);
 
+    // Hands off! Don't call this method unless you know what you
+    // are doing. It's only supposed to be called from within
+    // the principal destructor to avoid a tangling pointer.
+    void clearLoadingPrincipal() {
+      mLoadingPrincipal = nullptr;
+    }
+
   private:
     bool permitsInternal(CSPDirective aDir,
                          nsIURI* aContentLocation,
                          nsIURI* aOriginalURI,
                          const nsAString& aNonce,
                          bool aWasRedirected,
                          bool aIsPreload,
                          bool aSpecific,
@@ -71,23 +95,32 @@ class nsCSPContext : public nsIContentSe
     // helper to report inline script/style violations
     void reportInlineViolation(nsContentPolicyType aContentType,
                                const nsAString& aNonce,
                                const nsAString& aContent,
                                const nsAString& aViolatedDirective,
                                uint32_t aViolatedPolicyIndex,
                                uint32_t aLineNumber);
 
-    nsCOMPtr<nsIURI>                           mReferrer;
+    nsString                                   mReferrer;
     uint64_t                                   mInnerWindowID; // used for web console logging
     nsTArray<nsCSPPolicy*>                     mPolicies;
     nsCOMPtr<nsIURI>                           mSelfURI;
     nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
     nsCOMPtr<nsILoadGroup>                     mCallingChannelLoadGroup;
     nsWeakPtr                                  mLoadingContext;
+    // The CSP hangs off the principal, so let's store a raw pointer of the principal
+    // to avoid memory leaks. Within the destructor of the principal we explicitly
+    // set mLoadingPrincipal to null.
+    nsIPrincipal*                              mLoadingPrincipal;
+
+    // helper members used to queue up web console messages till
+    // the windowID becomes available. see flushConsoleMessages()
+    nsTArray<ConsoleMsgQueueElem>              mConsoleMsgQueue;
+    bool                                       mQueueUpMessages;
 };
 
 // Class that listens to violation report transmission and logs errors.
 class CSPViolationReportListener : public nsIStreamListener
 {
   public:
     NS_DECL_NSISTREAMLISTENER
     NS_DECL_NSIREQUESTOBSERVER
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -117,24 +117,24 @@ nsCSPTokenizer::tokenizeCSPPolicy(const 
 
   tokenizer.generateTokens(outTokens);
 }
 
 /* ===== nsCSPParser ==================== */
 
 nsCSPParser::nsCSPParser(cspTokens& aTokens,
                          nsIURI* aSelfURI,
-                         uint64_t aInnerWindowID)
+                         nsCSPContext* aCSPContext)
  : mHasHashOrNonce(false)
  , mUnsafeInlineKeywordSrc(nullptr)
  , mChildSrc(nullptr)
  , mFrameSrc(nullptr)
  , mTokens(aTokens)
  , mSelfURI(aSelfURI)
- , mInnerWindowID(aInnerWindowID)
+ , mCSPContext(aCSPContext)
 {
   CSPPARSERLOG(("nsCSPParser::nsCSPParser"));
 }
 
 nsCSPParser::~nsCSPParser()
 {
   CSPPARSERLOG(("nsCSPParser::~nsCSPParser"));
 }
@@ -291,26 +291,26 @@ nsCSPParser::atValidPathChar()
 
 void
 nsCSPParser::logWarningErrorToConsole(uint32_t aSeverityFlag,
                                       const char* aProperty,
                                       const char16_t* aParams[],
                                       uint32_t aParamsLength)
 {
   CSPPARSERLOG(("nsCSPParser::logWarningErrorToConsole: %s", aProperty));
-
-  nsXPIDLString logMsg;
-  CSP_GetLocalizedStr(NS_ConvertUTF8toUTF16(aProperty).get(),
-                      aParams,
-                      aParamsLength,
-                      getter_Copies(logMsg));
-
-  CSP_LogMessage(logMsg, EmptyString(), EmptyString(),
-                 0, 0, aSeverityFlag,
-                 "CSP", mInnerWindowID);
+  // send console messages off to the context and let the context
+  // deal with it (potentially messages need to be queued up)
+  mCSPContext->logToConsole(NS_ConvertUTF8toUTF16(aProperty).get(),
+                            aParams,
+                            aParamsLength,
+                            EmptyString(), // aSourceName
+                            EmptyString(), // aSourceLine
+                            0,             // aLineNumber
+                            0,             // aColumnNumber
+                            aSeverityFlag); // aFlags
 }
 
 bool
 nsCSPParser::hostChar()
 {
   if (atEnd()) {
     return false;
   }
@@ -1111,17 +1111,17 @@ nsCSPParser::policy()
 
   return mPolicy;
 }
 
 nsCSPPolicy*
 nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
                                         nsIURI *aSelfURI,
                                         bool aReportOnly,
-                                        uint64_t aInnerWindowID)
+                                        nsCSPContext* aCSPContext)
 {
   if (CSPPARSERLOGENABLED()) {
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, policy: %s",
                  NS_ConvertUTF16toUTF8(aPolicyString).get()));
     nsAutoCString spec;
     aSelfURI->GetSpec(spec);
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, selfURI: %s", spec.get()));
     CSPPARSERLOG(("nsCSPParser::parseContentSecurityPolicy, reportOnly: %s",
@@ -1133,17 +1133,17 @@ nsCSPParser::parseContentSecurityPolicy(
   // Separate all input into tokens and store them in the form of:
   // [ [ name, src, src, ... ], [ name, src, src, ... ], ... ]
   // The tokenizer itself can not fail; all eventual errors
   // are detected in the parser itself.
 
   nsTArray< nsTArray<nsString> > tokens;
   nsCSPTokenizer::tokenizeCSPPolicy(aPolicyString, tokens);
 
-  nsCSPParser parser(tokens, aSelfURI, aInnerWindowID);
+  nsCSPParser parser(tokens, aSelfURI, aCSPContext);
 
   // Start the parser to generate a new CSPPolicy using the generated tokens.
   nsCSPPolicy* policy = parser.policy();
 
   // Check that report-only policies define a report-uri, otherwise log warning.
   if (aReportOnly) {
     policy->setReportOnlyFlag(true);
     if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -95,22 +95,22 @@ class nsCSPParser {
      * Internally the input string is separated into string tokens and policy() is called, which starts
      * parsing the policy. The parser calls one function after the other according the the source-list
      * from http://www.w3.org/TR/CSP11/#source-list. E.g., the parser can only call port() after the parser
      * has already processed any possible host in host(), similar to a finite state machine.
      */
     static nsCSPPolicy* parseContentSecurityPolicy(const nsAString &aPolicyString,
                                                    nsIURI *aSelfURI,
                                                    bool aReportOnly,
-                                                   uint64_t aInnerWindowID);
+                                                   nsCSPContext* aCSPContext);
 
   private:
     nsCSPParser(cspTokens& aTokens,
                 nsIURI* aSelfURI,
-                uint64_t aInnerWindowID);
+                nsCSPContext* aCSPContext);
     ~nsCSPParser();
 
 
     // Parsing the CSP using the source-list from http://www.w3.org/TR/CSP11/#source-list
     nsCSPPolicy*    policy();
     void            directive();
     nsCSPDirective* directiveName();
     void            directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs);
@@ -240,12 +240,12 @@ class nsCSPParser {
     // decide whether it will handle frames, or if there is a frame-src we
     // should honor instead.
     nsCSPChildSrcDirective* mChildSrc;
     nsCSPDirective*         mFrameSrc;
 
     cspTokens          mTokens;
     nsIURI*            mSelfURI;
     nsCSPPolicy*       mPolicy;
-    uint64_t           mInnerWindowID; // used for console reporting
+    nsCSPContext*      mCSPContext; // used for console logging
 };
 
 #endif /* nsCSPParser_h___ */
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -232,19 +232,24 @@ DoContentSecurityChecks(nsIURI* aURI, ns
       }
       requestingContext = aLoadInfo->LoadingNode();
       MOZ_ASSERT(!requestingContext ||
                  requestingContext->NodeType() == nsIDOMNode::ELEMENT_NODE,
                  "type_media requires requestingContext of type Element");
       break;
     }
 
-    case nsIContentPolicy::TYPE_WEBSOCKET:
+    case nsIContentPolicy::TYPE_WEBSOCKET: {
+      MOZ_ASSERT(false, "contentPolicyType not supported yet");
+      break;
+    }
+
     case nsIContentPolicy::TYPE_CSP_REPORT: {
-      MOZ_ASSERT(false, "contentPolicyType not supported yet");
+      mimeTypeGuess = EmptyCString();
+      requestingContext = aLoadInfo->LoadingNode();
       break;
     }
 
     case nsIContentPolicy::TYPE_XSLT: {
       mimeTypeGuess = NS_LITERAL_CSTRING("application/xml");
       requestingContext = aLoadInfo->LoadingNode();
       MOZ_ASSERT(!requestingContext ||
                  requestingContext->NodeType() == nsIDOMNode::DOCUMENT_NODE,
@@ -415,17 +420,18 @@ nsContentSecurityManager::IsURIPotential
 
   *aIsTrustWorthy = false;
   nsAutoCString scheme;
   nsresult rv = aURI->GetScheme(scheme);
   NS_ENSURE_SUCCESS(rv, NS_OK);
 
   if (scheme.EqualsLiteral("https") ||
       scheme.EqualsLiteral("file") ||
-      scheme.EqualsLiteral("app")) {
+      scheme.EqualsLiteral("app") ||
+      scheme.EqualsLiteral("wss")) {
     *aIsTrustWorthy = true;
     return NS_OK;
   }
 
   nsAutoCString host;
   rv = aURI->GetHost(host);
   NS_ENSURE_SUCCESS(rv, NS_OK);
 
--- a/dom/security/test/TestCSPParser.cpp
+++ b/dom/security/test/TestCSPParser.cpp
@@ -85,49 +85,38 @@ struct PolicyTest
   char policy[kMaxPolicyLength];
   char expectedResult[kMaxPolicyLength];
 };
 
 nsresult runTest(uint32_t aExpectedPolicyCount, // this should be 0 for policies which should fail to parse
                  const char* aPolicy,
                  const char* aExpextedResult) {
 
-  // we init the csp with http://www.selfuri.com
-  nsCOMPtr<nsIURI> selfURI;
-  nsresult rv = NS_NewURI(getter_AddRefs(selfURI), "http://www.selfuri.com");
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  nsresult rv;
   nsCOMPtr<nsIScriptSecurityManager> secman =
     do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
-     nsCOMPtr<nsIPrincipal> systemPrincipal;
-  rv = secman->GetSystemPrincipal(getter_AddRefs(systemPrincipal));
+
+  // we init the csp with http://www.selfuri.com
+  nsCOMPtr<nsIURI> selfURI;
+  rv = NS_NewURI(getter_AddRefs(selfURI), "http://www.selfuri.com");
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // we also init the csp with a dummyChannel, which is unused
-  // for the parser tests but surpresses assertions in SetRequestContext.
-  nsCOMPtr<nsIChannel> dummyChannel;
-  rv = NS_NewChannel(getter_AddRefs(dummyChannel),
-                     selfURI,
-                     systemPrincipal,
-                     nsILoadInfo::SEC_NORMAL,
-                     nsIContentPolicy::TYPE_OTHER);
+  nsCOMPtr<nsIPrincipal> selfURIPrincipal;
+  rv = secman->GetSimpleCodebasePrincipal(selfURI, getter_AddRefs(selfURIPrincipal));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // create a CSP object
   nsCOMPtr<nsIContentSecurityPolicy> csp =
     do_CreateInstance(NS_CSPCONTEXT_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // for testing the parser we only need to set selfURI which is needed
-  // to translate the keyword 'self' into an actual URI. All other
-  // arguments can be nullptrs.
-  csp->SetRequestContext(selfURI,
-                         nullptr,  // nsIURI* aReferrer
-                         dummyChannel);
+  // for testing the parser we only need to set a principal which is needed
+  // to translate the keyword 'self' into an actual URI.
+  rv = csp->SetRequestContext(nullptr, selfURIPrincipal);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // append a policy
   nsString policyStr;
   policyStr.AssignASCII(aPolicy);
   rv = csp->AppendPolicy(policyStr, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
--- a/dom/security/test/csp/test_bug949549.html
+++ b/dom/security/test/csp/test_bug949549.html
@@ -16,47 +16,39 @@
 
   const csp = SpecialPowers.Cc["@mozilla.org/cspcontext;1"]
               .createInstance(SpecialPowers.Ci.nsIContentSecurityPolicy);
 
   const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
   SimpleTest.waitForExplicitFinish();
   var app;
+  var principal;
 
   function setupTest() {
     // We have to install an app in order for the app URL to be valid
     // (otherwise we get a "DummyChannel" that throws NS_NOT_IMPLEMENTED)
     SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     SpecialPowers.autoConfirmAppInstall(function () {
       let req = navigator.mozApps.install(gManifestURL);
       req.onsuccess = function () {
         app = this.result;
         runTest();
       }
     });
   }
 
   function runTest() {
-    // We have to use a mochitest to test app:// urls,
-    // as app channels can't be instanciated in xpcshell.
-    // Because app protocol depends on webapps.jsm,
-    // which doesn't instanciate properly on xpcshell without many hacks
-    let appchan = SpecialPowers.Services.io.newChannel2(gManifestURL,
-                                                        null,
-                                                        null,
-                                                        null,      // aLoadingNode
-                                                        SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal(),
-                                                        null,      // aTriggeringPrincipal
-                                                        SpecialPowers.Ci.nsILoadInfo.SEC_NORMAL,
-                                                        SpecialPowers.Ci.nsIContentPolicy.TYPE_OTHER);
-
     try {
-      csp.setRequestContext(null, null, appchan);
+      var secMan = SpecialPowers.Cc["@mozilla.org/scriptsecuritymanager;1"]
+                   .getService(SpecialPowers.Ci.nsIScriptSecurityManager);
+      var manifestURI = SpecialPowers.Services.io.newURI(gManifestURL, null, null);
+      principal = secMan.getSimpleCodebasePrincipal(manifestURI);
+      csp.setRequestContext(null, principal);
       ok(true, "setRequestContext hasn't thown");
     } catch(e) {
       ok(false, "setRequestContext throws");
     }
 
     cleanup()
   }
 
--- a/dom/security/test/unit/test_csp_reports.js
+++ b/dom/security/test/unit/test_csp_reports.js
@@ -10,16 +10,18 @@ var Cr = Components.results;
 Cu.import('resource://gre/modules/NetUtil.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://testing-common/httpd.js");
 
 var httpServer = new HttpServer();
 httpServer.start(-1);
 var testsToFinish = 0;
 
+var principal;
+
 const REPORT_SERVER_PORT = httpServer.identity.primaryPort;
 const REPORT_SERVER_URI = "http://localhost";
 const REPORT_SERVER_PATH = "/report";
 
 /**
  * Construct a callback that listens to a report submission and either passes
  * or fails a test based on what it gets.
  */
@@ -67,24 +69,23 @@ function makeTest(id, expectedJSON, useR
               .createInstance(Ci.nsIContentSecurityPolicy);
   var policy = "default-src 'none'; " +
                "report-uri " + REPORT_SERVER_URI +
                                ":" + REPORT_SERVER_PORT +
                                "/test" + id;
   var selfuri = NetUtil.newURI(REPORT_SERVER_URI +
                                ":" + REPORT_SERVER_PORT +
                                "/foo/self");
-  var selfchan = NetUtil.newChannel({
-    uri: selfuri,
-    loadUsingSystemPrincipal: true});
 
   dump("Created test " + id + " : " + policy + "\n\n");
 
-  // make the reports seem authentic by "binding" them to a channel.
-  csp.setRequestContext(selfuri, null, selfchan);
+  let ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
+              .getService(Ci.nsIScriptSecurityManager);
+  principal = ssm.getSimpleCodebasePrincipal(selfuri);
+  csp.setRequestContext(null, principal);
 
   // Load up the policy
   // set as report-only if that's the case
   csp.appendPolicy(policy, useReportOnlyPolicy);
 
   // prime the report server
   var handler = makeReportHandler("/test" + id, "Test " + id, expectedJSON);
   httpServer.registerPathHandler("/test" + id, handler);
--- a/dom/webidl/BrowserElementDictionaries.webidl
+++ b/dom/webidl/BrowserElementDictionaries.webidl
@@ -2,25 +2,16 @@
 /* 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/.
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
-dictionary AsyncScrollEventDetail {
-  float top = 0;
-  float left = 0;
-  float width = 0;
-  float height = 0;
-  float scrollWidth = 0;
-  float scrollHeight = 0;
-};
-
 dictionary OpenWindowEventDetail {
   DOMString url = "";
   DOMString name = "";
   DOMString features = "";
   Node? frameElement = null;
 };
 
 dictionary DOMWindowResizeEventDetail {
--- a/gfx/2d/BaseRect.h
+++ b/gfx/2d/BaseRect.h
@@ -525,21 +525,21 @@ struct BaseRect {
    */
   MOZ_WARN_UNUSED_RESULT Point ClampPoint(const Point& aPoint) const
   {
     return Point(std::max(x, std::min(XMost(), aPoint.x)),
                  std::max(y, std::min(YMost(), aPoint.y)));
   }
 
   /**
-   * Clamp this rectangle to be inside aRect. The function returns a copy of
-   * this rect after it is forced inside the bounds of aRect. It will attempt to
-   * retain the size but will shrink the dimensions that don't fit.
+   * Translate this rectangle to be inside aRect. If it doesn't fit inside
+   * aRect then the dimensions that don't fit will be shrunk so that they
+   * do fit. The resulting rect is returned.
    */
-  MOZ_WARN_UNUSED_RESULT Sub ForceInside(const Sub& aRect) const
+  MOZ_WARN_UNUSED_RESULT Sub MoveInsideAndClamp(const Sub& aRect) const
   {
     Sub rect(std::max(aRect.x, x),
              std::max(aRect.y, y),
              std::min(aRect.width, width),
              std::min(aRect.height, height));
     rect.x = std::min(rect.XMost(), aRect.XMost()) - rect.width;
     rect.y = std::min(rect.YMost(), aRect.YMost()) - rect.height;
     return rect;
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -1252,16 +1252,17 @@ DrawTargetCairo::Mask(const Pattern &aSo
   if (cairo_pattern_status(source) || cairo_pattern_status(mask)) {
     cairo_pattern_destroy(source);
     cairo_pattern_destroy(mask);
     gfxWarning() << "Invalid pattern";
     return;
   }
 
   cairo_set_source(mContext, source);
+  cairo_set_operator(mContext, GfxOpToCairoOp(aOptions.mCompositionOp));
   cairo_mask(mContext, mask);
 
   cairo_pattern_destroy(mask);
   cairo_pattern_destroy(source);
 }
 
 void
 DrawTargetCairo::MaskSurface(const Pattern &aSource,
--- a/gfx/2d/Rect.h
+++ b/gfx/2d/Rect.h
@@ -224,12 +224,18 @@ IntRectTyped<units> RoundedOut(const Rec
   RectTyped<units> copy(aRect);
   copy.RoundOut();
   return IntRectTyped<units>(int32_t(copy.x),
                              int32_t(copy.y),
                              int32_t(copy.width),
                              int32_t(copy.height));
 }
 
+template<class units>
+RectTyped<units> IntRectToRect(const IntRectTyped<units>& aRect)
+{
+  return RectTyped<units>(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+
 } // namespace gfx
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_RECT_H_ */
--- a/gfx/gl/GLContextProviderCGL.mm
+++ b/gfx/gl/GLContextProviderCGL.mm
@@ -10,16 +10,18 @@
 #include "nsIWidget.h"
 #include <OpenGL/gl.h>
 #include "gfxFailure.h"
 #include "gfxPrefs.h"
 #include "prenv.h"
 #include "GeckoProfiler.h"
 #include "mozilla/gfx/MacIOSurface.h"
 
+#include <OpenGL/OpenGL.h>
+
 namespace mozilla {
 namespace gl {
 
 using namespace mozilla::gfx;
 
 class CGLLibrary
 {
 public:
@@ -291,16 +293,19 @@ CreateOffscreenFBOContext(CreateContextF
         NS_WARNING("Failed to create NSOpenGLContext.");
         return nullptr;
     }
 
     SurfaceCaps dummyCaps = SurfaceCaps::Any();
     RefPtr<GLContextCGL> glContext = new GLContextCGL(dummyCaps, context,
                                                         true, profile);
 
+    if (gfxPrefs::GLMultithreaded()) {
+        CGLEnable(glContext->GetCGLContext(), kCGLCEMPEngine);
+    }
     return glContext.forget();
 }
 
 already_AddRefed<GLContext>
 GLContextProviderCGL::CreateHeadless(CreateContextFlags flags)
 {
     RefPtr<GLContextCGL> gl;
     gl = CreateOffscreenFBOContext(flags);
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -61,21 +61,22 @@ Compositor::DrawDiagnostics(DiagnosticFl
   }
 
   if (aVisibleRegion.GetNumRects() > 1) {
     nsIntRegionRectIterator screenIter(aVisibleRegion);
 
     while (const gfx::IntRect* rect = screenIter.Next())
     {
       DrawDiagnostics(aFlags | DiagnosticFlags::REGION_RECT,
-                      ToRect(*rect), aClipRect, aTransform, aFlashCounter);
+                      IntRectToRect(*rect), aClipRect, aTransform,
+                      aFlashCounter);
     }
   }
 
-  DrawDiagnostics(aFlags, ToRect(aVisibleRegion.GetBounds()),
+  DrawDiagnostics(aFlags, IntRectToRect(aVisibleRegion.GetBounds()),
                   aClipRect, aTransform, aFlashCounter);
 }
 
 void
 Compositor::DrawDiagnostics(DiagnosticFlags aFlags,
                             const gfx::Rect& aVisibleRect,
                             const gfx::Rect& aClipRect,
                             const gfx::Matrix4x4& aTransform,
--- a/gfx/layers/LayerSorter.cpp
+++ b/gfx/layers/LayerSorter.cpp
@@ -71,18 +71,18 @@ static gfxFloat RecoverZDepth(const Matr
  * depths and use this point to determine an ordering for the two layers.
  * For layers that are intersecting in 3d space, this essentially guesses an 
  * order. In a lot of cases we only intersect right at the edge point (3d cubes
  * in particular) and this generates the 'correct' looking ordering. For planes
  * that truely intersect, then there is no correct ordering and this remains
  * unsolved without changing our rendering code.
  */
 static LayerSortOrder CompareDepth(Layer* aOne, Layer* aTwo) {
-  gfxRect ourRect = aOne->GetEffectiveVisibleRegion().GetBounds();
-  gfxRect otherRect = aTwo->GetEffectiveVisibleRegion().GetBounds();
+  gfxRect ourRect = ThebesRect(aOne->GetEffectiveVisibleRegion().GetBounds());
+  gfxRect otherRect = ThebesRect(aTwo->GetEffectiveVisibleRegion().GetBounds());
 
   MOZ_ASSERT(aOne->GetParent() && aOne->GetParent()->Extend3DContext() &&
              aTwo->GetParent() && aTwo->GetParent()->Extend3DContext());
   // Effective transform of leaves may had been projected to 2D.
   Matrix4x4 ourTransform =
     aOne->GetLocalTransform() * aOne->GetParent()->GetEffectiveTransform();
   Matrix4x4 otherTransform =
     aTwo->GetLocalTransform() * aTwo->GetParent()->GetEffectiveTransform();
--- a/gfx/layers/LayerTreeInvalidation.cpp
+++ b/gfx/layers/LayerTreeInvalidation.cpp
@@ -28,16 +28,44 @@
 using namespace mozilla::gfx;
 
 namespace mozilla {
 namespace layers {
 
 struct LayerPropertiesBase;
 UniquePtr<LayerPropertiesBase> CloneLayerTreePropertiesInternal(Layer* aRoot, bool aIsMask = false);
 
+/**
+ * Get accumulated transform of from the context creating layer to the
+ * given layer.
+ */
+static Matrix4x4
+GetTransformIn3DContext(Layer* aLayer) {
+  Matrix4x4 transform = aLayer->GetLocalTransform();
+  for (Layer* layer = aLayer->GetParent();
+       layer && layer->Extend3DContext();
+       layer = layer->GetParent()) {
+    transform = transform * layer->GetLocalTransform();
+  }
+  return transform;
+}
+
+/**
+ * Get a transform for the given layer depending on extending 3D
+ * context.
+ *
+ * @return local transform for layers not participating 3D rendering
+ * context, or the accmulated transform in the context for else.
+ */
+static Matrix4x4
+GetTransformForInvalidation(Layer* aLayer) {
+  return (!aLayer->Is3DContextLeaf() && !aLayer->Extend3DContext() ?
+          aLayer->GetLocalTransform() : GetTransformIn3DContext(aLayer));
+}
+
 static IntRect
 TransformRect(const IntRect& aRect, const Matrix4x4& aTransform)
 {
   if (aRect.IsEmpty()) {
     return IntRect();
   }
 
   Rect rect(aRect.x, aRect.y, aRect.width, aRect.height);
@@ -118,17 +146,17 @@ struct LayerPropertiesBase : public Laye
     }
     for (size_t i = 0; i < aLayer->GetAncestorMaskLayerCount(); i++) {
       Layer* maskLayer = aLayer->GetAncestorMaskLayerAt(i);
       mAncestorMaskLayers.AppendElement(CloneLayerTreePropertiesInternal(maskLayer, true));
     }
     if (mUseClipRect) {
       mClipRect = *aLayer->GetEffectiveClipRect();
     }
-    mTransform = aLayer->GetLocalTransform();
+    mTransform = GetTransformForInvalidation(aLayer);
   }
   LayerPropertiesBase()
     : mLayer(nullptr)
     , mMaskLayer(nullptr)
   {
     MOZ_COUNT_CTOR(LayerPropertiesBase);
   }
   ~LayerPropertiesBase()
@@ -140,19 +168,19 @@ struct LayerPropertiesBase : public Laye
                                          NotifySubDocInvalidationFunc aCallback,
                                          bool* aGeometryChanged);
 
   virtual void MoveBy(const IntPoint& aOffset);
 
   nsIntRegion ComputeChange(NotifySubDocInvalidationFunc aCallback,
                             bool& aGeometryChanged)
   {
-    bool transformChanged = !mTransform.FuzzyEqualsMultiplicative(mLayer->GetLocalTransform()) ||
-                            mLayer->GetPostXScale() != mPostXScale ||
-                            mLayer->GetPostYScale() != mPostYScale;
+    bool transformChanged = !mTransform.FuzzyEqualsMultiplicative(GetTransformForInvalidation(mLayer)) ||
+                             mLayer->GetPostXScale() != mPostXScale ||
+                             mLayer->GetPostYScale() != mPostYScale;
     const Maybe<ParentLayerIntRect>& otherClip = mLayer->GetEffectiveClipRect();
     nsIntRegion result;
 
     bool ancestorMaskChanged = mAncestorMaskLayers.Length() != mLayer->GetAncestorMaskLayerCount();
     if (!ancestorMaskChanged) {
       for (size_t i = 0; i < mAncestorMaskLayers.Length(); i++) {
         if (mLayer->GetAncestorMaskLayerAt(i) != mAncestorMaskLayers[i]->mLayer) {
           ancestorMaskChanged = true;
@@ -191,28 +219,29 @@ struct LayerPropertiesBase : public Laye
                            mAncestorMaskLayers[i]->ComputeChange(aCallback, aGeometryChanged),
                            mTransform);
     }
 
     if (mUseClipRect && otherClip) {
       if (!mClipRect.IsEqualInterior(*otherClip)) {
         aGeometryChanged = true;
         nsIntRegion tmp; 
-        tmp.Xor(ParentLayerIntRect::ToUntyped(mClipRect), ParentLayerIntRect::ToUntyped(*otherClip)); 
+        tmp.Xor(mClipRect.ToUnknownRect(), otherClip->ToUnknownRect());
         AddRegion(result, tmp);
       }
     }
 
     mLayer->ClearInvalidRect();
     return result;
   }
 
   IntRect NewTransformedBounds()
   {
-    return TransformRect(mLayer->GetVisibleRegion().GetBounds(), mLayer->GetLocalTransform());
+    return TransformRect(mLayer->GetVisibleRegion().GetBounds(),
+                         GetTransformForInvalidation(mLayer));
   }
 
   IntRect OldTransformedBounds()
   {
     return TransformRect(mVisibleRegion.GetBounds(), mTransform);
   }
 
   virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback,
@@ -245,25 +274,26 @@ struct ContainerLayerProperties : public
       mChildren.AppendElement(Move(CloneLayerTreePropertiesInternal(child)));
     }
   }
 
   virtual nsIntRegion ComputeChangeInternal(NotifySubDocInvalidationFunc aCallback,
                                             bool& aGeometryChanged)
   {
     ContainerLayer* container = mLayer->AsContainerLayer();
-    nsIntRegion result;
+    nsIntRegion invalidOfLayer; // Invalid regions of this layer.
+    nsIntRegion result;         // Invliad regions for children only.
 
     bool childrenChanged = false;
 
     if (mPreXScale != container->GetPreXScale() ||
         mPreYScale != container->GetPreYScale()) {
       aGeometryChanged = true;
-      result = OldTransformedBounds();
-      AddRegion(result, NewTransformedBounds());
+      invalidOfLayer = OldTransformedBounds();
+      AddRegion(invalidOfLayer, NewTransformedBounds());
       childrenChanged = true;
 
       // Can't bail out early, we need to update the child container layers
     }
 
     // A low frame rate is especially visible to users when scrolling, so we
     // particularly want to avoid unnecessary invalidation at that time. For us
     // here, that means avoiding unnecessary invalidation of child items when
@@ -312,17 +342,18 @@ struct ContainerLayerProperties : public
           invalidateChildsCurrentArea = true;
         }
       } else {
         // |child| is new, or was reordered to a higher index
         invalidateChildsCurrentArea = true;
       }
       if (invalidateChildsCurrentArea) {
         aGeometryChanged = true;
-        AddTransformedRegion(result, child->GetVisibleRegion(), child->GetLocalTransform());
+        AddTransformedRegion(result, child->GetVisibleRegion(),
+                             GetTransformForInvalidation(child));
         if (aCallback) {
           NotifySubdocumentInvalidationRecursive(child, aCallback);
         } else {
           ClearInvalidations(child);
         }
       }
       childrenChanged |= invalidateChildsCurrentArea;
     }
@@ -337,17 +368,23 @@ struct ContainerLayerProperties : public
     if (aCallback) {
       aCallback(container, result);
     }
 
     if (childrenChanged) {
       container->SetChildrenChanged(true);
     }
 
-    result.Transform(mLayer->GetLocalTransform());
+    if (!mLayer->Extend3DContext()) {
+      // |result| contains invalid regions only of children.
+      result.Transform(GetTransformForInvalidation(mLayer));
+    }
+    // else, effective transforms have applied on children.
+
+    result.OrWith(invalidOfLayer);
 
     return result;
   }
 
   // The old list of children:
   nsAutoTArray<UniquePtr<LayerPropertiesBase>,1> mChildren;
   float mPreXScale;
   float mPreYScale;
@@ -441,17 +478,17 @@ struct ImageLayerProperties : public Lay
         IntSize size;
         if (container) {
           size = container->GetCurrentSize();
         }
         if (host) {
           size = host->GetImageSize();
         }
         IntRect rect(0, 0, size.width, size.height);
-        return TransformRect(rect, mLayer->GetLocalTransform());
+        return TransformRect(rect, GetTransformForInvalidation(mLayer));
       }
       return NewTransformedBounds();
     }
 
     return IntRect();
   }
 
   RefPtr<ImageContainer> mContainer;
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -1001,19 +1001,18 @@ Layer::ComputeEffectiveTransformForMaskL
   aMaskLayer->mEffectiveTransform = aMaskLayer->GetLocalTransform() *
     aMaskLayer->mEffectiveTransform;
 }
 
 RenderTargetRect
 Layer::TransformRectToRenderTarget(const LayerIntRect& aRect)
 {
   LayerRect rect(aRect);
-  RenderTargetRect quad = RenderTargetRect::FromUnknown(
-    GetEffectiveTransform().TransformBounds(
-      LayerPixel::ToUnknown(rect)));
+  RenderTargetRect quad = RenderTargetRect::FromUnknownRect(
+    GetEffectiveTransform().TransformBounds(rect.ToUnknownRect()));
   return quad;
 }
 
 bool
 Layer::GetVisibleRegionRelativeToRootLayer(nsIntRegion& aResult,
                                            nsIntPoint* aLayerOffset)
 {
   MOZ_ASSERT(aLayerOffset, "invalid offset pointer");
@@ -1036,17 +1035,17 @@ Layer::GetVisibleRegionRelativeToRootLay
 
     // Translate the accumulated visible region of |this| by the offset of
     // |layer|.
     aResult.MoveBy(currentLayerOffset.x, currentLayerOffset.y);
 
     // If the parent layer clips its lower layers, clip the visible region
     // we're accumulating.
     if (layer->GetEffectiveClipRect()) {
-      aResult.AndWith(ParentLayerIntRect::ToUntyped(*layer->GetEffectiveClipRect()));
+      aResult.AndWith(layer->GetEffectiveClipRect()->ToUnknownRect());
     }
 
     // Now we need to walk across the list of siblings for this parent layer,
     // checking to see if any of these layer trees obscure |this|. If so,
     // remove these areas from the visible region as well. This will pick up
     // chrome overlays like a tab modal prompt.
     Layer* sibling;
     for (sibling = layer->GetNextSibling(); sibling;
@@ -1062,17 +1061,17 @@ Layer::GetVisibleRegionRelativeToRootLay
       IntPoint siblingOffset = RoundedToInt(siblingMatrix.GetTranslation());
       nsIntRegion siblingVisibleRegion(sibling->GetEffectiveVisibleRegion());
       // Translate the siblings region to |layer|'s origin.
       siblingVisibleRegion.MoveBy(-siblingOffset.x, -siblingOffset.y);
       // Apply the sibling's clip.
       // Layer clip rects are not affected by the layer's transform.
       Maybe<ParentLayerIntRect> clipRect = sibling->GetEffectiveClipRect();
       if (clipRect) {
-        siblingVisibleRegion.AndWith(ParentLayerIntRect::ToUntyped(*clipRect));
+        siblingVisibleRegion.AndWith(clipRect->ToUnknownRect());
       }
       // Subtract the sibling visible region from the visible region of |this|.
       aResult.SubOut(siblingVisibleRegion);
     }
 
     // Keep track of the total offset for aLayerOffset.  We use this in plugin
     // positioning code.
     offset += currentLayerOffset;
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1143,35 +1143,43 @@ public:
    *   - |aScrollId| identifies the scroll frame that this element is fixed
    *     with respect to.
    *
    *   - |aAnchor| is the point on the layer that is considered the "anchor"
    *     point, that is, the point which remains in the same position when
    *     compositing the layer tree with a transformation (such as when
    *     asynchronously scrolling and zooming).
    *
+   *   - |aSides| is the set of sides to which the element is fixed relative to.
+   *     This is used if the viewport size is changed in the compositor and
+   *     fixed position items need to shift accordingly. This value is made up
+   *     combining appropriate values from mozilla::SideBits.
+   *
    *   - |aIsClipFixed| is true if this layer's clip rect and mask layer
    *     should also remain fixed during async scrolling/animations.
    *     This is the case for fixed position layers, but not for
    *     fixed background layers.
    */
   void SetFixedPositionData(FrameMetrics::ViewID aScrollId,
                             const LayerPoint& aAnchor,
+                            int32_t aSides,
                             bool aIsClipFixed)
   {
     if (!mFixedPositionData ||
         mFixedPositionData->mScrollId != aScrollId ||
         mFixedPositionData->mAnchor != aAnchor ||
+        mFixedPositionData->mSides != aSides ||
         mFixedPositionData->mIsClipFixed != aIsClipFixed) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) FixedPositionData", this));
       if (!mFixedPositionData) {
         mFixedPositionData = MakeUnique<FixedPositionData>();
       }
       mFixedPositionData->mScrollId = aScrollId;
       mFixedPositionData->mAnchor = aAnchor;
+      mFixedPositionData->mSides = aSides;
       mFixedPositionData->mIsClipFixed = aIsClipFixed;
       Mutated();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
    * If a layer is "sticky position", |aScrollId| holds the scroll identifier
@@ -1256,16 +1264,17 @@ public:
   const gfx::Matrix4x4& GetBaseTransform() const { return mTransform; }
   // Note: these are virtual because ContainerLayerComposite overrides them.
   virtual float GetPostXScale() const { return mPostXScale; }
   virtual float GetPostYScale() const { return mPostYScale; }
   bool GetIsFixedPosition() { return mIsFixedPosition; }
   bool GetIsStickyPosition() { return mStickyPositionData; }
   FrameMetrics::ViewID GetFixedPositionScrollContainerId() { return mFixedPositionData ? mFixedPositionData->mScrollId : FrameMetrics::NULL_SCROLL_ID; }
   LayerPoint GetFixedPositionAnchor() { return mFixedPositionData ? mFixedPositionData->mAnchor : LayerPoint(); }
+  int32_t GetFixedPositionSides() { return mFixedPositionData ? mFixedPositionData->mSides : eSideBitsNone; }
   bool IsClipFixed() { return mFixedPositionData ? mFixedPositionData->mIsClipFixed : false; }
   FrameMetrics::ViewID GetStickyScrollContainerId() { return mStickyPositionData->mScrollId; }
   const LayerRect& GetStickyScrollRangeOuter() { return mStickyPositionData->mOuter; }
   const LayerRect& GetStickyScrollRangeInner() { return mStickyPositionData->mInner; }
   FrameMetrics::ViewID GetScrollbarTargetContainerId() { return mScrollbarTargetId; }
   ScrollDirection GetScrollbarDirection() { return mScrollbarDirection; }
   float GetScrollbarThumbRatio() { return mScrollbarThumbRatio; }
   bool IsScrollbarContainer() { return mIsScrollbarContainer; }
@@ -1785,16 +1794,17 @@ protected:
   nsIntRegion mInvalidRegion;
   nsTArray<RefPtr<AsyncPanZoomController> > mApzcs;
   uint32_t mContentFlags;
   bool mUseTileSourceRect;
   bool mIsFixedPosition;
   struct FixedPositionData {
     FrameMetrics::ViewID mScrollId;
     LayerPoint mAnchor;
+    int32_t mSides;
     bool mIsClipFixed;
   };
   UniquePtr<FixedPositionData> mFixedPositionData;
   struct StickyPositionData {
     FrameMetrics::ViewID mScrollId;
     LayerRect mOuter;
     LayerRect mInner;
   };
@@ -2056,17 +2066,17 @@ public:
    * in this layer's coordinate system.
    *
    * NOTE: Since this layer has an intermediate surface it follows
    *       that LayerPixel == RenderTargetPixel
    */
   RenderTargetIntRect GetIntermediateSurfaceRect()
   {
     NS_ASSERTION(mUseIntermediateSurface, "Must have intermediate surface");
-    return RenderTargetPixel::FromUntyped(mVisibleRegion.GetBounds());
+    return RenderTargetIntRect::FromUnknownRect(mVisibleRegion.GetBounds());
   }
 
   /**
    * Returns true if this container has more than one non-empty child
    */
   bool HasMultipleChildren();
 
   /**
--- a/gfx/layers/RotatedBuffer.cpp
+++ b/gfx/layers/RotatedBuffer.cpp
@@ -104,27 +104,27 @@ RotatedBuffer::DrawBufferQuadrant(gfx::D
   // (to avoid flickering) but direct2d is ok since it defers rendering.
   // We should try abstract this logic in a helper when we have other use
   // cases.
   if ((aTarget->GetBackendType() == BackendType::DIRECT2D ||
        aTarget->GetBackendType() == BackendType::DIRECT2D1_1) &&
       aOperator == CompositionOp::OP_SOURCE) {
     aOperator = CompositionOp::OP_OVER;
     if (snapshot->GetFormat() == SurfaceFormat::B8G8R8A8) {
-      aTarget->ClearRect(ToRect(fillRect));
+      aTarget->ClearRect(IntRectToRect(fillRect));
     }
   }
 
   // OP_SOURCE is unbounded in Azure, and we really don't want that behaviour here.
   // We also can't do a ClearRect+FillRect since we need the drawing to happen
   // as an atomic operation (to prevent flickering).
   // We also need this clip in the case where we have a mask, since the mask surface
   // might cover more than fillRect, but we only want to touch the pixels inside
   // fillRect.
-  aTarget->PushClipRect(gfx::ToRect(fillRect));
+  aTarget->PushClipRect(IntRectToRect(fillRect));
 
   if (aMask) {
     Matrix oldTransform = aTarget->GetTransform();
 
     // Transform from user -> buffer space.
     Matrix transform =
       Matrix::Translation(quadrantTranslation.x, quadrantTranslation.y);
 
@@ -144,17 +144,17 @@ RotatedBuffer::DrawBufferQuadrant(gfx::D
     aTarget->MaskSurface(source, aMask, Point(0, 0), DrawOptions(aOpacity, aOperator));
     aTarget->SetTransform(oldTransform);
   } else {
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
     DrawSurfaceOptions options(Filter::POINT);
 #else
     DrawSurfaceOptions options;
 #endif
-    aTarget->DrawSurface(snapshot, ToRect(fillRect),
+    aTarget->DrawSurface(snapshot, IntRectToRect(fillRect),
                          GetSourceRectangle(aXSide, aYSide),
                          options,
                          DrawOptions(aOpacity, aOperator));
   }
 
   aTarget->PopClip();
 }
 
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -70,25 +70,16 @@ public:
    * current scroll offset.
    */
   virtual void HandleLongTap(const CSSPoint& aPoint,
                              Modifiers aModifiers,
                              const ScrollableLayerGuid& aGuid,
                              uint64_t aInputBlockId) = 0;
 
   /**
-   * Requests sending a mozbrowserasyncscroll domevent to embedder.
-   * |aContentRect| is in CSS pixels, relative to the current cssPage.
-   * |aScrollableSize| is the current content width/height in CSS pixels.
-   */
-  virtual void SendAsyncScrollDOMEvent(bool aIsRootContent,
-                                       const CSSRect &aContentRect,
-                                       const CSSSize &aScrollableSize) = 0;
-
-  /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    * This method must always be called on the controller thread.
    */
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) = 0;
 
   /**
    * APZ uses |FrameMetrics::mCompositionBounds| for hit testing. Sometimes,
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -301,17 +301,17 @@ APZCTreeManager::AttachNodeToTree(HitTes
   }
 }
 
 static EventRegions
 GetEventRegions(const LayerMetricsWrapper& aLayer)
 {
   if (aLayer.IsScrollInfoLayer()) {
     ParentLayerIntRect compositionBounds(RoundedToInt(aLayer.Metrics().GetCompositionBounds()));
-    nsIntRegion hitRegion(ParentLayerIntRect::ToUntyped(compositionBounds));
+    nsIntRegion hitRegion(compositionBounds.ToUnknownRect());
     EventRegions eventRegions(hitRegion);
     eventRegions.mDispatchToContentHitRegion = eventRegions.mHitRegion;
     return eventRegions;
   }
   return aLayer.GetEventRegions();
 }
 
 already_AddRefed<HitTestingTreeNode>
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -99,24 +99,16 @@ using mozilla::gfx::PointTyped;
  * \page APZCPrefs APZ preferences
  *
  * The following prefs are used to control the behaviour of the APZC.
  * The default values are provided in gfxPrefs.h.
  *
  * \li\b apz.allow_checkerboarding
  * Pref that allows or disallows checkerboarding
  *
- * \li\b apz.asyncscroll.throttle
- * The time period that throttles mozbrowserasyncscroll event.\n
- * Units: milliseconds
- *
- * \li\b apz.asyncscroll.timeout
- * The timeout for mAsyncScrollTimeoutTask delay task.\n
- * Units: milliseconds
- *
  * \li\b apz.axis_lock.mode
  * The preferred axis locking style. See AxisLockMode for possible values.
  *
  * \li\b apz.axis_lock.lock_angle
  * Angle from axis within which we stay axis-locked.\n
  * Units: radians
  *
  * \li\b apz.axis_lock.breakout_threshold
@@ -834,20 +826,16 @@ AsyncPanZoomController::AsyncPanZoomCont
      mTreeManager(aTreeManager),
      mSharingFrameMetricsAcrossProcesses(false),
      mMonitor("AsyncPanZoomController"),
      mX(this),
      mY(this),
      mPanDirRestricted(false),
      mZoomConstraints(false, false, MIN_ZOOM, MAX_ZOOM),
      mLastSampleTime(GetFrameTime()),
-     mLastAsyncScrollTime(GetFrameTime()),
-     mLastAsyncScrollOffset(0, 0),
-     mCurrentAsyncScrollOffset(0, 0),
-     mAsyncScrollTimeoutTask(nullptr),
      mState(NOTHING),
      mNotificationBlockers(0),
      mInputQueue(aInputQueue),
      mAPZCId(sAsyncPanZoomControllerCount++),
      mSharedLock(nullptr),
      mAsyncTransformAppliedToContent(false)
 {
   if (aGestures == USE_GESTURE_DETECTOR) {
@@ -1297,17 +1285,16 @@ nsEventStatus AsyncPanZoomController::On
 
   // In case no touch behavior triggered previously we can avoid sending
   // scroll events or requesting content repaint. This condition is added
   // to make tests consistent - in case touch-action is NONE (and therefore
   // no pans/zooms can be performed) we expected neither scroll or repaint
   // events.
   if (mState != NOTHING) {
     ReentrantMonitorAutoEnter lock(mMonitor);
-    SendAsyncScrollEvent();
   }
 
   switch (mState) {
   case FLING:
     // Should never happen.
     NS_WARNING("Received impossible touch end in OnTouchEnd.");
     MOZ_FALLTHROUGH;
   case ANIMATING_ZOOM:
@@ -2622,17 +2609,17 @@ const ScreenMargin AsyncPanZoomControlle
   CSSRect displayPort = CSSRect(scrollOffset + (velocity * paintFactor * gfxPrefs::APZVelocityBias()),
                                 displayPortSize);
 
   // Re-center the displayport based on its expansion over the composition size.
   displayPort.MoveBy((compositionSize.width - displayPort.width)/2.0f,
                      (compositionSize.height - displayPort.height)/2.0f);
 
   // Make sure the displayport remains within the scrollable rect.
-  displayPort = displayPort.ForceInside(scrollableRect) - scrollOffset;
+  displayPort = displayPort.MoveInsideAndClamp(scrollableRect) - scrollOffset;
 
   APZC_LOG_FM(aFrameMetrics,
     "Calculated displayport as (%f %f %f %f) from velocity %s paint time %f metrics",
     displayPort.x, displayPort.y, displayPort.width, displayPort.height,
     ToString(aVelocity).c_str(), (float)estimatedPaintDurationMillis);
 
   CSSMargin cssMargins;
   cssMargins.left = -displayPort.x;
@@ -2752,17 +2739,16 @@ void AsyncPanZoomController::RequestCont
       fabsf(aFrameMetrics.GetViewport().width -
             mLastPaintRequestMetrics.GetViewport().width) < EPSILON &&
       fabsf(aFrameMetrics.GetViewport().height -
             mLastPaintRequestMetrics.GetViewport().height) < EPSILON &&
       aFrameMetrics.GetScrollGeneration() == mLastPaintRequestMetrics.GetScrollGeneration()) {
     return;
   }
 
-  SendAsyncScrollEvent();
   if (aThrottled) {
     mPaintThrottler->PostTask(
       FROM_HERE,
       UniquePtr<CancelableTask>(NewRunnableMethod(this,
                         &AsyncPanZoomController::DispatchRepaintRequest,
                         aFrameMetrics)),
       GetFrameTime());
   } else {
@@ -2796,26 +2782,16 @@ AsyncPanZoomController::DispatchRepaintR
     } else {
       NS_DispatchToMainThread(NS_NewRunnableMethodWithArg<FrameMetrics>(
         controller, &GeckoContentController::RequestContentRepaint, aFrameMetrics));
     }
     mLastDispatchedPaintMetrics = aFrameMetrics;
   }
 }
 
-void
-AsyncPanZoomController::FireAsyncScrollOnTimeout()
-{
-  if (mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    SendAsyncScrollEvent();
-  }
-  mAsyncScrollTimeoutTask = nullptr;
-}
-
 bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
                                              Vector<Task*>* aOutDeferredTasks)
 {
   APZThreadUtils::AssertOnCompositorThread();
 
   // This function may get called multiple with the same sample time, because
   // there may be multiple layers with this APZC, and each layer invokes this
   // function during composition. However we only want to do one animation step
@@ -2832,17 +2808,16 @@ bool AsyncPanZoomController::UpdateAnima
     if (continueAnimation) {
       if (mPaintThrottler->TimeSinceLastRequest(aSampleTime) >
           mAnimation->mRepaintInterval) {
         RequestContentRepaint();
       }
     } else {
       mAnimation = nullptr;
       SetState(NOTHING);
-      SendAsyncScrollEvent();
       RequestContentRepaint();
     }
     UpdateSharedCompositorFrameMetrics();
     return true;
   }
   return false;
 }
 
@@ -2911,59 +2886,31 @@ bool AsyncPanZoomController::AdvanceAnim
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     requestAnimationFrame = UpdateAnimation(aSampleTime, &deferredTasks);
 
     LogRendertraceRect(GetGuid(), "viewport", "red",
       CSSRect(mFrameMetrics.GetScrollOffset(),
               mFrameMetrics.CalculateCompositedSizeInCssPixels()));
-
-    mCurrentAsyncScrollOffset = mFrameMetrics.GetScrollOffset();
   }
 
   // Execute any deferred tasks queued up by mAnimation's Sample() (called by
   // UpdateAnimation()). This needs to be done after the monitor is released
   // since the tasks are allowed to call APZCTreeManager methods which can grab
   // the tree lock.
   for (uint32_t i = 0; i < deferredTasks.length(); ++i) {
     deferredTasks[i]->Run();
     delete deferredTasks[i];
   }
 
   // One of the deferred tasks may have started a new animation. In this case,
   // we want to ask the compositor to schedule a new composite.
   requestAnimationFrame |= (mAnimation != nullptr);
 
-  // Cancel the mAsyncScrollTimeoutTask because we will fire a
-  // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again.
-  if (mAsyncScrollTimeoutTask) {
-    mAsyncScrollTimeoutTask->Cancel();
-    mAsyncScrollTimeoutTask = nullptr;
-  }
-  // Fire the mozbrowserasyncscroll event immediately if it's been
-  // sAsyncScrollThrottleTime ms since the last time we fired the event and the
-  // current scroll offset is different than the mLastAsyncScrollOffset we sent
-  // with the last event.
-  // Otherwise, start a timer to fire the event sAsyncScrollTimeout ms from now.
-  TimeDuration delta = aSampleTime - mLastAsyncScrollTime;
-  if (delta.ToMilliseconds() > gfxPrefs::APZAsyncScrollThrottleTime() &&
-      mCurrentAsyncScrollOffset != mLastAsyncScrollOffset) {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-    mLastAsyncScrollTime = aSampleTime;
-    mLastAsyncScrollOffset = mCurrentAsyncScrollOffset;
-    SendAsyncScrollEvent();
-  } else {
-    mAsyncScrollTimeoutTask =
-      NewRunnableMethod(this, &AsyncPanZoomController::FireAsyncScrollOnTimeout);
-    MessageLoop::current()->PostDelayedTask(FROM_HERE,
-                                            mAsyncScrollTimeoutTask,
-                                            gfxPrefs::APZAsyncScrollTimeout());
-  }
-
   return requestAnimationFrame;
 }
 
 void AsyncPanZoomController::SampleContentTransformForFrame(ViewTransform* aOutTransform,
                                                             ParentLayerPoint& aScrollOffset)
 {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
@@ -3463,37 +3410,16 @@ AsyncPanZoomController::GetZoomConstrain
 void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) {
   APZThreadUtils::AssertOnControllerThread();
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     controller->PostDelayedTask(aTask, aDelayMs);
   }
 }
 
-void AsyncPanZoomController::SendAsyncScrollEvent() {
-  RefPtr<GeckoContentController> controller = GetGeckoContentController();
-  if (!controller) {
-    return;
-  }
-
-  bool isRootContent;
-  CSSRect contentRect;
-  CSSSize scrollableSize;
-  {
-    ReentrantMonitorAutoEnter lock(mMonitor);
-
-    isRootContent = mFrameMetrics.IsRootContent();
-    scrollableSize = mFrameMetrics.GetScrollableRect().Size();
-    contentRect = mFrameMetrics.CalculateCompositedRectInCssPixels();
-    contentRect.MoveTo(mCurrentAsyncScrollOffset);
-  }
-
-  controller->SendAsyncScrollDOMEvent(isRootContent, contentRect, scrollableSize);
-}
-
 bool AsyncPanZoomController::Matches(const ScrollableLayerGuid& aGuid)
 {
   return aGuid == GetGuid();
 }
 
 bool AsyncPanZoomController::HasTreeManager(const APZCTreeManager* aTreeManager) const
 {
   return GetApzcTreeManager() == aTreeManager;
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -256,22 +256,16 @@ public:
    * checkerboard immediately. This includes a bunch of logic, including
    * algorithms to bias painting in the direction of the velocity.
    */
   static const ScreenMargin CalculatePendingDisplayPort(
     const FrameMetrics& aFrameMetrics,
     const ParentLayerPoint& aVelocity,
     double aEstimatedPaintDuration);
 
-  /**
-   * Send an mozbrowserasyncscroll event.
-   * *** The monitor must be held while calling this.
-   */
-  void SendAsyncScrollEvent();
-
   nsEventStatus HandleDragEvent(const MouseInput& aEvent,
                                 const AsyncDragMetrics& aDragMetrics);
 
   /**
    * Handler for events which should not be intercepted by the touch listener.
    */
   nsEventStatus HandleInputEvent(const InputData& aEvent,
                                  const Matrix4x4& aTransformToApzc);
@@ -613,24 +607,16 @@ protected:
   APZCTreeManager* GetApzcTreeManager() const;
 
   /**
    * Gets a ref to the input queue that is shared across the entire tree manager.
    */
   const RefPtr<InputQueue>& GetInputQueue() const;
 
   /**
-   * Timeout function for mozbrowserasyncscroll event. Because we throttle
-   * mozbrowserasyncscroll events in some conditions, this function ensures
-   * that the last mozbrowserasyncscroll event will be fired after a period of
-   * time.
-   */
-  void FireAsyncScrollOnTimeout();
-
-  /**
    * Convert ScreenPoint relative to the screen to CSSPoint relative
    * to the parent document. This excludes the transient compositor transform.
    * NOTE: This must be converted to CSSPoint relative to the child
    * document before sending over IPC to a child process.
    */
   bool ConvertToGecko(const ScreenIntPoint& aPoint, CSSPoint* aOut);
 
   enum AxisLockMode {
@@ -727,29 +713,16 @@ private:
   // The last time the compositor has sampled the content transform for this
   // frame.
   TimeStamp mLastSampleTime;
 
   // Stores the previous focus point if there is a pinch gesture happening. Used
   // to allow panning by moving multiple fingers (thus moving the focus point).
   ParentLayerPoint mLastZoomFocus;
 
-  // The last time and offset we fire the mozbrowserasyncscroll event when
-  // compositor has sampled the content transform for this frame.
-  TimeStamp mLastAsyncScrollTime;
-  CSSPoint mLastAsyncScrollOffset;
-
-  // The current offset drawn on the screen, it may not be sent since we have
-  // throttling policy for mozbrowserasyncscroll event.
-  CSSPoint mCurrentAsyncScrollOffset;
-
-  // The delay task triggered by the throttling mozbrowserasyncscroll event
-  // ensures the last mozbrowserasyncscroll event is always been fired.
-  CancelableTask* mAsyncScrollTimeoutTask;
-
   RefPtr<AsyncPanZoomAnimation> mAnimation;
 
   friend class Axis;
 
 
 
   /* ===================================================================
    * The functions and members in this section are used to manage
--- a/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/layers/apz/test/gtest/TestAsyncPanZoomController.cpp
@@ -62,17 +62,16 @@ private:
 class MockContentController : public GeckoContentController {
 public:
   MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
   MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD3(HandleDoubleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
   MOCK_METHOD3(HandleSingleTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&));
   MOCK_METHOD4(HandleLongTap, void(const CSSPoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
-  MOCK_METHOD3(SendAsyncScrollDOMEvent, void(bool aIsRoot, const CSSRect &aContentRect, const CSSSize &aScrollableSize));
   MOCK_METHOD2(PostDelayedTask, void(Task* aTask, int aDelayMs));
   MOCK_METHOD3(NotifyAPZStateChange, void(const ScrollableLayerGuid& aGuid, APZStateChange aChange, int aArg));
   MOCK_METHOD0(NotifyFlushComplete, void());
 };
 
 class MockContentControllerDelayed : public MockContentController {
 public:
   MockContentControllerDelayed()
@@ -799,20 +798,18 @@ protected:
 
   void DoPinchTest(bool aShouldTriggerPinch,
                    nsTArray<uint32_t> *aAllowedTouchBehaviors = nullptr)
   {
     apzc->SetFrameMetrics(GetPinchableFrameMetrics());
     MakeApzcZoomable();
 
     if (aShouldTriggerPinch) {
-      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
       EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
     } else {
-      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtMost(2));
       EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
     }
 
     int touchInputId = 0;
     if (mGestureBehavior == AsyncPanZoomController::USE_GESTURE_DETECTOR) {
       PinchWithTouchInputAndCheckStatus(apzc, 250, 300, 1.25, touchInputId, aShouldTriggerPinch, aAllowedTouchBehaviors);
     } else {
       PinchWithPinchInputAndCheckStatus(apzc, 250, 300, 1.25, aShouldTriggerPinch);
@@ -933,17 +930,16 @@ TEST_F(APZCBasicTester, Overzoom) {
   fm.SetScrollableRect(CSSRect(0, 0, 125, 150));
   fm.SetScrollOffset(CSSPoint(10, 0));
   fm.SetZoom(CSSToParentLayerScale2D(1.0, 1.0));
   fm.SetIsRootContent(true);
   apzc->SetFrameMetrics(fm);
 
   MakeApzcZoomable();
 
-  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
 
   PinchWithPinchInputAndCheckStatus(apzc, 50, 50, 0.5, true);
 
   fm = apzc->GetFrameMetrics();
   EXPECT_EQ(0.8f, fm.GetZoom().ToScaleFactor().scale);
   // bug 936721 - PGO builds introduce rounding error so
   // use a fuzzy match instead
@@ -1080,20 +1076,18 @@ TEST_F(APZCPinchTester, Panning_TwoFinge
   EXPECT_EQ(2.0, fm.GetZoom().ToScaleFactor().scale);
 }
 
 class APZCPanningTester : public APZCBasicTester {
 protected:
   void DoPanTest(bool aShouldTriggerScroll, bool aShouldBeConsumed, uint32_t aBehavior)
   {
     if (aShouldTriggerScroll) {
-      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
       EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
     } else {
-      EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
       EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
     }
 
     int touchStart = 50;
     int touchEnd = 10;
     ParentLayerPoint pointOut;
     ViewTransform viewTransformOut;
 
@@ -1191,17 +1185,16 @@ TEST_F(APZCPanningTester, PanWithPrevent
 }
 
 TEST_F(APZCPanningTester, PanWithPreventDefault) {
   SCOPED_GFX_PREF(TouchActionEnabled, bool, false);
   DoPanWithPreventDefaultTest();
 }
 
 TEST_F(APZCBasicTester, Fling) {
-  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(AtLeast(1));
   EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
 
   int touchStart = 50;
   int touchEnd = 10;
   ParentLayerPoint pointOut;
   ViewTransform viewTransformOut;
 
   // Fling down. Each step scroll further down
@@ -1605,17 +1598,16 @@ protected:
     check.Call("postHandleSingleTap");
 
     apzc->AssertStateIsReset();
   }
 
   void DoLongPressPreventDefaultTest(uint32_t aBehavior) {
     MakeApzcUnzoomable();
 
-    EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(0);
     EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(0);
 
     int touchX = 10,
         touchStartY = 10,
         touchEndY = 50;
 
     uint64_t blockId = 0;
     nsEventStatus status = TouchDown(apzc, touchX, touchStartY, mcc->Time(), &blockId);
@@ -1931,17 +1923,17 @@ protected:
     metrics.SetAllowVerticalScrollWithWheel();
     aLayer->SetFrameMetrics(metrics);
     aLayer->SetClipRect(Some(ViewAs<ParentLayerPixel>(layerBound)));
     if (!aScrollableRect.IsEqualEdges(CSSRect(-1, -1, -1, -1))) {
       // The purpose of this is to roughly mimic what layout would do in the
       // case of a scrollable frame with the event regions and clip. This lets
       // us exercise the hit-testing code in APZCTreeManager
       EventRegions er = aLayer->GetEventRegions();
-      IntRect scrollRect = LayerIntRect::ToUntyped(RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()));
+      IntRect scrollRect = RoundedToInt(aScrollableRect * metrics.LayersPixelsPerCSSPixel()).ToUnknownRect();
       er.mHitRegion = nsIntRegion(IntRect(layerBound.TopLeft(), scrollRect.Size()));
       aLayer->SetEventRegions(er);
     }
   }
 
   void SetScrollHandoff(Layer* aChild, Layer* aParent) {
     FrameMetrics metrics = aChild->GetFrameMetrics(0);
     metrics.SetScrollParentId(aParent->GetFrameMetrics(0).GetScrollId());
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -46,18 +46,16 @@ public:
 
   virtual void HandleDoubleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
   virtual void HandleSingleTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid) override;
   virtual void HandleLongTap(const mozilla::CSSPoint& aPoint, Modifiers aModifiers,
                                const ScrollableLayerGuid& aGuid,
                                uint64_t aInputBlockId) override;
-  virtual void SendAsyncScrollDOMEvent(bool aIsRootContent, const mozilla::CSSRect &aContentRect,
-                                       const mozilla::CSSSize &aScrollableSize) override {}
   virtual void NotifyAPZStateChange(const ScrollableLayerGuid& aGuid,
                                     APZStateChange aChange,
                                     int aArg) override;
   virtual void NotifyMozMouseScrollEvent(const FrameMetrics::ViewID& aScrollId,
                                          const nsString& aEvent) override;
   virtual void NotifyFlushComplete() override;
 private:
   nsCOMPtr<nsIWidget> mWidget;
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -582,17 +582,17 @@ BasicCompositor::EndFrame()
   // Pop aClipRectIn/bounds rect
   mRenderTarget->mDrawTarget->PopClip();
 
   if (gfxPrefs::WidgetUpdateFlashing()) {
     float r = float(rand()) / RAND_MAX;
     float g = float(rand()) / RAND_MAX;
     float b = float(rand()) / RAND_MAX;
     // We're still clipped to mInvalidRegion, so just fill the bounds.
-    mRenderTarget->mDrawTarget->FillRect(ToRect(mInvalidRegion.GetBounds()),
+    mRenderTarget->mDrawTarget->FillRect(IntRectToRect(mInvalidRegion.GetBounds()),
                                          ColorPattern(Color(r, g, b, 0.2f)));
   }
 
   // Pop aInvalidregion
   mRenderTarget->mDrawTarget->PopClip();
 
   // Note: Most platforms require us to buffer drawing to the widget surface.
   // That's why we don't draw to mDrawTarget directly.
--- a/gfx/layers/basic/BasicContainerLayer.cpp
+++ b/gfx/layers/basic/BasicContainerLayer.cpp
@@ -120,17 +120,17 @@ BasicContainerLayer::ChildrenPartitionVi
     if (!l->GetEffectiveTransform().CanDraw2D(&childTransform) ||
         ThebesMatrix(childTransform).HasNonIntegerTranslation() ||
         l->GetEffectiveOpacity() != 1.0)
       return false;
     nsIntRegion childRegion = l->GetEffectiveVisibleRegion();
     childRegion.MoveBy(int32_t(childTransform._31), int32_t(childTransform._32));
     childRegion.And(childRegion, rect);
     if (l->GetClipRect()) {
-      childRegion.And(childRegion, ParentLayerIntRect::ToUntyped(*l->GetClipRect()) + offset);
+      childRegion.And(childRegion, l->GetClipRect()->ToUnknownRect() + offset);
     }
     nsIntRegion intersection;
     intersection.And(covered, childRegion);
     if (!intersection.IsEmpty())
       return false;
     covered.Or(covered, childRegion);
   }
 
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -76,46 +76,123 @@ ClipToContain(gfxContext* aContext, cons
   aContext->NewPath();
   aContext->Rectangle(deviceRect);
   aContext->Clip();
   aContext->SetMatrix(currentMatrix);
 
   return aContext->DeviceToUser(deviceRect).IsEqualInterior(userRect);
 }
 
-already_AddRefed<gfxContext>
-BasicLayerManager::PushGroupForLayer(gfxContext* aContext, Layer* aLayer,
-                                     const nsIntRegion& aRegion,
-                                     bool* aNeedsClipToVisibleRegion)
+BasicLayerManager::PushedGroup
+BasicLayerManager::PushGroupForLayer(gfxContext* aContext, Layer* aLayer, const nsIntRegion& aRegion)
 {
+  PushedGroup group;
+
+  group.mVisibleRegion = aRegion;
+  group.mFinalTarget = aContext;
+  group.mOperator = GetEffectiveOperator(aLayer);
+  group.mOpacity = aLayer->GetEffectiveOpacity();
+
   // If we need to call PushGroup, we should clip to the smallest possible
   // area first to minimize the size of the temporary surface.
   bool didCompleteClip = ClipToContain(aContext, aRegion.GetBounds());
 
-  RefPtr<gfxContext> result;
+  bool canPushGroup = group.mOperator == CompositionOp::OP_OVER ||
+    (group.mOperator == CompositionOp::OP_SOURCE && (aLayer->CanUseOpaqueSurface() || aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA));
+
+  if (!canPushGroup) {
+    aContext->Save();
+    gfxUtils::ClipToRegion(group.mFinalTarget, group.mVisibleRegion);
+
+    // PushGroup/PopGroup do not support non operator over.
+    gfxMatrix oldMat = aContext->CurrentMatrix();
+    aContext->SetMatrix(gfxMatrix());
+    gfxRect rect = aContext->GetClipExtents();
+    aContext->SetMatrix(oldMat);
+    rect.RoundOut();
+    IntRect surfRect;
+    ToRect(rect).ToIntRect(&surfRect);
+
+    RefPtr<DrawTarget> dt = aContext->GetDrawTarget()->CreateSimilarDrawTarget(surfRect.Size(), SurfaceFormat::B8G8R8A8);
+
+    RefPtr<gfxContext> ctx = new gfxContext(dt, ToRect(rect).TopLeft());
+    ctx->SetMatrix(oldMat);
+
+    group.mGroupOffset = surfRect.TopLeft();
+    group.mGroupTarget = ctx;
+
+    group.mMaskSurface = GetMaskForLayer(aLayer, &group.mMaskTransform);
+    return group;
+  }
+
+  Matrix maskTransform;
+  RefPtr<SourceSurface> maskSurf = GetMaskForLayer(aLayer, &maskTransform);
+
   if (aLayer->CanUseOpaqueSurface() &&
       ((didCompleteClip && aRegion.GetNumRects() == 1) ||
        !aContext->CurrentMatrix().HasNonIntegerTranslation())) {
     // If the layer is opaque in its visible region we can push a gfxContentType::COLOR
     // group. We need to make sure that only pixels inside the layer's visible
     // region are copied back to the destination. Remember if we've already
     // clipped precisely to the visible region.
-    *aNeedsClipToVisibleRegion = !didCompleteClip || aRegion.GetNumRects() > 1;
-    aContext->PushGroup(gfxContentType::COLOR);
-    result = aContext;
+    group.mNeedsClipToVisibleRegion = !didCompleteClip || aRegion.GetNumRects() > 1;
+    if (group.mNeedsClipToVisibleRegion) {
+      group.mFinalTarget->Save();
+      gfxUtils::ClipToRegion(group.mFinalTarget, group.mVisibleRegion);
+    }
+
+    aContext->PushGroupForBlendBack(gfxContentType::COLOR, group.mOpacity, maskSurf, maskTransform);
   } else {
-    *aNeedsClipToVisibleRegion = false;
-    result = aContext;
     if (aLayer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA) {
-      aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA);
+      aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, group.mOpacity, maskSurf, maskTransform);
     } else {
-      aContext->PushGroup(gfxContentType::COLOR_ALPHA);
+      aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, group.mOpacity, maskSurf, maskTransform);
     }
   }
-  return result.forget();
+
+  group.mGroupTarget = group.mFinalTarget;
+  return group;
+}
+
+void
+BasicLayerManager::PopGroupForLayer(PushedGroup &group)
+{
+  if (group.mFinalTarget == group.mGroupTarget) {
+    group.mFinalTarget->PopGroupAndBlend();
+    if (group.mNeedsClipToVisibleRegion) {
+      group.mFinalTarget->Restore();
+    }
+    return;
+  }
+
+  DrawTarget* dt = group.mFinalTarget->GetDrawTarget();
+  RefPtr<DrawTarget> sourceDT = group.mGroupTarget->GetDrawTarget();
+  group.mGroupTarget = nullptr;
+
+  RefPtr<SourceSurface> src = sourceDT->Snapshot();
+
+  if (group.mMaskSurface) {
+    dt->SetTransform(group.mMaskTransform * Matrix::Translation(-group.mFinalTarget->GetDeviceOffset()));
+    dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, Matrix::Translation(group.mGroupOffset.x, group.mGroupOffset.y)),
+                    group.mMaskSurface, Point(0, 0), DrawOptions(group.mOpacity, group.mOperator));
+  } else {
+    // For now this is required since our group offset is in device space of the final target,
+    // context but that may still have its own device offset. Once PushGroup/PopGroup logic is
+    // migrated to DrawTargets this can go as gfxContext::GetDeviceOffset will essentially
+    // always become null.
+    dt->SetTransform(Matrix::Translation(-group.mFinalTarget->GetDeviceOffset()));
+    dt->DrawSurface(src, Rect(group.mGroupOffset.x, group.mGroupOffset.y, src->GetSize().width, src->GetSize().height),
+                    Rect(0, 0, src->GetSize().width, src->GetSize().height), DrawSurfaceOptions(Filter::POINT), DrawOptions(group.mOpacity, group.mOperator));
+  }
+
+  if (group.mNeedsClipToVisibleRegion) {
+    dt->PopClip();
+  }
+
+  group.mFinalTarget->Restore();
 }
 
 static IntRect
 ToInsideIntRect(const gfxRect& aRect)
 {
   gfxRect r = aRect;
   r.RoundIn();
   return IntRect(r.X(), r.Y(), r.Width(), r.Height());
@@ -319,17 +396,17 @@ MarkLayersHidden(Layer* aLayer, const In
   // only if it's opaque.
   if (aLayer->GetOpacity() != 1.0f) {
     newFlags &= ~ALLOW_OPAQUE;
   }
 
   {
     const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetEffectiveClipRect();
     if (clipRect) {
-      IntRect cr = ParentLayerIntRect::ToUntyped(*clipRect);
+      IntRect cr = clipRect->ToUnknownRect();
       // clipRect is in the container's coordinate system. Get it into the
       // global coordinate system.
       if (aLayer->GetParent()) {
         Matrix tr;
         if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) {
           // Clip rect is applied after aLayer's transform, i.e., in the coordinate
           // system of aLayer's parent.
           TransformIntRect(cr, tr, ToInsideIntRect);
@@ -399,17 +476,17 @@ ApplyDoubleBuffering(Layer* aLayer, cons
   if (data->IsHidden())
     return;
 
   IntRect newVisibleRect(aVisibleRect);
 
   {
     const Maybe<ParentLayerIntRect>& clipRect = aLayer->GetEffectiveClipRect();
     if (clipRect) {
-      IntRect cr = ParentLayerIntRect::ToUntyped(*clipRect);
+      IntRect cr = clipRect->ToUnknownRect();
       // clipRect is in the container's coordinate system. Get it into the
       // global coordinate system.
       if (aLayer->GetParent()) {
         Matrix tr;
         if (aLayer->GetParent()->GetEffectiveTransform().CanDraw2D(&tr)) {
           NS_ASSERTION(!ThebesMatrix(tr).HasNonIntegerTranslation(),
                        "Parent can only have an integer translation");
           cr += nsIntPoint(int32_t(tr._31), int32_t(tr._32));
@@ -968,21 +1045,20 @@ BasicLayerManager::PaintLayer(gfxContext
   bool clipIsEmpty = aTarget->GetClipExtents().IsEmpty();
   if (clipIsEmpty) {
     PaintSelfOrChildren(paintLayerContext, aTarget);
     return;
   }
 
   if (is2D) {
     if (needsGroup) {
-      RefPtr<gfxContext> groupTarget = PushGroupForLayer(aTarget, aLayer, aLayer->GetEffectiveVisibleRegion(),
-                                      &needsClipToVisibleRegion);
-      PaintSelfOrChildren(paintLayerContext, groupTarget);
-      aTarget->PopGroupToSource();
-      FlushGroup(paintLayerContext, needsClipToVisibleRegion);
+      PushedGroup pushedGroup =
+        PushGroupForLayer(aTarget, aLayer, aLayer->GetEffectiveVisibleRegion());
+      PaintSelfOrChildren(paintLayerContext, pushedGroup.mGroupTarget);
+      PopGroupForLayer(pushedGroup);
     } else {
       PaintSelfOrChildren(paintLayerContext, aTarget);
     }
   } else {
     if (!needsGroup && container) {
       PaintSelfOrChildren(paintLayerContext, aTarget);
       return;
     }
@@ -1013,17 +1089,17 @@ BasicLayerManager::PaintLayer(gfxContext
 
       RefPtr<gfxContext> temp = new gfxContext(untransformedDT, Point(bounds.x, bounds.y));
       temp->SetColor(color);
       temp->Paint();
     }
 #endif
     Matrix4x4 effectiveTransform = aLayer->GetEffectiveTransform();
     RefPtr<gfxASurface> result =
-      Transform3D(untransformedDT->Snapshot(), aTarget, bounds,
+      Transform3D(untransformedDT->Snapshot(), aTarget, ThebesRect(bounds),
                   effectiveTransform, destRect);
 
     if (result) {
       aTarget->SetSource(result, destRect.TopLeft());
       // Azure doesn't support EXTEND_NONE, so to avoid extending the edges
       // of the source surface out to the current clip region, clip to
       // the rectangle of the result surface now.
       aTarget->NewPath();
--- a/gfx/layers/basic/BasicLayers.h
+++ b/gfx/layers/basic/BasicLayers.h
@@ -133,19 +133,33 @@ public:
   virtual const char* Name() const override { return "Basic"; }
 
   // Clear the cached contents of this layer tree.
   virtual void ClearCachedResources(Layer* aSubtree = nullptr) override;
 
   void SetTransactionIncomplete() { mTransactionIncomplete = true; }
   bool IsTransactionIncomplete() { return mTransactionIncomplete; }
 
-  already_AddRefed<gfxContext> PushGroupForLayer(gfxContext* aContext, Layer* aLayer,
-                                                 const nsIntRegion& aRegion,
-                                                 bool* aNeedsClipToVisibleRegion);
+  struct PushedGroup
+  {
+    PushedGroup() : mFinalTarget(nullptr), mNeedsClipToVisibleRegion(false) {}
+    gfxContext* mFinalTarget;
+    RefPtr<gfxContext> mGroupTarget;
+    nsIntRegion mVisibleRegion;
+    bool mNeedsClipToVisibleRegion;
+    gfx::IntPoint mGroupOffset;
+    gfx::CompositionOp mOperator;
+    gfx::Float mOpacity;
+    RefPtr<gfx::SourceSurface> mMaskSurface;
+    gfx::Matrix mMaskTransform;
+  };
+
+  PushedGroup PushGroupForLayer(gfxContext* aContext, Layer* aLayerContext, const nsIntRegion& aRegion);
+
+  void PopGroupForLayer(PushedGroup& aGroup);
 
   virtual bool IsCompositingCheap() override { return false; }
   virtual int32_t GetMaxTextureSize() const override { return INT32_MAX; }
   bool CompositorMightResample() { return mCompositorMightResample; }
 
   virtual bool SupportsMixBlendModes(EnumSet<gfx::CompositionOp>& aMixBlendModes) override { return true; }
 
 protected:
--- a/gfx/layers/basic/BasicLayersImpl.cpp
+++ b/gfx/layers/basic/BasicLayersImpl.cpp
@@ -34,16 +34,35 @@ GetMaskData(Layer* aMaskLayer,
       transform.PostTranslate(-aDeviceOffset.x, -aDeviceOffset.y);
       aMaskData->Construct(transform, surface);
       return true;
     }
   }
   return false;
 }
 
+already_AddRefed<SourceSurface>
+GetMaskForLayer(Layer* aLayer, Matrix* aMaskTransform)
+{
+  if (!aLayer->GetMaskLayer()) {
+    return nullptr;
+  }
+
+  MOZ_ASSERT(aMaskTransform);
+
+  AutoMoz2DMaskData mask;
+  if (GetMaskData(aLayer->GetMaskLayer(), Point(), &mask)) {
+    *aMaskTransform = mask.GetTransform();
+    RefPtr<SourceSurface> surf = mask.GetSurface();
+    return surf.forget();
+  }
+
+  return nullptr;
+}
+
 void
 PaintWithMask(gfxContext* aContext, float aOpacity, Layer* aMaskLayer)
 {
   AutoMoz2DMaskData mask;
   if (GetMaskData(aMaskLayer, Point(), &mask)) {
     aContext->SetMatrix(ThebesMatrix(mask.GetTransform()));
     aContext->Mask(mask.GetSurface(), aOpacity);
     return;
--- a/gfx/layers/basic/BasicLayersImpl.h
+++ b/gfx/layers/basic/BasicLayersImpl.h
@@ -83,16 +83,18 @@ protected:
  * false otherwise.
  * The transform for the layer will be put in aMaskData
  */
 bool
 GetMaskData(Layer* aMaskLayer,
             const gfx::Point& aDeviceOffset,
             AutoMoz2DMaskData* aMaskData);
 
+already_AddRefed<gfx::SourceSurface> GetMaskForLayer(Layer* aLayer, gfx::Matrix* aMaskTransform);
+
 // Paint the current source to a context using a mask, if present
 void
 PaintWithMask(gfxContext* aContext, float aOpacity, Layer* aMaskLayer);
 
 // Fill the rect with the source, using a mask and opacity, if present
 void
 FillRectWithMask(gfx::DrawTarget* aDT,
                  const gfx::Rect& aRect,
--- a/gfx/layers/basic/BasicPaintedLayer.cpp
+++ b/gfx/layers/basic/BasicPaintedLayer.cpp
@@ -70,41 +70,33 @@ BasicPaintedLayer::PaintThebes(gfxContex
     if (!toDraw.IsEmpty() && !IsHidden()) {
       if (!aCallback) {
         BasicManager()->SetTransactionIncomplete();
         return;
       }
 
       aContext->Save();
 
-      bool needsClipToVisibleRegion = GetClipToVisibleRegion();
       bool needsGroup = opacity != 1.0 ||
                         effectiveOperator != CompositionOp::OP_OVER ||
                         aMaskLayer;
       RefPtr<gfxContext> groupContext;
+      BasicLayerManager::PushedGroup group;
       if (needsGroup) {
-        groupContext =
-          BasicManager()->PushGroupForLayer(aContext, this, toDraw,
-                                            &needsClipToVisibleRegion);
-        if (effectiveOperator != CompositionOp::OP_OVER) {
-          needsClipToVisibleRegion = true;
-        }
+        group =
+          BasicManager()->PushGroupForLayer(aContext, this, toDraw);
+        groupContext = group.mGroupTarget;
       } else {
         groupContext = aContext;
       }
       SetAntialiasingFlags(this, groupContext->GetDrawTarget());
       aCallback(this, groupContext, toDraw, toDraw,
                 DrawRegionClip::NONE, nsIntRegion(), aCallbackData);
       if (needsGroup) {
-        aContext->PopGroupToSource();
-        if (needsClipToVisibleRegion) {
-          gfxUtils::ClipToRegion(aContext, toDraw);
-        }
-        AutoSetOperator setOptimizedOperator(aContext, effectiveOperator);
-        PaintWithMask(aContext, opacity, aMaskLayer);
+        BasicManager()->PopGroupForLayer(group);
       }
 
       aContext->Restore();
     }
 
     RenderTraceInvalidateEnd(this, "FFFF00");
     return;
   }
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -301,30 +301,30 @@ ClientTiledPaintedLayer::RenderHighPreci
   if (UseProgressiveDraw() &&
       mContentClient->GetTiledBuffer()->GetFrameResolution() == mPaintData.mResolution) {
     // Store the old valid region, then clear it before painting.
     // We clip the old valid region to the visible region, as it only gets
     // used to decide stale content (currently valid and previously visible)
     nsIntRegion oldValidRegion = mContentClient->GetTiledBuffer()->GetValidRegion();
     oldValidRegion.And(oldValidRegion, aVisibleRegion);
     if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
-      oldValidRegion.And(oldValidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+      oldValidRegion.And(oldValidRegion, mPaintData.mCriticalDisplayPort.ToUnknownRect());
     }
 
     TILING_LOG("TILING %p: Progressive update with old valid region %s\n", this, Stringify(oldValidRegion).c_str());
 
     return mContentClient->GetTiledBuffer()->ProgressiveUpdate(mValidRegion, aInvalidRegion,
                       oldValidRegion, &mPaintData, aCallback, aCallbackData);
   }
 
   // Otherwise do a non-progressive paint
 
   mValidRegion = aVisibleRegion;
   if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
-    mValidRegion.And(mValidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+    mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort.ToUnknownRect());
   }
 
   TILING_LOG("TILING %p: Non-progressive paint invalid region %s\n", this, Stringify(aInvalidRegion).c_str());
   TILING_LOG("TILING %p: Non-progressive paint new valid region %s\n", this, Stringify(mValidRegion).c_str());
 
   mContentClient->GetTiledBuffer()->SetFrameResolution(mPaintData.mResolution);
   mContentClient->GetTiledBuffer()->PaintThebes(mValidRegion, aInvalidRegion, aInvalidRegion,
                                                 aCallback, aCallbackData);
@@ -335,17 +335,17 @@ ClientTiledPaintedLayer::RenderHighPreci
 bool
 ClientTiledPaintedLayer::RenderLowPrecision(nsIntRegion& aInvalidRegion,
                                            const nsIntRegion& aVisibleRegion,
                                            LayerManager::DrawPaintedLayerCallback aCallback,
                                            void* aCallbackData)
 {
   // Render the low precision buffer, if the visible region is larger than the
   // critical display port.
-  if (!nsIntRegion(LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort)).Contains(aVisibleRegion)) {
+  if (!nsIntRegion(mPaintData.mCriticalDisplayPort.ToUnknownRect()).Contains(aVisibleRegion)) {
     nsIntRegion oldValidRegion = mContentClient->GetLowPrecisionTiledBuffer()->GetValidRegion();
     oldValidRegion.And(oldValidRegion, aVisibleRegion);
 
     bool updatedBuffer = false;
 
     // If the frame resolution or format have changed, invalidate the buffer
     if (mContentClient->GetLowPrecisionTiledBuffer()->GetFrameResolution() != mPaintData.mResolution ||
         mContentClient->GetLowPrecisionTiledBuffer()->HasFormatChanged()) {
@@ -482,25 +482,25 @@ ClientTiledPaintedLayer::RenderLayer()
       return;
     }
 
     // Make sure that tiles that fall outside of the visible region or outside of the
     // critical displayport are discarded on the first update. Also make sure that we
     // only draw stuff inside the critical displayport on the first update.
     mValidRegion.And(mValidRegion, neededRegion);
     if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
-      mValidRegion.And(mValidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
-      invalidRegion.And(invalidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+      mValidRegion.And(mValidRegion, mPaintData.mCriticalDisplayPort.ToUnknownRect());
+      invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort.ToUnknownRect());
     }
 
     TILING_LOG("TILING %p: First-transaction valid region %s\n", this, Stringify(mValidRegion).c_str());
     TILING_LOG("TILING %p: First-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str());
   } else {
     if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
-      invalidRegion.And(invalidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+      invalidRegion.And(invalidRegion, mPaintData.mCriticalDisplayPort.ToUnknownRect());
     }
     TILING_LOG("TILING %p: Repeat-transaction invalid region %s\n", this, Stringify(invalidRegion).c_str());
   }
 
   nsIntRegion lowPrecisionInvalidRegion;
   if (mContentClient->GetLowPrecisionTiledBuffer()) {
     // Calculate the invalid region for the low precision buffer. Make sure
     // to remove the valid high-precision area so we don't double-paint it.
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -1503,23 +1503,23 @@ ClientMultiTiledLayerBuffer::ComputeProg
   // single transaction. This is to avoid rendering glitches on animated
   // page content, and when layers change size/shape.
   // On Fennec uploads are more expensive because we're not using gralloc, so
   // we use a coherent update rect that is intersected with the screen at the
   // time of issuing the draw command. This will paint faster but also potentially
   // make the progressive paint more visible to the user while scrolling.
   // On B2G uploads are cheaper and we value coherency more, especially outside
   // the browser, so we always use the entire user-visible area.
-  IntRect coherentUpdateRect(LayerIntRect::ToUntyped(RoundedOut(
+  IntRect coherentUpdateRect(RoundedOut(
 #ifdef MOZ_WIDGET_ANDROID
     transformedCompositionBounds->Intersect(aPaintData->mCompositionBounds)
 #else
     *transformedCompositionBounds
 #endif
-  )));
+  ).ToUnknownRect());
 
   TILING_LOG("TILING %p: Progressive update final coherency rect %s\n", mPaintedLayer, Stringify(coherentUpdateRect).c_str());
 
   aRegionToPaint.And(aInvalidRegion, coherentUpdateRect);
   aRegionToPaint.Or(aRegionToPaint, staleRegion);
   bool drawingStale = !aRegionToPaint.IsEmpty();
   if (!drawingStale) {
     aRegionToPaint = aInvalidRegion;
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -278,27 +278,31 @@ AccumulateLayerTransforms(Layer* aLayer,
 static LayerPoint
 GetLayerFixedMarginsOffset(Layer* aLayer,
                            const ScreenMargin& aFixedLayerMargins)
 {
   // Work out the necessary translation, in root scrollable layer space.
   // Because fixed layer margins are stored relative to the root scrollable
   // layer, we can just take the difference between these values.
   LayerPoint translation;
-  const LayerPoint& anchor = aLayer->GetFixedPositionAnchor();
+  int32_t sides = aLayer->GetFixedPositionSides();
 
-  if (anchor.x > 0) {
+  if ((sides & eSideBitsLeftRight) == eSideBitsLeftRight) {
+    translation.x += (aFixedLayerMargins.left - aFixedLayerMargins.right) / 2;
+  } else if (sides & eSideBitsRight) {
     translation.x -= aFixedLayerMargins.right;
-  } else {
+  } else if (sides & eSideBitsLeft) {
     translation.x += aFixedLayerMargins.left;
   }
 
-  if (anchor.y > 0) {
+  if ((sides & eSideBitsTopBottom) == eSideBitsTopBottom) {
+    translation.y += (aFixedLayerMargins.top - aFixedLayerMargins.bottom) / 2;
+  } else if (sides & eSideBitsBottom) {
     translation.y -= aFixedLayerMargins.bottom;
-  } else {
+  } else if (sides & eSideBitsTop) {
     translation.y += aFixedLayerMargins.top;
   }
 
   return translation;
 }
 
 static gfxFloat
 IntervalOverlap(gfxFloat aTranslation, gfxFloat aMin, gfxFloat aMax)
--- a/gfx/layers/composite/ContainerLayerComposite.cpp
+++ b/gfx/layers/composite/ContainerLayerComposite.cpp
@@ -221,19 +221,21 @@ ContainerRenderVR(ContainerT* aContainer
       // the eye resolution (that is, returned by the recommended
       // render rect from the HMD device), then we need to scale it
       // up/down.
       Rect layerBounds;
       // XXX this is a hack! Canvas layers aren't reporting the
       // proper bounds here (visible region bounds are 0,0,0,0)
       // and I'm not sure if this is the bounds we want anyway.
       if (layer->GetType() == Layer::TYPE_CANVAS) {
-        layerBounds = ToRect(static_cast<CanvasLayer*>(layer)->GetBounds());
+        layerBounds =
+          IntRectToRect(static_cast<CanvasLayer*>(layer)->GetBounds());
       } else {
-        layerBounds = ToRect(layer->GetEffectiveVisibleRegion().GetBounds());
+        layerBounds =
+          IntRectToRect(layer->GetEffectiveVisibleRegion().GetBounds());
       }
       const gfx::Matrix4x4 childTransform = layer->GetEffectiveTransform();
       layerBounds = childTransform.TransformBounds(layerBounds);
 
       DUMP("  layer %p [type %d] bounds [%f %f %f %f] surfaceRect [%d %d %d %d]\n", layer, (int) layer->GetType(),
            XYWH(layerBounds), XYWH(surfaceRect));
 
       bool restoreTransform = false;
@@ -404,17 +406,17 @@ ContainerPrepare(ContainerT* aContainer,
       }
 
       if (!surface) {
         // If we don't need a copy we can render to the intermediate now to avoid
         // unecessary render target switching. This brings a big perf boost on mobile gpus.
         surface = CreateOrRecycleTarget(aContainer, aManager);
 
         MOZ_PERFORMANCE_WARNING("gfx", "[%p] Container layer requires intermediate surface rendering\n", aContainer);
-        RenderIntermediate(aContainer, aManager, RenderTargetPixel::ToUntyped(aClipRect), surface);
+        RenderIntermediate(aContainer, aManager, aClipRect.ToUnknownRect(), surface);
         aContainer->SetChildrenChanged(false);
       }
 
       aContainer->mPrepared->mTmpTarget = surface;
     } else {
       MOZ_PERFORMANCE_WARNING("gfx", "[%p] Container layer requires intermediate surface copy\n", aContainer);
       aContainer->mPrepared->mNeedsSurfaceCopy = true;
       aContainer->mLastIntermediateSurface = nullptr;
@@ -556,17 +558,17 @@ RenderLayers(ContainerT* aContainer,
       gfx::IntRect clearRect = layerToRender->GetClearRect();
       if (!clearRect.IsEmpty()) {
         // Clear layer's visible rect on FrameBuffer with transparent pixels
         gfx::Rect fbRect(clearRect.x, clearRect.y, clearRect.width, clearRect.height);
         compositor->ClearRect(fbRect);
         layerToRender->SetClearRect(gfx::IntRect(0, 0, 0, 0));
       }
     } else {
-      layerToRender->RenderLayer(RenderTargetPixel::ToUntyped(clipRect));
+      layerToRender->RenderLayer(clipRect.ToUnknownRect());
     }
 
     if (gfxPrefs::UniformityInfo()) {
       PrintUniformityInfo(layer);
     }
 
     if (gfxPrefs::DrawLayerInfo()) {
       DrawLayerInfo(clipRect, aManager, layer);
@@ -664,17 +666,17 @@ RenderIntermediate(ContainerT* aContaine
   RefPtr<CompositingRenderTarget> previousTarget = compositor->GetCurrentRenderTarget();
 
   if (!surface) {
     return;
   }
 
   compositor->SetRenderTarget(surface);
   // pre-render all of the layers into our temporary
-  RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect));
+  RenderLayers(aContainer, aManager, RenderTargetIntRect::FromUnknownRect(aClipRect));
   // Unbind the current surface and rebind the previous one.
   compositor->SetRenderTarget(previousTarget);
 }
 
 template<class ContainerT> void
 ContainerRender(ContainerT* aContainer,
                  LayerManagerComposite* aManager,
                  const gfx::IntRect& aClipRect)
@@ -720,17 +722,17 @@ ContainerRender(ContainerT* aContainer,
     RenderWithAllMasks(aContainer, compositor, aClipRect,
                        [&, surface, compositor, container](EffectChain& effectChain, const Rect& clipRect) {
       effectChain.mPrimaryEffect = new EffectRenderTarget(surface);
       compositor->DrawQuad(visibleRect, clipRect, effectChain,
                            container->GetEffectiveOpacity(),
                            container->GetEffectiveTransform());
     });
   } else {
-    RenderLayers(aContainer, aManager, RenderTargetPixel::FromUntyped(aClipRect));
+    RenderLayers(aContainer, aManager, RenderTargetIntRect::FromUnknownRect(aClipRect));
   }
   aContainer->mPrepared = nullptr;
 
   // If it is a scrollable container layer with no child layers, and one of the APZCs
   // attached to it has a nonempty async transform, then that transform is not applied
   // to any visible content. Display a warning box (conditioned on the FPS display being
   // enabled).
   if (gfxPrefs::LayersDrawFPS() && aContainer->IsScrollInfoLayer()) {
--- a/gfx/layers/composite/LayerManagerComposite.cpp
+++ b/gfx/layers/composite/LayerManagerComposite.cpp
@@ -242,17 +242,17 @@ LayerManagerComposite::ApplyOcclusionCul
       !aLayer->HasMaskLayers() &&
       aLayer->IsOpaqueForVisibility()) {
     if (aLayer->GetContentFlags() & Layer::CONTENT_OPAQUE) {
       localOpaque.Or(localOpaque, composite->GetFullyRenderedRegion());
     }
     localOpaque.MoveBy(transform2d._31, transform2d._32);
     const Maybe<ParentLayerIntRect>& clip = aLayer->GetEffectiveClipRect();
     if (clip) {
-      localOpaque.And(localOpaque, ParentLayerIntRect::ToUntyped(*clip));
+      localOpaque.And(localOpaque, clip->ToUnknownRect());
     }
     aOpaqueRegion.Or(aOpaqueRegion, localOpaque);
   }
 }
 
 void
 LayerManagerComposite::EndTransaction(const TimeStamp& aTimeStamp,
                                       EndTransactionFlags aFlags)
@@ -298,18 +298,23 @@ LayerManagerComposite::EndTransaction(co
   MOZ_LAYERS_LOG(("]----- EndTransaction"));
 #endif
 }
 
 void
 LayerManagerComposite::UpdateAndRender()
 {
   nsIntRegion invalid;
+  bool didEffectiveTransforms = false;
 
   if (mClonedLayerTreeProperties) {
+    // Effective transforms are needed by ComputeDifferences().
+    mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
+    didEffectiveTransforms = true;
+
     // We need to compute layer tree differences even if we're not going to
     // immediately use the resulting damage area, since ComputeDifferences
     // is also responsible for invalidates intermediate surfaces in
     // ContainerLayers.
     nsIntRegion changed = mClonedLayerTreeProperties->ComputeDifferences(mRoot, nullptr, &mGeometryChanged);
 
     if (mTarget) {
       // Since we're composing to an external target, we're not going to use
@@ -344,19 +349,21 @@ LayerManagerComposite::UpdateAndRender()
     // Composition requested, but nothing has changed. Don't do any work.
     return;
   }
 
   // We don't want our debug overlay to cause more frames to happen
   // so we will invalidate after we've decided if something changed.
   InvalidateDebugOverlay(mRenderBounds);
 
-  // The results of our drawing always go directly into a pixel buffer,
-  // so we don't need to pass any global transform here.
-  mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
+  if (!didEffectiveTransforms) {
+    // The results of our drawing always go directly into a pixel buffer,
+    // so we don't need to pass any global transform here.
+    mRoot->ComputeEffectiveTransforms(gfx::Matrix4x4());
+  }
 
   nsIntRegion opaque;
   ApplyOcclusionCulling(mRoot, opaque);
 
   Render(invalid);
 #if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   RenderToPresentationSurface();
 #endif
@@ -781,29 +788,29 @@ LayerManagerComposite::Render(const nsIn
   if (haveLayerEffects) {
     previousTarget = PushGroupForLayerEffects();
   } else {
     mTwoPassTmpTarget = nullptr;
   }
 
   // Render our layers.
   RootLayer()->Prepare(ViewAs<RenderTargetPixel>(clipRect, PixelCastJustification::RenderTargetIsParentLayerForRoot));
-  RootLayer()->RenderLayer(ParentLayerIntRect::ToUntyped(clipRect));
+  RootLayer()->RenderLayer(clipRect.ToUnknownRect());
 
   if (!mRegionToClear.IsEmpty()) {
     nsIntRegionRectIterator iter(mRegionToClear);
     const IntRect *r;
     while ((r = iter.Next())) {
       mCompositor->ClearRect(Rect(r->x, r->y, r->width, r->height));
     }
   }
 
   if (mTwoPassTmpTarget) {
     MOZ_ASSERT(haveLayerEffects);
-    PopGroupForLayerEffects(previousTarget, ParentLayerIntRect::ToUntyped(clipRect),
+    PopGroupForLayerEffects(previousTarget, clipRect.ToUnknownRect(),
                             grayscaleVal, invertVal, contrastVal);
   }
 
   // Allow widget to render a custom foreground.
   mCompositor->GetWidget()->DrawWindowOverlay(this, IntRect(actualBounds.x,
                                                               actualBounds.y,
                                                               actualBounds.width,
                                                               actualBounds.height));
@@ -1026,17 +1033,17 @@ LayerManagerComposite::RenderToPresentat
   // chrome such as the URL bar. Override that so that the entire frame buffer
   // is cleared.
   ScopedScissorRect scissorRect(egl, 0, 0, actualWidth, actualHeight);
   egl->fClearColor(0.0, 0.0, 0.0, 0.0);
   egl->fClear(LOCAL_GL_COLOR_BUFFER_BIT);
 
   const IntRect clipRect = IntRect(0, 0, actualWidth, actualHeight);
 
-  RootLayer()->Prepare(RenderTargetPixel::FromUntyped(clipRect));
+  RootLayer()->Prepare(RenderTargetIntRect::FromUnknownRect(clipRect));
   RootLayer()->RenderLayer(clipRect);
 
   mCompositor->EndFrame();
 #ifdef MOZ_WIDGET_GONK
   mCompositor->SetDispAcquireFence(mRoot,
                                    mirrorScreenWidget); // Call after EndFrame()
 
   RefPtr<Composer2D> composer2D;
@@ -1056,17 +1063,17 @@ SubtractTransformedRegion(nsIntRegion& a
   if (aRegionToSubtract.IsEmpty()) {
     return;
   }
 
   // For each rect in the region, find out its bounds in screen space and
   // subtract it from the screen region.
   nsIntRegionRectIterator it(aRegionToSubtract);
   while (const IntRect* rect = it.Next()) {
-    Rect incompleteRect = aTransform.TransformAndClipBounds(ToRect(*rect),
+    Rect incompleteRect = aTransform.TransformAndClipBounds(IntRectToRect(*rect),
                                                             Rect::MaxIntRect());
     aRegion.Sub(aRegion, IntRect(incompleteRect.x,
                                    incompleteRect.y,
                                    incompleteRect.width,
                                    incompleteRect.height));
   }
 }
 
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -322,16 +322,17 @@ LayerTransactionParent::RecvUpdate(Infal
       layer->SetOpacity(common.opacity());
       layer->SetClipRect(common.useClipRect() ? Some(common.clipRect()) : Nothing());
       layer->SetBaseTransform(common.transform().value());
       layer->SetPostScale(common.postXScale(), common.postYScale());
       layer->SetIsFixedPosition(common.isFixedPosition());
       if (common.isFixedPosition()) {
         layer->SetFixedPositionData(common.fixedPositionScrollContainerId(),
                                     common.fixedPositionAnchor(),
+                                    common.fixedPositionSides(),
                                     common.isClipFixed());
       }
       if (common.isStickyPosition()) {
         layer->SetStickyPositionData(common.stickyScrollContainerId(),
                                      common.stickyScrollRangeOuter(),
                                      common.stickyScrollRangeInner());
       }
       layer->SetScrollbarData(common.scrollbarTargetContainerId(),
@@ -775,17 +776,17 @@ GetAPZCForViewID(Layer* aLayer, FrameMet
       }
     }
   }
   return nullptr;
 }
 
 bool
 LayerTransactionParent::RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aScrollID,
-                                                 const int32_t& aX, const int32_t& aY)
+                                                 const float& aX, const float& aY)
 {
   if (mDestroyed || !layer_manager() || layer_manager()->IsDestroyed()) {
     return false;
   }
 
   AsyncPanZoomController* controller = GetAPZCForViewID(mRoot, aScrollID);
   if (!controller) {
     return false;
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -127,17 +127,17 @@ protected:
   virtual bool RecvSetTestSampleTime(const TimeStamp& aTime) override;
   virtual bool RecvLeaveTestMode() override;
   virtual bool RecvGetOpacity(PLayerParent* aParent,
                               float* aOpacity) override;
   virtual bool RecvGetAnimationTransform(PLayerParent* aParent,
                                          MaybeTransform* aTransform)
                                          override;
   virtual bool RecvSetAsyncScrollOffset(const FrameMetrics::ViewID& aId,
-                                        const int32_t& aX, const int32_t& aY) override;
+                                        const float& aX, const float& aY) override;
   virtual bool RecvSetAsyncZoom(const FrameMetrics::ViewID& aId,
                                 const float& aValue) override;
   virtual bool RecvFlushApzRepaints() override;
   virtual bool RecvGetAPZTestData(APZTestData* aOutData) override;
   virtual bool RecvRequestProperty(const nsString& aProperty, float* aValue) override;
   virtual bool RecvSetConfirmedTargetAPZC(const uint64_t& aBlockId,
                                           nsTArray<ScrollableLayerGuid>&& aTargets) override;
 
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -212,16 +212,17 @@ struct CommonLayerAttributes {
   float postYScale;
   uint32_t contentFlags;
   float opacity;
   bool useClipRect;
   ParentLayerIntRect clipRect;
   bool isFixedPosition;
   uint64_t fixedPositionScrollContainerId;
   LayerPoint fixedPositionAnchor;
+  int32_t fixedPositionSides;
   bool isClipFixed;
   bool isStickyPosition;
   uint64_t stickyScrollContainerId;
   LayerRect stickyScrollRangeOuter;
   LayerRect stickyScrollRangeInner;
   uint64_t scrollbarTargetContainerId;
   uint32_t scrollbarDirection;
   float scrollbarThumbRatio;
--- a/gfx/layers/ipc/PLayerTransaction.ipdl
+++ b/gfx/layers/ipc/PLayerTransaction.ipdl
@@ -85,17 +85,17 @@ parent:
   // of the corresponding frame and transform origin and after converting to CSS
   // pixels. If the layer is not transformed by animation, the return value will
   // be void_t.
   sync GetAnimationTransform(PLayer layer) returns (MaybeTransform transform);
 
   // The next time the layer tree is composited, add this async scroll offset in
   // CSS pixels for the given ViewID.
   // Useful for testing rendering of async scrolling.
-  sync SetAsyncScrollOffset(ViewID id, int32_t x, int32_t y);
+  sync SetAsyncScrollOffset(ViewID id, float x, float y);
 
   // The next time the layer tree is composited, include this async zoom in
   // for the given ViewID.
   // Useful for testing rendering of async zooming.
   sync SetAsyncZoom(ViewID id, float zoom);
 
   // Flush any pending APZ repaints to the main thread.
   async FlushApzRepaints();
--- a/gfx/src/nsRegion.h
+++ b/gfx/src/nsRegion.h
@@ -839,22 +839,22 @@ public:
   {
     return Super::operator=(aRegion);
   }
   IntRegionTyped& operator=(IntRegionTyped&& aRegion)
   {
     return Super::operator=(mozilla::Move(aRegion));
   }
 
-  static IntRegionTyped FromUntyped(const IntRegionTyped<UnknownUnits>& aRegion)
+  static IntRegionTyped FromUnknownRegion(const IntRegionTyped<UnknownUnits>& aRegion)
   {
     return IntRegionTyped(aRegion.Impl());
   }
 private:
-  // This is deliberately private, so calling code uses FromUntyped().
+  // This is deliberately private, so calling code uses FromUnknownRegion().
   explicit IntRegionTyped(const nsRegion& aRegion) : Super(aRegion) {}
 };
 
 } // namespace gfx
 } // namespace mozilla
 
 typedef mozilla::gfx::IntRegion nsIntRegion;
 typedef nsIntRegion::RectIterator nsIntRegionRectIterator;
--- a/gfx/thebes/gfx2DGlue.h
+++ b/gfx/thebes/gfx2DGlue.h
@@ -24,21 +24,16 @@ inline Rect ToRect(const gfxRect &aRect)
               Float(aRect.width), Float(aRect.height));
 }
 
 inline RectDouble ToRectDouble(const gfxRect &aRect)
 {
   return RectDouble(aRect.x, aRect.y, aRect.width, aRect.height);
 }
 
-inline Rect ToRect(const IntRect &aRect)
-{
-  return Rect(aRect.x, aRect.y, aRect.width, aRect.height);
-}
-
 inline Matrix ToMatrix(const gfxMatrix &aMatrix)
 {
   return Matrix(Float(aMatrix._11), Float(aMatrix._12), Float(aMatrix._21),
                 Float(aMatrix._22), Float(aMatrix._31), Float(aMatrix._32));
 }
 
 inline gfxMatrix ThebesMatrix(const Matrix &aMatrix)
 {
@@ -66,16 +61,21 @@ inline gfxSize ThebesSize(const Size &aS
   return gfxSize(aSize.width, aSize.height);
 }
 
 inline gfxRect ThebesRect(const Rect &aRect)
 {
   return gfxRect(aRect.x, aRect.y, aRect.width, aRect.height);
 }
 
+inline gfxRect ThebesRect(const IntRect &aRect)
+{
+  return gfxRect(aRect.x, aRect.y, aRect.width, aRect.height);
+}
+
 inline gfxRect ThebesRect(const RectDouble &aRect)
 {
   return gfxRect(aRect.x, aRect.y, aRect.width, aRect.height);
 }
 
 inline gfxImageFormat SurfaceFormatToImageFormat(SurfaceFormat aFormat)
 {
   switch (aFormat) {
--- a/gfx/thebes/gfxBlur.cpp
+++ b/gfx/thebes/gfxBlur.cpp
@@ -950,26 +950,27 @@ gfxAlphaBoxBlur::GetInsetBlur(IntMargin&
     RefPtr<SourceSurface> cachedBlur = cached->mBlur;
     return cachedBlur.forget();
   }
 
   // Dirty rect and skip rect are null for the min inset shadow.
   // When rendering inset box shadows, we respect the spread radius by changing
   //  the shape of the unblurred shadow, and can pass a spread radius of zero here.
   IntSize zeroSpread(0, 0);
-  gfxContext* minGfxContext = Init(ThebesRect(ToRect(outerRect)),
+  gfxContext* minGfxContext = Init(ThebesRect(outerRect),
                                    zeroSpread, aBlurRadius, nullptr, nullptr);
   if (!minGfxContext) {
     return nullptr;
   }
 
   DrawTarget* minDrawTarget = minGfxContext->GetDrawTarget();
-  RefPtr<Path> maskPath = GetBoxShadowInsetPath(minDrawTarget, ToRect(outerRect),
-                                                ToRect(innerRect), aHasBorderRadius,
-                                                aInnerClipRadii);
+  RefPtr<Path> maskPath =
+    GetBoxShadowInsetPath(minDrawTarget, IntRectToRect(outerRect),
+                          IntRectToRect(innerRect), aHasBorderRadius,
+                          aInnerClipRadii);
 
   Color black(0.f, 0.f, 0.f, 1.f);
   minGfxContext->SetColor(black);
   minGfxContext->SetPath(maskPath);
   minGfxContext->Fill();
 
   IntPoint topLeft;
   RefPtr<SourceSurface> minMask = DoBlur(minDrawTarget, &topLeft);
--- a/gfx/thebes/gfxContext.cpp
+++ b/gfx/thebes/gfxContext.cpp
@@ -785,24 +785,24 @@ gfxContext::SetFontSmoothingBackgroundCo
 Color
 gfxContext::GetFontSmoothingBackgroundColor()
 {
   return CurrentState().fontSmoothingBackgroundColor;
 }
 
 // masking
 void
-gfxContext::Mask(SourceSurface* aSurface, const Matrix& aTransform)
+gfxContext::Mask(SourceSurface* aSurface, Float aAlpha, const Matrix& aTransform)
 {
   Matrix old = mTransform;
   Matrix mat = aTransform * mTransform;
 
   ChangeTransform(mat);
   mDT->MaskSurface(PatternFromState(this), aSurface, Point(),
-                   DrawOptions(1.0f, CurrentState().op, CurrentState().aaMode));
+                   DrawOptions(aAlpha, CurrentState().op, CurrentState().aaMode));
   ChangeTransform(old);
 }
 
 void
 gfxContext::Mask(gfxASurface *surface, const gfxPoint& offset)
 {
   PROFILER_LABEL("gfxContext", "Mask",
     js::ProfileEntry::Category::GRAPHICS);
@@ -861,43 +861,46 @@ gfxContext::Paint(gfxFloat alpha)
   Matrix mat = mDT->GetTransform();
   mat.Invert();
   Rect paintRect = mat.TransformBounds(Rect(Point(0, 0), Size(mDT->GetSize())));
 
   mDT->FillRect(paintRect, PatternFromState(this),
                 DrawOptions(Float(alpha), GetOp()));
 }
 
-// groups
-
 void
-gfxContext::PushGroup(gfxContentType content)
+gfxContext::PushGroupForBlendBack(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
 {
   DrawTarget* oldDT = mDT;
 
   PushNewDT(content);
 
   if (oldDT != mDT) {
     PushClipsToDT(mDT);
   }
   mDT->SetTransform(GetDTTransform());
+
+  CurrentState().mBlendOpacity = aOpacity;
+  CurrentState().mBlendMask = aMask;
+  CurrentState().mWasPushedForBlendBack = true;
+  CurrentState().mBlendMaskTransform = aMaskTransform;
 }
 
 static gfxRect
 GetRoundOutDeviceClipExtents(gfxContext* aCtx)
 {
   gfxContextMatrixAutoSaveRestore save(aCtx);
   aCtx->SetMatrix(gfxMatrix());
   gfxRect r = aCtx->GetClipExtents();
   r.RoundOut();
   return r;
 }
 
 void
-gfxContext::PushGroupAndCopyBackground(gfxContentType content)
+gfxContext::PushGroupAndCopyBackground(gfxContentType content, Float aOpacity, SourceSurface* aMask, const Matrix& aMaskTransform)
 {
   IntRect clipExtents;
   if (mDT->GetFormat() != SurfaceFormat::B8G8R8X8) {
     gfxRect clipRect = GetRoundOutDeviceClipExtents(this);
     clipExtents = IntRect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
   }
   if ((mDT->GetFormat() == SurfaceFormat::B8G8R8X8 ||
        mDT->GetOpaqueRect().Contains(clipExtents)) &&
@@ -908,16 +911,21 @@ gfxContext::PushGroupAndCopyBackground(g
 
     PushNewDT(gfxContentType::COLOR);
 
     if (oldDT == mDT) {
       // Creating new DT failed.
       return;
     }
 
+    CurrentState().mBlendOpacity = aOpacity;
+    CurrentState().mBlendMask = aMask;
+    CurrentState().mWasPushedForBlendBack = true;
+    CurrentState().mBlendMaskTransform = aMaskTransform;
+
     Point offset = CurrentState().deviceOffset - oldDeviceOffset;
     Rect surfRect(0, 0, Float(mDT->GetSize().width), Float(mDT->GetSize().height));
     Rect sourceRect = surfRect + offset;
 
     mDT->SetTransform(Matrix());
 
     // XXX: It's really sad that we have to do this (for performance).
     // Once DrawTarget gets a PushLayer API we can implement this within
@@ -942,71 +950,68 @@ gfxContext::PushGroupAndCopyBackground(g
       mDT->DrawSurface(source, surfRect, sourceRect);
     }
     mDT->SetOpaqueRect(oldDT->GetOpaqueRect());
 
     PushClipsToDT(mDT);
     mDT->SetTransform(GetDTTransform());
     return;
   }
-  PushGroup(content);
-}
+  DrawTarget* oldDT = mDT;
 
-already_AddRefed<gfxPattern>
-gfxContext::PopGroup()
-{
-  RefPtr<SourceSurface> src = mDT->Snapshot();
-  Point deviceOffset = CurrentState().deviceOffset;
-
-  Restore();
-
-  Matrix mat = mTransform;
-  mat.Invert();
-  mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation
-
-  RefPtr<gfxPattern> pat = new gfxPattern(src, mat);
+  PushNewDT(content);
 
-  return pat.forget();
-}
-
-already_AddRefed<SourceSurface>
-gfxContext::PopGroupToSurface(Matrix* aTransform)
-{
-  RefPtr<SourceSurface> src = mDT->Snapshot();
-  Point deviceOffset = CurrentState().deviceOffset;
+  if (oldDT != mDT) {
+    PushClipsToDT(mDT);
+  }
 
-  Restore();
-
-  Matrix mat = mTransform;
-  mat.Invert();
-
-  Matrix deviceOffsetTranslation;
-  deviceOffsetTranslation.PreTranslate(deviceOffset.x, deviceOffset.y);
-
-  *aTransform = deviceOffsetTranslation * mat;
-  return src.forget();
+  mDT->SetTransform(GetDTTransform());
+  CurrentState().mBlendOpacity = aOpacity;
+  CurrentState().mBlendMask = aMask;
+  CurrentState().mWasPushedForBlendBack = true;
+  CurrentState().mBlendMaskTransform = aMaskTransform;
 }
 
 void
-gfxContext::PopGroupToSource()
+gfxContext::PopGroupAndBlend()
 {
+  MOZ_ASSERT(CurrentState().mWasPushedForBlendBack);
+  Float opacity = CurrentState().mBlendOpacity;
+  RefPtr<SourceSurface> mask = CurrentState().mBlendMask;
+  Matrix maskTransform = CurrentState().mBlendMaskTransform;
+
   RefPtr<SourceSurface> src = mDT->Snapshot();
   Point deviceOffset = CurrentState().deviceOffset;
   Restore();
   CurrentState().sourceSurfCairo = nullptr;
   CurrentState().sourceSurface = src;
   CurrentState().sourceSurfaceDeviceOffset = deviceOffset;
   CurrentState().pattern = nullptr;
   CurrentState().patternTransformChanged = false;
 
   Matrix mat = mTransform;
   mat.Invert();
   mat.PreTranslate(deviceOffset.x, deviceOffset.y); // device offset translation
 
   CurrentState().surfTransform = mat;
+
+  CompositionOp oldOp = GetOp();
+  SetOp(CompositionOp::OP_OVER);
+
+  if (mask) {
+    if (!maskTransform.HasNonTranslation()) {
+      Mask(mask, opacity, Point(maskTransform._31, maskTransform._32));
+    } else {
+      Mask(mask, opacity, maskTransform);
+    }
+  } else {
+    Paint(opacity);
+  }
+
+  SetOp(oldOp);
 }
 
 #ifdef MOZ_DUMP_PAINTING
 void
 gfxContext::WriteAsPNG(const char* aFile)
 {
   gfxUtils::WriteAsPNG(mDT, aFile);
 }
--- a/gfx/thebes/gfxContext.h
+++ b/gfx/thebes/gfxContext.h
@@ -310,17 +310,18 @@ public:
 
     /**
      ** Painting with a Mask
      **/
     /**
      * Like Paint, except that it only draws the source where pattern is
      * non-transparent.
      */
-    void Mask(mozilla::gfx::SourceSurface *aSurface, const mozilla::gfx::Matrix& aTransform);
+    void Mask(mozilla::gfx::SourceSurface *aSurface, mozilla::gfx::Float aAlpha, const mozilla::gfx::Matrix& aTransform);
+    void Mask(mozilla::gfx::SourceSurface *aSurface, const mozilla::gfx::Matrix& aTransform) { Mask(aSurface, 1.0f, aTransform); }
 
     /**
      * Shorthand for creating a pattern and calling the pattern-taking
      * variant of Mask.
      */
     void Mask(gfxASurface *surface, const gfxPoint& offset = gfxPoint(0.0, 0.0));
 
     void Mask(mozilla::gfx::SourceSurface *surface, float alpha = 1.0f, const mozilla::gfx::Point& offset = mozilla::gfx::Point());
@@ -425,34 +426,34 @@ public:
      /**
       * Exports the current clip using the provided exporter.
       */
     bool ExportClip(ClipExporter& aExporter);
 
     /**
      * Groups
      */
-    void PushGroup(gfxContentType content = gfxContentType::COLOR);
+    void PushGroupForBlendBack(gfxContentType content, mozilla::gfx::Float aOpacity = 1.0f,
+                               mozilla::gfx::SourceSurface* aMask = nullptr,
+                               const mozilla::gfx::Matrix& aMaskTransform = mozilla::gfx::Matrix());
+
     /**
-     * Like PushGroup, but if the current surface is gfxContentType::COLOR and
+     * Like PushGroupForBlendBack, but if the current surface is gfxContentType::COLOR and
      * content is gfxContentType::COLOR_ALPHA, makes the pushed surface gfxContentType::COLOR
      * instead and copies the contents of the current surface to the pushed
      * surface. This is good for pushing opacity groups, since blending the
      * group back to the current surface with some alpha applied will give
      * the correct results and using an opaque pushed surface gives better
      * quality and performance.
-     * This API really only makes sense if you do a PopGroupToSource and
-     * immediate Paint with OP_OVER.
      */
-    void PushGroupAndCopyBackground(gfxContentType content = gfxContentType::COLOR);
-    already_AddRefed<gfxPattern> PopGroup();
-    void PopGroupToSource();
-
-    already_AddRefed<mozilla::gfx::SourceSurface>
-    PopGroupToSurface(mozilla::gfx::Matrix* aMatrix);
+    void PushGroupAndCopyBackground(gfxContentType content = gfxContentType::COLOR,
+                                    mozilla::gfx::Float aOpacity = 1.0f,
+                                    mozilla::gfx::SourceSurface* aMask = nullptr,
+                                    const mozilla::gfx::Matrix& aMaskTransform = mozilla::gfx::Matrix());
+    void PopGroupAndBlend();
 
     mozilla::gfx::Point GetDeviceOffset() const;
 
     // Work out whether cairo will snap inter-glyph spacing to pixels.
     void GetRoundOffsetsToPixels(bool *aRoundX, bool *aRoundY);
 
 #ifdef MOZ_DUMP_PAINTING
     /**
@@ -522,16 +523,21 @@ private:
     RefPtr<DrawTarget> drawTarget;
     RefPtr<DrawTarget> parentTarget;
     mozilla::gfx::AntialiasMode aaMode;
     bool patternTransformChanged;
     Matrix patternTransform;
     Color fontSmoothingBackgroundColor;
     // This is used solely for using minimal intermediate surface size.
     mozilla::gfx::Point deviceOffset;
+    // Support groups
+    mozilla::gfx::Float mBlendOpacity;
+    RefPtr<SourceSurface> mBlendMask;
+    Matrix mBlendMaskTransform;
+    mozilla::DebugOnly<bool> mWasPushedForBlendBack;
   };
 
   // This ensures mPath contains a valid path (in user space!)
   void EnsurePath();
   // This ensures mPathBuilder contains a valid PathBuilder (in user space!)
   void EnsurePathBuilder();
   void FillAzure(const Pattern& aPattern, mozilla::gfx::Float aOpacity);
   void PushClipsToDT(mozilla::gfx::DrawTarget *aDT);
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -134,18 +134,16 @@ private:
   // a method accessing a pref already exists. Just add yours in the list.
 
   // It's a short time fix, and will be removed after landing bug 1206637.
   DECL_GFX_PREF(Live, "accessibility.monoaudio.enable",        MonoAudio, bool, false);
 
   // The apz prefs are explained in AsyncPanZoomController.cpp
   DECL_GFX_PREF(Live, "apz.allow_checkerboarding",             APZAllowCheckerboarding, bool, true);
   DECL_GFX_PREF(Live, "apz.allow_zooming",                     APZAllowZooming, bool, false);
-  DECL_GFX_PREF(Live, "apz.asyncscroll.throttle",              APZAsyncScrollThrottleTime, int32_t, 100);
-  DECL_GFX_PREF(Live, "apz.asyncscroll.timeout",               APZAsyncScrollTimeout, int32_t, 300);
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_angle",          APZAxisBreakoutAngle, float, float(M_PI / 8.0) /* 22.5 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.breakout_threshold",      APZAxisBreakoutThreshold, float, 1.0f / 32.0f);
   DECL_GFX_PREF(Live, "apz.axis_lock.direct_pan_angle",        APZAllowedDirectPanAngle, float, float(M_PI / 3.0) /* 60 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle",              APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.mode",                    APZAxisLockMode, int32_t, 0);
   DECL_GFX_PREF(Live, "apz.content_response_timeout",          APZContentResponseTimeout, int32_t, 300);
   DECL_GFX_PREF(Live, "apz.cross_slide.enabled",               APZCrossSlideEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
@@ -267,16 +265,19 @@ private:
   // On b2g, in really bad cases, I've seen up to 80 ms delays between touch events and the main thread
   // processing them. So 80 ms / 16 = 5 vsync events. Double it up just to be on the safe side, so 10.
   DECL_GFX_PREF(Once, "gfx.vsync.compositor.unobserve-count",  CompositorUnobserveCount, int32_t, 10);
   // Use vsync events generated by hardware
   DECL_GFX_PREF(Once, "gfx.work-around-driver-bugs",           WorkAroundDriverBugs, bool, true);
   DECL_GFX_PREF(Once, "gfx.screen-mirroring.enabled",          ScreenMirroringEnabled, bool, false);
 
   DECL_GFX_PREF(Live, "gl.msaa-level",                         MSAALevel, uint32_t, 2);
+#if defined(XP_MACOSX)
+  DECL_GFX_PREF(Live, "gl.multithreaded",                      GLMultithreaded, bool, false);
+#endif
   DECL_GFX_PREF(Live, "gl.require-hardware",                   RequireHardwareGL, bool, false);
 
   DECL_GFX_PREF(Once, "image.cache.size",                      ImageCacheSize, int32_t, 5*1024*1024);
   DECL_GFX_PREF(Once, "image.cache.timeweight",                ImageCacheTimeWeight, int32_t, 500);
   DECL_GFX_PREF(Live, "image.decode-immediately.enabled",      ImageDecodeImmediatelyEnabled, bool, false);
   DECL_GFX_PREF(Live, "image.downscale-during-decode.enabled", ImageDownscaleDuringDecodeEnabled, bool, true);
   DECL_GFX_PREF(Live, "image.infer-src-animation.threshold-ms", ImageInferSrcAnimationThresholdMS, uint32_t, 2000);
   DECL_GFX_PREF(Once, "image.mem.decode_bytes_at_a_time",      ImageMemDecodeBytesAtATime, uint32_t, 200000);
--- a/gfx/thebes/gfxRect.h
+++ b/gfx/thebes/gfxRect.h
@@ -66,18 +66,16 @@ struct gfxRect :
     public mozilla::gfx::BaseRect<gfxFloat, gfxRect, gfxPoint, gfxSize, gfxMargin> {
     typedef mozilla::gfx::BaseRect<gfxFloat, gfxRect, gfxPoint, gfxSize, gfxMargin> Super;
 
     gfxRect() : Super() {}
     gfxRect(const gfxPoint& aPos, const gfxSize& aSize) :
         Super(aPos, aSize) {}
     gfxRect(gfxFloat aX, gfxFloat aY, gfxFloat aWidth, gfxFloat aHeight) :
         Super(aX, aY, aWidth, aHeight) {}
-    MOZ_IMPLICIT gfxRect(const mozilla::gfx::IntRect& aRect) :
-        Super(aRect.x, aRect.y, aRect.width, aRect.height) {}
 
     /**
      * Return true if all components of this rect are within
      * aEpsilon of integer coordinates, defined as
      *   |round(coord) - coord| <= |aEpsilon|
      * for x,y,width,height.
      */
     bool WithinEpsilonOfIntegerPixels(gfxFloat aEpsilon) const;
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -526,48 +526,43 @@ HasNonOpaqueNonTransparentColor(gfxConte
     }
     return false;
 }
 
 // helper class for double-buffering drawing with non-opaque color
 struct BufferAlphaColor {
     explicit BufferAlphaColor(gfxContext *aContext)
         : mContext(aContext)
-        , mAlpha(0.0)
     {
 
     }
 
     ~BufferAlphaColor() {}
 
     void PushSolidColor(const gfxRect& aBounds, const Color& aAlphaColor, uint32_t appsPerDevUnit)
     {
         mContext->Save();
         mContext->NewPath();
         mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit,
                     aBounds.Y() / appsPerDevUnit,
                     aBounds.Width() / appsPerDevUnit,
                     aBounds.Height() / appsPerDevUnit), true);
         mContext->Clip();
         mContext->SetColor(Color(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
-        mContext->PushGroup(gfxContentType::COLOR_ALPHA);
-        mAlpha = aAlphaColor.a;
+        mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, aAlphaColor.a);
     }
 
     void PopAlpha()
     {
         // pop the text, using the color alpha as the opacity
-        mContext->PopGroupToSource();
-        mContext->SetOp(CompositionOp::OP_OVER);
-        mContext->Paint(mAlpha);
+        mContext->PopGroupAndBlend();
         mContext->Restore();
     }
 
     gfxContext *mContext;
-    gfxFloat mAlpha;
 };
 
 void
 gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode,
                  uint32_t aStart, uint32_t aLength,
                  PropertyProvider *aProvider, gfxFloat *aAdvanceWidth,
                  gfxTextContextPaint *aContextPaint,
                  gfxTextRunDrawCallbacks *aCallbacks)
--- a/gfx/thebes/gfxUtils.cpp
+++ b/gfx/thebes/gfxUtils.cpp
@@ -503,27 +503,26 @@ struct MOZ_STACK_CLASS AutoCairoPixmanBu
         // Clip the rounded-out-to-device-pixels bounds of the
         // transformed fill area. This is the area for the group we
         // want to push.
         mContext->SetMatrix(gfxMatrix());
         gfxRect bounds = currentMatrix.TransformBounds(aFill);
         bounds.RoundOut();
         mContext->Clip(bounds);
         mContext->SetMatrix(currentMatrix);
-        mContext->PushGroup(gfxContentType::COLOR_ALPHA);
+        mContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA);
         mContext->SetOp(CompositionOp::OP_OVER);
 
         mPushedGroup = true;
     }
 
     ~AutoCairoPixmanBugWorkaround()
     {
         if (mPushedGroup) {
-            mContext->PopGroupToSource();
-            mContext->Paint();
+            mContext->PopGroupAndBlend();
             mContext->Restore();
         }
     }
 
     bool PushedGroup() { return mPushedGroup; }
     bool Succeeded() { return mSucceeded; }
 
 private:
--- a/image/imgFrame.cpp
+++ b/image/imgFrame.cpp
@@ -299,17 +299,17 @@ imgFrame::InitWithDrawable(gfxDrawable* 
     mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Draw using the drawable the caller provided.
   nsIntRect imageRect(0, 0, mSize.width, mSize.height);
   RefPtr<gfxContext> ctx = new gfxContext(target);
   gfxUtils::DrawPixelSnapped(ctx, aDrawable, mSize,
-                             ImageRegion::Create(imageRect),
+                             ImageRegion::Create(ThebesRect(imageRect)),
                              mFormat, aFilter, aImageFlags);
 
   if (canUseDataSurface && !mImageSurface) {
     NS_WARNING("Failed to create VolatileDataSourceSurface");
     mAborted = true;
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -55,17 +55,17 @@ using namespace js::gc;
 using mozilla::DebugOnly;
 using mozilla::PodArrayZero;
 using mozilla::PointerRangeSize;
 using mozilla::UniquePtr;
 
 bool
 js::AutoCycleDetector::init()
 {
-    ObjectSet& set = cx->cycleDetectorSet;
+    AutoCycleDetector::Set& set = cx->cycleDetectorSet;
     hashsetAddPointer = set.lookupForAdd(obj);
     if (!hashsetAddPointer) {
         if (!set.add(hashsetAddPointer, obj))
             return false;
         cyclic = false;
         hashsetGenerationAtInit = set.generation();
     }
     return true;
@@ -77,24 +77,20 @@ js::AutoCycleDetector::~AutoCycleDetecto
         if (hashsetGenerationAtInit == cx->cycleDetectorSet.generation())
             cx->cycleDetectorSet.remove(hashsetAddPointer);
         else
             cx->cycleDetectorSet.remove(obj);
     }
 }
 
 void
-js::TraceCycleDetectionSet(JSTracer* trc, js::ObjectSet& set)
+js::TraceCycleDetectionSet(JSTracer* trc, AutoCycleDetector::Set& set)
 {
-    for (js::ObjectSet::Enum e(set); !e.empty(); e.popFront()) {
-        JSObject* key = e.front();
-        TraceRoot(trc, &key, "cycle detector table entry");
-        if (key != e.front())
-            e.rekeyFront(key);
-    }
+    for (AutoCycleDetector::Set::Enum e(set); !e.empty(); e.popFront())
+        TraceRoot(trc, &e.mutableFront(), "cycle detector table entry");
 }
 
 JSContext*
 js::NewContext(JSRuntime* rt, size_t stackChunkSize)
 {
     JS_AbortIfWrongThread(rt);
 
     JSContext* cx = js_new<JSContext>(rt);
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -24,47 +24,49 @@ struct DtoaState;
 
 namespace js {
 
 namespace jit {
 class JitContext;
 class DebugModeOSRVolatileJitFrameIterator;
 } // namespace jit
 
-typedef HashSet<JSObject*> ObjectSet;
 typedef HashSet<Shape*> ShapeSet;
 
 /* Detects cycles when traversing an object graph. */
 class MOZ_RAII AutoCycleDetector
 {
-    JSContext* cx;
-    RootedObject obj;
-    bool cyclic;
-    uint32_t hashsetGenerationAtInit;
-    ObjectSet::AddPtr hashsetAddPointer;
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  public:
+    using Set = HashSet<JSObject*, MovableCellHasher<JSObject*>>;
 
-  public:
     AutoCycleDetector(JSContext* cx, HandleObject objArg
                       MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx(cx), obj(cx, objArg), cyclic(true)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     ~AutoCycleDetector();
 
     bool init();
 
     bool foundCycle() { return cyclic; }
+
+  private:
+    JSContext* cx;
+    RootedObject obj;
+    bool cyclic;
+    uint32_t hashsetGenerationAtInit;
+    Set::AddPtr hashsetAddPointer;
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /* Updates references in the cycle detection set if the GC moves them. */
 extern void
-TraceCycleDetectionSet(JSTracer* trc, ObjectSet& set);
+TraceCycleDetectionSet(JSTracer* trc, AutoCycleDetector::Set& set);
 
 struct AutoResolving;
 
 namespace frontend { struct CompileError; }
 
 /*
  * Execution Context Overview:
  *
@@ -344,17 +346,17 @@ struct JSContext : public js::ExclusiveC
     typedef js::Vector<SavedFrameChain, 1, js::SystemAllocPolicy> SaveStack;
     SaveStack           savedFrameChains_;
   public:
     bool saveFrameChain();
     void restoreFrameChain();
 
   public:
     /* State for object and array toSource conversion. */
-    js::ObjectSet       cycleDetectorSet;
+    js::AutoCycleDetector::Set cycleDetectorSet;
 
     /* Client opaque pointers. */
     void*               data;
     void*               data2;
 
   public:
 
     /*
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -2016,21 +2016,28 @@ CloneString(JSContext* cx, JSFlatString*
            ? NewStringCopyN<CanGC>(cx, chars.latin1Range().start().get(), len)
            : NewStringCopyNDontDeflate<CanGC>(cx, chars.twoByteRange().start().get(), len);
 }
 
 static JSObject*
 CloneObject(JSContext* cx, HandleNativeObject selfHostedObject)
 {
 #ifdef DEBUG
-    AutoCycleDetector detect(cx, selfHostedObject);
-    if (!detect.init())
-        return nullptr;
-    if (detect.foundCycle())
-        MOZ_CRASH("SelfHosted cloning cannot handle cyclic object graphs.");
+    // Object hash identities are owned by the hashed object, which may be on a
+    // different thread than the clone target. In theory, these objects are all
+    // tenured and will not be compacted; however, we simply avoid the issue
+    // altogether by skipping the cycle-detection when off the main thread.
+    Maybe<AutoCycleDetector> detect;
+    if (js::CurrentThreadCanAccessZone(selfHostedObject->zoneFromAnyThread())) {
+        detect.emplace(cx, selfHostedObject);
+        if (!detect->init())
+            return nullptr;
+        if (detect->foundCycle())
+            MOZ_CRASH("SelfHosted cloning cannot handle cyclic object graphs.");
+    }
 #endif
 
     RootedObject clone(cx);
     if (selfHostedObject->is<JSFunction>()) {
         RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>());
         bool hasName = selfHostedFunction->atom() != nullptr;
         // Arrow functions use the first extended slot for their lexical |this| value.
         MOZ_ASSERT(!selfHostedFunction->isArrow());
--- a/layout/base/FrameLayerBuilder.cpp
+++ b/layout/base/FrameLayerBuilder.cpp
@@ -3877,16 +3877,23 @@ ContainerState::ProcessDisplayItems(nsDi
 
     NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
       "items in a container layer should all have the same app units per dev pixel");
 
     if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
       aList->SetNeedsTransparentSurface();
     }
 
+    nsDisplayItem::Type itemType = item->GetType();
+
+    if (mParameters.mForEventsOnly && !item->GetChildren() &&
+        itemType != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
+      continue;
+    }
+
     LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
     if (layerState == LAYER_INACTIVE &&
         nsDisplayItem::ForceActiveLayers()) {
       layerState = LAYER_ACTIVE;
     }
 
     bool forceInactive;
     const nsIFrame* animatedGeometryRoot;
@@ -3915,18 +3922,16 @@ ContainerState::ProcessDisplayItems(nsDi
         lastAnimatedGeometryRoot = animatedGeometryRoot;
         topLeft = animatedGeometryRoot->GetOffsetToCrossDoc(mContainerReferenceFrame);
       }
     }
     if (!animatedGeometryRootForScrollMetadata) {
       animatedGeometryRootForScrollMetadata = animatedGeometryRoot;
     }
 
-    nsDisplayItem::Type itemType = item->GetType();
-
     if (animatedGeometryRoot != realAnimatedGeometryRootOfItem) {
       // Pick up any scroll clips that should apply to the item and apply them.
       DisplayItemClip clip =
         GetScrollClipIntersection(mBuilder, realAnimatedGeometryRootOfItem,
                                   animatedGeometryRoot,
                                   IsCaretWithCustomClip(item, itemType));
       clip.IntersectWith(item->GetClip());
       item->SetClip(mBuilder, clip);
@@ -3956,17 +3961,17 @@ ContainerState::ProcessDisplayItems(nsDi
     bool prerenderedTransform = itemType == nsDisplayItem::TYPE_TRANSFORM &&
         static_cast<nsDisplayTransform*>(item)->ShouldPrerender(mBuilder);
     ParentLayerIntRect clipRect;
     const DisplayItemClip& itemClip = item->GetClip();
     if (itemClip.HasClip()) {
       itemContent.IntersectRect(itemContent, itemClip.GetClipRect());
       clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClip.GetClipRect()));
       if (!prerenderedTransform) {
-        itemDrawRect.IntersectRect(itemDrawRect, ParentLayerIntRect::ToUntyped(clipRect));
+        itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect());
       }
       clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
     }
 #ifdef DEBUG
     nsRect bounds = itemContent;
     bool dummy;
     if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) {
       bounds = item->GetBounds(mBuilder, &dummy);
@@ -4043,17 +4048,17 @@ ContainerState::ProcessDisplayItems(nsDi
       nscolor* uniformColorPtr = !mayDrawOutOfOrder ? &uniformColor : nullptr;
       nsIntRect clipRectUntyped;
       const DisplayItemClip& layerClip = shouldFixToViewport ? fixedToViewportClip : itemClip;
       ParentLayerIntRect layerClipRect;
       nsIntRect* clipPtr = nullptr;
       if (layerClip.HasClip()) {
         layerClipRect = ViewAs<ParentLayerPixel>(
           ScaleToNearestPixels(layerClip.GetClipRect()) + mParameters.mOffset);
-        clipRectUntyped = ParentLayerIntRect::ToUntyped(layerClipRect);
+        clipRectUntyped = layerClipRect.ToUnknownRect();
         clipPtr = &clipRectUntyped;
       }
       if (animatedGeometryRoot == item->Frame() &&
           animatedGeometryRoot != mBuilder->RootReferenceFrame()) {
         // This is the case for scrollbar thumbs, for example. In that case the
         // clip we care about is the overflow:hidden clip on the scrollbar.
         const nsIFrame* clipAnimatedGeometryRoot =
           mPaintedLayerDataTree.GetParentAnimatedGeometryRoot(animatedGeometryRoot);
@@ -4511,16 +4516,18 @@ FrameLayerBuilder::AddPaintedDisplayItem
         std::stringstream stream;
         tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
         fprint_stderr(gfxUtils::sDumpPaintFile, stream);  // not a typo, fprint_stderr declared in LayersLogging.h
       }
 #endif
 
       nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
       props->MoveBy(-offset);
+      // Effective transforms are needed by ComputeDifferences().
+      tmpLayer->ComputeEffectiveTransforms(Matrix4x4());
       nsIntRegion invalid = props->ComputeDifferences(tmpLayer, nullptr);
       if (aLayerState == LAYER_SVG_EFFECTS) {
         invalid = nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(aItem->Frame(),
                                                                         aItem->ToReferenceFrame(),
                                                                         invalid);
       }
       if (!invalid.IsEmpty()) {
 #ifdef MOZ_DUMP_PAINTING
@@ -4881,17 +4888,17 @@ ContainerState::PostprocessRetainedLayer
 
     SetupScrollingMetadata(e);
 
     if (hideAll) {
       e->mVisibleRegion.SetEmpty();
     } else if (!e->mLayer->IsScrollbarContainer()) {
       const Maybe<ParentLayerIntRect>& clipRect = GetStationaryClipInContainer(e->mLayer);
       if (clipRect && opaqueRegionForContainer >= 0 &&
-          opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(ParentLayerIntRect::ToUntyped(*clipRect))) {
+          opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(clipRect->ToUnknownRect())) {
         e->mVisibleRegion.SetEmpty();
       } else if (data) {
         e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
       }
     }
 
     SetOuterVisibleRegionForLayer(e->mLayer,
                                   e->mVisibleRegion,
@@ -4922,17 +4929,17 @@ ContainerState::PostprocessRetainedLayer
         data = opaqueRegions.AppendElement();
         data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
         data->mFixedPosFrameForLayerData = e->mFixedPosFrameForLayerData;
       }
 
       nsIntRegion clippedOpaque = e->mOpaqueRegion;
       Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect();
       if (clipRect) {
-        clippedOpaque.AndWith(ParentLayerIntRect::ToUntyped(*clipRect));
+        clippedOpaque.AndWith(clipRect->ToUnknownRect());
       }
       if (e->mLayer->GetIsFixedPosition() && !e->mLayer->IsClipFixed()) {
         // The clip can move asynchronously, so we can't rely on opaque parts
         // staying in the same place.
         clippedOpaque.SetEmpty();
       }
       data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque);
       if (e->mHideAllLayersBelow) {
@@ -5873,17 +5880,17 @@ FrameLayerBuilder::DrawPaintedLayer(Pain
 
   nsRenderingContext rc(aContext);
 
   if (shouldDrawRectsSeparately) {
     nsIntRegionRectIterator it(aRegionToDraw);
     while (const nsIntRect* iterRect = it.Next()) {
       gfxContextAutoSaveRestore save(aContext);
       aContext->NewPath();
-      aContext->Rectangle(*iterRect);
+      aContext->Rectangle(ThebesRect(*iterRect));
       aContext->Clip();
 
       DrawForcedBackgroundColor(aDrawTarget, aLayer,
                                 userData->mForcedBackgroundColor);
 
       // Apply the residual transform if it has been enabled, to ensure that
       // snapping when we draw into aContext exactly matches the ideal transform.
       // See above for why this is OK.
--- a/layout/base/FrameLayerBuilder.h
+++ b/layout/base/FrameLayerBuilder.h
@@ -102,17 +102,17 @@ struct ContainerLayerParameters {
   nsIntRect* mLayerContentsVisibleRect;
 
   /**
    * An offset to apply to all child layers created.
    */
   nsIntPoint mOffset;
 
   LayerIntPoint Offset() const {
-    return LayerIntPoint::FromUntyped(mOffset);
+    return LayerIntPoint::FromUnknownPoint(mOffset);
   }
 
   nscolor mBackgroundColor;
   bool mInTransformedSubtree;
   bool mInActiveTransformedSubtree;
   bool mDisableSubpixelAntialiasingInDescendants;
   bool mInLowPrecisionDisplayPort;
   bool mForEventsOnly;
--- a/layout/base/Units.h
+++ b/layout/base/Units.h
@@ -230,32 +230,16 @@ struct CSSPixel {
  * The pixels that are referred to as "device pixels" in layout code. In
  * general values measured in LayoutDevicePixels are obtained by dividing a
  * value in app units by AppUnitsPerDevPixel(). Conversion between CSS pixels
  * and LayoutDevicePixels is affected by:
  * 1) the "full zoom" (see nsPresContext::SetFullZoom)
  * 2) the "widget scale" (see nsIWidget::GetDefaultScale)
  */
 struct LayoutDevicePixel {
-  static LayoutDeviceIntPoint FromUntyped(const nsIntPoint& aPoint) {
-    return LayoutDeviceIntPoint(aPoint.x, aPoint.y);
-  }
-
-  static nsIntPoint ToUntyped(const LayoutDeviceIntPoint& aPoint) {
-    return nsIntPoint(aPoint.x, aPoint.y);
-  }
-
-  static LayoutDeviceIntRect FromUntyped(const nsIntRect& aRect) {
-    return LayoutDeviceIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static nsIntRect ToUntyped(const LayoutDeviceIntRect& aRect) {
-    return nsIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
   static LayoutDeviceRect FromAppUnits(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
     return LayoutDeviceRect(NSAppUnitsToFloatPixels(aRect.x, float(aAppUnitsPerDevPixel)),
                             NSAppUnitsToFloatPixels(aRect.y, float(aAppUnitsPerDevPixel)),
                             NSAppUnitsToFloatPixels(aRect.width, float(aAppUnitsPerDevPixel)),
                             NSAppUnitsToFloatPixels(aRect.height, float(aAppUnitsPerDevPixel)));
   }
 
   static LayoutDevicePoint FromAppUnits(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
@@ -271,21 +255,21 @@ struct LayoutDevicePixel {
   }
 
   static LayoutDeviceIntPoint FromAppUnitsRounded(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
     return LayoutDeviceIntPoint(NSAppUnitsToIntPixels(aPoint.x, aAppUnitsPerDevPixel),
                                 NSAppUnitsToIntPixels(aPoint.y, aAppUnitsPerDevPixel));
   }
 
   static LayoutDeviceIntPoint FromAppUnitsToNearest(const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
-    return FromUntyped(aPoint.ToNearestPixels(aAppUnitsPerDevPixel));
+    return LayoutDeviceIntPoint::FromUnknownPoint(aPoint.ToNearestPixels(aAppUnitsPerDevPixel));
   }
 
   static LayoutDeviceIntRect FromAppUnitsToNearest(const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
-    return FromUntyped(aRect.ToNearestPixels(aAppUnitsPerDevPixel));
+    return LayoutDeviceIntRect::FromUnknownRect(aRect.ToNearestPixels(aAppUnitsPerDevPixel));
   }
 
   static LayoutDeviceIntSize FromAppUnitsRounded(const nsSize& aSize, nscoord aAppUnitsPerDevPixel) {
     return LayoutDeviceIntSize(
       NSAppUnitsToIntPixels(aSize.width, aAppUnitsPerDevPixel),
       NSAppUnitsToIntPixels(aSize.height, aAppUnitsPerDevPixel));
   }
 
@@ -317,109 +301,53 @@ struct LayoutDevicePixel {
  * These also are generally referred to as "device pixels" in layout code.
  * Conversion between CSS pixels and LayerPixels is affected by:
  * 1) the "display resolution" (see nsIPresShell::SetResolution)
  * 2) the "full zoom" (see nsPresContext::SetFullZoom)
  * 3) the "widget scale" (see nsIWidget::GetDefaultScale)
  * 4) rasterizing at a different scale in the presence of some CSS transforms
  */
 struct LayerPixel {
-  static nsIntRect ToUntyped(const LayerIntRect& aRect) {
-    return nsIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static nsIntPoint ToUntyped(const LayerIntPoint& aPoint) {
-    return nsIntPoint(aPoint.x, aPoint.y);
-  }
-
-  static gfx::IntRect ToUnknown(const LayerIntRect& aRect) {
-    return gfx::IntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static gfx::Rect ToUnknown(const LayerRect& aRect) {
-    return gfx::Rect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static LayerIntRect FromUntyped(const nsIntRect& aRect) {
-    return LayerIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static LayerIntPoint FromUntyped(const nsIntPoint& aPoint) {
-    return LayerIntPoint(aPoint.x, aPoint.y);
-  }
 };
 
 /*
  * Layers are always composited to a render target. This unit
  * represents one pixel in the render target. Note that for the
  * root render target RenderTargetPixel == ScreenPixel. Also
  * any ContainerLayer providing an intermediate surface will
  * have RenderTargetPixel == LayerPixel.
  */
 struct RenderTargetPixel {
-  static nsIntPoint ToUntyped(const RenderTargetIntPoint& aPoint) {
-    return nsIntPoint(aPoint.x, aPoint.y);
-  }
-
-  static nsIntRect ToUntyped(const RenderTargetIntRect& aRect) {
-    return nsIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static gfx::IntRect ToUnknown(const RenderTargetIntRect& aRect) {
-    return gfx::IntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static gfx::Rect ToUnknown(const RenderTargetRect& aRect) {
-    return gfx::Rect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static RenderTargetRect FromUnknown(const gfx::Rect& aRect) {
-    return RenderTargetRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
-
-  static RenderTargetIntRect FromUntyped(const nsIntRect& aRect) {
-    return RenderTargetIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
 };
 
 /*
  * The pixels that are displayed on the screen.
  * On non-OMTC platforms this should be equivalent to LayerPixel units.
  * On OMTC platforms these may diverge from LayerPixel units temporarily,
  * while an asynchronous zoom is happening, but should eventually converge
  * back to LayerPixel units. Some variables (such as those representing
  * chrome UI element sizes) that are not subject to content zoom should
  * generally be represented in ScreenPixel units.
  */
 struct ScreenPixel {
-  static nsIntSize ToUntyped(const ScreenIntSize& aSize) {
-    return nsIntSize(aSize.width, aSize.height);
-  }
-
-  static ScreenIntPoint FromUntyped(const nsIntPoint& aPoint) {
-    return ScreenIntPoint(aPoint.x, aPoint.y);
-  }
 };
 
 /* The layer coordinates of the parent frame.
  * This can be arrived at in three ways:
  *   - Start with the CSS coordinates of the parent frame, multiply by the
  *     device scale and the cumulative resolution of the parent frame.
  *   - Start with the CSS coordinates of current frame, multiply by the device
  *     scale, the cumulative resolution of the current frame, and the scales
  *     from the CSS and async transforms of the current frame.
  *   - Start with global screen coordinates and unapply all CSS and async
  *     transforms from the root down to and including the parent.
  * It's helpful to look at https://wiki.mozilla.org/Platform/GFX/APZ#Coordinate_systems
  * to get a picture of how the various coordinate systems relate to each other.
  */
 struct ParentLayerPixel {
-  static nsIntRect ToUntyped(const ParentLayerIntRect& aRect) {
-    return nsIntRect(aRect.x, aRect.y, aRect.width, aRect.height);
-  }
 };
 
 // Operators to apply ScaleFactors directly to Coords, Points, Rects, Sizes and Margins
 
 template<class src, class dst>
 gfx::CoordTyped<dst> operator*(const gfx::CoordTyped<src>& aCoord, const gfx::ScaleFactor<src, dst>& aScale) {
   return gfx::CoordTyped<dst>(aCoord.value * aScale.scale);
 }
--- a/layout/base/nsCSSRendering.cpp
+++ b/layout/base/nsCSSRendering.cpp
@@ -5048,35 +5048,23 @@ nsImageRenderer::Draw(nsPresContext*    
     {
       RefPtr<gfxDrawable> drawable = DrawableForElement(aDest,
                                                           aRenderingContext);
       if (!drawable) {
         NS_WARNING("Could not create drawable for element");
         return DrawResult::TEMPORARY_ERROR;
       }
 
-      gfxContext* ctx = aRenderingContext.ThebesContext();
-      CompositionOp op = ctx->CurrentOp();
-      if (op != CompositionOp::OP_OVER) {
-        ctx->PushGroup(gfxContentType::COLOR_ALPHA);
-        ctx->SetOp(CompositionOp::OP_OVER);
-      }
-
       nsCOMPtr<imgIContainer> image(ImageOps::CreateFromDrawable(drawable));
       DrawResult result =
         nsLayoutUtils::DrawImage(*aRenderingContext.ThebesContext(),
                                  aPresContext, image,
                                  filter, aDest, aFill, aAnchor, aDirtyRect,
                                  ConvertImageRendererToDrawFlags(mFlags));
 
-      if (op != CompositionOp::OP_OVER) {
-        ctx->PopGroupToSource();
-        ctx->Paint();
-      }
-
       return result;
     }
     case eStyleImageType_Null:
     default:
       return DrawResult::SUCCESS;
   }
 }
 
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1185,19 +1185,19 @@ nsDisplayListBuilder::AdjustWindowDraggi
       LayoutDevicePixel::FromAppUnits(borderBox, aFrame->PresContext()->AppUnitsPerDevPixel());
     LayoutDeviceRect transformedDevPixelBorderBox =
       TransformTo<LayoutDevicePixel>(referenceFrameToRootReferenceFrame, devPixelBorderBox);
     transformedDevPixelBorderBox.Round();
     LayoutDeviceIntRect transformedDevPixelBorderBoxInt;
     if (transformedDevPixelBorderBox.ToIntRect(&transformedDevPixelBorderBoxInt)) {
       const nsStyleUserInterface* styleUI = aFrame->StyleUserInterface();
       if (styleUI->mWindowDragging == NS_STYLE_WINDOW_DRAGGING_DRAG) {
-        mWindowDraggingRegion.OrWith(LayoutDevicePixel::ToUntyped(transformedDevPixelBorderBoxInt));
+        mWindowDraggingRegion.OrWith(transformedDevPixelBorderBoxInt.ToUnknownRect());
       } else {
-        mWindowDraggingRegion.SubOut(LayoutDevicePixel::ToUntyped(transformedDevPixelBorderBoxInt));
+        mWindowDraggingRegion.SubOut(transformedDevPixelBorderBoxInt.ToUnknownRect());
       }
     }
   }
 }
 
 const uint32_t gWillChangeAreaMultiplier = 3;
 static uint32_t GetWillChangeCost(nsIFrame* aFrame,
                                   const nsSize& aSize) {
@@ -4004,18 +4004,23 @@ bool
 nsDisplayOpacity::CanApplyOpacity() const
 {
   return true;
 }
 
 bool
 nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder)
 {
-  if (NeedsActiveLayer(aBuilder))
+  if (NeedsActiveLayer(aBuilder) || mOpacity == 0.0) {
+    // If our opacity is zero then we'll discard all descendant display items
+    // except for layer event regions, so there's no point in doing this
+    // optimization (and if we do do it, then invalidations of those descendants
+    // might trigger repainting).
     return false;
+  }
 
   nsDisplayItem* child = mList.GetBottom();
   // Only try folding our opacity down if we have at most three children
   // that don't overlap and can all apply the opacity to themselves.
   if (!child) {
     return false;
   }
   struct {
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -401,17 +401,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)
@@ -452,17 +451,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) {
@@ -1048,37 +1046,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;
 
@@ -1244,26 +1238,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;
@@ -1272,45 +1262,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;
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -135,20 +135,20 @@ typedef struct CapturingContentInfo {
   // capture should only be allowed during a mousedown event
   bool mAllowed;
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   mozilla::StaticRefPtr<nsIContent> mContent;
 } CapturingContentInfo;
 
-// 0ff43c2e-5688-464f-a23f-a3de223df684
+// ae50e013-b2f1-4d4e-94c7-77cbcdab1c23
 #define NS_IPRESSHELL_IID \
-{ 0x0ff43c2e, 0x5688, 0x464f, \
-  { 0xa2, 0x3f, 0xa3, 0xde, 0x22, 0x3d, 0xf6, 0x84 } }
+{ 0xae50e013, 0xb2f1, 0x4d4e, \
+  { 0x94, 0xc7, 0x77, 0xcb, 0xcd, 0xab, 0x1c, 0x23 } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -1242,17 +1242,17 @@ public:
   {
     mObservesMutationsForPrint = aObserve;
   }
   bool ObservesNativeAnonMutationsForPrint()
   {
     return mObservesMutationsForPrint;
   }
 
-  virtual nsresult SetIsActive(bool aIsActive) = 0;
+  virtual nsresult SetIsActive(bool aIsActive, bool aIsHidden = true) = 0;
 
   bool IsActive()
   {
     return mIsActive;
   }
 
   // mouse capturing
   static CapturingContentInfo gCaptureInfo;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -983,20 +983,16 @@ GetDisplayPortFromMarginsData(nsIContent
     // coordinate space, so it doesn't include the local resolution.
     float localRes = presContext->PresShell()->GetResolution();
     parentRes.xScale /= localRes;
     parentRes.yScale /= localRes;
   }
   ScreenRect screenRect = LayoutDeviceRect::FromAppUnits(base, auPerDevPixel)
                         * parentRes;
 
-  nsRect expandedScrollableRect =
-    nsLayoutUtils::CalculateExpandedScrollableRect(frame);
-
-
   // Note on the correctness of applying the alignment in Screen space:
   //   The correct space to apply the alignment in would be Layer space, but
   //   we don't necessarily know the scale to convert to Layer space at this
   //   point because Layout may not yet have chosen the resolution at which to
   //   render (it chooses that in FrameLayerBuilder, but this can be called
   //   during display list building). Therefore, we perform the alignment in
   //   Screen space, which basically assumes that Layout chose to render at
   //   screen resolution; since this is what Layout does most of the time,
@@ -1074,31 +1070,26 @@ GetDisplayPortFromMarginsData(nsIContent
   screenRect += scrollPosScreen;
   float x = alignment.width * floor(screenRect.x / alignment.width);
   float y = alignment.height * floor(screenRect.y / alignment.height);
   float w = alignment.width * ceil(screenRect.width / alignment.width + 1);
   float h = alignment.height * ceil(screenRect.height / alignment.height + 1);
   screenRect = ScreenRect(x, y, w, h);
   screenRect -= scrollPosScreen;
 
-  ScreenRect screenExpScrollableRect =
-    LayoutDeviceRect::FromAppUnits(expandedScrollableRect,
-                                   auPerDevPixel) * res;
-
-  // Make sure the displayport remains within the scrollable rect.
-  screenRect = screenRect.ForceInside(screenExpScrollableRect - scrollPosScreen);
-
   // Convert the aligned rect back into app units.
   nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel);
 
   // Expand it for the low-res buffer if needed
   result = ApplyRectMultiplier(result, aMultiplier);
 
-  // Finally, clamp it to the expanded scrollable rect.
-  result = expandedScrollableRect.Intersect(result + scrollPos) - scrollPos;
+  // Make sure the displayport remains within the scrollable rect.
+  nsRect expandedScrollableRect =
+    nsLayoutUtils::CalculateExpandedScrollableRect(frame);
+  result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
 
   return result;
 }
 
 static bool
 GetDisplayPortImpl(nsIContent* aContent, nsRect *aResult, float aMultiplier)
 {
   DisplayPortPropertyData* rectData =
@@ -1849,41 +1840,46 @@ nsLayoutUtils::SetFixedPositionLayerData
   }
 
   // Work out the anchor point for this fixed position layer. We assume that
   // any positioning set (left/top/right/bottom) indicates that the
   // corresponding side of its container should be the anchor point,
   // defaulting to top-left.
   LayerPoint anchor(anchorRect.x, anchorRect.y);
 
+  int32_t sides = eSideBitsNone;
   if (aFixedPosFrame != aViewportFrame) {
     const nsStylePosition* position = aFixedPosFrame->StylePosition();
     if (position->mOffset.GetRightUnit() != eStyleUnit_Auto) {
+      sides |= eSideBitsRight;
       if (position->mOffset.GetLeftUnit() != eStyleUnit_Auto) {
+        sides |= eSideBitsLeft;
         anchor.x = anchorRect.x + anchorRect.width / 2.f;
       } else {
         anchor.x = anchorRect.XMost();
       }
     }
     if (position->mOffset.GetBottomUnit() != eStyleUnit_Auto) {
+      sides |= eSideBitsBottom;
       if (position->mOffset.GetTopUnit() != eStyleUnit_Auto) {
+        sides |= eSideBitsTop;
         anchor.y = anchorRect.y + anchorRect.height / 2.f;
       } else {
         anchor.y = anchorRect.YMost();
       }
     }
   }
 
   ViewID id = FrameMetrics::NULL_SCROLL_ID;
   if (nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame()) {
     if (nsIContent* content = rootScrollFrame->GetContent()) {
       id = FindOrCreateIDFor(content);
     }
   }
-  aLayer->SetFixedPositionData(id, anchor, aIsClipFixed);
+  aLayer->SetFixedPositionData(id, anchor, sides, aIsClipFixed);
 }
 
 bool
 nsLayoutUtils::ViewportHasDisplayPort(nsPresContext* aPresContext, nsRect* aDisplayPort)
 {
   nsIFrame* rootScrollFrame =
     aPresContext->PresShell()->GetRootScrollFrame();
   return rootScrollFrame &&
@@ -6386,16 +6382,18 @@ DrawImageInternal(gfxContext&           
                   Filter                 aGraphicsFilter,
                   const nsRect&          aDest,
                   const nsRect&          aFill,
                   const nsPoint&         aAnchor,
                   const nsRect&          aDirty,
                   const SVGImageContext* aSVGContext,
                   uint32_t               aImageFlags)
 {
+  DrawResult result = DrawResult::SUCCESS;
+
   if (aPresContext->Type() == nsPresContext::eContext_Print) {
     // We want vector images to be passed on as vector commands, not a raster
     // image.
     aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
   }
   if (aDest.Contains(aFill)) {
     aImageFlags |= imgIContainer::FLAG_CLAMP;
   }
@@ -6403,31 +6401,58 @@ DrawImageInternal(gfxContext&           
    aPresContext->AppUnitsPerDevPixel();
 
   SnappedImageDrawingParameters params =
     ComputeSnappedImageDrawingParameters(&aContext, appUnitsPerDevPixel, aDest,
                                          aFill, aAnchor, aDirty, aImage,
                                          aGraphicsFilter, aImageFlags);
 
   if (!params.shouldDraw) {
-    return DrawResult::SUCCESS;
-  }
-
-  gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
-  aContext.SetMatrix(params.imageSpaceToDeviceSpace);
-
-  Maybe<SVGImageContext> svgContext = ToMaybe(aSVGContext);
-  if (!svgContext) {
-    // Use the default viewport.
-    svgContext = Some(SVGImageContext(params.svgViewportSize, Nothing()));
-  }
-
-  return aImage->Draw(&aContext, params.size, params.region,
-                      imgIContainer::FRAME_CURRENT, aGraphicsFilter,
-                      svgContext, aImageFlags);
+    return result;
+  }
+
+  {
+    gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
+
+    RefPtr<gfxContext> destCtx = &aContext;
+
+    IntRect tmpDTRect;
+
+    if (destCtx->CurrentOp() != CompositionOp::OP_OVER) {
+      Rect imageRect = ToRect(params.imageSpaceToDeviceSpace.TransformBounds(params.region.Rect()));
+      imageRect.ToIntRect(&tmpDTRect);
+
+      RefPtr<DrawTarget> tempDT = destCtx->GetDrawTarget()->CreateSimilarDrawTarget(tmpDTRect.Size(), SurfaceFormat::B8G8R8A8);
+      destCtx = new gfxContext(tempDT, imageRect.TopLeft());
+    }
+    destCtx->SetMatrix(params.imageSpaceToDeviceSpace);
+
+    Maybe<SVGImageContext> svgContext = ToMaybe(aSVGContext);
+    if (!svgContext) {
+      // Use the default viewport.
+      svgContext = Some(SVGImageContext(params.svgViewportSize, Nothing()));
+    }
+
+    result = aImage->Draw(destCtx, params.size, params.region,
+                          imgIContainer::FRAME_CURRENT, aGraphicsFilter,
+                          svgContext, aImageFlags);
+
+    if (!tmpDTRect.IsEmpty()) {
+      DrawTarget* dt = aContext.GetDrawTarget();
+      RefPtr<SourceSurface> surf = destCtx->GetDrawTarget()->Snapshot();
+
+      dt->SetTransform(Matrix::Translation(-aContext.GetDeviceOffset()));
+      dt->DrawSurface(surf, Rect(tmpDTRect.x, tmpDTRect.y, tmpDTRect.width, tmpDTRect.height),
+                      Rect(0, 0, tmpDTRect.width, tmpDTRect.height),
+                      DrawSurfaceOptions(Filter::POINT),
+                      DrawOptions(1.0f, aContext.CurrentOp()));
+    }
+  }
+
+  return result;
 }
 
 /* static */ DrawResult
 nsLayoutUtils::DrawSingleUnscaledImage(gfxContext&          aContext,
                                        nsPresContext*       aPresContext,
                                        imgIContainer*       aImage,
                                        Filter               aGraphicsFilter,
                                        const nsPoint&       aDest,
@@ -7962,17 +7987,17 @@ nsLayoutUtils::GetContentViewerSize(nsPr
   nsCOMPtr<nsIContentViewer> cv;
   docShell->GetContentViewer(getter_AddRefs(cv));
   if (!cv) {
     return false;
   }
 
   nsIntRect bounds;
   cv->GetBounds(bounds);
-  aOutSize = LayoutDeviceIntRect::FromUntyped(bounds).Size();
+  aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
   return true;
 }
 
 static bool
 UpdateCompositionBoundsForRCDRSF(ParentLayerRect& aCompBounds,
                                  nsPresContext* aPresContext,
                                  const nsRect& aFrameBounds,
                                  bool aScaleContentViewerSize,
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -4637,46 +4637,19 @@ PresShell::RenderDocument(const nsRect& 
     // Nothing to paint, just fill the rect
     aThebesContext->SetColor(Color::FromABGR(aBackgroundColor));
     aThebesContext->Fill();
     return NS_OK;
   }
 
   gfxContextAutoSaveRestore save(aThebesContext);
 
-  CompositionOp oldOp = aThebesContext->CurrentOp();
-  if (oldOp == CompositionOp::OP_OVER) {
-    // Clip to the destination rectangle before we push the group,
-    // to limit the size of the temporary surface
-    aThebesContext->Clip();
-  }
-
-  // we want the window to be composited as a single image using
-  // whatever operator was set; set OP_OVER here, which is
-  // either already the case, or overrides the operator in a group.
-  // the original operator will be present when we PopGroup.
-  // we can avoid using a temporary surface if we're using OP_OVER
-  bool needsGroup = oldOp != CompositionOp::OP_OVER;
-
-  if (needsGroup) {
-    aThebesContext->PushGroup(NS_GET_A(aBackgroundColor) == 0xff ?
-                              gfxContentType::COLOR :
-                              gfxContentType::COLOR_ALPHA);
-    aThebesContext->Save();
-
-    if (oldOp != CompositionOp::OP_OVER) {
-      // Clip now while we paint to the temporary surface. For
-      // non-source-bounded operators (e.g., SOURCE), we need to do clip
-      // here after we've pushed the group, so that eventually popping
-      // the group and painting it will be able to clear the entire
-      // destination surface.
-      aThebesContext->Clip();
-      aThebesContext->SetOp(CompositionOp::OP_OVER);
-    }
-  }
+  MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
+
+  aThebesContext->Clip();
 
   nsDeviceContext* devCtx = mPresContext->DeviceContext();
 
   gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
                   -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
   gfxFloat scale = gfxFloat(devCtx->AppUnitsPerDevPixel())/nsPresContext::AppUnitsPerCSSPixel();
 
   // Since canvas APIs use floats to set up their matrices, we may have some
@@ -4738,23 +4711,16 @@ PresShell::RenderDocument(const nsRect& 
   // Don't let drawWindow blow away our retained layer tree
   if ((flags & nsLayoutUtils::PAINT_WIDGET_LAYERS) && wouldFlushRetainedLayers) {
     flags &= ~nsLayoutUtils::PAINT_WIDGET_LAYERS;
   }
 
   nsLayoutUtils::PaintFrame(&rc, rootFrame, nsRegion(aRect),
                             aBackgroundColor, flags);
 
-  // if we had to use a group, paint it to the destination now
-  if (needsGroup) {
-    aThebesContext->Restore();
-    aThebesContext->PopGroupToSource();
-    aThebesContext->Paint();
-  }
-
   return NS_OK;
 }
 
 /*
  * Clip the display list aList to a range. Returns the clipped
  * rectangle surrounding the range.
  */
 nsRect
@@ -8192,17 +8158,17 @@ PresShell::AdjustContextMenuKeyEvent(Wid
       nsIFrame* itemFrame =
         (static_cast<nsMenuPopupFrame *>(popupFrame))->GetCurrentMenuItem();
       if (!itemFrame)
         itemFrame = popupFrame;
 
       nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
       aEvent->widget = widget;
       LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
-      aEvent->refPoint = LayoutDeviceIntPoint::FromUntyped(
+      aEvent->refPoint = LayoutDeviceIntPoint::FromUnknownPoint(
         itemFrame->GetScreenRect().BottomLeft()) - widgetPoint;
 
       mCurrentEventContent = itemFrame->GetContent();
       mCurrentEventFrame = itemFrame;
 
       return true;
     }
   }
@@ -10606,17 +10572,17 @@ SetPluginIsActive(nsISupports* aSupports
   nsIFrame *frame = content->GetPrimaryFrame();
   nsIObjectFrame *objectFrame = do_QueryFrame(frame);
   if (objectFrame) {
     objectFrame->SetIsDocumentActive(*static_cast<bool*>(aClosure));
   }
 }
 
 nsresult
-PresShell::SetIsActive(bool aIsActive)
+PresShell::SetIsActive(bool aIsActive, bool aIsHidden)
 {
   NS_PRECONDITION(mDocument, "should only be called with a document");
 
   mIsActive = aIsActive;
   nsPresContext* presContext = GetPresContext();
   if (presContext &&
       presContext->RefreshDriver()->PresContext() == presContext) {
     presContext->RefreshDriver()->SetThrottled(!mIsActive);
@@ -10650,31 +10616,32 @@ PresShell::SetIsActive(bool aIsActive)
   // is problematic when those layers uselessly hold on to precious
   // resources like directly texturable memory.
   //
   // PresShell::SetIsActive() is the first C++ entry point at which we
   // (i) know that our parent process wants our content to be hidden;
   // and (ii) has easy access to the TabChild.  So we use this
   // notification to signal the TabChild to drop its layer tree and
   // stop trying to repaint.
-  if (TabChild* tab = TabChild::GetFrom(this)) {
-    if (aIsActive) {
-      tab->MakeVisible();
-      if (!mIsZombie) {
-        if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
-          FrameLayerBuilder::InvalidateAllLayersForFrame(
-            nsLayoutUtils::GetDisplayRootFrame(root));
-          root->SchedulePaint();
+  if (aIsHidden) {
+    if (TabChild* tab = TabChild::GetFrom(this)) {
+      if (aIsActive) {
+        tab->MakeVisible();
+        if (!mIsZombie) {
+          if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
+            FrameLayerBuilder::InvalidateAllLayersForFrame(
+              nsLayoutUtils::GetDisplayRootFrame(root));
+            root->SchedulePaint();
+          }
         }
-      }
-    } else {
-      tab->MakeHidden();
-    }
-  }
-
+      } else {
+        tab->MakeHidden();
+      }
+    }
+  }
   return rv;
 }
 
 /*
  * Determines the current image locking state. Called when one of the
  * dependent factors changes.
  */
 nsresult
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -350,17 +350,17 @@ public:
 
   virtual void AddPrintPreviewBackgroundItem(nsDisplayListBuilder& aBuilder,
                                              nsDisplayList& aList,
                                              nsIFrame* aFrame,
                                              const nsRect& aBounds) override;
 
   virtual nscolor ComputeBackstopColor(nsView* aDisplayRoot) override;
 
-  virtual nsresult SetIsActive(bool aIsActive) override;
+  virtual nsresult SetIsActive(bool aIsActive, bool aIsHidden = true) override;
 
   virtual bool GetIsViewportOverridden() override {
     return (mMobileViewportManager != nullptr);
   }
 
   virtual bool IsLayoutFlushObserver() override
   {
     return GetPresContext()->RefreshDriver()->
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1223568-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<div style="display:flex; justify-content: stretch end">a
new file mode 100644
--- /dev/null
+++ b/layout/generic/crashtests/1223568-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<span style="justify-content: stretch end true; display: inline-flex;"><span style="writing-mode: vertical-rl; display: -moz-inline-box;">f</span></span>
+</body>
+</html>
--- a/layout/generic/crashtests/crashtests.list
+++ b/layout/generic/crashtests/crashtests.list
@@ -584,16 +584,18 @@ pref(layout.css.grid.enabled,true) load 
 load 1157011.html
 load 1169420-1.html
 load 1169420-2.html
 load 1183431.html
 load 1221112-1.html
 load 1221112-2.html
 load 1221874-1.html
 load 1222783.xhtml
+load 1223568-1.html
+load 1223568-2.html
 load first-letter-638937-1.html
 load first-letter-638937-2.html
 pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) asserts(1-100) load font-inflation-762332.html # bug 762332
 load outline-on-frameset.xhtml
 load text-overflow-bug666751-1.html
 load text-overflow-bug666751-2.html
 load text-overflow-bug670564.xhtml
 load text-overflow-bug671796.xhtml
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -4609,22 +4609,23 @@ nsFrame::ReflowAbsoluteFrames(nsPresCont
 {
   if (HasAbsolutelyPositionedChildren()) {
     nsAbsoluteContainingBlock* absoluteContainer = GetAbsoluteContainingBlock();
 
     // Let the absolutely positioned container reflow any absolutely positioned
     // child frames that need to be reflowed
 
     // The containing block for the abs pos kids is formed by our padding edge.
-    nsMargin computedBorder =
-      aReflowState.ComputedPhysicalBorderPadding() - aReflowState.ComputedPhysicalPadding();
+    nsMargin usedBorder = GetUsedBorder();
     nscoord containingBlockWidth =
-      aDesiredSize.Width() - computedBorder.LeftRight();
+      aDesiredSize.Width() - usedBorder.LeftRight();
+    MOZ_ASSERT(containingBlockWidth >= 0);
     nscoord containingBlockHeight =
-      aDesiredSize.Height() - computedBorder.TopBottom();
+      aDesiredSize.Height() - usedBorder.TopBottom();
+    MOZ_ASSERT(containingBlockHeight >= 0);
 
     nsContainerFrame* container = do_QueryFrame(this);
     NS_ASSERTION(container, "Abs-pos children only supported on container frames for now");
 
     nsRect containingBlock(0, 0, containingBlockWidth, containingBlockHeight);
     absoluteContainer->Reflow(container, aPresContext, aReflowState, aStatus,
                               containingBlock,
                               aConstrainBSize, true, true, // XXX could be optimized
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -196,35 +196,16 @@ public:
     if (mRenderFrame) {
       TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
       browser->HandleLongTap(aPoint, aModifiers, aGuid, aInputBlockId);
     }
   }
 
   void ClearRenderFrame() { mRenderFrame = nullptr; }
 
-  virtual void SendAsyncScrollDOMEvent(bool aIsRootContent,
-                                       const CSSRect& aContentRect,
-                                       const CSSSize& aContentSize) override
-  {
-    if (MessageLoop::current() != mUILoop) {
-      mUILoop->PostTask(
-        FROM_HERE,
-        NewRunnableMethod(this,
-                          &RemoteContentController::SendAsyncScrollDOMEvent,
-                          aIsRootContent, aContentRect, aContentSize));
-      return;
-    }
-    if (mRenderFrame && aIsRootContent) {
-      TabParent* browser = TabParent::GetFrom(mRenderFrame->Manager());
-      BrowserElementParent::DispatchAsyncScrollEvent(browser, aContentRect,
-                                                     aContentSize);
-    }
-  }
-
   virtual void PostDelayedTask(Task* aTask, int aDelayMs) override
   {
 #ifdef MOZ_ANDROID_APZ
     AndroidBridge::Bridge()->PostTaskToUiThread(aTask, aDelayMs);
 #else
     (MessageLoop::current() ? MessageLoop::current() : mUILoop)->
        PostDelayedTask(FROM_HERE, aTask, aDelayMs);
 #endif
--- a/layout/reftests/invalidation/reftest.list
+++ b/layout/reftests/invalidation/reftest.list
@@ -66,8 +66,10 @@ pref(layout.animated-image-layers.enable
 != layer-splitting-7.html about:blank
 fuzzy-if(gtkWidget,2,4) fuzzy-if(asyncPan,2,3955) fuzzy-if(OSX,179,30) == image-scrolling-zoom-1.html image-scrolling-zoom-1-ref.html
 != image-scrolling-zoom-1-ref.html image-scrolling-zoom-1-notref.html
 != fast-scrolling.html about:blank
 != fractional-transform-1.html about:blank
 != fractional-transform-2.html about:blank
 != fractional-transform-3.html about:blank
 == background-position-1.html background-position-1-ref.html
+== zero-opacity-animation.html about:blank
+== zero-opacity-text.html about:blank
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/zero-opacity-animation.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<div style="opacity:0">
+  <div id="d" class="reftest-no-paint" style="height:50px; border:2px solid black"></div>
+</div>
+<script type="application/javascript">
+function doTest() {
+  d.style.border = "2px solid green";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/invalidation/zero-opacity-text.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<div style="opacity:0">
+  <div id="d" class="reftest-no-paint" style="height:50px;">abc</div>
+</div>
+<script type="application/javascript">
+function doTest() {
+  d.textContent = "def";
+  document.documentElement.removeAttribute("class");
+}
+document.addEventListener("MozReftestInvalidate", doTest, false);
+</script>
+</body>
+</html>
--- a/layout/reftests/scrolling/deferred-anchor-ref.xhtml
+++ b/layout/reftests/scrolling/deferred-anchor-ref.xhtml
@@ -1,11 +1,11 @@
 <?xml version="1.0"?>
 <!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%; overflow: hidden;">
 <head>
 </head>
 <body style="margin: 0;">
 <div style="height: 50px; width: 50px; background-color: red;"/>
 <div style="height: 1000px;"/>
 <div id="d" style="height: 50px; width: 50px; background-color: green;"/>
 <div style="height: 1000px;"/>
 </body>
--- a/layout/reftests/scrolling/deferred-anchor.xhtml
+++ b/layout/reftests/scrolling/deferred-anchor.xhtml
@@ -1,11 +1,11 @@
 <?xml version="1.0"?>
 <!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml" style="height: 100%; overflow: hidden;">
 <head>
 <script>
 var xhr = new XMLHttpRequest();
 xhr.onprogress = function() {
 };
 xhr.onload = function() {
   document.documentElement.innerHTML = this.responseXML.documentElement.innerHTML;
 };
--- a/layout/reftests/scrolling/iframe-deferred-anchor.xhtml
+++ b/layout/reftests/scrolling/iframe-deferred-anchor.xhtml
@@ -1,11 +1,11 @@
 <?xml version="1.0"?>
 <!DOCTYPE html>
-<html xmlns="http://www.w3.org/1999/xhtml">
+<html xmlns="http://www.w3.org/1999/xhtml" style="overflow:hidden">
 <head>
 <script>document.location.hash = "#d";</script>
 </head>
 <body style="margin: 0; background-color: white;">
 <div style="height: 50px; width: 50px; background-color: red;"/>
 <div style="height: 1000px;"/>
 <div id="d" style="height: 50px; width: 50px; background-color: green;"/>
 <div style="height: 1000px;"/>
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1737,19 +1737,23 @@ nsStylePosition::ComputedAlignSelf(const
 }
 
 uint16_t
 nsStylePosition::ComputedJustifyContent(const nsStyleDisplay* aDisplay) const
 {
   switch (aDisplay->mDisplay) {
     case NS_STYLE_DISPLAY_FLEX:
     case NS_STYLE_DISPLAY_INLINE_FLEX:
+      // For flex containers, css-align-3 says the justify-content value
+      // "'stretch' computes to 'flex-start'."
+      // https://drafts.csswg.org/css-align-3/#propdef-justify-content
       // XXX maybe map 'auto' too? (ISSUE 8 in the spec)
       // https://drafts.csswg.org/css-align-3/#content-distribution
-      if (mJustifyContent == NS_STYLE_JUSTIFY_STRETCH) {
+      if ((mJustifyContent & NS_STYLE_ALIGN_ALL_BITS) ==
+          NS_STYLE_JUSTIFY_STRETCH) {
         return NS_STYLE_JUSTIFY_FLEX_START;
       }
       break;
   }
   uint8_t val = mJustifyContent & NS_STYLE_JUSTIFY_ALL_BITS;
   val = MapLeftRightToStart(val, eLogicalAxisInline, aDisplay);
   uint8_t fallback = mJustifyContent >> NS_STYLE_JUSTIFY_ALL_SHIFT;
   fallback = MapLeftRightToStart(fallback, eLogicalAxisInline, aDisplay);
--- a/layout/svg/SVGTextFrame.cpp
+++ b/layout/svg/SVGTextFrame.cpp
@@ -2969,17 +2969,17 @@ SVGTextDrawPathCallbacks::MakeFillPatter
 }
 
 void
 SVGTextDrawPathCallbacks::FillAndStrokeGeometry()
 {
   bool pushedGroup = false;
   if (mColor == NS_40PERCENT_FOREGROUND_COLOR) {
     pushedGroup = true;
-    gfx->PushGroup(gfxContentType::COLOR_ALPHA);
+    gfx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 0.4f);
   }
 
   uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder;
   if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) {
     FillGeometry();
     StrokeGeometry();
   } else {
     while (paintOrder) {
@@ -2993,18 +2993,17 @@ SVGTextDrawPathCallbacks::FillAndStrokeG
           StrokeGeometry();
           break;
       }
       paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
     }
   }
 
   if (pushedGroup) {
-    gfx->PopGroupToSource();
-    gfx->Paint(0.4);
+    gfx->PopGroupAndBlend();
   }
 }
 
 void
 SVGTextDrawPathCallbacks::FillGeometry()
 {
   GeneralPattern fillPattern;
   MakeFillPattern(&fillPattern);
new file mode 100644
--- /dev/null
+++ b/layout/svg/crashtests/1223281-1.svg
@@ -0,0 +1,24 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script>
+<![CDATA[
+
+function forceFrameConstruction()
+{
+    document.documentElement.getBoundingClientRect();
+}
+
+function boom()
+{
+    document.documentElement.style.overflow = "scroll";
+    forceFrameConstruction()
+    document.documentElement.style.visibility = "visible";
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<rect style="perspective: 10em;" />
+</svg>
+
--- a/layout/svg/crashtests/crashtests.list
+++ b/layout/svg/crashtests/crashtests.list
@@ -189,9 +189,10 @@ load 979407-1.svg
 load 979407-2.svg
 load 993443.svg
 load 1016145.svg
 load 1028512.svg
 load 1140080-1.svg
 load 1149542-1.svg
 load 1182496-1.html
 load 1209525-1.svg
+load 1223281-1.svg
 load extref-test-1.xhtml
--- a/layout/svg/nsFilterInstance.cpp
+++ b/layout/svg/nsFilterInstance.cpp
@@ -371,17 +371,17 @@ nsFilterInstance::BuildSourcePaint(Sourc
                   gfxMatrix::Translation(-neededRect.TopLeft()));
     GeneralPattern pattern;
     if (aSource == &mFillPaint) {
       nsSVGUtils::MakeFillPatternFor(mTargetFrame, gfx, &pattern);
     } else if (aSource == &mStrokePaint) {
       nsSVGUtils::MakeStrokePatternFor(mTargetFrame, gfx, &pattern);
     }
     if (pattern.GetPattern()) {
-      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(neededRect)),
+      offscreenDT->FillRect(ToRect(FilterSpaceToUserSpace(ThebesRect(neededRect))),
                             pattern);
     }
     gfx->Restore();
   }
 
   aSource->mSourceSurface = offscreenDT->Snapshot();
   aSource->mSurfaceRect = neededRect;
 
@@ -417,17 +417,17 @@ nsFilterInstance::BuildSourceImage(DrawT
 
   RefPtr<DrawTarget> offscreenDT =
     gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
       neededRect.Size(), SurfaceFormat::B8G8R8A8);
   if (!offscreenDT) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  gfxRect r = FilterSpaceToUserSpace(neededRect);
+  gfxRect r = FilterSpaceToUserSpace(ThebesRect(neededRect));
   r.RoundOut();
   nsIntRect dirty;
   if (!gfxUtils::GfxRectToIntRect(r, &dirty))
     return NS_ERROR_FAILURE;
 
   // SVG graphics paint to device space, so we need to set an initial device
   // space to filter space transform on the gfxContext that SourceGraphic
   // and SourceAlpha will paint to.
@@ -479,17 +479,17 @@ nsFilterInstance::Render(gfxContext* aCo
   nsresult rv = BuildSourceImage(dt);
   if (NS_FAILED(rv))
     return rv;
   rv = BuildSourcePaints(dt);
   if (NS_FAILED(rv))
     return rv;
 
   FilterSupport::RenderFilterDescription(
-    dt, mFilterDescription, ToRect(filterRect),
+    dt, mFilterDescription, IntRectToRect(filterRect),
     mSourceGraphic.mSourceSurface, mSourceGraphic.mSurfaceRect,
     mFillPaint.mSourceSurface, mFillPaint.mSurfaceRect,
     mStrokePaint.mSourceSurface, mStrokePaint.mSurfaceRect,
     mInputImages, Point(0, 0));
 
   return NS_OK;
 }
 
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -90,17 +90,20 @@ nsSVGClipPathFrame::ApplyClipOrPaintClip
     nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr);
   bool referencedClipIsTrivial;
   if (clipPathFrame) {
     referencedClipIsTrivial = clipPathFrame->IsTrivial();
     aContext.Save();
     if (referencedClipIsTrivial) {
       clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
     } else {
-      aContext.PushGroup(gfxContentType::ALPHA);
+      Matrix maskTransform;
+      RefPtr<SourceSurface> mask = clipPathFrame->GetClipMask(aContext, aClippedFrame, aMatrix, &maskTransform);
+
+      aContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform);
     }
   }
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid;
        kid = kid->GetNextSibling()) {
     nsISVGChildFrame* SVGFrame = do_QueryFrame(kid);
     if (SVGFrame) {
       // The CTM of each frame referencing us can be different.
@@ -116,70 +119,97 @@ nsSVGClipPathFrame::ApplyClipOrPaintClip
       bool isTrivial;
 
       if (clipPathFrame) {
         isTrivial = clipPathFrame->IsTrivial();
         aContext.Save();
         if (isTrivial) {
           clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
         } else {
-          aContext.PushGroup(gfxContentType::ALPHA);
+          Matrix maskTransform;
+          RefPtr<SourceSurface> mask = clipPathFrame->GetClipMask(aContext, aClippedFrame, aMatrix, &maskTransform);
+
+          aContext.PushGroupForBlendBack(gfxContentType::ALPHA, 1.0, mask, maskTransform);
         }
       }
 
       gfxMatrix toChildsUserSpace = mMatrixForChildren;
       nsIFrame* child = do_QueryFrame(SVGFrame);
       nsIContent* childContent = child->GetContent();
       if (childContent->IsSVGElement()) {
         toChildsUserSpace =
           static_cast<const nsSVGElement*>(childContent)->
             PrependLocalTransformsTo(mMatrixForChildren,
                                      nsSVGElement::eUserSpaceToParent);
       }
       SVGFrame->PaintSVG(aContext, toChildsUserSpace);
 
       if (clipPathFrame) {
         if (!isTrivial) {
-          aContext.PopGroupToSource();
-
-          aContext.PushGroup(gfxContentType::ALPHA);
-
-          clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
-          Matrix maskTransform;
-          RefPtr<SourceSurface> clipMaskSurface = aContext.PopGroupToSurface(&maskTransform);
-
-          if (clipMaskSurface) {
-            aContext.Mask(clipMaskSurface, maskTransform);
-          }
+          aContext.PopGroupAndBlend();
         }
         aContext.Restore();
       }
     }
   }
 
   if (clipPathFrame) {
     if (!referencedClipIsTrivial) {
-      aContext.PopGroupToSource();
-
-      aContext.PushGroup(gfxContentType::ALPHA);
-
-      clipPathFrame->ApplyClipOrPaintClipMask(aContext, aClippedFrame, aMatrix);
-      Matrix maskTransform;
-      RefPtr<SourceSurface> clipMaskSurface = aContext.PopGroupToSurface(&maskTransform);
-
-      if (clipMaskSurface) {
-        aContext.Mask(clipMaskSurface, maskTransform);
-      }
+      aContext.PopGroupAndBlend();
     }
     aContext.Restore();
   }
 
   return NS_OK;
 }
 
+already_AddRefed<SourceSurface>
+nsSVGClipPathFrame::GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+                                const gfxMatrix& aMatrix, Matrix* aMaskTransform,
+                                SourceSurface* aInputMask, const Matrix& aInputMaskTransform)
+{
+  if (IsTrivial()) {
+    return nullptr;
+  }
+
+  IntRect intRect;
+  {
+    gfxContextMatrixAutoSaveRestore autoRestoreMatrix(&aReferenceContext);
+
+    aReferenceContext.SetMatrix(gfxMatrix());
+    gfxRect rect = aReferenceContext.GetClipExtents();
+    intRect = RoundedOut(ToRect(rect));
+  }
+
+  RefPtr<DrawTarget> maskDT = aReferenceContext.GetDrawTarget()->CreateSimilarDrawTarget(intRect.Size(), SurfaceFormat::A8);
+
+  gfxMatrix mat =
+    aReferenceContext.CurrentMatrix() * gfxMatrix::Translation(-intRect.TopLeft());
+  {
+    RefPtr<gfxContext> ctx = new gfxContext(maskDT);
+    ctx->SetMatrix(mat);
+    ApplyClipOrPaintClipMask(*ctx, aClippedFrame, aMatrix);
+  }
+
+  mat.Invert();
+
+  if (aInputMask) {
+    MOZ_ASSERT(!aInputMaskTransform.HasNonTranslation());
+
+    RefPtr<SourceSurface> currentMask = maskDT->Snapshot();
+    maskDT->SetTransform(Matrix());
+    maskDT->ClearRect(Rect(0, 0, intRect.width, intRect.height));
+    maskDT->MaskSurface(SurfacePattern(currentMask, ExtendMode::CLAMP), aInputMask,
+                        Point(aInputMaskTransform._31 - intRect.x, aInputMaskTransform._32 - intRect.y));
+  }
+
+  *aMaskTransform = ToMatrix(mat);
+  return maskDT->Snapshot();
+}
+
 bool
 nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame,
                                           const gfxPoint &aPoint)
 {
   // If the flag is set when we get here, it means this clipPath frame
   // has already been used in hit testing against the current clip,
   // and the document has a clip reference loop.
   if (mInUse) {
--- a/layout/svg/nsSVGClipPathFrame.h
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -38,27 +38,39 @@ public:
 
   // nsSVGClipPathFrame methods:
 
   /**
    * If the SVG clipPath is simple (as determined by the IsTrivial() method),
    * calling this method simply pushes a clip path onto the DrawTarget.  If the
    * SVG clipPath is not simple then calling this method will paint the
    * clipPath's contents (geometry being filled only, with opaque black) to the
-   * DrawTarget.  In this latter case callers are expected to first push a
-   * group before calling this method, then pop the group after calling and use
-   * it as a mask to mask the clipped frame.
+   * DrawTarget.
    *
    * XXXjwatt Maybe split this into two methods.
    */
   nsresult ApplyClipOrPaintClipMask(gfxContext& aContext,
                                     nsIFrame* aClippedFrame,
                                     const gfxMatrix &aMatrix);
 
   /**
+   * If the SVG clipPath is simple (as determined by the IsTrivial() method),
+   * calling this method simply returns null.  If the SVG clipPath is not
+   * simple then calling this method will return a mask surface containing
+   * the clipped geometry. The reference context will be used to determine the
+   * backend for the SourceSurface as well as the size, which will be limited
+   * to the device clip extents on the context.
+   */
+  already_AddRefed<mozilla::gfx::SourceSurface>
+    GetClipMask(gfxContext& aReferenceContext, nsIFrame* aClippedFrame,
+                const gfxMatrix& aMatrix, Matrix* aMaskTransform,
+                mozilla::gfx::SourceSurface* aInputMask = nullptr,
+                const mozilla::gfx::Matrix& aInputMaskTransform = mozilla::gfx::Matrix());
+
+  /**
    * aPoint is expected to be in aClippedFrame's SVG user space.
    */
   bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint);
 
   // Check if this clipPath is made up of more than one geometry object.
   // If so, the clipping API in cairo isn't enough and we need to use
   // mask based clipping.
   bool IsTrivial(nsISVGChildFrame **aSingleChild = nullptr);
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -335,17 +335,17 @@ nsSVGImageFrame::PaintSVG(gfxContext& aC
     // optimize group opacity, the opacity used for compositing the
     // image into the current canvas is just the group opacity.
     float opacity = 1.0f;
     if (nsSVGUtils::CanOptimizeOpacity(this)) {
       opacity = StyleDisplay()->mOpacity;
     }
 
     if (opacity != 1.0f || StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
-      aContext.PushGroup(gfxContentType::COLOR_ALPHA);
+      aContext.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
     }
 
     nscoord appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
     nsRect dirtyRect; // only used if aDirtyRect is non-null
     if (aDirtyRect) {
       NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() ||
                    (mState & NS_FRAME_IS_NONDISPLAY),
                    "Display lists handle dirty rect intersection test");
@@ -399,19 +399,17 @@ nsSVGImageFrame::PaintSVG(gfxContext& aC
         mImageContainer,
         nsLayoutUtils::GetGraphicsFilterForFrame(this),
         nsPoint(0, 0),
         aDirtyRect ? &dirtyRect : nullptr,
         drawFlags);
     }
 
     if (opacity != 1.0f || StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
-      aContext.PopGroupToSource();
-      aContext.SetOp(CompositionOp::OP_OVER);
-      aContext.Paint(opacity);
+      aContext.PopGroupAndBlend();
     }
     // gfxContextAutoSaveRestore goes out of scope & cleans up our gfxContext
   }
 
   return rv;
 }
 
 nsIFrame*
--- a/layout/svg/nsSVGIntegrationUtils.cpp
+++ b/layout/svg/nsSVGIntegrationUtils.cpp
@@ -506,28 +506,78 @@ nsSVGIntegrationUtils::PaintFramesWithEf
   gfxPoint devPixelOffsetToUserSpace =
     nsLayoutUtils::PointToGfxPoint(offsetToUserSpace,
                                    aFrame->PresContext()->AppUnitsPerDevPixel());
   aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
 
   gfxMatrix cssPxToDevPxMatrix = GetCSSPxToDevPxMatrix(aFrame);
 
   bool complexEffects = false;
+
+  // These are used if we require a temporary surface for a custom blend mode.
+  RefPtr<gfxContext> target = &aContext;
+  IntPoint targetOffset;
+
   /* Check if we need to do additional operations on this child's
    * rendering, which necessitates rendering into another surface. */
   if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)
       || aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
     complexEffects = true;
+
+    Matrix maskTransform;
+    RefPtr<SourceSurface> maskSurface =
+      maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext,
+                                                    aFrame, cssPxToDevPxMatrix, opacity, &maskTransform)
+                : nullptr;
+
+    if (maskFrame && !maskSurface) {
+      // Entire surface is clipped out.
+      return;
+    }
+
     aContext.Save();
     nsRect clipRect =
       aFrame->GetVisualOverflowRectRelativeToSelf() + toUserSpace;
     aContext.Clip(NSRectToSnappedRect(clipRect,
                                   aFrame->PresContext()->AppUnitsPerDevPixel(),
                                   *drawTarget));
-    aContext.PushGroup(gfxContentType::COLOR_ALPHA);
+
+    if (aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
+      // Create a temporary context to draw to so we can blend it back with
+      // another operator.
+      gfxRect clipRect;
+      {
+        gfxContextMatrixAutoSaveRestore matRestore(&aContext);
+
+        aContext.SetMatrix(gfxMatrix());
+        clipRect = aContext.GetClipExtents();
+      }
+
+      IntRect drawRect = RoundedOut(ToRect(clipRect));
+
+      RefPtr<DrawTarget> targetDT = aContext.GetDrawTarget()->CreateSimilarDrawTarget(drawRect.Size(), SurfaceFormat::B8G8R8A8);
+      target = new gfxContext(targetDT);
+      target->SetMatrix(aContext.CurrentMatrix() * gfxMatrix::Translation(-drawRect.TopLeft()));
+      targetOffset = drawRect.TopLeft();
+    }
+
+    if (clipPathFrame && !isTrivialClip) {
+      Matrix clippedMaskTransform;
+      RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(aContext, aFrame, cssPxToDevPxMatrix,
+                                                                         &clippedMaskTransform, maskSurface, maskTransform);
+
+      if (clipMaskSurface) {
+        maskSurface = clipMaskSurface;
+        maskTransform = clippedMaskTransform;
+      }
+    }
+
+    if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) {
+      target->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity, maskSurface, maskTransform);
+    }
   }
 
   /* If this frame has only a trivial clipPath, set up cairo's clipping now so
    * we can just do normal painting and get it clipped appropriately.
    */
   if (clipPathFrame && isTrivialClip) {
     aContext.Save();
     clipPathFrame->ApplyClipOrPaintClipMask(aContext, aFrame, cssPxToDevPxMatrix);
@@ -535,65 +585,48 @@ nsSVGIntegrationUtils::PaintFramesWithEf
 
   /* Paint the child */
   if (effectProperties.HasValidFilter()) {
     RegularFramePaintCallback callback(aBuilder, aLayerManager,
                                        offsetToUserSpace);
 
     nsRegion dirtyRegion = aDirtyRect - offsetToBoundingBox;
     gfxMatrix tm = nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(aFrame);
-    nsFilterInstance::PaintFilteredFrame(aFrame, aContext, tm, &callback, &dirtyRegion);
+    nsFilterInstance::PaintFilteredFrame(aFrame, *target, tm, &callback, &dirtyRegion);
   } else {
-    aContext.SetMatrix(matrixAutoSaveRestore.Matrix());
+    target->SetMatrix(matrixAutoSaveRestore.Matrix());
+    BasicLayerManager* basic = static_cast<BasicLayerManager*>(aLayerManager);
+    RefPtr<gfxContext> oldCtx = basic->GetTarget();
+    basic->SetTarget(target);
     aLayerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
-    aContext.SetMatrix(aContext.CurrentMatrix().Translate(devPixelOffsetToUserSpace));
+    basic->SetTarget(oldCtx);
   }
 
   if (clipPathFrame && isTrivialClip) {
     aContext.Restore();
   }
 
   /* No more effects, we're done. */
   if (!complexEffects) {
     return;
   }
 
-  aContext.PopGroupToSource();
-
-  Matrix maskTransform;
-  RefPtr<SourceSurface> maskSurface =
-    maskFrame ? maskFrame->GetMaskForMaskedFrame(&aContext,
-                                                 aFrame, cssPxToDevPxMatrix,
-                                                 opacity, &maskTransform)
-              : nullptr;
-
-  if (clipPathFrame && !isTrivialClip) {
-    aContext.PushGroup(gfxContentType::COLOR_ALPHA);
-
-    nsresult rv = clipPathFrame->ApplyClipOrPaintClipMask(aContext, aFrame, cssPxToDevPxMatrix);
-    Matrix clippedMaskTransform;
-    RefPtr<SourceSurface> clipMaskSurface = aContext.PopGroupToSurface(&clippedMaskTransform);
-
-    if (NS_SUCCEEDED(rv) && clipMaskSurface) {
-      // Still more set after clipping, so clip to another surface
-      if (maskSurface || opacity != 1.0f) {
-        aContext.PushGroup(gfxContentType::COLOR_ALPHA);
-        aContext.Mask(clipMaskSurface, clippedMaskTransform);
-        aContext.PopGroupToSource();
-      } else {
-        aContext.Mask(clipMaskSurface, clippedMaskTransform);
-      }
-    }
+  if (opacity != 1.0f || maskFrame || (clipPathFrame && !isTrivialClip)) {
+    target->PopGroupAndBlend();
   }
 
-  if (maskSurface) {
-    aContext.Mask(maskSurface, maskTransform);
-  } else if (opacity != 1.0f ||
-             aFrame->StyleDisplay()->mMixBlendMode != NS_STYLE_BLEND_NORMAL) {
-    aContext.Paint(opacity);
+  if (a