/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /* * structures that represent things to be painted (ordered in z-order), * used during painting and hit testing */ #include "nsDisplayList.h" #include #include #include #include "gfxContext.h" #include "gfxUtils.h" #include "mozilla/DisplayPortUtils.h" #include "mozilla/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::CreateASRForFrame( const ActiveScrolledRoot* aParent, nsIScrollableFrame* aScrollableFrame, bool aIsRetained) { nsIFrame* f = do_QueryFrame(aScrollableFrame); RefPtr asr; if (aIsRetained) { asr = f->GetProperty(ActiveScrolledRootCache()); } if (!asr) { asr = new ActiveScrolledRoot(); if (aIsRetained) { RefPtr ref = asr; f->SetProperty(ActiveScrolledRootCache(), ref.forget().take()); } } asr->mParent = aParent; asr->mScrollableFrame = aScrollableFrame; asr->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& 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 animationData = aManager->CommandBuilder() .CreateOrRecycleWebRenderUserData(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 sourceTarget = sourceCtx->GetDrawTarget(); RefPtr maskDT = sourceTarget->CreateClippedDrawTarget( bounds.ToUnknownRect(), SurfaceFormat::A8); if (!maskDT || !maskDT->IsValid()) { return false; } RefPtr 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 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& 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(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 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 = GetFocusedPresShell(); if (presShell) { RefPtr caret = presShell->GetCaret(); mCaretFrame = caret->GetPaintGeometry(&mCaretRect); // The focused pres shell may not be in the document that we're // painting, or be in a popup. Check if the display root for // the caret matches the display root that we're painting, and // only use it if it matches. if (mCaretFrame && nsLayoutUtils::GetDisplayRootFrame(mCaretFrame) != nsLayoutUtils::GetDisplayRootFrame(mReferenceFrame)) { mCaretFrame = nullptr; } } } void nsDisplayListBuilder::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 dummy; MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot, dummy) == AGR_YES); RefPtr result; if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) { MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aAnimatedGeometryRoot)); RefPtr 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 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 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 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 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(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(mCurrentFrame), isAsync) == AGR_YES) { AnimatedGeometryRoot* oldAGR = mCurrentAGR; mCurrentAGR = WrapAGRForFrame(const_cast(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 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(FindReferenceFrameFor(aFrame)); if (IsInTransform()) { // Only support 2d rectilinear transforms. Transform support is needed for // the horizontal flip transform that's applied to the urlbar textbox in // RTL mode - it should be able to exclude itself from the draggable region. referenceFrameToRootReferenceFrame = ViewAs( nsLayoutUtils::GetTransformToAncestor(RelativeTo{referenceFrame}, RelativeTo{mReferenceFrame}) .GetMatrix()); Matrix referenceFrameToRootReferenceFrame2d; if (!referenceFrameToRootReferenceFrame.Is2D( &referenceFrameToRootReferenceFrame2d) || !referenceFrameToRootReferenceFrame2d.IsRectilinear()) { return; } } else { MOZ_ASSERT(referenceFrame == mReferenceFrame, "referenceFrameToRootReferenceFrame needs to be adjusted"); } // We do some basic visibility checking on the frame's border box here. // We intersect it both with the current dirty rect and with the current // clip. Either one is just a conservative approximation on its own, but // their intersection luckily works well enough for our purposes, so that // we don't have to do full-blown visibility computations. // The most important case we need to handle is the scrolled-off tab: // If the tab bar overflows, tab parts that are clipped by the scrollbox // should not be allowed to interfere with the window dragging region. Using // just the current DisplayItemClip is not enough to cover this case // completely because clips are reset while building stacking context // contents, so for example we'd fail to clip frames that have a clip path // applied to them. But the current dirty rect doesn't get reset in that // case, so we use it to make this case work. nsRect borderBox = aFrame->GetRectRelativeToSelf().Intersect(mVisibleRect); borderBox += ToReferenceFrame(aFrame); const DisplayItemClipChain* clip = ClipState().GetCurrentCombinedClipChain(this); borderBox = ApplyAllClipNonRoundedIntersection(clip, borderBox); if (borderBox.IsEmpty()) { return; } LayoutDeviceRect devPixelBorderBox = LayoutDevicePixel::FromAppUnits( borderBox, aFrame->PresContext()->AppUnitsPerDevPixel()); LayoutDeviceRect transformedDevPixelBorderBox = TransformBy(referenceFrameToRootReferenceFrame, devPixelBorderBox); transformedDevPixelBorderBox.Round(); LayoutDeviceIntRect transformedDevPixelBorderBoxInt; if (!transformedDevPixelBorderBox.ToIntRect( &transformedDevPixelBorderBoxInt)) { return; } LayoutDeviceIntRegion& region = styleUI->mWindowDragging == StyleWindowDragging::Drag ? mWindowDraggingRegion : mWindowNoDraggingRegion; if (!IsRetainingDisplayList()) { region.OrWith(transformedDevPixelBorderBoxInt); return; } 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 = 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 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( 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* 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 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 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( 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 root; { AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_LayerBuilding); #ifdef MOZ_GECKO_PROFILER nsCOMPtr 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()); } 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 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 nsDisplayList::PaintRoot( nsDisplayListBuilder* aBuilder, gfxContext* aCtx, uint32_t aFlags) { AUTO_PROFILER_LABEL("nsDisplayList::PaintRoot", GRAPHICS); RefPtr 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(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 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 mFrames; }; // Sort the frames by depth and then moves all the contained frames to the // destination static void FlushFramesArray(nsTArray& aSource, nsTArray* aDest) { if (aSource.IsEmpty()) { return; } aSource.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* aOutFrames) const { nsDisplayItem* item; if (aState->mInPreserves3D) { // Collect leaves of the current 3D rendering context. for (nsDisplayItem* item : *this) { auto itemType = item->GetType(); if (itemType != DisplayItemType::TYPE_TRANSFORM || !static_cast(item)->IsLeafOf3DContext()) { item->HitTest(aBuilder, aRect, aState, aOutFrames); } else { // One of leaves in the current 3D rendering context. aState->mItemBuffer.AppendElement(item); } } return; } int32_t itemBufferStart = aState->mItemBuffer.Length(); for (nsDisplayItem* item : *this) { aState->mItemBuffer.AppendElement(item); } AutoTArray temp; for (int32_t i = aState->mItemBuffer.Length() - 1; i >= itemBufferStart; --i) { // Pop element off the end of the buffer. We want to shorten the buffer // so that recursive calls to HitTest have more buffer space. item = aState->mItemBuffer[i]; aState->mItemBuffer.SetLength(i); bool snap; nsRect r = item->GetBounds(aBuilder, &snap).Intersect(aRect); auto itemType = item->GetType(); bool same3DContext = (itemType == DisplayItemType::TYPE_TRANSFORM && static_cast(item)->IsParticipating3DContext()) || (itemType == DisplayItemType::TYPE_PERSPECTIVE && item->Frame()->Extend3DContext()); if (same3DContext && (itemType != DisplayItemType::TYPE_TRANSFORM || !static_cast(item)->IsLeafOf3DContext())) { if (!item->GetClip().MayIntersect(aRect)) { continue; } AutoTArray neverUsed; // Start 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 outFrames; item->HitTest(aBuilder, aRect, aState, &outFrames); // For 3d transforms with preserve-3d we add hit frames into the temp list // so we can sort them later, otherwise we add them directly to the output // list. nsTArray* writeFrames = aOutFrames; if (item->GetType() == DisplayItemType::TYPE_TRANSFORM && static_cast(item)->IsLeafOf3DContext()) { if (outFrames.Length()) { nsDisplayTransform* transform = static_cast(item); nsPoint point = aRect.TopLeft(); // A 1x1 rect means a point, otherwise use the center of the rect if (aRect.width != 1 || aRect.height != 1) { point = aRect.Center(); } temp.AppendElement( FramesWithDepth(transform->GetHitDepthAtPoint(aBuilder, point))); writeFrames = &temp[temp.Length() - 1].mFrames; } } else { // We may have just finished a run of consecutive preserve-3d // transforms, so flush these into the destination array before // processing our frame list. FlushFramesArray(temp, aOutFrames); } for (uint32_t j = 0; j < outFrames.Length(); j++) { nsIFrame* f = outFrames.ElementAt(j); // Filter out some frames depending on the type of hittest // we are doing. For visibility tests, pass through all frames. // For pointer tests, only pass through frames that are styled // to receive pointer events. if (aBuilder->HitTestIsForVisibility() || IsFrameReceivingPointerEvents(f)) { writeFrames->AppendElement(f); } } if (aBuilder->HitTestIsForVisibility()) { aState->mHitOccludingItem = [&] { if (aState->mHitOccludingItem) { // We already hit something before. return true; } if (aState->mCurrentOpacity == 1.0f && item->GetOpaqueRegion(aBuilder, &snap).Contains(aRect)) { // An opaque item always occludes everything. Note that we need to // check wrapping opacity and such as well. return true; } float threshold = aBuilder->VisibilityThreshold(); if (threshold == 1.0f) { return false; } float itemOpacity = [&] { switch (item->GetType()) { case DisplayItemType::TYPE_OPACITY: return static_cast(item)->GetOpacity(); case DisplayItemType::TYPE_BACKGROUND_COLOR: return static_cast(item) ->GetOpacity(); default: // Be conservative and assume it won't occlude other items. return 0.0f; } }(); return itemOpacity * aState->mCurrentOpacity >= threshold; }(); if (aState->mHitOccludingItem) { // We're exiting early, so pop the remaining items off the buffer. aState->mItemBuffer.TruncateLength(itemBufferStart); break; } } } } // Clear any remaining preserve-3d transforms. FlushFramesArray(temp, aOutFrames); NS_ASSERTION(aState->mItemBuffer.Length() == uint32_t(itemBufferStart), "How did we forget to pop some elements?"); } static nsIContent* FindContentInDocument(nsDisplayItem* aItem, Document* aDoc) { nsIFrame* f = aItem->Frame(); while (f) { nsPresContext* pc = f->PresContext(); if (pc->Document() == aDoc) { return f->GetContent(); } f = nsLayoutUtils::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(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(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(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 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 nsDisplayContainer::GetClipWithRespectToASR( nsDisplayListBuilder* aBuilder, const ActiveScrolledRoot* aASR) const { // Our children should have finite bounds with respect to |aASR|. if (aASR == mActiveScrolledRoot) { return Some(mBounds); } return Some(mChildren.GetClippedBoundsWithRespectToASR(aBuilder, aASR)); } void nsDisplayContainer::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray* aOutFrames) { mChildren.HitTest(aBuilder, aRect, aState, aOutFrames); } void nsDisplayContainer::UpdateBounds(nsDisplayListBuilder* aBuilder) { // Container item bounds are expected to be clipped. mBounds = mChildren.GetClippedBoundsWithRespectToASR(aBuilder, mActiveScrolledRoot); } nsRect nsDisplaySolidColor::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const { *aSnap = true; return mBounds; } LayerState nsDisplaySolidColor::GetLayerState( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { if (ForceActiveLayers()) { return LayerState::LAYER_ACTIVE; } return LayerState::LAYER_NONE; } already_AddRefed nsDisplaySolidColor::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { RefPtr layer = static_cast( 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 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 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 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(cutoutRegion)); region.MoveBy(aBuilder->ToReferenceFrame(aFrame)); aList->AppendNewToTop(aBuilder, aFrame, region, aColor); return true; } enum class TableType : uint8_t { Table, TableCol, TableColGroup, TableRow, TableRowGroup, TableCell, MAX, }; enum class TableTypeBits : uint8_t { Count = 3 }; static_assert(static_cast(TableType::MAX) < (1 << (static_cast(TableTypeBits::Count) + 1)), "TableType cannot fit with TableTypeBits::Count"); TableType GetTableTypeFromFrame(nsIFrame* aFrame); static uint16_t CalculateTablePerFrameKey(const uint16_t aIndex, const TableType aType) { const uint32_t key = (aIndex << static_cast(TableTypeBits::Count)) | static_cast(aType); return static_cast(key); } static nsDisplayBackgroundImage* CreateBackgroundImage( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, const nsDisplayBackgroundImage::InitData& aBgData) { const auto index = aBgData.layer; if (aSecondaryFrame) { const auto tableType = GetTableTypeFromFrame(aFrame); const uint16_t tableItemIndex = CalculateTablePerFrameKey(index, tableType); return MakeDisplayItemWithIndex( aBuilder, aSecondaryFrame, tableItemIndex, aBgData, aFrame); } return MakeDisplayItemWithIndex(aBuilder, aFrame, index, aBgData); } static nsDisplayThemedBackground* CreateThemedBackground( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, nsRect& aBgRect) { if (aSecondaryFrame) { const uint16_t index = static_cast(GetTableTypeFromFrame(aFrame)); return MakeDisplayItemWithIndex( aBuilder, aSecondaryFrame, index, aBgRect, aFrame); } return MakeDisplayItem(aBuilder, aFrame, aBgRect); } static nsDisplayBackgroundColor* CreateBackgroundColor( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, nsRect& aBgRect, ComputedStyle* aBgSC, nscolor aColor) { if (aSecondaryFrame) { const uint16_t index = static_cast(GetTableTypeFromFrame(aFrame)); return MakeDisplayItemWithIndex( aBuilder, aSecondaryFrame, index, aBgRect, aBgSC, aColor, aFrame); } return MakeDisplayItem(aBuilder, aFrame, aBgRect, aBgSC, aColor); } /*static*/ bool nsDisplayBackgroundImage::AppendBackgroundItemsToTop( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, const nsRect& aBackgroundRect, nsDisplayList* aList, bool aAllowWillPaintBorderOptimization, ComputedStyle* aComputedStyle, const nsRect& aBackgroundOriginRect, nsIFrame* aSecondaryReferenceFrame, Maybe* 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 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( aBuilder, aSecondaryReferenceFrame, index, &thisItemList, bg->mImage.mLayers[i].mBlendMode, asr, aFrame, true); } else { thisItemList.AppendNewToTopWithIndex( aBuilder, aFrame, i + 1, &thisItemList, bg->mImage.mLayers[i].mBlendMode, asr, true); } } bgItemList.AppendToTop(&thisItemList); } if (needBlendContainer) { 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 nsDisplayBackgroundImage::GetImage() { nsCOMPtr 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 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 nsDisplayBackgroundImage::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aParameters) { RefPtr layer = static_cast( aManager->GetLayerBuilder()->GetLeafLayerFor(aBuilder, this)); if (!layer) { layer = aManager->CreateImageLayer(); if (!layer) { return nullptr; } } RefPtr 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* 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(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 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(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(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* aOutFrames) { // Assume that any point in our background rect is a hit. if (mBackgroundRect.Intersects(aRect)) { aOutFrames->AppendElement(mFrame); } } nsRegion nsDisplayThemedBackground::GetOpaqueRegion( nsDisplayListBuilder* aBuilder, bool* aSnap) const { nsRegion result; *aSnap = false; if (mThemeTransparency == nsITheme::eOpaque) { *aSnap = true; result = mBackgroundRect; } return result; } Maybe nsDisplayThemedBackground::IsUniform( nsDisplayListBuilder* aBuilder) const { 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(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 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 nsDisplayImageContainer::GetContainer( LayerManager* aManager, nsDisplayListBuilder* aBuilder) { nsCOMPtr 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 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 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(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 nsDisplayBackgroundColor::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { if (mColor == sRGBColor()) { return nullptr; } RefPtr layer = static_cast( 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 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 nsDisplayBackgroundColor::IsUniform( nsDisplayListBuilder* aBuilder) const { return Some(mColor.ToABGR()); } void nsDisplayBackgroundColor::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray* aOutFrames) { if (!RoundedBorderIntersectsRect(mFrame, ToReferenceFrame(), aRect)) { // aRect doesn't intersect our border-radius curve. return; } aOutFrames->AppendElement(mFrame); } void nsDisplayBackgroundColor::WriteDebugInfo(std::stringstream& aStream) { aStream << " (rgba " << mColor.r << "," << mColor.g << "," << mColor.b << "," << mColor.a << ")"; aStream << " backgroundRect" << mBackgroundRect; } nsRect nsDisplayOutline::GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const { *aSnap = false; return mFrame->InkOverflowRectRelativeToSelf() + ToReferenceFrame(); } 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 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* 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& 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&& 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 fixedTarget = aBuilder.GetContainingFixedPosScrollTarget(asr); if (fixedTarget) { return *fixedTarget; } if (asr) { return asr->GetViewId(); } return ScrollableLayerGuid::NULL_SCROLL_ID; }); Maybe 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(*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(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* 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 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 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(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 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 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(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* 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 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* aOutFrames) { AutoRestore opacity(aState->mCurrentOpacity); aState->mCurrentOpacity *= mOpacity; // TODO(emilio): special-casing zero is a bit arbitrary... Maybe we should // only consider fully opaque items? Or make this configurable somehow? if (aBuilder->HitTestIsForVisibility() && mOpacity == 0.0f) { return; } nsDisplayWrapList::HitTest(aBuilder, aRect, aState, aOutFrames); } nsRegion nsDisplayOpacity::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) const { *aSnap = false; // The only time where mOpacity == 1.0 should be when we have will-change. // We could report this as opaque then but when the will-change value starts // animating the element would become non opaque and could cause repaints. return nsRegion(); } // nsDisplayOpacity uses layers for rendering already_AddRefed nsDisplayOpacity::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { MOZ_ASSERT(mChildOpacityState != ChildOpacityState::Applied); ContainerLayerParameters params = aContainerParameters; RefPtr 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& 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 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(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(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 nsDisplayBlendMode::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { ContainerLayerParameters newContainerParameters = aContainerParameters; newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; RefPtr 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(aItem); if (mIsForBackground || item->mIsForBackground) { // Don't merge background-blend-mode items return false; } return true; } /* static */ nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForMixBlendMode( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) { return MakeDisplayItem(aBuilder, aFrame, aList, aActiveScrolledRoot, false); } /* static */ nsDisplayBlendContainer* nsDisplayBlendContainer::CreateForBackgroundBlendMode( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsIFrame* aSecondaryFrame, nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot) { if (aSecondaryFrame) { auto type = GetTableTypeFromFrame(aFrame); auto index = static_cast(type); return MakeDisplayItemWithIndex( aBuilder, aSecondaryFrame, index, aList, aActiveScrolledRoot, true, aFrame); } return MakeDisplayItemWithIndex( aBuilder, aFrame, 1, aList, aActiveScrolledRoot, true); } nsDisplayBlendContainer::nsDisplayBlendContainer( nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, nsDisplayList* aList, const ActiveScrolledRoot* aActiveScrolledRoot, bool aIsForBackground) : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot, true), mIsForBackground(aIsForBackground) { MOZ_COUNT_CTOR(nsDisplayBlendContainer); } // nsDisplayBlendContainer uses layers for rendering already_AddRefed 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 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 nsDisplayOwnLayer::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { RefPtr 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 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 animationData = aManager->CommandBuilder() .CreateOrRecycleWebRenderUserData(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( aBuilder, aSecondaryFrame, index, &temp, aFrame); } return MakeDisplayItemWithIndex(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 nsDisplayFixedPosition::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { RefPtr 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 nsDisplayStickyPosition::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { RefPtr 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 saccHelper; if (stickyScrollContainer) { float auPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); bool snap; nsRect itemBounds = GetBounds(aDisplayListBuilder, &snap); Maybe topMargin; Maybe rightMargin; Maybe bottomMargin; Maybe leftMargin; wr::StickyOffsetBounds vBounds = {0.0, 0.0}; wr::StickyOffsetBounds hBounds = {0.0, 0.0}; nsPoint appliedOffset; nsRectAbsolute outer; nsRectAbsolute inner; stickyScrollContainer->GetScrollRanges(mFrame, &outer, &inner); nsIFrame* scrollFrame = do_QueryFrame(stickyScrollContainer->ScrollFrame()); nsPoint offset = scrollFrame->GetOffsetToCrossDoc(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 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 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(new ScrollMetadata(metadata)); } bool nsDisplayScrollInfoLayer::UpdateScrollData( mozilla::layers::WebRenderScrollData* aData, mozilla::layers::WebRenderLayerScrollData* aLayerData) { if (aLayerData) { UniquePtr 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* 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* 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 nsDisplayAsyncZoom::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { PresShell* presShell = mFrame->PresShell(); ContainerLayerParameters containerParameters( presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(), aContainerParameters); RefPtr 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::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 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( 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 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 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 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 #endif /* HitTest does some fun stuff with matrix transforms to obtain the answer. */ void nsDisplayTransform::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray* aOutFrames) { if (aState->mInPreserves3D) { GetChildren()->HitTest(aBuilder, aRect, aState, aOutFrames); return; } /* Here's how this works: * 1. Get the matrix. If it's singular, abort (clearly we didn't hit * anything). * 2. Invert the matrix. * 3. Use it to transform the rect into the correct space. * 4. Pass that rect down through to the list's version of HitTest. */ // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); if (!IsFrameVisible(mFrame, matrix)) { return; } /* 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(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(clock()), dynamic_cast(aOutFrames.ElementAt(0))); printf("=== end of hit test ===\n"); #endif } float nsDisplayTransform::GetHitDepthAtPoint(nsDisplayListBuilder* aBuilder, const nsPoint& aPoint) { // GetTransform always operates in dev pixels. float factor = mFrame->PresContext()->AppUnitsPerDevPixel(); Matrix4x4 matrix = GetAccumulatedPreserved3DTransform(aBuilder); NS_ASSERTION(IsFrameVisible(mFrame, matrix), "We can't have hit a frame that isn't visible!"); Matrix4x4 inverse = matrix; inverse.Invert(); Point4D point = inverse.ProjectPoint(Point(NSAppUnitsToFloatPixels(aPoint.x, factor), NSAppUnitsToFloatPixels(aPoint.y, factor))); Point point2d = point.As2DPoint(); Point3D transformed = matrix.TransformPoint(Point3D(point2d.x, point2d.y, 0)); return transformed.z; } /* The transform is opaque iff the transform consists solely of scales and * translations and if the underlying content is opaque. Thus if the transform * is of the form * * |a c e| * |b d f| * |0 0 1| * * We need b and c to be zero. * * We also need to check whether the underlying opaque content completely fills * our visible rect. We use UntransformRect which expands to the axis-aligned * bounding rect, but that's OK since if * mStoredList.GetVisibleRect().Contains(untransformedVisible), then it * certainly contains the actual (non-axis-aligned) untransformed rect. */ nsRegion nsDisplayTransform::GetOpaqueRegion(nsDisplayListBuilder* aBuilder, bool* aSnap) const { *aSnap = false; nsRect untransformedVisible; if (!UntransformBuildingRect(aBuilder, &untransformedVisible)) { return nsRegion(); } const Matrix4x4Flagged& matrix = GetTransform(); Matrix matrix2d; if (!matrix.Is2D(&matrix2d) || !matrix2d.PreservesAxisAlignedRectangles()) { return nsRegion(); } nsRegion result; bool tmpSnap; const nsRect bounds = GetUntransformedBounds(aBuilder, &tmpSnap); const nsRegion opaque = ::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 nsDisplayPerspective::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { float appUnitsPerPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); Matrix4x4 perspectiveMatrix; DebugOnly hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( mFrame, appUnitsPerPixel, perspectiveMatrix); MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); /* * ClipListToRange can remove our child after we were created. */ if (!GetChildren()->GetTop()) { return 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(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 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 hasPerspective = nsDisplayTransform::ComputePerspectiveMatrix( mFrame, appUnitsPerPixel, perspectiveMatrix); MOZ_ASSERT(hasPerspective, "Why did we create nsDisplayPerspective?"); /* * ClipListToRange can remove our child after we were created. */ if (!GetChildren()->GetTop()) { return false; } /* * The resulting matrix is still in the coordinate space of the transformed * frame. Append a translation to the reference frame coordinates. */ nsDisplayTransform* transform = static_cast(GetChildren()->GetTop()); Point3D newOrigin = Point3D(NSAppUnitsToFloatPixels(transform->ToReferenceFrame().x, appUnitsPerPixel), NSAppUnitsToFloatPixels(transform->ToReferenceFrame().y, appUnitsPerPixel), 0.0f); Point3D roundedOrigin(NS_round(newOrigin.x), NS_round(newOrigin.y), 0); perspectiveMatrix.PostTranslate(roundedOrigin); nsIFrame* perspectiveFrame = mFrame->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 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& 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(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(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 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(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(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(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(aGeometry); nsTextFrame* f = static_cast(mFrame); nsTextFrame::TextDecorations decorations; f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations); bool snap; const nsRect& newRect = geometry->mBounds; nsRect oldRect = GetBounds(aBuilder, &snap); if (decorations != geometry->mDecorations || mVisIStartEdge != geometry->mVisIStartEdge || mVisIEndEdge != geometry->mVisIEndEdge || !oldRect.IsEqualInterior(newRect) || !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) || mOpacity != geometry->mOpacity) { aInvalidRegion->Or(oldRect, newRect); } } void nsDisplayText::WriteDebugInfo(std::stringstream& aStream) { #ifdef DEBUG aStream << " (\""; nsTextFrame* f = static_cast(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* 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(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(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 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 nsDisplayMasksAndClipPaths::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { if (!IsValidMask()) { return nullptr; } RefPtr 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(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& 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 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 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 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 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 clip = CreateWRClipPathAndMasks( this, bounds, aResources, aBuilder, aSc, aManager, aDisplayListBuilder); Maybe layer; const StackingContextHelper* sc = &aSc; if (clip) { // Create a new stacking context to attach the mask to, ensuring the mask is // applied to the aggregate, and not the individual elements. // The stacking context shouldn't have any offset. bounds.MoveTo(0, 0); Maybe opacity = 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 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 masks; // XXX check return value? SVGObserverUtils::GetAndObserveMasks(firstFrame, &masks); if (!masks.IsEmpty() && masks[0]) { if (!first) { aTo += ", "; } aTo += "mask"; } aTo += ")"; } #endif already_AddRefed nsDisplayBackdropRootContainer::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { RefPtr 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 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 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 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(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 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 = 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 = aBuilder->GetWidgetLayerManager(); if (layerManager && layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { return false; } return true; } already_AddRefed nsDisplaySVGWrapper::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { ContainerLayerParameters newContainerParameters = aContainerParameters; newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; RefPtr 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 = 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 = aBuilder->GetWidgetLayerManager(); if (layerManager && layerManager->GetBackendType() == layers::LayersBackend::LAYERS_WR) { return false; } return true; } already_AddRefed nsDisplayForeignObject::BuildLayer( nsDisplayListBuilder* aBuilder, LayerManager* aManager, const ContainerLayerParameters& aContainerParameters) { ContainerLayerParameters newContainerParameters = aContainerParameters; newContainerParameters.mDisableSubpixelAntialiasingInDescendants = true; RefPtr 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 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::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(totalMs)); // Helpers for recording large/small paints. auto recordLarge = [=](const nsCString& aKey, double aDurationMs) -> void { MOZ_ASSERT(aDurationMs <= totalMs); uint32_t amount = static_cast((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((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; }