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 DISPLAY_REFLOW(aPresContext
, this, aReflowInput
, aReflowOutput
, aStatus
);
39 MOZ_ASSERT(aStatus
.IsEmpty(), "Caller should pass a fresh reflow status!");
40 MOZ_ASSERT(mPD
, "Need a pointer to nsSharedPageData before reflow starts");
42 if (GetPrevInFlow() && HasAnyStateBits(NS_FRAME_FIRST_REFLOW
)) {
44 aPresContext
->PresShell()->FrameConstructor()->ReplicateFixedFrames(
51 // Set our size up front, since some parts of reflow depend on it
52 // being already set. Note that the computed height may be
53 // unconstrained; that's ok. Consumers should watch out for that.
54 const nsSize
maxSize(aReflowInput
.ComputedWidth(),
55 aReflowInput
.ComputedHeight());
58 // Writing mode for the page content frame.
59 const WritingMode pcfWM
= aReflowInput
.GetWritingMode();
60 aReflowOutput
.ISize(pcfWM
) = aReflowInput
.ComputedISize();
61 if (aReflowInput
.ComputedBSize() != NS_UNCONSTRAINEDSIZE
) {
62 aReflowOutput
.BSize(pcfWM
) = aReflowInput
.ComputedBSize();
64 aReflowOutput
.SetOverflowAreasToDesiredBounds();
66 // A PageContentFrame must always have one child: the canvas frame.
67 // Resize our frame allowing it only to be as big as we are
68 // XXX Pay attention to the page's border and padding...
69 if (mFrames
.NotEmpty()) {
70 nsIFrame
* const frame
= mFrames
.FirstChild();
71 const WritingMode frameWM
= frame
->GetWritingMode();
72 const LogicalSize
logicalSize(frameWM
, maxSize
);
73 ReflowInput
kidReflowInput(aPresContext
, aReflowInput
, frame
, logicalSize
);
74 kidReflowInput
.SetComputedBSize(logicalSize
.BSize(frameWM
));
75 ReflowOutput
kidReflowOutput(kidReflowInput
);
76 ReflowChild(frame
, aPresContext
, kidReflowOutput
, kidReflowInput
, 0, 0,
77 ReflowChildFlags::Default
, aStatus
);
79 // The document element's background should cover the entire canvas, so
80 // take into account the combined area and any space taken up by
81 // absolutely positioned elements
82 nsMargin
padding(0, 0, 0, 0);
84 // XXXbz this screws up percentage padding (sets padding to zero
85 // in the percentage padding case)
86 frame
->StylePadding()->GetPadding(padding
);
88 // This is for shrink-to-fit, and therefore we want to use the
89 // scrollable overflow, since the purpose of shrink to fit is to
90 // make the content that ought to be reachable (represented by the
91 // scrollable overflow) fit in the page.
92 if (frame
->HasOverflowAreas()) {
93 // The background covers the content area and padding area, so check
94 // for children sticking outside the child frame's padding edge
95 nscoord xmost
= kidReflowOutput
.ScrollableOverflow().XMost();
96 if (xmost
> kidReflowOutput
.Width()) {
97 const nscoord widthToFit
=
98 xmost
+ padding
.right
+
99 kidReflowInput
.mStyleBorder
->GetComputedBorderWidth(eSideRight
);
100 const float ratio
= float(maxSize
.width
) / float(widthToFit
);
101 NS_ASSERTION(ratio
>= 0.0 && ratio
< 1.0,
102 "invalid shrink-to-fit ratio");
103 mPD
->mShrinkToFitRatio
= std::min(mPD
->mShrinkToFitRatio
, ratio
);
105 // In the case of pdf.js documents, we also want to consider the height,
106 // so that we don't clip the page in either axis if the aspect ratio of
107 // the PDF doesn't match the destination.
108 if (nsContentUtils::IsPDFJS(PresContext()->Document()->GetPrincipal())) {
109 nscoord ymost
= kidReflowOutput
.ScrollableOverflow().YMost();
110 if (ymost
> kidReflowOutput
.Height()) {
111 const nscoord heightToFit
=
112 ymost
+ padding
.bottom
+
113 kidReflowInput
.mStyleBorder
->GetComputedBorderWidth(eSideBottom
);
114 const float ratio
= float(maxSize
.height
) / float(heightToFit
);
115 MOZ_ASSERT(ratio
>= 0.0 && ratio
< 1.0);
116 mPD
->mShrinkToFitRatio
= std::min(mPD
->mShrinkToFitRatio
, ratio
);
119 // pdf.js pages should never overflow given the scaling above.
120 // nsPrintJob::SetupToPrintContent ignores some ratios close to 1.0
121 // though and doesn't reflow us again in that case, so we need to clear
122 // the overflow area here in case that happens. (bug 1689789)
123 frame
->ClearOverflowRects();
124 kidReflowOutput
.mOverflowAreas
= aReflowOutput
.mOverflowAreas
;
128 // Place and size the child
129 FinishReflowChild(frame
, aPresContext
, kidReflowOutput
, &kidReflowInput
, 0,
130 0, ReflowChildFlags::Default
);
132 NS_ASSERTION(aPresContext
->IsDynamic() || !aStatus
.IsFullyComplete() ||
133 !frame
->GetNextInFlow(),
134 "bad child flow list");
136 aReflowOutput
.mOverflowAreas
.UnionWith(kidReflowOutput
.mOverflowAreas
);
139 FinishAndStoreOverflow(&aReflowOutput
);
141 // Reflow our fixed frames
142 nsReflowStatus fixedStatus
;
143 ReflowAbsoluteFrames(aPresContext
, aReflowOutput
, aReflowInput
, fixedStatus
);
144 NS_ASSERTION(fixedStatus
.IsComplete(),
145 "fixed frames can be truncated, but not incomplete");
147 if (StaticPrefs::layout_display_list_improve_fragmentation() &&
148 mFrames
.NotEmpty()) {
149 auto* const previous
=
150 static_cast<nsPageContentFrame
*>(GetPrevContinuation());
151 const nscoord previousPageOverflow
=
152 previous
? previous
->mRemainingOverflow
: 0;
153 const nsSize
containerSize(aReflowInput
.AvailableWidth(),
154 aReflowInput
.AvailableHeight());
155 const nscoord pageBSize
= GetLogicalRect(containerSize
).BSize(pcfWM
);
156 const nscoord overflowBSize
=
157 LogicalRect(pcfWM
, ScrollableOverflowRect(), GetSize()).BEnd(pcfWM
);
158 const nscoord currentPageOverflow
= overflowBSize
- pageBSize
;
159 nscoord remainingOverflow
=
160 std::max(currentPageOverflow
, previousPageOverflow
- pageBSize
);
162 if (aStatus
.IsFullyComplete() && remainingOverflow
> 0) {
163 // If we have ScrollableOverflow off the end of our page, then we report
164 // ourselves as overflow-incomplete in order to produce an additional
165 // content-less page, which we expect to draw our overflow on our behalf.
166 aStatus
.SetOverflowIncomplete();
169 mRemainingOverflow
= std::max(remainingOverflow
, 0);
173 using PageAndOffset
= std::pair
<nsPageContentFrame
*, nscoord
>;
175 // Returns the previous continuation PageContentFrames that have overflow areas,
176 // and their offsets to the top of the given PageContentFrame |aPage|. Since the
177 // iteration is done backwards, the returned pages are arranged in descending
178 // order of page number.
179 static nsTArray
<PageAndOffset
> GetPreviousPagesWithOverflow(
180 nsPageContentFrame
* aPage
) {
181 nsTArray
<PageAndOffset
> pages(8);
183 auto GetPreviousPageContentFrame
= [](nsPageContentFrame
* aPageCF
) {
184 nsIFrame
* prevCont
= aPageCF
->GetPrevContinuation();
185 MOZ_ASSERT(!prevCont
|| prevCont
->IsPageContentFrame(),
186 "Expected nsPageContentFrame or nullptr");
188 return static_cast<nsPageContentFrame
*>(prevCont
);
191 nsPageContentFrame
* pageCF
= aPage
;
192 // The collective height of all prev-continuations we've traversed so far:
193 nscoord offsetToCurrentPageBStart
= 0;
194 const auto wm
= pageCF
->GetWritingMode();
195 while ((pageCF
= GetPreviousPageContentFrame(pageCF
))) {
196 offsetToCurrentPageBStart
+= pageCF
->BSize(wm
);
198 if (pageCF
->HasOverflowAreas()) {
199 pages
.EmplaceBack(pageCF
, offsetToCurrentPageBStart
);
206 static void BuildPreviousPageOverflow(nsDisplayListBuilder
* aBuilder
,
207 nsPageFrame
* aPageFrame
,
208 nsPageContentFrame
* aCurrentPageCF
,
209 const nsDisplayListSet
& aLists
) {
210 const auto previousPagesAndOffsets
=
211 GetPreviousPagesWithOverflow(aCurrentPageCF
);
213 const auto wm
= aCurrentPageCF
->GetWritingMode();
214 for (const PageAndOffset
& pair
: Reversed(previousPagesAndOffsets
)) {
215 auto* prevPageCF
= pair
.first
;
216 const nscoord offsetToCurrentPageBStart
= pair
.second
;
217 // Only scrollable overflow create new pages, not ink overflow.
218 const LogicalRect
scrollableOverflow(
219 wm
, prevPageCF
->ScrollableOverflowRectRelativeToSelf(),
220 prevPageCF
->GetSize());
221 const auto remainingOverflow
=
222 scrollableOverflow
.BEnd(wm
) - offsetToCurrentPageBStart
;
223 if (remainingOverflow
<= 0) {
227 // This rect represents the piece of prevPageCF's overflow that ends up on
228 // the current pageContentFrame (in prevPageCF's coordinate system).
229 // Note that we use InkOverflow here since this is for painting.
230 LogicalRect
overflowRect(wm
, prevPageCF
->InkOverflowRectRelativeToSelf(),
231 prevPageCF
->GetSize());
232 overflowRect
.BStart(wm
) = offsetToCurrentPageBStart
;
233 overflowRect
.BSize(wm
) = std::min(remainingOverflow
, prevPageCF
->BSize(wm
));
236 // Convert the overflowRect to the coordinate system of aPageFrame, and
237 // set it as the visible rect for display list building.
238 const nsRect visibleRect
=
239 overflowRect
.GetPhysicalRect(wm
, prevPageCF
->GetSize()) +
240 prevPageCF
->GetOffsetTo(aPageFrame
);
241 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
242 aBuilder
, aPageFrame
, visibleRect
, visibleRect
);
244 // This part is tricky. Because display items are positioned based on the
245 // frame tree, building a display list for the previous page yields
246 // display items that are outside of the current page bounds.
247 // To fix that, an additional reference frame offset is added, which
248 // shifts the display items down (block axis) as if the current and
249 // previous page were one long page in the same coordinate system.
250 const nsSize containerSize
= aPageFrame
->GetSize();
251 LogicalPoint
pageOffset(wm
, aCurrentPageCF
->GetOffsetTo(prevPageCF
),
253 pageOffset
.B(wm
) -= offsetToCurrentPageBStart
;
254 buildingForChild
.SetAdditionalOffset(
255 pageOffset
.GetPhysicalPoint(wm
, containerSize
));
257 aPageFrame
->BuildDisplayListForChild(aBuilder
, prevPageCF
, aLists
);
263 * Remove all leaf display items that are not for descendants of
264 * aBuilder->GetReferenceFrame() from aList.
265 * @param aPage the page we're constructing the display list for
266 * @param aList the list that is modified in-place
268 static void PruneDisplayListForExtraPage(nsDisplayListBuilder
* aBuilder
,
270 nsDisplayList
* aList
) {
271 for (nsDisplayItem
* i
: aList
->TakeItems()) {
273 nsDisplayList
* subList
= i
->GetSameCoordinateSystemChildren();
275 PruneDisplayListForExtraPage(aBuilder
, aPage
, subList
);
276 i
->UpdateBounds(aBuilder
);
278 nsIFrame
* f
= i
->Frame();
279 if (!nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(aPage
, f
)) {
280 // We're throwing this away so call its destructor now. The memory
281 // is owned by aBuilder which destroys all items at once.
282 i
->Destroy(aBuilder
);
286 aList
->AppendToTop(i
);
290 static void BuildDisplayListForExtraPage(nsDisplayListBuilder
* aBuilder
,
292 nsIFrame
* aExtraPage
,
293 nsDisplayList
* aList
) {
294 // The only content in aExtraPage we care about is out-of-flow content from
295 // aPage, whose placeholders have occurred in aExtraPage. If
296 // NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is not set, then aExtraPage has
298 if (!aExtraPage
->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO
)) {
301 nsDisplayList
list(aBuilder
);
302 aExtraPage
->BuildDisplayListForStackingContext(aBuilder
, &list
);
303 PruneDisplayListForExtraPage(aBuilder
, aPage
, &list
);
304 aList
->AppendToTop(&list
);
307 static gfx::Matrix4x4
ComputePageContentTransform(const nsIFrame
* aFrame
,
308 float aAppUnitsPerPixel
) {
309 float scale
= aFrame
->PresContext()->GetPageScale();
310 return gfx::Matrix4x4::Scaling(scale
, scale
, 1);
313 nsIFrame::ComputeTransformFunction
nsPageContentFrame::GetTransformGetter()
315 return ComputePageContentTransform
;
318 void nsPageContentFrame::BuildDisplayList(nsDisplayListBuilder
* aBuilder
,
319 const nsDisplayListSet
& aLists
) {
320 MOZ_ASSERT(GetParent());
321 MOZ_ASSERT(GetParent()->IsPageFrame());
322 auto* pageFrame
= static_cast<nsPageFrame
*>(GetParent());
323 auto pageNum
= pageFrame
->GetPageNum();
324 NS_ASSERTION(pageNum
<= 255, "Too many pages to handle OOFs");
326 if (aBuilder
->GetBuildingExtraPagesForPageNum()) {
327 return mozilla::ViewportFrame::BuildDisplayList(aBuilder
, aLists
);
330 nsDisplayListCollection
set(aBuilder
);
332 nsDisplayList
content(aBuilder
);
334 const nsRect
clipRect(aBuilder
->ToReferenceFrame(this), GetSize());
335 DisplayListClipState::AutoSaveRestore
clipState(aBuilder
);
337 // Overwrite current clip, since we're going to wrap in a transform and the
338 // current clip is no longer meaningful.
340 clipState
.ClipContentDescendants(clipRect
);
342 if (StaticPrefs::layout_display_list_improve_fragmentation() &&
344 nsDisplayListBuilder::AutoPageNumberSetter
p(aBuilder
, pageNum
);
345 BuildPreviousPageOverflow(aBuilder
, pageFrame
, this, set
);
347 mozilla::ViewportFrame::BuildDisplayList(aBuilder
, set
);
349 set
.SerializeWithCorrectZOrder(&content
, GetContent());
351 // We may need to paint out-of-flow frames whose placeholders are on other
352 // pages. Add those pages to our display list. Note that out-of-flow frames
353 // can't be placed after their placeholders so
354 // we don't have to process earlier pages. The display lists for
355 // these extra pages are pruned so that only display items for the
356 // page we currently care about (which we would have reached by
357 // following placeholders to their out-of-flows) end up on the list.
359 // Stacking context frames that wrap content on their normal page,
360 // as well as OOF content for this page will have their container
361 // items duplicated. We tell the builder to include our page number
362 // in the unique key for any extra page items so that they can be
363 // differentiated from the ones created on the normal page.
364 if (pageNum
<= 255) {
365 const nsRect overflowRect
= ScrollableOverflowRectRelativeToSelf();
366 nsDisplayListBuilder::AutoPageNumberSetter
p(aBuilder
, pageNum
);
368 // The static_cast here is technically unnecessary, but it helps
369 // devirtualize the GetNextContinuation() function call if pcf has a
370 // concrete type (with an inherited `final` GetNextContinuation() impl).
372 while ((pageCF
= static_cast<nsPageContentFrame
*>(
373 pageCF
->GetNextContinuation()))) {
374 nsRect childVisible
= overflowRect
+ GetOffsetTo(pageCF
);
376 nsDisplayListBuilder::AutoBuildingDisplayList
buildingForChild(
377 aBuilder
, pageCF
, childVisible
, childVisible
);
378 BuildDisplayListForExtraPage(aBuilder
, pageFrame
, pageCF
, &content
);
382 // Add the canvas background color to the bottom of the list. This
383 // happens after we've built the list so that AddCanvasBackgroundColorItem
384 // can monkey with the contents if necessary.
385 const nsRect
backgroundRect(aBuilder
->ToReferenceFrame(this), GetSize());
386 PresShell()->AddCanvasBackgroundColorItem(
387 aBuilder
, &content
, this, backgroundRect
, NS_RGBA(0, 0, 0, 0));
390 content
.AppendNewToTop
<nsDisplayTransform
>(
391 aBuilder
, this, &content
, content
.GetBuildingRect(),
392 nsDisplayTransform::WithTransformGetter
);
394 aLists
.Content()->AppendToTop(&content
);
397 void nsPageContentFrame::AppendDirectlyOwnedAnonBoxes(
398 nsTArray
<OwnedAnonBox
>& aResult
) {
399 MOZ_ASSERT(mFrames
.FirstChild(),
400 "pageContentFrame must have a canvasFrame child");
401 aResult
.AppendElement(mFrames
.FirstChild());
404 void nsPageContentFrame::EnsurePageName() {
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 // We don't need to resolve any further styling if the page name is empty.
416 if (mPageName
== nsGkAtoms::_empty
) {
419 RefPtr
<ComputedStyle
> pageContentPseudoStyle
=
420 PresShell()->StyleSet()->ResolvePageContentStyle(mPageName
);
421 SetComputedStyleWithoutNotification(pageContentPseudoStyle
);
424 #ifdef DEBUG_FRAME_DUMP
425 nsresult
nsPageContentFrame::GetFrameName(nsAString
& aResult
) const {
426 return MakeFrameName(u
"PageContent"_ns
, aResult
);
428 void nsPageContentFrame::ExtraContainerFrameInfo(nsACString
& aTo
) const {
431 aTo
+= nsAtomCString(mPageName
);