/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- * 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/. */ #include "mozilla/DebugOnly.h" #include "FrameLayerBuilder.h" #include "mozilla/LookAndFeel.h" #include "mozilla/Maybe.h" #include "mozilla/dom/ProfileTimelineMarkerBinding.h" #include "mozilla/gfx/Matrix.h" #include "ActiveLayerTracker.h" #include "BasicLayers.h" #include "DisplayItemScrollClip.h" #include "ImageContainer.h" #include "ImageLayers.h" #include "LayerTreeInvalidation.h" #include "Layers.h" #include "LayerUserData.h" #include "MaskLayerImageCache.h" #include "UnitTransforms.h" #include "Units.h" #include "gfx2DGlue.h" #include "gfxEnv.h" #include "gfxUtils.h" #include "nsAutoPtr.h" #include "nsAnimationManager.h" #include "nsDisplayList.h" #include "nsDocShell.h" #include "nsIScrollableFrame.h" #include "nsImageFrame.h" #include "nsLayoutUtils.h" #include "nsPresContext.h" #include "nsPrintfCString.h" #include "nsRenderingContext.h" #include "nsSVGIntegrationUtils.h" #include "nsTransitionManager.h" #include "mozilla/LayerTimelineMarker.h" #include "mozilla/EffectCompositor.h" #include "mozilla/Move.h" #include "mozilla/ReverseIterator.h" #include "mozilla/gfx/2D.h" #include "mozilla/gfx/Tools.h" #include "mozilla/layers/ShadowLayers.h" #include "mozilla/layers/TextureClient.h" #include "mozilla/layers/TextureWrapperImage.h" #include "mozilla/Unused.h" #include "GeckoProfiler.h" #include "LayersLogging.h" #include "gfxPrefs.h" #include <algorithm> using namespace mozilla::layers; using namespace mozilla::gfx; namespace mozilla { class PaintedDisplayItemLayerUserData; static nsTHashtable<nsPtrHashKey<FrameLayerBuilder::DisplayItemData>>* sAliveDisplayItemDatas; /** * The address of gPaintedDisplayItemLayerUserData is used as the user * data key for PaintedLayers created by FrameLayerBuilder. * It identifies PaintedLayers used to draw non-layer content, which are * therefore eligible for recycling. We want display items to be able to * create their own dedicated PaintedLayers in BuildLayer, if necessary, * and we wouldn't want to accidentally recycle those. * The user data is a PaintedDisplayItemLayerUserData. */ uint8_t gPaintedDisplayItemLayerUserData; /** * The address of gColorLayerUserData is used as the user * data key for ColorLayers created by FrameLayerBuilder. * The user data is null. */ uint8_t gColorLayerUserData; /** * The address of gImageLayerUserData is used as the user * data key for ImageLayers created by FrameLayerBuilder. * The user data is null. */ uint8_t gImageLayerUserData; /** * The address of gLayerManagerUserData is used as the user * data key for retained LayerManagers managed by FrameLayerBuilder. * The user data is a LayerManagerData. */ uint8_t gLayerManagerUserData; /** * The address of gMaskLayerUserData is used as the user * data key for mask layers managed by FrameLayerBuilder. * The user data is a MaskLayerUserData. */ uint8_t gMaskLayerUserData; /** * The address of gCSSMaskLayerUserData is used as the user * data key for mask layers of css masking managed by FrameLayerBuilder. * The user data is a CSSMaskLayerUserData. */ uint8_t gCSSMaskLayerUserData; // a global cache of image containers used for mask layers static MaskLayerImageCache* gMaskLayerImageCache = nullptr; static inline MaskLayerImageCache* GetMaskLayerImageCache() { if (!gMaskLayerImageCache) { gMaskLayerImageCache = new MaskLayerImageCache(); } return gMaskLayerImageCache; } FrameLayerBuilder::FrameLayerBuilder() : mRetainingManager(nullptr) , mDetectedDOMModification(false) , mInvalidateAllLayers(false) , mInLayerTreeCompressionMode(false) , mContainerLayerGeneration(0) , mMaxContainerLayerGeneration(0) { MOZ_COUNT_CTOR(FrameLayerBuilder); } FrameLayerBuilder::~FrameLayerBuilder() { GetMaskLayerImageCache()->Sweep(); MOZ_COUNT_DTOR(FrameLayerBuilder); } FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey, Layer* aLayer, nsIFrame* aFrame) : mParent(aParent) , mLayer(aLayer) , mDisplayItemKey(aKey) , mItem(nullptr) , mUsed(true) , mIsInvalid(false) { MOZ_COUNT_CTOR(FrameLayerBuilder::DisplayItemData); if (!sAliveDisplayItemDatas) { sAliveDisplayItemDatas = new nsTHashtable<nsPtrHashKey<FrameLayerBuilder::DisplayItemData>>(); } MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this)); sAliveDisplayItemDatas->PutEntry(this); MOZ_RELEASE_ASSERT(mLayer); if (aFrame) { AddFrame(aFrame); } } void FrameLayerBuilder::DisplayItemData::AddFrame(nsIFrame* aFrame) { MOZ_RELEASE_ASSERT(mLayer); mFrameList.AppendElement(aFrame); nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty()); if (!array) { array = new nsTArray<DisplayItemData*>(); aFrame->Properties().Set(FrameLayerBuilder::LayerManagerDataProperty(), array); } array->AppendElement(this); } void FrameLayerBuilder::DisplayItemData::RemoveFrame(nsIFrame* aFrame) { MOZ_RELEASE_ASSERT(mLayer); bool result = mFrameList.RemoveElement(aFrame); MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!"); nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty()); MOZ_RELEASE_ASSERT(array, "Must be already stored on the frame!"); array->RemoveElement(this); } void FrameLayerBuilder::DisplayItemData::EndUpdate() { MOZ_RELEASE_ASSERT(mLayer); MOZ_ASSERT(!mItem); mIsInvalid = false; mUsed = false; } void FrameLayerBuilder::DisplayItemData::EndUpdate(nsAutoPtr<nsDisplayItemGeometry> aGeometry) { MOZ_RELEASE_ASSERT(mLayer); MOZ_ASSERT(mItem); MOZ_ASSERT(mGeometry || aGeometry); if (aGeometry) { mGeometry = aGeometry; } mClip = mItem->GetClip(); mFrameListChanges.Clear(); mItem = nullptr; EndUpdate(); } void FrameLayerBuilder::DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState, uint32_t aContainerLayerGeneration, nsDisplayItem* aItem /* = nullptr */) { MOZ_RELEASE_ASSERT(mLayer); MOZ_RELEASE_ASSERT(aLayer); mLayer = aLayer; mOptLayer = nullptr; mInactiveManager = nullptr; mLayerState = aState; mContainerLayerGeneration = aContainerLayerGeneration; mUsed = true; if (aLayer->AsPaintedLayer()) { mItem = aItem; } if (!aItem) { return; } // We avoid adding or removing element unnecessarily // since we have to modify userdata each time AutoTArray<nsIFrame*, 4> copy(mFrameList); if (!copy.RemoveElement(aItem->Frame())) { AddFrame(aItem->Frame()); mFrameListChanges.AppendElement(aItem->Frame()); } AutoTArray<nsIFrame*,4> mergedFrames; aItem->GetMergedFrames(&mergedFrames); for (uint32_t i = 0; i < mergedFrames.Length(); ++i) { if (!copy.RemoveElement(mergedFrames[i])) { AddFrame(mergedFrames[i]); mFrameListChanges.AppendElement(mergedFrames[i]); } } for (uint32_t i = 0; i < copy.Length(); i++) { RemoveFrame(copy[i]); mFrameListChanges.AppendElement(copy[i]); } } static const nsIFrame* sDestroyedFrame = nullptr; FrameLayerBuilder::DisplayItemData::~DisplayItemData() { MOZ_COUNT_DTOR(FrameLayerBuilder::DisplayItemData); MOZ_RELEASE_ASSERT(mLayer); for (uint32_t i = 0; i < mFrameList.Length(); i++) { nsIFrame* frame = mFrameList[i]; if (frame == sDestroyedFrame) { continue; } nsTArray<DisplayItemData*> *array = reinterpret_cast<nsTArray<DisplayItemData*>*>(frame->Properties().Get(LayerManagerDataProperty())); array->RemoveElement(this); } MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas && sAliveDisplayItemDatas->Contains(this)); sAliveDisplayItemDatas->RemoveEntry(this); if (sAliveDisplayItemDatas->Count() == 0) { delete sAliveDisplayItemDatas; sAliveDisplayItemDatas = nullptr; } } void FrameLayerBuilder::DisplayItemData::ClearAnimationCompositorState() { if (mDisplayItemKey != nsDisplayItem::TYPE_TRANSFORM && mDisplayItemKey != nsDisplayItem::TYPE_OPACITY) { return; } for (nsIFrame* frame : mFrameList) { nsCSSPropertyID prop = mDisplayItemKey == nsDisplayItem::TYPE_TRANSFORM ? eCSSProperty_transform : eCSSProperty_opacity; EffectCompositor::ClearIsRunningOnCompositor(frame, prop); } } const nsTArray<nsIFrame*>& FrameLayerBuilder::DisplayItemData::GetFrameListChanges() { return mFrameListChanges; } /** * This is the userdata we associate with a layer manager. */ class LayerManagerData : public LayerUserData { public: explicit LayerManagerData(LayerManager *aManager) : mLayerManager(aManager) #ifdef DEBUG_DISPLAY_ITEM_DATA , mParent(nullptr) #endif , mInvalidateAllLayers(false) { MOZ_COUNT_CTOR(LayerManagerData); } ~LayerManagerData() { MOZ_COUNT_DTOR(LayerManagerData); } #ifdef DEBUG_DISPLAY_ITEM_DATA void Dump(const char *aPrefix = "") { printf_stderr("%sLayerManagerData %p\n", aPrefix, this); for (auto iter = mDisplayItems.Iter(); !iter.Done(); iter.Next()) { FrameLayerBuilder::DisplayItemData* data = iter.Get()->GetKey(); nsAutoCString prefix; prefix += aPrefix; prefix += " "; const char* layerState; switch (data->mLayerState) { case LAYER_NONE: layerState = "LAYER_NONE"; break; case LAYER_INACTIVE: layerState = "LAYER_INACTIVE"; break; case LAYER_ACTIVE: layerState = "LAYER_ACTIVE"; break; case LAYER_ACTIVE_FORCE: layerState = "LAYER_ACTIVE_FORCE"; break; case LAYER_ACTIVE_EMPTY: layerState = "LAYER_ACTIVE_EMPTY"; break; case LAYER_SVG_EFFECTS: layerState = "LAYER_SVG_EFFECTS"; break; } uint32_t mask = (1 << nsDisplayItem::TYPE_BITS) - 1; nsAutoCString str; str += prefix; str += nsPrintfCString("Frame %p ", data->mFrameList[0]); str += nsDisplayItem::DisplayItemTypeName(static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask)); if ((data->mDisplayItemKey >> nsDisplayItem::TYPE_BITS)) { str += nsPrintfCString("(%i)", data->mDisplayItemKey >> nsDisplayItem::TYPE_BITS); } str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get()); if (data->mOptLayer) { str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get()); } if (data->mInactiveManager) { str += nsPrintfCString(", InactiveLayerManager %p", data->mInactiveManager.get()); } str += "\n"; printf_stderr("%s", str.get()); if (data->mInactiveManager) { prefix += " "; printf_stderr("%sDumping inactive layer info:\n", prefix.get()); LayerManagerData* lmd = static_cast<LayerManagerData*> (data->mInactiveManager->GetUserData(&gLayerManagerUserData)); lmd->Dump(prefix.get()); } } } #endif /** * Tracks which frames have layers associated with them. */ LayerManager *mLayerManager; #ifdef DEBUG_DISPLAY_ITEM_DATA LayerManagerData *mParent; #endif nsTHashtable<nsRefPtrHashKey<FrameLayerBuilder::DisplayItemData> > mDisplayItems; bool mInvalidateAllLayers; }; /* static */ void FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame) { FrameProperties props = aFrame->Properties(); props.Delete(LayerManagerDataProperty()); } struct AssignedDisplayItem { AssignedDisplayItem(nsDisplayItem* aItem, const DisplayItemClip& aClip, LayerState aLayerState) : mItem(aItem) , mClip(aClip) , mLayerState(aLayerState) {} nsDisplayItem* mItem; DisplayItemClip mClip; LayerState mLayerState; }; /** * We keep a stack of these to represent the PaintedLayers that are * currently available to have display items added to. * We use a stack here because as much as possible we want to * assign display items to existing PaintedLayers, and to the lowest * PaintedLayer in z-order. This reduces the number of layers and * makes it more likely a display item will be rendered to an opaque * layer, giving us the best chance of getting subpixel AA. */ class PaintedLayerData { public: PaintedLayerData() : mAnimatedGeometryRoot(nullptr), mScrollClip(nullptr), mReferenceFrame(nullptr), mLayer(nullptr), mSolidColor(NS_RGBA(0, 0, 0, 0)), mIsSolidColorInVisibleRegion(false), mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)), mSingleItemFixedToViewport(false), mNeedComponentAlpha(false), mForceTransparentSurface(false), mHideAllLayersBelow(false), mOpaqueForAnimatedGeometryRootParent(false), mDisableFlattening(false), mBackfaceHidden(false), mImage(nullptr), mCommonClipCount(-1), mNewChildLayersIndex(-1) {} #ifdef MOZ_DUMP_PAINTING /** * Keep track of important decisions for debugging. */ nsCString mLog; #define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \ if (gfxPrefs::LayersDumpDecision()) { \ pld->mLog.AppendPrintf("\t\t\t\t"); \ pld->mLog.AppendPrintf(__VA_ARGS__); \ } #else #define FLB_LOG_PAINTED_LAYER_DECISION(...) #endif /** * Record that an item has been added to the PaintedLayer, so we * need to update our regions. * @param aVisibleRect the area of the item that's visible * @param aSolidColor if non-null, the visible area of the item is * a constant color given by *aSolidColor */ void Accumulate(ContainerState* aState, nsDisplayItem* aItem, const nsIntRegion& aClippedOpaqueRegion, const nsIntRect& aVisibleRect, const DisplayItemClip& aClip, LayerState aLayerState); AnimatedGeometryRoot* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; } /** * A region including the horizontal pan, vertical pan, and no action regions. */ nsRegion CombinedTouchActionRegion(); /** * Add the given hit regions to the hit regions to the hit retions for this * PaintedLayer. */ void AccumulateEventRegions(ContainerState* aState, nsDisplayLayerEventRegions* aEventRegions); /** * If this represents only a nsDisplayImage, and the image type supports being * optimized to an ImageLayer, returns true. */ bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder); /** * If this represents only a nsDisplayImage, and the image type supports being * optimized to an ImageLayer, returns an ImageContainer for the underlying * image if one is available. */ already_AddRefed<ImageContainer> GetContainerForImageLayer(nsDisplayListBuilder* aBuilder); bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const { return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty(); } bool VisibleRegionIntersects(const nsIntRegion& aRegion) const { return !mVisibleRegion.Intersect(aRegion).IsEmpty(); } /** * The region of visible content in the layer, relative to the * container layer (which is at the snapped top-left of the display * list reference frame). */ nsIntRegion mVisibleRegion; /** * The region of visible content in the layer that is opaque. * Same coordinate system as mVisibleRegion. */ nsIntRegion mOpaqueRegion; /** * The definitely-hit region for this PaintedLayer. */ nsRegion mHitRegion; /** * The maybe-hit region for this PaintedLayer. */ nsRegion mMaybeHitRegion; /** * The dispatch-to-content hit region for this PaintedLayer. */ nsRegion mDispatchToContentHitRegion; /** * The region for this PaintedLayer that is sensitive to events * but disallows panning and zooming. This is an approximation * and any deviation from the true region will be part of the * mDispatchToContentHitRegion. */ nsRegion mNoActionRegion; /** * The region for this PaintedLayer that is sensitive to events and * allows horizontal panning but not zooming. This is an approximation * and any deviation from the true region will be part of the * mDispatchToContentHitRegion. */ nsRegion mHorizontalPanRegion; /** * The region for this PaintedLayer that is sensitive to events and * allows vertical panning but not zooming. This is an approximation * and any deviation from the true region will be part of the * mDispatchToContentHitRegion. */ nsRegion mVerticalPanRegion; /** * Scaled versions of the bounds of mHitRegion and mMaybeHitRegion. * We store these because FindPaintedLayerFor() needs to consume them * in this form, and it's a hot code path so we don't want to scale * them inside that function. */ nsIntRect mScaledHitRegionBounds; nsIntRect mScaledMaybeHitRegionBounds; /** * The "active scrolled root" for all content in the layer. Must * be non-null; all content in a PaintedLayer must have the same * active scrolled root. */ AnimatedGeometryRoot* mAnimatedGeometryRoot; /** * The scroll clip for this layer. */ const DisplayItemScrollClip* mScrollClip; /** * The offset between mAnimatedGeometryRoot and the reference frame. */ nsPoint mAnimatedGeometryRootOffset; /** * If non-null, the frame from which we'll extract "fixed positioning" * metadata for this layer. This can be a position:fixed frame or a viewport * frame; the latter case is used for background-attachment:fixed content. */ const nsIFrame* mReferenceFrame; PaintedLayer* mLayer; /** * If mIsSolidColorInVisibleRegion is true, this is the color of the visible * region. */ nscolor mSolidColor; /** * True if every pixel in mVisibleRegion will have color mSolidColor. */ bool mIsSolidColorInVisibleRegion; /** * The target background color for smoothing fonts that are drawn on top of * transparent parts of the layer. */ nscolor mFontSmoothingBackgroundColor; /** * True if the layer contains exactly one item that returned true for * ShouldFixToViewport. */ bool mSingleItemFixedToViewport; /** * True if there is any text visible in the layer that's over * transparent pixels in the layer. */ bool mNeedComponentAlpha; /** * Set if the layer should be treated as transparent, even if its entire * area is covered by opaque display items. For example, this needs to * be set if something is going to "punch holes" in the layer by clearing * part of its surface. */ bool mForceTransparentSurface; /** * Set if all layers below this PaintedLayer should be hidden. */ bool mHideAllLayersBelow; /** * Set if the opaque region for this layer can be applied to the parent * animated geometry root of this layer's animated geometry root. * We set this when a PaintedLayer's animated geometry root is a scrollframe * and the PaintedLayer completely fills the displayport of the scrollframe. */ bool mOpaqueForAnimatedGeometryRootParent; /** * Set if there is content in the layer that must avoid being flattened. */ bool mDisableFlattening; /** * Set if the backface of this region is hidden to the user. * Content that backface is hidden should not be draw on the layer * with visible backface. */ bool mBackfaceHidden; /** * Stores the pointer to the nsDisplayImage if we want to * convert this to an ImageLayer. */ nsDisplayImageContainer* mImage; /** * Stores the clip that we need to apply to the image or, if there is no * image, a clip for SOME item in the layer. There is no guarantee which * item's clip will be stored here and mItemClip should not be used to clip * the whole layer - only some part of the clip should be used, as determined * by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even be * no part at all. */ DisplayItemClip mItemClip; /** * The first mCommonClipCount rounded rectangle clips are identical for * all items in the layer. * -1 if there are no items in the layer; must be >=0 by the time that this * data is popped from the stack. */ int32_t mCommonClipCount; /** * Index of this layer in mNewChildLayers. */ int32_t mNewChildLayersIndex; /* * Updates mCommonClipCount by checking for rounded rect clips in common * between the clip on a new item (aCurrentClip) and the common clips * on items already in the layer (the first mCommonClipCount rounded rects * in mItemClip). */ void UpdateCommonClipCount(const DisplayItemClip& aCurrentClip); /** * The union of all the bounds of the display items in this layer. */ nsIntRect mBounds; /** * The region of visible content above the layer and below the * next PaintedLayerData currently in the stack, if any. * This is a conservative approximation: it contains the true region. */ nsIntRegion mVisibleAboveRegion; /** * All the display items that have been assigned to this painted layer. * These items get added by Accumulate(). */ nsTArray<AssignedDisplayItem> mAssignedDisplayItems; }; struct NewLayerEntry { NewLayerEntry() : mAnimatedGeometryRoot(nullptr) , mScrollClip(nullptr) , mLayerContentsVisibleRect(0, 0, -1, -1) , mLayerState(LAYER_INACTIVE) , mHideAllLayersBelow(false) , mOpaqueForAnimatedGeometryRootParent(false) , mPropagateComponentAlphaFlattening(true) , mUntransformedVisibleRegion(false) {} // mLayer is null if the previous entry is for a PaintedLayer that hasn't // been optimized to some other form (yet). RefPtr<Layer> mLayer; AnimatedGeometryRoot* mAnimatedGeometryRoot; const DisplayItemScrollClip* mScrollClip; // If non-null, this ScrollMetadata is set to the be the first ScrollMetadata // on the layer. UniquePtr<ScrollMetadata> mBaseScrollMetadata; // The following are only used for retained layers (for occlusion // culling of those layers). These regions are all relative to the // container reference frame. nsIntRegion mVisibleRegion; nsIntRegion mOpaqueRegion; // This rect is in the layer's own coordinate space. The computed visible // region for the layer cannot extend beyond this rect. nsIntRect mLayerContentsVisibleRect; LayerState mLayerState; bool mHideAllLayersBelow; // When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of // this layer is opaque in the same position even subject to the animation of // geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot // is a scrolled frame and the scrolled content is opaque everywhere in the // displayport, we can set this flag. // When this flag is set, we can treat this opaque region as covering // content whose animated geometry root is the animated geometry root for // mAnimatedGeometryRoot->GetParent(). bool mOpaqueForAnimatedGeometryRootParent; // If true, then the content flags for this layer should contribute // to our decision to flatten component alpha layers, false otherwise. bool mPropagateComponentAlphaFlattening; // mVisibleRegion is relative to the associated frame before // transform. bool mUntransformedVisibleRegion; }; class PaintedLayerDataTree; /** * This is tree node type for PaintedLayerDataTree. * Each node corresponds to a different animated geometry root, and contains * a stack of PaintedLayerDatas, in bottom-to-top order. * There is at most one node per animated geometry root. The ancestor and * descendant relations in PaintedLayerDataTree tree mirror those in the frame * tree. * Each node can have clip that describes the potential extents that items in * this node can cover. If mHasClip is false, it means that the node's contents * can move anywhere. * Testing against the clip instead of the node's actual contents has the * advantage that the node's contents can move or animate without affecting * content in other nodes. So we don't need to re-layerize during animations * (sync or async), and during async animations everything is guaranteed to * look correct. * The contents of a node's PaintedLayerData stack all share the node's * animated geometry root. The child nodes are on top of the PaintedLayerData * stack, in z-order, and the clip rects of the child nodes are allowed to * intersect with the visible region or visible above region of their parent * node's PaintedLayerDatas. */ class PaintedLayerDataNode { public: PaintedLayerDataNode(PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent, AnimatedGeometryRoot* aAnimatedGeometryRoot); ~PaintedLayerDataNode(); AnimatedGeometryRoot* GetAnimatedGeometryRoot() const { return mAnimatedGeometryRoot; } /** * Whether this node's contents can potentially intersect aRect. * aRect is in our tree's ContainerState's coordinate space. */ bool Intersects(const nsIntRect& aRect) const { return !mHasClip || mClipRect.Intersects(aRect); } /** * Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our * children, and return it. */ PaintedLayerDataNode* AddChildNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot); /** * Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be * added to. Creates a new PaintedLayerData by calling * aNewPaintedLayerCallback if necessary. */ template<typename NewPaintedLayerCallbackType> PaintedLayerData* FindPaintedLayerFor(const nsIntRect& aVisibleRect, bool aBackfaceHidden, const DisplayItemScrollClip* aScrollClip, NewPaintedLayerCallbackType aNewPaintedLayerCallback); /** * Find an opaque background color for aRegion. Pulls a color from the parent * geometry root if appropriate, but only if that color is present underneath * the whole clip of this node, so that this node's contents can animate or * move (possibly async) without having to change the background color. * @param aUnderIndex Searching will start in mPaintedLayerDataStack right * below aUnderIndex. */ enum { ABOVE_TOP = -1 }; nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion, int32_t aUnderIndex = ABOVE_TOP) const; /** * Same as FindOpaqueBackgroundColor, but only returns a color if absolutely * nothing is in between, so that it can be used for a layer that can move * anywhere inside our clip. */ nscolor FindOpaqueBackgroundColorCoveringEverything() const; /** * Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion, * or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty. */ void AddToVisibleAboveRegion(const nsIntRect& aRect); /** * Call this if all of our existing content can potentially be covered, so * nothing can merge with it and all new content needs to create new items * on top. This will finish all of our children and pop our whole * mPaintedLayerDataStack. */ void SetAllDrawingAbove(); /** * Finish this node: Finish all children, finish our PaintedLayer contents, * and (if requested) adjust our parent's visible above region to include * our clip. */ void Finish(bool aParentNeedsAccurateVisibleAboveRegion); /** * Finish any children that intersect aRect. */ void FinishChildrenIntersecting(const nsIntRect& aRect); /** * Finish all children. */ void FinishAllChildren() { FinishAllChildren(true); } protected: /** * Finish the topmost item in mPaintedLayerDataStack and pop it from the * stack. */ void PopPaintedLayerData(); /** * Finish all items in mPaintedLayerDataStack and clear the stack. */ void PopAllPaintedLayerData(); /** * Finish all of our child nodes, but don't touch mPaintedLayerDataStack. */ void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion); /** * Pass off opaque background color searching to our parent node, if we have * one. */ nscolor FindOpaqueBackgroundColorInParentNode() const; PaintedLayerDataTree& mTree; PaintedLayerDataNode* mParent; AnimatedGeometryRoot* mAnimatedGeometryRoot; /** * Our contents: a PaintedLayerData stack and our child nodes. */ nsTArray<PaintedLayerData> mPaintedLayerDataStack; /** * UniquePtr is used here in the sense of "unique ownership", i.e. there is * only one owner. Not in the sense of "this is the only pointer to the * node": There are two other, non-owning, pointers to our child nodes: The * node's respective children point to their parent node with their mParent * pointer, and the tree keeps a map of animated geometry root to node in its * mNodes member. These outside pointers are the reason that mChildren isn't * just an nsTArray<PaintedLayerDataNode> (since the pointers would become * invalid whenever the array expands its capacity). */ nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren; /** * The region that's covered between our "background" and the bottom of * mPaintedLayerDataStack. This is used to indicate whether we can pull * a background color from our parent node. If mVisibleAboveBackgroundRegion * should be considered infinite, mAllDrawingAboveBackground will be true and * the value of mVisibleAboveBackgroundRegion will be meaningless. */ nsIntRegion mVisibleAboveBackgroundRegion; /** * Our clip, if we have any. If not, that means we can move anywhere, and * mHasClip will be false and mClipRect will be meaningless. */ nsIntRect mClipRect; bool mHasClip; /** * Whether mVisibleAboveBackgroundRegion should be considered infinite. */ bool mAllDrawingAboveBackground; }; class ContainerState; /** * A tree of PaintedLayerDataNodes. At any point in time, the tree only * contains nodes for animated geometry roots that new items can potentially * merge into. Any time content is added on top that overlaps existing things * in such a way that we no longer want to merge new items with some existing * content, that existing content gets "finished". * The public-facing methods of this class are FindPaintedLayerFor, * AddingOwnLayer, and Finish. The other public methods are for * PaintedLayerDataNode. * The tree calls out to its containing ContainerState for some things. * All coordinates / rects in the tree or the tree nodes are in the * ContainerState's coordinate space, i.e. relative to the reference frame and * in layer pixels. * The clip rects of sibling nodes never overlap. This is ensured by finishing * existing nodes before adding new ones, if this property were to be violated. * The root tree node doesn't get finished until the ContainerState is * finished. * The tree's root node is always the root reference frame of the builder. We * don't stop at the container state's mContainerAnimatedGeometryRoot because * some of our contents can have animated geometry roots that are not * descendants of the container's animated geometry root. Every animated * geometry root we encounter for our contents needs to have a defined place in * the tree. */ class PaintedLayerDataTree { public: PaintedLayerDataTree(ContainerState& aContainerState, nscolor& aBackgroundColor) : mContainerState(aContainerState) , mContainerUniformBackgroundColor(aBackgroundColor) {} ~PaintedLayerDataTree() { MOZ_ASSERT(!mRoot); MOZ_ASSERT(mNodes.Count() == 0); } /** * Notify our contents that some non-PaintedLayer content has been added. * *aRect needs to be a rectangle that doesn't move with respect to * aAnimatedGeometryRoot and that contains the added item. * If aRect is null, the extents will be considered infinite. * If aOutUniformBackgroundColor is non-null, it will be set to an opaque * color that can be pulled into the background of the added content, or * transparent if that is not possible. */ void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect, nscolor* aOutUniformBackgroundColor); /** * Find a PaintedLayerData for aItem. This can either be an existing * PaintedLayerData from inside a node in our tree, or a new one that gets * created by a call out to aNewPaintedLayerCallback. */ template<typename NewPaintedLayerCallbackType> PaintedLayerData* FindPaintedLayerFor(AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemScrollClip* aScrollClip, const nsIntRect& aVisibleRect, bool aBackfaceidden, NewPaintedLayerCallbackType aNewPaintedLayerCallback); /** * Finish everything. */ void Finish(); /** * Get the parent animated geometry root of aAnimatedGeometryRoot. * That's either aAnimatedGeometryRoot's animated geometry root, or, if * that's aAnimatedGeometryRoot itself, then it's the animated geometry * root for aAnimatedGeometryRoot's cross-doc parent frame. */ AnimatedGeometryRoot* GetParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot); /** * Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with * respect to aAnimatedGeometryRoot's parent animated geometry root. * If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's * scroll port, otherwise there is no clip. * This method doesn't have much to do with PaintedLayerDataTree, but this is * where we have easy access to a display list builder, which we use to get * the clip rect result into the right coordinate space. */ bool IsClippedWithRespectToParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip); /** * Called by PaintedLayerDataNode when it is finished, so that we can drop * our pointers to it. */ void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot); nsDisplayListBuilder* Builder() const; ContainerState& ContState() const { return mContainerState; } nscolor UniformBackgroundColor() const { return mContainerUniformBackgroundColor; } protected: /** * Finish all nodes that potentially intersect *aRect, where *aRect is a rect * that doesn't move with respect to aAnimatedGeometryRoot. * If aRect is null, *aRect will be considered infinite. */ void FinishPotentiallyIntersectingNodes(AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect); /** * Make sure that there is a node for aAnimatedGeometryRoot and all of its * ancestor geometry roots. Return the node for aAnimatedGeometryRoot. */ PaintedLayerDataNode* EnsureNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot); /** * Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot. * *aOutAncestorChild will be set to the last ancestor that was encountered * in the search up from aAnimatedGeometryRoot; it will be a child animated * geometry root of the result, if neither are null. */ PaintedLayerDataNode* FindNodeForAncestorAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot, AnimatedGeometryRoot** aOutAncestorChild); ContainerState& mContainerState; UniquePtr<PaintedLayerDataNode> mRoot; /** * The uniform opaque color from behind this container layer, or * NS_RGBA(0,0,0,0) if the background behind this container layer is not * uniform and opaque. This color can be pulled into PaintedLayers that are * directly above the background. */ nscolor mContainerUniformBackgroundColor; /** * A hash map for quick access the node belonging to a particular animated * geometry root. */ nsDataHashtable<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*> mNodes; }; /** * This is a helper object used to build up the layer children for * a ContainerLayer. */ class ContainerState { public: ContainerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager, FrameLayerBuilder* aLayerBuilder, nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, const nsRect& aContainerBounds, ContainerLayer* aContainerLayer, const ContainerLayerParameters& aParameters, bool aFlattenToSingleLayer, nscolor aBackgroundColor, const DisplayItemScrollClip* aContainerScrollClip) : mBuilder(aBuilder), mManager(aManager), mLayerBuilder(aLayerBuilder), mContainerFrame(aContainerFrame), mContainerLayer(aContainerLayer), mContainerBounds(aContainerBounds), mContainerScrollClip(aContainerScrollClip), mScrollClipForPerspectiveChild(aParameters.mScrollClipForPerspectiveChild), mParameters(aParameters), mPaintedLayerDataTree(*this, aBackgroundColor), mFlattenToSingleLayer(aFlattenToSingleLayer) { nsPresContext* presContext = aContainerFrame->PresContext(); mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); mContainerReferenceFrame = const_cast<nsIFrame*>(aContainerItem ? aContainerItem->ReferenceFrameForChildren() : mBuilder->FindReferenceFrameFor(mContainerFrame)); bool isAtRoot = !aContainerItem || (aContainerItem->Frame() == mBuilder->RootReferenceFrame()); MOZ_ASSERT_IF(isAtRoot, mContainerReferenceFrame == mBuilder->RootReferenceFrame()); mContainerAnimatedGeometryRoot = isAtRoot ? aBuilder->GetRootAnimatedGeometryRoot() : aContainerItem->GetAnimatedGeometryRoot(); MOZ_ASSERT(!mBuilder->IsPaintingToWindow() || nsLayoutUtils::IsAncestorFrameCrossDoc(mBuilder->RootReferenceFrame(), *mContainerAnimatedGeometryRoot)); // When AllowResidualTranslation is false, display items will be drawn // scaled with a translation by integer pixels, so we know how the snapping // will work. mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() && !mParameters.AllowResidualTranslation(); CollectOldLayers(); } /** * This is the method that actually walks a display list and builds * the child layers. */ void ProcessDisplayItems(nsDisplayList* aList); /** * This finalizes all the open PaintedLayers by popping every element off * mPaintedLayerDataStack, then sets the children of the container layer * to be all the layers in mNewChildLayers in that order and removes any * layers as children of the container that aren't in mNewChildLayers. * @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA, * set *aTextContentFlags to CONTENT_COMPONENT_ALPHA */ void Finish(uint32_t *aTextContentFlags, const nsIntRect& aContainerPixelBounds, nsDisplayList* aChildItems, bool* aHasComponentAlphaChildren); nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; } nsIntRect ScaleToNearestPixels(const nsRect& aRect) const { return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const { return aRegion.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIntRect ScaleToOutsidePixels(const nsRect& aRect, bool aSnap = false) const { if (aSnap && mSnappingEnabled) { return ScaleToNearestPixels(aRect); } return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const { if (aSnap && mSnappingEnabled) { return ScaleToNearestPixels(aRect); } return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, bool aSnap = false) const { if (aSnap && mSnappingEnabled) { return ScaleRegionToNearestPixels(aRegion); } return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, bool aSnap = false) const { if (aSnap && mSnappingEnabled) { return ScaleRegionToNearestPixels(aRegion); } return aRegion.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel); } nsIFrame* GetContainerFrame() const { return mContainerFrame; } nsDisplayListBuilder* Builder() const { return mBuilder; } /** * Check if we are currently inside an inactive layer. */ bool IsInInactiveLayer() const { return mLayerBuilder->GetContainingPaintedLayerData(); } /** * Sets aOuterVisibleRegion as aLayer's visible region. * @param aOuterVisibleRegion * is in the coordinate space of the container reference frame. * @param aLayerContentsVisibleRect, if non-null, is in the layer's own * coordinate system. * @param aOuterUntransformed is true if the given aOuterVisibleRegion * is already untransformed with the matrix of the layer. */ void SetOuterVisibleRegionForLayer(Layer* aLayer, const nsIntRegion& aOuterVisibleRegion, const nsIntRect* aLayerContentsVisibleRect = nullptr, bool aOuterUntransformed = false) const; /** * Try to determine whether the PaintedLayer aData has a single opaque color * covering aRect. If successful, return that color, otherwise return * NS_RGBA(0,0,0,0). * If aRect turns out not to intersect any content in the layer, * *aOutIntersectsLayer will be set to false. */ nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData, const nsIntRect& aRect, bool* aOutIntersectsLayer) const; /** * Indicate that we are done adding items to the PaintedLayer represented by * aData. Make sure that a real PaintedLayer exists for it, and set the final * visible region and opaque-content. */ template<typename FindOpaqueBackgroundColorCallbackType> void FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor); protected: friend class PaintedLayerData; LayerManager::PaintedLayerCreationHint GetLayerCreationHint(AnimatedGeometryRoot* aAnimatedGeometryRoot); /** * Creates a new PaintedLayer and sets up the transform on the PaintedLayer * to account for scrolling. */ already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData); /** * Find a PaintedLayer for recycling, recycle it and prepare it for use, or * return null if no suitable layer was found. */ already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem, const nsPoint& aTopLeft); /** * Recycle aLayer and do any necessary invalidation. */ PaintedDisplayItemLayerUserData* RecyclePaintedLayer(PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, bool& didResetScrollPositionForLayerPixelAlignment); /** * Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer: * Initialize aData, set up the layer's transform for scrolling, and * invalidate the layer for layer pixel alignment changes if necessary. */ void PreparePaintedLayerForUse(PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft, bool aDidResetScrollPositionForLayerPixelAlignment); /** * Attempt to prepare an ImageLayer based upon the provided PaintedLayerData. * Returns nullptr on failure. */ already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData); /** * Attempt to prepare a ColorLayer based upon the provided PaintedLayerData. * Returns nullptr on failure. */ already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData); /** * Grab the next recyclable ColorLayer, or create one if there are no * more recyclable ColorLayers. */ already_AddRefed<ColorLayer> CreateOrRecycleColorLayer(PaintedLayer* aPainted); /** * Grab the next recyclable ImageLayer, or create one if there are no * more recyclable ImageLayers. */ already_AddRefed<ImageLayer> CreateOrRecycleImageLayer(PaintedLayer* aPainted); /** * Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a * mask layer which has been used for aLayer before), or create one if such * a layer doesn't exist. * * Since mask layers can exist either on the layer directly, or as a side- * attachment to FrameMetrics (for ancestor scrollframe clips), we key the * recycle operation on both the originating layer and the mask layer's * index in the layer, if any. */ struct MaskLayerKey; already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey, mozilla::function<void(Layer* aLayer)> aSetUserData); /** * Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes them * available for recycling. */ void CollectOldLayers(); /** * If aItem used to belong to a PaintedLayer, invalidates the area of * aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area of * aItem in that layer. */ void InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer); /** * Returns true if aItem's opaque area (in aOpaque) covers the entire * scrollable area of its presshell. */ bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque); /** * Set FrameMetrics and scroll-induced clipping on aEntry's layer. */ void SetupScrollingMetadata(NewLayerEntry* aEntry); /** * Applies occlusion culling. * For each layer in mNewChildLayers, remove from its visible region the * opaque regions of the layers at higher z-index, but only if they have * the same animated geometry root and fixed-pos frame ancestor. * The opaque region for the child layers that share the same animated * geometry root as the container frame is returned in * *aOpaqueRegionForContainer. * * Also sets scroll metadata on the layers. */ void PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer); /** * Computes the snapped opaque area of aItem. Sets aList's opaque flag * if it covers the entire list bounds. Sets *aHideAllLayersBelow to true * this item covers the entire viewport so that all layers below are * permanently invisible. */ nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemClip& aClip, nsDisplayList* aList, bool* aHideAllLayersBelow, bool* aOpaqueForAnimatedGeometryRootParent); /** * Return a PaintedLayerData object that is initialized for a layer that * aItem will be assigned to. * @param aItem The item that is going to be added. * @param aVisibleRect The visible rect of the item. * @param aAnimatedGeometryRoot The item's animated geometry root. * @param aScrollClip The scroll clip for this PaintedLayer. * @param aTopLeft The offset between aAnimatedGeometryRoot and * the reference frame. * @param aShouldFixToViewport If true, aAnimatedGeometryRoot is the * viewport and we will be adding fixed-pos * metadata for this layer because the display * item returned true from ShouldFixToViewport. */ PaintedLayerData NewPaintedLayerData(nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemScrollClip* aScrollClip, const nsPoint& aTopLeft, bool aShouldFixToViewport); /* Build a mask layer to represent the clipping region. Will return null if * there is no clipping specified or a mask layer cannot be built. * Builds an ImageLayer for the appropriate backend; the mask is relative to * aLayer's visible region. * aLayer is the layer to be clipped. * relative to the container reference frame * aRoundedRectClipCount is used when building mask layers for PaintedLayers, * SetupMaskLayer will build a mask layer for only the first * aRoundedRectClipCount rounded rects in aClip */ void SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip, uint32_t aRoundedRectClipCount = UINT32_MAX); /** * If |aClip| has rounded corners, create a mask layer for them, and * add it to |aLayer|'s ancestor mask layers, returning an index into * the array of ancestor mask layers. Returns an empty Maybe if * |aClip| does not have rounded corners, or if no mask layer could * be created. */ Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer, const DisplayItemClip& aClip); /* * Create/find a mask layer with suitable size for aMaskItem to paint * css-positioned-masking onto. */ void SetupMaskLayerForCSSMask(Layer* aLayer, nsDisplayMask* aMaskItem); already_AddRefed<Layer> CreateMaskLayer( Layer *aLayer, const DisplayItemClip& aClip, const Maybe<size_t>& aForAncestorMaskLayer, uint32_t aRoundedRectClipCount = UINT32_MAX); bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList, AnimatedGeometryRoot **aAnimatedGeometryRoot); nsDisplayListBuilder* mBuilder; LayerManager* mManager; FrameLayerBuilder* mLayerBuilder; nsIFrame* mContainerFrame; nsIFrame* mContainerReferenceFrame; AnimatedGeometryRoot* mContainerAnimatedGeometryRoot; ContainerLayer* mContainerLayer; nsRect mContainerBounds; const DisplayItemScrollClip* mContainerScrollClip; const DisplayItemScrollClip* mScrollClipForPerspectiveChild; #ifdef DEBUG nsRect mAccumulatedChildBounds; #endif ContainerLayerParameters mParameters; /** * The region of PaintedLayers that should be invalidated every time * we recycle one. */ nsIntRegion mInvalidPaintedContent; PaintedLayerDataTree mPaintedLayerDataTree; /** * We collect the list of children in here. During ProcessDisplayItems, * the layers in this array either have mContainerLayer as their parent, * or no parent. * PaintedLayers have two entries in this array: the second one is used only if * the PaintedLayer is optimized away to a ColorLayer or ImageLayer. * It's essential that this array is only appended to, since PaintedLayerData * records the index of its PaintedLayer in this array. */ typedef AutoTArray<NewLayerEntry,1> AutoLayersArray; AutoLayersArray mNewChildLayers; nsTHashtable<nsRefPtrHashKey<PaintedLayer>> mPaintedLayersAvailableForRecycling; nscoord mAppUnitsPerDevPixel; bool mSnappingEnabled; bool mFlattenToSingleLayer; struct MaskLayerKey { MaskLayerKey() : mLayer(nullptr) {} MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex) : mLayer(aLayer), mAncestorIndex(aAncestorIndex) {} PLDHashNumber Hash() const { // Hash the layer and add the layer index to the hash. return (NS_PTR_TO_UINT32(mLayer) >> 2) + (mAncestorIndex ? (*mAncestorIndex + 1) : 0); } bool operator ==(const MaskLayerKey& aOther) const { return mLayer == aOther.mLayer && mAncestorIndex == aOther.mAncestorIndex; } Layer* mLayer; Maybe<size_t> mAncestorIndex; }; nsDataHashtable<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>> mRecycledMaskImageLayers; }; class PaintedDisplayItemLayerUserData : public LayerUserData { public: PaintedDisplayItemLayerUserData() : mMaskClipCount(0), mForcedBackgroundColor(NS_RGBA(0,0,0,0)), mFontSmoothingBackgroundColor(NS_RGBA(0,0,0,0)), mXScale(1.f), mYScale(1.f), mAppUnitsPerDevPixel(0), mTranslation(0, 0), mAnimatedGeometryRootPosition(0, 0) {} /** * Record the number of clips in the PaintedLayer's mask layer. * Should not be reset when the layer is recycled since it is used to track * changes in the use of mask layers. */ uint32_t mMaskClipCount; /** * A color that should be painted over the bounds of the layer's visible * region before any other content is painted. */ nscolor mForcedBackgroundColor; /** * The target background color for smoothing fonts that are drawn on top of * transparent parts of the layer. */ nscolor mFontSmoothingBackgroundColor; /** * The resolution scale used. */ float mXScale, mYScale; /** * The appunits per dev pixel for the items in this layer. */ nscoord mAppUnitsPerDevPixel; /** * The offset from the PaintedLayer's 0,0 to the * reference frame. This isn't necessarily the same as the transform * set on the PaintedLayer since we might also be applying an extra * offset specified by the parent ContainerLayer/ */ nsIntPoint mTranslation; /** * We try to make 0,0 of the PaintedLayer be the top-left of the * border-box of the "active scrolled root" frame (i.e. the nearest ancestor * frame for the display items that is being actively scrolled). But * we force the PaintedLayer transform to be an integer translation, and we may * have a resolution scale, so we have to snap the PaintedLayer transform, so * 0,0 may not be exactly the top-left of the active scrolled root. Here we * store the coordinates in PaintedLayer space of the top-left of the * active scrolled root. */ gfxPoint mAnimatedGeometryRootPosition; nsIntRegion mRegionToInvalidate; // The offset between the active scrolled root of this layer // and the root of the container for the previous and current // paints respectively. nsPoint mLastAnimatedGeometryRootOrigin; nsPoint mAnimatedGeometryRootOrigin; RefPtr<ColorLayer> mColorLayer; RefPtr<ImageLayer> mImageLayer; // The region for which display item visibility for this layer has already // been calculated. Used to reduce the number of calls to // RecomputeVisibilityForItems if it is known in advance that a larger // region will be painted during a transaction than in a single call to // DrawPaintedLayer, for example when progressive paint is enabled. nsIntRegion mVisibilityComputedRegion; }; /* * User data for layers which will be used as masks. */ struct MaskLayerUserData : public LayerUserData { MaskLayerUserData() : mScaleX(-1.0f) , mScaleY(-1.0f) , mAppUnitsPerDevPixel(-1) { } MaskLayerUserData(const DisplayItemClip& aClip, uint32_t aRoundedRectClipCount, int32_t aAppUnitsPerDevPixel, const ContainerLayerParameters& aParams) : mScaleX(aParams.mXScale) , mScaleY(aParams.mYScale) , mOffset(aParams.mOffset) , mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) { aClip.AppendRoundedRects(&mRoundedClipRects, aRoundedRectClipCount); } void operator=(MaskLayerUserData&& aOther) { mScaleX = aOther.mScaleX; mScaleY = aOther.mScaleY; mOffset = aOther.mOffset; mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel; mRoundedClipRects.SwapElements(aOther.mRoundedClipRects); } bool operator== (const MaskLayerUserData& aOther) const { return mRoundedClipRects == aOther.mRoundedClipRects && mScaleX == aOther.mScaleX && mScaleY == aOther.mScaleY && mOffset == aOther.mOffset && mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel; } // Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var MaskLayerImageCache::MaskLayerImageKeyRef mImageKey; // properties of the mask layer; the mask layer may be re-used if these // remain unchanged. nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects; // scale from the masked layer which is applied to the mask float mScaleX, mScaleY; // The ContainerLayerParameters offset which is applied to the mask's transform. nsIntPoint mOffset; int32_t mAppUnitsPerDevPixel; }; /* * User data for layers which will be used as masks for css positioned mask. */ struct CSSMaskLayerUserData : public LayerUserData { CSSMaskLayerUserData() : mImageLayers(nsStyleImageLayers::LayerType::Mask) { } CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aBounds) : mImageLayers(aFrame->StyleSVGReset()->mMask), mContentRect(aFrame->GetContentRectRelativeToSelf()), mPaddingRect(aFrame->GetPaddingRectRelativeToSelf()), mBorderRect(aFrame->GetRectRelativeToSelf()), mMarginRect(aFrame->GetMarginRectRelativeToSelf()), mBounds(aBounds) { Hash(aFrame); } CSSMaskLayerUserData& operator=(const CSSMaskLayerUserData& aOther) { mImageLayers = aOther.mImageLayers; mContentRect = aOther.mContentRect; mPaddingRect = aOther.mPaddingRect; mBorderRect = aOther.mBorderRect; mMarginRect = aOther.mMarginRect; mBounds = aOther.mBounds; mHash = aOther.mHash; return *this; } bool operator==(const CSSMaskLayerUserData& aOther) const { if (mHash != aOther.mHash) { return false; } if (mImageLayers.mLayers != aOther.mImageLayers.mLayers) { return false; } if (!mContentRect.IsEqualEdges(aOther.mContentRect) || !mPaddingRect.IsEqualEdges(aOther.mPaddingRect) || !mBorderRect.IsEqualEdges(aOther.mBorderRect) || !mMarginRect.IsEqualEdges(aOther.mMarginRect)) { return false; } if (!mBounds.IsEqualEdges(aOther.mBounds)) { return false; } return true; } private: void Hash(nsIFrame* aFrame) { uint32_t hash = 0; const nsStyleImageLayers& imageLayers = aFrame->StyleSVGReset()->mMask; for (uint32_t i = 0; i < imageLayers.mLayers.Length(); i++) { const nsStyleImageLayers::Layer& newLayer = imageLayers.mLayers[i]; hash = AddToHash(hash, HashBytes(&newLayer, sizeof(newLayer))); } hash = AddToHash(hash, HashBytes(&mContentRect, sizeof(mContentRect))); hash = AddToHash(hash, HashBytes(&mPaddingRect, sizeof(mPaddingRect))); hash = AddToHash(hash, HashBytes(&mBorderRect, sizeof(mBorderRect))); hash = AddToHash(hash, HashBytes(&mMarginRect, sizeof(mMarginRect))); hash = AddToHash(hash, HashBytes(&mBounds, sizeof(mBounds))); mHash = hash; } nsStyleImageLayers mImageLayers; nsRect mContentRect; nsRect mPaddingRect; nsRect mBorderRect; nsRect mMarginRect; nsIntRect mBounds; uint32_t mHash; }; /* * A helper object to create a draw target for painting mask and create a * image container to hold the drawing result. The caller can then bind this * image container with a image mask layer via ImageLayer::SetContainer. */ class MaskImageData { public: MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager) : mTextureClientLocked(false) , mSize(aSize) , mLayerManager(aLayerManager) { MOZ_ASSERT(!mSize.IsEmpty()); MOZ_ASSERT(mLayerManager); } ~MaskImageData() { if (mTextureClientLocked) { MOZ_ASSERT(mTextureClient); // Clear DrawTarget before Unlock. mDrawTarget = nullptr; mTextureClient->Unlock(); } } gfx::DrawTarget* CreateDrawTarget() { if (mDrawTarget) { return mDrawTarget; } if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) { mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize); return mDrawTarget; } MOZ_ASSERT(mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT); ShadowLayerForwarder* fwd = mLayerManager->AsShadowForwarder(); if (!fwd) { return nullptr; } mTextureClient = TextureClient::CreateForDrawing(fwd, SurfaceFormat::A8, mSize, BackendSelector::Content, TextureFlags::DISALLOW_BIGIMAGE, TextureAllocationFlags::ALLOC_CLEAR_BUFFER); if (!mTextureClient) { return nullptr; } mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE); if (!mTextureClientLocked) { return nullptr; } mDrawTarget = mTextureClient->BorrowDrawTarget(); return mDrawTarget; } already_AddRefed<ImageContainer> CreateImageAndImageContainer() { RefPtr<ImageContainer> container = mLayerManager->CreateImageContainer(); RefPtr<Image> image = CreateImage(); if (!image) { return nullptr; } container->SetCurrentImageInTransaction(image); return container.forget(); } private: already_AddRefed<Image> CreateImage() { if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC && mDrawTarget) { RefPtr<SourceSurface> surface = mDrawTarget->Snapshot(); RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface); // Disallow BIGIMAGE (splitting into multiple textures) for mask // layer images image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE); return image.forget(); } if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT && mTextureClient && mDrawTarget) { RefPtr<TextureWrapperImage> image = new TextureWrapperImage(mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize)); return image.forget(); } return nullptr; } bool mTextureClientLocked; gfx::IntSize mSize; LayerManager* mLayerManager; RefPtr<gfx::DrawTarget> mDrawTarget; RefPtr<TextureClient> mTextureClient; }; /** * Helper functions for getting user data and casting it to the correct type. * aLayer is the layer where the user data is stored. */ MaskLayerUserData* GetMaskLayerUserData(Layer* aLayer) { return static_cast<MaskLayerUserData*>(aLayer->GetUserData(&gMaskLayerUserData)); } PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(Layer* aLayer) { return static_cast<PaintedDisplayItemLayerUserData*>( aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); } /* static */ void FrameLayerBuilder::Shutdown() { if (gMaskLayerImageCache) { delete gMaskLayerImageCache; gMaskLayerImageCache = nullptr; } } void FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager, PaintedLayerData* aLayerData) { mDisplayListBuilder = aBuilder; mRootPresContext = aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext(); if (mRootPresContext) { mInitialDOMGeneration = mRootPresContext->GetDOMGeneration(); } mContainingPaintedLayer = aLayerData; aManager->SetUserData(&gLayerManagerLayerBuilder, this); } void FrameLayerBuilder::FlashPaint(gfxContext *aContext) { float r = float(rand()) / RAND_MAX; float g = float(rand()) / RAND_MAX; float b = float(rand()) / RAND_MAX; aContext->SetColor(Color(r, g, b, 0.4f)); aContext->Paint(); } static FrameLayerBuilder::DisplayItemData* AssertDisplayItemData(FrameLayerBuilder::DisplayItemData* aData) { MOZ_RELEASE_ASSERT(aData); MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas && sAliveDisplayItemDatas->Contains(aData)); MOZ_RELEASE_ASSERT(aData->mLayer); return aData; } FrameLayerBuilder::DisplayItemData* FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame, uint32_t aKey) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (array) { for (uint32_t i = 0; i < array->Length(); i++) { DisplayItemData* item = AssertDisplayItemData(array->ElementAt(i)); if (item->mDisplayItemKey == aKey && item->mLayer->Manager() == mRetainingManager) { return item; } } } return nullptr; } nsACString& AppendToString(nsACString& s, const nsIntRect& r, const char* pfx="", const char* sfx="") { s += pfx; s += nsPrintfCString( "(x=%d, y=%d, w=%d, h=%d)", r.x, r.y, r.width, r.height); return s += sfx; } nsACString& AppendToString(nsACString& s, const nsIntRegion& r, const char* pfx="", const char* sfx="") { s += pfx; s += "< "; for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) { AppendToString(s, iter.Get()) += "; "; } s += ">"; return s += sfx; } /** * Invalidate aRegion in aLayer. aLayer is in the coordinate system * *after* aTranslation has been applied, so we need to * apply the inverse of that transform before calling InvalidateRegion. */ static void InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsIntRegion& aRegion, const nsIntPoint& aTranslation) { // Convert the region from the coordinates of the container layer // (relative to the snapped top-left of the display list reference frame) // to the PaintedLayer's own coordinates nsIntRegion rgn = aRegion; rgn.MoveBy(-aTranslation); aLayer->InvalidateRegion(rgn); #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { nsAutoCString str; AppendToString(str, rgn); printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get()); } #endif } static void InvalidatePostTransformRegion(PaintedLayer* aLayer, const nsRect& aRect, const DisplayItemClip& aClip, const nsIntPoint& aTranslation) { PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*>(aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); nsRect rect = aClip.ApplyNonRoundedIntersection(aRect); nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, data->mAppUnitsPerDevPixel); InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation); } static nsIntPoint GetTranslationForPaintedLayer(PaintedLayer* aLayer) { PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*> (aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); NS_ASSERTION(data, "Must be a tracked painted layer!"); return data->mTranslation; } /** * Some frames can have multiple, nested, retaining layer managers * associated with them (normal manager, inactive managers, SVG effects). * In these cases we store the 'outermost' LayerManager data property * on the frame since we can walk down the chain from there. * * If one of these frames has just been destroyed, we will free the inner * layer manager when removing the entry from mFramesWithLayers. Destroying * the layer manager destroys the LayerManagerData and calls into * the DisplayItemData destructor. If the inner layer manager had any * items with the same frame, then we attempt to retrieve properties * from the deleted frame. * * Cache the destroyed frame pointer here so we can avoid crashing in this case. */ /* static */ void FrameLayerBuilder::RemoveFrameFromLayerManager(const nsIFrame* aFrame, nsTArray<DisplayItemData*>* aArray) { MOZ_RELEASE_ASSERT(!sDestroyedFrame); sDestroyedFrame = aFrame; // Hold a reference to all the items so that they don't get // deleted from under us. nsTArray<RefPtr<DisplayItemData> > arrayCopy; for (DisplayItemData* data : *aArray) { arrayCopy.AppendElement(data); } #ifdef DEBUG_DISPLAY_ITEM_DATA if (aArray->Length()) { LayerManagerData *rootData = aArray->ElementAt(0)->mParent; while (rootData->mParent) { rootData = rootData->mParent; } printf_stderr("Removing frame %p - dumping display data\n", aFrame); rootData->Dump(); } #endif for (DisplayItemData* data : *aArray) { PaintedLayer* t = data->mLayer->AsPaintedLayer(); if (t) { PaintedDisplayItemLayerUserData* paintedData = static_cast<PaintedDisplayItemLayerUserData*>(t->GetUserData(&gPaintedDisplayItemLayerUserData)); if (paintedData) { nsRegion old = data->mGeometry->ComputeInvalidationRegion(); nsIntRegion rgn = old.ScaleToOutsidePixels(paintedData->mXScale, paintedData->mYScale, paintedData->mAppUnitsPerDevPixel); rgn.MoveBy(-GetTranslationForPaintedLayer(t)); paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate, rgn); paintedData->mRegionToInvalidate.SimplifyOutward(8); } } data->mParent->mDisplayItems.RemoveEntry(data); } arrayCopy.Clear(); delete aArray; sDestroyedFrame = nullptr; } void FrameLayerBuilder::DidBeginRetainedLayerTransaction(LayerManager* aManager) { mRetainingManager = aManager; LayerManagerData* data = static_cast<LayerManagerData*> (aManager->GetUserData(&gLayerManagerUserData)); if (data) { mInvalidateAllLayers = data->mInvalidateAllLayers; } else { data = new LayerManagerData(aManager); aManager->SetUserData(&gLayerManagerUserData, data); } } void FrameLayerBuilder::StoreOptimizedLayerForFrame(nsDisplayItem* aItem, Layer* aLayer) { if (!mRetainingManager) { return; } DisplayItemData* data = GetDisplayItemDataForManager(aItem, aLayer->Manager()); NS_ASSERTION(data, "Must have already stored data for this item!"); data->mOptLayer = aLayer; } void FrameLayerBuilder::DidEndTransaction() { GetMaskLayerImageCache()->Sweep(); } void FrameLayerBuilder::WillEndTransaction() { if (!mRetainingManager) { return; } // We need to save the data we'll need to support retaining. LayerManagerData* data = static_cast<LayerManagerData*> (mRetainingManager->GetUserData(&gLayerManagerUserData)); NS_ASSERTION(data, "Must have data!"); // Update all the frames that used to have layers. for (auto iter = data->mDisplayItems.Iter(); !iter.Done(); iter.Next()) { DisplayItemData* data = iter.Get()->GetKey(); if (!data->mUsed) { // This item was visible, but isn't anymore. PaintedLayer* t = data->mLayer->AsPaintedLayer(); if (t && data->mGeometry) { #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Invalidating unused display item (%i) belonging to frame %p from layer %p\n", data->mDisplayItemKey, data->mFrameList[0], t); } #endif InvalidatePostTransformRegion(t, data->mGeometry->ComputeInvalidationRegion(), data->mClip, GetLastPaintOffset(t)); } data->ClearAnimationCompositorState(); iter.Remove(); } else { ComputeGeometryChangeForItem(data); } } data->mInvalidateAllLayers = false; } /* static */ FrameLayerBuilder::DisplayItemData* FrameLayerBuilder::GetDisplayItemDataForManager(nsDisplayItem* aItem, LayerManager* aManager) { const nsTArray<DisplayItemData*>* array = aItem->Frame()->Properties().Get(LayerManagerDataProperty()); if (array) { for (uint32_t i = 0; i < array->Length(); i++) { DisplayItemData* item = AssertDisplayItemData(array->ElementAt(i)); if (item->mDisplayItemKey == aItem->GetPerFrameKey() && item->mLayer->Manager() == aManager) { return item; } } } return nullptr; } bool FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (array) { for (uint32_t i = 0; i < array->Length(); i++) { if (AssertDisplayItemData(array->ElementAt(i))->mDisplayItemKey == aDisplayItemKey) { return true; } } } return false; } void FrameLayerBuilder::IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (!array) { return; } for (uint32_t i = 0; i < array->Length(); i++) { DisplayItemData* data = AssertDisplayItemData(array->ElementAt(i)); if (data->mDisplayItemKey != nsDisplayItem::TYPE_ZERO) { aCallback(aFrame, data); } } } FrameLayerBuilder::DisplayItemData* FrameLayerBuilder::GetOldLayerForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey) { // If we need to build a new layer tree, then just refuse to recycle // anything. if (!mRetainingManager || mInvalidateAllLayers) return nullptr; DisplayItemData *data = GetDisplayItemData(aFrame, aDisplayItemKey); if (data && data->mLayer->Manager() == mRetainingManager) { return data; } return nullptr; } Layer* FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem, nsDisplayItemGeometry** aOldGeometry, DisplayItemClip** aOldClip) { uint32_t key = aItem->GetPerFrameKey(); nsIFrame* frame = aItem->Frame(); DisplayItemData* oldData = GetOldLayerForFrame(frame, key); if (oldData) { if (aOldGeometry) { *aOldGeometry = oldData->mGeometry.get(); } if (aOldClip) { *aOldClip = &oldData->mClip; } return oldData->mLayer; } return nullptr; } void FrameLayerBuilder::ClearCachedGeometry(nsDisplayItem* aItem) { uint32_t key = aItem->GetPerFrameKey(); nsIFrame* frame = aItem->Frame(); DisplayItemData* oldData = GetOldLayerForFrame(frame, key); if (oldData) { oldData->mGeometry = nullptr; } } /* static */ Layer* FrameLayerBuilder::GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (!array) { return nullptr; } for (uint32_t i = 0; i < array->Length(); i++) { DisplayItemData *data = AssertDisplayItemData(array->ElementAt(i)); if (data->mDisplayItemKey == aDisplayItemKey) { return data->mLayer; } } return nullptr; } /* static */ PaintedLayer* FrameLayerBuilder::GetDebugSingleOldPaintedLayerForFrame(nsIFrame* aFrame) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (!array) { return nullptr; } Layer* layer = nullptr; for (DisplayItemData* data : *array) { AssertDisplayItemData(data); if (!data->mLayer->AsPaintedLayer()) { continue; } if (layer && layer != data->mLayer) { // More than one layer assigned, bail. return nullptr; } layer = data->mLayer; } if (!layer) { return nullptr; } return layer->AsPaintedLayer(); } // Reset state that should not persist when a layer is recycled. static void ResetLayerStateForRecycling(Layer* aLayer) { // Currently, this clears the mask layer and ancestor mask layers. // Other cleanup may be added here. aLayer->SetMaskLayer(nullptr); aLayer->SetAncestorMaskLayers({}); } already_AddRefed<ColorLayer> ContainerState::CreateOrRecycleColorLayer(PaintedLayer *aPainted) { PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*>(aPainted->GetUserData(&gPaintedDisplayItemLayerUserData)); RefPtr<ColorLayer> layer = data->mColorLayer; if (layer) { ResetLayerStateForRecycling(layer); layer->ClearExtraDumpInfo(); } else { // Create a new layer layer = mManager->CreateColorLayer(); if (!layer) return nullptr; // Mark this layer as being used for painting display items data->mColorLayer = layer; layer->SetUserData(&gColorLayerUserData, nullptr); // Remove other layer types we might have stored for this PaintedLayer data->mImageLayer = nullptr; } return layer.forget(); } already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleImageLayer(PaintedLayer *aPainted) { PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*>(aPainted->GetUserData(&gPaintedDisplayItemLayerUserData)); RefPtr<ImageLayer> layer = data->mImageLayer; if (layer) { ResetLayerStateForRecycling(layer); layer->ClearExtraDumpInfo(); } else { // Create a new layer layer = mManager->CreateImageLayer(); if (!layer) return nullptr; // Mark this layer as being used for painting display items data->mImageLayer = layer; layer->SetUserData(&gImageLayerUserData, nullptr); // Remove other layer types we might have stored for this PaintedLayer data->mColorLayer = nullptr; } return layer.forget(); } already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleMaskImageLayerFor(const MaskLayerKey& aKey, mozilla::function<void(Layer* aLayer)> aSetUserData) { RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey); if (result) { mRecycledMaskImageLayers.Remove(aKey); aKey.mLayer->ClearExtraDumpInfo(); // XXX if we use clip on mask layers, null it out here } else { // Create a new layer result = mManager->CreateImageLayer(); if (!result) return nullptr; aSetUserData(result); } return result.forget(); } static const double SUBPIXEL_OFFSET_EPSILON = 0.02; /** * This normally computes NSToIntRoundUp(aValue). However, if that would * give a residual near 0.5 while aOldResidual is near -0.5, or * it would give a residual near -0.5 while aOldResidual is near 0.5, then * instead we return the integer in the other direction so that the residual * is close to aOldResidual. */ static int32_t RoundToMatchResidual(double aValue, double aOldResidual) { int32_t v = NSToIntRoundUp(aValue); double residual = aValue - v; if (aOldResidual < 0) { if (residual > 0 && fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) { // Round up instead return int32_t(ceil(aValue)); } } else if (aOldResidual > 0) { if (residual < 0 && fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) { // Round down instead return int32_t(floor(aValue)); } } return v; } static void ResetScrollPositionForLayerPixelAlignment(AnimatedGeometryRoot* aAnimatedGeometryRoot) { nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); if (sf) { sf->ResetScrollPositionForLayerPixelAlignment(); } } static void InvalidateEntirePaintedLayer(PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, const char *aReason) { #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason); } #endif nsIntRect invalidate = aLayer->GetValidRegion().GetBounds(); aLayer->InvalidateRegion(invalidate); aLayer->SetInvalidRectToVisibleRegion(); ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot); } LayerManager::PaintedLayerCreationHint ContainerState::GetLayerCreationHint(AnimatedGeometryRoot* aAnimatedGeometryRoot) { // Check whether the layer will be scrollable. This is used as a hint to // influence whether tiled layers are used or not. // Check creation hint inherited from our parent. if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) { return LayerManager::SCROLLABLE; } // Check whether there's any active scroll frame on the animated geometry // root chain. for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot; agr && agr != mContainerAnimatedGeometryRoot; agr = agr->mParentAGR) { nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrame(*agr); if (!fParent) { break; } nsIScrollableFrame* scrollable = do_QueryFrame(fParent); if (scrollable #ifdef MOZ_B2G && scrollable->WantAsyncScroll() #endif ) { // WantAsyncScroll() returns false when the frame has overflow:hidden, // so we won't create tiled layers for overflow:hidden frames even if // they have a display port. The main purpose of the WantAsyncScroll check // is to allow the B2G camera app to use hardware composer for compositing. return LayerManager::SCROLLABLE; } } return LayerManager::NONE; } already_AddRefed<PaintedLayer> ContainerState::AttemptToRecyclePaintedLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem, const nsPoint& aTopLeft) { Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem); if (!oldLayer || !oldLayer->AsPaintedLayer() || !mPaintedLayersAvailableForRecycling.Contains(oldLayer->AsPaintedLayer())) { return nullptr; } // Try to recycle a layer RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer(); mPaintedLayersAvailableForRecycling.RemoveEntry(layer); // Check if the layer hint has changed and whether or not the layer should // be recreated because of it. if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) { return nullptr; } bool didResetScrollPositionForLayerPixelAlignment = false; PaintedDisplayItemLayerUserData* data = RecyclePaintedLayer(layer, aAnimatedGeometryRoot, didResetScrollPositionForLayerPixelAlignment); PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aItem->ReferenceFrame(), aTopLeft, didResetScrollPositionForLayerPixelAlignment); return layer.forget(); } already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer(PaintedLayerData* aData) { LayerManager::PaintedLayerCreationHint creationHint = GetLayerCreationHint(aData->mAnimatedGeometryRoot); // Create a new painted layer RefPtr<PaintedLayer> layer = mManager->CreatePaintedLayerWithHint(creationHint); if (!layer) { return nullptr; } // Mark this layer as being used for painting display items PaintedDisplayItemLayerUserData* userData = new PaintedDisplayItemLayerUserData(); layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData); ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot); PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot, aData->mReferenceFrame, aData->mAnimatedGeometryRootOffset, true); return layer.forget(); } PaintedDisplayItemLayerUserData* ContainerState::RecyclePaintedLayer(PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot, bool& didResetScrollPositionForLayerPixelAlignment) { // Clear clip rect and mask layer so we don't accidentally stay clipped. // We will reapply any necessary clipping. ResetLayerStateForRecycling(aLayer); aLayer->ClearExtraDumpInfo(); PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*>( aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); NS_ASSERTION(data, "Recycled PaintedLayers must have user data"); // This gets called on recycled PaintedLayers that are going to be in the // final layer tree, so it's a convenient time to invalidate the // content that changed where we don't know what PaintedLayer it belonged // to, or if we need to invalidate the entire layer, we can do that. // This needs to be done before we update the PaintedLayer to its new // transform. See nsGfxScrollFrame::InvalidateInternal, where // we ensure that mInvalidPaintedContent is updated according to the // scroll position as of the most recent paint. if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) || !FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) || data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) { #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Recycled layer %p changed scale\n", aLayer); } #endif InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, "recycled layer changed state"); didResetScrollPositionForLayerPixelAlignment = true; } if (!data->mRegionToInvalidate.IsEmpty()) { #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Invalidating deleted frame content from layer %p\n", aLayer); } #endif aLayer->InvalidateRegion(data->mRegionToInvalidate); #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { nsAutoCString str; AppendToString(str, data->mRegionToInvalidate); printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get()); } #endif data->mRegionToInvalidate.SetEmpty(); } return data; } void ContainerState::PreparePaintedLayerForUse(PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft, bool didResetScrollPositionForLayerPixelAlignment) { aData->mXScale = mParameters.mXScale; aData->mYScale = mParameters.mYScale; aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin; aData->mAnimatedGeometryRootOrigin = aTopLeft; aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel; aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation()); mLayerBuilder->SavePreviousDataForLayer(aLayer, aData->mMaskClipCount); // Set up transform so that 0,0 in the PaintedLayer corresponds to the // (pixel-snapped) top-left of the aAnimatedGeometryRoot. nsPoint offset = (*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame); nscoord appUnitsPerDevPixel = (*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel(); gfxPoint scaledOffset( NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel)*mParameters.mXScale, NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel)*mParameters.mYScale); // We call RoundToMatchResidual here so that the residual after rounding // is close to aData->mAnimatedGeometryRootPosition if possible. nsIntPoint pixOffset(RoundToMatchResidual(scaledOffset.x, aData->mAnimatedGeometryRootPosition.x), RoundToMatchResidual(scaledOffset.y, aData->mAnimatedGeometryRootPosition.y)); aData->mTranslation = pixOffset; pixOffset += mParameters.mOffset; Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y); aLayer->SetBaseTransform(Matrix4x4::From2D(matrix)); aData->mVisibilityComputedRegion.SetEmpty(); // FIXME: Temporary workaround for bug 681192 and bug 724786. #ifndef MOZ_WIDGET_ANDROID // Calculate exact position of the top-left of the active scrolled root. // This might not be 0,0 due to the snapping in ScaleToNearestPixels. gfxPoint animatedGeometryRootTopLeft = scaledOffset - ThebesPoint(matrix.GetTranslation()) + mParameters.mOffset; // If it has changed, then we need to invalidate the entire layer since the // pixels in the layer buffer have the content at a (subpixel) offset // from what we need. if (!animatedGeometryRootTopLeft.WithinEpsilonOf(aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) { aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot, "subpixel offset"); } else if (didResetScrollPositionForLayerPixelAlignment) { aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; } #else Unused << didResetScrollPositionForLayerPixelAlignment; #endif } #if defined(DEBUG) || defined(MOZ_DUMP_PAINTING) /** * Returns the appunits per dev pixel for the item's frame */ static int32_t AppUnitsPerDevPixel(nsDisplayItem* aItem) { // The underlying frame for zoom items is the root frame of the subdocument. // But zoom display items report their bounds etc using the parent document's // APD because zoom items act as a conversion layer between the two different // APDs. if (aItem->GetType() == nsDisplayItem::TYPE_ZOOM) { return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel(); } return aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); } #endif /** * Set the visible region for aLayer. * aOuterVisibleRegion is the visible region relative to the parent layer. * aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's * own coordinate system to which the layer's visible region is restricted. * Consumes *aOuterVisibleRegion. */ static void SetOuterVisibleRegion(Layer* aLayer, nsIntRegion* aOuterVisibleRegion, const nsIntRect* aLayerContentsVisibleRect = nullptr, bool aOuterUntransformed = false) { Matrix4x4 transform = aLayer->GetTransform(); Matrix transform2D; if (aOuterUntransformed) { if (aLayerContentsVisibleRect) { aOuterVisibleRegion->And(*aOuterVisibleRegion, *aLayerContentsVisibleRect); } } else if (transform.Is2D(&transform2D) && !transform2D.HasNonIntegerTranslation()) { aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32)); if (aLayerContentsVisibleRect) { aOuterVisibleRegion->And(*aOuterVisibleRegion, *aLayerContentsVisibleRect); } } else { nsIntRect outerRect = aOuterVisibleRegion->GetBounds(); // if 'transform' is not invertible, then nothing will be displayed // for the layer, so it doesn't really matter what we do here Rect outerVisible(outerRect.x, outerRect.y, outerRect.width, outerRect.height); transform.Invert(); Rect layerContentsVisible(-float(INT32_MAX) / 2, -float(INT32_MAX) / 2, float(INT32_MAX), float(INT32_MAX)); if (aLayerContentsVisibleRect) { NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 && aLayerContentsVisibleRect->height >= 0, "Bad layer contents rectangle"); // restrict to aLayerContentsVisibleRect before call GfxRectToIntRect, // in case layerVisible is extremely large (as it can be when // projecting through the inverse of a 3D transform) layerContentsVisible = Rect( aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y, aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height); } gfxRect layerVisible = ThebesRect(transform.ProjectRectBounds(outerVisible, layerContentsVisible)); layerVisible.RoundOut(); nsIntRect visRect; if (gfxUtils::GfxRectToIntRect(layerVisible, &visRect)) { *aOuterVisibleRegion = visRect; } else { aOuterVisibleRegion->SetEmpty(); } } aLayer->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion)); } void ContainerState::SetOuterVisibleRegionForLayer(Layer* aLayer, const nsIntRegion& aOuterVisibleRegion, const nsIntRect* aLayerContentsVisibleRect, bool aOuterUntransformed) const { nsIntRegion visRegion = aOuterVisibleRegion; if (!aOuterUntransformed) { visRegion.MoveBy(mParameters.mOffset); } SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect, aOuterUntransformed); } nscolor ContainerState::FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData, const nsIntRect& aRect, bool* aOutIntersectsLayer) const { *aOutIntersectsLayer = true; // Scan the candidate's display items. nsIntRect deviceRect = aRect; nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel); appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale); for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) { nsDisplayItem* item = assignedItem.mItem; bool snap; nsRect bounds = item->GetBounds(mBuilder, &snap); if (snap && mSnappingEnabled) { nsIntRect snappedBounds = ScaleToNearestPixels(bounds); if (!snappedBounds.Intersects(deviceRect)) continue; if (!snappedBounds.Contains(deviceRect)) return NS_RGBA(0,0,0,0); } else { // The layer's visible rect is already (close enough to) pixel // aligned, so no need to round out and in here. if (!bounds.Intersects(appUnitRect)) continue; if (!bounds.Contains(appUnitRect)) return NS_RGBA(0,0,0,0); } if (item->IsInvisibleInRect(appUnitRect)) { continue; } if (assignedItem.mClip.IsRectAffectedByClip(deviceRect, mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel)) { return NS_RGBA(0,0,0,0); } Maybe<nscolor> color = item->IsUniform(mBuilder); if (color && NS_GET_A(*color) == 255) return *color; return NS_RGBA(0,0,0,0); } *aOutIntersectsLayer = false; return NS_RGBA(0,0,0,0); } nscolor PaintedLayerDataNode::FindOpaqueBackgroundColor(const nsIntRegion& aTargetVisibleRegion, int32_t aUnderIndex) const { if (aUnderIndex == ABOVE_TOP) { aUnderIndex = mPaintedLayerDataStack.Length(); } for (int32_t i = aUnderIndex - 1; i >= 0; --i) { const PaintedLayerData* candidate = &mPaintedLayerDataStack[i]; if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) { // Some non-PaintedLayer content between target and candidate; this is // hopeless return NS_RGBA(0,0,0,0); } if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) { // The layer doesn't intersect our target, ignore it and move on continue; } bool intersectsLayer = true; nsIntRect rect = aTargetVisibleRegion.GetBounds(); nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer( candidate, rect, &intersectsLayer); if (!intersectsLayer) { continue; } return color; } if (mAllDrawingAboveBackground || !mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion).IsEmpty()) { // Some non-PaintedLayer content is between this node's background and target. return NS_RGBA(0,0,0,0); } return FindOpaqueBackgroundColorInParentNode(); } nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything() const { if (!mPaintedLayerDataStack.IsEmpty() || mAllDrawingAboveBackground || !mVisibleAboveBackgroundRegion.IsEmpty()) { return NS_RGBA(0,0,0,0); } return FindOpaqueBackgroundColorInParentNode(); } nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const { if (mParent) { if (mHasClip) { // Check whether our parent node has uniform content behind our whole // clip. // There's one tricky case here: If our parent node is also a scrollable, // and is currently scrolled in such a way that this inner one is // clipped by it, then it's not really clear how we should determine // whether we have a uniform background in the parent: There might be // non-uniform content in the parts that our scroll port covers in the // parent and that are currently outside the parent's clip. // For now, we'll fail to pull a background color in that case. return mParent->FindOpaqueBackgroundColor(mClipRect); } return mParent->FindOpaqueBackgroundColorCoveringEverything(); } // We are the root. return mTree.UniformBackgroundColor(); } void PaintedLayerData::UpdateCommonClipCount( const DisplayItemClip& aCurrentClip) { if (mCommonClipCount >= 0) { mCommonClipCount = mItemClip.GetCommonRoundedRectCount(aCurrentClip, mCommonClipCount); } else { // first item in the layer mCommonClipCount = aCurrentClip.GetRoundedRectCount(); } } bool PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder) { if (!mImage) { return false; } return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder); } already_AddRefed<ImageContainer> PaintedLayerData::GetContainerForImageLayer(nsDisplayListBuilder* aBuilder) { if (!mImage) { return nullptr; } return mImage->GetContainer(mLayer->Manager(), aBuilder); } PaintedLayerDataNode::PaintedLayerDataNode(PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent, AnimatedGeometryRoot* aAnimatedGeometryRoot) : mTree(aTree) , mParent(aParent) , mAnimatedGeometryRoot(aAnimatedGeometryRoot) , mAllDrawingAboveBackground(false) { MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot)); mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(mAnimatedGeometryRoot, &mClipRect); } PaintedLayerDataNode::~PaintedLayerDataNode() { MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty()); MOZ_ASSERT(mChildren.IsEmpty()); } PaintedLayerDataNode* PaintedLayerDataNode::AddChildNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot) { MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot); UniquePtr<PaintedLayerDataNode> child = MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot); mChildren.AppendElement(Move(child)); return mChildren.LastElement().get(); } template<typename NewPaintedLayerCallbackType> PaintedLayerData* PaintedLayerDataNode::FindPaintedLayerFor(const nsIntRect& aVisibleRect, bool aBackfaceHidden, const DisplayItemScrollClip* aScrollClip, NewPaintedLayerCallbackType aNewPaintedLayerCallback) { if (!mPaintedLayerDataStack.IsEmpty()) { PaintedLayerData* lowestUsableLayer = nullptr; for (auto& data : Reversed(mPaintedLayerDataStack)) { if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) { break; } if (data.mBackfaceHidden == aBackfaceHidden && data.mScrollClip == aScrollClip) { lowestUsableLayer = &data; } nsIntRegion visibleRegion = data.mVisibleRegion; // Also check whether the event-regions intersect the visible rect, // unless we're in an inactive layer, in which case the event-regions // will be hoisted out into their own layer. // For performance reasons, we check the intersection with the bounds // of the event-regions. if (!mTree.ContState().IsInInactiveLayer() && (data.mScaledHitRegionBounds.Intersects(aVisibleRect) || data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) { break; } if (visibleRegion.Intersects(aVisibleRect)) { break; } } if (lowestUsableLayer) { return lowestUsableLayer; } } return mPaintedLayerDataStack.AppendElement(aNewPaintedLayerCallback()); } void PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect) { for (int32_t i = mChildren.Length() - 1; i >= 0; i--) { if (mChildren[i]->Intersects(aRect)) { mChildren[i]->Finish(true); mChildren.RemoveElementAt(i); } } } void PaintedLayerDataNode::FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion) { for (int32_t i = mChildren.Length() - 1; i >= 0; i--) { mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion); } mChildren.Clear(); } void PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion) { // Skip "visible above region" maintenance, because this node is going away. FinishAllChildren(false); PopAllPaintedLayerData(); if (mParent && aParentNeedsAccurateVisibleAboveRegion) { if (mHasClip) { mParent->AddToVisibleAboveRegion(mClipRect); } else { mParent->SetAllDrawingAbove(); } } mTree.NodeWasFinished(mAnimatedGeometryRoot); } void PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect) { nsIntRegion& visibleAboveRegion = mPaintedLayerDataStack.IsEmpty() ? mVisibleAboveBackgroundRegion : mPaintedLayerDataStack.LastElement().mVisibleAboveRegion; visibleAboveRegion.Or(visibleAboveRegion, aRect); visibleAboveRegion.SimplifyOutward(8); } void PaintedLayerDataNode::SetAllDrawingAbove() { PopAllPaintedLayerData(); mAllDrawingAboveBackground = true; mVisibleAboveBackgroundRegion.SetEmpty(); } void PaintedLayerDataNode::PopPaintedLayerData() { MOZ_ASSERT(!mPaintedLayerDataStack.IsEmpty()); size_t lastIndex = mPaintedLayerDataStack.Length() - 1; PaintedLayerData& data = mPaintedLayerDataStack[lastIndex]; mTree.ContState().FinishPaintedLayerData(data, [this, &data, lastIndex]() { return this->FindOpaqueBackgroundColor(data.mVisibleRegion, lastIndex); }); mPaintedLayerDataStack.RemoveElementAt(lastIndex); } void PaintedLayerDataNode::PopAllPaintedLayerData() { while (!mPaintedLayerDataStack.IsEmpty()) { PopPaintedLayerData(); } } nsDisplayListBuilder* PaintedLayerDataTree::Builder() const { return mContainerState.Builder(); } void PaintedLayerDataTree::Finish() { if (mRoot) { mRoot->Finish(false); } MOZ_ASSERT(mNodes.Count() == 0); mRoot = nullptr; } void PaintedLayerDataTree::NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot) { mNodes.Remove(aAnimatedGeometryRoot); } void PaintedLayerDataTree::AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect, nscolor* aOutUniformBackgroundColor) { FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect); PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot); if (aRect) { if (aOutUniformBackgroundColor) { *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect); } node->AddToVisibleAboveRegion(*aRect); } else { if (aOutUniformBackgroundColor) { *aOutUniformBackgroundColor = node->FindOpaqueBackgroundColorCoveringEverything(); } node->SetAllDrawingAbove(); } } template<typename NewPaintedLayerCallbackType> PaintedLayerData* PaintedLayerDataTree::FindPaintedLayerFor(AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemScrollClip* aScrollClip, const nsIntRect& aVisibleRect, bool aBackfaceHidden, NewPaintedLayerCallbackType aNewPaintedLayerCallback) { const nsIntRect* bounds = &aVisibleRect; FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds); PaintedLayerDataNode* node = EnsureNodeFor(aAnimatedGeometryRoot); PaintedLayerData* data = node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aScrollClip, aNewPaintedLayerCallback); return data; } void PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect) { AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr; PaintedLayerDataNode* ancestorNode = FindNodeForAncestorAnimatedGeometryRoot(aAnimatedGeometryRoot, &ancestorThatIsChildOfCommonAncestor); if (!ancestorNode) { // None of our ancestors are in the tree. This should only happen if this // is the very first item we're looking at. MOZ_ASSERT(!mRoot); return; } if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) { // aAnimatedGeometryRoot already has a node in the tree. // This is the common case. MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor); if (aRect) { ancestorNode->FinishChildrenIntersecting(*aRect); } else { ancestorNode->FinishAllChildren(); } return; } // We have found an existing ancestor, but it's a proper ancestor of our // animated geometry root. // ancestorThatIsChildOfCommonAncestor is the last animated geometry root // encountered on the way up from aAnimatedGeometryRoot to ancestorNode. MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor); MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(*ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot)); MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR == ancestorNode->GetAnimatedGeometryRoot()); // ancestorThatIsChildOfCommonAncestor is not in the tree yet! MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor)); // We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we // finish all intersecting siblings. nsIntRect clip; if (IsClippedWithRespectToParentAnimatedGeometryRoot(ancestorThatIsChildOfCommonAncestor, &clip)) { ancestorNode->FinishChildrenIntersecting(clip); } else { ancestorNode->FinishAllChildren(); } } PaintedLayerDataNode* PaintedLayerDataTree::EnsureNodeFor(AnimatedGeometryRoot* aAnimatedGeometryRoot) { MOZ_ASSERT(aAnimatedGeometryRoot); PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot); if (node) { return node; } AnimatedGeometryRoot* parentAnimatedGeometryRoot = aAnimatedGeometryRoot->mParentAGR; if (!parentAnimatedGeometryRoot) { MOZ_ASSERT(!mRoot); MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame()); mRoot = MakeUnique<PaintedLayerDataNode>(*this, nullptr, aAnimatedGeometryRoot); node = mRoot.get(); } else { PaintedLayerDataNode* parentNode = EnsureNodeFor(parentAnimatedGeometryRoot); MOZ_ASSERT(parentNode); node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot); } MOZ_ASSERT(node); mNodes.Put(aAnimatedGeometryRoot, node); return node; } bool PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip) { nsIScrollableFrame* scrollableFrame = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); if (!scrollableFrame) { return false; } nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame); nsRect scrollPort = scrollableFrame->GetScrollPortRect() + Builder()->ToReferenceFrame(scrollFrame); *aOutClip = mContainerState.ScaleToNearestPixels(scrollPort); return true; } PaintedLayerDataNode* PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(AnimatedGeometryRoot* aAnimatedGeometryRoot, AnimatedGeometryRoot** aOutAncestorChild) { if (!aAnimatedGeometryRoot) { return nullptr; } PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot); if (node) { return node; } *aOutAncestorChild = aAnimatedGeometryRoot; return FindNodeForAncestorAnimatedGeometryRoot(aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild); } static bool CanOptimizeAwayPaintedLayer(PaintedLayerData* aData, FrameLayerBuilder* aLayerBuilder) { if (!aLayerBuilder->IsBuildingRetainedLayers()) { return false; } // If there's no painted layer with valid content in it that we can reuse, // always create a color or image layer (and potentially throw away an // existing completely invalid painted layer). if (aData->mLayer->GetValidRegion().IsEmpty()) { return true; } // There is an existing painted layer we can reuse. Throwing it away can make // compositing cheaper (see bug 946952), but it might cause us to re-allocate // the painted layer frequently due to an animation. So we only discard it if // we're in tree compression mode, which is triggered at a low frequency. return aLayerBuilder->CheckInLayerTreeCompressionMode(); } #ifdef DEBUG static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray, Layer* aLayer) { for (uint32_t i = 0; i < aArray.Length(); ++i) { if (aArray[i].mLayer == aLayer) { return i; } } return -1; } #endif already_AddRefed<Layer> ContainerState::PrepareImageLayer(PaintedLayerData* aData) { RefPtr<ImageContainer> imageContainer = aData->GetContainerForImageLayer(mBuilder); if (!imageContainer) { return nullptr; } RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer); imageLayer->SetContainer(imageContainer); aData->mImage->ConfigureLayer(imageLayer, mParameters); imageLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale); if (aData->mItemClip.HasClip()) { ParentLayerIntRect clip = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(aData->mItemClip.GetClipRect())); clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset)); imageLayer->SetClipRect(Some(clip)); } else { imageLayer->SetClipRect(Nothing()); } mLayerBuilder->StoreOptimizedLayerForFrame(aData->mImage, imageLayer); FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected image layer=%p\n", imageLayer.get()); return imageLayer.forget(); } already_AddRefed<Layer> ContainerState::PrepareColorLayer(PaintedLayerData* aData) { RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer); colorLayer->SetColor(Color::FromABGR(aData->mSolidColor)); // Copy transform colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform()); colorLayer->SetPostScale(aData->mLayer->GetPostXScale(), aData->mLayer->GetPostYScale()); nsIntRect visibleRect = aData->mVisibleRegion.GetBounds(); visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer)); colorLayer->SetBounds(visibleRect); colorLayer->SetClipRect(Nothing()); FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected color layer=%p\n", colorLayer.get()); return colorLayer.forget(); } static void SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer) { if (aBackfaceHidden) { aLayer->SetContentFlags(aLayer->GetContentFlags() | Layer::CONTENT_BACKFACE_HIDDEN); } else { aLayer->SetContentFlags(aLayer->GetContentFlags() & ~Layer::CONTENT_BACKFACE_HIDDEN); } } template<typename FindOpaqueBackgroundColorCallbackType> void ContainerState::FinishPaintedLayerData(PaintedLayerData& aData, FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) { PaintedLayerData* data = &aData; if (!data->mLayer) { // No layer was recycled, so we create a new one. RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data); data->mLayer = paintedLayer; NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0, "Layer already in list???"); mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget(); } for (auto& item : data->mAssignedDisplayItems) { MOZ_ASSERT(item.mItem->GetType() != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS); InvalidateForLayerChange(item.mItem, data->mLayer); mLayerBuilder->AddPaintedDisplayItem(data, item.mItem, item.mClip, *this, item.mLayerState, data->mAnimatedGeometryRootOffset); } NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex]; RefPtr<Layer> layer; bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder); FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data); FLB_LOG_PAINTED_LAYER_DECISION(data, " Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n", data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n', CanOptimizeAwayPaintedLayer(data, mLayerBuilder)); if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) && CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) { NS_ASSERTION(!(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer), "Can't be a solid color as well as an image!"); layer = canOptimizeToImageLayer ? PrepareImageLayer(data) : PrepareColorLayer(data); if (layer) { NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0, "Layer already in list???"); NS_ASSERTION(newLayerEntry->mLayer == data->mLayer, "Painted layer at wrong index"); // Store optimized layer in reserved slot newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1]; NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?"); newLayerEntry->mLayer = layer; newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot; newLayerEntry->mScrollClip = data->mScrollClip; // Hide the PaintedLayer. We leave it in the layer tree so that we // can find and recycle it later. ParentLayerIntRect emptyRect; data->mLayer->SetClipRect(Some(emptyRect)); data->mLayer->SetVisibleRegion(LayerIntRegion()); data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion().GetBounds()); data->mLayer->SetEventRegions(EventRegions()); } } if (!layer) { // We couldn't optimize to an image layer or a color layer above. layer = data->mLayer; layer->SetClipRect(Nothing()); FLB_LOG_PAINTED_LAYER_DECISION(data, " Selected painted layer=%p\n", layer.get()); } // If the layer is a fixed background layer, the clip on the fixed background // display item was not applied to the opaque region in // ContainerState::ComputeOpaqueRect(), but was saved in data->mItemClip. // Apply it to the opaque region now. Note that it's important to do this // before the opaque region is propagated to the NewLayerEntry below. if (data->mSingleItemFixedToViewport && data->mItemClip.HasClip()) { nsRect clipRect = data->mItemClip.GetClipRect(); nsRect insideRoundedCorners = data->mItemClip.ApproximateIntersectInward(clipRect); nsIntRect insideRoundedCornersScaled = ScaleToInsidePixels(insideRoundedCorners); data->mOpaqueRegion.AndWith(insideRoundedCornersScaled); } if (mLayerBuilder->IsBuildingRetainedLayers()) { newLayerEntry->mVisibleRegion = data->mVisibleRegion; newLayerEntry->mOpaqueRegion = data->mOpaqueRegion; newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow; newLayerEntry->mOpaqueForAnimatedGeometryRootParent = data->mOpaqueForAnimatedGeometryRootParent; } else { SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion); } nsIntRect layerBounds = data->mBounds; layerBounds.MoveBy(-GetTranslationForPaintedLayer(data->mLayer)); layer->SetLayerBounds(layerBounds); #ifdef MOZ_DUMP_PAINTING if (!data->mLog.IsEmpty()) { if (PaintedLayerData* containingPld = mLayerBuilder->GetContainingPaintedLayerData()) { containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog)); } else { layer->AddExtraDumpInfo(nsCString(data->mLog)); } } #endif nsIntRegion transparentRegion; transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion); bool isOpaque = transparentRegion.IsEmpty(); // For translucent PaintedLayers, try to find an opaque background // color that covers the entire area beneath it so we can pull that // color into this layer to make it opaque. if (layer == data->mLayer) { nscolor backgroundColor = NS_RGBA(0,0,0,0); if (!isOpaque) { backgroundColor = aFindOpaqueBackgroundColor(); if (NS_GET_A(backgroundColor) == 255) { isOpaque = true; } } // Store the background color PaintedDisplayItemLayerUserData* userData = GetPaintedDisplayItemLayerUserData(data->mLayer); NS_ASSERTION(userData, "where did our user data go?"); if (userData->mForcedBackgroundColor != backgroundColor) { // Invalidate the entire target PaintedLayer since we're changing // the background color #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Forced background color has changed from #%08X to #%08X on layer %p\n", userData->mForcedBackgroundColor, backgroundColor, data->mLayer); nsAutoCString str; AppendToString(str, data->mLayer->GetValidRegion()); printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get()); } #endif data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion()); } userData->mForcedBackgroundColor = backgroundColor; userData->mFontSmoothingBackgroundColor = data->mFontSmoothingBackgroundColor; // use a mask layer for rounded rect clipping. // data->mCommonClipCount may be -1 if we haven't put any actual // drawable items in this layer (i.e. it's only catching events). int32_t commonClipCount; // If the layer contains a single item fixed to the viewport, we removed // its clip in ProcessDisplayItems() and saved it to set on the layer instead. // Set the clip on the layer now. if (data->mSingleItemFixedToViewport && data->mItemClip.HasClip()) { nsIntRect layerClipRect = ScaleToNearestPixels(data->mItemClip.GetClipRect()); layerClipRect.MoveBy(mParameters.mOffset); // The clip from such an item becomes part of the layer's scrolled clip, // and the associated mask layer one of the layer's "ancestor mask layers". LayerClip scrolledClip; scrolledClip.SetClipRect(ViewAs<ParentLayerPixel>(layerClipRect)); scrolledClip.SetMaskLayerIndex( SetupMaskLayerForScrolledClip(data->mLayer, data->mItemClip)); data->mLayer->SetScrolledClip(Some(scrolledClip)); // There is only one item, so all of the clips are in common to all items. // data->mCommonClipCount will be zero because we removed the clip from // the display item. (It could also be -1 if we're inside an inactive // layer tree in which we don't call UpdateCommonClipCount() at all.) MOZ_ASSERT(data->mCommonClipCount == -1 || data->mCommonClipCount == 0); commonClipCount = data->mItemClip.GetRoundedRectCount(); } else { commonClipCount = std::max(0, data->mCommonClipCount); SetupMaskLayer(layer, data->mItemClip, commonClipCount); } // copy commonClipCount to the entry FrameLayerBuilder::PaintedLayerItemsEntry* entry = mLayerBuilder-> GetPaintedLayerItemsEntry(static_cast<PaintedLayer*>(layer.get())); entry->mCommonClipCount = commonClipCount; } else { // mask layer for image and color layers SetupMaskLayer(layer, data->mItemClip); } uint32_t flags = 0; nsIWidget* widget = mContainerReferenceFrame->PresContext()->GetRootWidget(); // See bug 941095. Not quite ready to disable this. bool hidpi = false && widget && widget->GetDefaultScale().scale >= 2; if (hidpi) { flags |= Layer::CONTENT_DISABLE_SUBPIXEL_AA; } if (isOpaque && !data->mForceTransparentSurface) { flags |= Layer::CONTENT_OPAQUE; } else if (data->mNeedComponentAlpha && !hidpi) { flags |= Layer::CONTENT_COMPONENT_ALPHA; } if (data->mDisableFlattening) { flags |= Layer::CONTENT_DISABLE_FLATTENING; } layer->SetContentFlags(flags); PaintedLayerData* containingPaintedLayerData = mLayerBuilder->GetContainingPaintedLayerData(); if (containingPaintedLayerData) { if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) { nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor( mContainerReferenceFrame, data->mDispatchToContentHitRegion.GetBounds(), containingPaintedLayerData->mReferenceFrame); containingPaintedLayerData->mDispatchToContentHitRegion.Or( containingPaintedLayerData->mDispatchToContentHitRegion, rect); } if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) { nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor( mContainerReferenceFrame, data->mMaybeHitRegion.GetBounds(), containingPaintedLayerData->mReferenceFrame); containingPaintedLayerData->mMaybeHitRegion.Or( containingPaintedLayerData->mMaybeHitRegion, rect); containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8); } Maybe<Matrix4x4> matrixCache; nsLayoutUtils::TransformToAncestorAndCombineRegions( data->mHitRegion, mContainerReferenceFrame, containingPaintedLayerData->mReferenceFrame, &containingPaintedLayerData->mHitRegion, &containingPaintedLayerData->mMaybeHitRegion, &matrixCache); // See the comment in nsDisplayList::AddFrame, where the touch action regions // are handled. The same thing applies here. bool alreadyHadRegions = !containingPaintedLayerData->mNoActionRegion.IsEmpty() || !containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() || !containingPaintedLayerData->mVerticalPanRegion.IsEmpty(); nsLayoutUtils::TransformToAncestorAndCombineRegions( data->mNoActionRegion, mContainerReferenceFrame, containingPaintedLayerData->mReferenceFrame, &containingPaintedLayerData->mNoActionRegion, &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache); nsLayoutUtils::TransformToAncestorAndCombineRegions( data->mHorizontalPanRegion, mContainerReferenceFrame, containingPaintedLayerData->mReferenceFrame, &containingPaintedLayerData->mHorizontalPanRegion, &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache); nsLayoutUtils::TransformToAncestorAndCombineRegions( data->mVerticalPanRegion, mContainerReferenceFrame, containingPaintedLayerData->mReferenceFrame, &containingPaintedLayerData->mVerticalPanRegion, &containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache); if (alreadyHadRegions) { containingPaintedLayerData->mDispatchToContentHitRegion.OrWith( containingPaintedLayerData->CombinedTouchActionRegion()); } } else { EventRegions regions; regions.mHitRegion = ScaleRegionToOutsidePixels(data->mHitRegion); regions.mNoActionRegion = ScaleRegionToOutsidePixels(data->mNoActionRegion); regions.mHorizontalPanRegion = ScaleRegionToOutsidePixels(data->mHorizontalPanRegion); regions.mVerticalPanRegion = ScaleRegionToOutsidePixels(data->mVerticalPanRegion); // Points whose hit-region status we're not sure about need to be dispatched // to the content thread. If a point is in both maybeHitRegion and hitRegion // then it's not a "maybe" any more, and doesn't go into the dispatch-to- // content region. nsIntRegion maybeHitRegion = ScaleRegionToOutsidePixels(data->mMaybeHitRegion); regions.mDispatchToContentHitRegion.Sub(maybeHitRegion, regions.mHitRegion); regions.mDispatchToContentHitRegion.OrWith( ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion)); regions.mHitRegion.OrWith(maybeHitRegion); Matrix mat = layer->GetTransform().As2D(); mat.Invert(); regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22); layer->SetEventRegions(regions); } SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer); if (layer != data->mLayer) { SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer); } } static bool IsItemAreaInWindowOpaqueRegion(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem, const nsRect& aComponentAlphaBounds) { if (!aItem->Frame()->PresContext()->IsChrome()) { // Assume that Web content is always in the window opaque region. return true; } if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) { // aItem is probably in some transformed subtree. // We're not going to bother figuring out where this landed, we're just // going to assume it might have landed over a transparent part of // the window. return false; } return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds); } void PaintedLayerData::Accumulate(ContainerState* aState, nsDisplayItem* aItem, const nsIntRegion& aClippedOpaqueRegion, const nsIntRect& aVisibleRect, const DisplayItemClip& aClip, LayerState aLayerState) { FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", aItem->Name(), aItem, aItem->Frame(), this); bool snap; nsRect itemBounds = aItem->GetBounds(aState->mBuilder, &snap); mBounds = mBounds.Union(aState->ScaleToOutsidePixels(itemBounds, snap)); if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) { mForceTransparentSurface = true; } if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) { // Disable component alpha. // Note that the transform (if any) on the PaintedLayer is always an integer translation so // we don't have to factor that in here. aItem->DisableComponentAlpha(); } bool clipMatches = mItemClip == aClip; mItemClip = aClip; mAssignedDisplayItems.AppendElement(AssignedDisplayItem(aItem, aClip, aLayerState)); if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) && mVisibleRegion.Contains(aVisibleRect) && !mImage) { // A very common case! Most pages have a PaintedLayer with the page // background (opaque) visible and most or all of the page content over the // top of that background. // The rest of this method won't do anything. mVisibleRegion and mOpaqueRegion // don't need updating. mVisibleRegion contains aVisibleRect already, // mOpaqueRegion contains aVisibleRect and therefore whatever the opaque // region of the item is. mVisibleRegion must contain mOpaqueRegion // and therefore aVisibleRect. return; } /* Mark as available for conversion to image layer if this is a nsDisplayImage and * it's the only thing visible in this layer. */ if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) && aClippedOpaqueRegion.Contains(mVisibleRegion) && aItem->SupportsOptimizingToImage()) { mImage = static_cast<nsDisplayImageContainer*>(aItem); FLB_LOG_PAINTED_LAYER_DECISION(this, " Tracking image: nsDisplayImageContainer covers the layer\n"); } else if (mImage) { FLB_LOG_PAINTED_LAYER_DECISION(this, " No longer tracking image\n"); mImage = nullptr; } bool isFirstVisibleItem = mVisibleRegion.IsEmpty(); if (isFirstVisibleItem) { nscolor fontSmoothingBGColor; if (aItem->ProvidesFontSmoothingBackgroundColor(&fontSmoothingBGColor)) { mFontSmoothingBackgroundColor = fontSmoothingBGColor; } } Maybe<nscolor> uniformColor = aItem->IsUniform(aState->mBuilder); // Some display items have to exist (so they can set forceTransparentSurface // below) but don't draw anything. They'll return true for isUniform but // a color with opacity 0. if (!uniformColor || NS_GET_A(*uniformColor) > 0) { // Make sure that the visible area is covered by uniform pixels. In // particular this excludes cases where the edges of the item are not // pixel-aligned (thus the item will not be truly uniform). if (uniformColor) { bool snap; nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap); if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) { uniformColor = Nothing(); FLB_LOG_PAINTED_LAYER_DECISION(this, " Display item does not cover the visible rect\n"); } } if (uniformColor) { if (isFirstVisibleItem) { // This color is all we have mSolidColor = *uniformColor; mIsSolidColorInVisibleRegion = true; } else if (mIsSolidColorInVisibleRegion && mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) && clipMatches) { // we can just blend the colors together mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor); } else { FLB_LOG_PAINTED_LAYER_DECISION(this, " Layer not a solid color: Can't blend colors togethers\n"); mIsSolidColorInVisibleRegion = false; } } else { FLB_LOG_PAINTED_LAYER_DECISION(this, " Layer is not a solid color: Display item is not uniform over the visible bound\n"); mIsSolidColorInVisibleRegion = false; } mVisibleRegion.Or(mVisibleRegion, aVisibleRect); mVisibleRegion.SimplifyOutward(4); } if (!aClippedOpaqueRegion.IsEmpty()) { for (auto iter = aClippedOpaqueRegion.RectIter(); !iter.Done(); iter.Next()) { // We don't use SimplifyInward here since it's not defined exactly // what it will discard. For our purposes the most important case // is a large opaque background at the bottom of z-order (e.g., // a canvas background), so we need to make sure that the first rect // we see doesn't get discarded. nsIntRegion tmp; tmp.Or(mOpaqueRegion, iter.Get()); // Opaque display items in chrome documents whose window is partially // transparent are always added to the opaque region. This helps ensure // that we get as much subpixel-AA as possible in the chrome. if (tmp.GetNumRects() <= 4 || aItem->Frame()->PresContext()->IsChrome()) { mOpaqueRegion = Move(tmp); } } } if (!aState->mParameters.mDisableSubpixelAntialiasingInDescendants) { nsRect componentAlpha = aItem->GetComponentAlphaBounds(aState->mBuilder); if (!componentAlpha.IsEmpty()) { nsIntRect componentAlphaRect = aState->ScaleToOutsidePixels(componentAlpha, false).Intersect(aVisibleRect); if (!mOpaqueRegion.Contains(componentAlphaRect)) { if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem, componentAlpha.Intersect(aItem->GetVisibleRect()))) { mNeedComponentAlpha = true; } else { aItem->DisableComponentAlpha(); } } } } // Ensure animated text does not get flattened, even if it forces other // content in the container to be layerized. The content backend might // not support subpixel positioning of text that animated transforms can // generate. bug 633097 if (aState->mParameters.mInActiveTransformedSubtree && (mNeedComponentAlpha || !aItem->GetComponentAlphaBounds(aState->mBuilder).IsEmpty())) { mDisableFlattening = true; } } nsRegion PaintedLayerData::CombinedTouchActionRegion() { nsRegion result; result.Or(mHorizontalPanRegion, mVerticalPanRegion); result.OrWith(mNoActionRegion); return result; } void PaintedLayerData::AccumulateEventRegions(ContainerState* aState, nsDisplayLayerEventRegions* aEventRegions) { FLB_LOG_PAINTED_LAYER_DECISION(this, "Accumulating event regions %p against pld=%p\n", aEventRegions, this); mHitRegion.OrWith(aEventRegions->HitRegion()); mMaybeHitRegion.OrWith(aEventRegions->MaybeHitRegion()); mDispatchToContentHitRegion.OrWith(aEventRegions->DispatchToContentHitRegion()); // See the comment in nsDisplayList::AddFrame, where the touch action regions // are handled. The same thing applies here. bool alreadyHadRegions = !mNoActionRegion.IsEmpty() || !mHorizontalPanRegion.IsEmpty() || !mVerticalPanRegion.IsEmpty(); mNoActionRegion.OrWith(aEventRegions->NoActionRegion()); mHorizontalPanRegion.OrWith(aEventRegions->HorizontalPanRegion()); mVerticalPanRegion.OrWith(aEventRegions->VerticalPanRegion()); if (alreadyHadRegions) { mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion()); } // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion // for quick access in FindPaintedLayerFor(). mScaledHitRegionBounds = aState->ScaleToOutsidePixels(mHitRegion.GetBounds()); mScaledMaybeHitRegionBounds = aState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds()); } PaintedLayerData ContainerState::NewPaintedLayerData(nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemScrollClip* aScrollClip, const nsPoint& aTopLeft, bool aShouldFixToViewport) { PaintedLayerData data; data.mAnimatedGeometryRoot = aAnimatedGeometryRoot; data.mScrollClip = aScrollClip; data.mAnimatedGeometryRootOffset = aTopLeft; data.mReferenceFrame = aItem->ReferenceFrame(); data.mSingleItemFixedToViewport = aShouldFixToViewport; data.mBackfaceHidden = aItem->Frame()->In3DContextAndBackfaceIsHidden(); data.mNewChildLayersIndex = mNewChildLayers.Length(); NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement(); newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot; newLayerEntry->mScrollClip = aScrollClip; // newLayerEntry->mOpaqueRegion is filled in later from // paintedLayerData->mOpaqueRegion, if necessary. // Allocate another entry for this layer's optimization to ColorLayer/ImageLayer mNewChildLayers.AppendElement(); return data; } #ifdef MOZ_DUMP_PAINTING static void DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface) { nsCString string(aItem->Name()); string.Append('-'); string.AppendInt((uint64_t)aItem); fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"", string.BeginReading()); gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile); fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n"); } #endif static void PaintInactiveLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsDisplayItem* aItem, gfxContext* aContext, nsRenderingContext* aCtx) { // This item has an inactive layer. Render it to a PaintedLayer // using a temporary BasicLayerManager. BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager); RefPtr<gfxContext> context = aContext; #ifdef MOZ_DUMP_PAINTING int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem); nsIntRect itemVisibleRect = aItem->GetVisibleRect().ToOutsidePixels(appUnitsPerDevPixel); RefPtr<DrawTarget> tempDT; if (gfxEnv::DumpPaint()) { tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( itemVisibleRect.Size(), SurfaceFormat::B8G8R8A8); if (tempDT) { context = gfxContext::CreateOrNull(tempDT); if (!context) { // Leave this as crash, it's in the debugging code, we want to know gfxDevCrash(LogReason::InvalidContext) << "PaintInactive context problem " << gfx::hexa(tempDT); return; } context->SetMatrix(gfxMatrix::Translation(-itemVisibleRect.x, -itemVisibleRect.y)); } } #endif basic->BeginTransaction(); basic->SetTarget(context); if (aItem->GetType() == nsDisplayItem::TYPE_MASK) { static_cast<nsDisplayMask*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic); if (basic->InTransaction()) { basic->AbortTransaction(); } } else if (aItem->GetType() == nsDisplayItem::TYPE_FILTER){ static_cast<nsDisplayFilter*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic); if (basic->InTransaction()) { basic->AbortTransaction(); } } else { basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder); } FrameLayerBuilder *builder = static_cast<FrameLayerBuilder*>(basic->GetUserData(&gLayerManagerLayerBuilder)); if (builder) { builder->DidEndTransaction(); } basic->SetTarget(nullptr); #ifdef MOZ_DUMP_PAINTING if (gfxEnv::DumpPaint() && tempDT) { RefPtr<SourceSurface> surface = tempDT->Snapshot(); DumpPaintedImage(aItem, surface); DrawTarget* drawTarget = aContext->GetDrawTarget(); Rect rect(itemVisibleRect.x, itemVisibleRect.y, itemVisibleRect.width, itemVisibleRect.height); drawTarget->DrawSurface(surface, rect, Rect(Point(0,0), rect.Size())); aItem->SetPainted(); } #endif } /** * Chooses a single active scrolled root for the entire display list, used * when we are flattening layers. */ bool ContainerState::ChooseAnimatedGeometryRoot(const nsDisplayList& aList, AnimatedGeometryRoot **aAnimatedGeometryRoot) { for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) { LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters); // Don't use an item that won't be part of any PaintedLayers to pick the // active scrolled root. if (layerState == LAYER_ACTIVE_FORCE) { continue; } // Try using the actual active scrolled root of the backmost item, as that // should result in the least invalidation when scrolling. *aAnimatedGeometryRoot = item->GetAnimatedGeometryRoot(); return true; } return false; } nsIntRegion ContainerState::ComputeOpaqueRect(nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot, const DisplayItemClip& aClip, nsDisplayList* aList, bool* aHideAllLayersBelow, bool* aOpaqueForAnimatedGeometryRootParent) { bool snapOpaque; nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque); if (opaque.IsEmpty()) { return nsIntRegion(); } nsIntRegion opaquePixels; nsRegion opaqueClipped; for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) { opaqueClipped.Or(opaqueClipped, aClip.ApproximateIntersectInward(iter.Get())); } if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot && opaqueClipped.Contains(mContainerBounds)) { *aHideAllLayersBelow = true; aList->SetIsOpaque(); } // Add opaque areas to the "exclude glass" region. Only do this when our // container layer is going to be the rootmost layer, otherwise transforms // etc will mess us up (and opaque contributions from other containers are // not needed). if (!nsLayoutUtils::GetCrossDocParentFrame(mContainerFrame)) { mBuilder->AddWindowOpaqueRegion(opaqueClipped); } opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque); if (IsInInactiveLayer()) { return opaquePixels; } nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot); if (sf) { nsRect displayport; bool usingDisplayport = nsLayoutUtils::GetDisplayPort((*aAnimatedGeometryRoot)->GetContent(), &displayport, RelativeTo::ScrollFrame); if (!usingDisplayport) { // No async scrolling, so all that matters is that the layer contents // cover the scrollport. displayport = sf->GetScrollPortRect(); } nsIFrame* scrollFrame = do_QueryFrame(sf); displayport += scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame); if (opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) { *aOpaqueForAnimatedGeometryRootParent = true; } } return opaquePixels; } static const DisplayItemScrollClip* InnermostScrollClipApplicableToAGR(const DisplayItemScrollClip* aItemScrollClip, AnimatedGeometryRoot* aAnimatedGeometryRoot) { // "Applicable" scroll clips are those that are for nsIScrollableFrames // that are ancestors of aAnimatedGeometryRoot or ancestors of aContainerScrollClip. // They can be applied to all items sharing this animated geometry root, so // instead of applying to the items individually, they can be applied to the // whole layer. for (const DisplayItemScrollClip* scrollClip = aItemScrollClip; scrollClip; scrollClip = scrollClip->mParent) { nsIFrame* scrolledFrame = scrollClip->mScrollableFrame->GetScrolledFrame(); if (nsLayoutUtils::IsAncestorFrameCrossDoc(scrolledFrame, *aAnimatedGeometryRoot)) { // scrollClip and all its ancestors are applicable. return scrollClip; } } return nullptr; } Maybe<size_t> ContainerState::SetupMaskLayerForScrolledClip(Layer* aLayer, const DisplayItemClip& aClip) { if (aClip.GetRoundedRectCount() > 0) { Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount()); if (RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, maskLayerIndex, aClip.GetRoundedRectCount())) { aLayer->AddAncestorMaskLayer(maskLayer); return maskLayerIndex; } // Fall through to |return Nothing()|. } return Nothing(); } void ContainerState::SetupMaskLayerForCSSMask(Layer* aLayer, nsDisplayMask* aMaskItem) { MOZ_ASSERT(mManager->IsCompositingCheap()); RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(MaskLayerKey(aLayer, Nothing()), [](Layer* aMaskLayer) { aMaskLayer->SetUserData(&gCSSMaskLayerUserData, new CSSMaskLayerUserData()); } ); CSSMaskLayerUserData* oldUserData = static_cast<CSSMaskLayerUserData*>(maskLayer->GetUserData(&gCSSMaskLayerUserData)); bool snap; nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap); nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap); CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect); if (*oldUserData == newUserData) { aLayer->SetMaskLayer(maskLayer); return; } int32_t maxSize = mManager->GetMaxTextureSize(); IntSize surfaceSize(std::min(itemRect.width, maxSize), std::min(itemRect.height, maxSize)); if (surfaceSize.IsEmpty()) { // Return early if we know that the size of this mask surface is empty. return; } MaskImageData imageData(surfaceSize, mManager); RefPtr<DrawTarget> dt = imageData.CreateDrawTarget(); if (!dt || !dt->IsValid()) { NS_WARNING("Could not create DrawTarget for mask layer."); return; } RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt); maskCtx->SetMatrix(gfxMatrix::Translation(-itemRect.TopLeft())); maskCtx->Multiply(gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale)); if (!aMaskItem->PaintMask(mBuilder, maskCtx)) { // Mostly because of mask resource is not ready. return; } // Setup mask layer offset. Matrix4x4 matrix; matrix.PreTranslate(itemRect.x, itemRect.y, 0); matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0); maskLayer->SetBaseTransform(matrix); RefPtr<ImageContainer> imgContainer = imageData.CreateImageAndImageContainer(); if (!imgContainer) { return; } maskLayer->SetContainer(imgContainer); *oldUserData = newUserData; aLayer->SetMaskLayer(maskLayer); } /* * Iterate through the non-clip items in aList and its descendants. * For each item we compute the effective clip rect. Each item is assigned * to a layer. We invalidate the areas in PaintedLayers where an item * has moved from one PaintedLayer to another. Also, * aState->mInvalidPaintedContent is invalidated in every PaintedLayer. * We set the clip rect for items that generated their own layer, and * create a mask layer to do any rounded rect clipping. * (PaintedLayers don't need a clip rect on the layer, we clip the items * individually when we draw them.) * We set the visible rect for all layers, although the actual setting * of visible rects for some PaintedLayers is deferred until the calling * of ContainerState::Finish. */ void ContainerState::ProcessDisplayItems(nsDisplayList* aList) { PROFILER_LABEL("ContainerState", "ProcessDisplayItems", js::ProfileEntry::Category::GRAPHICS); AnimatedGeometryRoot* lastAnimatedGeometryRoot = mContainerAnimatedGeometryRoot; nsPoint lastAGRTopLeft; nsPoint topLeft(0,0); // When NO_COMPONENT_ALPHA is set, items will be flattened into a single // layer, so we need to choose which active scrolled root to use for all // items. if (mFlattenToSingleLayer) { if (ChooseAnimatedGeometryRoot(*aList, &lastAnimatedGeometryRoot)) { lastAGRTopLeft = (*lastAnimatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame); } } int32_t maxLayers = gfxPrefs::MaxActiveLayers(); int layerCount = 0; nsDisplayList savedItems; nsDisplayItem* item; while ((item = aList->RemoveBottom()) != nullptr) { nsDisplayItem::Type itemType = item->GetType(); // If the item is a event regions item, but is empty (has no regions in it) // then we should just throw it out if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { nsDisplayLayerEventRegions* eventRegions = static_cast<nsDisplayLayerEventRegions*>(item); if (eventRegions->IsEmpty()) { item->~nsDisplayItem(); continue; } } // Peek ahead to the next item and try merging with it or swapping with it // if necessary. nsDisplayItem* aboveItem; while ((aboveItem = aList->GetBottom()) != nullptr) { if (aboveItem->TryMerge(item)) { aList->RemoveBottom(); item->~nsDisplayItem(); item = aboveItem; itemType = item->GetType(); } else { break; } } nsDisplayList* itemSameCoordinateSystemChildren = item->GetSameCoordinateSystemChildren(); if (item->ShouldFlattenAway(mBuilder)) { aList->AppendToBottom(itemSameCoordinateSystemChildren); item->~nsDisplayItem(); continue; } savedItems.AppendToTop(item); NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item), "items in a container layer should all have the same app units per dev pixel"); if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) { aList->SetNeedsTransparentSurface(); } if (mParameters.mForEventsAndPluginsOnly && !item->GetChildren() && (itemType != nsDisplayItem::TYPE_LAYER_EVENT_REGIONS && itemType != nsDisplayItem::TYPE_PLUGIN)) { continue; } LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters); if (layerState == LAYER_INACTIVE && nsDisplayItem::ForceActiveLayers()) { layerState = LAYER_ACTIVE; } bool forceInactive; AnimatedGeometryRoot* animatedGeometryRoot; AnimatedGeometryRoot* animatedGeometryRootForClip = nullptr; if (mFlattenToSingleLayer && layerState != LAYER_ACTIVE_FORCE) { forceInactive = true; animatedGeometryRoot = lastAnimatedGeometryRoot; topLeft = lastAGRTopLeft; } else { forceInactive = false; if (mManager->IsWidgetLayerManager()) { animatedGeometryRoot = item->GetAnimatedGeometryRoot(); animatedGeometryRootForClip = item->AnimatedGeometryRootForScrollMetadata(); } else { // For inactive layer subtrees, splitting content into PaintedLayers // based on animated geometry roots is pointless. It's more efficient // to build the minimum number of layers. animatedGeometryRoot = mContainerAnimatedGeometryRoot; } topLeft = (*animatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame); } if (!animatedGeometryRootForClip) { animatedGeometryRootForClip = animatedGeometryRoot; } const DisplayItemScrollClip* itemScrollClip = item->ScrollClip(); // Now we need to separate the item's scroll clip chain into those scroll // clips that can be applied to the whole layer (i.e. to all items // sharing the item's animated geometry root), and those that need to be // applied to the item itself. const DisplayItemScrollClip* agrScrollClip = InnermostScrollClipApplicableToAGR(itemScrollClip, animatedGeometryRootForClip); MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(agrScrollClip, itemScrollClip)); if (agrScrollClip != itemScrollClip) { // Pick up any scroll clips that should apply to the item and apply them. DisplayItemClip clip = item->GetClip(); for (const DisplayItemScrollClip* scrollClip = itemScrollClip; scrollClip && scrollClip != agrScrollClip && scrollClip != mContainerScrollClip; scrollClip = scrollClip->mParent) { if (scrollClip->mClip) { clip.IntersectWith(*scrollClip->mClip); } } item->SetClip(mBuilder, clip); } bool clipMovesWithLayer = (animatedGeometryRoot == animatedGeometryRootForClip); bool shouldFixToViewport = !clipMovesWithLayer && !(*animatedGeometryRoot)->GetParent() && item->ShouldFixToViewport(mBuilder); // For items that are fixed to the viewport, remove their clip at the // display item level because additional areas could be brought into // view by async scrolling. Save the clip so we can set it on the layer // instead later. DisplayItemClip fixedToViewportClip = DisplayItemClip::NoClip(); if (shouldFixToViewport) { fixedToViewportClip = item->GetClip(); item->SetClip(mBuilder, DisplayItemClip::NoClip()); } bool snap; nsRect itemContent = item->GetBounds(mBuilder, &snap); if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { nsDisplayLayerEventRegions* eventRegions = static_cast<nsDisplayLayerEventRegions*>(item); itemContent = eventRegions->GetHitRegionBounds(mBuilder, &snap); } nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap); bool prerenderedTransform = itemType == nsDisplayItem::TYPE_TRANSFORM && static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder); ParentLayerIntRect clipRect; const DisplayItemClip& itemClip = item->GetClip(); if (itemClip.HasClip()) { itemContent.IntersectRect(itemContent, itemClip.GetClipRect()); clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClip.GetClipRect())); if (!prerenderedTransform) { itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect()); } clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset)); } #ifdef DEBUG nsRect bounds = itemContent; bool dummy; if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { bounds = item->GetBounds(mBuilder, &dummy); if (itemClip.HasClip()) { bounds.IntersectRect(bounds, itemClip.GetClipRect()); } } bounds = fixedToViewportClip.ApplyNonRoundedIntersection(bounds); if (!bounds.IsEmpty()) { for (const DisplayItemScrollClip* scrollClip = itemScrollClip; scrollClip && scrollClip != mContainerScrollClip; scrollClip = scrollClip->mParent) { if (scrollClip->mClip) { if (scrollClip->mIsAsyncScrollable) { bounds = scrollClip->mClip->GetClipRect(); } else { bounds = scrollClip->mClip->ApplyNonRoundedIntersection(bounds); } } } } ((nsRect&)mAccumulatedChildBounds).UnionRect(mAccumulatedChildBounds, bounds); #endif nsIntRect itemVisibleRect = itemDrawRect; // We haven't computed visibility at this point, so item->GetVisibleRect() // is just the dirty rect that item was initialized with. We intersect it // with the clipped item bounds to get a tighter visible rect. itemVisibleRect = itemVisibleRect.Intersect( ScaleToOutsidePixels(item->GetVisibleRect(), false)); if (maxLayers != -1 && layerCount >= maxLayers) { forceInactive = true; } // Assign the item to a layer if (layerState == LAYER_ACTIVE_FORCE || (layerState == LAYER_INACTIVE && !mManager->IsWidgetLayerManager()) || (!forceInactive && (layerState == LAYER_ACTIVE_EMPTY || layerState == LAYER_ACTIVE))) { layerCount++; // LAYER_ACTIVE_EMPTY means the layer is created just for its metadata. // We should never see an empty layer with any visible content! NS_ASSERTION(layerState != LAYER_ACTIVE_EMPTY || itemVisibleRect.IsEmpty(), "State is LAYER_ACTIVE_EMPTY but visible rect is not."); // As long as the new layer isn't going to be a PaintedLayer, // InvalidateForLayerChange doesn't need the new layer pointer. // We also need to check the old data now, because BuildLayer // can overwrite it. InvalidateForLayerChange(item, nullptr); // If the item would have its own layer but is invisible, just hide it. // Note that items without their own layers can't be skipped this // way, since their PaintedLayer may decide it wants to draw them // into its buffer even if they're currently covered. if (itemVisibleRect.IsEmpty() && !item->ShouldBuildLayerEvenIfInvisible(mBuilder)) { continue; } if (mScrollClipForPerspectiveChild) { // We are the single transform child item of an nsDisplayPerspective. // Our parent forwarded a scroll clip to us. Pick it up. // We do this after any clipping has been applied, because this // forwarded scroll clip is only used for scrolling (in the form of // APZ frame metrics), not for clipping - the clip still belongs on // the perspective item. MOZ_ASSERT(itemType == nsDisplayItem::TYPE_TRANSFORM); MOZ_ASSERT(!itemScrollClip); MOZ_ASSERT(!agrScrollClip); MOZ_ASSERT(DisplayItemScrollClip::IsAncestor(mContainerScrollClip, mScrollClipForPerspectiveChild)); itemScrollClip = mScrollClipForPerspectiveChild; agrScrollClip = mScrollClipForPerspectiveChild; } // 3D-transformed layers don't necessarily draw in the order in which // they're added to their parent container layer. bool mayDrawOutOfOrder = itemType == nsDisplayItem::TYPE_TRANSFORM && (item->Frame()->Combines3DTransformWithAncestors() || item->Frame()->Extend3DContext()); // Let mPaintedLayerDataTree know about this item, so that // FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this // item, even though it's not in any PaintedLayerDataStack. // Ideally we'd only need the "else" case here and have // mPaintedLayerDataTree figure out the right clip from the animated // geometry root that we give it, but it can't easily figure about // overflow:hidden clips on ancestors just by looking at the frame. // So we'll do a little hand holding and pass the clip instead of the // visible rect for the two important cases. nscolor uniformColor = NS_RGBA(0,0,0,0); nscolor* uniformColorPtr = (mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr : &uniformColor; nsIntRect clipRectUntyped; const DisplayItemClip& layerClip = shouldFixToViewport ? fixedToViewportClip : itemClip; ParentLayerIntRect layerClipRect; nsIntRect* clipPtr = nullptr; if (layerClip.HasClip()) { layerClipRect = ViewAs<ParentLayerPixel>( ScaleToNearestPixels(layerClip.GetClipRect()) + mParameters.mOffset); clipRectUntyped = layerClipRect.ToUnknownRect(); clipPtr = &clipRectUntyped; } if (*animatedGeometryRoot == item->Frame() && *animatedGeometryRoot != mBuilder->RootReferenceFrame()) { // This is the case for scrollbar thumbs, for example. In that case the // clip we care about is the overflow:hidden clip on the scrollbar. mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot->mParentAGR, clipPtr, uniformColorPtr); } else if (prerenderedTransform) { mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot, clipPtr, uniformColorPtr); } else if (shouldFixToViewport) { mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRootForClip, clipPtr, uniformColorPtr); } else { // Using itemVisibleRect here isn't perfect. itemVisibleRect can be // larger or smaller than the potential bounds of item's contents in // animatedGeometryRoot: It's too large if there's a clipped display // port somewhere among item's contents (see bug 1147673), and it can // be too small if the contents can move, because it only looks at the // contents' current bounds and doesn't anticipate any animations. // Time will tell whether this is good enough, or whether we need to do // something more sophisticated here. mPaintedLayerDataTree.AddingOwnLayer(animatedGeometryRoot, &itemVisibleRect, uniformColorPtr); } ContainerLayerParameters params = mParameters; params.mBackgroundColor = uniformColor; params.mLayerCreationHint = GetLayerCreationHint(animatedGeometryRoot); params.mScrollClip = agrScrollClip; params.mScrollClipForPerspectiveChild = nullptr; if (itemType == nsDisplayItem::TYPE_PERSPECTIVE) { // Perspective items have a single child item, an nsDisplayTransform. // If the perspective item is scrolled, but the perspective-inducing // frame is outside the scroll frame (indicated by this items AGR // being outside that scroll frame), we have to take special care to // make APZ scrolling work properly. APZ needs us to put the scroll // frame's FrameMetrics on our child transform ContainerLayer instead. // Our agrScrollClip is the scroll clip that's applicable to our // perspective frame, so it won't be the scroll clip for the scrolled // frame in the case that we care about, and we'll forward that scroll // clip to our child. params.mScrollClipForPerspectiveChild = itemScrollClip; } // Just use its layer. // Set layerContentsVisibleRect.width/height to -1 to indicate we // currently don't know. If BuildContainerLayerFor gets called by // item->BuildLayer, this will be set to a proper rect. nsIntRect layerContentsVisibleRect(0, 0, -1, -1); params.mLayerContentsVisibleRect = &layerContentsVisibleRect; RefPtr<Layer> ownLayer = item->BuildLayer(mBuilder, mManager, params); if (!ownLayer) { continue; } NS_ASSERTION(!ownLayer->AsPaintedLayer(), "Should never have created a dedicated Painted layer!"); if (item->BackfaceIsHidden()) { ownLayer->SetContentFlags(ownLayer->GetContentFlags() | Layer::CONTENT_BACKFACE_HIDDEN); } else { ownLayer->SetContentFlags(ownLayer->GetContentFlags() & ~Layer::CONTENT_BACKFACE_HIDDEN); } nsRect invalid; if (item->IsInvalid(invalid)) { ownLayer->SetInvalidRectToVisibleRegion(); } // If it's not a ContainerLayer, we need to apply the scale transform // ourselves. if (!ownLayer->AsContainerLayer()) { ownLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale); } // Update that layer's clip and visible rects. NS_ASSERTION(ownLayer->Manager() == mManager, "Wrong manager"); NS_ASSERTION(!ownLayer->HasUserData(&gLayerManagerUserData), "We shouldn't have a FrameLayerBuilder-managed layer here!"); NS_ASSERTION(layerClip.HasClip() || layerClip.GetRoundedRectCount() == 0, "If we have rounded rects, we must have a clip rect"); // It has its own layer. Update that layer's clip and visible rects. ownLayer->SetClipRect(Nothing()); ownLayer->SetScrolledClip(Nothing()); if (layerClip.HasClip()) { // For layers fixed to the viewport, the clip becomes part of the // layer's scrolled clip. Otherwise, it becomes part of the layer clip. if (shouldFixToViewport) { LayerClip scrolledClip; scrolledClip.SetClipRect(layerClipRect); if (layerClip.GetRoundedRectCount() > 0) { scrolledClip.SetMaskLayerIndex( SetupMaskLayerForScrolledClip(ownLayer.get(), layerClip)); } ownLayer->SetScrolledClip(Some(scrolledClip)); } else { ownLayer->SetClipRect(Some(layerClipRect)); // rounded rectangle clipping using mask layers // (must be done after visible rect is set on layer) if (layerClip.GetRoundedRectCount() > 0) { SetupMaskLayer(ownLayer, layerClip); } } } else if (item->GetType() == nsDisplayItem::TYPE_MASK) { nsDisplayMask* maskItem = static_cast<nsDisplayMask*>(item); SetupMaskLayerForCSSMask(ownLayer, maskItem); } ContainerLayer* oldContainer = ownLayer->GetParent(); if (oldContainer && oldContainer != mContainerLayer) { oldContainer->RemoveChild(ownLayer); } NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0, "Layer already in list???"); NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement(); newLayerEntry->mLayer = ownLayer; newLayerEntry->mAnimatedGeometryRoot = animatedGeometryRoot; newLayerEntry->mScrollClip = agrScrollClip; newLayerEntry->mLayerState = layerState; // Don't attempt to flatten compnent alpha layers that are within // a forced active layer, or an active transform; if (itemType == nsDisplayItem::TYPE_TRANSFORM || layerState == LAYER_ACTIVE_FORCE) { newLayerEntry->mPropagateComponentAlphaFlattening = false; } // nsDisplayTransform::BuildLayer must set layerContentsVisibleRect. // We rely on this to ensure 3D transforms compute a reasonable // layer visible region. NS_ASSERTION(itemType != nsDisplayItem::TYPE_TRANSFORM || layerContentsVisibleRect.width >= 0, "Transform items must set layerContentsVisibleRect!"); if (mLayerBuilder->IsBuildingRetainedLayers()) { newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect; if (itemType == nsDisplayItem::TYPE_PERSPECTIVE || (itemType == nsDisplayItem::TYPE_TRANSFORM && (item->Frame()->Extend3DContext() || item->Frame()->Combines3DTransformWithAncestors() || item->Frame()->HasPerspective()))) { // Give untransformed visible region as outer visible region // to avoid failure caused by singular transforms. newLayerEntry->mUntransformedVisibleRegion = true; newLayerEntry->mVisibleRegion = item->GetVisibleRectForChildren().ToOutsidePixels(mAppUnitsPerDevPixel); } else { newLayerEntry->mVisibleRegion = itemVisibleRect; } newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(item, animatedGeometryRoot, layerClip, aList, &newLayerEntry->mHideAllLayersBelow, &newLayerEntry->mOpaqueForAnimatedGeometryRootParent); } else { bool useChildrenVisible = itemType == nsDisplayItem::TYPE_TRANSFORM && (item->Frame()->IsPreserve3DLeaf() || item->Frame()->HasPerspective()); const nsIntRegion &visible = useChildrenVisible ? item->GetVisibleRectForChildren().ToOutsidePixels(mAppUnitsPerDevPixel): itemVisibleRect; SetOuterVisibleRegionForLayer(ownLayer, visible, layerContentsVisibleRect.width >= 0 ? &layerContentsVisibleRect : nullptr, useChildrenVisible); } if (itemType == nsDisplayItem::TYPE_SCROLL_INFO_LAYER) { nsDisplayScrollInfoLayer* scrollItem = static_cast<nsDisplayScrollInfoLayer*>(item); newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false; newLayerEntry->mBaseScrollMetadata = scrollItem->ComputeScrollMetadata(ownLayer, mParameters); } else if ((itemType == nsDisplayItem::TYPE_SUBDOCUMENT || itemType == nsDisplayItem::TYPE_ZOOM || itemType == nsDisplayItem::TYPE_RESOLUTION) && gfxPrefs::LayoutUseContainersForRootFrames()) { newLayerEntry->mBaseScrollMetadata = static_cast<nsDisplaySubDocument*>(item)->ComputeScrollMetadata(ownLayer, mParameters); } /** * No need to allocate geometry for items that aren't * part of a PaintedLayer. */ mLayerBuilder->AddLayerDisplayItem(ownLayer, item, layerState, nullptr); } else { PaintedLayerData* paintedLayerData = mPaintedLayerDataTree.FindPaintedLayerFor(animatedGeometryRoot, agrScrollClip, itemVisibleRect, item->Frame()->In3DContextAndBackfaceIsHidden(), [&]() { return NewPaintedLayerData(item, animatedGeometryRoot, agrScrollClip, topLeft, shouldFixToViewport); }); if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { nsDisplayLayerEventRegions* eventRegions = static_cast<nsDisplayLayerEventRegions*>(item); paintedLayerData->AccumulateEventRegions(this, eventRegions); } else { // check to see if the new item has rounded rect clips in common with // other items in the layer if (mManager->IsWidgetLayerManager()) { paintedLayerData->UpdateCommonClipCount(itemClip); } nsIntRegion opaquePixels = ComputeOpaqueRect(item, animatedGeometryRoot, itemClip, aList, &paintedLayerData->mHideAllLayersBelow, &paintedLayerData->mOpaqueForAnimatedGeometryRootParent); MOZ_ASSERT(nsIntRegion(itemDrawRect).Contains(opaquePixels)); opaquePixels.AndWith(itemVisibleRect); paintedLayerData->Accumulate(this, item, opaquePixels, itemVisibleRect, itemClip, layerState); // If we removed the clip from the display item above because it's // fixed to the viewport, save it on the PaintedLayerData so we can // set it on the layer later. if (fixedToViewportClip.HasClip()) { paintedLayerData->mItemClip = fixedToViewportClip; } if (!paintedLayerData->mLayer) { // Try to recycle the old layer of this display item. RefPtr<PaintedLayer> layer = AttemptToRecyclePaintedLayer(animatedGeometryRoot, item, topLeft); if (layer) { paintedLayerData->mLayer = layer; NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0, "Layer already in list???"); mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer = layer.forget(); } } } } if (itemSameCoordinateSystemChildren && itemSameCoordinateSystemChildren->NeedsTransparentSurface()) { aList->SetNeedsTransparentSurface(); } } aList->AppendToTop(&savedItems); } void ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer) { NS_ASSERTION(aItem->GetPerFrameKey(), "Display items that render using Thebes must have a key"); nsDisplayItemGeometry* oldGeometry = nullptr; DisplayItemClip* oldClip = nullptr; Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem, &oldGeometry, &oldClip); if (aNewLayer != oldLayer && oldLayer) { // The item has changed layers. // Invalidate the old bounds in the old layer and new bounds in the new layer. PaintedLayer* t = oldLayer->AsPaintedLayer(); if (t && oldGeometry) { // Note that whenever the layer's scale changes, we invalidate the whole thing, // so it doesn't matter whether we are using the old scale at last paint // or a new scale here #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Display item type %s(%p) changed layers %p to %p!\n", aItem->Name(), aItem->Frame(), t, aNewLayer); } #endif InvalidatePostTransformRegion(t, oldGeometry->ComputeInvalidationRegion(), *oldClip, mLayerBuilder->GetLastPaintOffset(t)); } // Clear the old geometry so that invalidation thinks the item has been // added this paint. mLayerBuilder->ClearCachedGeometry(aItem); aItem->NotifyRenderingChanged(); } } void FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData) { nsDisplayItem *item = aData->mItem; PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer(); if (!item || !paintedLayer) { aData->EndUpdate(); return; } PaintedLayerItemsEntry* entry = mPaintedLayerItems.GetEntry(paintedLayer); nsAutoPtr<nsDisplayItemGeometry> geometry; PaintedDisplayItemLayerUserData* layerData = static_cast<PaintedDisplayItemLayerUserData*>(aData->mLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); nsPoint shift = layerData->mAnimatedGeometryRootOrigin - layerData->mLastAnimatedGeometryRootOrigin; const DisplayItemClip& clip = item->GetClip(); // If the frame is marked as invalidated, and didn't specify a rect to invalidate then we want to // invalidate both the old and new bounds, otherwise we only want to invalidate the changed areas. // If we do get an invalid rect, then we want to add this on top of the change areas. nsRect invalid; nsRegion combined; bool notifyRenderingChanged = true; if (!aData->mGeometry) { // This item is being added for the first time, invalidate its entire area. geometry = item->AllocateGeometry(mDisplayListBuilder); combined = clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion()); #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Display item type %s(%p) added to layer %p!\n", item->Name(), item->Frame(), aData->mLayer.get()); } #endif } else if (aData->mIsInvalid || (item->IsInvalid(invalid) && invalid.IsEmpty())) { // Layout marked item/frame as needing repainting (without an explicit rect), invalidate the entire old and new areas. geometry = item->AllocateGeometry(mDisplayListBuilder); combined = aData->mClip.ApplyNonRoundedIntersection(aData->mGeometry->ComputeInvalidationRegion()); combined.MoveBy(shift); combined.Or(combined, clip.ApplyNonRoundedIntersection(geometry->ComputeInvalidationRegion())); #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Display item type %s(%p) (in layer %p) belongs to an invalidated frame!\n", item->Name(), item->Frame(), aData->mLayer.get()); } #endif } else { // Let the display item check for geometry changes and decide what needs to be // repainted. const nsTArray<nsIFrame*>& changedFrames = aData->GetFrameListChanges(); aData->mGeometry->MoveBy(shift); item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry, &combined); // We have an optimization to cache the drawing of background-attachment: fixed canvas // background images so we can scroll and just blit them when they are flattened into // the same layer as scrolling content. NotifyRenderingChanged is only used to tell // the canvas bg image item to purge this cache. We want to be careful not to accidentally // purge the cache if we are just invalidating due to scrolling (ie the background image // moves on the scrolling layer but it's rendering stays the same) so if // AddOffsetAndComputeDifference is the only thing that will invalidate we skip the // NotifyRenderingChanged call (ComputeInvalidationRegion for background images also calls // NotifyRenderingChanged if anything changes). // Only allocate a new geometry object if something actually changed, otherwise the existing // one should be fine. We always reallocate for inactive layers, since these types don't // implement ComputeInvalidateRegion (and rely on the ComputeDifferences call in // AddPaintedDisplayItem instead). if (!combined.IsEmpty() || aData->mLayerState == LAYER_INACTIVE) { geometry = item->AllocateGeometry(mDisplayListBuilder); } else if (aData->mClip == clip && invalid.IsEmpty() && changedFrames.Length() == 0) { notifyRenderingChanged = false; } aData->mClip.AddOffsetAndComputeDifference(entry->mCommonClipCount, shift, aData->mGeometry->ComputeInvalidationRegion(), clip, entry->mLastCommonClipCount, geometry ? geometry->ComputeInvalidationRegion() : aData->mGeometry->ComputeInvalidationRegion(), &combined); // Add in any rect that the frame specified combined.Or(combined, invalid); for (uint32_t i = 0; i < changedFrames.Length(); i++) { combined.Or(combined, changedFrames[i]->GetVisualOverflowRect()); } // Restrict invalidation to the clipped region nsRegion clipRegion; if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) { combined.And(combined, clipRegion); } #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { if (!combined.IsEmpty()) { printf_stderr("Display item type %s(%p) (in layer %p) changed geometry!\n", item->Name(), item->Frame(), aData->mLayer.get()); } } #endif } if (!combined.IsEmpty()) { if (notifyRenderingChanged) { item->NotifyRenderingChanged(); } InvalidatePostTransformRegion(paintedLayer, combined.ScaleToOutsidePixels(layerData->mXScale, layerData->mYScale, layerData->mAppUnitsPerDevPixel), layerData->mTranslation); } aData->EndUpdate(geometry); } void FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData, nsDisplayItem* aItem, const DisplayItemClip& aClip, ContainerState& aContainerState, LayerState aLayerState, const nsPoint& aTopLeft) { PaintedLayer* layer = aLayerData->mLayer; PaintedDisplayItemLayerUserData* paintedData = static_cast<PaintedDisplayItemLayerUserData*> (layer->GetUserData(&gPaintedDisplayItemLayerUserData)); RefPtr<BasicLayerManager> tempManager; nsIntRect intClip; bool hasClip = false; if (aLayerState != LAYER_NONE) { DisplayItemData *data = GetDisplayItemDataForManager(aItem, layer->Manager()); if (data) { tempManager = data->mInactiveManager; } if (!tempManager) { tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE); } // We need to grab these before calling AddLayerDisplayItem because it will overwrite them. nsRegion clip; DisplayItemClip* oldClip = nullptr; GetOldLayerFor(aItem, nullptr, &oldClip); hasClip = aClip.ComputeRegionInClips(oldClip, aTopLeft - paintedData->mLastAnimatedGeometryRootOrigin, &clip); if (hasClip) { intClip = clip.GetBounds().ScaleToOutsidePixels(paintedData->mXScale, paintedData->mYScale, paintedData->mAppUnitsPerDevPixel); } } AddLayerDisplayItem(layer, aItem, aLayerState, tempManager); PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(layer); if (entry) { entry->mContainerLayerFrame = aContainerState.GetContainerFrame(); if (entry->mContainerLayerGeneration == 0) { entry->mContainerLayerGeneration = mContainerLayerGeneration; } if (tempManager) { FLB_LOG_PAINTED_LAYER_DECISION(aLayerData, "Creating nested FLB for item %p\n", aItem); FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData); tempManager->BeginTransaction(); if (mRetainingManager) { layerBuilder->DidBeginRetainedLayerTransaction(tempManager); } UniquePtr<LayerProperties> props(LayerProperties::CloneFrom(tempManager->GetRoot())); RefPtr<Layer> tmpLayer = aItem->BuildLayer(mDisplayListBuilder, tempManager, ContainerLayerParameters()); // We have no easy way of detecting if this transaction will ever actually get finished. // For now, I've just silenced the warning with nested transactions in BasicLayers.cpp if (!tmpLayer) { tempManager->EndTransaction(nullptr, nullptr); tempManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); return; } bool snap; nsRect visibleRect = aItem->GetVisibleRect().Intersect(aItem->GetBounds(mDisplayListBuilder, &snap)); nsIntRegion rgn = visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel); SetOuterVisibleRegion(tmpLayer, &rgn); // If BuildLayer didn't call BuildContainerLayerFor, then our new layer won't have been // stored in layerBuilder. Manually add it now. if (mRetainingManager) { #ifdef DEBUG_DISPLAY_ITEM_DATA LayerManagerData* parentLmd = static_cast<LayerManagerData*> (layer->Manager()->GetUserData(&gLayerManagerUserData)); LayerManagerData* lmd = static_cast<LayerManagerData*> (tempManager->GetUserData(&gLayerManagerUserData)); lmd->mParent = parentLmd; #endif layerBuilder->StoreDataForFrame(aItem, tmpLayer, LAYER_ACTIVE); } tempManager->SetRoot(tmpLayer); layerBuilder->WillEndTransaction(); tempManager->AbortTransaction(); #ifdef MOZ_DUMP_PAINTING if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) { fprintf_stderr(gfxUtils::sDumpPaintFile, "Basic layer tree for painting contents of display item %s(%p):\n", aItem->Name(), aItem->Frame()); std::stringstream stream; tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile()); fprint_stderr(gfxUtils::sDumpPaintFile, stream); // not a typo, fprint_stderr declared in LayersLogging.h } #endif nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer); props->MoveBy(-offset); // Effective transforms are needed by ComputeDifferences(). tmpLayer->ComputeEffectiveTransforms(Matrix4x4()); nsIntRegion invalid = props->ComputeDifferences(tmpLayer, nullptr); if (aLayerState == LAYER_SVG_EFFECTS) { invalid = nsSVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(aItem->Frame(), aItem->ToReferenceFrame(), invalid); } if (!invalid.IsEmpty()) { #ifdef MOZ_DUMP_PAINTING if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { printf_stderr("Inactive LayerManager(%p) for display item %s(%p) has an invalid region - invalidating layer %p\n", tempManager.get(), aItem->Name(), aItem->Frame(), layer); } #endif invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale); if (hasClip) { invalid.And(invalid, intClip); } InvalidatePostTransformRegion(layer, invalid, GetTranslationForPaintedLayer(layer)); } } ClippedDisplayItem* cdi = entry->mItems.AppendElement(ClippedDisplayItem(aItem, mContainerLayerGeneration)); cdi->mInactiveLayerManager = tempManager; } } FrameLayerBuilder::DisplayItemData* FrameLayerBuilder::StoreDataForFrame(nsDisplayItem* aItem, Layer* aLayer, LayerState aState) { DisplayItemData* oldData = GetDisplayItemDataForManager(aItem, mRetainingManager); if (oldData) { if (!oldData->mUsed) { oldData->BeginUpdate(aLayer, aState, mContainerLayerGeneration, aItem); } return oldData; } LayerManagerData* lmd = static_cast<LayerManagerData*> (mRetainingManager->GetUserData(&gLayerManagerUserData)); RefPtr<DisplayItemData> data = new DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer); data->BeginUpdate(aLayer, aState, mContainerLayerGeneration, aItem); lmd->mDisplayItems.PutEntry(data); return data; } void FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame, uint32_t aDisplayItemKey, Layer* aLayer, LayerState aState) { DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey); if (oldData && oldData->mFrameList.Length() == 1) { oldData->BeginUpdate(aLayer, aState, mContainerLayerGeneration); return; } LayerManagerData* lmd = static_cast<LayerManagerData*> (mRetainingManager->GetUserData(&gLayerManagerUserData)); RefPtr<DisplayItemData> data = new DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame); data->BeginUpdate(aLayer, aState, mContainerLayerGeneration); lmd->mDisplayItems.PutEntry(data); } FrameLayerBuilder::ClippedDisplayItem::ClippedDisplayItem(nsDisplayItem* aItem, uint32_t aGeneration) : mItem(aItem) , mContainerLayerGeneration(aGeneration) { } FrameLayerBuilder::ClippedDisplayItem::~ClippedDisplayItem() { if (mInactiveLayerManager) { mInactiveLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr); } } FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayer *aKey) : nsPtrHashKey<PaintedLayer>(aKey) , mContainerLayerFrame(nullptr) , mLastCommonClipCount(0) , mContainerLayerGeneration(0) , mHasExplicitLastPaintOffset(false) , mCommonClipCount(0) { } FrameLayerBuilder::PaintedLayerItemsEntry::PaintedLayerItemsEntry(const PaintedLayerItemsEntry& aOther) : nsPtrHashKey<PaintedLayer>(aOther.mKey) , mItems(aOther.mItems) { NS_ERROR("Should never be called, since we ALLOW_MEMMOVE"); } FrameLayerBuilder::PaintedLayerItemsEntry::~PaintedLayerItemsEntry() { } void FrameLayerBuilder::AddLayerDisplayItem(Layer* aLayer, nsDisplayItem* aItem, LayerState aLayerState, BasicLayerManager* aManager) { if (aLayer->Manager() != mRetainingManager) return; DisplayItemData *data = StoreDataForFrame(aItem, aLayer, aLayerState); data->mInactiveManager = aManager; } nsIntPoint FrameLayerBuilder::GetLastPaintOffset(PaintedLayer* aLayer) { PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(aLayer); if (entry) { if (entry->mContainerLayerGeneration == 0) { entry->mContainerLayerGeneration = mContainerLayerGeneration; } if (entry->mHasExplicitLastPaintOffset) return entry->mLastPaintOffset; } return GetTranslationForPaintedLayer(aLayer); } void FrameLayerBuilder::SavePreviousDataForLayer(PaintedLayer* aLayer, uint32_t aClipCount) { PaintedLayerItemsEntry* entry = mPaintedLayerItems.PutEntry(aLayer); if (entry) { if (entry->mContainerLayerGeneration == 0) { entry->mContainerLayerGeneration = mContainerLayerGeneration; } entry->mLastPaintOffset = GetTranslationForPaintedLayer(aLayer); entry->mHasExplicitLastPaintOffset = true; entry->mLastCommonClipCount = aClipCount; } } bool FrameLayerBuilder::CheckInLayerTreeCompressionMode() { if (mInLayerTreeCompressionMode) { return true; } // If we wanted to be in layer tree compression mode, but weren't, then scheduled // a delayed repaint where we will be. mRootPresContext->PresShell()->GetRootFrame()->SchedulePaint(nsIFrame::PAINT_DELAYED_COMPRESS); return false; } void ContainerState::CollectOldLayers() { for (Layer* layer = mContainerLayer->GetFirstChild(); layer; layer = layer->GetNextSibling()) { NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData), "Mask layers should not be part of the layer tree."); if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type"); mPaintedLayersAvailableForRecycling.PutEntry(static_cast<PaintedLayer*>(layer)); } if (Layer* maskLayer = layer->GetMaskLayer()) { NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE, "Could not recycle mask layer, unsupported layer type."); mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Nothing()), static_cast<ImageLayer*>(maskLayer)); } for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) { Layer* maskLayer = layer->GetAncestorMaskLayerAt(i); NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE, "Could not recycle mask layer, unsupported layer type."); mRecycledMaskImageLayers.Put(MaskLayerKey(layer, Some(i)), static_cast<ImageLayer*>(maskLayer)); } } } struct OpaqueRegionEntry { AnimatedGeometryRoot* mAnimatedGeometryRoot; nsIntRegion mOpaqueRegion; }; static OpaqueRegionEntry* FindOpaqueRegionEntry(nsTArray<OpaqueRegionEntry>& aEntries, AnimatedGeometryRoot* aAnimatedGeometryRoot) { for (uint32_t i = 0; i < aEntries.Length(); ++i) { OpaqueRegionEntry* d = &aEntries[i]; if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot) { return d; } } return nullptr; } void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) { if (mFlattenToSingleLayer) { // animated geometry roots are forced to all match, so we can't // use them and we don't get async scrolling. return; } if (!mBuilder->IsPaintingToWindow()) { // async scrolling not possible, and async scrolling info not computed // for this paint. return; } AutoTArray<ScrollMetadata,2> metricsArray; if (aEntry->mBaseScrollMetadata) { metricsArray.AppendElement(*aEntry->mBaseScrollMetadata); // The base FrameMetrics was not computed by the nsIScrollableframe, so it // should not have a mask layer. MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer()); } // Any extra mask layers we need to attach to ScrollMetadatas. // The list may already contain an entry added for the layer's scrolled clip // so add to it rather than overwriting it (we clear the list when recycling // a layer). nsTArray<RefPtr<Layer>> maskLayers(aEntry->mLayer->GetAllAncestorMaskLayers()); for (const DisplayItemScrollClip* scrollClip = aEntry->mScrollClip; scrollClip && scrollClip != mContainerScrollClip; scrollClip = scrollClip->mParent) { if (!scrollClip->mIsAsyncScrollable) { // This scroll clip was created for a scroll frame that didn't know // whether it needs to be async scrollable for scroll handoff. It was // not activated, so we don't need to create a frame metrics for it. continue; } nsIScrollableFrame* scrollFrame = scrollClip->mScrollableFrame; const DisplayItemClip* clip = scrollClip->mClip; Maybe<ScrollMetadata> metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer, mContainerReferenceFrame, mParameters, clip); if (!metadata) { continue; } if (clip && clip->HasClip() && clip->GetRoundedRectCount() > 0) { // The clip in between this scrollframe and its ancestor scrollframe // requires a mask layer. Since this mask layer should not move with // the APZC associated with this FrameMetrics, we attach the mask // layer as an additional, separate clip. Maybe<size_t> nextIndex = Some(maskLayers.Length()); RefPtr<Layer> maskLayer = CreateMaskLayer(aEntry->mLayer, *clip, nextIndex, clip->GetRoundedRectCount()); if (maskLayer) { MOZ_ASSERT(metadata->HasScrollClip()); metadata->ScrollClip().SetMaskLayerIndex(nextIndex); maskLayers.AppendElement(maskLayer); } } metricsArray.AppendElement(*metadata); } // Watch out for FrameMetrics copies in profiles aEntry->mLayer->SetScrollMetadata(metricsArray); aEntry->mLayer->SetAncestorMaskLayers(maskLayers); } static inline Maybe<ParentLayerIntRect> GetStationaryClipInContainer(Layer* aLayer) { if (size_t metricsCount = aLayer->GetScrollMetadataCount()) { return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect(); } return aLayer->GetClipRect(); } void ContainerState::PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer) { AutoTArray<OpaqueRegionEntry,4> opaqueRegions; bool hideAll = false; int32_t opaqueRegionForContainer = -1; for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) { NewLayerEntry* e = &mNewChildLayers.ElementAt(i); if (!e->mLayer) { continue; } OpaqueRegionEntry* data = FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot); SetupScrollingMetadata(e); if (hideAll) { e->mVisibleRegion.SetEmpty(); } else if (!e->mLayer->IsScrollbarContainer()) { Maybe<ParentLayerIntRect> clipRect = GetStationaryClipInContainer(e->mLayer); if (clipRect && opaqueRegionForContainer >= 0 && opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(clipRect->ToUnknownRect())) { e->mVisibleRegion.SetEmpty(); } else if (data) { e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion); } } SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion, e->mLayerContentsVisibleRect.width >= 0 ? &e->mLayerContentsVisibleRect : nullptr, e->mUntransformedVisibleRegion); if (!e->mOpaqueRegion.IsEmpty()) { AnimatedGeometryRoot* animatedGeometryRootToCover = e->mAnimatedGeometryRoot; if (e->mOpaqueForAnimatedGeometryRootParent && e->mAnimatedGeometryRoot->mParentAGR == mContainerAnimatedGeometryRoot) { animatedGeometryRootToCover = mContainerAnimatedGeometryRoot; data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover); } if (!data) { if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot) { NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?"); opaqueRegionForContainer = opaqueRegions.Length(); } data = opaqueRegions.AppendElement(); data->mAnimatedGeometryRoot = animatedGeometryRootToCover; } nsIntRegion clippedOpaque = e->mOpaqueRegion; Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect(); if (clipRect) { clippedOpaque.AndWith(clipRect->ToUnknownRect()); } if (e->mLayer->GetIsFixedPosition() && e->mLayer->GetScrolledClip()) { // The clip can move asynchronously, so we can't rely on opaque parts // staying in the same place. clippedOpaque.SetEmpty(); } else if (e->mHideAllLayersBelow) { hideAll = true; } data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque); } if (e->mLayer->GetType() == Layer::TYPE_READBACK) { // ReadbackLayers need to accurately read what's behind them. So, // we don't want to do any occlusion culling of layers behind them. // Theoretically we could just punch out the ReadbackLayer's rectangle // from all mOpaqueRegions, but that's probably not worth doing. opaqueRegions.Clear(); opaqueRegionForContainer = -1; } } if (opaqueRegionForContainer >= 0) { aOpaqueRegionForContainer->Or(*aOpaqueRegionForContainer, opaqueRegions[opaqueRegionForContainer].mOpaqueRegion); } } void ContainerState::Finish(uint32_t* aTextContentFlags, const nsIntRect& aContainerPixelBounds, nsDisplayList* aChildItems, bool* aHasComponentAlphaChildren) { mPaintedLayerDataTree.Finish(); if (!mParameters.mForEventsAndPluginsOnly) { NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds), "Bounds computation mismatch"); } if (mLayerBuilder->IsBuildingRetainedLayers()) { nsIntRegion containerOpaqueRegion; PostprocessRetainedLayers(&containerOpaqueRegion); if (containerOpaqueRegion.Contains(aContainerPixelBounds)) { aChildItems->SetIsOpaque(); } } uint32_t textContentFlags = 0; // Make sure that current/existing layers are added to the parent and are // in the correct order. Layer* layer = nullptr; Layer* prevChild = nullptr; for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) { if (!mNewChildLayers[i].mLayer) { continue; } layer = mNewChildLayers[i].mLayer; if (!layer->GetVisibleRegion().IsEmpty()) { textContentFlags |= layer->GetContentFlags() & (Layer::CONTENT_COMPONENT_ALPHA | Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT | Layer::CONTENT_DISABLE_FLATTENING); // Notify the parent of component alpha children unless it's coming from // within a child that has asked not to contribute to layer flattening. if (aHasComponentAlphaChildren && mNewChildLayers[i].mPropagateComponentAlphaFlattening && (layer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA)) { for (int32_t j = i - 1; j >= 0; j--) { if (mNewChildLayers[j].mVisibleRegion.Intersects(mNewChildLayers[i].mVisibleRegion.GetBounds())) { if (mNewChildLayers[j].mLayerState != LAYER_ACTIVE_FORCE) { *aHasComponentAlphaChildren = true; } break; } } } } if (!layer->GetParent()) { // This is not currently a child of the container, so just add it // now. mContainerLayer->InsertAfter(layer, prevChild); } else { NS_ASSERTION(layer->GetParent() == mContainerLayer, "Layer shouldn't be the child of some other container"); if (layer->GetPrevSibling() != prevChild) { mContainerLayer->RepositionChild(layer, prevChild); } } } // Remove old layers that have become unused. if (!layer) { layer = mContainerLayer->GetFirstChild(); } else { layer = layer->GetNextSibling(); } while (layer) { Layer *layerToRemove = layer; layer = layer->GetNextSibling(); mContainerLayer->RemoveChild(layerToRemove); } *aTextContentFlags = textContentFlags; } static inline gfxSize RoundToFloatPrecision(const gfxSize& aSize) { return gfxSize(float(aSize.width), float(aSize.height)); } static void RestrictScaleToMaxLayerSize(gfxSize& aScale, const nsRect& aVisibleRect, nsIFrame* aContainerFrame, Layer* aContainerLayer) { if (!aContainerLayer->Manager()->IsWidgetLayerManager()) { return; } nsIntRect pixelSize = aVisibleRect.ScaleToOutsidePixels(aScale.width, aScale.height, aContainerFrame->PresContext()->AppUnitsPerDevPixel()); int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize(); if (pixelSize.width > maxLayerSize) { float scale = (float)pixelSize.width / maxLayerSize; scale = gfxUtils::ClampToScaleFactor(scale); aScale.width /= scale; } if (pixelSize.height > maxLayerSize) { float scale = (float)pixelSize.height / maxLayerSize; scale = gfxUtils::ClampToScaleFactor(scale); aScale.height /= scale; } } static bool ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder, nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, const nsRect& aVisibleRect, const Matrix4x4* aTransform, const ContainerLayerParameters& aIncomingScale, ContainerLayer* aLayer, LayerState aState, ContainerLayerParameters& aOutgoingScale) { nsIntPoint offset; Matrix4x4 transform = Matrix4x4::Scaling(aIncomingScale.mXScale, aIncomingScale.mYScale, 1.0); if (aTransform) { // aTransform is applied first, then the scale is applied to the result transform = (*aTransform)*transform; // Set any matrix entries close to integers to be those exact integers. // This protects against floating-point inaccuracies causing problems // in the checks below. // We use the fixed epsilon version here because we don't want the nudging // to depend on the scroll position. transform.NudgeToIntegersFixedEpsilon(); } Matrix transform2d; if (aContainerFrame && (aState == LAYER_INACTIVE || aState == LAYER_SVG_EFFECTS) && (!aTransform || (aTransform->Is2D(&transform2d) && !transform2d.HasNonTranslation()))) { // When we have an inactive ContainerLayer, translate the container by the offset to the // reference frame (and offset all child layers by the reverse) so that the coordinate // space of the child layers isn't affected by scrolling. // This gets confusing for complicated transform (since we'd have to compute the scale // factors for the matrix), so we don't bother. Any frames that are building an nsDisplayTransform // for a css transform would have 0,0 as their offset to the reference frame, so this doesn't // matter. nsPoint appUnitOffset = aDisplayListBuilder->ToReferenceFrame(aContainerFrame); nscoord appUnitsPerDevPixel = aContainerFrame->PresContext()->AppUnitsPerDevPixel(); offset = nsIntPoint( NS_lround(NSAppUnitsToDoublePixels(appUnitOffset.x, appUnitsPerDevPixel)*aIncomingScale.mXScale), NS_lround(NSAppUnitsToDoublePixels(appUnitOffset.y, appUnitsPerDevPixel)*aIncomingScale.mYScale)); } transform.PostTranslate(offset.x + aIncomingScale.mOffset.x, offset.y + aIncomingScale.mOffset.y, 0); if (transform.IsSingular()) { return false; } bool canDraw2D = transform.CanDraw2D(&transform2d); gfxSize scale; // XXX Should we do something for 3D transforms? if (canDraw2D && !aContainerFrame->Combines3DTransformWithAncestors() && !aContainerFrame->HasPerspective()) { // If the container's transform is animated off main thread, fix a suitable scale size // for animation if (aContainerItem && aContainerItem->GetType() == nsDisplayItem::TYPE_TRANSFORM && EffectCompositor::HasAnimationsForCompositor( aContainerFrame, eCSSProperty_transform)) { // Use the size of the nearest widget as the maximum size. This // is important since it might be a popup that is bigger than the // pres context's size. nsPresContext* presContext = aContainerFrame->PresContext(); nsIWidget* widget = aContainerFrame->GetNearestWidget(); nsSize displaySize; if (widget) { LayoutDeviceIntSize widgetSize = widget->GetClientSize(); int32_t p2a = presContext->AppUnitsPerDevPixel(); displaySize.width = NSIntPixelsToAppUnits(widgetSize.width, p2a); displaySize.height = NSIntPixelsToAppUnits(widgetSize.height, p2a); } else { displaySize = presContext->GetVisibleArea().Size(); } // compute scale using the animation on the container (ignoring // its ancestors) scale = nsLayoutUtils::ComputeSuitableScaleForAnimation( aContainerFrame, aVisibleRect.Size(), displaySize); // multiply by the scale inherited from ancestors--we use a uniform // scale factor to prevent blurring when the layer is rotated. float incomingScale = std::max(aIncomingScale.mXScale, aIncomingScale.mYScale); scale.width *= incomingScale; scale.height *= incomingScale; } else { // Scale factors are normalized to a power of 2 to reduce the number of resolution changes scale = RoundToFloatPrecision(ThebesMatrix(transform2d).ScaleFactors(true)); // For frames with a changing transform that's not just a translation, // round scale factors up to nearest power-of-2 boundary so that we don't // keep having to redraw the content as it scales up and down. Rounding up to nearest // power-of-2 boundary ensures we never scale up, only down --- avoiding // jaggies. It also ensures we never scale down by more than a factor of 2, // avoiding bad downscaling quality. Matrix frameTransform; if (ActiveLayerTracker::IsStyleAnimated(aDisplayListBuilder, aContainerFrame, eCSSProperty_transform) && aTransform && (!aTransform->Is2D(&frameTransform) || frameTransform.HasNonTranslationOrFlip())) { // Don't clamp the scale factor when the new desired scale factor matches the old one // or it was previously unscaled. bool clamp = true; Matrix oldFrameTransform2d; if (aLayer->GetBaseTransform().Is2D(&oldFrameTransform2d)) { gfxSize oldScale = RoundToFloatPrecision(ThebesMatrix(oldFrameTransform2d).ScaleFactors(true)); if (oldScale == scale || oldScale == gfxSize(1.0, 1.0)) { clamp = false; } } if (clamp) { scale.width = gfxUtils::ClampToScaleFactor(scale.width); scale.height = gfxUtils::ClampToScaleFactor(scale.height); } } else { // XXX Do we need to move nearly-integer values to integers here? } } // If the scale factors are too small, just use 1.0. The content is being // scaled out of sight anyway. if (fabs(scale.width) < 1e-8 || fabs(scale.height) < 1e-8) { scale = gfxSize(1.0, 1.0); } // If this is a transform container layer, then pre-rendering might // mean we try render a layer bigger than the max texture size. If we have // tiling, that's not a problem, since we'll automatically choose a tiled // layer for layers of that size. If not, we need to apply clamping to // prevent this. if (aTransform && !gfxPrefs::LayersTilesEnabled()) { RestrictScaleToMaxLayerSize(scale, aVisibleRect, aContainerFrame, aLayer); } } else { scale = gfxSize(1.0, 1.0); } // Store the inverse of our resolution-scale on the layer aLayer->SetBaseTransform(transform); aLayer->SetPreScale(1.0f/float(scale.width), 1.0f/float(scale.height)); aLayer->SetInheritedScale(aIncomingScale.mXScale, aIncomingScale.mYScale); aOutgoingScale = ContainerLayerParameters(scale.width, scale.height, -offset, aIncomingScale); if (aTransform) { aOutgoingScale.mInTransformedSubtree = true; if (ActiveLayerTracker::IsStyleAnimated(aDisplayListBuilder, aContainerFrame, eCSSProperty_transform)) { aOutgoingScale.mInActiveTransformedSubtree = true; } } if ((aLayerBuilder->IsBuildingRetainedLayers() && (!canDraw2D || transform2d.HasNonIntegerTranslation())) || aContainerFrame->Extend3DContext() || aContainerFrame->Combines3DTransformWithAncestors()) { aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true; } return true; } already_AddRefed<ContainerLayer> FrameLayerBuilder::BuildContainerLayerFor(nsDisplayListBuilder* aBuilder, LayerManager* aManager, nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem, nsDisplayList* aChildren, const ContainerLayerParameters& aParameters, const Matrix4x4* aTransform, uint32_t aFlags) { uint32_t containerDisplayItemKey = aContainerItem ? aContainerItem->GetPerFrameKey() : nsDisplayItem::TYPE_ZERO; NS_ASSERTION(aContainerFrame, "Container display items here should have a frame"); NS_ASSERTION(!aContainerItem || aContainerItem->Frame() == aContainerFrame, "Container display item must match given frame"); if (!aParameters.mXScale || !aParameters.mYScale) { return nullptr; } RefPtr<ContainerLayer> containerLayer; if (aManager == mRetainingManager) { // Using GetOldLayerFor will search merged frames, as well as the underlying // frame. The underlying frame can change when a page scrolls, so this // avoids layer recreation in the situation that a new underlying frame is // picked for a layer. Layer* oldLayer = nullptr; if (aContainerItem) { oldLayer = GetOldLayerFor(aContainerItem); } else { DisplayItemData *data = GetOldLayerForFrame(aContainerFrame, containerDisplayItemKey); if (data) { oldLayer = data->mLayer; } } if (oldLayer) { NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager"); if (oldLayer->HasUserData(&gPaintedDisplayItemLayerUserData)) { // The old layer for this item is actually our PaintedLayer // because we rendered its layer into that PaintedLayer. So we // don't actually have a retained container layer. } else { NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER, "Wrong layer type"); containerLayer = static_cast<ContainerLayer*>(oldLayer); ResetLayerStateForRecycling(containerLayer); } } } if (!containerLayer) { // No suitable existing layer was found. containerLayer = aManager->CreateContainerLayer(); if (!containerLayer) return nullptr; } LayerState state = aContainerItem ? aContainerItem->GetLayerState(aBuilder, aManager, aParameters) : LAYER_ACTIVE; if (state == LAYER_INACTIVE && nsDisplayItem::ForceActiveLayers()) { state = LAYER_ACTIVE; } if (aContainerItem && state == LAYER_ACTIVE_EMPTY) { // Empty layers only have metadata and should never have display items. We // early exit because later, invalidation will walk up the frame tree to // determine which painted layer gets invalidated. Since an empty layer // should never have anything to paint, it should never be invalidated. NS_ASSERTION(aChildren->IsEmpty(), "Should have no children"); return containerLayer.forget(); } const DisplayItemScrollClip* containerScrollClip = aParameters.mScrollClip; ContainerLayerParameters scaleParameters; nsRect bounds = aChildren->GetScrollClippedBoundsUpTo(aBuilder, containerScrollClip); nsRect childrenVisible = aContainerItem ? aContainerItem->GetVisibleRectForChildren() : aContainerFrame->GetVisualOverflowRectRelativeToSelf(); if (!ChooseScaleAndSetTransform(this, aBuilder, aContainerFrame, aContainerItem, bounds.Intersect(childrenVisible), aTransform, aParameters, containerLayer, state, scaleParameters)) { return nullptr; } uint32_t oldGeneration = mContainerLayerGeneration; mContainerLayerGeneration = ++mMaxContainerLayerGeneration; if (mRetainingManager) { if (aContainerItem) { StoreDataForFrame(aContainerItem, containerLayer, LAYER_ACTIVE); } else { StoreDataForFrame(aContainerFrame, containerDisplayItemKey, containerLayer, LAYER_ACTIVE); } } LayerManagerData* data = static_cast<LayerManagerData*> (aManager->GetUserData(&gLayerManagerUserData)); nsIntRect pixBounds; nscoord appUnitsPerDevPixel; bool flattenToSingleLayer = false; if ((aContainerFrame->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) && mRetainingManager && mRetainingManager->ShouldAvoidComponentAlphaLayers() && !nsLayoutUtils::AsyncPanZoomEnabled(aContainerFrame)) { flattenToSingleLayer = true; } nscolor backgroundColor = NS_RGBA(0,0,0,0); if (aFlags & CONTAINER_ALLOW_PULL_BACKGROUND_COLOR) { backgroundColor = aParameters.mBackgroundColor; } uint32_t flags; while (true) { ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(), aContainerFrame, aContainerItem, bounds, containerLayer, scaleParameters, flattenToSingleLayer, backgroundColor, containerScrollClip); state.ProcessDisplayItems(aChildren); // Set CONTENT_COMPONENT_ALPHA if any of our children have it. // This is suboptimal ... a child could have text that's over transparent // pixels in its own layer, but over opaque parts of previous siblings. bool hasComponentAlphaChildren = false; bool mayFlatten = mRetainingManager && mRetainingManager->ShouldAvoidComponentAlphaLayers() && !flattenToSingleLayer && !nsLayoutUtils::AsyncPanZoomEnabled(aContainerFrame); pixBounds = state.ScaleToOutsidePixels(bounds, false); appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel(); state.Finish(&flags, pixBounds, aChildren, mayFlatten ? &hasComponentAlphaChildren : nullptr); if (hasComponentAlphaChildren && !(flags & Layer::CONTENT_DISABLE_FLATTENING) && containerLayer->HasMultipleChildren()) { // Since we don't want any component alpha layers on BasicLayers, we repeat // the layer building process with this explicitely forced off. // We restore the previous FrameLayerBuilder state since the first set // of layer building will have changed it. flattenToSingleLayer = true; // Restore DisplayItemData for (auto iter = data->mDisplayItems.Iter(); !iter.Done(); iter.Next()) { DisplayItemData* data = iter.Get()->GetKey(); if (data->mUsed && data->mContainerLayerGeneration >= mContainerLayerGeneration) { iter.Remove(); } } // Restore PaintedLayerItemEntries for (auto iter = mPaintedLayerItems.Iter(); !iter.Done(); iter.Next()) { PaintedLayerItemsEntry* entry = iter.Get(); if (entry->mContainerLayerGeneration >= mContainerLayerGeneration) { // We can just remove these items rather than attempting to revert them // because we're going to want to invalidate everything when transitioning // to component alpha flattening. iter.Remove(); continue; } for (uint32_t i = 0; i < entry->mItems.Length(); i++) { if (entry->mItems[i].mContainerLayerGeneration >= mContainerLayerGeneration) { entry->mItems.TruncateLength(i); break; } } } aContainerFrame->AddStateBits(NS_FRAME_NO_COMPONENT_ALPHA); continue; } break; } // CONTENT_COMPONENT_ALPHA is propogated up to the nearest CONTENT_OPAQUE // ancestor so that BasicLayerManager knows when to copy the background into // pushed groups. Accelerated layers managers can't necessarily do this (only // when the visible region is a simple rect), so we propogate // CONTENT_COMPONENT_ALPHA_DESCENDANT all the way to the root. if (flags & Layer::CONTENT_COMPONENT_ALPHA) { flags |= Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT; } // Make sure that rounding the visible region out didn't add any area // we won't paint if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) { bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale); if (bounds.Contains(ToAppUnits(pixBounds, appUnitsPerDevPixel))) { // Clear CONTENT_COMPONENT_ALPHA and add CONTENT_OPAQUE instead. flags &= ~Layer::CONTENT_COMPONENT_ALPHA; flags |= Layer::CONTENT_OPAQUE; } } containerLayer->SetContentFlags(flags); // If aContainerItem is non-null some BuildContainerLayer further up the // call stack is responsible for setting containerLayer's visible region. if (!aContainerItem) { containerLayer->SetVisibleRegion(LayerIntRegion::FromUnknownRegion(pixBounds)); } if (aParameters.mLayerContentsVisibleRect) { *aParameters.mLayerContentsVisibleRect = pixBounds + scaleParameters.mOffset; } mContainerLayerGeneration = oldGeneration; nsPresContext::ClearNotifySubDocInvalidationData(containerLayer); return containerLayer.forget(); } Layer* FrameLayerBuilder::GetLeafLayerFor(nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem) { Layer* layer = GetOldLayerFor(aItem); if (!layer) return nullptr; if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { // This layer was created to render Thebes-rendered content for this // display item. The display item should not use it for its own // layer rendering. return nullptr; } ResetLayerStateForRecycling(layer); return layer; } /* static */ void FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager) { LayerManagerData* data = static_cast<LayerManagerData*> (aManager->GetUserData(&gLayerManagerUserData)); if (data) { data->mInvalidateAllLayers = true; } } /* static */ void FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame *aFrame) { const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (array) { for (uint32_t i = 0; i < array->Length(); i++) { AssertDisplayItemData(array->ElementAt(i))->mParent->mInvalidateAllLayers = true; } } } /* static */ Layer* FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame, uint32_t aDisplayItemKey) { //TODO: This isn't completely correct, since a frame could exist as a layer // in the normal widget manager, and as a different layer (or no layer) // in the secondary manager const nsTArray<DisplayItemData*>* array = aFrame->Properties().Get(LayerManagerDataProperty()); if (array) { for (uint32_t i = 0; i < array->Length(); i++) { DisplayItemData *element = AssertDisplayItemData(array->ElementAt(i)); if (!element->mParent->mLayerManager->IsWidgetLayerManager()) { continue; } if (element->mDisplayItemKey == aDisplayItemKey) { if (element->mOptLayer) { return element->mOptLayer; } Layer* layer = element->mLayer; if (!layer->HasUserData(&gColorLayerUserData) && !layer->HasUserData(&gImageLayerUserData) && !layer->HasUserData(&gPaintedDisplayItemLayerUserData)) { return layer; } } } } return nullptr; } static gfxSize PredictScaleForContent(nsIFrame* aFrame, nsIFrame* aAncestorWithScale, const gfxSize& aScale) { Matrix4x4 transform = Matrix4x4::Scaling(aScale.width, aScale.height, 1.0); if (aFrame != aAncestorWithScale) { // aTransform is applied first, then the scale is applied to the result transform = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestorWithScale)*transform; } Matrix transform2d; if (transform.CanDraw2D(&transform2d)) { return ThebesMatrix(transform2d).ScaleFactors(true); } return gfxSize(1.0, 1.0); } gfxSize FrameLayerBuilder::GetPaintedLayerScaleForFrame(nsIFrame* aFrame) { MOZ_ASSERT(aFrame, "need a frame"); nsIFrame* last = nullptr; for (nsIFrame* f = aFrame; f; f = nsLayoutUtils::GetCrossDocParentFrame(f)) { last = f; if (nsLayoutUtils::IsPopup(f)) { // Don't examine ancestors of a popup. It won't make sense to check // the transform from some content inside the popup to some content // which is an ancestor of the popup. break; } const nsTArray<DisplayItemData*>* array = f->Properties().Get(LayerManagerDataProperty()); if (!array) { continue; } for (uint32_t i = 0; i < array->Length(); i++) { Layer* layer = AssertDisplayItemData(array->ElementAt(i))->mLayer; ContainerLayer* container = layer->AsContainerLayer(); if (!container || !layer->Manager()->IsWidgetLayerManager()) { continue; } for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) { PaintedDisplayItemLayerUserData* data = static_cast<PaintedDisplayItemLayerUserData*> (l->GetUserData(&gPaintedDisplayItemLayerUserData)); if (data) { return PredictScaleForContent(aFrame, f, gfxSize(data->mXScale, data->mYScale)); } } } } float presShellResolution = last->PresContext()->PresShell()->GetResolution(); return PredictScaleForContent(aFrame, last, gfxSize(presShellResolution, presShellResolution)); } #ifdef MOZ_DUMP_PAINTING static void DebugPaintItem(DrawTarget& aDrawTarget, nsPresContext* aPresContext, nsDisplayItem *aItem, nsDisplayListBuilder* aBuilder) { bool snap; Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap), aPresContext->AppUnitsPerDevPixel()); RefPtr<DrawTarget> tempDT = aDrawTarget.CreateSimilarDrawTarget(IntSize::Truncate(bounds.width, bounds.height), SurfaceFormat::B8G8R8A8); RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT); if (!context) { // Leave this as crash, it's in the debugging code, we want to know gfxDevCrash(LogReason::InvalidContext) << "DebugPaintItem context problem " << gfx::hexa(tempDT); return; } context->SetMatrix(gfxMatrix::Translation(-bounds.x, -bounds.y)); nsRenderingContext ctx(context); aItem->Paint(aBuilder, &ctx); RefPtr<SourceSurface> surface = tempDT->Snapshot(); DumpPaintedImage(aItem, surface); aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0,0), bounds.Size())); aItem->SetPainted(); } #endif /* static */ void FrameLayerBuilder::RecomputeVisibilityForItems(nsTArray<ClippedDisplayItem>& aItems, nsDisplayListBuilder *aBuilder, const nsIntRegion& aRegionToDraw, const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale, float aYScale) { uint32_t i; // Update visible regions. We perform visibility analysis to take account // of occlusion culling. nsRegion visible = aRegionToDraw.ToAppUnits(aAppUnitsPerDevPixel); visible.MoveBy(NSIntPixelsToAppUnits(aOffset.x, aAppUnitsPerDevPixel), NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel)); visible.ScaleInverseRoundOut(aXScale, aYScale); for (i = aItems.Length(); i > 0; --i) { ClippedDisplayItem* cdi = &aItems[i - 1]; const DisplayItemClip& clip = cdi->mItem->GetClip(); NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel, "a painted layer should contain items only at the same zoom"); MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0, "If we have rounded rects, we must have a clip rect"); if (!clip.IsRectAffectedByClip(visible.GetBounds())) { cdi->mItem->RecomputeVisibility(aBuilder, &visible); continue; } // Do a little dance to account for the fact that we're clipping // to cdi->mClipRect nsRegion clipped; clipped.And(visible, clip.NonRoundedIntersection()); nsRegion finalClipped = clipped; cdi->mItem->RecomputeVisibility(aBuilder, &finalClipped); // If we have rounded clip rects, don't subtract from the visible // region since we aren't displaying everything inside the rect. if (clip.GetRoundedRectCount() == 0) { nsRegion removed; removed.Sub(clipped, finalClipped); nsRegion newVisible; newVisible.Sub(visible, removed); // Don't let the visible region get too complex. if (newVisible.GetNumRects() <= 15) { visible = newVisible; } } } } void FrameLayerBuilder::PaintItems(nsTArray<ClippedDisplayItem>& aItems, const nsIntRect& aRect, gfxContext *aContext, nsRenderingContext *aRC, nsDisplayListBuilder* aBuilder, nsPresContext* aPresContext, const nsIntPoint& aOffset, float aXScale, float aYScale, int32_t aCommonClipCount) { DrawTarget& aDrawTarget = *aRC->GetDrawTarget(); int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel); boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel), NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel)); boundRect.ScaleInverseRoundOut(aXScale, aYScale); DisplayItemClip currentClip; bool currentClipIsSetInContext = false; DisplayItemClip tmpClip; for (uint32_t i = 0; i < aItems.Length(); ++i) { ClippedDisplayItem* cdi = &aItems[i]; nsRect paintRect = cdi->mItem->GetVisibleRect().Intersect(boundRect); if (paintRect.IsEmpty()) continue; #ifdef MOZ_DUMP_PAINTING PROFILER_LABEL_PRINTF("DisplayList", "Draw", js::ProfileEntry::Category::GRAPHICS, "%s", cdi->mItem->Name()); #else PROFILER_LABEL("DisplayList", "Draw", js::ProfileEntry::Category::GRAPHICS); #endif // If the new desired clip state is different from the current state, // update the clip. const DisplayItemClip* clip = &cdi->mItem->GetClip(); if (clip->GetRoundedRectCount() > 0 && !clip->IsRectClippedByRoundedCorner(cdi->mItem->GetVisibleRect())) { tmpClip = *clip; tmpClip.RemoveRoundedCorners(); clip = &tmpClip; } if (currentClipIsSetInContext != clip->HasClip() || (clip->HasClip() && *clip != currentClip)) { if (currentClipIsSetInContext) { aContext->Restore(); } currentClipIsSetInContext = clip->HasClip(); if (currentClipIsSetInContext) { currentClip = *clip; aContext->Save(); NS_ASSERTION(aCommonClipCount < 100, "Maybe you really do have more than a hundred clipping rounded rects, or maybe something has gone wrong."); currentClip.ApplyTo(aContext, aPresContext, aCommonClipCount); aContext->NewPath(); } } if (cdi->mInactiveLayerManager) { bool saved = aDrawTarget.GetPermitSubpixelAA(); PaintInactiveLayer(aBuilder, cdi->mInactiveLayerManager, cdi->mItem, aContext, aRC); aDrawTarget.SetPermitSubpixelAA(saved); } else { nsIFrame* frame = cdi->mItem->Frame(); if (aBuilder->IsPaintingToWindow()) { frame->AddStateBits(NS_FRAME_PAINTED_THEBES); } #ifdef MOZ_DUMP_PAINTING if (gfxEnv::DumpPaintItems()) { DebugPaintItem(aDrawTarget, aPresContext, cdi->mItem, aBuilder); } else { #else { #endif cdi->mItem->Paint(aBuilder, aRC); } } if (CheckDOMModified()) break; } if (currentClipIsSetInContext) { aContext->Restore(); } } /** * Returns true if it is preferred to draw the list of display * items separately for each rect in the visible region rather * than clipping to a complex region. */ static bool ShouldDrawRectsSeparately(DrawTarget* aDrawTarget, DrawRegionClip aClip) { if (!gfxPrefs::LayoutPaintRectsSeparately() || aClip == DrawRegionClip::NONE) { return false; } return !aDrawTarget->SupportsRegionClipping(); } static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget, const IntRect& aBounds, nscolor aBackgroundColor) { if (NS_GET_A(aBackgroundColor) > 0) { ColorPattern color(ToDeviceColor(aBackgroundColor)); aDrawTarget.FillRect(Rect(aBounds), color); } } /* * A note on residual transforms: * * In a transformed subtree we sometimes apply the PaintedLayer's * "residual transform" when drawing content into the PaintedLayer. * This is a translation by components in the range [-0.5,0.5) provided * by the layer system; applying the residual transform followed by the * transforms used by layer compositing ensures that the subpixel alignment * of the content of the PaintedLayer exactly matches what it would be if * we used cairo/Thebes to draw directly to the screen without going through * retained layer buffers. * * The visible and valid regions of the PaintedLayer are computed without * knowing the residual transform (because we don't know what the residual * transform is going to be until we've built the layer tree!). So we have to * consider whether content painted in the range [x, xmost) might be painted * outside the visible region we computed for that content. The visible region * would be [floor(x), ceil(xmost)). The content would be rendered at * [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could * indeed fall outside the computed visible region, which is not a big deal; * similar issues already arise when we snap cliprects to nearest pixels. * Note that if the rendering of the content is snapped to nearest pixels --- * which it often is --- then the content is actually rendered at * [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r) * and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content * always falls within the visible region we computed. */ /* static */ void FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer, gfxContext* aContext, const nsIntRegion& aRegionToDraw, const nsIntRegion& aDirtyRegion, DrawRegionClip aClip, const nsIntRegion& aRegionToInvalidate, void* aCallbackData) { DrawTarget& aDrawTarget = *aContext->GetDrawTarget(); PROFILER_LABEL("FrameLayerBuilder", "DrawPaintedLayer", js::ProfileEntry::Category::GRAPHICS); nsDisplayListBuilder* builder = static_cast<nsDisplayListBuilder*> (aCallbackData); FrameLayerBuilder *layerBuilder = aLayer->Manager()->GetLayerBuilder(); NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!"); if (layerBuilder->CheckDOMModified()) return; PaintedLayerItemsEntry* entry = layerBuilder->mPaintedLayerItems.GetEntry(aLayer); NS_ASSERTION(entry, "We shouldn't be drawing into a layer with no items!"); if (!entry->mContainerLayerFrame) { return; } PaintedDisplayItemLayerUserData* userData = static_cast<PaintedDisplayItemLayerUserData*> (aLayer->GetUserData(&gPaintedDisplayItemLayerUserData)); NS_ASSERTION(userData, "where did our user data go?"); bool shouldDrawRectsSeparately = ShouldDrawRectsSeparately(&aDrawTarget, aClip); if (!shouldDrawRectsSeparately) { if (aClip == DrawRegionClip::DRAW) { gfxUtils::ClipToRegion(aContext, aRegionToDraw); } DrawForcedBackgroundColor(aDrawTarget, aRegionToDraw.GetBounds(), userData->mForcedBackgroundColor); } if (NS_GET_A(userData->mFontSmoothingBackgroundColor) > 0) { aContext->SetFontSmoothingBackgroundColor( Color::FromABGR(userData->mFontSmoothingBackgroundColor)); } // make the origin of the context coincide with the origin of the // PaintedLayer gfxContextMatrixAutoSaveRestore saveMatrix(aContext); nsIntPoint offset = GetTranslationForPaintedLayer(aLayer); nsPresContext* presContext = entry->mContainerLayerFrame->PresContext(); if (!userData->mVisibilityComputedRegion.Contains(aDirtyRegion) && !layerBuilder->GetContainingPaintedLayerData()) { // Recompute visibility of items in our PaintedLayer, if required. Note // that this recomputes visibility for all descendants of our display // items too, so there's no need to do this for the items in inactive // PaintedLayers. If aDirtyRegion has not changed since the previous call // then we can skip this. int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); RecomputeVisibilityForItems(entry->mItems, builder, aDirtyRegion, offset, appUnitsPerDevPixel, userData->mXScale, userData->mYScale); userData->mVisibilityComputedRegion = aDirtyRegion; } nsRenderingContext rc(aContext); if (shouldDrawRectsSeparately) { for (auto iter = aRegionToDraw.RectIter(); !iter.Done(); iter.Next()) { const nsIntRect& iterRect = iter.Get(); gfxContextAutoSaveRestore save(aContext); aContext->NewPath(); aContext->Rectangle(ThebesRect(iterRect)); aContext->Clip(); DrawForcedBackgroundColor(aDrawTarget, iterRect, userData->mForcedBackgroundColor); // Apply the residual transform if it has been enabled, to ensure that // snapping when we draw into aContext exactly matches the ideal transform. // See above for why this is OK. aContext->SetMatrix( aContext->CurrentMatrix().Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)). Scale(userData->mXScale, userData->mYScale)); layerBuilder->PaintItems(entry->mItems, iterRect, aContext, &rc, builder, presContext, offset, userData->mXScale, userData->mYScale, entry->mCommonClipCount); if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) { aLayer->Manager()->AddPaintedPixelCount(iterRect.Area()); } } } else { // Apply the residual transform if it has been enabled, to ensure that // snapping when we draw into aContext exactly matches the ideal transform. // See above for why this is OK. aContext->SetMatrix( aContext->CurrentMatrix().Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)). Scale(userData->mXScale,userData->mYScale)); layerBuilder->PaintItems(entry->mItems, aRegionToDraw.GetBounds(), aContext, &rc, builder, presContext, offset, userData->mXScale, userData->mYScale, entry->mCommonClipCount); if (gfxPrefs::GfxLoggingPaintedPixelCountEnabled()) { aLayer->Manager()->AddPaintedPixelCount( aRegionToDraw.GetBounds().Area()); } } aContext->SetFontSmoothingBackgroundColor(Color()); bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager(); if (presContext->GetPaintFlashing() && isActiveLayerManager) { gfxContextAutoSaveRestore save(aContext); if (shouldDrawRectsSeparately) { if (aClip == DrawRegionClip::DRAW) { gfxUtils::ClipToRegion(aContext, aRegionToDraw); } } FlashPaint(aContext); } if (presContext->GetDocShell() && isActiveLayerManager) { nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell()); RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); if (timelines && timelines->HasConsumer(docShell)) { timelines->AddMarkerForDocShell(docShell, Move( MakeUnique<LayerTimelineMarker>(aRegionToDraw))); } } if (!aRegionToInvalidate.IsEmpty()) { aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds()); } } bool FrameLayerBuilder::CheckDOMModified() { if (!mRootPresContext || mInitialDOMGeneration == mRootPresContext->GetDOMGeneration()) return false; if (mDetectedDOMModification) { // Don't spam the console with extra warnings return true; } mDetectedDOMModification = true; // Painting is not going to complete properly. There's not much // we can do here though. Invalidating the window to get another repaint // is likely to lead to an infinite repaint loop. NS_WARNING("Detected DOM modification during paint, bailing out!"); return true; } /* static */ void FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager, std::stringstream& aStream, bool aDumpHtml) { aManager->Dump(aStream, "", aDumpHtml); } nsDisplayItemGeometry* FrameLayerBuilder::GetMostRecentGeometry(nsDisplayItem* aItem) { typedef nsTArray<DisplayItemData*> DataArray; // Retrieve the array of DisplayItemData associated with our frame. FrameProperties properties = aItem->Frame()->Properties(); const DataArray* dataArray = properties.Get(LayerManagerDataProperty()); if (!dataArray) { return nullptr; } // Find our display item data, if it exists, and return its geometry. uint32_t itemPerFrameKey = aItem->GetPerFrameKey(); for (uint32_t i = 0; i < dataArray->Length(); i++) { DisplayItemData* data = AssertDisplayItemData(dataArray->ElementAt(i)); if (data->GetDisplayItemKey() == itemPerFrameKey) { return data->GetGeometry(); } } return nullptr; } gfx::Rect CalculateBounds(const nsTArray<DisplayItemClip::RoundedRect>& aRects, int32_t A2D) { nsRect bounds = aRects[0].mRect; for (uint32_t i = 1; i < aRects.Length(); ++i) { bounds.UnionRect(bounds, aRects[i].mRect); } return gfx::ToRect(nsLayoutUtils::RectToGfxRect(bounds, A2D)); } static void SetClipCount(PaintedDisplayItemLayerUserData* apaintedData, uint32_t aClipCount) { if (apaintedData) { apaintedData->mMaskClipCount = aClipCount; } } void ContainerState::SetupMaskLayer(Layer *aLayer, const DisplayItemClip& aClip, uint32_t aRoundedRectClipCount) { // if the number of clips we are going to mask has decreased, then aLayer might have // cached graphics which assume the existence of a soon-to-be non-existent mask layer // in that case, invalidate the whole layer. PaintedDisplayItemLayerUserData* paintedData = GetPaintedDisplayItemLayerUserData(aLayer); if (paintedData && aRoundedRectClipCount < paintedData->mMaskClipCount) { PaintedLayer* painted = aLayer->AsPaintedLayer(); painted->InvalidateRegion(painted->GetValidRegion().GetBounds()); } // don't build an unnecessary mask if (aClip.GetRoundedRectCount() == 0 || aRoundedRectClipCount == 0) { SetClipCount(paintedData, 0); return; } RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, Nothing(), aRoundedRectClipCount); if (!maskLayer) { SetClipCount(paintedData, 0); return; } aLayer->SetMaskLayer(maskLayer); SetClipCount(paintedData, aRoundedRectClipCount); } already_AddRefed<Layer> ContainerState::CreateMaskLayer(Layer *aLayer, const DisplayItemClip& aClip, const Maybe<size_t>& aForAncestorMaskLayer, uint32_t aRoundedRectClipCount) { // aLayer will never be the container layer created by an nsDisplayMask // because nsDisplayMask propagates the DisplayItemClip to its contents // and is not clipped itself. // This assertion will fail if that ever stops being the case. MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData), "A layer contains round clips should not have css-mask on it."); // check if we can re-use the mask layer MaskLayerKey recycleKey(aLayer, aForAncestorMaskLayer); RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(recycleKey, [](Layer* aMaskLayer) { aMaskLayer->SetUserData(&gMaskLayerUserData, new MaskLayerUserData()); } ); MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer); int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel(); MaskLayerUserData newData(aClip, aRoundedRectClipCount, A2D, mParameters); if (*userData == newData) { return maskLayer.forget(); } // calculate a more precise bounding rect gfx::Rect boundingRect = CalculateBounds(newData.mRoundedClipRects, newData.mAppUnitsPerDevPixel); boundingRect.Scale(mParameters.mXScale, mParameters.mYScale); uint32_t maxSize = mManager->GetMaxTextureSize(); NS_ASSERTION(maxSize > 0, "Invalid max texture size"); #ifdef MOZ_GFX_OPTIMIZE_MOBILE // Make mask image width aligned to 4. See Bug 1245552. gfx::Size surfaceSize(std::min<gfx::Float>(GetAlignedStride<4>(NSToIntCeil(boundingRect.Width()), 1), maxSize), std::min<gfx::Float>(boundingRect.Height(), maxSize)); #else gfx::Size surfaceSize(std::min<gfx::Float>(boundingRect.Width(), maxSize), std::min<gfx::Float>(boundingRect.Height(), maxSize)); #endif // maskTransform is applied to the clip when it is painted into the mask (as a // component of imageTransform), and its inverse used when the mask is used for // masking. // It is the transform from the masked layer's space to mask space gfx::Matrix maskTransform = Matrix::Scaling(surfaceSize.width / boundingRect.Width(), surfaceSize.height / boundingRect.Height()); if (surfaceSize.IsEmpty()) { // Return early if we know that the size of this mask surface is empty. return nullptr; } gfx::Point p = boundingRect.TopLeft(); maskTransform.PreTranslate(-p.x, -p.y); // imageTransform is only used when the clip is painted to the mask gfx::Matrix imageTransform = maskTransform; imageTransform.PreScale(mParameters.mXScale, mParameters.mYScale); nsAutoPtr<MaskLayerImageCache::MaskLayerImageKey> newKey( new MaskLayerImageCache::MaskLayerImageKey()); // copy and transform the rounded rects for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) { newKey->mRoundedClipRects.AppendElement( MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i], mContainerFrame->PresContext())); newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform); } newKey->mForwarder = mManager->AsShadowForwarder(); const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey; // check to see if we can reuse a mask image RefPtr<ImageContainer> container = GetMaskLayerImageCache()->FindImageFor(&lookupKey); if (!container) { IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width), NSToIntCeil(surfaceSize.height)); // no existing mask image, so build a new one MaskImageData imageData(surfaceSizeInt, mManager); RefPtr<DrawTarget> dt = imageData.CreateDrawTarget(); // fail if we can't get the right surface if (!dt || !dt->IsValid()) { NS_WARNING("Could not create DrawTarget for mask layer."); return nullptr; } RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt); MOZ_ASSERT(context); // already checked the draw target above context->Multiply(ThebesMatrix(imageTransform)); // paint the clipping rects with alpha to create the mask aClip.FillIntersectionOfRoundedRectClips(context, Color(1.f, 1.f, 1.f, 1.f), newData.mAppUnitsPerDevPixel, 0, aRoundedRectClipCount); // build the image and container MOZ_ASSERT(aLayer->Manager() == mManager); container = imageData.CreateImageAndImageContainer(); NS_ASSERTION(container, "Could not create image container for mask layer."); if (!container) { return nullptr; } GetMaskLayerImageCache()->PutImage(newKey.forget(), container); } maskLayer->SetContainer(container); maskTransform.Invert(); Matrix4x4 matrix = Matrix4x4::From2D(maskTransform); matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0); maskLayer->SetBaseTransform(matrix); // save the details of the clip in user data *userData = Move(newData); userData->mImageKey.Reset(lookupKey); return maskLayer.forget(); } } // namespace mozilla