diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp index 3c1896c16283..3879465e7934 100644 --- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -8042,6 +8042,20 @@ nsIFrame* nsCSSFrameConstructor::CreateContinuingFrame( return newFrame; } +void nsCSSFrameConstructor::MaybeSetNextPageContentFramePageName( + const nsIFrame* aFrame) { + MOZ_ASSERT(aFrame, "Frame should not be null"); + // No parent means the root frame, which isn't what this funciton is for. + MOZ_ASSERT(aFrame->GetParent(), + "Frame should be the first child placed on a new page, not the " + "root frame."); + if (mNextPageContentFramePageName) { + return; + } + const nsAtom* const autoValue = aFrame->GetParent()->GetAutoPageValue(); + mNextPageContentFramePageName = aFrame->ComputePageValue(autoValue); +} + nsresult nsCSSFrameConstructor::ReplicateFixedFrames( nsPageContentFrame* aParentFrame) { // Now deal with fixed-pos things.... They should appear on all pages, diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h index 1b48fd15dcc4..eff10f77a7ed 100644 --- a/layout/base/nsCSSFrameConstructor.h +++ b/layout/base/nsCSSFrameConstructor.h @@ -297,12 +297,39 @@ class nsCSSFrameConstructor final : public nsFrameManager { nsContainerFrame* aParentFrame, bool aIsFluid = true); - void SetNextPageContentFramePageName(const nsAtom* aAtom) { + /** + * Sets the page name when a page break is being generated due to a change + * in page name. + * + * Should only be used during paginated reflow, to signal what page value + * the next page content frame should have. + * + * It is an error to set this if a new page name has already been set, either + * through SetNextPageContentFramePageName or + * MaybeSetNextPageContentFramePageName. + */ + void SetNextPageContentFramePageName(const nsAtom* aPageName) { + MOZ_ASSERT(aPageName, "New page name should never be null"); MOZ_ASSERT(!mNextPageContentFramePageName, "PageContentFrame page name was already set"); - mNextPageContentFramePageName = aAtom; + mNextPageContentFramePageName = aPageName; } + /** + * If a new page name has not been set for the next page, sets the value + * using the given frame. + * + * |aFrame| should be a frame to be placed on the new page. + * + * This function handles the work of resolving an atom for the frame, and + * avoids doing this extra work when not necessary. + * + * This is used during block reflow when a page break has occurred but it was + * not caused by a change in page name. It should only be used during + * paginated reflow. + */ + void MaybeSetNextPageContentFramePageName(const nsIFrame* aFrame); + // Copy over fixed frames from aParentFrame's prev-in-flow nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame); diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp index 54976ecc470f..43ab08f64a82 100644 --- a/layout/generic/nsBlockFrame.cpp +++ b/layout/generic/nsBlockFrame.cpp @@ -3153,9 +3153,12 @@ bool nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) { // Immediately fragment for page-name. It is possible we could break // out of the loop right here, but this should make it more similar to // what happens when reflow causes fragmentation. - PushTruncatedLine(aState, line, &keepGoing); + // Set the page name, so that PushTruncatedLine does not need to + // recalculate the new page name. PresShell()->FrameConstructor()->SetNextPageContentFramePageName( nextPageName ? nextPageName : GetAutoPageValue()); + PushTruncatedLine(aState, line, &keepGoing, + ComputeNewPageNameIfNeeded::No); } else { // Reflow the dirty line. If it's an incremental reflow, then force // it to invalidate the dirty area if necessary @@ -4730,11 +4733,25 @@ void nsBlockFrame::SetBreakBeforeStatusBeforeLine(BlockReflowState& aState, *aKeepReflowGoing = false; } -void nsBlockFrame::PushTruncatedLine(BlockReflowState& aState, - LineIterator aLine, - bool* aKeepReflowGoing) { +void nsBlockFrame::PushTruncatedLine( + BlockReflowState& aState, LineIterator aLine, bool* aKeepReflowGoing, + ComputeNewPageNameIfNeeded aComputeNewPageName) { PushLines(aState, aLine.prev()); *aKeepReflowGoing = false; + + if (aComputeNewPageName == ComputeNewPageNameIfNeeded::Yes) { + // mCanHaveClassABreakpoints can only be true during paginated reflow, and + // we expect this function to only be called when the available bsize is + // constrained. + const WritingMode wm = GetWritingMode(); + const bool canBreakForPageNames = + aState.mReflowInput.mFlags.mCanHaveClassABreakpoints && + PresShell()->GetRootFrame()->GetWritingMode().IsOrthogonalTo(wm); + if (canBreakForPageNames) { + PresShell()->FrameConstructor()->MaybeSetNextPageContentFramePageName( + aLine->mFirstChild); + } + } aState.mReflowStatus.SetIncomplete(); } diff --git a/layout/generic/nsBlockFrame.h b/layout/generic/nsBlockFrame.h index 15dd4c32783c..8c5933957b65 100644 --- a/layout/generic/nsBlockFrame.h +++ b/layout/generic/nsBlockFrame.h @@ -848,13 +848,24 @@ class nsBlockFrame : public nsContainerFrame { LineIterator aLine, bool* aKeepReflowGoing); + /** + * Indicates if we need to compute a page name for the next page when pushing + * a truncated line. + * + * Using a value of No saves work when a new page name has already been set + * with nsCSSFrameConstructor::SetNextPageContentFramePageName. + */ + enum class ComputeNewPageNameIfNeeded : uint8_t { Yes, No }; + /** * Push aLine (and any after it), since it cannot be placed on this * page/column. Set aKeepReflowGoing to false and set * flag aState.mReflowStatus as incomplete. */ void PushTruncatedLine(BlockReflowState& aState, LineIterator aLine, - bool* aKeepReflowGoing); + bool* aKeepReflowGoing, + ComputeNewPageNameIfNeeded aComputeNewPageName = + ComputeNewPageNameIfNeeded::Yes); void SplitLine(BlockReflowState& aState, nsLineLayout& aLineLayout, LineIterator aLine, nsIFrame* aFrame, diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp index f4684e28a6f7..0d5af5511014 100644 --- a/layout/generic/nsIFrame.cpp +++ b/layout/generic/nsIFrame.cpp @@ -2117,8 +2117,8 @@ nsIFrame::CaretBlockAxisMetrics nsIFrame::GetCaretBlockAxisMetrics( return CaretBlockAxisMetrics{.mOffset = baseline - ascent, .mExtent = height}; } -const nsAtom* nsIFrame::ComputePageValue() const { - const nsAtom* value = nsGkAtoms::_empty; +const nsAtom* nsIFrame::ComputePageValue(const nsAtom* aAutoValue) const { + const nsAtom* value = aAutoValue ? aAutoValue : nsGkAtoms::_empty; const nsIFrame* frame = this; // Find what CSS page name value this frame's subtree has, if any. // Starting with this frame, check if a page name other than auto is present, diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h index d5011f70ffb6..81c915e3f0e2 100644 --- a/layout/generic/nsIFrame.h +++ b/layout/generic/nsIFrame.h @@ -1643,7 +1643,13 @@ class nsIFrame : public nsQueryFrame { // This is intended to be used either on the root frame to find the first // page's page-name, or on a newly created continuation to find what the new // page's page-name will be. - const nsAtom* ComputePageValue() const MOZ_NONNULL_RETURN; + // + // The auto page value can be set by the caller. This is useful when trying + // to compute a page value in the middle of a frame tree. In that case the + // auto value can be found from the AutoPageValue frame property of the + // parent frame. A null auto value is interpreted as the empty-string atom. + const nsAtom* ComputePageValue(const nsAtom* aAutoValue = nullptr) const + MOZ_NONNULL_RETURN; /////////////////////////////////////////////////////////////////////////////// // The public visibility API.