Bug 1779645 Part 1 - Switch CSS named page fragmentation to happen at reflow instead of frame construction r=dholbert
This also adds a small post-processing step for frame-construction to ensure that replaced frames (or more generally frames with content but no children) have the auto page-name set on them. When page-name value tracking is switched to be lazy, this post-processing step will likely be redundant and can be removed. Differential Revision: https://phabricator.services.mozilla.com/D152701
This commit is contained in:
@@ -5502,89 +5502,13 @@ void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
|
||||
return;
|
||||
}
|
||||
|
||||
bool pageNameBreak = false;
|
||||
// TODO: We should document why the TextIsOnlyWhitespace() check is needed.
|
||||
// This will be documented as part of fixing Bug 1782324
|
||||
if (aParentFrame && aState.mPresContext->IsPaginated() &&
|
||||
StaticPrefs::layout_css_named_pages_enabled() &&
|
||||
!aContent->TextIsOnlyWhitespace()) {
|
||||
// TODO: This is slightly incorrect! See Bug 1764437
|
||||
// We should be waiting all of our descendent frames to be constructed.
|
||||
//
|
||||
// Alternatively, we could propagate this back up the frame tree after
|
||||
// constructing this frame's first child, inspecting the parent frames and
|
||||
// rewriting their first child page-name.
|
||||
const StylePageName& pageName = aComputedStyle->StylePage()->mPage;
|
||||
|
||||
// Resolve auto against the parent frame's used page name, which has been
|
||||
// determined and set on aState.mAutoPageNameValue. If this item is not
|
||||
// block-level then we use the value that auto resolves to.
|
||||
//
|
||||
// This is to achieve the propagation behavior described in the spec:
|
||||
//
|
||||
// "A start page value and end page value is determined for each box as
|
||||
// the value (if any) propagated from its first or last child box
|
||||
// (respectively), else the used value on the box itself."
|
||||
//
|
||||
// "A child propagates its own start or end page value if and only if the
|
||||
// page property applies to it."
|
||||
//
|
||||
// The page property only applies to "boxes that create class A break
|
||||
// points". When taken together, means that non block-level children do
|
||||
// not propagate start/end page values, and instead we use "the used
|
||||
// value on the box itself", the "box itself" being aParentFrame. This
|
||||
// value has been determined and saved as aState.mAutoPageNameValue
|
||||
//
|
||||
// https://www.w3.org/TR/css-page-3/#using-named-pages
|
||||
// https://www.w3.org/TR/css-break-3/#btw-blocks
|
||||
const nsAtom* const pageNameAtom =
|
||||
(pageName.IsPageName() &&
|
||||
aComputedStyle->StyleDisplay()->IsBlockOutsideStyle())
|
||||
? pageName.AsPageName().AsAtom()
|
||||
: aState.mAutoPageNameValue;
|
||||
|
||||
// Check if we are the first child of our parent. If so, propagate this
|
||||
// child's page name up the frame tree for every frame while our ancestor
|
||||
// is the first child of its parent.
|
||||
nsIFrame::PageValues* const framePageValues =
|
||||
aParentFrame->GetProperty(nsIFrame::PageValuesProperty());
|
||||
// TODO alaskanemily: This assert should be removed when we move to lazily
|
||||
// setting the PageValuesProperty
|
||||
MOZ_ASSERT(framePageValues,
|
||||
"child box page names should have been created by "
|
||||
"AutoFrameConstructionPageName");
|
||||
if (!framePageValues->mStartPageValue) {
|
||||
framePageValues->mStartPageValue = pageNameAtom;
|
||||
if (nsIFrame* const prevFrame = aParentFrame->GetPrevSibling()) {
|
||||
const nsIFrame::PageValues* const prevPageValues =
|
||||
prevFrame->GetProperty(nsIFrame::PageValuesProperty());
|
||||
if (prevPageValues && prevPageValues->mEndPageValue != pageNameAtom) {
|
||||
pageNameBreak = true;
|
||||
}
|
||||
}
|
||||
// Propagate the start page value back up the frame tree.
|
||||
// If the frame already has mStartPageValue set, then we are not a
|
||||
// descendant of the frame's first child.
|
||||
for (nsContainerFrame* frame = aParentFrame->GetParent(); frame;
|
||||
frame = frame->GetParent()) {
|
||||
nsIFrame::PageValues* const parentPageValues =
|
||||
frame->GetProperty(nsIFrame::PageValuesProperty());
|
||||
if (!parentPageValues || parentPageValues->mStartPageValue) {
|
||||
break;
|
||||
}
|
||||
parentPageValues->mStartPageValue = pageNameAtom;
|
||||
}
|
||||
}
|
||||
framePageValues->mEndPageValue = pageNameAtom;
|
||||
}
|
||||
|
||||
const bool canHavePageBreak =
|
||||
aFlags.contains(ItemFlag::AllowPageBreak) &&
|
||||
aState.mPresContext->IsPaginated() &&
|
||||
!display.IsAbsolutelyPositionedStyle() &&
|
||||
!(aParentFrame && aParentFrame->IsGridContainerFrame()) &&
|
||||
!(bits & FCDATA_IS_TABLE_PART) && !(bits & FCDATA_IS_SVG_TEXT);
|
||||
if (canHavePageBreak && (pageNameBreak || display.BreakBefore())) {
|
||||
if (canHavePageBreak && display.BreakBefore()) {
|
||||
AppendPageBreakItem(aContent, aItems);
|
||||
}
|
||||
|
||||
@@ -9626,6 +9550,87 @@ inline void nsCSSFrameConstructor::ConstructFramesFromItemList(
|
||||
|
||||
VerifyGridFlexContainerChildren(aParentFrame, aFrameList);
|
||||
|
||||
// Calculate and propagate page-name values for each frame in the frame list.
|
||||
// This will be affected by https://bugzilla.mozilla.org/1782597
|
||||
if (aState.mPresContext->IsPaginated() &&
|
||||
StaticPrefs::layout_css_named_pages_enabled()) {
|
||||
// Set the start/end page values while iterating the frame list, to walk
|
||||
// up the frame tree only once after iterating the frame list.
|
||||
// This also avoids extra property lookups on these frames.
|
||||
const nsAtom* startPageValue = nullptr;
|
||||
const nsAtom* endPageValue = nullptr;
|
||||
for (nsIFrame* f : aFrameList) {
|
||||
// Resolve auto against the parent frame's used page name, which has been
|
||||
// determined and set on aState.mAutoPageNameValue. If this item is not
|
||||
// block-level then we use the value that auto resolves to.
|
||||
//
|
||||
// This is to achieve the propagation behavior described in the spec:
|
||||
//
|
||||
// "A start page value and end page value is determined for each box as
|
||||
// the value (if any) propagated from its first or last child box
|
||||
// (respectively), else the used value on the box itself."
|
||||
//
|
||||
// "A child propagates its own start or end page value if and only if the
|
||||
// page property applies to it."
|
||||
//
|
||||
// The page property only applies to "boxes that create class A break
|
||||
// points". When taken together, this means that non block-level children
|
||||
// do not propagate start/end page values, and instead we use "the used
|
||||
// value on the box itself", the "box itself" being aParentFrame. This
|
||||
// value has been determined and saved as aState.mAutoPageNameValue
|
||||
//
|
||||
// https://www.w3.org/TR/css-page-3/#using-named-pages
|
||||
// https://www.w3.org/TR/css-break-3/#btw-blocks
|
||||
const StylePageName& pageName = f->StylePage()->mPage;
|
||||
const nsAtom* const pageNameAtom =
|
||||
(pageName.IsPageName() && f->IsBlockOutside())
|
||||
? pageName.AsPageName().AsAtom()
|
||||
: aState.mAutoPageNameValue;
|
||||
nsIFrame::PageValues* pageValues =
|
||||
f->GetProperty(nsIFrame::PageValuesProperty());
|
||||
if (!pageValues) {
|
||||
pageValues = new nsIFrame::PageValues();
|
||||
f->AddProperty(nsIFrame::PageValuesProperty(), pageValues);
|
||||
}
|
||||
MOZ_ASSERT(!pageValues->mStartPageValue == !pageValues->mEndPageValue,
|
||||
"Both or neither mStartPageValue and mEndPageValue should "
|
||||
"have been set");
|
||||
if (!pageValues->mStartPageValue) {
|
||||
pageValues->mStartPageValue = pageNameAtom;
|
||||
pageValues->mEndPageValue = pageNameAtom;
|
||||
}
|
||||
if (!startPageValue) {
|
||||
startPageValue = pageValues->mStartPageValue;
|
||||
}
|
||||
endPageValue = pageValues->mEndPageValue;
|
||||
}
|
||||
MOZ_ASSERT(!startPageValue == !endPageValue,
|
||||
"Should have set both or neither page values");
|
||||
if (startPageValue) {
|
||||
// TODO: This is slightly incorrect! See Bug 1764437
|
||||
// We should be waiting all of our descendent frames to be constructed.
|
||||
//
|
||||
// Alternatively, we could propagate this back up the frame tree after
|
||||
// constructing this frame's first child, inspecting the parent frames
|
||||
// and rewriting their first child page-name.
|
||||
for (nsContainerFrame* frame = aParentFrame; frame;
|
||||
frame = frame->GetParent()) {
|
||||
nsIFrame::PageValues* const parentPageValues =
|
||||
frame->GetProperty(nsIFrame::PageValuesProperty());
|
||||
// Propagate the start page value back up the frame tree.
|
||||
// If the frame already has mStartPageValue set, then we are not a
|
||||
// descendant of the frame's first child.
|
||||
if (!parentPageValues) {
|
||||
break;
|
||||
}
|
||||
if (!parentPageValues->mStartPageValue) {
|
||||
parentPageValues->mStartPageValue = startPageValue;
|
||||
}
|
||||
parentPageValues->mEndPageValue = endPageValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (aParentIsWrapperAnonBox) {
|
||||
for (nsIFrame* f : aFrameList) {
|
||||
f->SetParentIsWrapperAnonBox();
|
||||
|
||||
@@ -2626,6 +2626,29 @@ void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
|
||||
|
||||
LineIterator line = LinesBegin(), line_end = LinesEnd();
|
||||
|
||||
// Determine if children of this frame could have breaks between them for
|
||||
// page names.
|
||||
//
|
||||
// We need to check for paginated layout, the named-page pref, and if the
|
||||
// available block-size is constrained.
|
||||
//
|
||||
// Note that we need to check for paginated layout as named-pages are only
|
||||
// used during paginated reflow. We need to additionally check for
|
||||
// unconstrained block-size to avoid introducing fragmentation breaks during
|
||||
// "measuring" reflows within an overall paginated reflow, and to avoid
|
||||
// fragmentation in monolithic containers like 'inline-block'.
|
||||
//
|
||||
// Because we can only break for named pages using Class A breakpoints, we
|
||||
// also need to check that the block flow direction of the containing frame
|
||||
// of these items (which is this block) is parallel to that of this page.
|
||||
// See: https://www.w3.org/TR/css-break-3/#btw-blocks
|
||||
const nsPresContext* const presCtx = aState.mPresContext;
|
||||
const bool canBreakForPageNames =
|
||||
aState.mReflowInput.AvailableBSize() != NS_UNCONSTRAINEDSIZE &&
|
||||
presCtx->IsPaginated() && StaticPrefs::layout_css_named_pages_enabled() &&
|
||||
presCtx->GetPresShell()->GetRootFrame()->GetWritingMode().IsVertical() ==
|
||||
GetWritingMode().IsVertical();
|
||||
|
||||
// Reflow the lines that are already ours
|
||||
for (; line != line_end; ++line, aState.AdvanceToNextLine()) {
|
||||
DumpLine(aState, line, deltaBCoord, 0);
|
||||
@@ -2779,6 +2802,38 @@ void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for a page break caused by CSS named pages.
|
||||
//
|
||||
// We should break for named pages when two frames meet at a class A
|
||||
// breakpoint, where the first frame has a different end page value to the
|
||||
// second frame's start page value. canBreakForPageNames is true iff
|
||||
// children of this frame can form class A breakpoints, and that we are not
|
||||
// in a measurement reflow or in a monolithic container such as
|
||||
// 'inline-block'.
|
||||
//
|
||||
// We specifically do not want to cause a page-break for named pages when
|
||||
// we are at the top of a page. This would otherwise happen when the
|
||||
// previous sibling is an nsPageBreakFrame, or all previous siblings on the
|
||||
// current page are zero-height. The latter may not be per-spec, but is
|
||||
// compatible with Chrome's implementation of named pages.
|
||||
bool shouldBreakForPageName = false;
|
||||
if (canBreakForPageNames && (!aState.mReflowInput.mFlags.mIsTopOfPage ||
|
||||
!aState.IsAdjacentWithBStart())) {
|
||||
const nsIFrame* const frame = line->mFirstChild;
|
||||
if (const nsIFrame* prevFrame = frame->GetPrevSibling()) {
|
||||
if (const nsIFrame::PageValues* const pageValues =
|
||||
frame->GetProperty(nsIFrame::PageValuesProperty())) {
|
||||
const nsIFrame::PageValues* const prevPageValues =
|
||||
prevFrame->GetProperty(nsIFrame::PageValuesProperty());
|
||||
if (prevPageValues &&
|
||||
prevPageValues->mEndPageValue != pageValues->mStartPageValue) {
|
||||
shouldBreakForPageName = true;
|
||||
line->MarkDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (needToRecoverState && line->IsDirty()) {
|
||||
// We need to reconstruct the block-end margin only if we didn't
|
||||
// reflow the previous line and we do need to reflow (or repair
|
||||
@@ -2826,9 +2881,18 @@ void nsBlockFrame::ReflowDirtyLines(BlockReflowState& aState) {
|
||||
"Don't reflow blocks while willReflowAgain is true, reflow "
|
||||
"of block abs-pos children depends on this");
|
||||
|
||||
// Reflow the dirty line. If it's an incremental reflow, then force
|
||||
// it to invalidate the dirty area if necessary
|
||||
ReflowLine(aState, line, &keepGoing);
|
||||
if (shouldBreakForPageName) {
|
||||
// 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.
|
||||
keepGoing = false;
|
||||
PushLines(aState, line.prev());
|
||||
aState.mReflowStatus.SetIncomplete();
|
||||
} else {
|
||||
// Reflow the dirty line. If it's an incremental reflow, then force
|
||||
// it to invalidate the dirty area if necessary
|
||||
ReflowLine(aState, line, &keepGoing);
|
||||
}
|
||||
|
||||
if (aState.mReflowInput.WillReflowAgainForClearance()) {
|
||||
line->MarkDirty();
|
||||
|
||||
@@ -2,10 +2,10 @@ defaults pref(layout.css.named-pages.enabled,true)
|
||||
|
||||
# https://bugzilla.mozilla.org/1764437
|
||||
== page-name-display-none-child.html page-name-display-none-child-ref.html
|
||||
fails == page-name-canvas-001.html page-name-canvas-001-ref.html
|
||||
fails == page-name-canvas-002.html page-name-canvas-002-ref.html
|
||||
== page-name-canvas-001.html page-name-canvas-001-ref.html
|
||||
== page-name-canvas-002.html page-name-canvas-002-ref.html
|
||||
== page-name-img-001.html page-name-img-001-ref.html
|
||||
fails == page-name-img-002.html page-name-img-002-ref.html
|
||||
== page-name-img-002.html page-name-img-002-ref.html
|
||||
== page-name-siblings-001.html page-name-siblings-ref.html
|
||||
== page-name-siblings-002.html page-name-siblings-ref.html
|
||||
== page-name-siblings-003.html page-name-siblings-ref.html
|
||||
|
||||
Reference in New Issue
Block a user