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
7 /* rendering object that goes directly inside the document's scrollbars */
8
9 #include "nsCanvasFrame.h"
10
11 #include "gfxContext.h"
12 #include "gfxUtils.h"
13 #include "Layers.h"
14 #include "nsContainerFrame.h"
15 #include "nsContentCreatorFunctions.h"
16 #include "nsCSSRendering.h"
17 #include "nsPresContext.h"
18 #include "nsPopupSetFrame.h"
19 #include "nsGkAtoms.h"
20 #include "nsIFrameInlines.h"
21 #include "nsDisplayList.h"
22 #include "nsCSSFrameConstructor.h"
23 #include "nsFrameManager.h"
24 #include "gfxPlatform.h"
25 #include "nsPrintfCString.h"
26 #include "mozilla/AccessibleCaretEventHub.h"
27 #include "mozilla/BasePrincipal.h"
28 #include "mozilla/ComputedStyle.h"
29 #include "mozilla/StaticPrefs_browser.h"
30 #include "mozilla/dom/AnonymousContent.h"
31 #include "mozilla/layers/StackingContextHelper.h"
32 #include "mozilla/layers/RenderRootStateManager.h"
33 #include "mozilla/PresShell.h"
34 // for focus
35 #include "nsIScrollableFrame.h"
36 #ifdef DEBUG_CANVAS_FOCUS
37 # include "nsIDocShell.h"
38 #endif
39
40 //#define DEBUG_CANVAS_FOCUS
41
42 using namespace mozilla;
43 using namespace mozilla::dom;
44 using namespace mozilla::layout;
45 using namespace mozilla::gfx;
46 using namespace mozilla::layers;
47
NS_NewCanvasFrame(PresShell * aPresShell,ComputedStyle * aStyle)48 nsCanvasFrame* NS_NewCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle) {
49 return new (aPresShell) nsCanvasFrame(aStyle, aPresShell->GetPresContext());
50 }
51
52 NS_IMPL_FRAMEARENA_HELPERS(nsCanvasFrame)
53
NS_QUERYFRAME_HEAD(nsCanvasFrame)54 NS_QUERYFRAME_HEAD(nsCanvasFrame)
55 NS_QUERYFRAME_ENTRY(nsCanvasFrame)
56 NS_QUERYFRAME_ENTRY(nsIAnonymousContentCreator)
57 NS_QUERYFRAME_ENTRY(nsIPopupContainer)
58 NS_QUERYFRAME_TAIL_INHERITING(nsContainerFrame)
59
60 void nsCanvasFrame::ShowCustomContentContainer() {
61 if (mCustomContentContainer) {
62 mCustomContentContainer->UnsetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
63 true);
64 }
65 }
66
HideCustomContentContainer()67 void nsCanvasFrame::HideCustomContentContainer() {
68 if (mCustomContentContainer) {
69 mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::hidden,
70 u"true"_ns, true);
71 }
72 }
73
CreateAnonymousContent(nsTArray<ContentInfo> & aElements)74 nsresult nsCanvasFrame::CreateAnonymousContent(
75 nsTArray<ContentInfo>& aElements) {
76 MOZ_ASSERT(!mCustomContentContainer);
77
78 if (!mContent) {
79 return NS_OK;
80 }
81
82 nsCOMPtr<Document> doc = mContent->OwnerDoc();
83
84 RefPtr<AccessibleCaretEventHub> eventHub =
85 PresShell()->GetAccessibleCaretEventHub();
86
87 // This will go through InsertAnonymousContent and such, and we don't really
88 // want it to end up inserting into our content container.
89 //
90 // FIXME(emilio): The fact that this enters into InsertAnonymousContent is a
91 // bit nasty, can we avoid it, maybe doing this off a scriptrunner?
92 if (eventHub) {
93 eventHub->Init();
94 }
95
96 // Create the custom content container.
97 mCustomContentContainer = doc->CreateHTMLElement(nsGkAtoms::div);
98 #ifdef DEBUG
99 // We restyle our mCustomContentContainer, even though it's root anonymous
100 // content. Normally that's not OK because the frame constructor doesn't know
101 // how to order the frame tree in such cases, but we make this work for this
102 // particular case, so it's OK.
103 mCustomContentContainer->SetProperty(nsGkAtoms::restylableAnonymousNode,
104 reinterpret_cast<void*>(true));
105 #endif // DEBUG
106
107 mCustomContentContainer->SetProperty(
108 nsGkAtoms::docLevelNativeAnonymousContent, reinterpret_cast<void*>(true));
109
110 // This will usually be done by the caller, but in this case we do it here,
111 // since we reuse the document's AnoymousContent list, and those survive
112 // across reframes and thus may already be flagged as being in an anonymous
113 // subtree. We don't really want to have this semi-broken state where
114 // anonymous nodes have a non-anonymous.
115 mCustomContentContainer->SetIsNativeAnonymousRoot();
116
117 aElements.AppendElement(mCustomContentContainer);
118
119 // Do not create an accessible object for the container.
120 mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::role,
121 u"presentation"_ns, false);
122
123 mCustomContentContainer->SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
124 u"moz-custom-content-container"_ns, false);
125
126 // Only create a frame for mCustomContentContainer if it has some children.
127 if (doc->GetAnonymousContents().IsEmpty()) {
128 HideCustomContentContainer();
129 }
130
131 for (RefPtr<AnonymousContent>& anonContent : doc->GetAnonymousContents()) {
132 if (nsCOMPtr<nsINode> parent = anonContent->ContentNode().GetParentNode()) {
133 // Parent had better be an old custom content container already removed
134 // from a reframe. Forget about it since we're about to get inserted in a
135 // new one.
136 //
137 // TODO(emilio): Maybe we should extend PostDestroyData and do this stuff
138 // there instead, or something...
139 MOZ_ASSERT(parent != mCustomContentContainer);
140 MOZ_ASSERT(parent->IsElement());
141 MOZ_ASSERT(parent->AsElement()->IsRootOfNativeAnonymousSubtree());
142 MOZ_ASSERT(!parent->IsInComposedDoc());
143 MOZ_ASSERT(!parent->GetParentNode());
144
145 parent->RemoveChildNode(&anonContent->ContentNode(), false);
146 }
147
148 mCustomContentContainer->AppendChildTo(&anonContent->ContentNode(), false,
149 IgnoreErrors());
150 }
151
152 // Create a popupgroup element for system privileged non-XUL documents to
153 // support context menus and tooltips.
154 if (XRE_IsParentProcess() && doc->NodePrincipal()->IsSystemPrincipal()) {
155 nsNodeInfoManager* nodeInfoManager = doc->NodeInfoManager();
156 RefPtr<NodeInfo> nodeInfo =
157 nodeInfoManager->GetNodeInfo(nsGkAtoms::popupgroup, nullptr,
158 kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
159
160 nsresult rv = NS_NewXULElement(getter_AddRefs(mPopupgroupContent),
161 nodeInfo.forget(), dom::NOT_FROM_PARSER);
162 NS_ENSURE_SUCCESS(rv, rv);
163
164 mPopupgroupContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
165 reinterpret_cast<void*>(true));
166
167 aElements.AppendElement(mPopupgroupContent);
168
169 nodeInfo = nodeInfoManager->GetNodeInfo(
170 nsGkAtoms::tooltip, nullptr, kNameSpaceID_XUL, nsINode::ELEMENT_NODE);
171
172 rv = NS_NewXULElement(getter_AddRefs(mTooltipContent), nodeInfo.forget(),
173 dom::NOT_FROM_PARSER);
174 NS_ENSURE_SUCCESS(rv, rv);
175
176 mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::_default, u"true"_ns,
177 false);
178 // Set the page attribute so XULTooltipElement::PostHandleEvent will find
179 // the text for the tooltip from the currently hovered element.
180 mTooltipContent->SetAttr(kNameSpaceID_None, nsGkAtoms::page, u"true"_ns,
181 false);
182
183 mTooltipContent->SetProperty(nsGkAtoms::docLevelNativeAnonymousContent,
184 reinterpret_cast<void*>(true));
185
186 aElements.AppendElement(mTooltipContent);
187 }
188
189 #ifdef DEBUG
190 for (auto& element : aElements) {
191 MOZ_ASSERT(element.mContent->GetProperty(
192 nsGkAtoms::docLevelNativeAnonymousContent),
193 "NAC from the canvas frame needs to be document-level, otherwise"
194 " it (1) inherits from the document which is unexpected, and (2)"
195 " StyleChildrenIterator won't be able to find it properly");
196 }
197 #endif
198 return NS_OK;
199 }
200
AppendAnonymousContentTo(nsTArray<nsIContent * > & aElements,uint32_t aFilter)201 void nsCanvasFrame::AppendAnonymousContentTo(nsTArray<nsIContent*>& aElements,
202 uint32_t aFilter) {
203 if (mCustomContentContainer) {
204 aElements.AppendElement(mCustomContentContainer);
205 }
206 if (mPopupgroupContent) {
207 aElements.AppendElement(mPopupgroupContent);
208 }
209 if (mTooltipContent) {
210 aElements.AppendElement(mTooltipContent);
211 }
212 }
213
DestroyFrom(nsIFrame * aDestructRoot,PostDestroyData & aPostDestroyData)214 void nsCanvasFrame::DestroyFrom(nsIFrame* aDestructRoot,
215 PostDestroyData& aPostDestroyData) {
216 nsIScrollableFrame* sf =
217 PresContext()->GetPresShell()->GetRootScrollFrameAsScrollable();
218 if (sf) {
219 sf->RemoveScrollPositionListener(this);
220 }
221
222 aPostDestroyData.AddAnonymousContent(mCustomContentContainer.forget());
223 if (mPopupgroupContent) {
224 aPostDestroyData.AddAnonymousContent(mPopupgroupContent.forget());
225 }
226 if (mTooltipContent) {
227 aPostDestroyData.AddAnonymousContent(mTooltipContent.forget());
228 }
229
230 MOZ_ASSERT(!mPopupSetFrame ||
231 nsLayoutUtils::IsProperAncestorFrame(this, mPopupSetFrame),
232 "Someone forgot to clear popup set frame");
233 nsContainerFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
234 }
235
ScrollPositionWillChange(nscoord aX,nscoord aY)236 void nsCanvasFrame::ScrollPositionWillChange(nscoord aX, nscoord aY) {
237 if (mDoPaintFocus) {
238 mDoPaintFocus = false;
239 PresShell()->GetRootFrame()->InvalidateFrameSubtree();
240 }
241 }
242
243 NS_IMETHODIMP
SetHasFocus(bool aHasFocus)244 nsCanvasFrame::SetHasFocus(bool aHasFocus) {
245 if (mDoPaintFocus != aHasFocus) {
246 mDoPaintFocus = aHasFocus;
247 PresShell()->GetRootFrame()->InvalidateFrameSubtree();
248
249 if (!mAddedScrollPositionListener) {
250 nsIScrollableFrame* sf =
251 PresContext()->GetPresShell()->GetRootScrollFrameAsScrollable();
252 if (sf) {
253 sf->AddScrollPositionListener(this);
254 mAddedScrollPositionListener = true;
255 }
256 }
257 }
258 return NS_OK;
259 }
260
SetInitialChildList(ChildListID aListID,nsFrameList & aChildList)261 void nsCanvasFrame::SetInitialChildList(ChildListID aListID,
262 nsFrameList& aChildList) {
263 NS_ASSERTION(aListID != kPrincipalList || aChildList.IsEmpty() ||
264 aChildList.OnlyChild(),
265 "Primary child list can have at most one frame in it");
266 nsContainerFrame::SetInitialChildList(aListID, aChildList);
267 }
268
AppendFrames(ChildListID aListID,nsFrameList & aFrameList)269 void nsCanvasFrame::AppendFrames(ChildListID aListID, nsFrameList& aFrameList) {
270 #ifdef DEBUG
271 MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
272 if (!mFrames.IsEmpty()) {
273 for (nsFrameList::Enumerator e(aFrameList); !e.AtEnd(); e.Next()) {
274 // We only allow native anonymous child frames to be in principal child
275 // list in canvas frame.
276 MOZ_ASSERT(e.get()->GetContent()->IsInNativeAnonymousSubtree(),
277 "invalid child list");
278 }
279 }
280 nsIFrame::VerifyDirtyBitSet(aFrameList);
281 #endif
282 nsContainerFrame::AppendFrames(aListID, aFrameList);
283 }
284
InsertFrames(ChildListID aListID,nsIFrame * aPrevFrame,const nsLineList::iterator * aPrevFrameLine,nsFrameList & aFrameList)285 void nsCanvasFrame::InsertFrames(ChildListID aListID, nsIFrame* aPrevFrame,
286 const nsLineList::iterator* aPrevFrameLine,
287 nsFrameList& aFrameList) {
288 // Because we only support a single child frame inserting is the same
289 // as appending
290 MOZ_ASSERT(!aPrevFrame, "unexpected previous sibling frame");
291 AppendFrames(aListID, aFrameList);
292 }
293
294 #ifdef DEBUG
RemoveFrame(ChildListID aListID,nsIFrame * aOldFrame)295 void nsCanvasFrame::RemoveFrame(ChildListID aListID, nsIFrame* aOldFrame) {
296 MOZ_ASSERT(aListID == kPrincipalList, "unexpected child list");
297 nsContainerFrame::RemoveFrame(aListID, aOldFrame);
298 }
299 #endif
300
CanvasArea() const301 nsRect nsCanvasFrame::CanvasArea() const {
302 // Not clear which overflow rect we want here, but it probably doesn't
303 // matter.
304 nsRect result(InkOverflowRect());
305
306 nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetParent());
307 if (scrollableFrame) {
308 nsRect portRect = scrollableFrame->GetScrollPortRect();
309 result.UnionRect(result, nsRect(nsPoint(0, 0), portRect.Size()));
310 }
311 return result;
312 }
313
GetPopupSetFrame()314 nsPopupSetFrame* nsCanvasFrame::GetPopupSetFrame() { return mPopupSetFrame; }
315
SetPopupSetFrame(nsPopupSetFrame * aPopupSet)316 void nsCanvasFrame::SetPopupSetFrame(nsPopupSetFrame* aPopupSet) {
317 MOZ_ASSERT(!aPopupSet || !mPopupSetFrame,
318 "Popup set is already defined! Only 1 allowed.");
319 mPopupSetFrame = aPopupSet;
320 }
321
GetDefaultTooltip()322 Element* nsCanvasFrame::GetDefaultTooltip() { return mTooltipContent; }
323
SetDefaultTooltip(Element * aTooltip)324 void nsCanvasFrame::SetDefaultTooltip(Element* aTooltip) {
325 MOZ_ASSERT(!aTooltip || aTooltip == mTooltipContent,
326 "Default tooltip should be anonymous content tooltip.");
327 mTooltipContent = aTooltip;
328 }
329
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)330 void nsDisplayCanvasBackgroundColor::Paint(nsDisplayListBuilder* aBuilder,
331 gfxContext* aCtx) {
332 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
333 nsPoint offset = ToReferenceFrame();
334 nsRect bgClipRect = frame->CanvasArea() + offset;
335 if (NS_GET_A(mColor) > 0) {
336 DrawTarget* drawTarget = aCtx->GetDrawTarget();
337 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
338 Rect devPxRect =
339 NSRectToSnappedRect(bgClipRect, appUnitsPerDevPixel, *drawTarget);
340 drawTarget->FillRect(devPxRect, ColorPattern(ToDeviceColor(mColor)));
341 }
342 }
343
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)344 bool nsDisplayCanvasBackgroundColor::CreateWebRenderCommands(
345 mozilla::wr::DisplayListBuilder& aBuilder,
346 mozilla::wr::IpcResourceUpdateQueue& aResources,
347 const StackingContextHelper& aSc, RenderRootStateManager* aManager,
348 nsDisplayListBuilder* aDisplayListBuilder) {
349 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
350 nsPoint offset = ToReferenceFrame();
351 nsRect bgClipRect = frame->CanvasArea() + offset;
352 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
353
354 LayoutDeviceRect rect =
355 LayoutDeviceRect::FromAppUnits(bgClipRect, appUnitsPerDevPixel);
356
357 wr::LayoutRect r = wr::ToLayoutRect(rect);
358 aBuilder.PushRect(r, r, !BackfaceIsHidden(), false,
359 wr::ToColorF(ToDeviceColor(mColor)));
360 return true;
361 }
362
WriteDebugInfo(std::stringstream & aStream)363 void nsDisplayCanvasBackgroundColor::WriteDebugInfo(
364 std::stringstream& aStream) {
365 aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor)
366 << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor)
367 << ")";
368 }
369
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)370 void nsDisplayCanvasBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
371 gfxContext* aCtx) {
372 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
373 nsPoint offset = ToReferenceFrame();
374 nsRect bgClipRect = frame->CanvasArea() + offset;
375
376 PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &bgClipRect);
377 }
378
IsSingleFixedPositionImage(nsDisplayListBuilder * aBuilder,const nsRect & aClipRect,gfxRect * aDestRect)379 bool nsDisplayCanvasBackgroundImage::IsSingleFixedPositionImage(
380 nsDisplayListBuilder* aBuilder, const nsRect& aClipRect,
381 gfxRect* aDestRect) {
382 if (!mBackgroundStyle) return false;
383
384 if (mBackgroundStyle->StyleBackground()->mImage.mLayers.Length() != 1)
385 return false;
386
387 nsPresContext* presContext = mFrame->PresContext();
388 uint32_t flags = aBuilder->GetBackgroundPaintFlags();
389 nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
390 const nsStyleImageLayers::Layer& layer =
391 mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
392
393 if (layer.mAttachment != StyleImageLayerAttachment::Fixed) return false;
394
395 nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
396 presContext, mFrame, flags, borderArea, aClipRect, layer);
397
398 // We only care about images here, not gradients.
399 if (!mIsRasterImage) return false;
400
401 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
402 *aDestRect =
403 nsLayoutUtils::RectToGfxRect(state.mFillArea, appUnitsPerDevPixel);
404
405 return true;
406 }
407
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)408 void nsDisplayCanvasThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
409 gfxContext* aCtx) {
410 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
411 nsPoint offset = ToReferenceFrame();
412 nsRect bgClipRect = frame->CanvasArea() + offset;
413
414 PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &bgClipRect);
415 }
416
417 /**
418 * A display item to paint the focus ring for the document.
419 *
420 * The only reason this can't use nsDisplayGeneric is overriding GetBounds.
421 */
422 class nsDisplayCanvasFocus : public nsPaintedDisplayItem {
423 public:
nsDisplayCanvasFocus(nsDisplayListBuilder * aBuilder,nsCanvasFrame * aFrame)424 nsDisplayCanvasFocus(nsDisplayListBuilder* aBuilder, nsCanvasFrame* aFrame)
425 : nsPaintedDisplayItem(aBuilder, aFrame) {
426 MOZ_COUNT_CTOR(nsDisplayCanvasFocus);
427 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvasFocus)428 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvasFocus)
429
430 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
431 bool* aSnap) const override {
432 *aSnap = false;
433 // This is an overestimate, but that's not a problem.
434 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
435 return frame->CanvasArea() + ToReferenceFrame();
436 }
437
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)438 virtual void Paint(nsDisplayListBuilder* aBuilder,
439 gfxContext* aCtx) override {
440 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
441 frame->PaintFocus(aCtx->GetDrawTarget(), ToReferenceFrame());
442 }
443
444 NS_DISPLAY_DECL_NAME("CanvasFocus", TYPE_CANVAS_FOCUS)
445 };
446
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)447 void nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
448 const nsDisplayListSet& aLists) {
449 if (GetPrevInFlow()) {
450 DisplayOverflowContainers(aBuilder, aLists);
451 }
452
453 // Force a background to be shown. We may have a background propagated to us,
454 // in which case StyleBackground wouldn't have the right background
455 // and the code in nsIFrame::DisplayBorderBackgroundOutline might not give us
456 // a background.
457 // We don't have any border or outline, and our background draws over
458 // the overflow area, so just add nsDisplayCanvasBackground instead of
459 // calling DisplayBorderBackgroundOutline.
460 if (IsVisibleForPainting()) {
461 ComputedStyle* bg = nullptr;
462 nsIFrame* dependentFrame = nullptr;
463 bool isThemed = IsThemed();
464 if (!isThemed &&
465 nsCSSRendering::FindBackgroundFrame(this, &dependentFrame)) {
466 bg = dependentFrame->Style();
467 if (dependentFrame == this) {
468 dependentFrame = nullptr;
469 }
470 }
471
472 if (isThemed) {
473 aLists.BorderBackground()
474 ->AppendNewToTop<nsDisplayCanvasThemedBackground>(aBuilder, this);
475 return;
476 }
477
478 if (!bg) {
479 return;
480 }
481
482 const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
483
484 bool needBlendContainer = false;
485 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
486
487 const bool suppressBackgroundImage = [&] {
488 // Handle print settings.
489 if (!ComputeShouldPaintBackground().mImage) {
490 return true;
491 }
492 // In high-contrast-mode, we suppress background-image on the canvas frame
493 // (even when backplating), because users expect site backgrounds to
494 // conform to their HCM background color when a solid color is rendered,
495 // and some websites use solid-color images instead of an overwritable
496 // background color.
497 if (PresContext()->ForcingColors() &&
498 StaticPrefs::
499 browser_display_suppress_canvas_background_image_on_forced_colors()) {
500 return true;
501 }
502 return false;
503 }();
504
505 nsDisplayList layerItems(aBuilder);
506
507 // Create separate items for each background layer.
508 const nsStyleImageLayers& layers = bg->StyleBackground()->mImage;
509 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
510 if (layers.mLayers[i].mImage.IsNone() || suppressBackgroundImage) {
511 continue;
512 }
513 if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
514 needBlendContainer = true;
515 }
516
517 nsRect bgRect =
518 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
519
520 const ActiveScrolledRoot* thisItemASR = asr;
521 nsDisplayList thisItemList(aBuilder);
522 nsDisplayBackgroundImage::InitData bgData =
523 nsDisplayBackgroundImage::GetInitData(aBuilder, this, i, bgRect, bg);
524
525 if (bgData.shouldFixToViewport) {
526 auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData();
527 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
528 aBuilder, this, aBuilder->GetVisibleRect(),
529 aBuilder->GetDirtyRect());
530
531 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
532 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
533 aBuilder);
534 if (displayData) {
535 nsPoint offset =
536 GetOffsetTo(PresContext()->GetPresShell()->GetRootFrame());
537 aBuilder->SetVisibleRect(displayData->mVisibleRect + offset);
538 aBuilder->SetDirtyRect(displayData->mDirtyRect + offset);
539
540 clipState.SetClipChainForContainingBlockDescendants(
541 displayData->mContainingBlockClipChain);
542 asrSetter.SetCurrentActiveScrolledRoot(
543 displayData->mContainingBlockActiveScrolledRoot);
544 thisItemASR = displayData->mContainingBlockActiveScrolledRoot;
545 }
546 nsDisplayCanvasBackgroundImage* bgItem = nullptr;
547 {
548 DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
549 bgImageClip.Clear();
550 bgItem = MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
551 aBuilder, this, /* aIndex = */ i, bgData);
552 if (bgItem) {
553 bgItem->SetDependentFrame(aBuilder, dependentFrame);
554 }
555 }
556 if (bgItem) {
557 thisItemList.AppendToTop(
558 nsDisplayFixedPosition::CreateForFixedBackground(
559 aBuilder, this, nullptr, bgItem, i, asr));
560 }
561
562 } else {
563 nsDisplayCanvasBackgroundImage* bgItem =
564 MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
565 aBuilder, this, /* aIndex = */ i, bgData);
566 if (bgItem) {
567 bgItem->SetDependentFrame(aBuilder, dependentFrame);
568 thisItemList.AppendToTop(bgItem);
569 }
570 }
571
572 if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
573 DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
574 thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>(
575 aBuilder, this, i + 1, &thisItemList, layers.mLayers[i].mBlendMode,
576 thisItemASR, true);
577 }
578 layerItems.AppendToTop(&thisItemList);
579 }
580
581 bool hasFixedBottomLayer =
582 layers.mImageCount > 0 &&
583 layers.mLayers[0].mAttachment == StyleImageLayerAttachment::Fixed;
584
585 if (!hasFixedBottomLayer || needBlendContainer) {
586 // Put a scrolled background color item in place, at the bottom of the
587 // list. The color of this item will be filled in during
588 // PresShell::AddCanvasBackgroundColorItem.
589 // Do not add this item if there's a fixed background image at the bottom
590 // (unless we have to, for correct blending); with a fixed background,
591 // it's better to allow the fixed background image to combine itself with
592 // a non-scrolled background color directly underneath, rather than
593 // interleaving the two with a scrolled background color.
594 // PresShell::AddCanvasBackgroundColorItem makes sure there always is a
595 // non-scrolled background color item at the bottom.
596 aLists.BorderBackground()->AppendNewToTop<nsDisplayCanvasBackgroundColor>(
597 aBuilder, this);
598 }
599
600 aLists.BorderBackground()->AppendToTop(&layerItems);
601
602 if (needBlendContainer) {
603 const ActiveScrolledRoot* containerASR = contASRTracker.GetContainerASR();
604 DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder);
605 aLists.BorderBackground()->AppendToTop(
606 nsDisplayBlendContainer::CreateForBackgroundBlendMode(
607 aBuilder, this, nullptr, aLists.BorderBackground(),
608 containerASR));
609 }
610 }
611
612 for (nsIFrame* kid : PrincipalChildList()) {
613 // Put our child into its own pseudo-stack.
614 BuildDisplayListForChild(aBuilder, kid, aLists);
615 }
616
617 #ifdef DEBUG_CANVAS_FOCUS
618 nsCOMPtr<nsIContent> focusContent;
619 aPresContext->EventStateManager()->GetFocusedContent(
620 getter_AddRefs(focusContent));
621
622 bool hasFocus = false;
623 nsCOMPtr<nsISupports> container;
624 aPresContext->GetContainer(getter_AddRefs(container));
625 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(container));
626 if (docShell) {
627 docShell->GetHasFocus(&hasFocus);
628 nsRect dirty = aBuilder->GetDirtyRect();
629 printf("%p - nsCanvasFrame::Paint R:%d,%d,%d,%d DR: %d,%d,%d,%d\n", this,
630 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
631 dirty.width, dirty.height);
632 }
633 printf("%p - Focus: %s c: %p DoPaint:%s\n", docShell.get(),
634 hasFocus ? "Y" : "N", focusContent.get(), mDoPaintFocus ? "Y" : "N");
635 #endif
636
637 if (!mDoPaintFocus) return;
638 // Only paint the focus if we're visible
639 if (!StyleVisibility()->IsVisible()) return;
640
641 aLists.Outlines()->AppendNewToTop<nsDisplayCanvasFocus>(aBuilder, this);
642 }
643
PaintFocus(DrawTarget * aDrawTarget,nsPoint aPt)644 void nsCanvasFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt) {
645 nsRect focusRect(aPt, GetSize());
646
647 nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetParent());
648 if (scrollableFrame) {
649 nsRect portRect = scrollableFrame->GetScrollPortRect();
650 focusRect.width = portRect.width;
651 focusRect.height = portRect.height;
652 focusRect.MoveBy(scrollableFrame->GetScrollPosition());
653 }
654
655 // XXX use the root frame foreground color, but should we find BODY frame
656 // for HTML documents?
657 nsIFrame* root = mFrames.FirstChild();
658 const auto* text = root ? root->StyleText() : StyleText();
659 nsCSSRendering::PaintFocus(PresContext(), aDrawTarget, focusRect,
660 text->mColor.ToColor());
661 }
662
663 /* virtual */
GetMinISize(gfxContext * aRenderingContext)664 nscoord nsCanvasFrame::GetMinISize(gfxContext* aRenderingContext) {
665 nscoord result;
666 DISPLAY_MIN_INLINE_SIZE(this, result);
667 if (mFrames.IsEmpty())
668 result = 0;
669 else
670 result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
671 return result;
672 }
673
674 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)675 nscoord nsCanvasFrame::GetPrefISize(gfxContext* aRenderingContext) {
676 nscoord result;
677 DISPLAY_PREF_INLINE_SIZE(this, result);
678 if (mFrames.IsEmpty())
679 result = 0;
680 else
681 result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
682 return result;
683 }
684
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)685 void nsCanvasFrame::Reflow(nsPresContext* aPresContext,
686 ReflowOutput& aDesiredSize,
687 const ReflowInput& aReflowInput,
688 nsReflowStatus& aStatus) {
689 MarkInReflow();
690 DO_GLOBAL_REFLOW_COUNT("nsCanvasFrame");
691 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
692 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
693 NS_FRAME_TRACE_REFLOW_IN("nsCanvasFrame::Reflow");
694
695 nsCanvasFrame* prevCanvasFrame = static_cast<nsCanvasFrame*>(GetPrevInFlow());
696 if (prevCanvasFrame) {
697 AutoFrameListPtr overflow(aPresContext,
698 prevCanvasFrame->StealOverflowFrames());
699 if (overflow) {
700 NS_ASSERTION(overflow->OnlyChild(),
701 "must have doc root as canvas frame's only child");
702 nsContainerFrame::ReparentFrameViewList(*overflow, prevCanvasFrame, this);
703 // Prepend overflow to the our child list. There may already be
704 // children placeholders for fixed-pos elements, which don't get
705 // reflowed but must not be lost until the canvas frame is destroyed.
706 mFrames.InsertFrames(this, nullptr, *overflow);
707 }
708 }
709
710 // Set our size up front, since some parts of reflow depend on it
711 // being already set. Note that the computed height may be
712 // unconstrained; that's ok. Consumers should watch out for that.
713 SetSize(nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()));
714
715 // Reflow our children. Typically, we only have one child - the root
716 // element's frame or a placeholder for that frame, if the root element
717 // is abs-pos or fixed-pos. Note that this child might be missing though
718 // if that frame was Complete in one of our earlier continuations. This
719 // happens when we create additional pages purely to make room for painting
720 // overflow (painted by BuildPreviousPageOverflow in nsPageFrame.cpp).
721 // We may have additional children which are placeholders for continuations
722 // of fixed-pos content, see nsCSSFrameConstructor::ReplicateFixedFrames.
723 // We may also have a nsPopupSetFrame child (mPopupSetFrame).
724 const WritingMode wm = aReflowInput.GetWritingMode();
725 aDesiredSize.SetSize(wm, aReflowInput.ComputedSize());
726 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
727 // Set the block-size to zero for now in case we don't have any non-
728 // placeholder children that would update the size in the loop below.
729 aDesiredSize.BSize(wm) = nscoord(0);
730 }
731 aDesiredSize.SetOverflowAreasToDesiredBounds();
732 nsIFrame* nextKid = nullptr;
733 for (auto* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = nextKid) {
734 nextKid = kidFrame->GetNextSibling();
735 if (kidFrame == mPopupSetFrame) {
736 // This child is handled separately after this loop.
737 continue;
738 }
739
740 ReflowOutput kidDesiredSize(aReflowInput);
741 bool kidDirty = kidFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
742 WritingMode kidWM = kidFrame->GetWritingMode();
743 auto availableSize = aReflowInput.AvailableSize(kidWM);
744 nscoord bOffset = 0;
745 nscoord canvasBSizeSum = 0;
746 if (prevCanvasFrame && availableSize.BSize(kidWM) != NS_UNCONSTRAINEDSIZE &&
747 !kidFrame->IsPlaceholderFrame() &&
748 StaticPrefs::layout_display_list_improve_fragmentation()) {
749 for (auto* pif = prevCanvasFrame; pif;
750 pif = static_cast<nsCanvasFrame*>(pif->GetPrevInFlow())) {
751 canvasBSizeSum += pif->BSize(kidWM);
752 auto* pifChild = pif->PrincipalChildList().FirstChild();
753 if (pifChild) {
754 nscoord layoutOverflow = pifChild->BSize(kidWM) - canvasBSizeSum;
755 // A negative value means that the :root frame does not fill
756 // the canvas. In this case we can't determine the offset exactly
757 // so we use the end edge of the scrollable overflow as the offset
758 // instead. This will likely push down the content below where it
759 // should be placed, creating a gap. That's preferred over making
760 // content overlap which would otherwise occur.
761 // See layout/reftests/pagination/inline-block-slice-7.html for an
762 // example of this.
763 if (layoutOverflow < 0) {
764 LogicalRect so(kidWM, pifChild->ScrollableOverflowRect(),
765 pifChild->GetSize());
766 layoutOverflow = so.BEnd(kidWM) - canvasBSizeSum;
767 }
768 bOffset = std::max(bOffset, layoutOverflow);
769 }
770 }
771 availableSize.BSize(kidWM) -= bOffset;
772 }
773
774 if (MOZ_LIKELY(availableSize.BSize(kidWM) > 0)) {
775 ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
776 availableSize);
777
778 if (aReflowInput.IsBResizeForWM(kidReflowInput.GetWritingMode()) &&
779 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
780 // Tell our kid it's being block-dir resized too. Bit of a
781 // hack for framesets.
782 kidReflowInput.SetBResize(true);
783 }
784
785 nsSize containerSize = aReflowInput.ComputedPhysicalSize();
786 LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(kidWM);
787 LogicalPoint kidPt(kidWM, margin.IStart(kidWM), margin.BStart(kidWM));
788 (kidWM.IsOrthogonalTo(wm) ? kidPt.I(kidWM) : kidPt.B(kidWM)) += bOffset;
789
790 nsReflowStatus kidStatus;
791 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, kidWM,
792 kidPt, containerSize, ReflowChildFlags::Default, kidStatus);
793
794 FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, &kidReflowInput,
795 kidWM, kidPt, containerSize,
796 ReflowChildFlags::ApplyRelativePositioning);
797
798 if (!kidStatus.IsFullyComplete()) {
799 nsIFrame* nextFrame = kidFrame->GetNextInFlow();
800 NS_ASSERTION(nextFrame || kidStatus.NextInFlowNeedsReflow(),
801 "If it's incomplete and has no nif yet, it must flag a "
802 "nif reflow.");
803 if (!nextFrame) {
804 nextFrame = aPresContext->PresShell()
805 ->FrameConstructor()
806 ->CreateContinuingFrame(kidFrame, this);
807 SetOverflowFrames(nsFrameList(nextFrame, nextFrame));
808 // Root overflow containers will be normal children of
809 // the canvas frame, but that's ok because there
810 // aren't any other frames we need to isolate them from
811 // during reflow.
812 }
813 if (kidStatus.IsOverflowIncomplete()) {
814 nextFrame->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
815 }
816 }
817 aStatus.MergeCompletionStatusFrom(kidStatus);
818
819 // If the child frame was just inserted, then we're responsible for making
820 // sure it repaints
821 if (kidDirty) {
822 // But we have a new child, which will affect our background, so
823 // invalidate our whole rect.
824 // Note: Even though we request to be sized to our child's size, our
825 // scroll frame ensures that we are always the size of the viewport.
826 // Also note: GetPosition() on a CanvasFrame is always going to return
827 // (0, 0). We only want to invalidate GetRect() since Get*OverflowRect()
828 // could also include overflow to our top and left (out of the viewport)
829 // which doesn't need to be painted.
830 nsIFrame* viewport = PresContext()->GetPresShell()->GetRootFrame();
831 viewport->InvalidateFrame();
832 }
833
834 // Return our desired size. Normally it's what we're told, but
835 // sometimes we can be given an unconstrained block-size (when a window
836 // is sizing-to-content), and we should compute our desired block-size.
837 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
838 !kidFrame->IsPlaceholderFrame()) {
839 LogicalSize finalSize = aReflowInput.ComputedSize();
840 finalSize.BSize(wm) =
841 kidFrame->GetLogicalSize(wm).BSize(wm) +
842 kidReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
843 aDesiredSize.SetSize(wm, finalSize);
844 aDesiredSize.SetOverflowAreasToDesiredBounds();
845 }
846 aDesiredSize.mOverflowAreas.UnionWith(kidDesiredSize.mOverflowAreas +
847 kidFrame->GetPosition());
848 } else if (kidFrame->IsPlaceholderFrame()) {
849 // Placeholders always fit even if there's no available block-size left.
850 } else {
851 // This only occurs in paginated mode. There is no available space on
852 // this page due to reserving space for overflow from a previous page,
853 // so we push our child to the next page. Note that we can have some
854 // placeholders for fixed pos. frames in mFrames too, so we need to be
855 // careful to only push `kidFrame`.
856 mFrames.RemoveFrame(kidFrame);
857 SetOverflowFrames(nsFrameList(kidFrame, kidFrame));
858 aStatus.SetIncomplete();
859 }
860 }
861
862 if (prevCanvasFrame) {
863 ReflowOverflowContainerChildren(aPresContext, aReflowInput,
864 aDesiredSize.mOverflowAreas,
865 ReflowChildFlags::Default, aStatus);
866 }
867
868 if (mPopupSetFrame) {
869 MOZ_ASSERT(mFrames.ContainsFrame(mPopupSetFrame),
870 "Only normal flow supported.");
871 nsReflowStatus popupStatus;
872 ReflowOutput popupDesiredSize(aReflowInput.GetWritingMode());
873 WritingMode wm = mPopupSetFrame->GetWritingMode();
874 LogicalSize availSize = aReflowInput.ComputedSize(wm);
875 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
876 ReflowInput popupReflowInput(aPresContext, aReflowInput, mPopupSetFrame,
877 availSize);
878 ReflowChild(mPopupSetFrame, aPresContext, popupDesiredSize,
879 popupReflowInput, 0, 0, ReflowChildFlags::NoMoveFrame,
880 popupStatus);
881 FinishReflowChild(mPopupSetFrame, aPresContext, popupDesiredSize,
882 &popupReflowInput, 0, 0, ReflowChildFlags::NoMoveFrame);
883 }
884
885 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
886 aStatus);
887
888 NS_FRAME_TRACE_REFLOW_OUT("nsCanvasFrame::Reflow", aStatus);
889 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
890 }
891
GetContentForEvent(WidgetEvent * aEvent,nsIContent ** aContent)892 nsresult nsCanvasFrame::GetContentForEvent(WidgetEvent* aEvent,
893 nsIContent** aContent) {
894 NS_ENSURE_ARG_POINTER(aContent);
895 nsresult rv = nsIFrame::GetContentForEvent(aEvent, aContent);
896 if (NS_FAILED(rv) || !*aContent) {
897 nsIFrame* kid = mFrames.FirstChild();
898 if (kid) {
899 rv = kid->GetContentForEvent(aEvent, aContent);
900 }
901 }
902
903 return rv;
904 }
905
906 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const907 nsresult nsCanvasFrame::GetFrameName(nsAString& aResult) const {
908 return MakeFrameName(u"Canvas"_ns, aResult);
909 }
910 #endif
911