From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- layout/painting/nsDisplayList.cpp | 8655 +++++++++++++++++++++++++++++++++++++ 1 file changed, 8655 insertions(+) create mode 100644 layout/painting/nsDisplayList.cpp (limited to 'layout/painting/nsDisplayList.cpp') diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp new file mode 100644 index 0000000000..46f8b05f82 --- /dev/null +++ b/layout/painting/nsDisplayList.cpp @@ -0,0 +1,8655 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +/* + * structures that represent things to be painted (ordered in z-order), + * used during painting and hit testing + */ + +#include "nsDisplayList.h" + +#include +#include +#include + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/Likely.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/HTMLCanvasElement.h" +#include "mozilla/dom/RemoteBrowser.h" +#include "mozilla/dom/Selection.h" +#include "mozilla/dom/ServiceWorkerRegistrar.h" +#include "mozilla/dom/ServiceWorkerRegistration.h" +#include "mozilla/dom/SVGElement.h" +#include "mozilla/dom/TouchEvent.h" +#include "mozilla/dom/PerformanceMainThread.h" +#include "mozilla/gfx/2D.h" +#include "mozilla/PresShell.h" +#include "mozilla/ShapeUtils.h" +#include "mozilla/StaticPrefs_apz.h" +#include "mozilla/StaticPrefs_gfx.h" +#include "mozilla/StaticPrefs_layers.h" +#include "mozilla/StaticPrefs_layout.h" +#include "mozilla/StaticPrefs_print.h" +#include "mozilla/SVGIntegrationUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/ViewportUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.h" +#include "nsCaseTreatment.h" +#include "nsRefreshDriver.h" +#include "nsRegion.h" +#include "nsStyleStructInlines.h" +#include "nsStyleTransformMatrix.h" +#include "nsTransitionManager.h" +#include "gfxMatrix.h" +#include "nsLayoutUtils.h" +#include "nsIScrollableFrame.h" +#include "nsIFrameInlines.h" +#include "nsStyleConsts.h" +#include "BorderConsts.h" +#include "mozilla/MathAlgorithms.h" + +#include "imgIContainer.h" +#include "nsImageFrame.h" +#include "nsSubDocumentFrame.h" +#include "nsViewManager.h" +#include "ImageContainer.h" +#include "nsCanvasFrame.h" +#include "nsSubDocumentFrame.h" +#include "StickyScrollContainer.h" +#include "mozilla/AnimationPerformanceWarning.h" +#include "mozilla/AnimationUtils.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/EffectCompositor.h" +#include "mozilla/EffectSet.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/HashTable.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/Preferences.h" +#include "mozilla/ProfilerLabels.h" +#include "mozilla/ProfilerMarkers.h" +#include "mozilla/StyleAnimationValue.h" +#include "mozilla/ServoBindings.h" +#include "mozilla/SVGClipPathFrame.h" +#include "mozilla/SVGMaskFrame.h" +#include "mozilla/SVGObserverUtils.h" +#include "mozilla/Telemetry.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/ViewportFrame.h" +#include "mozilla/gfx/gfxVars.h" +#include "ActiveLayerTracker.h" +#include "nsEscape.h" +#include "nsPrintfCString.h" +#include "UnitTransforms.h" +#include "LayerAnimationInfo.h" +#include "mozilla/EventStateManager.h" +#include "nsCaret.h" +#include "nsDOMTokenList.h" +#include "nsCSSProps.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" +#include "nsTextFrame.h" +#include "nsTextPaintStyle.h" +#include "nsSliderFrame.h" +#include "nsFocusManager.h" +#include "TextDrawTarget.h" +#include "mozilla/layers/AnimationHelper.h" +#include "mozilla/layers/CompositorThread.h" +#include "mozilla/layers/InputAPZContext.h" +#include "mozilla/layers/RenderRootStateManager.h" +#include "mozilla/layers/StackingContextHelper.h" +#include "mozilla/layers/TreeTraversal.h" +#include "mozilla/layers/WebRenderBridgeChild.h" +#include "mozilla/layers/WebRenderLayerManager.h" +#include "mozilla/layers/WebRenderMessages.h" +#include "mozilla/layers/WebRenderScrollData.h" + +namespace mozilla { + +using namespace dom; +using namespace gfx; +using namespace layout; +using namespace layers; +using namespace image; + +LazyLogModule sContentDisplayListLog("dl.content"); +LazyLogModule sParentDisplayListLog("dl.parent"); + +LazyLogModule& GetLoggerByProcess() { + return XRE_IsContentProcess() ? sContentDisplayListLog + : sParentDisplayListLog; +} + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem) { + for (nsDisplayItem* i : aItem->Frame()->DisplayItems()) { + if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->IsPreProcessedItem() || i->IsPreProcessed()) { + continue; + } + MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!"); + } + } +} +#endif + +bool ShouldBuildItemForEvents(const DisplayItemType aType) { + return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO || + (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER); +} + +static bool ItemTypeSupportsHitTesting(const DisplayItemType aType) { + switch (aType) { + case DisplayItemType::TYPE_BACKGROUND: + case DisplayItemType::TYPE_BACKGROUND_COLOR: + case DisplayItemType::TYPE_THEMED_BACKGROUND: + return true; + default: + return false; + } +} + +void InitializeHitTestInfo(nsDisplayListBuilder* aBuilder, + nsPaintedDisplayItem* aItem, + const DisplayItemType aType) { + if (ItemTypeSupportsHitTesting(aType)) { + aItem->InitializeHitTestInfo(aBuilder); + } +} + +/* static */ +already_AddRefed ActiveScrolledRoot::CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained) { + nsIFrame* f = do_QueryFrame(aScrollableFrame); + + RefPtr asr; + if (aIsRetained) { + asr = f->GetProperty(ActiveScrolledRootCache()); + } + + if (!asr) { + asr = new ActiveScrolledRoot(); + + if (aIsRetained) { + RefPtr ref = asr; + f->SetProperty(ActiveScrolledRootCache(), ref.forget().take()); + } + } + asr->mParent = aParent; + asr->mScrollableFrame = aScrollableFrame; + asr->mDepth = aParent ? aParent->mDepth + 1 : 1; + asr->mRetained = aIsRetained; + + return asr.forget(); +} + +/* static */ +bool ActiveScrolledRoot::IsAncestor(const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant) { + if (!aAncestor) { + // nullptr is the root + return true; + } + if (Depth(aAncestor) > Depth(aDescendant)) { + return false; + } + const ActiveScrolledRoot* asr = aDescendant; + while (asr) { + if (asr == aAncestor) { + return true; + } + asr = asr->mParent; + } + return false; +} + +/* static */ +bool ActiveScrolledRoot::IsProperAncestor( + const ActiveScrolledRoot* aAncestor, + const ActiveScrolledRoot* aDescendant) { + return aAncestor != aDescendant && IsAncestor(aAncestor, aDescendant); +} + +/* static */ +nsCString ActiveScrolledRoot::ToString( + const ActiveScrolledRoot* aActiveScrolledRoot) { + nsAutoCString str; + for (const auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) { + str.AppendPrintf("<0x%p>", asr->mScrollableFrame); + if (asr->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +ScrollableLayerGuid::ViewID ActiveScrolledRoot::ComputeViewId() const { + nsIContent* content = mScrollableFrame->GetScrolledFrame()->GetContent(); + return nsLayoutUtils::FindOrCreateIDFor(content); +} + +ActiveScrolledRoot::~ActiveScrolledRoot() { + if (mScrollableFrame && mRetained) { + nsIFrame* f = do_QueryFrame(mScrollableFrame); + f->RemoveProperty(ActiveScrolledRootCache()); + } +} + +static uint64_t AddAnimationsForWebRender( + nsDisplayItem* aItem, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const Maybe& aPosition = Nothing()) { + auto* effects = EffectSet::GetForFrame(aItem->Frame(), aItem->GetType()); + if (!effects || effects->IsEmpty()) { + // If there is no animation on the nsIFrame, that means + // 1) we've never created any animations on this frame or + // 2) the frame was reconstruced or + // 3) all animations on the frame have finished + // in such cases we don't need do anything here. + // + // Even if there is a WebRenderAnimationData for the display item type on + // this frame, it's going to be discarded since it's not marked as being + // used. + return 0; + } + + RefPtr animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData(aItem); + AnimationInfo& animationInfo = animationData->GetAnimationInfo(); + nsIFrame* frame = aItem->Frame(); + animationInfo.AddAnimationsForDisplayItem( + frame, aDisplayListBuilder, aItem, aItem->GetType(), + aManager->LayerManager(), aPosition); + + // Note that animationsId can be 0 (uninitialized in AnimationInfo) if there + // are no active animations. + uint64_t animationsId = animationInfo.GetCompositorAnimationsId(); + if (!animationInfo.GetAnimations().IsEmpty()) { + OpAddCompositorAnimations anim( + CompositorAnimations(animationInfo.GetAnimations(), animationsId)); + aManager->WrBridge()->AddWebRenderParentCommand(anim); + aManager->AddActiveCompositorAnimationId(animationsId); + } else if (animationsId) { + aManager->AddCompositorAnimationsIdForDiscard(animationsId); + animationsId = 0; + } + + return animationsId; +} + +static bool GenerateAndPushTextMask(nsIFrame* aFrame, gfxContext* aContext, + const nsRect& aFillRect, + nsDisplayListBuilder* aBuilder) { + if (aBuilder->IsForGenerateGlyphMask()) { + return false; + } + + SVGObserverUtils::GetAndObserveBackgroundClip(aFrame); + + // The main function of enabling background-clip:text property value. + // When a nsDisplayBackgroundImage detects "text" bg-clip style, it will call + // this function to + // 1. Generate a mask by all descendant text frames + // 2. Push the generated mask into aContext. + + gfxContext* sourceCtx = aContext; + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + aFillRect, aFrame->PresContext()->AppUnitsPerDevPixel()); + + // Create a mask surface. + RefPtr sourceTarget = sourceCtx->GetDrawTarget(); + RefPtr maskDT = sourceTarget->CreateClippedDrawTarget( + bounds.ToUnknownRect(), SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return false; + } + gfxContext maskCtx(maskDT, /* aPreserveTransform */ true); + maskCtx.Multiply(Matrix::Translation(bounds.TopLeft().ToUnknownPoint())); + + // Shade text shape into mask A8 surface. + nsLayoutUtils::PaintFrame( + &maskCtx, aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), + NS_RGB(255, 255, 255), nsDisplayListBuilderMode::GenerateGlyph); + + // Push the generated mask into aContext, so that the caller can pop and + // blend with it. + + Matrix currentMatrix = sourceCtx->CurrentMatrix(); + Matrix invCurrentMatrix = currentMatrix; + invCurrentMatrix.Invert(); + + RefPtr maskSurface = maskDT->Snapshot(); + sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, + maskSurface, invCurrentMatrix); + + return true; +} + +nsDisplayWrapper* nsDisplayWrapList::CreateShallowCopy( + nsDisplayListBuilder* aBuilder) { + const nsDisplayWrapList* wrappedItem = AsDisplayWrapList(); + MOZ_ASSERT(wrappedItem); + + // Create a new nsDisplayWrapList using a copy-constructor. This is done + // to preserve the information about bounds. + nsDisplayWrapper* wrapper = + new (aBuilder) nsDisplayWrapper(aBuilder, *wrappedItem); + wrapper->SetType(nsDisplayWrapper::ItemType()); + MOZ_ASSERT(wrapper); + + // Set the display list pointer of the new wrapper item to the display list + // of the wrapped item. + wrapper->mListPtr = wrappedItem->mListPtr; + return wrapper; +} + +nsDisplayWrapList* nsDisplayListBuilder::MergeItems( + nsTArray& aItems) { + // For merging, we create a temporary item by cloning the last item of the + // mergeable items list. This ensures that the temporary item will have the + // correct frame and bounds. + nsDisplayWrapList* last = aItems.PopLastElement()->AsDisplayWrapList(); + MOZ_ASSERT(last); + nsDisplayWrapList* merged = last->Clone(this); + MOZ_ASSERT(merged); + AddTemporaryItem(merged); + + // Create nsDisplayWrappers that point to the internal display lists of the + // items we are merging. These nsDisplayWrappers are added to the display list + // of the temporary item. + for (nsDisplayItem* item : aItems) { + MOZ_ASSERT(item); + MOZ_ASSERT(merged->CanMerge(item)); + merged->Merge(item); + MOZ_ASSERT(item->AsDisplayWrapList()); + merged->GetChildren()->AppendToTop( + static_cast(item)->CreateShallowCopy(this)); + } + + merged->GetChildren()->AppendToTop(last->CreateShallowCopy(this)); + + return merged; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + SetCurrentActiveScrolledRoot( + const ActiveScrolledRoot* aActiveScrolledRoot) { + MOZ_ASSERT(!mUsed); + + // Set the builder's mCurrentActiveScrolledRoot. + mBuilder->mCurrentActiveScrolledRoot = aActiveScrolledRoot; + + // We also need to adjust the builder's mCurrentContainerASR. + // mCurrentContainerASR needs to be an ASR that all the container's + // contents have finite bounds with respect to. If aActiveScrolledRoot + // is an ancestor ASR of mCurrentContainerASR, that means we need to + // set mCurrentContainerASR to aActiveScrolledRoot, because otherwise + // the items that will be created with aActiveScrolledRoot wouldn't + // have finite bounds with respect to mCurrentContainerASR. There's one + // exception, in the case where there's a content clip on the builder + // that is scrolled by a descendant ASR of aActiveScrolledRoot. This + // content clip will clip all items that are created while this + // AutoCurrentActiveScrolledRootSetter exists. This means that the items + // created during our lifetime will have finite bounds with respect to + // the content clip's ASR, even if the items' actual ASR is an ancestor + // of that. And it also means that mCurrentContainerASR only needs to be + // set to the content clip's ASR and not all the way to aActiveScrolledRoot. + // This case is tested by fixed-pos-scrolled-clip-opacity-layerize.html + // and fixed-pos-scrolled-clip-opacity-inside-layerize.html. + + // finiteBoundsASR is the leafmost ASR that all items created during + // object's lifetime have finite bounds with respect to. + const ActiveScrolledRoot* finiteBoundsASR = + ActiveScrolledRoot::PickDescendant(mContentClipASR, aActiveScrolledRoot); + + // mCurrentContainerASR is adjusted so that it's still an ancestor of + // finiteBoundsASR. + mBuilder->mCurrentContainerASR = ActiveScrolledRoot::PickAncestor( + mBuilder->mCurrentContainerASR, finiteBoundsASR); + + // If we are entering out-of-flow content inside a CSS filter, mark + // scroll frames wrt. which the content is fixed as containing such content. + if (mBuilder->mFilterASR && ActiveScrolledRoot::IsAncestor( + aActiveScrolledRoot, mBuilder->mFilterASR)) { + for (const ActiveScrolledRoot* asr = mBuilder->mFilterASR; + asr && asr != aActiveScrolledRoot; asr = asr->mParent) { + asr->mScrollableFrame->SetHasOutOfFlowContentInsideFilter(); + } + } + + mUsed = true; +} + +void nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter:: + InsertScrollFrame(nsIScrollableFrame* aScrollableFrame) { + MOZ_ASSERT(!mUsed); + size_t descendantsEndIndex = mBuilder->mActiveScrolledRoots.Length(); + const ActiveScrolledRoot* parentASR = mBuilder->mCurrentActiveScrolledRoot; + const ActiveScrolledRoot* asr = + mBuilder->AllocateActiveScrolledRoot(parentASR, aScrollableFrame); + mBuilder->mCurrentActiveScrolledRoot = asr; + + // All child ASRs of parentASR that were created while this + // AutoCurrentActiveScrolledRootSetter object was on the stack belong to us + // now. Reparent them to asr. + for (size_t i = mDescendantsStartIndex; i < descendantsEndIndex; i++) { + ActiveScrolledRoot* descendantASR = mBuilder->mActiveScrolledRoots[i]; + if (ActiveScrolledRoot::IsAncestor(parentASR, descendantASR)) { + descendantASR->IncrementDepth(); + if (descendantASR->mParent == parentASR) { + descendantASR->mParent = asr; + } + } + } + + mUsed = true; +} + +nsDisplayListBuilder::AutoContainerASRTracker::AutoContainerASRTracker( + nsDisplayListBuilder* aBuilder) + : mBuilder(aBuilder), mSavedContainerASR(aBuilder->mCurrentContainerASR) { + mBuilder->mCurrentContainerASR = mBuilder->mCurrentActiveScrolledRoot; +} + +nsPresContext* nsDisplayListBuilder::CurrentPresContext() { + return CurrentPresShellState()->mPresShell->GetPresContext(); +} + +/* static */ +nsRect nsDisplayListBuilder::OutOfFlowDisplayData::ComputeVisibleRectForFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + nsRect* aOutDirtyRect) { + nsRect visible = aVisibleRect; + nsRect dirtyRectRelativeToDirtyFrame = aDirtyRect; + + bool inPartialUpdate = + aBuilder->IsRetainingDisplayList() && aBuilder->IsPartialUpdate(); + if (StaticPrefs::apz_allow_zooming() && + DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame) && + aBuilder->IsPaintingToWindow() && !inPartialUpdate) { + dirtyRectRelativeToDirtyFrame = + nsRect(nsPoint(0, 0), aFrame->GetParent()->GetSize()); + + // If there's a visual viewport size set, restrict the amount of the + // fixed-position element we paint to the visual viewport. (In general + // the fixed-position element can be as large as the layout viewport, + // which at a high zoom level can cause us to paint too large of an + // area.) + PresShell* presShell = aFrame->PresShell(); + if (presShell->IsVisualViewportSizeSet()) { + dirtyRectRelativeToDirtyFrame = + nsRect(presShell->GetVisualViewportOffsetRelativeToLayoutViewport(), + presShell->GetVisualViewportSize()); + // But if we have a displayport, expand it to the displayport, so + // that async-scrolling the visual viewport within the layout viewport + // will not checkerboard. + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsRect displayport; + // Note that the displayport here is already in the right coordinate + // space: it's relative to the scroll port (= layout viewport), but + // covers the visual viewport with some margins around it, which is + // exactly what we want. + if (DisplayPortUtils::GetDisplayPort( + rootScrollFrame->GetContent(), &displayport, + DisplayPortOptions().With(ContentGeometryType::Fixed))) { + dirtyRectRelativeToDirtyFrame = displayport; + } + } + } + visible = dirtyRectRelativeToDirtyFrame; + if (StaticPrefs::apz_test_logging_enabled() && + presShell->GetDocument()->IsContentDocument()) { + nsLayoutUtils::LogAdditionalTestData( + aBuilder, "fixedPosDisplayport", + ToString(CSSSize::FromAppUnits(visible))); + } + } + + *aOutDirtyRect = dirtyRectRelativeToDirtyFrame - aFrame->GetPosition(); + visible -= aFrame->GetPosition(); + + nsRect overflowRect = aFrame->InkOverflowRect(); + + if (aFrame->IsTransformed() && EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + /** + * Add a fuzz factor to the overflow rectangle so that elements only + * just out of view are pulled into the display list, so they can be + * prerendered if necessary. + */ + overflowRect.Inflate(nsPresContext::CSSPixelsToAppUnits(32)); + } + + visible.IntersectRect(visible, overflowRect); + aOutDirtyRect->IntersectRect(*aOutDirtyRect, overflowRect); + + return visible; +} + +nsDisplayListBuilder::Linkifier::Linkifier(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : mList(aList) { + // Find the element that we need to check for link-ness, bailing out if + // we can't find one. + Element* elem = Element::FromNodeOrNull(aFrame->GetContent()); + if (!elem) { + return; + } + + // If the element has an id and/or name attribute, generate a destination + // for possible internal linking. + auto maybeGenerateDest = [&](const nsAtom* aAttr) { + nsAutoString attrValue; + elem->GetAttr(aAttr, attrValue); + if (!attrValue.IsEmpty()) { + NS_ConvertUTF16toUTF8 dest(attrValue); + // Ensure that we only emit a given destination once, although there may + // be multiple frames associated with a given element; we'll simply use + // the first of them as the target of any links to it. + // XXX(jfkthame) This prevents emitting duplicate destinations *on the + // same page*, but does not prevent duplicates on subsequent pages, as + // each new page is handled by a new temporary DisplayListBuilder. This + // seems to be harmless in practice, though a bit wasteful of space. To + // fix, we need to maintain the set of already-seen destinations globally + // for the print job, rather than attached to the (per-page) builder. + if (aBuilder->mDestinations.EnsureInserted(dest)) { + auto* destination = MakeDisplayItem( + aBuilder, aFrame, dest.get(), aFrame->GetRect().TopLeft()); + mList->AppendToTop(destination); + } + } + }; + + if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled()) { + if (elem->HasID()) { + maybeGenerateDest(nsGkAtoms::id); + } + if (elem->HasName()) { + maybeGenerateDest(nsGkAtoms::name); + } + } + + // Links don't nest, so if the builder already has a destination, no need to + // check for a link element here. + if (!aBuilder->mLinkSpec.IsEmpty()) { + return; + } + + // Check if we have actually found a link. + if (!elem->IsLink()) { + return; + } + + nsCOMPtr uri = elem->GetHrefURI(); + if (!uri) { + return; + } + + // Is it a local (in-page) destination? + bool hasRef, eqExRef; + nsIURI* docURI; + if (StaticPrefs::print_save_as_pdf_internal_destinations_enabled() && + NS_SUCCEEDED(uri->GetHasRef(&hasRef)) && hasRef && + (docURI = aFrame->PresContext()->Document()->GetDocumentURI()) && + NS_SUCCEEDED(uri->EqualsExceptRef(docURI, &eqExRef)) && eqExRef) { + if (NS_FAILED(uri->GetRef(aBuilder->mLinkSpec)) || + aBuilder->mLinkSpec.IsEmpty()) { + return; + } + // The destination name is simply a string; we don't want URL-escaping + // applied to it. + NS_UnescapeURL(aBuilder->mLinkSpec); + // Mark the link spec as being an internal destination + aBuilder->mLinkSpec.Insert('#', 0); + } else { + if (NS_FAILED(uri->GetSpec(aBuilder->mLinkSpec)) || + aBuilder->mLinkSpec.IsEmpty()) { + return; + } + } + + // Record that we need to reset the builder's state on destruction. + mBuilderToReset = aBuilder; +} + +void nsDisplayListBuilder::Linkifier::MaybeAppendLink( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + // Note that we may generate a link here even if the constructor bailed out + // without updating aBuilder->LinkSpec(), because it may have been set by + // an ancestor that was associated with a link element. + if (!aBuilder->mLinkSpec.IsEmpty()) { + auto* link = MakeDisplayItem( + aBuilder, aFrame, aBuilder->mLinkSpec.get(), aFrame->GetRect()); + mList->AppendToTop(link); + } +} + +uint32_t nsDisplayListBuilder::sPaintSequenceNumber(1); + +nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, + bool aBuildCaret, + bool aRetainingDisplayList) + : mReferenceFrame(aReferenceFrame), + mIgnoreScrollFrame(nullptr), + mCurrentActiveScrolledRoot(nullptr), + mCurrentContainerASR(nullptr), + mCurrentFrame(aReferenceFrame), + mCurrentReferenceFrame(aReferenceFrame), + mCaretFrame(nullptr), + mScrollInfoItemsForHoisting(nullptr), + mFirstClipChainToDestroy(nullptr), + mTableBackgroundSet(nullptr), + mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID), + mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mFilterASR(nullptr), + mDirtyRect(-1, -1, -1, -1), + mBuildingExtraPagesForPageNum(0), + mMode(aMode), + mContainsBlendMode(false), + mIsBuildingScrollbar(false), + mCurrentScrollbarWillHaveLayer(false), + mBuildCaret(aBuildCaret), + mRetainingDisplayList(aRetainingDisplayList), + mPartialUpdate(false), + mIgnoreSuppression(false), + mIncludeAllOutOfFlows(false), + mDescendIntoSubdocuments(true), + mSelectedFramesOnly(false), + mAllowMergingAndFlattening(true), + mInTransform(false), + mInEventsOnly(false), + mInFilter(false), + mInPageSequence(false), + mIsInChromePresContext(false), + mSyncDecodeImages(false), + mIsPaintingToWindow(false), + mUseHighQualityScaling(false), + mIsPaintingForWebRender(false), + mIsCompositingCheap(false), + mAncestorHasApzAwareEventHandler(false), + mHaveScrollableDisplayPort(false), + mWindowDraggingAllowed(false), + mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)), + mForceLayerForScrollParent(false), + mContainsNonMinimalDisplayPort(false), + mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)), + mBuildingInvisibleItems(false), + mIsBuilding(false), + mInInvalidSubtree(false), + mDisablePartialUpdates(false), + mPartialBuildFailed(false), + mIsInActiveDocShell(false), + mBuildAsyncZoomContainer(false), + mIsRelativeToLayoutViewport(false), + mUseOverlayScrollbars(false), + mAlwaysLayerizeScrollbars(false) { + MOZ_COUNT_CTOR(nsDisplayListBuilder); + + mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting(); + + ShouldRebuildDisplayListDueToPrefChange(); + + mUseOverlayScrollbars = + !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars); + + mAlwaysLayerizeScrollbars = + StaticPrefs::layout_scrollbars_always_layerize_track(); + + static_assert( + static_cast(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS), + "Check TYPE_MAX should not overflow"); + + mIsReusingStackingContextItems = + mRetainingDisplayList && StaticPrefs::layout_display_list_retain_sc(); +} + +static PresShell* GetFocusedPresShell() { + nsPIDOMWindowOuter* focusedWnd = + nsFocusManager::GetFocusManager()->GetFocusedWindow(); + if (!focusedWnd) { + return nullptr; + } + + nsCOMPtr focusedDocShell = focusedWnd->GetDocShell(); + if (!focusedDocShell) { + return nullptr; + } + + return focusedDocShell->GetPresShell(); +} + +void nsDisplayListBuilder::BeginFrame() { + nsCSSRendering::BeginFrameTreesLocked(); + + mIsPaintingToWindow = false; + mUseHighQualityScaling = false; + mIgnoreSuppression = false; + mInTransform = false; + mInFilter = false; + mSyncDecodeImages = false; + + if (!mBuildCaret) { + return; + } + + RefPtr presShell = GetFocusedPresShell(); + if (presShell) { + RefPtr caret = presShell->GetCaret(); + mCaretFrame = caret->GetPaintGeometry(&mCaretRect); + + // The focused pres shell may not be in the document that we're + // painting, or be in a popup. Check if the display root for + // the caret matches the display root that we're painting, and + // only use it if it matches. + if (mCaretFrame && + nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) != + nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) { + mCaretFrame = nullptr; + } + } +} + +void nsDisplayListBuilder::AddEffectUpdate(dom::RemoteBrowser* aBrowser, + const dom::EffectsInfo& aUpdate) { + dom::EffectsInfo update = aUpdate; + // For printing we create one display item for each page that an iframe + // appears on, the proper visible rect is the union of all the visible rects + // we get from each display item. + nsPresContext* pc = + mReferenceFrame ? mReferenceFrame->PresContext() : nullptr; + if (pc && pc->Type() != nsPresContext::eContext_Galley) { + Maybe existing = mEffectsUpdates.MaybeGet(aBrowser); + if (existing) { + // Only the visible rect should differ, the scales should match. + MOZ_ASSERT(existing->mRasterScale == aUpdate.mRasterScale && + existing->mTransformToAncestorScale == + aUpdate.mTransformToAncestorScale); + if (existing->mVisibleRect) { + if (update.mVisibleRect) { + update.mVisibleRect = + Some(update.mVisibleRect->Union(*existing->mVisibleRect)); + } else { + update.mVisibleRect = existing->mVisibleRect; + } + } + } + } + mEffectsUpdates.InsertOrUpdate(aBrowser, update); +} + +void nsDisplayListBuilder::EndFrame() { + NS_ASSERTION(!mInInvalidSubtree, + "Someone forgot to cleanup mInInvalidSubtree!"); + mCurrentContainerASR = nullptr; + mActiveScrolledRoots.Clear(); + mEffectsUpdates.Clear(); + FreeClipChains(); + FreeTemporaryItems(); + nsCSSRendering::EndFrameTreesLocked(); + mCaretFrame = nullptr; +} + +void nsDisplayListBuilder::MarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + mFramesMarkedForDisplay.AppendElement(aFrame); + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->AddStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::AddFrameMarkedForDisplayIfVisible(nsIFrame* aFrame) { + mFramesMarkedForDisplayIfVisible.AppendElement(aFrame); +} + +static void MarkFrameForDisplayIfVisibleInternal(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && !parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + MarkFrameForDisplayIfVisibleInternal(parent, aStopAtFrame); + } + } + + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::MarkFrameForDisplayIfVisible( + nsIFrame* aFrame, const nsIFrame* aStopAtFrame) { + AddFrameMarkedForDisplayIfVisible(aFrame); + + MarkFrameForDisplayIfVisibleInternal(aFrame, aStopAtFrame); +} + +void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() { + mIsRelativeToLayoutViewport = true; + UpdateShouldBuildAsyncZoomContainer(); +} + +void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() { + const Document* document = mReferenceFrame->PresContext()->Document(); + mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport && + !document->Fullscreen() && + nsLayoutUtils::AllowZoomingForDocument(document); +} + +// Certain prefs may cause display list items to be added or removed when they +// are toggled. In those cases, we need to fully rebuild the display list. +bool nsDisplayListBuilder::ShouldRebuildDisplayListDueToPrefChange() { + // If we transition between wrapping the RCD-RSF contents into an async + // zoom container vs. not, we need to rebuild the display list. This only + // happens when the zooming or container scrolling prefs are toggled + // (manually by the user, or during test setup). + bool didBuildAsyncZoomContainer = mBuildAsyncZoomContainer; + UpdateShouldBuildAsyncZoomContainer(); + + bool hadOverlayScrollbarsLastTime = mUseOverlayScrollbars; + mUseOverlayScrollbars = + !!LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars); + + bool alwaysLayerizedScrollbarsLastTime = mAlwaysLayerizeScrollbars; + mAlwaysLayerizeScrollbars = + StaticPrefs::layout_scrollbars_always_layerize_track(); + + if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) { + return true; + } + + if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) { + return true; + } + + if (alwaysLayerizedScrollbarsLastTime != mAlwaysLayerizeScrollbars) { + return true; + } + + return false; +} + +void nsDisplayListBuilder::AddScrollFrameToNotify( + nsIScrollableFrame* aScrollFrame) { + mScrollFramesToNotify.insert(aScrollFrame); +} + +void nsDisplayListBuilder::NotifyAndClearScrollFrames() { + for (const auto& it : mScrollFramesToNotify) { + it->NotifyApzTransaction(); + } + mScrollFramesToNotify.clear(); +} + +bool nsDisplayListBuilder::MarkOutOfFlowFrameForDisplay( + nsIFrame* aDirtyFrame, nsIFrame* aFrame, const nsRect& aVisibleRect, + const nsRect& aDirtyRect) { + MOZ_ASSERT(aFrame->GetParent() == aDirtyFrame); + nsRect dirty; + nsRect visible = OutOfFlowDisplayData::ComputeVisibleRectForFrame( + this, aFrame, aVisibleRect, aDirtyRect, &dirty); + if (!aFrame->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO) && + visible.IsEmpty()) { + return false; + } + + // Only MarkFrameForDisplay if we're dirty. If this is a nested out-of-flow + // frame, then it will also mark any outer frames to ensure that building + // reaches the dirty feame. + if (!dirty.IsEmpty() || aFrame->ForceDescendIntoIfVisible()) { + MarkFrameForDisplay(aFrame, aDirtyFrame); + } + + return true; +} + +static void UnmarkFrameForDisplay(nsIFrame* aFrame, + const nsIFrame* aStopAtFrame) { + for (nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) { + if (!f->HasAnyStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO)) { + return; + } + f->RemoveStateBits(NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +static void UnmarkFrameForDisplayIfVisible(nsIFrame* aFrame) { + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (!f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(false); + + // This condition must match the condition in + // nsLayoutUtils::GetParentOrPlaceholderFor which is used by + // nsLayoutUtils::GetDisplayListParent + if (f->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && !f->GetPrevInFlow()) { + nsIFrame* parent = f->GetParent(); + if (parent && parent->ForceDescendIntoIfVisible()) { + // If the GetDisplayListParent call is going to walk to a placeholder, + // in rare cases the placeholder might be contained in a different + // continuation from the oof. So we have to make sure to mark the oofs + // parent. In the common case this doesn't make us do any extra work, + // just changes the order in which we visit the frames since walking + // through placeholders will walk through the parent, and we stop when + // we find a ForceDescendIntoIfVisible bit set. + UnmarkFrameForDisplayIfVisible(f); + } + } + } +} + +nsDisplayListBuilder::~nsDisplayListBuilder() { + NS_ASSERTION(mFramesMarkedForDisplay.Length() == 0, + "All frames should have been unmarked"); + NS_ASSERTION(mFramesWithOOFData.Length() == 0, + "All OOF data should have been removed"); + NS_ASSERTION(mPresShellStates.Length() == 0, + "All presshells should have been exited"); + + DisplayItemClipChain* c = mFirstClipChainToDestroy; + while (c) { + DisplayItemClipChain* next = c->mNextClipChainToDestroy; + c->DisplayItemClipChain::~DisplayItemClipChain(); + c = next; + } + + MOZ_COUNT_DTOR(nsDisplayListBuilder); +} + +uint32_t nsDisplayListBuilder::GetBackgroundPaintFlags() { + uint32_t flags = 0; + if (mSyncDecodeImages) { + flags |= nsCSSRendering::PAINTBG_SYNC_DECODE_IMAGES; + } + if (mIsPaintingToWindow) { + flags |= nsCSSRendering::PAINTBG_TO_WINDOW; + } + if (mUseHighQualityScaling) { + flags |= nsCSSRendering::PAINTBG_HIGH_QUALITY_SCALING; + } + return flags; +} + +// TODO(emilio): Maybe unify BackgroundPaintFlags and IamgeRendererFlags. +uint32_t nsDisplayListBuilder::GetImageRendererFlags() const { + uint32_t flags = 0; + if (mSyncDecodeImages) { + flags |= nsImageRenderer::FLAG_SYNC_DECODE_IMAGES; + } + if (mIsPaintingToWindow) { + flags |= nsImageRenderer::FLAG_PAINTING_TO_WINDOW; + } + if (mUseHighQualityScaling) { + flags |= nsImageRenderer::FLAG_HIGH_QUALITY_SCALING; + } + return flags; +} + +uint32_t nsDisplayListBuilder::GetImageDecodeFlags() const { + uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (mSyncDecodeImages) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } else { + flags |= imgIContainer::FLAG_SYNC_DECODE_IF_FAST; + } + if (mIsPaintingToWindow || mUseHighQualityScaling) { + flags |= imgIContainer::FLAG_HIGH_QUALITY_SCALING; + } + return flags; +} + +void nsDisplayListBuilder::SubtractFromVisibleRegion(nsRegion* aVisibleRegion, + const nsRegion& aRegion) { + if (aRegion.IsEmpty()) { + return; + } + + nsRegion tmp; + tmp.Sub(*aVisibleRegion, aRegion); + // Don't let *aVisibleRegion get too complex, but don't let it fluff out + // to its bounds either, which can be very bad (see bug 516740). + // Do let aVisibleRegion get more complex if by doing so we reduce its + // area by at least half. + if (tmp.GetNumRects() <= 15 || tmp.Area() <= aVisibleRegion->Area() / 2) { + *aVisibleRegion = tmp; + } +} + +nsCaret* nsDisplayListBuilder::GetCaret() { + RefPtr caret = CurrentPresShellState()->mPresShell->GetCaret(); + return caret; +} + +void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) { + if (mIsPaintingToWindow) { + aPresShell->IncrementPaintCount(); + } +} + +void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame, + bool aPointerEventsNoneDoc) { + PresShellState* state = mPresShellStates.AppendElement(); + state->mPresShell = aReferenceFrame->PresShell(); + state->mFirstFrameMarkedForDisplay = mFramesMarkedForDisplay.Length(); + state->mFirstFrameWithOOFData = mFramesWithOOFData.Length(); + + nsIScrollableFrame* sf = state->mPresShell->GetRootScrollFrameAsScrollable(); + if (sf && IsInSubdocument()) { + // We are forcing a rebuild of nsDisplayCanvasBackgroundColor to make sure + // that the canvas background color will be set correctly, and that only one + // unscrollable item will be created. + // This is done to avoid, for example, a case where only scrollbar frames + // are invalidated - we would skip creating nsDisplayCanvasBackgroundColor + // and possibly end up with an extra nsDisplaySolidColor item. + // We skip this for the root document, since we don't want to use + // MarkFrameForDisplayIfVisible before ComputeRebuildRegion. We'll + // do it manually there. + nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame()); + if (canvasFrame) { + MarkFrameForDisplayIfVisible(canvasFrame, aReferenceFrame); + } + } + +#ifdef DEBUG + state->mAutoLayoutPhase.emplace(aReferenceFrame->PresContext(), + nsLayoutPhase::DisplayListBuilding); +#endif + + state->mPresShell->UpdateCanvasBackground(); + + bool buildCaret = mBuildCaret; + if (mIgnoreSuppression || !state->mPresShell->IsPaintingSuppressed()) { + state->mIsBackgroundOnly = false; + } else { + state->mIsBackgroundOnly = true; + buildCaret = false; + } + + bool pointerEventsNone = aPointerEventsNoneDoc; + if (IsInSubdocument()) { + pointerEventsNone |= mPresShellStates[mPresShellStates.Length() - 2] + .mInsidePointerEventsNoneDoc; + } + state->mInsidePointerEventsNoneDoc = pointerEventsNone; + + state->mPresShellIgnoreScrollFrame = + state->mPresShell->IgnoringViewportScrolling() + ? state->mPresShell->GetRootScrollFrame() + : nullptr; + + nsPresContext* pc = aReferenceFrame->PresContext(); + mIsInChromePresContext = pc->IsChrome(); + nsIDocShell* docShell = pc->GetDocShell(); + + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + + state->mTouchEventPrefEnabledDoc = dom::TouchEvent::PrefEnabled(docShell); + + if (!buildCaret) { + return; + } + + // Caret frames add visual area to their frame, but we don't update the + // overflow area. Use flags to make sure we build display items for that frame + // instead. + if (mCaretFrame && mCaretFrame->PresShell() == state->mPresShell) { + MarkFrameForDisplay(mCaretFrame, aReferenceFrame); + } +} + +// A non-blank paint is a paint that does not just contain the canvas +// background. +static bool DisplayListIsNonBlank(nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + switch (i->GetType()) { + case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR: + case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE: + continue; + case DisplayItemType::TYPE_SOLID_COLOR: + case DisplayItemType::TYPE_BACKGROUND: + case DisplayItemType::TYPE_BACKGROUND_COLOR: + if (i->Frame()->IsCanvasFrame()) { + continue; + } + return true; + default: + return true; + } + } + return false; +} + +// A contentful paint is a paint that does contains DOM content (text, +// images, non-blank canvases, SVG): "First Contentful Paint entry +// contains a DOMHighResTimeStamp reporting the time when the browser +// first rendered any text, image (including background images), +// non-white canvas or SVG. This excludes any content of iframes, but +// includes text with pending webfonts. This is the first time users +// could start consuming page content." +static bool DisplayListIsContentful(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList) { + for (nsDisplayItem* i : *aList) { + DisplayItemType type = i->GetType(); + nsDisplayList* children = i->GetChildren(); + + switch (type) { + case DisplayItemType::TYPE_SUBDOCUMENT: // iframes are ignored + break; + // CANVASes check if they may have been modified (as a stand-in + // actually tracking all modifications) + default: + if (i->IsContentful()) { + bool dummy; + nsRect bound = i->GetBounds(aBuilder, &dummy); + if (!bound.IsEmpty()) { + return true; + } + } + if (children) { + if (DisplayListIsContentful(aBuilder, children)) { + return true; + } + } + break; + } + } + return false; +} + +void nsDisplayListBuilder::LeavePresShell(const nsIFrame* aReferenceFrame, + nsDisplayList* aPaintedContents) { + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aReferenceFrame->PresShell(), + "Presshell mismatch"); + + if (mIsPaintingToWindow && aPaintedContents) { + nsPresContext* pc = aReferenceFrame->PresContext(); + if (!pc->HadNonBlankPaint()) { + if (!CurrentPresShellState()->mIsBackgroundOnly && + DisplayListIsNonBlank(aPaintedContents)) { + pc->NotifyNonBlankPaint(); + } + } + nsRootPresContext* rootPresContext = pc->GetRootPresContext(); + if (!pc->HasStoppedGeneratingLCP() && rootPresContext) { + if (!CurrentPresShellState()->mIsBackgroundOnly) { + if (pc->HasEverBuiltInvisibleText() || + DisplayListIsContentful(this, aPaintedContents)) { + pc->NotifyContentfulPaint(); + } + } + } + } + + ResetMarkedFramesForDisplayList(aReferenceFrame); + mPresShellStates.RemoveLastElement(); + + if (!mPresShellStates.IsEmpty()) { + nsPresContext* pc = CurrentPresContext(); + nsIDocShell* docShell = pc->GetDocShell(); + if (docShell) { + docShell->GetWindowDraggingAllowed(&mWindowDraggingAllowed); + } + mIsInChromePresContext = pc->IsChrome(); + } else { + for (uint32_t i = 0; i < mFramesMarkedForDisplayIfVisible.Length(); ++i) { + UnmarkFrameForDisplayIfVisible(mFramesMarkedForDisplayIfVisible[i]); + } + mFramesMarkedForDisplayIfVisible.SetLength(0); + } +} + +void nsDisplayListBuilder::FreeClipChains() { + // Iterate the clip chains from newest to oldest (forward + // iteration), so that we destroy descendants first which + // will drop the ref count on their ancestors. + DisplayItemClipChain** indirect = &mFirstClipChainToDestroy; + + while (*indirect) { + if (!(*indirect)->mRefCount) { + DisplayItemClipChain* next = (*indirect)->mNextClipChainToDestroy; + + mClipDeduplicator.erase(*indirect); + (*indirect)->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, *indirect); + + *indirect = next; + } else { + indirect = &(*indirect)->mNextClipChainToDestroy; + } + } +} + +void nsDisplayListBuilder::FreeTemporaryItems() { + for (nsDisplayItem* i : mTemporaryItems) { + // Temporary display items are not added to the frames. + MOZ_ASSERT(i->Frame()); + i->RemoveFrame(i->Frame()); + i->Destroy(this); + } + + mTemporaryItems.Clear(); +} + +void nsDisplayListBuilder::ResetMarkedFramesForDisplayList( + const nsIFrame* aReferenceFrame) { + // Unmark and pop off the frames marked for display in this pres shell. + uint32_t firstFrameForShell = + CurrentPresShellState()->mFirstFrameMarkedForDisplay; + for (uint32_t i = firstFrameForShell; i < mFramesMarkedForDisplay.Length(); + ++i) { + UnmarkFrameForDisplay(mFramesMarkedForDisplay[i], aReferenceFrame); + } + mFramesMarkedForDisplay.SetLength(firstFrameForShell); + + firstFrameForShell = CurrentPresShellState()->mFirstFrameWithOOFData; + for (uint32_t i = firstFrameForShell; i < mFramesWithOOFData.Length(); ++i) { + mFramesWithOOFData[i]->RemoveProperty(OutOfFlowDisplayDataProperty()); + } + mFramesWithOOFData.SetLength(firstFrameForShell); +} + +void nsDisplayListBuilder::ClearFixedBackgroundDisplayData() { + CurrentPresShellState()->mFixedBackgroundDisplayData = Nothing(); +} + +void nsDisplayListBuilder::MarkFramesForDisplayList( + nsIFrame* aDirtyFrame, const nsFrameList& aFrames) { + nsRect visibleRect = GetVisibleRect(); + nsRect dirtyRect = GetDirtyRect(); + + // If we are entering content that is fixed to the RCD-RSF, we are + // crossing the async zoom container boundary, and need to convert from + // visual to layout coordinates. + if (ViewportFrame* viewportFrame = do_QueryFrame(aDirtyFrame)) { + if (IsForEventDelivery() && ShouldBuildAsyncZoomContainer() && + viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + if (viewportFrame->PresShell()->GetRootScrollFrame()) { +#ifdef DEBUG + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(ViewportUtils::IsZoomedContentRoot(f)); + } +#endif + visibleRect = ViewportUtils::VisualToLayout(visibleRect, + viewportFrame->PresShell()); + dirtyRect = ViewportUtils::VisualToLayout(dirtyRect, + viewportFrame->PresShell()); + } +#ifdef DEBUG + else { + // This is an edge case that should only happen if we are in a + // document with a XUL root element so that it does not have a root + // scroll frame but it has fixed pos content and all of the frames in + // aFrames are that fixed pos content. + for (nsIFrame* f : aFrames) { + MOZ_ASSERT(!ViewportUtils::IsZoomedContentRoot(f) && + f->GetParent() == aDirtyFrame && + f->StyleDisplay()->mPosition == + StylePositionProperty::Fixed); + } + // There's no root scroll frame so there can't be any zooming or async + // panning so we don't need to adjust the visible and dirty rects. + } +#endif + } + } + + bool markedFrames = false; + for (nsIFrame* e : aFrames) { + // Skip the AccessibleCaret frame when building no caret. + if (!IsBuildingCaret()) { + nsIContent* content = e->GetContent(); + if (content && content->IsInNativeAnonymousSubtree() && + content->IsElement()) { + const nsAttrValue* classes = content->AsElement()->GetClasses(); + if (classes && + classes->Contains(nsGkAtoms::mozAccessiblecaret, eCaseMatters)) { + continue; + } + } + } + if (MarkOutOfFlowFrameForDisplay(aDirtyFrame, e, visibleRect, dirtyRect)) { + markedFrames = true; + } + } + + if (markedFrames) { + // mClipState.GetClipChainForContainingBlockDescendants can return pointers + // to objects on the stack, so we need to clone the chain. + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + + OutOfFlowDisplayData* data = new OutOfFlowDisplayData( + clipChain, combinedClipChain, asr, this->mCurrentScrollParentId, + visibleRect, dirtyRect); + aDirtyFrame->SetProperty( + nsDisplayListBuilder::OutOfFlowDisplayDataProperty(), data); + mFramesWithOOFData.AppendElement(aDirtyFrame); + } + + if (!aDirtyFrame->GetParent()) { + // This is the viewport frame of aDirtyFrame's presshell. + // Store the current display data so that it can be used for fixed + // background images. + NS_ASSERTION( + CurrentPresShellState()->mPresShell == aDirtyFrame->PresShell(), + "Presshell mismatch"); + MOZ_ASSERT(!CurrentPresShellState()->mFixedBackgroundDisplayData, + "already traversed this presshell's root frame?"); + + const DisplayItemClipChain* clipChain = + CopyWholeChain(mClipState.GetClipChainForContainingBlockDescendants()); + const DisplayItemClipChain* combinedClipChain = + mClipState.GetCurrentCombinedClipChain(this); + const ActiveScrolledRoot* asr = mCurrentActiveScrolledRoot; + CurrentPresShellState()->mFixedBackgroundDisplayData.emplace( + clipChain, combinedClipChain, asr, this->mCurrentScrollParentId, + GetVisibleRect(), GetDirtyRect()); + } +} + +/** + * Mark all preserve-3d children with + * NS_FRAME_FORCE_DISPLAY_LIST_DESCEND_INTO to make sure + * nsIFrame::BuildDisplayListForChild() would visit them. Also compute + * dirty rect for preserve-3d children. + * + * @param aDirtyFrame is the frame to mark children extending context. + */ +void nsDisplayListBuilder::MarkPreserve3DFramesForDisplayList( + nsIFrame* aDirtyFrame) { + for (const auto& childList : aDirtyFrame->ChildLists()) { + for (nsIFrame* child : childList.mList) { + if (child->Combines3DTransformWithAncestors()) { + MarkFrameForDisplay(child, aDirtyFrame); + } + + if (child->IsBlockWrapper()) { + // Mark preserve-3d frames inside the block wrapper. + MarkPreserve3DFramesForDisplayList(child); + } + } + } +} + +ActiveScrolledRoot* nsDisplayListBuilder::AllocateActiveScrolledRoot( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame) { + RefPtr asr = ActiveScrolledRoot::CreateASRForFrame( + aParent, aScrollableFrame, IsRetainingDisplayList()); + mActiveScrolledRoots.AppendElement(asr); + return asr; +} + +const DisplayItemClipChain* nsDisplayListBuilder::AllocateDisplayItemClipChain( + const DisplayItemClip& aClip, const ActiveScrolledRoot* aASR, + const DisplayItemClipChain* aParent) { + MOZ_DIAGNOSTIC_ASSERT(!(aParent && aParent->mOnStack)); + void* p = Allocate(sizeof(DisplayItemClipChain), + DisplayListArenaObjectId::CLIPCHAIN); + DisplayItemClipChain* c = new (KnownNotNull, p) + DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy); +#if defined(DEBUG) || defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) + c->mOnStack = false; +#endif + auto result = mClipDeduplicator.insert(c); + if (!result.second) { + // An equivalent clip chain item was already created, so let's return that + // instead. Destroy the one we just created. + // Note that this can cause clip chains from different coordinate systems to + // collapse into the same clip chain object, because clip chains do not keep + // track of the reference frame that they were created in. + c->DisplayItemClipChain::~DisplayItemClipChain(); + Destroy(DisplayListArenaObjectId::CLIPCHAIN, c); + return *(result.first); + } + mFirstClipChainToDestroy = c; + return c; +} + +struct ClipChainItem { + DisplayItemClip clip; + const ActiveScrolledRoot* asr; +}; + +static const DisplayItemClipChain* FindCommonAncestorClipForIntersection( + const DisplayItemClipChain* aOne, const DisplayItemClipChain* aTwo) { + for (const ActiveScrolledRoot* asr = + ActiveScrolledRoot::PickDescendant(aOne->mASR, aTwo->mASR); + asr; asr = asr->mParent) { + if (aOne == aTwo) { + return aOne; + } + if (aOne->mASR == asr) { + aOne = aOne->mParent; + } + if (aTwo->mASR == asr) { + aTwo = aTwo->mParent; + } + if (!aOne) { + return aTwo; + } + if (!aTwo) { + return aOne; + } + } + return nullptr; +} + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + AutoTArray intersectedClips; + + const DisplayItemClipChain* clip1 = aLeafClip1; + const DisplayItemClipChain* clip2 = aLeafClip2; + + const ActiveScrolledRoot* asr = ActiveScrolledRoot::PickDescendant( + clip1 ? clip1->mASR : nullptr, clip2 ? clip2->mASR : nullptr); + + // Build up the intersection from the leaf to the root and put it into + // intersectedClips. The loop below will convert intersectedClips into an + // actual DisplayItemClipChain. + // (We need to do this in two passes because we need the parent clip in order + // to create the DisplayItemClipChain object, but the parent clip has not + // been created at that point.) + while (!aAncestor || asr != aAncestor->mASR) { + if (clip1 && clip1->mASR == asr) { + if (clip2 && clip2->mASR == asr) { + DisplayItemClip intersection = clip1->mClip; + intersection.IntersectWith(clip2->mClip); + intersectedClips.AppendElement(ClipChainItem{intersection, asr}); + clip2 = clip2->mParent; + } else { + intersectedClips.AppendElement(ClipChainItem{clip1->mClip, asr}); + } + clip1 = clip1->mParent; + } else if (clip2 && clip2->mASR == asr) { + intersectedClips.AppendElement(ClipChainItem{clip2->mClip, asr}); + clip2 = clip2->mParent; + } + if (!asr) { + MOZ_ASSERT(!aAncestor, "We should have exited this loop earlier"); + break; + } + asr = asr->mParent; + } + + // Convert intersectedClips into a DisplayItemClipChain. + const DisplayItemClipChain* parentSC = aAncestor; + for (auto& sc : Reversed(intersectedClips)) { + parentSC = AllocateDisplayItemClipChain(sc.clip, sc.asr, parentSC); + } + return parentSC; +} + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + // aLeafClip2 might be a reference to a clip on the stack. We need to make + // sure that CreateClipChainIntersection will allocate the actual intersected + // clip in the builder's arena, so for the aLeafClip1 == nullptr case, + // we supply nullptr as the common ancestor so that + // CreateClipChainIntersection clones the whole chain. + const DisplayItemClipChain* ancestorClip = + aLeafClip1 ? FindCommonAncestorClipForIntersection(aLeafClip1, aLeafClip2) + : nullptr; + + return CreateClipChainIntersection(ancestorClip, aLeafClip1, aLeafClip2); +} + +const DisplayItemClipChain* nsDisplayListBuilder::CopyWholeChain( + const DisplayItemClipChain* aClipChain) { + return CreateClipChainIntersection(nullptr, aClipChain, nullptr); +} + +const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor( + const nsIFrame* aFrame, nsPoint* aOffset) const { + auto MaybeApplyAdditionalOffset = [&]() { + if (auto offset = AdditionalOffset()) { + *aOffset += *offset; + } + }; + + if (aFrame == mCurrentFrame) { + if (aOffset) { + *aOffset = mCurrentOffsetToReferenceFrame; + } + return mCurrentReferenceFrame; + } + + for (const nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) { + if (f == mReferenceFrame || f->IsTransformed()) { + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(f); + MaybeApplyAdditionalOffset(); + } + return f; + } + } + + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame); + MaybeApplyAdditionalOffset(); + } + + return mReferenceFrame; +} + +// Sticky frames are active if their nearest scrollable frame is also active. +static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky); + + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(aFrame); + return stickyScrollContainer && + stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled(); +} + +bool nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, + nsIFrame** aParent) { + if (aFrame == mReferenceFrame) { + return true; + } + + if (!IsPaintingToWindow()) { + if (aParent) { + *aParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + } + return false; + } + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + if (!parent) { + return true; + } + *aParent = parent; + + if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky && + IsStickyFrameActive(this, aFrame)) { + return true; + } + + if (aFrame->IsTransformed()) { + if (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + return true; + } + } + + LayoutFrameType parentType = parent->Type(); + if (parentType == LayoutFrameType::Scroll || + parentType == LayoutFrameType::ListControl) { + nsIScrollableFrame* sf = do_QueryFrame(parent); + if (sf->GetScrolledFrame() == aFrame) { + MOZ_ASSERT(!aFrame->IsTransformed()); + return sf->IsMaybeAsynchronouslyScrolled(); + } + } + + return false; +} + +nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor( + nsIFrame* aFrame) { + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDocInProcess( + RootReferenceFrame(), aFrame)); + nsIFrame* cursor = aFrame; + while (cursor != RootReferenceFrame()) { + nsIFrame* next; + if (IsAnimatedGeometryRoot(cursor, &next)) { + return cursor; + } + cursor = next; + } + return cursor; +} + +static nsRect ApplyAllClipNonRoundedIntersection( + const DisplayItemClipChain* aClipChain, const nsRect& aRect) { + nsRect result = aRect; + while (aClipChain) { + result = aClipChain->mClip.ApplyNonRoundedIntersection(result); + aClipChain = aClipChain->mParent; + } + return result; +} + +void nsDisplayListBuilder::AdjustWindowDraggingRegion(nsIFrame* aFrame) { + if (!mWindowDraggingAllowed || !IsForPainting()) { + return; + } + + const nsStyleUIReset* styleUI = aFrame->StyleUIReset(); + if (styleUI->mWindowDragging == StyleWindowDragging::Default) { + // This frame has the default value and doesn't influence the window + // dragging region. + return; + } + + LayoutDeviceToLayoutDeviceMatrix4x4 referenceFrameToRootReferenceFrame; + + // The const_cast is for nsLayoutUtils::GetTransformToAncestor. + nsIFrame* referenceFrame = + const_cast(FindReferenceFrameFor(aFrame)); + + if (IsInTransform()) { + // Only support 2d rectilinear transforms. Transform support is needed for + // the horizontal flip transform that's applied to the urlbar textbox in + // RTL mode - it should be able to exclude itself from the draggable region. + referenceFrameToRootReferenceFrame = + ViewAs( + nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame}, + RelativeTo{mReferenceFrame}) + .GetMatrix()); + Matrix referenceFrameToRootReferenceFrame2d; + if (!referenceFrameToRootReferenceFrame.Is2D( + &referenceFrameToRootReferenceFrame2d) || + !referenceFrameToRootReferenceFrame2d.IsRectilinear()) { + return; + } + } else { + MOZ_ASSERT(referenceFrame == mReferenceFrame, + "referenceFrameToRootReferenceFrame needs to be adjusted"); + } + + // We do some basic visibility checking on the frame's border box here. + // We intersect it both with the current dirty rect and with the current + // clip. Either one is just a conservative approximation on its own, but + // their intersection luckily works well enough for our purposes, so that + // we don't have to do full-blown visibility computations. + // The most important case we need to handle is the scrolled-off tab: + // If the tab bar overflows, tab parts that are clipped by the scrollbox + // should not be allowed to interfere with the window dragging region. Using + // just the current DisplayItemClip is not enough to cover this case + // completely because clips are reset while building stacking context + // contents, so for example we'd fail to clip frames that have a clip path + // applied to them. But the current dirty rect doesn't get reset in that + // case, so we use it to make this case work. + nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect); + borderBox += ToReferenceFrame(aFrame); + const DisplayItemClipChain* clip = + ClipState().GetCurrentCombinedClipChain(this); + borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox); + if (borderBox.IsEmpty()) { + return; + } + + LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits( + borderBox, aFrame->PresContext()->AppUnitsPerDevPixel()); + + LayoutDeviceRect transformedDevPixelBorderBox = + TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox); + transformedDevPixelBorderBox.Round(); + LayoutDeviceIntRect transformedDevPixelBorderBoxInt; + + if (!transformedDevPixelBorderBox.ToIntRect( + &transformedDevPixelBorderBoxInt)) { + return; + } + + LayoutDeviceIntRegion& region = + styleUI->mWindowDragging == StyleWindowDragging::Drag + ? mWindowDraggingRegion + : mWindowNoDraggingRegion; + + if (!IsRetainingDisplayList()) { + region.OrWith(transformedDevPixelBorderBoxInt); + return; + } + + gfx::IntRect rect(transformedDevPixelBorderBoxInt.ToUnknownRect()); + if (styleUI->mWindowDragging == StyleWindowDragging::Drag) { + mRetainedWindowDraggingRegion.Add(aFrame, rect); + } else { + mRetainedWindowNoDraggingRegion.Add(aFrame, rect); + } +} + +LayoutDeviceIntRegion nsDisplayListBuilder::GetWindowDraggingRegion() const { + LayoutDeviceIntRegion result; + if (!IsRetainingDisplayList()) { + result.Sub(mWindowDraggingRegion, mWindowNoDraggingRegion); + return result; + } + + LayoutDeviceIntRegion dragRegion = + mRetainedWindowDraggingRegion.ToLayoutDeviceIntRegion(); + + LayoutDeviceIntRegion noDragRegion = + mRetainedWindowNoDraggingRegion.ToLayoutDeviceIntRegion(); + + result.Sub(dragRegion, noDragRegion); + return result; +} + +void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes); + aSizes.mLayoutRetainedDisplayListSize += + aSizes.mState.mMallocSizeOf(mTransformPreserves3D.get()); +} + +void nsDisplayListBuilder::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + mPool.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::DisplayList); + + size_t n = 0; + MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf; + n += mDocumentWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mFrameWillChangeBudgets.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowNoDraggingRegion.SizeOfExcludingThis(mallocSizeOf); + n += mRetainedWindowOpaqueRegion.SizeOfExcludingThis(mallocSizeOf); + // XXX can't measure mClipDeduplicator since it uses std::unordered_set. + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +void RetainedDisplayList::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + for (nsDisplayItem* item : *this) { + item->AddSizeOfExcludingThis(aSizes); + if (RetainedDisplayList* children = item->GetChildren()) { + children->AddSizeOfExcludingThis(aSizes); + } + } + + size_t n = 0; + + n += mDAG.mDirectPredecessorList.ShallowSizeOfExcludingThis( + aSizes.mState.mMallocSizeOf); + n += mDAG.mNodesInfo.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + n += mOldItems.ShallowSizeOfExcludingThis(aSizes.mState.mMallocSizeOf); + + aSizes.mLayoutRetainedDisplayListSize += n; +} + +size_t nsDisplayListBuilder::WeakFrameRegion::SizeOfExcludingThis( + MallocSizeOf aMallocSizeOf) const { + size_t n = 0; + n += mFrames.ShallowSizeOfExcludingThis(aMallocSizeOf); + for (const auto& frame : mFrames) { + const UniquePtr& weakFrame = frame.mWeakFrame; + n += aMallocSizeOf(weakFrame.get()); + } + n += mRects.ShallowSizeOfExcludingThis(aMallocSizeOf); + return n; +} + +/** + * Removes modified frames and rects from this WeakFrameRegion. + */ +void nsDisplayListBuilder::WeakFrameRegion::RemoveModifiedFramesAndRects() { + MOZ_ASSERT(mFrames.Length() == mRects.Length()); + + uint32_t i = 0; + uint32_t length = mFrames.Length(); + + while (i < length) { + auto& wrapper = mFrames[i]; + + if (!wrapper.mWeakFrame->IsAlive() || + AnyContentAncestorModified(wrapper.mWeakFrame->GetFrame())) { + // To avoid multiple O(n) shifts in the array, move the last element of + // the array to the current position and decrease the array length. + mFrameSet.Remove(wrapper.mFrame); + mFrames[i] = std::move(mFrames[length - 1]); + mRects[i] = std::move(mRects[length - 1]); + length--; + } else { + i++; + } + } + + mFrames.TruncateLength(length); + mRects.TruncateLength(length); +} + +void nsDisplayListBuilder::RemoveModifiedWindowRegions() { + mRetainedWindowDraggingRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowNoDraggingRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects(); +} + +void nsDisplayListBuilder::ClearRetainedWindowRegions() { + mRetainedWindowDraggingRegion.Clear(); + mRetainedWindowNoDraggingRegion.Clear(); + mRetainedWindowOpaqueRegion.Clear(); +} + +const uint32_t gWillChangeAreaMultiplier = 3; +static uint32_t GetLayerizationCost(const nsSize& aSize) { + // There's significant overhead for each layer created from Gecko + // (IPC+Shared Objects) and from the backend (like an OpenGL texture). + // Therefore we set a minimum cost threshold of a 64x64 area. + const int minBudgetCost = 64 * 64; + + const uint32_t budgetCost = std::max( + minBudgetCost, nsPresContext::AppUnitsToIntCSSPixels(aSize.width) * + nsPresContext::AppUnitsToIntCSSPixels(aSize.height)); + + return budgetCost; +} + +bool nsDisplayListBuilder::AddToWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + MOZ_ASSERT(IsForPainting()); + + if (aFrame->MayHaveWillChangeBudget()) { + // The frame is already in the will-change budget. + return true; + } + + const nsPresContext* presContext = aFrame->PresContext(); + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + const uint32_t cost = GetLayerizationCost(aSize); + + DocumentWillChangeBudget& documentBudget = + mDocumentWillChangeBudgets.LookupOrInsert(presContext); + + const bool onBudget = + (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit; + + if (onBudget) { + documentBudget += cost; + mFrameWillChangeBudgets.InsertOrUpdate( + aFrame, FrameWillChangeBudget(presContext, cost)); + aFrame->SetMayHaveWillChangeBudget(true); + } + + return onBudget; +} + +bool nsDisplayListBuilder::IsInWillChangeBudget(nsIFrame* aFrame, + const nsSize& aSize) { + if (!IsForPainting()) { + // If this nsDisplayListBuilder is not for painting, the layerization should + // not matter. Do the simple thing and return false. + return false; + } + + const bool onBudget = AddToWillChangeBudget(aFrame, aSize); + if (onBudget) { + return true; + } + + auto* pc = aFrame->PresContext(); + auto* doc = pc->Document(); + if (!doc->HasWarnedAbout(Document::eIgnoringWillChangeOverBudget)) { + AutoTArray params; + params.AppendElement()->AppendInt(gWillChangeAreaMultiplier); + + nsRect area = pc->GetVisibleArea(); + uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + params.AppendElement()->AppendInt(budgetLimit); + + doc->WarnOnceAbout(Document::eIgnoringWillChangeOverBudget, false, params); + } + + return false; +} + +void nsDisplayListBuilder::ClearWillChangeBudgetStatus(nsIFrame* aFrame) { + MOZ_ASSERT(IsForPainting()); + + if (!aFrame->MayHaveWillChangeBudget()) { + return; + } + + aFrame->SetMayHaveWillChangeBudget(false); + RemoveFromWillChangeBudgets(aFrame); +} + +void nsDisplayListBuilder::RemoveFromWillChangeBudgets(const nsIFrame* aFrame) { + if (auto entry = mFrameWillChangeBudgets.Lookup(aFrame)) { + const FrameWillChangeBudget& frameBudget = entry.Data(); + + auto documentBudget = + mDocumentWillChangeBudgets.Lookup(frameBudget.mPresContext); + + if (documentBudget) { + *documentBudget -= frameBudget.mUsage; + } + + entry.Remove(); + } +} + +void nsDisplayListBuilder::ClearWillChangeBudgets() { + mFrameWillChangeBudgets.Clear(); + mDocumentWillChangeBudgets.Clear(); +} + +void nsDisplayListBuilder::EnterSVGEffectsContents( + nsIFrame* aEffectsFrame, nsDisplayList* aHoistedItemsStorage) { + MOZ_ASSERT(aHoistedItemsStorage); + if (mSVGEffectsFrames.IsEmpty()) { + MOZ_ASSERT(!mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting = aHoistedItemsStorage; + } + mSVGEffectsFrames.AppendElement(aEffectsFrame); +} + +void nsDisplayListBuilder::ExitSVGEffectsContents() { + MOZ_ASSERT(!mSVGEffectsFrames.IsEmpty()); + mSVGEffectsFrames.RemoveLastElement(); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + if (mSVGEffectsFrames.IsEmpty()) { + mScrollInfoItemsForHoisting = nullptr; + } +} + +bool nsDisplayListBuilder::ShouldBuildScrollInfoItemsForHoisting() const { + /* + * Note: if changing the conditions under which scroll info layers + * are created, make a corresponding change to + * ScrollFrameWillBuildScrollInfoLayer() in nsSliderFrame.cpp. + */ + for (nsIFrame* frame : mSVGEffectsFrames) { + if (SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(frame)) { + return true; + } + } + return false; +} + +void nsDisplayListBuilder::AppendNewScrollInfoItemForHoisting( + nsDisplayScrollInfoLayer* aScrollInfoItem) { + MOZ_ASSERT(ShouldBuildScrollInfoItemsForHoisting()); + MOZ_ASSERT(mScrollInfoItemsForHoisting); + mScrollInfoItemsForHoisting->AppendToTop(aScrollInfoItem); +} + +void nsDisplayListBuilder::BuildCompositorHitTestInfoIfNeeded( + nsIFrame* aFrame, nsDisplayList* aList) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aList); + + if (!BuildCompositorHitTestInfo()) { + return; + } + + const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this); + if (info != CompositorHitTestInvisibleToHit) { + aList->AppendNewToTop(this, aFrame); + } +} + +void nsDisplayListBuilder::AddReusableDisplayItem(nsDisplayItem* aItem) { + mReuseableItems.Insert(aItem); +} + +void nsDisplayListBuilder::RemoveReusedDisplayItem(nsDisplayItem* aItem) { + MOZ_ASSERT(aItem->IsReusedItem()); + mReuseableItems.Remove(aItem); +} + +void nsDisplayListBuilder::ClearReuseableDisplayItems() { + const size_t total = mReuseableItems.Count(); + + size_t reused = 0; + for (auto* item : mReuseableItems) { + if (item->IsReusedItem()) { + reused++; + item->SetReusable(); + } else { + item->Destroy(this); + } + } + + DL_LOGI("RDL - Reused %zu of %zu SC display items", reused, total); + mReuseableItems.Clear(); +} + +void nsDisplayListBuilder::ReuseDisplayItem(nsDisplayItem* aItem) { + const auto* previous = mCurrentContainerASR; + const auto* asr = aItem->GetActiveScrolledRoot(); + mCurrentContainerASR = + ActiveScrolledRoot::PickAncestor(asr, mCurrentContainerASR); + + if (previous != mCurrentContainerASR) { + DL_LOGV("RDL - Changed mCurrentContainerASR from %p to %p", previous, + mCurrentContainerASR); + } + + aItem->SetReusedItem(); +} + +void nsDisplayListSet::CopyTo(const nsDisplayListSet& aDestination) const { + for (size_t i = 0; i < mLists.size(); ++i) { + auto* from = mLists[i]; + auto* to = aDestination.mLists[i]; + + from->CopyTo(to); + } +} + +void nsDisplayListSet::MoveTo(const nsDisplayListSet& aDestination) const { + aDestination.BorderBackground()->AppendToTop(BorderBackground()); + aDestination.BlockBorderBackgrounds()->AppendToTop(BlockBorderBackgrounds()); + aDestination.Floats()->AppendToTop(Floats()); + aDestination.Content()->AppendToTop(Content()); + aDestination.PositionedDescendants()->AppendToTop(PositionedDescendants()); + aDestination.Outlines()->AppendToTop(Outlines()); +} + +nsRect nsDisplayList::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + bounds.UnionRect(bounds, i->GetClippedBounds(aBuilder)); + } + return bounds; +} + +nsRect nsDisplayList::GetClippedBoundsWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR, + nsRect* aBuildingRect) const { + nsRect bounds; + for (nsDisplayItem* i : *this) { + nsRect r = i->GetClippedBounds(aBuilder); + if (aASR != i->GetActiveScrolledRoot() && !r.IsEmpty()) { + if (Maybe clip = i->GetClipWithRespectToASR(aBuilder, aASR)) { + r = clip.ref(); + } + } + if (aBuildingRect) { + aBuildingRect->UnionRect(*aBuildingRect, i->GetBuildingRect()); + } + bounds.UnionRect(bounds, r); + } + return bounds; +} + +nsRect nsDisplayList::GetBuildingRect() const { + nsRect result; + for (nsDisplayItem* i : *this) { + result.UnionRect(result, i->GetBuildingRect()); + } + return result; +} + +WindowRenderer* nsDisplayListBuilder::GetWidgetWindowRenderer(nsView** aView) { + if (aView) { + *aView = RootReferenceFrame()->GetView(); + } + if (RootReferenceFrame() != + nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) { + return nullptr; + } + nsIWidget* window = RootReferenceFrame()->GetNearestWidget(); + if (window) { + return window->GetWindowRenderer(); + } + return nullptr; +} + +WebRenderLayerManager* nsDisplayListBuilder::GetWidgetLayerManager( + nsView** aView) { + WindowRenderer* renderer = GetWidgetWindowRenderer(); + if (renderer) { + return renderer->AsWebRender(); + } + return nullptr; +} + +void nsDisplayList::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + int32_t aAppUnitsPerDevPixel) { + FlattenedDisplayListIterator iter(aBuilder, this); + while (iter.HasNext()) { + nsPaintedDisplayItem* item = iter.GetNextItem()->AsPaintedDisplayItem(); + if (!item) { + continue; + } + + nsRect visible = item->GetClippedBounds(aBuilder); + visible = visible.Intersect(item->GetPaintRect(aBuilder, aCtx)); + if (visible.IsEmpty()) { + continue; + } + + DisplayItemClip currentClip = item->GetClip(); + if (currentClip.HasClip()) { + aCtx->Save(); + if (currentClip.IsRectClippedByRoundedCorner(visible)) { + currentClip.ApplyTo(aCtx, aAppUnitsPerDevPixel); + } else { + currentClip.ApplyRectTo(aCtx, aAppUnitsPerDevPixel); + } + } + aCtx->NewPath(); + + item->Paint(aBuilder, aCtx); + + if (currentClip.HasClip()) { + aCtx->Restore(); + } + } +} + +/** + * We paint by executing a layer manager transaction, constructing a + * single layer representing the display list, and then making it the + * root of the layer manager, drawing into the PaintedLayers. + */ +void nsDisplayList::PaintRoot(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + uint32_t aFlags, + Maybe aDisplayListBuildTime) { + AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS); + + RefPtr layerManager; + WindowRenderer* renderer = nullptr; + bool widgetTransaction = false; + bool doBeginTransaction = true; + nsView* view = nullptr; + if (aFlags & PAINT_USE_WIDGET_LAYERS) { + renderer = aBuilder->GetWidgetWindowRenderer(&view); + if (renderer) { + // The fallback renderer doesn't retain any content, so it's + // not meaningful to use it when drawing to an external context. + if (aCtx && renderer->AsFallback()) { + MOZ_ASSERT(!(aFlags & PAINT_EXISTING_TRANSACTION)); + renderer = nullptr; + } else { + layerManager = renderer->AsWebRender(); + doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION); + widgetTransaction = true; + } + } + } + + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + Document* document = presShell->GetDocument(); + + ScopeExit g([&]() { +#ifdef DEBUG + MOZ_ASSERT(!layerManager || !layerManager->GetTarget()); +#endif + + // For layers-free mode, we check the invalidation state bits in the + // EndTransaction. So we clear the invalidation state bits after + // EndTransaction. + if (widgetTransaction || + // SVG-as-an-image docs don't paint as part of the retained layer tree, + // but they still need the invalidation state bits cleared in order for + // invalidation for CSS/SMIL animation to work properly. + (document && document->IsBeingUsedAsImage())) { + DL_LOGD("Clearing invalidation state bits"); + frame->ClearInvalidationStateBits(); + } + }); + + if (!renderer) { + if (!aCtx) { + NS_WARNING("Nowhere to paint into"); + return; + } + bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(false); + Paint(aBuilder, aCtx, presContext->AppUnitsPerDevPixel()); + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + return; + } + + if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) { + MOZ_ASSERT(layerManager); + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx, nsCString())) { + return; + } + } else { + if (!layerManager->BeginTransaction(nsCString())) { + return; + } + } + } + + bool prevIsCompositingCheap = aBuilder->SetIsCompositingCheap(true); + layerManager->SetTransactionIdAllocator(presContext->RefreshDriver()); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + MOZ_ASSERT(!aCtx); + sent = layerManager->EndEmptyTransaction(); + } + + if (!sent) { + auto* wrManager = static_cast(layerManager.get()); + + nsIDocShell* docShell = presContext->GetDocShell(); + WrFiltersHolder wrFilters; + gfx::Matrix5x4* colorMatrix = + nsDocShell::Cast(docShell)->GetColorMatrix(); + if (colorMatrix) { + wrFilters.filters.AppendElement( + wr::FilterOp::ColorMatrix(colorMatrix->components)); + } + + wrManager->EndTransactionWithoutLayer(this, aBuilder, + std::move(wrFilters), nullptr, + aDisplayListBuildTime.valueOr(0.0)); + } + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + if (presContext->RefreshDriver()->HasScheduleFlush()) { + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + frame->GetRect()); + } + + return; + } + + FallbackRenderer* fallback = renderer->AsFallback(); + MOZ_ASSERT(fallback); + + if (doBeginTransaction) { + MOZ_ASSERT(!aCtx); + if (!fallback->BeginTransaction()) { + return; + } + } + + bool temp = aBuilder->SetIsCompositingCheap(renderer->IsCompositingCheap()); + fallback->EndTransactionWithList(aBuilder, this, + presContext->AppUnitsPerDevPixel(), + WindowRenderer::END_DEFAULT); + + aBuilder->SetIsCompositingCheap(temp); +} + +void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) { + for (auto* item : TakeItems()) { + item->Destroy(aBuilder); + } +} + +static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) { + return aFrame->Style()->PointerEvents() != StylePointerEvents::None; +} + +// A list of frames, and their z depth. Used for sorting +// the results of hit testing. +struct FramesWithDepth { + explicit FramesWithDepth(float aDepth) : mDepth(aDepth) {} + + bool operator<(const FramesWithDepth& aOther) const { + if (!FuzzyEqual(mDepth, aOther.mDepth, 0.1f)) { + // We want to sort so that the shallowest item (highest depth value) is + // first + return mDepth > aOther.mDepth; + } + return false; + } + bool operator==(const FramesWithDepth& aOther) const { + return this == &aOther; + } + + float mDepth; + nsTArray mFrames; +}; + +// Sort the frames by depth and then moves all the contained frames to the +// destination +static void FlushFramesArray(nsTArray& aSource, + nsTArray* aDest) { + if (aSource.IsEmpty()) { + return; + } + aSource.StableSort(); + uint32_t length = aSource.Length(); + for (uint32_t i = 0; i < length; i++) { + aDest->AppendElements(std::move(aSource[i].mFrames)); + } + aSource.Clear(); +} + +void nsDisplayList::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray* aOutFrames) const { + nsDisplayItem* item; + + if (aState->mInPreserves3D) { + // Collect leaves of the current 3D rendering context. + for (nsDisplayItem* item : *this) { + auto itemType = item->GetType(); + if (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast(item)->IsLeafOf3DContext()) { + item->HitTest(aBuilder, aRect, aState, aOutFrames); + } else { + // One of leaves in the current 3D rendering context. + aState->mItemBuffer.AppendElement(item); + } + } + return; + } + + int32_t itemBufferStart = aState->mItemBuffer.Length(); + for (nsDisplayItem* item : *this) { + aState->mItemBuffer.AppendElement(item); + } + + AutoTArray temp; + for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; + --i) { + // Pop element off the end of the buffer. We want to shorten the buffer + // so that recursive calls to HitTest have more buffer space. + item = aState->mItemBuffer[i]; + aState->mItemBuffer.SetLength(i); + + bool snap; + nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect); + auto itemType = item->GetType(); + bool same3DContext = + (itemType == DisplayItemType::TYPE_TRANSFORM && + static_cast(item)->IsParticipating3DContext()) || + (itemType == DisplayItemType::TYPE_PERSPECTIVE && + item->Frame()->Extend3DContext()); + if (same3DContext && + (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast(item)->IsLeafOf3DContext())) { + if (!item->GetClip().MayIntersect(aRect)) { + continue; + } + AutoTArray neverUsed; + // Start gathering leaves of the 3D rendering context, and + // append leaves at the end of mItemBuffer. Leaves are + // processed at following iterations. + aState->mInPreserves3D = true; + item->HitTest(aBuilder, aRect, aState, &neverUsed); + aState->mInPreserves3D = false; + i = aState->mItemBuffer.Length(); + continue; + } + if (same3DContext || item->GetClip().MayIntersect(r)) { + AutoTArray outFrames; + item->HitTest(aBuilder, aRect, aState, &outFrames); + + // For 3d transforms with preserve-3d we add hit frames into the temp list + // so we can sort them later, otherwise we add them directly to the output + // list. + nsTArray* writeFrames = aOutFrames; + if (item->GetType() == DisplayItemType::TYPE_TRANSFORM && + static_cast(item)->IsLeafOf3DContext()) { + if (outFrames.Length()) { + nsDisplayTransform* transform = + static_cast(item); + nsPoint point = aRect.TopLeft(); + // A 1x1 rect means a point, otherwise use the center of the rect + if (aRect.width != 1 || aRect.height != 1) { + point = aRect.Center(); + } + temp.AppendElement( + FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point))); + writeFrames = &temp[temp.Length() - 1].mFrames; + } + } else { + // We may have just finished a run of consecutive preserve-3d + // transforms, so flush these into the destination array before + // processing our frame list. + FlushFramesArray(temp, aOutFrames); + } + + for (uint32_t j = 0; j < outFrames.Length(); j++) { + nsIFrame* f = outFrames.ElementAt(j); + // Filter out some frames depending on the type of hittest + // we are doing. For visibility tests, pass through all frames. + // For pointer tests, only pass through frames that are styled + // to receive pointer events. + if (aBuilder->HitTestIsForVisibility() || + IsFrameReceivingPointerEvents(f)) { + writeFrames->AppendElement(f); + } + } + + if (aBuilder->HitTestIsForVisibility()) { + aState->mHitOccludingItem = [&] { + if (aState->mHitOccludingItem) { + // We already hit something before. + return true; + } + if (aState->mCurrentOpacity == 1.0f && + item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) { + // An opaque item always occludes everything. Note that we need to + // check wrapping opacity and such as well. + return true; + } + float threshold = aBuilder->VisibilityThreshold(); + if (threshold == 1.0f) { + return false; + } + float itemOpacity = [&] { + switch (item->GetType()) { + case DisplayItemType::TYPE_OPACITY: + return static_cast(item)->GetOpacity(); + case DisplayItemType::TYPE_BACKGROUND_COLOR: + return static_cast(item) + ->GetOpacity(); + default: + // Be conservative and assume it won't occlude other items. + return 0.0f; + } + }(); + return itemOpacity * aState->mCurrentOpacity >= threshold; + }(); + + if (aState->mHitOccludingItem) { + // We're exiting early, so pop the remaining items off the buffer. + aState->mItemBuffer.TruncateLength(itemBufferStart); + break; + } + } + } + } + // Clear any remaining preserve-3d transforms. + FlushFramesArray(temp, aOutFrames); + NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart), + "How did we forget to pop some elements?"); +} + +static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) { + nsIFrame* f = aItem->Frame(); + while (f) { + nsPresContext* pc = f->PresContext(); + if (pc->Document() == aDoc) { + return f->GetContent(); + } + f = nsLayoutUtils::GetCrossDocParentFrameInProcess( + pc->PresShell()->GetRootFrame()); + } + return nullptr; +} + +struct ZSortItem { + nsDisplayItem* item; + int32_t zIndex; + + explicit ZSortItem(nsDisplayItem* aItem) + : item(aItem), zIndex(aItem->ZIndex()) {} + + operator nsDisplayItem*() { return item; } +}; + +struct ZOrderComparator { + bool operator()(const ZSortItem& aLeft, const ZSortItem& aRight) const { + // Note that we can't just take the difference of the two + // z-indices here, because that might overflow a 32-bit int. + return aLeft.zIndex < aRight.zIndex; + } +}; + +void nsDisplayList::SortByZOrder() { Sort(ZOrderComparator()); } + +struct ContentComparator { + nsIContent* mCommonAncestor; + + explicit ContentComparator(nsIContent* aCommonAncestor) + : mCommonAncestor(aCommonAncestor) {} + + bool operator()(nsDisplayItem* aLeft, nsDisplayItem* aRight) const { + // It's possible that the nsIContent for aItem1 or aItem2 is in a + // subdocument of commonAncestor, because display items for subdocuments + // have been mixed into the same list. Ensure that we're looking at content + // in commonAncestor's document. + Document* commonAncestorDoc = mCommonAncestor->OwnerDoc(); + nsIContent* content1 = FindContentInDocument(aLeft, commonAncestorDoc); + nsIContent* content2 = FindContentInDocument(aRight, commonAncestorDoc); + if (!content1 || !content2) { + NS_ERROR("Document trees are mixed up!"); + // Something weird going on + return true; + } + return nsContentUtils::CompareTreePosition( + content1, content2, mCommonAncestor) < 0; + } +}; + +void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) { + Sort(ContentComparator(aCommonAncestor)); +} + +#if !defined(DEBUG) && !defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED) +static_assert(sizeof(nsDisplayItem) <= 176, "nsDisplayItem has grown"); +#endif + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) + : nsDisplayItem(aBuilder, aFrame, aBuilder->CurrentActiveScrolledRoot()) {} + +nsDisplayItem::nsDisplayItem(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot) + : mFrame(aFrame), mActiveScrolledRoot(aActiveScrolledRoot) { + MOZ_COUNT_CTOR(nsDisplayItem); + MOZ_ASSERT(mFrame); + if (aBuilder->IsRetainingDisplayList()) { + mFrame->AddDisplayItem(this); + } + + aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame); + NS_ASSERTION( + aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(), + "visible rect not set"); + + mClipChain = aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder); + + // The visible rect is for mCurrentFrame, so we have to use + // mCurrentOffsetToReferenceFrame + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + SetBuildingRect(visible); + + const nsStyleDisplay* disp = mFrame->StyleDisplay(); + if (mFrame->BackfaceIsHidden(disp)) { + mItemFlags += ItemFlag::BackfaceHidden; + } + if (mFrame->Combines3DTransformWithAncestors()) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } +} + +void nsDisplayItem::SetDeletedFrame() { mItemFlags += ItemFlag::DeletedFrame; } + +bool nsDisplayItem::HasDeletedFrame() const { + bool retval = mItemFlags.contains(ItemFlag::DeletedFrame) || + (GetType() == DisplayItemType::TYPE_REMOTE && + !static_cast(this)->GetFrameLoader()); + MOZ_ASSERT(retval || mFrame); + return retval; +} + +/* static */ +bool nsDisplayItem::ForceActiveLayers() { + return StaticPrefs::layers_force_active(); +} + +int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); } + +void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) { + mClipChain = aClipChain; +} + +Maybe nsDisplayItem::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } +#ifdef DEBUG + NS_ASSERTION(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +const DisplayItemClip& nsDisplayItem::GetClip() const { + const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(mClipChain, mActiveScrolledRoot); + return clip ? *clip : DisplayItemClip::NoClip(); +} + +void nsDisplayItem::IntersectClip(nsDisplayListBuilder* aBuilder, + const DisplayItemClipChain* aOther, + bool aStore) { + if (!aOther || mClipChain == aOther) { + return; + } + + // aOther might be a reference to a clip on the stack. We need to make sure + // that CreateClipChainIntersection will allocate the actual intersected + // clip in the builder's arena, so for the mClipChain == nullptr case, + // we supply nullptr as the common ancestor so that + // CreateClipChainIntersection clones the whole chain. + const DisplayItemClipChain* ancestorClip = + mClipChain ? FindCommonAncestorClipForIntersection(mClipChain, aOther) + : nullptr; + + SetClipChain( + aBuilder->CreateClipChainIntersection(ancestorClip, mClipChain, aOther), + aStore); +} + +nsRect nsDisplayItem::GetClippedBounds(nsDisplayListBuilder* aBuilder) const { + bool snap; + nsRect r = GetBounds(aBuilder, &snap); + return GetClip().ApplyNonRoundedIntersection(r); +} + +nsDisplayContainer::nsDisplayContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const ActiveScrolledRoot* aActiveScrolledRoot, nsDisplayList* aList) + : nsDisplayItem(aBuilder, aFrame, aActiveScrolledRoot), + mChildren(aBuilder) { + MOZ_COUNT_CTOR(nsDisplayContainer); + mChildren.AppendToTop(aList); + UpdateBounds(aBuilder); + + // Clear and store the clip chain set by nsDisplayItem constructor. + nsDisplayItem::SetClipChain(nullptr, true); +} + +nsRect nsDisplayItem::GetPaintRect(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + bool dummy; + nsRect result = GetBounds(aBuilder, &dummy); + if (aCtx) { + result.IntersectRect(result, + nsLayoutUtils::RoundGfxRectToAppRect( + aCtx->GetClipExtents(), + mFrame->PresContext()->AppUnitsPerDevPixel())); + } + return result; +} + +bool nsDisplayContainer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources, + false); + return true; +} + +nsRect nsDisplayContainer::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayContainer::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mChildren.GetComponentAlphaBounds(aBuilder); +} + +static nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + const nsRect& aListBounds) { + return aList->GetOpaqueRegion(aBuilder); +} + +nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), + GetBounds(aBuilder, aSnap)); +} + +Maybe nsDisplayContainer::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + // Our children should have finite bounds with respect to |aASR|. + if (aASR == mActiveScrolledRoot) { + return Some(mBounds); + } + + return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR)); +} + +void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { + mChildren.HitTest(aBuilder, aRect, aState, aOutFrames); +} + +void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) { + // Container item bounds are expected to be clipped. + mBounds = + mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot); +} + +nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + Rect rect = NSRectToSnappedRect(GetPaintRect(aBuilder, aCtx), + appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, ColorPattern(ToDeviceColor(mColor))); +} + +void nsDisplaySolidColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << (int)NS_GET_R(mColor) << "," << (int)NS_GET_G(mColor) + << "," << (int)NS_GET_B(mColor) << "," << (int)NS_GET_A(mColor) + << ")"; +} + +bool nsDisplaySolidColor::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, mIsCheckerboardBackground, + wr::ToColorF(ToDeviceColor(mColor))); + + return true; +} + +nsRect nsDisplaySolidColorRegion::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mRegion.GetBounds(); +} + +void nsDisplaySolidColorRegion::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + ColorPattern color(ToDeviceColor(mColor)); + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + Rect rect = + NSRectToSnappedRect(iter.Get(), appUnitsPerDevPixel, *drawTarget); + drawTarget->FillRect(rect, color); + } +} + +void nsDisplaySolidColorRegion::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << int(mColor.r * 255) << "," << int(mColor.g * 255) + << "," << int(mColor.b * 255) << "," << mColor.a << ")"; +} + +bool nsDisplaySolidColorRegion::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + for (auto iter = mRegion.RectIter(); !iter.Done(); iter.Next()) { + nsRect rect = iter.Get(); + LayoutDeviceRect layerRects = LayoutDeviceRect::FromAppUnits( + rect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(layerRects); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false, + wr::ToColorF(ToDeviceColor(mColor))); + } + + return true; +} + +static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, nsIFrame* aFrame, + nsITheme::ThemeGeometryType aType) { + if (aBuilder->IsInChromeDocumentOrPopup()) { + nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); + bool preservesAxisAlignedRectangles = false; + nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor( + aFrame, aFrame->GetRectRelativeToSelf(), displayRoot, + &preservesAxisAlignedRectangles); + if (preservesAxisAlignedRectangles) { + aBuilder->RegisterThemeGeometry( + aType, aItem, + LayoutDeviceIntRect::FromUnknownRect(borderBox.ToNearestPixels( + aFrame->PresContext()->AppUnitsPerDevPixel()))); + } + } +} + +// Return the bounds of the viewport relative to |aFrame|'s reference frame. +// Returns Nothing() if transforming into |aFrame|'s coordinate space fails. +static Maybe GetViewportRectRelativeToReferenceFrame( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame(); + nsRect rootRect = rootFrame->GetRectRelativeToSelf(); + if (nsLayoutUtils::TransformRect(rootFrame, aFrame, rootRect) == + nsLayoutUtils::TRANSFORM_SUCCEEDED) { + return Some(rootRect + aBuilder->ToReferenceFrame(aFrame)); + } + return Nothing(); +} + +/* static */ nsDisplayBackgroundImage::InitData +nsDisplayBackgroundImage::GetInitData(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, uint16_t aLayer, + const nsRect& aBackgroundRect, + const ComputedStyle* aBackgroundStyle) { + nsPresContext* presContext = aFrame->PresContext(); + uint32_t flags = aBuilder->GetBackgroundPaintFlags(); + const nsStyleImageLayers::Layer& layer = + aBackgroundStyle->StyleBackground()->mImage.mLayers[aLayer]; + + bool isTransformedFixed; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, aBackgroundRect, aBackgroundRect, layer, + &isTransformedFixed); + + // background-attachment:fixed is treated as background-attachment:scroll + // if it's affected by a transform. + // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=17521. + bool shouldTreatAsFixed = + layer.mAttachment == StyleImageLayerAttachment::Fixed && + !isTransformedFixed; + + bool shouldFixToViewport = shouldTreatAsFixed && !layer.mImage.IsNone(); + bool isRasterImage = state.mImageRenderer.IsRasterImage(); + nsCOMPtr image; + if (isRasterImage) { + image = state.mImageRenderer.GetImage(); + } + return InitData{aBuilder, aBackgroundStyle, image, + aBackgroundRect, state.mFillArea, state.mDestArea, + aLayer, isRasterImage, shouldFixToViewport}; +} + +nsDisplayBackgroundImage::nsDisplayBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aInitData, + nsIFrame* aFrameForBounds) + : nsPaintedDisplayItem(aBuilder, aFrame), + mBackgroundStyle(aInitData.backgroundStyle), + mImage(aInitData.image), + mDependentFrame(nullptr), + mBackgroundRect(aInitData.backgroundRect), + mFillRect(aInitData.fillArea), + mDestRect(aInitData.destArea), + mLayer(aInitData.layer), + mIsRasterImage(aInitData.isRasterImage), + mShouldFixToViewport(aInitData.shouldFixToViewport) { + MOZ_COUNT_CTOR(nsDisplayBackgroundImage); +#ifdef DEBUG + if (mBackgroundStyle && mBackgroundStyle != mFrame->Style()) { + // If this changes, then you also need to adjust css::ImageLoader to + // invalidate mFrame as needed. + MOZ_ASSERT(mFrame->IsCanvasFrame() || mFrame->IsTablePart()); + } +#endif + + mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds); + if (mShouldFixToViewport) { + // Expand the item's visible rect to cover the entire bounds, limited to the + // viewport rect. This is necessary because the background's clip can move + // asynchronously. + if (Maybe viewportRect = GetViewportRectRelativeToReferenceFrame( + aInitData.builder, mFrame)) { + SetBuildingRect(mBounds.Intersect(*viewportRect)); + } + } +} + +nsDisplayBackgroundImage::~nsDisplayBackgroundImage() { + MOZ_COUNT_DTOR(nsDisplayBackgroundImage); + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } +} + +static void SetBackgroundClipRegion( + DisplayListClipState::AutoSaveRestore& aClipState, nsIFrame* aFrame, + const nsStyleImageLayers::Layer& aLayer, const nsRect& aBackgroundRect, + bool aWillPaintBorder) { + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + aLayer, aFrame, *aFrame->StyleBorder(), aBackgroundRect, aBackgroundRect, + aWillPaintBorder, aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + if (clip.mHasAdditionalBGClipArea) { + aClipState.ClipContentDescendants( + clip.mAdditionalBGClipArea, clip.mBGClipArea, + clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } else { + aClipState.ClipContentDescendants( + clip.mBGClipArea, clip.mHasRoundedCorners ? clip.mRadii : nullptr); + } +} + +/** + * This is used for the find bar highlighter overlay. It's only accessible + * through the AnonymousContent API, so it's not exposed to general web pages. + */ +static bool SpecialCutoutRegionCase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsRect& aBackgroundRect, + nsDisplayList* aList, nscolor aColor) { + nsIContent* content = aFrame->GetContent(); + if (!content) { + return false; + } + + void* cutoutRegion = content->GetProperty(nsGkAtoms::cutoutregion); + if (!cutoutRegion) { + return false; + } + + if (NS_GET_A(aColor) == 0) { + return true; + } + + nsRegion region; + region.Sub(aBackgroundRect, *static_cast(cutoutRegion)); + region.MoveBy(aBuilder->ToReferenceFrame(aFrame)); + aList->AppendNewToTop(aBuilder, aFrame, region, + aColor); + + return true; +} + +enum class TableType : uint8_t { + Table, + TableCol, + TableColGroup, + TableRow, + TableRowGroup, + TableCell, + + MAX, +}; + +enum class TableTypeBits : uint8_t { Count = 3 }; + +static_assert(static_cast(TableType::MAX) < + (1 << (static_cast(TableTypeBits::Count) + 1)), + "TableType cannot fit with TableTypeBits::Count"); +TableType GetTableTypeFromFrame(nsIFrame* aFrame); + +static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex, + const TableType aType) { + const uint32_t key = (aIndex << static_cast(TableTypeBits::Count)) | + static_cast(aType); + + return static_cast(key); +} + +static nsDisplayBackgroundImage* CreateBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + const nsDisplayBackgroundImage::InitData& aBgData) { + const auto index = aBgData.layer; + + if (aSecondaryFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType); + + return MakeDisplayItemWithIndex( + aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame); + } + + return MakeDisplayItemWithIndex(aBuilder, aFrame, + index, aBgData); +} + +static nsDisplayThemedBackground* CreateThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + const nsRect& aBgRect) { + if (aSecondaryFrame) { + const uint16_t index = static_cast(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex( + aBuilder, aSecondaryFrame, index, aBgRect, aFrame); + } + + return MakeDisplayItem(aBuilder, aFrame, aBgRect); +} + +static nsDisplayBackgroundColor* CreateBackgroundColor( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect, const ComputedStyle* aBgSC, nscolor aColor) { + if (aSecondaryFrame) { + const uint16_t index = static_cast(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex( + aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame); + } + + return MakeDisplayItem(aBuilder, aFrame, aBgRect, + aBgSC, aColor); +} + +static void DealWithWindowsAppearanceHacks(nsIFrame* aFrame, + nsDisplayListBuilder* aBuilder) { + const auto& disp = *aFrame->StyleDisplay(); + + // We use default appearance rather than effective appearance because we want + // to handle when titlebar buttons that have appearance: none. + const auto defaultAppearance = disp.mDefaultAppearance; + if (MOZ_LIKELY(defaultAppearance == StyleAppearance::None)) { + return; + } + + if (auto type = disp.GetWindowButtonType()) { + if (auto* widget = aFrame->GetNearestWidget()) { + auto rect = LayoutDevicePixel::FromAppUnitsToNearest( + nsRect(aBuilder->ToReferenceFrame(aFrame), aFrame->GetSize()), + aFrame->PresContext()->AppUnitsPerDevPixel()); + widget->SetWindowButtonRect(*type, rect); + } + } +} + +/*static*/ +AppendedBackgroundType nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization, const nsRect& aBackgroundOriginRect, + nsIFrame* aSecondaryReferenceFrame, + Maybe* + aAutoBuildingDisplayList) { + MOZ_ASSERT(!aFrame->IsCanvasFrame(), + "We don't expect propagated canvas backgrounds here"); +#ifdef DEBUG + { + nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aFrame); + MOZ_ASSERT( + !bgFrame || bgFrame == aFrame, + "Should only suppress backgrounds, never propagate to another frame"); + } +#endif + + DealWithWindowsAppearanceHacks(aFrame, aBuilder); + + const bool isThemed = aFrame->IsThemed(); + + const ComputedStyle* bgSC = aFrame->Style(); + const nsStyleBackground* bg = bgSC->StyleBackground(); + const bool needsBackgroundColor = + aBuilder->IsForEventDelivery() || + (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_BACKGROUND_COLOR) && + !isThemed); + if (!needsBackgroundColor && !isThemed && bg->IsTransparent(bgSC)) { + return AppendedBackgroundType::None; + } + + bool drawBackgroundColor = false; + bool drawBackgroundImage = false; + nscolor color = NS_RGBA(0, 0, 0, 0); + // Don't get background color / images if we propagated our background to the + // canvas (that is, if FindBackgroundFrame is null). But don't early return + // yet, since we might still need a background-color item for hit-testing. + if (!isThemed && nsCSSRendering::FindBackgroundFrame(aFrame)) { + color = nsCSSRendering::DetermineBackgroundColor( + aFrame->PresContext(), bgSC, aFrame, drawBackgroundImage, + drawBackgroundColor); + } + + if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList, + color)) { + return AppendedBackgroundType::None; + } + + const nsStyleBorder& border = *aFrame->StyleBorder(); + const bool willPaintBorder = + aAllowWillPaintBorderOptimization && !isThemed && + !aFrame->StyleEffects()->HasBoxShadowWithInset(true) && + border.HasBorder(); + + auto EnsureBuildingDisplayList = [&] { + if (!aAutoBuildingDisplayList || *aAutoBuildingDisplayList) { + return; + } + nsPoint offset = aBuilder->GetCurrentFrame()->GetOffsetTo(aFrame); + aAutoBuildingDisplayList->emplace(aBuilder, aFrame, + aBuilder->GetVisibleRect() + offset, + aBuilder->GetDirtyRect() + offset); + }; + + // An auxiliary list is necessary in case we have background blending; if that + // is the case, background items need to be wrapped by a blend container to + // isolate blending to the background + nsDisplayList bgItemList(aBuilder); + // Even if we don't actually have a background color to paint, we may still + // need to create an item for hit testing and we still need to create an item + // for background-color animations. + if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) || + needsBackgroundColor) { + EnsureBuildingDisplayList(); + Maybe clipState; + nsRect bgColorRect = aBackgroundRect; + if (!isThemed && !aBuilder->IsForEventDelivery()) { + // Disable the will-paint-border optimization for background + // colors with no border-radius. Enabling it for background colors + // doesn't help much (there are no tiling issues) and clipping the + // background breaks detection of the element's border-box being + // opaque. For nonzero border-radius we still need it because we + // want to inset the background if possible to avoid antialiasing + // artifacts along the rounded corners. + const bool useWillPaintBorderOptimization = + willPaintBorder && + nsLayoutUtils::HasNonZeroCorner(border.mBorderRadius); + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + bg->BottomLayer(), aFrame, border, aBackgroundRect, aBackgroundRect, + useWillPaintBorderOptimization, + aFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + bgColorRect = bgColorRect.Intersect(clip.mBGClipArea); + if (clip.mHasAdditionalBGClipArea) { + bgColorRect = bgColorRect.Intersect(clip.mAdditionalBGClipArea); + } + if (clip.mHasRoundedCorners) { + clipState.emplace(aBuilder); + clipState->ClipContentDescendants(clip.mBGClipArea, clip.mRadii); + } + } + + nsDisplayBackgroundColor* bgItem = CreateBackgroundColor( + aBuilder, aFrame, aSecondaryReferenceFrame, bgColorRect, bgSC, + drawBackgroundColor ? color : NS_RGBA(0, 0, 0, 0)); + + if (bgItem) { + bgItemList.AppendToTop(bgItem); + } + } + + if (isThemed) { + nsDisplayThemedBackground* bgItem = CreateThemedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, aBackgroundRect); + + if (bgItem) { + bgItem->Init(aBuilder); + bgItemList.AppendToTop(bgItem); + } + + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::ThemedBackground; + } + + return AppendedBackgroundType::None; + } + + if (!drawBackgroundImage) { + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::Background; + } + + return AppendedBackgroundType::None; + } + + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + + bool needBlendContainer = false; + const nsRect& bgOriginRect = + aBackgroundOriginRect.IsEmpty() ? aBackgroundRect : aBackgroundOriginRect; + + // Passing bg == nullptr in this macro will result in one iteration with + // i = 0. + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, bg->mImage) { + if (bg->mImage.mLayers[i].mImage.IsNone()) { + continue; + } + + EnsureBuildingDisplayList(); + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + needBlendContainer = true; + } + + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + if (!aBuilder->IsForEventDelivery()) { + const nsStyleImageLayers::Layer& layer = bg->mImage.mLayers[i]; + SetBackgroundClipRegion(clipState, aFrame, layer, aBackgroundRect, + willPaintBorder); + } + + nsDisplayList thisItemList(aBuilder); + nsDisplayBackgroundImage::InitData bgData = + nsDisplayBackgroundImage::GetInitData(aBuilder, aFrame, i, bgOriginRect, + bgSC); + + if (bgData.shouldFixToViewport) { + auto* displayData = aBuilder->GetCurrentFixedBackgroundDisplayData(); + nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList( + aBuilder, aFrame, aBuilder->GetVisibleRect(), + aBuilder->GetDirtyRect()); + + nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter( + aBuilder); + if (displayData) { + asrSetter.SetCurrentActiveScrolledRoot( + displayData->mContainingBlockActiveScrolledRoot); + asrSetter.SetCurrentScrollParentId(displayData->mScrollParentId); + if (nsLayoutUtils::UsesAsyncScrolling(aFrame)) { + // Override the dirty rect on the builder to be the dirty rect of + // the viewport. + // displayData->mDirtyRect is relative to the presshell's viewport + // frame (the root frame), and we need it to be relative to aFrame. + nsIFrame* rootFrame = + aBuilder->CurrentPresShellState()->mPresShell->GetRootFrame(); + // There cannot be any transforms between aFrame and rootFrame + // because then bgData.shouldFixToViewport would have been false. + nsRect visibleRect = + displayData->mVisibleRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetVisibleRect(visibleRect); + nsRect dirtyRect = + displayData->mDirtyRect + aFrame->GetOffsetTo(rootFrame); + aBuilder->SetDirtyRect(dirtyRect); + } + } + + nsDisplayBackgroundImage* bgItem = nullptr; + { + // The clip is captured by the nsDisplayFixedPosition, so clear the + // clip for the nsDisplayBackgroundImage inside. + DisplayListClipState::AutoSaveRestore bgImageClip(aBuilder); + bgImageClip.Clear(); + bgItem = CreateBackgroundImage(aBuilder, aFrame, + aSecondaryReferenceFrame, bgData); + } + if (bgItem) { + thisItemList.AppendToTop( + nsDisplayFixedPosition::CreateForFixedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i, asr)); + } + } else { // bgData.shouldFixToViewport == false + nsDisplayBackgroundImage* bgItem = CreateBackgroundImage( + aBuilder, aFrame, aSecondaryReferenceFrame, bgData); + if (bgItem) { + thisItemList.AppendToTop(bgItem); + } + } + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + // asr is scrolled. Even if we wrap a fixed background layer, that's + // fine, because the item will have a scrolled clip that limits the + // item with respect to asr. + if (aSecondaryReferenceFrame) { + const auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(i + 1, tableType); + + thisItemList.AppendNewToTopWithIndex( + aBuilder, aSecondaryReferenceFrame, index, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true); + } else { + thisItemList.AppendNewToTopWithIndex( + aBuilder, aFrame, i + 1, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, true); + } + } + bgItemList.AppendToTop(&thisItemList); + } + + if (needBlendContainer) { + bgItemList.AppendToTop( + nsDisplayBlendContainer::CreateForBackgroundBlendMode( + aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr)); + } + + if (!bgItemList.IsEmpty()) { + aList->AppendToTop(&bgItemList); + return AppendedBackgroundType::Background; + } + + return AppendedBackgroundType::None; +} + +// Check that the rounded border of aFrame, added to aToReferenceFrame, +// intersects aRect. Assumes that the unrounded border has already +// been checked for intersection. +static bool RoundedBorderIntersectsRect(nsIFrame* aFrame, + const nsPoint& aFrameToReferenceFrame, + const nsRect& aTestRect) { + if (!nsRect(aFrameToReferenceFrame, aFrame->GetSize()) + .Intersects(aTestRect)) { + return false; + } + + nscoord radii[8]; + return !aFrame->GetBorderRadii(radii) || + nsLayoutUtils::RoundedRectIntersectsRect( + nsRect(aFrameToReferenceFrame, aFrame->GetSize()), radii, + aTestRect); +} + +// Returns TRUE if aContainedRect is guaranteed to be contained in +// the rounded rect defined by aRoundedRect and aRadii. Complex cases are +// handled conservatively by returning FALSE in some situations where +// a more thorough analysis could return TRUE. +// +// See also RoundedRectIntersectsRect. +static bool RoundedRectContainsRect(const nsRect& aRoundedRect, + const nscoord aRadii[8], + const nsRect& aContainedRect) { + nsRegion rgn = nsLayoutUtils::RoundedRectIntersectRect(aRoundedRect, aRadii, + aContainedRect); + return rgn.Contains(aContainedRect); +} + +bool nsDisplayBackgroundImage::CanApplyOpacity( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + return CanBuildWebRenderDisplayItems(aManager, aBuilder); +} + +bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip != + StyleGeometryBox::Text && + nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + aManager, *StyleFrame()->PresContext(), StyleFrame(), + mBackgroundStyle->StyleBackground(), mLayer, + aBuilder->GetBackgroundPaintFlags()); +} + +bool nsDisplayBackgroundImage::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(), + aDisplayListBuilder)) { + return false; + } + + uint32_t paintFlags = aDisplayListBuilder->GetBackgroundPaintFlags(); + bool dummy; + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), GetBounds(aDisplayListBuilder, &dummy), + mBackgroundRect, StyleFrame(), paintFlags, mLayer, + CompositionOp::OP_OVER, aBuilder.GetInheritedOpacity()); + params.bgClipRect = &mBounds; + ImgDrawResult result = + nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + params, aBuilder, aResources, aSc, aManager, this); + if (result == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + + if (nsIContent* content = StyleFrame()->GetContent()) { + if (imgRequestProxy* requestProxy = mBackgroundStyle->StyleBackground() + ->mImage.mLayers[mLayer] + .mImage.GetImageRequest()) { + // LCP don't consider gradient backgrounds. + LCPHelpers::FinalizeLCPEntryForImage(content->AsElement(), requestProxy, + mBounds - ToReferenceFrame()); + } + } + + return true; +} + +void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray* aOutFrames) { + if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +static nsRect GetInsideClipRect(const nsDisplayItem* aItem, + StyleGeometryBox aClip, const nsRect& aRect, + const nsRect& aBackgroundRect) { + if (aRect.IsEmpty()) { + return {}; + } + + nsIFrame* frame = aItem->Frame(); + + nsRect clipRect = aBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast(frame); + clipRect = canvasFrame->CanvasArea() + aItem->ToReferenceFrame(); + } else if (aClip == StyleGeometryBox::PaddingBox || + aClip == StyleGeometryBox::ContentBox) { + nsMargin border = frame->GetUsedBorder(); + if (aClip == StyleGeometryBox::ContentBox) { + border += frame->GetUsedPadding(); + } + border.ApplySkipSides(frame->GetSkipSides()); + clipRect.Deflate(border); + } + + return clipRect.Intersect(aRect); +} + +nsRegion nsDisplayBackgroundImage::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (!mBackgroundStyle) { + return result; + } + + *aSnap = true; + + // For StyleBoxDecorationBreak::Slice, don't try to optimize here, since + // this could easily lead to O(N^2) behavior inside InlineBackgroundData, + // which expects frames to be sent to it in content order, not reverse + // content order which we'll produce here. + // Of course, if there's only one frame in the flow, it doesn't matter. + if (mFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone || + (!mFrame->GetPrevContinuation() && !mFrame->GetNextContinuation())) { + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + if (layer.mImage.IsOpaque() && layer.mBlendMode == StyleBlend::Normal && + layer.mRepeat.mXRepeat != StyleImageLayerRepeat::Space && + layer.mRepeat.mYRepeat != StyleImageLayerRepeat::Space && + layer.mClip != StyleGeometryBox::Text) { + result = GetInsideClipRect(this, layer.mClip, mBounds, mBackgroundRect); + } + } + + return result; +} + +Maybe nsDisplayBackgroundImage::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (!mBackgroundStyle) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayBackgroundImage::GetPositioningArea() const { + if (!mBackgroundStyle) { + return nsRect(); + } + nsIFrame* attachedToFrame; + bool transformedFixed; + return nsCSSRendering::ComputeImageLayerPositioningArea( + mFrame->PresContext(), mFrame, mBackgroundRect, + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer], + &attachedToFrame, &transformedFixed) + + ToReferenceFrame(); +} + +bool nsDisplayBackgroundImage::RenderingMightDependOnPositioningAreaSizeChange() + const { + if (!mBackgroundStyle) { + return false; + } + + nscoord radii[8]; + if (mFrame->GetBorderRadii(radii)) { + // A change in the size of the positioning area might change the position + // of the rounded corners. + return true; + } + + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + return layer.RenderingMightDependOnPositioningAreaSizeChange(); +} + +void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), &mBounds); +} + +void nsDisplayBackgroundImage::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + gfxContext* ctx = aCtx; + StyleGeometryBox clip = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip; + + if (clip == StyleGeometryBox::Text) { + if (!GenerateAndPushTextMask(StyleFrame(), aCtx, mBackgroundRect, + aBuilder)) { + return; + } + } + + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), aBounds, mBackgroundRect, StyleFrame(), + aBuilder->GetBackgroundPaintFlags(), mLayer, CompositionOp::OP_OVER, + 1.0f); + params.bgClipRect = aClipRect; + Unused << nsCSSRendering::PaintStyleImageLayer(params, *aCtx); + + if (clip == StyleGeometryBox::Text) { + ctx->PopGroupAndBlend(); + } +} + +void nsDisplayBackgroundImage::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + if (!mBackgroundStyle) { + return; + } + + const auto* geometry = + static_cast(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (positioningArea.TopLeft() != geometry->mPositioningArea.TopLeft() || + (positioningArea.Size() != geometry->mPositioningArea.Size() && + RenderingMightDependOnPositioningAreaSizeChange())) { + // Positioning area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!mDestRect.IsEqualInterior(geometry->mDestRect)) { + // Dest area changed in a way that could cause everything to change, + // so invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } +} + +nsRect nsDisplayBackgroundImage::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayBackgroundImage::GetBoundsInternal( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrameForBounds) { + // This allows nsDisplayTableBackgroundImage to change the frame used for + // bounds calculation. + nsIFrame* frame = aFrameForBounds ? aFrameForBounds : mFrame; + + nsPresContext* presContext = frame->PresContext(); + + if (!mBackgroundStyle) { + return nsRect(); + } + + nsRect clipRect = mBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast(frame); + clipRect = canvasFrame->CanvasArea() + ToReferenceFrame(); + } + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + return nsCSSRendering::GetBackgroundLayerRect( + presContext, frame, mBackgroundRect, clipRect, layer, + aBuilder->GetBackgroundPaintFlags()); +} + +nsDisplayTableBackgroundImage::nsDisplayTableBackgroundImage( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const InitData& aData, + nsIFrame* aCellFrame) + : nsDisplayBackgroundImage(aBuilder, aFrame, aData, aCellFrame), + mStyleFrame(aCellFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mStyleFrame->AddDisplayItem(this); + } +} + +nsDisplayTableBackgroundImage::~nsDisplayTableBackgroundImage() { + if (mStyleFrame) { + mStyleFrame->RemoveDisplayItem(this); + } +} + +bool nsDisplayTableBackgroundImage::IsInvalid(nsRect& aRect) const { + bool result = mStyleFrame ? mStyleFrame->IsInvalid(aRect) : false; + aRect += ToReferenceFrame(); + return result; +} + +nsDisplayThemedBackground::nsDisplayThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect) + : nsPaintedDisplayItem(aBuilder, aFrame), mBackgroundRect(aBackgroundRect) { + MOZ_COUNT_CTOR(nsDisplayThemedBackground); +} + +void nsDisplayThemedBackground::Init(nsDisplayListBuilder* aBuilder) { + const nsStyleDisplay* disp = StyleFrame()->StyleDisplay(); + mAppearance = disp->EffectiveAppearance(); + StyleFrame()->IsThemed(disp, &mThemeTransparency); + + // Perform necessary RegisterThemeGeometry + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + nsITheme::ThemeGeometryType type = + theme->ThemeGeometryTypeForWidget(StyleFrame(), mAppearance); + if (type != nsITheme::eThemeGeometryTypeUnknown) { + RegisterThemeGeometry(aBuilder, this, StyleFrame(), type); + } + + mBounds = GetBoundsInternal(); +} + +void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (themed, appearance:" << (int)mAppearance << ")"; +} + +void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray* aOutFrames) { + // Assume that any point in our background rect is a hit. + if (mBackgroundRect.Intersects(aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +nsRegion nsDisplayThemedBackground::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + nsRegion result; + *aSnap = false; + + if (mThemeTransparency == nsITheme::eOpaque) { + *aSnap = true; + result = mBackgroundRect; + } + return result; +} + +Maybe nsDisplayThemedBackground::IsUniform( + nsDisplayListBuilder* aBuilder) const { + return Nothing(); +} + +nsRect nsDisplayThemedBackground::GetPositioningArea() const { + return mBackgroundRect; +} + +void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(aBuilder, aCtx), nullptr); +} + +void nsDisplayThemedBackground::PaintInternal(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const nsRect& aBounds, + nsRect* aClipRect) { + // XXXzw this ignores aClipRect. + nsPresContext* presContext = StyleFrame()->PresContext(); + nsITheme* theme = presContext->Theme(); + nsRect drawing(mBackgroundRect); + theme->GetWidgetOverflow(presContext->DeviceContext(), StyleFrame(), + mAppearance, &drawing); + drawing.IntersectRect(drawing, aBounds); + theme->DrawWidgetBackground(aCtx, StyleFrame(), mAppearance, mBackgroundRect, + drawing); +} + +bool nsDisplayThemedBackground::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, + aManager, StyleFrame(), + mAppearance, mBackgroundRect); +} + +bool nsDisplayThemedBackground::IsWindowActive() const { + return !mFrame->PresContext()->Document()->IsTopLevelWindowInactive(); +} + +void nsDisplayThemedBackground::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast(aGeometry); + + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRect positioningArea = GetPositioningArea(); + if (!positioningArea.IsEqualInterior(geometry->mPositioningArea)) { + // Invalidate everything (both old and new painting areas). + aInvalidRegion->Or(bounds, geometry->mBounds); + return; + } + if (!bounds.IsEqualInterior(geometry->mBounds)) { + // Positioning area is unchanged, so invalidate just the change in the + // painting area. + aInvalidRegion->Xor(bounds, geometry->mBounds); + } + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + if (theme->WidgetAppearanceDependsOnWindowFocus(mAppearance) && + IsWindowActive() != geometry->mWindowIsActive) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + } +} + +nsRect nsDisplayThemedBackground::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +nsRect nsDisplayThemedBackground::GetBoundsInternal() { + nsPresContext* presContext = mFrame->PresContext(); + + nsRect r = mBackgroundRect - ToReferenceFrame(); + presContext->Theme()->GetWidgetOverflow( + presContext->DeviceContext(), mFrame, + mFrame->StyleDisplay()->EffectiveAppearance(), &r); + return r + ToReferenceFrame(); +} + +#if defined(MOZ_REFLOW_PERF_DSP) && defined(MOZ_REFLOW_PERF) +void nsDisplayReflowCount::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + mFrame->PresShell()->PaintCount(mFrameName, aCtx, mFrame->PresContext(), + mFrame, ToReferenceFrame(), mColor); +} +#endif + +bool nsDisplayBackgroundColor::CanApplyOpacity( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder) const { + // Don't apply opacity if the background color is animated since the color is + // going to be changed on the compositor. + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR); +} + +bool nsDisplayBackgroundColor::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + gfx::sRGBColor color = mColor; + color.a *= aBuilder.GetInheritedOpacity(); + + if (color == sRGBColor() && + !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return true; + } + + if (HasBackgroundClipText()) { + return false; + } + + uint64_t animationsId = 0; + // We don't support background-color animations on table elements yet. + if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + } + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + + if (animationsId) { + wr::WrAnimationProperty prop{ + wr::WrAnimationType::BackgroundColor, + animationsId, + }; + aBuilder.PushRectWithAnimation(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(color)), &prop); + } else { + aBuilder.StartGroup(this); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), false, false, + wr::ToColorF(ToDeviceColor(color))); + aBuilder.FinishGroup(); + } + + return true; +} + +void nsDisplayBackgroundColor::PaintWithClip(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + const DisplayItemClip& aClip) { + MOZ_ASSERT(!HasBackgroundClipText()); + + if (mColor == sRGBColor()) { + return; + } + + nsRect fillRect = mBackgroundRect; + if (aClip.HasClip()) { + fillRect.IntersectRect(fillRect, aClip.GetClipRect()); + } + + DrawTarget* dt = aCtx->GetDrawTarget(); + int32_t A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + Rect bounds = ToRect(nsLayoutUtils::RectToGfxRect(fillRect, A2D)); + MaybeSnapToDevicePixels(bounds, *dt); + ColorPattern fill(ToDeviceColor(mColor)); + + if (aClip.GetRoundedRectCount()) { + MOZ_ASSERT(aClip.GetRoundedRectCount() == 1); + + AutoTArray roundedRect; + aClip.AppendRoundedRects(&roundedRect); + + bool pushedClip = false; + if (!fillRect.Contains(roundedRect[0].mRect)) { + dt->PushClipRect(bounds); + pushedClip = true; + } + + RectCornerRadii pixelRadii; + nsCSSRendering::ComputePixelRadii(roundedRect[0].mRadii, A2D, &pixelRadii); + dt->FillRoundedRect( + RoundedRect(NSRectToSnappedRect(roundedRect[0].mRect, A2D, *dt), + pixelRadii), + fill); + if (pushedClip) { + dt->PopClip(); + } + } else { + dt->FillRect(bounds, fill); + } +} + +void nsDisplayBackgroundColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + if (mColor == sRGBColor()) { + return; + } + +#if 0 + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1148418#c21 for why this + // results in a precision induced rounding issue that makes the rect one + // pixel shorter in rare cases. Disabled in favor of the old code for now. + // Note that the pref layout.css.devPixelsPerPx needs to be set to 1 to + // reproduce the bug. + // + // TODO: + // This new path does not include support for background-clip:text; need to + // be fixed if/when we switch to this new code path. + + DrawTarget& aDrawTarget = *aCtx->GetDrawTarget(); + + Rect rect = NSRectToSnappedRect(mBackgroundRect, + mFrame->PresContext()->AppUnitsPerDevPixel(), + aDrawTarget); + ColorPattern color(ToDeviceColor(mColor)); + aDrawTarget.FillRect(rect, color); +#else + gfxContext* ctx = aCtx; + gfxRect bounds = nsLayoutUtils::RectToGfxRect( + mBackgroundRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (HasBackgroundClipText()) { + if (!GenerateAndPushTextMask(mFrame, aCtx, mBackgroundRect, aBuilder)) { + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); + ctx->PopGroupAndBlend(); + return; + } + + ctx->SetColor(mColor); + ctx->NewPath(); + ctx->SnappedRectangle(bounds); + ctx->Fill(); +#endif +} + +nsRegion nsDisplayBackgroundColor::GetOpaqueRegion( + nsDisplayListBuilder* aBuilder, bool* aSnap) const { + *aSnap = false; + + if (mColor.a != 1 || + // Even if the current alpha channel is 1, we treat this item as if it's + // non-opaque if there is a background-color animation since the animation + // might change the alpha channel. + EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return nsRegion(); + } + + if (!mHasStyle || HasBackgroundClipText()) { + return nsRegion(); + } + + *aSnap = true; + return GetInsideClipRect(this, mBottomLayerClip, mBackgroundRect, + mBackgroundRect); +} + +Maybe nsDisplayBackgroundColor::IsUniform( + nsDisplayListBuilder* aBuilder) const { + return Some(mColor.ToABGR()); +} + +void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << "," + << mColor.a << ")"; + aStream << " backgroundRect" << mBackgroundRect; +} + +nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); +} + +nsRect nsDisplayOutline::GetInnerRect() const { + if (nsRect* savedOutlineInnerRect = + mFrame->GetProperty(nsIFrame::OutlineInnerRectProperty())) { + return *savedOutlineInnerRect; + } + return mFrame->GetRectRelativeToSelf(); +} + +void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // TODO join outlines together + MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(), + "Should have not created a nsDisplayOutline!"); + + nsRect rect = GetInnerRect() + ToReferenceFrame(); + nsPresContext* pc = mFrame->PresContext(); + if (IsThemedOutline()) { + rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect)); + pc->Theme()->DrawWidgetBackground(aCtx, mFrame, + StyleAppearance::FocusOutline, rect, + GetPaintRect(aBuilder, aCtx)); + return; + } + + nsCSSRendering::PaintNonThemedOutline( + pc, *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), rect, mFrame->Style()); +} + +bool nsDisplayOutline::IsThemedOutline() const { +#ifdef DEBUG + nsPresContext* pc = mFrame->PresContext(); + MOZ_ASSERT( + pc->Theme()->ThemeSupportsWidget(pc, mFrame, + StyleAppearance::FocusOutline), + "All of our supported platforms have support for themed focus-outlines"); +#endif + return mFrame->StyleOutline()->mOutlineStyle.IsAuto(); +} + +bool nsDisplayOutline::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsPresContext* pc = mFrame->PresContext(); + nsRect rect = GetInnerRect() + ToReferenceFrame(); + if (IsThemedOutline()) { + rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect)); + return pc->Theme()->CreateWebRenderCommandsForWidget( + aBuilder, aResources, aSc, aManager, mFrame, + StyleAppearance::FocusOutline, rect); + } + + bool dummy; + Maybe borderRenderer = + nsCSSRendering::CreateBorderRendererForNonThemedOutline( + pc, /* aDrawTarget = */ nullptr, mFrame, + GetBounds(aDisplayListBuilder, &dummy), rect, mFrame->Style()); + + if (!borderRenderer) { + // No border renderer means "there is no outline". + // Paint nothing and return success. + return true; + } + + borderRenderer->CreateWebRenderCommands(this, aBuilder, aResources, aSc); + return true; +} + +bool nsDisplayOutline::HasRadius() const { + const auto& radius = mFrame->StyleBorder()->mBorderRadius; + return !nsLayoutUtils::HasNonZeroCorner(radius); +} + +bool nsDisplayOutline::IsInvisibleInRect(const nsRect& aRect) const { + const nsStyleOutline* outline = mFrame->StyleOutline(); + nsRect borderBox(ToReferenceFrame(), mFrame->GetSize()); + if (borderBox.Contains(aRect) && !HasRadius() && + outline->mOutlineOffset.ToCSSPixels() >= 0.0f) { + // aRect is entirely inside the border-rect, and the outline isn't rendered + // inside the border-rect, so the outline is not visible. + return true; + } + return false; +} + +void nsDisplayEventReceiver::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return true; +} + +int32_t nsDisplayCompositorHitTestInfo::ZIndex() const { + return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex(); +} + +void nsDisplayCompositorHitTestInfo::SetOverrideZIndex(int32_t aZIndex) { + mOverrideZIndex = Some(aZIndex); +} + +nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder, + nsIFrame* aCaretFrame) + : nsPaintedDisplayItem(aBuilder, aCaretFrame), + mCaret(aBuilder->GetCaret()), + mBounds(aBuilder->GetCaretRect() + ToReferenceFrame()) { + MOZ_COUNT_CTOR(nsDisplayCaret); + // The presence of a caret doesn't change the overflow rect + // of the owning frame, so the normal building rect might not + // include the caret at all. We use MarkFrameForDisplay to ensure + // we build this item, and here we override the building rect + // to cover the pixels we're going to draw. + SetBuildingRect(mBounds); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayCaret::~nsDisplayCaret() { MOZ_COUNT_DTOR(nsDisplayCaret); } +#endif + +nsRect nsDisplayCaret::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + // The caret returns a rect in the coordinates of mFrame. + return mBounds; +} + +void nsDisplayCaret::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // Note: Because we exist, we know that the caret is visible, so we don't + // need to check for the caret's visibility. + mCaret->PaintCaret(*aCtx->GetDrawTarget(), mFrame, ToReferenceFrame()); +} + +bool nsDisplayCaret::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + using namespace layers; + nsRect caretRect; + nsRect hookRect; + nscolor caretColor; + nsIFrame* frame = + mCaret->GetPaintGeometry(&caretRect, &hookRect, &caretColor); + MOZ_ASSERT(frame == mFrame, "We're referring different frame"); + if (!frame) { + return true; + } + + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + gfx::DeviceColor color = ToDeviceColor(caretColor); + LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits( + caretRect + ToReferenceFrame(), appUnitsPerDevPixel); + LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits( + hookRect + ToReferenceFrame(), appUnitsPerDevPixel); + + wr::LayoutRect caret = wr::ToLayoutRect(devCaretRect); + wr::LayoutRect hook = wr::ToLayoutRect(devHookRect); + + // Note, WR will pixel snap anything that is layout aligned. + aBuilder.PushRect(caret, caret, !BackfaceIsHidden(), false, false, + wr::ToColorF(color)); + + if (!devHookRect.IsEmpty()) { + aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), false, false, + wr::ToColorF(color)); + } + return true; +} + +nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBorder); + + mBounds = CalculateBounds(*mFrame->StyleBorder()); +} + +bool nsDisplayBorder::IsInvisibleInRect(const nsRect& aRect) const { + nsRect paddingRect = GetPaddingRect(); + const nsStyleBorder* styleBorder; + if (paddingRect.Contains(aRect) && + !(styleBorder = mFrame->StyleBorder())->IsBorderImageSizeAvailable() && + !nsLayoutUtils::HasNonZeroCorner(styleBorder->mBorderRadius)) { + // aRect is entirely inside the content rect, and no part + // of the border is rendered inside the content rect, so we are not + // visible + // Skip this if there's a border-image (which draws a background + // too) or if there is a border-radius (which makes the border draw + // further in). + return true; + } + + return false; +} + +nsDisplayItemGeometry* nsDisplayBorder::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayBorderGeometry(this, aBuilder); +} + +void nsDisplayBorder::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = static_cast(aGeometry); + bool snap; + + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap))) { + // We can probably get away with only invalidating the difference + // between the border and padding rects, but the XUL ui at least + // is apparently painting a background with this? + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } +} + +bool nsDisplayBorder::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize()); + + ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder( + this, mFrame, rect, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + + if (drawResult == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + return true; +}; + +void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + + PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SyncDecodeImages + : PaintBorderFlags(); + + Unused << nsCSSRendering::PaintBorder( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(aBuilder, aCtx), + nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags, + mFrame->GetSkipSides()); +} + +nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + nsPresContext* presContext = mFrame->PresContext(); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS); + + nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect, + GetPaintRect(aBuilder, aCtx), 1.0f); +} + +nsRect nsDisplayBoxShadowOuter::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRect nsDisplayBoxShadowOuter::GetBoundsInternal() { + return nsLayoutUtils::GetBoxShadowRectForFrame(mFrame, mFrame->GetSize()) + + ToReferenceFrame(); +} + +bool nsDisplayBoxShadowOuter::IsInvisibleInRect(const nsRect& aRect) const { + nsPoint origin = ToReferenceFrame(); + nsRect frameRect(origin, mFrame->GetSize()); + if (!frameRect.Contains(aRect)) { + return false; + } + + // the visible region is entirely inside the border-rect, and box shadows + // never render within the border-rect (unless there's a border radius). + nscoord twipsRadii[8]; + bool hasBorderRadii = mFrame->GetBorderRadii(twipsRadii); + if (!hasBorderRadii) { + return true; + } + + return RoundedRectContainsRect(frameRect, twipsRadii, aRect); +} + +bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() const { + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + bool hasBorderRadius; + // We don't support native themed things yet like box shadows around + // input buttons. + // + // TODO(emilio): The non-native theme could provide the right rect+radius + // instead relatively painlessly, if we find this causes performance issues or + // what not. + return !nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); +} + +bool nsDisplayBoxShadowOuter::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems()) { + return false; + } + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + bool snap; + nsRect bounds = GetBounds(aDisplayListBuilder, &snap); + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); + + // Don't need the full size of the shadow rect like we do in + // nsCSSRendering since WR takes care of calculations for blur + // and spread radius. + nsRect frameRect = + nsCSSRendering::GetShadowRect(borderRect, nativeTheme, mFrame); + + RectCornerRadii borderRadii; + if (hasBorderRadius) { + hasBorderRadius = nsCSSRendering::GetBorderRadii(frameRect, borderRect, + mFrame, borderRadii); + } + + // Everything here is in app units, change to device units. + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(bounds, appUnitsPerDevPixel); + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + MOZ_ASSERT(!shadows.IsEmpty()); + + for (const auto& shadow : Reversed(shadows)) { + if (shadow.inset) { + continue; + } + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + gfx::sRGBColor shadowColor = nsCSSRendering::GetShadowColor( + shadow.base, mFrame, aBuilder.GetInheritedOpacity()); + + // We don't move the shadow rect here since WR does it for us + // Now translate everything to device pixels. + const nsRect& shadowRect = frameRect; + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + LayoutDeviceRect deviceBox = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceBoxRect = wr::ToLayoutRect(deviceBox); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + + LayoutDeviceSize zeroSize; + wr::BorderRadius borderRadius = + wr::ToBorderRadius(zeroSize, zeroSize, zeroSize, zeroSize); + if (hasBorderRadius) { + borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(borderRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(borderRadii.BottomRight())); + } + + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow(deviceBoxRect, deviceClipRect, !BackfaceIsHidden(), + deviceBoxRect, wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, + spreadRadius, borderRadius, + wr::BoxShadowClipMode::Outset); + } + + return true; +} + +void nsDisplayBoxShadowOuter::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + nsRegion oldShadow, newShadow; + nscoord dontCare[8]; + bool hasBorderRadius = mFrame->GetBorderRadii(dontCare); + if (hasBorderRadius) { + // If we have rounded corners then we need to invalidate the frame area + // too since we paint into it. + oldShadow = geometry->mBounds; + newShadow = GetBounds(aBuilder, &snap); + } else { + oldShadow.Sub(geometry->mBounds, geometry->mBorderRect); + newShadow.Sub(GetBounds(aBuilder, &snap), GetBorderRect()); + } + aInvalidRegion->Or(oldShadow, newShadow); + } +} + +void nsDisplayBoxShadowInner::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsPresContext* presContext = mFrame->PresContext(); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS); + + nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect); +} + +bool nsDisplayBoxShadowInner::CanCreateWebRenderCommands( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsPoint& aReferenceOffset) { + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + // Means we don't have to paint anything + return true; + } + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(aFrame, hasBorderRadius); + + // We don't support native themed things yet like box shadows around + // input buttons. + return !nativeTheme; +} + +/* static */ +void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc, + nsRect& aVisibleRect, nsIFrame* aFrame, const nsRect& aBorderRect) { + if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) { + return; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(aVisibleRect, appUnitsPerDevPixel); + + for (const auto& shadow : Reversed(shadows)) { + if (!shadow.inset) { + continue; + } + + nsRect shadowRect = + nsCSSRendering::GetBoxShadowInnerPaddingRect(aFrame, aBorderRect); + RectCornerRadii innerRadii; + nsCSSRendering::GetShadowInnerRadii(aFrame, aBorderRect, innerRadii); + + // Now translate everything to device pixels. + LayoutDeviceRect deviceBoxRect = + LayoutDeviceRect::FromAppUnits(shadowRect, appUnitsPerDevPixel); + wr::LayoutRect deviceClipRect = wr::ToLayoutRect(clipRect); + sRGBColor shadowColor = + nsCSSRendering::GetShadowColor(shadow.base, aFrame, 1.0); + + LayoutDevicePoint shadowOffset = LayoutDevicePoint::FromAppUnits( + nsPoint(shadow.base.horizontal.ToAppUnits(), + shadow.base.vertical.ToAppUnits()), + appUnitsPerDevPixel); + + float blurRadius = + float(shadow.base.blur.ToAppUnits()) / float(appUnitsPerDevPixel); + + wr::BorderRadius borderRadius = wr::ToBorderRadius( + LayoutDeviceSize::FromUnknownSize(innerRadii.TopLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.TopRight()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomLeft()), + LayoutDeviceSize::FromUnknownSize(innerRadii.BottomRight())); + // NOTE: Any spread radius > 0 will render nothing. WR Bug. + float spreadRadius = + float(shadow.spread.ToAppUnits()) / float(appUnitsPerDevPixel); + + aBuilder.PushBoxShadow( + wr::ToLayoutRect(deviceBoxRect), deviceClipRect, + !aFrame->BackfaceIsHidden(), wr::ToLayoutRect(deviceBoxRect), + wr::ToLayoutVector2D(shadowOffset), + wr::ToColorF(ToDeviceColor(shadowColor)), blurRadius, spreadRadius, + borderRadius, wr::BoxShadowClipMode::Inset); + } +} + +bool nsDisplayBoxShadowInner::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame, + ToReferenceFrame())) { + return false; + } + + bool snap; + nsRect visible = GetBounds(aDisplayListBuilder, &snap); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + aBuilder, aSc, visible, mFrame, borderRect); + + return true; +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), false) {} + +nsDisplayWrapList::nsDisplayWrapList( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsPaintedDisplayItem(aBuilder, aFrame, aActiveScrolledRoot), + mList(aBuilder), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false), + mClearingClipChain(aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aList); + mOriginalClipChain = mClipChain; + nsDisplayWrapList::UpdateBounds(aBuilder); +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayItem* aItem) + : nsPaintedDisplayItem(aBuilder, aFrame, + aBuilder->CurrentActiveScrolledRoot()), + mList(aBuilder), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aItem); + mOriginalClipChain = mClipChain; + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // See the previous nsDisplayWrapList constructor + if (aItem->Frame() == aFrame) { + mToReferenceFrame = aItem->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); } + +void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { + mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + bool snap; + return ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), + GetBounds(aBuilder, &snap)); +} + +Maybe nsDisplayWrapList::IsUniform( + nsDisplayListBuilder* aBuilder) const { + // We could try to do something but let's conservatively just return Nothing. + return Nothing(); +} + +void nsDisplayWrapper::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + NS_ERROR("nsDisplayWrapper should have been flattened away for painting"); +} + +nsRect nsDisplayWrapList::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mListPtr->GetComponentAlphaBounds(aBuilder); +} + +bool nsDisplayWrapList::CreateWebRenderCommandsNewClipListOption( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, bool aNewClipList) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources, + aNewClipList); + return true; +} + +static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsDisplayItemWrapper* aWrapper) { + if (!aList->GetTop()) { + return NS_OK; + } + nsDisplayItem* item = aWrapper->WrapList(aBuilder, aFrame, aList); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + // aList was emptied + aList->AppendToTop(item); + return NS_OK; +} + +static nsresult WrapEachDisplayItem(nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsDisplayItemWrapper* aWrapper) { + for (nsDisplayItem* item : aList->TakeItems()) { + item = aWrapper->WrapItem(aBuilder, item); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + aList->AppendToTop(item); + } + // aList was emptied + return NS_OK; +} + +nsresult nsDisplayItemWrapper::WrapLists(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + const nsDisplayListSet& aIn, + const nsDisplayListSet& aOut) { + nsresult rv = WrapListsInPlace(aBuilder, aFrame, aIn); + NS_ENSURE_SUCCESS(rv, rv); + + if (&aOut == &aIn) { + return NS_OK; + } + aOut.BorderBackground()->AppendToTop(aIn.BorderBackground()); + aOut.BlockBorderBackgrounds()->AppendToTop(aIn.BlockBorderBackgrounds()); + aOut.Floats()->AppendToTop(aIn.Floats()); + aOut.Content()->AppendToTop(aIn.Content()); + aOut.PositionedDescendants()->AppendToTop(aIn.PositionedDescendants()); + aOut.Outlines()->AppendToTop(aIn.Outlines()); + return NS_OK; +} + +nsresult nsDisplayItemWrapper::WrapListsInPlace( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsDisplayListSet& aLists) { + nsresult rv; + if (WrapBorderBackground()) { + // Our border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BorderBackground(), this); + NS_ENSURE_SUCCESS(rv, rv); + } + // Our block border-backgrounds are in-flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.BlockBorderBackgrounds(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The floats are not in flow + rv = WrapEachDisplayItem(aBuilder, aLists.Floats(), this); + NS_ENSURE_SUCCESS(rv, rv); + // Our child content is in flow + rv = WrapDisplayList(aBuilder, aFrame, aLists.Content(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The positioned descendants may not be in-flow + rv = WrapEachDisplayItem(aBuilder, aLists.PositionedDescendants(), this); + NS_ENSURE_SUCCESS(rv, rv); + // The outlines may not be in-flow + return WrapEachDisplayItem(aBuilder, aLists.Outlines(), this); +} + +nsDisplayOpacity::nsDisplayOpacity( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aForEventsOnly, + bool aNeedsActiveLayer, bool aWrapsBackdropFilter) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mOpacity(aFrame->StyleEffects()->mOpacity), + mForEventsOnly(aForEventsOnly), + mNeedsActiveLayer(aNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayOpacity); +} + +void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray* aOutFrames) { + AutoRestore opacity(aState->mCurrentOpacity); + aState->mCurrentOpacity *= mOpacity; + + // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should + // only consider fully opaque items? Or make this configurable somehow? + if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) { + return; + } + nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // The only time where mOpacity == 1.0 should be when we have will-change. + // We could report this as opaque then but when the will-change value starts + // animating the element would become non opaque and could cause repaints. + return nsRegion(); +} + +void nsDisplayOpacity::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + if (GetOpacity() == 0.0f) { + return; + } + + if (GetOpacity() == 1.0f) { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + return; + } + + // TODO: Compute a bounds rect to pass to PushLayer for a smaller + // allocation. + aCtx->GetDrawTarget()->PushLayer(false, GetOpacity(), nullptr, gfx::Matrix()); + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + aCtx->GetDrawTarget()->PopLayer(); +} + +/* static */ +bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_OPACITY) || + (ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties())); +} + +bool nsDisplayOpacity::CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const { + return !EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_OPACITY); +} + +// Only try folding our opacity down if we have at most |kOpacityMaxChildCount| +// children that don't overlap and can all apply the opacity to themselves. +static const size_t kOpacityMaxChildCount = 3; + +// |kOpacityMaxListSize| defines an early exit condition for opacity items that +// are likely have more child items than |kOpacityMaxChildCount|. +static const size_t kOpacityMaxListSize = kOpacityMaxChildCount * 2; + +/** + * Recursively iterates through |aList| and collects at most + * |kOpacityMaxChildCount| display item pointers to items that return true for + * CanApplyOpacity(). The item pointers are added to |aArray|. + * + * LayerEventRegions and WrapList items are ignored. + * + * We need to do this recursively, because the child display items might contain + * nested nsDisplayWrapLists. + * + * Returns false if there are more than |kOpacityMaxChildCount| items, or if an + * item that returns false for CanApplyOpacity() is encountered. + * Otherwise returns true. + */ +static bool CollectItemsWithOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder, + nsDisplayList* aList, + nsTArray& aArray) { + if (aList->Length() > kOpacityMaxListSize) { + // Exit early, since |aList| will likely contain more than + // |kOpacityMaxChildCount| items. + return false; + } + + for (nsDisplayItem* i : *aList) { + const DisplayItemType type = i->GetType(); + + if (type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + continue; + } + + // Descend only into wraplists. + if (type == DisplayItemType::TYPE_WRAP_LIST || + type == DisplayItemType::TYPE_CONTAINER) { + // The current display item has children, process them first. + if (!CollectItemsWithOpacity(aManager, aBuilder, i->GetChildren(), + aArray)) { + return false; + } + + continue; + } + + if (aArray.Length() == kOpacityMaxChildCount) { + return false; + } + + auto* item = i->AsPaintedDisplayItem(); + if (!item || !item->CanApplyOpacity(aManager, aBuilder)) { + return false; + } + + aArray.AppendElement(item); + } + + return true; +} + +bool nsDisplayOpacity::CanApplyToChildren(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) { + if (mChildOpacityState == ChildOpacityState::Deferred) { + return false; + } + + // Iterate through the child display list and copy at most + // |kOpacityMaxChildCount| child display item pointers to a temporary list. + AutoTArray items; + if (!CollectItemsWithOpacity(aManager, aBuilder, &mList, items)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + + struct { + nsPaintedDisplayItem* item{}; + nsRect bounds; + } children[kOpacityMaxChildCount]; + + bool snap; + size_t childCount = 0; + for (nsPaintedDisplayItem* item : items) { + children[childCount].item = item; + children[childCount].bounds = item->GetBounds(aBuilder, &snap); + childCount++; + } + + for (size_t i = 0; i < childCount; i++) { + for (size_t j = i + 1; j < childCount; j++) { + if (children[i].bounds.Intersects(children[j].bounds)) { + mChildOpacityState = ChildOpacityState::Deferred; + return false; + } + } + } + + mChildOpacityState = ChildOpacityState::Applied; + return true; +} + +/** + * Returns true if this nsDisplayOpacity contains only a filter or a mask item + * that has the same frame as the opacity item, and that supports painting with + * opacity. In this case the opacity item can be optimized away. + */ +bool nsDisplayOpacity::ApplyToMask() { + if (mList.Length() != 1) { + return false; + } + + nsDisplayItem* item = mList.GetBottom(); + if (item->Frame() != mFrame) { + // The effect item needs to have the same frame as the opacity item. + return false; + } + + const DisplayItemType type = item->GetType(); + if (type == DisplayItemType::TYPE_MASK) { + return true; + } + + return false; +} + +bool nsDisplayOpacity::CanApplyOpacityToChildren( + WebRenderLayerManager* aManager, nsDisplayListBuilder* aBuilder, + float aInheritedOpacity) { + if (mFrame->GetPrevContinuation() || mFrame->GetNextContinuation() || + mFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) { + // If we've been split, then we might need to merge, so + // don't flatten us away. + return false; + } + + if (mNeedsActiveLayer || mOpacity == 0.0) { + // If our opacity is zero then we'll discard all descendant display items + // except for layer event regions, so there's no point in doing this + // optimization (and if we do do it, then invalidations of those descendants + // might trigger repainting). + return false; + } + + if (mList.IsEmpty()) { + return false; + } + + // We can only flatten opacity items into a mask if we haven't + // already flattened an earlier ancestor, since the SVG code pulls the opacity + // from style directly, and won't know about the outer opacity value. + if (aInheritedOpacity == 1.0f && ApplyToMask()) { + MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame)); + mChildOpacityState = ChildOpacityState::Applied; + return true; + } + + // Return true if we successfully applied opacity to child items. + return CanApplyToChildren(aManager, aBuilder); +} + +void nsDisplayOpacity::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast(aGeometry); + + bool snap; + if (mOpacity != geometry->mOpacity) { + aInvalidRegion->Or(GetBounds(aBuilder, &snap), geometry->mBounds); + } +} + +void nsDisplayOpacity::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (opacity " << mOpacity << ", mChildOpacityState: "; + switch (mChildOpacityState) { + case ChildOpacityState::Unknown: + aStream << "Unknown"; + break; + case ChildOpacityState::Applied: + aStream << "Applied"; + break; + case ChildOpacityState::Deferred: + aStream << "Deferred"; + break; + default: + break; + } + + aStream << ")"; +} + +bool nsDisplayOpacity::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + float oldOpacity = aBuilder.GetInheritedOpacity(); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedOpacity(1.0f); + aBuilder.SetInheritedClipChain(nullptr); + float opacity = mOpacity * oldOpacity; + float* opacityForSC = &opacity; + + uint64_t animationsId = + AddAnimationsForWebRender(this, aManager, aDisplayListBuilder); + wr::WrAnimationProperty prop{ + wr::WrAnimationType::Opacity, + animationsId, + }; + + wr::StackingContextParams params; + params.animation = animationsId ? &prop : nullptr; + params.opacity = opacityForSC; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + &mList, this, aDisplayListBuilder, sc, aBuilder, aResources); + aBuilder.SetInheritedOpacity(oldOpacity); + aBuilder.SetInheritedClipChain(oldClipChain); + return true; +} + +nsDisplayBlendMode::nsDisplayBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + StyleBlend aBlendMode, const ActiveScrolledRoot* aActiveScrolledRoot, + const bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mBlendMode(aBlendMode), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendMode); +} + +nsRegion nsDisplayBlendMode::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + // We are never considered opaque + return nsRegion(); +} + +bool nsDisplayBlendMode::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.mix_blend_mode = + wr::ToMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +void nsDisplayBlendMode::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // This should be switched to use PushLayerWithBlend, once it's + // been implemented for all DrawTarget backends. + DrawTarget* dt = aCtx->GetDrawTarget(); + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Rect rect = NSRectToRect(GetPaintRect(aBuilder, aCtx), appUnitsPerDevPixel); + rect.RoundOut(); + + // Create a temporary DrawTarget that is clipped to the area that + // we're going to draw to. This will include the same transform as + // is currently on |dt|. + RefPtr temp = + dt->CreateClippedDrawTarget(rect, SurfaceFormat::B8G8R8A8); + if (!temp) { + return; + } + + gfxContext ctx(temp, /* aPreserveTransform */ true); + + GetChildren()->Paint(aBuilder, &ctx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + // Draw the temporary DT to the real destination, applying the blend mode, but + // no transform. + temp->Flush(); + RefPtr surface = temp->Snapshot(); + gfxContextMatrixAutoSaveRestore saveMatrix(aCtx); + dt->SetTransform(Matrix()); + dt->DrawSurface( + surface, Rect(surface->GetRect()), Rect(surface->GetRect()), + DrawSurfaceOptions(), + DrawOptions(1.0f, nsCSSRendering::GetGFXBlendMode(mBlendMode))); +} + +gfx::CompositionOp nsDisplayBlendMode::BlendMode() { + return nsCSSRendering::GetGFXBlendMode(mBlendMode); +} + +bool nsDisplayBlendMode::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + const auto* item = static_cast(aItem); + if (mIsForBackground || item->mIsForBackground) { + // Don't merge background-blend-mode items + return false; + } + + return true; +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) { + return MakeDisplayItem(aBuilder, aFrame, aList, + aActiveScrolledRoot, false); +} + +/* static */ +nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) { + if (aSecondaryFrame) { + auto type = GetTableTypeFromFrame(aFrame); + auto index = static_cast(type); + + return MakeDisplayItemWithIndex( + aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true, + aFrame); + } + + return MakeDisplayItemWithIndex( + aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true); +} + +nsDisplayBlendContainer::nsDisplayBlendContainer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mIsForBackground(aIsForBackground) { + MOZ_COUNT_CTOR(nsDisplayBlendContainer); +} + +void nsDisplayBlendContainer::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + aCtx->GetDrawTarget()->PushLayer(false, 1.0, nullptr, gfx::Matrix()); + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + aCtx->GetDrawTarget()->PopLayer(); +} + +bool nsDisplayBlendContainer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.flags |= wr::StackingContextFlags::IS_BLEND_CONTAINER; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, sc, aManager, aDisplayListBuilder); +} + +nsDisplayOwnLayer::nsDisplayOwnLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + nsDisplayOwnLayerFlags aFlags, const ScrollbarData& aScrollbarData, + bool aForceActive, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain), + mFlags(aFlags), + mScrollbarData(aScrollbarData), + mForceActive(aForceActive), + mWrAnimationId(0) { + MOZ_COUNT_CTOR(nsDisplayOwnLayer); +} + +bool nsDisplayOwnLayer::IsScrollThumbLayer() const { + return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Thumb; +} + +bool nsDisplayOwnLayer::IsScrollbarContainer() const { + return mScrollbarData.mScrollbarLayerType == ScrollbarLayerType::Container; +} + +bool nsDisplayOwnLayer::IsRootScrollbarContainer() const { + return IsScrollbarContainer() && IsScrollbarLayerForRoot(); +} + +bool nsDisplayOwnLayer::IsScrollbarLayerForRoot() const { + return mFrame->PresContext()->IsRootContentDocumentCrossProcess() && + mScrollbarData.mTargetViewId == + nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayOwnLayer::IsZoomingLayer() const { + return GetType() == DisplayItemType::TYPE_ASYNC_ZOOM; +} + +bool nsDisplayOwnLayer::IsFixedPositionLayer() const { + return GetType() == DisplayItemType::TYPE_FIXED_POSITION || + GetType() == DisplayItemType::TYPE_TABLE_FIXED_POSITION; +} + +bool nsDisplayOwnLayer::IsStickyPositionLayer() const { + return GetType() == DisplayItemType::TYPE_STICKY_POSITION; +} + +bool nsDisplayOwnLayer::HasDynamicToolbar() const { + if (!mFrame->PresContext()->IsRootContentDocumentCrossProcess()) { + return false; + } + return mFrame->PresContext()->HasDynamicToolbar() || + // For tests on Android, this pref is set to simulate the dynamic + // toolbar + StaticPrefs::apz_fixed_margin_override_enabled(); +} + +bool nsDisplayOwnLayer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + Maybe prop; + bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && + (IsScrollThumbLayer() || IsZoomingLayer() || + ShouldGetFixedOrStickyAnimationId() || + (IsRootScrollbarContainer() && HasDynamicToolbar())); + + if (needsProp) { + // APZ is enabled and this is a scroll thumb or zooming layer, so we need + // to create and set an animation id. That way APZ can adjust the position/ + // zoom of this content asynchronously as needed. + RefPtr animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData(this); + mWrAnimationId = animationData->GetAnimationId(); + + prop.emplace(); + prop->id = mWrAnimationId; + prop->key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::APZ); + prop->effect_type = wr::WrAnimationType::Transform; + } + + wr::StackingContextParams params; + params.animation = prop.ptrOr(nullptr); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (IsScrollbarContainer() && IsRootScrollbarContainer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER; + } + if (IsZoomingLayer() || + (ShouldGetFixedOrStickyAnimationId() || + (IsRootScrollbarContainer() && HasDynamicToolbar()))) { + params.is_2d_scale_translation = true; + params.should_snap = true; + } + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +bool nsDisplayOwnLayer::UpdateScrollData(WebRenderScrollData* aData, + WebRenderLayerScrollData* aLayerData) { + bool isRelevantToApz = + (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() || + ShouldGetFixedOrStickyAnimationId()); + + if (!isRelevantToApz) { + return false; + } + + if (!aLayerData) { + return true; + } + + if (IsZoomingLayer()) { + aLayerData->SetZoomAnimationId(mWrAnimationId); + return true; + } + + if (IsFixedPositionLayer() && ShouldGetFixedOrStickyAnimationId()) { + aLayerData->SetFixedPositionAnimationId(mWrAnimationId); + return true; + } + + if (IsStickyPositionLayer() && ShouldGetFixedOrStickyAnimationId()) { + aLayerData->SetStickyPositionAnimationId(mWrAnimationId); + return true; + } + + MOZ_ASSERT(IsScrollbarContainer() || IsScrollThumbLayer()); + + aLayerData->SetScrollbarData(mScrollbarData); + + if (IsRootScrollbarContainer() && HasDynamicToolbar()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + return true; + } + + if (IsScrollThumbLayer()) { + aLayerData->SetScrollbarAnimationId(mWrAnimationId); + LayoutDeviceRect bounds = LayoutDeviceIntRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + // Subframe scrollbars are subject to the pinch-zoom scale, + // but root scrollbars are not because they are outside of the + // region that is zoomed. + const float resolution = + IsScrollbarLayerForRoot() + ? 1.0f + : mFrame->PresShell()->GetCumulativeResolution(); + LayerIntRect layerBounds = + RoundedOut(bounds * LayoutDeviceToLayerScale(resolution)); + aLayerData->SetVisibleRect(layerBounds); + } + return true; +} + +void nsDisplayOwnLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString(" (flags 0x%x) (scrolltarget %" PRIu64 ")", + (int)mFlags, mScrollbarData.mTargetViewId) + .get(); +} + +nsDisplaySubDocument::nsDisplaySubDocument(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, + nsDisplayOwnLayerFlags aFlags) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot(), aFlags), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mShouldFlatten(false), + mSubDocFrame(aSubDocFrame) { + MOZ_COUNT_CTOR(nsDisplaySubDocument); + + if (mSubDocFrame && mSubDocFrame != mFrame) { + mSubDocFrame->AddDisplayItem(this); + } +} + +nsDisplaySubDocument::~nsDisplaySubDocument() { + MOZ_COUNT_DTOR(nsDisplaySubDocument); + if (mSubDocFrame) { + mSubDocFrame->RemoveDisplayItem(this); + } +} + +nsIFrame* nsDisplaySubDocument::FrameForInvalidation() const { + return mSubDocFrame ? mSubDocFrame : mFrame; +} + +void nsDisplaySubDocument::RemoveFrame(nsIFrame* aFrame) { + if (aFrame == mSubDocFrame) { + mSubDocFrame = nullptr; + SetDeletedFrame(); + } + nsDisplayOwnLayer::RemoveFrame(aFrame); +} + +static bool UseDisplayPortForViewport(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) { + return aBuilder->IsPaintingToWindow() && + DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext()); +} + +nsRect nsDisplaySubDocument::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return mFrame->GetRect() + aBuilder->ToReferenceFrame(mFrame); + } + + return nsDisplayOwnLayer::GetBounds(aBuilder, aSnap); +} + +nsRegion nsDisplaySubDocument::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if ((mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) && + usingDisplayPort) { + *aSnap = false; + return nsRegion(); + } + + return nsDisplayOwnLayer::GetOpaqueRegion(aBuilder, aSnap); +} + +/* static */ +nsDisplayFixedPosition* nsDisplayFixedPosition::CreateForFixedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsDisplayBackgroundImage* aImage, const uint16_t aIndex, + const ActiveScrolledRoot* aScrollTargetASR) { + nsDisplayList temp(aBuilder); + temp.AppendToTop(aImage); + + if (aSecondaryFrame) { + auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType); + return MakeDisplayItemWithIndex( + aBuilder, aSecondaryFrame, index, &temp, aFrame, aScrollTargetASR); + } + + return MakeDisplayItemWithIndex( + aBuilder, aFrame, aIndex + 1, &temp, aScrollTargetASR); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mScrollTargetASR(aScrollTargetASR), + mIsFixedBackground(false) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot()), + // For fixed backgrounds, this is the ASR for the nearest scroll frame. + mScrollTargetASR(aScrollTargetASR), + mIsFixedBackground(true) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); +} + +ScrollableLayerGuid::ViewID nsDisplayFixedPosition::GetScrollTargetId() const { + if (mScrollTargetASR && + (mIsFixedBackground || !nsLayoutUtils::IsReallyFixedPos(mFrame))) { + return mScrollTargetASR->GetViewId(); + } + return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayFixedPosition::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + SideBits sides = SideBits::eNone; + if (!mIsFixedBackground) { + sides = nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + } + + // We install this RAII scrolltarget tracker so that any + // nsDisplayCompositorHitTestInfo items inside this fixed-pos item (and that + // share the same ASR as this item) use the correct scroll target. That way + // attempts to scroll on those items will scroll the root scroll frame. + wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker( + aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides); + return nsDisplayOwnLayer::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +bool nsDisplayFixedPosition::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + if (!mIsFixedBackground) { + aLayerData->SetFixedPositionSides( + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame)); + } + aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId()); + } + nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return true; +} + +bool nsDisplayFixedPosition::ShouldGetFixedOrStickyAnimationId() { +#if defined(MOZ_WIDGET_ANDROID) + return mFrame->PresContext()->IsRootContentDocumentCrossProcess() && + nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()) == + GetScrollTargetId(); +#else + return false; +#endif +} + +void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString( + " (containerASR %s) (scrolltarget %" PRIu64 ")", + ActiveScrolledRoot::ToString(mScrollTargetASR).get(), + GetScrollTargetId()) + .get(); +} + +TableType GetTableTypeFromFrame(nsIFrame* aFrame) { + if (aFrame->IsTableFrame()) { + return TableType::Table; + } + + if (aFrame->IsTableColFrame()) { + return TableType::TableCol; + } + + if (aFrame->IsTableColGroupFrame()) { + return TableType::TableColGroup; + } + + if (aFrame->IsTableRowFrame()) { + return TableType::TableRow; + } + + if (aFrame->IsTableRowGroupFrame()) { + return TableType::TableRowGroup; + } + + if (aFrame->IsTableCellFrame()) { + return TableType::TableCell; + } + + MOZ_ASSERT_UNREACHABLE("Invalid frame."); + return TableType::Table; +} + +nsDisplayTableFixedPosition::nsDisplayTableFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aAncestorFrame, const ActiveScrolledRoot* aScrollTargetASR) + : nsDisplayFixedPosition(aBuilder, aFrame, aList, aScrollTargetASR), + mAncestorFrame(aAncestorFrame) { + if (aBuilder->IsRetainingDisplayList()) { + mAncestorFrame->AddDisplayItem(this); + } +} + +nsDisplayStickyPosition::nsDisplayStickyPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR, bool aClippedToDisplayPort) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mContainerASR(aContainerASR), + mClippedToDisplayPort(aClippedToDisplayPort), + mShouldFlatten(false) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); +} + +// Returns the smallest distance from "0" to the range [min, max] where +// min <= max. Despite the name, the return value is actually a 1-D vector, +// and so may be negative if max < 0. +static nscoord DistanceToRange(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max < 0) { + return max; + } + if (min > 0) { + return min; + } + MOZ_ASSERT(min <= 0 && max >= 0); + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is greater +// than zero. The return value is always non-negative. +static nscoord PositivePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (min >= 0) { + return max - min; + } + if (max > 0) { + return max; + } + return 0; +} + +// Returns the magnitude of the part of the range [min, max] that is less +// than zero. The return value is always non-negative. +static nscoord NegativePart(nscoord min, nscoord max) { + MOZ_ASSERT(min <= max); + if (max <= 0) { + return max - min; + } + if (min < 0) { + return 0 - min; + } + return 0; +} + +StickyScrollContainer* nsDisplayStickyPosition::GetStickyScrollContainer() { + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame); + if (stickyScrollContainer) { + // If there's no ASR for the scrollframe that this sticky item is attached + // to, then don't create a WR sticky item for it either. Trying to do so + // will end in sadness because WR will interpret some coordinates as + // relative to the nearest enclosing scrollframe, which will correspond + // to the nearest ancestor ASR on the gecko side. That ASR will not be the + // same as the scrollframe this sticky item is actually supposed to be + // attached to, thus the sadness. + // Not sending WR the sticky item is ok, because the enclosing scrollframe + // will never be asynchronously scrolled. Instead we will always position + // the sticky items correctly on the gecko side and WR will never need to + // adjust their position itself. + MOZ_ASSERT( + stickyScrollContainer->ScrollFrame()->IsMaybeAsynchronouslyScrolled()); + if (!stickyScrollContainer->ScrollFrame() + ->IsMaybeAsynchronouslyScrolled()) { + stickyScrollContainer = nullptr; + } + } + return stickyScrollContainer; +} + +bool nsDisplayStickyPosition::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + + Maybe saccHelper; + + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + bool snap; + nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap); + + Maybe topMargin; + Maybe rightMargin; + Maybe bottomMargin; + Maybe leftMargin; + wr::StickyOffsetBounds vBounds = {0.0, 0.0}; + wr::StickyOffsetBounds hBounds = {0.0, 0.0}; + nsPoint appliedOffset; + + nsRectAbsolute outer; + nsRectAbsolute inner; + stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + + nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); + nsPoint offset = + scrollFrame->GetOffsetToCrossDoc(Frame()) + ToReferenceFrame(); + + // Adjust the scrollPort coordinates to be relative to the reference frame, + // so that it is in the same space as everything else. + nsRect scrollPort = + stickyScrollContainer->ScrollFrame()->GetScrollPortRect(); + scrollPort += offset; + + // The following computations make more sense upon understanding the + // semantics of "inner" and "outer", which is explained in the comment on + // SetStickyPositionData in Layers.h. + + if (outer.YMost() != inner.YMost()) { + // Question: How far will itemBounds.y be from the top of the scrollport + // when we have scrolled from the current scroll position of "0" to + // reach the range [inner.YMost(), outer.YMost()] where the item gets + // stuck? + // Answer: the current distance is "itemBounds.y - scrollPort.y". That + // needs to be adjusted by the distance to the range, less any other + // sticky ranges that fall between 0 and the range. If the distance is + // negative (i.e. inner.YMost() <= outer.YMost() < 0) then we would be + // scrolling upwards (decreasing scroll offset) to reach that range, + // which would increase itemBounds.y and make it farther away from the + // top of the scrollport. So in that case the adjustment is -distance. + // If the distance is positive (0 < inner.YMost() <= outer.YMost()) then + // we would be scrolling downwards, itemBounds.y would decrease, and we + // again need to adjust by -distance. If we are already in the range + // then no adjustment is needed and distance is 0 so again using + // -distance works. If the distance is positive, and the item has both + // top and bottom sticky ranges, then the bottom sticky range may fall + // (entirely[1] or partly[2]) between the current scroll position. + // [1]: 0 <= outer.Y() <= inner.Y() < inner.YMost() <= outer.YMost() + // [2]: outer.Y() < 0 <= inner.Y() < inner.YMost() <= outer.YMost() + // In these cases, the item doesn't actually move for that part of the + // distance, so we need to subtract out that bit, which can be computed + // as the positive portion of the range [outer.Y(), inner.Y()]. + nscoord distance = DistanceToRange(inner.YMost(), outer.YMost()); + if (distance > 0) { + distance -= PositivePart(outer.Y(), inner.Y()); + } + topMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.y - scrollPort.y - distance, auPerDevPixel)); + // Question: What is the maximum positive ("downward") offset that WR + // will have to apply to this item in order to prevent the item from + // visually moving? + // Answer: Since the item is "sticky" in the range [inner.YMost(), + // outer.YMost()], the maximum offset will be the size of the range, which + // is outer.YMost() - inner.YMost(). + vBounds.max = + NSAppUnitsToFloatPixels(outer.YMost() - inner.YMost(), auPerDevPixel); + // Question: how much of an offset has layout already applied to the item? + // Answer: if we are + // (a) inside the sticky range (inner.YMost() < 0 <= outer.YMost()), or + // (b) past the sticky range (inner.YMost() < outer.YMost() < 0) + // then layout has already applied some offset to the position of the + // item. The amount of the adjustment is |0 - inner.YMost()| in case (a) + // and |outer.YMost() - inner.YMost()| in case (b). + if (inner.YMost() < 0) { + appliedOffset.y = std::min(0, outer.YMost()) - inner.YMost(); + MOZ_ASSERT(appliedOffset.y > 0); + } + } + if (outer.Y() != inner.Y()) { + // Similar logic as in the previous section, but this time we care about + // the distance from itemBounds.YMost() to scrollPort.YMost(). + nscoord distance = DistanceToRange(outer.Y(), inner.Y()); + if (distance < 0) { + distance += NegativePart(inner.YMost(), outer.YMost()); + } + bottomMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.YMost() - itemBounds.YMost() + distance, auPerDevPixel)); + // And here WR will be moving the item upwards rather than downwards so + // again things are inverted from the previous block. + vBounds.min = + NSAppUnitsToFloatPixels(outer.Y() - inner.Y(), auPerDevPixel); + // We can't have appliedOffset be both positive and negative, and the top + // adjustment takes priority. So here we only update appliedOffset.y if + // it wasn't set by the top-sticky case above. + if (appliedOffset.y == 0 && inner.Y() > 0) { + appliedOffset.y = std::max(0, outer.Y()) - inner.Y(); + MOZ_ASSERT(appliedOffset.y < 0); + } + } + // Same as above, but for the x-axis + if (outer.XMost() != inner.XMost()) { + nscoord distance = DistanceToRange(inner.XMost(), outer.XMost()); + if (distance > 0) { + distance -= PositivePart(outer.X(), inner.X()); + } + leftMargin = Some(NSAppUnitsToFloatPixels( + itemBounds.x - scrollPort.x - distance, auPerDevPixel)); + hBounds.max = + NSAppUnitsToFloatPixels(outer.XMost() - inner.XMost(), auPerDevPixel); + if (inner.XMost() < 0) { + appliedOffset.x = std::min(0, outer.XMost()) - inner.XMost(); + MOZ_ASSERT(appliedOffset.x > 0); + } + } + if (outer.X() != inner.X()) { + nscoord distance = DistanceToRange(outer.X(), inner.X()); + if (distance < 0) { + distance += NegativePart(inner.XMost(), outer.XMost()); + } + rightMargin = Some(NSAppUnitsToFloatPixels( + scrollPort.XMost() - itemBounds.XMost() + distance, auPerDevPixel)); + hBounds.min = + NSAppUnitsToFloatPixels(outer.X() - inner.X(), auPerDevPixel); + if (appliedOffset.x == 0 && inner.X() > 0) { + appliedOffset.x = std::max(0, outer.X()) - inner.X(); + MOZ_ASSERT(appliedOffset.x < 0); + } + } + + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(itemBounds, auPerDevPixel); + wr::LayoutVector2D applied = { + NSAppUnitsToFloatPixels(appliedOffset.x, auPerDevPixel), + NSAppUnitsToFloatPixels(appliedOffset.y, auPerDevPixel)}; + wr::WrSpatialId spatialId = aBuilder.DefineStickyFrame( + wr::ToLayoutRect(bounds), topMargin.ptrOr(nullptr), + rightMargin.ptrOr(nullptr), bottomMargin.ptrOr(nullptr), + leftMargin.ptrOr(nullptr), vBounds, hBounds, applied, + wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Sticky)); + + saccHelper.emplace(aBuilder, spatialId); + aManager->CommandBuilder().PushOverrideForASR(mContainerASR, spatialId); + } + + { + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, + aBuilder, params); + nsDisplayOwnLayer::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + } + + if (stickyScrollContainer) { + aManager->CommandBuilder().PopOverrideForASR(mContainerASR); + } + + return true; +} + +void nsDisplayStickyPosition::CalculateLayerScrollRanges( + StickyScrollContainer* aStickyScrollContainer, float aAppUnitsPerDevPixel, + float aScaleX, float aScaleY, LayerRectAbsolute& aStickyOuter, + LayerRectAbsolute& aStickyInner) { + nsRectAbsolute outer; + nsRectAbsolute inner; + aStickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); + aStickyOuter.SetBox( + NSAppUnitsToFloatPixels(outer.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(outer.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(outer.YMost(), aAppUnitsPerDevPixel) * aScaleY); + aStickyInner.SetBox( + NSAppUnitsToFloatPixels(inner.X(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.Y(), aAppUnitsPerDevPixel) * aScaleY, + NSAppUnitsToFloatPixels(inner.XMost(), aAppUnitsPerDevPixel) * aScaleX, + NSAppUnitsToFloatPixels(inner.YMost(), aAppUnitsPerDevPixel) * aScaleY); +} + +bool nsDisplayStickyPosition::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + bool hasDynamicToolbar = HasDynamicToolbar(); + if (aLayerData && hasDynamicToolbar) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + float cumulativeResolution = + mFrame->PresShell()->GetCumulativeResolution(); + LayerRectAbsolute stickyOuter; + LayerRectAbsolute stickyInner; + CalculateLayerScrollRanges(stickyScrollContainer, auPerDevPixel, + cumulativeResolution, cumulativeResolution, + stickyOuter, stickyInner); + aLayerData->SetStickyScrollRangeOuter(stickyOuter); + aLayerData->SetStickyScrollRangeInner(stickyInner); + + SideBits sides = + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame); + aLayerData->SetFixedPositionSides(sides); + + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame() + ->GetScrolledFrame() + ->GetContent()); + aLayerData->SetStickyPositionScrollContainerId(scrollId); + } + } + // Return true if either there is a dynamic toolbar affecting this sticky + // item or the OwnLayer base implementation returns true for some other + // reason. + bool ret = hasDynamicToolbar; + ret |= nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return ret; +} + +bool nsDisplayStickyPosition::ShouldGetFixedOrStickyAnimationId() { +#if defined(MOZ_WIDGET_ANDROID) + if (HasDynamicToolbar()) { // also implies being in the cross-process RCD + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + if (stickyScrollContainer) { + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(stickyScrollContainer->ScrollFrame() + ->GetScrolledFrame() + ->GetContent()); + return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()) == + scrollId; + } + } +#endif + return false; +} + +nsDisplayScrollInfoLayer::nsDisplayScrollInfoLayer( + nsDisplayListBuilder* aBuilder, nsIFrame* aScrolledFrame, + nsIFrame* aScrollFrame, const CompositorHitTestInfo& aHitInfo, + const nsRect& aHitArea) + : nsDisplayWrapList(aBuilder, aScrollFrame), + mScrollFrame(aScrollFrame), + mScrolledFrame(aScrolledFrame), + mScrollParentId(aBuilder->GetCurrentScrollParentId()), + mHitInfo(aHitInfo), + mHitArea(aHitArea) { +#ifdef NS_BUILD_REFCNT_LOGGING + MOZ_COUNT_CTOR(nsDisplayScrollInfoLayer); +#endif +} + +UniquePtr nsDisplayScrollInfoLayer::ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager) { + ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata( + mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), Frame(), + ToReferenceFrame(), aLayerManager, mScrollParentId, + mScrollFrame->GetSize(), false); + metadata.GetMetrics().SetIsScrollInfoLayer(true); + nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + aBuilder->AddScrollFrameToNotify(scrollableFrame); + } + + return UniquePtr(new ScrollMetadata(metadata)); +} + +bool nsDisplayScrollInfoLayer::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + UniquePtr metadata = + ComputeScrollMetadata(aData->GetBuilder(), aData->GetManager()); + MOZ_ASSERT(aData); + MOZ_ASSERT(metadata); + aLayerData->AppendScrollMetadata(*aData, *metadata); + } + return true; +} + +bool nsDisplayScrollInfoLayer::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + ScrollableLayerGuid::ViewID scrollId = + nsLayoutUtils::FindOrCreateIDFor(mScrollFrame->GetContent()); + + const LayoutDeviceRect devRect = LayoutDeviceRect::FromAppUnits( + mHitArea, mScrollFrame->PresContext()->AppUnitsPerDevPixel()); + + const wr::LayoutRect rect = wr::ToLayoutRect(devRect); + + aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, mHitInfo, + SideBits::eNone); + + return true; +} + +void nsDisplayScrollInfoLayer::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (scrollframe " << mScrollFrame << " scrolledFrame " + << mScrolledFrame << ")"; +} + +nsDisplayZoom::nsDisplayZoom(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + nsSubDocumentFrame* aSubDocFrame, + nsDisplayList* aList, int32_t aAPD, + int32_t aParentAPD, nsDisplayOwnLayerFlags aFlags) + : nsDisplaySubDocument(aBuilder, aFrame, aSubDocFrame, aList, aFlags), + mAPD(aAPD), + mParentAPD(aParentAPD) { + MOZ_COUNT_CTOR(nsDisplayZoom); +} + +nsRect nsDisplayZoom::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + nsRect bounds = nsDisplaySubDocument::GetBounds(aBuilder, aSnap); + *aSnap = false; + return bounds.ScaleToOtherAppUnitsRoundOut(mAPD, mParentAPD); +} + +void nsDisplayZoom::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, + HitTestState* aState, + nsTArray* aOutFrames) { + nsRect rect; + // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1 + // rect as well instead of possibly rounding the width or height to zero. + if (aRect.width == 1 && aRect.height == 1) { + rect.MoveTo(aRect.TopLeft().ScaleToOtherAppUnits(mParentAPD, mAPD)); + rect.width = rect.height = 1; + } else { + rect = aRect.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + } + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +nsDisplayAsyncZoom::nsDisplayAsyncZoom( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, FrameMetrics::ViewID aViewID) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mViewID(aViewID) { + MOZ_COUNT_CTOR(nsDisplayAsyncZoom); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayAsyncZoom::~nsDisplayAsyncZoom() { + MOZ_COUNT_DTOR(nsDisplayAsyncZoom); +} +#endif + +void nsDisplayAsyncZoom::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { +#ifdef DEBUG + nsIScrollableFrame* scrollFrame = do_QueryFrame(mFrame); + MOZ_ASSERT(scrollFrame && ViewportUtils::IsZoomedContentRoot( + scrollFrame->GetScrolledFrame())); +#endif + nsRect rect = ViewportUtils::VisualToLayout(aRect, mFrame->PresShell()); + mList.HitTest(aBuilder, rect, aState, aOutFrames); +} + +bool nsDisplayAsyncZoom::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + bool ret = nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + MOZ_ASSERT(ret); + if (aLayerData) { + aLayerData->SetAsyncZoomContainerId(mViewID); + } + return ret; +} + +/////////////////////////////////////////////////// +// nsDisplayTransform Implementation +// + +#ifndef DEBUG +static_assert(sizeof(nsDisplayTransform) <= 512, + "nsDisplayTransform has grown"); +#endif + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mTransform(Some(Matrix4x4())), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(true), + mHasTransformGetter(false), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + PrerenderDecision aPrerenderDecision) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(aPrerenderDecision), + mIsTransformSeparator(false), + mHasTransformGetter(false), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + SetReferenceFrameToAncestor(aBuilder); + Init(aBuilder, aList); +} + +nsDisplayTransform::nsDisplayTransform(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + const nsRect& aChildrenBuildingRect, + decltype(WithTransformGetter)) + : nsPaintedDisplayItem(aBuilder, aFrame), + mChildren(aBuilder), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(false), + mHasTransformGetter(true), + mHasAssociatedPerspective(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + MOZ_ASSERT(aFrame->GetTransformGetter()); + Init(aBuilder, aList); +} + +void nsDisplayTransform::SetReferenceFrameToAncestor( + nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } + // We manually recompute mToReferenceFrame without going through the + // builder, since this won't apply the 'additional offset'. Our + // children will already be painting with that applied, and we don't + // want to include it a second time in our transform. We don't recompute + // our visible/building rects, since those should still include the additional + // offset. + // TODO: Are there are things computed using our ToReferenceFrame that should + // have the additional offset applied? Should we instead just manually remove + // the offset from our transform instead of this more general value? + // Can we instead apply the additional offset to us and not our children, like + // we do for all other offsets (and how reference frames are supposed to + // work)? + nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrameInProcess(mFrame); + const nsIFrame* referenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); + mToReferenceFrame = mFrame->GetOffsetToCrossDoc(referenceFrame); +} + +void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder, + nsDisplayList* aChildren) { + mChildren.AppendToTop(aChildren); + UpdateBounds(aBuilder); +} + +bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return false; +} + +/* Returns the delta specified by the transform-origin property. + * This is a positive delta, meaning that it indicates the direction to move + * to get from (0, 0) of the frame to the transform origin. This function is + * called off the main thread. + */ +/* static */ +Point3D nsDisplayTransform::GetDeltaToTransformOrigin( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + + if (!aFrame->IsTransformed()) { + return Point3D(); + } + + /* For both of the coordinates, if the value of transform is a + * percentage, it's relative to the size of the frame. Otherwise, if it's + * a distance, it's already computed for us! + */ + const nsStyleDisplay* display = aFrame->StyleDisplay(); + + const StyleTransformOrigin& transformOrigin = display->mTransformOrigin; + CSSPoint origin = nsStyleTransformMatrix::Convert2DPosition( + transformOrigin.horizontal, transformOrigin.vertical, aRefBox); + + // Note: + // 1. SVG frames have a reference box that can be (and typically is) offset + // from the TopLeft() of the frame. We need to account for that here. + // 2. If we are using transform-box:content-box in CSS layout, we have the + // offset from TopLeft() of the frame as well. + origin.x += CSSPixel::FromAppUnits(aRefBox.X()); + origin.y += CSSPixel::FromAppUnits(aRefBox.Y()); + + float scale = AppUnitsPerCSSPixel() / float(aAppUnitsPerPixel); + float z = transformOrigin.depth._0; + return Point3D(origin.x * scale, origin.y * scale, z * scale); +} + +/* static */ +bool nsDisplayTransform::ComputePerspectiveMatrix(const nsIFrame* aFrame, + float aAppUnitsPerPixel, + Matrix4x4& aOutMatrix) { + MOZ_ASSERT(aFrame, "Can't get delta for a null frame!"); + MOZ_ASSERT(aFrame->IsTransformed() || aFrame->BackfaceIsHidden() || + aFrame->Combines3DTransformWithAncestors(), + "Shouldn't get a delta for an untransformed frame!"); + MOZ_ASSERT(aOutMatrix.IsIdentity(), "Must have a blank output matrix"); + + if (!aFrame->IsTransformed()) { + return false; + } + + // TODO: Is it possible that the perspectiveFrame's bounds haven't been set + // correctly yet (similar to the aBoundsOverride case for + // GetResultingTransformMatrix)? + nsIFrame* perspectiveFrame = + aFrame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + if (!perspectiveFrame) { + return false; + } + + /* Grab the values for perspective and perspective-origin (if present) */ + const nsStyleDisplay* perspectiveDisplay = perspectiveFrame->StyleDisplay(); + if (perspectiveDisplay->mChildPerspective.IsNone()) { + return false; + } + + MOZ_ASSERT(perspectiveDisplay->mChildPerspective.IsLength()); + float perspective = + perspectiveDisplay->mChildPerspective.AsLength().ToCSSPixels(); + perspective = std::max(1.0f, perspective); + if (perspective < std::numeric_limits::epsilon()) { + return true; + } + + TransformReferenceBox refBox(perspectiveFrame); + + Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition( + perspectiveDisplay->mPerspectiveOrigin.horizontal, + perspectiveDisplay->mPerspectiveOrigin.vertical, refBox, + aAppUnitsPerPixel); + + /* GetOffsetTo computes the offset required to move from 0,0 in + * perspectiveFrame to 0,0 in aFrame. Although we actually want the inverse of + * this, it's faster to compute this way. + */ + nsPoint frameToPerspectiveOffset = -aFrame->GetOffsetTo(perspectiveFrame); + Point frameToPerspectiveGfxOffset( + NSAppUnitsToFloatPixels(frameToPerspectiveOffset.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(frameToPerspectiveOffset.y, aAppUnitsPerPixel)); + + /* Move the perspective origin to be relative to aFrame, instead of relative + * to the containing block which is how it was specified in the style system. + */ + perspectiveOrigin += frameToPerspectiveGfxOffset; + + aOutMatrix._34 = + -1.0 / NSAppUnitsToFloatPixels(CSSPixel::ToAppUnits(perspective), + aAppUnitsPerPixel); + + aOutMatrix.ChangeBasis(Point3D(perspectiveOrigin.x, perspectiveOrigin.y, 0)); + return true; +} + +nsDisplayTransform::FrameTransformProperties::FrameTransformProperties( + const nsIFrame* aFrame, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) + : mFrame(aFrame), + mTranslate(aFrame->StyleDisplay()->mTranslate), + mRotate(aFrame->StyleDisplay()->mRotate), + mScale(aFrame->StyleDisplay()->mScale), + mTransform(aFrame->StyleDisplay()->mTransform), + mMotion(aFrame->StyleDisplay()->mOffsetPath.IsNone() + ? Nothing() + : MotionPathUtils::ResolveMotionPath(aFrame, aRefBox)), + mToTransformOrigin( + GetDeltaToTransformOrigin(aFrame, aRefBox, aAppUnitsPerPixel)) {} + +/* Wraps up the transform matrix in a change-of-basis matrix pair that + * translates from local coordinate space to transform coordinate space, then + * hands it back. + */ +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + float aAppUnitsPerPixel) { + return GetResultingTransformMatrixInternal(aProperties, aRefBox, nsPoint(), + aAppUnitsPerPixel, 0); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrix( + const nsIFrame* aFrame, const nsPoint& aOrigin, float aAppUnitsPerPixel, + uint32_t aFlags) { + TransformReferenceBox refBox(aFrame); + FrameTransformProperties props(aFrame, refBox, aAppUnitsPerPixel); + return GetResultingTransformMatrixInternal(props, refBox, aOrigin, + aAppUnitsPerPixel, aFlags); +} + +Matrix4x4 nsDisplayTransform::GetResultingTransformMatrixInternal( + const FrameTransformProperties& aProperties, TransformReferenceBox& aRefBox, + const nsPoint& aOrigin, float aAppUnitsPerPixel, uint32_t aFlags) { + const nsIFrame* frame = aProperties.mFrame; + NS_ASSERTION(frame || !(aFlags & INCLUDE_PERSPECTIVE), + "Must have a frame to compute perspective!"); + + // Get the underlying transform matrix: + + /* Get the matrix, then change its basis to factor in the origin. */ + Matrix4x4 result; + // Call IsSVGTransformed() regardless of the value of + // aProperties.HasTransform(), since we still need any + // potential parentsChildrenOnlyTransform. + Matrix svgTransform, parentsChildrenOnlyTransform; + const bool hasSVGTransforms = + frame && frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED) && + frame->IsSVGTransformed(&svgTransform, &parentsChildrenOnlyTransform); + bool shouldRound = nsLayoutUtils::ShouldSnapToGrid(frame); + + /* Transformed frames always have a transform, or are preserving 3d (and might + * still have perspective!) */ + if (aProperties.HasTransform()) { + result = nsStyleTransformMatrix::ReadTransforms( + aProperties.mTranslate, aProperties.mRotate, aProperties.mScale, + aProperties.mMotion.ptrOr(nullptr), aProperties.mTransform, aRefBox, + aAppUnitsPerPixel); + } else if (hasSVGTransforms) { + // Correct the translation components for zoom: + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + svgTransform._31 *= pixelsPerCSSPx; + svgTransform._32 *= pixelsPerCSSPx; + result = Matrix4x4::From2D(svgTransform); + } + + // Apply any translation due to 'transform-origin' and/or 'transform-box': + result.ChangeBasis(aProperties.mToTransformOrigin); + + // See the comment for SVGContainerFrame::HasChildrenOnlyTransform for + // an explanation of what children-only transforms are. + const bool parentHasChildrenOnlyTransform = + hasSVGTransforms && !parentsChildrenOnlyTransform.IsIdentity(); + + if (parentHasChildrenOnlyTransform) { + float pixelsPerCSSPx = AppUnitsPerCSSPixel() / aAppUnitsPerPixel; + parentsChildrenOnlyTransform._31 *= pixelsPerCSSPx; + parentsChildrenOnlyTransform._32 *= pixelsPerCSSPx; + + Point3D frameOffset( + NSAppUnitsToFloatPixels(-frame->GetPosition().x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(-frame->GetPosition().y, aAppUnitsPerPixel), 0); + Matrix4x4 parentsChildrenOnlyTransform3D = + Matrix4x4::From2D(parentsChildrenOnlyTransform) + .ChangeBasis(frameOffset); + + result *= parentsChildrenOnlyTransform3D; + } + + Matrix4x4 perspectiveMatrix; + bool hasPerspective = aFlags & INCLUDE_PERSPECTIVE; + if (hasPerspective) { + if (ComputePerspectiveMatrix(frame, aAppUnitsPerPixel, perspectiveMatrix)) { + result *= perspectiveMatrix; + } + } + + if ((aFlags & INCLUDE_PRESERVE3D_ANCESTORS) && frame && + frame->Combines3DTransformWithAncestors()) { + // Include the transform set on our parent + nsIFrame* parentFrame = + frame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + NS_ASSERTION(parentFrame && parentFrame->IsTransformed() && + parentFrame->Extend3DContext(), + "Preserve3D mismatch!"); + TransformReferenceBox refBox(parentFrame); + FrameTransformProperties props(parentFrame, refBox, aAppUnitsPerPixel); + + uint32_t flags = + aFlags & (INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE); + + // If this frame isn't transformed (but we exist for backface-visibility), + // then we're not a reference frame so no offset to origin will be added. + // Otherwise we need to manually translate into our parent's coordinate + // space. + if (frame->IsTransformed()) { + nsLayoutUtils::PostTranslate(result, frame->GetPosition(), + aAppUnitsPerPixel, shouldRound); + } + Matrix4x4 parent = GetResultingTransformMatrixInternal( + props, refBox, nsPoint(0, 0), aAppUnitsPerPixel, flags); + result = result * parent; + } + + if (aFlags & OFFSET_BY_ORIGIN) { + nsLayoutUtils::PostTranslate(result, aOrigin, aAppUnitsPerPixel, + shouldRound); + } + + return result; +} + +bool nsDisplayOpacity::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + static constexpr nsCSSPropertyIDSet opacitySet = + nsCSSPropertyIDSet::OpacityProperties(); + if (ActiveLayerTracker::IsStyleAnimated(aBuilder, mFrame, opacitySet)) { + return true; + } + + EffectCompositor::SetPerformanceWarning( + mFrame, opacitySet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::OpacityFrameInactive)); + + return false; +} + +bool nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder) { + return mPrerenderDecision != PrerenderDecision::No; +} + +bool nsDisplayBackgroundColor::CanUseAsyncAnimations( + nsDisplayListBuilder* aBuilder) { + return StaticPrefs::gfx_omta_background_color(); +} + +static bool IsInStickyPositionedSubtree(const nsIFrame* aFrame) { + for (const nsIFrame* frame = aFrame; frame; + frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame)) { + if (frame->IsStickyPositioned()) { + return true; + } + } + return false; +} + +static bool ShouldUsePartialPrerender(const nsIFrame* aFrame) { + return StaticPrefs::layout_animation_prerender_partial() && + // Bug 1642547: Support partial prerender for position:sticky elements. + !IsInStickyPositionedSubtree(aFrame); +} + +/* static */ +auto nsDisplayTransform::ShouldPrerenderTransformedContent( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsRect* aDirtyRect) + -> PrerenderInfo { + PrerenderInfo result; + // If we are in a preserve-3d tree, and we've disallowed async animations, we + // return No prerender decision directly. + if ((aFrame->Extend3DContext() || + aFrame->Combines3DTransformWithAncestors()) && + !aBuilder->GetPreserves3DAllowAsyncAnimation()) { + return result; + } + + // Elements whose transform has been modified recently, or which + // have a compositor-animated transform, can be prerendered. An element + // might have only just had its transform animated in which case + // the ActiveLayerManager may not have been notified yet. + static constexpr nsCSSPropertyIDSet transformSet = + nsCSSPropertyIDSet::TransformLikeProperties(); + if (!ActiveLayerTracker::IsTransformMaybeAnimated(aFrame) && + !EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM)) { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::TransformFrameInactive)); + + // This case happens when we're sure that the frame is not animated and its + // preserve-3d ancestors are not, either. So we don't need to pre-render. + // However, this decision shouldn't affect the decisions for other frames in + // the preserve-3d context. We need this flag to determine whether we should + // block async animations on other frames in the current preserve-3d tree. + result.mHasAnimations = false; + return result; + } + + // We should not allow prerender if any ancestor container element has + // mask/clip-path effects. + // + // With prerender and async transform animation, we do not need to restyle an + // animated element to respect position changes, since that transform is done + // by layer animation. As a result, the container element is not aware of + // position change of that containing element and loses the chance to update + // the content of mask/clip-path. + // + // Why do we need to update a mask? This is relative to how we generate a + // mask layer in ContainerState::SetupMaskLayerForCSSMask. While creating a + // mask layer, to reduce memory usage, we did not choose the size of the + // masked element as mask size. Instead, we read the union of bounds of all + // children display items by nsDisplayWrapList::GetBounds, which is smaller + // than or equal to the masked element's boundary, and use it as the position + // size of the mask layer. That union bounds is actually affected by the + // geometry of the animated element. To keep the content of mask up to date, + // forbidding of prerender is required. + for (nsIFrame* container = + nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame); + container; + container = nsLayoutUtils::GetCrossDocParentFrameInProcess(container)) { + const nsStyleSVGReset* svgReset = container->StyleSVGReset(); + if (svgReset->HasMask() || svgReset->HasClipPath()) { + return result; + } + } + + // If the incoming dirty rect already contains the entire overflow area, + // we are already rendering the entire content. + nsRect overflow = aFrame->InkOverflowRectRelativeToSelf(); + // UntransformRect will not touch the output rect (`&untranformedDirtyRect`) + // in cases of non-invertible transforms, so we set `untransformedRect` to + // `aDirtyRect` as an initial value for such cases. + nsRect untransformedDirtyRect = *aDirtyRect; + UntransformRect(*aDirtyRect, overflow, aFrame, &untransformedDirtyRect); + if (untransformedDirtyRect.Contains(overflow)) { + *aDirtyRect = untransformedDirtyRect; + result.mDecision = PrerenderDecision::Full; + return result; + } + + float viewportRatio = + StaticPrefs::layout_animation_prerender_viewport_ratio_limit(); + uint32_t absoluteLimitX = + StaticPrefs::layout_animation_prerender_absolute_limit_x(); + uint32_t absoluteLimitY = + StaticPrefs::layout_animation_prerender_absolute_limit_y(); + nsSize refSize = aBuilder->RootReferenceFrame()->GetSize(); + + float resolution = aFrame->PresShell()->GetCumulativeResolution(); + if (resolution < 1.0f) { + refSize.SizeTo( + NSCoordSaturatingNonnegativeMultiply(refSize.width, 1.0f / resolution), + NSCoordSaturatingNonnegativeMultiply(refSize.height, + 1.0f / resolution)); + } + + // Only prerender if the transformed frame's size is <= a multiple of the + // reference frame size (~viewport), and less than an absolute limit. + // Both the ratio and the absolute limit are configurable. + nscoord maxLength = std::max(nscoord(refSize.width * viewportRatio), + nscoord(refSize.height * viewportRatio)); + nsSize relativeLimit(maxLength, maxLength); + nsSize absoluteLimit( + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitX), + aFrame->PresContext()->DevPixelsToAppUnits(absoluteLimitY)); + nsSize maxSize = Min(relativeLimit, absoluteLimit); + + const auto transform = nsLayoutUtils::GetTransformToAncestor( + RelativeTo{aFrame}, + RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)}); + const gfxRect transformedBounds = transform.TransformAndClipBounds( + gfxRect(overflow.x, overflow.y, overflow.width, overflow.height), + gfxRect::MaxIntRect()); + const nsSize frameSize = + nsSize(transformedBounds.width, transformedBounds.height); + + uint64_t maxLimitArea = uint64_t(maxSize.width) * maxSize.height; + uint64_t frameArea = uint64_t(frameSize.width) * frameSize.height; + if (frameArea <= maxLimitArea && frameSize <= absoluteLimit) { + *aDirtyRect = overflow; + result.mDecision = PrerenderDecision::Full; + return result; + } + + if (ShouldUsePartialPrerender(aFrame)) { + *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea( + aFrame, untransformedDirtyRect, overflow, maxSize); + result.mDecision = PrerenderDecision::Partial; + return result; + } + + if (frameArea > maxLimitArea) { + uint64_t appUnitsPerPixel = AppUnitsPerCSSPixel(); + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLargeArea, + { + int(frameArea / (appUnitsPerPixel * appUnitsPerPixel)), + int(maxLimitArea / (appUnitsPerPixel * appUnitsPerPixel)), + })); + } else { + EffectCompositor::SetPerformanceWarning( + aFrame, transformSet, + AnimationPerformanceWarning( + AnimationPerformanceWarning::Type::ContentTooLarge, + { + nsPresContext::AppUnitsToIntCSSPixels(frameSize.width), + nsPresContext::AppUnitsToIntCSSPixels(frameSize.height), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(relativeLimit.height), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.width), + nsPresContext::AppUnitsToIntCSSPixels(absoluteLimit.height), + })); + } + + return result; +} + +/* If the matrix is singular, or a hidden backface is shown, the frame won't be + * visible or hit. */ +static bool IsFrameVisible(nsIFrame* aFrame, const Matrix4x4& aMatrix) { + if (aMatrix.IsSingular()) { + return false; + } + if (aFrame->BackfaceIsHidden() && aMatrix.IsBackfaceVisible()) { + return false; + } + return true; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetTransform() const { + if (mTransform) { + return *mTransform; + } + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + + if (mHasTransformGetter) { + mTransform.emplace((mFrame->GetTransformGetter())(mFrame, scale)); + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(mToReferenceFrame.x, scale), + NSAppUnitsToFloatPixels(mToReferenceFrame.y, scale), 0.0f); + mTransform->ChangeBasis(newOrigin.x, newOrigin.y, newOrigin.z); + } else if (!mIsTransformSeparator) { + DebugOnly isReference = mFrame->IsTransformed() || + mFrame->Combines3DTransformWithAncestors() || + mFrame->Extend3DContext(); + MOZ_ASSERT(isReference); + mTransform.emplace( + GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN)); + } else { + // Use identity matrix + mTransform.emplace(); + } + + return *mTransform; +} + +const Matrix4x4Flagged& nsDisplayTransform::GetInverseTransform() const { + if (mInverseTransform) { + return *mInverseTransform; + } + + MOZ_ASSERT(!GetTransform().IsSingular()); + + mInverseTransform.emplace(GetTransform().Inverse()); + + return *mInverseTransform; +} + +Matrix4x4 nsDisplayTransform::GetTransformForRendering( + LayoutDevicePoint* aOutOrigin) const { + if (!mFrame->HasPerspective() || mHasTransformGetter || + mIsTransformSeparator) { + if (!mHasTransformGetter && !mIsTransformSeparator && aOutOrigin) { + // If aOutOrigin is provided, put the offset to origin into it, because + // we need to keep it separate for webrender. The combination of + // *aOutOrigin and the returned matrix here should always be equivalent + // to what GetTransform() would have returned. + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + *aOutOrigin = LayoutDevicePoint::FromAppUnits(ToReferenceFrame(), scale); + + // The rounding behavior should also be the same as GetTransform(). + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + aOutOrigin->Round(); + } + return GetResultingTransformMatrix(mFrame, nsPoint(0, 0), scale, + INCLUDE_PERSPECTIVE); + } + return GetTransform().GetMatrix(); + } + MOZ_ASSERT(!mHasTransformGetter); + + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + // Don't include perspective transform, or the offset to origin, since + // nsDisplayPerspective will handle both of those. + return GetResultingTransformMatrix(mFrame, ToReferenceFrame(), scale, 0); +} + +const Matrix4x4& nsDisplayTransform::GetAccumulatedPreserved3DTransform( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(!mFrame->Extend3DContext() || IsLeafOf3DContext()); + + if (!IsLeafOf3DContext()) { + return GetTransform().GetMatrix(); + } + + if (!mTransformPreserves3D) { + const nsIFrame* establisher; // Establisher of the 3D rendering context. + for (establisher = mFrame; + establisher && establisher->Combines3DTransformWithAncestors(); + establisher = + establisher->GetClosestFlattenedTreeAncestorPrimaryFrame()) { + } + const nsIFrame* establisherReference = aBuilder->FindReferenceFrameFor( + nsLayoutUtils::GetCrossDocParentFrameInProcess(establisher)); + + nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference); + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + uint32_t flags = + INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN; + mTransformPreserves3D = MakeUnique( + GetResultingTransformMatrix(mFrame, offset, scale, flags)); + } + + return *mTransformPreserves3D; +} + +bool nsDisplayTransform::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + // We want to make sure we don't pollute the transform property in the WR + // stacking context by including the position of this frame (relative to the + // parent reference frame). We need to keep those separate; the position of + // this frame goes into the stacking context bounds while the transform goes + // into the transform. + LayoutDevicePoint position; + Matrix4x4 newTransformMatrix = GetTransformForRendering(&position); + + gfx::Matrix4x4* transformForSC = &newTransformMatrix; + if (newTransformMatrix.IsIdentity()) { + // If the transform is an identity transform, strip it out so that WR + // doesn't turn this stacking context into a reference frame, as it + // affects positioning. Bug 1345577 tracks a better fix. + transformForSC = nullptr; + + // In ChooseScaleAndSetTransform, we round the offset from the reference + // frame used to adjust the transform, if there is no transform, or it + // is just a translation. We need to do the same here. + if (nsLayoutUtils::ShouldSnapToGrid(mFrame)) { + position.Round(); + } + } + + auto key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Transform); + + // We don't send animations for transform separator display items. + uint64_t animationsId = + mIsTransformSeparator + ? 0 + : AddAnimationsForWebRender( + this, aManager, aDisplayListBuilder, + IsPartialPrerender() ? Some(position) : Nothing()); + wr::WrAnimationProperty prop{wr::WrAnimationType::Transform, animationsId, + key}; + + nsDisplayTransform* deferredTransformItem = nullptr; + if (!mFrame->ChildrenHavePerspective()) { + // If it has perspective, we create a new scroll data via the + // UpdateScrollData call because that scenario is more complex. Otherwise + // we can just stash the transform on the StackingContextHelper and + // apply it to any scroll data that are created inside this + // nsDisplayTransform. + deferredTransformItem = this; + } + + // Determine if we're possibly animated (= would need an active layer in FLB). + bool animated = !mIsTransformSeparator && + ActiveLayerTracker::IsTransformMaybeAnimated(Frame()); + + wr::StackingContextParams params; + params.mBoundTransform = &newTransformMatrix; + params.animation = animationsId ? &prop : nullptr; + + wr::WrTransformInfo transform_info; + if (transformForSC) { + transform_info.transform = wr::ToLayoutTransform(newTransformMatrix); + transform_info.key = key; + params.mTransformPtr = &transform_info; + } else { + params.mTransformPtr = nullptr; + } + + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.paired_with_perspective = mHasAssociatedPerspective; + params.mDeferredTransformItem = deferredTransformItem; + params.mAnimated = animated; + // Determine if we would have to rasterize any items in local raster space + // (i.e. disable subpixel AA). We don't always need to rasterize locally even + // if the stacking context is possibly animated (at the cost of potentially + // some false negatives with respect to will-change handling), so we pass in + // this determination separately to accurately match with when FLB would + // normally disable subpixel AA. + params.mRasterizeLocally = animated && Frame()->HasAnimationOfTransform(); + params.SetPreserve3D(mFrame->Extend3DContext() && !mIsTransformSeparator); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + LayoutDeviceSize boundsSize = LayoutDeviceSize::FromAppUnits( + mChildBounds.Size(), mFrame->PresContext()->AppUnitsPerDevPixel()); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params, LayoutDeviceRect(position, boundsSize)); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + return true; +} + +bool nsDisplayTransform::UpdateScrollData( + WebRenderScrollData* aData, WebRenderLayerScrollData* aLayerData) { + if (!mFrame->ChildrenHavePerspective()) { + // This case is handled in CreateWebRenderCommands by stashing the transform + // on the stacking context. + return false; + } + if (aLayerData) { + aLayerData->SetTransform(GetTransform().GetMatrix()); + aLayerData->SetTransformIsPerspective(true); + } + return true; +} + +bool nsDisplayTransform::ShouldSkipTransform( + nsDisplayListBuilder* aBuilder) const { + return (aBuilder->RootReferenceFrame() == mFrame) && + aBuilder->IsForGenerateGlyphMask(); +} + +void nsDisplayTransform::Collect3DTransformLeaves( + nsDisplayListBuilder* aBuilder, nsTArray& aLeaves) { + if (!IsParticipating3DContext() || IsLeafOf3DContext()) { + aLeaves.AppendElement(this); + return; + } + + FlattenedDisplayListIterator iter(aBuilder, &mChildren); + while (iter.HasNext()) { + nsDisplayItem* item = iter.GetNextItem(); + if (item->GetType() == DisplayItemType::TYPE_PERSPECTIVE) { + auto* perspective = static_cast(item); + if (!perspective->GetChildren()->GetTop()) { + continue; + } + item = perspective->GetChildren()->GetTop(); + } + if (item->GetType() != DisplayItemType::TYPE_TRANSFORM) { + gfxCriticalError() << "Invalid child item within 3D transform of type: " + << item->Name(); + continue; + } + static_cast(item)->Collect3DTransformLeaves(aBuilder, + aLeaves); + } +} + +static RefPtr BuildPathFromPolygon(const RefPtr& aDT, + const gfx::Polygon& aPolygon) { + MOZ_ASSERT(!aPolygon.IsEmpty()); + + RefPtr pathBuilder = aDT->CreatePathBuilder(); + const nsTArray& points = aPolygon.GetPoints(); + + pathBuilder->MoveTo(points[0].As2DPoint()); + + for (size_t i = 1; i < points.Length(); ++i) { + pathBuilder->LineTo(points[i].As2DPoint()); + } + + pathBuilder->Close(); + return pathBuilder->Finish(); +} + +void nsDisplayTransform::CollectSorted3DTransformLeaves( + nsDisplayListBuilder* aBuilder, nsTArray& aLeaves) { + std::list inputLayers; + + nsTArray leaves; + Collect3DTransformLeaves(aBuilder, leaves); + for (nsDisplayTransform* item : leaves) { + auto bounds = LayoutDeviceRect::FromAppUnits( + item->mChildBounds, item->mFrame->PresContext()->AppUnitsPerDevPixel()); + Matrix4x4 transform = item->GetAccumulatedPreserved3DTransform(aBuilder); + + if (!IsFrameVisible(item->mFrame, transform)) { + continue; + } + gfx::Polygon polygon = + gfx::Polygon::FromRect(gfx::Rect(bounds.ToUnknownRect())); + + polygon.TransformToScreenSpace(transform); + + if (polygon.GetPoints().Length() >= 3) { + inputLayers.push_back(TransformPolygon(item, std::move(polygon))); + } + } + + if (inputLayers.empty()) { + return; + } + + BSPTree tree(inputLayers); + nsTArray orderedLayers(tree.GetDrawOrder()); + + for (TransformPolygon& polygon : orderedLayers) { + Matrix4x4 inverse = + polygon.data->GetAccumulatedPreserved3DTransform(aBuilder).Inverse(); + + MOZ_ASSERT(polygon.geometry); + polygon.geometry->TransformToLayerSpace(inverse); + } + + aLeaves = std::move(orderedLayers); +} + +void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + Paint(aBuilder, aCtx, Nothing()); +} + +void nsDisplayTransform::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const Maybe& aPolygon) { + if (IsParticipating3DContext() && !IsLeafOf3DContext()) { + MOZ_ASSERT(!aPolygon); + nsTArray leaves; + CollectSorted3DTransformLeaves(aBuilder, leaves); + for (TransformPolygon& item : leaves) { + item.data->Paint(aBuilder, aCtx, item.geometry); + } + return; + } + + gfxContextMatrixAutoSaveRestore saveMatrix(aCtx); + Matrix4x4 trans = ShouldSkipTransform(aBuilder) + ? Matrix4x4() + : GetAccumulatedPreserved3DTransform(aBuilder); + if (!IsFrameVisible(mFrame, trans)) { + return; + } + + Matrix trans2d; + if (trans.CanDraw2D(&trans2d)) { + aCtx->Multiply(ThebesMatrix(trans2d)); + + if (aPolygon) { + RefPtr path = + BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon); + aCtx->GetDrawTarget()->PushClip(path); + } + + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (aPolygon) { + aCtx->GetDrawTarget()->PopClip(); + } + return; + } + + // TODO: Implement 3d transform handling, including plane splitting and + // sorting. See BasicCompositor. + auto pixelBounds = LayoutDeviceRect::FromAppUnitsToOutside( + mChildBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + RefPtr untransformedDT = + gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( + IntSize(pixelBounds.Width(), pixelBounds.Height()), + SurfaceFormat::B8G8R8A8, true); + if (!untransformedDT || !untransformedDT->IsValid()) { + return; + } + untransformedDT->SetTransform( + Matrix::Translation(-Point(pixelBounds.X(), pixelBounds.Y()))); + + gfxContext groupTarget(untransformedDT, /* aPreserveTransform */ true); + + if (aPolygon) { + RefPtr path = + BuildPathFromPolygon(aCtx->GetDrawTarget(), *aPolygon); + aCtx->GetDrawTarget()->PushClip(path); + } + + GetChildren()->Paint(aBuilder, &groupTarget, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + if (aPolygon) { + aCtx->GetDrawTarget()->PopClip(); + } + + RefPtr untransformedSurf = untransformedDT->Snapshot(); + + trans.PreTranslate(pixelBounds.X(), pixelBounds.Y(), 0); + aCtx->GetDrawTarget()->Draw3DTransformedSurface(untransformedSurf, trans); +} + +bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder) const { + // If EffectCompositor::HasAnimationsForCompositor() is true then we can + // completely bypass the main thread for this animation, so it is always + // worthwhile. + // For ActiveLayerTracker::IsTransformAnimated() cases the main thread is + // already involved so there is less to be gained. + // Therefore we check that the *post-transform* bounds of this item are + // big enough to justify an active layer. + return EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_TRANSFORM) || + (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame)); +} + +nsRect nsDisplayTransform::TransformUntransformedBounds( + nsDisplayListBuilder* aBuilder, const Matrix4x4Flagged& aMatrix) const { + bool snap; + const nsRect untransformedBounds = GetUntransformedBounds(aBuilder, &snap); + // GetTransform always operates in dev pixels. + const float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + return nsLayoutUtils::MatrixTransformRect(untransformedBounds, aMatrix, + factor); +} + +/** + * Returns the bounds for this transform. The bounds are calculated during + * display list building and merging, see |nsDisplayTransform::UpdateBounds()|. + */ +nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +void nsDisplayTransform::ComputeBounds(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() || IsLeafOf3DContext()); + + /* Some transforms can get empty bounds in 2D, but might get transformed again + * and get non-empty bounds. A simple example of this would be a 180 degree + * rotation getting applied twice. + * We should not depend on transforming bounds level by level. + * + * This function collects the bounds of this transform and stores it in + * nsDisplayListBuilder. If this is not a leaf of a 3D context, we recurse + * down and include the bounds of the child transforms. + * The bounds are transformed with the accumulated transformation matrix up to + * the 3D context root coordinate space. + */ + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.Accumulate(GetTransform().GetMatrix()); + + // Do not dive into another 3D context. + if (!IsLeafOf3DContext()) { + for (nsDisplayItem* i : *GetChildren()) { + i->DoUpdateBoundsPreserves3D(aBuilder); + } + } + + /* The child transforms that extend 3D context further will have empty bounds, + * so the untransformed bounds here is the bounds of all the non-preserve-3d + * content under this transform. + */ + const nsRect rect = TransformUntransformedBounds( + aBuilder, accTransform.GetCurrentTransform()); + aBuilder->AccumulateRect(rect); +} + +void nsDisplayTransform::DoUpdateBoundsPreserves3D( + nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Combines3DTransformWithAncestors() || + IsTransformSeparator()); + // Updating is not going through to child 3D context. + ComputeBounds(aBuilder); +} + +void nsDisplayTransform::UpdateBounds(nsDisplayListBuilder* aBuilder) { + UpdateUntransformedBounds(aBuilder); + + if (IsTransformSeparator()) { + MOZ_ASSERT(GetTransform().IsIdentity()); + mBounds = mChildBounds; + return; + } + + if (mFrame->Extend3DContext()) { + if (!Combines3DTransformWithAncestors()) { + // The transform establishes a 3D context. |UpdateBoundsFor3D()| will + // collect the bounds from the child transforms. + UpdateBoundsFor3D(aBuilder); + } else { + // With nested 3D transforms, the 2D bounds might not be useful. + mBounds = nsRect(); + } + + return; + } + + MOZ_ASSERT(!mFrame->Extend3DContext()); + + // We would like to avoid calculating 2D bounds here for nested 3D transforms, + // but mix-blend-mode relies on having bounds set. See bug 1556956. + + // A stand-alone transform. + mBounds = TransformUntransformedBounds(aBuilder, GetTransform()); +} + +void nsDisplayTransform::UpdateBoundsFor3D(nsDisplayListBuilder* aBuilder) { + MOZ_ASSERT(mFrame->Extend3DContext() && + !mFrame->Combines3DTransformWithAncestors() && + !IsTransformSeparator()); + + // Always start updating from an establisher of a 3D rendering context. + nsDisplayListBuilder::AutoAccumulateRect accRect(aBuilder); + nsDisplayListBuilder::AutoAccumulateTransform accTransform(aBuilder); + accTransform.StartRoot(); + ComputeBounds(aBuilder); + mBounds = aBuilder->GetAccumulatedRect(); +} + +void nsDisplayTransform::UpdateUntransformedBounds( + nsDisplayListBuilder* aBuilder) { + mChildBounds = GetChildren()->GetClippedBoundsWithRespectToASR( + aBuilder, mActiveScrolledRoot); +} + +#ifdef DEBUG_HIT +# include +#endif + +/* HitTest does some fun stuff with matrix transforms to obtain the answer. */ +void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { + if (aState->mInPreserves3D) { + GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); + return; + } + + /* Here's how this works: + * 1. Get the matrix. If it's singular, abort (clearly we didn't hit + * anything). + * 2. Invert the matrix. + * 3. Use it to transform the rect into the correct space. + * 4. Pass that rect down through to the list's version of HitTest. + */ + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + if (!IsFrameVisible(mFrame, matrix)) { + return; + } + + const bool oldHitOccludingItem = aState->mHitOccludingItem; + + /* We want to go from transformed-space to regular space. + * Thus we have to invert the matrix, which normally does + * the reverse operation (e.g. regular->transformed) + */ + + /* Now, apply the transform and pass it down the channel. */ + matrix.Invert(); + nsRect resultingRect; + // Magic width/height indicating we're hit testing a point, not a rect + const bool testingPoint = aRect.width == 1 && aRect.height == 1; + if (testingPoint) { + Point4D point = + matrix.ProjectPoint(Point(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor))); + if (!point.HasPositiveWCoord()) { + return; + } + + Point point2d = point.As2DPoint(); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(point2d.x), factor), + NSFloatPixelsToAppUnits(float(point2d.y), factor), 1, 1); + + } else { + Rect originalRect(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + Rect childGfxBounds(NSAppUnitsToFloatPixels(mChildBounds.x, factor), + NSAppUnitsToFloatPixels(mChildBounds.y, factor), + NSAppUnitsToFloatPixels(mChildBounds.width, factor), + NSAppUnitsToFloatPixels(mChildBounds.height, factor)); + + Rect rect = matrix.ProjectRectBounds(originalRect, childGfxBounds); + + resultingRect = + nsRect(NSFloatPixelsToAppUnits(float(rect.X()), factor), + NSFloatPixelsToAppUnits(float(rect.Y()), factor), + NSFloatPixelsToAppUnits(float(rect.Width()), factor), + NSFloatPixelsToAppUnits(float(rect.Height()), factor)); + } + + if (resultingRect.IsEmpty()) { + return; + } + +#ifdef DEBUG_HIT + printf("Frame: %p\n", dynamic_cast(mFrame)); + printf(" Untransformed point: (%f, %f)\n", resultingRect.X(), + resultingRect.Y()); + uint32_t originalFrameCount = aOutFrames.Length(); +#endif + + GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames); + + if (aState->mHitOccludingItem && !testingPoint && + !mChildBounds.Contains(aRect)) { + MOZ_ASSERT(aBuilder->HitTestIsForVisibility()); + // We're hit-testing a rect that's bigger than our child bounds, but + // resultingRect is clipped by our bounds (in ProjectRectBounds above), so + // we can't stop hit-testing altogether. + // + // FIXME(emilio): I think this means that theoretically we might include + // some frames fully behind other transformed-but-opaque frames? Then again + // that's our pre-existing behavior for other untransformed content that + // doesn't fill the whole rect. To be fully correct I think we'd need proper + // "known occluded region" tracking, but that might be overkill for our + // purposes here. + aState->mHitOccludingItem = oldHitOccludingItem; + } + +#ifdef DEBUG_HIT + if (originalFrameCount != aOutFrames.Length()) + printf(" Hit! Time: %f, first frame: %p\n", static_cast(clock()), + dynamic_cast(aOutFrames.ElementAt(0))); + printf("=== end of hit test ===\n"); +#endif +} + +float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, + const nsPoint& aPoint) { + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); + + NS_ASSERTION(IsFrameVisible(mFrame, matrix), + "We can't have hit a frame that isn't visible!"); + + Matrix4x4 inverse = matrix; + inverse.Invert(); + Point4D point = + inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor), + NSAppUnitsToFloatPixels(aPoint.y, factor))); + + Point point2d = point.As2DPoint(); + + Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0)); + return transformed.z; +} + +/* The transform is opaque iff the transform consists solely of scales and + * translations and if the underlying content is opaque. Thus if the transform + * is of the form + * + * |a c e| + * |b d f| + * |0 0 1| + * + * We need b and c to be zero. + * + * We also need to check whether the underlying opaque content completely fills + * our visible rect. We use UntransformRect which expands to the axis-aligned + * bounding rect, but that's OK since if + * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it + * certainly contains the actual (non-axis-aligned) untransformed rect. + */ +nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + + nsRect untransformedVisible; + if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) { + return nsRegion(); + } + + const Matrix4x4Flagged& matrix = GetTransform(); + Matrix matrix2d; + if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) { + return nsRegion(); + } + + nsRegion result; + + bool tmpSnap; + const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap); + const nsRegion opaque = + ::mozilla::GetOpaqueRegion(aBuilder, GetChildren(), bounds); + + if (opaque.Contains(untransformedVisible)) { + result = GetBuildingRect().Intersect(GetBounds(aBuilder, &tmpSnap)); + } + return result; +} + +nsRect nsDisplayTransform::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + if (GetChildren()->GetComponentAlphaBounds(aBuilder).IsEmpty()) { + return nsRect(); + } + + bool snap; + return GetBounds(aBuilder, &snap); +} + +/* TransformRect takes in as parameters a rectangle (in app space) and returns + * the smallest rectangle (in app space) containing the transformed image of + * that rectangle. That is, it takes the four corners of the rectangle, + * transforms them according to the matrix associated with the specified frame, + * then returns the smallest rectangle containing the four transformed points. + * + * @param aUntransformedBounds The rectangle (in app units) to transform. + * @param aFrame The frame whose transformation should be applied. + * @param aOrigin The delta from the frame origin to the coordinate space origin + * @return The smallest rectangle containing the image of the transformed + * rectangle. + */ +nsRect nsDisplayTransform::TransformRect(const nsRect& aUntransformedBounds, + const nsIFrame* aFrame, + TransformReferenceBox& aRefBox) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + + FrameTransformProperties props(aFrame, aRefBox, factor); + return nsLayoutUtils::MatrixTransformRect( + aUntransformedBounds, + GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(), factor, + kTransformRectFlags), + factor); +} + +bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const nsIFrame* aFrame, + nsRect* aOutRect) { + MOZ_ASSERT(aFrame, "Can't take the transform based on a null frame!"); + + float factor = aFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 transform = GetResultingTransformMatrix(aFrame, nsPoint(), factor, + kTransformRectFlags); + return UntransformRect(aTransformedBounds, aChildBounds, transform, factor, + aOutRect); +} + +bool nsDisplayTransform::UntransformRect(const nsRect& aTransformedBounds, + const nsRect& aChildBounds, + const Matrix4x4& aMatrix, + float aAppUnitsPerPixel, + nsRect* aOutRect) { + if (aMatrix.IsSingular()) { + return false; + } + + RectDouble result( + NSAppUnitsToFloatPixels(aTransformedBounds.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.y, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.width, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aTransformedBounds.height, aAppUnitsPerPixel)); + + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(aChildBounds.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.y, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.width, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(aChildBounds.height, aAppUnitsPerPixel)); + + result = aMatrix.Inverse().ProjectRectBounds(result, childGfxBounds); + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), + aAppUnitsPerPixel); + return true; +} + +bool nsDisplayTransform::UntransformRect(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsRect* aOutRect) const { + if (GetTransform().IsSingular()) { + return false; + } + + // GetTransform always operates in dev pixels. + float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + RectDouble result(NSAppUnitsToFloatPixels(aRect.x, factor), + NSAppUnitsToFloatPixels(aRect.y, factor), + NSAppUnitsToFloatPixels(aRect.width, factor), + NSAppUnitsToFloatPixels(aRect.height, factor)); + + bool snap; + nsRect childBounds = GetUntransformedBounds(aBuilder, &snap); + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.height, factor)); + + /* We want to untransform the matrix, so invert the transformation first! */ + result = GetInverseTransform().ProjectRectBounds(result, childGfxBounds); + + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); + + return true; +} + +void nsDisplayTransform::WriteDebugInfo(std::stringstream& aStream) { + aStream << GetTransform().GetMatrix(); + if (IsTransformSeparator()) { + aStream << " transform-separator"; + } + if (IsLeafOf3DContext()) { + aStream << " 3d-context-leaf"; + } + if (mFrame->Extend3DContext()) { + aStream << " extends-3d-context"; + } + if (mFrame->Combines3DTransformWithAncestors()) { + aStream << " combines-3d-with-ancestors"; + } + + aStream << " prerender("; + switch (mPrerenderDecision) { + case PrerenderDecision::No: + aStream << "no"; + break; + case PrerenderDecision::Partial: + aStream << "partial"; + break; + case PrerenderDecision::Full: + aStream << "full"; + break; + } + aStream << ")"; + aStream << " childrenBuildingRect" << mChildrenBuildingRect; +} + +nsDisplayPerspective::nsDisplayPerspective(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsPaintedDisplayItem(aBuilder, aFrame), mList(aBuilder) { + mList.AppendToTop(aList); + MOZ_ASSERT(mList.Length() == 1); + MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM); +} + +void nsDisplayPerspective::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // Just directly recurse into children, since we'll include the persepctive + // value in any nsDisplayTransform children. + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + if (!GetChildren()->GetTop()) { + *aSnap = false; + return nsRegion(); + } + + return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap); +} + +bool nsDisplayPerspective::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 perspectiveMatrix; + DebugOnly hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( + mFrame, appUnitsPerPixel, perspectiveMatrix); + MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); + + /* + * ClipListToRange can remove our child after we were created. + */ + if (!GetChildren()->GetTop()) { + return false; + } + + /* + * The resulting matrix is still in the coordinate space of the transformed + * frame. Append a translation to the reference frame coordinates. + */ + nsDisplayTransform* transform = + static_cast(GetChildren()->GetTop()); + + Point3D newOrigin = + Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, + appUnitsPerPixel), + NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, + appUnitsPerPixel), + 0.0f); + Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0); + + perspectiveMatrix.PostTranslate(roundedOrigin); + + nsIFrame* perspectiveFrame = + mFrame->GetClosestFlattenedTreeAncestorPrimaryFrame(); + + // Passing true here is always correct, since perspective always combines + // transforms with the descendants. However that'd make WR do a lot of work + // that it doesn't really need to do if there aren't other transforms forming + // part of the 3D context. + // + // WR knows how to treat perspective in that case, so the only thing we need + // to do is to ensure we pass true when we're involved in a 3d context in any + // other way via the transform-style property on either the transformed frame + // or the perspective frame in order to not confuse WR's preserve-3d code in + // very awful ways. + bool preserve3D = + mFrame->Extend3DContext() || perspectiveFrame->Extend3DContext(); + + wr::StackingContextParams params; + + wr::WrTransformInfo transform_info; + transform_info.transform = wr::ToLayoutTransform(perspectiveMatrix); + transform_info.key = wr::SpatialKey(uint64_t(mFrame), GetPerFrameKey(), + wr::SpatialKeyKind::Perspective); + params.mTransformPtr = &transform_info; + + params.reference_frame_kind = wr::WrReferenceFrameKind::Perspective; + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + params.SetPreserve3D(preserve3D); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + + Maybe scrollingRelativeTo; + for (const auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) { + // In OOP documents, the root scrollable frame of the in-process root + // document is always active, so using IsAncestorFrameCrossDocInProcess + // should be fine here. + if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess( + asr->mScrollableFrame->GetScrolledFrame(), perspectiveFrame)) { + scrollingRelativeTo.emplace(asr->GetViewId()); + break; + } + } + + // We put the perspective reference frame wrapping the transformed frame, + // even though there may be arbitrarily nested scroll frames in between. + // + // We need to know how many ancestor scroll-frames are we nested in, in order + // for the async scrolling code in WebRender to calculate the right + // transformation for the perspective contents. + params.scrolling_relative_to = scrollingRelativeTo.ptrOr(nullptr); + + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, sc, aBuilder, aResources); + + return true; +} + +nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, + nsTextFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame), + mVisIStartEdge(0), + mVisIEndEdge(0) { + MOZ_COUNT_CTOR(nsDisplayText); + mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); + // Bug 748228 + mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); + mVisibleRect = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); +} + +bool nsDisplayText::CanApplyOpacity(WebRenderLayerManager* aManager, + nsDisplayListBuilder* aBuilder) const { + auto* f = static_cast(mFrame); + + if (f->IsSelected()) { + return false; + } + + const nsStyleText* textStyle = f->StyleText(); + if (textStyle->HasTextShadow()) { + return false; + } + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + return !decorations.HasDecorationLines(); +} + +void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS); + // We don't pass mVisibleRect here, since this can be called from within + // the WebRender fallback painting path, and we don't want to issue + // recorded commands that are dependent on the visible/building rect. + RenderToContext(aCtx, aBuilder, GetPaintRect(aBuilder, aCtx)); + + auto* textFrame = static_cast(mFrame); + LCPTextFrameHelper::MaybeUnionTextFrame(textFrame, + mBounds - ToReferenceFrame()); +} + +bool nsDisplayText::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + auto* f = static_cast(mFrame); + auto appUnitsPerDevPixel = f->PresContext()->AppUnitsPerDevPixel(); + + nsRect bounds = f->WebRenderBounds() + ToReferenceFrame(); + // Bug 748228 + bounds.Inflate(appUnitsPerDevPixel); + + if (bounds.IsEmpty()) { + return true; + } + + // For large font sizes, punt to a blob image, to avoid the blurry rendering + // that results from WR clamping the glyph size used for rasterization. + // + // (See FONT_SIZE_LIMIT in webrender/src/glyph_rasterizer/mod.rs.) + // + // This is not strictly accurate, as final used font sizes might not be the + // same as claimed by the fontGroup's style.size (eg: due to font-size-adjust + // altering the used size of the font actually used). + // It also fails to consider how transforms might affect the device-font-size + // that webrender uses (and clamps). + // But it should be near enough for practical purposes; the limitations just + // mean we might sometimes end up with webrender still applying some bitmap + // scaling, or bail out when we didn't really need to. + constexpr float kWebRenderFontSizeLimit = 320.0; + f->EnsureTextRun(nsTextFrame::eInflated); + gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); + if (textRun && + textRun->GetFontGroup()->GetStyle()->size > kWebRenderFontSizeLimit) { + return false; + } + + gfx::Point deviceOffset = + LayoutDevicePoint::FromAppUnits(bounds.TopLeft(), appUnitsPerDevPixel) + .ToUnknownPoint(); + + // Clipping the bounds to the PaintRect (factoring in what's covered by parent + // frames) lets us early reject a bunch of things. + nsRect visible = mVisibleRect; + + // Add the "source rect" area from which the given shadows could intersect + // with mVisibleRect, and which therefore needs to included in the paint + // operation, to the `visible` rect that we will use to limit the bounds of + // what we send to the renderer. + auto addShadowSourceToVisible = [&](Span aShadows) { + for (const auto& shadow : aShadows) { + nsRect sourceRect = mVisibleRect; + // Negate the offsets, because we're looking for the "source" rect that + // could cast a shadow into the visible rect, rather than a "target" area + // onto which the visible rect would cast a shadow. + sourceRect.MoveBy(-shadow.horizontal.ToAppUnits(), + -shadow.vertical.ToAppUnits()); + // Inflate to account for the shadow blur. + sourceRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin( + shadow.blur.ToAppUnits(), appUnitsPerDevPixel)); + visible.OrWith(sourceRect); + } + }; + + // Shadows can translate things back into view, so we enlarge the notional + // "visible" rect to ensure we don't skip painting relevant parts that might + // cast a shadow within the visible area. + addShadowSourceToVisible(f->StyleText()->mTextShadow.AsSpan()); + + // Similarly for shadows that may be cast by ::selection. + if (f->IsSelected()) { + nsTextPaintStyle textPaint(f); + Span shadows; + f->GetSelectionTextShadow(SelectionType::eNormal, textPaint, &shadows); + addShadowSourceToVisible(shadows); + } + + // Inflate a little extra to allow for potential antialiasing "blur". + visible.Inflate(3 * appUnitsPerDevPixel); + bounds = bounds.Intersect(visible); + + gfxContext* textDrawer = aBuilder.GetTextContext(aResources, aSc, aManager, + this, bounds, deviceOffset); + + LCPTextFrameHelper::MaybeUnionTextFrame(f, bounds - ToReferenceFrame()); + + aBuilder.StartGroup(this); + + RenderToContext(textDrawer, aDisplayListBuilder, mVisibleRect, + aBuilder.GetInheritedOpacity(), true); + const bool result = textDrawer->GetTextDrawer()->Finish(); + + if (result) { + aBuilder.FinishGroup(); + } else { + aBuilder.CancelGroup(true); + } + + return result; +} + +void nsDisplayText::RenderToContext(gfxContext* aCtx, + nsDisplayListBuilder* aBuilder, + const nsRect& aVisibleRect, float aOpacity, + bool aIsRecording) { + nsTextFrame* f = static_cast(mFrame); + + // Add 1 pixel of dirty area around mVisibleRect to allow us to paint + // antialiased pixels beyond the measured text extents. + // This is temporary until we do this in the actual calculation of text + // extents. + auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect extraVisible = + LayoutDeviceRect::FromAppUnits(aVisibleRect, A2D); + extraVisible.Inflate(1); + + gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width, + extraVisible.height); + pixelVisible.Inflate(2); + pixelVisible.RoundOut(); + + gfxClipAutoSaveRestore autoSaveClip(aCtx); + if (!aBuilder->IsForGenerateGlyphMask() && !aIsRecording) { + autoSaveClip.Clip(pixelVisible); + } + + NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge"); + NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge"); + + gfxContextMatrixAutoSaveRestore matrixSR; + + nsPoint framePt = ToReferenceFrame(); + if (f->Style()->IsTextCombined()) { + float scaleFactor = nsTextFrame::GetTextCombineScaleFactor(f); + if (scaleFactor != 1.0f) { + if (auto* textDrawer = aCtx->GetTextDrawer()) { + // WebRender doesn't support scaling text like this yet + textDrawer->FoundUnsupportedFeature(); + return; + } + matrixSR.SetContext(aCtx); + // Setup matrix to compress text for text-combine-upright if + // necessary. This is done here because we want selection be + // compressed at the same time as text. + gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D); + gfxTextRun* textRun = f->GetTextRun(nsTextFrame::eInflated); + if (textRun && textRun->IsRightToLeft()) { + pt.x += gfxFloat(f->GetSize().width) / A2D; + } + gfxMatrix mat = aCtx->CurrentMatrixDouble() + .PreTranslate(pt) + .PreScale(scaleFactor, 1.0) + .PreTranslate(-pt); + aCtx->SetMatrixDouble(mat); + } + } + nsTextFrame::PaintTextParams params(aCtx); + params.framePt = gfx::Point(framePt.x, framePt.y); + params.dirtyRect = extraVisible; + + if (aBuilder->IsForGenerateGlyphMask()) { + params.state = nsTextFrame::PaintTextParams::GenerateTextMask; + } else { + params.state = nsTextFrame::PaintTextParams::PaintText; + } + + f->PaintText(params, mVisIStartEdge, mVisIEndEdge, ToReferenceFrame(), + f->IsSelected(), aOpacity); +} + +// This could go to nsDisplayListInvalidation.h, but +// |nsTextFrame::TextDecorations| requires including of nsTextFrame.h which +// would produce circular dependencies. +class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mVisIStartEdge(aItem->VisIStartEdge()), + mVisIEndEdge(aItem->VisIEndEdge()) { + nsTextFrame* f = static_cast(aItem->Frame()); + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + mDecorations); + } + + /** + * We store the computed text decorations here since they are + * computed using style data from parent frames. Any changes to these + * styles will only invalidate the parent frame and not this frame. + */ + nsTextFrame::TextDecorations mDecorations; + nscoord mVisIStartEdge; + nscoord mVisIEndEdge; +}; + +nsDisplayItemGeometry* nsDisplayText::AllocateGeometry( + nsDisplayListBuilder* aBuilder) { + return new nsDisplayTextGeometry(this, aBuilder); +} + +void nsDisplayText::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const nsDisplayTextGeometry* geometry = + static_cast(aGeometry); + nsTextFrame* f = static_cast(mFrame); + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + + bool snap; + const nsRect& newRect = geometry->mBounds; + nsRect oldRect = GetBounds(aBuilder, &snap); + if (decorations != geometry->mDecorations || + mVisIStartEdge != geometry->mVisIStartEdge || + mVisIEndEdge != geometry->mVisIEndEdge || + !oldRect.IsEqualInterior(newRect) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect())) { + aInvalidRegion->Or(oldRect, newRect); + } +} + +void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) { +#ifdef DEBUG + aStream << " (\""; + + nsTextFrame* f = static_cast(mFrame); + nsCString buf; + f->ToCString(buf); + + aStream << buf.get() << "\")"; +#endif +} + +nsDisplayEffectsBase::nsDisplayEffectsBase( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsRegion nsDisplayEffectsBase::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return nsRegion(); +} + +void nsDisplayEffectsBase::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray* aOutFrames) { + nsPoint rectCenter(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2); + if (SVGIntegrationUtils::HitTestFrameForEffects( + mFrame, rectCenter - ToReferenceFrame())) { + mList.HitTest(aBuilder, aRect, aState, aOutFrames); + } +} + +gfxRect nsDisplayEffectsBase::BBoxInUserSpace() const { + return SVGUtils::GetBBox(mFrame); +} + +gfxPoint nsDisplayEffectsBase::UserSpaceOffset() const { + return SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(mFrame); +} + +void nsDisplayEffectsBase::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + const auto* geometry = + static_cast(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() || + geometry->mUserSpaceOffset != UserSpaceOffset() || + !geometry->mBBox.IsEqualInterior(BBoxInUserSpace())) { + // Filter and mask output can depend on the location of the frame's user + // space and on the frame's BBox. We need to invalidate if either of these + // change relative to the reference frame. + // Invalidations from our inactive layer manager are not enough to catch + // some of these cases because filters can produce output even if there's + // nothing in the filter input. + aInvalidRegion->Or(bounds, geometry->mBounds); + } +} + +bool nsDisplayEffectsBase::ValidateSVGFrame() { + if (mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame); + if (!svgFrame) { + return false; + } + if (auto* svgElement = SVGElement::FromNode(mFrame->GetContent())) { + // The SVG spec says only to draw filters if the element + // has valid dimensions. + return svgElement->HasValidDimensions(); + } + return false; + } + + return true; +} + +using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams; + +static void ComputeMaskGeometry(PaintFramesParams& aParams) { + // Properties are added lazily and may have been removed by a restyle, so + // make sure all applicable ones are set again. + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(aParams.frame); + + const nsStyleSVGReset* svgReset = firstFrame->StyleSVGReset(); + + nsTArray maskFrames; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); + + if (maskFrames.Length() == 0) { + return; + } + + gfxContext& ctx = aParams.ctx; + nsIFrame* frame = aParams.frame; + + nsPoint offsetToUserSpace = + nsLayoutUtils::ComputeOffsetToUserSpace(aParams.builder, aParams.frame); + + auto cssToDevScale = frame->PresContext()->CSSToDevPixelScale(); + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + gfxPoint devPixelOffsetToUserSpace = + nsLayoutUtils::PointToGfxPoint(offsetToUserSpace, appUnitsPerDevPixel); + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + ctx.SetMatrixDouble( + ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace)); + + // Convert boaderArea and dirtyRect to user space. + nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace; + nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace; + + // Union all mask layer rectangles in user space. + LayoutDeviceRect maskInUserSpace; + for (size_t i = 0; i < maskFrames.Length(); i++) { + SVGMaskFrame* maskFrame = maskFrames[i]; + LayoutDeviceRect currentMaskSurfaceRect; + + if (maskFrame) { + auto rect = maskFrame->GetMaskArea(aParams.frame); + currentMaskSurfaceRect = + CSSRect::FromUnknownRect(ToRect(rect)) * cssToDevScale; + } else { + nsCSSRendering::ImageLayerClipState clipState; + nsCSSRendering::GetImageLayerClip( + svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(), + userSpaceBorderArea, userSpaceDirtyRect, + /* aWillPaintBorder = */ false, appUnitsPerDevPixel, &clipState); + currentMaskSurfaceRect = LayoutDeviceRect::FromUnknownRect( + ToRect(clipState.mDirtyRectInDevPx)); + } + + maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect); + } + + if (!maskInUserSpace.IsEmpty()) { + aParams.maskRect = Some(maskInUserSpace); + } else { + aParams.maskRect = Nothing(); + } +} + +nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aWrapsBackdropFilter) + : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayMasksAndClipPaths); + + nsPresContext* presContext = mFrame->PresContext(); + uint32_t flags = + aBuilder->GetBackgroundPaintFlags() | nsCSSRendering::PAINTBG_MASK_IMAGE; + const nsStyleSVGReset* svgReset = aFrame->StyleSVGReset(); + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) { + const auto& layer = svgReset->mMask.mLayers[i]; + if (!layer.mImage.IsResolved()) { + continue; + } + const nsRect& borderArea = mFrame->GetRectRelativeToSelf(); + // NOTE(emilio): We only care about the dest rect so we don't bother + // computing a clip. + bool isTransformedFixed = false; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, borderArea, borderArea, layer, + &isTransformedFixed); + mDestRects.AppendElement(state.mDestArea); + } +} + +static bool CanMergeDisplayMaskFrame(nsIFrame* aFrame) { + // Do not merge items for box-decoration-break:clone elements, + // since each box should have its own mask in that case. + if (aFrame->StyleBorder()->mBoxDecorationBreak == + StyleBoxDecorationBreak::Clone) { + return false; + } + + // Do not merge if either frame has a mask. Continuation frames should apply + // the mask independently (just like nsDisplayBackgroundImage). + if (aFrame->StyleSVGReset()->HasMask()) { + return false; + } + + return true; +} + +bool nsDisplayMasksAndClipPaths::CanMerge(const nsDisplayItem* aItem) const { + // Items for the same content element should be merged into a single + // compositing group. + if (!HasDifferentFrame(aItem) || !HasSameTypeAndClip(aItem) || + !HasSameContent(aItem)) { + return false; + } + + return CanMergeDisplayMaskFrame(mFrame) && + CanMergeDisplayMaskFrame(aItem->Frame()); +} + +bool nsDisplayMasksAndClipPaths::IsValidMask() { + if (!ValidateSVGFrame()) { + return false; + } + + return SVGUtils::DetermineMaskUsage(mFrame, false).UsingMaskOrClipPath(); +} + +bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder, + gfxContext* aMaskContext, + bool aHandleOpacity, + bool* aMaskPainted) { + MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aMaskContext, mFrame, mBounds, borderArea, aBuilder, + aHandleOpacity, imgParams); + ComputeMaskGeometry(params); + bool maskIsComplete = false; + bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete); + if (aMaskPainted) { + *aMaskPainted = painted; + } + + return maskIsComplete && + (imgParams.result == ImgDrawResult::SUCCESS || + imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE || + imgParams.result == ImgDrawResult::WRONG_SIZE); +} + +void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + const auto* geometry = + static_cast(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + if (mDestRects.Length() != geometry->mDestRects.Length()) { + aInvalidRegion->Or(bounds, geometry->mBounds); + } else { + for (size_t i = 0; i < mDestRects.Length(); i++) { + if (!mDestRects[i].IsEqualInterior(geometry->mDestRects[i])) { + aInvalidRegion->Or(bounds, geometry->mBounds); + break; + } + } + } +} + +void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function& aPaintChildren) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + Rect bounds = NSRectToRect(GetPaintRect(aBuilder, aCtx), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + gfxClipAutoSaveRestore autoSaveClip(aCtx); + autoSaveClip.Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aCtx, mFrame, GetPaintRect(aBuilder, aCtx), + borderArea, aBuilder, false, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren); +} + +void nsDisplayMasksAndClipPaths::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + if (!IsValidMask()) { + return; + } + PaintWithContentsPaintCallback(aBuilder, aCtx, [&] { + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); + }); +} + +static Maybe CreateSimpleClipRegion( + const nsDisplayMasksAndClipPaths& aDisplayItem, + wr::DisplayListBuilder& aBuilder) { + nsIFrame* frame = aDisplayItem.Frame(); + const auto* style = frame->StyleSVGReset(); + MOZ_ASSERT(style->HasClipPath() || style->HasMask()); + if (!SVGUtils::DetermineMaskUsage(frame, false).IsSimpleClipShape()) { + return Nothing(); + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + const nsRect refBox = + nsLayoutUtils::ComputeClipPathGeometryBox(frame, clipPath.AsShape()._1); + + wr::WrClipId clipId{}; + + switch (shape.tag) { + case StyleBasicShape::Tag::Rect: { + const nsRect rect = + ShapeUtils::ComputeInsetRect(shape.AsRect().rect, refBox) + + aDisplayItem.ToReferenceFrame(); + + nscoord radii[8] = {0}; + if (ShapeUtils::ComputeRectRadii(shape.AsRect().round, refBox, rect, + radii)) { + clipId = aBuilder.DefineRoundedRectClip( + Nothing(), + wr::ToComplexClipRegion(rect, radii, appUnitsPerDevPixel)); + } else { + clipId = aBuilder.DefineRectClip( + Nothing(), wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits( + rect, appUnitsPerDevPixel))); + } + + break; + } + case StyleBasicShape::Tag::Ellipse: + case StyleBasicShape::Tag::Circle: { + nsPoint center = ShapeUtils::ComputeCircleOrEllipseCenter(shape, refBox); + + nsSize radii; + if (shape.IsEllipse()) { + radii = ShapeUtils::ComputeEllipseRadii(shape, center, refBox); + } else { + nscoord radius = ShapeUtils::ComputeCircleRadius(shape, center, refBox); + radii = {radius, radius}; + } + + nsRect ellipseRect(aDisplayItem.ToReferenceFrame() + center - + nsPoint(radii.width, radii.height), + radii * 2); + + nscoord ellipseRadii[8]; + for (const auto corner : AllPhysicalHalfCorners()) { + ellipseRadii[corner] = + HalfCornerIsX(corner) ? radii.width : radii.height; + } + + clipId = aBuilder.DefineRoundedRectClip( + Nothing(), wr::ToComplexClipRegion(ellipseRect, ellipseRadii, + appUnitsPerDevPixel)); + + break; + } + default: + // Please don't add more exceptions, try to find a way to define the clip + // without using a mask image. + // + // And if you _really really_ need to add an exception, add it to + // SVGUtils::DetermineMaskUsage + MOZ_ASSERT_UNREACHABLE("Unhandled shape id?"); + return Nothing(); + } + + wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true); + + return Some(clipChainId); +} + +static void FillPolygonDataForDisplayItem( + const nsDisplayMasksAndClipPaths& aDisplayItem, + nsTArray& aPoints, wr::FillRule& aFillRule) { + nsIFrame* frame = aDisplayItem.Frame(); + const auto* style = frame->StyleSVGReset(); + bool isPolygon = style->HasClipPath() && style->mClipPath.IsShape() && + style->mClipPath.AsShape()._0->IsPolygon(); + if (!isPolygon) { + return; + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + const nsRect refBox = + nsLayoutUtils::ComputeClipPathGeometryBox(frame, clipPath.AsShape()._1); + + // We only fill polygon data for polygons that are below a complexity + // limit. + nsTArray vertices = + ShapeUtils::ComputePolygonVertices(shape, refBox); + if (vertices.Length() > wr::POLYGON_CLIP_VERTEX_MAX) { + return; + } + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + for (size_t i = 0; i < vertices.Length(); ++i) { + wr::LayoutPoint point = wr::ToLayoutPoint( + LayoutDevicePoint::FromAppUnits(vertices[i], appUnitsPerDevPixel)); + aPoints.AppendElement(point); + } + + aFillRule = (shape.AsPolygon().fill == StyleFillRule::Nonzero) + ? wr::FillRule::Nonzero + : wr::FillRule::Evenodd; +} + +static Maybe CreateWRClipPathAndMasks( + nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds, + wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) { + return clip; + } + + Maybe mask = aManager->CommandBuilder().BuildWrMaskImage( + aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds); + if (!mask) { + return Nothing(); + } + + // We couldn't create a simple clip region, but before we create an image + // mask clip, see if we can get a polygon clip to add to it. + nsTArray points; + wr::FillRule fillRule = wr::FillRule::Nonzero; + FillPolygonDataForDisplayItem(*aDisplayItem, points, fillRule); + + wr::WrClipId clipId = + aBuilder.DefineImageMaskClip(mask.ref(), points, fillRule); + + wr::WrClipChainId clipChainId = aBuilder.DefineClipChain({clipId}, true); + + return Some(clipChainId); +} + +bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + bool snap; + auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap); + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel); + + Maybe clip = CreateWRClipPathAndMasks( + this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder); + + float oldOpacity = aBuilder.GetInheritedOpacity(); + + Maybe layer; + const StackingContextHelper* sc = &aSc; + if (clip) { + // Create a new stacking context to attach the mask to, ensuring the mask is + // applied to the aggregate, and not the individual elements. + + // The stacking context shouldn't have any offset. + bounds.MoveTo(0, 0); + + Maybe opacity = + (SVGUtils::DetermineMaskUsage(mFrame, false).IsSimpleClipShape() && + aBuilder.GetInheritedOpacity() != 1.0f) + ? Some(aBuilder.GetInheritedOpacity()) + : Nothing(); + + wr::StackingContextParams params; + params.clip = wr::WrStackingContextClip::ClipChain(clip->id); + params.opacity = opacity.ptrOr(nullptr); + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params, + bounds); + sc = layer.ptr(); + } + + aBuilder.SetInheritedOpacity(1.0f); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedClipChain(nullptr); + CreateWebRenderCommandsNewClipListOption(aBuilder, aResources, *sc, aManager, + aDisplayListBuilder, layer.isSome()); + aBuilder.SetInheritedOpacity(oldOpacity); + aBuilder.SetInheritedClipChain(oldClipChain); + + return true; +} + +Maybe nsDisplayMasksAndClipPaths::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } + // This item does not have a clip with respect to |aASR|. However, we + // might still have finite bounds with respect to |aASR|. Check our + // children. + nsDisplayList* childList = GetSameCoordinateSystemChildren(); + if (childList) { + return Some(childList->GetClippedBoundsWithRespectToASR(aBuilder, aASR)); + } +#ifdef DEBUG + NS_ASSERTION(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayMasksAndClipPaths::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + SVGClipPathFrame* clipPathFrame; + // XXX Check return value? + SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); + if (clipPathFrame) { + if (!first) { + aTo += ", "; + } + aTo += nsPrintfCString( + "clip(%s)", clipPathFrame->IsTrivial() ? "trivial" : "non-trivial"); + first = false; + } else if (mFrame->StyleSVGReset()->HasClipPath()) { + if (!first) { + aTo += ", "; + } + aTo += "clip(basic-shape)"; + first = false; + } + + nsTArray masks; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks); + if (!masks.IsEmpty() && masks[0]) { + if (!first) { + aTo += ", "; + } + aTo += "mask"; + } + aTo += ")"; +} +#endif + +bool nsDisplayBackdropFilters::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style(); + auto filterChain = style.StyleEffects()->mBackdropFilters.AsSpan(); + bool initialized = true; + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters( + mFrame, filterChain, StyleFilterType::BackdropFilter, wrFilters, + initialized)) { + // TODO: If painting backdrop-filters on the content side is implemented, + // consider returning false to fall back to that. + wrFilters = {}; + } + + if (!initialized) { + wrFilters = {}; + } + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + style.StyleBackground()->BottomLayer(), mFrame, *style.StyleBorder(), + mBackdropRect, mBackdropRect, false, + mFrame->PresContext()->AppUnitsPerDevPixel(), &clip); + + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBackdropRect, mFrame->PresContext()->AppUnitsPerDevPixel()); + + wr::ComplexClipRegion region = + wr::ToComplexClipRegion(clip.mBGClipArea, clip.mRadii, + mFrame->PresContext()->AppUnitsPerDevPixel()); + + aBuilder.PushBackdropFilter(wr::ToLayoutRect(bounds), region, + wrFilters.filters, wrFilters.filter_datas, + !BackfaceIsHidden()); + + wr::StackingContextParams params; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +void nsDisplayBackdropFilters::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + // TODO: Implement backdrop filters + GetChildren()->Paint(aBuilder, aCtx, + mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +nsRect nsDisplayBackdropFilters::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + nsRect childBounds = nsDisplayWrapList::GetBounds(aBuilder, aSnap); + + *aSnap = false; + + return mBackdropRect.Union(childBounds); +} + +/* static */ +nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsIFrame* aStyleFrame, + bool aWrapsBackdropFilter) + : nsDisplayEffectsBase(aBuilder, aFrame, aList), + mStyle(aFrame == aStyleFrame ? nullptr : aStyleFrame->Style()), + mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()), + mWrapsBackdropFilter(aWrapsBackdropFilter) { + MOZ_COUNT_CTOR(nsDisplayFilters); + mVisibleRect = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); +} + +void nsDisplayFilters::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + PaintWithContentsPaintCallback(aBuilder, aCtx, [&](gfxContext* aContext) { + GetChildren()->Paint(aBuilder, aContext, + mFrame->PresContext()->AppUnitsPerDevPixel()); + }); +} + +void nsDisplayFilters::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function& aPaintChildren) { + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + PaintFramesParams params(*aCtx, mFrame, mVisibleRect, borderArea, aBuilder, + false, imgParams); + + gfxPoint userSpaceToFrameSpaceOffset = + SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(mFrame, params); + + auto filterChain = mStyle ? mStyle->StyleEffects()->mFilters.AsSpan() + : mFrame->StyleEffects()->mFilters.AsSpan(); + SVGIntegrationUtils::PaintFilter( + params, filterChain, + [&](gfxContext& aContext, imgDrawingParams&, const gfxMatrix*, + const nsIntRect*) { + gfxContextMatrixAutoSaveRestore autoSR(&aContext); + aContext.SetMatrixDouble(aContext.CurrentMatrixDouble().PreTranslate( + -userSpaceToFrameSpaceOffset)); + aPaintChildren(&aContext); + }); +} + +bool nsDisplayFilters::CanCreateWebRenderCommands() const { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame); +} + +bool nsDisplayFilters::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + const ComputedStyle& style = mStyle ? *mStyle : *mFrame->Style(); + auto filterChain = style.StyleEffects()->mFilters.AsSpan(); + bool initialized = true; + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + StyleFilterType::Filter, + wrFilters, initialized)) { + if (mStyle) { + // TODO(bug 1769223): Support fallback filters in the root code-path, + // perhaps. For now treat it the same way as invalid filters. + wrFilters = {}; + } else { + // Draw using fallback. + return false; + } + } + + if (!initialized) { + // https://drafts.fxtf.org/filter-effects/#typedef-filter-url: + // + // If the filter references a non-existent object or the referenced object + // is not a filter element, then the whole filter chain is ignored. No + // filter is applied to the object. + // + // Note that other engines have a weird discrepancy between SVG and HTML + // content here, but the spec is clear. + wrFilters = {}; + } + + uint64_t clipChainId; + if (wrFilters.post_filters_clip) { + auto devPxRect = LayoutDeviceRect::FromAppUnits( + wrFilters.post_filters_clip.value() + ToReferenceFrame(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + auto clipId = + aBuilder.DefineRectClip(Nothing(), wr::ToLayoutRect(devPxRect)); + clipChainId = aBuilder.DefineClipChain({clipId}, true).id; + } else { + clipChainId = aBuilder.CurrentClipChainId(); + } + wr::WrStackingContextClip clip = + wr::WrStackingContextClip::ClipChain(clipChainId); + + float opacity = aBuilder.GetInheritedOpacity(); + aBuilder.SetInheritedOpacity(1.0f); + const DisplayItemClipChain* oldClipChain = aBuilder.GetInheritedClipChain(); + aBuilder.SetInheritedClipChain(nullptr); + wr::StackingContextParams params; + params.mFilters = std::move(wrFilters.filters); + params.mFilterDatas = std::move(wrFilters.filter_datas); + params.opacity = opacity != 1.0f ? &opacity : nullptr; + params.clip = clip; + if (mWrapsBackdropFilter) { + params.flags |= wr::StackingContextFlags::WRAPS_BACKDROP_FILTER; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + aBuilder.SetInheritedOpacity(opacity); + aBuilder.SetInheritedClipChain(oldClipChain); + + return true; +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayFilters::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + // We may exist for a mix of CSS filter functions and/or references to SVG + // filters. If we have invalid references to SVG filters then we paint + // nothing, but otherwise we will apply one or more filters. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) != + SVGObserverUtils::eHasRefsSomeInvalid) { + if (!first) { + aTo += ", "; + } + aTo += "filter"; + } + aTo += ")"; +} +#endif + +nsDisplaySVGWrapper::nsDisplaySVGWrapper(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplaySVGWrapper); +} + +bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return !aBuilder->GetWidgetLayerManager(); +} + +bool nsDisplaySVGWrapper::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return CreateWebRenderCommandsNewClipListOption( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false); +} + +nsDisplayForeignObject::nsDisplayForeignObject(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList) { + MOZ_COUNT_CTOR(nsDisplayForeignObject); +} + +#ifdef NS_BUILD_REFCNT_LOGGING +nsDisplayForeignObject::~nsDisplayForeignObject() { + MOZ_COUNT_DTOR(nsDisplayForeignObject); +} +#endif + +bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + return !aBuilder->GetWidgetLayerManager(); +} + +bool nsDisplayForeignObject::CreateWebRenderCommands( + wr::DisplayListBuilder& aBuilder, wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + AutoRestore restoreDoGrouping(aManager->CommandBuilder().mDoGrouping); + aManager->CommandBuilder().mDoGrouping = false; + return CreateWebRenderCommandsNewClipListOption( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder, false); +} + +void nsDisplayLink::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel(); + aCtx->GetDrawTarget()->Link( + mLinkSpec.get(), NSRectToRect(GetPaintRect(aBuilder, aCtx), appPerDev)); +} + +void nsDisplayDestination::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + auto appPerDev = mFrame->PresContext()->AppUnitsPerDevPixel(); + aCtx->GetDrawTarget()->Destination( + mDestinationName.get(), + NSPointToPoint(GetPaintRect(aBuilder, aCtx).TopLeft(), appPerDev)); +} + +void nsDisplayListCollection::SerializeWithCorrectZOrder( + nsDisplayList* aOutResultList, nsIContent* aContent) { + // Sort PositionedDescendants() in CSS 'z-order' order. The list is already + // in content document order and SortByZOrder is a stable sort which + // guarantees that boxes produced by the same element are placed together + // in the sort. Consider a position:relative inline element that breaks + // across lines and has absolutely positioned children; all the abs-pos + // children should be z-ordered after all the boxes for the position:relative + // element itself. + PositionedDescendants()->SortByZOrder(); + + // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html + // 1,2: backgrounds and borders + aOutResultList->AppendToTop(BorderBackground()); + // 3: negative z-index children. + for (auto* item : PositionedDescendants()->TakeItems()) { + if (item->ZIndex() < 0) { + aOutResultList->AppendToTop(item); + } else { + PositionedDescendants()->AppendToTop(item); + } + } + + // 4: block backgrounds + aOutResultList->AppendToTop(BlockBorderBackgrounds()); + // 5: floats + aOutResultList->AppendToTop(Floats()); + // 7: general content + aOutResultList->AppendToTop(Content()); + // 7.5: outlines, in content tree order. We need to sort by content order + // because an element with outline that breaks and has children with outline + // might have placed child outline items between its own outline items. + // The element's outline items need to all come before any child outline + // items. + if (aContent) { + Outlines()->SortByContentOrder(aContent); + } + aOutResultList->AppendToTop(Outlines()); + // 8, 9: non-negative z-index children + aOutResultList->AppendToTop(PositionedDescendants()); +} + +uint32_t PaintTelemetry::sPaintLevel = 0; + +PaintTelemetry::AutoRecordPaint::AutoRecordPaint() { + // Don't record nested paints. + if (sPaintLevel++ > 0) { + return; + } + + mStart = TimeStamp::Now(); +} + +PaintTelemetry::AutoRecordPaint::~AutoRecordPaint() { + MOZ_ASSERT(sPaintLevel != 0); + if (--sPaintLevel > 0) { + return; + } + + // If we're in multi-process mode, don't include paint times for the parent + // process. + if (gfxVars::BrowserTabsRemoteAutostart() && XRE_IsParentProcess()) { + return; + } + + // Record the total time. + mozilla::glean::gfx_content::paint_time.AccumulateRawDuration( + TimeStamp::Now() - mStart); +} + +static nsIFrame* GetSelfOrPlaceholderFor(nsIFrame* aFrame) { + if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) { + return aFrame; + } + + if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && + !aFrame->GetPrevInFlow()) { + return aFrame->GetPlaceholderFrame(); + } + + return aFrame; +} + +static nsIFrame* GetAncestorFor(nsIFrame* aFrame) { + nsIFrame* f = GetSelfOrPlaceholderFor(aFrame); + MOZ_ASSERT(f); + return nsLayoutUtils::GetCrossDocParentFrameInProcess(f); +} + +nsDisplayListBuilder::AutoBuildingDisplayList::AutoBuildingDisplayList( + nsDisplayListBuilder* aBuilder, nsIFrame* aForChild, + const nsRect& aVisibleRect, const nsRect& aDirtyRect, + const bool aIsTransformed) + : mBuilder(aBuilder), + mPrevFrame(aBuilder->mCurrentFrame), + mPrevReferenceFrame(aBuilder->mCurrentReferenceFrame), + mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame), + mPrevAdditionalOffset(aBuilder->mAdditionalOffset), + mPrevVisibleRect(aBuilder->mVisibleRect), + mPrevDirtyRect(aBuilder->mDirtyRect), + mPrevCompositorHitTestInfo(aBuilder->mCompositorHitTestInfo), + mPrevAncestorHasApzAwareEventHandler( + aBuilder->mAncestorHasApzAwareEventHandler), + mPrevBuildingInvisibleItems(aBuilder->mBuildingInvisibleItems), + mPrevInInvalidSubtree(aBuilder->mInInvalidSubtree) { + if (aIsTransformed) { + aBuilder->mCurrentOffsetToReferenceFrame = + aBuilder->AdditionalOffset().refOr(nsPoint()); + aBuilder->mCurrentReferenceFrame = aForChild; + } else if (aBuilder->mCurrentFrame == aForChild->GetParent()) { + aBuilder->mCurrentOffsetToReferenceFrame += aForChild->GetPosition(); + } else { + aBuilder->mCurrentReferenceFrame = aBuilder->FindReferenceFrameFor( + aForChild, &aBuilder->mCurrentOffsetToReferenceFrame); + } + + // If aForChild is being visited from a frame other than it's ancestor frame, + // mInInvalidSubtree will need to be recalculated the slow way. + if (aForChild == mPrevFrame || GetAncestorFor(aForChild) == mPrevFrame) { + aBuilder->mInInvalidSubtree = + aBuilder->mInInvalidSubtree || aForChild->IsFrameModified(); + } else { + aBuilder->mInInvalidSubtree = AnyContentAncestorModified(aForChild); + } + + aBuilder->mCurrentFrame = aForChild; + aBuilder->mVisibleRect = aVisibleRect; + aBuilder->mDirtyRect = + aBuilder->mInInvalidSubtree ? aVisibleRect : aDirtyRect; +} + +} // namespace mozilla -- cgit v1.2.3