Bug 1236035 - Take the most up-to-date scroll position into account when determining what to paint. r?kats
--- 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;