diff options
Diffstat (limited to 'layout/painting/nsDisplayList.cpp')
-rw-r--r-- | layout/painting/nsDisplayList.cpp | 10324 |
1 files changed, 10324 insertions, 0 deletions
diff --git a/layout/painting/nsDisplayList.cpp b/layout/painting/nsDisplayList.cpp new file mode 100644 index 0000000000..dfe894b670 --- /dev/null +++ b/layout/painting/nsDisplayList.cpp @@ -0,0 +1,10324 @@ +/* -*- 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 <stdint.h> +#include <algorithm> +#include <limits> + +#include "gfxContext.h" +#include "gfxUtils.h" +#include "mozilla/DisplayPortUtils.h" +#include "mozilla/dom/BrowserChild.h" +#include "mozilla/dom/HTMLCanvasElement.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/gfx/2D.h" +#include "mozilla/layers/PLayerTransaction.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/SVGIntegrationUtils.h" +#include "mozilla/SVGUtils.h" +#include "mozilla/ViewportUtils.h" +#include "nsCSSRendering.h" +#include "nsCSSRenderingGradients.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 "LayerTreeInvalidation.h" +#include "mozilla/MathAlgorithms.h" + +#include "imgIContainer.h" +#include "BasicLayers.h" +#include "nsBoxFrame.h" +#include "nsImageFrame.h" +#include "nsSubDocumentFrame.h" +#include "GeckoProfiler.h" +#include "nsViewManager.h" +#include "ImageLayers.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/EventStates.h" +#include "mozilla/HashTable.h" +#include "mozilla/LookAndFeel.h" +#include "mozilla/OperatorNewExtensions.h" +#include "mozilla/PendingAnimationTracker.h" +#include "mozilla/Preferences.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 "nsPrintfCString.h" +#include "UnitTransforms.h" +#include "LayerAnimationInfo.h" +#include "FrameLayerBuilder.h" +#include "mozilla/EventStateManager.h" +#include "nsCaret.h" +#include "nsDOMTokenList.h" +#include "nsCSSProps.h" +#include "nsTableCellFrame.h" +#include "nsTableColFrame.h" +#include "nsTextFrame.h" +#include "nsSliderFrame.h" +#include "nsFocusManager.h" +#include "ClientLayerManager.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" + +using namespace mozilla; +using namespace mozilla::layers; +using namespace mozilla::dom; +using namespace mozilla::layout; +using namespace mozilla::gfx; + +typedef ScrollableLayerGuid::ViewID ViewID; +typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox; + +#ifdef DEBUG +static bool SpammyLayoutWarningsEnabled() { + static bool sValue = false; + static bool sValueInitialized = false; + + if (!sValueInitialized) { + Preferences::GetBool("layout.spammy_warnings.enabled", &sValue); + sValueInitialized = true; + } + + return sValue; +} +#endif + +#ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED +void AssertUniqueItem(nsDisplayItem* aItem) { + nsIFrame::DisplayItemArray* items = + aItem->Frame()->GetProperty(nsIFrame::DisplayItems()); + if (!items) { + return; + } + for (nsDisplayItemBase* i : *items) { + if (i != aItem && !i->HasDeletedFrame() && i->Frame() == aItem->Frame() && + i->GetPerFrameKey() == aItem->GetPerFrameKey()) { + if (i->IsPreProcessedItem()) { + continue; + } + MOZ_DIAGNOSTIC_ASSERT(false, "Duplicate display item!"); + } + } +} +#endif + +bool ShouldBuildItemForEventsOrPlugins(const DisplayItemType aType) { + return aType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO || + aType == DisplayItemType::TYPE_PLUGIN || + (GetDisplayItemFlagsForType(aType) & TYPE_IS_CONTAINER); +} + +void UpdateDisplayItemData(nsPaintedDisplayItem* aItem) { + for (mozilla::DisplayItemData* did : aItem->Frame()->DisplayItemData()) { + if (did->GetDisplayItemKey() == aItem->GetPerFrameKey() && + did->GetLayer()->AsPaintedLayer()) { + if (!did->HasMergedFrames()) { + aItem->SetDisplayItemData(did, did->GetLayer()->Manager()); + } + + return; + } + } +} + +/* static */ +already_AddRefed<ActiveScrolledRoot> ActiveScrolledRoot::CreateASRForFrame( + const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, + bool aIsRetained) { + nsIFrame* f = do_QueryFrame(aScrollableFrame); + + RefPtr<ActiveScrolledRoot> asr; + if (aIsRetained) { + asr = f->GetProperty(ActiveScrolledRootCache()); + } + + if (!asr) { + asr = new ActiveScrolledRoot(); + + if (aIsRetained) { + RefPtr<ActiveScrolledRoot> ref = asr; + f->SetProperty(ActiveScrolledRootCache(), ref.forget().take()); + } + } + asr->mParent = aParent; + asr->mScrollableFrame = aScrollableFrame; + asr->mViewId = Nothing(); + 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 */ +nsCString ActiveScrolledRoot::ToString( + const ActiveScrolledRoot* aActiveScrolledRoot) { + nsAutoCString str; + for (auto* asr = aActiveScrolledRoot; asr; asr = asr->mParent) { + str.AppendPrintf("<0x%p>", asr->mScrollableFrame); + if (asr->mParent) { + str.AppendLiteral(", "); + } + } + return std::move(str); +} + +mozilla::layers::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, mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder, + const Maybe<LayoutDevicePoint>& aPosition = Nothing()) { + EffectSet* effects = + EffectSet::GetEffectSetForFrame(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<WebRenderAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAnimationData>(aItem); + AnimationInfo& animationInfo = animationData->GetAnimationInfo(); + animationInfo.AddAnimationsForDisplayItem( + aItem->Frame(), aDisplayListBuilder, aItem, aItem->GetType(), + aManager->LayerManager(), aPosition); + animationInfo.StartPendingAnimations( + aManager->LayerManager()->GetAnimationReadyTime()); + + // 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<DrawTarget> sourceTarget = sourceCtx->GetDrawTarget(); + RefPtr<DrawTarget> maskDT = sourceTarget->CreateClippedDrawTarget( + bounds.ToUnknownRect(), SurfaceFormat::A8); + if (!maskDT || !maskDT->IsValid()) { + return false; + } + RefPtr<gfxContext> maskCtx = + gfxContext::CreatePreservingTransformOrNull(maskDT); + MOZ_ASSERT(maskCtx); + 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<SourceSurface> maskSurface = maskDT->Snapshot(); + sourceCtx->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 1.0, + maskSurface, invCurrentMatrix); + + return true; +} + +/* static */ +void nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + Layer* aLayer, nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, + nsIFrame* aFrame, DisplayItemType aType) { + // This function can be called in two ways: from + // nsDisplay*::BuildLayer while constructing a layer (with all + // pointers non-null), or from RestyleManager's handling of + // UpdateOpacityLayer/UpdateTransformLayer hints. + MOZ_ASSERT(!aBuilder == !aItem, + "should only be called in two configurations, with both " + "aBuilder and aItem, or with neither"); + MOZ_ASSERT(!aItem || aFrame == aItem->Frame(), "frame mismatch"); + + // Only send animations to a layer that is actually using + // off-main-thread compositing. + LayersBackend backend = aLayer->Manager()->GetBackendType(); + if (!(backend == layers::LayersBackend::LAYERS_CLIENT || + backend == layers::LayersBackend::LAYERS_WR)) { + return; + } + + AnimationInfo& animationInfo = aLayer->GetAnimationInfo(); + animationInfo.AddAnimationsForDisplayItem(aFrame, aBuilder, aItem, aType, + aLayer->Manager()); + animationInfo.TransferMutatedFlagToLayer(aLayer); +} + +nsDisplayWrapList* nsDisplayListBuilder::MergeItems( + nsTArray<nsDisplayWrapList*>& 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* merged = nullptr; + + for (nsDisplayWrapList* item : Reversed(aItems)) { + MOZ_ASSERT(item); + + if (!merged) { + // Create the temporary item. + merged = item->Clone(this); + MOZ_ASSERT(merged); + + AddTemporaryItem(merged); + } else { + // Merge the item properties (frame/bounds/etc) with the previously + // created temporary item. + MOZ_ASSERT(merged->CanMerge(item)); + merged->Merge(item); + } + + // Create nsDisplayWrapList that points to the internal display list of the + // item we are merging. This nsDisplayWrapList is added to the display list + // of the temporary item. + merged->MergeDisplayListFromItem(this, item); + } + + 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; +} + +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::GetHighResolutionDisplayPort( + 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() && + mozilla::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::nsDisplayListBuilder(nsIFrame* aReferenceFrame, + nsDisplayListBuilderMode aMode, + bool aBuildCaret, + bool aRetainingDisplayList) + : mReferenceFrame(aReferenceFrame), + mIgnoreScrollFrame(nullptr), + mCurrentActiveScrolledRoot(nullptr), + mCurrentContainerASR(nullptr), + mCurrentFrame(aReferenceFrame), + mCurrentReferenceFrame(aReferenceFrame), + mRootAGR(AnimatedGeometryRoot::CreateAGRForFrame( + aReferenceFrame, nullptr, true, aRetainingDisplayList)), + mCurrentAGR(mRootAGR), + mBuildingExtraPagesForPageNum(0), + mUsedAGRBudget(0), + mDirtyRect(-1, -1, -1, -1), + mGlassDisplayItem(nullptr), + mCaretFrame(nullptr), + mScrollInfoItemsForHoisting(nullptr), + mFirstClipChainToDestroy(nullptr), + mMode(aMode), + mTableBackgroundSet(nullptr), + mCurrentScrollParentId(ScrollableLayerGuid::NULL_SCROLL_ID), + mCurrentScrollbarTarget(ScrollableLayerGuid::NULL_SCROLL_ID), + mFilterASR(nullptr), + mContainsBlendMode(false), + mIsBuildingScrollbar(false), + mCurrentScrollbarWillHaveLayer(false), + mBuildCaret(aBuildCaret), + mRetainingDisplayList(aRetainingDisplayList), + mPartialUpdate(false), + mIgnoreSuppression(false), + mIncludeAllOutOfFlows(false), + mDescendIntoSubdocuments(true), + mSelectedFramesOnly(false), + mAllowMergingAndFlattening(true), + mWillComputePluginGeometry(false), + mInTransform(false), + mInEventsAndPluginsOnly(false), + mInFilter(false), + mInPageSequence(false), + mIsInChromePresContext(false), + mSyncDecodeImages(false), + mIsPaintingToWindow(false), + mUseHighQualityScaling(false), + mIsPaintingForWebRender(false), + mIsCompositingCheap(false), + mContainsPluginItem(false), + mAncestorHasApzAwareEventHandler(false), + mHaveScrollableDisplayPort(false), + mWindowDraggingAllowed(false), + mIsBuildingForPopup(nsLayoutUtils::IsPopup(aReferenceFrame)), + mForceLayerForScrollParent(false), + mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)), + mBuildingInvisibleItems(false), + mIsBuilding(false), + mInInvalidSubtree(false), + mDisablePartialUpdates(false), + mPartialBuildFailed(false), + mIsInActiveDocShell(false), + mBuildAsyncZoomContainer(false), + mContainsBackdropFilter(false), + mIsRelativeToLayoutViewport(false), + mUseOverlayScrollbars(false), + mHitTestArea(), + mHitTestInfo(CompositorHitTestInvisibleToHit) { + MOZ_COUNT_CTOR(nsDisplayListBuilder); + + mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting(); + + ShouldRebuildDisplayListDueToPrefChange(); + + mUseOverlayScrollbars = + (LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) != 0); + + static_assert( + static_cast<uint32_t>(DisplayItemType::TYPE_MAX) < (1 << TYPE_BITS), + "Check TYPE_MAX should not overflow"); +} + +static PresShell* GetFocusedPresShell() { + nsPIDOMWindowOuter* focusedWnd = + nsFocusManager::GetFocusManager()->GetFocusedWindow(); + if (!focusedWnd) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> focusedDocShell = focusedWnd->GetDocShell(); + if (!focusedDocShell) { + return nullptr; + } + + return focusedDocShell->GetPresShell(); +} + +void nsDisplayListBuilder::BeginFrame() { + nsCSSRendering::BeginFrameTreesLocked(); + mCurrentAGR = mRootAGR; + mFrameToAnimatedGeometryRootMap.Put(mReferenceFrame, mRootAGR); + + mIsPaintingToWindow = false; + mUseHighQualityScaling = false; + mIgnoreSuppression = false; + mInTransform = false; + mInFilter = false; + mSyncDecodeImages = false; + + if (!mBuildCaret) { + return; + } + + RefPtr<PresShell> presShell = GetFocusedPresShell(); + if (presShell) { + RefPtr<nsCaret> 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::EndFrame() { + NS_ASSERTION(!mInInvalidSubtree, + "Someone forgot to cleanup mInInvalidSubtree!"); + mFrameToAnimatedGeometryRootMap.Clear(); + mAGRBudgetSet.Clear(); + 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); +} + +void nsDisplayListBuilder::MarkFrameForDisplayIfVisible( + nsIFrame* aFrame, const nsIFrame* aStopAtFrame) { + AddFrameMarkedForDisplayIfVisible(aFrame); + for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetDisplayListParent(f)) { + if (f->ForceDescendIntoIfVisible()) { + return; + } + f->SetForceDescendIntoIfVisible(true); + if (f == aStopAtFrame) { + // we've reached a frame that we know will be painted, so we can stop. + break; + } + } +} + +void nsDisplayListBuilder::SetGlassDisplayItem(nsDisplayItem* aItem) { + // Web pages or extensions could trigger the "Multiple glass backgrounds + // found?" warning by using -moz-appearance:win-borderless-glass etc on their + // own elements (as long as they are DocElementBoxFrames, which is rare as + // each xul doc only gets one near the root). We only care about first one, + // since that will be the background of the root window. + + if (IsPartialUpdate()) { + if (aItem->Frame()->IsDocElementBoxFrame()) { +#ifdef DEBUG + if (mHasGlassItemDuringPartial) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mHasGlassItemDuringPartial) { + mHasGlassItemDuringPartial = true; + aItem->SetIsGlassItem(); + } + } + return; + } + + if (aItem->Frame()->IsDocElementBoxFrame()) { +#ifdef DEBUG + if (mGlassDisplayItem) { + NS_WARNING("Multiple glass backgrounds found?"); + } else +#endif + if (!mGlassDisplayItem) { + mGlassDisplayItem = aItem; + mGlassDisplayItem->SetIsGlassItem(); + } + } +} + +bool nsDisplayListBuilder::NeedToForceTransparentSurfaceForItem( + nsDisplayItem* aItem) { + return aItem == mGlassDisplayItem; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::WrapAGRForFrame( + nsIFrame* aAnimatedGeometryRoot, bool aIsAsync, + AnimatedGeometryRoot* aParent /* = nullptr */) { + DebugOnly<bool> dummy; + MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot, dummy) == AGR_YES); + + RefPtr<AnimatedGeometryRoot> result; + if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) { + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), + aAnimatedGeometryRoot)); + RefPtr<AnimatedGeometryRoot> parent = aParent; + if (!parent) { + nsIFrame* parentFrame = + nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot); + if (parentFrame) { + bool isAsync; + nsIFrame* parentAGRFrame = + FindAnimatedGeometryRootFrameFor(parentFrame, isAsync); + parent = WrapAGRForFrame(parentAGRFrame, isAsync); + } + } + result = AnimatedGeometryRoot::CreateAGRForFrame( + aAnimatedGeometryRoot, parent, aIsAsync, IsRetainingDisplayList()); + mFrameToAnimatedGeometryRootMap.Put(aAnimatedGeometryRoot, result); + } + MOZ_ASSERT(!aParent || result->mParentAGR == aParent); + return result; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::AnimatedGeometryRootForASR( + const ActiveScrolledRoot* aASR) { + if (!aASR) { + return GetRootAnimatedGeometryRoot(); + } + nsIFrame* scrolledFrame = aASR->mScrollableFrame->GetScrolledFrame(); + return FindAnimatedGeometryRootFor(scrolledFrame); +} + +AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor( + nsIFrame* aFrame) { + if (!IsPaintingToWindow()) { + return mRootAGR; + } + if (aFrame == mCurrentFrame) { + return mCurrentAGR; + } + RefPtr<AnimatedGeometryRoot> result; + if (mFrameToAnimatedGeometryRootMap.Get(aFrame, &result)) { + return result; + } + + bool isAsync; + nsIFrame* agrFrame = FindAnimatedGeometryRootFrameFor(aFrame, isAsync); + result = WrapAGRForFrame(agrFrame, isAsync); + mFrameToAnimatedGeometryRootMap.Put(aFrame, result); + return result; +} + +AnimatedGeometryRoot* nsDisplayListBuilder::FindAnimatedGeometryRootFor( + nsDisplayItem* aItem) { + if (aItem->ShouldFixToViewport(this)) { + // Make its active scrolled root be the active scrolled root of + // the enclosing viewport, since it shouldn't be scrolled by scrolled + // frames in its document. InvalidateFixedBackgroundFramesFromList in + // nsGfxScrollFrame will not repaint this item when scrolling occurs. + nsIFrame* viewportFrame = nsLayoutUtils::GetClosestFrameOfType( + aItem->Frame(), LayoutFrameType::Viewport, RootReferenceFrame()); + if (viewportFrame) { + return FindAnimatedGeometryRootFor(viewportFrame); + } + } + return FindAnimatedGeometryRootFor(aItem->Frame()); +} + +void nsDisplayListBuilder::SetIsRelativeToLayoutViewport() { + mIsRelativeToLayoutViewport = true; + UpdateShouldBuildAsyncZoomContainer(); +} + +void nsDisplayListBuilder::UpdateShouldBuildAsyncZoomContainer() { + Document* document = mReferenceFrame->PresContext()->Document(); + // On desktop, we want to disable zooming in fullscreen mode (bug 1650488). + // On mobile (and RDM), we need zooming even in fullscreen mode to respect + // mobile viewport sizing (bug 1659761). + bool disableZoomingForFullscreen = + document->Fullscreen() && + !document->GetPresShell()->UsesMobileViewportSizing(); + mBuildAsyncZoomContainer = !mIsRelativeToLayoutViewport && + !disableZoomingForFullscreen && + 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) != 0); + + if (didBuildAsyncZoomContainer != mBuildAsyncZoomContainer) { + return true; + } + + if (hadOverlayScrollbarsLastTime != mUseOverlayScrollbars) { + 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); + } +} + +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; +} + +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 (GetAccurateVisibleRegions() || tmp.GetNumRects() <= 15 || + tmp.Area() <= aVisibleRegion->Area() / 2) { + *aVisibleRegion = tmp; + } +} + +nsCaret* nsDisplayListBuilder::GetCaret() { + RefPtr<nsCaret> caret = CurrentPresShellState()->mPresShell->GetCaret(); + return caret; +} + +void nsDisplayListBuilder::IncrementPresShellPaintCount(PresShell* aPresShell) { + if (mIsPaintingToWindow) { + mReferenceFrame->AddPaintedPresShell(aPresShell); + 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->HadContentfulPaint() && rootPresContext && + rootPresContext->RefreshDriver()->IsInRefresh()) { + 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 { + mCurrentAGR = mRootAGR; + + 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()) { + auto classList = content->AsElement()->ClassList(); + if (classList->Contains(u"moz-accessiblecaret"_ns)) { + 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, 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, 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<ActiveScrolledRoot> 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_ASSERT(!(aParent && aParent->mOnStack)); + void* p = Allocate(sizeof(DisplayItemClipChain), + DisplayListArenaObjectId::CLIPCHAIN); + DisplayItemClipChain* c = new (KnownNotNull, p) + DisplayItemClipChain(aClip, aASR, aParent, mFirstClipChainToDestroy); +#ifdef DEBUG + 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; +}; + +const DisplayItemClipChain* nsDisplayListBuilder::CreateClipChainIntersection( + const DisplayItemClipChain* aAncestor, + const DisplayItemClipChain* aLeafClip1, + const DisplayItemClipChain* aLeafClip2) { + AutoTArray<ClipChainItem, 8> 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::CopyWholeChain( + const DisplayItemClipChain* aClipChain) { + return CreateClipChainIntersection(nullptr, aClipChain, nullptr); +} + +const DisplayItemClipChain* nsDisplayListBuilder::FuseClipChainUpTo( + const DisplayItemClipChain* aClipChain, const ActiveScrolledRoot* aASR) { + if (!aClipChain) { + return nullptr; + } + + const DisplayItemClipChain* sc = aClipChain; + DisplayItemClip mergedClip; + while (sc && ActiveScrolledRoot::PickDescendant(aASR, sc->mASR) == sc->mASR) { + mergedClip.IntersectWith(sc->mClip); + sc = sc->mParent; + } + + if (!mergedClip.HasClip()) { + return nullptr; + } + + return AllocateDisplayItemClipChain(mergedClip, aASR, sc); +} + +const nsIFrame* nsDisplayListBuilder::FindReferenceFrameFor( + const nsIFrame* aFrame, nsPoint* aOffset) const { + auto MaybeApplyAdditionalOffset = [&]() { + if (AdditionalOffset()) { + // The additional reference frame offset should only affect descendants + // of |mAdditionalOffsetFrame|. + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mAdditionalOffsetFrame, + aFrame)); + *aOffset += *AdditionalOffset(); + } + }; + + if (aFrame == mCurrentFrame) { + if (aOffset) { + *aOffset = mCurrentOffsetToReferenceFrame; + } + return mCurrentReferenceFrame; + } + + for (const nsIFrame* f = aFrame; f; + f = nsLayoutUtils::GetCrossDocParentFrame(f)) { + if (f == mReferenceFrame || f->IsTransformed()) { + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(f); + MaybeApplyAdditionalOffset(); + } + return f; + } + } + + if (aOffset) { + *aOffset = aFrame->GetOffsetToCrossDoc(mReferenceFrame); + } + + return mReferenceFrame; +} + +// Sticky frames are active if their nearest scrollable frame is also active. +static bool IsStickyFrameActive(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsIFrame* aParent) { + MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky); + + // Find the nearest scrollframe. + nsIScrollableFrame* sf = nsLayoutUtils::GetNearestScrollableFrame( + aFrame->GetParent(), nsLayoutUtils::SCROLLABLE_SAME_DOC | + nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN); + if (!sf) { + return false; + } + + return sf->IsScrollingActive(aBuilder); +} + +nsDisplayListBuilder::AGRState nsDisplayListBuilder::IsAnimatedGeometryRoot( + nsIFrame* aFrame, bool& aIsAsync, nsIFrame** aParent) { + // We can return once we know that this frame is an AGR, and we're either + // async, or sure that none of the later conditions might make us async. + // The exception to this is when IsPaintingToWindow() == false. + aIsAsync = false; + if (aFrame == mReferenceFrame) { + aIsAsync = true; + return AGR_YES; + } + + if (!IsPaintingToWindow()) { + if (aParent) { + *aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + } + return AGR_NO; + } + + nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(aFrame); + if (!parent) { + aIsAsync = true; + return AGR_YES; + } + + if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Sticky && + IsStickyFrameActive(this, aFrame, parent)) { + aIsAsync = true; + return AGR_YES; + } + + if (aFrame->IsTransformed()) { + aIsAsync = EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_TRANSFORM); + return AGR_YES; + } + + LayoutFrameType parentType = parent->Type(); + if (parentType == LayoutFrameType::Scroll || + parentType == LayoutFrameType::ListControl) { + nsIScrollableFrame* sf = do_QueryFrame(parent); + if (sf->GetScrolledFrame() == aFrame && sf->IsScrollingActive(this)) { + MOZ_ASSERT(!aFrame->IsTransformed()); + aIsAsync = sf->IsMaybeAsynchronouslyScrolled(); + return AGR_YES; + } + } + + // Treat the slider thumb as being as an active scrolled root when it wants + // its own layer so that it can move without repainting. + if (parentType == LayoutFrameType::Slider) { + auto* sf = static_cast<nsSliderFrame*>(parent)->GetScrollFrame(); + // The word "Maybe" in IsMaybeScrollingActive might be confusing but we do + // indeed need to always consider scroll thumbs as AGRs if + // IsMaybeScrollingActive is true because that is the same condition we use + // in ScrollFrameHelper::AppendScrollPartsTo to layerize scroll thumbs. + if (sf && sf->IsMaybeScrollingActive()) { + return AGR_YES; + } + } + + if (nsLayoutUtils::IsPopup(aFrame)) { + return AGR_YES; + } + + if (ActiveLayerTracker::IsOffsetStyleAnimated(aFrame)) { + const bool inBudget = AddToAGRBudget(aFrame); + if (inBudget) { + return AGR_YES; + } + } + + if (!aFrame->GetParent() && + DisplayPortUtils::ViewportHasDisplayPort(aFrame->PresContext())) { + // Viewport frames in a display port need to be animated geometry roots + // for background-attachment:fixed elements. + return AGR_YES; + } + + // Fixed-pos frames are parented by the viewport frame, which has no parent. + if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(aFrame)) { + return AGR_YES; + } + + if (aParent) { + *aParent = parent; + } + + return AGR_NO; +} + +nsIFrame* nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor( + nsIFrame* aFrame, bool& aIsAsync) { + MOZ_ASSERT( + nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aFrame)); + nsIFrame* cursor = aFrame; + while (cursor != RootReferenceFrame()) { + nsIFrame* next; + if (IsAnimatedGeometryRoot(cursor, aIsAsync, &next) == AGR_YES) { + return cursor; + } + cursor = next; + } + // Root frame is always an async agr. + aIsAsync = true; + return cursor; +} + +void nsDisplayListBuilder::RecomputeCurrentAnimatedGeometryRoot() { + bool isAsync; + if (*mCurrentAGR != mCurrentFrame && + IsAnimatedGeometryRoot(const_cast<nsIFrame*>(mCurrentFrame), isAsync) == + AGR_YES) { + AnimatedGeometryRoot* oldAGR = mCurrentAGR; + mCurrentAGR = WrapAGRForFrame(const_cast<nsIFrame*>(mCurrentFrame), isAsync, + mCurrentAGR); + + // Iterate the AGR cache and look for any objects that reference the old AGR + // and check to see if they need to be updated. AGRs can be in the cache + // multiple times, so we may end up doing the work multiple times for AGRs + // that don't change. + for (auto iter = mFrameToAnimatedGeometryRootMap.Iter(); !iter.Done(); + iter.Next()) { + RefPtr<AnimatedGeometryRoot> cached = iter.UserData(); + if (cached->mParentAGR == oldAGR && cached != mCurrentAGR) { + // It's possible that this cached AGR struct that has the old AGR as a + // parent should instead have mCurrentFrame has a parent. + nsIFrame* parent = FindAnimatedGeometryRootFrameFor(*cached, isAsync); + MOZ_ASSERT(parent == mCurrentFrame || parent == *oldAGR); + if (parent == mCurrentFrame) { + cached->mParentAGR = mCurrentAGR; + } + } + } + } +} + +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<nsIFrame*>(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<LayoutDeviceToLayoutDeviceMatrix4x4>( + 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; + } + + mozilla::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 nsDisplayHitTestInfoBase::AddSizeOfExcludingThis( + nsWindowSizes& aSizes) const { + nsPaintedDisplayItem::AddSizeOfExcludingThis(aSizes); + aSizes.mLayoutRetainedDisplayListSize += + aSizes.mState.mMallocSizeOf(mHitTestInfo.get()); +} + +void nsDisplayTransform::AddSizeOfExcludingThis(nsWindowSizes& aSizes) const { + nsDisplayHitTestInfoBase::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 += mAGRBudgetSet.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mEffectsUpdates.ShallowSizeOfExcludingThis(mallocSizeOf); + n += mWindowExcludeGlassRegion.SizeOfExcludingThis(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 (auto& frame : mFrames) { + const UniquePtr<WeakFrame>& 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.RemoveEntry(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(); + mWindowExcludeGlassRegion.RemoveModifiedFramesAndRects(); + mRetainedWindowOpaqueRegion.RemoveModifiedFramesAndRects(); + + mHasGlassItemDuringPartial = false; +} + +void nsDisplayListBuilder::ClearRetainedWindowRegions() { + mRetainedWindowDraggingRegion.Clear(); + mRetainedWindowNoDraggingRegion.Clear(); + mWindowExcludeGlassRegion.Clear(); + mRetainedWindowOpaqueRegion.Clear(); + + mGlassDisplayItem = nullptr; +} + +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.GetOrInsert(presContext); + + const bool onBudget = + (documentBudget + cost) / gWillChangeAreaMultiplier < budgetLimit; + + if (onBudget) { + documentBudget += cost; + mFrameWillChangeBudgets.Put(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<nsString, 2> 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(); + + DocumentWillChangeBudget* documentBudget = + mDocumentWillChangeBudgets.GetValue(frameBudget.mPresContext); + + if (documentBudget) { + *documentBudget -= frameBudget.mUsage; + } + + entry.Remove(); + } +} + +void nsDisplayListBuilder::ClearWillChangeBudgets() { + mFrameWillChangeBudgets.Clear(); + mDocumentWillChangeBudgets.Clear(); +} + +#ifdef MOZ_GFX_OPTIMIZE_MOBILE +const float gAGRBudgetAreaMultiplier = 0.3; +#else +const float gAGRBudgetAreaMultiplier = 3.0; +#endif + +bool nsDisplayListBuilder::AddToAGRBudget(nsIFrame* aFrame) { + if (mAGRBudgetSet.Contains(aFrame)) { + return true; + } + + const nsPresContext* presContext = + aFrame->PresContext()->GetRootPresContext(); + if (!presContext) { + return false; + } + + const nsRect area = presContext->GetVisibleArea(); + const uint32_t budgetLimit = + gAGRBudgetAreaMultiplier * + nsPresContext::AppUnitsToIntCSSPixels(area.width) * + nsPresContext::AppUnitsToIntCSSPixels(area.height); + + const uint32_t cost = GetLayerizationCost(aFrame->GetSize()); + const bool onBudget = mUsedAGRBudget + cost < budgetLimit; + + if (onBudget) { + mUsedAGRBudget += cost; + mAGRBudgetSet.PutEntry(aFrame); + } + + return onBudget; +} + +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, const bool aBuildNew) { + MOZ_ASSERT(aFrame); + MOZ_ASSERT(aList); + + if (!BuildCompositorHitTestInfo()) { + return; + } + + const CompositorHitTestInfo info = aFrame->GetCompositorHitTestInfo(this); + if (info == CompositorHitTestInvisibleToHit) { + return; + } + + const nsRect area = aFrame->GetCompositorHitTestArea(this); + if (!aBuildNew && GetHitTestInfo() == info && + GetHitTestArea().Contains(area)) { + return; + } + + auto* item = MakeDisplayItem<nsDisplayCompositorHitTestInfo>( + this, aFrame, info, Some(area)); + MOZ_ASSERT(item); + + SetCompositorHitTestInfo(area, info); + aList->AppendToTop(item); +} + +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()); +} + +static void MoveListTo(nsDisplayList* aList, + nsTArray<nsDisplayItem*>* aElements) { + nsDisplayItem* item; + while ((item = aList->RemoveBottom()) != nullptr) { + aElements->AppendElement(item); + } +} + +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<nsRect> 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; +} + +bool nsDisplayList::ComputeVisibilityForRoot(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + AUTO_PROFILER_LABEL("nsDisplayList::ComputeVisibilityForRoot", GRAPHICS); + + nsRegion r; + const ActiveScrolledRoot* rootASR = nullptr; + r.And(*aVisibleRegion, GetClippedBoundsWithRespectToASR(aBuilder, rootASR)); + return ComputeVisibilityForSublist(aBuilder, aVisibleRegion, r.GetBounds()); +} + +static nsRegion TreatAsOpaque(nsDisplayItem* aItem, + nsDisplayListBuilder* aBuilder) { + bool snap; + nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap); + MOZ_ASSERT( + (aBuilder->IsForEventDelivery() && aBuilder->HitTestIsForVisibility()) || + !opaque.IsComplex()); + if (aBuilder->IsForPluginGeometry() && + aItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) { + // Treat all leaf chrome items as opaque, unless their frames are opacity:0. + // Since opacity:0 frames generate an nsDisplayOpacity, that item will + // not be treated as opaque here, so opacity:0 chrome content will be + // effectively ignored, as it should be. + // We treat leaf chrome items as opaque to ensure that they cover + // content plugins, for security reasons. + // Non-leaf chrome items don't render contents of their own so shouldn't + // be treated as opaque (and their bounds is just the union of their + // children, which might be a large area their contents don't really cover). + nsIFrame* f = aItem->Frame(); + if (f->PresContext()->IsChrome() && !aItem->GetChildren() && + f->StyleEffects()->mOpacity != 0.0) { + opaque = aItem->GetBounds(aBuilder, &snap); + } + } + if (opaque.IsEmpty()) { + return opaque; + } + nsRegion opaqueClipped; + for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) { + opaqueClipped.Or(opaqueClipped, + aItem->GetClip().ApproximateIntersectInward(iter.Get())); + } + return opaqueClipped; +} + +bool nsDisplayList::ComputeVisibilityForSublist( + nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion, + const nsRect& aListVisibleBounds) { +#ifdef DEBUG + nsRegion r; + r.And(*aVisibleRegion, GetClippedBounds(aBuilder)); + // XXX this fails sometimes: + NS_WARNING_ASSERTION(r.GetBounds().IsEqualInterior(aListVisibleBounds), + "bad aListVisibleBounds"); +#endif + + bool anyVisible = false; + + AutoTArray<nsDisplayItem*, 512> elements; + MoveListTo(this, &elements); + + for (int32_t i = elements.Length() - 1; i >= 0; --i) { + nsDisplayItem* item = elements[i]; + + if (item->ForceNotVisible() && !item->GetSameCoordinateSystemChildren()) { + NS_ASSERTION(item->GetBuildingRect().IsEmpty(), + "invisible items should have empty vis rect"); + item->SetPaintRect(nsRect()); + } else { + nsRect bounds = item->GetClippedBounds(aBuilder); + + nsRegion itemVisible; + itemVisible.And(*aVisibleRegion, bounds); + item->SetPaintRect(itemVisible.GetBounds()); + } + + if (item->ComputeVisibility(aBuilder, aVisibleRegion)) { + anyVisible = true; + + nsRegion opaque = TreatAsOpaque(item, aBuilder); + // Subtract opaque item from the visible region + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque); + } + AppendToBottom(item); + } + + mIsOpaque = !aVisibleRegion->Intersects(aListVisibleBounds); + return anyVisible; +} + +static void TriggerPendingAnimations(Document& aDoc, + const TimeStamp& aReadyTime) { + MOZ_ASSERT(!aReadyTime.IsNull(), + "Animation ready time is not set. Perhaps we're using a layer" + " manager that doesn't update it"); + if (PendingAnimationTracker* tracker = aDoc.GetPendingAnimationTracker()) { + PresShell* presShell = aDoc.GetPresShell(); + // If paint-suppression is in effect then we haven't finished painting + // this document yet so we shouldn't start animations + if (!presShell || !presShell->IsPaintingSuppressed()) { + tracker->TriggerPendingAnimationsOnNextTick(aReadyTime); + } + } + auto recurse = [&aReadyTime](Document& aDoc) { + TriggerPendingAnimations(aDoc, aReadyTime); + return CallState::Continue; + }; + aDoc.EnumerateSubDocuments(recurse); +} + +LayerManager* nsDisplayListBuilder::GetWidgetLayerManager(nsView** aView) { + if (aView) { + *aView = RootReferenceFrame()->GetView(); + } + if (RootReferenceFrame() != + nsLayoutUtils::GetDisplayRootFrame(RootReferenceFrame())) { + return nullptr; + } + nsIWidget* window = RootReferenceFrame()->GetNearestWidget(); + if (window) { + return window->GetLayerManager(); + } + return nullptr; +} + +// Find the layer which should house the root scroll metadata for a given +// layer tree. This is the async zoom container layer if there is one, +// otherwise it's the root layer. +Layer* GetLayerForRootMetadata(Layer* aRootLayer, ViewID aRootScrollId) { + Layer* asyncZoomContainer = DepthFirstSearch<ForwardIterator>( + aRootLayer, [aRootScrollId](Layer* aLayer) { + if (auto id = aLayer->IsAsyncZoomContainer()) { + return *id == aRootScrollId; + } + return false; + }); + return asyncZoomContainer ? asyncZoomContainer : aRootLayer; +} + +FrameLayerBuilder* nsDisplayList::BuildLayers(nsDisplayListBuilder* aBuilder, + LayerManager* aLayerManager, + uint32_t aFlags, + bool aIsWidgetTransaction) { + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(aBuilder, aLayerManager); + + if (aFlags & PAINT_COMPRESSED) { + layerBuilder->SetLayerTreeCompressionMode(); + } + + RefPtr<ContainerLayer> root; + { + AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding); +#ifdef MOZ_GECKO_PROFILER + nsCOMPtr<nsIDocShell> docShell = presContext->GetDocShell(); + AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "LayerBuilding", GRAPHICS, + docShell); +#endif + + if (XRE_IsContentProcess() && StaticPrefs::gfx_content_always_paint()) { + FrameLayerBuilder::InvalidateAllLayers(aLayerManager); + } + + if (aIsWidgetTransaction) { + layerBuilder->DidBeginRetainedLayerTransaction(aLayerManager); + } + + // Clear any ScrollMetadata that may have been set on the root layer on a + // previous paint. This paint will set new metrics if necessary, and if we + // don't clear the old one here, we may be left with extra metrics. + if (Layer* rootLayer = aLayerManager->GetRoot()) { + rootLayer->SetScrollMetadata(nsTArray<ScrollMetadata>()); + } + + float resolutionUniform = 1.0f; + float resolutionX = resolutionUniform; + float resolutionY = resolutionUniform; + + // If we are in a remote browser, then apply scaling from ancestor browsers + if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) { + if (!browserChild->IsTopLevel()) { + resolutionX *= browserChild->GetEffectsInfo().mScaleX; + resolutionY *= browserChild->GetEffectsInfo().mScaleY; + } + } + + ContainerLayerParameters containerParameters(resolutionX, resolutionY); + + { + PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::Layerization); + + root = layerBuilder->BuildContainerLayerFor(aBuilder, aLayerManager, + frame, nullptr, this, + containerParameters, nullptr); + + aBuilder->NotifyAndClearScrollFrames(); + + if (!record.GetStart().IsNull() && + StaticPrefs::layers_acceleration_draw_fps()) { + if (PaintTiming* pt = + ClientLayerManager::MaybeGetPaintTiming(aLayerManager)) { + pt->flbMs() = (TimeStamp::Now() - record.GetStart()).ToMilliseconds(); + } + } + } + + if (!root) { + return nullptr; + } + // Root is being scaled up by the X/Y resolution. Scale it back down. + root->SetPostScale(1.0f / resolutionX, 1.0f / resolutionY); + + auto callback = [root](ScrollableLayerGuid::ViewID aScrollId) -> bool { + return nsLayoutUtils::ContainsMetricsWithId(root, aScrollId); + }; + if (Maybe<ScrollMetadata> rootMetadata = nsLayoutUtils::GetRootMetadata( + aBuilder, root->Manager(), containerParameters, callback)) { + GetLayerForRootMetadata(root, rootMetadata->GetMetrics().GetScrollId()) + ->SetScrollMetadata(rootMetadata.value()); + } + + // NS_WARNING is debug-only, so don't even bother checking the conditions + // in a release build. +#ifdef DEBUG + bool usingDisplayport = false; + if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) { + nsIContent* content = rootScrollFrame->GetContent(); + if (content) { + usingDisplayport = DisplayPortUtils::HasDisplayPort(content); + } + } + if (usingDisplayport && + !(root->GetContentFlags() & Layer::CONTENT_OPAQUE) && + SpammyLayoutWarningsEnabled()) { + // See bug 693938, attachment 567017 + NS_WARNING("Transparent content with displayports can be expensive."); + } +#endif + + aLayerManager->SetRoot(root); + layerBuilder->WillEndTransaction(); + } + return layerBuilder; +} + +/** + * 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. + */ +already_AddRefed<LayerManager> nsDisplayList::PaintRoot( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags) { + AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS); + + RefPtr<LayerManager> layerManager; + bool widgetTransaction = false; + bool doBeginTransaction = true; + nsView* view = nullptr; + if (aFlags & PAINT_USE_WIDGET_LAYERS) { + layerManager = aBuilder->GetWidgetLayerManager(&view); + if (layerManager) { + layerManager->SetContainsSVG(false); + + doBeginTransaction = !(aFlags & PAINT_EXISTING_TRANSACTION); + widgetTransaction = true; + } + } + if (!layerManager) { + if (!aCtx) { + NS_WARNING("Nowhere to paint into"); + return nullptr; + } + layerManager = new BasicLayerManager(BasicLayerManager::BLM_OFFSCREEN); + } + + nsIFrame* frame = aBuilder->RootReferenceFrame(); + nsPresContext* presContext = frame->PresContext(); + PresShell* presShell = presContext->PresShell(); + Document* document = presShell->GetDocument(); + + if (layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx)) { + return nullptr; + } + } else { + if (!layerManager->BeginTransaction()) { + return nullptr; + } + } + } + + bool prevIsCompositingCheap = + aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); + MaybeSetupTransactionIdAllocator(layerManager, presContext); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + sent = layerManager->EndEmptyTransaction(); + } + + if (!sent) { + // Windowed plugins are not supported with WebRender enabled. + // But PluginGeometry needs to be updated to show plugin. + // Windowed plugins are going to be removed by Bug 1296400. + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (rootPresContext && XRE_IsContentProcess()) { + if (aBuilder->WillComputePluginGeometry()) { + rootPresContext->ComputePluginGeometryUpdates( + aBuilder->RootReferenceFrame(), aBuilder, this); + } + // This must be called even if PluginGeometryUpdates were not computed. + rootPresContext->CollectPluginGeometryUpdates(layerManager); + } + + auto* wrManager = static_cast<WebRenderLayerManager*>(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)); + } + + // 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())) { + frame->ClearInvalidationStateBits(); + } + + aBuilder->SetIsCompositingCheap(prevIsCompositingCheap); + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, + layerManager->GetAnimationReadyTime()); + } + + if (presContext->RefreshDriver()->HasScheduleFlush()) { + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + frame->GetRect()); + } + + return layerManager.forget(); + } + + NotifySubDocInvalidationFunc computeInvalidFunc = + presContext->MayHavePaintEventListenerInSubDocument() + ? nsPresContext::NotifySubDocInvalidation + : nullptr; + + UniquePtr<LayerProperties> props; + + bool computeInvalidRect = + (computeInvalidFunc || (!layerManager->IsCompositingCheap() && + layerManager->NeedsWidgetInvalidation())) && + widgetTransaction; + + if (computeInvalidRect) { + props = LayerProperties::CloneFrom(layerManager->GetRoot()); + } + + if (doBeginTransaction) { + if (aCtx) { + if (!layerManager->BeginTransactionWithTarget(aCtx)) { + return nullptr; + } + } else { + if (!layerManager->BeginTransaction()) { + return nullptr; + } + } + } + + bool temp = + aBuilder->SetIsCompositingCheap(layerManager->IsCompositingCheap()); + LayerManager::EndTransactionFlags flags = LayerManager::END_DEFAULT; + if (layerManager->NeedsWidgetInvalidation()) { + if (aFlags & PAINT_NO_COMPOSITE) { + flags = LayerManager::END_NO_COMPOSITE; + } + } else { + // Client layer managers never composite directly, so + // we don't need to worry about END_NO_COMPOSITE. + if (aBuilder->WillComputePluginGeometry()) { + flags = LayerManager::END_NO_REMOTE_COMPOSITE; + } + } + + MaybeSetupTransactionIdAllocator(layerManager, presContext); + + bool sent = false; + if (aFlags & PAINT_IDENTICAL_DISPLAY_LIST) { + sent = layerManager->EndEmptyTransaction(flags); + } + + if (!sent) { + FrameLayerBuilder* layerBuilder = + BuildLayers(aBuilder, layerManager, aFlags, widgetTransaction); + + if (!layerBuilder) { + layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + return nullptr; + } + + // If this is the content process, we ship plugin geometry updates over with + // layer updates, so calculate that now before we call EndTransaction. + nsRootPresContext* rootPresContext = presContext->GetRootPresContext(); + if (rootPresContext && XRE_IsContentProcess()) { + if (aBuilder->WillComputePluginGeometry()) { + rootPresContext->ComputePluginGeometryUpdates( + aBuilder->RootReferenceFrame(), aBuilder, this); + } + // The layer system caches plugin configuration information for forwarding + // with layer updates which needs to get set during reflow. This must be + // called even if there are no windowed plugins in the page. + rootPresContext->CollectPluginGeometryUpdates(layerManager); + } + + layerManager->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder, + flags); + layerBuilder->DidEndTransaction(); + } + + 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())) { + frame->ClearInvalidationStateBits(); + } + + aBuilder->SetIsCompositingCheap(temp); + + if (document && widgetTransaction) { + TriggerPendingAnimations(*document, layerManager->GetAnimationReadyTime()); + } + + nsIntRegion invalid; + if (props) { + if (!props->ComputeDifferences(layerManager->GetRoot(), invalid, + computeInvalidFunc)) { + invalid = nsIntRect::MaxIntRect(); + } + } else if (widgetTransaction) { + LayerProperties::ClearInvalidations(layerManager->GetRoot()); + } + + bool shouldInvalidate = layerManager->NeedsWidgetInvalidation(); + + if (view) { + if (props) { + if (!invalid.IsEmpty()) { + nsIntRect bounds = invalid.GetBounds(); + nsRect rect(presContext->DevPixelsToAppUnits(bounds.x), + presContext->DevPixelsToAppUnits(bounds.y), + presContext->DevPixelsToAppUnits(bounds.width), + presContext->DevPixelsToAppUnits(bounds.height)); + if (shouldInvalidate) { + view->GetViewManager()->InvalidateViewNoSuppression(view, rect); + } + presContext->NotifyInvalidation(layerManager->GetLastTransactionId(), + bounds); + } + } else if (shouldInvalidate) { + view->GetViewManager()->InvalidateView(view); + } + } + + layerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); + return layerManager.forget(); +} + +nsDisplayItem* nsDisplayList::RemoveBottom() { + nsDisplayItem* item = mSentinel.mAbove; + if (!item) { + return nullptr; + } + mSentinel.mAbove = item->mAbove; + if (item == mTop) { + // must have been the only item + mTop = &mSentinel; + } + item->mAbove = nullptr; + mLength--; + return item; +} + +void nsDisplayList::DeleteAll(nsDisplayListBuilder* aBuilder) { + nsDisplayItem* item; + while ((item = RemoveBottom()) != nullptr) { + item->Destroy(aBuilder); + } +} + +static bool IsFrameReceivingPointerEvents(nsIFrame* aFrame) { + return StylePointerEvents::None != + aFrame->StyleUI()->GetEffectivePointerEvents(aFrame); +} + +// 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 this < &aOther; + } + bool operator==(const FramesWithDepth& aOther) const { + return this == &aOther; + } + + float mDepth; + nsTArray<nsIFrame*> mFrames; +}; + +// Sort the frames by depth and then moves all the contained frames to the +// destination +static void FlushFramesArray(nsTArray<FramesWithDepth>& aSource, + nsTArray<nsIFrame*>* aDest) { + if (aSource.IsEmpty()) { + return; + } + aSource.Sort(); + 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<nsIFrame*>* 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<nsDisplayTransform*>(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<FramesWithDepth, 16> 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<nsDisplayTransform*>(item)->IsParticipating3DContext()) || + (itemType == DisplayItemType::TYPE_PERSPECTIVE && + item->Frame()->Extend3DContext()); + if (same3DContext && + (itemType != DisplayItemType::TYPE_TRANSFORM || + !static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext())) { + if (!item->GetClip().MayIntersect(aRect)) { + continue; + } + AutoTArray<nsIFrame*, 1> neverUsed; + // Start gethering 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<nsIFrame*, 16> 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<nsIFrame*>* writeFrames = aOutFrames; + if (item->GetType() == DisplayItemType::TYPE_TRANSFORM && + static_cast<nsDisplayTransform*>(item)->IsLeafOf3DContext()) { + if (outFrames.Length()) { + nsDisplayTransform* transform = + static_cast<nsDisplayTransform*>(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<nsDisplayOpacity*>(item)->GetOpacity(); + case DisplayItemType::TYPE_BACKGROUND_COLOR: + return static_cast<nsDisplayBackgroundColor*>(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::GetCrossDocParentFrame(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<ZSortItem>(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 nsLayoutUtils::CompareTreePosition(content1, content2, + mCommonAncestor) < 0; + } +}; + +void nsDisplayList::SortByContentOrder(nsIContent* aCommonAncestor) { + Sort<nsDisplayItem*>(ContentComparator(aCommonAncestor)); +} + +bool nsDisplayItemBase::HasModifiedFrame() const { + return mItemFlags.contains(ItemBaseFlag::ModifiedFrame); +} + +void nsDisplayItemBase::SetModifiedFrame(bool aModified) { + if (aModified) { + mItemFlags += ItemBaseFlag::ModifiedFrame; + } else { + mItemFlags -= ItemBaseFlag::ModifiedFrame; + } +} + +void nsDisplayItemBase::SetDeletedFrame() { + mItemFlags += ItemBaseFlag::DeletedFrame; +} + +bool nsDisplayItemBase::HasDeletedFrame() const { + bool retval = mItemFlags.contains(ItemBaseFlag::DeletedFrame) || + (GetType() == DisplayItemType::TYPE_REMOTE && + !static_cast<const nsDisplayRemote*>(this)->GetFrameLoader()); + MOZ_ASSERT(retval || mFrame); + return retval; +} + +#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) + : nsDisplayItemBase(aBuilder, aFrame), + mActiveScrolledRoot(aActiveScrolledRoot), + mAnimatedGeometryRoot(nullptr) { + MOZ_COUNT_CTOR(nsDisplayItem); + + mReferenceFrame = aBuilder->FindReferenceFrameFor(aFrame, &mToReferenceFrame); + // This can return the wrong result if the item override + // ShouldFixToViewport(), the item needs to set it again in its constructor. + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(aFrame); + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + aBuilder->RootReferenceFrame(), *mAnimatedGeometryRoot), + "Bad"); + NS_ASSERTION( + aBuilder->GetVisibleRect().width >= 0 || !aBuilder->IsForPainting(), + "visible rect not set"); + + nsDisplayItem::SetClipChain( + aBuilder->ClipState().GetCurrentCombinedClipChain(aBuilder), true); + + // 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(disp)) { + mItemFlags += ItemFlag::Combines3DTransformWithAncestors; + } +} + +/* static */ +bool nsDisplayItem::ForceActiveLayers() { + return StaticPrefs::layers_force_active(); +} + +int32_t nsDisplayItem::ZIndex() const { return mFrame->ZIndex().valueOr(0); } + +bool nsDisplayItem::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return !GetPaintRect().IsEmpty() && + !IsInvisibleInRect(aVisibleRegion->GetBounds()); +} + +bool nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (ForceNotVisible() && !GetSameCoordinateSystemChildren()) { + // mForceNotVisible wants to ensure that this display item doesn't render + // anything itself. If this item has contents, then we obviously want to + // render those, so we don't need this check in that case. + NS_ASSERTION(GetBuildingRect().IsEmpty(), + "invisible items without children should have empty vis rect"); + SetPaintRect(nsRect()); + } else { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + + nsRegion itemVisible; + itemVisible.And(*aVisibleRegion, bounds); + SetPaintRect(itemVisible.GetBounds()); + } + + // When we recompute visibility within layers we don't need to + // expand the visible region for content behind plugins (the plugin + // is not in the layer). + if (!ComputeVisibility(aBuilder, aVisibleRegion)) { + SetPaintRect(nsRect()); + return false; + } + + nsRegion opaque = TreatAsOpaque(this, aBuilder); + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, opaque); + return true; +} + +void nsDisplayItem::SetClipChain(const DisplayItemClipChain* aClipChain, + bool aStore) { + mClipChain = aClipChain; + mClip = DisplayItemClipChain::ClipForASR(aClipChain, mActiveScrolledRoot); + + if (aStore) { + mState.mClipChain = mClipChain; + mState.mClip = mClip; + } +} + +Maybe<nsRect> nsDisplayItem::GetClipWithRespectToASR( + nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { + if (const DisplayItemClip* clip = + DisplayItemClipChain::ClipForASR(GetClipChain(), aASR)) { + return Some(clip->GetClipRect()); + } +#ifdef DEBUG + MOZ_ASSERT(false, "item should have finite clip with respect to aASR"); +#endif + return Nothing(); +} + +void nsDisplayItem::FuseClipChainUpTo(nsDisplayListBuilder* aBuilder, + const ActiveScrolledRoot* aASR) { + mClipChain = aBuilder->FuseClipChainUpTo(mClipChain, aASR); + + if (mClipChain) { + mClip = &mClipChain->mClip; + } else { + mClip = nullptr; + } +} + +bool nsDisplayItem::ShouldUseAdvancedLayer(LayerManager* aManager, + PrefFunc aFunc) const { + return CanUseAdvancedLayer(aManager) ? aFunc() : false; +} + +bool nsDisplayItem::CanUseAdvancedLayer(LayerManager* aManager) const { + return StaticPrefs::layers_advanced_basic_layer_enabled() || !aManager || + aManager->GetBackendType() == layers::LayersBackend::LAYERS_WR; +} + +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; +} + +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) { + MOZ_COUNT_CTOR(nsDisplayContainer); + mChildren.AppendToTop(aList); + UpdateBounds(aBuilder); + + // Clear and store the clip chain set by nsDisplayItem constructor. + nsDisplayItem::SetClipChain(nullptr, true); +} + +bool nsDisplayContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources); + return true; +} + +/** + * Like |nsDisplayList::ComputeVisibilityForSublist()|, but restricts + * |aVisibleRegion| to given |aBounds| of the list. + */ +static bool ComputeClippedVisibilityForSubList(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion, + nsDisplayList* aList, + const nsRect& aBounds) { + nsRegion visibleRegion; + visibleRegion.And(*aVisibleRegion, aBounds); + nsRegion originalVisibleRegion = visibleRegion; + + const bool anyItemVisible = + aList->ComputeVisibilityForSublist(aBuilder, &visibleRegion, aBounds); + nsRegion removed; + // removed = originalVisibleRegion - visibleRegion + removed.Sub(originalVisibleRegion, visibleRegion); + // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications + // SubtractFromVisibleRegion does) + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + + return anyItemVisible; +} + +bool nsDisplayContainer::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion, + GetChildren(), GetPaintRect()); +} + +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) { + if (aList->IsOpaque()) { + // Everything within list bounds that's visible is opaque. This is an + // optimization to avoid calculating the opaque region. + return aListBounds; + } + + if (aBuilder->HitTestIsForVisibility()) { + // If we care about an accurate opaque region, iterate the display list + // and build up a region of opaque bounds. + return aList->GetOpaqueRegion(aBuilder); + } + + return nsRegion(); +} + +nsRegion nsDisplayContainer::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + return ::GetOpaqueRegion(aBuilder, GetChildren(), GetBounds(aBuilder, aSnap)); +} + +Maybe<nsRect> 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<nsIFrame*>* 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; +} + +LayerState nsDisplaySolidColor::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (ForceActiveLayers()) { + return LayerState::LAYER_ACTIVE; + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplaySolidColor::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<ColorLayer> layer = static_cast<ColorLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateColorLayer(); + if (!layer) { + return nullptr; + } + } + layer->SetColor(ToDeviceColor(mColor)); + + const int32_t appUnitsPerDevPixel = + mFrame->PresContext()->AppUnitsPerDevPixel(); + layer->SetBounds(mBounds.ToNearestPixels(appUnitsPerDevPixel)); + layer->SetBaseTransform(gfx::Matrix4x4::Translation( + aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0)); + + return layer.forget(); +} + +void nsDisplaySolidColor::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + Rect rect = + NSRectToSnappedRect(GetPaintRect(), 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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits( + mBounds, mFrame->PresContext()->AppUnitsPerDevPixel()); + wr::LayoutRect r = wr::ToLayoutRect(bounds); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), + 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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::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(), + wr::ToColorF(ToDeviceColor(mColor))); + } + + return true; +} + +static void RegisterThemeGeometry(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, nsIFrame* aFrame, + nsITheme::ThemeGeometryType aType) { + if (aBuilder->IsInChromeDocumentOrPopup() && !aBuilder->IsInTransform()) { + nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(aFrame); + nsPoint offset = aBuilder->IsInSubdocument() + ? aBuilder->ToReferenceFrame(aFrame) + : aFrame->GetOffsetTo(displayRoot); + nsRect borderBox = nsRect(offset, aFrame->GetSize()); + 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<nsRect> 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, + 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<imgIContainer> 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) + : nsDisplayImageContainer(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), + mImageFlags(0) { + 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->IsFrameOfType(nsIFrame::eTablePart)); + } +#endif + + mBounds = GetBoundsInternal(aInitData.builder, aFrameForBounds); + if (mShouldFixToViewport) { + mAnimatedGeometryRoot = + aInitData.builder->FindAnimatedGeometryRootFor(this); + + // 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<nsRect> viewportRect = GetViewportRectRelativeToReferenceFrame( + aInitData.builder, mFrame)) { + SetBuildingRect(mBounds.Intersect(*viewportRect)); + } + } +} + +nsDisplayBackgroundImage::~nsDisplayBackgroundImage() { + MOZ_COUNT_DTOR(nsDisplayBackgroundImage); + if (mDependentFrame) { + mDependentFrame->RemoveDisplayItem(this); + } +} + +static nsIFrame* GetBackgroundComputedStyleFrame(nsIFrame* aFrame) { + nsIFrame* f; + if (!nsCSSRendering::FindBackgroundFrame(aFrame, &f)) { + // We don't want to bail out if moz-appearance is set on a root + // node. If it has a parent content node, bail because it's not + // a root, other wise keep going in order to let the theme stuff + // draw the background. The canvas really should be drawing the + // bg, but there's no way to hook that up via css. + if (!aFrame->StyleDisplay()->HasAppearance()) { + return nullptr; + } + + nsIContent* content = aFrame->GetContent(); + if (!content || content->GetParent()) { + return nullptr; + } + + f = aFrame; + } + return f; +} + +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<nsRegion*>(cutoutRegion)); + region.MoveBy(aBuilder->ToReferenceFrame(aFrame)); + aList->AppendNewToTop<nsDisplaySolidColorRegion>(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<uint8_t>(TableType::MAX) < + (1 << (static_cast<uint8_t>(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<uint8_t>(TableTypeBits::Count)) | + static_cast<uint8_t>(aType); + + return static_cast<uint16_t>(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<nsDisplayTableBackgroundImage>( + aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBackgroundImage>(aBuilder, aFrame, + index, aBgData); +} + +static nsDisplayThemedBackground* CreateThemedBackground( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableThemedBackground>( + aBuilder, aSecondaryFrame, index, aBgRect, aFrame); + } + + return MakeDisplayItem<nsDisplayThemedBackground>(aBuilder, aFrame, aBgRect); +} + +static nsDisplayBackgroundColor* CreateBackgroundColor( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, + nsRect& aBgRect, ComputedStyle* aBgSC, nscolor aColor) { + if (aSecondaryFrame) { + const uint16_t index = static_cast<uint16_t>(GetTableTypeFromFrame(aFrame)); + return MakeDisplayItemWithIndex<nsDisplayTableBackgroundColor>( + aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame); + } + + return MakeDisplayItem<nsDisplayBackgroundColor>(aBuilder, aFrame, aBgRect, + aBgSC, aColor); +} + +/*static*/ +bool nsDisplayBackgroundImage::AppendBackgroundItemsToTop( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const nsRect& aBackgroundRect, nsDisplayList* aList, + bool aAllowWillPaintBorderOptimization, ComputedStyle* aComputedStyle, + const nsRect& aBackgroundOriginRect, nsIFrame* aSecondaryReferenceFrame, + Maybe<nsDisplayListBuilder::AutoBuildingDisplayList>* + aAutoBuildingDisplayList) { + ComputedStyle* bgSC = aComputedStyle; + const nsStyleBackground* bg = nullptr; + nsRect bgRect = aBackgroundRect; + nsRect bgOriginRect = bgRect; + if (!aBackgroundOriginRect.IsEmpty()) { + bgOriginRect = aBackgroundOriginRect; + } + nsPresContext* presContext = aFrame->PresContext(); + bool isThemed = aFrame->IsThemed(); + nsIFrame* dependentFrame = nullptr; + if (!isThemed) { + if (!bgSC) { + dependentFrame = GetBackgroundComputedStyleFrame(aFrame); + if (dependentFrame) { + bgSC = dependentFrame->Style(); + if (dependentFrame == aFrame) { + dependentFrame = nullptr; + } + } + } + if (bgSC) { + bg = bgSC->StyleBackground(); + } + } + + bool drawBackgroundColor = false; + // XUL root frames need special handling for now even though they return true + // from nsCSSRendering::IsCanvasFrame they rely on us painting the background + // image from here, see bug 1665476. + bool drawBackgroundImage = + aFrame->IsXULRootFrame() && aFrame->ComputeShouldPaintBackground().mImage; + nscolor color = NS_RGBA(0, 0, 0, 0); + if (!nsCSSRendering::IsCanvasFrame(aFrame) && bg) { + color = nsCSSRendering::DetermineBackgroundColor( + presContext, bgSC, aFrame, drawBackgroundImage, drawBackgroundColor); + } + + if (SpecialCutoutRegionCase(aBuilder, aFrame, aBackgroundRect, aList, + color)) { + return false; + } + + const nsStyleBorder* borderStyle = aFrame->StyleBorder(); + const nsStyleEffects* effectsStyle = aFrame->StyleEffects(); + bool hasInsetShadow = effectsStyle->HasBoxShadowWithInset(true); + bool willPaintBorder = aAllowWillPaintBorderOptimization && !isThemed && + !hasInsetShadow && borderStyle->HasBorder(); + + // 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; + // Even if we don't actually have a background color to paint, we may still + // need to create an item for hit testing. + if ((drawBackgroundColor && color != NS_RGBA(0, 0, 0, 0)) || + aBuilder->IsForEventDelivery()) { + if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) { + aAutoBuildingDisplayList->emplace(aBuilder, aFrame); + } + Maybe<DisplayListClipState::AutoSaveRestore> clipState; + nsRect bgColorRect = bgRect; + if (bg && !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. + bool useWillPaintBorderOptimization = + willPaintBorder && + nsLayoutUtils::HasNonZeroCorner(borderStyle->mBorderRadius); + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + bg->BottomLayer(), aFrame, *aFrame->StyleBorder(), bgRect, bgRect, + 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) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + bgItemList.AppendToTop(bgItem); + } + } + + if (isThemed) { + nsDisplayThemedBackground* bgItem = CreateThemedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgRect); + + if (bgItem) { + bgItem->Init(aBuilder); + bgItemList.AppendToTop(bgItem); + } + + aList->AppendToTop(&bgItemList); + return true; + } + + if (!bg || !drawBackgroundImage) { + aList->AppendToTop(&bgItemList); + return false; + } + + const ActiveScrolledRoot* asr = aBuilder->CurrentActiveScrolledRoot(); + + bool needBlendContainer = false; + + // 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; + } + + if (aAutoBuildingDisplayList && !*aAutoBuildingDisplayList) { + aAutoBuildingDisplayList->emplace(aBuilder, aFrame); + } + + 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, bgRect, + willPaintBorder); + } + + nsDisplayList thisItemList; + 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); + 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) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + + thisItemList.AppendToTop( + nsDisplayFixedPosition::CreateForFixedBackground( + aBuilder, aFrame, aSecondaryReferenceFrame, bgItem, i)); + } + } else { // bgData.shouldFixToViewport == false + nsDisplayBackgroundImage* bgItem = CreateBackgroundImage( + aBuilder, aFrame, aSecondaryReferenceFrame, bgData); + if (bgItem) { + bgItem->SetDependentFrame(aBuilder, dependentFrame); + thisItemList.AppendToTop(bgItem); + } + } + + if (bg->mImage.mLayers[i].mBlendMode != StyleBlend::Normal) { + DisplayListClipState::AutoSaveRestore blendClip(aBuilder); + // 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<nsDisplayTableBlendMode>( + aBuilder, aSecondaryReferenceFrame, index, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true); + } else { + thisItemList.AppendNewToTopWithIndex<nsDisplayBlendMode>( + aBuilder, aFrame, i + 1, &thisItemList, + bg->mImage.mLayers[i].mBlendMode, asr, true); + } + } + bgItemList.AppendToTop(&thisItemList); + } + + if (needBlendContainer) { + DisplayListClipState::AutoSaveRestore blendContainerClip(aBuilder); + + bgItemList.AppendToTop( + nsDisplayBlendContainer::CreateForBackgroundBlendMode( + aBuilder, aFrame, aSecondaryReferenceFrame, &bgItemList, asr)); + } + + aList->AppendToTop(&bgItemList); + return false; +} + +// 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::CanOptimizeToImageLayer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + if (!mBackgroundStyle) { + return false; + } + + // We currently can't handle tiled backgrounds. + if (!mDestRect.Contains(mFillRect)) { + return false; + } + + // For 'contain' and 'cover', we allow any pixel of the image to be sampled + // because there isn't going to be any spriting/atlasing going on. + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + bool allowPartialImages = layer.mSize.IsContain() || layer.mSize.IsCover(); + if (!allowPartialImages && !mFillRect.Contains(mDestRect)) { + return false; + } + + return nsDisplayImageContainer::CanOptimizeToImageLayer(aManager, aBuilder); +} + +nsRect nsDisplayBackgroundImage::GetDestRect() const { return mDestRect; } + +already_AddRefed<imgIContainer> nsDisplayBackgroundImage::GetImage() { + nsCOMPtr<imgIContainer> image = mImage; + return image.forget(); +} + +nsDisplayBackgroundImage::ImageLayerization +nsDisplayBackgroundImage::ShouldCreateOwnLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager) { + if (ForceActiveLayers()) { + return WHENEVER_POSSIBLE; + } + + nsIFrame* backgroundStyleFrame = + nsCSSRendering::FindBackgroundStyleFrame(StyleFrame()); + if (ActiveLayerTracker::IsBackgroundPositionAnimated(aBuilder, + backgroundStyleFrame)) { + return WHENEVER_POSSIBLE; + } + + if (StaticPrefs::layout_animated_image_layers_enabled() && mBackgroundStyle) { + const nsStyleImageLayers::Layer& layer = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer]; + const auto* image = &layer.mImage; + if (auto* request = image->GetImageRequest()) { + nsCOMPtr<imgIContainer> image; + if (NS_SUCCEEDED(request->GetImage(getter_AddRefs(image))) && image) { + bool animated = false; + if (NS_SUCCEEDED(image->GetAnimated(&animated)) && animated) { + return WHENEVER_POSSIBLE; + } + } + } + } + + if (nsLayoutUtils::GPUImageScalingEnabled() && + aManager->IsCompositingCheap()) { + return ONLY_FOR_SCALING; + } + + return NO_LAYER_NEEDED; +} + +static void CheckForBorderItem(nsDisplayItem* aItem, uint32_t& aFlags) { + nsDisplayItem* nextItem = aItem->GetAbove(); + while (nextItem && nextItem->GetType() == DisplayItemType::TYPE_BACKGROUND) { + nextItem = nextItem->GetAbove(); + } + if (nextItem && nextItem->Frame() == aItem->Frame() && + nextItem->GetType() == DisplayItemType::TYPE_BORDER) { + aFlags |= nsCSSRendering::PAINTBG_WILL_PAINT_BORDER; + } +} + +LayerState nsDisplayBackgroundImage::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + mImageFlags = aBuilder->GetBackgroundPaintFlags(); + CheckForBorderItem(this, mImageFlags); + + ImageLayerization shouldLayerize = ShouldCreateOwnLayer(aBuilder, aManager); + if (shouldLayerize == NO_LAYER_NEEDED) { + // We can skip the call to CanOptimizeToImageLayer if we don't want a + // layer anyway. + return LayerState::LAYER_NONE; + } + + if (CanOptimizeToImageLayer(aManager, aBuilder)) { + if (shouldLayerize == WHENEVER_POSSIBLE) { + return LayerState::LAYER_ACTIVE; + } + + MOZ_ASSERT(shouldLayerize == ONLY_FOR_SCALING, + "unhandled ImageLayerization value?"); + + MOZ_ASSERT(mImage); + int32_t imageWidth; + int32_t imageHeight; + mImage->GetWidth(&imageWidth); + mImage->GetHeight(&imageHeight); + NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + LayoutDeviceRect destRect = + LayoutDeviceRect::FromAppUnits(GetDestRect(), appUnitsPerDevPixel); + + const LayerRect destLayerRect = destRect * aParameters.Scale(); + + // Calculate the scaling factor for the frame. + const gfxSize scale = gfxSize(destLayerRect.width / imageWidth, + destLayerRect.height / imageHeight); + + if ((scale.width != 1.0f || scale.height != 1.0f) && + (destLayerRect.width * destLayerRect.height >= 64 * 64)) { + // Separate this image into a layer. + // There's no point in doing this if we are not scaling at all or if the + // target size is pretty small. + return LayerState::LAYER_ACTIVE; + } + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplayBackgroundImage::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<ImageLayer> layer = static_cast<ImageLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateImageLayer(); + if (!layer) { + return nullptr; + } + } + RefPtr<ImageContainer> imageContainer = GetContainer(aManager, aBuilder); + layer->SetContainer(imageContainer); + ConfigureLayer(layer, aParameters); + return layer.forget(); +} + +bool nsDisplayBackgroundImage::CanBuildWebRenderDisplayItems( + LayerManager* aManager, nsDisplayListBuilder* aDisplayListBuilder) { + if (aDisplayListBuilder) { + mImageFlags = aDisplayListBuilder->GetBackgroundPaintFlags(); + } + + return mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mClip != + StyleGeometryBox::Text && + nsCSSRendering::CanBuildWebRenderDisplayItemsForStyleImageLayer( + aManager, *StyleFrame()->PresContext(), StyleFrame(), + mBackgroundStyle->StyleBackground(), mLayer, mImageFlags); +} + +bool nsDisplayBackgroundImage::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems(aManager->LayerManager(), + aDisplayListBuilder)) { + return false; + } + + CheckForBorderItem(this, mImageFlags); + nsCSSRendering::PaintBGParams params = + nsCSSRendering::PaintBGParams::ForSingleLayer( + *StyleFrame()->PresContext(), GetPaintRect(), mBackgroundRect, + StyleFrame(), mImageFlags, mLayer, CompositionOp::OP_OVER); + params.bgClipRect = &mBounds; + ImgDrawResult result = + nsCSSRendering::BuildWebRenderDisplayItemsForStyleImageLayer( + params, aBuilder, aResources, aSc, aManager, this); + if (result == ImgDrawResult::NOT_SUPPORTED) { + return false; + } + + nsDisplayBackgroundGeometry::UpdateDrawResult(this, result); + return true; +} + +void nsDisplayBackgroundImage::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + if (RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + aOutFrames->AppendElement(mFrame); + } +} + +bool nsDisplayBackgroundImage::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsDisplayImageContainer::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + // Return false if the background was propagated away from this + // frame. We don't want this display item to show up and confuse + // anything. + return mBackgroundStyle; +} + +/* static */ +nsRegion nsDisplayBackgroundImage::GetInsideClipRegion( + const nsDisplayItem* aItem, StyleGeometryBox aClip, const nsRect& aRect, + const nsRect& aBackgroundRect) { + nsRegion result; + if (aRect.IsEmpty()) { + return result; + } + + nsIFrame* frame = aItem->Frame(); + + nsRect clipRect = aBackgroundRect; + if (frame->IsCanvasFrame()) { + nsCanvasFrame* canvasFrame = static_cast<nsCanvasFrame*>(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 = GetInsideClipRegion(this, layer.mClip, mBounds, mBackgroundRect); + } + } + + return result; +} + +Maybe<nscolor> 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]; + if (layer.RenderingMightDependOnPositioningAreaSizeChange()) { + return true; + } + return false; +} + +void nsDisplayBackgroundImage::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(), &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(), + mImageFlags, mLayer, CompositionOp::OP_OVER); + params.bgClipRect = aClipRect; + ImgDrawResult result = nsCSSRendering::PaintStyleImageLayer(params, *aCtx); + + if (clip == StyleGeometryBox::Text) { + ctx->PopGroupAndBlend(); + } + + nsDisplayBackgroundGeometry::UpdateDrawResult(this, result); +} + +void nsDisplayBackgroundImage::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + if (!mBackgroundStyle) { + return; + } + + auto* geometry = static_cast<const nsDisplayBackgroundGeometry*>(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 (aBuilder->ShouldSyncDecodeImages()) { + const auto& image = + mBackgroundStyle->StyleBackground()->mImage.mLayers[mLayer].mImage; + if (image.IsImageRequestType() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + } + } + 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<nsCanvasFrame*>(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); + } + + if (mAppearance == StyleAppearance::MozWinBorderlessGlass || + mAppearance == StyleAppearance::MozWinGlass) { + aBuilder->SetGlassDisplayItem(this); + } + + mBounds = GetBoundsInternal(); +} + +void nsDisplayThemedBackground::WriteDebugInfo(std::stringstream& aStream) { + aStream << " (themed, appearance:" << (int)mAppearance << ")"; +} + +void nsDisplayThemedBackground::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* 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<nscolor> nsDisplayThemedBackground::IsUniform( + nsDisplayListBuilder* aBuilder) const { + if (mAppearance == StyleAppearance::MozWinBorderlessGlass || + mAppearance == StyleAppearance::MozWinGlass) { + return Some(NS_RGBA(0, 0, 0, 0)); + } + return Nothing(); +} + +nsRect nsDisplayThemedBackground::GetPositioningArea() const { + return mBackgroundRect; +} + +void nsDisplayThemedBackground::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + PaintInternal(aBuilder, aCtx, GetPaintRect(), 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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsITheme* theme = StyleFrame()->PresContext()->Theme(); + return theme->CreateWebRenderCommandsForWidget(aBuilder, aResources, aSc, + aManager, StyleFrame(), + mAppearance, mBackgroundRect); +} + +bool nsDisplayThemedBackground::IsWindowActive() const { + EventStates docState = mFrame->GetContent()->OwnerDoc()->GetDocumentState(); + return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE); +} + +void nsDisplayThemedBackground::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = + static_cast<const nsDisplayThemedBackgroundGeometry*>(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(); +} + +void nsDisplayImageContainer::ConfigureLayer( + ImageLayer* aLayer, const ContainerLayerParameters& aParameters) { + aLayer->SetSamplingFilter(nsLayoutUtils::GetSamplingFilterForFrame(mFrame)); + + nsCOMPtr<imgIContainer> image = GetImage(); + MOZ_ASSERT(image); + int32_t imageWidth; + int32_t imageHeight; + image->GetWidth(&imageWidth); + image->GetHeight(&imageHeight); + NS_ASSERTION(imageWidth != 0 && imageHeight != 0, "Invalid image size!"); + + if (imageWidth > 0 && imageHeight > 0) { + // We're actually using the ImageContainer. Let our frame know that it + // should consider itself to have painted successfully. + UpdateDrawResult(ImgDrawResult::SUCCESS); + } + + // It's possible (for example, due to downscale-during-decode) that the + // ImageContainer this ImageLayer is holding has a different size from the + // intrinsic size of the image. For this reason we compute the transform using + // the ImageContainer's size rather than the image's intrinsic size. + // XXX(seth): In reality, since the size of the ImageContainer may change + // asynchronously, this is not enough. Bug 1183378 will provide a more + // complete fix, but this solution is safe in more cases than simply relying + // on the intrinsic size. + IntSize containerSize = aLayer->GetContainer() + ? aLayer->GetContainer()->GetCurrentSize() + : IntSize(imageWidth, imageHeight); + + const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + const LayoutDeviceRect destRect( + LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor)); + + const LayoutDevicePoint p = destRect.TopLeft(); + Matrix transform = Matrix::Translation(p.x + aParameters.mOffset.x, + p.y + aParameters.mOffset.y); + transform.PreScale(destRect.width / containerSize.width, + destRect.height / containerSize.height); + aLayer->SetBaseTransform(gfx::Matrix4x4::From2D(transform)); +} + +already_AddRefed<ImageContainer> nsDisplayImageContainer::GetContainer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + nsCOMPtr<imgIContainer> image = GetImage(); + if (!image) { + MOZ_ASSERT_UNREACHABLE( + "Must call CanOptimizeToImage() and get true " + "before calling GetContainer()"); + return nullptr; + } + + uint32_t flags = imgIContainer::FLAG_ASYNC_NOTIFY; + if (aBuilder->ShouldSyncDecodeImages()) { + flags |= imgIContainer::FLAG_SYNC_DECODE; + } + + RefPtr<ImageContainer> container = image->GetImageContainer(aManager, flags); + if (!container || !container->HasCurrentImage()) { + return nullptr; + } + + return container.forget(); +} + +bool nsDisplayImageContainer::CanOptimizeToImageLayer( + LayerManager* aManager, nsDisplayListBuilder* aBuilder) { + uint32_t flags = aBuilder->ShouldSyncDecodeImages() + ? imgIContainer::FLAG_SYNC_DECODE + : imgIContainer::FLAG_NONE; + + nsCOMPtr<imgIContainer> image = GetImage(); + if (!image) { + return false; + } + + if (!image->IsImageContainerAvailable(aManager, flags)) { + return false; + } + + int32_t imageWidth; + int32_t imageHeight; + image->GetWidth(&imageWidth); + image->GetHeight(&imageHeight); + + if (imageWidth == 0 || imageHeight == 0) { + NS_ASSERTION(false, "invalid image size"); + return false; + } + + const int32_t factor = mFrame->PresContext()->AppUnitsPerDevPixel(); + const LayoutDeviceRect destRect( + LayoutDeviceIntRect::FromAppUnitsToNearest(GetDestRect(), factor)); + + // Calculate the scaling factor for the frame. + const gfxSize scale = + gfxSize(destRect.width / imageWidth, destRect.height / imageHeight); + + if (scale.width < 0.34 || scale.height < 0.34) { + // This would look awful as long as we can't use high-quality downscaling + // for image layers (bug 803703), so don't turn this into an image layer. + return false; + } + + if (mFrame->IsImageFrame() || mFrame->IsImageControlFrame()) { + // Image layer doesn't support draw focus ring for image map. + nsImageFrame* f = static_cast<nsImageFrame*>(mFrame); + if (f->HasImageMap()) { + return false; + } + } + + return true; +} + +#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 + +void nsDisplayBackgroundColor::ApplyOpacity(nsDisplayListBuilder* aBuilder, + float aOpacity, + const DisplayItemClipChain* aClip) { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mColor.a = mColor.a * aOpacity; + IntersectClip(aBuilder, aClip, false); +} + +bool nsDisplayBackgroundColor::CanApplyOpacity() 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); +} + +LayerState nsDisplayBackgroundColor::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (ForceActiveLayers() && !HasBackgroundClipText()) { + return LayerState::LAYER_ACTIVE; + } + + if (EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_BACKGROUND_COLOR)) { + return LayerState::LAYER_ACTIVE_FORCE; + } + + return LayerState::LAYER_NONE; +} + +already_AddRefed<Layer> nsDisplayBackgroundColor::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (mColor == sRGBColor()) { + return nullptr; + } + + RefPtr<ColorLayer> layer = static_cast<ColorLayer*>( + aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); + if (!layer) { + layer = aManager->CreateColorLayer(); + if (!layer) { + return nullptr; + } + } + layer->SetColor(ToDeviceColor(mColor)); + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + layer->SetBounds(mBackgroundRect.ToNearestPixels(appUnitsPerDevPixel)); + layer->SetBaseTransform(gfx::Matrix4x4::Translation( + aContainerParameters.mOffset.x, aContainerParameters.mOffset.y, 0)); + + // Both nsDisplayBackgroundColor and nsDisplayTableBackgroundColor use this + // function, but only nsDisplayBackgroundColor supports compositor animations. + if (GetType() == DisplayItemType::TYPE_BACKGROUND_COLOR) { + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + layer, aBuilder, this, mFrame, GetType()); + } + return layer.forget(); +} + +bool nsDisplayBackgroundColor::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (mColor == sRGBColor()) { + 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(mColor)), &prop); + } else { + aBuilder.StartGroup(this); + aBuilder.PushRect(r, r, !BackfaceIsHidden(), + wr::ToColorF(ToDeviceColor(mColor))); + 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<DisplayItemClip::RoundedRect, 1> 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 nsDisplayBackgroundImage::GetInsideClipRegion( + this, mBottomLayerClip, mBackgroundRect, mBackgroundRect); +} + +Maybe<nscolor> nsDisplayBackgroundColor::IsUniform( + nsDisplayListBuilder* aBuilder) const { + return Some(mColor.ToABGR()); +} + +void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + HitTestState* aState, + nsTArray<nsIFrame*>* 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(); +} + +void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + // TODO join outlines together + MOZ_ASSERT(mFrame->StyleOutline()->ShouldPaintOutline(), + "Should have not created a nsDisplayOutline!"); + + nsPoint offset = ToReferenceFrame(); + nsCSSRendering::PaintOutline( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), mFrame->Style()); +} + +bool nsDisplayOutline::IsThemedOutline() const { + const auto& outlineStyle = mFrame->StyleOutline()->mOutlineStyle; + if (!outlineStyle.IsAuto() || + !StaticPrefs::layout_css_outline_style_auto_enabled()) { + return false; + } + + nsPresContext* pc = mFrame->PresContext(); + nsITheme* theme = pc->Theme(); + return theme->ThemeSupportsWidget(pc, mFrame, StyleAppearance::FocusOutline); +} + +bool nsDisplayOutline::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (IsThemedOutline()) { + return false; + } + + nsPoint offset = ToReferenceFrame(); + + mozilla::Maybe<nsCSSBorderRenderer> borderRenderer = + nsCSSRendering::CreateBorderRendererForOutline( + mFrame->PresContext(), nullptr, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), 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::IsInvisibleInRect(const nsRect& aRect) const { + const nsStyleOutline* outline = mFrame->StyleOutline(); + nsRect borderBox(ToReferenceFrame(), mFrame->GetSize()); + if (borderBox.Contains(aRect) && + !nsLayoutUtils::HasNonZeroCorner(outline->mOutlineRadius)) { + if (outline->mOutlineOffset._0 >= 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<nsIFrame*>* aOutFrames) { + if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { + // aRect doesn't intersect our border-radius curve. + return; + } + + aOutFrames->AppendElement(mFrame); +} + +nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + const mozilla::gfx::CompositorHitTestInfo& aHitTestFlags, + const mozilla::Maybe<nsRect>& aArea) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + // We should never even create this display item if we're not building + // compositor hit-test info or if the computed hit info indicated the + // frame is invisible to hit-testing + MOZ_ASSERT(aBuilder->BuildCompositorHitTestInfo()); + MOZ_ASSERT(aHitTestFlags != CompositorHitTestInvisibleToHit); + + const nsRect& area = + aArea.isSome() ? *aArea : aFrame->GetCompositorHitTestArea(aBuilder); + + SetHitTestInfo(area, aHitTestFlags); + InitializeScrollTarget(aBuilder); +} + +nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, + mozilla::UniquePtr<HitTestInfo>&& aHitTestInfo) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel()) { + MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo); + SetHitTestInfo(std::move(aHitTestInfo)); + InitializeScrollTarget(aBuilder); +} + +void nsDisplayCompositorHitTestInfo::InitializeScrollTarget( + nsDisplayListBuilder* aBuilder) { + if (aBuilder->GetCurrentScrollbarDirection().isSome()) { + // In the case of scrollbar frames, we use the scrollbar's target + // scrollframe instead of the scrollframe with which the scrollbar actually + // moves. + MOZ_ASSERT(HitTestFlags().contains(CompositorHitTestFlags::eScrollbar)); + mScrollTarget = mozilla::Some(aBuilder->GetCurrentScrollbarTarget()); + } +} + +bool nsDisplayCompositorHitTestInfo::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (HitTestArea().IsEmpty()) { + return true; + } + + // XXX: eventually this scrollId computation and the SetHitTestInfo + // call will get moved out into the WR display item iteration code so that + // we don't need to do it as often, and so that we can do it for other + // display item types as well (reducing the need for as many instances of + // this display item). + ScrollableLayerGuid::ViewID scrollId = + mScrollTarget.valueOrFrom([&]() -> ScrollableLayerGuid::ViewID { + const ActiveScrolledRoot* asr = GetActiveScrolledRoot(); + Maybe<ScrollableLayerGuid::ViewID> fixedTarget = + aBuilder.GetContainingFixedPosScrollTarget(asr); + if (fixedTarget) { + return *fixedTarget; + } + if (asr) { + return asr->GetViewId(); + } + return ScrollableLayerGuid::NULL_SCROLL_ID; + }); + + Maybe<SideBits> sideBits = + aBuilder.GetContainingFixedPosSideBits(GetActiveScrolledRoot()); + + // Insert a transparent rectangle with the hit-test info + const LayoutDeviceRect devRect = + LayoutDeviceRect::FromAppUnits(HitTestArea(), mAppUnitsPerDevPixel); + + const wr::LayoutRect rect = wr::ToLayoutRect(devRect); + + aBuilder.StartGroup(this); + aBuilder.PushHitTest(rect, rect, !BackfaceIsHidden(), scrollId, + HitTestFlags(), sideBits.valueOr(SideBits::eNone)); + aBuilder.FinishGroup(); + + return true; +} + +int32_t nsDisplayCompositorHitTestInfo::ZIndex() const { + return mOverrideZIndex ? *mOverrideZIndex + : nsDisplayHitTestInfoBase::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); +} + +#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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + using namespace mozilla::layers; + int32_t contentOffset; + nsIFrame* frame = mCaret->GetFrame(&contentOffset); + if (!frame) { + return true; + } + NS_ASSERTION(frame == mFrame, "We're referring different frame"); + + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + + nsRect caretRect; + nsRect hookRect; + mCaret->ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect); + + gfx::DeviceColor color = ToDeviceColor(frame->GetCaretColorAt(contentOffset)); + 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(), wr::ToColorF(color)); + + if (!devHookRect.IsEmpty()) { + aBuilder.PushRect(hook, hook, !BackfaceIsHidden(), wr::ToColorF(color)); + } + return true; +} + +nsDisplayBorder::nsDisplayBorder(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame) + : nsPaintedDisplayItem(aBuilder, aFrame) { + MOZ_COUNT_CTOR(nsDisplayBorder); + + mBounds = CalculateBounds<nsRect>(*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 { + auto* geometry = static_cast<const nsDisplayBorderGeometry*>(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); + } + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + aInvalidRegion->Or(*aInvalidRegion, GetBounds(aBuilder, &snap)); + } +} + +LayerState nsDisplayBorder::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_NONE; +} + +bool nsDisplayBorder::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + nsRect rect = nsRect(ToReferenceFrame(), mFrame->GetSize()); + + aBuilder.StartGroup(this); + ImgDrawResult drawResult = nsCSSRendering::CreateWebRenderCommandsForBorder( + this, mFrame, rect, aBuilder, aResources, aSc, aManager, + aDisplayListBuilder); + + if (drawResult == ImgDrawResult::NOT_SUPPORTED) { + aBuilder.CancelGroup(true); + return false; + } + + aBuilder.FinishGroup(); + + nsDisplayBorderGeometry::UpdateDrawResult(this, drawResult); + return true; +}; + +void nsDisplayBorder::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + + PaintBorderFlags flags = aBuilder->ShouldSyncDecodeImages() + ? PaintBorderFlags::SyncDecodeImages + : PaintBorderFlags(); + + ImgDrawResult result = nsCSSRendering::PaintBorder( + mFrame->PresContext(), *aCtx, mFrame, GetPaintRect(), + nsRect(offset, mFrame->GetSize()), mFrame->Style(), flags, + mFrame->GetSkipSides()); + + nsDisplayBorderGeometry::UpdateDrawResult(this, result); +} + +nsRect nsDisplayBorder::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = true; + return mBounds; +} + +// Given a region, compute a conservative approximation to it as a list +// of rectangles that aren't vertically adjacent (i.e., vertically +// adjacent or overlapping rectangles are combined). +// Right now this is only approximate, some vertically overlapping rectangles +// aren't guaranteed to be combined. +static void ComputeDisjointRectangles(const nsRegion& aRegion, + nsTArray<nsRect>* aRects) { + nscoord accumulationMargin = nsPresContext::CSSPixelsToAppUnits(25); + nsRect accumulated; + + for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) { + const nsRect& r = iter.Get(); + if (accumulated.IsEmpty()) { + accumulated = r; + continue; + } + + if (accumulated.YMost() >= r.y - accumulationMargin) { + accumulated.UnionRect(accumulated, r); + } else { + aRects->AppendElement(accumulated); + accumulated = r; + } + } + + // Finish the in-flight rectangle, if there is one. + if (!accumulated.IsEmpty()) { + aRects->AppendElement(accumulated); + } +} + +void nsDisplayBoxShadowOuter::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + nsPresContext* presContext = mFrame->PresContext(); + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(mVisibleRegion, &rects); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowOuter::Paint", GRAPHICS); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + nsCSSRendering::PaintBoxShadowOuter(presContext, *aCtx, mFrame, borderRect, + rects[i], mOpacity); + } +} + +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::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + mVisibleRegion.And(*aVisibleRegion, GetPaintRect()); + return true; +} + +bool nsDisplayBoxShadowOuter::CanBuildWebRenderDisplayItems() { + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + if (shadows.IsEmpty()) { + return false; + } + + bool hasBorderRadius; + bool nativeTheme = + nsCSSRendering::HasBoxShadowNativeTheme(mFrame, hasBorderRadius); + + // We don't support native themed things yet like box shadows around + // input buttons. + if (nativeTheme) { + return false; + } + + return true; +} + +bool nsDisplayBoxShadowOuter::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanBuildWebRenderDisplayItems()) { + return false; + } + + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = mFrame->VisualBorderRectRelativeToSelf() + offset; + AutoTArray<nsRect, 10> rects; + bool snap; + nsRect bounds = GetBounds(aDisplayListBuilder, &snap); + ComputeDisjointRectangles(bounds, &rects); + + 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. + for (uint32_t i = 0; i < rects.Length(); ++i) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel); + auto shadows = mFrame->StyleEffects()->mBoxShadow.AsSpan(); + MOZ_ASSERT(!shadows.IsEmpty()); + + for (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, mOpacity); + + // 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 { + auto* geometry = + static_cast<const nsDisplayBoxShadowOuterGeometry*>(aGeometry); + bool snap; + if (!geometry->mBounds.IsEqualInterior(GetBounds(aBuilder, &snap)) || + !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) || + mOpacity != geometry->mOpacity) { + 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(); + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(mVisibleRegion, &rects); + + AUTO_PROFILER_LABEL("nsDisplayBoxShadowInner::Paint", GRAPHICS); + + DrawTarget* drawTarget = aCtx->GetDrawTarget(); + gfxContext* gfx = aCtx; + int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + gfx->Save(); + gfx->Clip(NSRectToSnappedRect(rects[i], appUnitsPerDevPixel, *drawTarget)); + nsCSSRendering::PaintBoxShadowInner(presContext, *aCtx, mFrame, borderRect); + gfx->Restore(); + } +} + +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. + if (nativeTheme) { + return false; + } + + return true; +} + +/* static */ +void nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, const StackingContextHelper& aSc, + nsRegion& aVisibleRegion, nsIFrame* aFrame, const nsRect& aBorderRect) { + if (!nsCSSRendering::ShouldPaintBoxShadowInner(aFrame)) { + return; + } + + int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel(); + + AutoTArray<nsRect, 10> rects; + ComputeDisjointRectangles(aVisibleRegion, &rects); + + auto shadows = aFrame->StyleEffects()->mBoxShadow.AsSpan(); + + for (uint32_t i = 0; i < rects.Length(); ++i) { + LayoutDeviceRect clipRect = + LayoutDeviceRect::FromAppUnits(rects[i], appUnitsPerDevPixel); + + for (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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (!CanCreateWebRenderCommands(aDisplayListBuilder, mFrame, + ToReferenceFrame())) { + return false; + } + + bool snap; + nsRegion visible = GetBounds(aDisplayListBuilder, &snap); + nsPoint offset = ToReferenceFrame(); + nsRect borderRect = nsRect(offset, mFrame->GetSize()); + nsDisplayBoxShadowInner::CreateInsetBoxShadowWebRenderCommands( + aBuilder, aSc, visible, mFrame, borderRect); + + return true; +} + +bool nsDisplayBoxShadowInner::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + if (!nsPaintedDisplayItem::ComputeVisibility(aBuilder, aVisibleRegion)) { + return false; + } + + mVisibleRegion.And(*aVisibleRegion, GetPaintRect()); + 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) + : nsDisplayHitTestInfoBase(aBuilder, aFrame, aActiveScrolledRoot), + mFrameActiveScrolledRoot(aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false), + mClearingClipChain(aClearClipChain) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aList); + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // If we're a transformed frame, then we need to find out if we're inside + // the nsDisplayTransform or outside of it. Frames inside the transform + // need mReferenceFrame == mFrame, outside needs the next ancestor + // reference frame. + // If we're inside the transform, then the nsDisplayItem constructor + // will have done the right thing. + // If we're outside the transform, then we should have only one child + // (since nsDisplayTransform wraps all actual content), and that child + // will have the correct reference frame set (since nsDisplayTransform + // handles this explictly). + nsDisplayItem* i = mListPtr->GetBottom(); + if (i && + (!i->GetAbove() || i->GetType() == DisplayItemType::TYPE_TRANSFORM) && + i->Frame() == mFrame) { + mReferenceFrame = i->ReferenceFrame(); + mToReferenceFrame = i->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::nsDisplayWrapList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayItem* aItem) + : nsDisplayHitTestInfoBase(aBuilder, aFrame, + aBuilder->CurrentActiveScrolledRoot()), + mOverrideZIndex(0), + mHasZIndexOverride(false) { + MOZ_COUNT_CTOR(nsDisplayWrapList); + + mBaseBuildingRect = GetBuildingRect(); + + mListPtr = &mList; + mListPtr->AppendToTop(aItem); + nsDisplayWrapList::UpdateBounds(aBuilder); + + if (!aFrame || !aFrame->IsTransformed()) { + return; + } + + // See the previous nsDisplayWrapList constructor + if (aItem->Frame() == aFrame) { + mReferenceFrame = aItem->ReferenceFrame(); + mToReferenceFrame = aItem->ToReferenceFrame(); + } + + nsRect visible = aBuilder->GetVisibleRect() + + aBuilder->GetCurrentFrameOffsetToReferenceFrame(); + + SetBuildingRect(visible); +} + +nsDisplayWrapList::~nsDisplayWrapList() { MOZ_COUNT_DTOR(nsDisplayWrapList); } + +void nsDisplayWrapList::MergeDisplayListFromItem( + nsDisplayListBuilder* aBuilder, const nsDisplayWrapList* aItem) { + const nsDisplayWrapList* wrappedItem = aItem->AsDisplayWrapList(); + MOZ_ASSERT(wrappedItem); + + // Create a new nsDisplayWrapList using a copy-constructor. This is done + // to preserve the information about bounds. + nsDisplayWrapList* wrapper = + MakeClone<nsDisplayWrapList>(aBuilder, wrappedItem); + 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; + + mListPtr->AppendToBottom(wrapper); +} + +void nsDisplayWrapList::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + mListPtr->HitTest(aBuilder, aRect, aState, aOutFrames); +} + +nsRect nsDisplayWrapList::GetBounds(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + return mBounds; +} + +bool nsDisplayWrapList::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return ::ComputeClippedVisibilityForSubList(aBuilder, aVisibleRegion, + GetChildren(), GetPaintRect()); +} + +nsRegion nsDisplayWrapList::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + *aSnap = false; + bool snap; + return ::GetOpaqueRegion(aBuilder, GetChildren(), GetBounds(aBuilder, &snap)); +} + +Maybe<nscolor> nsDisplayWrapList::IsUniform( + nsDisplayListBuilder* aBuilder) const { + // We could try to do something but let's conservatively just return Nothing. + return Nothing(); +} + +void nsDisplayWrapList::Paint(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx) { + NS_ERROR("nsDisplayWrapList should have been flattened away for painting"); +} + +/** + * Returns true if all descendant display items can be placed in the same + * PaintedLayer --- GetLayerState returns LayerState::LAYER_INACTIVE or + * LayerState::LAYER_NONE, and they all have the expected animated geometry + * root. + */ +static LayerState RequiredLayerStateForChildren( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters, const nsDisplayList& aList, + const AnimatedGeometryRoot* aExpectedAGRForChildren, + const ActiveScrolledRoot* aExpectedASRForChildren) { + LayerState result = LayerState::LAYER_INACTIVE; + for (nsDisplayItem* i : aList) { + if (result == LayerState::LAYER_INACTIVE && + (i->GetAnimatedGeometryRoot() != aExpectedAGRForChildren || + i->GetActiveScrolledRoot() != aExpectedASRForChildren)) { + result = LayerState::LAYER_ACTIVE; + } + + LayerState state = i->GetLayerState(aBuilder, aManager, aParameters); + if (state == LayerState::LAYER_ACTIVE && + (i->GetType() == DisplayItemType::TYPE_BLEND_MODE || + i->GetType() == DisplayItemType::TYPE_TABLE_BLEND_MODE)) { + // nsDisplayBlendMode always returns LayerState::LAYER_ACTIVE to ensure + // that the blending operation happens in the intermediate surface of its + // parent display item (usually an nsDisplayBlendContainer). But this does + // not mean that it needs all its ancestor display items to become active. + // So we ignore its layer state and look at its children instead. + state = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, + *i->GetSameCoordinateSystemChildren(), i->GetAnimatedGeometryRoot(), + i->GetActiveScrolledRoot()); + } + if ((state == LayerState::LAYER_ACTIVE || + state == LayerState::LAYER_ACTIVE_FORCE) && + state > result) { + result = state; + } + if (state == LayerState::LAYER_ACTIVE_EMPTY && state > result) { + result = LayerState::LAYER_ACTIVE_FORCE; + } + if (state == LayerState::LAYER_NONE) { + nsDisplayList* list = i->GetSameCoordinateSystemChildren(); + if (list) { + LayerState childState = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, *list, aExpectedAGRForChildren, + aExpectedASRForChildren); + if (childState > result) { + result = childState; + } + } + } + } + return result; +} + +nsRect nsDisplayWrapList::GetComponentAlphaBounds( + nsDisplayListBuilder* aBuilder) const { + return mListPtr->GetComponentAlphaBounds(aBuilder); +} + +bool nsDisplayWrapList::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + GetChildren(), this, aDisplayListBuilder, aSc, aBuilder, aResources); + return true; +} + +static nsresult WrapDisplayList(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList, + nsDisplayWrapper* 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, + nsDisplayWrapper* aWrapper) { + nsDisplayList newList; + nsDisplayItem* item; + while ((item = aList->RemoveBottom())) { + item = aWrapper->WrapItem(aBuilder, item); + if (!item) { + return NS_ERROR_OUT_OF_MEMORY; + } + newList.AppendToTop(item); + } + // aList was emptied + aList->AppendToTop(&newList); + return NS_OK; +} + +nsresult nsDisplayWrapper::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 nsDisplayWrapper::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 aForEventsAndPluginsOnly, bool aNeedsActiveLayer) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mOpacity(aFrame->StyleEffects()->mOpacity), + mForEventsAndPluginsOnly(aForEventsAndPluginsOnly), + mNeedsActiveLayer(aNeedsActiveLayer), + mChildOpacityState(ChildOpacityState::Unknown) { + MOZ_COUNT_CTOR(nsDisplayOpacity); + mState.mOpacity = mOpacity; +} + +void nsDisplayOpacity::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, + nsDisplayItem::HitTestState* aState, + nsTArray<nsIFrame*>* aOutFrames) { + AutoRestore<float> 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(); +} + +// nsDisplayOpacity uses layers for rendering +already_AddRefed<Layer> nsDisplayOpacity::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + + ContainerLayerParameters params = aContainerParameters; + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, params, nullptr, + FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); + if (!container) { + return nullptr; + } + + container->SetOpacity(mOpacity); + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + container, aBuilder, this, mFrame, GetType()); + return container.forget(); +} + +/** + * This doesn't take into account layer scaling --- the layer may be + * rendered at a higher (or lower) resolution, affecting the retained layer + * size --- but this should be good enough. + */ +static bool IsItemTooSmallForActiveLayer(nsIFrame* aFrame) { + nsIntRect visibleDevPixels = + aFrame->InkOverflowRectRelativeToSelf().ToOutsidePixels( + aFrame->PresContext()->AppUnitsPerDevPixel()); + return visibleDevPixels.Size() < + nsIntSize(StaticPrefs::layout_min_active_layer_size(), + StaticPrefs::layout_min_active_layer_size()); +} + +/* static */ +bool nsDisplayOpacity::NeedsActiveLayer(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + bool aEnforceMinimumSize) { + if (EffectCompositor::HasAnimationsForCompositor( + aFrame, DisplayItemType::TYPE_OPACITY) || + (ActiveLayerTracker::IsStyleAnimated( + aBuilder, aFrame, nsCSSPropertyIDSet::OpacityProperties()) && + !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(aFrame)))) { + return true; + } + return false; +} + +void nsDisplayOpacity::ApplyOpacity(nsDisplayListBuilder* aBuilder, + float aOpacity, + const DisplayItemClipChain* aClip) { + NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed"); + mOpacity = mOpacity * aOpacity; + IntersectClip(aBuilder, aClip, false); +} + +bool nsDisplayOpacity::CanApplyOpacity() 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(nsDisplayList* aList, + nsTArray<nsPaintedDisplayItem*>& aArray) { + if (aList->Count() > 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(i->GetChildren(), aArray)) { + return false; + } + + continue; + } + + if (aArray.Length() == kOpacityMaxChildCount) { + return false; + } + + auto* item = i->AsPaintedDisplayItem(); + if (!item || !item->CanApplyOpacity()) { + return false; + } + + aArray.AppendElement(item); + } + + return true; +} + +bool nsDisplayOpacity::ApplyToChildren(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<nsPaintedDisplayItem*, kOpacityMaxChildCount> items; + if (!CollectItemsWithOpacity(&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; + } + } + } + + for (uint32_t i = 0; i < childCount; i++) { + children[i].item->ApplyOpacity(aBuilder, mOpacity, mClipChain); + } + + 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::ApplyToFilterOrMask(const bool aUsingLayers) { + if (mList.Count() != 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 || + type == DisplayItemType::TYPE_FILTER) { + auto* filterOrMaskItem = static_cast<nsDisplayEffectsBase*>(item); + filterOrMaskItem->SelectOpacityOptimization(aUsingLayers); + return true; + } + + return false; +} + +bool nsDisplayOpacity::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + 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; + } + + const bool usingLayers = !aBuilder->IsPaintingForWebRender(); + + if (ApplyToFilterOrMask(usingLayers)) { + MOZ_ASSERT(SVGIntegrationUtils::UsingEffectsForFrame(mFrame)); + mChildOpacityState = ChildOpacityState::Applied; + return true; + } + + // Return true if we successfully applied opacity to child items, or if + // WebRender is not in use. In the latter case, the opacity gets flattened and + // applied during layer building. + return ApplyToChildren(aBuilder) || usingLayers; +} + +nsDisplayItem::LayerState nsDisplayOpacity::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + // If we only created this item so that we'd get correct nsDisplayEventRegions + // for child frames, then force us to inactive to avoid unnecessary + // layerization changes for content that won't ever be painted. + if (mForEventsAndPluginsOnly) { + MOZ_ASSERT(mOpacity == 0); + return LayerState::LAYER_INACTIVE; + } + + if (mNeedsActiveLayer) { + // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for + // async animations. + return LayerState::LAYER_ACTIVE_FORCE; + } + + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayOpacity::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Our children are translucent so we should not allow them to subtract + // area from aVisibleRegion. We do need to find out what is visible under + // our children in the temporary compositing buffer, because if our children + // paint our entire bounds opaquely then we don't need an alpha channel in + // the temporary compositing buffer. + nsRect bounds = GetClippedBounds(aBuilder); + nsRegion visibleUnderChildren; + visibleUnderChildren.And(*aVisibleRegion, bounds); + return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren); +} + +void nsDisplayOpacity::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + auto* geometry = static_cast<const nsDisplayOpacityGeometry*>(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( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); + float* opacityForSC = &mOpacity; + + 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()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + aManager->CommandBuilder().CreateWebRenderCommandsFromDisplayList( + &mList, this, aDisplayListBuilder, sc, aBuilder, aResources); + return true; +} + +nsDisplayBlendMode::nsDisplayBlendMode( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + mozilla::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(); +} + +LayerState nsDisplayBlendMode::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE; +} + +bool nsDisplayBlendMode::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::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); +} + +// nsDisplayBlendMode uses layers for rendering +already_AddRefed<Layer> nsDisplayBlendMode::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + if (!container) { + return nullptr; + } + + container->SetMixBlendMode(nsCSSRendering::GetGFXBlendMode(mBlendMode)); + + return container.forget(); +} + +mozilla::gfx::CompositionOp nsDisplayBlendMode::BlendMode() { + return nsCSSRendering::GetGFXBlendMode(mBlendMode); +} + +bool nsDisplayBlendMode::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Our children are need their backdrop so we should not allow them to + // subtract area from aVisibleRegion. We do need to find out what is visible + // under our children in the temporary compositing buffer, because if our + // children paint our entire bounds opaquely then we don't need an alpha + // channel in the temporary compositing buffer. + nsRect bounds = GetClippedBounds(aBuilder); + nsRegion visibleUnderChildren; + visibleUnderChildren.And(*aVisibleRegion, bounds); + return nsDisplayWrapList::ComputeVisibility(aBuilder, &visibleUnderChildren); +} + +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<const nsDisplayBlendMode*>(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<nsDisplayBlendContainer>(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<uint16_t>(type); + + return MakeDisplayItemWithIndex<nsDisplayTableBlendContainer>( + aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true, + aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayBlendContainer>( + 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); +} + +// nsDisplayBlendContainer uses layers for rendering +already_AddRefed<Layer> nsDisplayBlendContainer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // turn off anti-aliasing in the parent stacking context because it changes + // how the group is initialized. + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + if (!container) { + return nullptr; + } + + container->SetForceIsolatedGroup(true); + return container.forget(); +} + +LayerState nsDisplayBlendContainer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayBlendContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::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); + + // For scroll thumb layers, override the AGR to be the thumb's AGR rather + // than the AGR for mFrame (which is the slider frame). + if (IsScrollThumbLayer()) { + if (nsIFrame* thumbFrame = nsIFrame::GetChildXULBox(mFrame)) { + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(thumbFrame); + } + } +} + +LayerState nsDisplayOwnLayer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (mForceActive) { + return mozilla::LayerState::LAYER_ACTIVE_FORCE; + } + + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayOwnLayer::IsScrollThumbLayer() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Thumb; +} + +bool nsDisplayOwnLayer::IsScrollbarContainer() const { + return mScrollbarData.mScrollbarLayerType == + layers::ScrollbarLayerType::Container; +} + +bool nsDisplayOwnLayer::IsRootScrollbarContainer() const { + if (!IsScrollbarContainer()) { + return false; + } + + 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; +} + +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(); +} + +// nsDisplayOpacity uses layers for rendering +already_AddRefed<Layer> nsDisplayOwnLayer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<ContainerLayer> layer = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, + nullptr, FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); + + if (IsScrollThumbLayer() || IsScrollbarContainer()) { + layer->SetScrollbarData(mScrollbarData); + } + + if (mFlags & nsDisplayOwnLayerFlags::GenerateSubdocInvalidations) { + mFrame->PresContext()->SetNotifySubDocInvalidationData(layer); + } + return layer.forget(); +} + +bool nsDisplayOwnLayer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + Maybe<wr::WrAnimationProperty> prop; + bool needsProp = aManager->LayerManager()->AsyncPanZoomEnabled() && + (IsScrollThumbLayer() || IsZoomingLayer() || + (IsFixedPositionLayer() && HasDynamicToolbar()) || + (IsStickyPositionLayer() && HasDynamicToolbar()) || + (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<WebRenderAPZAnimationData> animationData = + aManager->CommandBuilder() + .CreateOrRecycleWebRenderUserData<WebRenderAPZAnimationData>(this); + mWrAnimationId = animationData->GetAnimationId(); + + prop.emplace(); + prop->id = mWrAnimationId; + prop->effect_type = wr::WrAnimationType::Transform; + } + + wr::StackingContextParams params; + params.animation = prop.ptrOr(nullptr); + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + if (IsScrollbarContainer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_CONTAINER; + } + if (IsScrollThumbLayer()) { + params.prim_flags |= wr::PrimitiveFlags::IS_SCROLLBAR_THUMB; + } + if (IsZoomingLayer()) { + params.reference_frame_kind = wr::WrReferenceFrameKind::Zoom; + } + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +bool nsDisplayOwnLayer::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + bool isRelevantToApz = + (IsScrollThumbLayer() || IsScrollbarContainer() || IsZoomingLayer() || + (IsFixedPositionLayer() && HasDynamicToolbar()) || + (IsStickyPositionLayer() && HasDynamicToolbar())); + + if (!isRelevantToApz) { + return false; + } + + if (!aLayerData) { + return true; + } + + if (IsZoomingLayer()) { + aLayerData->SetZoomAnimationId(mWrAnimationId); + return true; + } + + if (IsFixedPositionLayer() && HasDynamicToolbar()) { + aLayerData->SetFixedPositionAnimationId(mWrAnimationId); + return true; + } + + if (IsStickyPositionLayer() && HasDynamicToolbar()) { + 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()); + // We use a resolution of 1.0 because this is a WebRender codepath which + // always uses containerless scrolling, and so resolution doesn't apply to + // scrollbars. + LayerIntRect layerBounds = + RoundedOut(bounds * LayoutDeviceToLayerScale(1.0f)); + aLayerData->SetVisibleRegion(LayerIntRegion(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); + + // The SubDocument display item is conceptually outside the viewport frame, + // so in cases where the viewport frame is an AGR, the SubDocument's AGR + // should be not the viewport frame itself, but its parent AGR. + if (*mAnimatedGeometryRoot == mFrame && mAnimatedGeometryRoot->mParentAGR) { + mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR; + } + + 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); +} + +void nsDisplaySubDocument::Disown() { + if (mFrame) { + mFrame->RemoveDisplayItem(this); + RemoveFrame(mFrame); + } + if (mSubDocFrame) { + mSubDocFrame->RemoveDisplayItem(this); + RemoveFrame(mSubDocFrame); + } +} + +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); +} + +bool nsDisplaySubDocument::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + + if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) || + !usingDisplayPort) { + return nsDisplayWrapList::ComputeVisibility(aBuilder, aVisibleRegion); + } + + nsRect displayport; + nsIFrame* rootScrollFrame = mFrame->PresShell()->GetRootScrollFrame(); + MOZ_ASSERT(rootScrollFrame); + Unused << DisplayPortUtils::GetDisplayPort( + rootScrollFrame->GetContent(), &displayport, + DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame)); + + nsRegion childVisibleRegion; + // The visible region for the children may be much bigger than the hole we + // are viewing the children from, so that the compositor process has enough + // content to asynchronously pan while content is being refreshed. + childVisibleRegion = + displayport + mFrame->GetOffsetToCrossDoc(ReferenceFrame()); + + nsRect boundedRect = childVisibleRegion.GetBounds().Intersect( + mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot)); + bool visible = mList.ComputeVisibilityForSublist( + aBuilder, &childVisibleRegion, boundedRect); + + // If APZ is enabled then don't allow this computation to influence + // aVisibleRegion, on the assumption that the layer can be asynchronously + // scrolled so we'll definitely need all the content under it. + if (!nsLayoutUtils::UsesAsyncScrolling(mFrame)) { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + nsRegion removed; + removed.Sub(bounds, childVisibleRegion); + + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + } + + return visible; +} + +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) { + nsDisplayList temp; + temp.AppendToTop(aImage); + + if (aSecondaryFrame) { + auto tableType = GetTableTypeFromFrame(aFrame); + const uint16_t index = CalculateTablePerFrameKey(aIndex + 1, tableType); + return MakeDisplayItemWithIndex<nsDisplayTableFixedPosition>( + aBuilder, aSecondaryFrame, index, &temp, aFrame); + } + + return MakeDisplayItemWithIndex<nsDisplayFixedPosition>(aBuilder, aFrame, + aIndex + 1, &temp); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + const ActiveScrolledRoot* aContainerASR) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, aActiveScrolledRoot), + mContainerASR(aContainerASR), + mIsFixedBackground(false) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + Init(aBuilder); +} + +nsDisplayFixedPosition::nsDisplayFixedPosition(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayOwnLayer(aBuilder, aFrame, aList, + aBuilder->CurrentActiveScrolledRoot()), + mContainerASR(nullptr), // XXX maybe this should be something? + mIsFixedBackground(true) { + MOZ_COUNT_CTOR(nsDisplayFixedPosition); + Init(aBuilder); +} + +void nsDisplayFixedPosition::Init(nsDisplayListBuilder* aBuilder) { + mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot; + if (ShouldFixToViewport(aBuilder)) { + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor(this); + } +} + +already_AddRefed<Layer> nsDisplayFixedPosition::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters); + + layer->SetIsFixedPosition(true); + + nsPresContext* presContext = mFrame->PresContext(); + nsIFrame* fixedFrame = + mIsFixedBackground ? presContext->PresShell()->GetRootFrame() : mFrame; + + const nsIFrame* viewportFrame = fixedFrame->GetParent(); + // anchorRect will be in the container's coordinate system (aLayer's parent + // layer). This is the same as the display items' reference frame. + nsRect anchorRect; + if (viewportFrame) { + anchorRect.SizeTo(viewportFrame->GetSize()); + // Fixed position frames are reflowed into the scroll-port size if one has + // been set. + if (const ViewportFrame* viewport = do_QueryFrame(viewportFrame)) { + anchorRect.SizeTo( + viewport->AdjustViewportSizeForFixedPosition(anchorRect)); + } + } else { + // A display item directly attached to the viewport. + // For background-attachment:fixed items, the anchor point is always the + // top-left of the viewport currently. + viewportFrame = fixedFrame; + } + // The anchorRect top-left is always the viewport top-left. + anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(ReferenceFrame())); + + nsLayoutUtils::SetFixedPositionLayerData(layer, viewportFrame, anchorRect, + fixedFrame, presContext, + aContainerParameters); + + return layer.forget(); +} + +ViewID nsDisplayFixedPosition::GetScrollTargetId() { + if (mContainerASR && !nsLayoutUtils::IsReallyFixedPos(mFrame)) { + return mContainerASR->GetViewId(); + } + return nsLayoutUtils::ScrollIdForRootScrollFrame(mFrame->PresContext()); +} + +bool nsDisplayFixedPosition::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::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. + mozilla::wr::DisplayListBuilder::FixedPosScrollTargetTracker tracker( + aBuilder, GetActiveScrolledRoot(), GetScrollTargetId(), sides); + return nsDisplayOwnLayer::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +bool nsDisplayFixedPosition::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + if (!mIsFixedBackground) { + aLayerData->SetFixedPositionSides( + nsLayoutUtils::GetSideBitsForFixedPositionContent(mFrame)); + } + aLayerData->SetFixedPositionScrollContainerId(GetScrollTargetId()); + } + nsDisplayOwnLayer::UpdateScrollData(aData, aLayerData); + return true; +} + +void nsDisplayFixedPosition::WriteDebugInfo(std::stringstream& aStream) { + aStream << nsPrintfCString(" (containerASR %s) (scrolltarget %" PRIu64 ")", + ActiveScrolledRoot::ToString(mContainerASR).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) + : nsDisplayFixedPosition(aBuilder, aFrame, aList), + 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) { + MOZ_COUNT_CTOR(nsDisplayStickyPosition); +} + +void nsDisplayStickyPosition::SetClipChain( + const DisplayItemClipChain* aClipChain, bool aStore) { + mClipChain = aClipChain; + mClip = nullptr; + + MOZ_ASSERT(!mClip, + "There should never be a clip on this item because no clip moves " + "with it."); + + if (aStore) { + mState.mClipChain = aClipChain; + mState.mClip = mClip; + } +} + +already_AddRefed<Layer> nsDisplayStickyPosition::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, aContainerParameters); + + StickyScrollContainer* stickyScrollContainer = + StickyScrollContainer::GetStickyScrollContainerForFrame(mFrame); + if (!stickyScrollContainer) { + return layer.forget(); + } + + nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); + nsPresContext* presContext = scrollFrame->PresContext(); + + // Sticky position frames whose scroll frame is the root scroll frame are + // reflowed into the scroll-port size if one has been set. + nsSize scrollFrameSize = scrollFrame->GetSize(); + if (scrollFrame == presContext->PresShell()->GetRootScrollFrame() && + presContext->PresShell()->IsVisualViewportSizeSet()) { + scrollFrameSize = presContext->PresShell()->GetVisualViewportSize(); + } + + nsLayoutUtils::SetFixedPositionLayerData( + layer, scrollFrame, + nsRect(scrollFrame->GetOffsetToCrossDoc(ReferenceFrame()), + scrollFrameSize), + mFrame, presContext, aContainerParameters); + + ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor( + stickyScrollContainer->ScrollFrame()->GetScrolledFrame()->GetContent()); + + float factor = presContext->AppUnitsPerDevPixel(); + LayerRectAbsolute stickyOuter; + LayerRectAbsolute stickyInner; + CalculateLayerScrollRanges( + stickyScrollContainer, factor, aContainerParameters.mXScale, + aContainerParameters.mYScale, stickyOuter, stickyInner); + layer->SetStickyPositionData(scrollId, stickyOuter, stickyInner); + + return layer.forget(); +} + +// 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. + if (!stickyScrollContainer->ScrollFrame() + ->IsMaybeAsynchronouslyScrolled()) { + stickyScrollContainer = nullptr; + } + } + return stickyScrollContainer; +} + +bool nsDisplayStickyPosition::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + StickyScrollContainer* stickyScrollContainer = GetStickyScrollContainer(); + + Maybe<wr::SpaceAndClipChainHelper> saccHelper; + + if (stickyScrollContainer) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + bool snap; + nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap); + + Maybe<float> topMargin; + Maybe<float> rightMargin; + Maybe<float> bottomMargin; + Maybe<float> 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(ReferenceFrame()); + + // 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); + + 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( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::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); + + 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; +} + +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 +} + +already_AddRefed<Layer> nsDisplayScrollInfoLayer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // In general for APZ with event-regions we no longer have a need for + // scrollinfo layers. However, in some cases, there might be content that + // cannot be layerized, and so needs to scroll synchronously. To handle those + // cases, we still want to generate scrollinfo layers. + + return aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr, + FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR); +} + +LayerState nsDisplayScrollInfoLayer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE_EMPTY; +} + +UniquePtr<ScrollMetadata> nsDisplayScrollInfoLayer::ComputeScrollMetadata( + nsDisplayListBuilder* aBuilder, LayerManager* aLayerManager, + const ContainerLayerParameters& aContainerParameters) { + ScrollMetadata metadata = nsLayoutUtils::ComputeScrollMetadata( + mScrolledFrame, mScrollFrame, mScrollFrame->GetContent(), + ReferenceFrame(), aLayerManager, mScrollParentId, mScrollFrame->GetSize(), + Nothing(), false, Some(aContainerParameters)); + metadata.GetMetrics().SetIsScrollInfoLayer(true); + nsIScrollableFrame* scrollableFrame = mScrollFrame->GetScrollTargetFrame(); + if (scrollableFrame) { + aBuilder->AddScrollFrameToNotify(scrollableFrame); + } + + return UniquePtr<ScrollMetadata>(new ScrollMetadata(metadata)); +} + +bool nsDisplayScrollInfoLayer::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::WebRenderLayerScrollData* aLayerData) { + if (aLayerData) { + UniquePtr<ScrollMetadata> metadata = ComputeScrollMetadata( + aData->GetBuilder(), aData->GetManager(), ContainerLayerParameters()); + MOZ_ASSERT(aData); + MOZ_ASSERT(metadata); + aLayerData->AppendScrollMetadata(*aData, *metadata); + } + return true; +} + +bool nsDisplayScrollInfoLayer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::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<nsIFrame*>* 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); +} + +bool nsDisplayZoom::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // Convert the passed in visible region to our appunits. + nsRegion visibleRegion; + // mVisibleRect has been clipped to GetClippedBounds + visibleRegion.And(*aVisibleRegion, GetPaintRect()); + visibleRegion = visibleRegion.ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + nsRegion originalVisibleRegion = visibleRegion; + + nsRect transformedVisibleRect = + GetPaintRect().ScaleToOtherAppUnitsRoundOut(mParentAPD, mAPD); + bool retval; + // If we are to generate a scrollable layer we call + // nsDisplaySubDocument::ComputeVisibility to make the necessary adjustments + // for ComputeVisibility, it does all it's calculations in the child APD. + bool usingDisplayPort = UseDisplayPortForViewport(aBuilder, mFrame); + if (!(mFlags & nsDisplayOwnLayerFlags::GenerateScrollableLayer) || + !usingDisplayPort) { + retval = mList.ComputeVisibilityForSublist(aBuilder, &visibleRegion, + transformedVisibleRect); + } else { + retval = nsDisplaySubDocument::ComputeVisibility(aBuilder, &visibleRegion); + } + + nsRegion removed; + // removed = originalVisibleRegion - visibleRegion + removed.Sub(originalVisibleRegion, visibleRegion); + // Convert removed region to parent appunits. + removed = removed.ScaleToOtherAppUnitsRoundIn(mAPD, mParentAPD); + // aVisibleRegion = aVisibleRegion - removed (modulo any simplifications + // SubtractFromVisibleRegion does) + aBuilder->SubtractFromVisibleRegion(aVisibleRegion, removed); + + return retval; +} + +nsDisplayAsyncZoom::nsDisplayAsyncZoom( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, + mozilla::layers::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<nsIFrame*>* 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); +} + +already_AddRefed<Layer> nsDisplayAsyncZoom::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + PresShell* presShell = mFrame->PresShell(); + ContainerLayerParameters containerParameters( + presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(), + aContainerParameters); + + RefPtr<Layer> layer = + nsDisplayOwnLayer::BuildLayer(aBuilder, aManager, containerParameters); + + layer->SetIsAsyncZoomContainer(Some(mViewID)); + + layer->SetPostScale(1.0f / presShell->GetResolution(), + 1.0f / presShell->GetResolution()); + layer->AsContainerLayer()->SetScaleToResolution(presShell->GetResolution()); + + return layer.forget(); +} + +bool nsDisplayAsyncZoom::UpdateScrollData( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::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) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransform(Some(Matrix4x4())), + mTransformGetter(nullptr), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(true) { + 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) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransformGetter(nullptr), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(aPrerenderDecision), + mIsTransformSeparator(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, + ComputeTransformFunction aTransformGetter) + : nsDisplayHitTestInfoBase(aBuilder, aFrame), + mTransformGetter(aTransformGetter), + mAnimatedGeometryRootForChildren(mAnimatedGeometryRoot), + mAnimatedGeometryRootForScrollMetadata(mAnimatedGeometryRoot), + mChildrenBuildingRect(aChildrenBuildingRect), + mPrerenderDecision(PrerenderDecision::No), + mIsTransformSeparator(false) { + MOZ_COUNT_CTOR(nsDisplayTransform); + MOZ_ASSERT(aFrame, "Must have a frame!"); + Init(aBuilder, aList); +} + +void nsDisplayTransform::SetReferenceFrameToAncestor( + nsDisplayListBuilder* aBuilder) { + if (mFrame == aBuilder->RootReferenceFrame()) { + return; + } + nsIFrame* outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame); + mReferenceFrame = aBuilder->FindReferenceFrameFor(outerFrame); + mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame); + if (DisplayPortUtils::IsFixedPosFrameInDisplayPort(mFrame)) { + // This is an odd special case. If we are both IsFixedPosFrameInDisplayPort + // and transformed that we are our own AGR parent. + // We want our frame to be our AGR because FrameLayerBuilder uses our AGR to + // determine if we are inside a fixed pos subtree. If we use the outer AGR + // from outside the fixed pos subtree FLB can't tell that we are fixed pos. + mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren; + } else if (mFrame->StyleDisplay()->mPosition == + StylePositionProperty::Sticky && + IsStickyFrameActive(aBuilder, mFrame, nullptr)) { + // Similar to the IsFixedPosFrameInDisplayPort case we are our own AGR. + // We are inside the sticky position, so our AGR is the sticky positioned + // frame, which is our AGR, not the parent AGR. + mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren; + } else if (mAnimatedGeometryRoot->mParentAGR) { + mAnimatedGeometryRootForScrollMetadata = mAnimatedGeometryRoot->mParentAGR; + if (!MayBeAnimated(aBuilder)) { + // If we're an animated transform then we want the same AGR as our + // children so that FrameLayerBuilder knows that this layer moves with the + // transform and won't compute occlusions. If we're not animated then use + // our parent AGR so that inactive transform layers can go in the same + // PaintedLayer as surrounding content. + mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR; + } + } + + SetBuildingRect(aBuilder->GetVisibleRect() + mToReferenceFrame); +} + +void nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder, + nsDisplayList* aChildren) { + mShouldFlatten = false; + mChildren.AppendToTop(aChildren); + UpdateBounds(aBuilder); +} + +bool nsDisplayTransform::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + if (gfxVars::UseWebRender() || + !StaticPrefs::layout_display_list_flatten_transform()) { + return false; + } + + MOZ_ASSERT(!mShouldFlatten); + mShouldFlatten = GetTransform().Is2D(); + return mShouldFlatten; +} + +/* 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); + + if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { + // SVG frames (unlike other 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. + origin.x += CSSPixel::FromAppUnits(aRefBox.X()); + origin.y += CSSPixel::FromAppUnits(aRefBox.Y()); + } + + float scale = mozilla::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; + } + + /* Find our containing block, which is the element that provides the + * value for perspective we need to use + */ + + // TODO: Is it possible that the cbFrame's bounds haven't been set correctly + // yet + // (similar to the aBoundsOverride case for GetResultingTransformMatrix)? + nsIFrame* cbFrame = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); + if (!cbFrame) { + return false; + } + + /* Grab the values for perspective and perspective-origin (if present) */ + const nsStyleDisplay* cbDisplay = cbFrame->StyleDisplay(); + if (cbDisplay->mChildPerspective.IsNone()) { + return false; + } + + MOZ_ASSERT(cbDisplay->mChildPerspective.IsLength()); + // TODO(emilio): Seems quite silly to go through app units just to convert to + // float pixels below. + nscoord perspective = cbDisplay->mChildPerspective.length._0.ToAppUnits(); + if (perspective < std::numeric_limits<Float>::epsilon()) { + return true; + } + + TransformReferenceBox refBox(cbFrame); + + Point perspectiveOrigin = nsStyleTransformMatrix::Convert2DPosition( + cbDisplay->mPerspectiveOrigin.horizontal, + cbDisplay->mPerspectiveOrigin.vertical, refBox, aAppUnitsPerPixel); + + /* GetOffsetTo computes the offset required to move from 0,0 in cbFrame to 0,0 + * in aFrame. Although we actually want the inverse of this, it's faster to + * compute this way. + */ + nsPoint frameToCbOffset = -aFrame->GetOffsetTo(cbFrame); + Point frameToCbGfxOffset( + NSAppUnitsToFloatPixels(frameToCbOffset.x, aAppUnitsPerPixel), + NSAppUnitsToFloatPixels(frameToCbOffset.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 += frameToCbGfxOffset; + + aOutMatrix._34 = + -1.0 / NSAppUnitsToFloatPixels(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(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 + // disp->mSpecifiedTransform, since we still need any + // parentsChildrenOnlyTransform. + Matrix svgTransform, parentsChildrenOnlyTransform; + bool hasSVGTransforms = + frame && + 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, 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. + 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::GetCrossDocParentFrame(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::GetCrossDocParentFrame(aFrame); + container; + container = nsLayoutUtils::GetCrossDocParentFrame(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 (mTransformGetter) { + mTransform.emplace(mTransformGetter(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<bool> 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() || mTransformGetter || mIsTransformSeparator) { + if (!mTransformGetter && !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(!mTransformGetter); + + 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(); + } + + // XXX: should go back to fix mTransformGetter. + 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::GetCrossDocParentFrame(establisher)); + + nsPoint offset = establisher->GetOffsetToCrossDoc(establisherReference); + float scale = mFrame->PresContext()->AppUnitsPerDevPixel(); + uint32_t flags = + INCLUDE_PRESERVE3D_ANCESTORS | INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN; + mTransformPreserves3D = MakeUnique<Matrix4x4>( + GetResultingTransformMatrix(mFrame, offset, scale, flags)); + } + + return *mTransformPreserves3D; +} + +bool nsDisplayTransform::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::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(); + } + } + + // 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, + }; + + Maybe<nsDisplayTransform*> deferredTransformItem; + 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 = Some(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; + params.mTransformPtr = transformForSC; + params.prim_flags = !BackfaceIsHidden() + ? wr::PrimitiveFlags::IS_BACKFACE_VISIBLE + : wr::PrimitiveFlags{0}; + 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( + mozilla::layers::WebRenderScrollData* aData, + mozilla::layers::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(); +} + +already_AddRefed<Layer> nsDisplayTransform::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + // While generating a glyph mask, the transform vector of the root frame had + // been applied into the target context, so stop applying it again here. + const bool shouldSkipTransform = ShouldSkipTransform(aBuilder); + + /* For frames without transform, it would not be removed for + * backface hidden here. But, it would be removed by the init + * function of nsDisplayTransform. + */ + const Matrix4x4 newTransformMatrix = + shouldSkipTransform ? Matrix4x4() : GetTransformForRendering(); + + uint32_t flags = FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR; + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters, + &newTransformMatrix, flags); + + if (!container) { + return nullptr; + } + + // Add the preserve-3d flag for this layer, BuildContainerLayerFor clears all + // flags, so we never need to explicitly unset this flag. + if (mFrame->Extend3DContext() && !mIsTransformSeparator) { + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_EXTEND_3D_CONTEXT); + } else { + container->SetContentFlags(container->GetContentFlags() & + ~Layer::CONTENT_EXTEND_3D_CONTEXT); + } + + if (CanUseAsyncAnimations(aBuilder)) { + mFrame->SetProperty(nsIFrame::RefusedAsyncAnimationProperty(), false); + } + + // We don't send animations for transform separator display items. + if (!mIsTransformSeparator) { + nsDisplayListBuilder::AddAnimationsAndTransitionsToLayer( + container, aBuilder, this, mFrame, GetType()); + } + + if (CanUseAsyncAnimations(aBuilder) && MayBeAnimated(aBuilder)) { + // Only allow async updates to the transform if we're an animated layer, + // since that's what triggers us to set the correct AGR in the constructor + // and makes sure FrameLayerBuilder won't compute occlusions for this layer. + container->SetUserData(nsIFrame::LayerIsPrerenderedDataKey(), + /*the value is irrelevant*/ nullptr); + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_MAY_CHANGE_TRANSFORM); + } else { + container->RemoveUserData(nsIFrame::LayerIsPrerenderedDataKey()); + container->SetContentFlags(container->GetContentFlags() & + ~Layer::CONTENT_MAY_CHANGE_TRANSFORM); + } + return container.forget(); +} + +bool nsDisplayTransform::MayBeAnimated(nsDisplayListBuilder* aBuilder, + bool aEnforceMinimumSize) 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. + if (EffectCompositor::HasAnimationsForCompositor( + mFrame, DisplayItemType::TYPE_TRANSFORM) || + (ActiveLayerTracker::IsTransformAnimated(aBuilder, mFrame) && + !(aEnforceMinimumSize && IsItemTooSmallForActiveLayer(mFrame)))) { + return true; + } + return false; +} + +nsDisplayItem::LayerState nsDisplayTransform::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + // If the transform is 3d, the layer takes part in preserve-3d + // sorting, or the layer is a separator then we *always* want this + // to be an active layer. + // Checking HasPerspective() is needed to handle perspective value 0 when + // the transform is 2D. + if (!GetTransform().Is2D() || Combines3DTransformWithAncestors() || + mIsTransformSeparator || mFrame->HasPerspective()) { + return LayerState::LAYER_ACTIVE_FORCE; + } + + if (MayBeAnimated(aBuilder)) { + // Returns LayerState::LAYER_ACTIVE_FORCE to avoid flatterning the layer for + // async animations. + return LayerState::LAYER_ACTIVE_FORCE; + } + + // Expect the child display items to have this frame as their animated + // geometry root (since it will be their reference frame). If they have a + // different animated geometry root, we'll make this an active layer so the + // animation can be accelerated. + return RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, *GetChildren(), + mAnimatedGeometryRootForChildren, GetActiveScrolledRoot()); +} + +bool nsDisplayTransform::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + // nsDisplayTransform::GetBounds() returns an empty rect in nested 3d context. + // Calling mStoredList.RecomputeVisibility below for such transform causes the + // child display items to end up with empty visible rect. + // We avoid this by bailing out always if we are dealing with a 3d context. + if (mFrame->Extend3DContext() || Combines3DTransformWithAncestors()) { + return true; + } + + /* As we do this, we need to be sure to + * untransform the visible rect, since we want everything that's painting to + * think that it's painting in its original rectangular coordinate space. + * If we can't untransform, take the entire overflow rect */ + nsRect untransformedVisibleRect; + if (!UntransformPaintRect(aBuilder, &untransformedVisibleRect)) { + untransformedVisibleRect = mFrame->InkOverflowRectRelativeToSelf(); + } + + bool snap; + const nsRect bounds = GetUntransformedBounds(aBuilder, &snap); + nsRegion visibleRegion; + visibleRegion.And(bounds, untransformedVisibleRect); + GetChildren()->ComputeVisibilityForSublist(aBuilder, &visibleRegion, + visibleRegion.GetBounds()); + + return true; +} + +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 <time.h> +#endif + +/* HitTest does some fun stuff with matrix transforms to obtain the answer. */ +void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder, + const nsRect& aRect, HitTestState* aState, + nsTArray<nsIFrame*>* 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; + } + + /* 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; + if (aRect.width == 1 && aRect.height == 1) { + // Magic width/height indicating we're hit testing a point, not a rect + 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)); + + bool snap; + nsRect childBounds = GetUntransformedBounds(aBuilder, &snap); + Rect childGfxBounds(NSAppUnitsToFloatPixels(childBounds.x, factor), + NSAppUnitsToFloatPixels(childBounds.y, factor), + NSAppUnitsToFloatPixels(childBounds.width, factor), + NSAppUnitsToFloatPixels(childBounds.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<void*>(mFrame)); + printf(" Untransformed point: (%f, %f)\n", resultingRect.X(), + resultingRect.Y()); + uint32_t originalFrameCount = aOutFrames.Length(); +#endif + + GetChildren()->HitTest(aBuilder, resultingRect, aState, aOutFrames); + +#ifdef DEBUG_HIT + if (originalFrameCount != aOutFrames.Length()) + printf(" Hit! Time: %f, first frame: %p\n", static_cast<double>(clock()), + dynamic_cast<void*>(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 = ::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(); + + uint32_t flags = + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS; + FrameTransformProperties props(aFrame, aRefBox, factor); + return nsLayoutUtils::MatrixTransformRect( + aUntransformedBounds, + GetResultingTransformMatrixInternal(props, aRefBox, nsPoint(0, 0), factor, + flags), + 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(); + + uint32_t flags = + INCLUDE_PERSPECTIVE | OFFSET_BY_ORIGIN | INCLUDE_PRESERVE3D_ANCESTORS; + + Matrix4x4 transform = + GetResultingTransformMatrix(aFrame, nsPoint(0, 0), factor, flags); + if (transform.IsSingular()) { + return false; + } + + RectDouble result(NSAppUnitsToFloatPixels(aTransformedBounds.x, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.y, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.width, factor), + NSAppUnitsToFloatPixels(aTransformedBounds.height, factor)); + + RectDouble childGfxBounds( + NSAppUnitsToFloatPixels(aChildBounds.x, factor), + NSAppUnitsToFloatPixels(aChildBounds.y, factor), + NSAppUnitsToFloatPixels(aChildBounds.width, factor), + NSAppUnitsToFloatPixels(aChildBounds.height, factor)); + + result = transform.Inverse().ProjectRectBounds(result, childGfxBounds); + *aOutRect = nsLayoutUtils::RoundGfxRectToAppRect(ThebesRect(result), factor); + 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) + : nsDisplayHitTestInfoBase(aBuilder, aFrame) { + mList.AppendToTop(aList); + MOZ_ASSERT(mList.Count() == 1); + MOZ_ASSERT(mList.GetTop()->GetType() == DisplayItemType::TYPE_TRANSFORM); + mAnimatedGeometryRoot = aBuilder->FindAnimatedGeometryRootFor( + mFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME)); +} + +already_AddRefed<Layer> nsDisplayPerspective::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + Matrix4x4 perspectiveMatrix; + DebugOnly<bool> 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 nullptr; + } + + /* + * 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<nsDisplayTransform*>(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); + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, GetChildren(), aContainerParameters, + &perspectiveMatrix, 0); + + if (!container) { + return nullptr; + } + + // Sort of a lie, but we want to pretend that the perspective layer extends a + // 3d context so that it gets its transform combined with children. Might need + // a better name that reflects this use case and isn't specific to + // preserve-3d. + container->SetContentFlags(container->GetContentFlags() | + Layer::CONTENT_EXTEND_3D_CONTEXT); + container->SetTransformIsPerspective(true); + + return container.forget(); +} + +LayerState nsDisplayPerspective::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_ACTIVE_FORCE; +} + +nsRegion nsDisplayPerspective::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, + bool* aSnap) const { + if (!GetChildren()->GetTop()) { + *aSnap = false; + return nsRegion(); + } + + return GetChildren()->GetTop()->GetOpaqueRegion(aBuilder, aSnap); +} + +bool nsDisplayPerspective::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + Matrix4x4 perspectiveMatrix; + DebugOnly<bool> 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<nsDisplayTransform*>(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->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME); + + // 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; + params.mTransformPtr = &perspectiveMatrix; + 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<uint64_t> scrollingRelativeTo; + for (auto* asr = GetActiveScrolledRoot(); asr; asr = asr->mParent) { + if (nsLayoutUtils::IsAncestorFrameCrossDoc( + 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; +} + +bool nsDisplayPerspective::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + return mList.ComputeVisibilityForSublist(aBuilder, aVisibleRegion, + GetPaintRect()); +} + +nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, + nsTextFrame* aFrame, + const Maybe<bool>& aIsSelected) + : nsPaintedDisplayItem(aBuilder, aFrame), + mOpacity(1.0f), + mVisIStartEdge(0), + mVisIEndEdge(0) { + MOZ_COUNT_CTOR(nsDisplayText); + mIsFrameSelected = aIsSelected; + mBounds = mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); + // Bug 748228 + mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel()); +} + +bool nsDisplayText::CanApplyOpacity() const { + if (IsSelected()) { + return false; + } + + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + const nsStyleText* textStyle = f->StyleText(); + if (textStyle->HasTextShadow()) { + return false; + } + + nsTextFrame::TextDecorations decorations; + f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, + decorations); + if (decorations.HasDecorationLines()) { + return false; + } + + return true; +} + +void nsDisplayText::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) { + AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS); + + DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(), + IsSubpixelAADisabled()); + RenderToContext(aCtx, aBuilder); +} + +bool nsDisplayText::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + auto* f = static_cast<nsTextFrame*>(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) let's us early reject a bunch of things, but it can produce + // incorrect results for shadows, because they can translate things back into + // view. Also if we're selected we might have some shadows from the + // ::selected and ::inctive-selected pseudo-selectors. So don't do this + // optimization if we have shadows or a selection. + if (!(IsSelected() || f->StyleText()->HasTextShadow())) { + nsRect visible = GetPaintRect(); + visible.Inflate(3 * appUnitsPerDevPixel); + bounds = bounds.Intersect(visible); + } + + RefPtr<gfxContext> textDrawer = aBuilder.GetTextContext( + aResources, aSc, aManager, this, bounds, deviceOffset); + + aBuilder.StartGroup(this); + + RenderToContext(textDrawer, aDisplayListBuilder, true); + const bool result = textDrawer->GetTextDrawer()->Finish(); + + if (result) { + aBuilder.FinishGroup(); + } else { + aBuilder.CancelGroup(true); + } + + return result; +} + +void nsDisplayText::RenderToContext(gfxContext* aCtx, + nsDisplayListBuilder* aBuilder, + bool aIsRecording) { + nsTextFrame* f = static_cast<nsTextFrame*>(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(GetPaintRect(), A2D); + extraVisible.Inflate(1); + + gfxRect pixelVisible(extraVisible.x, extraVisible.y, extraVisible.width, + extraVisible.height); + pixelVisible.Inflate(2); + pixelVisible.RoundOut(); + + bool willClip = !aBuilder->IsForGenerateGlyphMask() && !aIsRecording; + if (willClip) { + aCtx->NewPath(); + aCtx->Rectangle(pixelVisible); + aCtx->Clip(); + } + + 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); + 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(), + IsSelected(), mOpacity); + + if (willClip) { + aCtx->PopClip(); + } +} + +bool nsDisplayText::IsSelected() const { + if (mIsFrameSelected.isNothing()) { + MOZ_ASSERT((nsTextFrame*)do_QueryFrame(mFrame)); + auto* f = static_cast<nsTextFrame*>(mFrame); + mIsFrameSelected.emplace(f->IsSelected()); + } + + return mIsFrameSelected.value(); +} + +class nsDisplayTextGeometry : public nsDisplayItemGenericGeometry { + public: + nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder) + : nsDisplayItemGenericGeometry(aItem, aBuilder), + mOpacity(aItem->Opacity()), + mVisIStartEdge(aItem->VisIStartEdge()), + mVisIEndEdge(aItem->VisIEndEdge()) { + nsTextFrame* f = static_cast<nsTextFrame*>(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; + float mOpacity; + 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<const nsDisplayTextGeometry*>(aGeometry); + nsTextFrame* f = static_cast<nsTextFrame*>(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()) || + mOpacity != geometry->mOpacity) { + aInvalidRegion->Or(oldRect, newRect); + } +} + +void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) { +#ifdef DEBUG + aStream << " (\""; + + nsTextFrame* f = static_cast<nsTextFrame*>(mFrame); + nsCString buf; + int32_t totalContentLength; + f->ToCString(buf, &totalContentLength); + + aStream << buf.get() << "\")"; +#endif +} + +nsDisplayEffectsBase::nsDisplayEffectsBase( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot, bool aClearClipChain) + : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, + aClearClipChain), + mHandleOpacity(false) { + MOZ_COUNT_CTOR(nsDisplayEffectsBase); +} + +nsDisplayEffectsBase::nsDisplayEffectsBase(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, + nsDisplayList* aList) + : nsDisplayWrapList(aBuilder, aFrame, aList), mHandleOpacity(false) { + 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<nsIFrame*>* 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 { + auto* geometry = static_cast<const nsDisplaySVGEffectGeometry*>(aGeometry); + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + if (geometry->mFrameOffsetToReferenceFrame != ToReferenceFrame() || + geometry->mUserSpaceOffset != UserSpaceOffset() || + !geometry->mBBox.IsEqualInterior(BBoxInUserSpace()) || + geometry->mOpacity != mFrame->StyleEffects()->mOpacity || + geometry->mHandleOpacity != ShouldHandleOpacity()) { + // 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() { + const nsIContent* content = mFrame->GetContent(); + bool hasSVGLayout = mFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); + if (hasSVGLayout) { + ISVGDisplayableFrame* svgFrame = do_QueryFrame(mFrame); + if (!svgFrame || !mFrame->GetContent()->IsSVGElement()) { + NS_ASSERTION(false, "why?"); + return false; + } + if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) { + return false; // The SVG spec says not to draw filters for this + } + } + + return true; +} + +typedef SVGIntegrationUtils::PaintFramesParams 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<SVGMaskFrame*> 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); + + gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint( + offsetToUserSpace, frame->PresContext()->AppUnitsPerDevPixel()); + + gfxContextMatrixAutoSaveRestore matSR(&ctx); + ctx.SetMatrixDouble( + ctx.CurrentMatrixDouble().PreTranslate(devPixelOffsetToUserSpace)); + + // Convert boaderArea and dirtyRect to user space. + int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + nsRect userSpaceBorderArea = aParams.borderArea - offsetToUserSpace; + nsRect userSpaceDirtyRect = aParams.dirtyRect - offsetToUserSpace; + + // Union all mask layer rectangles in user space. + gfxRect maskInUserSpace; + for (size_t i = 0; i < maskFrames.Length(); i++) { + SVGMaskFrame* maskFrame = maskFrames[i]; + gfxRect currentMaskSurfaceRect; + + if (maskFrame) { + currentMaskSurfaceRect = maskFrame->GetMaskArea(aParams.frame); + } else { + nsCSSRendering::ImageLayerClipState clipState; + nsCSSRendering::GetImageLayerClip( + svgReset->mMask.mLayers[i], frame, *frame->StyleBorder(), + userSpaceBorderArea, userSpaceDirtyRect, false, /* aWillPaintBorder */ + appUnitsPerDevPixel, &clipState); + currentMaskSurfaceRect = clipState.mDirtyRectInDevPx; + } + + maskInUserSpace = maskInUserSpace.Union(currentMaskSurfaceRect); + } + + if (!maskInUserSpace.IsEmpty()) { + aParams.maskRect = Some(ToRect(maskInUserSpace)); + } else { + aParams.maskRect = Nothing(); + } +} + +nsDisplayMasksAndClipPaths::nsDisplayMasksAndClipPaths( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, + const ActiveScrolledRoot* aActiveScrolledRoot) + : nsDisplayEffectsBase(aBuilder, aFrame, aList, aActiveScrolledRoot, true), + mApplyOpacityWithSimpleClipPath(false) { + 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) { + if (!svgReset->mMask.mLayers[i].mImage.IsResolved()) { + continue; + } + bool isTransformedFixed; + nsBackgroundLayerState state = nsCSSRendering::PrepareImageLayer( + presContext, aFrame, flags, mFrame->GetRectRelativeToSelf(), + mFrame->GetRectRelativeToSelf(), svgReset->mMask.mLayers[i], + &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 == + mozilla::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; + } + + if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) { + return false; + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + + if (SVGObserverUtils::GetAndObserveClipPath(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid || + SVGObserverUtils::GetAndObserveMasks(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return false; + } + + return true; +} + +already_AddRefed<Layer> nsDisplayMasksAndClipPaths::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (!IsValidMask()) { + return nullptr; + } + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplayMasksAndClipPaths::PaintMask(nsDisplayListBuilder* aBuilder, + gfxContext* aMaskContext, + bool* aMaskPainted) { + MOZ_ASSERT(aMaskContext->GetDrawTarget()->GetFormat() == SurfaceFormat::A8); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aMaskContext, mFrame, mBounds, + borderArea, aBuilder, nullptr, + mHandleOpacity, imgParams); + ComputeMaskGeometry(params); + bool maskIsComplete = false; + bool painted = SVGIntegrationUtils::PaintMask(params, maskIsComplete); + if (aMaskPainted) { + *aMaskPainted = painted; + } + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); + + return maskIsComplete && + (imgParams.result == ImgDrawResult::SUCCESS || + imgParams.result == ImgDrawResult::SUCCESS_NOT_COMPLETE || + imgParams.result == ImgDrawResult::WRONG_SIZE); +} + +LayerState nsDisplayMasksAndClipPaths::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + if (CanPaintOnMaskLayer(aManager)) { + LayerState result = RequiredLayerStateForChildren( + aBuilder, aManager, aParameters, mList, GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); + // When we're not active, FrameLayerBuilder will call PaintAsLayer() + // on us during painting. In that case we don't want a mask layer to + // be created, because PaintAsLayer() takes care of applying the mask. + // So we return LayerState::LAYER_SVG_EFFECTS instead of + // LayerState::LAYER_INACTIVE so that FrameLayerBuilder doesn't set a mask + // layer on our layer. + return result == LayerState::LAYER_INACTIVE ? LayerState::LAYER_SVG_EFFECTS + : result; + } + + return LayerState::LAYER_SVG_EFFECTS; +} + +bool nsDisplayMasksAndClipPaths::CanPaintOnMaskLayer(LayerManager* aManager) { + if (!aManager->IsWidgetLayerManager()) { + return false; + } + + if (!SVGIntegrationUtils::IsMaskResourceReady(mFrame)) { + return false; + } + + if (StaticPrefs::layers_draw_mask_debug()) { + return false; + } + + // We don't currently support this item creating a mask + // for both the clip-path, and rounded rect clipping. + if (GetClip().GetRoundedRectCount() != 0) { + return false; + } + + return true; +} + +bool nsDisplayMasksAndClipPaths::ComputeVisibility( + nsDisplayListBuilder* aBuilder, nsRegion* aVisibleRegion) { + // Our children may be made translucent or arbitrarily deformed so we should + // not allow them to subtract area from aVisibleRegion. + nsRegion childrenVisible(GetPaintRect()); + nsRect r = GetPaintRect().Intersect(mList.GetClippedBounds(aBuilder)); + mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r); + return true; +} + +void nsDisplayMasksAndClipPaths::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + auto* geometry = + static_cast<const nsDisplayMasksAndClipPathsGeometry*>(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; + } + } + } + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + const nsStyleSVGReset* svgReset = mFrame->StyleSVGReset(); + NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, svgReset->mMask) { + const auto& image = svgReset->mMask.mLayers[i].mImage; + if (image.IsImageRequestType()) { + aInvalidRegion->Or(*aInvalidRegion, bounds); + break; + } + } + } +} + +void nsDisplayMasksAndClipPaths::PaintAsLayer(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, + LayerManager* aManager) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + gfxContext* context = aCtx; + + Rect bounds = NSRectToRect(GetPaintRect(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + context->Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, aManager, + mHandleOpacity, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params); + + context->PopClip(); + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); +} + +void nsDisplayMasksAndClipPaths::PaintWithContentsPaintCallback( + nsDisplayListBuilder* aBuilder, gfxContext* aCtx, + const std::function<void()>& aPaintChildren) { + // Clip the drawing target by mVisibleRect, which contains the visible + // region of the target frame and its out-of-flow and inflow descendants. + gfxContext* context = aCtx; + + Rect bounds = NSRectToRect(GetPaintRect(), + mFrame->PresContext()->AppUnitsPerDevPixel()); + bounds.RoundOut(); + context->Clip(bounds); + + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, nullptr, + mHandleOpacity, imgParams); + + ComputeMaskGeometry(params); + + SVGIntegrationUtils::PaintMaskAndClipPath(params, aPaintChildren); + + context->PopClip(); + + nsDisplayMasksAndClipPathsGeometry::UpdateDrawResult(this, imgParams.result); +} + +static Maybe<wr::WrClipId> CreateSimpleClipRegion( + const nsDisplayMasksAndClipPaths& aDisplayItem, + wr::DisplayListBuilder& aBuilder) { + nsIFrame* frame = aDisplayItem.Frame(); + auto* style = frame->StyleSVGReset(); + MOZ_ASSERT(style->HasClipPath() || style->HasMask()); + if (!SVGIntegrationUtils::UsingSimpleClipPathForFrame(frame)) { + return Nothing(); + } + + const auto& clipPath = style->mClipPath; + const auto& shape = *clipPath.AsShape()._0; + + auto appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel(); + const nsRect refBox = + nsLayoutUtils::ComputeGeometryBox(frame, clipPath.AsShape()._1); + + AutoTArray<wr::ComplexClipRegion, 1> clipRegions; + + wr::LayoutRect rect; + switch (shape.tag) { + case StyleBasicShape::Tag::Inset: { + const nsRect insetRect = ShapeUtils::ComputeInsetRect(shape, refBox) + + aDisplayItem.ToReferenceFrame(); + + nscoord radii[8] = {0}; + + if (ShapeUtils::ComputeInsetRadii(shape, insetRect, refBox, radii)) { + clipRegions.AppendElement( + wr::ToComplexClipRegion(insetRect, radii, appUnitsPerDevPixel)); + } + + rect = wr::ToLayoutRect( + LayoutDeviceRect::FromAppUnits(insetRect, 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 : mozilla::AllPhysicalHalfCorners()) { + ellipseRadii[corner] = + HalfCornerIsX(corner) ? radii.width : radii.height; + } + + clipRegions.AppendElement(wr::ToComplexClipRegion( + ellipseRect, ellipseRadii, appUnitsPerDevPixel)); + + rect = wr::ToLayoutRect( + LayoutDeviceRect::FromAppUnits(ellipseRect, 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 + // SVGIntegrationUtils::UsingSimpleClipPathForFrame + MOZ_ASSERT_UNREACHABLE("Unhandled shape id?"); + return Nothing(); + } + wr::WrClipId clipId = aBuilder.DefineClip(Nothing(), rect, &clipRegions); + return Some(clipId); +} + +static Maybe<wr::WrClipId> CreateWRClipPathAndMasks( + nsDisplayMasksAndClipPaths* aDisplayItem, const LayoutDeviceRect& aBounds, + wr::IpcResourceUpdateQueue& aResources, wr::DisplayListBuilder& aBuilder, + const StackingContextHelper& aSc, layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + if (auto clip = CreateSimpleClipRegion(*aDisplayItem, aBuilder)) { + return clip; + } + + Maybe<wr::ImageMask> mask = aManager->CommandBuilder().BuildWrMaskImage( + aDisplayItem, aBuilder, aResources, aSc, aDisplayListBuilder, aBounds); + if (!mask) { + return Nothing(); + } + + wr::WrClipId clipId = aBuilder.DefineImageMaskClip(mask.ref()); + + return Some(clipId); +} + +bool nsDisplayMasksAndClipPaths::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + bool snap; + auto appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + nsRect displayBounds = GetBounds(aDisplayListBuilder, &snap); + LayoutDeviceRect bounds = + LayoutDeviceRect::FromAppUnits(displayBounds, appUnitsPerDevPixel); + + Maybe<wr::WrClipId> clip = CreateWRClipPathAndMasks( + this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder); + + Maybe<StackingContextHelper> 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<float> opacity = mApplyOpacityWithSimpleClipPath + ? Some(mFrame->StyleEffects()->mOpacity) + : Nothing(); + + wr::StackingContextParams params; + params.clip = wr::WrStackingContextClip::ClipId(*clip); + params.opacity = opacity.ptrOr(nullptr); + layer.emplace(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, params, + bounds); + sc = layer.ptr(); + } + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, *sc, + aManager, aDisplayListBuilder); + + return true; +} + +void nsDisplayMasksAndClipPaths::SelectOpacityOptimization( + const bool aUsingLayers) { + if (aUsingLayers || + !SVGIntegrationUtils::UsingSimpleClipPathForFrame(mFrame)) { + // Handle opacity in mask and clip-path drawing code. + SetHandleOpacity(); + MOZ_ASSERT(!mApplyOpacityWithSimpleClipPath); + } else { + // Allow WebRender simple clip paths to also handle opacity. + mApplyOpacityWithSimpleClipPath = true; + } +} + +Maybe<nsRect> 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 + MOZ_ASSERT(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=("; + if (mHandleOpacity) { + first = false; + aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity); + } + 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<SVGMaskFrame*> masks; + // XXX check return value? + SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks); + if (!masks.IsEmpty() && masks[0]) { + if (!first) { + aTo += ", "; + } + aTo += "mask"; + } + aTo += ")"; +} +#endif + +already_AddRefed<Layer> nsDisplayBackdropRootContainer::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + RefPtr<Layer> container = aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, aContainerParameters, nullptr); + if (!container) { + return nullptr; + } + + return container.forget(); +} + +LayerState nsDisplayBackdropRootContainer::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return RequiredLayerStateForChildren(aBuilder, aManager, aParameters, mList, + GetAnimatedGeometryRoot(), + GetActiveScrolledRoot()); +} + +bool nsDisplayBackdropRootContainer::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + wr::StackingContextParams params; + params.flags |= wr::StackingContextFlags::IS_BACKDROP_ROOT; + params.clip = + wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayWrapList::CreateWebRenderCommands(aBuilder, aResources, sc, aManager, + aDisplayListBuilder); + return true; +} + +/* static */ +bool nsDisplayBackdropFilters::CanCreateWebRenderCommands( + nsDisplayListBuilder* aBuilder, nsIFrame* aFrame) { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame); +} + +bool nsDisplayBackdropFilters::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + WrFiltersHolder wrFilters; + Maybe<nsRect> filterClip; + auto filterChain = mFrame->StyleEffects()->mBackdropFilters.AsSpan(); + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, filterClip)) { + return false; + } + + nsCSSRendering::ImageLayerClipState clip; + nsCSSRendering::GetImageLayerClip( + mFrame->StyleBackground()->BottomLayer(), mFrame, *mFrame->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; +} + +/* static */ +nsDisplayFilters::nsDisplayFilters(nsDisplayListBuilder* aBuilder, + nsIFrame* aFrame, nsDisplayList* aList) + : nsDisplayEffectsBase(aBuilder, aFrame, aList), + mEffectsBounds(aFrame->InkOverflowRectRelativeToSelf()) { + MOZ_COUNT_CTOR(nsDisplayFilters); +} + +already_AddRefed<Layer> nsDisplayFilters::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + if (!ValidateSVGFrame()) { + return nullptr; + } + + if (mFrame->StyleEffects()->mOpacity == 0.0f && mHandleOpacity) { + return nullptr; + } + + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + + // 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, so no need for a layer. + if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) == + SVGObserverUtils::eHasRefsSomeInvalid) { + return nullptr; + } + + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + return container.forget(); +} + +LayerState nsDisplayFilters::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + return LayerState::LAYER_SVG_EFFECTS; +} + +bool nsDisplayFilters::ComputeVisibility(nsDisplayListBuilder* aBuilder, + nsRegion* aVisibleRegion) { + nsPoint offset = ToReferenceFrame(); + nsRect dirtyRect = SVGIntegrationUtils::GetRequiredSourceForInvalidArea( + mFrame, GetPaintRect() - offset) + + offset; + + // Our children may be made translucent or arbitrarily deformed so we should + // not allow them to subtract area from aVisibleRegion. + nsRegion childrenVisible(dirtyRect); + nsRect r = dirtyRect.Intersect( + mList.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot)); + mList.ComputeVisibilityForSublist(aBuilder, &childrenVisible, r); + return true; +} + +void nsDisplayFilters::ComputeInvalidationRegion( + nsDisplayListBuilder* aBuilder, const nsDisplayItemGeometry* aGeometry, + nsRegion* aInvalidRegion) const { + nsDisplayEffectsBase::ComputeInvalidationRegion(aBuilder, aGeometry, + aInvalidRegion); + + auto* geometry = static_cast<const nsDisplayFiltersGeometry*>(aGeometry); + + if (aBuilder->ShouldSyncDecodeImages() && + geometry->ShouldInvalidateToSyncDecodeImages()) { + bool snap; + nsRect bounds = GetBounds(aBuilder, &snap); + aInvalidRegion->Or(*aInvalidRegion, bounds); + } +} + +void nsDisplayFilters::PaintAsLayer(nsDisplayListBuilder* aBuilder, + gfxContext* aCtx, LayerManager* aManager) { + imgDrawingParams imgParams(aBuilder->GetImageDecodeFlags()); + nsRect borderArea = nsRect(ToReferenceFrame(), mFrame->GetSize()); + SVGIntegrationUtils::PaintFramesParams params(*aCtx, mFrame, GetPaintRect(), + borderArea, aBuilder, aManager, + mHandleOpacity, imgParams); + SVGIntegrationUtils::PaintFilter(params); + nsDisplayFiltersGeometry::UpdateDrawResult(this, imgParams.result); +} + +bool nsDisplayFilters::CanCreateWebRenderCommands() { + return SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(mFrame); +} + +bool nsDisplayFilters::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); + + WrFiltersHolder wrFilters; + Maybe<nsRect> filterClip; + auto filterChain = mFrame->StyleEffects()->mFilters.AsSpan(); + if (!SVGIntegrationUtils::CreateWebRenderCSSFilters(filterChain, mFrame, + wrFilters) && + !SVGIntegrationUtils::BuildWebRenderFilters(mFrame, filterChain, + wrFilters, filterClip)) { + return false; + } + + wr::WrStackingContextClip clip; + if (filterClip) { + auto devPxRect = LayoutDeviceRect::FromAppUnits( + filterClip.value() + ToReferenceFrame(), auPerDevPixel); + wr::WrClipId clipId = aBuilder.DefineRectClip(wr::ToLayoutRect(devPxRect)); + clip = wr::WrStackingContextClip::ClipId(clipId); + } else { + clip = wr::WrStackingContextClip::ClipChain(aBuilder.CurrentClipChainId()); + } + + float opacity = mFrame->StyleEffects()->mOpacity; + wr::StackingContextParams params; + params.mFilters = std::move(wrFilters.filters); + params.mFilterDatas = std::move(wrFilters.filter_datas); + params.opacity = opacity != 1.0f && mHandleOpacity ? &opacity : nullptr; + params.clip = clip; + StackingContextHelper sc(aSc, GetActiveScrolledRoot(), mFrame, this, aBuilder, + params); + + nsDisplayEffectsBase::CreateWebRenderCommands(aBuilder, aResources, sc, + aManager, aDisplayListBuilder); + + return true; +} + +#ifdef MOZ_DUMP_PAINTING +void nsDisplayFilters::PrintEffects(nsACString& aTo) { + nsIFrame* firstFrame = + nsLayoutUtils::FirstContinuationOrIBSplitSibling(mFrame); + bool first = true; + aTo += " effects=("; + if (mHandleOpacity) { + first = false; + aTo += nsPrintfCString("opacity(%f)", mFrame->StyleEffects()->mOpacity); + } + // 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); +} + +LayerState nsDisplaySVGWrapper::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return LayerState::LAYER_ACTIVE_FORCE; + } + return LayerState::LAYER_NONE; +} + +bool nsDisplaySVGWrapper::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return false; + } + return true; +} + +already_AddRefed<Layer> nsDisplaySVGWrapper::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplaySVGWrapper::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +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 + +LayerState nsDisplayForeignObject::GetLayerState( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aParameters) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return LayerState::LAYER_ACTIVE_FORCE; + } + return LayerState::LAYER_NONE; +} + +bool nsDisplayForeignObject::ShouldFlattenAway(nsDisplayListBuilder* aBuilder) { + RefPtr<LayerManager> layerManager = aBuilder->GetWidgetLayerManager(); + if (layerManager && + layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { + return false; + } + return true; +} + +already_AddRefed<Layer> nsDisplayForeignObject::BuildLayer( + nsDisplayListBuilder* aBuilder, LayerManager* aManager, + const ContainerLayerParameters& aContainerParameters) { + ContainerLayerParameters newContainerParameters = aContainerParameters; + newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; + + RefPtr<ContainerLayer> container = + aManager->GetLayerBuilder()->BuildContainerLayerFor( + aBuilder, aManager, mFrame, this, &mList, newContainerParameters, + nullptr); + + return container.forget(); +} + +bool nsDisplayForeignObject::CreateWebRenderCommands( + mozilla::wr::DisplayListBuilder& aBuilder, + mozilla::wr::IpcResourceUpdateQueue& aResources, + const StackingContextHelper& aSc, + mozilla::layers::RenderRootStateManager* aManager, + nsDisplayListBuilder* aDisplayListBuilder) { + AutoRestore<bool> restoreDoGrouping(aManager->CommandBuilder().mDoGrouping); + aManager->CommandBuilder().mDoGrouping = false; + return nsDisplayWrapList::CreateWebRenderCommands( + aBuilder, aResources, aSc, aManager, aDisplayListBuilder); +} + +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 (;;) { + nsDisplayItem* item = PositionedDescendants()->GetBottom(); + if (item && item->ZIndex() < 0) { + PositionedDescendants()->RemoveBottom(); + aOutResultList->AppendToTop(item); + continue; + } + break; + } + // 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()); +} + +namespace mozilla { + +uint32_t PaintTelemetry::sPaintLevel = 0; +uint32_t PaintTelemetry::sMetricLevel = 0; +EnumeratedArray<PaintTelemetry::Metric, PaintTelemetry::Metric::COUNT, double> + PaintTelemetry::sMetrics; + +PaintTelemetry::AutoRecordPaint::AutoRecordPaint() { + // Don't record nested paints. + if (sPaintLevel++ > 0) { + return; + } + + // Reset metrics for a new paint. + for (auto& metric : sMetrics) { + metric = 0.0; + } + 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; + } + + double totalMs = (TimeStamp::Now() - mStart).ToMilliseconds(); + + // Record the total time. + Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME, + static_cast<uint32_t>(totalMs)); + + // Helpers for recording large/small paints. + auto recordLarge = [=](const nsCString& aKey, double aDurationMs) -> void { + MOZ_ASSERT(aDurationMs <= totalMs); + uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0); + Telemetry::Accumulate(Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT, aKey, + amount); + }; + auto recordSmall = [=](const nsCString& aKey, double aDurationMs) -> void { + MOZ_ASSERT(aDurationMs <= totalMs); + uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0); + Telemetry::Accumulate(Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT, aKey, + amount); + }; + + double dlMs = sMetrics[Metric::DisplayList]; + double flbMs = sMetrics[Metric::Layerization]; + double frMs = sMetrics[Metric::FlushRasterization]; + double rMs = sMetrics[Metric::Rasterization]; + + // If the total time was >= 16ms, then it's likely we missed a frame due to + // painting. We bucket these metrics separately. + if (totalMs >= 16.0) { + recordLarge("dl"_ns, dlMs); + recordLarge("flb"_ns, flbMs); + recordLarge("fr"_ns, frMs); + recordLarge("r"_ns, rMs); + } else { + recordSmall("dl"_ns, dlMs); + recordSmall("flb"_ns, flbMs); + recordSmall("fr"_ns, frMs); + recordSmall("r"_ns, rMs); + } + + Telemetry::Accumulate(Telemetry::PAINT_BUILD_LAYERS_TIME, flbMs); +} + +PaintTelemetry::AutoRecord::AutoRecord(Metric aMetric) : mMetric(aMetric) { + // Don't double-record anything nested. + if (sMetricLevel++ > 0) { + return; + } + + // Don't record inside nested paints, or outside of paints. + if (sPaintLevel != 1) { + return; + } + + mStart = TimeStamp::Now(); +} + +PaintTelemetry::AutoRecord::~AutoRecord() { + MOZ_ASSERT(sMetricLevel != 0); + + sMetricLevel--; + if (mStart.IsNull()) { + return; + } + + sMetrics[mMetric] += (TimeStamp::Now() - mStart).ToMilliseconds(); +} + +} // namespace mozilla + +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::GetCrossDocParentFrame(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), + mPrevHitTestArea(aBuilder->mHitTestArea), + mPrevHitTestInfo(aBuilder->mHitTestInfo), + mPrevOffset(aBuilder->mCurrentOffsetToReferenceFrame), + mPrevAdditionalOffset(aBuilder->mAdditionalOffset), + mPrevVisibleRect(aBuilder->mVisibleRect), + mPrevDirtyRect(aBuilder->mDirtyRect), + mPrevAGR(aBuilder->mCurrentAGR), + 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); + } + + bool isAsync; + mCurrentAGRState = aBuilder->IsAnimatedGeometryRoot(aForChild, isAsync); + + if (aBuilder->mCurrentFrame == aForChild->GetParent()) { + if (mCurrentAGRState == AGR_YES) { + aBuilder->mCurrentAGR = + aBuilder->WrapAGRForFrame(aForChild, isAsync, aBuilder->mCurrentAGR); + } + } else if (aBuilder->mCurrentFrame != aForChild) { + aBuilder->mCurrentAGR = aBuilder->FindAnimatedGeometryRootFor(aForChild); + } + + MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc( + aBuilder->RootReferenceFrame(), *aBuilder->mCurrentAGR)); + + // 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; +} |