nsPageContentFrame.cpp (19627B)
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" 7 8 #include "mozilla/AbsoluteContainingBlock.h" 9 #include "mozilla/PresShell.h" 10 #include "mozilla/PresShellInlines.h" 11 #include "mozilla/StaticPrefs_layout.h" 12 #include "mozilla/dom/Document.h" 13 #include "nsCSSFrameConstructor.h" 14 #include "nsContentUtils.h" 15 #include "nsGkAtoms.h" 16 #include "nsLayoutUtils.h" 17 #include "nsPageFrame.h" 18 #include "nsPageSequenceFrame.h" 19 #include "nsPresContext.h" 20 21 using namespace mozilla; 22 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)); 28 } 29 30 NS_IMPL_FRAMEARENA_HELPERS(nsPageContentFrame) 31 32 void nsPageContentFrame::Reflow(nsPresContext* aPresContext, 33 ReflowOutput& aReflowOutput, 34 const ReflowInput& aReflowInput, 35 nsReflowStatus& aStatus) { 36 MarkInReflow(); 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"); 40 41 if (GetPrevInFlow() && HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) { 42 nsresult rv = 43 aPresContext->PresShell()->FrameConstructor()->ReplicateFixedFrames( 44 this); 45 if (NS_FAILED(rv)) { 46 return; 47 } 48 } 49 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(); 54 SetSize(maxSize); 55 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(); 61 } 62 aReflowOutput.SetOverflowAreasToDesiredBounds(); 63 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); 76 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); 81 82 // XXXbz this screws up percentage padding (sets padding to zero 83 // in the percentage padding case) 84 frame->StylePadding()->GetPadding(padding); 85 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); 102 } 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); 115 } 116 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; 123 } 124 } 125 126 // Place and size the child 127 FinishReflowChild(frame, aPresContext, kidReflowOutput, &kidReflowInput, 0, 128 0, ReflowChildFlags::Default); 129 130 NS_ASSERTION(aPresContext->IsDynamic() || !aStatus.IsFullyComplete() || 131 !frame->GetNextInFlow(), 132 "bad child flow list"); 133 134 aReflowOutput.mOverflowAreas.UnionWith(kidReflowOutput.mOverflowAreas); 135 } 136 137 FinishAndStoreOverflow(&aReflowOutput); 138 139 // Reflow any fixed-pos children. Note that we don't need to call 140 // PrepareAbsoluteFrames() because the fixed pos frames cannot split. 141 nsReflowStatus fixedStatus; 142 if (auto* absCB = GetAbsoluteContainingBlock(); 143 absCB && absCB->HasAbsoluteFrames()) { 144 // The containing block for the fixed-pos children is formed by our padding 145 // edge. 146 const auto wm = GetWritingMode(); 147 LogicalRect cbRect(wm, LogicalPoint(wm), aReflowOutput.Size(wm)); 148 cbRect.Deflate(wm, GetLogicalUsedBorder(wm).ApplySkipSides( 149 PreReflowBlockLevelLogicalSkipSides())); 150 151 // XXX: To optimize the performance, set the flags only when the CB width or 152 // height actually changes. 153 AbsPosReflowFlags flags{AbsPosReflowFlag::CBWidthChanged, 154 AbsPosReflowFlag::CBHeightChanged}; 155 156 // PageContentFrame replicates fixed-pos children, so we really don't want 157 // them contributing to overflow areas; otherwise we'll create new pages ad 158 // infinitum if one of them overflows the page. 159 absCB->Reflow(this, aPresContext, aReflowInput, fixedStatus, 160 cbRect.GetPhysicalRect(wm, aReflowOutput.PhysicalSize()), 161 flags, 162 /* aOverflowAreas */ nullptr); 163 } 164 NS_ASSERTION(fixedStatus.IsComplete(), 165 "fixed frames can be truncated, but not incomplete"); 166 167 if (StaticPrefs::layout_display_list_improve_fragmentation() && 168 mFrames.NotEmpty()) { 169 auto* const previous = 170 static_cast<nsPageContentFrame*>(GetPrevContinuation()); 171 const nscoord previousPageOverflow = 172 previous ? previous->mRemainingOverflow : 0; 173 const nsSize containerSize(aReflowInput.AvailableWidth(), 174 aReflowInput.AvailableHeight()); 175 const nscoord pageBSize = GetLogicalRect(containerSize).BSize(pcfWM); 176 const nscoord overflowBSize = 177 LogicalRect(pcfWM, ScrollableOverflowRect(), GetSize()).BEnd(pcfWM); 178 const nscoord currentPageOverflow = overflowBSize - pageBSize; 179 nscoord remainingOverflow = 180 std::max(currentPageOverflow, previousPageOverflow - pageBSize); 181 182 if (aStatus.IsFullyComplete() && remainingOverflow > 0) { 183 // If we have ScrollableOverflow off the end of our page, then we report 184 // ourselves as overflow-incomplete in order to produce an additional 185 // content-less page, which we expect to draw our overflow on our behalf. 186 aStatus.SetOverflowIncomplete(); 187 } 188 189 mRemainingOverflow = std::max(remainingOverflow, 0); 190 } 191 } 192 193 using PageAndOffset = std::pair<nsPageContentFrame*, nscoord>; 194 195 // Returns the previous continuation PageContentFrames that have overflow areas, 196 // and their offsets to the top of the given PageContentFrame |aPage|. Since the 197 // iteration is done backwards, the returned pages are arranged in descending 198 // order of page number. 199 static nsTArray<PageAndOffset> GetPreviousPagesWithOverflow( 200 nsPageContentFrame* aPage) { 201 nsTArray<PageAndOffset> pages(8); 202 203 auto GetPreviousPageContentFrame = [](nsPageContentFrame* aPageCF) { 204 nsIFrame* prevCont = aPageCF->GetPrevContinuation(); 205 MOZ_ASSERT(!prevCont || prevCont->IsPageContentFrame(), 206 "Expected nsPageContentFrame or nullptr"); 207 208 return static_cast<nsPageContentFrame*>(prevCont); 209 }; 210 211 nsPageContentFrame* pageCF = aPage; 212 // The collective height of all prev-continuations we've traversed so far: 213 nscoord offsetToCurrentPageBStart = 0; 214 const auto wm = pageCF->GetWritingMode(); 215 while ((pageCF = GetPreviousPageContentFrame(pageCF))) { 216 offsetToCurrentPageBStart += pageCF->BSize(wm); 217 218 if (pageCF->HasOverflowAreas()) { 219 pages.EmplaceBack(pageCF, offsetToCurrentPageBStart); 220 } 221 } 222 223 return pages; 224 } 225 226 static void BuildPreviousPageOverflow(nsDisplayListBuilder* aBuilder, 227 nsPageFrame* aPageFrame, 228 nsPageContentFrame* aCurrentPageCF, 229 const nsDisplayListSet& aLists) { 230 const auto previousPagesAndOffsets = 231 GetPreviousPagesWithOverflow(aCurrentPageCF); 232 233 const auto wm = aCurrentPageCF->GetWritingMode(); 234 for (const PageAndOffset& pair : Reversed(previousPagesAndOffsets)) { 235 auto* prevPageCF = pair.first; 236 const nscoord offsetToCurrentPageBStart = pair.second; 237 // Only scrollable overflow create new pages, not ink overflow. 238 const LogicalRect scrollableOverflow( 239 wm, prevPageCF->ScrollableOverflowRectRelativeToSelf(), 240 prevPageCF->GetSize()); 241 const auto remainingOverflow = 242 scrollableOverflow.BEnd(wm) - offsetToCurrentPageBStart; 243 if (remainingOverflow <= 0) { 244 continue; 245 } 246 247 // This rect represents the piece of prevPageCF's overflow that ends up on 248 // the current pageContentFrame (in prevPageCF's coordinate system). 249 // Note that we use InkOverflow here since this is for painting. 250 LogicalRect overflowRect(wm, prevPageCF->InkOverflowRectRelativeToSelf(), 251 prevPageCF->GetSize()); 252 overflowRect.BStart(wm) = offsetToCurrentPageBStart; 253 overflowRect.BSize(wm) = std::min(remainingOverflow, prevPageCF->BSize(wm)); 254 255 { 256 // Convert the overflowRect to the coordinate system of aPageFrame, and 257 // set it as the visible rect for display list building. 258 const nsRect visibleRect = 259 overflowRect.GetPhysicalRect(wm, prevPageCF->GetSize()) + 260 prevPageCF->GetOffsetTo(aPageFrame); 261 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 262 aBuilder, aPageFrame, visibleRect, visibleRect); 263 264 // This part is tricky. Because display items are positioned based on the 265 // frame tree, building a display list for the previous page yields 266 // display items that are outside of the current page bounds. 267 // To fix that, an additional reference frame offset is added, which 268 // shifts the display items down (block axis) as if the current and 269 // previous page were one long page in the same coordinate system. 270 const nsSize containerSize = aPageFrame->GetSize(); 271 LogicalPoint pageOffset(wm, aCurrentPageCF->GetOffsetTo(prevPageCF), 272 containerSize); 273 pageOffset.B(wm) -= offsetToCurrentPageBStart; 274 buildingForChild.SetAdditionalOffset( 275 pageOffset.GetPhysicalPoint(wm, containerSize)); 276 277 aPageFrame->BuildDisplayListForChild(aBuilder, prevPageCF, aLists); 278 } 279 } 280 } 281 282 /** 283 * Remove all leaf display items that are not for descendants of 284 * aBuilder->GetReferenceFrame() from aList. 285 * @param aPage the page we're constructing the display list for 286 * @param aList the list that is modified in-place 287 */ 288 static void PruneDisplayListForExtraPage(nsDisplayListBuilder* aBuilder, 289 nsPageFrame* aPage, 290 nsDisplayList* aList) { 291 for (nsDisplayItem* i : aList->TakeItems()) { 292 if (!i) { 293 break; 294 } 295 nsDisplayList* subList = i->GetSameCoordinateSystemChildren(); 296 if (subList) { 297 PruneDisplayListForExtraPage(aBuilder, aPage, subList); 298 i->UpdateBounds(aBuilder); 299 } else { 300 nsIFrame* f = i->Frame(); 301 if (!nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(aPage, f)) { 302 // We're throwing this away so call its destructor now. The memory 303 // is owned by aBuilder which destroys all items at once. 304 i->Destroy(aBuilder); 305 continue; 306 } 307 } 308 aList->AppendToTop(i); 309 } 310 } 311 312 static void BuildDisplayListForExtraPage(nsDisplayListBuilder* aBuilder, 313 nsPageFrame* aPage, 314 nsIFrame* aExtraPage, 315 nsDisplayList* aList) { 316 // The only content in aExtraPage we care about is out-of-flow content from 317 // aPage, whose placeholders have occurred in aExtraPage. If 318 // NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO is not set, then aExtraPage has 319 // no such content. 320 if (!aExtraPage->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { 321 return; 322 } 323 nsDisplayList list(aBuilder); 324 aExtraPage->BuildDisplayListForStackingContext(aBuilder, &list); 325 PruneDisplayListForExtraPage(aBuilder, aPage, &list); 326 aList->AppendToTop(&list); 327 } 328 329 static gfx::Matrix4x4 ComputePageContentTransform(const nsIFrame* aFrame, 330 float aAppUnitsPerPixel) { 331 float scale = aFrame->PresContext()->GetPageScale(); 332 return gfx::Matrix4x4::Scaling(scale, scale, 1); 333 } 334 335 nsIFrame::ComputeTransformFunction nsPageContentFrame::GetTransformGetter() 336 const { 337 return ComputePageContentTransform; 338 } 339 340 void nsPageContentFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, 341 const nsDisplayListSet& aLists) { 342 MOZ_ASSERT(GetParent()); 343 MOZ_ASSERT(GetParent()->IsPageFrame()); 344 auto* pageFrame = static_cast<nsPageFrame*>(GetParent()); 345 346 if (auto pageNum = aBuilder->GetBuildingPageNum()) { 347 // We're an extra page, avoid building duplicate OOFs that are going to be 348 // built already. 349 nsDisplayListBuilder::AutoPageNumberSetter p( 350 aBuilder, pageNum, /* aAvoidBuildingDuplicateOofs = */ true); 351 return mozilla::ViewportFrame::BuildDisplayList(aBuilder, aLists); 352 } 353 354 nsDisplayListCollection set(aBuilder); 355 356 nsDisplayList content(aBuilder); 357 { 358 nsDisplayListBuilder::AutoPageNumberSetter p(aBuilder, 359 pageFrame->GetPageNum()); 360 NS_ASSERTION(!aBuilder->AvoidBuildingDuplicateOofs(), 361 "Too many pages to handle OOFs"); 362 const nsRect clipRect(aBuilder->ToReferenceFrame(this), GetSize()); 363 DisplayListClipState::AutoSaveRestore clipState(aBuilder); 364 365 // Overwrite current clip, since we're going to wrap in a transform and the 366 // current clip is no longer meaningful. 367 clipState.Clear(); 368 clipState.ClipContentDescendants(clipRect); 369 370 if (StaticPrefs::layout_display_list_improve_fragmentation() && 371 !aBuilder->AvoidBuildingDuplicateOofs()) { 372 BuildPreviousPageOverflow(aBuilder, pageFrame, this, set); 373 } 374 mozilla::ViewportFrame::BuildDisplayList(aBuilder, set); 375 376 set.SerializeWithCorrectZOrder(&content, GetContent()); 377 378 // We may need to paint out-of-flow frames whose placeholders are on other 379 // pages. Add those pages to our display list. Note that out-of-flow frames 380 // can't be placed after their placeholders so 381 // we don't have to process earlier pages. The display lists for 382 // these extra pages are pruned so that only display items for the 383 // page we currently care about (which we would have reached by 384 // following placeholders to their out-of-flows) end up on the list. 385 // 386 // Stacking context frames that wrap content on their normal page, 387 // as well as OOF content for this page will have their container 388 // items duplicated. We tell the builder to include our page number 389 // in the unique key for any extra page items so that they can be 390 // differentiated from the ones created on the normal page. 391 if (!aBuilder->AvoidBuildingDuplicateOofs()) { 392 const nsRect overflowRect = ScrollableOverflowRectRelativeToSelf(); 393 // The static_cast here is technically unnecessary, but it helps 394 // devirtualize the GetNextContinuation() function call if pcf has a 395 // concrete type (with an inherited `final` GetNextContinuation() impl). 396 auto* pageCF = this; 397 while ((pageCF = static_cast<nsPageContentFrame*>( 398 pageCF->GetNextContinuation()))) { 399 nsRect childVisible = overflowRect + GetOffsetTo(pageCF); 400 401 nsDisplayListBuilder::AutoBuildingDisplayList buildingForChild( 402 aBuilder, pageCF, childVisible, childVisible); 403 BuildDisplayListForExtraPage(aBuilder, pageFrame, pageCF, &content); 404 } 405 } 406 } 407 408 content.AppendNewToTop<nsDisplayTransform>( 409 aBuilder, this, &content, content.GetBuildingRect(), 410 nsDisplayTransform::WithTransformGetter); 411 412 aLists.Content()->AppendToTop(&content); 413 } 414 415 void nsPageContentFrame::AppendDirectlyOwnedAnonBoxes( 416 nsTArray<OwnedAnonBox>& aResult) { 417 MOZ_ASSERT(mFrames.FirstChild(), 418 "pageContentFrame must have a canvasFrame child"); 419 aResult.AppendElement(mFrames.FirstChild()); 420 } 421 422 void nsPageContentFrame::EnsurePageName() { 423 MOZ_ASSERT(HasAnyStateBits(NS_FRAME_FIRST_REFLOW), 424 "Should only have been called on first reflow"); 425 if (mPageName) { 426 return; 427 } 428 MOZ_ASSERT(!GetPrevInFlow(), 429 "Only the first page should initially have a null page name."); 430 // This was the first page, we need to find our own page name and then set 431 // our computed style based on that. 432 mPageName = ComputePageValue(); 433 434 MOZ_ASSERT(mPageName, "Page name should never be null"); 435 // Resolve the computed style given this page-name and the :first pseudo. 436 RefPtr<ComputedStyle> pageContentPseudoStyle = 437 PresShell()->StyleSet()->ResolvePageContentStyle( 438 mPageName, StylePagePseudoClassFlags::FIRST); 439 SetComputedStyleWithoutNotification(pageContentPseudoStyle); 440 } 441 442 #ifdef DEBUG_FRAME_DUMP 443 nsresult nsPageContentFrame::GetFrameName(nsAString& aResult) const { 444 return MakeFrameName(u"PageContent"_ns, aResult); 445 } 446 void nsPageContentFrame::ExtraContainerFrameInfo(nsACString& aTo, bool) const { 447 if (mPageName) { 448 aTo += " [page="; 449 aTo += nsAtomCString(mPageName); 450 aTo += "]"; 451 } 452 } 453 #endif