Compare commits

...
Author SHA1 Message Date
Moonchild bdc924d25e WIP: basic scroll-anchoring attempt.
This is incomplete and won't build due to prerequisites.
2022-09-30 12:24:19 +00:00
9 changed files with 323 additions and 6 deletions

No files matched your search

+6
View File
@@ -485,6 +485,12 @@ public:
*/
virtual nsCanvasFrame* GetCanvasFrame() const = 0;
/**
* Returns whether the scollable frame need to have a position adjustment
* based on scroll-anchoring.
*/
virtual void ScrollableFrameNeedsAnchorAdjustment(nsIScrollableFrame* aFrame) = 0;
/**
* Tell the pres shell that a frame needs to be marked dirty and needs
* Reflow. It's OK if this is an ancestor of the frame needing reflow as
+27 -5
View File
@@ -1225,6 +1225,7 @@ PresShell::Destroy()
}
mFramesToDirty.Clear();
mScrollAnchorAdjustments.Clear();
if (mViewManager) {
// Clear the view manager's weak pointer back to |this| in case it
@@ -2049,6 +2050,11 @@ PresShell::NotifyDestroyingFrame(nsIFrame* aFrame)
}
mFramesToDirty.RemoveEntry(aFrame);
nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
if (scrollableFrame) {
mScrollAnchorAdjustments.RemoveEntry(scrollableFrame);
}
} else {
// We must delete this property in situ so that its destructor removes the
// frame from FrameLayerBuilder::DisplayItemData::mFrameList -- otherwise
@@ -2583,6 +2589,12 @@ PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame)
}
#endif
void
PresShell::ScrollableFrameNeedsAnchorAdjustment(nsIScrollableFrame* aFrame)
{
mScrollAnchorAdjustments.PutEntry(aFrame);
}
void
PresShell::FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
nsFrameState aBitToAdd,
@@ -4106,12 +4118,21 @@ PresShell::FlushPendingNotifications(mozilla::ChangesToFlush aFlush)
didLayoutFlush = true;
mFrameConstructor->RecalcQuotesAndCounters();
viewManager->FlushDelayedResize(true);
if (ProcessReflowCommands(flushType < Flush_Layout) && mContentToScrollTo) {
// We didn't get interrupted. Go ahead and scroll to our content
DoScrollContentIntoView();
if (ProcessReflowCommands(flushType < Flush_Layout)) {
// Apply scroll offset updates for scroll anchors.
for (auto iter = mScrollAnchorAdjustments.Iter(); !iter.Done(); iter.Next()) {
nsIScrollableFrame* frame = iter.Get()->GetKey();
frame->ApplyScrollAnchorOffsetAdjustment();
}
mScrollAnchorAdjustments.Clear();
if (mContentToScrollTo) {
mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
mContentToScrollTo = nullptr;
// We didn't get interrupted; go ahead and scroll to our content.
DoScrollContentIntoView();
if (mContentToScrollTo) {
mContentToScrollTo->DeleteProperty(nsGkAtoms::scrolling);
mContentToScrollTo = nullptr;
}
}
}
}
@@ -10833,6 +10854,7 @@ PresShell::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
}
*aPresShellSize += mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
*aPresShellSize += mFramesToDirty.ShallowSizeOfExcludingThis(aMallocSizeOf);
*aPresShellSize += mScrollAnchorAdjustments.ShallowSizeOfExcludingThis(aMallocSizeOf);
*aPresShellSize += aArenaObjectsSize->mOther;
if (nsStyleSet* styleSet = StyleSet()->GetAsGecko()) {
+6 -1
View File
@@ -129,6 +129,8 @@ public:
virtual nsIPageSequenceFrame* GetPageSequenceFrame() const override;
virtual nsCanvasFrame* GetCanvasFrame() const override;
virtual void ScrollableFrameNeedsAnchorAdjustment(nsIScrollableFrame* aFrame) override;
virtual void FrameNeedsReflow(nsIFrame *aFrame, IntrinsicDirty aIntrinsicDirty,
nsFrameState aBitToAdd,
ReflowRootHandling aRootHandling =
@@ -427,9 +429,10 @@ public:
void SetNextPaintCompressed() { mNextPaintCompressed = true; }
protected:
virtual ~PresShell();
protected:
void HandlePostedReflowCallbacks(bool aInterruptible);
void CancelPostedReflowCallbacks();
@@ -852,6 +855,8 @@ protected:
// Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
// we finish reflowing mCurrentReflowRoot.
nsTHashtable<nsPtrHashKey<nsIFrame> > mFramesToDirty;
nsTHashtable<nsPtrHashKey<nsIScrollableFrame> > mScrollAnchorAdjustments;
// Reflow roots that need to be reflowed.
nsTArray<nsIFrame*> mDirtyRoots;
+57
View File
@@ -688,6 +688,20 @@ nsFrame::DestroyFrom(nsIFrame* aDestructRoot)
}
}
if (HasAnyStateBits(NS_FRAME_IS_SCROLL_ANCHOR)) {
// Find the nearest scroll frame, that's the one that marked us as a
// scroll anchor
nsIFrame* currentFrame = this;
while (currentFrame) {
nsIScrollableFrame* scrollTarget = currentFrame->GetScrollTargetFrame();
if (scrollTarget) {
scrollTarget->ScrollAnchorWillDestroy();
break;
}
currentFrame = currentFrame->GetParent();
}
}
if (HasCSSAnimations() || HasCSSTransitions() ||
EffectSet::GetEffectSet(this)) {
// If no new frame for this element is created by the end of the
@@ -814,6 +828,35 @@ AddAndRemoveImageAssociations(nsFrame* aFrame,
}
}
void
nsIFrame::MaybeNotifyScrollAnchor()
{
bool isScrollAnchor = HasAnyStateBits(NS_FRAME_IS_SCROLL_ANCHOR);
bool containsScrollAnchor = HasAnyStateBits(NS_FRAME_CONTAINS_SCROLL_ANCHOR);
if (isScrollAnchor) {
printf_stderr("nsIFrame(%p)::MaybeNotifyScrollAnchor scroll anchor offset changed, queueing adjustment.\n", this);
}
if (containsScrollAnchor) {
printf_stderr("nsIFrame(%p)::MaybeNotifyScrollAnchor offset for scroll anchor container changed, queueing adjustment.\n", this);
}
MOZ_ASSERT(!(isScrollAnchor && containsScrollAnchor));
if (isScrollAnchor || containsScrollAnchor) {
// Find the nearest scroll frame, that's the one that marked us as a
// scroll anchor
nsIFrame* currentFrame = this;
while (currentFrame) {
nsIScrollableFrame* scrollTarget = currentFrame->GetScrollTargetFrame();
if (scrollTarget) {
PresShell().ScrollableFrameNeedsAnchorAdjustment(scrollTarget);
break;
}
currentFrame = currentFrame->GetParent();
}
}
}
// Subclass hook for style post processing
/* virtual */ void
nsFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext)
@@ -2902,6 +2945,13 @@ nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
aBuilder->AdjustWindowDraggingRegion(child);
child->BuildDisplayList(aBuilder, aLists);
/* // Visualise scroll anchor
if (child->HasAnyStateBits(NS_FRAME_IS_SCROLL_ANCHOR)) {
nsRect bounds = child->GetContentRectRelativeToSelf() +
aBuilder->ToReferenceFrame(child);
list.AppendToTop(MakeDisplayItem<nsDisplaySolidColor>(aBuilder, child, bounds, NS_RGBA(255, 0, 0, 55)));
}
// */
aBuilder->DisplayCaret(child, aLists.Content());
#ifdef DEBUG
DisplayDebugBorders(aBuilder, child, aLists);
@@ -2916,6 +2966,13 @@ nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder* aBuilder,
nsDisplayListCollection pseudoStack(aBuilder);
aBuilder->AdjustWindowDraggingRegion(child);
child->BuildDisplayList(aBuilder, pseudoStack);
/* // Visualise scroll anchor
if (child->HasAnyStateBits(NS_FRAME_IS_SCROLL_ANCHOR)) {
nsRect bounds = child->GetContentRectRelativeToSelf() +
aBuilder->ToReferenceFrame(child);
list.AppendToTop(MakeDisplayItem<nsDisplaySolidColor>(aBuilder, child, bounds, NS_RGBA(255, 0, 0, 55)));
}
// */
aBuilder->DisplayCaret(child, pseudoStack.Content());
list.AppendToTop(pseudoStack.BorderBackground());
+6
View File
@@ -266,6 +266,12 @@ FRAME_STATE_BIT(Generic, 53, NS_FRAME_IS_NONDISPLAY)
// Frame has a LayerActivityProperty property
FRAME_STATE_BIT(Generic, 54, NS_FRAME_HAS_LAYER_ACTIVITY_PROPERTY)
// For scroll-anchoring
FRAME_STATE_BIT(Generic, 55, NS_FRAME_CONTAINS_SCROLL_ANCHOR)
FRAME_STATE_BIT(Generic, 56, NS_FRAME_IS_SCROLL_ANCHOR)
// Bit 57 is currently unused
// Set for all descendants of MathML sub/supscript elements (other than the
// base frame) to indicate that the SSTY font feature should be used.
FRAME_STATE_BIT(Generic, 58, NS_FRAME_MATHML_SCRIPT_DESCENDANT)
+184
View File
@@ -55,6 +55,7 @@
#include "nsSVGIntegrationUtils.h"
#include "nsIScrollPositionListener.h"
#include "StickyScrollContainer.h"
#include "nsIFrame.h"
#include "nsIFrameInlines.h"
#include "gfxPlatform.h"
#include "gfxPrefs.h"
@@ -2022,6 +2023,7 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
, mScrolledFrame(nullptr)
, mScrollCornerBox(nullptr)
, mResizerBox(nullptr)
, mAnchorNode(nullptr)
, mOuter(aOuter)
, mAsyncScroll(nullptr)
, mAsyncSmoothMSDScroll(nullptr)
@@ -2033,6 +2035,7 @@ ScrollFrameHelper::ScrollFrameHelper(nsContainerFrame* aOuter,
, mScrollPosAtLastPaint(0, 0)
, mRestorePos(-1, -1)
, mLastPos(-1, -1)
, mLastAnchorPos(0, 0)
, mScrollPosForLayerPixelAlignment(-1, -1)
, mLastUpdateFramesPos(-1, -1)
, mHadDisplayPortAtLastFrameUpdate(false)
@@ -2250,6 +2253,146 @@ ScrollFrameHelper::GetScrollPositionCSSPixels()
return CSSIntPoint::FromAppUnitsRounded(GetScrollPosition());
}
static void
MarkAsScrollAnchorNode(nsIFrame* aScrolledFrame, nsIFrame* aAnchorNode)
{
aAnchorNode->AddStateBits(NS_FRAME_IS_SCROLL_ANCHOR);
nsIFrame* containsAnchor = aAnchorNode->GetParent();
while (containsAnchor && containsAnchor != aScrolledFrame) {
containsAnchor->AddStateBits(NS_FRAME_CONTAINS_SCROLL_ANCHOR);
containsAnchor = containsAnchor->GetParent();
}
}
static void
UnmarkAsScrollAnchorNode(nsIFrame* aScrolledFrame, nsIFrame* aAnchorNode)
{
aAnchorNode->RemoveStateBits(NS_FRAME_IS_SCROLL_ANCHOR);
nsIFrame* containsAnchor = aAnchorNode->GetParent();
while (containsAnchor && containsAnchor != aScrolledFrame) {
containsAnchor->RemoveStateBits(NS_FRAME_CONTAINS_SCROLL_ANCHOR);
containsAnchor = containsAnchor->GetParent();
}
}
static nsRect
FindScrollAnchorRect(nsIFrame* aScrollableFrame, nsIFrame* aAnchorNode)
{
nsRect rect = aAnchorNode->GetContentRectRelativeToSelf();
rect.MoveBy(aAnchorNode->GetOffsetTo(aScrollableFrame->GetParent()));
return rect;
}
void
ScrollFrameHelper::UpdateScrollAnchor()
{
printf_stderr("\nnsGfxScrollFrame(%p)::UpdateScrollAnchor scrollport=(%d, %d)x(%d, %d)\n",
this,
mScrollPort.x,
mScrollPort.y,
mScrollPort.width,
mScrollPort.height);
MOZ_ASSERT(mScrolledFrame);
nsIFrame* oldAnchorNode = mAnchorNode;
for (nsIFrame* kid : mScrolledFrame->PrincipalChildList()) {
nsIFrame* candidate = SelectScrollAnchorImpl(kid);
if (candidate) {
mAnchorNode = candidate;
break;
}
}
if (oldAnchorNode != mAnchorNode) {
if (oldAnchorNode) {
UnmarkAsScrollAnchorNode(mScrolledFrame, oldAnchorNode);
// Make sure we stop highlighting the old anchor
oldAnchorNode->InvalidateFrame();
}
if (mAnchorNode) {
MarkAsScrollAnchorNode(mScrolledFrame, mAnchorNode);
// Make sure we start highlighting the new anchor
mAnchorNode->InvalidateFrame();
}
}
if (mAnchorNode) {
mLastAnchorPos = FindScrollAnchorRect(mOuter, mAnchorNode).TopLeft();
} else {
mLastAnchorPos = nsPoint();
}
}
nsIFrame*
ScrollFrameHelper::SelectScrollAnchorImpl(nsIFrame* aNode)
{
nsRect rect = FindScrollAnchorRect(mOuter, aNode);
bool excludedSubtree =
aNode->IsTransformed() ||
aNode->GetType() == mozilla::LayoutFrameType::Placeholder ||
aNode->GetType() == mozilla::LayoutFrameType::Inline ||
aNode->GetType() == mozilla::LayoutFrameType::Line ||
aNode->IsRelativelyPositioned(); // should exclude only sticky, this was easier
bool opaqueSubtree = aNode->GetType() == mozilla::LayoutFrameType::Scroll;
nsCString tag;
nsIFrame::ListTag(tag, aNode);
if (excludedSubtree) {
printf_stderr("Found excluded subtree [frame=%s]\n",
tag.get());
return nullptr;
}
bool fullyVisible = mScrollPort.Contains(rect) && !rect.IsEmpty();
// If this frame is fully visible then select it as the scroll anchor
if (fullyVisible) {
printf_stderr("Found contained frame [frame=%s rect=(%d, %d)x(%d, %d)]\n",
tag.get(),
rect.x,
rect.y,
rect.width,
rect.height);
return aNode;
}
// If this frame is partially visible then examine its children for a better
// scroll anchor
if (mScrollPort.Intersects(rect)) {
printf_stderr("Found intersected frame [frame=%s rect=(%d, %d)x(%d, %d)]\n",
tag.get(),
rect.x,
rect.y,
rect.width,
rect.height);
// This frame is opaque and can be a scroll anchor, but its contents should
// not be scroll anchors
if (opaqueSubtree) {
return aNode;
}
// Try to find a deeper frame that is fully visible in the scroll port
for (nsIFrame* kid : aNode->PrincipalChildList()) {
nsIFrame* candidate = SelectScrollAnchorImpl(kid);
if (candidate) {
printf_stderr("Using a closer child frame [frame=%p]\n", candidate);
return candidate;
}
}
printf_stderr("Fallback to intersected frame [frame=%s]\n",
tag.get());
return aNode;
}
return nullptr;
}
/*
* this method wraps calls to ScrollToImpl(), either in one shot or incrementally,
* based on the setting of the smoothness scroll pref
@@ -2833,6 +2976,7 @@ ScrollFrameHelper::ScrollToImpl(nsPoint aPt, const nsRect& aRange, nsIAtom* aOri
mScrollGeneration = ++sScrollGenerationCounter;
ScrollVisual();
UpdateScrollAnchor();
bool schedulePaint = true;
if (nsLayoutUtils::AsyncPanZoomEnabled(mOuter) && gfxPrefs::APZPaintSkipping()) {
@@ -4342,6 +4486,7 @@ ScrollFrameHelper::ReloadChildFrames()
mVScrollbarBox = nullptr;
mScrollCornerBox = nullptr;
mResizerBox = nullptr;
mAnchorNode = nullptr;
for (nsIFrame* frame : mOuter->PrincipalChildList()) {
nsIContent* content = frame->GetContent();
@@ -6250,3 +6395,42 @@ ScrollFrameHelper::UsesContainerScrolling() const
return false;
}
void
ScrollFrameHelper::ScrollAnchorWillDestroy()
{
if (mAnchorNode) {
UnmarkAsScrollAnchorNode(mScrolledFrame, mAnchorNode);
mAnchorNode = nullptr;
mLastAnchorPos = nsPoint();
// XXX Should we calculate a new one anchor here, or push a callback
// to do that at a safer time?
}
}
void
ScrollFrameHelper::ApplyScrollAnchorOffsetAdjustment()
{
MOZ_ASSERT(mAnchorNode);
nsPoint currentAnchorPos = FindScrollAnchorRect(mOuter, mAnchorNode).TopLeft();
nsPoint adjustment = currentAnchorPos - mLastAnchorPos;
nsIntPoint adjustmentDevicePixels = adjustment
.ToNearestPixels(mOuter->PresContext()->AppUnitsPerDevPixel());
printf_stderr("nsGfxScrollFrame(%p)::ApplyScrollAnchorOffsetAdjustment adjustment=(%d, %d)\n",
this,
adjustmentDevicePixels.x,
adjustmentDevicePixels.y);
nsIFrame* scrollAnchor = mAnchorNode;
ScrollBy(adjustmentDevicePixels,
nsIScrollableFrame::DEVICE_PIXELS,
nsIScrollableFrame::INSTANT,
nullptr,
nsGkAtoms::relative);
// The last scroll anchor offset should be updated within ScrollToImpl and
// the scroll anchor should not have changed
MOZ_ASSERT(scrollAnchor == mAnchorNode);
}
+30
View File
@@ -458,6 +458,11 @@ public:
return mSuppressScrollbarRepaints;
}
void ScrollAnchorWillDestroy();
void ApplyScrollAnchorOffsetAdjustment();
// owning references to the nsIAnonymousContentCreator-built content
nsCOMPtr<nsIContent> mHScrollbarContent;
nsCOMPtr<nsIContent> mVScrollbarContent;
@@ -472,6 +477,7 @@ public:
nsIFrame* mScrolledFrame;
nsIFrame* mScrollCornerBox;
nsIFrame* mResizerBox;
nsIFrame* mAnchorNode;
nsContainerFrame* mOuter;
RefPtr<AsyncScroll> mAsyncScroll;
RefPtr<AsyncSmoothMSDScroll> mAsyncSmoothMSDScroll;
@@ -500,6 +506,11 @@ public:
// other than trying to restore mRestorePos.
nsPoint mLastPos;
// The last scroll position of the scroll anchor node. This is in the same
// coordinate space as mScrollPort, that is, relative to the parent of this
// scrollable frame.
nsPoint mLastAnchorPos;
nsExpirationState mActivityExpirationState;
nsCOMPtr<nsITimer> mScrollActivityTimer;
@@ -619,6 +630,9 @@ protected:
bool mOldSuppressValue;
};
void UpdateScrollAnchor();
nsIFrame* SelectScrollAnchorImpl(nsIFrame* aNode);
/**
* @note This method might destroy the frame, pres shell and other objects.
*/
@@ -1047,6 +1061,14 @@ public:
virtual mozilla::a11y::AccType AccessibleType() override;
#endif
virtual void ScrollAnchorWillDestroy() override {
mHelper.ScrollAnchorWillDestroy();
}
virtual void ApplyScrollAnchorOffsetAdjustment() override {
mHelper.ApplyScrollAnchorOffsetAdjustment();
}
protected:
nsHTMLScrollFrame(nsStyleContext* aContext, bool aIsRoot);
void SetSuppressScrollbarUpdate(bool aSuppress) {
@@ -1471,6 +1493,14 @@ public:
virtual nsresult GetFrameName(nsAString& aResult) const override;
#endif
virtual void ScrollAnchorWillDestroy() override {
mHelper.ScrollAnchorWillDestroy();
}
virtual void ApplyScrollAnchorOffsetAdjustment() override {
mHelper.ApplyScrollAnchorOffsetAdjustment();
}
protected:
nsXULScrollFrame(nsStyleContext* aContext, bool aIsRoot,
bool aClipAllDescendants);
+4
View File
@@ -861,6 +861,7 @@ public:
} else {
mRect = aRect;
}
MaybeNotifyScrollAnchor();
}
/**
* Set this frame's rect from a logical rect in its own writing direction
@@ -922,6 +923,7 @@ public:
// the top right of the frame instead of the top left.
mRect.MoveTo(aPt.GetPhysicalPoint(aWritingMode,
aContainerSize - mRect.Size()));
MaybeNotifyScrollAnchor();
}
/**
@@ -2742,6 +2744,8 @@ public:
const nsRect* aFrameDamageRect = nullptr,
uint32_t aFlags = 0);
void MaybeNotifyScrollAnchor();
/**
* Returns a rect that encompasses everything that might be painted by
* this frame. This includes this frame, all its descendant frames, this
+3
View File
@@ -475,6 +475,9 @@ public:
virtual ScrollSnapInfo GetScrollSnapInfo() const = 0;
virtual void SetScrollsClipOnUnscrolledOutOfFlow() = 0;
virtual void ScrollAnchorWillDestroy() = 0;
virtual void ApplyScrollAnchorOffsetAdjustment() = 0;
};
#endif