Bug 1453425 - Add RepaintRequest for use of FrameMetrics in repaint requests. r=botond
authorRyan Hunt <rhunt@eqrion.net>
Wed, 19 Sep 2018 13:50:20 -0500
changeset 491201 d73aa682bf9a50db1252dc9c7e7874078073ebfd
parent 491200 42961627bc9a1bfd12acb11c62bdbc327ffd7303
child 491202 214cc7e7efb60b79fda610e02f4f24c50616ba9d
push id247
push userfmarier@mozilla.com
push dateSat, 27 Oct 2018 01:06:44 +0000
reviewersbotond
bugs1453425
milestone65.0a1
Bug 1453425 - Add RepaintRequest for use of FrameMetrics in repaint requests. r=botond FrameMetrics is currently used in about three ways. 1. Main thread to APZ transactions 2. Storing information in AsyncPanZoomController 3. APZ to main thread repaint requests There's overlap in the use of fields in all these use cases, but it's not perfect. In a following commit, I'd like to change the information used for (1) to support relative scroll offset updates. This information isn't needed for (2) or (3), so it would be good to refactor FrameMetrics out into these use cases. This commit refactors out (3) as it is fairly easy to do. I'd like to refactor (2) out as well, but that is trickier. I'd like to leave that for a future followup. Differential Revision: https://phabricator.services.mozilla.com/D7127
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
gfx/layers/FrameMetrics.h
gfx/layers/RepaintRequest.h
gfx/layers/apz/public/GeckoContentController.h
gfx/layers/apz/src/AsyncPanZoomController.cpp
gfx/layers/apz/src/AsyncPanZoomController.h
gfx/layers/apz/test/gtest/APZTestCommon.h
gfx/layers/apz/util/APZCCallbackHelper.cpp
gfx/layers/apz/util/APZCCallbackHelper.h
gfx/layers/apz/util/ChromeProcessController.cpp
gfx/layers/apz/util/ChromeProcessController.h
gfx/layers/apz/util/ContentProcessController.cpp
gfx/layers/apz/util/ContentProcessController.h
gfx/layers/ipc/APZChild.cpp
gfx/layers/ipc/APZChild.h
gfx/layers/ipc/LayersMessageUtils.h
gfx/layers/ipc/PAPZ.ipdl
gfx/layers/ipc/RemoteContentController.cpp
gfx/layers/ipc/RemoteContentController.h
gfx/layers/moz.build
layout/base/nsLayoutUtils.cpp
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -239,48 +239,46 @@ TabChildBase::DispatchMessageManagerMess
     RefPtr<TabChildMessageManager> kungFuDeathGrip(mTabChildMessageManager);
     RefPtr<nsFrameMessageManager> mm = kungFuDeathGrip->GetMessageManager();
     mm->ReceiveMessage(static_cast<EventTarget*>(kungFuDeathGrip), nullptr,
                        aMessageName, false, &data, nullptr, nullptr, nullptr,
                        IgnoreErrors());
 }
 
 bool
-TabChildBase::UpdateFrameHandler(const FrameMetrics& aFrameMetrics)
+TabChildBase::UpdateFrameHandler(const RepaintRequest& aRequest)
 {
-  MOZ_ASSERT(aFrameMetrics.GetScrollId() != FrameMetrics::NULL_SCROLL_ID);
-
-  if (aFrameMetrics.IsRootContent()) {
+  MOZ_ASSERT(aRequest.GetScrollId() != FrameMetrics::NULL_SCROLL_ID);
+
+  if (aRequest.IsRootContent()) {
     if (nsCOMPtr<nsIPresShell> shell = GetPresShell()) {
       // Guard against stale updates (updates meant for a pres shell which
       // has since been torn down and destroyed).
-      if (aFrameMetrics.GetPresShellId() == shell->GetPresShellId()) {
-        ProcessUpdateFrame(aFrameMetrics);
+      if (aRequest.GetPresShellId() == shell->GetPresShellId()) {
+        ProcessUpdateFrame(aRequest);
         return true;
       }
     }
   } else {
-    // aFrameMetrics.mIsRoot is false, so we are trying to update a subframe.
+    // aRequest.mIsRoot is false, so we are trying to update a subframe.
     // This requires special handling.
-    FrameMetrics newSubFrameMetrics(aFrameMetrics);
-    APZCCallbackHelper::UpdateSubFrame(newSubFrameMetrics);
+    APZCCallbackHelper::UpdateSubFrame(aRequest);
     return true;
   }
   return true;
 }
 
 void
-TabChildBase::ProcessUpdateFrame(const FrameMetrics& aFrameMetrics)
+TabChildBase::ProcessUpdateFrame(const RepaintRequest& aRequest)
 {
-    if (!mTabChildMessageManager) {
-        return;
-    }
-
-    FrameMetrics newMetrics = aFrameMetrics;
-    APZCCallbackHelper::UpdateRootFrame(newMetrics);
+  if (!mTabChildMessageManager) {
+      return;
+  }
+
+  APZCCallbackHelper::UpdateRootFrame(aRequest);
 }
 
 NS_IMETHODIMP
 ContentListener::HandleEvent(Event* aEvent)
 {
   RemoteDOMEvent remoteEvent;
   remoteEvent.mEvent = aEvent;
   NS_ENSURE_STATE(remoteEvent.mEvent);
@@ -1298,19 +1296,19 @@ TabChild::RecvSizeModeChanged(const nsSi
   nsPresContext* presContext = document->GetPresContext();
   if (presContext) {
     presContext->SizeModeChanged(aSizeMode);
   }
   return IPC_OK();
 }
 
 bool
-TabChild::UpdateFrame(const FrameMetrics& aFrameMetrics)
+TabChild::UpdateFrame(const RepaintRequest& aRequest)
 {
-  return TabChildBase::UpdateFrameHandler(aFrameMetrics);
+  return TabChildBase::UpdateFrameHandler(aRequest);
 }
 
 mozilla::ipc::IPCResult
 TabChild::RecvSuppressDisplayport(const bool& aEnabled)
 {
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell()) {
     shell->SuppressDisplayport(aEnabled);
   }
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -180,19 +180,19 @@ protected:
   // Wraps up a JSON object as a structured clone and sends it to the browser
   // chrome script.
   //
   // XXX/bug 780335: Do the work the browser chrome script does in C++ instead
   // so we don't need things like this.
   void DispatchMessageManagerMessage(const nsAString& aMessageName,
                                      const nsAString& aJSONData);
 
-  void ProcessUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics);
+  void ProcessUpdateFrame(const mozilla::layers::RepaintRequest& aRequest);
 
-  bool UpdateFrameHandler(const mozilla::layers::FrameMetrics& aFrameMetrics);
+  bool UpdateFrameHandler(const mozilla::layers::RepaintRequest& aRequest);
 
 protected:
   RefPtr<TabChildMessageManager> mTabChildMessageManager;
   nsCOMPtr<nsIWebBrowserChrome3> mWebBrowserChrome;
 };
 
 class TabChild final : public TabChildBase,
                        public PBrowserChild,
@@ -630,17 +630,17 @@ public:
                               const LayoutDevicePoint& aPoint,
                               const Modifiers& aModifiers,
                               const ScrollableLayerGuid& aGuid,
                               const uint64_t& aInputBlockId) override;
 
   void SetAllowedTouchBehavior(uint64_t aInputBlockId,
                                const nsTArray<TouchBehaviorFlags>& aFlags) const;
 
-  bool UpdateFrame(const FrameMetrics& aFrameMetrics);
+  bool UpdateFrame(const layers::RepaintRequest& aRequest);
   bool NotifyAPZStateChange(const ViewID& aViewId,
                             const layers::GeckoContentController::APZStateChange& aChange,
                             const int& aArg);
   void StartScrollbarDrag(const layers::AsyncDragMetrics& aDragMetrics);
   void ZoomToRect(const uint32_t& aPresShellId,
                   const FrameMetrics::ViewID& aViewId,
                   const CSSRect& aRect,
                   const uint32_t& aFlags);
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -57,20 +57,16 @@ public:
 
   MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(
     ScrollOffsetUpdateType, uint8_t, (
       eNone,          // The default; the scroll offset was not updated
       eMainThread,    // The scroll offset was updated by the main thread.
       ePending,       // The scroll offset was updated on the main thread, but not
                       // painted, so the layer texture data is still at the old
                       // offset.
-      eUserAction,    // In an APZ repaint request, this means the APZ generated
-                      // the scroll position based on user action (the alternative
-                      // is eNone which means it's just request a repaint because
-                      // it got a scroll update from the main thread).
       eRestore        // The scroll offset was updated by the main thread, but as
                       // a restore from history or after a frame reconstruction.
                       // In this case, APZ can ignore the offset change if the
                       // user has done an APZ scroll already.
   ));
 
   FrameMetrics()
     : mScrollId(NULL_SCROLL_ID)
@@ -266,21 +262,16 @@ public:
 
   void UpdatePendingScrollInfo(const ScrollUpdateInfo& aInfo)
   {
     mScrollOffset = aInfo.mScrollOffset;
     mScrollGeneration = aInfo.mScrollGeneration;
     mScrollUpdateType = ePending;
   }
 
-  void SetRepaintDrivenByUserAction(bool aUserAction)
-  {
-    mScrollUpdateType = aUserAction ? eUserAction : eNone;
-  }
-
 public:
   void SetPresShellResolution(float aPresShellResolution)
   {
     mPresShellResolution = aPresShellResolution;
   }
 
   float GetPresShellResolution() const
   {
new file mode 100644
--- /dev/null
+++ b/gfx/layers/RepaintRequest.h
@@ -0,0 +1,357 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_REPAINTREQUEST_H
+#define GFX_REPAINTREQUEST_H
+
+#include <stdint.h>                     // for uint8_t, uint32_t, uint64_t
+
+#include "FrameMetrics.h"               // for FrameMetrics
+#include "mozilla/DefineEnum.h"         // for MOZ_DEFINE_ENUM
+#include "mozilla/gfx/BasePoint.h"      // for BasePoint
+#include "mozilla/gfx/Rect.h"           // for RoundedIn
+#include "mozilla/gfx/ScaleFactor.h"    // for ScaleFactor
+#include "mozilla/TimeStamp.h"          // for TimeStamp
+#include "Units.h"                      // for CSSRect, CSSPixel, etc
+
+namespace IPC {
+template <typename T> struct ParamTraits;
+} // namespace IPC
+
+namespace mozilla {
+namespace layers {
+
+struct RepaintRequest {
+  friend struct IPC::ParamTraits<mozilla::layers::RepaintRequest>;
+public:
+
+  MOZ_DEFINE_ENUM_WITH_BASE_AT_CLASS_SCOPE(
+    ScrollOffsetUpdateType, uint8_t, (
+      eNone,             // The default; the scroll offset was not updated.
+      eUserAction        // The scroll offset was updated by APZ.
+  ));
+
+  RepaintRequest()
+    : mScrollId(FrameMetrics::NULL_SCROLL_ID)
+    , mPresShellResolution(1)
+    , mCompositionBounds(0, 0, 0, 0)
+    , mCumulativeResolution()
+    , mDevPixelsPerCSSPixel(1)
+    , mScrollOffset(0, 0)
+    , mZoom()
+    , mScrollGeneration(0)
+    , mDisplayPortMargins(0, 0, 0, 0)
+    , mPresShellId(-1)
+    , mViewport(0, 0, 0, 0)
+    , mExtraResolution()
+    , mPaintRequestTime()
+    , mScrollUpdateType(eNone)
+    , mIsRootContent(false)
+    , mUseDisplayPortMargins(false)
+    , mIsScrollInfoLayer(false)
+  {
+  }
+
+  RepaintRequest(const FrameMetrics& aOther, const ScrollOffsetUpdateType aScrollUpdateType)
+    : mScrollId(aOther.GetScrollId())
+    , mPresShellResolution(aOther.GetPresShellResolution())
+    , mCompositionBounds(aOther.GetCompositionBounds())
+    , mCumulativeResolution(aOther.GetCumulativeResolution())
+    , mDevPixelsPerCSSPixel(aOther.GetDevPixelsPerCSSPixel())
+    , mScrollOffset(aOther.GetScrollOffset())
+    , mZoom(aOther.GetZoom())
+    , mScrollGeneration(aOther.GetScrollGeneration())
+    , mDisplayPortMargins(aOther.GetDisplayPortMargins())
+    , mPresShellId(aOther.GetPresShellId())
+    , mViewport(aOther.GetViewport())
+    , mExtraResolution(aOther.GetExtraResolution())
+    , mPaintRequestTime(aOther.GetPaintRequestTime())
+    , mScrollUpdateType(aScrollUpdateType)
+    , mIsRootContent(aOther.IsRootContent())
+    , mUseDisplayPortMargins(aOther.GetUseDisplayPortMargins())
+    , mIsScrollInfoLayer(aOther.IsScrollInfoLayer())
+  {
+  }
+
+  // Default copy ctor and operator= are fine
+
+  bool operator==(const RepaintRequest& aOther) const
+  {
+    // Put mScrollId at the top since it's the most likely one to fail.
+    return mScrollId == aOther.mScrollId &&
+           mPresShellResolution == aOther.mPresShellResolution &&
+           mCompositionBounds.IsEqualEdges(aOther.mCompositionBounds) &&
+           mCumulativeResolution == aOther.mCumulativeResolution &&
+           mDevPixelsPerCSSPixel == aOther.mDevPixelsPerCSSPixel &&
+           mScrollOffset == aOther.mScrollOffset &&
+           // don't compare mZoom
+           mScrollGeneration == aOther.mScrollGeneration &&
+           mDisplayPortMargins == aOther.mDisplayPortMargins &&
+           mPresShellId == aOther.mPresShellId &&
+           mViewport.IsEqualEdges(aOther.mViewport) &&
+           mExtraResolution == aOther.mExtraResolution &&
+           mPaintRequestTime == aOther.mPaintRequestTime &&
+           mScrollUpdateType == aOther.mScrollUpdateType &&
+           mIsRootContent == aOther.mIsRootContent &&
+           mUseDisplayPortMargins == aOther.mUseDisplayPortMargins &&
+           mIsScrollInfoLayer == aOther.mIsScrollInfoLayer;
+  }
+
+  bool operator!=(const RepaintRequest& aOther) const
+  {
+    return !operator==(aOther);
+  }
+
+  CSSToScreenScale2D DisplayportPixelsPerCSSPixel() const
+  {
+    // Note: use 'mZoom * ParentLayerToLayerScale(1.0f)' as the CSS-to-Layer scale
+    // instead of LayersPixelsPerCSSPixel(), because displayport calculations
+    // are done in the context of a repaint request, where we ask Layout to
+    // repaint at a new resolution that includes any async zoom. Until this
+    // repaint request is processed, LayersPixelsPerCSSPixel() does not yet
+    // include the async zoom, but it will when the displayport is interpreted
+    // for the repaint.
+    return mZoom * ParentLayerToLayerScale(1.0f) / mExtraResolution;
+  }
+
+  CSSToLayerScale2D LayersPixelsPerCSSPixel() const
+  {
+    return mDevPixelsPerCSSPixel * mCumulativeResolution;
+  }
+
+  // Get the amount by which this frame has been zoomed since the last repaint.
+  LayerToParentLayerScale GetAsyncZoom() const
+  {
+    // The async portion of the zoom should be the same along the x and y
+    // axes.
+    return (mZoom / LayersPixelsPerCSSPixel()).ToScaleFactor();
+  }
+
+  CSSSize CalculateCompositedSizeInCssPixels() const
+  {
+    if (GetZoom() == CSSToParentLayerScale2D(0, 0)) {
+      return CSSSize();  // avoid division by zero
+    }
+    return mCompositionBounds.Size() / GetZoom();
+  }
+
+  float GetPresShellResolution() const
+  {
+    return mPresShellResolution;
+  }
+
+  const ParentLayerRect& GetCompositionBounds() const
+  {
+    return mCompositionBounds;
+  }
+
+  const LayoutDeviceToLayerScale2D& GetCumulativeResolution() const
+  {
+    return mCumulativeResolution;
+  }
+
+  const CSSToLayoutDeviceScale& GetDevPixelsPerCSSPixel() const
+  {
+    return mDevPixelsPerCSSPixel;
+  }
+
+  bool IsRootContent() const
+  {
+    return mIsRootContent;
+  }
+
+  const CSSPoint& GetScrollOffset() const
+  {
+    return mScrollOffset;
+  }
+
+  const CSSToParentLayerScale2D& GetZoom() const
+  {
+    return mZoom;
+  }
+
+  ScrollOffsetUpdateType GetScrollUpdateType() const
+  {
+    return mScrollUpdateType;
+  }
+
+  bool GetScrollOffsetUpdated() const
+  {
+    return mScrollUpdateType != eNone;
+  }
+
+  uint32_t GetScrollGeneration() const
+  {
+    return mScrollGeneration;
+  }
+
+  FrameMetrics::ViewID GetScrollId() const
+  {
+    return mScrollId;
+  }
+
+  const ScreenMargin& GetDisplayPortMargins() const
+  {
+    return mDisplayPortMargins;
+  }
+
+  bool GetUseDisplayPortMargins() const
+  {
+    return mUseDisplayPortMargins;
+  }
+
+  uint32_t GetPresShellId() const
+  {
+    return mPresShellId;
+  }
+
+  const CSSRect& GetViewport() const
+  {
+    return mViewport;
+  }
+
+  const ScreenToLayerScale2D& GetExtraResolution() const
+  {
+    return mExtraResolution;
+  }
+
+  const TimeStamp& GetPaintRequestTime() const {
+    return mPaintRequestTime;
+  }
+
+  bool IsScrollInfoLayer() const {
+    return mIsScrollInfoLayer;
+  }
+
+protected:
+  void SetIsRootContent(bool aIsRootContent)
+  {
+    mIsRootContent = aIsRootContent;
+  }
+
+  void SetUseDisplayPortMargins(bool aValue)
+  {
+    mUseDisplayPortMargins = aValue;
+  }
+
+  void SetIsScrollInfoLayer(bool aIsScrollInfoLayer)
+  {
+    mIsScrollInfoLayer = aIsScrollInfoLayer;
+  }
+
+private:
+  // A unique ID assigned to each scrollable frame.
+  FrameMetrics::ViewID mScrollId;
+
+  // The pres-shell resolution that has been induced on the document containing
+  // this scroll frame as a result of zooming this scroll frame (whether via
+  // user action, or choosing an initial zoom level on page load). This can
+  // only be different from 1.0 for frames that are zoomable, which currently
+  // is just the root content document's root scroll frame (mIsRoot = true).
+  // This is a plain float rather than a ScaleFactor because in and of itself
+  // it does not convert between any coordinate spaces for which we have names.
+  float mPresShellResolution;
+
+  // This is the area within the widget that we're compositing to. It is in the
+  // same coordinate space as the reference frame for the scrolled frame.
+  //
+  // This is useful because, on mobile, the viewport and composition dimensions
+  // are not always the same. In this case, we calculate the displayport using
+  // an area bigger than the region we're compositing to. If we used the
+  // viewport dimensions to calculate the displayport, we'd run into situations
+  // where we're prerendering the wrong regions and the content may be clipped,
+  // or too much of it prerendered. If the composition dimensions are the same
+  // as the viewport dimensions, there is no need for this and we can just use
+  // the viewport instead.
+  //
+  // This value is valid for nested scrollable layers as well, and is still
+  // relative to the layer tree origin. This value is provided by Gecko at
+  // layout/paint time.
+  ParentLayerRect mCompositionBounds;
+
+  // The cumulative resolution that the current frame has been painted at.
+  // This is the product of the pres-shell resolutions of the document
+  // containing this scroll frame and its ancestors, and any css-driven
+  // resolution. This information is provided by Gecko at layout/paint time.
+  // Note that this is allowed to have different x- and y-scales, but only
+  // for subframes (mIsRoot = false). (The same applies to other scales that
+  // "inherit" the 2D-ness of this one, such as mZoom.)
+  LayoutDeviceToLayerScale2D mCumulativeResolution;
+
+  // The conversion factor between CSS pixels and device pixels for this frame.
+  // This can vary based on a variety of things, such as reflowing-zoom. The
+  // conversion factor for device pixels to layers pixels is just the
+  // resolution.
+  CSSToLayoutDeviceScale mDevPixelsPerCSSPixel;
+
+  // The position of the top-left of the CSS viewport, relative to the document
+  // (or the document relative to the viewport, if that helps understand it).
+  //
+  // Thus it is relative to the document. It is in the same coordinate space as
+  // |mScrollableRect|, but a different coordinate space than |mViewport| and
+  // |mDisplayPort|.
+  //
+  // It is required that the rect:
+  // { x = mScrollOffset.x, y = mScrollOffset.y,
+  //   width = mCompositionBounds.x / mResolution.scale,
+  //   height = mCompositionBounds.y / mResolution.scale }
+  // Be within |mScrollableRect|.
+  //
+  // This is valid for any layer, but is always relative to this frame and
+  // not any parents, regardless of parent transforms.
+  CSSPoint mScrollOffset;
+
+  // The "user zoom". Content is painted by gecko at mResolution * mDevPixelsPerCSSPixel,
+  // but will be drawn to the screen at mZoom. In the steady state, the
+  // two will be the same, but during an async zoom action the two may
+  // diverge. This information is initialized in Gecko but updated in the APZC.
+  CSSToParentLayerScale2D mZoom;
+
+  // The scroll generation counter used to acknowledge the scroll offset update.
+  uint32_t mScrollGeneration;
+
+  // A display port expressed as layer margins that apply to the rect of what
+  // is drawn of the scrollable element.
+  ScreenMargin mDisplayPortMargins;
+
+  uint32_t mPresShellId;
+
+  // The CSS viewport, which is the dimensions we're using to constrain the
+  // <html> element of this frame, relative to the top-left of the layer. Note
+  // that its offset is structured in such a way that it doesn't depend on the
+  // method layout uses to scroll content.
+  //
+  // This is mainly useful on the root layer, however nested iframes can have
+  // their own viewport, which will just be the size of the window of the
+  // iframe. For layers that don't correspond to a document, this metric is
+  // meaningless and invalid.
+  CSSRect mViewport;
+
+  // The extra resolution at which content in this scroll frame is drawn beyond
+  // that necessary to draw one Layer pixel per Screen pixel.
+  ScreenToLayerScale2D mExtraResolution;
+
+  // The time at which the APZC last requested a repaint for this scrollframe.
+  TimeStamp mPaintRequestTime;
+
+  // The type of repaint request this represents.
+  ScrollOffsetUpdateType mScrollUpdateType;
+
+  // Whether or not this is the root scroll frame for the root content document.
+  bool mIsRootContent:1;
+
+  // If this is true then we use the display port margins on this metrics,
+  // otherwise use the display port rect.
+  bool mUseDisplayPortMargins:1;
+
+  // Whether or not this frame has a "scroll info layer" to capture events.
+  bool mIsScrollInfoLayer:1;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif /* GFX_REPAINTREQUEST_H */
--- a/gfx/layers/apz/public/GeckoContentController.h
+++ b/gfx/layers/apz/public/GeckoContentController.h
@@ -8,39 +8,40 @@
 #define mozilla_layers_GeckoContentController_h
 
 #include "FrameMetrics.h"               // for FrameMetrics, etc
 #include "InputData.h"                  // for PinchGestureInput
 #include "Units.h"                      // for CSSPoint, CSSRect, etc
 #include "mozilla/Assertions.h"         // for MOZ_ASSERT_HELPER2
 #include "mozilla/DefineEnum.h"         // for MOZ_DEFINE_ENUM
 #include "mozilla/EventForwards.h"      // for Modifiers
+#include "mozilla/layers/RepaintRequest.h" // for RepaintRequest
 #include "nsISupportsImpl.h"
 
 namespace mozilla {
 
 class Runnable;
 
 namespace layers {
 
 class GeckoContentController
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GeckoContentController)
 
   /**
-   * Requests a paint of the given FrameMetrics |aFrameMetrics| from Gecko.
+   * Requests a paint of the given RepaintRequest |aRequest| from Gecko.
    * Implementations per-platform are responsible for actually handling this.
    *
    * This method must always be called on the repaint thread, which depends
    * on the GeckoContentController. For ChromeProcessController it is the
    * Gecko main thread, while for RemoteContentController it is the compositor
    * thread where it can send IPDL messages.
    */
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) = 0;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) = 0;
 
   /**
    * Different types of tap-related events that can be sent in
    * the HandleTap function. The names should be relatively self-explanatory.
    * Note that the eLongTapUp will always be preceded by an eLongTap, but not
    * all eLongTap notifications will be followed by an eLongTapUp (for instance,
    * if the user moves their finger after triggering the long-tap but before
    * lifting it).
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3588,94 +3588,96 @@ bool AsyncPanZoomController::IsScrollInf
   return Metrics().IsScrollInfoLayer();
 }
 
 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   return listener ? listener->GetLastTouchIdentifier() : -1;
 }
 
-void AsyncPanZoomController::RequestContentRepaint(bool aUserAction) {
+void AsyncPanZoomController::RequestContentRepaint(RepaintUpdateType aUpdateType) {
   // Reinvoke this method on the repaint thread if it's not there already. It's
   // important to do this before the call to CalculatePendingDisplayPort, so
   // that CalculatePendingDisplayPort uses the most recent available version of
   // Metrics(). just before the paint request is dispatched to content.
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   if (!controller->IsRepaintThread()) {
     // use the local variable to resolve the function overload.
-    auto func = static_cast<void (AsyncPanZoomController::*)(bool)>
+    auto func = static_cast<void (AsyncPanZoomController::*)(RepaintUpdateType)>
         (&AsyncPanZoomController::RequestContentRepaint);
-    controller->DispatchToRepaintThread(NewRunnableMethod<bool>(
+    controller->DispatchToRepaintThread(NewRunnableMethod<RepaintUpdateType>(
       "layers::AsyncPanZoomController::RequestContentRepaint",
       this,
       func,
-      aUserAction));
+      aUpdateType));
     return;
   }
 
   MOZ_ASSERT(controller->IsRepaintThread());
 
   RecursiveMutexAutoLock lock(mRecursiveMutex);
   ParentLayerPoint velocity = GetVelocityVector();
   Metrics().SetDisplayPortMargins(CalculatePendingDisplayPort(Metrics(), velocity));
   Metrics().SetUseDisplayPortMargins(true);
   Metrics().SetPaintRequestTime(TimeStamp::Now());
-  Metrics().SetRepaintDrivenByUserAction(aUserAction);
-  RequestContentRepaint(Metrics(), velocity);
+  RequestContentRepaint(Metrics(), velocity, aUpdateType);
 }
 
 /*static*/ CSSRect
 GetDisplayPortRect(const FrameMetrics& aFrameMetrics)
 {
   // This computation is based on what happens in CalculatePendingDisplayPort. If that
   // changes then this might need to change too
   CSSRect baseRect(aFrameMetrics.GetScrollOffset(),
                    aFrameMetrics.CalculateBoundedCompositedSizeInCssPixels());
   baseRect.Inflate(aFrameMetrics.GetDisplayPortMargins() / aFrameMetrics.DisplayportPixelsPerCSSPixel());
   return baseRect;
 }
 
 void
 AsyncPanZoomController::RequestContentRepaint(const FrameMetrics& aFrameMetrics,
-                                              const ParentLayerPoint& aVelocity)
+                                              const ParentLayerPoint& aVelocity,
+                                              RepaintUpdateType aUpdateType)
 {
   RefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (!controller) {
     return;
   }
   MOZ_ASSERT(controller->IsRepaintThread());
 
+  RepaintRequest request(aFrameMetrics, aUpdateType);
+
   // If we're trying to paint what we already think is painted, discard this
   // request since it's a pointless paint.
   ScreenMargin marginDelta = (mLastPaintRequestMetrics.GetDisplayPortMargins()
-                           - aFrameMetrics.GetDisplayPortMargins());
+                           - request.GetDisplayPortMargins());
   if (fabsf(marginDelta.left) < EPSILON &&
       fabsf(marginDelta.top) < EPSILON &&
       fabsf(marginDelta.right) < EPSILON &&
       fabsf(marginDelta.bottom) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().x -
-            aFrameMetrics.GetScrollOffset().x) < EPSILON &&
+            request.GetScrollOffset().x) < EPSILON &&
       fabsf(mLastPaintRequestMetrics.GetScrollOffset().y -
-            aFrameMetrics.GetScrollOffset().y) < EPSILON &&
-      aFrameMetrics.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
-      aFrameMetrics.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
-      fabsf(aFrameMetrics.GetViewport().Width() -
+            request.GetScrollOffset().y) < EPSILON &&
+      request.GetPresShellResolution() == mLastPaintRequestMetrics.GetPresShellResolution() &&
+      request.GetZoom() == mLastPaintRequestMetrics.GetZoom() &&
+      fabsf(request.GetViewport().Width() -
             mLastPaintRequestMetrics.GetViewport().Width()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().Height() -
+      fabsf(request.GetViewport().Height() -
             mLastPaintRequestMetrics.GetViewport().Height()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().X() -
+      fabsf(request.GetViewport().X() -
             mLastPaintRequestMetrics.GetViewport().X()) < EPSILON &&
-      fabsf(aFrameMetrics.GetViewport().Y() -
+      fabsf(request.GetViewport().Y() -
             mLastPaintRequestMetrics.GetViewport().Y()) < EPSILON &&
-      aFrameMetrics.GetScrollGeneration() ==
+      request.GetScrollGeneration() ==
             mLastPaintRequestMetrics.GetScrollGeneration() &&
-      aFrameMetrics.GetScrollUpdateType() ==
+      request.GetScrollUpdateType() ==
             mLastPaintRequestMetrics.GetScrollUpdateType()) {
     return;
   }
 
   APZC_LOG_FM(aFrameMetrics, "%p requesting content repaint", this);
   { // scope lock
     MutexAutoLock lock(mCheckerboardEventLock);
     if (mCheckerboardEvent && mCheckerboardEvent->IsRecordingTrace()) {
@@ -3683,21 +3685,19 @@ AsyncPanZoomController::RequestContentRe
       info << " velocity " << aVelocity;
       std::string str = info.str();
       mCheckerboardEvent->UpdateRendertraceProperty(
           CheckerboardEvent::RequestedDisplayPort, GetDisplayPortRect(aFrameMetrics),
           str);
     }
   }
 
-  MOZ_ASSERT(aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eNone ||
-             aFrameMetrics.GetScrollUpdateType() == FrameMetrics::eUserAction);
-  controller->RequestContentRepaint(aFrameMetrics);
+  controller->RequestContentRepaint(request);
   mExpectedGeckoMetrics = aFrameMetrics;
-  mLastPaintRequestMetrics = aFrameMetrics;
+  mLastPaintRequestMetrics = request;
 }
 
 bool AsyncPanZoomController::UpdateAnimation(const TimeStamp& aSampleTime,
                                              nsTArray<RefPtr<Runnable>>* aOutDeferredTasks)
 {
   AssertOnSamplerThread();
 
   // This function may get called multiple with the same sample time, for two
@@ -4438,17 +4438,17 @@ void AsyncPanZoomController::NotifyLayer
     // visual viewport.
     // Note: it's important to do this _after_ we've accepted any
     // updated composition bounds.
     Metrics().RecalculateViewportOffset();
   }
 
   if (needContentRepaint) {
     // This repaint request is not driven by a user action on the APZ side
-    RequestContentRepaint(false);
+    RequestContentRepaint(RepaintUpdateType::eNone);
   }
   UpdateSharedCompositorFrameMetrics();
 }
 
 FrameMetrics& AsyncPanZoomController::Metrics() {
   return mScrollMetadata.GetMetrics();
 }
 
@@ -4615,35 +4615,35 @@ void AsyncPanZoomController::ZoomToRect(
 
     // Schedule a repaint now, so the new displayport will be painted before the
     // animation finishes.
     ParentLayerPoint velocity(0, 0);
     endZoomToMetrics.SetDisplayPortMargins(
       CalculatePendingDisplayPort(endZoomToMetrics, velocity));
     endZoomToMetrics.SetUseDisplayPortMargins(true);
     endZoomToMetrics.SetPaintRequestTime(TimeStamp::Now());
-    endZoomToMetrics.SetRepaintDrivenByUserAction(true);
 
     RefPtr<GeckoContentController> controller = GetGeckoContentController();
     if (!controller) {
       return;
     }
     if (controller->IsRepaintThread()) {
-      RequestContentRepaint(endZoomToMetrics, velocity);
+      RequestContentRepaint(endZoomToMetrics, velocity, RepaintUpdateType::eUserAction);
     } else {
       // use a local var to resolve the function overload
-      auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&)>
+      auto func = static_cast<void (AsyncPanZoomController::*)(const FrameMetrics&, const ParentLayerPoint&, RepaintUpdateType)>
           (&AsyncPanZoomController::RequestContentRepaint);
       controller->DispatchToRepaintThread(
-        NewRunnableMethod<FrameMetrics, ParentLayerPoint>(
+        NewRunnableMethod<FrameMetrics, ParentLayerPoint, RepaintUpdateType>(
           "layers::AsyncPanZoomController::ZoomToRect",
           this,
           func,
           endZoomToMetrics,
-          velocity));
+          velocity,
+          RepaintUpdateType::eUserAction));
     }
   }
 }
 
 InputBlockState*
 AsyncPanZoomController::GetCurrentInputBlock() const
 {
   return GetInputQueue()->GetCurrentBlock();
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_layers_AsyncPanZoomController_h
 #define mozilla_layers_AsyncPanZoomController_h
 
 #include "CrossProcessMutex.h"
 #include "mozilla/layers/GeckoContentController.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/RecursiveMutex.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Atomics.h"
 #include "InputData.h"
@@ -145,16 +146,18 @@ struct AncestorTransform {
  * asynchronously scrolled subframes, we want to have one AsyncPanZoomController
  * per frame.
  */
 class AsyncPanZoomController {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AsyncPanZoomController)
 
   typedef mozilla::MonitorAutoLock MonitorAutoLock;
   typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+  typedef mozilla::layers::RepaintRequest::ScrollOffsetUpdateType
+    RepaintUpdateType;
 
 public:
   enum GestureBehavior {
     // The platform code is responsible for forwarding gesture events here. We
     // will not attempt to generate gesture events from MultiTouchInputs.
     DEFAULT_GESTURES,
     // An instance of GestureEventListener is used to detect gestures. This is
     // handled completely internally within this class.
@@ -803,25 +806,27 @@ protected:
 
   /**
    * Utility function to send updated FrameMetrics to Gecko so that it can paint
    * the displayport area. Calls into GeckoContentController to do the actual
    * work. This call will use the current metrics. If this function is called
    * from a non-main thread, it will redispatch itself to the main thread, and
    * use the latest metrics during the redispatch.
    */
-  void RequestContentRepaint(bool aUserAction = true);
+  void RequestContentRepaint(RepaintUpdateType aUpdateType =
+                               RepaintUpdateType::eUserAction);
 
   /**
    * Send the provided metrics to Gecko to trigger a repaint. This function
    * may filter duplicate calls with the same metrics. This function must be
    * called on the main thread.
    */
   void RequestContentRepaint(const FrameMetrics& aFrameMetrics,
-                             const ParentLayerPoint& aVelocity);
+                             const ParentLayerPoint& aVelocity,
+                             RepaintUpdateType aUpdateType);
 
   /**
    * Gets the current frame metrics. This is *not* the Gecko copy stored in the
    * layers code.
    */
   const FrameMetrics& GetFrameMetrics() const;
 
   /**
@@ -928,18 +933,18 @@ private:
   // Metadata of the container layer corresponding to this APZC. This is
   // stored here so that it is accessible from the UI/controller thread.
   // These are the metrics at last content paint, the most recent
   // values we were notified of in NotifyLayersUpdate(). Since it represents
   // the Gecko state, it should be used as a basis for untransformation when
   // sending messages back to Gecko.
   ScrollMetadata mLastContentPaintMetadata;
   FrameMetrics& mLastContentPaintMetrics;  // for convenience, refers to mLastContentPaintMetadata.mMetrics
-  // The last metrics used for a content repaint request.
-  FrameMetrics mLastPaintRequestMetrics;
+  // The last content repaint request.
+  RepaintRequest mLastPaintRequestMetrics;
   // The metrics that we expect content to have. This is updated when we
   // request a content repaint, and when we receive a shadow layers update.
   // This allows us to transform events into Gecko's coordinate space.
   FrameMetrics mExpectedGeckoMetrics;
 
   // These variables cache the layout viewport, scroll offset, and zoom stored
   // in |Metrics()| the last time SampleCompositedAsyncTransform() was
   // called.
--- a/gfx/layers/apz/test/gtest/APZTestCommon.h
+++ b/gfx/layers/apz/test/gtest/APZTestCommon.h
@@ -96,17 +96,17 @@ private:
 
 static TimeStamp GetStartupTime() {
   static TimeStamp sStartupTime = TimeStamp::Now();
   return sStartupTime;
 }
 
 class MockContentController : public GeckoContentController {
 public:
-  MOCK_METHOD1(RequestContentRepaint, void(const FrameMetrics&));
+  MOCK_METHOD1(RequestContentRepaint, void(const RepaintRequest&));
   MOCK_METHOD2(RequestFlingSnap, void(const FrameMetrics::ViewID& aScrollId, const mozilla::CSSPoint& aDestination));
   MOCK_METHOD2(AcknowledgeScrollUpdate, void(const FrameMetrics::ViewID&, const uint32_t& aScrollGeneration));
   MOCK_METHOD5(HandleTap, void(TapType, const LayoutDevicePoint&, Modifiers, const ScrollableLayerGuid&, uint64_t));
   MOCK_METHOD4(NotifyPinchGesture, void(PinchGestureInput::PinchGestureType, const ScrollableLayerGuid&, LayoutDeviceCoord, Modifiers));
   // Can't use the macros with already_AddRefed :(
   void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) {
     RefPtr<Runnable> task = aTask;
   }
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -43,71 +43,71 @@
 
 namespace mozilla {
 namespace layers {
 
 using dom::TabParent;
 
 uint64_t APZCCallbackHelper::sLastTargetAPZCNotificationInputBlock = uint64_t(-1);
 
-void
+ScreenMargin
 APZCCallbackHelper::AdjustDisplayPortForScrollDelta(
-    mozilla::layers::FrameMetrics& aFrameMetrics,
+    const RepaintRequest& aRequest,
     const CSSPoint& aActualScrollOffset)
 {
   // Correct the display-port by the difference between the requested scroll
   // offset and the resulting scroll offset after setting the requested value.
   ScreenPoint shift =
-      (aFrameMetrics.GetScrollOffset() - aActualScrollOffset) *
-      aFrameMetrics.DisplayportPixelsPerCSSPixel();
-  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+      (aRequest.GetScrollOffset() - aActualScrollOffset) *
+      aRequest.DisplayportPixelsPerCSSPixel();
+  ScreenMargin margins = aRequest.GetDisplayPortMargins();
   margins.left -= shift.x;
   margins.right += shift.x;
   margins.top -= shift.y;
   margins.bottom += shift.y;
-  aFrameMetrics.SetDisplayPortMargins(margins);
+  return margins;
 }
 
-static void
-RecenterDisplayPort(mozilla::layers::FrameMetrics& aFrameMetrics)
+static ScreenMargin
+RecenterDisplayPort(const ScreenMargin& aDisplayPort)
 {
-  ScreenMargin margins = aFrameMetrics.GetDisplayPortMargins();
+  ScreenMargin margins = aDisplayPort;
   margins.right = margins.left = margins.LeftRight() / 2;
   margins.top = margins.bottom = margins.TopBottom() / 2;
-  aFrameMetrics.SetDisplayPortMargins(margins);
+  return margins;
 }
 
 static already_AddRefed<nsIPresShell>
 GetPresShell(const nsIContent* aContent)
 {
   nsCOMPtr<nsIPresShell> result;
   if (nsIDocument* doc = aContent->GetComposedDoc()) {
     result = doc->GetShell();
   }
   return result.forget();
 }
 
 static CSSPoint
-ScrollFrameTo(nsIScrollableFrame* aFrame, const FrameMetrics& aMetrics, bool& aSuccessOut)
+ScrollFrameTo(nsIScrollableFrame* aFrame, const RepaintRequest& aRequest, bool& aSuccessOut)
 {
   aSuccessOut = false;
-  CSSPoint targetScrollPosition = aMetrics.IsRootContent()
-    ? aMetrics.GetViewport().TopLeft()
-    : aMetrics.GetScrollOffset();
+  CSSPoint targetScrollPosition = aRequest.IsRootContent()
+    ? aRequest.GetViewport().TopLeft()
+    : aRequest.GetScrollOffset();
 
   if (!aFrame) {
     return targetScrollPosition;
   }
 
   CSSPoint geckoScrollPosition = CSSPoint::FromAppUnits(aFrame->GetScrollPosition());
 
   // If the repaint request was triggered due to a previous main-thread scroll
   // offset update sent to the APZ, then we don't need to do another scroll here
   // and we can just return.
-  if (!aMetrics.GetScrollOffsetUpdated()) {
+  if (!aRequest.GetScrollOffsetUpdated()) {
     return geckoScrollPosition;
   }
 
   // If this frame is overflow:hidden, then the expectation is that it was
   // sized in a way that respects its scrollable boundaries. For the root
   // frame, this means that it cannot be scrolled in such a way that it moves
   // the layout viewport. For a non-root frame, this means that it cannot be
   // scrolled at all.
@@ -154,148 +154,147 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
   // Return the final scroll position after setting it so that anything that relies
   // on it can have an accurate value. Note that even if we set it above re-querying it
   // is a good idea because it may have gotten clamped or rounded.
   return geckoScrollPosition;
 }
 
 /**
  * Scroll the scroll frame associated with |aContent| to the scroll position
- * requested in |aMetrics|.
- * The scroll offset in |aMetrics| is updated to reflect the actual scroll
- * position.
- * The displayport stored in |aMetrics| and the callback-transform stored on
- * the content are updated to reflect any difference between the requested
- * and actual scroll positions.
+ * requested in |aRequest|.
+ *
+ * Any difference between the requested and actual scroll positions is used to
+ * update the callback-transform stored on the content, and return a new
+ * display port.
  */
-static void
+static ScreenMargin
 ScrollFrame(nsIContent* aContent,
-            FrameMetrics& aMetrics)
+            const RepaintRequest& aRequest)
 {
   // Scroll the window to the desired spot
-  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aRequest.GetScrollId());
   if (sf) {
-    sf->ResetScrollInfoIfGeneration(aMetrics.GetScrollGeneration());
-    sf->SetScrollableByAPZ(!aMetrics.IsScrollInfoLayer());
+    sf->ResetScrollInfoIfGeneration(aRequest.GetScrollGeneration());
+    sf->SetScrollableByAPZ(!aRequest.IsScrollInfoLayer());
     if (sf->IsRootScrollFrameOfDocument()) {
       if (nsCOMPtr<nsIPresShell> shell = GetPresShell(aContent)) {
-        shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aMetrics.GetScrollOffset()));
+        shell->SetVisualViewportOffset(CSSPoint::ToAppUnits(aRequest.GetScrollOffset()));
       }
     }
   }
   bool scrollUpdated = false;
-  CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
-  CSSPoint actualScrollOffset = ScrollFrameTo(sf, aMetrics, scrollUpdated);
+  ScreenMargin displayPortMargins = aRequest.GetDisplayPortMargins();
+  CSSPoint apzScrollOffset = aRequest.GetScrollOffset();
+  CSSPoint actualScrollOffset = ScrollFrameTo(sf, aRequest, scrollUpdated);
 
   if (scrollUpdated) {
-    if (aMetrics.IsScrollInfoLayer()) {
+    if (aRequest.IsScrollInfoLayer()) {
       // In cases where the APZ scroll offset is different from the content scroll
       // offset, we want to interpret the margins as relative to the APZ scroll
       // offset except when the frame is not scrollable by APZ. Therefore, if the
       // layer is a scroll info layer, we leave the margins as-is and they will
       // be interpreted as relative to the content scroll offset.
       if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
         frame->SchedulePaint();
       }
     } else {
       // Correct the display port due to the difference between mScrollOffset and the
       // actual scroll offset.
-      APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+      displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aRequest, actualScrollOffset);
     }
-  } else if (aMetrics.IsRootContent() &&
-             aMetrics.GetScrollOffset() != aMetrics.GetViewport().TopLeft()) {
+  } else if (aRequest.IsRootContent() &&
+             aRequest.GetScrollOffset() != aRequest.GetViewport().TopLeft()) {
     // APZ uses the visual viewport's offset to calculate where to place the
     // display port, so the display port is misplaced when a pinch zoom occurs.
     //
     // We need to force a display port adjustment in the following paint to
     // account for a difference between mScrollOffset and the actual scroll
     // offset in repaints requested by AsyncPanZoomController::NotifyLayersUpdated.
-    APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, actualScrollOffset);
+    displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aRequest, actualScrollOffset);
   } else {
     // For whatever reason we couldn't update the scroll offset on the scroll frame,
     // which means the data APZ used for its displayport calculation is stale. Fall
     // back to a sane default behaviour. Note that we don't tile-align the recentered
     // displayport because tile-alignment depends on the scroll position, and the
     // scroll position here is out of our control. See bug 966507 comment 21 for a
     // more detailed explanation.
-    RecenterDisplayPort(aMetrics);
+    displayPortMargins = RecenterDisplayPort(aRequest.GetDisplayPortMargins());
   }
 
-  aMetrics.SetScrollOffset(actualScrollOffset);
-
   // APZ transforms inputs assuming we applied the exact scroll offset it
   // requested (|apzScrollOffset|). Since we may not have, record the difference
   // between what APZ asked for and what we actually applied, and apply it to
   // input events to compensate.
   // Note that if the main-thread had a change in its scroll position, we don't
   // want to record that difference here, because it can be large and throw off
   // input events by a large amount. It is also going to be transient, because
   // any main-thread scroll position change will be synced to APZ and we will
   // get another repaint request when APZ confirms. In the interval while this
   // is happening we can just leave the callback transform as it was.
   bool mainThreadScrollChanged =
-    sf && sf->CurrentScrollGeneration() != aMetrics.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
+    sf && sf->CurrentScrollGeneration() != aRequest.GetScrollGeneration() && nsLayoutUtils::CanScrollOriginClobberApz(sf->LastScrollOrigin());
   if (aContent && !mainThreadScrollChanged) {
     CSSPoint scrollDelta = apzScrollOffset - actualScrollOffset;
     aContent->SetProperty(nsGkAtoms::apzCallbackTransform, new CSSPoint(scrollDelta),
                           nsINode::DeleteProperty<CSSPoint>);
   }
+
+  return displayPortMargins;
 }
 
 static void
 SetDisplayPortMargins(nsIPresShell* aPresShell,
                       nsIContent* aContent,
-                      const FrameMetrics& aMetrics)
+                      ScreenMargin aDisplayPortMargins,
+                      CSSSize aDisplayPortBase)
 {
   if (!aContent) {
     return;
   }
 
   bool hadDisplayPort = nsLayoutUtils::HasDisplayPort(aContent);
-  ScreenMargin margins = aMetrics.GetDisplayPortMargins();
-  nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, margins, 0);
+  nsLayoutUtils::SetDisplayPortMargins(aContent, aPresShell, aDisplayPortMargins, 0);
   if (!hadDisplayPort) {
     nsLayoutUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
         aContent->GetPrimaryFrame(), nsLayoutUtils::RepaintMode::Repaint);
   }
 
-  CSSSize baseSize = aMetrics.CalculateCompositedSizeInCssPixels();
   nsRect base(0, 0,
-              baseSize.width * AppUnitsPerCSSPixel(),
-              baseSize.height * AppUnitsPerCSSPixel());
+              aDisplayPortBase.width * AppUnitsPerCSSPixel(),
+              aDisplayPortBase.height * AppUnitsPerCSSPixel());
   nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
 }
 
 static void
 SetPaintRequestTime(nsIContent* aContent, const TimeStamp& aPaintRequestTime)
 {
   aContent->SetProperty(nsGkAtoms::paintRequestTime,
                         new TimeStamp(aPaintRequestTime),
                         nsINode::DeleteProperty<TimeStamp>);
 }
 
 void
-APZCCallbackHelper::UpdateRootFrame(FrameMetrics& aMetrics)
+APZCCallbackHelper::UpdateRootFrame(const RepaintRequest& aRequest)
 {
-  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+  if (aRequest.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
     return;
   }
-  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+  nsIContent* content = nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
   if (!content) {
     return;
   }
 
   nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
-  if (!shell || aMetrics.GetPresShellId() != shell->GetPresShellId()) {
+  if (!shell || aRequest.GetPresShellId() != shell->GetPresShellId()) {
     return;
   }
 
-  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+  MOZ_ASSERT(aRequest.GetUseDisplayPortMargins());
 
-  if (gfxPrefs::APZAllowZooming() && aMetrics.GetScrollOffsetUpdated()) {
+  if (gfxPrefs::APZAllowZooming() && aRequest.GetScrollOffsetUpdated()) {
     // If zooming is disabled then we don't really want to let APZ fiddle
     // with these things. In theory setting the resolution here should be a
     // no-op, but setting the visual viewport size is bad because it can cause a
     // stale value to be returned by window.innerWidth/innerHeight (see bug 1187792).
     //
     // We also skip this codepath unless the metrics has a scroll offset update
     // type other eNone, because eNone just means that this repaint request
     // was triggered by APZ in response to a main-thread update. In this
@@ -303,55 +302,61 @@ APZCCallbackHelper::UpdateRootFrame(Fram
     // it can trigger unnecessary reflows.
 
     float presShellResolution = shell->GetResolution();
 
     // If the pres shell resolution has changed on the content side side
     // the time this repaint request was fired, consider this request out of date
     // and drop it; setting a zoom based on the out-of-date resolution can have
     // the effect of getting us stuck with the stale resolution.
-    if (!FuzzyEqualsMultiplicative(presShellResolution, aMetrics.GetPresShellResolution())) {
+    if (!FuzzyEqualsMultiplicative(presShellResolution, aRequest.GetPresShellResolution())) {
       return;
     }
 
     // The pres shell resolution is updated by the the async zoom since the
     // last paint.
-    presShellResolution = aMetrics.GetPresShellResolution()
-                        * aMetrics.GetAsyncZoom().scale;
+    presShellResolution = aRequest.GetPresShellResolution()
+                        * aRequest.GetAsyncZoom().scale;
     shell->SetResolutionAndScaleTo(presShellResolution);
   }
 
   // Do this as late as possible since scrolling can flush layout. It also
   // adjusts the display port margins, so do it before we set those.
-  ScrollFrame(content, aMetrics);
+  ScreenMargin displayPortMargins = ScrollFrame(content, aRequest);
 
-  SetDisplayPortMargins(shell, content, aMetrics);
-  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+  SetDisplayPortMargins(shell,
+    content,
+    displayPortMargins,
+    aRequest.CalculateCompositedSizeInCssPixels());
+  SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
 }
 
 void
-APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
+APZCCallbackHelper::UpdateSubFrame(const RepaintRequest& aRequest)
 {
-  if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
+  if (aRequest.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
     return;
   }
-  nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
+  nsIContent* content = nsLayoutUtils::FindContentFor(aRequest.GetScrollId());
   if (!content) {
     return;
   }
 
-  MOZ_ASSERT(aMetrics.GetUseDisplayPortMargins());
+  MOZ_ASSERT(aRequest.GetUseDisplayPortMargins());
 
   // We don't currently support zooming for subframes, so nothing extra
   // needs to be done beyond the tasks common to this and UpdateRootFrame.
-  ScrollFrame(content, aMetrics);
+  ScreenMargin displayPortMargins = ScrollFrame(content, aRequest);
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
-    SetDisplayPortMargins(shell, content, aMetrics);
+    SetDisplayPortMargins(shell,
+      content,
+      displayPortMargins,
+      aRequest.CalculateCompositedSizeInCssPixels());
   }
-  SetPaintRequestTime(content, aMetrics.GetPaintRequestTime());
+  SetPaintRequestTime(content, aRequest.GetPaintRequestTime());
 }
 
 bool
 APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                                  uint32_t* aPresShellIdOut,
                                                  FrameMetrics::ViewID* aViewIdOut)
 {
     if (!aContent) {
--- a/gfx/layers/apz/util/APZCCallbackHelper.h
+++ b/gfx/layers/apz/util/APZCCallbackHelper.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_layers_APZCCallbackHelper_h
 #define mozilla_layers_APZCCallbackHelper_h
 
 #include "FrameMetrics.h"
 #include "InputData.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/layers/APZUtils.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "nsIDOMWindowUtils.h"
 #include "nsRefreshDriver.h"
 
 #include <functional>
 
 class nsIContent;
 class nsIDocument;
 class nsIPresShell;
@@ -59,27 +60,25 @@ class APZCCallbackHelper
     typedef mozilla::layers::FrameMetrics FrameMetrics;
     typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
 
 public:
     /* Applies the scroll and zoom parameters from the given FrameMetrics object
        to the root frame for the given metrics' scrollId. If tiled thebes layers
        are enabled, this will align the displayport to tile boundaries. Setting
        the scroll position can cause some small adjustments to be made to the
-       actual scroll position. aMetrics' display port and scroll position will
-       be updated with any modifications made. */
-    static void UpdateRootFrame(FrameMetrics& aMetrics);
+       actual scroll position. */
+    static void UpdateRootFrame(const RepaintRequest& aRequest);
 
     /* Applies the scroll parameters from the given FrameMetrics object to the
        subframe corresponding to given metrics' scrollId. If tiled thebes
        layers are enabled, this will align the displayport to tile boundaries.
        Setting the scroll position can cause some small adjustments to be made
-       to the actual scroll position. aMetrics' display port and scroll position
-       will be updated with any modifications made. */
-    static void UpdateSubFrame(FrameMetrics& aMetrics);
+       to the actual scroll position. */
+    static void UpdateSubFrame(const RepaintRequest& aRequest);
 
     /* Get the presShellId and view ID for the given content element.
      * If the view ID does not exist, one is created.
      * The pres shell ID should generally already exist; if it doesn't for some
      * reason, false is returned. */
     static bool GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                              uint32_t* aPresShellIdOut,
                                              FrameMetrics::ViewID* aViewIdOut);
@@ -189,18 +188,18 @@ public:
     /* Notify content that the repaint flush is complete. */
     static void NotifyFlushComplete(nsIPresShell* aShell);
 
     static void NotifyAsyncScrollbarDragRejected(const FrameMetrics::ViewID& aScrollId);
     static void NotifyAsyncAutoscrollRejected(const FrameMetrics::ViewID& aScrollId);
 
     static void CancelAutoscroll(const FrameMetrics::ViewID& aScrollId);
 
-    static void
-    AdjustDisplayPortForScrollDelta(mozilla::layers::FrameMetrics& aFrameMetrics,
+    static ScreenMargin
+    AdjustDisplayPortForScrollDelta(const RepaintRequest& aRequest,
                                     const CSSPoint& aActualScrollOffset);
 
     /*
      * Check if the scrollable frame is currently in the middle of an async
      * or smooth scroll. We want to discard certain scroll input if this is
      * true to prevent clobbering higher priority origins.
      */
     static bool
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -48,25 +48,24 @@ ChromeProcessController::~ChromeProcessC
 
 void
 ChromeProcessController::InitializeRoot()
 {
   APZCCallbackHelper::InitializeRootDisplayport(GetPresShell());
 }
 
 void
-ChromeProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+ChromeProcessController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(IsRepaintThread());
 
-  FrameMetrics metrics = aFrameMetrics;
-  if (metrics.IsRootContent()) {
-    APZCCallbackHelper::UpdateRootFrame(metrics);
+  if (aRequest.IsRootContent()) {
+    APZCCallbackHelper::UpdateRootFrame(aRequest);
   } else {
-    APZCCallbackHelper::UpdateSubFrame(metrics);
+    APZCCallbackHelper::UpdateSubFrame(aRequest);
   }
 }
 
 void
 ChromeProcessController::PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs)
 {
   MessageLoop::current()->PostDelayedTask(std::move(aTask), aDelayMs);
 }
--- a/gfx/layers/apz/util/ChromeProcessController.h
+++ b/gfx/layers/apz/util/ChromeProcessController.h
@@ -40,17 +40,17 @@ protected:
   typedef mozilla::layers::ScrollableLayerGuid ScrollableLayerGuid;
 
 public:
   explicit ChromeProcessController(nsIWidget* aWidget, APZEventState* aAPZEventState, IAPZCTreeManager* aAPZCTreeManager);
   ~ChromeProcessController();
   virtual void Destroy() override;
 
   // GeckoContentController interface
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) override;
   virtual void PostDelayedTask(already_AddRefed<Runnable> aTask, int aDelayMs) override;
   virtual bool IsRepaintThread() override;
   virtual void DispatchToRepaintThread(already_AddRefed<Runnable> aTask) override;
   MOZ_CAN_RUN_SCRIPT
   virtual void HandleTap(TapType aType,
                          const mozilla::LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
--- a/gfx/layers/apz/util/ContentProcessController.cpp
+++ b/gfx/layers/apz/util/ContentProcessController.cpp
@@ -18,20 +18,20 @@ namespace layers {
 
 ContentProcessController::ContentProcessController(const RefPtr<dom::TabChild>& aBrowser)
     : mBrowser(aBrowser)
 {
   MOZ_ASSERT(mBrowser);
 }
 
 void
-ContentProcessController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+ContentProcessController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   if (mBrowser) {
-    mBrowser->UpdateFrame(aFrameMetrics);
+    mBrowser->UpdateFrame(aRequest);
   }
 }
 
 void
 ContentProcessController::HandleTap(
                         TapType aType,
                         const LayoutDevicePoint& aPoint,
                         Modifiers aModifiers,
--- a/gfx/layers/apz/util/ContentProcessController.h
+++ b/gfx/layers/apz/util/ContentProcessController.h
@@ -35,17 +35,17 @@ class APZChild;
 class ContentProcessController final
       : public GeckoContentController
 {
 public:
   explicit ContentProcessController(const RefPtr<dom::TabChild>& aBrowser);
 
   // GeckoContentController
 
-  void RequestContentRepaint(const FrameMetrics& frame) override;
+  void RequestContentRepaint(const RepaintRequest& aRequest) override;
 
   void HandleTap(TapType aType,
                  const LayoutDevicePoint& aPoint,
                  Modifiers aModifiers,
                  const ScrollableLayerGuid& aGuid,
                  uint64_t aInputBlockId) override;
 
   void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
--- a/gfx/layers/ipc/APZChild.cpp
+++ b/gfx/layers/ipc/APZChild.cpp
@@ -25,21 +25,21 @@ APZChild::~APZChild()
 {
   if (mController) {
     mController->Destroy();
     mController = nullptr;
   }
 }
 
 mozilla::ipc::IPCResult
-APZChild::RecvRequestContentRepaint(const FrameMetrics& aFrameMetrics)
+APZChild::RecvRequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(mController->IsRepaintThread());
 
-  mController->RequestContentRepaint(aFrameMetrics);
+  mController->RequestContentRepaint(aRequest);
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 APZChild::RecvUpdateOverscrollVelocity(const float& aX, const float& aY, const bool& aIsRootContent)
 {
   mController->UpdateOverscrollVelocity(aX, aY, aIsRootContent);
   return IPC_OK();
--- a/gfx/layers/ipc/APZChild.h
+++ b/gfx/layers/ipc/APZChild.h
@@ -20,17 +20,17 @@ class GeckoContentController;
  * that lives in a different process than where APZ lives.
  */
 class APZChild final : public PAPZChild
 {
 public:
   explicit APZChild(RefPtr<GeckoContentController> aController);
   ~APZChild();
 
-  mozilla::ipc::IPCResult RecvRequestContentRepaint(const FrameMetrics& frame) override;
+  mozilla::ipc::IPCResult RecvRequestContentRepaint(const RepaintRequest& aRequest) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollVelocity(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
   mozilla::ipc::IPCResult RecvUpdateOverscrollOffset(const float& aX, const float& aY, const bool& aIsRootContent) override;
 
   mozilla::ipc::IPCResult RecvNotifyMozMouseScrollEvent(const ViewID& aScrollId,
                                                         const nsString& aEvent) override;
 
--- a/gfx/layers/ipc/LayersMessageUtils.h
+++ b/gfx/layers/ipc/LayersMessageUtils.h
@@ -18,16 +18,17 @@
 #include "mozilla/layers/CompositorOptions.h"
 #include "mozilla/layers/CompositorTypes.h"
 #include "mozilla/layers/FocusTarget.h"
 #include "mozilla/layers/GeckoContentController.h"
 #include "mozilla/layers/KeyboardMap.h"
 #include "mozilla/layers/LayerAttributes.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "mozilla/layers/RefCountedShmem.h"
+#include "mozilla/layers/RepaintRequest.h"
 #include "mozilla/Move.h"
 
 #include <stdint.h>
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4800 )
 #endif
 
@@ -89,16 +90,24 @@ struct ParamTraits<mozilla::layers::Scro
 template<>
 struct ParamTraits<mozilla::layers::FrameMetrics::ScrollOffsetUpdateType>
   : public ContiguousEnumSerializerInclusive<
              mozilla::layers::FrameMetrics::ScrollOffsetUpdateType,
              mozilla::layers::FrameMetrics::ScrollOffsetUpdateType::eNone,
              mozilla::layers::FrameMetrics::sHighestScrollOffsetUpdateType>
 {};
 
+template<>
+struct ParamTraits<mozilla::layers::RepaintRequest::ScrollOffsetUpdateType>
+  : public ContiguousEnumSerializerInclusive<
+             mozilla::layers::RepaintRequest::ScrollOffsetUpdateType,
+             mozilla::layers::RepaintRequest::ScrollOffsetUpdateType::eNone,
+             mozilla::layers::RepaintRequest::sHighestScrollOffsetUpdateType>
+{};
+
 template <>
 struct ParamTraits<mozilla::layers::OverscrollBehavior>
   : public ContiguousEnumSerializerInclusive<
             mozilla::layers::OverscrollBehavior,
             mozilla::layers::OverscrollBehavior::Auto,
             mozilla::layers::kHighestOverscrollBehavior>
 {};
 
@@ -205,16 +214,65 @@ struct ParamTraits<mozilla::layers::Fram
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRootContent) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetDoSmoothScroll) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUseDisplayPortMargins) &&
             ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsScrollInfoLayer));
   }
 };
 
 template <>
+struct ParamTraits<mozilla::layers::RepaintRequest>
+    : BitfieldHelper<mozilla::layers::RepaintRequest>
+{
+  typedef mozilla::layers::RepaintRequest paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam)
+  {
+    WriteParam(aMsg, aParam.mScrollId);
+    WriteParam(aMsg, aParam.mPresShellResolution);
+    WriteParam(aMsg, aParam.mCompositionBounds);
+    WriteParam(aMsg, aParam.mCumulativeResolution);
+    WriteParam(aMsg, aParam.mDevPixelsPerCSSPixel);
+    WriteParam(aMsg, aParam.mScrollOffset);
+    WriteParam(aMsg, aParam.mZoom);
+    WriteParam(aMsg, aParam.mScrollGeneration);
+    WriteParam(aMsg, aParam.mDisplayPortMargins);
+    WriteParam(aMsg, aParam.mPresShellId);
+    WriteParam(aMsg, aParam.mViewport);
+    WriteParam(aMsg, aParam.mExtraResolution);
+    WriteParam(aMsg, aParam.mPaintRequestTime);
+    WriteParam(aMsg, aParam.mScrollUpdateType);
+    WriteParam(aMsg, aParam.mIsRootContent);
+    WriteParam(aMsg, aParam.mUseDisplayPortMargins);
+    WriteParam(aMsg, aParam.mIsScrollInfoLayer);
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
+  {
+    return (ReadParam(aMsg, aIter, &aResult->mScrollId) &&
+            ReadParam(aMsg, aIter, &aResult->mPresShellResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mCompositionBounds) &&
+            ReadParam(aMsg, aIter, &aResult->mCumulativeResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mDevPixelsPerCSSPixel) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollOffset) &&
+            ReadParam(aMsg, aIter, &aResult->mZoom) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollGeneration) &&
+            ReadParam(aMsg, aIter, &aResult->mDisplayPortMargins) &&
+            ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
+            ReadParam(aMsg, aIter, &aResult->mViewport) &&
+            ReadParam(aMsg, aIter, &aResult->mExtraResolution) &&
+            ReadParam(aMsg, aIter, &aResult->mPaintRequestTime) &&
+            ReadParam(aMsg, aIter, &aResult->mScrollUpdateType) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsRootContent) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetUseDisplayPortMargins) &&
+            ReadBoolForBitfield(aMsg, aIter, aResult, &paramType::SetIsScrollInfoLayer));
+  }
+};
+
+template <>
 struct ParamTraits<mozilla::layers::ScrollSnapInfo>
 {
   typedef mozilla::layers::ScrollSnapInfo paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mScrollSnapTypeX);
     WriteParam(aMsg, aParam.mScrollSnapTypeY);
--- a/gfx/layers/ipc/PAPZ.ipdl
+++ b/gfx/layers/ipc/PAPZ.ipdl
@@ -6,17 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include "mozilla/GfxMessageUtils.h";
 include "mozilla/layers/LayersMessageUtils.h";
 
 include protocol PCompositorBridge;
 
 using CSSRect from "Units.h";
-using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
+using struct mozilla::layers::RepaintRequest from "mozilla/layers/RepaintRequest.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
 using mozilla::layers::FrameMetrics::ViewID from "FrameMetrics.h";
 using mozilla::layers::MaybeZoomConstraints from "FrameMetrics.h";
 using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h";
 using mozilla::dom::TabId from "mozilla/dom/ipc/IdType.h";
 using mozilla::dom::ContentParentId from "mozilla/dom/ipc/IdType.h";
 using mozilla::layers::AsyncDragMetrics from "mozilla/layers/AsyncDragMetrics.h";
 using class nsRegion from "nsRegion.h";
@@ -45,17 +45,17 @@ sync protocol PAPZ
   manager PCompositorBridge;
 
 parent:
 
   async __delete__();
 
 child:
 
-  async RequestContentRepaint(FrameMetrics frame);
+  async RequestContentRepaint(RepaintRequest request);
 
   async UpdateOverscrollVelocity(float aX, float aY, bool aIsRootContent);
 
   async UpdateOverscrollOffset(float aX, float aY, bool aIsRootContent);
 
   async NotifyMozMouseScrollEvent(ViewID aScrollId, nsString aEvent);
 
   async NotifyAPZStateChange(ScrollableLayerGuid aGuid, APZStateChange aChange, int aArg);
--- a/gfx/layers/ipc/RemoteContentController.cpp
+++ b/gfx/layers/ipc/RemoteContentController.cpp
@@ -30,22 +30,22 @@ RemoteContentController::RemoteContentCo
 {
 }
 
 RemoteContentController::~RemoteContentController()
 {
 }
 
 void
-RemoteContentController::RequestContentRepaint(const FrameMetrics& aFrameMetrics)
+RemoteContentController::RequestContentRepaint(const RepaintRequest& aRequest)
 {
   MOZ_ASSERT(IsRepaintThread());
 
   if (mCanSend) {
-    Unused << SendRequestContentRepaint(aFrameMetrics);
+    Unused << SendRequestContentRepaint(aRequest);
   }
 }
 
 void
 RemoteContentController::HandleTapOnMainThread(TapType aTapType,
                                                LayoutDevicePoint aPoint,
                                                Modifiers aModifiers,
                                                ScrollableLayerGuid aGuid,
--- a/gfx/layers/ipc/RemoteContentController.h
+++ b/gfx/layers/ipc/RemoteContentController.h
@@ -34,17 +34,17 @@ class RemoteContentController : public G
   using GeckoContentController::TapType;
   using GeckoContentController::APZStateChange;
 
 public:
   RemoteContentController();
 
   virtual ~RemoteContentController();
 
-  virtual void RequestContentRepaint(const FrameMetrics& aFrameMetrics) override;
+  virtual void RequestContentRepaint(const RepaintRequest& aRequest) override;
 
   virtual void HandleTap(TapType aTapType,
                          const LayoutDevicePoint& aPoint,
                          Modifiers aModifiers,
                          const ScrollableLayerGuid& aGuid,
                          uint64_t aInputBlockId) override;
 
   virtual void NotifyPinchGesture(PinchGestureInput::PinchGestureType aType,
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -222,16 +222,17 @@ EXPORTS.mozilla.layers += [
     'opengl/MacIOSurfaceTextureClientOGL.h',
     'opengl/MacIOSurfaceTextureHostOGL.h',
     'opengl/TextureClientOGL.h',
     'opengl/TextureHostOGL.h',
     'PaintThread.h',
     'PersistentBufferProvider.h',
     'ProfilerScreenshots.h',
     'RenderTrace.h',
+    'RepaintRequest.h',
     'RotatedBuffer.h',
     'ShareableCanvasRenderer.h',
     'SourceSurfaceSharedData.h',
     'SourceSurfaceVolatileData.h',
     'SyncObject.h',
     'TextureSourceProvider.h',
     'TextureWrapperImage.h',
     'TransactionIdAllocator.h',
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9732,17 +9732,17 @@ GetPresShell(const nsIContent* aContent)
 {
   nsCOMPtr<nsIPresShell> result;
   if (nsIDocument* doc = aContent->GetComposedDoc()) {
     result = doc->GetShell();
   }
   return result.forget();
 }
 
-static void UpdateDisplayPortMarginsForPendingMetrics(FrameMetrics& aMetrics) {
+static void UpdateDisplayPortMarginsForPendingMetrics(const RepaintRequest& aMetrics) {
   nsIContent* content = nsLayoutUtils::FindContentFor(aMetrics.GetScrollId());
   if (!content) {
     return;
   }
 
   nsCOMPtr<nsIPresShell> shell = GetPresShell(content);
   if (!shell) {
     return;
@@ -9773,39 +9773,39 @@ static void UpdateDisplayPortMarginsForP
 
   DisplayPortMarginsPropertyData* currentData =
     static_cast<DisplayPortMarginsPropertyData*>(content->GetProperty(nsGkAtoms::DisplayPortMargins));
   if (!currentData) {
     return;
   }
 
   CSSPoint frameScrollOffset = CSSPoint::FromAppUnits(frame->GetScrollPosition());
-  APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset);
+  ScreenMargin displayPortMargins = APZCCallbackHelper::AdjustDisplayPortForScrollDelta(aMetrics, frameScrollOffset);
 
   nsLayoutUtils::SetDisplayPortMargins(content, shell,
-                                       aMetrics.GetDisplayPortMargins(), 0);
+                                       displayPortMargins, 0);
 }
 
 /* static */ void
 nsLayoutUtils::UpdateDisplayPortMarginsFromPendingMessages()
 {
   if (XRE_IsContentProcess() &&
       mozilla::layers::CompositorBridgeChild::Get() &&
       mozilla::layers::CompositorBridgeChild::Get()->GetIPCChannel()) {
     CompositorBridgeChild::Get()->GetIPCChannel()->PeekMessages(
       [](const IPC::Message& aMsg) -> bool {
         if (aMsg.type() == mozilla::layers::PAPZ::Msg_RequestContentRepaint__ID) {
           PickleIterator iter(aMsg);
-          FrameMetrics frame;
-          if (!IPC::ReadParam(&aMsg, &iter, &frame)) {
+          RepaintRequest request;
+          if (!IPC::ReadParam(&aMsg, &iter, &request)) {
             MOZ_ASSERT(false);
             return true;
           }
 
-          UpdateDisplayPortMarginsForPendingMetrics(frame);
+          UpdateDisplayPortMarginsForPendingMetrics(request);
         }
         return true;
       });
   }
 }
 
 /* static */ bool
 nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame)