Bug 1236035 - Take the most up-to-date scroll position into account when determining what to paint. r?kats draft
authorMarkus Stange <mstange@themasta.com>
Thu, 31 Dec 2015 16:21:41 +0100
changeset 318187 acc7e020c59be78b080b3d57ad7effbd72573c11
parent 318186 d7cdfb5fffa2b3640c9f5cc6905a5e03030d6a63
child 318188 d193f59697588191937ca624fa026930303f2693
child 318192 208cae926bae1f22c5ab533db02b85cb93a27ff9
push id8848
push usermstange@themasta.com
push dateThu, 31 Dec 2015 15:26:17 +0000
reviewerskats
bugs1236035
milestone46.0a1
Bug 1236035 - Take the most up-to-date scroll position into account when determining what to paint. r?kats
gfx/layers/apz/util/APZCCallbackHelper.cpp
layout/base/nsLayoutUtils.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsGfxScrollFrame.h
layout/generic/nsIScrollableFrame.h
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -1,22 +1,24 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "APZCCallbackHelper.h"
 
+#include "ClientLayerManager.h"
 #include "ContentHelper.h"
 #include "gfxPlatform.h" // For gfxPlatform::UseTiling
 #include "gfxPrefs.h"
 #include "LayersLogging.h"  // For Stringify
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "mozilla/layers/ShadowLayers.h"
 #include "mozilla/TouchEvents.h"
 #include "nsContentUtils.h"
 #include "nsIScrollableFrame.h"
 #include "nsLayoutUtils.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIContent.h"
@@ -113,24 +115,23 @@ ScrollFrameTo(nsIScrollableFrame* aFrame
  * 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.
  */
 static void
-ScrollFrame(nsIContent* aContent,
+ScrollFrame(nsIContent* aContent, nsIScrollableFrame* aScrollableFrame,
             FrameMetrics& aMetrics)
 {
   // Scroll the window to the desired spot
-  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
   bool scrollUpdated = false;
   CSSPoint apzScrollOffset = aMetrics.GetScrollOffset();
-  CSSPoint actualScrollOffset = ScrollFrameTo(sf, apzScrollOffset, scrollUpdated);
+  CSSPoint actualScrollOffset = ScrollFrameTo(aScrollableFrame, apzScrollOffset, scrollUpdated);
 
   if (scrollUpdated) {
     if (aMetrics.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.
@@ -184,16 +185,44 @@ SetDisplayPortMargins(nsIPresShell* aPre
 
   CSSRect baseCSS = aMetrics.CalculateCompositedRectInCssPixels();
   nsRect base(0, 0,
               baseCSS.width * nsPresContext::AppUnitsPerCSSPixel(),
               baseCSS.height * nsPresContext::AppUnitsPerCSSPixel());
   nsLayoutUtils::SetDisplayPortBaseIfNotSet(aContent, base);
 }
 
+static void
+UpdateCurrentCompositorScrollPos(nsIPresShell* aPresShell,
+                                 nsIScrollableFrame* aScrollableFrame,
+                                 const FrameMetrics::ViewID& aScrollId)
+{
+  // Update our knowledge of the current compositor scroll position.
+  // If there's an upcoming paint in the ipc queue (i.e. a vsync message), we
+  // want that paint to take into account the most up-to-date scroll position
+  // it can get, and not just the one whose update ipc message we've already
+  // processed.
+  // The reason we read the compositor scroll position here, and not, say, in
+  // nsLayoutUtils::GetDisplayPort, is that we need to make sure to trigger
+  // paints whenever the display port actually changes. If we don't have a
+  // controlled sync-up point, it's easy for display port changes to go
+  // unhandled because we didn't SchedulePaint() for them.
+  if (aScrollableFrame) {
+    LayerManager* lm = aPresShell->GetLayerManager();
+    ClientLayerManager* clm = lm ? lm->AsClientLayerManager() : nullptr;
+    CompositorChild* compositor = clm ? clm->GetCompositorChild() : nullptr;
+
+    FrameMetrics compositorMetrics;
+    if (compositor &&
+        compositor->LookupCompositorFrameMetrics(aScrollId, compositorMetrics)) {
+      aScrollableFrame->UpdateCurrentCompositorScrollPosition(compositorMetrics);
+    }
+  }
+}
+
 static already_AddRefed<nsIPresShell>
 GetPresShell(const nsIContent* aContent)
 {
   nsCOMPtr<nsIPresShell> result;
   if (nsIDocument* doc = aContent->GetComposedDoc()) {
     result = doc->GetShell();
   }
   return result.forget();
@@ -237,20 +266,22 @@ APZCCallbackHelper::UpdateRootFrame(Fram
     // last paint.
     presShellResolution = aMetrics.GetPresShellResolution()
                         * aMetrics.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);
+  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+  ScrollFrame(content, sf, aMetrics);
 
   MOZ_ASSERT(nsLayoutUtils::GetDisplayPort(content));
   SetDisplayPortMargins(shell, content, aMetrics);
+  UpdateCurrentCompositorScrollPos(shell, sf, aMetrics.GetScrollId());
 }
 
 void
 APZCCallbackHelper::UpdateSubFrame(FrameMetrics& aMetrics)
 {
   if (aMetrics.GetScrollId() == FrameMetrics::NULL_SCROLL_ID) {
     return;
   }
@@ -258,19 +289,21 @@ APZCCallbackHelper::UpdateSubFrame(Frame
   if (!content) {
     return;
   }
 
   MOZ_ASSERT(aMetrics.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);
+  nsIScrollableFrame* sf = nsLayoutUtils::FindScrollableFrameFor(aMetrics.GetScrollId());
+  ScrollFrame(content, sf, aMetrics);
   if (nsCOMPtr<nsIPresShell> shell = GetPresShell(content)) {
     SetDisplayPortMargins(shell, content, aMetrics);
+    UpdateCurrentCompositorScrollPos(shell, sf, aMetrics.GetScrollId());
   }
 }
 
 bool
 APZCCallbackHelper::GetOrCreateScrollIdentifiers(nsIContent* aContent,
                                                  uint32_t* aPresShellIdOut,
                                                  FrameMetrics::ViewID* aViewIdOut)
 {
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -889,16 +889,21 @@ GetDisplayPortFromMarginsData(nsIContent
     }
 
     isRoot = true;
   }
 
   nsPoint scrollPos;
   if (nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame()) {
     scrollPos = scrollableFrame->GetScrollPosition();
+
+    if (Maybe<nsPoint> compositorScrollPos = scrollableFrame->GetCurrentCompositorScrollPosition()) {
+      // Shift the display port so that it's based the compositor scroll position.
+      base += *compositorScrollPos - scrollPos;
+    }
   }
 
   nsPresContext* presContext = frame->PresContext();
   int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
 
   LayoutDeviceToScreenScale2D res(presContext->PresShell()->GetCumulativeResolution()
                                 * nsLayoutUtils::GetTransformToAncestorScale(frame));
 
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -2543,16 +2543,19 @@ ScrollFrameHelper::ScrollToImpl(nsPoint 
   nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &oldDisplayPort);
   oldDisplayPort.MoveBy(-mScrolledFrame->GetPosition());
 
   // Update frame position for scrolling
   mScrolledFrame->SetPosition(mScrollPort.TopLeft() - pt);
   mLastScrollOrigin = aOrigin;
   mLastSmoothScrollOrigin = nullptr;
   mScrollGeneration = ++sScrollGenerationCounter;
+  if (aOrigin != nsGkAtoms::apz) {
+    mCurrentCompositorScrollPosition.reset();
+  }
 
   ScrollVisual();
 
   if (LastScrollOrigin() == nsGkAtoms::apz) {
     // If this was an apz scroll and the displayport (relative to the
     // scrolled frame) hasn't changed, then this won't trigger
     // any painting, so no need to schedule one.
     nsRect displayPort;
@@ -3331,16 +3334,44 @@ ScrollFrameHelper::ComputeFrameMetrics(L
   nsRect scrollport = mScrollPort + toReferenceFrame;
 
   return Some(nsLayoutUtils::ComputeFrameMetrics(
     mScrolledFrame, mOuter, mOuter->GetContent(),
     aContainerReferenceFrame, aLayer, mScrollParentID,
     scrollport, parentLayerClip, isRootContent, aParameters));
 }
 
+void
+ScrollFrameHelper::UpdateCurrentCompositorScrollPosition(const FrameMetrics& aCompositorFrameMetrics)
+{
+  if (mLastScrollOrigin && mLastScrollOrigin != nsGkAtoms::apz) {
+    // The scroll position has been updated on the main thread and we haven't
+    // received an acknowledgement message from the compositor yet. But the
+    // new scroll position might already have reached the compositor - check
+    // the scroll generation.
+    if (mScrollGeneration != aCompositorFrameMetrics.GetScrollGeneration()) {
+      // No, the scroll position update has not reached the compositor thread.
+      return;
+    }
+  }
+
+  nsIContent* content = mOuter->GetContent();
+  nsRect oldDisplayPort, newDisplayPort;
+  nsLayoutUtils::GetDisplayPort(content, &oldDisplayPort);
+
+  mCurrentCompositorScrollPosition.emplace(
+    CSSPoint::ToAppUnits(aCompositorFrameMetrics.GetScrollOffset()));
+
+  nsLayoutUtils::GetDisplayPort(content, &newDisplayPort);
+  if (!newDisplayPort.IsEqualEdges(oldDisplayPort)) {
+    mOuter->SchedulePaint();
+  }
+}
+
+
 bool
 ScrollFrameHelper::IsRectNearlyVisible(const nsRect& aRect) const
 {
   // Use the right rect depending on if a display port is set.
   nsRect displayPort;
   bool usingDisplayport = nsLayoutUtils::GetDisplayPort(mOuter->GetContent(), &displayPort);
   return aRect.Intersects(ExpandRectToNearlyVisible(usingDisplayport ? displayPort : mScrollPort));
 }
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -381,16 +381,23 @@ public:
   uint32_t CurrentScrollGeneration() const { return mScrollGeneration; }
   nsPoint LastScrollDestination() const { return mDestination; }
   void ResetScrollInfoIfGeneration(uint32_t aGeneration) {
     if (aGeneration == mScrollGeneration) {
       mLastScrollOrigin = nullptr;
       mLastSmoothScrollOrigin = nullptr;
     }
   }
+
+  mozilla::Maybe<nsPoint> GetCurrentCompositorScrollPosition() const
+  { return mCurrentCompositorScrollPosition; }
+
+  void UpdateCurrentCompositorScrollPosition(
+    const mozilla::layers::FrameMetrics& aCompositorFrameMetrics);
+
   bool WantAsyncScroll() const;
   Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const;
 
   // nsIScrollbarMediator
   void ScrollByPage(nsScrollbarFrame* aScrollbar, int32_t aDirection,
@@ -460,16 +467,18 @@ public:
   nsCOMPtr<nsITimer> mScrollActivityTimer;
   nsPoint mScrollPosForLayerPixelAlignment;
 
   // The scroll position where we last updated image visibility.
   nsPoint mLastUpdateImagesPos;
 
   nsRect mPrevScrolledRect;
 
+  mozilla::Maybe<nsPoint> mCurrentCompositorScrollPosition;
+
   FrameMetrics::ViewID mScrollParentID;
 
   bool mNeverHasVerticalScrollbar:1;
   bool mNeverHasHorizontalScrollbar:1;
   bool mHasVerticalScrollbar:1;
   bool mHasHorizontalScrollbar:1;
   bool mFrameIsUpdatingScrollbar:1;
   bool mDidHistoryRestore:1;
@@ -705,16 +714,19 @@ public:
     return mHelper.GetScrolledRect();
   }
   virtual nsRect GetScrollPortRect() const override {
     return mHelper.GetScrollPortRect();
   }
   virtual nsPoint GetScrollPosition() const override {
     return mHelper.GetScrollPosition();
   }
+  virtual mozilla::Maybe<nsPoint> GetCurrentCompositorScrollPosition() const override {
+    return mHelper.GetCurrentCompositorScrollPosition();
+  }
   virtual nsPoint GetLogicalScrollPosition() const override {
     return mHelper.GetLogicalScrollPosition();
   }
   virtual nsRect GetScrollRange() const override {
     return mHelper.GetScrollRange();
   }
   virtual nsSize GetScrollPositionClampingScrollPortSize() const override {
     return mHelper.GetScrollPositionClampingScrollPortSize();
@@ -833,16 +845,20 @@ public:
   }
   virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
     return mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame, aParameters, aClip);
   }
+  virtual void UpdateCurrentCompositorScrollPosition(
+    const mozilla::layers::FrameMetrics& aCompositorFrameMetrics) override {
+    mHelper.UpdateCurrentCompositorScrollPosition(aCompositorFrameMetrics);
+  }
   virtual bool IsIgnoringViewportClipping() const override {
     return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
   virtual bool UsesContainerScrolling() const override {
     return mHelper.UsesContainerScrolling();
@@ -1101,16 +1117,19 @@ public:
     return mHelper.GetScrolledRect();
   }
   virtual nsRect GetScrollPortRect() const override {
     return mHelper.GetScrollPortRect();
   }
   virtual nsPoint GetScrollPosition() const override {
     return mHelper.GetScrollPosition();
   }
+  virtual mozilla::Maybe<nsPoint> GetCurrentCompositorScrollPosition() const override {
+    return mHelper.GetCurrentCompositorScrollPosition();
+  }
   virtual nsPoint GetLogicalScrollPosition() const override {
     return mHelper.GetLogicalScrollPosition();
   }
   virtual nsRect GetScrollRange() const override {
     return mHelper.GetScrollRange();
   }
   virtual nsSize GetScrollPositionClampingScrollPortSize() const override {
     return mHelper.GetScrollPositionClampingScrollPortSize();
@@ -1225,16 +1244,20 @@ public:
   }
   virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
     Layer* aLayer, nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const override
   {
     return mHelper.ComputeFrameMetrics(aLayer, aContainerReferenceFrame, aParameters, aClip);
   }
+  virtual void UpdateCurrentCompositorScrollPosition(
+    const mozilla::layers::FrameMetrics& aCompositorFrameMetrics) override {
+    mHelper.UpdateCurrentCompositorScrollPosition(aCompositorFrameMetrics);
+  }
   virtual bool IsIgnoringViewportClipping() const override {
     return mHelper.IsIgnoringViewportClipping();
   }
   virtual void MarkScrollbarsDirtyForReflow() const override {
     mHelper.MarkScrollbarsDirtyForReflow();
   }
 
   // nsIStatefulFrame
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -127,16 +127,23 @@ public:
   virtual nsRect GetScrollPortRect() const = 0;
   /**
    * Get the offset of the scrollport origin relative to the scrolled
    * frame origin. Typically the position will be non-negative.
    * This will always be a multiple of device pixels.
    */
   virtual nsPoint GetScrollPosition() const = 0;
   /**
+   * Get the scroll position of this scroll frame on the compositor, as
+   * observed during a call to UpdateCurrentCompositorScrollPosition.
+   * Returns Nothing() if this frame was recently scrolled on the main thread
+   * and the compositor hasn't received that scroll update yet.
+   */
+  virtual mozilla::Maybe<nsPoint> GetCurrentCompositorScrollPosition() const = 0;
+  /**
    * As GetScrollPosition(), but uses the top-right as origin for RTL frames. 
    */
   virtual nsPoint GetLogicalScrollPosition() const = 0;
   /**
    * Get the area that must contain the scroll position. Typically
    * (but not always, e.g. for RTL content) x and y will be 0, and
    * width or height will be nonzero if the content can be scrolled in
    * that direction. Since scroll positions must be a multiple of
@@ -402,16 +409,24 @@ public:
    */
   virtual mozilla::Maybe<mozilla::layers::FrameMetrics> ComputeFrameMetrics(
     mozilla::layers::Layer* aLayer,
     nsIFrame* aContainerReferenceFrame,
     const ContainerLayerParameters& aParameters,
     const mozilla::DisplayItemClip* aClip) const = 0;
 
   /**
+   * Update this frame's knowledge of the current scroll position of this frame
+   * on the compositor thread. Ignored if our internal scroll position is more
+   * up-to-date than the one on the compositor.
+   */
+  virtual void UpdateCurrentCompositorScrollPosition(
+    const mozilla::layers::FrameMetrics& aCompositorFrameMetrics) = 0;
+
+  /**
    * If this scroll frame is ignoring viewporting clipping
    */
   virtual bool IsIgnoringViewportClipping() const = 0;
 
   /**
    * Mark the scrollbar frames for reflow.
    */
   virtual void MarkScrollbarsDirtyForReflow() const = 0;