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
BuildLayer(nsDisplayListBuilder * aBuilder,LayerManager * aManager,const ContainerLayerParameters & aContainerParameters)344 already_AddRefed<Layer> nsDisplayCanvasBackgroundColor::BuildLayer(
345 nsDisplayListBuilder* aBuilder, LayerManager* aManager,
346 const ContainerLayerParameters& aContainerParameters) {
347 if (NS_GET_A(mColor) == 0) {
348 return nullptr;
349 }
350
351 RefPtr<ColorLayer> layer = static_cast<ColorLayer*>(
352 aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this));
353 if (!layer) {
354 layer = aManager->CreateColorLayer();
355 if (!layer) {
356 return nullptr;
357 }
358 }
359 layer->SetColor(ToDeviceColor(mColor));
360
361 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
362 nsPoint offset = ToReferenceFrame();
363 nsRect bgClipRect = frame->CanvasArea() + offset;
364
365 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
366
367 layer->SetBounds(bgClipRect.ToNearestPixels(appUnitsPerDevPixel));
368 layer->SetBaseTransform(gfx::Matrix4x4::Translation(
369 aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0));
370
371 return layer.forget();
372 }
373
CreateWebRenderCommands(mozilla::wr::DisplayListBuilder & aBuilder,mozilla::wr::IpcResourceUpdateQueue & aResources,const StackingContextHelper & aSc,RenderRootStateManager * aManager,nsDisplayListBuilder * aDisplayListBuilder)374 bool nsDisplayCanvasBackgroundColor::CreateWebRenderCommands(
375 mozilla::wr::DisplayListBuilder& aBuilder,
376 mozilla::wr::IpcResourceUpdateQueue& aResources,
377 const StackingContextHelper& aSc, RenderRootStateManager* aManager,
378 nsDisplayListBuilder* aDisplayListBuilder) {
379 ContainerLayerParameters parameter;
380
381 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
382 nsPoint offset = ToReferenceFrame();
383 nsRect bgClipRect = frame->CanvasArea() + offset;
384 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
385
386 LayoutDeviceRect rect =
387 LayoutDeviceRect::FromAppUnits(bgClipRect, appUnitsPerDevPixel);
388
389 wr::LayoutRect r = wr::ToLayoutRect(rect);
390 aBuilder.PushRect(r, r, !BackfaceIsHidden(),
391 wr::ToColorF(ToDeviceColor(mColor)));
392 return true;
393 }
394
WriteDebugInfo(std::stringstream & aStream)395 void nsDisplayCanvasBackgroundColor::WriteDebugInfo(
396 std::stringstream& aStream) {
397 aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor)
398 << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor)
399 << ")";
400 }
401
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)402 void nsDisplayCanvasBackgroundImage::Paint(nsDisplayListBuilder* aBuilder,
403 gfxContext* aCtx) {
404 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
405 nsPoint offset = ToReferenceFrame();
406 nsRect bgClipRect = frame->CanvasArea() + offset;
407
408 PaintInternal(aBuilder, aCtx, GetPaintRect(), &bgClipRect);
409 }
410
IsSingleFixedPositionImage(nsDisplayListBuilder * aBuilder,const nsRect & aClipRect,gfxRect * aDestRect)411 bool nsDisplayCanvasBackgroundImage::IsSingleFixedPositionImage(
412 nsDisplayListBuilder* aBuilder, const nsRect& aClipRect,
413 gfxRect* aDestRect) {
414 if (!mBackgroundStyle) return false;
415
416 if (mBackgroundStyle->StyleBackground()->mImage.mLayers.Length() != 1)
417 return false;
418
419 nsPresContext* presContext = mFrame->PresContext();
420 uint32_t flags = aBuilder->GetBackgroundPaintFlags();
421 nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize());
422 const nsStyleImageLayers::Layer& layer =
423 mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer];
424
425 if (layer.mAttachment != StyleImageLayerAttachment::Fixed) return false;
426
427 nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer(
428 presContext, mFrame, flags, borderArea, aClipRect, layer);
429
430 // We only care about images here, not gradients.
431 if (!mIsRasterImage) return false;
432
433 int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
434 *aDestRect =
435 nsLayoutUtils::RectToGfxRect(state.mFillArea, appUnitsPerDevPixel);
436
437 return true;
438 }
439
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)440 void nsDisplayCanvasThemedBackground::Paint(nsDisplayListBuilder* aBuilder,
441 gfxContext* aCtx) {
442 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
443 nsPoint offset = ToReferenceFrame();
444 nsRect bgClipRect = frame->CanvasArea() + offset;
445
446 PaintInternal(aBuilder, aCtx, GetPaintRect(), &bgClipRect);
447 }
448
449 /**
450 * A display item to paint the focus ring for the document.
451 *
452 * The only reason this can't use nsDisplayGeneric is overriding GetBounds.
453 */
454 class nsDisplayCanvasFocus : public nsPaintedDisplayItem {
455 public:
nsDisplayCanvasFocus(nsDisplayListBuilder * aBuilder,nsCanvasFrame * aFrame)456 nsDisplayCanvasFocus(nsDisplayListBuilder* aBuilder, nsCanvasFrame* aFrame)
457 : nsPaintedDisplayItem(aBuilder, aFrame) {
458 MOZ_COUNT_CTOR(nsDisplayCanvasFocus);
459 }
MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvasFocus)460 MOZ_COUNTED_DTOR_OVERRIDE(nsDisplayCanvasFocus)
461
462 virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
463 bool* aSnap) const override {
464 *aSnap = false;
465 // This is an overestimate, but that's not a problem.
466 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
467 return frame->CanvasArea() + ToReferenceFrame();
468 }
469
Paint(nsDisplayListBuilder * aBuilder,gfxContext * aCtx)470 virtual void Paint(nsDisplayListBuilder* aBuilder,
471 gfxContext* aCtx) override {
472 nsCanvasFrame* frame = static_cast<nsCanvasFrame*>(mFrame);
473 frame->PaintFocus(aCtx->GetDrawTarget(), ToReferenceFrame());
474 }
475
476 NS_DISPLAY_DECL_NAME("CanvasFocus", TYPE_CANVAS_FOCUS)
477 };
478
BuildDisplayList(nsDisplayListBuilder * aBuilder,const nsDisplayListSet & aLists)479 void nsCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
480 const nsDisplayListSet& aLists) {
481 if (GetPrevInFlow()) {
482 DisplayOverflowContainers(aBuilder, aLists);
483 }
484
485 // Force a background to be shown. We may have a background propagated to us,
486 // in which case StyleBackground wouldn't have the right background
487 // and the code in nsIFrame::DisplayBorderBackgroundOutline might not give us
488 // a background.
489 // We don't have any border or outline, and our background draws over
490 // the overflow area, so just add nsDisplayCanvasBackground instead of
491 // calling DisplayBorderBackgroundOutline.
492 if (IsVisibleForPainting()) {
493 ComputedStyle* bg = nullptr;
494 nsIFrame* dependentFrame = nullptr;
495 bool isThemed = IsThemed();
496 if (!isThemed &&
497 nsCSSRendering::FindBackgroundFrame(this, &dependentFrame)) {
498 bg = dependentFrame->Style();
499 if (dependentFrame == this) {
500 dependentFrame = nullptr;
501 }
502 }
503
504 if (isThemed) {
505 aLists.BorderBackground()
506 ->AppendNewToTop<nsDisplayCanvasThemedBackground>(aBuilder, this);
507 return;
508 }
509
510 if (!bg) {
511 return;
512 }
513
514 const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot();
515
516 bool needBlendContainer = false;
517 nsDisplayListBuilder::AutoContainerASRTracker contASRTracker(aBuilder);
518
519 const bool suppressBackgroundImage = [&] {
520 // Handle print settings.
521 if (!ComputeShouldPaintBackground().mImage) {
522 return true;
523 }
524 // In high-contrast-mode, we suppress background-image on the canvas frame
525 // (even when backplating), because users expect site backgrounds to
526 // conform to their HCM background color when a solid color is rendered,
527 // and some websites use solid-color images instead of an overwritable
528 // background color.
529 if (!PresContext()->PrefSheetPrefs().mUseDocumentColors &&
530 StaticPrefs::
531 browser_display_suppress_canvas_background_image_on_forced_colors()) {
532 return true;
533 }
534 return false;
535 }();
536
537 // Create separate items for each background layer.
538 const nsStyleImageLayers& layers = bg->StyleBackground()->mImage;
539 NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
540 if (layers.mLayers[i].mImage.IsNone() || suppressBackgroundImage) {
541 continue;
542 }
543 if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
544 needBlendContainer = true;
545 }
546
547 nsRect bgRect =
548 GetRectRelativeToSelf() + aBuilder->ToReferenceFrame(this);
549
550 const ActiveScrolledRoot* thisItemASR = asr;
551 nsDisplayList thisItemList;
552 nsDisplayBackgroundImage::InitData bgData =
553 nsDisplayBackgroundImage::GetInitData(aBuilder, this, i, bgRect, bg);
554
555 if (bgData.shouldFixToViewport) {
556 auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData();
557 nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
558 aBuilder, this, aBuilder->GetVisibleRect(),
559 aBuilder->GetDirtyRect());
560
561 DisplayListClipState::AutoSaveRestore clipState(aBuilder);
562 nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
563 aBuilder);
564 if (displayData) {
565 nsPoint offset =
566 GetOffsetTo(PresContext()->GetPresShell()->GetRootFrame());
567 aBuilder->SetVisibleRect(displayData->mVisibleRect + offset);
568 aBuilder->SetDirtyRect(displayData->mDirtyRect + offset);
569
570 clipState.SetClipChainForContainingBlockDescendants(
571 displayData->mContainingBlockClipChain);
572 asrSetter.SetCurrentActiveScrolledRoot(
573 displayData->mContainingBlockActiveScrolledRoot);
574 thisItemASR = displayData->mContainingBlockActiveScrolledRoot;
575 }
576 nsDisplayCanvasBackgroundImage* bgItem = nullptr;
577 {
578 DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder);
579 bgImageClip.Clear();
580 bgItem = MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
581 aBuilder, this, /* aIndex = */ i, bgData);
582 if (bgItem) {
583 bgItem->SetDependentFrame(aBuilder, dependentFrame);
584 }
585 }
586 if (bgItem) {
587 thisItemList.AppendToTop(
588 nsDisplayFixedPosition::CreateForFixedBackground(
589 aBuilder, this, nullptr, bgItem, i));
590 }
591
592 } else {
593 nsDisplayCanvasBackgroundImage* bgItem =
594 MakeDisplayItemWithIndex<nsDisplayCanvasBackgroundImage>(
595 aBuilder, this, /* aIndex = */ i, bgData);
596 if (bgItem) {
597 bgItem->SetDependentFrame(aBuilder, dependentFrame);
598 thisItemList.AppendToTop(bgItem);
599 }
600 }
601
602 if (layers.mLayers[i].mBlendMode != StyleBlend::Normal) {
603 DisplayListClipState::AutoSaveRestore blendClip(aBuilder);
604 thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>(
605 aBuilder, this, i + 1, &thisItemList, layers.mLayers[i].mBlendMode,
606 thisItemASR, true);
607 }
608 aLists.BorderBackground()->AppendToTop(&thisItemList);
609 }
610
611 bool hasFixedBottomLayer =
612 layers.mImageCount > 0 &&
613 layers.mLayers[0].mAttachment == StyleImageLayerAttachment::Fixed;
614
615 if (!hasFixedBottomLayer || needBlendContainer) {
616 // Put a scrolled background color item in place, at the bottom of the
617 // list. The color of this item will be filled in during
618 // PresShell::AddCanvasBackgroundColorItem.
619 // Do not add this item if there's a fixed background image at the bottom
620 // (unless we have to, for correct blending); with a fixed background,
621 // it's better to allow the fixed background image to combine itself with
622 // a non-scrolled background color directly underneath, rather than
623 // interleaving the two with a scrolled background color.
624 // PresShell::AddCanvasBackgroundColorItem makes sure there always is a
625 // non-scrolled background color item at the bottom.
626 aLists.BorderBackground()
627 ->AppendNewToBottom<nsDisplayCanvasBackgroundColor>(aBuilder, this);
628 }
629
630 if (needBlendContainer) {
631 const ActiveScrolledRoot* containerASR = contASRTracker.GetContainerASR();
632 DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder);
633 aLists.BorderBackground()->AppendToTop(
634 nsDisplayBlendContainer::CreateForBackgroundBlendMode(
635 aBuilder, this, nullptr, aLists.BorderBackground(),
636 containerASR));
637 }
638 }
639
640 for (nsIFrame* kid : PrincipalChildList()) {
641 // Put our child into its own pseudo-stack.
642 BuildDisplayListForChild(aBuilder, kid, aLists);
643 }
644
645 #ifdef DEBUG_CANVAS_FOCUS
646 nsCOMPtr<nsIContent> focusContent;
647 aPresContext->EventStateManager()->GetFocusedContent(
648 getter_AddRefs(focusContent));
649
650 bool hasFocus = false;
651 nsCOMPtr<nsISupports> container;
652 aPresContext->GetContainer(getter_AddRefs(container));
653 nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(container));
654 if (docShell) {
655 docShell->GetHasFocus(&hasFocus);
656 nsRect dirty = aBuilder->GetDirtyRect();
657 printf("%p - nsCanvasFrame::Paint R:%d,%d,%d,%d DR: %d,%d,%d,%d\n", this,
658 mRect.x, mRect.y, mRect.width, mRect.height, dirty.x, dirty.y,
659 dirty.width, dirty.height);
660 }
661 printf("%p - Focus: %s c: %p DoPaint:%s\n", docShell.get(),
662 hasFocus ? "Y" : "N", focusContent.get(), mDoPaintFocus ? "Y" : "N");
663 #endif
664
665 if (!mDoPaintFocus) return;
666 // Only paint the focus if we're visible
667 if (!StyleVisibility()->IsVisible()) return;
668
669 aLists.Outlines()->AppendNewToTop<nsDisplayCanvasFocus>(aBuilder, this);
670 }
671
PaintFocus(DrawTarget * aDrawTarget,nsPoint aPt)672 void nsCanvasFrame::PaintFocus(DrawTarget* aDrawTarget, nsPoint aPt) {
673 nsRect focusRect(aPt, GetSize());
674
675 nsIScrollableFrame* scrollableFrame = do_QueryFrame(GetParent());
676 if (scrollableFrame) {
677 nsRect portRect = scrollableFrame->GetScrollPortRect();
678 focusRect.width = portRect.width;
679 focusRect.height = portRect.height;
680 focusRect.MoveBy(scrollableFrame->GetScrollPosition());
681 }
682
683 // XXX use the root frame foreground color, but should we find BODY frame
684 // for HTML documents?
685 nsIFrame* root = mFrames.FirstChild();
686 const auto* text = root ? root->StyleText() : StyleText();
687 nsCSSRendering::PaintFocus(PresContext(), aDrawTarget, focusRect,
688 text->mColor.ToColor());
689 }
690
691 /* virtual */
GetMinISize(gfxContext * aRenderingContext)692 nscoord nsCanvasFrame::GetMinISize(gfxContext* aRenderingContext) {
693 nscoord result;
694 DISPLAY_MIN_INLINE_SIZE(this, result);
695 if (mFrames.IsEmpty())
696 result = 0;
697 else
698 result = mFrames.FirstChild()->GetMinISize(aRenderingContext);
699 return result;
700 }
701
702 /* virtual */
GetPrefISize(gfxContext * aRenderingContext)703 nscoord nsCanvasFrame::GetPrefISize(gfxContext* aRenderingContext) {
704 nscoord result;
705 DISPLAY_PREF_INLINE_SIZE(this, result);
706 if (mFrames.IsEmpty())
707 result = 0;
708 else
709 result = mFrames.FirstChild()->GetPrefISize(aRenderingContext);
710 return result;
711 }
712
Reflow(nsPresContext * aPresContext,ReflowOutput & aDesiredSize,const ReflowInput & aReflowInput,nsReflowStatus & aStatus)713 void nsCanvasFrame::Reflow(nsPresContext* aPresContext,
714 ReflowOutput& aDesiredSize,
715 const ReflowInput& aReflowInput,
716 nsReflowStatus& aStatus) {
717 MarkInReflow();
718 DO_GLOBAL_REFLOW_COUNT("nsCanvasFrame");
719 DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
720 MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
721 NS_FRAME_TRACE_REFLOW_IN("nsCanvasFrame::Reflow");
722
723 nsCanvasFrame* prevCanvasFrame = static_cast<nsCanvasFrame*>(GetPrevInFlow());
724 if (prevCanvasFrame) {
725 AutoFrameListPtr overflow(aPresContext,
726 prevCanvasFrame->StealOverflowFrames());
727 if (overflow) {
728 NS_ASSERTION(overflow->OnlyChild(),
729 "must have doc root as canvas frame's only child");
730 nsContainerFrame::ReparentFrameViewList(*overflow, prevCanvasFrame, this);
731 // Prepend overflow to the our child list. There may already be
732 // children placeholders for fixed-pos elements, which don't get
733 // reflowed but must not be lost until the canvas frame is destroyed.
734 mFrames.InsertFrames(this, nullptr, *overflow);
735 }
736 }
737
738 // Set our size up front, since some parts of reflow depend on it
739 // being already set. Note that the computed height may be
740 // unconstrained; that's ok. Consumers should watch out for that.
741 SetSize(nsSize(aReflowInput.ComputedWidth(), aReflowInput.ComputedHeight()));
742
743 // Reflow our children. Typically, we only have one child - the root
744 // element's frame or a placeholder for that frame, if the root element
745 // is abs-pos or fixed-pos. Note that this child might be missing though
746 // if that frame was Complete in one of our earlier continuations. This
747 // happens when we create additional pages purely to make room for painting
748 // overflow (painted by BuildPreviousPageOverflow in nsPageFrame.cpp).
749 // We may have additional children which are placeholders for continuations
750 // of fixed-pos content, see nsCSSFrameConstructor::ReplicateFixedFrames.
751 // We may also have a nsPopupSetFrame child (mPopupSetFrame).
752 const WritingMode wm = aReflowInput.GetWritingMode();
753 aDesiredSize.SetSize(wm, aReflowInput.ComputedSize());
754 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE) {
755 // Set the block-size to zero for now in case we don't have any non-
756 // placeholder children that would update the size in the loop below.
757 aDesiredSize.BSize(wm) = nscoord(0);
758 }
759 aDesiredSize.SetOverflowAreasToDesiredBounds();
760 nsIFrame* nextKid = nullptr;
761 for (auto* kidFrame = mFrames.FirstChild(); kidFrame; kidFrame = nextKid) {
762 nextKid = kidFrame->GetNextSibling();
763 if (kidFrame == mPopupSetFrame) {
764 // This child is handled separately after this loop.
765 continue;
766 }
767
768 ReflowOutput kidDesiredSize(aReflowInput);
769 bool kidDirty = kidFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY);
770 WritingMode kidWM = kidFrame->GetWritingMode();
771 auto availableSize = aReflowInput.AvailableSize(kidWM);
772 nscoord bOffset = 0;
773 nscoord canvasBSizeSum = 0;
774 if (prevCanvasFrame && availableSize.BSize(kidWM) != NS_UNCONSTRAINEDSIZE &&
775 !kidFrame->IsPlaceholderFrame() &&
776 StaticPrefs::layout_display_list_improve_fragmentation()) {
777 for (auto* pif = prevCanvasFrame; pif;
778 pif = static_cast<nsCanvasFrame*>(pif->GetPrevInFlow())) {
779 canvasBSizeSum += pif->BSize(kidWM);
780 auto* pifChild = pif->PrincipalChildList().FirstChild();
781 if (pifChild) {
782 nscoord layoutOverflow = pifChild->BSize(kidWM) - canvasBSizeSum;
783 // A negative value means that the :root frame does not fill
784 // the canvas. In this case we can't determine the offset exactly
785 // so we use the end edge of the scrollable overflow as the offset
786 // instead. This will likely push down the content below where it
787 // should be placed, creating a gap. That's preferred over making
788 // content overlap which would otherwise occur.
789 // See layout/reftests/pagination/inline-block-slice-7.html for an
790 // example of this.
791 if (layoutOverflow < 0) {
792 LogicalRect so(kidWM, pifChild->ScrollableOverflowRect(),
793 pifChild->GetSize());
794 layoutOverflow = so.BEnd(kidWM) - canvasBSizeSum;
795 }
796 bOffset = std::max(bOffset, layoutOverflow);
797 }
798 }
799 availableSize.BSize(kidWM) -= bOffset;
800 }
801
802 if (MOZ_LIKELY(availableSize.BSize(kidWM) > 0)) {
803 ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
804 availableSize);
805
806 if (aReflowInput.IsBResizeForWM(kidReflowInput.GetWritingMode()) &&
807 kidFrame->HasAnyStateBits(NS_FRAME_CONTAINS_RELATIVE_BSIZE)) {
808 // Tell our kid it's being block-dir resized too. Bit of a
809 // hack for framesets.
810 kidReflowInput.SetBResize(true);
811 }
812
813 nsSize containerSize = aReflowInput.ComputedPhysicalSize();
814 LogicalMargin margin = kidReflowInput.ComputedLogicalMargin(kidWM);
815 LogicalPoint kidPt(kidWM, margin.IStart(kidWM), margin.BStart(kidWM));
816 (kidWM.IsOrthogonalTo(wm) ? kidPt.I(kidWM) : kidPt.B(kidWM)) += bOffset;
817
818 nsReflowStatus kidStatus;
819 ReflowChild(kidFrame, aPresContext, kidDesiredSize, kidReflowInput, kidWM,
820 kidPt, containerSize, ReflowChildFlags::Default, kidStatus);
821
822 FinishReflowChild(kidFrame, aPresContext, kidDesiredSize, &kidReflowInput,
823 kidWM, kidPt, containerSize,
824 ReflowChildFlags::ApplyRelativePositioning);
825
826 if (!kidStatus.IsFullyComplete()) {
827 nsIFrame* nextFrame = kidFrame->GetNextInFlow();
828 NS_ASSERTION(nextFrame || kidStatus.NextInFlowNeedsReflow(),
829 "If it's incomplete and has no nif yet, it must flag a "
830 "nif reflow.");
831 if (!nextFrame) {
832 nextFrame = aPresContext->PresShell()
833 ->FrameConstructor()
834 ->CreateContinuingFrame(kidFrame, this);
835 SetOverflowFrames(nsFrameList(nextFrame, nextFrame));
836 // Root overflow containers will be normal children of
837 // the canvas frame, but that's ok because there
838 // aren't any other frames we need to isolate them from
839 // during reflow.
840 }
841 if (kidStatus.IsOverflowIncomplete()) {
842 nextFrame->AddStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER);
843 }
844 }
845 aStatus.MergeCompletionStatusFrom(kidStatus);
846
847 // If the child frame was just inserted, then we're responsible for making
848 // sure it repaints
849 if (kidDirty) {
850 // But we have a new child, which will affect our background, so
851 // invalidate our whole rect.
852 // Note: Even though we request to be sized to our child's size, our
853 // scroll frame ensures that we are always the size of the viewport.
854 // Also note: GetPosition() on a CanvasFrame is always going to return
855 // (0, 0). We only want to invalidate GetRect() since Get*OverflowRect()
856 // could also include overflow to our top and left (out of the viewport)
857 // which doesn't need to be painted.
858 nsIFrame* viewport = PresContext()->GetPresShell()->GetRootFrame();
859 viewport->InvalidateFrame();
860 }
861
862 // Return our desired size. Normally it's what we're told, but
863 // sometimes we can be given an unconstrained block-size (when a window
864 // is sizing-to-content), and we should compute our desired block-size.
865 if (aReflowInput.ComputedBSize() == NS_UNCONSTRAINEDSIZE &&
866 !kidFrame->IsPlaceholderFrame()) {
867 LogicalSize finalSize = aReflowInput.ComputedSize();
868 finalSize.BSize(wm) =
869 kidFrame->GetLogicalSize(wm).BSize(wm) +
870 kidReflowInput.ComputedLogicalMargin(wm).BStartEnd(wm);
871 aDesiredSize.SetSize(wm, finalSize);
872 aDesiredSize.SetOverflowAreasToDesiredBounds();
873 }
874 aDesiredSize.mOverflowAreas.UnionWith(kidDesiredSize.mOverflowAreas +
875 kidFrame->GetPosition());
876 } else if (kidFrame->IsPlaceholderFrame()) {
877 // Placeholders always fit even if there's no available block-size left.
878 } else {
879 // This only occurs in paginated mode. There is no available space on
880 // this page due to reserving space for overflow from a previous page,
881 // so we push our child to the next page. Note that we can have some
882 // placeholders for fixed pos. frames in mFrames too, so we need to be
883 // careful to only push `kidFrame`.
884 mFrames.RemoveFrame(kidFrame);
885 SetOverflowFrames(nsFrameList(kidFrame, kidFrame));
886 aStatus.SetIncomplete();
887 }
888 }
889
890 if (prevCanvasFrame) {
891 ReflowOverflowContainerChildren(aPresContext, aReflowInput,
892 aDesiredSize.mOverflowAreas,
893 ReflowChildFlags::Default, aStatus);
894 }
895
896 if (mPopupSetFrame) {
897 MOZ_ASSERT(mFrames.ContainsFrame(mPopupSetFrame),
898 "Only normal flow supported.");
899 nsReflowStatus popupStatus;
900 ReflowOutput popupDesiredSize(aReflowInput.GetWritingMode());
901 WritingMode wm = mPopupSetFrame->GetWritingMode();
902 LogicalSize availSize = aReflowInput.ComputedSize(wm);
903 availSize.BSize(wm) = NS_UNCONSTRAINEDSIZE;
904 ReflowInput popupReflowInput(aPresContext, aReflowInput, mPopupSetFrame,
905 availSize);
906 ReflowChild(mPopupSetFrame, aPresContext, popupDesiredSize,
907 popupReflowInput, 0, 0, ReflowChildFlags::NoMoveFrame,
908 popupStatus);
909 FinishReflowChild(mPopupSetFrame, aPresContext, popupDesiredSize,
910 &popupReflowInput, 0, 0, ReflowChildFlags::NoMoveFrame);
911 }
912
913 FinishReflowWithAbsoluteFrames(aPresContext, aDesiredSize, aReflowInput,
914 aStatus);
915
916 NS_FRAME_TRACE_REFLOW_OUT("nsCanvasFrame::Reflow", aStatus);
917 NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
918 }
919
GetContentForEvent(WidgetEvent * aEvent,nsIContent ** aContent)920 nsresult nsCanvasFrame::GetContentForEvent(WidgetEvent* aEvent,
921 nsIContent** aContent) {
922 NS_ENSURE_ARG_POINTER(aContent);
923 nsresult rv = nsIFrame::GetContentForEvent(aEvent, aContent);
924 if (NS_FAILED(rv) || !*aContent) {
925 nsIFrame* kid = mFrames.FirstChild();
926 if (kid) {
927 rv = kid->GetContentForEvent(aEvent, aContent);
928 }
929 }
930
931 return rv;
932 }
933
934 #ifdef DEBUG_FRAME_DUMP
GetFrameName(nsAString & aResult) const935 nsresult nsCanvasFrame::GetFrameName(nsAString& aResult) const {
936 return MakeFrameName(u"Canvas"_ns, aResult);
937 }
938 #endif
939