diff -r 000000000000 -r 6474c204b198 layout/base/FrameLayerBuilder.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/layout/base/FrameLayerBuilder.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,4036 @@ +/* -*- 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 "nsDisplayList.h" +#include "nsPresContext.h" +#include "nsLayoutUtils.h" +#include "Layers.h" +#include "BasicLayers.h" +#include "gfxUtils.h" +#include "nsRenderingContext.h" +#include "MaskLayerImageCache.h" +#include "nsIScrollableFrame.h" +#include "nsPrintfCString.h" +#include "LayerTreeInvalidation.h" +#include "nsSVGIntegrationUtils.h" +#include "ImageContainer.h" +#include "ActiveLayerTracker.h" +#include "gfx2DGlue.h" + +#include "GeckoProfiler.h" +#include "mozilla/gfx/Tools.h" +#include "mozilla/gfx/2D.h" +#include "gfxPrefs.h" + +#include + +using namespace mozilla::layers; +using namespace mozilla::gfx; + +namespace mozilla { + +class ContainerState; + +FrameLayerBuilder::DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey, + Layer* aLayer, LayerState aLayerState, uint32_t aGeneration) + + : mParent(aParent) + , mLayer(aLayer) + , mDisplayItemKey(aKey) + , mContainerLayerGeneration(aGeneration) + , mLayerState(aLayerState) + , mUsed(true) + , mIsInvalid(false) +{ +} + +FrameLayerBuilder::DisplayItemData::DisplayItemData(DisplayItemData &toCopy) +{ + // This isn't actually a copy-constructor; notice that it steals toCopy's + // mGeometry pointer. Be careful. + mParent = toCopy.mParent; + mLayer = toCopy.mLayer; + mInactiveManager = toCopy.mInactiveManager; + mFrameList = toCopy.mFrameList; + mGeometry = toCopy.mGeometry; + mDisplayItemKey = toCopy.mDisplayItemKey; + mClip = toCopy.mClip; + mContainerLayerGeneration = toCopy.mContainerLayerGeneration; + mLayerState = toCopy.mLayerState; + mUsed = toCopy.mUsed; +} + +void +FrameLayerBuilder::DisplayItemData::AddFrame(nsIFrame* aFrame) +{ + mFrameList.AppendElement(aFrame); + + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty())); + if (!array) { + array = new nsTArray(); + aFrame->Properties().Set(FrameLayerBuilder::LayerManagerDataProperty(), array); + } + array->AppendElement(this); +} + +void +FrameLayerBuilder::DisplayItemData::RemoveFrame(nsIFrame* aFrame) +{ + DebugOnly result = mFrameList.RemoveElement(aFrame); + NS_ASSERTION(result, "Can't remove a frame that wasn't added!"); + + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(FrameLayerBuilder::LayerManagerDataProperty())); + NS_ASSERTION(array, "Must be already stored on the frame!"); + array->RemoveElement(this); +} + +void +FrameLayerBuilder::DisplayItemData::UpdateContents(Layer* aLayer, LayerState aState, + uint32_t aContainerLayerGeneration, + nsDisplayItem* aItem /* = nullptr */) +{ + mLayer = aLayer; + mOptLayer = nullptr; + mInactiveManager = nullptr; + mLayerState = aState; + mContainerLayerGeneration = aContainerLayerGeneration; + mGeometry = nullptr; + mClip = DisplayItemClip(); + mUsed = true; + + if (!aItem) { + return; + } + + nsAutoTArray copy(mFrameList); + if (!copy.RemoveElement(aItem->Frame())) { + AddFrame(aItem->Frame()); + } + + nsAutoTArray mergedFrames; + aItem->GetMergedFrames(&mergedFrames); + for (uint32_t i = 0; i < mergedFrames.Length(); ++i) { + if (!copy.RemoveElement(mergedFrames[i])) { + AddFrame(mergedFrames[i]); + } + } + + for (uint32_t i = 0; i < copy.Length(); i++) { + RemoveFrame(copy[i]); + } +} + +static nsIFrame* sDestroyedFrame = nullptr; +FrameLayerBuilder::DisplayItemData::~DisplayItemData() +{ + for (uint32_t i = 0; i < mFrameList.Length(); i++) { + nsIFrame* frame = mFrameList[i]; + if (frame == sDestroyedFrame) { + continue; + } + nsTArray *array = + reinterpret_cast*>(frame->Properties().Get(LayerManagerDataProperty())); + array->RemoveElement(this); + } +} + +void +FrameLayerBuilder::DisplayItemData::GetFrameListChanges(nsDisplayItem* aOther, + nsTArray& aOut) +{ + aOut = mFrameList; + nsAutoTArray added; + if (!aOut.RemoveElement(aOther->Frame())) { + added.AppendElement(aOther->Frame()); + } + + nsAutoTArray mergedFrames; + aOther->GetMergedFrames(&mergedFrames); + for (uint32_t i = 0; i < mergedFrames.Length(); ++i) { + if (!aOut.RemoveElement(mergedFrames[i])) { + added.AppendElement(mergedFrames[i]); + } + } + + aOut.AppendElements(added); +} + +/** + * This is the userdata we associate with a layer manager. + */ +class LayerManagerData : public LayerUserData { +public: + 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); + nsAutoCString prefix; + prefix += aPrefix; + prefix += " "; + mDisplayItems.EnumerateEntries( + FrameLayerBuilder::DumpDisplayItemDataForFrame, (void*)prefix.get()); + } +#endif + + /** + * Tracks which frames have layers associated with them. + */ + LayerManager *mLayerManager; +#ifdef DEBUG_DISPLAY_ITEM_DATA + LayerManagerData *mParent; +#endif + nsTHashtable > mDisplayItems; + bool mInvalidateAllLayers; +}; + +/* static */ void +FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame) +{ + FrameProperties props = aFrame->Properties(); + props.Delete(LayerManagerDataProperty()); +} + +// 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; +} + +/** + * We keep a stack of these to represent the ThebesLayers 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 ThebesLayers, and to the lowest + * ThebesLayer 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 ThebesLayerData { +public: + ThebesLayerData() : + mAnimatedGeometryRoot(nullptr), + mFixedPosFrameForLayerData(nullptr), + mReferenceFrame(nullptr), + mLayer(nullptr), + mIsSolidColorInVisibleRegion(false), + mSingleItemFixedToViewport(false), + mNeedComponentAlpha(false), + mForceTransparentSurface(false), + mImage(nullptr), + mCommonClipCount(-1), + mAllDrawingAbove(false) + {} + /** + * Record that an item has been added to the ThebesLayer, so we + * need to update our regions. + * @param aVisibleRect the area of the item that's visible + * @param aDrawRect the area of the item that would be drawn if it + * was completely visible + * @param aOpaqueRect if non-null, the area of the item that's opaque. + * We pass in a separate opaque rect because the opaque rect can be + * bigger than the visible rect, and we want to have the biggest + * opaque rect that we can. + * @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 nsIntRect& aVisibleRect, + const nsIntRect& aDrawRect, + const DisplayItemClip& aClip); + const nsIFrame* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; } + + /** + * Add aHitRegion and aDispatchToContentHitRegion to the hit regions for + * this ThebesLayer. + */ + void AccumulateEventRegions(const nsIntRegion& aHitRegion, + const nsIntRegion& aMaybeHitRegion, + const nsIntRegion& aDispatchToContentHitRegion) + { + mHitRegion.Or(mHitRegion, aHitRegion); + mMaybeHitRegion.Or(mMaybeHitRegion, aMaybeHitRegion); + mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, aDispatchToContentHitRegion); + } + + /** + * If this represents only a nsDisplayImage, and the image type + * supports being optimized to an ImageLayer (TYPE_RASTER only) returns + * an ImageContainer for the image. + */ + already_AddRefed CanOptimizeImageLayer(nsDisplayListBuilder* aBuilder); + + void AddDrawAboveRegion(const nsIntRegion& aAbove) + { + if (!mAllDrawingAbove) { + mDrawAboveRegion.Or(mDrawAboveRegion, aAbove); + mDrawAboveRegion.SimplifyOutward(4); + } + } + + void AddVisibleAboveRegion(const nsIntRegion& aAbove) + { + if (!mAllDrawingAbove) { + mVisibleAboveRegion.Or(mVisibleAboveRegion, aAbove); + mVisibleAboveRegion.SimplifyOutward(4); + } + } + + void CopyAboveRegion(ThebesLayerData* aOther) + { + if (aOther->mAllDrawingAbove || mAllDrawingAbove) { + SetAllDrawingAbove(); + } else { + mVisibleAboveRegion.Or(mVisibleAboveRegion, aOther->mVisibleAboveRegion); + mVisibleAboveRegion.Or(mVisibleAboveRegion, aOther->mVisibleRegion); + mVisibleAboveRegion.SimplifyOutward(4); + mDrawAboveRegion.Or(mDrawAboveRegion, aOther->mDrawAboveRegion); + mDrawAboveRegion.Or(mDrawAboveRegion, aOther->mDrawRegion); + mDrawAboveRegion.SimplifyOutward(4); + } + } + + void SetAllDrawingAbove() + { + mAllDrawingAbove = true; + mDrawAboveRegion.SetEmpty(); + mVisibleAboveRegion.SetEmpty(); + } + + bool DrawAboveRegionIntersects(const nsIntRect& aRect) + { + return mAllDrawingAbove || mDrawAboveRegion.Intersects(aRect); + } + + bool DrawRegionIntersects(const nsIntRect& aRect) + { + return IsSubjectToAsyncTransforms() || mDrawRegion.Intersects(aRect); + } + + bool IntersectsVisibleAboveRegion(const nsIntRegion& aVisibleRegion) + { + if (mAllDrawingAbove) { + return true; + } + nsIntRegion visibleAboveIntersection; + visibleAboveIntersection.And(mVisibleAboveRegion, aVisibleRegion); + if (visibleAboveIntersection.IsEmpty()) { + return false; + } + return true; + } + + bool IsSubjectToAsyncTransforms() + { + return mFixedPosFrameForLayerData != nullptr; + } + + /** + * 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 containing the bounds of all display items in the layer, + * regardless of visbility. + * Same coordinate system as mVisibleRegion. + * This is a conservative approximation: it contains the true region. + */ + nsIntRegion mDrawRegion; + /** + * The region of visible content in the layer that is opaque. + * Same coordinate system as mVisibleRegion. + */ + nsIntRegion mOpaqueRegion; + /** + * The definitely-hit region for this ThebesLayer. + */ + nsIntRegion mHitRegion; + /** + * The maybe-hit region for this ThebesLayer. + */ + nsIntRegion mMaybeHitRegion; + /** + * The dispatch-to-content hit region for this ThebesLayer. + */ + nsIntRegion mDispatchToContentHitRegion; + /** + * The "active scrolled root" for all content in the layer. Must + * be non-null; all content in a ThebesLayer must have the same + * active scrolled root. + */ + const nsIFrame* mAnimatedGeometryRoot; + /** + * 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* mFixedPosFrameForLayerData; + const nsIFrame* mReferenceFrame; + ThebesLayer* 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; + /** + * 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; + + /** + * 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 ThebesDisplayItemLayerUserData::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; + /* + * 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); + +private: + /** + * The region of visible content above the layer and below the + * next ThebesLayerData currently in the stack, if any. Note that not + * all ThebesLayers for the container are in the ThebesLayerData stack. + * Same coordinate system as mVisibleRegion. + * This is a conservative approximation: it contains the true region. + */ + nsIntRegion mVisibleAboveRegion; + /** + * The region containing the bounds of all display items (regardless + * of visibility) in the layer and below the next ThebesLayerData + * currently in the stack, if any. + * Note that not all ThebesLayers for the container are in the + * ThebesLayerData stack. + * Same coordinate system as mVisibleRegion. + */ + nsIntRegion mDrawAboveRegion; + /** + * True if mDrawAboveRegion and mVisibleAboveRegion should be treated + * as infinite, and all display items should be considered 'above' this layer. + */ + bool mAllDrawingAbove; +}; + +/** + * 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, + ContainerLayer* aContainerLayer, + const ContainerLayerParameters& aParameters) : + mBuilder(aBuilder), mManager(aManager), + mLayerBuilder(aLayerBuilder), + mContainerFrame(aContainerFrame), + mContainerLayer(aContainerLayer), + mParameters(aParameters), + mNextFreeRecycledThebesLayer(0) + { + nsPresContext* presContext = aContainerFrame->PresContext(); + mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + mContainerReferenceFrame = aContainerItem ? aContainerItem->ReferenceFrameForChildren() : + mBuilder->FindReferenceFrameFor(mContainerFrame); + mContainerAnimatedGeometryRoot = aContainerItem + ? nsLayoutUtils::GetAnimatedGeometryRootFor(aContainerItem, aBuilder) + : mContainerReferenceFrame; + // 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(); + } + + enum ProcessDisplayItemsFlags { + NO_COMPONENT_ALPHA = 0x01, + }; + + /** + * This is the method that actually walks a display list and builds + * the child layers. + */ + void ProcessDisplayItems(const nsDisplayList& aList, uint32_t aFlags); + /** + * This finalizes all the open ThebesLayers by popping every element off + * mThebesLayerDataStack, 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, LayerManagerData* aData); + + nsRect GetChildrenBounds() { return mBounds; } + + nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; } + + nsIntRect ScaleToNearestPixels(const nsRect& aRect) + { + return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) + { + return aRegion.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRect ScaleToOutsidePixels(const nsRect& aRect, bool aSnap = false) + { + if (aSnap && mSnappingEnabled) { + return ScaleToNearestPixels(aRect); + } + return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) + { + if (aSnap && mSnappingEnabled) { + return ScaleToNearestPixels(aRect); + } + return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + + nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion, bool aSnap = false) + { + if (aSnap && mSnappingEnabled) { + return ScaleRegionToNearestPixels(aRegion); + } + return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + + nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion, bool aSnap = false) + { + if (aSnap && mSnappingEnabled) { + return ScaleRegionToNearestPixels(aRegion); + } + return aRegion.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale, + mAppUnitsPerDevPixel); + } + +protected: + friend class ThebesLayerData; + + /** + * Grab the next recyclable ThebesLayer, or create one if there are no + * more recyclable ThebesLayers. Does any necessary invalidation of + * a recycled ThebesLayer, and sets up the transform on the ThebesLayer + * to account for scrolling. + */ + already_AddRefed CreateOrRecycleThebesLayer(const nsIFrame* aAnimatedGeometryRoot, + const nsIFrame *aReferenceFrame, + const nsPoint& aTopLeft); + /** + * Grab the next recyclable ColorLayer, or create one if there are no + * more recyclable ColorLayers. + */ + already_AddRefed CreateOrRecycleColorLayer(ThebesLayer* aThebes); + /** + * Grab the next recyclable ImageLayer, or create one if there are no + * more recyclable ImageLayers. + */ + already_AddRefed CreateOrRecycleImageLayer(ThebesLayer* aThebes); + /** + * 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. + */ + already_AddRefed CreateOrRecycleMaskImageLayerFor(Layer* aLayer); + /** + * Grabs all ThebesLayers and ColorLayers from the ContainerLayer and makes them + * available for recycling. + */ + void CollectOldLayers(); + /** + * If aItem used to belong to a ThebesLayer, invalidates the area of + * aItem in that layer. If aNewLayer is a ThebesLayer, invalidates the area of + * aItem in that layer. + */ + void InvalidateForLayerChange(nsDisplayItem* aItem, + Layer* aNewLayer, + const DisplayItemClip& aClip, + const nsPoint& aTopLeft, + nsDisplayItemGeometry *aGeometry); + /** + * Try to determine whether the ThebesLayer at aThebesLayerIndex + * has a single opaque color behind it, over the entire bounds of its visible + * region. + * If successful, return that color, otherwise return NS_RGBA(0,0,0,0). + */ + nscolor FindOpaqueBackgroundColorFor(int32_t aThebesLayerIndex); + /** + * Find the fixed-pos frame, if any, containing (or equal to) + * aAnimatedGeometryRoot. Only return a fixed-pos frame if its viewport + * has a displayport. Updates *aVisibleRegion to be the intersection of + * aDrawRegion and the displayport, and updates *aIsSolidColorInVisibleRegion + * (if non-null) to false if the visible region grows. + * aDisplayItemFixedToViewport is true if the layer contains a single display + * item which returned true for ShouldFixToViewport. + * This can return the actual viewport frame for layers whose display items + * are directly on the viewport (e.g. background-attachment:fixed backgrounds). + */ + const nsIFrame* FindFixedPosFrameForLayerData(const nsIFrame* aAnimatedGeometryRoot, + bool aDisplayItemFixedToViewport); + void AdjustLayerDataForFixedPositioning(const nsIFrame* aFixedPosFrame, + const nsIntRegion& aDrawRegion, + nsIntRegion* aVisibleRegion, + bool* aIsSolidColorInVisibleRegion = nullptr); + /** + * Set fixed-pos layer metadata on aLayer according to the data for aFixedPosFrame. + */ + void SetFixedPositionLayerData(Layer* aLayer, + const nsIFrame* aFixedPosFrame); + /** + * Indicate that we are done adding items to the ThebesLayer at the top of + * mThebesLayerDataStack. Set the final visible region and opaque-content + * flag, and pop it off the stack. + */ + void PopThebesLayerData(); + /** + * Find the ThebesLayer to which we should assign the next display item. + * We scan the ThebesLayerData stack to find the topmost ThebesLayer + * that is compatible with the display item (i.e., has the same + * active scrolled root), and that has no content from other layers above + * it and intersecting the aVisibleRect. + * Returns the layer, and also updates the ThebesLayerData. Will + * push a new ThebesLayerData onto the stack if no suitable existing + * layer is found. If we choose a ThebesLayer that's already on the + * ThebesLayerData stack, later elements on the stack will be popped off. + * @param aVisibleRect the area of the next display item that's visible + * @param aAnimatedGeometryRoot the active scrolled root for the next + * display item + * @param aOpaqueRect if non-null, a region of the display item that is opaque + * @param aSolidColor if non-null, indicates that every pixel in aVisibleRect + * will be painted with aSolidColor by the item + * @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. + */ + ThebesLayerData* FindThebesLayerFor(nsDisplayItem* aItem, + const nsIntRect& aVisibleRect, + const nsIFrame* aAnimatedGeometryRoot, + const nsPoint& aTopLeft, + bool aShouldFixToViewport); + ThebesLayerData* GetTopThebesLayerData() + { + return mThebesLayerDataStack.IsEmpty() ? nullptr + : mThebesLayerDataStack[mThebesLayerDataStack.Length() - 1].get(); + } + + /* 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. + * aRoundedRectClipCount is used when building mask layers for ThebesLayers, + * 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); + + bool ChooseAnimatedGeometryRoot(const nsDisplayList& aList, + const nsIFrame **aAnimatedGeometryRoot); + + nsDisplayListBuilder* mBuilder; + LayerManager* mManager; + FrameLayerBuilder* mLayerBuilder; + nsIFrame* mContainerFrame; + const nsIFrame* mContainerReferenceFrame; + const nsIFrame* mContainerAnimatedGeometryRoot; + ContainerLayer* mContainerLayer; + ContainerLayerParameters mParameters; + /** + * The region of ThebesLayers that should be invalidated every time + * we recycle one. + */ + nsIntRegion mInvalidThebesContent; + nsRect mBounds; + nsAutoTArray,1> mThebesLayerDataStack; + /** + * We collect the list of children in here. During ProcessDisplayItems, + * the layers in this array either have mContainerLayer as their parent, + * or no parent. + */ + typedef nsAutoTArray,1> AutoLayersArray; + AutoLayersArray mNewChildLayers; + nsTArray > mRecycledThebesLayers; + nsDataHashtable, nsRefPtr > + mRecycledMaskImageLayers; + uint32_t mNextFreeRecycledThebesLayer; + nscoord mAppUnitsPerDevPixel; + bool mSnappingEnabled; +}; + +class ThebesDisplayItemLayerUserData : public LayerUserData +{ +public: + ThebesDisplayItemLayerUserData() : + mMaskClipCount(0), + mForcedBackgroundColor(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 Thebes layer'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 resolution scale used. + */ + float mXScale, mYScale; + + /** + * The appunits per dev pixel for the items in this layer. + */ + nscoord mAppUnitsPerDevPixel; + + /** + * The offset from the ThebesLayer's 0,0 to the + * reference frame. This isn't necessarily the same as the transform + * set on the ThebesLayer since we might also be applying an extra + * offset specified by the parent ContainerLayer/ + */ + nsIntPoint mTranslation; + + /** + * We try to make 0,0 of the ThebesLayer 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 ThebesLayer transform to be an integer translation, and we may + * have a resolution scale, so we have to snap the ThebesLayer transform, so + * 0,0 may not be exactly the top-left of the active scrolled root. Here we + * store the coordinates in ThebesLayer 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; + + nsRefPtr mColorLayer; + nsRefPtr mImageLayer; +}; + +/* + * User data for layers which will be used as masks. + */ +struct MaskLayerUserData : public LayerUserData +{ + MaskLayerUserData() + : mScaleX(-1.0f) + , mScaleY(-1.0f) + , mAppUnitsPerDevPixel(-1) + { } + + bool + operator== (const MaskLayerUserData& aOther) const + { + return mRoundedClipRects == aOther.mRoundedClipRects && + mScaleX == aOther.mScaleX && + mScaleY == aOther.mScaleY && + mOffset == aOther.mOffset && + mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel; + } + + nsRefPtr mImageKey; + // properties of the mask layer; the mask layer may be re-used if these + // remain unchanged. + nsTArray 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; +}; + +/** + * The address of gThebesDisplayItemLayerUserData is used as the user + * data key for ThebesLayers created by FrameLayerBuilder. + * It identifies ThebesLayers used to draw non-layer content, which are + * therefore eligible for recycling. We want display items to be able to + * create their own dedicated ThebesLayers in BuildLayer, if necessary, + * and we wouldn't want to accidentally recycle those. + * The user data is a ThebesDisplayItemLayerUserData. + */ +uint8_t gThebesDisplayItemLayerUserData; +/** + * 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; + +/** + * 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(aLayer->GetUserData(&gMaskLayerUserData)); +} + +ThebesDisplayItemLayerUserData* GetThebesDisplayItemLayerUserData(Layer* aLayer) +{ + return static_cast( + aLayer->GetUserData(&gThebesDisplayItemLayerUserData)); +} + +/* static */ void +FrameLayerBuilder::Shutdown() +{ + if (gMaskLayerImageCache) { + delete gMaskLayerImageCache; + gMaskLayerImageCache = nullptr; + } +} + +void +FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder, LayerManager* aManager, + ThebesLayerData* aLayerData) +{ + mDisplayListBuilder = aBuilder; + mRootPresContext = aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext(); + if (mRootPresContext) { + mInitialDOMGeneration = mRootPresContext->GetDOMGeneration(); + } + mContainingThebesLayer = 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(gfxRGBA(r, g, b, 0.4)); + aContext->Paint(); +} + +FrameLayerBuilder::DisplayItemData* +FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame, uint32_t aKey) +{ + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (array) { + for (uint32_t i = 0; i < array->Length(); i++) { + DisplayItemData* item = 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; + + nsIntRegionRectIterator it(r); + s += "< "; + while (const nsIntRect* sr = it.Next()) { + AppendToString(s, *sr) += "; "; + } + 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(ThebesLayer* 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 ThebesLayer'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(ThebesLayer* aLayer, const nsRect& aRect, + const DisplayItemClip& aClip, + const nsIntPoint& aTranslation) +{ + ThebesDisplayItemLayerUserData* data = + static_cast(aLayer->GetUserData(&gThebesDisplayItemLayerUserData)); + + nsRect rect = aClip.ApplyNonRoundedIntersection(aRect); + + nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale, data->mAppUnitsPerDevPixel); + InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation); +} + + +static nsIntPoint +GetTranslationForThebesLayer(ThebesLayer* aLayer) +{ + ThebesDisplayItemLayerUserData* data = + static_cast + (aLayer->GetUserData(&gThebesDisplayItemLayerUserData)); + NS_ASSERTION(data, "Must be a tracked thebes 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(nsIFrame* aFrame, + void* aPropertyValue) +{ + sDestroyedFrame = aFrame; + nsTArray *array = + reinterpret_cast*>(aPropertyValue); + + // Hold a reference to all the items so that they don't get + // deleted from under us. + nsTArray > arrayCopy; + for (uint32_t i = 0; i < array->Length(); ++i) { + arrayCopy.AppendElement(array->ElementAt(i)); + } + +#ifdef DEBUG_DISPLAY_ITEM_DATA + if (array->Length()) { + LayerManagerData *rootData = array->ElementAt(0)->mParent; + while (rootData->mParent) { + rootData = rootData->mParent; + } + printf_stderr("Removing frame %p - dumping display data\n", aFrame); + rootData->Dump(); + } +#endif + + for (uint32_t i = 0; i < array->Length(); ++i) { + DisplayItemData* data = array->ElementAt(i); + + ThebesLayer* t = data->mLayer->AsThebesLayer(); + if (t) { + ThebesDisplayItemLayerUserData* thebesData = + static_cast(t->GetUserData(&gThebesDisplayItemLayerUserData)); + if (thebesData) { + nsRegion old = data->mGeometry->ComputeInvalidationRegion(); + nsIntRegion rgn = old.ScaleToOutsidePixels(thebesData->mXScale, thebesData->mYScale, thebesData->mAppUnitsPerDevPixel); + rgn.MoveBy(-GetTranslationForThebesLayer(t)); + thebesData->mRegionToInvalidate.Or(thebesData->mRegionToInvalidate, rgn); + thebesData->mRegionToInvalidate.SimplifyOutward(8); + } + } + + data->mParent->mDisplayItems.RemoveEntry(data); + } + + arrayCopy.Clear(); + delete array; + sDestroyedFrame = nullptr; +} + +void +FrameLayerBuilder::DidBeginRetainedLayerTransaction(LayerManager* aManager) +{ + mRetainingManager = aManager; + LayerManagerData* data = static_cast + (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 + (mRetainingManager->GetUserData(&gLayerManagerUserData)); + NS_ASSERTION(data, "Must have data!"); + // Update all the frames that used to have layers. + data->mDisplayItems.EnumerateEntries(ProcessRemovedDisplayItems, this); + data->mInvalidateAllLayers = false; +} + +/* static */ PLDHashOperator +FrameLayerBuilder::ProcessRemovedDisplayItems(nsRefPtrHashKey* aEntry, + void* aUserArg) +{ + DisplayItemData* data = aEntry->GetKey(); + if (!data->mUsed) { + // This item was visible, but isn't anymore. + FrameLayerBuilder* layerBuilder = static_cast(aUserArg); + + ThebesLayer* t = data->mLayer->AsThebesLayer(); + if (t) { +#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, + layerBuilder->GetLastPaintOffset(t)); + } + return PL_DHASH_REMOVE; + } + + data->mUsed = false; + data->mIsInvalid = false; + return PL_DHASH_NEXT; +} + +/* static */ PLDHashOperator +FrameLayerBuilder::DumpDisplayItemDataForFrame(nsRefPtrHashKey* aEntry, + void* aClosure) +{ +#ifdef DEBUG_DISPLAY_ITEM_DATA + DisplayItemData *data = aEntry->GetKey(); + + nsAutoCString prefix; + prefix += static_cast(aClosure); + + 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(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 + (data->mInactiveManager->GetUserData(&gLayerManagerUserData)); + lmd->Dump(prefix.get()); + } +#endif + return PL_DHASH_NEXT; +} + +/* static */ FrameLayerBuilder::DisplayItemData* +FrameLayerBuilder::GetDisplayItemDataForManager(nsDisplayItem* aItem, + LayerManager* aManager) +{ + nsTArray *array = + reinterpret_cast*>(aItem->Frame()->Properties().Get(LayerManagerDataProperty())); + if (array) { + for (uint32_t i = 0; i < array->Length(); i++) { + DisplayItemData* item = array->ElementAt(i); + if (item->mDisplayItemKey == aItem->GetPerFrameKey() && + item->mLayer->Manager() == aManager) { + return item; + } + } + } + return nullptr; +} + +bool +FrameLayerBuilder::HasRetainedDataFor(nsIFrame* aFrame, uint32_t aDisplayItemKey) +{ + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (array) { + for (uint32_t i = 0; i < array->Length(); i++) { + if (array->ElementAt(i)->mDisplayItemKey == aDisplayItemKey) { + return true; + } + } + } + return false; +} + +void +FrameLayerBuilder::IterateRetainedDataFor(nsIFrame* aFrame, DisplayItemDataCallback aCallback) +{ + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (!array) { + return; + } + + for (uint32_t i = 0; i < array->Length(); i++) { + DisplayItemData* data = 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, + nsTArray* aChangedFrames, + bool *aIsInvalid) +{ + 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; + } + if (aChangedFrames) { + oldData->GetFrameListChanges(aItem, *aChangedFrames); + } + if (aIsInvalid) { + *aIsInvalid = oldData->mIsInvalid; + } + return oldData->mLayer; + } + + return nullptr; +} + +/* static */ Layer* +FrameLayerBuilder::GetDebugOldLayerFor(nsIFrame* aFrame, uint32_t aDisplayItemKey) +{ + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + + if (!array) { + return nullptr; + } + + for (uint32_t i = 0; i < array->Length(); i++) { + DisplayItemData *data = array->ElementAt(i); + + if (data->mDisplayItemKey == aDisplayItemKey) { + return data->mLayer; + } + } + return nullptr; +} + +already_AddRefed +ContainerState::CreateOrRecycleColorLayer(ThebesLayer *aThebes) +{ + ThebesDisplayItemLayerUserData* data = + static_cast(aThebes->GetUserData(&gThebesDisplayItemLayerUserData)); + nsRefPtr layer = data->mColorLayer; + if (layer) { + layer->SetMaskLayer(nullptr); + } else { + // Create a new layer + layer = mManager->CreateColorLayer(); + if (!layer) + return nullptr; + // Mark this layer as being used for Thebes-painting display items + data->mColorLayer = layer; + layer->SetUserData(&gColorLayerUserData, nullptr); + + // Remove other layer types we might have stored for this ThebesLayer + data->mImageLayer = nullptr; + } + return layer.forget(); +} + +already_AddRefed +ContainerState::CreateOrRecycleImageLayer(ThebesLayer *aThebes) +{ + ThebesDisplayItemLayerUserData* data = + static_cast(aThebes->GetUserData(&gThebesDisplayItemLayerUserData)); + nsRefPtr layer = data->mImageLayer; + if (layer) { + layer->SetMaskLayer(nullptr); + } else { + // Create a new layer + layer = mManager->CreateImageLayer(); + if (!layer) + return nullptr; + // Mark this layer as being used for Thebes-painting display items + data->mImageLayer = layer; + layer->SetUserData(&gImageLayerUserData, nullptr); + + // Remove other layer types we might have stored for this ThebesLayer + data->mColorLayer = nullptr; + } + return layer.forget(); +} + +already_AddRefed +ContainerState::CreateOrRecycleMaskImageLayerFor(Layer* aLayer) +{ + nsRefPtr result = mRecycledMaskImageLayers.Get(aLayer); + if (result) { + mRecycledMaskImageLayers.Remove(aLayer); + // XXX if we use clip on mask layers, null it out here + } else { + // Create a new layer + result = mManager->CreateImageLayer(); + if (!result) + return nullptr; + result->SetUserData(&gMaskLayerUserData, new MaskLayerUserData()); + result->SetDisallowBigImage(true); + } + + 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(const nsIFrame* aAnimatedGeometryRoot) +{ + nsIScrollableFrame* sf = nsLayoutUtils::GetScrollableFrameFor(aAnimatedGeometryRoot); + if (sf) { + sf->ResetScrollPositionForLayerPixelAlignment(); + } +} + +static void +InvalidateEntireThebesLayer(ThebesLayer* aLayer, const nsIFrame* aAnimatedGeometryRoot) +{ +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Invalidating entire layer %p\n", aLayer); + } +#endif + nsIntRect invalidate = aLayer->GetValidRegion().GetBounds(); + aLayer->InvalidateRegion(invalidate); + aLayer->SetInvalidRectToVisibleRegion(); + ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot); +} + +already_AddRefed +ContainerState::CreateOrRecycleThebesLayer(const nsIFrame* aAnimatedGeometryRoot, + const nsIFrame* aReferenceFrame, + const nsPoint& aTopLeft) +{ + // We need a new thebes layer + nsRefPtr layer; + ThebesDisplayItemLayerUserData* data; +#ifndef MOZ_ANDROID_OMTC + bool didResetScrollPositionForLayerPixelAlignment = false; +#endif + if (mNextFreeRecycledThebesLayer < mRecycledThebesLayers.Length()) { + // Recycle a layer + layer = mRecycledThebesLayers[mNextFreeRecycledThebesLayer]; + ++mNextFreeRecycledThebesLayer; + // Clear clip rect and mask layer so we don't accidentally stay clipped. + // We will reapply any necessary clipping. + layer->SetMaskLayer(nullptr); + + data = static_cast + (layer->GetUserData(&gThebesDisplayItemLayerUserData)); + NS_ASSERTION(data, "Recycled ThebesLayers must have user data"); + + // This gets called on recycled ThebesLayers 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 ThebesLayer 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 ThebesLayer to its new + // transform. See nsGfxScrollFrame::InvalidateInternal, where + // we ensure that mInvalidThebesContent 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) { + InvalidateEntireThebesLayer(layer, aAnimatedGeometryRoot); +#ifndef MOZ_ANDROID_OMTC + didResetScrollPositionForLayerPixelAlignment = true; +#endif + } + if (!data->mRegionToInvalidate.IsEmpty()) { +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Invalidating deleted frame content from layer %p\n", layer.get()); + } +#endif + layer->InvalidateRegion(data->mRegionToInvalidate); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + nsAutoCString str; + AppendToString(str, data->mRegionToInvalidate); + printf_stderr("Invalidating layer %p: %s\n", layer.get(), str.get()); + } +#endif + data->mRegionToInvalidate.SetEmpty(); + } + + // We do not need to Invalidate these areas in the widget because we + // assume the caller of InvalidateThebesLayerContents has ensured + // the area is invalidated in the widget. + } else { + // Check whether the layer will be scrollable. This is used as a hint to + // influence whether tiled layers are used or not. + bool canScroll = false; + nsIFrame* animatedGeometryRootParent = aAnimatedGeometryRoot->GetParent(); + if (animatedGeometryRootParent && + animatedGeometryRootParent->GetType() == nsGkAtoms::scrollFrame) { + canScroll = true; + } + // Create a new thebes layer + layer = mManager->CreateThebesLayerWithHint(canScroll ? LayerManager::SCROLLABLE : + LayerManager::NONE); + if (!layer) + return nullptr; + // Mark this layer as being used for Thebes-painting display items + data = new ThebesDisplayItemLayerUserData(); + layer->SetUserData(&gThebesDisplayItemLayerUserData, data); + ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot); +#ifndef MOZ_ANDROID_OMTC + didResetScrollPositionForLayerPixelAlignment = true; +#endif + } + data->mXScale = mParameters.mXScale; + data->mYScale = mParameters.mYScale; + data->mLastAnimatedGeometryRootOrigin = data->mAnimatedGeometryRootOrigin; + data->mAnimatedGeometryRootOrigin = aTopLeft; + data->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel; + layer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation()); + + mLayerBuilder->SaveLastPaintOffset(layer); + + // Set up transform so that 0,0 in the Thebes layer 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 data->mAnimatedGeometryRootPosition if possible. + nsIntPoint pixOffset(RoundToMatchResidual(scaledOffset.x, data->mAnimatedGeometryRootPosition.x), + RoundToMatchResidual(scaledOffset.y, data->mAnimatedGeometryRootPosition.y)); + data->mTranslation = pixOffset; + pixOffset += mParameters.mOffset; + Matrix matrix; + matrix.Translate(pixOffset.x, pixOffset.y); + layer->SetBaseTransform(Matrix4x4::From2D(matrix)); + + // FIXME: Temporary workaround for bug 681192 and bug 724786. +#ifndef MOZ_ANDROID_OMTC + // 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(data->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) { + data->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; + InvalidateEntireThebesLayer(layer, aAnimatedGeometryRoot); + } else if (didResetScrollPositionForLayerPixelAlignment) { + data->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft; + } +#endif + + return layer.forget(); +} + +#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(aItem)->GetParentAppUnitsPerDevPixel(); + } + return aItem->Frame()->PresContext()->AppUnitsPerDevPixel(); +} +#endif + +/** + * Restrict the visible region of aLayer to the region that is actually visible. + * Because we only reduce the visible region here, we don't need to worry + * about whether CONTENT_OPAQUE is set; if layer was opaque in the old + * visible region, it will still be opaque in the new one. + * @param aLayerVisibleRegion the visible region of the layer, in the layer's + * coordinate space + * @param aRestrictToRect the rect to restrict the visible region to, in the + * parent's coordinate system + */ +static void +SetVisibleRegionForLayer(Layer* aLayer, const nsIntRegion& aLayerVisibleRegion, + const nsIntRect& aRestrictToRect) +{ + gfx3DMatrix transform; + To3DMatrix(aLayer->GetTransform(), transform); + + // if 'transform' is not invertible, then nothing will be displayed + // for the layer, so it doesn't really matter what we do here + gfxRect itemVisible(aRestrictToRect.x, aRestrictToRect.y, + aRestrictToRect.width, aRestrictToRect.height); + nsIntRect childBounds = aLayerVisibleRegion.GetBounds(); + gfxRect childGfxBounds(childBounds.x, childBounds.y, + childBounds.width, childBounds.height); + gfxRect layerVisible = transform.UntransformBounds(itemVisible, childGfxBounds); + layerVisible.RoundOut(); + + nsIntRect visibleRect; + if (!gfxUtils::GfxRectToIntRect(layerVisible, &visibleRect)) { + aLayer->SetVisibleRegion(nsIntRegion()); + } else { + nsIntRegion rgn; + rgn.And(aLayerVisibleRegion, visibleRect); + aLayer->SetVisibleRegion(rgn); + } +} + +nscolor +ContainerState::FindOpaqueBackgroundColorFor(int32_t aThebesLayerIndex) +{ + ThebesLayerData* target = mThebesLayerDataStack[aThebesLayerIndex]; + for (int32_t i = aThebesLayerIndex - 1; i >= 0; --i) { + ThebesLayerData* candidate = mThebesLayerDataStack[i]; + if (candidate->IntersectsVisibleAboveRegion(target->mVisibleRegion)) { + // Some non-Thebes content between target and candidate; this is + // hopeless + break; + } + + nsIntRegion intersection; + intersection.And(candidate->mVisibleRegion, target->mVisibleRegion); + if (intersection.IsEmpty()) { + // The layer doesn't intersect our target, ignore it and move on + continue; + } + + // The candidate intersects our target. If any layer has a solid-color + // area behind our target, this must be it. Scan its display items. + nsIntRect deviceRect = target->mVisibleRegion.GetBounds(); + nsRect appUnitRect = deviceRect.ToAppUnits(mAppUnitsPerDevPixel); + appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale); + + FrameLayerBuilder::ThebesLayerItemsEntry* entry = + mLayerBuilder->GetThebesLayerItemsEntry(candidate->mLayer); + NS_ASSERTION(entry, "Must know about this layer!"); + for (int32_t j = entry->mItems.Length() - 1; j >= 0; --j) { + nsDisplayItem* item = entry->mItems[j].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)) + break; + + } 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)) + break; + } + + nscolor color; + if (item->IsUniform(mBuilder, &color) && NS_GET_A(color) == 255) + return color; + + break; + } + break; + } + return NS_RGBA(0,0,0,0); +} + +void +ThebesLayerData::UpdateCommonClipCount( + const DisplayItemClip& aCurrentClip) +{ + if (mCommonClipCount >= 0) { + mCommonClipCount = mItemClip.GetCommonRoundedRectCount(aCurrentClip, mCommonClipCount); + } else { + // first item in the layer + mCommonClipCount = aCurrentClip.GetRoundedRectCount(); + } +} + +already_AddRefed +ThebesLayerData::CanOptimizeImageLayer(nsDisplayListBuilder* aBuilder) +{ + if (!mImage) { + return nullptr; + } + + return mImage->GetContainer(mLayer->Manager(), aBuilder); +} + +const nsIFrame* +ContainerState::FindFixedPosFrameForLayerData(const nsIFrame* aAnimatedGeometryRoot, + bool aDisplayItemFixedToViewport) +{ + if (!mManager->IsWidgetLayerManager()) { + // Never attach any fixed-pos metadata to inactive layers, it's pointless! + return nullptr; + } + + nsPresContext* presContext = mContainerFrame->PresContext(); + nsIFrame* viewport = presContext->PresShell()->GetRootFrame(); + + if (viewport == aAnimatedGeometryRoot && aDisplayItemFixedToViewport && + nsLayoutUtils::ViewportHasDisplayPort(presContext)) { + // Probably a background-attachment:fixed item + return viewport; + } + // Viewports with no fixed-pos frames are not relevant. + if (!viewport->GetFirstChild(nsIFrame::kFixedList)) { + return nullptr; + } + for (const nsIFrame* f = aAnimatedGeometryRoot; f; f = f->GetParent()) { + if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(f)) { + return f; + } + if (f == mContainerReferenceFrame) { + // The metadata will go on an ancestor layer if necessary. + return nullptr; + } + } + return nullptr; +} + +void +ContainerState::AdjustLayerDataForFixedPositioning(const nsIFrame* aFixedPosFrame, + const nsIntRegion& aDrawRegion, + nsIntRegion* aVisibleRegion, + bool* aIsSolidColorInVisibleRegion) +{ + if (!aFixedPosFrame) { + return; + } + + nsRect fixedVisibleRect; + nsPresContext* presContext = aFixedPosFrame->PresContext(); + nsIPresShell* presShell = presContext->PresShell(); + DebugOnly hasDisplayPort = + nsLayoutUtils::ViewportHasDisplayPort(presContext, &fixedVisibleRect); + NS_ASSERTION(hasDisplayPort, "No fixed-pos layer data if there's no displayport"); + // Display ports are relative to the viewport, convert it to be relative + // to our reference frame. + nsIFrame* viewport = presShell->GetRootFrame(); + if (aFixedPosFrame != viewport) { + // position: fixed items are reflowed into and only drawn inside the + // viewport, or the scroll position clamping scrollport size, if one is + // set. We differentiate background-attachment: fixed items from + // position: fixed items by the fact that background-attachment: fixed + // items use the viewport as their aFixedPosFrame. + NS_ASSERTION(aFixedPosFrame->StyleDisplay()->mPosition == NS_STYLE_POSITION_FIXED, + "should be position fixed items only"); + fixedVisibleRect.MoveTo(0, 0); + if (presShell->IsScrollPositionClampingScrollPortSizeSet()) { + fixedVisibleRect.SizeTo(presShell->GetScrollPositionClampingScrollPortSize()); + } else { + fixedVisibleRect.SizeTo(viewport->GetSize()); + } + } + fixedVisibleRect += viewport->GetOffsetToCrossDoc(mContainerReferenceFrame); + nsIntRegion newVisibleRegion; + newVisibleRegion.And(ScaleToOutsidePixels(fixedVisibleRect, false), + aDrawRegion); + if (!aVisibleRegion->Contains(newVisibleRegion)) { + if (aIsSolidColorInVisibleRegion) { + *aIsSolidColorInVisibleRegion = false; + } + *aVisibleRegion = newVisibleRegion; + } +} + +void +ContainerState::SetFixedPositionLayerData(Layer* aLayer, + const nsIFrame* aFixedPosFrame) +{ + aLayer->SetIsFixedPosition(aFixedPosFrame != nullptr); + if (!aFixedPosFrame) { + return; + } + + nsPresContext* presContext = aFixedPosFrame->PresContext(); + + const nsIFrame* viewportFrame = aFixedPosFrame->GetParent(); + // anchorRect will be in the container's coordinate system (aLayer's parent layer). + // This is the same as the display items' reference frame. + nsRect anchorRect; + if (viewportFrame) { + // Fixed position frames are reflowed into the scroll-port size if one has + // been set. + if (presContext->PresShell()->IsScrollPositionClampingScrollPortSizeSet()) { + anchorRect.SizeTo(presContext->PresShell()->GetScrollPositionClampingScrollPortSize()); + } else { + anchorRect.SizeTo(viewportFrame->GetSize()); + } + } else { + // A display item directly attached to the viewport. + // For background-attachment:fixed items, the anchor point is always the + // top-left of the viewport currently. + viewportFrame = aFixedPosFrame; + } + // The anchorRect top-left is always the viewport top-left. + anchorRect.MoveTo(viewportFrame->GetOffsetToCrossDoc(mContainerReferenceFrame)); + + nsLayoutUtils::SetFixedPositionLayerData(aLayer, + viewportFrame, anchorRect, aFixedPosFrame, presContext, mParameters); +} + +static gfx3DMatrix +GetTransformToRoot(Layer* aLayer) +{ + Matrix4x4 transform = aLayer->GetTransform(); + for (Layer* l = aLayer->GetParent(); l; l = l->GetParent()) { + transform = transform * l->GetTransform(); + } + gfx3DMatrix result; + To3DMatrix(transform, result); + return result; +} + +static void +AddTransformedBoundsToRegion(const nsIntRegion& aRegion, + const gfx3DMatrix& aTransform, + nsIntRegion* aDest) +{ + nsIntRect bounds = aRegion.GetBounds(); + gfxRect transformed = + aTransform.TransformBounds(gfxRect(bounds.x, bounds.y, bounds.width, bounds.height)); + transformed.RoundOut(); + nsIntRect intRect; + if (!gfxUtils::GfxRectToIntRect(transformed, &intRect)) { + // This should only fail if coordinates are too big to fit in an int32 + *aDest = nsIntRect(-INT32_MAX/2, -INT32_MAX/2, INT32_MAX, INT32_MAX); + return; + } + aDest->Or(*aDest, intRect); +} + +static bool +CanOptimizeAwayThebesLayer(ThebesLayerData* aData, + FrameLayerBuilder* aLayerBuilder) +{ + bool isRetained = aData->mLayer->Manager()->IsWidgetLayerManager(); + if (!isRetained) { + return false; + } + + // If there's no thebes 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 thebes layer). + if (aData->mLayer->GetValidRegion().IsEmpty()) { + return true; + } + + // There is an existing thebes layer we can reuse. Throwing it away can make + // compositing cheaper (see bug 946952), but it might cause us to re-allocate + // the thebes 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(); +} + +void +ContainerState::PopThebesLayerData() +{ + NS_ASSERTION(!mThebesLayerDataStack.IsEmpty(), "Can't pop"); + + int32_t lastIndex = mThebesLayerDataStack.Length() - 1; + ThebesLayerData* data = mThebesLayerDataStack[lastIndex]; + + AdjustLayerDataForFixedPositioning(data->mFixedPosFrameForLayerData, + data->mDrawRegion, + &data->mVisibleRegion, + &data->mIsSolidColorInVisibleRegion); + nsRefPtr layer; + nsRefPtr imageContainer = data->CanOptimizeImageLayer(mBuilder); + + if ((data->mIsSolidColorInVisibleRegion || imageContainer) && + CanOptimizeAwayThebesLayer(data, mLayerBuilder)) { + NS_ASSERTION(!(data->mIsSolidColorInVisibleRegion && imageContainer), + "Can't be a solid color as well as an image!"); + if (imageContainer) { + nsRefPtr imageLayer = CreateOrRecycleImageLayer(data->mLayer); + imageLayer->SetContainer(imageContainer); + data->mImage->ConfigureLayer(imageLayer, mParameters.mOffset); + imageLayer->SetPostScale(mParameters.mXScale, + mParameters.mYScale); + if (data->mItemClip.HasClip()) { + nsIntRect clip = ScaleToNearestPixels(data->mItemClip.GetClipRect()); + clip.MoveBy(mParameters.mOffset); + imageLayer->SetClipRect(&clip); + } else { + imageLayer->SetClipRect(nullptr); + } + layer = imageLayer; + mLayerBuilder->StoreOptimizedLayerForFrame(data->mImage, + imageLayer); + } else { + nsRefPtr colorLayer = CreateOrRecycleColorLayer(data->mLayer); + colorLayer->SetColor(data->mSolidColor); + + // Copy transform + colorLayer->SetBaseTransform(data->mLayer->GetBaseTransform()); + colorLayer->SetPostScale(data->mLayer->GetPostXScale(), data->mLayer->GetPostYScale()); + + nsIntRect visibleRect = data->mVisibleRegion.GetBounds(); + visibleRect.MoveBy(-GetTranslationForThebesLayer(data->mLayer)); + colorLayer->SetBounds(visibleRect); + + layer = colorLayer; + } + + NS_ASSERTION(!mNewChildLayers.Contains(layer), "Layer already in list???"); + AutoLayersArray::index_type index = mNewChildLayers.IndexOf(data->mLayer); + NS_ASSERTION(index != AutoLayersArray::NoIndex, "Thebes layer not found?"); + mNewChildLayers.InsertElementAt(index + 1, layer); + + // Hide the ThebesLayer. We leave it in the layer tree so that we + // can find and recycle it later. + nsIntRect emptyRect; + data->mLayer->SetClipRect(&emptyRect); + data->mLayer->SetVisibleRegion(nsIntRegion()); + data->mLayer->SetEventRegions(EventRegions()); + } else { + layer = data->mLayer; + imageContainer = nullptr; + layer->SetClipRect(nullptr); + } + + Matrix transform; + if (!layer->GetTransform().Is2D(&transform)) { + NS_ERROR("Only 2D transformations currently supported"); + } + + // ImageLayers are already configured with a visible region + if (!imageContainer) { + NS_ASSERTION(!transform.HasNonIntegerTranslation(), + "Matrix not just an integer translation?"); + // Convert from relative to the container to relative to the + // ThebesLayer itself. + nsIntRegion rgn = data->mVisibleRegion; + rgn.MoveBy(-GetTranslationForThebesLayer(data->mLayer)); + layer->SetVisibleRegion(rgn); + } + + nsIntRegion transparentRegion; + transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion); + bool isOpaque = transparentRegion.IsEmpty(); + // For translucent ThebesLayers, 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 = FindOpaqueBackgroundColorFor(lastIndex); + if (NS_GET_A(backgroundColor) == 255) { + isOpaque = true; + } + } + + // Store the background color + ThebesDisplayItemLayerUserData* userData = + GetThebesDisplayItemLayerUserData(data->mLayer); + NS_ASSERTION(userData, "where did our user data go?"); + if (userData->mForcedBackgroundColor != backgroundColor) { + // Invalidate the entire target ThebesLayer since we're changing + // the background color + data->mLayer->InvalidateRegion(data->mLayer->GetValidRegion()); + } + userData->mForcedBackgroundColor = backgroundColor; + + // 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 = std::max(0, data->mCommonClipCount); + SetupMaskLayer(layer, data->mItemClip, commonClipCount); + // copy commonClipCount to the entry + FrameLayerBuilder::ThebesLayerItemsEntry* entry = mLayerBuilder-> + GetThebesLayerItemsEntry(static_cast(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; + } + layer->SetContentFlags(flags); + + SetFixedPositionLayerData(layer, data->mFixedPosFrameForLayerData); + + ThebesLayerData* containingThebesLayerData = + mLayerBuilder->GetContainingThebesLayerData(); + if (containingThebesLayerData) { + gfx3DMatrix matrix = GetTransformToRoot(layer); + nsIntPoint translatedDest = GetTranslationForThebesLayer(containingThebesLayerData->mLayer); + matrix.TranslatePost(-gfxPoint3D(translatedDest.x, translatedDest.y, 0)); + AddTransformedBoundsToRegion(data->mDispatchToContentHitRegion, matrix, + &containingThebesLayerData->mDispatchToContentHitRegion); + AddTransformedBoundsToRegion(data->mMaybeHitRegion, matrix, + &containingThebesLayerData->mMaybeHitRegion); + // Our definitely-hit region must go to the maybe-hit-region since + // this function is an approximation. + gfxMatrix matrix2D; + bool isPrecise = matrix.Is2D(&matrix2D) && !matrix2D.HasNonAxisAlignedTransform(); + AddTransformedBoundsToRegion(data->mHitRegion, matrix, + isPrecise ? &containingThebesLayerData->mHitRegion + : &containingThebesLayerData->mMaybeHitRegion); + } else { + EventRegions regions; + regions.mHitRegion.Swap(&data->mHitRegion); + // Points whose hit-region status we're not sure about need to be dispatched + // to the content thread. + regions.mDispatchToContentHitRegion.Sub(data->mMaybeHitRegion, regions.mHitRegion); + regions.mDispatchToContentHitRegion.Or(regions.mDispatchToContentHitRegion, + data->mDispatchToContentHitRegion); + layer->SetEventRegions(regions); + } + + if (lastIndex > 0) { + // Since we're going to pop off the last ThebesLayerData, the + // mVisibleAboveRegion of the second-to-last item will need to include + // the regions of the last item. + ThebesLayerData* nextData = mThebesLayerDataStack[lastIndex - 1]; + nextData->CopyAboveRegion(data); + } + + mThebesLayerDataStack.RemoveElementAt(lastIndex); +} + +static bool +SuppressComponentAlpha(nsDisplayListBuilder* aBuilder, + nsDisplayItem* aItem, + const nsRect& aComponentAlphaBounds) +{ + const nsRegion* windowTransparentRegion = aBuilder->GetFinalTransparentRegion(); + if (!windowTransparentRegion || windowTransparentRegion->IsEmpty()) + return false; + + // Suppress component alpha for items in the toplevel window that are over + // the window translucent area + nsIFrame* f = aItem->Frame(); + nsIFrame* ref = aBuilder->RootReferenceFrame(); + if (f->PresContext() != ref->PresContext()) + return false; + + for (nsIFrame* t = f; t; t = t->GetParent()) { + if (t->IsTransformed()) + return false; + } + + return windowTransparentRegion->Intersects(aComponentAlphaBounds); +} + +static bool +WindowHasTransparency(nsDisplayListBuilder* aBuilder) +{ + const nsRegion* windowTransparentRegion = aBuilder->GetFinalTransparentRegion(); + return windowTransparentRegion && !windowTransparentRegion->IsEmpty(); +} + +void +ThebesLayerData::Accumulate(ContainerState* aState, + nsDisplayItem* aItem, + const nsIntRect& aVisibleRect, + const nsIntRect& aDrawRect, + const DisplayItemClip& aClip) +{ + if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(aItem)) { + mForceTransparentSurface = true; + } + if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) { + // Disable component alpha. + // Note that the transform (if any) on the ThebesLayer is always an integer translation so + // we don't have to factor that in here. + aItem->DisableComponentAlpha(); + } + + /* Mark as available for conversion to image layer if this is a nsDisplayImage and + * we are the first visible item in the ThebesLayerData object. + */ + if (mVisibleRegion.IsEmpty() && + aItem->SupportsOptimizingToImage()) { + mImage = static_cast(aItem); + } else { + mImage = nullptr; + } + bool clipMatches = mItemClip == aClip; + mItemClip = aClip; + + if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aDrawRect) && + mVisibleRegion.Contains(aVisibleRect)) { + // A very common case! Most pages have a ThebesLayer 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, mOpaqueRegion + // and mDrawRegion don't need updating. mVisibleRegion contains aVisibleRect + // already, mOpaqueRegion contains aDrawRect and therefore whatever + // the opaque region of the item is. mDrawRegion must contain mOpaqueRegion + // and therefore aDrawRect. + NS_ASSERTION(mDrawRegion.Contains(aDrawRect), "Draw region not covered"); + return; + } + + nscolor uniformColor; + bool isUniform = aItem->IsUniform(aState->mBuilder, &uniformColor); + + // 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 (!isUniform || 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 (isUniform) { + bool snap; + nsRect bounds = aItem->GetBounds(aState->mBuilder, &snap); + if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) { + isUniform = false; + } + } + if (isUniform) { + if (mVisibleRegion.IsEmpty()) { + // 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 { + mIsSolidColorInVisibleRegion = false; + } + } else { + mIsSolidColorInVisibleRegion = false; + } + + mVisibleRegion.Or(mVisibleRegion, aVisibleRect); + mVisibleRegion.SimplifyOutward(4); + mDrawRegion.Or(mDrawRegion, aDrawRect); + mDrawRegion.SimplifyOutward(4); + } + + bool snap; + nsRegion opaque = aItem->GetOpaqueRegion(aState->mBuilder, &snap); + if (!opaque.IsEmpty()) { + nsRegion opaqueClipped; + nsRegionRectIterator iter(opaque); + for (const nsRect* r = iter.Next(); r; r = iter.Next()) { + opaqueClipped.Or(opaqueClipped, aClip.ApproximateIntersectInward(*r)); + } + + nsIntRegion opaquePixels = aState->ScaleRegionToInsidePixels(opaqueClipped, snap); + + nsIntRegionRectIterator iter2(opaquePixels); + for (const nsIntRect* r = iter2.Next(); r; r = iter2.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, *r); + // 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 || + (WindowHasTransparency(aState->mBuilder) && + aItem->Frame()->PresContext()->IsChrome())) { + mOpaqueRegion = 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 (SuppressComponentAlpha(aState->mBuilder, aItem, componentAlpha)) { + aItem->DisableComponentAlpha(); + } else { + mNeedComponentAlpha = true; + } + } + } + } +} + +ThebesLayerData* +ContainerState::FindThebesLayerFor(nsDisplayItem* aItem, + const nsIntRect& aVisibleRect, + const nsIFrame* aAnimatedGeometryRoot, + const nsPoint& aTopLeft, + bool aShouldFixToViewport) +{ + int32_t i; + int32_t lowestUsableLayerWithScrolledRoot = -1; + int32_t topmostLayerWithScrolledRoot = -1; + for (i = mThebesLayerDataStack.Length() - 1; i >= 0; --i) { + // Don't let should-fix-to-viewport items share a layer with any other items. + if (aShouldFixToViewport) { + ++i; + break; + } + ThebesLayerData* data = mThebesLayerDataStack[i]; + // Give up if there is content drawn above (in z-order) this layer that + // intersects aItem's visible region; aItem must be placed in a + // layer this layer. + if (data->DrawAboveRegionIntersects(aVisibleRect)) { + ++i; + break; + } + // If the animated scrolled roots are the same and we can share this layer + // with the item, note this as a usable layer. + if (data->mAnimatedGeometryRoot == aAnimatedGeometryRoot && + !data->mSingleItemFixedToViewport) { + lowestUsableLayerWithScrolledRoot = i; + if (topmostLayerWithScrolledRoot < 0) { + topmostLayerWithScrolledRoot = i; + } + } + // If the layer's drawn region intersects the item, stop now since no + // lower layer will be usable. Do the same if the layer is subject to + // async transforms, since we don't know where it will really be drawn. + if (data->DrawRegionIntersects(aVisibleRect)) + break; + } + if (topmostLayerWithScrolledRoot < 0) { + --i; + for (; i >= 0; --i) { + ThebesLayerData* data = mThebesLayerDataStack[i]; + if (data->mAnimatedGeometryRoot == aAnimatedGeometryRoot) { + topmostLayerWithScrolledRoot = i; + break; + } + } + } + + if (topmostLayerWithScrolledRoot >= 0) { + while (uint32_t(topmostLayerWithScrolledRoot + 1) < mThebesLayerDataStack.Length()) { + PopThebesLayerData(); + } + } + + ThebesLayerData* thebesLayerData = nullptr; + if (lowestUsableLayerWithScrolledRoot < 0) { + nsRefPtr layer = + CreateOrRecycleThebesLayer(aAnimatedGeometryRoot, aItem->ReferenceFrame(), aTopLeft); + + thebesLayerData = new ThebesLayerData(); + mThebesLayerDataStack.AppendElement(thebesLayerData); + thebesLayerData->mLayer = layer; + thebesLayerData->mAnimatedGeometryRoot = aAnimatedGeometryRoot; + thebesLayerData->mFixedPosFrameForLayerData = + FindFixedPosFrameForLayerData(aAnimatedGeometryRoot, aShouldFixToViewport); + thebesLayerData->mReferenceFrame = aItem->ReferenceFrame(); + thebesLayerData->mSingleItemFixedToViewport = aShouldFixToViewport; + + NS_ASSERTION(!mNewChildLayers.Contains(layer), "Layer already in list???"); + *mNewChildLayers.AppendElement() = layer.forget(); + } else { + thebesLayerData = mThebesLayerDataStack[lowestUsableLayerWithScrolledRoot]; + } + + return thebesLayerData; +} + +#ifdef MOZ_DUMP_PAINTING +static void +DumpPaintedImage(nsDisplayItem* aItem, gfxASurface* aSurf) +{ + nsCString string(aItem->Name()); + string.Append("-"); + string.AppendInt((uint64_t)aItem); + fprintf_stderr(gfxUtils::sDumpPaintFile, "array[\"%s\"]=\"", string.BeginReading()); + aSurf->DumpAsDataURL(gfxUtils::sDumpPaintFile); + fprintf_stderr(gfxUtils::sDumpPaintFile, "\";"); +} +#endif + +static void +PaintInactiveLayer(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + nsDisplayItem* aItem, + gfxContext* aContext, + nsRenderingContext* aCtx) +{ + // This item has an inactive layer. Render it to a ThebesLayer + // using a temporary BasicLayerManager. + BasicLayerManager* basic = static_cast(aManager); + nsRefPtr context = aContext; +#ifdef MOZ_DUMP_PAINTING + int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem); + nsIntRect itemVisibleRect = + aItem->GetVisibleRect().ToOutsidePixels(appUnitsPerDevPixel); + + nsRefPtr surf; + if (gfxUtils::sDumpPainting) { + surf = gfxPlatform::GetPlatform()->CreateOffscreenSurface(itemVisibleRect.Size().ToIntSize(), + gfxContentType::COLOR_ALPHA); + surf->SetDeviceOffset(-itemVisibleRect.TopLeft()); + context = new gfxContext(surf); + } +#endif + basic->BeginTransaction(); + basic->SetTarget(context); + + if (aItem->GetType() == nsDisplayItem::TYPE_SVG_EFFECTS) { + static_cast(aItem)->PaintAsLayer(aBuilder, aCtx, basic); + if (basic->InTransaction()) { + basic->AbortTransaction(); + } + } else { + basic->EndTransaction(FrameLayerBuilder::DrawThebesLayer, aBuilder); + } + FrameLayerBuilder *builder = static_cast(basic->GetUserData(&gLayerManagerLayerBuilder)); + if (builder) { + builder->DidEndTransaction(); + } + + basic->SetTarget(nullptr); + +#ifdef MOZ_DUMP_PAINTING + if (gfxUtils::sDumpPainting) { + DumpPaintedImage(aItem, surf); + + surf->SetDeviceOffset(gfxPoint(0, 0)); + aContext->SetSource(surf, itemVisibleRect.TopLeft()); + aContext->Rectangle(itemVisibleRect); + aContext->Fill(); + 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, + const nsIFrame **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 ThebesLayers 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 = + nsLayoutUtils::GetAnimatedGeometryRootFor(item, mBuilder); + return true; + } + return false; +} + +/* + * 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 ThebesLayers where an item + * has moved from one ThebesLayer to another. Also, + * aState->mInvalidThebesContent is invalidated in every ThebesLayer. + * We set the clip rect for items that generated their own layer, and + * create a mask layer to do any rounded rect clipping. + * (ThebesLayers 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 ThebesLayers is deferred until the calling + * of ContainerState::Finish. + */ +void +ContainerState::ProcessDisplayItems(const nsDisplayList& aList, + uint32_t aFlags) +{ + PROFILER_LABEL("ContainerState", "ProcessDisplayItems"); + + const nsIFrame* lastAnimatedGeometryRoot = mContainerReferenceFrame; + 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 (aFlags & NO_COMPONENT_ALPHA) { + if (ChooseAnimatedGeometryRoot(aList, &lastAnimatedGeometryRoot)) { + topLeft = lastAnimatedGeometryRoot->GetOffsetToCrossDoc(mContainerReferenceFrame); + } + } + + int32_t maxLayers = nsDisplayItem::MaxActiveLayers(); + int layerCount = 0; + + for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) { + NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item), + "items in a container layer should all have the same app units per dev pixel"); + + nsIntRect itemVisibleRect = + ScaleToOutsidePixels(item->GetVisibleRect(), false); + bool snap; + nsRect itemContent = item->GetBounds(mBuilder, &snap); + nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap); + nsDisplayItem::Type itemType = item->GetType(); + nsIntRect clipRect; + const DisplayItemClip& itemClip = item->GetClip(); + if (itemClip.HasClip()) { + itemContent.IntersectRect(itemContent, itemClip.GetClipRect()); + clipRect = ScaleToNearestPixels(itemClip.GetClipRect()); + itemDrawRect.IntersectRect(itemDrawRect, clipRect); + clipRect.MoveBy(mParameters.mOffset); + } + mBounds.UnionRect(mBounds, itemContent); + itemVisibleRect.IntersectRect(itemVisibleRect, itemDrawRect); + + LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters); + if (layerState == LAYER_INACTIVE && + nsDisplayItem::ForceActiveLayers()) { + layerState = LAYER_ACTIVE; + } + + bool forceInactive; + const nsIFrame* animatedGeometryRoot; + if (aFlags & NO_COMPONENT_ALPHA) { + forceInactive = true; + animatedGeometryRoot = lastAnimatedGeometryRoot; + } else { + forceInactive = false; + if (mManager->IsWidgetLayerManager()) { + animatedGeometryRoot = nsLayoutUtils::GetAnimatedGeometryRootFor(item, mBuilder); + } else { + // For inactive layer subtrees, splitting content into ThebesLayers + // based on animated geometry roots is pointless. It's more efficient + // to build the minimum number of layers. + animatedGeometryRoot = mContainerAnimatedGeometryRoot; + } + if (animatedGeometryRoot != lastAnimatedGeometryRoot) { + lastAnimatedGeometryRoot = animatedGeometryRoot; + topLeft = animatedGeometryRoot->GetOffsetToCrossDoc(mContainerReferenceFrame); + } + } + bool shouldFixToViewport = !animatedGeometryRoot->GetParent() && + item->ShouldFixToViewport(mBuilder); + + 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 ThebesLayer, + // 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, itemClip, topLeft, 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 ThebesLayer may decide it wants to draw them + // into its buffer even if they're currently covered. + if (itemVisibleRect.IsEmpty() && + !item->ShouldBuildLayerEvenIfInvisible(mBuilder)) { + continue; + } + + if (itemType == nsDisplayItem::TYPE_TRANSFORM) { + mParameters.mAncestorClipRect = itemClip.HasClip() ? &clipRect : nullptr; + } else { + mParameters.mAncestorClipRect = nullptr; + } + + // Just use its layer. + nsRefPtr ownLayer = item->BuildLayer(mBuilder, mManager, mParameters); + if (!ownLayer) { + continue; + } + + NS_ASSERTION(!ownLayer->AsThebesLayer(), + "Should never have created a dedicated Thebes layer!"); + + const nsIFrame* fixedPosFrame = + FindFixedPosFrameForLayerData(animatedGeometryRoot, shouldFixToViewport); + if (fixedPosFrame) { + nsIntRegion visibleRegion(itemVisibleRect); + AdjustLayerDataForFixedPositioning(fixedPosFrame, + nsIntRegion(itemDrawRect), &visibleRegion); + itemVisibleRect = visibleRegion.GetBounds(); + } + SetFixedPositionLayerData(ownLayer, fixedPosFrame); + + 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(itemClip.HasClip() || + itemClip.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. + if (itemClip.HasClip()) { + ownLayer->SetClipRect(&clipRect); + } else { + ownLayer->SetClipRect(nullptr); + } + ThebesLayerData* data = GetTopThebesLayerData(); + if (data) { + // Prerendered transform items can be updated without layer building + // (async animations or an empty transaction), so we treat all other + // content as being above this so that the transformed layer can correctly + // move behind other content. + if (item->GetType() == nsDisplayItem::TYPE_TRANSFORM && + nsDisplayTransform::ShouldPrerenderTransformedContent(mBuilder, + item->Frame(), + false)) { + data->SetAllDrawingAbove(); + } else { + data->AddVisibleAboveRegion(itemVisibleRect); + + // Add the entire bounds rect to the mDrawAboveRegion. + // The visible region may be excluding opaque content above the + // item, and we need to ensure that that content is not placed + // in a ThebesLayer below the item! + data->AddDrawAboveRegion(itemDrawRect); + } + } + itemVisibleRect.MoveBy(mParameters.mOffset); + if (item->SetVisibleRegionOnLayer()) { + SetVisibleRegionForLayer(ownLayer, ownLayer->GetVisibleRegion(), itemVisibleRect); + } + + // rounded rectangle clipping using mask layers + // (must be done after visible rect is set on layer) + if (itemClip.IsRectClippedByRoundedCorner(itemContent)) { + SetupMaskLayer(ownLayer, itemClip); + } + + ContainerLayer* oldContainer = ownLayer->GetParent(); + if (oldContainer && oldContainer != mContainerLayer) { + oldContainer->RemoveChild(ownLayer); + } + NS_ASSERTION(!mNewChildLayers.Contains(ownLayer), + "Layer already in list???"); + + mNewChildLayers.AppendElement(ownLayer); + + /** + * No need to allocate geometry for items that aren't + * part of a ThebesLayer. + */ + nsAutoPtr dummy; + mLayerBuilder->AddLayerDisplayItem(ownLayer, item, + itemClip, layerState, + topLeft, nullptr, + dummy); + } else { + ThebesLayerData* data = + FindThebesLayerFor(item, itemVisibleRect, animatedGeometryRoot, topLeft, + shouldFixToViewport); + + if (itemType == nsDisplayItem::TYPE_LAYER_EVENT_REGIONS) { + nsDisplayLayerEventRegions* eventRegions = + static_cast(item); + data->AccumulateEventRegions(ScaleRegionToOutsidePixels(eventRegions->HitRegion()), + ScaleRegionToOutsidePixels(eventRegions->MaybeHitRegion()), + ScaleRegionToOutsidePixels(eventRegions->DispatchToContentHitRegion())); + } else { + // check to see if the new item has rounded rect clips in common with + // other items in the layer + data->UpdateCommonClipCount(itemClip); + data->Accumulate(this, item, itemVisibleRect, itemDrawRect, itemClip); + + nsAutoPtr geometry(item->AllocateGeometry(mBuilder)); + InvalidateForLayerChange(item, data->mLayer, itemClip, topLeft, geometry); + + mLayerBuilder->AddThebesDisplayItem(data, item, itemClip, + mContainerFrame, + layerState, topLeft, + geometry); + } + } + } +} + +void +ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem, + Layer* aNewLayer, + const DisplayItemClip& aClip, + const nsPoint& aTopLeft, + nsDisplayItemGeometry *aGeometry) +{ + NS_ASSERTION(aItem->GetPerFrameKey(), + "Display items that render using Thebes must have a key"); + nsDisplayItemGeometry *oldGeometry = nullptr; + DisplayItemClip* oldClip = nullptr; + nsAutoTArray changedFrames; + bool isInvalid = false; + Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem, &oldGeometry, &oldClip, &changedFrames, &isInvalid); + if (aNewLayer != oldLayer && oldLayer) { + // The item has changed layers. + // Invalidate the old bounds in the old layer and new bounds in the new layer. + ThebesLayer* t = oldLayer->AsThebesLayer(); + if (t) { + // 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)); + } + if (aNewLayer) { + ThebesLayer* newThebesLayer = aNewLayer->AsThebesLayer(); + if (newThebesLayer) { + InvalidatePostTransformRegion(newThebesLayer, + aGeometry->ComputeInvalidationRegion(), + aClip, + GetTranslationForThebesLayer(newThebesLayer)); + } + } + aItem->NotifyRenderingChanged(); + return; + } + if (!aNewLayer) { + return; + } + + ThebesLayer* newThebesLayer = aNewLayer->AsThebesLayer(); + if (!newThebesLayer) { + return; + } + + ThebesDisplayItemLayerUserData* data = + static_cast(newThebesLayer->GetUserData(&gThebesDisplayItemLayerUserData)); + // 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; + nsPoint shift = aTopLeft - data->mLastAnimatedGeometryRootOrigin; + bool notifyRenderingChanged = true; + if (!oldLayer) { + // This item is being added for the first time, invalidate its entire area. + //TODO: We call GetGeometry again in AddThebesDisplayItem, we should reuse this. + combined = aClip.ApplyNonRoundedIntersection(aGeometry->ComputeInvalidationRegion()); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Display item type %s(%p) added to layer %p!\n", aItem->Name(), aItem->Frame(), aNewLayer); + } +#endif + } else if (isInvalid || (aItem->IsInvalid(invalid) && invalid.IsEmpty())) { + // Either layout marked item as needing repainting, invalidate the entire old and new areas. + combined = oldClip->ApplyNonRoundedIntersection(oldGeometry->ComputeInvalidationRegion()); + combined.MoveBy(shift); + combined.Or(combined, aClip.ApplyNonRoundedIntersection(aGeometry->ComputeInvalidationRegion())); +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + printf_stderr("Display item type %s(%p) (in layer %p) belongs to an invalidated frame!\n", aItem->Name(), aItem->Frame(), aNewLayer); + } +#endif + } else { + // Let the display item check for geometry changes and decide what needs to be + // repainted. + + // We have an optimization to cache the drawing 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). + if (oldGeometry->ComputeInvalidationRegion() == aGeometry->ComputeInvalidationRegion() && + *oldClip == aClip && invalid.IsEmpty() && changedFrames.Length() == 0) { + notifyRenderingChanged = false; + } + + oldGeometry->MoveBy(shift); + aItem->ComputeInvalidationRegion(mBuilder, oldGeometry, &combined); + oldClip->AddOffsetAndComputeDifference(shift, oldGeometry->ComputeInvalidationRegion(), + aClip, aGeometry->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 clip; + if (aClip.ComputeRegionInClips(oldClip, shift, &clip)) { + combined.And(combined, clip); + } +#ifdef MOZ_DUMP_PAINTING + if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) { + if (!combined.IsEmpty()) { + printf_stderr("Display item type %s(%p) (in layer %p) changed geometry!\n", aItem->Name(), aItem->Frame(), aNewLayer); + } + } +#endif + } + if (!combined.IsEmpty()) { + if (notifyRenderingChanged) { + aItem->NotifyRenderingChanged(); + } + InvalidatePostTransformRegion(newThebesLayer, + combined.ScaleToOutsidePixels(data->mXScale, data->mYScale, mAppUnitsPerDevPixel), + GetTranslationForThebesLayer(newThebesLayer)); + } +} + +void +FrameLayerBuilder::AddThebesDisplayItem(ThebesLayerData* aLayerData, + nsDisplayItem* aItem, + const DisplayItemClip& aClip, + nsIFrame* aContainerLayerFrame, + LayerState aLayerState, + const nsPoint& aTopLeft, + nsAutoPtr aGeometry) +{ + ThebesLayer* layer = aLayerData->mLayer; + ThebesDisplayItemLayerUserData* thebesData = + static_cast + (layer->GetUserData(&gThebesDisplayItemLayerUserData)); + nsRefPtr 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(); + } + + // 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 - thebesData->mLastAnimatedGeometryRootOrigin, + &clip); + + if (hasClip) { + intClip = clip.GetBounds().ScaleToOutsidePixels(thebesData->mXScale, + thebesData->mYScale, + thebesData->mAppUnitsPerDevPixel); + } + } + + AddLayerDisplayItem(layer, aItem, aClip, aLayerState, aTopLeft, tempManager, aGeometry); + + ThebesLayerItemsEntry* entry = mThebesLayerItems.PutEntry(layer); + if (entry) { + entry->mContainerLayerFrame = aContainerLayerFrame; + if (entry->mContainerLayerGeneration == 0) { + entry->mContainerLayerGeneration = mContainerLayerGeneration; + } + if (tempManager) { + FrameLayerBuilder* layerBuilder = new FrameLayerBuilder(); + layerBuilder->Init(mDisplayListBuilder, tempManager, aLayerData); + + tempManager->BeginTransaction(); + if (mRetainingManager) { + layerBuilder->DidBeginRetainedLayerTransaction(tempManager); + } + + nsAutoPtr props(LayerProperties::CloneFrom(tempManager->GetRoot())); + nsRefPtr 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; + } + + // 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 + (layer->Manager()->GetUserData(&gLayerManagerUserData)); + LayerManagerData* lmd = static_cast + (tempManager->GetUserData(&gLayerManagerUserData)); + lmd->mParent = parentLmd; +#endif + layerBuilder->StoreDataForFrame(aItem, tmpLayer, LAYER_ACTIVE); + } + + tempManager->SetRoot(tmpLayer); + layerBuilder->WillEndTransaction(); + tempManager->AbortTransaction(); + + nsIntPoint offset = GetLastPaintOffset(layer) - GetTranslationForThebesLayer(layer); + props->MoveBy(-offset); + 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 + if (hasClip) { + invalid.And(invalid, intClip); + } + + invalid.ScaleRoundOut(thebesData->mXScale, thebesData->mYScale); + InvalidatePostTransformRegion(layer, invalid, + GetTranslationForThebesLayer(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->UpdateContents(aLayer, aState, mContainerLayerGeneration, aItem); + } + return oldData; + } + + LayerManagerData* lmd = static_cast + (mRetainingManager->GetUserData(&gLayerManagerUserData)); + + nsRefPtr data = + new DisplayItemData(lmd, aItem->GetPerFrameKey(), + aLayer, aState, mContainerLayerGeneration); + + data->AddFrame(aItem->Frame()); + + nsAutoTArray mergedFrames; + aItem->GetMergedFrames(&mergedFrames); + + for (uint32_t i = 0; i < mergedFrames.Length(); ++i) { + data->AddFrame(mergedFrames[i]); + } + + 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->UpdateContents(aLayer, aState, mContainerLayerGeneration); + return; + } + + LayerManagerData* lmd = static_cast + (mRetainingManager->GetUserData(&gLayerManagerUserData)); + + nsRefPtr data = + new DisplayItemData(lmd, aDisplayItemKey, aLayer, + aState, mContainerLayerGeneration); + + data->AddFrame(aFrame); + + lmd->mDisplayItems.PutEntry(data); +} + +FrameLayerBuilder::ClippedDisplayItem::~ClippedDisplayItem() +{ + if (mInactiveLayerManager) { + BasicLayerManager* basic = static_cast(mInactiveLayerManager.get()); + basic->SetUserData(&gLayerManagerLayerBuilder, nullptr); + } +} + +void +FrameLayerBuilder::AddLayerDisplayItem(Layer* aLayer, + nsDisplayItem* aItem, + const DisplayItemClip& aClip, + LayerState aLayerState, + const nsPoint& aTopLeft, + BasicLayerManager* aManager, + nsAutoPtr aGeometry) +{ + if (aLayer->Manager() != mRetainingManager) + return; + + DisplayItemData *data = StoreDataForFrame(aItem, aLayer, aLayerState); + ThebesLayer *t = aLayer->AsThebesLayer(); + if (t) { + data->mGeometry = aGeometry; + data->mClip = aClip; + } + data->mInactiveManager = aManager; +} + +nsIntPoint +FrameLayerBuilder::GetLastPaintOffset(ThebesLayer* aLayer) +{ + ThebesLayerItemsEntry* entry = mThebesLayerItems.PutEntry(aLayer); + if (entry) { + if (entry->mContainerLayerGeneration == 0) { + entry->mContainerLayerGeneration = mContainerLayerGeneration; + } + if (entry->mHasExplicitLastPaintOffset) + return entry->mLastPaintOffset; + } + return GetTranslationForThebesLayer(aLayer); +} + +void +FrameLayerBuilder::SaveLastPaintOffset(ThebesLayer* aLayer) +{ + ThebesLayerItemsEntry* entry = mThebesLayerItems.PutEntry(aLayer); + if (entry) { + if (entry->mContainerLayerGeneration == 0) { + entry->mContainerLayerGeneration = mContainerLayerGeneration; + } + entry->mLastPaintOffset = GetTranslationForThebesLayer(aLayer); + entry->mHasExplicitLastPaintOffset = true; + } +} + +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 layer in layer tree; could not be recycled."); + if (layer->HasUserData(&gThebesDisplayItemLayerUserData)) { + NS_ASSERTION(layer->AsThebesLayer(), "Wrong layer type"); + mRecycledThebesLayers.AppendElement(static_cast(layer)); + } + + if (Layer* maskLayer = layer->GetMaskLayer()) { + NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE, + "Could not recycle mask layer, unsupported layer type."); + mRecycledMaskImageLayers.Put(layer, static_cast(maskLayer)); + } + } +} + +void +ContainerState::Finish(uint32_t* aTextContentFlags, LayerManagerData* aData) +{ + while (!mThebesLayerDataStack.IsEmpty()) { + PopThebesLayerData(); + } + + uint32_t textContentFlags = 0; + + // Make sure that current/existing layers are added to the parent and are + // in the correct order. + Layer* layer = nullptr; + for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i) { + Layer* prevChild = i == 0 ? nullptr : mNewChildLayers[i - 1].get(); + layer = mNewChildLayers[i]; + + if (!layer->GetVisibleRegion().IsEmpty()) { + textContentFlags |= layer->GetContentFlags() & Layer::CONTENT_COMPONENT_ALPHA; + } + + if (!layer->GetParent()) { + // This is not currently a child of the container, so just add it + // now. + mContainerLayer->InsertAfter(layer, prevChild); + continue; + } + + 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 bool +ChooseScaleAndSetTransform(FrameLayerBuilder* aLayerBuilder, + nsDisplayListBuilder* aDisplayListBuilder, + nsIFrame* aContainerFrame, + const gfx3DMatrix* aTransform, + const ContainerLayerParameters& aIncomingScale, + ContainerLayer* aLayer, + LayerState aState, + ContainerLayerParameters& aOutgoingScale) +{ + nsIntPoint offset; + + gfx3DMatrix transform = + gfx3DMatrix::ScalingMatrix(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. + transform.NudgeToIntegers(); + } + gfxMatrix 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 = transform * gfx3DMatrix::Translation(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) { + // If the container's transform is animated off main thread, fix a suitable scale size + // for animation + if (aContainerFrame->GetContent() && + nsLayoutUtils::HasAnimationsForCompositor( + aContainerFrame->GetContent(), eCSSProperty_transform)) { + scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(aContainerFrame->GetContent()); + } else { + // Scale factors are normalized to a power of 2 to reduce the number of resolution changes + scale = RoundToFloatPrecision(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. + gfxMatrix frameTransform; + if (ActiveLayerTracker::IsStyleAnimated(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); + } + } else { + scale = gfxSize(1.0, 1.0); + } + + // Store the inverse of our resolution-scale on the layer + Matrix4x4 baseTransform; + ToMatrix4x4(transform, baseTransform); + aLayer->SetBaseTransform(baseTransform); + 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(aContainerFrame, eCSSProperty_transform)) { + aOutgoingScale.mInActiveTransformedSubtree = true; + } + } + bool isRetained = aLayer->Manager()->IsWidgetLayerManager(); + if (isRetained && (!canDraw2D || transform2d.HasNonIntegerTranslation())) { + aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true; + } + return true; +} + +/* static */ PLDHashOperator +FrameLayerBuilder::RestoreDisplayItemData(nsRefPtrHashKey* aEntry, void* aUserArg) +{ + DisplayItemData* data = aEntry->GetKey(); + uint32_t *generation = static_cast(aUserArg); + + if (data->mUsed && data->mContainerLayerGeneration >= *generation) { + return PL_DHASH_REMOVE; + } + + return PL_DHASH_NEXT; +} + +/* static */ PLDHashOperator +FrameLayerBuilder::RestoreThebesLayerItemEntries(ThebesLayerItemsEntry* aEntry, void* aUserArg) +{ + uint32_t *generation = static_cast(aUserArg); + + if (aEntry->mContainerLayerGeneration >= *generation) { + // 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. + return PL_DHASH_REMOVE; + } + + for (uint32_t i = 0; i < aEntry->mItems.Length(); i++) { + if (aEntry->mItems[i].mContainerLayerGeneration >= *generation) { + aEntry->mItems.TruncateLength(i); + return PL_DHASH_NEXT; + } + } + + return PL_DHASH_NEXT; +} + +already_AddRefed +FrameLayerBuilder::BuildContainerLayerFor(nsDisplayListBuilder* aBuilder, + LayerManager* aManager, + nsIFrame* aContainerFrame, + nsDisplayItem* aContainerItem, + const nsDisplayList& aChildren, + const ContainerLayerParameters& aParameters, + const gfx3DMatrix* 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; + } + + nsRefPtr 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(&gThebesDisplayItemLayerUserData)) { + // The old layer for this item is actually our ThebesLayer + // because we rendered its layer into that ThebesLayer. So we + // don't actually have a retained container layer. + } else { + NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER, + "Wrong layer type"); + containerLayer = static_cast(oldLayer); + containerLayer->SetMaskLayer(nullptr); + } + } + } + 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 thebes 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(); + } + + ContainerLayerParameters scaleParameters; + if (!ChooseScaleAndSetTransform(this, aBuilder, aContainerFrame, aTransform, aParameters, + containerLayer, state, scaleParameters)) { + return nullptr; + } + + uint32_t oldGeneration = mContainerLayerGeneration; + mContainerLayerGeneration = ++mMaxContainerLayerGeneration; + + nsRefPtr thebesLayerInvalidRegion = nullptr; + if (mRetainingManager) { + if (aContainerItem) { + StoreDataForFrame(aContainerItem, containerLayer, LAYER_ACTIVE); + } else { + StoreDataForFrame(aContainerFrame, containerDisplayItemKey, containerLayer, LAYER_ACTIVE); + } + } + + LayerManagerData* data = static_cast + (aManager->GetUserData(&gLayerManagerUserData)); + + nsRect bounds; + nsIntRect pixBounds; + int32_t appUnitsPerDevPixel; + uint32_t stateFlags = 0; + if ((aContainerFrame->GetStateBits() & NS_FRAME_NO_COMPONENT_ALPHA) && + mRetainingManager && !mRetainingManager->AreComponentAlphaLayersEnabled()) { + stateFlags = ContainerState::NO_COMPONENT_ALPHA; + } + uint32_t flags; + while (true) { + ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(), + aContainerFrame, aContainerItem, + containerLayer, scaleParameters); + + state.ProcessDisplayItems(aChildren, stateFlags); + + // 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. + state.Finish(&flags, data); + bounds = state.GetChildrenBounds(); + pixBounds = state.ScaleToOutsidePixels(bounds, false); + appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel(); + + if ((flags & Layer::CONTENT_COMPONENT_ALPHA) && + mRetainingManager && + !mRetainingManager->AreComponentAlphaLayersEnabled() && + !stateFlags) { + // 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. + stateFlags = ContainerState::NO_COMPONENT_ALPHA; + data->mDisplayItems.EnumerateEntries(RestoreDisplayItemData, + &mContainerLayerGeneration); + mThebesLayerItems.EnumerateEntries(RestoreThebesLayerItemEntries, + &mContainerLayerGeneration); + aContainerFrame->AddStateBits(NS_FRAME_NO_COMPONENT_ALPHA); + continue; + } + break; + } + + NS_ASSERTION(bounds.IsEqualInterior(aChildren.GetBounds(aBuilder)), "Wrong bounds"); + pixBounds.MoveBy(nsIntPoint(scaleParameters.mOffset.x, scaleParameters.mOffset.y)); + if (aParameters.mAncestorClipRect && !(aFlags & CONTAINER_NOT_CLIPPED_BY_ANCESTORS)) { + SetVisibleRegionForLayer(containerLayer, nsIntRegion(pixBounds), + *aParameters.mAncestorClipRect); + } else { + containerLayer->SetVisibleRegion(pixBounds); + } + // 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(pixBounds.ToAppUnits(appUnitsPerDevPixel))) { + // Clear CONTENT_COMPONENT_ALPHA + flags = Layer::CONTENT_OPAQUE; + } + } + containerLayer->SetContentFlags(flags); + + 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(&gThebesDisplayItemLayerUserData)) { + // 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; + } + layer->SetMaskLayer(nullptr); + return layer; +} + +/* static */ void +FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager) +{ + LayerManagerData* data = static_cast + (aManager->GetUserData(&gLayerManagerUserData)); + if (data) { + data->mInvalidateAllLayers = true; + } +} + +/* static */ void +FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame *aFrame) +{ + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (array) { + for (uint32_t i = 0; i < array->Length(); i++) { + 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 + + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (array) { + for (uint32_t i = 0; i < array->Length(); i++) { + DisplayItemData *element = 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(&gThebesDisplayItemLayerUserData)) { + return layer; + } + } + } + } + return nullptr; +} + +static gfxSize +PredictScaleForContent(nsIFrame* aFrame, nsIFrame* aAncestorWithScale, + const gfxSize& aScale) +{ + gfx3DMatrix transform = + gfx3DMatrix::ScalingMatrix(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; + } + gfxMatrix transform2d; + if (transform.CanDraw2D(&transform2d)) { + return transform2d.ScaleFactors(true); + } + return gfxSize(1.0, 1.0); +} + +gfxSize +FrameLayerBuilder::GetThebesLayerScaleForFrame(nsIFrame* aFrame) +{ + nsIFrame* last; + 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; + } + + nsTArray *array = + reinterpret_cast*>(aFrame->Properties().Get(LayerManagerDataProperty())); + if (!array) { + continue; + } + + for (uint32_t i = 0; i < array->Length(); i++) { + Layer* layer = array->ElementAt(i)->mLayer; + ContainerLayer* container = layer->AsContainerLayer(); + if (!container || + !layer->Manager()->IsWidgetLayerManager()) { + continue; + } + for (Layer* l = container->GetFirstChild(); l; l = l->GetNextSibling()) { + ThebesDisplayItemLayerUserData* data = + static_cast + (l->GetUserData(&gThebesDisplayItemLayerUserData)); + if (data) { + return PredictScaleForContent(aFrame, f, gfxSize(data->mXScale, data->mYScale)); + } + } + } + } + + return PredictScaleForContent(aFrame, last, + last->PresContext()->PresShell()->GetResolution()); +} + +#ifdef MOZ_DUMP_PAINTING +static void DebugPaintItem(nsRenderingContext* aDest, nsDisplayItem *aItem, nsDisplayListBuilder* aBuilder) +{ + bool snap; + nsRect appUnitBounds = aItem->GetBounds(aBuilder, &snap); + gfxRect bounds(appUnitBounds.x, appUnitBounds.y, appUnitBounds.width, appUnitBounds.height); + bounds.ScaleInverse(aDest->AppUnitsPerDevPixel()); + + nsRefPtr surf = + gfxPlatform::GetPlatform()->CreateOffscreenSurface(IntSize(bounds.width, bounds.height), + gfxContentType::COLOR_ALPHA); + surf->SetDeviceOffset(-bounds.TopLeft()); + nsRefPtr context = new gfxContext(surf); + nsRefPtr ctx = new nsRenderingContext(); + ctx->Init(aDest->DeviceContext(), context); + + aItem->Paint(aBuilder, ctx); + DumpPaintedImage(aItem, surf); + aItem->SetPainted(); + + surf->SetDeviceOffset(gfxPoint(0, 0)); + aDest->ThebesContext()->SetSource(surf, bounds.TopLeft()); + aDest->ThebesContext()->Rectangle(bounds); + aDest->ThebesContext()->Fill(); +} +#endif + +/* static */ void +FrameLayerBuilder::RecomputeVisibilityForItems(nsTArray& aItems, + nsDisplayListBuilder *aBuilder, + const nsIntRegion& aRegionToDraw, + const nsIntPoint& aOffset, + int32_t aAppUnitsPerDevPixel, + float aXScale, + float aYScale) +{ + uint32_t i; + // Update visible regions. We need perform visibility analysis again + // because we may be asked to draw into part of a ThebesLayer that + // isn't actually visible in the window (e.g., because a ThebesLayer + // expanded its visible region to a rectangle internally), in which + // case the mVisibleRect stored in the display item may be wrong. + 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 thebes layer should contain items only at the same zoom"); + + NS_ABORT_IF_FALSE(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& aItems, + const nsIntRect& aRect, + gfxContext *aContext, + nsRenderingContext *aRC, + nsDisplayListBuilder* aBuilder, + nsPresContext* aPresContext, + const nsIntPoint& aOffset, + float aXScale, float aYScale, + int32_t aCommonClipCount) +{ + int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel(); + nsRect boundRect = aRect.ToAppUnits(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; + + // 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) { + PaintInactiveLayer(aBuilder, cdi->mInactiveLayerManager, cdi->mItem, aContext, aRC); + } else { + nsIFrame* frame = cdi->mItem->Frame(); + frame->AddStateBits(NS_FRAME_PAINTED_THEBES); +#ifdef MOZ_DUMP_PAINTING + + if (gfxUtils::sDumpPainting) { + DebugPaintItem(aRC, 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(gfxContext* aContext, DrawRegionClip aClip) +{ + if (!gfxPrefs::LayoutPaintRectsSeparately() || + aContext->IsCairo() || + aClip == DrawRegionClip::CLIP_NONE) { + return false; + } + + DrawTarget *dt = aContext->GetDrawTarget(); + return dt->GetType() == BackendType::DIRECT2D; +} + +static void DrawForcedBackgroundColor(gfxContext* aContext, Layer* aLayer, nscolor aBackgroundColor) +{ + if (NS_GET_A(aBackgroundColor) > 0) { + nsIntRect r = aLayer->GetVisibleRegion().GetBounds(); + aContext->NewPath(); + aContext->Rectangle(gfxRect(r.x, r.y, r.width, r.height)); + aContext->SetColor(gfxRGBA(aBackgroundColor)); + aContext->Fill(); + } +} + +/* + * A note on residual transforms: + * + * In a transformed subtree we sometimes apply the ThebesLayer's + * "residual transform" when drawing content into the ThebesLayer. + * 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 ThebesLayer 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 ThebesLayer 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::DrawThebesLayer(ThebesLayer* aLayer, + gfxContext* aContext, + const nsIntRegion& aRegionToDraw, + DrawRegionClip aClip, + const nsIntRegion& aRegionToInvalidate, + void* aCallbackData) +{ + PROFILER_LABEL("gfx", "DrawThebesLayer"); + + nsDisplayListBuilder* builder = static_cast + (aCallbackData); + + FrameLayerBuilder *layerBuilder = aLayer->Manager()->GetLayerBuilder(); + NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!"); + + if (layerBuilder->CheckDOMModified()) + return; + + ThebesLayerItemsEntry* entry = layerBuilder->mThebesLayerItems.GetEntry(aLayer); + NS_ASSERTION(entry, "We shouldn't be drawing into a layer with no items!"); + if (!entry->mContainerLayerFrame) { + return; + } + + + ThebesDisplayItemLayerUserData* userData = + static_cast + (aLayer->GetUserData(&gThebesDisplayItemLayerUserData)); + NS_ASSERTION(userData, "where did our user data go?"); + + bool shouldDrawRectsSeparately = ShouldDrawRectsSeparately(aContext, aClip); + + if (!shouldDrawRectsSeparately) { + if (aClip == DrawRegionClip::DRAW_SNAPPED) { + gfxUtils::ClipToRegionSnapped(aContext, aRegionToDraw); + } else if (aClip == DrawRegionClip::DRAW) { + gfxUtils::ClipToRegion(aContext, aRegionToDraw); + } + + DrawForcedBackgroundColor(aContext, aLayer, userData->mForcedBackgroundColor); + } + + // make the origin of the context coincide with the origin of the + // ThebesLayer + gfxContextMatrixAutoSaveRestore saveMatrix(aContext); + nsIntPoint offset = GetTranslationForThebesLayer(aLayer); + + nsPresContext* presContext = entry->mContainerLayerFrame->PresContext(); + int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); + + RecomputeVisibilityForItems(entry->mItems, builder, aRegionToDraw, + offset, appUnitsPerDevPixel, + userData->mXScale, userData->mYScale); + + nsRefPtr rc = new nsRenderingContext(); + rc->Init(presContext->DeviceContext(), aContext); + + if (shouldDrawRectsSeparately) { + nsIntRegionRectIterator it(aRegionToDraw); + while (const nsIntRect* iterRect = it.Next()) { + gfxContextAutoSaveRestore save(aContext); + aContext->NewPath(); + aContext->Rectangle(*iterRect, aClip == DrawRegionClip::DRAW_SNAPPED); + aContext->Clip(); + + DrawForcedBackgroundColor(aContext, aLayer, 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->Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)); + aContext->Scale(userData->mXScale, userData->mYScale); + + layerBuilder->PaintItems(entry->mItems, *iterRect, aContext, rc, + builder, presContext, + offset, userData->mXScale, userData->mYScale, + entry->mCommonClipCount); + } + } 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->Translate(aLayer->GetResidualTranslation() - gfxPoint(offset.x, offset.y)); + aContext->Scale(userData->mXScale, userData->mYScale); + + layerBuilder->PaintItems(entry->mItems, aRegionToDraw.GetBounds(), aContext, rc, + builder, presContext, + offset, userData->mXScale, userData->mYScale, + entry->mCommonClipCount); + } + + if (presContext->GetPaintFlashing()) { + FlashPaint(aContext); + } + + 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; +} + +#ifdef MOZ_DUMP_PAINTING +/* static */ void +FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager, FILE* aFile, bool aDumpHtml) +{ + aManager->Dump(aFile, "", aDumpHtml); +} +#endif + +gfx::Rect +CalculateBounds(const nsTArray& 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(ThebesDisplayItemLayerUserData* aThebesData, + uint32_t aClipCount) +{ + if (aThebesData) { + aThebesData->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. + ThebesDisplayItemLayerUserData* thebesData = GetThebesDisplayItemLayerUserData(aLayer); + if (thebesData && + aRoundedRectClipCount < thebesData->mMaskClipCount) { + ThebesLayer* thebes = aLayer->AsThebesLayer(); + thebes->InvalidateRegion(thebes->GetValidRegion().GetBounds()); + } + + // don't build an unnecessary mask + nsIntRect layerBounds = aLayer->GetVisibleRegion().GetBounds(); + if (aClip.GetRoundedRectCount() == 0 || + aRoundedRectClipCount == 0 || + layerBounds.IsEmpty()) { + SetClipCount(thebesData, 0); + return; + } + + // check if we can re-use the mask layer + nsRefPtr maskLayer = CreateOrRecycleMaskImageLayerFor(aLayer); + MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer); + + MaskLayerUserData newData; + aClip.AppendRoundedRects(&newData.mRoundedClipRects, aRoundedRectClipCount); + newData.mScaleX = mParameters.mXScale; + newData.mScaleY = mParameters.mYScale; + newData.mOffset = mParameters.mOffset; + newData.mAppUnitsPerDevPixel = mContainerFrame->PresContext()->AppUnitsPerDevPixel(); + + if (*userData == newData) { + aLayer->SetMaskLayer(maskLayer); + SetClipCount(thebesData, aRoundedRectClipCount); + return; + } + + // 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"); + gfx::Size surfaceSize(std::min(boundingRect.Width(), maxSize), + std::min(boundingRect.Height(), maxSize)); + + // 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; + maskTransform.Scale(surfaceSize.width/boundingRect.Width(), + surfaceSize.height/boundingRect.Height()); + gfx::Point p = boundingRect.TopLeft(); + maskTransform.Translate(-p.x, -p.y); + // imageTransform is only used when the clip is painted to the mask + gfx::Matrix imageTransform = maskTransform; + imageTransform.Scale(mParameters.mXScale, mParameters.mYScale); + + nsAutoPtr 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); + } + + const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey; + + // check to see if we can reuse a mask image + nsRefPtr container = + GetMaskLayerImageCache()->FindImageFor(&lookupKey); + + if (!container) { + IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width), + NSToIntCeil(surfaceSize.height)); + // no existing mask image, so build a new one + RefPtr dt = + aLayer->Manager()->CreateOptimalMaskDrawTarget(surfaceSizeInt); + + // fail if we can't get the right surface + if (!dt) { + NS_WARNING("Could not create DrawTarget for mask layer."); + SetClipCount(thebesData, 0); + return; + } + + nsRefPtr context = new gfxContext(dt); + context->Multiply(ThebesMatrix(imageTransform)); + + // paint the clipping rects with alpha to create the mask + context->SetColor(gfxRGBA(1, 1, 1, 1)); + aClip.DrawRoundedRectsTo(context, + newData.mAppUnitsPerDevPixel, + 0, + aRoundedRectClipCount); + + RefPtr surface = dt->Snapshot(); + + // build the image and container + container = aLayer->Manager()->CreateImageContainer(); + NS_ASSERTION(container, "Could not create image container for mask layer."); + nsRefPtr image = container->CreateImage(ImageFormat::CAIRO_SURFACE); + NS_ASSERTION(image, "Could not create image container for mask layer."); + CairoImage::Data data; + data.mSize = surfaceSizeInt; + data.mSourceSurface = surface; + + static_cast(image.get())->SetData(data); + container->SetCurrentImageInTransaction(image); + + GetMaskLayerImageCache()->PutImage(newKey.forget(), container); + } + + maskLayer->SetContainer(container); + + maskTransform.Invert(); + Matrix4x4 matrix = Matrix4x4::From2D(maskTransform); + matrix.Translate(mParameters.mOffset.x, mParameters.mOffset.y, 0); + maskLayer->SetBaseTransform(matrix); + + // save the details of the clip in user data + userData->mScaleX = newData.mScaleX; + userData->mScaleY = newData.mScaleY; + userData->mOffset = newData.mOffset; + userData->mAppUnitsPerDevPixel = newData.mAppUnitsPerDevPixel; + userData->mRoundedClipRects.SwapElements(newData.mRoundedClipRects); + userData->mImageKey = lookupKey; + + aLayer->SetMaskLayer(maskLayer); + SetClipCount(thebesData, aRoundedRectClipCount); + return; +} + +} // namespace mozilla