1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPageContentFrame.h"
8 #include "mozilla/PresShell.h"
9 #include "mozilla/PresShellInlines.h"
10 #include "mozilla/StaticPrefs_layout.h"
11 #include "mozilla/dom/Document.h"
13 #include "nsContentUtils.h"
14 #include "nsPageFrame.h"
15 #include "nsCSSFrameConstructor.h"
16 #include "nsPresContext.h"
17 #include "nsGkAtoms.h"
18 #include "nsLayoutUtils.h"
19 #include "nsPageSequenceFrame.h"
21 using namespace mozilla
;
23 nsPageContentFrame
* NS_NewPageContentFrame(
24 PresShell
* aPresShell
, ComputedStyle
* aStyle
,
25 already_AddRefed
<const nsAtom
> aPageName
) {
26 return new (aPresShell
) nsPageContentFrame(
27 aStyle
, aPresShell
->GetPresContext(), std::move(aPageName
));
30 NS_IMPL_FRAMEARENA_HELPERS(nsPageContentFrame
)
32 void nsPageContentFrame::Reflow(nsPresContext
* aPresContext
,
33 ReflowOutput
& aReflowOutput
,
34 const ReflowInput
& aReflowInput
,
35 nsReflowStatus
& aStatus
) {
37 DO_GLOBAL_REFLOW_COUNT("nsPageContentFrame");
38 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
39 MOZ_ASSERT(mPD
, "Need a pointer to nsSharedPageData before reflow starts");
41 if (GetPrevInFlow() && HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
43 aPresContext
->PresShell()->FrameConstructor()->ReplicateFixedFrames(
50 // Set our size up front, since some parts of reflow depend on it
51 // being already set. Note that the computed height may be
52 // unconstrained; that's ok. Consumers should watch out for that.
53 const nsSize maxSize
= aReflowInput
.ComputedPhysicalSize();
56 // Writing mode for the page content frame.
57 const WritingMode pcfWM
= aReflowInput
.GetWritingMode();
58 aReflowOutput
.ISize(pcfWM
) = aReflowInput
.ComputedISize();
59 if (aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
60 aReflowOutput
.BSize(pcfWM
) = aReflowInput
.ComputedBSize();
62 aReflowOutput
.SetOverflowAreasToDesiredBounds();
64 // A PageContentFrame must always have one child: the canvas frame.
65 // Resize our frame allowing it only to be as big as we are
66 // XXX Pay attention to the page's border and padding...
67 if (mFrames
.NotEmpty()) {
68 nsIFrame
* const frame
= mFrames
.FirstChild();
69 const WritingMode frameWM
= frame
->GetWritingMode();
70 const LogicalSize
logicalSize(frameWM
, maxSize
);
71 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, frame
, logicalSize
);
72 kidReflowInput
.SetComputedBSize(logicalSize
.BSize(frameWM
));
73 ReflowOutput
kidReflowOutput(kidReflowInput
);
74 ReflowChild(frame
, aPresContext
, kidReflowOutput
, kidReflowInput
, 0, 0,
75 ReflowChildFlags::Default
, aStatus
);
77 // The document element's background should cover the entire canvas, so
78 // take into account the combined area and any space taken up by
79 // absolutely positioned elements
80 nsMargin
padding(0, 0, 0, 0);
82 // XXXbz this screws up percentage padding (sets padding to zero
83 // in the percentage padding case)
84 frame
->StylePadding()->GetPadding(padding
);
86 // This is for shrink-to-fit, and therefore we want to use the
87 // scrollable overflow, since the purpose of shrink to fit is to
88 // make the content that ought to be reachable (represented by the
89 // scrollable overflow) fit in the page.
90 if (frame
->HasOverflowAreas()) {
91 // The background covers the content area and padding area, so check
92 // for children sticking outside the child frame's padding edge
93 nscoord xmost
= kidReflowOutput
.ScrollableOverflow().XMost();
94 if (xmost
> kidReflowOutput
.Width()) {
95 const nscoord widthToFit
=
96 xmost
+ padding
.right
+
97 kidReflowInput
.mStyleBorder
->GetComputedBorderWidth(eSideRight
);
98 const float ratio
= float(maxSize
.width
) / float(widthToFit
);
99 NS_ASSERTION(ratio
>= 0.0 && ratio
< 1.0,
100 "invalid shrink-to-fit ratio");
101 mPD
->mShrinkToFitRatio
= std::min(mPD
->mShrinkToFitRatio
, ratio
);
103 // In the case of pdf.js documents, we also want to consider the height,
104 // so that we don't clip the page in either axis if the aspect ratio of
105 // the PDF doesn't match the destination.
106 if (nsContentUtils::IsPDFJS(PresContext()->Document()->GetPrincipal())) {
107 nscoord ymost
= kidReflowOutput
.ScrollableOverflow().YMost();
108 if (ymost
> kidReflowOutput
.Height()) {
109 const nscoord heightToFit
=
110 ymost
+ padding
.bottom
+
111 kidReflowInput
.mStyleBorder
->GetComputedBorderWidth(eSideBottom
);
112 const float ratio
= float(maxSize
.height
) / float(heightToFit
);
113 MOZ_ASSERT(ratio
>= 0.0 && ratio
< 1.0);
114 mPD
->mShrinkToFitRatio
= std::min(mPD
->mShrinkToFitRatio
, ratio
);
117 // pdf.js pages should never overflow given the scaling above.
118 // nsPrintJob::SetupToPrintContent ignores some ratios close to 1.0
119 // though and doesn't reflow us again in that case, so we need to clear
120 // the overflow area here in case that happens. (bug 1689789)
121 frame
->ClearOverflowRects();
122 kidReflowOutput
.mOverflowAreas
= aReflowOutput
.mOverflowAreas
;
126 // Place and size the child
127 FinishReflowChild(frame
, aPresContext
, kidReflowOutput
, &kidReflowInput
, 0,
128 0, ReflowChildFlags::Default
);
130 NS_ASSERTION(aPresContext
->IsDynamic() || !aStatus
.IsFullyComplete() ||
131 !frame
->GetNextInFlow(),
132 "bad child flow list");
134 aReflowOutput
.mOverflowAreas
.UnionWith(kidReflowOutput
.mOverflowAreas
);
137 FinishAndStoreOverflow(&aReflowOutput
);
139 // Reflow our fixed frames
140 nsReflowStatus fixedStatus
;
141 ReflowAbsoluteFrames(aPresContext
, aReflowOutput
, aReflowInput
, fixedStatus
);
142 NS_ASSERTION(fixedStatus
.IsComplete(),
143 "fixed frames can be truncated, but not incomplete");
145 if (StaticPrefs::layout_display_list_improve_fragmentation() &&
146 mFrames
.NotEmpty()) {
147 auto* const previous
=
148 static_cast<nsPageContentFrame
*>(GetPrevContinuation());
149 const nscoord previousPageOverflow
=
150 previous
? previous
->mRemainingOverflow
: 0;
151 const nsSize
containerSize(aReflowInput
.AvailableWidth(),
152 aReflowInput
.AvailableHeight());
153 const nscoord pageBSize
= GetLogicalRect(containerSize
).BSize(pcfWM
);
154 const nscoord overflowBSize
=
155 LogicalRect(pcfWM
, ScrollableOverflowRect(), GetSize()).BEnd(pcfWM
);
156 const nscoord currentPageOverflow
= overflowBSize
- pageBSize
;
157 nscoord remainingOverflow
=
158 std::max(currentPageOverflow
, previousPageOverflow
- pageBSize
);
160 if (aStatus
.IsFullyComplete() && remainingOverflow
> 0) {
161 // If we have ScrollableOverflow off the end of our page, then we report
162 // ourselves as overflow-incomplete in order to produce an additional
163 // content-less page, which we expect to draw our overflow on our behalf.
164 aStatus
.SetOverflowIncomplete();
167 mRemainingOverflow
= std::max(remainingOverflow
, 0);
171 using PageAndOffset
= std::pair
<nsPageContentFrame
*, nscoord
>;
173 // Returns the previous continuation PageContentFrames that have overflow areas,
174 // and their offsets to the top of the given PageContentFrame |aPage|. Since the
175 // iteration is done backwards, the returned pages are arranged in descending
176 // order of page number.
177 static nsTArray
<PageAndOffset
> GetPreviousPagesWithOverflow(
178 nsPageContentFrame
* aPage
) {
179 nsTArray
<PageAndOffset
> pages(8);
181 auto GetPreviousPageContentFrame
= [](nsPageContentFrame
* aPageCF
) {
182 nsIFrame
* prevCont
= aPageCF
->GetPrevContinuation();
183 MOZ_ASSERT(!prevCont
|| prevCont
->IsPageContentFrame(),
184 "Expected nsPageContentFrame or nullptr");
186 return static_cast<nsPageContentFrame
*>(prevCont
);
189 nsPageContentFrame
* pageCF
= aPage
;
190 // The collective height of all prev-continuations we've traversed so far:
191 nscoord offsetToCurrentPageBStart
= 0;
192 const auto wm
= pageCF
->GetWritingMode();
193 while ((pageCF
= GetPreviousPageContentFrame(pageCF
))) {
194 offsetToCurrentPageBStart
+= pageCF
->BSize(wm
);
196 if (pageCF
->HasOverflowAreas()) {
197 pages
.EmplaceBack(pageCF
, offsetToCurrentPageBStart
);
204 static void BuildPreviousPageOverflow(nsDisplayListBuilder
* aBuilder
,
205 nsPageFrame
* aPageFrame
,
206 nsPageContentFrame
* aCurrentPageCF
,
207 const nsDisplayListSet
& aLists
) {
208 const auto previousPagesAndOffsets
=
209 GetPreviousPagesWithOverflow(aCurrentPageCF
);
211 const auto wm
= aCurrentPageCF
->GetWritingMode();
212 for (const PageAndOffset
& pair
: Reversed(previousPagesAndOffsets
)) {
213 auto* prevPageCF
= pair
.first
;
214 const nscoord offsetToCurrentPageBStart
= pair
.second
;
215 // Only scrollable overflow create new pages, not ink overflow.
216 const LogicalRect
scrollableOverflow(
217 wm
, prevPageCF
->ScrollableOverflowRectRelativeToSelf(),
218 prevPageCF
->GetSize());
219 const auto remainingOverflow
=
220 scrollableOverflow
.BEnd(wm
) - offsetToCurrentPageBStart
;
221 if (remainingOverflow
<= 0) {
225 // This rect represents the piece of prevPageCF's overflow that ends up on
226 // the current pageContentFrame (in prevPageCF's coordinate system).
227 // Note that we use InkOverflow here since this is for painting.
228 LogicalRect
overflowRect(wm
, prevPageCF
->InkOverflowRectRelativeToSelf(),
229 prevPageCF
->GetSize());
230 overflowRect
.BStart(wm
) = offsetToCurrentPageBStart
;
231 overflowRect
.BSize(wm
) = std::min(remainingOverflow
, prevPageCF
->BSize(wm
));
234 // Convert the overflowRect to the coordinate system of aPageFrame, and
235 // set it as the visible rect for display list building.
236 const nsRect visibleRect
=
237 overflowRect
.GetPhysicalRect(wm
, prevPageCF
->GetSize()) +
238 prevPageCF
->GetOffsetTo(aPageFrame
);
239 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
240 aBuilder
, aPageFrame
, visibleRect
, visibleRect
);
242 // This part is tricky. Because display items are positioned based on the
243 // frame tree, building a display list for the previous page yields
244 // display items that are outside of the current page bounds.
245 // To fix that, an additional reference frame offset is added, which
246 // shifts the display items down (block axis) as if the current and
247 // previous page were one long page in the same coordinate system.
248 const nsSize containerSize
= aPageFrame
->GetSize();
249 LogicalPoint
pageOffset(wm
, aCurrentPageCF
->GetOffsetTo(prevPageCF
),
251 pageOffset
.B(wm
) -= offsetToCurrentPageBStart
;
252 buildingForChild
.SetAdditionalOffset(
253 pageOffset
.GetPhysicalPoint(wm
, containerSize
));
255 aPageFrame
->BuildDisplayListForChild(aBuilder
, prevPageCF
, aLists
);
261 * Remove all leaf display items that are not for descendants of
262 * aBuilder->GetReferenceFrame() from aList.
263 * @param aPage the page we're constructing the display list for
264 * @param aList the list that is modified in-place
266 static void PruneDisplayListForExtraPage(nsDisplayListBuilder
* aBuilder
,
268 nsDisplayList
* aList
) {
269 for (nsDisplayItem
* i
: aList
->TakeItems()) {
271 nsDisplayList
* subList
= i
->GetSameCoordinateSystemChildren();
273 PruneDisplayListForExtraPage(aBuilder
, aPage
, subList
);
274 i
->UpdateBounds(aBuilder
);
276 nsIFrame
* f
= i
->Frame();
277 if (!nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(aPage
, f
)) {
278 // We're throwing this away so call its destructor now. The memory
279 // is owned by aBuilder which destroys all items at once.
280 i
->Destroy(aBuilder
);
284 aList
->AppendToTop(i
);
288 static void BuildDisplayListForExtraPage(nsDisplayListBuilder
* aBuilder
,
290 nsIFrame
* aExtraPage
,
291 nsDisplayList
* aList
) {
292 // The only content in aExtraPage we care about is out-of-flow content from
293 // aPage, whose placeholders have occurred in aExtraPage. If
294 // NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is not set, then aExtraPage has
296 if (!aExtraPage
->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO
)) {
299 nsDisplayList
list(aBuilder
);
300 aExtraPage
->BuildDisplayListForStackingContext(aBuilder
, &list
);
301 PruneDisplayListForExtraPage(aBuilder
, aPage
, &list
);
302 aList
->AppendToTop(&list
);
305 static gfx::Matrix4x4
ComputePageContentTransform(const nsIFrame
* aFrame
,
306 float aAppUnitsPerPixel
) {
307 float scale
= aFrame
->PresContext()->GetPageScale();
308 return gfx::Matrix4x4::Scaling(scale
, scale
, 1);
311 nsIFrame::ComputeTransformFunction
nsPageContentFrame::GetTransformGetter()
313 return ComputePageContentTransform
;
316 void nsPageContentFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
317 const nsDisplayListSet
& aLists
) {
318 MOZ_ASSERT(GetParent());
319 MOZ_ASSERT(GetParent()->IsPageFrame());
320 auto* pageFrame
= static_cast<nsPageFrame
*>(GetParent());
321 auto pageNum
= pageFrame
->GetPageNum();
322 NS_ASSERTION(pageNum
<= 255, "Too many pages to handle OOFs");
324 if (aBuilder
->GetBuildingExtraPagesForPageNum()) {
325 return mozilla::ViewportFrame::BuildDisplayList(aBuilder
, aLists
);
328 nsDisplayListCollection
set(aBuilder
);
330 nsDisplayList
content(aBuilder
);
332 const nsRect
clipRect(aBuilder
->ToReferenceFrame(this), GetSize());
333 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
335 // Overwrite current clip, since we're going to wrap in a transform and the
336 // current clip is no longer meaningful.
338 clipState
.ClipContentDescendants(clipRect
);
340 if (StaticPrefs::layout_display_list_improve_fragmentation() &&
342 nsDisplayListBuilder::AutoPageNumberSetter
p(aBuilder
, pageNum
);
343 BuildPreviousPageOverflow(aBuilder
, pageFrame
, this, set
);
345 mozilla::ViewportFrame::BuildDisplayList(aBuilder
, set
);
347 set
.SerializeWithCorrectZOrder(&content
, GetContent());
349 // We may need to paint out-of-flow frames whose placeholders are on other
350 // pages. Add those pages to our display list. Note that out-of-flow frames
351 // can't be placed after their placeholders so
352 // we don't have to process earlier pages. The display lists for
353 // these extra pages are pruned so that only display items for the
354 // page we currently care about (which we would have reached by
355 // following placeholders to their out-of-flows) end up on the list.
357 // Stacking context frames that wrap content on their normal page,
358 // as well as OOF content for this page will have their container
359 // items duplicated. We tell the builder to include our page number
360 // in the unique key for any extra page items so that they can be
361 // differentiated from the ones created on the normal page.
362 if (pageNum
<= 255) {
363 const nsRect overflowRect
= ScrollableOverflowRectRelativeToSelf();
364 nsDisplayListBuilder::AutoPageNumberSetter
p(aBuilder
, pageNum
);
366 // The static_cast here is technically unnecessary, but it helps
367 // devirtualize the GetNextContinuation() function call if pcf has a
368 // concrete type (with an inherited `final` GetNextContinuation() impl).
370 while ((pageCF
= static_cast<nsPageContentFrame
*>(
371 pageCF
->GetNextContinuation()))) {
372 nsRect childVisible
= overflowRect
+ GetOffsetTo(pageCF
);
374 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
375 aBuilder
, pageCF
, childVisible
, childVisible
);
376 BuildDisplayListForExtraPage(aBuilder
, pageFrame
, pageCF
, &content
);
380 // Add the canvas background color to the bottom of the list. This
381 // happens after we've built the list so that AddCanvasBackgroundColorItem
382 // can monkey with the contents if necessary.
383 const nsRect
backgroundRect(aBuilder
->ToReferenceFrame(this), GetSize());
384 PresShell()->AddCanvasBackgroundColorItem(
385 aBuilder
, &content
, this, backgroundRect
, NS_RGBA(0, 0, 0, 0));
388 content
.AppendNewToTop
<nsDisplayTransform
>(
389 aBuilder
, this, &content
, content
.GetBuildingRect(),
390 nsDisplayTransform::WithTransformGetter
);
392 aLists
.Content()->AppendToTop(&content
);
395 void nsPageContentFrame::AppendDirectlyOwnedAnonBoxes(
396 nsTArray
<OwnedAnonBox
>& aResult
) {
397 MOZ_ASSERT(mFrames
.FirstChild(),
398 "pageContentFrame must have a canvasFrame child");
399 aResult
.AppendElement(mFrames
.FirstChild());
402 void nsPageContentFrame::EnsurePageName() {
403 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_FIRST_REFLOW
),
404 "Should only have been called on first reflow");
408 MOZ_ASSERT(!GetPrevInFlow(),
409 "Only the first page should initially have a null page name.");
410 // This was the first page, we need to find our own page name and then set
411 // our computed style based on that.
412 mPageName
= ComputePageValue();
414 MOZ_ASSERT(mPageName
, "Page name should never be null");
415 // Resolve the computed style given this page-name and the :first pseudo.
416 RefPtr
<ComputedStyle
> pageContentPseudoStyle
=
417 PresShell()->StyleSet()->ResolvePageContentStyle(
418 mPageName
, StylePagePseudoClassFlags::FIRST
);
419 SetComputedStyleWithoutNotification(pageContentPseudoStyle
);
422 #ifdef DEBUG_FRAME_DUMP
423 nsresult
nsPageContentFrame::GetFrameName(nsAString
& aResult
) const {
424 return MakeFrameName(u
"PageContent"_ns
, aResult
);
426 void nsPageContentFrame::ExtraContainerFrameInfo(nsACString
& aTo
) const {
429 aTo
+= nsAtomCString(mPageName
);