Merge inbound to mozilla-central. a=merge
authorTiberius Oros <toros@mozilla.com>
Fri, 09 Feb 2018 12:02:51 +0200
changeset 403090 d49553765a743ebbd4f08e92a93c9d811ee064c2
parent 403089 dfd0afe71bb5c1430c3abca4ca31ac76d65cc1f1 (current diff)
parent 403050 5bc50083d6fd713ccce6fdcb78fca9b7a1f3c625 (diff)
child 403091 23171f69c8793d6e460aa0cd52019118f6bda926
child 403140 f53d15feeb781c5fd26ca62e1a9b9ec9dd287bca
push id99714
push usertoros@mozilla.com
push dateFri, 09 Feb 2018 10:15:09 +0000
treeherdermozilla-inbound@23171f69c879 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
d49553765a74 / 60.0a1 / 20180209102946 / files
nightly linux64
d49553765a74 / 60.0a1 / 20180209102946 / files
nightly mac
d49553765a74 / 60.0a1 / 20180209102946 / files
nightly win32
d49553765a74 / 60.0a1 / 20180209102946 / files
nightly win64
d49553765a74 / 60.0a1 / 20180209102946 / 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 inbound to mozilla-central. a=merge
--- a/dom/base/contentAreaDropListener.js
+++ b/dom/base/contentAreaDropListener.js
@@ -109,55 +109,74 @@ ContentAreaDropListener.prototype =
       uri = ioService.newURI(uriString);
     } catch (ex) { }
     if (!uri)
       return uriString;
 
     // uriString is a valid URI, so do the security check.
     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
                    getService(Ci.nsIScriptSecurityManager);
-    let sourceNode = dataTransfer.mozSourceNode;
     let flags = secMan.STANDARD;
     if (disallowInherit)
       flags |= secMan.DISALLOW_INHERIT_PRINCIPAL;
 
-    let principal;
-    if (sourceNode) {
-      principal = this._getTriggeringPrincipalFromSourceNode(sourceNode);
-    } else {
-      // Use file:/// as the default uri so that drops of file URIs are always
-      // allowed.
-      principal = secMan.createCodebasePrincipal(ioService.newURI("file:///"), {});
-    }
+    let principal = this._getTriggeringPrincipalFromDataTransfer(dataTransfer, false);
     secMan.checkLoadURIStrWithPrincipal(principal, uriString, flags);
 
     return uriString;
   },
 
-  _getTriggeringPrincipalFromSourceNode: function(aSourceNode)
+  _getTriggeringPrincipalFromDataTransfer: function(aDataTransfer,
+                                                    fallbackToSystemPrincipal)
   {
-    if (aSourceNode.localName == "browser" &&
-        aSourceNode.namespaceURI == "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul") {
-      return aSourceNode.contentPrincipal;
+    let sourceNode = aDataTransfer.mozSourceNode;
+    if (sourceNode &&
+        (sourceNode.localName !== "browser" ||
+         sourceNode.namespaceURI !== "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul")) {
+      // Use sourceNode's principal only if the sourceNode is not browser.
+      //
+      // If sourceNode is browser, the actual triggering principal may be
+      // differ than sourceNode's principal, since sourceNode's principal is
+      // top level document's one and the drag may be triggered from a frame
+      // with different principal.
+      if (sourceNode.nodePrincipal) {
+        return sourceNode.nodePrincipal;
+      }
     }
-    return aSourceNode.nodePrincipal;
+
+    // First, fallback to mozTriggeringPrincipalURISpec that is set when the
+    // drop comes from another content process.
+    let principalURISpec = aDataTransfer.mozTriggeringPrincipalURISpec;
+    if (!principalURISpec) {
+      // Fallback to either system principal or file principal, supposing
+      // the drop comes from outside of the browser, so that drops of file
+      // URIs are always allowed.
+      //
+      // TODO: Investigate and describe the difference between them,
+      //       or use only one principal. (Bug 1367038)
+      if (fallbackToSystemPrincipal) {
+        let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+            getService(Ci.nsIScriptSecurityManager);
+        return secMan.getSystemPrincipal();
+      } else {
+        principalURISpec = "file:///";
+      }
+    }
+    let ioService = Cc["@mozilla.org/network/io-service;1"]
+        .getService(Components.interfaces.nsIIOService);
+    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
+        getService(Ci.nsIScriptSecurityManager);
+    return secMan.createCodebasePrincipal(ioService.newURI(principalURISpec), {});
   },
 
   getTriggeringPrincipal: function(aEvent)
   {
     let dataTransfer = aEvent.dataTransfer;
-    let sourceNode = dataTransfer.mozSourceNode;
-    if (sourceNode) {
-      return this._getTriggeringPrincipalFromSourceNode(sourceNode, false);
-    }
-    // Bug 1367038: mozSourceNode is null if the drag event originated
-    // in an external application - needs better fallback!
-    let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].
-                   getService(Ci.nsIScriptSecurityManager);
-    return secMan.getSystemPrincipal();
+    return this._getTriggeringPrincipalFromDataTransfer(dataTransfer, true);
+
   },
 
   canDropLink: function(aEvent, aAllowSameDocument)
   {
     if (this._eventTargetIsDisabled(aEvent))
       return false;
 
     let dataTransfer = aEvent.dataTransfer;
--- a/dom/base/nsContentAreaDragDrop.cpp
+++ b/dom/base/nsContentAreaDragDrop.cpp
@@ -67,17 +67,18 @@ class MOZ_STACK_CLASS DragDataProducer
 public:
   DragDataProducer(nsPIDOMWindowOuter* aWindow,
                    nsIContent* aTarget,
                    nsIContent* aSelectionTargetNode,
                    bool aIsAltKeyPressed);
   nsresult Produce(DataTransfer* aDataTransfer,
                    bool* aCanDrag,
                    nsISelection** aSelection,
-                   nsIContent** aDragNode);
+                   nsIContent** aDragNode,
+                   nsACString& aPrincipalURISpec);
 
 private:
   void AddString(DataTransfer* aDataTransfer,
                  const nsAString& aFlavor,
                  const nsAString& aData,
                  nsIPrincipal* aPrincipal);
   nsresult AddStringsToDataTransfer(nsIContent* aDragNode,
                                     DataTransfer* aDataTransfer);
@@ -114,25 +115,27 @@ private:
 nsresult
 nsContentAreaDragDrop::GetDragData(nsPIDOMWindowOuter* aWindow,
                                    nsIContent* aTarget,
                                    nsIContent* aSelectionTargetNode,
                                    bool aIsAltKeyPressed,
                                    DataTransfer* aDataTransfer,
                                    bool* aCanDrag,
                                    nsISelection** aSelection,
-                                   nsIContent** aDragNode)
+                                   nsIContent** aDragNode,
+                                   nsACString& aPrincipalURISpec)
 {
   NS_ENSURE_TRUE(aSelectionTargetNode, NS_ERROR_INVALID_ARG);
 
   *aCanDrag = true;
 
   DragDataProducer
     provider(aWindow, aTarget, aSelectionTargetNode, aIsAltKeyPressed);
-  return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode);
+  return provider.Produce(aDataTransfer, aCanDrag, aSelection, aDragNode,
+                          aPrincipalURISpec);
 }
 
 
 NS_IMPL_ISUPPORTS(nsContentAreaDragDropDataProvider, nsIFlavorDataProvider)
 
 // SaveURIToFile
 // used on platforms where it's possible to drag items (e.g. images)
 // into the file system
@@ -360,17 +363,18 @@ DragDataProducer::GetNodeString(nsIConte
     range->ToString(outNodeString);
   }
 }
 
 nsresult
 DragDataProducer::Produce(DataTransfer* aDataTransfer,
                           bool* aCanDrag,
                           nsISelection** aSelection,
-                          nsIContent** aDragNode)
+                          nsIContent** aDragNode,
+                          nsACString& aPrincipalURISpec)
 {
   NS_PRECONDITION(aCanDrag && aSelection && aDataTransfer && aDragNode,
                   "null pointer passed to Produce");
   NS_ASSERTION(mWindow, "window not set");
   NS_ASSERTION(mSelectionTargetNode, "selection target node should have been set");
 
   *aDragNode = nullptr;
 
@@ -425,17 +429,17 @@ DragDataProducer::Produce(DataTransfer* 
     nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(mTarget);
     if (flo) {
       RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
       if (fl) {
         TabParent* tp = static_cast<TabParent*>(fl->GetRemoteBrowser());
         if (tp) {
           // We have a TabParent, so it may have data for dnd in case the child
           // process started a dnd session.
-          tp->AddInitialDnDDataTo(aDataTransfer);
+          tp->AddInitialDnDDataTo(aDataTransfer, aPrincipalURISpec);
         }
       }
     }
     return NS_OK;
   }
 
   if (isChromeShell && textControl) {
     // Only use the selection if the target node is in the selection.
--- a/dom/base/nsContentAreaDragDrop.h
+++ b/dom/base/nsContentAreaDragDrop.h
@@ -45,25 +45,29 @@ public:
    *                    not drag the link.
    * aDataTransfer - the dataTransfer for the drag event.
    * aCanDrag - [out] set to true if the drag may proceed, false to stop the
    *            drag entirely
    * aSelection - [out] set to the selection being dragged, or null if no
    *                    selection is being dragged.
    * aDragNode - [out] the link, image or area being dragged, or null if the
    *             drag occurred on another element.
+   * aPrincipalURISpec - [out] set to the URI of the triggering principal of
+   *                           the drag, or empty string if it's from
+   *                           browser chrome or OS
    */
   static nsresult GetDragData(nsPIDOMWindowOuter* aWindow,
                               nsIContent* aTarget,
                               nsIContent* aSelectionTargetNode,
                               bool aIsAltKeyPressed,
                               mozilla::dom::DataTransfer* aDataTransfer,
                               bool* aCanDrag,
                               nsISelection** aSelection,
-                              nsIContent** aDragNode);
+                              nsIContent** aDragNode,
+                              nsACString& aPrincipalURISpec);
 };
 
 // this is used to save images to disk lazily when the image data is asked for
 // during the drop instead of when it is added to the drag data transfer. This
 // ensures that the image data is only created when an image drop is allowed.
 class nsContentAreaDragDropDataProvider : public nsIFlavorDataProvider
 {
   virtual ~nsContentAreaDragDropDataProvider() {}
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -302,16 +302,30 @@ DataTransfer::SetDropEffectInt(uint32_t 
 
 NS_IMETHODIMP
 DataTransfer::GetEffectAllowedInt(uint32_t* aEffectAllowed)
 {
   *aEffectAllowed = mEffectAllowed;
   return  NS_OK;
 }
 
+void
+DataTransfer::GetMozTriggeringPrincipalURISpec(nsAString& aPrincipalURISpec)
+{
+  nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
+  if (!dragSession) {
+    aPrincipalURISpec.Truncate(0);
+    return;
+  }
+
+  nsCString principalURISpec;
+  dragSession->GetTriggeringPrincipalURISpec(principalURISpec);
+  CopyUTF8toUTF16(principalURISpec, aPrincipalURISpec);
+}
+
 NS_IMETHODIMP
 DataTransfer::SetEffectAllowedInt(uint32_t aEffectAllowed)
 {
   mEffectAllowed = aEffectAllowed;
   return  NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -206,16 +206,18 @@ public:
 
   bool MozUserCancelled() const
   {
     return mUserCancelled;
   }
 
   already_AddRefed<nsINode> GetMozSourceNode();
 
+  void GetMozTriggeringPrincipalURISpec(nsAString& aPrincipalURISpec);
+
   mozilla::dom::Element* GetDragTarget() const
   {
     return mDragTarget;
   }
 
   nsresult GetDataAtNoSecurityCheck(const nsAString& aFormat, uint32_t aIndex,
                                     nsIVariant** aData);
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1330,27 +1330,30 @@ EventStateManager::DispatchCrossProcessE
     RefPtr<TabParent> tabParent = remote;
     if (tabParent->Manager()->IsContentParent()) {
       tabParent->Manager()->AsContentParent()->MaybeInvokeDragSession(tabParent);
     }
 
     nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
     uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
     uint32_t action = nsIDragService::DRAGDROP_ACTION_NONE;
+    nsCString principalURISpec;
     if (dragSession) {
       dragSession->DragEventDispatchedToChildProcess();
       dragSession->GetDragAction(&action);
+      dragSession->GetTriggeringPrincipalURISpec(principalURISpec);
       nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
       dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
       if (initialDataTransfer) {
         initialDataTransfer->GetDropEffectInt(&dropEffect);
       }
     }
 
-    tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), action, dropEffect);
+    tabParent->SendRealDragEvent(*aEvent->AsDragEvent(), action, dropEffect,
+                                 principalURISpec);
     return;
   }
   case ePluginEventClass: {
     *aStatus = nsEventStatus_eConsumeNoDefault;
     remote->SendPluginEvent(*aEvent->AsPluginEvent());
     return;
   }
   default: {
@@ -1858,21 +1861,23 @@ EventStateManager::GenerateDragGesture(n
       auto protectDataTransfer = MakeScopeExit([&] {
         if (dataTransfer) {
           dataTransfer->Disconnect();
         }
       });
 
       nsCOMPtr<nsISelection> selection;
       nsCOMPtr<nsIContent> eventContent, targetContent;
+      nsCString principalURISpec;
       mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(eventContent));
       if (eventContent)
         DetermineDragTargetAndDefaultData(window, eventContent, dataTransfer,
                                           getter_AddRefs(selection),
-                                          getter_AddRefs(targetContent));
+                                          getter_AddRefs(targetContent),
+                                          principalURISpec);
 
       // Stop tracking the drag gesture now. This should stop us from
       // reentering GenerateDragGesture inside DOM event processing.
       StopTrackingDragGesture();
 
       if (!targetContent)
         return;
 
@@ -1924,17 +1929,18 @@ EventStateManager::GenerateDragGesture(n
       if (observerService) {
         observerService->NotifyObservers(dataTransfer,
                                          "on-datatransfer-available",
                                          nullptr);
       }
 
       if (status != nsEventStatus_eConsumeNoDefault) {
         bool dragStarted = DoDefaultDragStart(aPresContext, event, dataTransfer,
-                                              targetContent, selection);
+                                              targetContent, selection,
+                                              principalURISpec);
         if (dragStarted) {
           sActiveESM = nullptr;
           MaybeFirePointerCancel(aEvent);
           aEvent->StopPropagation();
         }
       }
 
       // Reset mCurretTargetContent to what it was
@@ -1947,32 +1953,34 @@ EventStateManager::GenerateDragGesture(n
   }
 } // GenerateDragGesture
 
 void
 EventStateManager::DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow,
                                                      nsIContent* aSelectionTarget,
                                                      DataTransfer* aDataTransfer,
                                                      nsISelection** aSelection,
-                                                     nsIContent** aTargetNode)
+                                                     nsIContent** aTargetNode,
+                                                     nsACString& aPrincipalURISpec)
 {
   *aTargetNode = nullptr;
 
   // GetDragData determines if a selection, link or image in the content
   // should be dragged, and places the data associated with the drag in the
   // data transfer.
   // mGestureDownContent is the node where the mousedown event for the drag
   // occurred, and aSelectionTarget is the node to use when a selection is used
   bool canDrag;
   nsCOMPtr<nsIContent> dragDataNode;
   bool wasAlt = (mGestureModifiers & MODIFIER_ALT) != 0;
   nsresult rv = nsContentAreaDragDrop::GetDragData(aWindow, mGestureDownContent,
                                                    aSelectionTarget, wasAlt,
                                                    aDataTransfer, &canDrag, aSelection,
-                                                   getter_AddRefs(dragDataNode));
+                                                   getter_AddRefs(dragDataNode),
+                                                   aPrincipalURISpec);
   if (NS_FAILED(rv) || !canDrag)
     return;
 
   // if GetDragData returned a node, use that as the node being dragged.
   // Otherwise, if a selection is being dragged, use the node within the
   // selection that was dragged. Otherwise, just use the mousedown target.
   nsIContent* dragContent = mGestureDownContent;
   if (dragDataNode)
@@ -2026,17 +2034,18 @@ EventStateManager::DetermineDragTargetAn
   }
 }
 
 bool
 EventStateManager::DoDefaultDragStart(nsPresContext* aPresContext,
                                       WidgetDragEvent* aDragEvent,
                                       DataTransfer* aDataTransfer,
                                       nsIContent* aDragTarget,
-                                      nsISelection* aSelection)
+                                      nsISelection* aSelection,
+                                      const nsACString& aPrincipalURISpec)
 {
   nsCOMPtr<nsIDragService> dragService =
     do_GetService("@mozilla.org/widget/dragservice;1");
   if (!dragService)
     return false;
 
   // Default handling for the dragstart event.
   //
@@ -2105,17 +2114,19 @@ EventStateManager::DoDefaultDragStart(ns
   RefPtr<DragEvent> event =
     NS_NewDOMDragEvent(dragTarget, aPresContext, aDragEvent);
 
   // Use InvokeDragSessionWithSelection if a selection is being dragged,
   // such that the image can be generated from the selected text. However,
   // use InvokeDragSessionWithImage if a custom image was set or something
   // other than a selection is being dragged.
   if (!dragImage && aSelection) {
-    dragService->InvokeDragSessionWithSelection(aSelection, transArray,
+    dragService->InvokeDragSessionWithSelection(aSelection,
+                                                aPrincipalURISpec,
+                                                transArray,
                                                 action, event, dataTransfer);
   }
   else {
     // if dragging within a XUL tree and no custom drag image was
     // set, the region argument to InvokeDragSessionWithImage needs
     // to be set to the area encompassing the selected rows of the
     // tree to ensure that the drag feedback gets clipped to those
     // rows. For other content, region should be null.
@@ -2128,17 +2139,18 @@ EventStateManager::DoDefaultDragStart(ns
           do_QueryFrame(dragTarget->GetPrimaryFrame());
         if (treeBody) {
           treeBody->GetSelectionRegion(getter_AddRefs(region));
         }
       }
     }
 #endif
 
-    dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(), transArray,
+    dragService->InvokeDragSessionWithImage(dragTarget->AsDOMNode(),
+                                            aPrincipalURISpec, transArray,
                                             region, action,
                                             dragImage ? dragImage->AsDOMNode() :
                                                         nullptr,
                                             imageX, imageY, event,
                                             dataTransfer);
   }
 
   return true;
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -920,38 +920,45 @@ protected:
    * Determine which node the drag should be targeted at.
    * This is either the node clicked when there is a selection, or, for HTML,
    * the element with a draggable property set to true.
    *
    * aSelectionTarget - target to check for selection
    * aDataTransfer - data transfer object that will contain the data to drag
    * aSelection - [out] set to the selection to be dragged
    * aTargetNode - [out] the draggable node, or null if there isn't one
+   * aPrincipalURISpec - [out] set to the URI of the triggering principal of
+   *                           the drag, or an empty string if it's from
+   *                           browser chrome or OS
    */
   void DetermineDragTargetAndDefaultData(nsPIDOMWindowOuter* aWindow,
                                          nsIContent* aSelectionTarget,
                                          dom::DataTransfer* aDataTransfer,
                                          nsISelection** aSelection,
-                                         nsIContent** aTargetNode);
+                                         nsIContent** aTargetNode,
+                                         nsACString& aPrincipalURISpec);
 
   /*
    * Perform the default handling for the dragstart event and set up a
    * drag for aDataTransfer if it contains any data. Returns true if a drag has
    * started.
    *
    * aDragEvent - the dragstart event
    * aDataTransfer - the data transfer that holds the data to be dragged
    * aDragTarget - the target of the drag
    * aSelection - the selection to be dragged
+   * aPrincipalURISpec - the URI of the triggering principal of the drag,
+   *                     or an empty string if it's from browser chrome or OS
    */
   bool DoDefaultDragStart(nsPresContext* aPresContext,
                           WidgetDragEvent* aDragEvent,
                           dom::DataTransfer* aDataTransfer,
                           nsIContent* aDragTarget,
-                          nsISelection* aSelection);
+                          nsISelection* aSelection,
+                          const nsACString& aPrincipalURISpec);
 
   bool IsTrackingDragGesture ( ) const { return mGestureDownContent != nullptr; }
   /**
    * Set the fields of aEvent to reflect the mouse position and modifier keys
    * that were set when the user first pressed the mouse button (stored by
    * BeginTrackingDragGesture). aEvent->mWidget must be
    * mCurrentTarget->GetNearestWidget().
    */
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -121,16 +121,17 @@ const char* mozilla::dom::ContentPrefs::
   "javascript.options.ion.offthread_compilation",
   "javascript.options.ion.threshold",
   "javascript.options.ion.unsafe_eager_compilation",
   "javascript.options.jit.full_debug_checks",
   "javascript.options.native_regexp",
   "javascript.options.parallel_parsing",
   "javascript.options.shared_memory",
   "javascript.options.spectre.index_masking",
+  "javascript.options.spectre.string_mitigations",
   "javascript.options.streams",
   "javascript.options.strict",
   "javascript.options.strict.debug",
   "javascript.options.throw_on_asmjs_validation_failure",
   "javascript.options.throw_on_debuggee_would_run",
   "javascript.options.wasm",
   "javascript.options.wasm_baselinejit",
   "javascript.options.wasm_ionjit",
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -561,17 +561,18 @@ parent:
 
     nested(inside_sync) sync DispatchWheelEvent(WidgetWheelEvent event);
     nested(inside_sync) sync DispatchMouseEvent(WidgetMouseEvent event);
     nested(inside_sync) sync DispatchKeyboardEvent(WidgetKeyboardEvent event);
 
     async InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action,
                             OptionalShmem visualData,
                             uint32_t stride, uint8_t format,
-                            LayoutDeviceIntRect dragRect);
+                            LayoutDeviceIntRect dragRect,
+                            nsCString principalURISpec);
 
     // After a compositor reset, it is necessary to reconnect each layers ID to
     // the compositor of the widget that will render those layers. Note that
     // this is sync so we can ensure that messages to the window compositor
     // arrive before the TabChild attempts to use its cross-process compositor
     // bridge.
     sync EnsureLayersConnected() returns (CompositorOptions compositorOptions);
 
@@ -700,17 +701,17 @@ child:
                                            uint64_t aInputBlockId,
                                            nsEventStatus aApzResponse);
 
     /*
      * We disable the input event queue when there is an active dnd session. We
      * don't need support RealDragEvent with input priority.
      */
     async RealDragEvent(WidgetDragEvent aEvent, uint32_t aDragAction,
-                        uint32_t aDropEffect);
+                        uint32_t aDropEffect, nsCString aPrincipalURISpec);
 
     async PluginEvent(WidgetPluginEvent aEvent);
 
     /**
      * @see nsIDOMWindowUtils sendKeyEvent.
      */
     async KeyEvent(nsString aType,
                    int32_t aKeyCode,
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1943,24 +1943,26 @@ TabChild::RecvNormalPriorityRealTouchMov
   const nsEventStatus& aApzResponse)
 {
   return RecvRealTouchMoveEvent(aEvent, aGuid, aInputBlockId, aApzResponse);
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvRealDragEvent(const WidgetDragEvent& aEvent,
                             const uint32_t& aDragAction,
-                            const uint32_t& aDropEffect)
+                            const uint32_t& aDropEffect,
+                            const nsCString& aPrincipalURISpec)
 {
   WidgetDragEvent localEvent(aEvent);
   localEvent.mWidget = mPuppetWidget;
 
   nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
   if (dragSession) {
     dragSession->SetDragAction(aDragAction);
+    dragSession->SetTriggeringPrincipalURISpec(aPrincipalURISpec);
     nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
     dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
     if (initialDataTransfer) {
       initialDataTransfer->SetDropEffectInt(aDropEffect);
     }
   }
 
   if (aEvent.mMessage == eDrop) {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -386,17 +386,18 @@ public:
                                                            const uint64_t& aInputBlockId) override;
   virtual mozilla::ipc::IPCResult
   RecvNormalPriorityRealMouseButtonEvent(const mozilla::WidgetMouseEvent& aEvent,
                                          const ScrollableLayerGuid& aGuid,
                                          const uint64_t& aInputBlockId) override;
 
   virtual mozilla::ipc::IPCResult RecvRealDragEvent(const WidgetDragEvent& aEvent,
                                                     const uint32_t& aDragAction,
-                                                    const uint32_t& aDropEffect) override;
+                                                    const uint32_t& aDropEffect,
+                                                    const nsCString& aPrincipalURISpec) override;
 
   virtual mozilla::ipc::IPCResult
   RecvRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult
   RecvNormalPriorityRealKeyEvent(const mozilla::WidgetKeyboardEvent& aEvent) override;
 
   virtual mozilla::ipc::IPCResult RecvMouseWheelEvent(const mozilla::WidgetWheelEvent& aEvent,
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1276,30 +1276,32 @@ TabParent::QueryDropLinksForVerification
     mVerifyDropLinks.Clear();
     return false;
   }
   return true;
 }
 
 void
 TabParent::SendRealDragEvent(WidgetDragEvent& aEvent, uint32_t aDragAction,
-                             uint32_t aDropEffect)
+                             uint32_t aDropEffect,
+                             const nsCString& aPrincipalURISpec)
 {
   if (mIsDestroyed || !mIsReadyToHandleInputEvents) {
     return;
   }
   MOZ_ASSERT(!Manager()->AsContentParent()->IsInputPriorityEventEnabled());
   aEvent.mRefPoint += GetChildProcessOffset();
   if (aEvent.mMessage == eDrop) {
     if (!QueryDropLinksForVerification()) {
       return;
     }
   }
   DebugOnly<bool> ret =
-    PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect);
+    PBrowserParent::SendRealDragEvent(aEvent, aDragAction, aDropEffect,
+                                      aPrincipalURISpec);
   NS_WARNING_ASSERTION(ret, "PBrowserParent::SendRealDragEvent() failed");
   MOZ_ASSERT(!ret || aEvent.HasBeenPostedToRemoteProcess());
 }
 
 LayoutDevicePoint
 TabParent::AdjustTapToChildWidget(const LayoutDevicePoint& aPoint)
 {
   return aPoint + LayoutDevicePoint(GetChildProcessOffset());
@@ -3340,17 +3342,18 @@ TabParent::RecvAsyncAuthPrompt(const nsC
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 TabParent::RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                                  const uint32_t& aAction,
                                  const OptionalShmem& aVisualDnDData,
                                  const uint32_t& aStride, const uint8_t& aFormat,
-                                 const LayoutDeviceIntRect& aDragRect)
+                                 const LayoutDeviceIntRect& aDragRect,
+                                 const nsCString& aPrincipalURISpec)
 {
   mInitialDataTransferItems.Clear();
   nsIPresShell* shell = mFrameElement->OwnerDoc()->GetShell();
   if (!shell) {
     if (Manager()->IsContentParent()) {
       Unused << Manager()->AsContentParent()->SendEndDragSession(true, true,
                                                                  LayoutDeviceIntPoint(),
                                                                  0);
@@ -3382,29 +3385,44 @@ TabParent::RecvInvokeDragSession(nsTArra
         gfx::CreateDataSourceSurfaceFromData(gfx::IntSize(aDragRect.width, aDragRect.height),
                                              static_cast<gfx::SurfaceFormat>(aFormat),
                                              aVisualDnDData.get_Shmem().get<uint8_t>(),
                                              aStride);
   }
 
   mDragValid = true;
   mDragRect = aDragRect;
+  mDragPrincipalURISpec = aPrincipalURISpec;
 
   esm->BeginTrackingRemoteDragGesture(mFrameElement);
 
   if (aVisualDnDData.type() == OptionalShmem::TShmem) {
     Unused << DeallocShmem(aVisualDnDData);
   }
 
   return IPC_OK();
 }
 
 void
-TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer)
+TabParent::AddInitialDnDDataTo(DataTransfer* aDataTransfer,
+                               nsACString& aPrincipalURISpec)
 {
+  aPrincipalURISpec.Assign(mDragPrincipalURISpec);
+
+  nsCOMPtr<nsIPrincipal> principal;
+  if (!mDragPrincipalURISpec.IsEmpty()) {
+    // If principal is given, try using it first.
+    principal = BasePrincipal::CreateCodebasePrincipal(mDragPrincipalURISpec);
+  }
+  if (!principal) {
+    // Fallback to system principal, to handle like the data is from browser
+    // chrome or OS.
+    principal = nsContentUtils::GetSystemPrincipal();
+  }
+
   for (uint32_t i = 0; i < mInitialDataTransferItems.Length(); ++i) {
     nsTArray<IPCDataTransferItem>& itemArray = mInitialDataTransferItems[i];
     for (auto& item : itemArray) {
       RefPtr<nsVariantCC> variant = new nsVariantCC();
       // Special case kFilePromiseMime so that we get the right
       // nsIFlavorDataProvider for it.
       if (item.flavor().EqualsLiteral(kFilePromiseMime)) {
         RefPtr<nsISupports> flavorDataProvider =
@@ -3430,29 +3448,27 @@ TabParent::AddInitialDnDDataTo(DataTrans
         } else {
           Shmem data = item.data().get_Shmem();
           variant->SetAsACString(nsDependentCString(data.get<char>(), data.Size<char>()));
         }
 
         mozilla::Unused << DeallocShmem(item.data().get_Shmem());
       }
 
-      // Using system principal here, since once the data is on parent process
-      // side, it can be handled as being from browser chrome or OS.
-
       // We set aHidden to false, as we don't need to worry about hiding data
       // from content in the parent process where there is no content.
       // XXX: Nested Content Processes may change this
       aDataTransfer->SetDataWithPrincipalFromOtherProcess(NS_ConvertUTF8toUTF16(item.flavor()),
                                                           variant, i,
-                                                          nsContentUtils::GetSystemPrincipal(),
+                                                          principal,
                                                           /* aHidden = */ false);
     }
   }
   mInitialDataTransferItems.Clear();
+  mDragPrincipalURISpec.Truncate(0);
 }
 
 bool
 TabParent::TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                  LayoutDeviceIntRect* aDragRect)
 {
   if (!mDragValid)
     return false;
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -437,17 +437,18 @@ public:
    * The following Send*Event() marks aEvent as posted to remote process if
    * it succeeded.  So, you can check the result with
    * aEvent.HasBeenPostedToRemoteProcess().
    */
   void SendRealMouseEvent(WidgetMouseEvent& aEvent);
 
   void SendRealDragEvent(WidgetDragEvent& aEvent,
                          uint32_t aDragAction,
-                         uint32_t aDropEffect);
+                         uint32_t aDropEffect,
+                         const nsCString& aPrincipalURISpec);
 
   void SendMouseWheelEvent(WidgetWheelEvent& aEvent);
 
   void SendRealKeyEvent(WidgetKeyboardEvent& aEvent);
 
   void SendRealTouchEvent(WidgetTouchEvent& aEvent);
 
   void SendPluginEvent(WidgetPluginEvent& aEvent);
@@ -580,19 +581,21 @@ public:
 
   void LayerTreeUpdate(uint64_t aEpoch, bool aActive);
 
   virtual mozilla::ipc::IPCResult
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                         const uint32_t& aAction,
                         const OptionalShmem& aVisualDnDData,
                         const uint32_t& aStride, const uint8_t& aFormat,
-                        const LayoutDeviceIntRect& aDragRect) override;
+                        const LayoutDeviceIntRect& aDragRect,
+                        const nsCString& aPrincipalURISpec) override;
 
-  void AddInitialDnDDataTo(DataTransfer* aDataTransfer);
+  void AddInitialDnDDataTo(DataTransfer* aDataTransfer,
+                           nsACString& aPrincipalURISpec);
 
   bool TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                              LayoutDeviceIntRect* aDragRect);
 
   layout::RenderFrameParent* GetRenderFrame();
 
   bool SetRenderFrame(PRenderFrameParent* aRFParent);
   bool GetRenderFrameInfo(TextureFactoryIdentifier* aTextureFactoryIdentifier,
@@ -689,16 +692,17 @@ private:
 
   uint32_t mChromeFlags;
 
   nsTArray<nsTArray<IPCDataTransferItem>> mInitialDataTransferItems;
 
   RefPtr<gfx::DataSourceSurface> mDnDVisualization;
   bool mDragValid;
   LayoutDeviceIntRect mDragRect;
+  nsCString mDragPrincipalURISpec;
 
   // When true, the TabParent is initialized without child side's request.
   // When false, the TabParent is initialized by window.open() from child side.
   bool mInitedByParent;
 
   nsCOMPtr<nsILoadContext> mLoadContext;
 
   // We keep a strong reference to the frameloader after we've sent the
--- a/dom/webidl/DataTransfer.webidl
+++ b/dom/webidl/DataTransfer.webidl
@@ -152,16 +152,24 @@ partial interface DataTransfer {
   /**
    * The node that the mouse was pressed over to begin the drag. For external
    * drags, or if the caller cannot access this node, this will be null.
    */
   [UseCounter]
   readonly attribute Node? mozSourceNode;
 
   /**
+   * The URI spec of the triggering principal.  This may be different than
+   * sourceNode's principal when sourceNode is xul:browser and the drag is
+   * triggered in a browsing context inside it.
+   */
+  [ChromeOnly]
+  readonly attribute DOMString mozTriggeringPrincipalURISpec;
+
+  /**
    * Copy the given DataTransfer for the given event. Used by testing code for
    * creating emulated Drag and Drop events in the UI.
    *
    * NOTE: Don't expose a DataTransfer produced with this method to the web or
    * use this for non-testing purposes. It can easily be used to get the
    * DataTransfer into an invalid state, and is an unstable implementation
    * detail of EventUtils.synthesizeDrag.
    */
--- a/gfx/2d/ScaledFontFontconfig.cpp
+++ b/gfx/2d/ScaledFontFontconfig.cpp
@@ -308,16 +308,19 @@ ScaledFontFontconfig::GetWRFontInstanceO
 
     // Match cairo-ft's handling of embeddedbitmap:
     // If AA is explicitly disabled, leave bitmaps enabled.
     // Otherwise, disable embedded bitmaps unless explicitly enabled.
     FcBool bitmap;
     if (FcPatternGetBool(mPattern, FC_EMBEDDED_BITMAP, 0, &bitmap) == FcResultMatch && bitmap) {
       options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
     }
+
+    // FIXME: Cairo-FT metrics are not compatible with subpixel positioning.
+    options.subpx_dir = wr::SubpixelDirection::None;
   } else {
     options.render_mode = wr::FontRenderMode::Mono;
     options.subpx_dir = wr::SubpixelDirection::None;
     platformOptions.hinting = wr::FontHinting::Mono;
     options.flags |= wr::FontInstanceFlags::EMBEDDED_BITMAPS;
   }
 
   FcBool hinting;
--- a/gfx/qcms/transform.c
+++ b/gfx/qcms/transform.c
@@ -990,16 +990,20 @@ void qcms_transform_release(qcms_transfo
 		free(t->input_gamma_table_b);
 
 	free(t->input_gamma_table_gray);
 
 	free(t->output_gamma_lut_r);
 	free(t->output_gamma_lut_g);
 	free(t->output_gamma_lut_b);
 
+	/* r_clut points to beginning of buffer allocated in qcms_transform_precacheLUT_float */
+	if (t->r_clut)
+		free(t->r_clut);
+
 	transform_free(t);
 }
 
 #ifdef X86
 // Determine if we can build with SSE2 (this was partly copied from jmorecfg.h in
 // mozilla/jpeg)
  // -------------------------------------------------------------------------
 #if defined(_M_IX86) && defined(_MSC_VER)
@@ -1186,16 +1190,17 @@ qcms_transform* qcms_transform_precacheL
 			} else {
 				transform->transform_fn = qcms_transform_data_tetra_clut;
 			}
 		}
 	}
 
 
 	//XXX: qcms_modular_transform_data may return either the src or dest buffer. If so it must not be free-ed
+	// It will be stored in r_clut, which will be cleaned up in qcms_transform_release.
 	if (src && lut != src) {
 		free(src);
 	}
 	if (dest && lut != dest) {
 		free(dest);
 	}
 
 	if (lut == NULL) {
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -1333,17 +1333,17 @@ PrepareAndExecuteRegExp(JSContext* cx, M
 
         // Check if |lastIndex > 0 && lastIndex < input->length()|.
         // lastIndex should already have no sign here.
         masm.branchTest32(Assembler::Zero, lastIndex, lastIndex, &done);
         masm.loadStringLength(input, temp2);
         masm.branch32(Assembler::AboveOrEqual, lastIndex, temp2, &done);
 
         // Check if input[lastIndex] is trail surrogate.
-        masm.loadStringChars(input, temp2);
+        masm.loadStringChars(input, temp2, CharEncoding::TwoByte);
         masm.computeEffectiveAddress(BaseIndex(temp2, lastIndex, TimesTwo), temp3);
         masm.load16ZeroExtend(Address(temp3, 0), temp3);
 
         masm.branch32(Assembler::Below, temp3, Imm32(unicode::TrailSurrogateMin), &done);
         masm.branch32(Assembler::Above, temp3, Imm32(unicode::TrailSurrogateMax), &done);
 
         // Check if input[lastIndex-1] is lead surrogate.
         masm.move32(lastIndex, temp3);
@@ -1369,30 +1369,32 @@ PrepareAndExecuteRegExp(JSContext* cx, M
         masm.add32(Imm32(1), temp2);
         masm.store32(temp2, pairCountAddress);
     }
 
     // Load the code pointer for the type of input string we have, and compute
     // the input start/end pointers in the InputOutputData.
     Register codePointer = temp1;
     {
-        masm.loadStringChars(input, temp2);
-        masm.storePtr(temp2, inputStartAddress);
         masm.loadStringLength(input, temp3);
 
         Label isLatin1, done;
         masm.branchLatin1String(input, &isLatin1);
         {
+            masm.loadStringChars(input, temp2, CharEncoding::TwoByte);
+            masm.storePtr(temp2, inputStartAddress);
             masm.lshiftPtr(Imm32(1), temp3);
             masm.loadPtr(Address(temp1, RegExpShared::offsetOfTwoByteJitCode(mode)),
                          codePointer);
-        }
-        masm.jump(&done);
+            masm.jump(&done);
+        }
+        masm.bind(&isLatin1);
         {
-            masm.bind(&isLatin1);
+            masm.loadStringChars(input, temp2, CharEncoding::Latin1);
+            masm.storePtr(temp2, inputStartAddress);
             masm.loadPtr(Address(temp1, RegExpShared::offsetOfLatin1JitCode(mode)),
                          codePointer);
         }
         masm.bind(&done);
 
         masm.addPtr(temp3, temp2);
         masm.storePtr(temp2, inputEndAddress);
     }
@@ -1575,25 +1577,26 @@ CreateDependentString::generate(MacroAss
         masm.push(base);
 
         // Adjust the start index address for the above pushes.
         MOZ_ASSERT(startIndexAddress.base == masm.getStackPointer());
         BaseIndex newStartIndexAddress = startIndexAddress;
         newStartIndexAddress.offset += 2 * sizeof(void*);
 
         // Load chars pointer for the new string.
-        masm.addPtr(ImmWord(JSInlineString::offsetOfInlineStorage()), string);
+        masm.loadInlineStringCharsForStore(string, string);
 
         // Load the source characters pointer.
-        masm.loadStringChars(base, base);
-        masm.load32(newStartIndexAddress, temp2);
+        masm.loadStringChars(base, temp2,
+                             latin1 ? CharEncoding::Latin1 : CharEncoding::TwoByte);
+        masm.load32(newStartIndexAddress, base);
         if (latin1)
             masm.addPtr(temp2, base);
         else
-            masm.computeEffectiveAddress(BaseIndex(base, temp2, TimesTwo), base);
+            masm.computeEffectiveAddress(BaseIndex(temp2, base, TimesTwo), base);
 
         CopyStringChars(masm, string, base, temp1, temp2, latin1 ? 1 : 2, latin1 ? 1 : 2);
 
         // Null-terminate.
         if (latin1)
             masm.store8(Imm32(0), Address(string, 0));
         else
             masm.store16(Imm32(0), Address(string, 0));
@@ -1609,34 +1612,35 @@ CreateDependentString::generate(MacroAss
         // Make a dependent string.
         int32_t flags = (latin1 ? JSString::LATIN1_CHARS_BIT : 0) | JSString::DEPENDENT_FLAGS;
 
         masm.newGCString(string, temp2, &fallbacks_[FallbackKind::NotInlineString]);
         masm.bind(&joins_[FallbackKind::NotInlineString]);
         masm.store32(Imm32(flags), Address(string, JSString::offsetOfFlags()));
         masm.store32(temp1, Address(string, JSString::offsetOfLength()));
 
-        masm.loadPtr(Address(base, JSString::offsetOfNonInlineChars()), temp1);
+        masm.loadNonInlineStringChars(base, temp1,
+                                      latin1 ? CharEncoding::Latin1 : CharEncoding::TwoByte);
         masm.load32(startIndexAddress, temp2);
         if (latin1)
             masm.addPtr(temp2, temp1);
         else
             masm.computeEffectiveAddress(BaseIndex(temp1, temp2, TimesTwo), temp1);
-        masm.storePtr(temp1, Address(string, JSString::offsetOfNonInlineChars()));
-        masm.storePtr(base, Address(string, JSDependentString::offsetOfBase()));
+        masm.storeNonInlineStringChars(temp1, string);
+        masm.storeDependentStringBase(base, string);
 
         // Follow any base pointer if the input is itself a dependent string.
         // Watch for undepended strings, which have a base pointer but don't
         // actually share their characters with it.
         Label noBase;
         masm.load32(Address(base, JSString::offsetOfFlags()), temp1);
         masm.and32(Imm32(JSString::TYPE_FLAGS_MASK), temp1);
         masm.branch32(Assembler::NotEqual, temp1, Imm32(JSString::DEPENDENT_FLAGS), &noBase);
-        masm.loadPtr(Address(base, JSDependentString::offsetOfBase()), temp1);
-        masm.storePtr(temp1, Address(string, JSDependentString::offsetOfBase()));
+        masm.loadDependentStringBase(base, temp1);
+        masm.storeDependentStringBase(temp1, string);
         masm.bind(&noBase);
     }
 
     masm.bind(&done);
 }
 
 static void*
 AllocateString(JSContext* cx)
@@ -2446,18 +2450,16 @@ CodeGenerator::visitOutOfLineRegExpInsta
 
     masm.jump(ool->rejoin());
 }
 
 static void
 FindFirstDollarIndex(MacroAssembler& masm, Register str, Register len, Register chars,
                      Register temp, Register output, bool isLatin1)
 {
-    masm.loadStringChars(str, chars);
-
     masm.move32(Imm32(0), output);
 
     Label start, done;
     masm.bind(&start);
     if (isLatin1)
         masm.load8ZeroExtend(BaseIndex(chars, output, TimesOne), temp);
     else
         masm.load16ZeroExtend(BaseIndex(chars, output, TimesTwo), temp);
@@ -2489,21 +2491,23 @@ CodeGenerator::visitGetFirstDollarIndex(
                                    StoreRegisterTo(output));
 
     masm.branchIfRope(str, ool->entry());
     masm.loadStringLength(str, len);
 
     Label isLatin1, done;
     masm.branchLatin1String(str, &isLatin1);
     {
+        masm.loadStringChars(str, temp0, CharEncoding::TwoByte);
         FindFirstDollarIndex(masm, str, len, temp0, temp1, output, /* isLatin1 = */ false);
-    }
-    masm.jump(&done);
+        masm.jump(&done);
+    }
+    masm.bind(&isLatin1);
     {
-        masm.bind(&isLatin1);
+        masm.loadStringChars(str, temp0, CharEncoding::Latin1);
         FindFirstDollarIndex(masm, str, len, temp0, temp1, output, /* isLatin1 = */ true);
     }
     masm.bind(&done);
     masm.bind(ool->rejoin());
 }
 
 typedef JSString* (*StringReplaceFn)(JSContext*, HandleString, HandleString, HandleString);
 static const VMFunction StringFlatReplaceInfo =
@@ -7769,23 +7773,25 @@ CopyStringCharsMaybeInflate(MacroAssembl
 {
     // destChars is TwoByte and input is a Latin1 or TwoByte string, so we may
     // have to inflate.
 
     Label isLatin1, done;
     masm.loadStringLength(input, temp1);
     masm.branchLatin1String(input, &isLatin1);
     {
-        masm.loadStringChars(input, input);
+        masm.loadStringChars(input, temp2, CharEncoding::TwoByte);
+        masm.movePtr(temp2, input);
         CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(char16_t), sizeof(char16_t));
         masm.jump(&done);
     }
     masm.bind(&isLatin1);
     {
-        masm.loadStringChars(input, input);
+        masm.loadStringChars(input, temp2, CharEncoding::Latin1);
+        masm.movePtr(temp2, input);
         CopyStringChars(masm, destChars, input, temp1, temp2, sizeof(char), sizeof(char16_t));
     }
     masm.bind(&done);
 }
 
 static void
 ConcatInlineString(MacroAssembler& masm, Register lhs, Register rhs, Register output,
                    Register temp1, Register temp2, Register temp3,
@@ -7823,35 +7829,37 @@ ConcatInlineString(MacroAssembler& masm,
         masm.store32(Imm32(flags), Address(output, JSString::offsetOfFlags()));
     }
     masm.bind(&allocDone);
 
     // Store length.
     masm.store32(temp2, Address(output, JSString::offsetOfLength()));
 
     // Load chars pointer in temp2.
-    masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()), temp2);
+    masm.loadInlineStringCharsForStore(output, temp2);
 
     {
         // Copy lhs chars. Note that this advances temp2 to point to the next
         // char. This also clobbers the lhs register.
         if (isTwoByte) {
             CopyStringCharsMaybeInflate(masm, lhs, temp2, temp1, temp3);
         } else {
             masm.loadStringLength(lhs, temp3);
-            masm.loadStringChars(lhs, lhs);
+            masm.loadStringChars(lhs, temp1, CharEncoding::Latin1);
+            masm.movePtr(temp1, lhs);
             CopyStringChars(masm, temp2, lhs, temp3, temp1, sizeof(char), sizeof(char));
         }
 
         // Copy rhs chars. Clobbers the rhs register.
         if (isTwoByte) {
             CopyStringCharsMaybeInflate(masm, rhs, temp2, temp1, temp3);
         } else {
             masm.loadStringLength(rhs, temp3);
-            masm.loadStringChars(rhs, rhs);
+            masm.loadStringChars(rhs, temp1, CharEncoding::Latin1);
+            masm.movePtr(temp1, rhs);
             CopyStringChars(masm, temp2, rhs, temp3, temp1, sizeof(char), sizeof(char));
         }
 
         // Null-terminate.
         if (isTwoByte)
             masm.store16(Imm32(0), Address(temp2, 0));
         else
             masm.store8(Imm32(0), Address(temp2, 0));
@@ -7902,77 +7910,79 @@ CodeGenerator::visitSubstr(LSubstr* lir)
     // Use slow path for ropes.
     masm.bind(&nonZero);
     masm.branchIfRopeOrExternal(string, temp, slowPath);
 
     // Handle inlined strings by creating a FatInlineString.
     masm.branchTest32(Assembler::Zero, stringFlags, Imm32(JSString::INLINE_CHARS_BIT), &notInline);
     masm.newGCFatInlineString(output, temp, slowPath);
     masm.store32(length, Address(output, JSString::offsetOfLength()));
-    Address stringStorage(string, JSInlineString::offsetOfInlineStorage());
-    Address outputStorage(output, JSInlineString::offsetOfInlineStorage());
 
     masm.branchLatin1String(string, &isInlinedLatin1);
     {
         masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS),
                      Address(output, JSString::offsetOfFlags()));
-        masm.computeEffectiveAddress(stringStorage, temp);
+        masm.loadInlineStringChars(string, temp, CharEncoding::TwoByte);
         if (temp2 == string)
             masm.push(string);
         BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t)));
         masm.computeEffectiveAddress(chars, temp2);
-        masm.computeEffectiveAddress(outputStorage, temp);
+        masm.loadInlineStringCharsForStore(output, temp);
         CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char16_t), sizeof(char16_t));
         masm.load32(Address(output, JSString::offsetOfLength()), length);
         masm.store16(Imm32(0), Address(temp, 0));
         if (temp2 == string)
             masm.pop(string);
         masm.jump(done);
     }
     masm.bind(&isInlinedLatin1);
     {
         masm.store32(Imm32(JSString::INIT_FAT_INLINE_FLAGS | JSString::LATIN1_CHARS_BIT),
                      Address(output, JSString::offsetOfFlags()));
-        if (temp2 == string)
+        if (temp2 == string) {
             masm.push(string);
-        masm.computeEffectiveAddress(stringStorage, temp2);
+            masm.loadInlineStringChars(string, temp, CharEncoding::Latin1);
+            masm.movePtr(temp, temp2);
+        } else {
+            masm.loadInlineStringChars(string, temp2, CharEncoding::Latin1);
+        }
         static_assert(sizeof(char) == 1, "begin index shouldn't need scaling");
         masm.addPtr(begin, temp2);
-        masm.computeEffectiveAddress(outputStorage, temp);
+        masm.loadInlineStringCharsForStore(output, temp);
         CopyStringChars(masm, temp, temp2, length, temp3, sizeof(char), sizeof(char));
         masm.load32(Address(output, JSString::offsetOfLength()), length);
         masm.store8(Imm32(0), Address(temp, 0));
         if (temp2 == string)
             masm.pop(string);
         masm.jump(done);
     }
 
     // Handle other cases with a DependentString.
     masm.bind(&notInline);
     masm.newGCString(output, temp, slowPath);
     masm.store32(length, Address(output, JSString::offsetOfLength()));
-    masm.storePtr(string, Address(output, JSDependentString::offsetOfBase()));
+    masm.storeDependentStringBase(string, output);
 
     masm.branchLatin1String(string, &isLatin1);
     {
         masm.store32(Imm32(JSString::DEPENDENT_FLAGS), Address(output, JSString::offsetOfFlags()));
-        masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp);
+        masm.loadNonInlineStringChars(string, temp, CharEncoding::TwoByte);
         BaseIndex chars(temp, begin, ScaleFromElemWidth(sizeof(char16_t)));
         masm.computeEffectiveAddress(chars, temp);
-        masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars()));
+        masm.storeNonInlineStringChars(temp, output);
         masm.jump(done);
     }
     masm.bind(&isLatin1);
     {
         masm.store32(Imm32(JSString::DEPENDENT_FLAGS | JSString::LATIN1_CHARS_BIT),
                      Address(output, JSString::offsetOfFlags()));
-        masm.loadPtr(Address(string, JSString::offsetOfNonInlineChars()), temp);
+        masm.loadNonInlineStringChars(string, temp, CharEncoding::Latin1);
         static_assert(sizeof(char) == 1, "begin index shouldn't need scaling");
         masm.addPtr(begin, temp);
-        masm.storePtr(temp, Address(output, JSString::offsetOfNonInlineChars()));
+        masm.storeNonInlineStringChars(temp, output);
         masm.jump(done);
     }
 
     masm.bind(done);
 }
 
 JitCode*
 JitCompartment::generateStringConcatStub(JSContext* cx)
@@ -8034,18 +8044,17 @@ JitCompartment::generateStringConcatStub
     // lhs and rhs flags, so we just have to clear the other flags to get our
     // rope flags (Latin1 if both lhs and rhs are Latin1).
     static_assert(JSString::INIT_ROPE_FLAGS == 0, "Rope type flags must be 0");
     masm.and32(Imm32(JSString::LATIN1_CHARS_BIT), temp1);
     masm.store32(temp1, Address(output, JSString::offsetOfFlags()));
     masm.store32(temp2, Address(output, JSString::offsetOfLength()));
 
     // Store left and right nodes.
-    masm.storePtr(lhs, Address(output, JSRope::offsetOfLeft()));
-    masm.storePtr(rhs, Address(output, JSRope::offsetOfRight()));
+    masm.storeRopeChildren(lhs, rhs, output);
     masm.ret();
 
     masm.bind(&leftEmpty);
     masm.mov(rhs, output);
     masm.ret();
 
     masm.bind(&rightEmpty);
     masm.mov(lhs, output);
@@ -8332,34 +8341,32 @@ CodeGenerator::visitFromCodePoint(LFromC
         Label isSupplementary;
         masm.branch32(Assembler::AboveOrEqual, codePoint, Imm32(unicode::NonBMPMin),
                       &isSupplementary);
         {
             // Store length.
             masm.store32(Imm32(1), Address(output, JSString::offsetOfLength()));
 
             // Load chars pointer in temp1.
-            masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()),
-                                         temp1);
+            masm.loadInlineStringCharsForStore(output, temp1);
 
             masm.store16(codePoint, Address(temp1, 0));
 
             // Null-terminate.
             masm.store16(Imm32(0), Address(temp1, sizeof(char16_t)));
 
             masm.jump(done);
         }
         masm.bind(&isSupplementary);
         {
             // Store length.
             masm.store32(Imm32(2), Address(output, JSString::offsetOfLength()));
 
             // Load chars pointer in temp1.
-            masm.computeEffectiveAddress(Address(output, JSInlineString::offsetOfInlineStorage()),
-                                         temp1);
+            masm.loadInlineStringCharsForStore(output, temp1);
 
             // Inlined unicode::LeadSurrogate(uint32_t).
             masm.move32(codePoint, temp2);
             masm.rshift32(Imm32(10), temp2);
             masm.add32(Imm32(unicode::LeadSurrogateMin - (unicode::NonBMPMin >> 10)), temp2);
 
             masm.store16(temp2, Address(temp1, 0));
 
--- a/js/src/jit/JitOptions.cpp
+++ b/js/src/jit/JitOptions.cpp
@@ -228,16 +228,17 @@ DefaultJitOptions::DefaultJitOptions()
     const char* forcedRegisterAllocatorEnv = "JIT_OPTION_forcedRegisterAllocator";
     if (const char* env = getenv(forcedRegisterAllocatorEnv)) {
         forcedRegisterAllocator = LookupRegisterAllocator(env);
         if (!forcedRegisterAllocator.isSome())
             Warn(forcedRegisterAllocatorEnv, env);
     }
 
     SET_DEFAULT(spectreIndexMasking, true);
+    SET_DEFAULT(spectreStringMitigations, false);
 
     // Toggles whether unboxed plain objects can be created by the VM.
     SET_DEFAULT(disableUnboxedObjects, false);
 
     // Test whether Atomics are allowed in asm.js code.
     SET_DEFAULT(asmJSAtomicsEnable, false);
 
     // Toggles the optimization whereby offsets are folded into loads and not
--- a/js/src/jit/JitOptions.h
+++ b/js/src/jit/JitOptions.h
@@ -90,16 +90,17 @@ struct DefaultJitOptions
     uint32_t branchPruningThreshold;
     uint32_t wasmBatchIonThreshold;
     uint32_t wasmBatchBaselineThreshold;
     mozilla::Maybe<uint32_t> forcedDefaultIonWarmUpThreshold;
     mozilla::Maybe<uint32_t> forcedDefaultIonSmallFunctionWarmUpThreshold;
     mozilla::Maybe<IonRegisterAllocator> forcedRegisterAllocator;
 
     bool spectreIndexMasking;
+    bool spectreStringMitigations;
 
     // The options below affect the rest of the VM, and not just the JIT.
     bool disableUnboxedObjects;
 
     DefaultJitOptions();
     bool isSmallFunction(JSScript* script) const;
     void setEagerCompilation();
     void setCompilerWarmUpThreshold(uint32_t warmUpThreshold);
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1362,29 +1362,165 @@ MacroAssembler::compareStrings(JSOp op, 
     loadStringLength(left, result);
     branch32(Assembler::Equal, Address(right, JSString::offsetOfLength()), result, fail);
     move32(Imm32(op == JSOP_NE || op == JSOP_STRICTNE), result);
 
     bind(&done);
 }
 
 void
-MacroAssembler::loadStringChars(Register str, Register dest)
+MacroAssembler::loadStringChars(Register str, Register dest, CharEncoding encoding)
 {
-    Label isInline, done;
-    branchTest32(Assembler::NonZero, Address(str, JSString::offsetOfFlags()),
-                 Imm32(JSString::INLINE_CHARS_BIT), &isInline);
+    MOZ_ASSERT(str != dest);
+
+    if (JitOptions.spectreStringMitigations) {
+        if (encoding == CharEncoding::Latin1) {
+            // If the string is a rope, zero the |str| register. The code below
+            // depends on str->flags so this should block speculative execution.
+            movePtr(ImmWord(0), dest);
+            test32MovePtr(Assembler::Zero,
+                          Address(str, JSString::offsetOfFlags()), Imm32(JSString::LINEAR_BIT),
+                          dest, str);
+        } else {
+            // If we're loading TwoByte chars, there's an additional risk:
+            // if the string has Latin1 chars, we could read out-of-bounds. To
+            // prevent this, we check both the Linear and Latin1 bits. We don't
+            // have a scratch register, so we use these flags also to block
+            // speculative execution, similar to the use of 0 above.
+            MOZ_ASSERT(encoding == CharEncoding::TwoByte);
+            static constexpr uint32_t Mask = JSString::LINEAR_BIT | JSString::LATIN1_CHARS_BIT;
+            static_assert(Mask < 1024,
+                          "Mask should be a small, near-null value to ensure we "
+                          "block speculative execution when it's used as string "
+                          "pointer");
+            move32(Imm32(Mask), dest);
+            and32(Address(str, JSString::offsetOfFlags()), dest);
+            cmp32MovePtr(Assembler::NotEqual, dest, Imm32(JSString::LINEAR_BIT),
+                         dest, str);
+        }
+    }
+
+    // Load the inline chars.
+    computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
+
+    // If it's not an inline string, load the non-inline chars. Use a
+    // conditional move to prevent speculative execution.
+    test32LoadPtr(Assembler::Zero,
+                  Address(str, JSString::offsetOfFlags()), Imm32(JSString::INLINE_CHARS_BIT),
+                  Address(str, JSString::offsetOfNonInlineChars()), dest);
+}
+
+void
+MacroAssembler::loadNonInlineStringChars(Register str, Register dest, CharEncoding encoding)
+{
+    MOZ_ASSERT(str != dest);
+
+    if (JitOptions.spectreStringMitigations) {
+        // If the string is a rope, has inline chars, or has a different
+        // character encoding, set str to a near-null value to prevent
+        // speculative execution below (when reading str->nonInlineChars).
+
+        static constexpr uint32_t Mask =
+            JSString::LINEAR_BIT |
+            JSString::INLINE_CHARS_BIT |
+            JSString::LATIN1_CHARS_BIT;
+        static_assert(Mask < 1024,
+                      "Mask should be a small, near-null value to ensure we "
+                      "block speculative execution when it's used as string "
+                      "pointer");
+
+        uint32_t expectedBits = JSString::LINEAR_BIT;
+        if (encoding == CharEncoding::Latin1)
+            expectedBits |= JSString::LATIN1_CHARS_BIT;
+
+        move32(Imm32(Mask), dest);
+        and32(Address(str, JSString::offsetOfFlags()), dest);
+
+        cmp32MovePtr(Assembler::NotEqual, dest, Imm32(expectedBits),
+                     dest, str);
+    }
 
     loadPtr(Address(str, JSString::offsetOfNonInlineChars()), dest);
-    jump(&done);
-
-    bind(&isInline);
+}
+
+void
+MacroAssembler::storeNonInlineStringChars(Register chars, Register str)
+{
+    MOZ_ASSERT(chars != str);
+    storePtr(chars, Address(str, JSString::offsetOfNonInlineChars()));
+}
+
+void
+MacroAssembler::loadInlineStringCharsForStore(Register str, Register dest)
+{
     computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
-
-    bind(&done);
+}
+
+void
+MacroAssembler::loadInlineStringChars(Register str, Register dest, CharEncoding encoding)
+{
+    MOZ_ASSERT(str != dest);
+
+    if (JitOptions.spectreStringMitigations) {
+        // Making this Spectre-safe is a bit complicated: using
+        // computeEffectiveAddress and then zeroing the output register if
+        // non-inline is not sufficient: when the index is very large, it would
+        // allow reading |nullptr + index|. Just fall back to loadStringChars
+        // for now.
+        loadStringChars(str, dest, encoding);
+    } else {
+        computeEffectiveAddress(Address(str, JSInlineString::offsetOfInlineStorage()), dest);
+    }
+}
+
+void
+MacroAssembler::loadRopeLeftChild(Register str, Register dest)
+{
+    MOZ_ASSERT(str != dest);
+
+    if (JitOptions.spectreStringMitigations) {
+        // Zero the output register if the input was not a rope.
+        movePtr(ImmWord(0), dest);
+        test32LoadPtr(Assembler::Zero,
+                      Address(str, JSString::offsetOfFlags()), Imm32(JSString::LINEAR_BIT),
+                      Address(str, JSRope::offsetOfLeft()), dest);
+    } else {
+        loadPtr(Address(str, JSRope::offsetOfLeft()), dest);
+    }
+}
+
+void
+MacroAssembler::storeRopeChildren(Register left, Register right, Register str)
+{
+    storePtr(left, Address(str, JSRope::offsetOfLeft()));
+    storePtr(right, Address(str, JSRope::offsetOfRight()));
+}
+
+void
+MacroAssembler::loadDependentStringBase(Register str, Register dest)
+{
+    MOZ_ASSERT(str != dest);
+
+    if (JitOptions.spectreStringMitigations) {
+        // If the string does not have a base-string, zero the |str| register.
+        // The code below loads str->base so this should block speculative
+        // execution.
+        movePtr(ImmWord(0), dest);
+        test32MovePtr(Assembler::Zero,
+                      Address(str, JSString::offsetOfFlags()), Imm32(JSString::HAS_BASE_BIT),
+                      dest, str);
+    }
+
+    loadPtr(Address(str, JSDependentString::offsetOfBase()), dest);
+}
+
+void
+MacroAssembler::storeDependentStringBase(Register base, Register str)
+{
+    storePtr(base, Address(str, JSDependentString::offsetOfBase()));
 }
 
 void
 MacroAssembler::loadStringChar(Register str, Register index, Register output, Register scratch,
                                Label* fail)
 {
     MOZ_ASSERT(str != output);
     MOZ_ASSERT(str != index);
@@ -1392,40 +1528,38 @@ MacroAssembler::loadStringChar(Register 
     MOZ_ASSERT(output != scratch);
 
     movePtr(str, output);
 
     // This follows JSString::getChar.
     Label notRope;
     branchIfNotRope(str, &notRope);
 
-    // Load leftChild.
-    loadPtr(Address(str, JSRope::offsetOfLeft()), output);
+    loadRopeLeftChild(str, output);
 
     // Check if the index is contained in the leftChild.
     // Todo: Handle index in the rightChild.
     boundsCheck32ForLoad(index, Address(output, JSString::offsetOfLength()), scratch, fail);
 
     // If the left side is another rope, give up.
     branchIfRope(output, fail);
 
     bind(&notRope);
 
     Label isLatin1, done;
     // We have to check the left/right side for ropes,
     // because a TwoByte rope might have a Latin1 child.
     branchLatin1String(output, &isLatin1);
-
-    loadStringChars(output, output);
-    load16ZeroExtend(BaseIndex(output, index, TimesTwo), output);
+    loadStringChars(output, scratch, CharEncoding::TwoByte);
+    load16ZeroExtend(BaseIndex(scratch, index, TimesTwo), output);
     jump(&done);
 
     bind(&isLatin1);
-    loadStringChars(output, output);
-    load8ZeroExtend(BaseIndex(output, index, TimesOne), output);
+    loadStringChars(output, scratch, CharEncoding::Latin1);
+    load8ZeroExtend(BaseIndex(scratch, index, TimesOne), output);
 
     bind(&done);
 }
 
 void
 MacroAssembler::loadStringIndexValue(Register str, Register dest, Label* fail)
 {
     MOZ_ASSERT(str != dest);
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -223,16 +223,18 @@ enum class CheckUnsafeCallWithABI {
     DontCheckHasExitFrame,
 
     // Don't check this callWithABI uses AutoUnsafeCallWithABI, for instance
     // because we're calling a simple helper function (like malloc or js_free)
     // that we can't change and/or that we know won't GC.
     DontCheckOther,
 };
 
+enum class CharEncoding { Latin1, TwoByte };
+
 // The public entrypoint for emitting assembly. Note that a MacroAssembler can
 // use cx->lifoAlloc, so take care not to interleave masm use with other
 // lifoAlloc use if one will be destroyed before the other.
 class MacroAssembler : public MacroAssemblerSpecific
 {
     MacroAssembler* thisFromCtor() {
         return this;
     }
@@ -1363,16 +1365,28 @@ class MacroAssembler : public MacroAssem
     inline void cmp32Move32(Condition cond, Register lhs, Register rhs, Register src,
                             Register dest)
         DEFINED_ON(arm, arm64, x86_shared);
 
     inline void cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
                             Register dest)
         DEFINED_ON(arm, arm64, x86_shared);
 
+    inline void cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+        DEFINED_ON(arm, arm64, x86, x64);
+
+    inline void test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+        DEFINED_ON(arm, arm64, x86, x64);
+
+    inline void test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+        DEFINED_ON(arm, arm64, x86, x64);
+
     // Performs a bounds check and zeroes the index register if out-of-bounds
     // (to mitigate Spectre).
     inline void boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
         DEFINED_ON(arm, arm64, x86_shared);
     inline void boundsCheck32ForLoad(Register index, const Address& length, Register scratch,
                                      Label* failure)
         DEFINED_ON(arm, arm64, x86_shared);
@@ -1949,20 +1963,34 @@ class MacroAssembler : public MacroAssem
         loadPtr(Address(obj, JSObject::offsetOfGroup()), dest);
         loadPtr(Address(dest, ObjectGroup::offsetOfProto()), dest);
     }
 
     void loadStringLength(Register str, Register dest) {
         load32(Address(str, JSString::offsetOfLength()), dest);
     }
 
-    void loadStringChars(Register str, Register dest);
+    void loadStringChars(Register str, Register dest, CharEncoding encoding);
+
+    void loadNonInlineStringChars(Register str, Register dest, CharEncoding encoding);
+    void loadNonInlineStringCharsForStore(Register str, Register dest);
+    void storeNonInlineStringChars(Register chars, Register str);
+
+    void loadInlineStringChars(Register str, Register dest, CharEncoding encoding);
+    void loadInlineStringCharsForStore(Register str, Register dest);
+
     void loadStringChar(Register str, Register index, Register output, Register scratch,
                         Label* fail);
 
+    void loadRopeLeftChild(Register str, Register dest);
+    void storeRopeChildren(Register left, Register right, Register str);
+
+    void loadDependentStringBase(Register str, Register dest);
+    void storeDependentStringBase(Register base, Register str);
+
     void loadStringIndexValue(Register str, Register dest, Label* fail);
 
     void loadJSContext(Register dest);
     void loadJitActivation(Register dest) {
         loadJSContext(dest);
         loadPtr(Address(dest, offsetof(JSContext, activation_)), dest);
     }
 
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -2140,26 +2140,53 @@ void
 MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs, Register src,
                             Register dest)
 {
     cmp32(lhs, rhs);
     ma_mov(src, dest, LeaveCC, cond);
 }
 
 void
+MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+{
+    cmp32(lhs, rhs);
+    ma_mov(src, dest, LeaveCC, cond);
+}
+
+void
 MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
                             Register dest)
 {
     ScratchRegisterScope scratch(*this);
     SecondScratchRegisterScope scratch2(*this);
     ma_ldr(rhs, scratch, scratch2);
     cmp32Move32(cond, lhs, scratch, src, dest);
 }
 
 void
+MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    ScratchRegisterScope scratch(*this);
+    ma_ldr(src, dest, scratch, Offset, cond);
+}
+
+void
+MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    ma_mov(src, dest, LeaveCC, cond);
+}
+
+void
 MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
 {
     MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != scratch);
     MOZ_ASSERT(index != scratch);
 
     if (JitOptions.spectreIndexMasking)
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -1722,16 +1722,50 @@ void
 MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
                             Register dest)
 {
     cmp32(lhs, rhs);
     Csel(ARMRegister(dest, 32), ARMRegister(src, 32), ARMRegister(dest, 32), cond);
 }
 
 void
+MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+{
+    cmp32(lhs, rhs);
+    Csel(ARMRegister(dest, 64), ARMRegister(src, 64), ARMRegister(dest, 64), cond);
+}
+
+void
+MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+
+    // ARM64 does not support conditional loads, so we use a branch with a CSel
+    // (to prevent Spectre attacks).
+    vixl::UseScratchRegisterScope temps(this);
+    const ARMRegister scratch64 = temps.AcquireX();
+    Label done;
+    branchTest32(Assembler::InvertCondition(cond), addr, mask, &done);
+    loadPtr(src, scratch64.asUnsized());
+    Csel(ARMRegister(dest, 64), scratch64, ARMRegister(dest, 64), cond);
+    bind(&done);
+}
+
+void
+MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    Csel(ARMRegister(dest, 64), ARMRegister(src, 64), ARMRegister(dest, 64), cond);
+}
+
+void
 MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
 {
     MOZ_ASSERT(index != length);
     MOZ_ASSERT(length != scratch);
     MOZ_ASSERT(index != scratch);
 
     branch32(Assembler::BelowOrEqual, length, index, failure);
--- a/js/src/jit/x64/MacroAssembler-x64-inl.h
+++ b/js/src/jit/x64/MacroAssembler-x64-inl.h
@@ -815,16 +815,42 @@ MacroAssembler::branchTestMagic(Conditio
 }
 
 void
 MacroAssembler::branchToComputedAddress(const BaseIndex& address)
 {
     jmp(Operand(address));
 }
 
+void
+MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+{
+    cmp32(lhs, rhs);
+    cmovCCq(cond, Operand(src), dest);
+}
+
+void
+MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    cmovCCq(cond, Operand(src), dest);
+}
+
+void
+MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    cmovCCq(cond, Operand(src), dest);
+}
+
 // ========================================================================
 // Truncate floating point.
 
 void
 MacroAssembler::truncateFloat32ToUInt64(Address src, Address dest, Register temp,
                                         FloatRegister floatTemp)
 {
     Label done;
--- a/js/src/jit/x86/MacroAssembler-x86-inl.h
+++ b/js/src/jit/x86/MacroAssembler-x86-inl.h
@@ -1004,16 +1004,42 @@ MacroAssembler::branchTestMagic(Conditio
 }
 
 void
 MacroAssembler::branchToComputedAddress(const BaseIndex& addr)
 {
     jmp(Operand(addr));
 }
 
+void
+MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+{
+    cmp32(lhs, rhs);
+    cmovCCl(cond, Operand(src), dest);
+}
+
+void
+MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    cmovCCl(cond, Operand(src), dest);
+}
+
+void
+MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+{
+    MOZ_ASSERT(cond == Assembler::Zero || cond == Assembler::NonZero);
+    test32(addr, mask);
+    cmovCCl(cond, Operand(src), dest);
+}
+
 // ========================================================================
 // Truncate floating point.
 
 void
 MacroAssembler::truncateFloat32ToUInt64(Address src, Address dest, Register temp,
                                         FloatRegister floatTemp)
 {
     Label done;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -7251,16 +7251,19 @@ JS_SetGlobalJitCompilerOption(JSContext*
         jit::JitOptions.jumpThreshold = value;
         break;
       case JSJITCOMPILER_SIMULATOR_ALWAYS_INTERRUPT:
         jit::JitOptions.simulatorAlwaysInterrupt = !!value;
         break;
       case JSJITCOMPILER_SPECTRE_INDEX_MASKING:
         jit::JitOptions.spectreIndexMasking = !!value;
         break;
+      case JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS:
+        jit::JitOptions.spectreStringMitigations = !!value;
+        break;
       case JSJITCOMPILER_ASMJS_ATOMICS_ENABLE:
         jit::JitOptions.asmJSAtomicsEnable = !!value;
         break;
       case JSJITCOMPILER_WASM_FOLD_OFFSETS:
         jit::JitOptions.wasmFoldOffsets = !!value;
         break;
       case JSJITCOMPILER_ION_INTERRUPT_WITHOUT_SIGNAL:
         jit::JitOptions.ionInterruptWithoutSignals = !!value;
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -5907,17 +5907,18 @@ JS_SetOffthreadIonCompilationEnabled(JSC
     Register(ION_INTERRUPT_WITHOUT_SIGNAL, "ion.interrupt-without-signals") \
     Register(ION_CHECK_RANGE_ANALYSIS, "ion.check-range-analysis")          \
     Register(BASELINE_ENABLE, "baseline.enable")                            \
     Register(OFFTHREAD_COMPILATION_ENABLE, "offthread-compilation.enable")  \
     Register(FULL_DEBUG_CHECKS, "jit.full-debug-checks")                    \
     Register(JUMP_THRESHOLD, "jump-threshold")                              \
     Register(SIMULATOR_ALWAYS_INTERRUPT, "simulator.always-interrupt")      \
     Register(SPECTRE_INDEX_MASKING, "spectre.index-masking")                \
-    Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable")                  \
+    Register(SPECTRE_STRING_MITIGATIONS, "spectre.string-mitigations")      \
+Register(ASMJS_ATOMICS_ENABLE, "asmjs.atomics.enable")  \
     Register(WASM_FOLD_OFFSETS, "wasm.fold-offsets")
 
 typedef enum JSJitCompilerOption {
 #define JIT_COMPILER_DECLARE(key, str) \
     JSJITCOMPILER_ ## key,
 
     JIT_COMPILER_OPTIONS(JIT_COMPILER_DECLARE)
 #undef JIT_COMPILER_DECLARE
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -8518,22 +8518,25 @@ SetContextOptions(JSContext* cx, const O
             jit::JitOptions.disableCacheIR = false;
         else if (strcmp(str, "off") == 0)
             jit::JitOptions.disableCacheIR = true;
         else
             return OptionFailure("cache-ir-stubs", str);
     }
 
     if (const char* str = op.getStringOption("spectre-mitigations")) {
-        if (strcmp(str, "on") == 0)
+        if (strcmp(str, "on") == 0) {
             jit::JitOptions.spectreIndexMasking = true;
-        else if (strcmp(str, "off") == 0)
+            jit::JitOptions.spectreStringMitigations = true;
+        } else if (strcmp(str, "off") == 0) {
             jit::JitOptions.spectreIndexMasking = false;
-        else
+            jit::JitOptions.spectreStringMitigations = false;
+        } else {
             return OptionFailure("spectre-mitigations", str);
+        }
     }
 
     if (const char* str = op.getStringOption("ion-scalar-replacement")) {
         if (strcmp(str, "on") == 0)
             jit::JitOptions.disableScalarReplacement = false;
         else if (strcmp(str, "off") == 0)
             jit::JitOptions.disableScalarReplacement = true;
         else
--- a/js/src/vm/String.h
+++ b/js/src/vm/String.h
@@ -511,23 +511,28 @@ class JSString : public js::gc::TenuredC
 
     static size_t offsetOfLength() {
         return offsetof(JSString, d.u1.length);
     }
     static size_t offsetOfFlags() {
         return offsetof(JSString, d.u1.flags);
     }
 
+  private:
+    // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
+    // to call the method below.
+    friend class js::jit::MacroAssembler;
     static size_t offsetOfNonInlineChars() {
         static_assert(offsetof(JSString, d.s.u2.nonInlineCharsTwoByte) ==
                       offsetof(JSString, d.s.u2.nonInlineCharsLatin1),
                       "nonInlineCharsTwoByte and nonInlineCharsLatin1 must have same offset");
         return offsetof(JSString, d.s.u2.nonInlineCharsTwoByte);
     }
 
+  public:
     static const JS::TraceKind TraceKind = JS::TraceKind::String;
 
 #ifdef DEBUG
     void dump(); // Debugger-friendly stderr dump.
     void dump(js::GenericPrinter& out);
     void dumpNoNewline(js::GenericPrinter& out);
     void dumpCharsNoNewline(js::GenericPrinter& out);
     void dumpRepresentation(js::GenericPrinter& out, int indent) const;
@@ -605,26 +610,31 @@ class JSRope : public JSString
 
     JSString* rightChild() const {
         MOZ_ASSERT(isRope());
         return d.s.u3.right;
     }
 
     void traceChildren(JSTracer* trc);
 
+#ifdef DEBUG
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
+#endif
+
+  private:
+    // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
+    // to call the methods below.
+    friend class js::jit::MacroAssembler;
+
     static size_t offsetOfLeft() {
         return offsetof(JSRope, d.s.u2.left);
     }
     static size_t offsetOfRight() {
         return offsetof(JSRope, d.s.u3.right);
     }
-
-#ifdef DEBUG
-    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
-#endif
 };
 
 static_assert(sizeof(JSRope) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSLinearString : public JSString
 {
     friend class JSString;
@@ -737,23 +747,28 @@ class JSDependentString : public JSLinea
         MOZ_ASSERT(offset < base()->length());
         return mozilla::Some(offset);
     }
 
   public:
     static inline JSLinearString* new_(JSContext* cx, JSLinearString* base,
                                        size_t start, size_t length);
 
+#ifdef DEBUG
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
+#endif
+
+  private:
+    // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
+    // to call the method below.
+    friend class js::jit::MacroAssembler;
+
     inline static size_t offsetOfBase() {
         return offsetof(JSDependentString, d.s.u3.base);
     }
-
-#ifdef DEBUG
-    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
-#endif
 };
 
 static_assert(sizeof(JSDependentString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 class JSFlatString : public JSLinearString
 {
     /* Vacuous and therefore unimplemented. */
@@ -875,23 +890,27 @@ class JSInlineString : public JSFlatStri
         MOZ_ASSERT(JSString::isInline());
         MOZ_ASSERT(hasTwoByteChars());
         return d.inlineStorageTwoByte;
     }
 
     template<typename CharT>
     static bool lengthFits(size_t length);
 
+#ifdef DEBUG
+    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
+#endif
+
+  private:
+    // To help avoid writing Spectre-unsafe code, we only allow MacroAssembler
+    // to call the method below.
+    friend class js::jit::MacroAssembler;
     static size_t offsetOfInlineStorage() {
         return offsetof(JSInlineString, d.inlineStorageTwoByte);
     }
-
-#ifdef DEBUG
-    void dumpRepresentation(js::GenericPrinter& out, int indent) const;
-#endif
 };
 
 static_assert(sizeof(JSInlineString) == sizeof(JSString),
               "string subclasses must be binary-compatible with JSString");
 
 /*
  * On 32-bit platforms, JSThinInlineString can store 7 Latin1 characters or 3
  * TwoByte characters (excluding null terminator) inline. On 64-bit platforms,
--- a/js/xpconnect/src/XPCJSContext.cpp
+++ b/js/xpconnect/src/XPCJSContext.cpp
@@ -804,16 +804,18 @@ ReloadPrefsCallback(const char* pref, vo
 
     bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
 
     bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
 
     bool streams = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
 
     bool spectreIndexMasking = Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking");
+    bool spectreStringMitigations =
+        Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations");
 
     sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
 
 #ifdef DEBUG
     sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug");
 #endif
 
 #ifdef JS_GC_ZEAL
@@ -866,16 +868,18 @@ ReloadPrefsCallback(const char* pref, vo
                                   useBaselineEager ? 0 : baselineThreshold);
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
                                   useIonEager ? 0 : ionThreshold);
 #ifdef DEBUG
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, fullJitDebugChecks);
 #endif
 
     JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING, spectreIndexMasking);
+    JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
+                                  spectreStringMitigations);
 }
 
 XPCJSContext::~XPCJSContext()
 {
     MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
     // Elsewhere we abort immediately if XPCJSContext initialization fails.
     // Therefore the context must be non-null.
     MOZ_ASSERT(MaybeContext());
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3626,16 +3626,17 @@ GetOrCreateRetainedDisplayListBuilder(ns
 
 nsresult
 nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
                           const nsRegion& aDirtyRegion, nscolor aBackstop,
                           nsDisplayListBuilderMode aBuilderMode,
                           PaintFrameFlags aFlags)
 {
   AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS);
+  typedef RetainedDisplayListBuilder::PartialUpdateResult PartialUpdateResult;
 
 #ifdef MOZ_DUMP_PAINTING
   if (!gPaintCountStack) {
     gPaintCountStack = new nsTArray<int>();
     ClearOnShutdown(&gPaintCountStack);
 
     gPaintCountStack->AppendElement(0);
   }
@@ -3783,16 +3784,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
   }
 
   builder.ClearHaveScrollableDisplayPort();
   if (builder.IsPaintingToWindow()) {
     MaybeCreateDisplayPortInFirstScrollFrameEncountered(aFrame, builder);
   }
 
   nsRect visibleRect = visibleRegion.GetBounds();
+  PartialUpdateResult updateState = PartialUpdateResult::Failed;
 
   {
     AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList",
                         GRAPHICS);
     AUTO_PROFILER_TRACING("Paint", "DisplayList");
 
     PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList);
     TimeStamp dlStart = TimeStamp::Now();
@@ -3832,51 +3834,50 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
 
       DisplayListChecker beforeMergeChecker;
       DisplayListChecker toBeMergedChecker;
       DisplayListChecker afterMergeChecker;
 
       // Attempt to do a partial build and merge into the existing list.
       // This calls BuildDisplayListForStacking context on a subset of the
       // viewport.
-      bool merged = false;
-
       if (useRetainedBuilder) {
         if (gfxPrefs::LayoutVerifyRetainDisplayList()) {
           beforeMergeChecker.Set(&list, "BM");
         }
-        merged = retainedBuilder->AttemptPartialUpdate(
+        updateState = retainedBuilder->AttemptPartialUpdate(
           aBackstop, beforeMergeChecker ? &toBeMergedChecker : nullptr);
-        if (merged && beforeMergeChecker) {
+        if ((updateState != PartialUpdateResult::Failed) && beforeMergeChecker) {
           afterMergeChecker.Set(&list, "AM");
         }
       }
 
-      if (merged &&
+      if ((updateState != PartialUpdateResult::Failed) &&
           (gfxPrefs::LayoutDisplayListBuildTwice() || afterMergeChecker)) {
-        merged = false;
+        updateState = PartialUpdateResult::Failed;
         if (gfxPrefs::LayersDrawFPS()) {
           if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
             if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
               pt->dl2Ms() = (TimeStamp::Now() - dlStart).ToMilliseconds();
             }
           }
         }
         dlStart = TimeStamp::Now();
       }
 
-      if (!merged) {
+      if (updateState == PartialUpdateResult::Failed) {
         list.DeleteAll(&builder);
         builder.EnterPresShell(aFrame);
         builder.SetDirtyRect(visibleRect);
         builder.ClearRetainedWindowRegions();
         aFrame->BuildDisplayListForStackingContext(&builder, &list);
         AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
 
         builder.LeavePresShell(aFrame, &list);
+        updateState = PartialUpdateResult::Updated;
 
         if (afterMergeChecker) {
           DisplayListChecker nonRetainedChecker(&list, "NR");
           std::stringstream ss;
           ss << "**** Differences between retained-after-merged (AM) and "
              << "non-retained (NR) display lists:";
           if (!nonRetainedChecker.CompareList(afterMergeChecker, ss)) {
             ss << "\n\n*** non-retained display items:";
@@ -3904,16 +3905,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
       if (RefPtr<LayerManager> lm = builder.GetWidgetLayerManager()) {
         if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(lm)) {
           pt->dlMs() = (TimeStamp::Now() - dlStart).ToMilliseconds();
         }
       }
     }
   }
 
+  MOZ_ASSERT(updateState != PartialUpdateResult::Failed);
   builder.Check();
 
   Telemetry::AccumulateTimeDelta(Telemetry::PAINT_BUILD_DISPLAYLIST_TIME,
                                  startBuildDisplayList);
 
   bool consoleNeedsDisplayList = gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint();
 #ifdef MOZ_DUMP_PAINTING
   FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
@@ -3978,16 +3980,19 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
     flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION;
   }
   if (aFlags & PaintFrameFlags::PAINT_NO_COMPOSITE) {
     flags |= nsDisplayList::PAINT_NO_COMPOSITE;
   }
   if (aFlags & PaintFrameFlags::PAINT_COMPRESSED) {
     flags |= nsDisplayList::PAINT_COMPRESSED;
   }
+  if (updateState == PartialUpdateResult::NoChange) {
+    flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST;
+  }
 
   TimeStamp paintStart = TimeStamp::Now();
   RefPtr<LayerManager> layerManager
     = list.PaintRoot(&builder, aRenderingContext, flags);
   Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME,
                                  paintStart);
 
   builder.Check();
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -189,26 +189,25 @@ nsPresContext::MakeColorPref(const nsStr
   }
 
   return result;
 }
 
 bool
 nsPresContext::IsDOMPaintEventPending()
 {
-  if (mFireAfterPaintEvents) {
+  if (!mTransactions.IsEmpty()) {
     return true;
   }
   nsRootPresContext* drpc = GetRootPresContext();
   if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
     // Since we're promising that there will be a MozAfterPaint event
     // fired, we record an empty invalidation in case display list
     // invalidation doesn't invalidate anything further.
     NotifyInvalidation(drpc->mRefreshDriver->LastTransactionId() + 1, nsRect(0, 0, 0, 0));
-    NS_ASSERTION(mFireAfterPaintEvents, "Why aren't we planning to fire the event?");
     return true;
   }
   return false;
 }
 
 void
 nsPresContext::PrefChangedCallback(const char* aPrefName, void* instance_data)
 {
@@ -310,17 +309,16 @@ nsPresContext::nsPresContext(nsIDocument
     mIsEmulatingMedia(false),
     mIsGlyph(false),
     mUsesRootEMUnits(false),
     mUsesExChUnits(false),
     mCounterStylesDirty(true),
     mFontFeatureValuesDirty(true),
     mSuppressResizeReflow(false),
     mIsVisual(false),
-    mFireAfterPaintEvents(false),
     mIsChrome(false),
     mIsChromeOriginImage(false),
     mPaintFlashing(false),
     mPaintFlashingInitialized(false),
     mHasWarnedAboutPositionedTableParts(false),
     mHasWarnedAboutTooLargeDashedOrDottedRadius(false),
     mQuirkSheetAdded(false),
     mNeedsPrefUpdate(false),
@@ -2591,52 +2589,57 @@ nsPresContext::NotifyInvalidation(uint64
 
   nsRect rect(DevPixelsToAppUnits(clampedRect.x),
               DevPixelsToAppUnits(clampedRect.y),
               DevPixelsToAppUnits(clampedRect.width),
               DevPixelsToAppUnits(clampedRect.height));
   NotifyInvalidation(aTransactionId, rect);
 }
 
+nsPresContext::TransactionInvalidations*
+nsPresContext::GetInvalidations(uint64_t aTransactionId)
+{
+  for (TransactionInvalidations& t : mTransactions) {
+    if (t.mTransactionId == aTransactionId) {
+      return &t;
+    }
+  }
+  return nullptr;
+}
+
 void
 nsPresContext::NotifyInvalidation(uint64_t aTransactionId, const nsRect& aRect)
 {
   MOZ_ASSERT(GetContainerWeak(), "Invalidation in detached pres context");
 
   // If there is no paint event listener, then we don't need to fire
   // the asynchronous event. We don't even need to record invalidation.
   // MayHavePaintEventListener is pretty cheap and we could make it
   // even cheaper by providing a more efficient
   // nsPIDOMWindow::GetListenerManager.
 
   nsPresContext* pc;
   for (pc = this; pc; pc = pc->GetParentPresContext()) {
-    if (pc->mFireAfterPaintEvents)
+    TransactionInvalidations* transaction = pc->GetInvalidations(aTransactionId);
+    if (transaction) {
       break;
-    pc->mFireAfterPaintEvents = true;
+    } else {
+      transaction = pc->mTransactions.AppendElement();
+      transaction->mTransactionId = aTransactionId;
+    }
   }
   if (!pc) {
     nsRootPresContext* rpc = GetRootPresContext();
     if (rpc) {
       rpc->EnsureEventualDidPaintEvent(aTransactionId);
     }
   }
 
-  TransactionInvalidations* transaction = nullptr;
-  for (TransactionInvalidations& t : mTransactions) {
-    if (t.mTransactionId == aTransactionId) {
-      transaction = &t;
-      break;
-    }
-  }
-  if (!transaction) {
-    transaction = mTransactions.AppendElement();
-    transaction->mTransactionId = aTransactionId;
-  }
-
+  TransactionInvalidations* transaction = GetInvalidations(aTransactionId);
+  MOZ_ASSERT(transaction);
   transaction->mInvalidations.AppendElement(aRect);
 }
 
 /* static */ void
 nsPresContext::NotifySubDocInvalidation(ContainerLayer* aContainer,
                                         const nsIntRegion* aRegion)
 {
   ContainerLayerPresContext *data =
@@ -2678,32 +2681,28 @@ nsPresContext::SetNotifySubDocInvalidati
 nsPresContext::ClearNotifySubDocInvalidationData(ContainerLayer* aContainer)
 {
   aContainer->SetUserData(&gNotifySubDocInvalidationData, nullptr);
 }
 
 struct NotifyDidPaintSubdocumentCallbackClosure {
   uint64_t mTransactionId;
   const mozilla::TimeStamp& mTimeStamp;
-  bool mNeedsAnotherDidPaintNotification;
 };
 /* static */ bool
 nsPresContext::NotifyDidPaintSubdocumentCallback(nsIDocument* aDocument, void* aData)
 {
   NotifyDidPaintSubdocumentCallbackClosure* closure =
     static_cast<NotifyDidPaintSubdocumentCallbackClosure*>(aData);
   nsIPresShell* shell = aDocument->GetShell();
   if (shell) {
     nsPresContext* pc = shell->GetPresContext();
     if (pc) {
       pc->NotifyDidPaintForSubtree(closure->mTransactionId,
                                    closure->mTimeStamp);
-      if (pc->mFireAfterPaintEvents) {
-        closure->mNeedsAnotherDidPaintNotification = true;
-      }
     }
   }
   return true;
 }
 
 class DelayedFireDOMPaintEvent : public Runnable {
 public:
   DelayedFireDOMPaintEvent(
@@ -2738,62 +2737,58 @@ public:
 
 void
 nsPresContext::NotifyDidPaintForSubtree(uint64_t aTransactionId,
                                         const mozilla::TimeStamp& aTimeStamp)
 {
   if (IsRoot()) {
     static_cast<nsRootPresContext*>(this)->CancelDidPaintTimers(aTransactionId);
 
-    if (!mFireAfterPaintEvents) {
+    if (mTransactions.IsEmpty()) {
       return;
     }
   }
 
-  if (!PresShell()->IsVisible() && !mFireAfterPaintEvents) {
+  if (!PresShell()->IsVisible() && mTransactions.IsEmpty()) {
     return;
   }
 
   // Non-root prescontexts fire MozAfterPaint to all their descendants
   // unconditionally, even if no invalidations have been collected. This is
   // because we don't want to eat the cost of collecting invalidations for
   // every subdocument (which would require putting every subdocument in its
   // own layer).
 
   bool sent = false;
   uint32_t i = 0;
   while (i < mTransactions.Length()) {
     if (mTransactions[i].mTransactionId <= aTransactionId) {
-      nsCOMPtr<nsIRunnable> ev =
-        new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations,
-                                     mTransactions[i].mTransactionId, aTimeStamp);
-      nsContentUtils::AddScriptRunner(ev);
-      sent = true;
+      if (!mTransactions[i].mInvalidations.IsEmpty()) {
+        nsCOMPtr<nsIRunnable> ev =
+          new DelayedFireDOMPaintEvent(this, &mTransactions[i].mInvalidations,
+                                       mTransactions[i].mTransactionId, aTimeStamp);
+        nsContentUtils::AddScriptRunner(ev);
+        sent = true;
+      }
       mTransactions.RemoveElementAt(i);
     } else {
       i++;
     }
   }
 
   if (!sent) {
     nsTArray<nsRect> dummy;
     nsCOMPtr<nsIRunnable> ev =
       new DelayedFireDOMPaintEvent(this, &dummy,
                                    aTransactionId, aTimeStamp);
     nsContentUtils::AddScriptRunner(ev);
   }
 
-  NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, aTimeStamp, false };
+  NotifyDidPaintSubdocumentCallbackClosure closure = { aTransactionId, aTimeStamp };
   mDocument->EnumerateSubDocuments(nsPresContext::NotifyDidPaintSubdocumentCallback, &closure);
-
-  if (!closure.mNeedsAnotherDidPaintNotification &&
-      mTransactions.IsEmpty()) {
-    // Nothing more to do for the moment.
-    mFireAfterPaintEvents = false;
-  }
 }
 
 bool
 nsPresContext::HasCachedStyleData()
 {
   if (!mShell) {
     return false;
   }
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -1286,16 +1286,22 @@ protected:
   bool HasCachedStyleData();
 
   // Creates a one-shot timer with the given aCallback & aDelay.
   // Returns a refcounted pointer to the timer (or nullptr on failure).
   already_AddRefed<nsITimer> CreateTimer(nsTimerCallbackFunc aCallback,
                                          const char* aName,
                                          uint32_t aDelay);
 
+  struct TransactionInvalidations {
+    uint64_t mTransactionId;
+    nsTArray<nsRect> mInvalidations;
+  };
+  TransactionInvalidations* GetInvalidations(uint64_t aTransactionId);
+
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   nsPresContextType     mType;
   // the nsPresShell owns a strong reference to the nsPresContext, and is responsible
   // for nulling this pointer before it is destroyed
   nsIPresShell* MOZ_NON_OWNING_REF mShell;         // [WEAK]
@@ -1354,20 +1360,16 @@ protected:
 
   nsCOMPtr<nsITheme> mTheme;
   nsLanguageAtomService* mLangService;
   nsCOMPtr<nsIPrintSettings> mPrintSettings;
   nsCOMPtr<nsITimer>    mPrefChangedTimer;
 
   mozilla::UniquePtr<nsBidi> mBidiEngine;
 
-  struct TransactionInvalidations {
-    uint64_t mTransactionId;
-    nsTArray<nsRect> mInvalidations;
-  };
   AutoTArray<TransactionInvalidations, 4> mTransactions;
 
   // text performance metrics
   nsAutoPtr<gfxTextPerfMetrics>   mTextPerf;
 
   nsAutoPtr<gfxMissingFontRecorder> mMissingFonts;
 
   nsRect                mVisibleArea;
@@ -1484,18 +1486,16 @@ protected:
   unsigned              mFontFeatureValuesDirty : 1;
 
   // resize reflow is suppressed when the only change has been to zoom
   // the document rather than to change the document's dimensions
   unsigned              mSuppressResizeReflow : 1;
 
   unsigned              mIsVisual : 1;
 
-  unsigned              mFireAfterPaintEvents : 1;
-
   unsigned              mIsChrome : 1;
   unsigned              mIsChromeOriginImage : 1;
 
   // Should we paint flash in this context? Do not use this variable directly.
   // Use GetPaintFlashing() method instead.
   mutable unsigned mPaintFlashing : 1;
   mutable unsigned mPaintFlashingInitialized : 1;
 
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -99,49 +99,55 @@ SelectAGRForFrame(nsIFrame* aFrame, Anim
 }
 
 // Removes any display items that belonged to a frame that was deleted,
 // and mark frames that belong to a different AGR so that get their
 // items built again.
 // TODO: We currently descend into all children even if we don't have an AGR
 // to mark, as child stacking contexts might. It would be nice if we could
 // jump into those immediately rather than walking the entire thing.
-void
+bool
 RetainedDisplayListBuilder::PreProcessDisplayList(nsDisplayList* aList,
                                                   AnimatedGeometryRoot* aAGR)
 {
+  bool modified = false;
   nsDisplayList saved;
   while (nsDisplayItem* i = aList->RemoveBottom()) {
     if (i->HasDeletedFrame() || !i->CanBeReused()) {
       i->Destroy(&mBuilder);
+      modified = true;
       continue;
     }
 
     nsIFrame* f = i->Frame();
 
     if (i->GetChildren()) {
-      PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR));
+      if (PreProcessDisplayList(i->GetChildren(), SelectAGRForFrame(f, aAGR))) {
+        modified = true;
+      }
     }
 
     // TODO: We should be able to check the clipped bounds relative
     // to the common AGR (of both the existing item and the invalidated
     // frame) and determine if they can ever intersect.
     if (aAGR && i->GetAnimatedGeometryRoot()->GetAsyncAGR() != aAGR) {
       mBuilder.MarkFrameForDisplayIfVisible(f, mBuilder.RootReferenceFrame());
+      modified = true;
     }
 
     // TODO: This is here because we sometimes reuse the previous display list
     // completely. For optimization, we could only restore the state for reused
     // display items.
     i->RestoreState();
 
     saved.AppendToTop(i);
   }
   aList->AppendToTop(&saved);
   aList->RestoreState();
+  return modified;
 }
 
 bool IsSameItem(nsDisplayItem* aFirst, nsDisplayItem* aSecond)
 {
   return aFirst->Frame() == aSecond->Frame() &&
          aFirst->GetPerFrameKey() == aSecond->GetPerFrameKey();
 }
 
@@ -194,42 +200,44 @@ void SwapAndRemove(nsTArray<T>& aArray, 
     T last = aArray.LastElement();
     aArray.LastElement() = aArray[aIndex];
     aArray[aIndex] = last;
   }
 
   aArray.RemoveElementAt(aArray.Length() - 1);
 }
 
-static void
+static bool
 MergeFrameRects(nsDisplayLayerEventRegions* aOldItem,
                 nsDisplayLayerEventRegions* aNewItem,
                 nsDisplayLayerEventRegions::FrameRects nsDisplayLayerEventRegions::*aRectList,
                 nsTArray<nsIFrame*>& aAddedFrames)
 {
+  bool modified = false;
   // Go through the old item's rect list and remove any rectangles
   // belonging to invalidated frames (deleted frames should
   // already be gone at this point)
   nsDisplayLayerEventRegions::FrameRects& oldRects = aOldItem->*aRectList;
   uint32_t i = 0;
   while (i < oldRects.mFrames.Length()) {
     // TODO: As mentioned in nsDisplayLayerEventRegions, this
     // operation might perform really poorly on a vector.
     nsIFrame* f = oldRects.mFrames[i];
     if (IsAnyAncestorModified(f)) {
       MOZ_ASSERT(f != aOldItem->Frame());
       f->RemoveDisplayItem(aOldItem);
       SwapAndRemove(oldRects.mFrames, i);
       SwapAndRemove(oldRects.mBoxes, i);
+      modified = true;
     } else {
       i++;
     }
   }
   if (!aNewItem) {
-    return;
+    return modified;
   }
 
   // Copy items from the source list to the dest list, but
   // only if the dest doesn't already include them.
   nsDisplayItem* destItem = aOldItem;
   nsDisplayLayerEventRegions::FrameRects* destRects = &(aOldItem->*aRectList);
   nsDisplayLayerEventRegions::FrameRects* srcRects = &(aNewItem->*aRectList);
 
@@ -240,46 +248,51 @@ MergeFrameRects(nsDisplayLayerEventRegio
       // then add it!
       destRects->Add(f, srcRects->mBoxes[i]);
 
       // We also need to update RealDisplayItemData for 'f',
       // but that'll mess up this check for the following
       // FrameRects lists, so defer that until the end.
       aAddedFrames.AppendElement(f);
       MOZ_ASSERT(f != aOldItem->Frame());
+
+      modified = true;
     }
 
   }
+  return modified;
 }
 
-void MergeLayerEventRegions(nsDisplayItem* aOldItem,
+bool MergeLayerEventRegions(nsDisplayItem* aOldItem,
                             nsDisplayItem* aNewItem)
 {
   nsDisplayLayerEventRegions* oldItem =
     static_cast<nsDisplayLayerEventRegions*>(aOldItem);
   nsDisplayLayerEventRegions* newItem =
     static_cast<nsDisplayLayerEventRegions*>(aNewItem);
 
   nsTArray<nsIFrame*> addedFrames;
 
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames);
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames);
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames);
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames);
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames);
-  MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames);
+  bool modified = false;
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHitRegion, addedFrames);
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mMaybeHitRegion, addedFrames);
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mDispatchToContentHitRegion, addedFrames);
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mNoActionRegion, addedFrames);
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mHorizontalPanRegion, addedFrames);
+  modified |= MergeFrameRects(oldItem, newItem, &nsDisplayLayerEventRegions::mVerticalPanRegion, addedFrames);
 
   // MergeFrameRects deferred updating the display item data list during
   // processing so that earlier calls didn't change the result of later
   // ones. Fix that up now.
   for (nsIFrame* f : addedFrames) {
     if (!f->HasDisplayItem(aOldItem)) {
       f->AddDisplayItem(aOldItem);
     }
   }
+  return modified;
 }
 
 void
 RetainedDisplayListBuilder::IncrementSubDocPresShellPaintCount(nsDisplayItem* aItem)
 {
   MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_SUBDOCUMENT);
 
   nsSubDocumentFrame* subDocFrame =
@@ -383,22 +396,24 @@ void UpdateASR(nsDisplayItem* aItem,
  * We then match A, add the new version the merged list and delete the old version.
  *
  * We then process the remainder of the old list, B is added (since it is valid,
  * and hasn't been mark as reused), C is destroyed since it's marked as reused and
  * is already present in the merged list.
  *
  * Merged List: C, A, B
  */
-void
+bool
 RetainedDisplayListBuilder::MergeDisplayLists(nsDisplayList* aNewList,
                                               nsDisplayList* aOldList,
                                               nsDisplayList* aOutList,
                                               Maybe<const ActiveScrolledRoot*>& aOutContainerASR)
 {
+  bool modified = false;
+
   nsDisplayList merged;
   const auto UseItem = [&](nsDisplayItem* aItem) {
     const ActiveScrolledRoot* itemClipASR =
       aItem->GetClipChain() ? aItem->GetClipChain()->mASR : nullptr;
 
     const ActiveScrolledRoot* finiteBoundsASR = ActiveScrolledRoot::PickDescendant(
       itemClipASR, aItem->GetActiveScrolledRoot());
     if (!aOutContainerASR) {
@@ -452,28 +467,32 @@ RetainedDisplayListBuilder::MergeDisplay
         // matched item.
         nsDisplayItem* old = nullptr;
         while ((old = aOldList->GetBottom()) && old != oldItem) {
           if (IsAnyAncestorModified(old->FrameForInvalidation())) {
             // The old item is invalid, discard it.
             oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() });
             aOldList->RemoveBottom();
             old->Destroy(&mBuilder);
+            modified = true;
           } else if (newListLookup.Get({ old->Frame(), old->GetPerFrameKey() })) {
             // This old item is also in the new list, but we haven't got to it yet.
             // Stop now, and we'll deal with it when we get to the new entry.
+            modified = true;
             break;
           } else {
             // Recurse into the child list (without a matching new list) to
             // ensure that we find and remove any invalidated items.
             if (old->GetChildren()) {
               nsDisplayList empty;
               Maybe<const ActiveScrolledRoot*> containerASRForChildren;
-              MergeDisplayLists(&empty, old->GetChildren(),
-                                old->GetChildren(), containerASRForChildren);
+              if (MergeDisplayLists(&empty, old->GetChildren(),
+                                    old->GetChildren(), containerASRForChildren)) {
+                modified = true;
+              }
               UpdateASR(old, containerASRForChildren);
               old->UpdateBounds(&mBuilder);
             }
             aOldList->RemoveBottom();
             ReuseItem(old);
           }
         }
         bool destroy = false;
@@ -493,38 +512,44 @@ RetainedDisplayListBuilder::MergeDisplay
         // the new one to the list.
         if (destroy &&
             oldItem->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
             !IsAnyAncestorModified(oldItem->FrameForInvalidation())) {
           // Event regions items don't have anything interesting other than
           // the lists of regions and frames, so we have no need to use the
           // newer item. Always use the old item instead since we assume it's
           // likely to have the bigger lists and merging will be quicker.
-          MergeLayerEventRegions(oldItem, newItem);
+          if (MergeLayerEventRegions(oldItem, newItem)) {
+            modified = true;
+          }
           ReuseItem(oldItem);
           newItem->Destroy(&mBuilder);
         } else {
-          if (!IsAnyAncestorModified(oldItem->FrameForInvalidation()) &&
-              oldItem->GetChildren()) {
+          if (IsAnyAncestorModified(oldItem->FrameForInvalidation())) {
+            modified = true;
+          } else if (oldItem->GetChildren()) {
             MOZ_ASSERT(newItem->GetChildren());
             Maybe<const ActiveScrolledRoot*> containerASRForChildren;
-            MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(),
-                              newItem->GetChildren(), containerASRForChildren);
+            if (MergeDisplayLists(newItem->GetChildren(), oldItem->GetChildren(),
+                                  newItem->GetChildren(), containerASRForChildren)) {
+              modified = true;
+            }
             UpdateASR(newItem, containerASRForChildren);
             newItem->UpdateBounds(&mBuilder);
           }
 
           if (destroy) {
             oldItem->Destroy(&mBuilder);
           }
           UseItem(newItem);
         }
       } else {
         // If there was no matching item in the old list, then we only need to
         // add the new item to the merged list.
+        modified = true;
         UseItem(newItem);
       }
     }
   }
 
   // Reuse the remaining valid items from the old display list.
   while (nsDisplayItem* old = aOldList->RemoveBottom()) {
     if (!IsAnyAncestorModified(old->FrameForInvalidation()) &&
@@ -532,31 +557,37 @@ RetainedDisplayListBuilder::MergeDisplay
       if (old->GetChildren()) {
         // We are calling MergeDisplayLists() to ensure that the display items
         // with modified or deleted children will be correctly handled.
         // Passing an empty new display list as an argument skips the merging
         // loop above and jumps back here.
         nsDisplayList empty;
         Maybe<const ActiveScrolledRoot*> containerASRForChildren;
 
-        MergeDisplayLists(&empty, old->GetChildren(),
-                          old->GetChildren(), containerASRForChildren);
+        if (MergeDisplayLists(&empty, old->GetChildren(),
+                              old->GetChildren(), containerASRForChildren)) {
+          modified = true;
+        }
         UpdateASR(old, containerASRForChildren);
         old->UpdateBounds(&mBuilder);
       }
       if (old->GetType() == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
-        MergeLayerEventRegions(old, nullptr);
+        if (MergeLayerEventRegions(old, nullptr)) {
+          modified = true;
+        }
       }
       ReuseItem(old);
     } else {
       old->Destroy(&mBuilder);
+      modified = true;
     }
   }
 
   aOutList->AppendToTop(&merged);
+  return modified;
 }
 
 static void
 TakeAndAddModifiedAndFramesWithPropsFromRootFrame(
   nsTArray<nsIFrame*>* aModifiedFrames,
   nsTArray<nsIFrame*>* aFramesWithProps,
   nsIFrame* aRootFrame)
 {
@@ -714,16 +745,18 @@ HandlePreserve3D(nsIFrame* aFrame, nsRec
 static void
 ProcessFrame(nsIFrame* aFrame, nsDisplayListBuilder& aBuilder,
              AnimatedGeometryRoot** aAGR, nsRect& aOverflow,
              nsIFrame* aStopAtFrame, nsTArray<nsIFrame*>& aOutFramesWithProps,
              const bool aStopAtStackingContext)
 {
   nsIFrame* currentFrame = aFrame;
 
+  aBuilder.MarkFrameForDisplayIfVisible(aFrame, aBuilder.RootReferenceFrame());
+
   while (currentFrame != aStopAtFrame) {
     CRR_LOG("currentFrame: %p (placeholder=%d), aOverflow: %d %d %d %d\n",
              currentFrame, !aStopAtStackingContext,
              aOverflow.x, aOverflow.y, aOverflow.width, aOverflow.height);
 
     currentFrame = HandlePreserve3D(currentFrame, aOverflow);
 
     // If the current frame is an OOF frame, DisplayListBuildingData needs to be
@@ -764,16 +797,20 @@ ProcessFrame(nsIFrame* aFrame, nsDisplay
 
     // Convert 'aOverflow' into the coordinate space of the nearest stacking context
     // or display port ancestor and update 'currentFrame' to point to that frame.
     aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame,
                                                            nullptr, nullptr,
                                                            /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
                                                            &currentFrame);
     MOZ_ASSERT(currentFrame);
+    aOverflow.IntersectRect(aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf());
+    if (aOverflow.IsEmpty()) {
+      break;
+    }
 
     if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
       CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
       nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
       MOZ_ASSERT(sf);
       nsRect displayPort;
       DebugOnly<bool> hasDisplayPort =
         nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort,
@@ -920,28 +957,30 @@ RetainedDisplayListBuilder::ComputeRebui
     // might have an empty overflow rect.
     if (f == mBuilder.GetCaretFrame()) {
       overflow.UnionRect(overflow, mBuilder.GetCaretRect());
     }
 
     ProcessFrame(f, mBuilder, &agr, overflow, mBuilder.RootReferenceFrame(),
                  aOutFramesWithProps, true);
 
-    aOutDirty->UnionRect(*aOutDirty, overflow);
-    CRR_LOG("Adding area to root draw area: %d %d %d %d\n",
-            overflow.x, overflow.y, overflow.width, overflow.height);
+    if (!overflow.IsEmpty()) {
+      aOutDirty->UnionRect(*aOutDirty, overflow);
+      CRR_LOG("Adding area to root draw area: %d %d %d %d\n",
+              overflow.x, overflow.y, overflow.width, overflow.height);
 
-    // If we get changed frames from multiple AGRS, then just give up as it gets really complex to
-    // track which items would need to be marked in MarkFramesForDifferentAGR.
-    if (!*aOutModifiedAGR) {
-      CRR_LOG("Setting %p as root stacking context AGR\n", agr);
-      *aOutModifiedAGR = agr;
-    } else if (agr && *aOutModifiedAGR != agr) {
-      CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
-      return false;
+      // If we get changed frames from multiple AGRS, then just give up as it gets really complex to
+      // track which items would need to be marked in MarkFramesForDifferentAGR.
+      if (!*aOutModifiedAGR) {
+        CRR_LOG("Setting %p as root stacking context AGR\n", agr);
+        *aOutModifiedAGR = agr;
+      } else if (agr && *aOutModifiedAGR != agr) {
+        CRR_LOG("Found multiple AGRs in root stacking context, giving up\n");
+        return false;
+      }
     }
   }
 
   return true;
 }
 
 /*
  * A simple early exit heuristic to avoid slow partial display list rebuilds.
@@ -984,117 +1023,134 @@ ClearFrameProps(nsTArray<nsIFrame*>& aFr
       f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingRect());
       f->DeleteProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
     }
 
     f->SetFrameIsModified(false);
   }
 }
 
+class AutoClearFramePropsArray
+{
+public:
+  AutoClearFramePropsArray() = default;
+
+  ~AutoClearFramePropsArray()
+  {
+    ClearFrameProps(mFrames);
+  }
+
+  nsTArray<nsIFrame*>& Frames() { return mFrames; }
+
+  bool IsEmpty() const { return mFrames.IsEmpty(); }
+
+private:
+  nsTArray<nsIFrame*> mFrames;
+};
+
 void
 RetainedDisplayListBuilder::ClearFramesWithProps()
 {
-  nsTArray<nsIFrame*> modifiedFrames;
-  nsTArray<nsIFrame*> framesWithProps;
-  GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames, &framesWithProps);
-
-  ClearFrameProps(modifiedFrames);
-  ClearFrameProps(framesWithProps);
+  AutoClearFramePropsArray modifiedFrames;
+  AutoClearFramePropsArray framesWithProps;
+  GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
 }
 
-bool
+auto
 RetainedDisplayListBuilder::AttemptPartialUpdate(
   nscolor aBackstop,
-  mozilla::DisplayListChecker* aChecker)
+  mozilla::DisplayListChecker* aChecker) -> PartialUpdateResult
 {
   mBuilder.RemoveModifiedWindowRegions();
   mBuilder.ClearWindowOpaqueRegion();
 
   if (mBuilder.ShouldSyncDecodeImages()) {
     MarkFramesWithItemsAndImagesModified(&mList);
   }
 
   mBuilder.EnterPresShell(mBuilder.RootReferenceFrame());
 
-  nsTArray<nsIFrame*> modifiedFrames;
-  nsTArray<nsIFrame*> framesWithProps;
-  GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames, &framesWithProps);
+  // We set the override dirty regions during ComputeRebuildRegion or in
+  // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also
+  // marks the frame modified, so those regions are cleared here as well.
+  AutoClearFramePropsArray modifiedFrames;
+  AutoClearFramePropsArray framesWithProps;
+  GetModifiedAndFramesWithProps(&mBuilder, &modifiedFrames.Frames(), &framesWithProps.Frames());
 
   // Do not allow partial builds if the retained display list is empty, or if
   // ShouldBuildPartial heuristic fails.
-  const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames);
+  const bool shouldBuildPartial = !mList.IsEmpty() && ShouldBuildPartial(modifiedFrames.Frames());
 
   if (mPreviousCaret != mBuilder.GetCaretFrame()) {
     if (mPreviousCaret) {
       if (mBuilder.MarkFrameModifiedDuringBuilding(mPreviousCaret)) {
-        modifiedFrames.AppendElement(mPreviousCaret);
+        modifiedFrames.Frames().AppendElement(mPreviousCaret);
       }
     }
 
     if (mBuilder.GetCaretFrame()) {
       if (mBuilder.MarkFrameModifiedDuringBuilding(mBuilder.GetCaretFrame())) {
-        modifiedFrames.AppendElement(mBuilder.GetCaretFrame());
+        modifiedFrames.Frames().AppendElement(mBuilder.GetCaretFrame());
       }
     }
 
     mPreviousCaret = mBuilder.GetCaretFrame();
   }
 
   nsRect modifiedDirty;
   AnimatedGeometryRoot* modifiedAGR = nullptr;
-  bool merged = false;
-  if (shouldBuildPartial &&
-      ComputeRebuildRegion(modifiedFrames, &modifiedDirty,
-                           &modifiedAGR, framesWithProps)) {
-    modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
-
-    PreProcessDisplayList(&mList, modifiedAGR);
-
-    nsDisplayList modifiedDL;
-    if (!modifiedDirty.IsEmpty() || !framesWithProps.IsEmpty()) {
-      mBuilder.SetDirtyRect(modifiedDirty);
-      mBuilder.SetPartialUpdate(true);
-      mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
-      nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
-                                             nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
-                                             mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
-                                             aBackstop);
-      mBuilder.SetPartialUpdate(false);
-
-      //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
-      //      modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
-      //nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
+  if (!shouldBuildPartial ||
+      !ComputeRebuildRegion(modifiedFrames.Frames(), &modifiedDirty,
+                           &modifiedAGR, framesWithProps.Frames())) {
+    mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
+    return PartialUpdateResult::Failed;
+  }
 
-    } else {
-      // TODO: We can also skip layer building and painting if
-      // PreProcessDisplayList didn't end up changing anything
-      // Invariant: display items should have their original state here.
-      // printf_stderr("Skipping display list building since nothing needed to be done\n");
-    }
-
-    if (aChecker) {
-      aChecker->Set(&modifiedDL, "TM");
-    }
+  modifiedDirty.IntersectRect(modifiedDirty, mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf());
 
-    // |modifiedDL| can sometimes be empty here. We still perform the
-    // display list merging to prune unused items (for example, items that
-    // are not visible anymore) from the old list.
-    // TODO: Optimization opportunity. In this case, MergeDisplayLists()
-    // unnecessarily creates a hashtable of the old items.
-    Maybe<const ActiveScrolledRoot*> dummy;
-    MergeDisplayLists(&modifiedDL, &mList, &mList, dummy);
-
-    //printf_stderr("Painting --- Merged list:\n");
-    //nsFrame::PrintDisplayList(&mBuilder, mList);
-
-    merged = true;
+  PartialUpdateResult result = PartialUpdateResult::NoChange;
+  if (PreProcessDisplayList(&mList, modifiedAGR) ||
+      !modifiedDirty.IsEmpty() ||
+      !framesWithProps.IsEmpty()) {
+    result = PartialUpdateResult::Updated;
   }
 
-  mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
+  mBuilder.SetDirtyRect(modifiedDirty);
+  mBuilder.SetPartialUpdate(true);
+
+  nsDisplayList modifiedDL;
+  mBuilder.RootReferenceFrame()->BuildDisplayListForStackingContext(&mBuilder, &modifiedDL);
+  if (!modifiedDL.IsEmpty()) {
+    nsLayoutUtils::AddExtraBackgroundItems(mBuilder, modifiedDL, mBuilder.RootReferenceFrame(),
+                                           nsRect(nsPoint(0, 0), mBuilder.RootReferenceFrame()->GetSize()),
+                                           mBuilder.RootReferenceFrame()->GetVisualOverflowRectRelativeToSelf(),
+                                           aBackstop);
+  }
+  mBuilder.SetPartialUpdate(false);
+
+  if (aChecker) {
+    aChecker->Set(&modifiedDL, "TM");
+  }
+
+  //printf_stderr("Painting --- Modified list (dirty %d,%d,%d,%d):\n",
+  //              modifiedDirty.x, modifiedDirty.y, modifiedDirty.width, modifiedDirty.height);
+  //nsFrame::PrintDisplayList(&mBuilder, modifiedDL);
 
-  // We set the override dirty regions during ComputeRebuildRegion or in
-  // nsLayoutUtils::InvalidateForDisplayPortChange. The display port change also
-  // marks the frame modified, so those regions are cleared here as well.
-  ClearFrameProps(modifiedFrames);
-  ClearFrameProps(framesWithProps);
+  // |modifiedDL| can sometimes be empty here. We still perform the
+  // display list merging to prune unused items (for example, items that
+  // are not visible anymore) from the old list.
+  // TODO: Optimization opportunity. In this case, MergeDisplayLists()
+  // unnecessarily creates a hashtable of the old items.
+  // TODO: Ideally we could skip this if result is NoChange, but currently when
+  // we call RestoreState on nsDisplayWrapList it resets the clip to the base
+  // clip, and we need the UpdateBounds call (within MergeDisplayLists) to
+  // move it to the correct inner clip.
+  Maybe<const ActiveScrolledRoot*> dummy;
+  if (MergeDisplayLists(&modifiedDL, &mList, &mList, dummy)) {
+    result = PartialUpdateResult::Updated;
+  }
 
-  return merged;
+  //printf_stderr("Painting --- Merged list:\n");
+  //nsFrame::PrintDisplayList(&mBuilder, mList);
+
+  mBuilder.LeavePresShell(mBuilder.RootReferenceFrame(), &mList);
+  return result;
 }
--- a/layout/painting/RetainedDisplayListBuilder.h
+++ b/layout/painting/RetainedDisplayListBuilder.h
@@ -24,33 +24,39 @@ struct RetainedDisplayListBuilder {
   {
     mList.DeleteAll(&mBuilder);
   }
 
   nsDisplayListBuilder* Builder() { return &mBuilder; }
 
   nsDisplayList* List() { return &mList; }
 
-  bool AttemptPartialUpdate(nscolor aBackstop,
-                            mozilla::DisplayListChecker* aChecker);
+  enum class PartialUpdateResult {
+    Failed,
+    NoChange,
+    Updated
+  };
+
+  PartialUpdateResult AttemptPartialUpdate(nscolor aBackstop,
+                                           mozilla::DisplayListChecker* aChecker);
 
   /**
    * Iterates through the display list builder reference frame document and
    * subdocuments, and clears the modified frame lists from the root frames.
    * Also clears the frame properties set by RetainedDisplayListBuilder for all
    * the frames in the modified frame lists.
    */
   void ClearFramesWithProps();
 
   NS_DECLARE_FRAME_PROPERTY_DELETABLE(Cached, RetainedDisplayListBuilder)
 
 private:
-  void PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR);
+  bool PreProcessDisplayList(nsDisplayList* aList, AnimatedGeometryRoot* aAGR);
 
-  void MergeDisplayLists(nsDisplayList* aNewList,
+  bool MergeDisplayLists(nsDisplayList* aNewList,
                          nsDisplayList* aOldList,
                          nsDisplayList* aOutList,
                          mozilla::Maybe<const mozilla::ActiveScrolledRoot*>& aOutContainerASR);
 
   bool ComputeRebuildRegion(nsTArray<nsIFrame*>& aModifiedFrames,
                             nsRect* aOutDirty,
                             AnimatedGeometryRoot** aOutModifiedAGR,
                             nsTArray<nsIFrame*>& aOutFramesWithProps);
--- a/layout/painting/nsDisplayItemTypesList.h
+++ b/layout/painting/nsDisplayItemTypesList.h
@@ -49,17 +49,17 @@ DECLARE_DISPLAY_ITEM_TYPE(PRINT_PLUGIN, 
 DECLARE_DISPLAY_ITEM_TYPE(RANGE_FOCUS_RING, 0)
 DECLARE_DISPLAY_ITEM_TYPE(REMOTE, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(RESOLUTION, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SCROLL_INFO_LAYER, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SELECTION_OVERLAY, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SOLID_COLOR_REGION, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SUBDOCUMENT, TYPE_RENDERS_NO_IMAGES)
-DECLARE_DISPLAY_ITEM_TYPE(MASK, TYPE_RENDERS_NO_IMAGES)
+DECLARE_DISPLAY_ITEM_TYPE(MASK, 0)
 DECLARE_DISPLAY_ITEM_TYPE(FILTER, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_OUTER_SVG, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_GEOMETRY, 0)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_TEXT, 0)
 DECLARE_DISPLAY_ITEM_TYPE(SVG_WRAPPER, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_BACKGROUND, 0)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_CELL_SELECTION, TYPE_RENDERS_NO_IMAGES)
 DECLARE_DISPLAY_ITEM_TYPE(TABLE_BORDER_COLLAPSE, 0)
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -2415,16 +2415,110 @@ nsDisplayListBuilder::GetWidgetLayerMana
   }
   nsIWidget* window = RootReferenceFrame()->GetNearestWidget();
   if (window) {
     return window->GetLayerManager();
   }
   return nullptr;
 }
 
+FrameLayerBuilder*
+nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder,
+                           LayerManager* aLayerManager,
+                           uint32_t aFlags,
+                           bool aIsWidgetTransaction)
+{
+  nsIFrame* frame = aBuilder->RootReferenceFrame();
+  nsPresContext* presContext = frame->PresContext();
+  nsIPresShell* presShell = presContext->PresShell();
+
+  FrameLayerBuilder *layerBuilder = new FrameLayerBuilder();
+  layerBuilder->Init(aBuilder, aLayerManager);
+
+  if (aFlags & PAINT_COMPRESSED) {
+    layerBuilder->SetLayerTreeCompressionMode();
+  }
+
+  RefPtr<ContainerLayer> root;
+  {
+    AUTO_PROFILER_TRACING("Paint", "LayerBuilding");
+
+    if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
+      FrameLayerBuilder::InvalidateAllLayers(aLayerManager);
+    }
+
+    if (aIsWidgetTransaction) {
+      layerBuilder->DidBeginRetainedLayerTransaction(aLayerManager);
+    }
+
+    // Clear any ScrollMetadata that may have been set on the root layer on a
+    // previous paint. This paint will set new metrics if necessary, and if we
+    // don't clear the old one here, we may be left with extra metrics.
+    if (Layer* rootLayer = aLayerManager->GetRoot()) {
+      rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>());
+    }
+
+    ContainerLayerParameters containerParameters
+      (presShell->GetResolution(), presShell->GetResolution());
+
+    {
+      PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
+
+      root = layerBuilder->
+        BuildContainerLayerFor(aBuilder, aLayerManager, frame, nullptr, this,
+                               containerParameters, nullptr);
+
+      if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) {
+        if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(aLayerManager)) {
+          pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
+        }
+      }
+    }
+
+    if (!root) {
+      return nullptr;
+    }
+    // Root is being scaled up by the X/Y resolution. Scale it back down.
+    root->SetPostScale(1.0f/containerParameters.mXScale,
+                       1.0f/containerParameters.mYScale);
+    root->SetScaleToResolution(presShell->ScaleToResolution(),
+        containerParameters.mXScale);
+
+    auto callback = [root](FrameMetrics::ViewID aScrollId) -> bool {
+      return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId);
+    };
+    if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata(
+          aBuilder, root->Manager(), containerParameters, callback)) {
+      root->SetScrollMetadata(rootMetadata.value());
+    }
+
+    // NS_WARNING is debug-only, so don't even bother checking the conditions in
+    // a release build.
+#ifdef DEBUG
+    bool usingDisplayport = false;
+    if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
+      nsIContent* content = rootScrollFrame->GetContent();
+      if (content) {
+        usingDisplayport = nsLayoutUtils::HasDisplayPort(content);
+      }
+    }
+    if (usingDisplayport &&
+        !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
+        SpammyLayoutWarningsEnabled()) {
+      // See bug 693938, attachment 567017
+      NS_WARNING("Transparent content with displayports can be expensive.");
+    }
+#endif
+
+    aLayerManager->SetRoot(root);
+    layerBuilder->WillEndTransaction();
+  }
+  return layerBuilder;
+}
+
 /**
  * We paint by executing a layer manager transaction, constructing a
  * single layer representing the display list, and then making it the
  * root of the layer manager, drawing into the PaintedLayers.
  */
 already_AddRefed<LayerManager> nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder,
                                                         gfxContext* aCtx,
                                                         uint32_t aFlags)
@@ -2507,177 +2601,111 @@ already_AddRefed<LayerManager> nsDisplay
 
     return layerManager.forget();
   }
 
   NotifySubDocInvalidationFunc computeInvalidFunc =
     presContext->MayHavePaintEventListenerInSubDocument() ? nsPresContext::NotifySubDocInvalidation : 0;
 
   UniquePtr<LayerProperties> props;
-  RefPtr<ContainerLayer> root;
-
-  // Store the existing layer builder to reinstate it on return.
-  FrameLayerBuilder *oldBuilder = layerManager->GetLayerBuilder();
-
-  FrameLayerBuilder *layerBuilder = new FrameLayerBuilder();
-  layerBuilder->Init(aBuilder, layerManager);
-
-  if (aFlags & PAINT_COMPRESSED) {
-    layerBuilder->SetLayerTreeCompressionMode();
-  }
-
-  {
-    AUTO_PROFILER_TRACING("Paint", "LayerBuilding");
-
-    if (doBeginTransaction) {
-      if (aCtx) {
-        if (!layerManager->BeginTransactionWithTarget(aCtx)) {
-          return nullptr;
-        }
-      } else {
-        if (!layerManager->BeginTransaction()) {
-          return nullptr;
-        }
+
+  bool computeInvalidRect = (computeInvalidFunc ||
+                             (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) &&
+                            widgetTransaction;
+
+  if (computeInvalidRect) {
+    props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
+  }
+
+  if (doBeginTransaction) {
+    if (aCtx) {
+      if (!layerManager->BeginTransactionWithTarget(aCtx)) {
+        return nullptr;
       }
-    }
-
-    if (XRE_IsContentProcess() && gfxPrefs::AlwaysPaint()) {
-      FrameLayerBuilder::InvalidateAllLayers(layerManager);
-    }
-
-    if (widgetTransaction) {
-      layerBuilder->DidBeginRetainedLayerTransaction(layerManager);
-    }
-
-    bool computeInvalidRect = (computeInvalidFunc ||
-                               (!layerManager->IsCompositingCheap() && layerManager->NeedsWidgetInvalidation())) &&
-                              widgetTransaction;
-
-    if (computeInvalidRect) {
-      props = Move(LayerProperties::CloneFrom(layerManager->GetRoot()));
-    }
-
-    // Clear any ScrollMetadata that may have been set on the root layer on a
-    // previous paint. This paint will set new metrics if necessary, and if we
-    // don't clear the old one here, we may be left with extra metrics.
-    if (Layer* rootLayer = layerManager->GetRoot()) {
-      rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>());
-    }
-
-    ContainerLayerParameters containerParameters
-      (presShell->GetResolution(), presShell->GetResolution());
-
-    {
-      PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization);
-
-      root = layerBuilder->
-        BuildContainerLayerFor(aBuilder, layerManager, frame, nullptr, this,
-                               containerParameters, nullptr);
-
-      if (!record.GetStart().IsNull() && gfxPrefs::LayersDrawFPS()) {
-        if (PaintTiming* pt = ClientLayerManager::MaybeGetPaintTiming(layerManager)) {
-          pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds();
-        }
+    } else {
+      if (!layerManager->BeginTransaction()) {
+        return nullptr;
       }
     }
-
-    if (!root) {
-      layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
-      return nullptr;
-    }
-    // Root is being scaled up by the X/Y resolution. Scale it back down.
-    root->SetPostScale(1.0f/containerParameters.mXScale,
-                       1.0f/containerParameters.mYScale);
-    root->SetScaleToResolution(presShell->ScaleToResolution(),
-        containerParameters.mXScale);
-
-    auto callback = [root](FrameMetrics::ViewID aScrollId) -> bool {
-      return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId);
-    };
-    if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata(
-          aBuilder, root->Manager(), containerParameters, callback)) {
-      root->SetScrollMetadata(rootMetadata.value());
-    }
-
-    // NS_WARNING is debug-only, so don't even bother checking the conditions in
-    // a release build.
-#ifdef DEBUG
-    bool usingDisplayport = false;
-    if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
-      nsIContent* content = rootScrollFrame->GetContent();
-      if (content) {
-        usingDisplayport = nsLayoutUtils::HasDisplayPort(content);
-      }
-    }
-    if (usingDisplayport &&
-        !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) &&
-        SpammyLayoutWarningsEnabled()) {
-      // See bug 693938, attachment 567017
-      NS_WARNING("Transparent content with displayports can be expensive.");
-    }
-#endif
-
-    layerManager->SetRoot(root);
-    layerBuilder->WillEndTransaction();
-  }
-
-  if (widgetTransaction ||
-      // SVG-as-an-image docs don't paint as part of the retained layer tree,
-      // but they still need the invalidation state bits cleared in order for
-      // invalidation for CSS/SMIL animation to work properly.
-      (document && document->IsBeingUsedAsImage())) {
-    frame->ClearInvalidationStateBits();
   }
 
   bool temp = aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap());
   LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT;
   if (layerManager->NeedsWidgetInvalidation()) {
     if (aFlags & PAINT_NO_COMPOSITE) {
       flags = LayerManager::END_NO_COMPOSITE;
     }
   } else {
     // Client layer managers never composite directly, so
     // we don't need to worry about END_NO_COMPOSITE.
     if (aBuilder->WillComputePluginGeometry()) {
       flags = LayerManager::END_NO_REMOTE_COMPOSITE;
     }
   }
 
-  // If this is the content process, we ship plugin geometry updates over with layer
-  // updates, so calculate that now before we call EndTransaction.
-  nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
-  if (rootPresContext && XRE_IsContentProcess()) {
-    if (aBuilder->WillComputePluginGeometry()) {
-      rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
-    }
-    // The layer system caches plugin configuration information for forwarding
-    // with layer updates which needs to get set during reflow. This must be
-    // called even if there are no windowed plugins in the page.
-    rootPresContext->CollectPluginGeometryUpdates(layerManager);
-  }
-
   MaybeSetupTransactionIdAllocator(layerManager, presContext);
 
-  layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
-                               aBuilder, flags);
+  // Store the existing layer builder to reinstate it on return.
+  FrameLayerBuilder *oldBuilder = layerManager->GetLayerBuilder();
+  FrameLayerBuilder *layerBuilder = nullptr;
+
+  bool sent = false;
+  if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) {
+    sent = layerManager->EndEmptyTransaction(flags);
+  }
+
+  if (!sent) {
+    layerBuilder = BuildLayers(aBuilder, layerManager,
+                               aFlags, widgetTransaction);
+
+    if (!layerBuilder) {
+      layerManager->SetUserData(&gLayerManagerLayerBuilder, oldBuilder);
+      return nullptr;
+    }
+
+    // If this is the content process, we ship plugin geometry updates over with layer
+    // updates, so calculate that now before we call EndTransaction.
+    nsRootPresContext* rootPresContext = presContext->GetRootPresContext();
+    if (rootPresContext && XRE_IsContentProcess()) {
+      if (aBuilder->WillComputePluginGeometry()) {
+        rootPresContext->ComputePluginGeometryUpdates(aBuilder->RootReferenceFrame(), aBuilder, this);
+      }
+      // The layer system caches plugin configuration information for forwarding
+      // with layer updates which needs to get set during reflow. This must be
+      // called even if there are no windowed plugins in the page.
+      rootPresContext->CollectPluginGeometryUpdates(layerManager);
+    }
+
+    layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer,
+                                 aBuilder, flags);
+    layerBuilder->DidEndTransaction();
+  }
+
+  if (widgetTransaction ||
+      // SVG-as-an-image docs don't paint as part of the retained layer tree,
+      // but they still need the invalidation state bits cleared in order for
+      // invalidation for CSS/SMIL animation to work properly.
+      (document && document->IsBeingUsedAsImage())) {
+    frame->ClearInvalidationStateBits();
+  }
+
   aBuilder->SetIsCompositingCheap(temp);
-  layerBuilder->DidEndTransaction();
 
   if (document && widgetTransaction) {
     TriggerPendingAnimations(document, layerManager->GetAnimationReadyTime());
   }
 
   nsIntRegion invalid;
   bool areaOverflowed = false;
   if (props) {
-    if (!props->ComputeDifferences(root, invalid, computeInvalidFunc)) {
+    if (!props->ComputeDifferences(layerManager->GetRoot(), invalid, computeInvalidFunc)) {
       areaOverflowed = true;
     }
   } else if (widgetTransaction) {
-    LayerProperties::ClearInvalidations(root);
+    LayerProperties::ClearInvalidations(layerManager->GetRoot());
   }
 
   bool shouldInvalidate = layerManager->NeedsWidgetInvalidation();
   if (view) {
     if (props && !areaOverflowed) {
       if (!invalid.IsEmpty()) {
         nsIntRect bounds = invalid.GetBounds();
         nsRect rect(presContext->DevPixelsToAppUnits(bounds.x),
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -3059,21 +3059,27 @@ public:
    * We return the layer manager used for painting --- mainly so that
    * callers can dump its layer tree if necessary.
    */
   enum {
     PAINT_DEFAULT = 0,
     PAINT_USE_WIDGET_LAYERS = 0x01,
     PAINT_EXISTING_TRANSACTION = 0x04,
     PAINT_NO_COMPOSITE = 0x08,
-    PAINT_COMPRESSED = 0x10
+    PAINT_COMPRESSED = 0x10,
+    PAINT_IDENTICAL_DISPLAY_LIST = 0x20
   };
   already_AddRefed<LayerManager> PaintRoot(nsDisplayListBuilder* aBuilder,
                                            gfxContext* aCtx,
                                            uint32_t aFlags);
+
+  mozilla::FrameLayerBuilder* BuildLayers(nsDisplayListBuilder* aBuilder,
+                                          LayerManager* aLayerManager,
+                                          uint32_t aFlags,
+                                          bool aIsWidgetTransaction);
   /**
    * Get the bounds. Takes the union of the bounds of all children.
    * The result is not cached.
    */
   nsRect GetBounds(nsDisplayListBuilder* aBuilder) const;
 
   /**
    * Get this list's bounds, respecting clips relative to aASR. The result is
@@ -4732,17 +4738,17 @@ private:
   {
     for (nsIFrame* f : aFrameRects.mFrames) {
       if (f != mFrame) {
         f->RemoveDisplayItem(this);
       }
     }
   }
 
-  friend void MergeLayerEventRegions(nsDisplayItem*, nsDisplayItem*);
+  friend bool MergeLayerEventRegions(nsDisplayItem*, nsDisplayItem*);
 
   // Relative to aFrame's reference frame.
   // These are the points that are definitely in the hit region.
   FrameRects mHitRegion;
   // These are points that may or may not be in the hit region. Only main-thread
   // event handling can tell for sure (e.g. because complex shapes are present).
   FrameRects mMaybeHitRegion;
   // These are points that need to be dispatched to the content thread for
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1436189-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html lang="en">
+<body>
+<div style="opacity:0.5" id="hi">
+  <div style="position:fixed; width:200px; height:200px; background-color:blue"></div>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1436189-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html lang="en" class="reftest-wait">
+<body>
+<div style="opacity:0.5" id="hi">
+  <div style="position:fixed; width:200px; height:200px; background-color:blue"></div>
+</div>
+
+<script>
+
+function doTest()
+{
+  var opacityElement = document.getElementById("hi");
+  opacityElement.style.left = '100px';
+  document.documentElement.removeAttribute("class");
+}
+
+document.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
--- a/layout/reftests/display-list/reftest.list
+++ b/layout/reftests/display-list/reftest.list
@@ -14,8 +14,9 @@ fuzzy(1,235200) == 1413073.html 1413073-
 == 1416291.html 1416291-ref.html
 == 1417601-1.html 1417601-1-ref.html
 == 1418945-1.html 1418945-1-ref.html
 skip-if(Android) == 1428993-1.html 1428993-1-ref.html
 == 1428993-2.html 1428993-2-ref.html
 needs-focus == 1429027-1.html 1429027-1-ref.html
 == 1432553-1.html 1432553-1-ref.html
 == 1432553-2.html 1432553-2-ref.html
+== 1436189-1.html 1436189-1-ref.html
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -1547,16 +1547,17 @@ pref("javascript.options.showInConsole",
 
 pref("javascript.options.shared_memory", false);
 
 pref("javascript.options.throw_on_debuggee_would_run", false);
 pref("javascript.options.dump_stack_on_debuggee_would_run", false);
 
 // Spectre security vulnerability mitigations.
 pref("javascript.options.spectre.index_masking", true);
+pref("javascript.options.spectre.string_mitigations", false);
 
 // Streams API
 pref("javascript.options.streams", false);
 
 // advanced prefs
 pref("advanced.mailftp",                    false);
 pref("image.animation_mode",                "normal");
 
--- a/toolkit/content/process-content.js
+++ b/toolkit/content/process-content.js
@@ -44,21 +44,23 @@ if (gInContentProcess) {
       if (msg.name != "Memory:GetSummary") {
         return;
       }
       let pid = Services.appinfo.processID;
       let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                      .getService(Ci.nsIMemoryReporterManager);
       let rss = memMgr.resident;
       let uss = memMgr.residentUnique;
+      let ghosts = memMgr.ghostWindows;
       Services.cpmm.sendAsyncMessage("Memory:Summary", {
         pid,
         summary: {
           uss,
           rss,
+          ghosts,
         }
       });
     },
 
     observe(subject, topic, data) {
       switch (topic) {
         case "inner-window-destroyed": {
           // Forward inner-window-destroyed notifications with the
--- a/toolkit/modules/Memory.jsm
+++ b/toolkit/modules/Memory.jsm
@@ -17,20 +17,22 @@ this.Memory = {
    * process.
    * @returns Promise
    * @resolves JS Object
    * An Object in the following format:
    * {
    *   "parent": {
    *     uss: <int>,
    *     rss: <int>,
+   *     ghosts: <int>,
    *   },
    *   <pid>: {
    *     uss: <int>,
    *     rss: <int>,
+   *     ghosts: <int>,
    *   },
    *   ...
    * }
    */
   summary() {
     if (!this._pendingPromise) {
       this._pendingPromise = new Promise((resolve) => {
         this._pendingResolve = resolve;
@@ -60,17 +62,18 @@ this.Memory = {
 
   finish() {
     // Code to gather the USS and RSS values for the parent process. This
     // functions the same way as in process-content.js.
     let memMgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                    .getService(Ci.nsIMemoryReporterManager);
     let rss = memMgr.resident;
     let uss = memMgr.residentUnique;
-    this._summaries.Parent = { uss, rss };
+    let ghosts = memMgr.ghostWindows;
+    this._summaries.Parent = { uss, rss, ghosts };
     this._pendingResolve(this._summaries);
     this._pendingResolve = null;
     this._summaries = null;
     this._pendingPromise = null;
     clearTimeout(this._pendingTimeout);
     Services.ppmm.removeMessageListener("Memory:Summary", this);
   }
 };
--- a/widget/gtk/nsDragService.cpp
+++ b/widget/gtk/nsDragService.cpp
@@ -300,32 +300,34 @@ GetGtkWindow(nsIDOMDocument *aDocument)
 
     return GTK_WINDOW(toplevel);
 }
 
 // nsIDragService
 
 NS_IMETHODIMP
 nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
+                                 const nsACString& aPrincipalURISpec,
                                  nsIArray * aArrayTransferables,
                                  nsIScriptableRegion * aRegion,
                                  uint32_t aActionType,
                                  nsContentPolicyType aContentPolicyType =
                                    nsIContentPolicy::TYPE_OTHER)
 {
     MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession"));
 
     // If the previous source drag has not yet completed, signal handlers need
     // to be removed from sGrabWidget and dragend needs to be dispatched to
     // the source node, but we can't call EndDragSession yet because we don't
     // know whether or not the drag succeeded.
     if (mSourceNode)
         return NS_ERROR_NOT_AVAILABLE;
 
-    return nsBaseDragService::InvokeDragSession(aDOMNode, aArrayTransferables,
+    return nsBaseDragService::InvokeDragSession(aDOMNode, aPrincipalURISpec,
+                                                aArrayTransferables,
                                                 aRegion, aActionType,
                                                 aContentPolicyType);
 }
 
 // nsBaseDragService
 nsresult
 nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
                                      nsIScriptableRegion* aRegion,
--- a/widget/gtk/nsDragService.h
+++ b/widget/gtk/nsDragService.h
@@ -59,16 +59,17 @@ public:
     NS_DECL_NSIOBSERVER
 
     // nsBaseDragService
     virtual nsresult InvokeDragSessionImpl(nsIArray* anArrayTransferables,
                                            nsIScriptableRegion* aRegion,
                                            uint32_t aActionType) override;
     // nsIDragService
     NS_IMETHOD InvokeDragSession (nsIDOMNode *aDOMNode,
+                                  const nsACString& aPrincipalURISpec,
                                   nsIArray * anArrayTransferables,
                                   nsIScriptableRegion * aRegion,
                                   uint32_t aActionType,
                                   nsContentPolicyType aContentPolicyType) override;
     NS_IMETHOD StartDragSession() override;
     NS_IMETHOD EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) override;
 
     // nsIDragSession
--- a/widget/nsBaseDragService.cpp
+++ b/widget/nsBaseDragService.cpp
@@ -164,16 +164,29 @@ NS_IMETHODIMP
 nsBaseDragService::GetSourceNode(nsIDOMNode** aSourceNode)
 {
   *aSourceNode = mSourceNode.get();
   NS_IF_ADDREF(*aSourceNode);
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsBaseDragService::GetTriggeringPrincipalURISpec(nsACString& aPrincipalURISpec)
+{
+  aPrincipalURISpec = mTriggeringPrincipalURISpec;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBaseDragService::SetTriggeringPrincipalURISpec(const nsACString& aPrincipalURISpec)
+{
+  mTriggeringPrincipalURISpec = aPrincipalURISpec;
+  return NS_OK;
+}
 
 //-------------------------------------------------------------------------
 
 NS_IMETHODIMP
 nsBaseDragService::GetData(nsITransferable * aTransferable,
                            uint32_t aItemIndex)
 {
   return NS_ERROR_FAILURE;
@@ -200,53 +213,57 @@ nsBaseDragService::SetDataTransfer(nsIDO
 {
   mDataTransfer = aDataTransfer;
   return NS_OK;
 }
 
 //-------------------------------------------------------------------------
 NS_IMETHODIMP
 nsBaseDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
+                                     const nsACString& aPrincipalURISpec,
                                      nsIArray* aTransferableArray,
                                      nsIScriptableRegion* aDragRgn,
                                      uint32_t aActionType,
                                      nsContentPolicyType aContentPolicyType =
                                        nsIContentPolicy::TYPE_OTHER)
 {
   AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER);
 
   NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG);
   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
 
   // stash the document of the dom node
   nsCOMPtr<nsINode> node = do_QueryInterface(aDOMNode);
   mSourceDocument = do_QueryInterface(node->OwnerDoc());
+  mTriggeringPrincipalURISpec.Assign(aPrincipalURISpec);
   mSourceNode = aDOMNode;
   mContentPolicyType = aContentPolicyType;
   mEndDragPoint = LayoutDeviceIntPoint(0, 0);
 
   // When the mouse goes down, the selection code starts a mouse
   // capture. However, this gets in the way of determining drag
   // feedback for things like trees because the event coordinates
   // are in the wrong coord system, so turn off mouse capture.
   nsIPresShell::ClearMouseCapture(nullptr);
 
   nsresult rv = InvokeDragSessionImpl(aTransferableArray,
                                       aDragRgn, aActionType);
 
   if (NS_FAILED(rv)) {
     mSourceNode = nullptr;
+    mTriggeringPrincipalURISpec.Truncate(0);
     mSourceDocument = nullptr;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode,
+                                              const nsACString& aPrincipalURISpec,
                                               nsIArray* aTransferableArray,
                                               nsIScriptableRegion* aRegion,
                                               uint32_t aActionType,
                                               nsIDOMNode* aImage,
                                               int32_t aImageX, int32_t aImageY,
                                               nsIDOMDragEvent* aDragEvent,
                                               nsIDOMDataTransfer* aDataTransfer)
 {
@@ -260,31 +277,33 @@ nsBaseDragService::InvokeDragSessionWith
   mDragPopup = nullptr;
   mImage = aImage;
   mImageOffset = CSSIntPoint(aImageX, aImageY);
 
   aDragEvent->GetScreenX(&mScreenPosition.x);
   aDragEvent->GetScreenY(&mScreenPosition.y);
   aDragEvent->GetMozInputSource(&mInputSource);
 
-  nsresult rv = InvokeDragSession(aDOMNode, aTransferableArray,
+  nsresult rv = InvokeDragSession(aDOMNode, aPrincipalURISpec,
+                                  aTransferableArray,
                                   aRegion, aActionType,
                                   nsIContentPolicy::TYPE_INTERNAL_IMAGE);
 
   if (NS_FAILED(rv)) {
     mImage = nullptr;
     mHasImage = false;
     mDataTransfer = nullptr;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsBaseDragService::InvokeDragSessionWithSelection(nsISelection* aSelection,
+                                                  const nsACString& aPrincipalURISpec,
                                                   nsIArray* aTransferableArray,
                                                   uint32_t aActionType,
                                                   nsIDOMDragEvent* aDragEvent,
                                                   nsIDOMDataTransfer* aDataTransfer)
 {
   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
@@ -301,17 +320,18 @@ nsBaseDragService::InvokeDragSessionWith
   aDragEvent->GetMozInputSource(&mInputSource);
 
   // just get the focused node from the selection
   // XXXndeakin this should actually be the deepest node that contains both
   // endpoints of the selection
   nsCOMPtr<nsIDOMNode> node;
   aSelection->GetFocusNode(getter_AddRefs(node));
 
-  nsresult rv = InvokeDragSession(node, aTransferableArray,
+  nsresult rv = InvokeDragSession(node, aPrincipalURISpec,
+                                  aTransferableArray,
                                   nullptr, aActionType,
                                   nsIContentPolicy::TYPE_OTHER);
 
   if (NS_FAILED(rv)) {
     mHasImage = false;
     mSelection = nullptr;
     mDataTransfer = nullptr;
   }
@@ -417,16 +437,17 @@ nsBaseDragService::EndDragSession(bool a
   }
 
   mDoingDrag = false;
   mCanDrop = false;
 
   // release the source we've been holding on to.
   mSourceDocument = nullptr;
   mSourceNode = nullptr;
+  mTriggeringPrincipalURISpec.Truncate(0);
   mSelection = nullptr;
   mDataTransfer = nullptr;
   mHasImage = false;
   mUserCancelled = false;
   mDragPopup = nullptr;
   mImage = nullptr;
   mImageOffset = CSSIntPoint();
   mScreenPosition = CSSIntPoint();
--- a/widget/nsBaseDragService.h
+++ b/widget/nsBaseDragService.h
@@ -9,16 +9,17 @@
 #include "nsIDragService.h"
 #include "nsIDragSession.h"
 #include "nsITransferable.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMDataTransfer.h"
 #include "nsCOMPtr.h"
 #include "nsRect.h"
 #include "nsPoint.h"
+#include "nsString.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/HTMLCanvasElement.h"
 #include "nsTArray.h"
 #include "Units.h"
 
 // translucency level for drag images
 #define DRAG_TRANSLUCENCY 0.65
@@ -154,16 +155,17 @@ protected:
 
   bool mDragEventDispatchedToChildProcess;
 
   uint32_t mDragAction;
   uint32_t mDragActionFromChildProcess;
 
   nsSize mTargetSize;
   nsCOMPtr<nsIDOMNode> mSourceNode;
+  nsCString mTriggeringPrincipalURISpec;
   nsCOMPtr<nsIDOMDocument> mSourceDocument;       // the document at the drag source. will be null
                                                   //  if it came from outside the app.
   nsContentPolicyType mContentPolicyType;         // the contentpolicy type passed to the channel
                                                   // when initiating the drag session
   nsCOMPtr<nsIDOMDataTransfer> mDataTransfer;
 
   // used to determine the image to appear on the cursor while dragging
   nsCOMPtr<nsIDOMNode> mImage;
--- a/widget/nsDragServiceProxy.cpp
+++ b/widget/nsDragServiceProxy.cpp
@@ -24,32 +24,54 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsDragServi
 nsDragServiceProxy::nsDragServiceProxy()
 {
 }
 
 nsDragServiceProxy::~nsDragServiceProxy()
 {
 }
 
+static void
+GetPrincipalURIFromNode(nsCOMPtr<nsIDOMNode>& sourceNode,
+                        nsCString& aPrincipalURISpec)
+{
+  nsCOMPtr<nsINode> node = do_QueryInterface(sourceNode);
+  if (!node) {
+    return;
+  }
+
+  nsCOMPtr<nsIPrincipal> principal = node->NodePrincipal();
+  nsCOMPtr<nsIURI> principalURI;
+  nsresult rv = principal->GetURI(getter_AddRefs(principalURI));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  principalURI->GetSpec(aPrincipalURISpec);
+}
+
 nsresult
 nsDragServiceProxy::InvokeDragSessionImpl(nsIArray* aArrayTransferables,
                                           nsIScriptableRegion* aRegion,
                                           uint32_t aActionType)
 {
   nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
   NS_ENSURE_STATE(doc->GetDocShell());
   TabChild* child = TabChild::GetFrom(doc->GetDocShell());
   NS_ENSURE_STATE(child);
   nsTArray<mozilla::dom::IPCDataTransfer> dataTransfers;
   nsContentUtils::TransferablesToIPCTransferables(aArrayTransferables,
                                                   dataTransfers,
                                                   false,
                                                   child->Manager(),
                                                   nullptr);
 
+  nsCString principalURISpec;
+  GetPrincipalURIFromNode(mSourceNode, principalURISpec);
+
   LayoutDeviceIntRect dragRect;
   if (mHasImage || mSelection) {
     nsPresContext* pc;
     RefPtr<mozilla::gfx::SourceSurface> surface;
     DrawDrag(mSourceNode, aRegion, mScreenPosition, &dragRect, &surface, &pc);
 
     if (surface) {
       RefPtr<mozilla::gfx::DataSourceSurface> dataSurface =
@@ -71,20 +93,21 @@ nsDragServiceProxy::InvokeDragSessionImp
         if (!surfaceData.IsReadable() || !surfaceData.get<char>()) {
           NS_WARNING("Failed to create shared memory for drag session.");
           return NS_ERROR_FAILURE;
         }
 
         mozilla::Unused <<
           child->SendInvokeDragSession(dataTransfers, aActionType, surfaceData,
                                        stride, static_cast<uint8_t>(dataSurface->GetFormat()),
-                                       dragRect);
+                                       dragRect, principalURISpec);
         StartDragSession();
         return NS_OK;
       }
     }
   }
 
   mozilla::Unused << child->SendInvokeDragSession(dataTransfers, aActionType,
-                                                  mozilla::void_t(), 0, 0, dragRect);
+                                                  mozilla::void_t(), 0, 0, dragRect,
+                                                  principalURISpec);
   StartDragSession();
   return NS_OK;
 }
--- a/widget/nsIDragService.idl
+++ b/widget/nsIDragService.idl
@@ -37,25 +37,28 @@ interface nsIDragService : nsISupports
   const long DRAGDROP_ACTION_LINK = 4;
   const long DRAGDROP_ACTION_UNINITIALIZED = 64;
 
   /**
     * Starts a modal drag session with an array of transaferables.
     *
     * Note: This method is deprecated for non-native code.
     *
+    * @param  aPrincipalURISpec - the URI of the triggering principal of the
+    *            drag, or an empty string if it's from browser chrome or OS
     * @param  aTransferables - an array of transferables to be dragged
     * @param  aRegion - a region containing rectangles for cursor feedback, 
     *            in window coordinates.
     * @param  aActionType - specified which of copy/move/link are allowed
     * @param  aContentPolicyType - the contentPolicyType that will be
     *           passed to the loadInfo when creating a new channel
     *           (defaults to TYPE_OTHER)
     */
   void invokeDragSession (in nsIDOMNode aDOMNode,
+                          in AUTF8String aPrincipalURISpec,
                           in nsIArray aTransferables, 
                           in nsIScriptableRegion aRegion,
                           in unsigned long aActionType,
                           [optional] in nsContentPolicyType aContentPolicyType);
 
   /**
    * Starts a modal drag session using an image. The first four arguments are
    * the same as invokeDragSession.
@@ -79,16 +82,17 @@ interface nsIDragService : nsISupports
    * rendered at its real size. For other types of elements, the element is
    * rendered into an offscreen buffer in the same manner as it is currently
    * displayed. The document selection is hidden while drawing.
    *
    * The aDragEvent must be supplied as the current screen coordinates of the
    * event are needed to calculate the image location.
    */
   void invokeDragSessionWithImage(in nsIDOMNode aDOMNode,
+                                  in AUTF8String aPrincipalURISpec,
                                   in nsIArray aTransferableArray,
                                   in nsIScriptableRegion aRegion,
                                   in unsigned long aActionType,
                                   in nsIDOMNode aImage,
                                   in long aImageX,
                                   in long aImageY,
                                   in nsIDOMDragEvent aDragEvent,
                                   in nsIDOMDataTransfer aDataTransfer);
@@ -96,16 +100,17 @@ interface nsIDragService : nsISupports
   /**
    * Start a modal drag session using the selection as the drag image.
    * The aDragEvent must be supplied as the current screen coordinates of the
    * event are needed to calculate the image location.
    *
    * Note: This method is deprecated for non-native code.
    */
   void invokeDragSessionWithSelection(in nsISelection aSelection,
+                                      in AUTF8String aPrincipalURISpec,
                                       in nsIArray aTransferableArray,
                                       in unsigned long aActionType,
                                       in nsIDOMDragEvent aDragEvent,
                                       in nsIDOMDataTransfer aDataTransfer);
 
   /**
     * Returns the current Drag Session  
     */
--- a/widget/nsIDragSession.idl
+++ b/widget/nsIDragSession.idl
@@ -58,16 +58,23 @@ interface nsIDragSession : nsISupports
 
   /**
     * The dom node that was originally dragged to start the session, which will be null if the
     * drag originated outside the application.
     */
   readonly attribute nsIDOMNode sourceNode;
 
   /**
+   * The URI spec of the triggering principal.  This may be different than
+   * sourceNode's principal when sourceNode is xul:browser and the drag is
+   * triggered in a browsing context inside it.
+   */
+  attribute AUTF8String triggeringPrincipalURISpec;
+
+  /**
    * The data transfer object for the current drag.
    */
   attribute nsIDOMDataTransfer dataTransfer;
 
   /**
     * Get data from a Drag&Drop. Can be called while the drag is in process
     * or after the drop has completed.  
     *