gecko-dev/layout/painting/FrameLayerBuilder.cpp

7517 строки
278 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "FrameLayerBuilder.h"
#include <algorithm>
#include <deque>
#include <functional>
#include <utility>
#include "ActiveLayerTracker.h"
#include "BasicLayers.h"
#include "ImageContainer.h"
#include "ImageLayers.h"
#include "LayerTreeInvalidation.h"
#include "LayerUserData.h"
#include "Layers.h"
#include "MaskLayerImageCache.h"
#include "MatrixStack.h"
#include "TransformClipNode.h"
#include "UnitTransforms.h"
#include "Units.h"
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxEnv.h"
#include "gfxUtils.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/DisplayPortUtils.h"
#include "mozilla/EffectCompositor.h"
#include "mozilla/LayerAnimationInfo.h"
#include "mozilla/LayerTimelineMarker.h"
#include "mozilla/LookAndFeel.h"
#include "mozilla/Maybe.h"
#include "mozilla/PerfStats.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ReverseIterator.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layers.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/SVGIntegrationUtils.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/EffectsInfo.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/dom/RemoteBrowser.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/Matrix.h"
#include "mozilla/gfx/Tools.h"
#include "mozilla/layers/ShadowLayers.h"
#include "mozilla/layers/TextureClient.h"
#include "mozilla/layers/TextureWrapperImage.h"
#include "mozilla/layers/WebRenderUserData.h"
#include "nsDisplayList.h"
#include "nsDocShell.h"
#include "nsIScrollableFrame.h"
#include "nsImageFrame.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsPrintfCString.h"
#include "nsSubDocumentFrame.h"
#include "nsTransitionManager.h"
#include "nsTHashMap.h"
using namespace mozilla::layers;
using namespace mozilla::gfx;
using mozilla::UniquePtr;
using mozilla::WrapUnique;
// PaintedLayerData::mAssignedDisplayItems is a std::vector, which is
// non-memmovable
MOZ_DECLARE_RELOCATE_USING_MOVE_CONSTRUCTOR(mozilla::PaintedLayerData);
namespace mozilla {
class PaintedDisplayItemLayerUserData;
static nsTHashSet<DisplayItemData*>* sAliveDisplayItemDatas;
// DO NOT MODIFY THE ARRAY RETURNED BY THIS FUNCTION.
// It might be the static empty array.
SmallPointerArray<DisplayItemData>& GetDisplayItemDataArray(
const nsIFrame* aFrame) {
static SmallPointerArray<DisplayItemData> sEmpty;
MOZ_ASSERT(sEmpty.IsEmpty());
auto* array = aFrame->DisplayItemData();
return array ? *array : sEmpty;
}
/**
* The address of gPaintedDisplayItemLayerUserData is used as the user
* data key for PaintedLayers created by FrameLayerBuilder.
* It identifies PaintedLayers used to draw non-layer content, which are
* therefore eligible for recycling. We want display items to be able to
* create their own dedicated PaintedLayers in BuildLayer, if necessary,
* and we wouldn't want to accidentally recycle those.
* The user data is a PaintedDisplayItemLayerUserData.
*/
uint8_t gPaintedDisplayItemLayerUserData;
/**
* The address of gColorLayerUserData is used as the user
* data key for ColorLayers created by FrameLayerBuilder.
* The user data is null.
*/
uint8_t gColorLayerUserData;
/**
* The address of gImageLayerUserData is used as the user
* data key for ImageLayers created by FrameLayerBuilder.
* The user data is null.
*/
uint8_t gImageLayerUserData;
/**
* The address of gLayerManagerUserData is used as the user
* data key for retained LayerManagers managed by FrameLayerBuilder.
* The user data is a LayerManagerData.
*/
uint8_t gLayerManagerUserData;
/**
* The address of gMaskLayerUserData is used as the user
* data key for mask layers managed by FrameLayerBuilder.
* The user data is a MaskLayerUserData.
*/
uint8_t gMaskLayerUserData;
/**
* The address of gCSSMaskLayerUserData is used as the user
* data key for mask layers of css masking managed by FrameLayerBuilder.
* The user data is a CSSMaskLayerUserData.
*/
uint8_t gCSSMaskLayerUserData;
// a global cache of image containers used for mask layers
static MaskLayerImageCache* gMaskLayerImageCache = nullptr;
static inline MaskLayerImageCache* GetMaskLayerImageCache() {
if (!gMaskLayerImageCache) {
gMaskLayerImageCache = new MaskLayerImageCache();
}
return gMaskLayerImageCache;
}
struct InactiveLayerData {
RefPtr<layers::BasicLayerManager> mLayerManager;
RefPtr<layers::Layer> mLayer;
UniquePtr<layers::LayerProperties> mProps;
~InactiveLayerData();
};
struct AssignedDisplayItem {
AssignedDisplayItem(nsPaintedDisplayItem* aItem, LayerState aLayerState,
DisplayItemData* aData, const nsRect& aContentRect,
DisplayItemEntryType aType, const bool aHasOpacity,
const RefPtr<TransformClipNode>& aTransform,
const bool aIsMerged);
AssignedDisplayItem(AssignedDisplayItem&& aRhs) = default;
bool HasOpacity() const { return mHasOpacity; }
bool HasTransform() const { return mTransform; }
nsPaintedDisplayItem* mItem;
DisplayItemData* mDisplayItemData;
/**
* If the display item is being rendered as an inactive
* layer, then this stores the layer manager being
* used for the inactive transaction.
*/
UniquePtr<InactiveLayerData> mInactiveLayerData;
RefPtr<TransformClipNode> mTransform;
nsRect mContentRect;
LayerState mLayerState;
DisplayItemEntryType mType;
bool mReused;
bool mMerged;
bool mHasOpacity;
bool mHasPaintRect;
};
struct DisplayItemEntry {
DisplayItemEntry(nsDisplayItem* aItem, DisplayItemEntryType aType)
: mItem(aItem), mType(aType) {}
nsDisplayItem* mItem;
DisplayItemEntryType mType;
};
/**
* Returns true if the given |aType| is an effect start marker.
*/
static bool IsEffectStartMarker(DisplayItemEntryType aType) {
return aType == DisplayItemEntryType::PushOpacity ||
aType == DisplayItemEntryType::PushOpacityWithBg ||
aType == DisplayItemEntryType::PushTransform;
}
/**
* Returns true if the given |aType| is an effect end marker.
*/
static bool IsEffectEndMarker(DisplayItemEntryType aType) {
return aType == DisplayItemEntryType::PopOpacity ||
aType == DisplayItemEntryType::PopTransform;
}
enum class MarkerType { StartMarker, EndMarker };
/**
* Returns true if the given nsDisplayOpacity |aItem| has had opacity applied
* to its children and can be flattened away.
*/
static bool IsOpacityAppliedToChildren(nsDisplayItem* aItem) {
MOZ_ASSERT(aItem->GetType() == DisplayItemType::TYPE_OPACITY);
return static_cast<nsDisplayOpacity*>(aItem)->OpacityAppliedToChildren();
}
/**
* Returns true if the given display item type supports flattening with markers.
*/
static bool SupportsFlatteningWithMarkers(const DisplayItemType& aType) {
return aType == DisplayItemType::TYPE_OPACITY ||
aType == DisplayItemType::TYPE_TRANSFORM;
}
/**
* Adds the effect marker to |aMarkers| based on the type of |aItem| and whether
* |markerType| is a start or end marker.
*/
template <MarkerType markerType>
static bool AddMarkerIfNeeded(nsDisplayItem* aItem,
std::deque<DisplayItemEntry>& aMarkers) {
const DisplayItemType type = aItem->GetType();
if (!SupportsFlatteningWithMarkers(type)) {
return false;
}
DisplayItemEntryType marker;
// Just a fancy way to avoid writing two separate functions to select between
// PUSH and POP markers. This is done during compile time based on |markerType|.
#define GET_MARKER(start_marker, end_marker) \
std::conditional< \
markerType == MarkerType::StartMarker, \
std::integral_constant<DisplayItemEntryType, start_marker>, \
std::integral_constant<DisplayItemEntryType, end_marker>>::type::value;
switch (type) {
case DisplayItemType::TYPE_OPACITY:
if (IsOpacityAppliedToChildren(aItem)) {
// TODO(miko): I am not a fan of this. The more correct solution would
// be to return an enum from nsDisplayItem::ShouldFlattenAway(), so that
// we could distinguish between different flattening methods and avoid
// entering this function when markers are not needed.
return false;
}
marker = GET_MARKER(DisplayItemEntryType::PushOpacity,
DisplayItemEntryType::PopOpacity);
break;
case DisplayItemType::TYPE_TRANSFORM:
marker = GET_MARKER(DisplayItemEntryType::PushTransform,
DisplayItemEntryType::PopTransform);
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid display item type!");
break;
}
aMarkers.emplace_back(aItem, marker);
return true;
}
DisplayItemData::DisplayItemData(LayerManagerData* aParent, uint32_t aKey,
Layer* aLayer, nsIFrame* aFrame)
: mRefCnt(0),
mParent(aParent),
mLayer(aLayer),
mDisplayItemKey(aKey),
mItem(nullptr),
mUsed(true),
mIsInvalid(false),
mReusedItem(false) {
MOZ_COUNT_CTOR(DisplayItemData);
if (!sAliveDisplayItemDatas) {
sAliveDisplayItemDatas = new nsTHashSet<DisplayItemData*>();
}
MOZ_RELEASE_ASSERT(!sAliveDisplayItemDatas->Contains(this));
sAliveDisplayItemDatas->Insert(this);
MOZ_RELEASE_ASSERT(mLayer);
if (aFrame) {
AddFrame(aFrame);
}
}
void DisplayItemData::Destroy() {
// Get the pres context.
RefPtr<nsPresContext> presContext = mFrameList[0]->PresContext();
// Call our destructor.
this->~DisplayItemData();
// Don't let the memory be freed, since it will be recycled
// instead. Don't call the global operator delete.
presContext->PresShell()->FreeByObjectID(eArenaObjectID_DisplayItemData,
this);
}
void DisplayItemData::AddFrame(nsIFrame* aFrame) {
MOZ_RELEASE_ASSERT(mLayer);
MOZ_RELEASE_ASSERT(!mFrameList.Contains(aFrame));
mFrameList.AppendElement(aFrame);
SmallPointerArray<DisplayItemData>* array = aFrame->DisplayItemData();
if (!array) {
array = new SmallPointerArray<DisplayItemData>();
aFrame->SetProperty(nsIFrame::DisplayItemDataProperty(), array);
}
array->AppendElement(this);
}
void DisplayItemData::RemoveFrame(nsIFrame* aFrame) {
MOZ_RELEASE_ASSERT(mLayer);
bool result = mFrameList.RemoveElement(aFrame);
MOZ_RELEASE_ASSERT(result, "Can't remove a frame that wasn't added!");
SmallPointerArray<DisplayItemData>* array = aFrame->DisplayItemData();
if (array) {
array->RemoveElement(this);
}
}
void DisplayItemData::EndUpdate() {
MOZ_RELEASE_ASSERT(mLayer);
mIsInvalid = false;
mUsed = false;
mReusedItem = false;
mOldTransform = nullptr;
}
void DisplayItemData::EndUpdate(UniquePtr<nsDisplayItemGeometry>&& aGeometry) {
MOZ_RELEASE_ASSERT(mLayer);
MOZ_ASSERT(mItem);
MOZ_ASSERT(mGeometry || aGeometry);
if (aGeometry) {
mGeometry = std::move(aGeometry);
}
mClip = mItem->GetClip();
mChangedFrameInvalidations.SetEmpty();
EndUpdate();
}
void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
bool aFirstUpdate,
nsPaintedDisplayItem* aItem /* = nullptr */) {
bool isReused = false;
bool isMerged = false;
if (aItem) {
isReused = !aFirstUpdate ? aItem->IsReused() : false;
const nsDisplayWrapList* wraplist = aItem->AsDisplayWrapList();
isMerged = wraplist && wraplist->HasMergedFrames();
}
BeginUpdate(aLayer, aState, aItem, isReused, isMerged);
}
void DisplayItemData::BeginUpdate(Layer* aLayer, LayerState aState,
nsPaintedDisplayItem* aItem, bool aIsReused,
bool aIsMerged) {
MOZ_RELEASE_ASSERT(mLayer);
MOZ_RELEASE_ASSERT(aLayer);
mLayer = aLayer;
mOptLayer = nullptr;
mInactiveManager = nullptr;
mLayerState = aState;
mUsed = true;
mItem = aItem;
mReusedItem = aIsReused;
if (!aItem) {
return;
}
if (!aIsMerged && mFrameList.Length() == 1) {
MOZ_ASSERT(mFrameList[0] == aItem->Frame());
return;
}
// We avoid adding or removing element unnecessarily
// since we have to modify userdata each time
CopyableAutoTArray<nsIFrame*, 4> copy(mFrameList);
if (!copy.RemoveElement(aItem->Frame())) {
AddFrame(aItem->Frame());
mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
aItem->Frame()->InkOverflowRect());
}
if (aIsMerged) {
MOZ_ASSERT(aItem->AsDisplayWrapList());
for (nsIFrame* frame : aItem->AsDisplayWrapList()->GetMergedFrames()) {
if (!copy.RemoveElement(frame)) {
AddFrame(frame);
mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
frame->InkOverflowRect());
}
}
}
for (nsIFrame* frame : copy) {
RemoveFrame(frame);
mChangedFrameInvalidations.Or(mChangedFrameInvalidations,
frame->InkOverflowRect());
}
}
static const nsIFrame* sDestroyedFrame = nullptr;
DisplayItemData::~DisplayItemData() {
MOZ_COUNT_DTOR(DisplayItemData);
for (nsIFrame* frame : mFrameList) {
if (frame == sDestroyedFrame) {
continue;
}
SmallPointerArray<DisplayItemData>* array = frame->DisplayItemData();
if (array) {
array->RemoveElement(this);
}
}
MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas);
const bool removed = sAliveDisplayItemDatas->EnsureRemoved(this);
MOZ_RELEASE_ASSERT(removed);
if (sAliveDisplayItemDatas->Count() == 0) {
delete sAliveDisplayItemDatas;
sAliveDisplayItemDatas = nullptr;
}
}
void DisplayItemData::NotifyRemoved() {
if (mDisplayItemKey > static_cast<uint8_t>(DisplayItemType::TYPE_MAX)) {
// This is sort of a hack. The display item key has higher bits set, which
// means that it is not the only display item for the frame.
// This branch skips separator transforms.
return;
}
const DisplayItemType type = GetDisplayItemTypeFromKey(mDisplayItemKey);
if (type == DisplayItemType::TYPE_REMOTE) {
// TYPE_REMOTE doesn't support merging, so access it directly
MOZ_ASSERT(mFrameList.Length() == 1);
if (mFrameList.Length() != 1) {
return;
}
// This is a remote browser that is going away, notify it that it is now
// hidden
nsIFrame* frame = mFrameList[0];
nsSubDocumentFrame* subdoc = static_cast<nsSubDocumentFrame*>(frame);
nsFrameLoader* frameLoader = subdoc->FrameLoader();
if (frameLoader && frameLoader->GetRemoteBrowser()) {
frameLoader->GetRemoteBrowser()->UpdateEffects(
mozilla::dom::EffectsInfo::FullyHidden());
}
}
if (type != DisplayItemType::TYPE_TRANSFORM &&
type != DisplayItemType::TYPE_OPACITY &&
type != DisplayItemType::TYPE_BACKGROUND_COLOR) {
return;
}
for (nsIFrame* frame : mFrameList) {
EffectCompositor::ClearIsRunningOnCompositor(frame, type);
}
}
const nsRegion& DisplayItemData::GetChangedFrameInvalidations() {
return mChangedFrameInvalidations;
}
DisplayItemData* DisplayItemData::AssertDisplayItemData(
DisplayItemData* aData) {
MOZ_RELEASE_ASSERT(aData);
MOZ_RELEASE_ASSERT(sAliveDisplayItemDatas &&
sAliveDisplayItemDatas->Contains(aData));
MOZ_RELEASE_ASSERT(aData->mLayer);
return aData;
}
void* DisplayItemData::operator new(size_t sz, nsPresContext* aPresContext) {
// Check the recycle list first.
return aPresContext->PresShell()->AllocateByObjectID(
eArenaObjectID_DisplayItemData, sz);
}
/**
* This is the userdata we associate with a layer manager.
*/
class LayerManagerData : public LayerUserData {
public:
explicit LayerManagerData(LayerManager* aManager)
: mLayerManager(aManager),
#ifdef DEBUG_DISPLAY_ITEM_DATA
mParent(nullptr),
#endif
mInvalidateAllLayers(false) {
MOZ_COUNT_CTOR(LayerManagerData);
}
~LayerManagerData() override { MOZ_COUNT_DTOR(LayerManagerData); }
#ifdef DEBUG_DISPLAY_ITEM_DATA
void Dump(const char* aPrefix = "") {
printf_stderr("%sLayerManagerData %p\n", aPrefix, this);
for (auto& data : mDisplayItems) {
nsAutoCString prefix;
prefix += aPrefix;
prefix += " ";
const char* layerState;
switch (data->mLayerState) {
case LayerState::LAYER_NONE:
layerState = "LAYER_NONE";
break;
case LayerState::LAYER_INACTIVE:
layerState = "LAYER_INACTIVE";
break;
case LayerState::LAYER_ACTIVE:
layerState = "LAYER_ACTIVE";
break;
case LayerState::LAYER_ACTIVE_FORCE:
layerState = "LAYER_ACTIVE_FORCE";
break;
case LayerState::LAYER_ACTIVE_EMPTY:
layerState = "LAYER_ACTIVE_EMPTY";
break;
case LayerState::LAYER_SVG_EFFECTS:
layerState = "LAYER_SVG_EFFECTS";
break;
}
uint32_t mask = (1 << TYPE_BITS) - 1;
nsAutoCString str;
str += prefix;
str += nsPrintfCString("Frame %p ", data->mFrameList[0]);
str += nsDisplayItem::DisplayItemTypeName(
static_cast<nsDisplayItem::Type>(data->mDisplayItemKey & mask));
if ((data->mDisplayItemKey >> TYPE_BITS)) {
str += nsPrintfCString("(%i)", data->mDisplayItemKey >> TYPE_BITS);
}
str += nsPrintfCString(", %s, Layer %p", layerState, data->mLayer.get());
if (data->mOptLayer) {
str += nsPrintfCString(", OptLayer %p", data->mOptLayer.get());
}
if (data->mInactiveManager) {
str += nsPrintfCString(", InactiveLayerManager %p",
data->mInactiveManager.get());
}
str += "\n";
printf_stderr("%s", str.get());
if (data->mInactiveManager) {
prefix += " ";
printf_stderr("%sDumping inactive layer info:\n", prefix.get());
LayerManagerData* lmd = static_cast<LayerManagerData*>(
data->mInactiveManager->GetUserData(&gLayerManagerUserData));
lmd->Dump(prefix.get());
}
}
}
#endif
/**
* Tracks which frames have layers associated with them.
*/
LayerManager* mLayerManager;
#ifdef DEBUG_DISPLAY_ITEM_DATA
LayerManagerData* mParent;
#endif
std::vector<RefPtr<DisplayItemData>> mDisplayItems;
bool mInvalidateAllLayers;
};
/* static */
void FrameLayerBuilder::DestroyDisplayItemDataFor(nsIFrame* aFrame) {
RemoveFrameFromLayerManager(aFrame);
// Destroying a WebRenderUserDataTable can cause destruction of other objects
// which can remove frame properties in their destructor. If we delete a frame
// property it runs the destructor of the stored object in the middle of
// updating the frame property table, so if the destruction of that object
// causes another update to the frame property table it would leave the frame
// property table in an inconsistent state. So we remove it from the table and
// then destroy it. (bug 1530657)
WebRenderUserDataTable* userDataTable =
aFrame->TakeProperty(WebRenderUserDataProperty::Key());
if (userDataTable) {
for (const auto& data : userDataTable->Values()) {
data->RemoveFromTable();
}
delete userDataTable;
}
}
/**
* We keep a stack of these to represent the PaintedLayers that are
* currently available to have display items added to.
* We use a stack here because as much as possible we want to
* assign display items to existing PaintedLayers, and to the lowest
* PaintedLayer in z-order. This reduces the number of layers and
* makes it more likely a display item will be rendered to an opaque
* layer, giving us the best chance of getting subpixel AA.
*/
class PaintedLayerData {
public:
PaintedLayerData()
: mAnimatedGeometryRoot(nullptr),
mASR(nullptr),
mClipChain(nullptr),
mReferenceFrame(nullptr),
mLayer(nullptr),
mSolidColor(NS_RGBA(0, 0, 0, 0)),
mIsSolidColorInVisibleRegion(false),
mNeedComponentAlpha(false),
mForceTransparentSurface(false),
mHideAllLayersBelow(false),
mOpaqueForAnimatedGeometryRootParent(false),
mBackfaceHidden(false),
mDTCRequiresTargetConfirmation(false),
mImage(nullptr),
mItemClip(nullptr),
mNewChildLayersIndex(-1)
#ifdef DEBUG
,
mTransformLevel(0)
#endif
{
}
PaintedLayerData(PaintedLayerData&& aRhs) = default;
~PaintedLayerData() { MOZ_ASSERT(mTransformLevel == 0); }
#ifdef MOZ_DUMP_PAINTING
/**
* Keep track of important decisions for debugging.
*/
nsCString mLog;
# define FLB_LOG_PAINTED_LAYER_DECISION(pld, ...) \
if (StaticPrefs::layers_dump_decision()) { \
pld->mLog.AppendPrintf("\t\t\t\t"); \
pld->mLog.AppendPrintf(__VA_ARGS__); \
}
#else
# define FLB_LOG_PAINTED_LAYER_DECISION(...)
#endif
/**
* Disables component alpha for |aItem| if the component alpha bounds are not
* contained in |mOpaqueRegion|. Alternatively if possible, sets
* |mNeedComponentAlpha| to true for this PaintedLayerData.
*/
bool SetupComponentAlpha(ContainerState* aState, nsPaintedDisplayItem* aItem,
const nsIntRect& aVisibleRect,
const TransformClipNode* aTransform);
/**
* Record that an item has been added to the PaintedLayer, so we
* need to update our regions.
* @param aVisibleRect the area of the item that's visible
*/
void Accumulate(ContainerState* aState, nsDisplayItem* aItem,
const nsIntRect& aVisibleRect, const nsRect& aContentRect,
const DisplayItemClip& aClip, LayerState aLayerState,
nsDisplayList* aList, DisplayItemEntryType aType,
nsTArray<size_t>& aOpacityIndices,
const RefPtr<TransformClipNode>& aTransform);
UniquePtr<InactiveLayerData> CreateInactiveLayerData(
ContainerState* aState, nsPaintedDisplayItem* aItem,
DisplayItemData* aData);
/**
* Updates the status of |mTransform| and |aOpacityIndices|, based on |aType|.
*/
void UpdateEffectStatus(DisplayItemEntryType aType,
nsTArray<size_t>& aOpacityIndices);
AnimatedGeometryRoot* GetAnimatedGeometryRoot() {
return mAnimatedGeometryRoot;
}
/**
* A region including the horizontal pan, vertical pan, and no action regions.
*/
nsRegion CombinedTouchActionRegion();
/**
* Add the given hit test info to the hit regions for this PaintedLayer.
*/
void AccumulateHitTestItem(ContainerState* aState, nsDisplayItem* aItem,
const DisplayItemClip& aClip,
TransformClipNode* aTransform);
void HitRegionsUpdated();
/**
* If this represents only a nsDisplayImage, and the image type supports being
* optimized to an ImageLayer, returns true.
*/
bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder);
/**
* If this represents only a nsDisplayImage, and the image type supports being
* optimized to an ImageLayer, returns an ImageContainer for the underlying
* image if one is available.
*/
already_AddRefed<ImageContainer> GetContainerForImageLayer(
nsDisplayListBuilder* aBuilder);
bool VisibleAboveRegionIntersects(const nsIntRegion& aRegion) const {
return !mVisibleAboveRegion.Intersect(aRegion).IsEmpty();
}
bool VisibleRegionIntersects(const nsIntRegion& aRegion) const {
return !mVisibleRegion.Intersect(aRegion).IsEmpty();
}
/**
* The owning ContainerState that created this PaintedLayerData.
*/
ContainerState* mState;
/**
* The region of visible content in the layer, relative to the
* container layer (which is at the snapped top-left of the display
* list reference frame).
*/
nsIntRegion mVisibleRegion;
/**
* The region of visible content in the layer that is opaque.
* Same coordinate system as mVisibleRegion.
*/
nsIntRegion mOpaqueRegion;
/**
* The definitely-hit region for this PaintedLayer.
*/
nsRegion mHitRegion;
/**
* The maybe-hit region for this PaintedLayer.
*/
nsRegion mMaybeHitRegion;
/**
* The dispatch-to-content hit region for this PaintedLayer.
*/
nsRegion mDispatchToContentHitRegion;
/**
* The region for this PaintedLayer that is sensitive to events
* but disallows panning and zooming. This is an approximation
* and any deviation from the true region will be part of the
* mDispatchToContentHitRegion.
*/
nsRegion mNoActionRegion;
/**
* The region for this PaintedLayer that is sensitive to events and
* allows horizontal panning but not zooming. This is an approximation
* and any deviation from the true region will be part of the
* mDispatchToContentHitRegion.
*/
nsRegion mHorizontalPanRegion;
/**
* The region for this PaintedLayer that is sensitive to events and
* allows vertical panning but not zooming. This is an approximation
* and any deviation from the true region will be part of the
* mDispatchToContentHitRegion.
*/
nsRegion mVerticalPanRegion;
bool mCollapsedTouchActions = false;
/**
* Scaled versions of the bounds of mHitRegion and mMaybeHitRegion.
* We store these because FindPaintedLayerFor() needs to consume them
* in this form, and it's a hot code path so we don't want to scale
* them inside that function.
*/
nsIntRect mScaledHitRegionBounds;
nsIntRect mScaledMaybeHitRegionBounds;
/**
* The "active scrolled root" for all content in the layer. Must
* be non-null; all content in a PaintedLayer must have the same
* active scrolled root.
*/
AnimatedGeometryRoot* mAnimatedGeometryRoot;
const ActiveScrolledRoot* mASR;
/**
* The chain of clips that should apply to this layer.
*/
const DisplayItemClipChain* mClipChain;
/**
* The offset between mAnimatedGeometryRoot and the reference frame.
*/
nsPoint mAnimatedGeometryRootOffset;
/**
* If non-null, the frame from which we'll extract "fixed positioning"
* metadata for this layer. This can be a position:fixed frame or a viewport
* frame; the latter case is used for background-attachment:fixed content.
*/
const nsIFrame* mReferenceFrame;
PaintedLayer* mLayer;
/**
* If mIsSolidColorInVisibleRegion is true, this is the color of the visible
* region.
*/
nscolor mSolidColor;
/**
* True if every pixel in mVisibleRegion will have color mSolidColor.
*/
bool mIsSolidColorInVisibleRegion;
/**
* True if there is any text visible in the layer that's over
* transparent pixels in the layer.
*/
bool mNeedComponentAlpha;
/**
* Set if the layer should be treated as transparent, even if its entire
* area is covered by opaque display items. For example, this needs to
* be set if something is going to "punch holes" in the layer by clearing
* part of its surface.
*/
bool mForceTransparentSurface;
/**
* Set if all layers below this PaintedLayer should be hidden.
*/
bool mHideAllLayersBelow;
/**
* Set if the opaque region for this layer can be applied to the parent
* animated geometry root of this layer's animated geometry root.
* We set this when a PaintedLayer's animated geometry root is a scrollframe
* and the PaintedLayer completely fills the displayport of the scrollframe.
*/
bool mOpaqueForAnimatedGeometryRootParent;
/**
* Set if the backface of this region is hidden to the user.
* Content that backface is hidden should not be draw on the layer
* with visible backface.
*/
bool mBackfaceHidden;
/**
* Set to true if events targeting the dispatch-to-content region
* require target confirmation.
* See CompositorHitTestFlags::eRequiresTargetConfirmation and
* EventRegions::mDTCRequiresTargetConfirmation.
*/
bool mDTCRequiresTargetConfirmation;
/**
* Stores the pointer to the nsDisplayImage if we want to
* convert this to an ImageLayer.
*/
nsDisplayImageContainer* mImage;
/**
* Stores the clip that we need to apply to the image or, if there is no
* image, a clip for SOME item in the layer. There is no guarantee which
* item's clip will be stored here and mItemClip should not be used to clip
* the whole layer - only some part of the clip should be used, as determined
* by PaintedDisplayItemLayerUserData::GetCommonClipCount() - which may even
* be no part at all.
*/
const DisplayItemClip* mItemClip;
/**
* Index of this layer in mNewChildLayers.
*/
int32_t mNewChildLayersIndex;
/**
* The region of visible content above the layer and below the
* next PaintedLayerData currently in the stack, if any.
* This is a conservative approximation: it contains the true region.
*/
nsIntRegion mVisibleAboveRegion;
/**
* All the display items that have been assigned to this painted layer.
* These items get added by Accumulate().
*/
std::vector<AssignedDisplayItem> mAssignedDisplayItems;
#ifdef DEBUG
/**
* Tracks the level of transform to ensure balanced PUSH/POP markers.
*/
int mTransformLevel;
#endif
};
struct NewLayerEntry {
NewLayerEntry()
: mAnimatedGeometryRoot(nullptr),
mASR(nullptr),
mClipChain(nullptr),
mScrollMetadataASR(nullptr),
mLayerContentsVisibleRect(0, 0, -1, -1),
mLayerState(LayerState::LAYER_INACTIVE),
mHideAllLayersBelow(false),
mOpaqueForAnimatedGeometryRootParent(false),
mUntransformedVisibleRegion(false),
mIsFixedToRootScrollFrame(false) {}
// mLayer is null if the previous entry is for a PaintedLayer that hasn't
// been optimized to some other form (yet).
RefPtr<Layer> mLayer;
AnimatedGeometryRoot* mAnimatedGeometryRoot;
const ActiveScrolledRoot* mASR;
const DisplayItemClipChain* mClipChain;
const ActiveScrolledRoot* mScrollMetadataASR;
// If non-null, this ScrollMetadata is set to the be the first ScrollMetadata
// on the layer.
UniquePtr<ScrollMetadata> mBaseScrollMetadata;
// The following are only used for retained layers (for occlusion
// culling of those layers). These regions are all relative to the
// container reference frame.
nsIntRegion mVisibleRegion;
nsIntRegion mOpaqueRegion;
// This rect is in the layer's own coordinate space. The computed visible
// region for the layer cannot extend beyond this rect.
nsIntRect mLayerContentsVisibleRect;
LayerState mLayerState;
bool mHideAllLayersBelow;
// When mOpaqueForAnimatedGeometryRootParent is true, the opaque region of
// this layer is opaque in the same position even subject to the animation of
// geometry of mAnimatedGeometryRoot. For example when mAnimatedGeometryRoot
// is a scrolled frame and the scrolled content is opaque everywhere in the
// displayport, we can set this flag.
// When this flag is set, we can treat this opaque region as covering
// content whose animated geometry root is the animated geometry root for
// mAnimatedGeometryRoot->GetParent().
bool mOpaqueForAnimatedGeometryRootParent;
// mVisibleRegion is relative to the associated frame before
// transform.
bool mUntransformedVisibleRegion;
bool mIsFixedToRootScrollFrame;
};
class PaintedLayerDataTree;
/**
* This is tree node type for PaintedLayerDataTree.
* Each node corresponds to a different animated geometry root, and contains
* a stack of PaintedLayerDatas, in bottom-to-top order.
* There is at most one node per animated geometry root. The ancestor and
* descendant relations in PaintedLayerDataTree tree mirror those in the frame
* tree.
* Each node can have clip that describes the potential extents that items in
* this node can cover. If mHasClip is false, it means that the node's contents
* can move anywhere.
* Testing against the clip instead of the node's actual contents has the
* advantage that the node's contents can move or animate without affecting
* content in other nodes. So we don't need to re-layerize during animations
* (sync or async), and during async animations everything is guaranteed to
* look correct.
* The contents of a node's PaintedLayerData stack all share the node's
* animated geometry root. The child nodes are on top of the PaintedLayerData
* stack, in z-order, and the clip rects of the child nodes are allowed to
* intersect with the visible region or visible above region of their parent
* node's PaintedLayerDatas.
*/
class PaintedLayerDataNode {
public:
PaintedLayerDataNode(PaintedLayerDataTree& aTree,
PaintedLayerDataNode* aParent,
AnimatedGeometryRoot* aAnimatedGeometryRoot);
~PaintedLayerDataNode();
AnimatedGeometryRoot* GetAnimatedGeometryRoot() const {
return mAnimatedGeometryRoot;
}
/**
* Whether this node's contents can potentially intersect aRect.
* aRect is in our tree's ContainerState's coordinate space.
*/
bool Intersects(const nsIntRect& aRect) const {
return !mHasClip || mClipRect.Intersects(aRect);
}
/**
* Create a PaintedLayerDataNode for aAnimatedGeometryRoot, add it to our
* children, and return it.
*/
PaintedLayerDataNode* AddChildNodeFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot);
/**
* Find a PaintedLayerData in our mPaintedLayerDataStack that aItem can be
* added to. Creates a new PaintedLayerData by calling
* aNewPaintedLayerCallback if necessary.
*/
template <typename NewPaintedLayerCallbackType>
PaintedLayerData* FindPaintedLayerFor(
const nsIntRect& aVisibleRect, bool aBackfaceHidden,
const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
NewPaintedLayerCallbackType aNewPaintedLayerCallback);
/**
* Find an opaque background color for aRegion. Pulls a color from the parent
* geometry root if appropriate, but only if that color is present underneath
* the whole clip of this node, so that this node's contents can animate or
* move (possibly async) without having to change the background color.
* @param aUnderIndex Searching will start in mPaintedLayerDataStack right
* below aUnderIndex.
*/
enum { ABOVE_TOP = -1 };
nscolor FindOpaqueBackgroundColor(const nsIntRegion& aRegion,
int32_t aUnderIndex = ABOVE_TOP) const;
/**
* Same as FindOpaqueBackgroundColor, but only returns a color if absolutely
* nothing is in between, so that it can be used for a layer that can move
* anywhere inside our clip.
*/
nscolor FindOpaqueBackgroundColorCoveringEverything() const;
/**
* Adds aRect to this node's top PaintedLayerData's mVisibleAboveRegion,
* or mVisibleAboveBackgroundRegion if mPaintedLayerDataStack is empty.
*/
void AddToVisibleAboveRegion(const nsIntRect& aRect);
/**
* Call this if all of our existing content can potentially be covered, so
* nothing can merge with it and all new content needs to create new items
* on top. This will finish all of our children and pop our whole
* mPaintedLayerDataStack.
*/
void SetAllDrawingAbove();
/**
* Finish this node: Finish all children, finish our PaintedLayer contents,
* and (if requested) adjust our parent's visible above region to include
* our clip.
*/
void Finish(bool aParentNeedsAccurateVisibleAboveRegion);
/**
* Finish any children that intersect aRect.
*/
void FinishChildrenIntersecting(const nsIntRect& aRect);
/**
* Finish all children.
*/
void FinishAllChildren() { FinishAllChildren(true); }
protected:
/**
* Finish all items in mPaintedLayerDataStack and clear the stack.
*/
void PopAllPaintedLayerData();
/**
* Finish all of our child nodes, but don't touch mPaintedLayerDataStack.
*/
void FinishAllChildren(bool aThisNodeNeedsAccurateVisibleAboveRegion);
/**
* Pass off opaque background color searching to our parent node, if we have
* one.
*/
nscolor FindOpaqueBackgroundColorInParentNode() const;
PaintedLayerDataTree& mTree;
PaintedLayerDataNode* mParent;
AnimatedGeometryRoot* mAnimatedGeometryRoot;
/**
* Our contents: a PaintedLayerData stack and our child nodes.
*/
AutoTArray<PaintedLayerData, 3> mPaintedLayerDataStack;
/**
* UniquePtr is used here in the sense of "unique ownership", i.e. there is
* only one owner. Not in the sense of "this is the only pointer to the
* node": There are two other, non-owning, pointers to our child nodes: The
* node's respective children point to their parent node with their mParent
* pointer, and the tree keeps a map of animated geometry root to node in its
* mNodes member. These outside pointers are the reason that mChildren isn't
* just an nsTArray<PaintedLayerDataNode> (since the pointers would become
* invalid whenever the array expands its capacity).
*/
nsTArray<UniquePtr<PaintedLayerDataNode>> mChildren;
/**
* The region that's covered between our "background" and the bottom of
* mPaintedLayerDataStack. This is used to indicate whether we can pull
* a background color from our parent node. If mVisibleAboveBackgroundRegion
* should be considered infinite, mAllDrawingAboveBackground will be true and
* the value of mVisibleAboveBackgroundRegion will be meaningless.
*/
nsIntRegion mVisibleAboveBackgroundRegion;
/**
* Our clip, if we have any. If not, that means we can move anywhere, and
* mHasClip will be false and mClipRect will be meaningless.
*/
nsIntRect mClipRect;
bool mHasClip;
/**
* Whether mVisibleAboveBackgroundRegion should be considered infinite.
*/
bool mAllDrawingAboveBackground;
};
class ContainerState;
/**
* A tree of PaintedLayerDataNodes. At any point in time, the tree only
* contains nodes for animated geometry roots that new items can potentially
* merge into. Any time content is added on top that overlaps existing things
* in such a way that we no longer want to merge new items with some existing
* content, that existing content gets "finished".
* The public-facing methods of this class are FindPaintedLayerFor,
* AddingOwnLayer, and Finish. The other public methods are for
* PaintedLayerDataNode.
* The tree calls out to its containing ContainerState for some things.
* All coordinates / rects in the tree or the tree nodes are in the
* ContainerState's coordinate space, i.e. relative to the reference frame and
* in layer pixels.
* The clip rects of sibling nodes never overlap. This is ensured by finishing
* existing nodes before adding new ones, if this property were to be violated.
* The root tree node doesn't get finished until the ContainerState is
* finished.
* The tree's root node is always the root reference frame of the builder. We
* don't stop at the container state's mContainerAnimatedGeometryRoot because
* some of our contents can have animated geometry roots that are not
* descendants of the container's animated geometry root. Every animated
* geometry root we encounter for our contents needs to have a defined place in
* the tree.
*/
class PaintedLayerDataTree {
public:
PaintedLayerDataTree(ContainerState& aContainerState,
nscolor& aBackgroundColor)
: mContainerState(aContainerState),
mContainerUniformBackgroundColor(aBackgroundColor),
mForInactiveLayer(false) {}
~PaintedLayerDataTree() {
MOZ_ASSERT(!mRoot);
MOZ_ASSERT(mNodes.Count() == 0);
}
void InitializeForInactiveLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot);
/**
* Notify our contents that some non-PaintedLayer content has been added.
* *aRect needs to be a rectangle that doesn't move with respect to
* aAnimatedGeometryRoot and that contains the added item.
* If aRect is null, the extents will be considered infinite.
* If aOutUniformBackgroundColor is non-null, it will be set to an opaque
* color that can be pulled into the background of the added content, or
* transparent if that is not possible.
*/
void AddingOwnLayer(AnimatedGeometryRoot* aAnimatedGeometryRoot,
const nsIntRect* aRect,
nscolor* aOutUniformBackgroundColor);
/**
* Find a PaintedLayerData for aItem. This can either be an existing
* PaintedLayerData from inside a node in our tree, or a new one that gets
* created by a call out to aNewPaintedLayerCallback.
*/
template <typename NewPaintedLayerCallbackType>
PaintedLayerData* FindPaintedLayerFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
NewPaintedLayerCallbackType aNewPaintedLayerCallback);
/**
* Finish everything.
*/
void Finish();
/**
* Get the parent animated geometry root of aAnimatedGeometryRoot.
* That's either aAnimatedGeometryRoot's animated geometry root, or, if
* that's aAnimatedGeometryRoot itself, then it's the animated geometry
* root for aAnimatedGeometryRoot's cross-doc parent frame.
*/
AnimatedGeometryRoot* GetParentAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot);
/**
* Whether aAnimatedGeometryRoot has an intrinsic clip that doesn't move with
* respect to aAnimatedGeometryRoot's parent animated geometry root.
* If aAnimatedGeometryRoot is a scroll frame, this will be the scroll frame's
* scroll port, otherwise there is no clip.
* This method doesn't have much to do with PaintedLayerDataTree, but this is
* where we have easy access to a display list builder, which we use to get
* the clip rect result into the right coordinate space.
*/
bool IsClippedWithRespectToParentAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip);
/**
* Called by PaintedLayerDataNode when it is finished, so that we can drop
* our pointers to it.
*/
void NodeWasFinished(AnimatedGeometryRoot* aAnimatedGeometryRoot);
nsDisplayListBuilder* Builder() const;
ContainerState& ContState() const { return mContainerState; }
nscolor UniformBackgroundColor() const {
return mContainerUniformBackgroundColor;
}
protected:
/**
* Finish all nodes that potentially intersect *aRect, where *aRect is a rect
* that doesn't move with respect to aAnimatedGeometryRoot.
* If aRect is null, *aRect will be considered infinite.
*/
void FinishPotentiallyIntersectingNodes(
AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect);
/**
* Make sure that there is a node for aAnimatedGeometryRoot and all of its
* ancestor geometry roots. Return the node for aAnimatedGeometryRoot.
*/
PaintedLayerDataNode* EnsureNodeFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot);
/**
* Find an existing node in the tree for an ancestor of aAnimatedGeometryRoot.
* *aOutAncestorChild will be set to the last ancestor that was encountered
* in the search up from aAnimatedGeometryRoot; it will be a child animated
* geometry root of the result, if neither are null.
*/
PaintedLayerDataNode* FindNodeForAncestorAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot,
AnimatedGeometryRoot** aOutAncestorChild);
ContainerState& mContainerState;
Maybe<PaintedLayerDataNode> mRoot;
/**
* The uniform opaque color from behind this container layer, or
* NS_RGBA(0,0,0,0) if the background behind this container layer is not
* uniform and opaque. This color can be pulled into PaintedLayers that are
* directly above the background.
*/
nscolor mContainerUniformBackgroundColor;
/**
* A hash map for quick access the node belonging to a particular animated
* geometry root.
*/
nsTHashMap<nsPtrHashKey<AnimatedGeometryRoot>, PaintedLayerDataNode*> mNodes;
bool mForInactiveLayer;
};
/**
* This is a helper object used to build up the layer children for
* a ContainerLayer.
*/
class ContainerState {
public:
ContainerState(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
FrameLayerBuilder* aLayerBuilder, nsIFrame* aContainerFrame,
nsDisplayItem* aContainerItem, const nsRect& aContainerBounds,
ContainerLayer* aContainerLayer,
const ContainerLayerParameters& aParameters,
nscolor aBackgroundColor,
const ActiveScrolledRoot* aContainerASR,
const ActiveScrolledRoot* aContainerScrollMetadataASR,
const ActiveScrolledRoot* aContainerCompositorASR)
: mBuilder(aBuilder),
mManager(aManager),
mLayerBuilder(aLayerBuilder),
mContainerFrame(aContainerFrame),
mContainerLayer(aContainerLayer),
mContainerBounds(aContainerBounds),
mContainerASR(aContainerASR),
mContainerScrollMetadataASR(aContainerScrollMetadataASR),
mContainerCompositorASR(aContainerCompositorASR),
mParameters(aParameters),
mPaintedLayerDataTree(*this, aBackgroundColor),
mLastDisplayPortAGR(nullptr),
mContainerItem(aContainerItem) {
nsPresContext* presContext = aContainerFrame->PresContext();
mAppUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
mContainerReferenceFrame = const_cast<nsIFrame*>(
aContainerItem ? aContainerItem->ReferenceFrameForChildren()
: mBuilder->FindReferenceFrameFor(mContainerFrame));
bool isAtRoot = !aContainerItem ||
(aContainerItem->Frame() == mBuilder->RootReferenceFrame());
MOZ_ASSERT(!isAtRoot ||
mContainerReferenceFrame == mBuilder->RootReferenceFrame());
mContainerAnimatedGeometryRoot =
isAtRoot ? aBuilder->GetRootAnimatedGeometryRoot()
: aContainerItem->GetAnimatedGeometryRoot();
MOZ_ASSERT(
!mBuilder->IsPaintingToWindow() ||
nsLayoutUtils::IsAncestorFrameCrossDoc(
mBuilder->RootReferenceFrame(), *mContainerAnimatedGeometryRoot));
// When AllowResidualTranslation is false, display items will be drawn
// scaled with a translation by integer pixels, so we know how the snapping
// will work.
mSnappingEnabled = aManager->IsSnappingEffectiveTransforms() &&
!mParameters.AllowResidualTranslation();
CollectOldLayers();
}
/**
* This is the method that actually walks a display list and builds
* the child layers.
*/
void ProcessDisplayItems(nsDisplayList* aList);
/**
* This finalizes all the open PaintedLayers by popping every element off
* mPaintedLayerDataStack, then sets the children of the container layer
* to be all the layers in mNewChildLayers in that order and removes any
* layers as children of the container that aren't in mNewChildLayers.
* @param aTextContentFlags if any child layer has CONTENT_COMPONENT_ALPHA,
* set *aTextContentFlags to CONTENT_COMPONENT_ALPHA
*/
void Finish(uint32_t* aTextContentFlags,
const nsIntRect& aContainerPixelBounds,
nsDisplayList* aChildItems);
nscoord GetAppUnitsPerDevPixel() { return mAppUnitsPerDevPixel; }
nsIntRect ScaleToNearestPixels(const nsRect& aRect) const {
return aRect.ScaleToNearestPixels(mParameters.mXScale, mParameters.mYScale,
mAppUnitsPerDevPixel);
}
nsIntRect ScaleToOutsidePixels(const nsRect& aRect,
bool aSnap = false) const {
if (aRect.IsEmpty()) {
return nsIntRect();
}
if (aSnap && mSnappingEnabled) {
return ScaleToNearestPixels(aRect);
}
return aRect.ScaleToOutsidePixels(mParameters.mXScale, mParameters.mYScale,
mAppUnitsPerDevPixel);
}
nsIntRect ScaleToInsidePixels(const nsRect& aRect, bool aSnap = false) const {
if (aSnap && mSnappingEnabled) {
return ScaleToNearestPixels(aRect);
}
return aRect.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
mAppUnitsPerDevPixel);
}
nsIntRegion ScaleRegionToNearestPixels(const nsRegion& aRegion) const {
return aRegion.ScaleToNearestPixels(
mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
}
nsIntRegion ScaleRegionToInsidePixels(const nsRegion& aRegion,
bool aSnap = false) const {
if (aSnap && mSnappingEnabled) {
return ScaleRegionToNearestPixels(aRegion);
}
return aRegion.ScaleToInsidePixels(mParameters.mXScale, mParameters.mYScale,
mAppUnitsPerDevPixel);
}
nsIntRegion ScaleRegionToOutsidePixels(const nsRegion& aRegion,
bool aSnap = false) const {
if (aRegion.IsEmpty()) {
return nsIntRegion();
}
if (aSnap && mSnappingEnabled) {
return ScaleRegionToNearestPixels(aRegion);
}
return aRegion.ScaleToOutsidePixels(
mParameters.mXScale, mParameters.mYScale, mAppUnitsPerDevPixel);
}
nsIFrame* GetContainerFrame() const { return mContainerFrame; }
nsDisplayListBuilder* Builder() const { return mBuilder; }
FrameLayerBuilder* LayerBuilder() const { return mLayerBuilder; }
/**
* Check if we are currently inside an inactive layer.
*/
bool IsInInactiveLayer() const {
return mLayerBuilder->GetContainingPaintedLayerData();
}
/**
* Sets aOuterVisibleRegion as aLayer's visible region.
* @param aOuterVisibleRegion
* is in the coordinate space of the container reference frame.
* @param aLayerContentsVisibleRect, if non-null, is in the layer's own
* coordinate system.
* @param aOuterUntransformed is true if the given aOuterVisibleRegion
* is already untransformed with the matrix of the layer.
*/
void SetOuterVisibleRegionForLayer(
Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
const nsIntRect* aLayerContentsVisibleRect = nullptr,
bool aOuterUntransformed = false) const;
/**
* Try to determine whether the PaintedLayer aData has a single opaque color
* covering aRect. If successful, return that color, otherwise return
* NS_RGBA(0,0,0,0).
* If aRect turns out not to intersect any content in the layer,
* *aOutIntersectsLayer will be set to false.
*/
nscolor FindOpaqueBackgroundColorInLayer(const PaintedLayerData* aData,
const nsIntRect& aRect,
bool* aOutIntersectsLayer) const;
/**
* Indicate that we are done adding items to the PaintedLayer represented by
* aData. Make sure that a real PaintedLayer exists for it, and set the final
* visible region and opaque-content.
*/
template <typename FindOpaqueBackgroundColorCallbackType>
void FinishPaintedLayerData(
PaintedLayerData& aData,
FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor);
protected:
friend class PaintedLayerData;
friend class FLBDisplayListIterator;
LayerManager::PaintedLayerCreationHint GetLayerCreationHint(
AnimatedGeometryRoot* aAnimatedGeometryRoot);
/**
* Creates a new PaintedLayer and sets up the transform on the PaintedLayer
* to account for scrolling.
*/
already_AddRefed<PaintedLayer> CreatePaintedLayer(PaintedLayerData* aData);
/**
* Find a PaintedLayer for recycling, recycle it and prepare it for use, or
* return null if no suitable layer was found.
*/
already_AddRefed<PaintedLayer> AttemptToRecyclePaintedLayer(
AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame);
/**
* Recycle aLayer and do any necessary invalidation.
*/
PaintedDisplayItemLayerUserData* RecyclePaintedLayer(
PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
bool& didResetScrollPositionForLayerPixelAlignment);
/**
* Perform the last step of CreatePaintedLayer / AttemptToRecyclePaintedLayer:
* Initialize aData, set up the layer's transform for scrolling, and
* invalidate the layer for layer pixel alignment changes if necessary.
*/
void PreparePaintedLayerForUse(
PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
AnimatedGeometryRoot* aAnimatedGeometryRoot,
const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
bool aDidResetScrollPositionForLayerPixelAlignment);
/**
* Attempt to prepare an ImageLayer based upon the provided PaintedLayerData.
* Returns nullptr on failure.
*/
already_AddRefed<Layer> PrepareImageLayer(PaintedLayerData* aData);
/**
* Attempt to prepare a ColorLayer based upon the provided PaintedLayerData.
* Returns nullptr on failure.
*/
already_AddRefed<Layer> PrepareColorLayer(PaintedLayerData* aData);
/**
* Grab the next recyclable ColorLayer, or create one if there are no
* more recyclable ColorLayers.
*/
already_AddRefed<ColorLayer> CreateOrRecycleColorLayer(
PaintedLayer* aPainted);
/**
* Grab the next recyclable ImageLayer, or create one if there are no
* more recyclable ImageLayers.
*/
already_AddRefed<ImageLayer> CreateOrRecycleImageLayer(
PaintedLayer* aPainted);
/**
* Grab a recyclable ImageLayer for use as a mask layer for aLayer (that is a
* mask layer which has been used for aLayer before), or create one if such
* a layer doesn't exist.
*
* Since mask layers can exist either on the layer directly, or as a side-
* attachment to FrameMetrics (for ancestor scrollframe clips), we key the
* recycle operation on both the originating layer and the mask layer's
* index in the layer, if any.
*/
struct MaskLayerKey;
template <typename UserData>
already_AddRefed<ImageLayer> CreateOrRecycleMaskImageLayerFor(
const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
void (*aSetDefaultUserData)(Layer* aLayer));
/**
* Grabs all PaintedLayers and ColorLayers from the ContainerLayer and makes
* them available for recycling.
*/
void CollectOldLayers();
/**
* If aItem used to belong to a PaintedLayer, invalidates the area of
* aItem in that layer. If aNewLayer is a PaintedLayer, invalidates the area
* of aItem in that layer.
*/
void InvalidateForLayerChange(nsDisplayItem* aItem, PaintedLayer* aNewLayer,
DisplayItemData* aData);
/**
* Returns true if aItem's opaque area (in aOpaque) covers the entire
* scrollable area of its presshell.
*/
bool ItemCoversScrollableArea(nsDisplayItem* aItem, const nsRegion& aOpaque);
/**
* Set ScrollMetadata and scroll-induced clipping on aEntry's layer.
*/
void SetupScrollingMetadata(NewLayerEntry* aEntry);
/**
* Applies occlusion culling.
* For each layer in mNewChildLayers, remove from its visible region the
* opaque regions of the layers at higher z-index, but only if they have
* the same animated geometry root and fixed-pos frame ancestor.
* The opaque region for the child layers that share the same animated
* geometry root as the container frame is returned in
* *aOpaqueRegionForContainer.
*
* Also sets scroll metadata on the layers.
*/
void PostprocessRetainedLayers(nsIntRegion* aOpaqueRegionForContainer);
/**
* Computes the snapped opaque area of aItem. Sets aList's opaque flag
* if it covers the entire list bounds. Sets *aHideAllLayersBelow to true
* this item covers the entire viewport so that all layers below are
* permanently invisible.
*/
nsIntRegion ComputeOpaqueRect(nsDisplayItem* aItem,
AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR,
const DisplayItemClip& aClip,
nsDisplayList* aList, bool* aHideAllLayersBelow,
bool* aOpaqueForAnimatedGeometryRootParent);
/**
* Fills a PaintedLayerData object that is initialized for a layer that the
* current item will be assigned to. Also creates mNewChildLayers entries.
* @param aData The PaintedLayerData that will be filled.
* @param aVisibleRect The visible rect of the item.
* @param aAnimatedGeometryRoot The item's animated geometry root.
* @param aASR The active scrolled root that moves this
* PaintedLayer.
* @param aClipChain The clip chain that the compositor needs to
* apply to this layer.
* @param aScrollMetadataASR The leaf ASR for which scroll metadata needs
* to be set on the layer, because either the layer itself or its scrolled
* clip need to move with that ASR.
* @param aTopLeft The offset between aAnimatedGeometryRoot and
* the reference frame.
* @param aReferenceFrame The reference frame for the item.
* @param aBackfaceHidden The backface visibility for the item frame.
*/
void NewPaintedLayerData(
PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
const nsIFrame* aReferenceFrame, const bool aBackfaceHidden);
/* Build a mask layer to represent the clipping region. Will return null if
* there is no clipping specified or a mask layer cannot be built.
* Builds an ImageLayer for the appropriate backend; the mask is relative to
* aLayer's visible region.
* aLayer is the layer to be clipped.
* relative to the container reference frame
* aRoundedRectClipCount is used when building mask layers for PaintedLayers,
*/
void SetupMaskLayer(Layer* aLayer, const DisplayItemClip& aClip);
/**
* If |aClip| has rounded corners, create a mask layer for them, and
* add it to |aLayer|'s ancestor mask layers, returning an index into
* the array of ancestor mask layers. Returns an empty Maybe if
* |aClip| does not have rounded corners, or if no mask layer could
* be created.
*/
Maybe<size_t> SetupMaskLayerForScrolledClip(Layer* aLayer,
const DisplayItemClip& aClip);
/**
* Create/find a mask layer with suitable size for aMaskItem to paint
* css-positioned-masking onto.
*/
void SetupMaskLayerForCSSMask(Layer* aLayer,
nsDisplayMasksAndClipPaths* aMaskItem);
already_AddRefed<Layer> CreateMaskLayer(
Layer* aLayer, const DisplayItemClip& aClip,
const Maybe<size_t>& aForAncestorMaskLayer);
/**
* Get the display port for an AGR.
* The result would be cached for later reusing.
*/
nsRect GetDisplayPortForAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot);
nsDisplayListBuilder* mBuilder;
LayerManager* mManager;
FrameLayerBuilder* mLayerBuilder;
nsIFrame* mContainerFrame;
nsIFrame* mContainerReferenceFrame;
AnimatedGeometryRoot* mContainerAnimatedGeometryRoot;
ContainerLayer* mContainerLayer;
nsRect mContainerBounds;
// Due to the way we store scroll annotations in the layer tree, we need to
// keep track of three (possibly different) ASRs here.
// mContainerASR is the ASR of the container display item that this
// ContainerState was created for.
// mContainerScrollMetadataASR is the ASR of the leafmost scroll metadata
// that's in effect on mContainerLayer.
// mContainerCompositorASR is the ASR that mContainerLayer moves with on
// the compositor / APZ side, taking into account both the scroll meta data
// and the fixed position annotation on itself and its ancestors.
const ActiveScrolledRoot* mContainerASR;
const ActiveScrolledRoot* mContainerScrollMetadataASR;
const ActiveScrolledRoot* mContainerCompositorASR;
#ifdef DEBUG
nsRect mAccumulatedChildBounds;
#endif
ContainerLayerParameters mParameters;
/**
* The region of PaintedLayers that should be invalidated every time
* we recycle one.
*/
nsIntRegion mInvalidPaintedContent;
PaintedLayerDataTree mPaintedLayerDataTree;
/**
* We collect the list of children in here. During ProcessDisplayItems,
* the layers in this array either have mContainerLayer as their parent,
* or no parent.
* PaintedLayers have two entries in this array: the second one is used only
* if the PaintedLayer is optimized away to a ColorLayer or ImageLayer. It's
* essential that this array is only appended to, since PaintedLayerData
* records the index of its PaintedLayer in this array.
*/
typedef AutoTArray<NewLayerEntry, 1> AutoLayersArray;
AutoLayersArray mNewChildLayers;
nsTHashSet<RefPtr<PaintedLayer>> mPaintedLayersAvailableForRecycling;
nscoord mAppUnitsPerDevPixel;
bool mSnappingEnabled;
struct MaskLayerKey {
MaskLayerKey() : mLayer(nullptr) {}
MaskLayerKey(Layer* aLayer, const Maybe<size_t>& aAncestorIndex)
: mLayer(aLayer), mAncestorIndex(aAncestorIndex) {}
PLDHashNumber Hash() const {
// Hash the layer and add the layer index to the hash.
return (NS_PTR_TO_UINT32(mLayer) >> 2) +
(mAncestorIndex ? (*mAncestorIndex + 1) : 0);
}
bool operator==(const MaskLayerKey& aOther) const {
return mLayer == aOther.mLayer && mAncestorIndex == aOther.mAncestorIndex;
}
Layer* mLayer;
Maybe<size_t> mAncestorIndex;
};
nsTHashMap<nsGenericHashKey<MaskLayerKey>, RefPtr<ImageLayer>>
mRecycledMaskImageLayers;
// Keep display port of AGR to avoid wasting time on doing the same
// thing repeatly.
AnimatedGeometryRoot* mLastDisplayPortAGR;
nsRect mLastDisplayPortRect;
nsDisplayItem* mContainerItem;
// Cache ScrollMetadata so it doesn't need recomputed if the ASR and clip are
// unchanged. If mASR == nullptr then mMetadata is not valid.
struct CachedScrollMetadata {
const ActiveScrolledRoot* mASR;
const DisplayItemClip* mClip;
Maybe<ScrollMetadata> mMetadata;
CachedScrollMetadata() : mASR(nullptr), mClip(nullptr) {}
};
CachedScrollMetadata mCachedScrollMetadata;
};
class FLBDisplayListIterator : public FlattenedDisplayListIterator {
public:
FLBDisplayListIterator(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
ContainerState* aState)
: FlattenedDisplayListIterator(aBuilder, aList, false), mState(aState) {
MOZ_ASSERT(mState);
ResolveFlattening();
}
DisplayItemEntry GetNextEntry() {
if (!mMarkers.empty()) {
DisplayItemEntry entry = mMarkers.front();
mMarkers.pop_front();
return entry;
}
return DisplayItemEntry{GetNextItem(), DisplayItemEntryType::Item};
}
bool HasNext() const override {
return FlattenedDisplayListIterator::HasNext() || !mMarkers.empty();
}
private:
bool ShouldFlattenNextItem() override {
if (!FlattenedDisplayListIterator::ShouldFlattenNextItem()) {
return false;
}
nsDisplayItem* next = PeekNext();
const DisplayItemType type = next->GetType();
if (type == DisplayItemType::TYPE_SVG_WRAPPER) {
// We mark SetContainsSVG for the CONTENT_FRAME_TIME_WITH_SVG metric
if (RefPtr<LayerManager> lm = mState->mBuilder->GetWidgetLayerManager()) {
lm->SetContainsSVG(true);
}
}
if (!SupportsFlatteningWithMarkers(type)) {
return true;
}
if (type == DisplayItemType::TYPE_OPACITY &&
IsOpacityAppliedToChildren(next)) {
// This is the previous opacity flattening path, where the opacity has
// been applied to children.
return true;
}
if (mState->IsInInactiveLayer() || !ItemWantsInactiveLayer(next)) {
// Do not flatten nested inactive display items, or display items that
// want an active layer.
return false;
}
// If we reach here, we will emit an effect start marker for
// nsDisplayTransform or nsDisplayOpacity.
MOZ_ASSERT(type == DisplayItemType::TYPE_TRANSFORM ||
!IsOpacityAppliedToChildren(next));
return true;
}
void EnterChildList(nsDisplayItem* aContainerItem) override {
mFlattenedLists.AppendElement(aContainerItem);
AddMarkerIfNeeded<MarkerType::StartMarker>(aContainerItem, mMarkers);
}
void ExitChildList() override {
MOZ_ASSERT(!mFlattenedLists.IsEmpty());
nsDisplayItem* aContainerItem = mFlattenedLists.PopLastElement();
AddMarkerIfNeeded<MarkerType::EndMarker>(aContainerItem, mMarkers);
}
bool ItemWantsInactiveLayer(nsDisplayItem* aItem) {
const LayerState layerState = aItem->GetLayerState(
mState->mBuilder, mState->mManager, mState->mParameters);
return layerState == LayerState::LAYER_INACTIVE;
}
std::deque<DisplayItemEntry> mMarkers;
AutoTArray<nsDisplayItem*, 16> mFlattenedLists;
ContainerState* mState;
};
class PaintedDisplayItemLayerUserData : public LayerUserData {
public:
PaintedDisplayItemLayerUserData()
: mForcedBackgroundColor(NS_RGBA(0, 0, 0, 0)),
mXScale(1.f),
mYScale(1.f),
mAppUnitsPerDevPixel(0),
mTranslation(0, 0),
mAnimatedGeometryRootPosition(0, 0),
mLastItemCount(0),
mContainerLayerFrame(nullptr),
mDisabledAlpha(false) {}
NS_INLINE_DECL_REFCOUNTING(PaintedDisplayItemLayerUserData);
/**
* 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 PaintedLayer's 0,0 to the
* reference frame. This isn't necessarily the same as the transform
* set on the PaintedLayer since we might also be applying an extra
* offset specified by the parent ContainerLayer/
*/
nsIntPoint mTranslation;
/**
* We try to make 0,0 of the PaintedLayer be the top-left of the
* border-box of the "active scrolled root" frame (i.e. the nearest ancestor
* frame for the display items that is being actively scrolled). But
* we force the PaintedLayer transform to be an integer translation, and we
* may have a resolution scale, so we have to snap the PaintedLayer transform,
* so 0,0 may not be exactly the top-left of the active scrolled root. Here we
* store the coordinates in PaintedLayer space of the top-left of the
* active scrolled root.
*/
gfxPoint mAnimatedGeometryRootPosition;
nsIntRegion mRegionToInvalidate;
// The offset between the active scrolled root of this layer
// and the root of the container for the previous and current
// paints respectively.
nsPoint mLastAnimatedGeometryRootOrigin;
nsPoint mAnimatedGeometryRootOrigin;
RefPtr<ColorLayer> mColorLayer;
RefPtr<ImageLayer> mImageLayer;
// The region for which display item visibility for this layer has already
// been calculated. Used to reduce the number of calls to
// RecomputeVisibilityForItems if it is known in advance that a larger
// region will be painted during a transaction than in a single call to
// DrawPaintedLayer, for example when progressive paint is enabled.
nsIntRegion mVisibilityComputedRegion;
// The area for which we called RecomputeVisibilityForItems on the
// previous paint.
nsRect mPreviousRecomputeVisibilityRect;
// The number of items assigned to this layer on the previous paint.
size_t mLastItemCount;
// The translation set on this PaintedLayer during the previous paint. This
// is needed when invalidating based on a display item's geometry information
// from the previous paint.
Maybe<nsIntPoint> mLastPaintOffset;
// Temporary state only valid during the FrameLayerBuilder's lifetime.
// FLB's mPaintedLayerItems is responsible for cleaning these up when
// we finish painting to avoid dangling pointers.
std::vector<AssignedDisplayItem> mItems;
nsIFrame* mContainerLayerFrame;
/**
* This is set when the painted layer has no component alpha.
*/
bool mDisabledAlpha;
protected:
~PaintedDisplayItemLayerUserData() override = default;
};
FrameLayerBuilder::FrameLayerBuilder()
: mRetainingManager(nullptr),
mDisplayListBuilder(nullptr),
mContainingPaintedLayer(nullptr),
mInactiveLayerClip(nullptr),
mInvalidateAllLayers(false),
mInLayerTreeCompressionMode(false),
mIsInactiveLayerManager(false) {
MOZ_COUNT_CTOR(FrameLayerBuilder);
}
FrameLayerBuilder::~FrameLayerBuilder() {
GetMaskLayerImageCache()->Sweep();
for (PaintedDisplayItemLayerUserData* userData : mPaintedLayerItems) {
userData->mLastPaintOffset = Some(userData->mTranslation);
userData->mItems.clear();
userData->mContainerLayerFrame = nullptr;
}
MOZ_COUNT_DTOR(FrameLayerBuilder);
}
void FrameLayerBuilder::AddPaintedLayerItemsEntry(
PaintedDisplayItemLayerUserData* aData) {
mPaintedLayerItems.AppendElement(aData);
}
/*
* User data for layers which will be used as masks.
*/
struct MaskLayerUserData : public LayerUserData {
MaskLayerUserData()
: mScaleX(-1.0f), mScaleY(-1.0f), mAppUnitsPerDevPixel(-1) {}
MaskLayerUserData(const DisplayItemClip& aClip, int32_t aAppUnitsPerDevPixel,
const ContainerLayerParameters& aParams)
: mScaleX(aParams.mXScale),
mScaleY(aParams.mYScale),
mOffset(aParams.mOffset),
mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {
aClip.AppendRoundedRects(&mRoundedClipRects);
}
void operator=(MaskLayerUserData&& aOther) {
mScaleX = aOther.mScaleX;
mScaleY = aOther.mScaleY;
mOffset = aOther.mOffset;
mAppUnitsPerDevPixel = aOther.mAppUnitsPerDevPixel;
mRoundedClipRects = std::move(aOther.mRoundedClipRects);
}
bool operator==(const MaskLayerUserData& aOther) const {
return mRoundedClipRects == aOther.mRoundedClipRects &&
mScaleX == aOther.mScaleX && mScaleY == aOther.mScaleY &&
mOffset == aOther.mOffset &&
mAppUnitsPerDevPixel == aOther.mAppUnitsPerDevPixel;
}
// Keeps a MaskLayerImageKey alive by managing its mLayerCount member-var
MaskLayerImageCache::MaskLayerImageKeyRef mImageKey;
// properties of the mask layer; the mask layer may be re-used if these
// remain unchanged.
nsTArray<DisplayItemClip::RoundedRect> mRoundedClipRects;
// scale from the masked layer which is applied to the mask
float mScaleX, mScaleY;
// The ContainerLayerParameters offset which is applied to the mask's
// transform.
nsIntPoint mOffset;
int32_t mAppUnitsPerDevPixel;
};
/*
* User data for layers which will be used as masks for css positioned mask.
*/
struct CSSMaskLayerUserData : public LayerUserData {
CSSMaskLayerUserData() : mMaskStyle(nsStyleImageLayers::LayerType::Mask) {}
CSSMaskLayerUserData(nsIFrame* aFrame, const nsIntRect& aMaskBounds,
const nsPoint& aMaskLayerOffset)
: mMaskBounds(aMaskBounds),
mMaskStyle(aFrame->StyleSVGReset()->mMask),
mMaskLayerOffset(aMaskLayerOffset) {}
void operator=(CSSMaskLayerUserData&& aOther) {
mMaskBounds = aOther.mMaskBounds;
mMaskStyle = std::move(aOther.mMaskStyle);
mMaskLayerOffset = aOther.mMaskLayerOffset;
}
bool operator==(const CSSMaskLayerUserData& aOther) const {
if (!mMaskBounds.IsEqualInterior(aOther.mMaskBounds)) {
return false;
}
// Make sure we draw the same portion of the mask onto mask layer.
if (mMaskLayerOffset != aOther.mMaskLayerOffset) {
return false;
}
return mMaskStyle == aOther.mMaskStyle;
}
private:
nsIntRect mMaskBounds;
nsStyleImageLayers mMaskStyle;
nsPoint mMaskLayerOffset; // The offset from the origin of mask bounds to
// the origin of mask layer.
};
/*
* A helper object to create a draw target for painting mask and create a
* image container to hold the drawing result. The caller can then bind this
* image container with a image mask layer via ImageLayer::SetContainer.
*/
class MaskImageData {
public:
MaskImageData(const gfx::IntSize& aSize, LayerManager* aLayerManager)
: mTextureClientLocked(false),
mSize(aSize),
mLayerManager(aLayerManager) {
MOZ_ASSERT(!mSize.IsEmpty());
MOZ_ASSERT(mLayerManager);
}
~MaskImageData() {
if (mTextureClientLocked) {
MOZ_ASSERT(mTextureClient);
// Clear DrawTarget before Unlock.
mDrawTarget = nullptr;
mTextureClient->Unlock();
}
}
gfx::DrawTarget* CreateDrawTarget() {
if (mDrawTarget) {
return mDrawTarget;
}
if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC) {
mDrawTarget = mLayerManager->CreateOptimalMaskDrawTarget(mSize);
return mDrawTarget;
}
MOZ_ASSERT(mLayerManager->GetBackendType() ==
LayersBackend::LAYERS_CLIENT ||
mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR);
KnowsCompositor* knowsCompositor = mLayerManager->AsKnowsCompositor();
if (!knowsCompositor) {
return nullptr;
}
mTextureClient = TextureClient::CreateForDrawing(
knowsCompositor, SurfaceFormat::A8, mSize, BackendSelector::Content,
TextureFlags::DISALLOW_BIGIMAGE,
TextureAllocationFlags::ALLOC_CLEAR_BUFFER);
if (!mTextureClient) {
return nullptr;
}
mTextureClientLocked = mTextureClient->Lock(OpenMode::OPEN_READ_WRITE);
if (!mTextureClientLocked) {
return nullptr;
}
mDrawTarget = mTextureClient->BorrowDrawTarget();
return mDrawTarget;
}
already_AddRefed<ImageContainer> CreateImageAndImageContainer() {
RefPtr<ImageContainer> container = LayerManager::CreateImageContainer();
RefPtr<Image> image = CreateImage();
if (!image) {
return nullptr;
}
container->SetCurrentImageInTransaction(image);
return container.forget();
}
private:
already_AddRefed<Image> CreateImage() {
if (mLayerManager->GetBackendType() == LayersBackend::LAYERS_BASIC &&
mDrawTarget) {
RefPtr<SourceSurface> surface = mDrawTarget->Snapshot();
RefPtr<SourceSurfaceImage> image = new SourceSurfaceImage(mSize, surface);
// Disallow BIGIMAGE (splitting into multiple textures) for mask
// layer images
image->SetTextureFlags(TextureFlags::DISALLOW_BIGIMAGE);
return image.forget();
}
if ((mLayerManager->GetBackendType() == LayersBackend::LAYERS_CLIENT ||
mLayerManager->GetBackendType() == LayersBackend::LAYERS_WR) &&
mTextureClient && mDrawTarget) {
RefPtr<TextureWrapperImage> image = new TextureWrapperImage(
mTextureClient, gfx::IntRect(gfx::IntPoint(0, 0), mSize));
return image.forget();
}
return nullptr;
}
bool mTextureClientLocked;
gfx::IntSize mSize;
LayerManager* mLayerManager;
RefPtr<gfx::DrawTarget> mDrawTarget;
RefPtr<TextureClient> mTextureClient;
};
/* static */
void FrameLayerBuilder::Shutdown() {
if (gMaskLayerImageCache) {
delete gMaskLayerImageCache;
gMaskLayerImageCache = nullptr;
}
}
void FrameLayerBuilder::Init(nsDisplayListBuilder* aBuilder,
LayerManager* aManager,
PaintedLayerData* aLayerData,
bool aIsInactiveLayerManager,
const DisplayItemClip* aInactiveLayerClip) {
mDisplayListBuilder = aBuilder;
mRootPresContext =
aBuilder->RootReferenceFrame()->PresContext()->GetRootPresContext();
mContainingPaintedLayer = aLayerData;
mIsInactiveLayerManager = aIsInactiveLayerManager;
mInactiveLayerClip = aInactiveLayerClip;
aManager->SetUserData(&gLayerManagerLayerBuilder, this);
}
void FrameLayerBuilder::FlashPaint(gfxContext* aContext) {
float r = float(rand()) / float(RAND_MAX);
float g = float(rand()) / float(RAND_MAX);
float b = float(rand()) / float(RAND_MAX);
aContext->SetColor(sRGBColor(r, g, b, 0.4f));
aContext->Paint();
}
DisplayItemData* FrameLayerBuilder::GetDisplayItemData(nsIFrame* aFrame,
uint32_t aKey) {
for (auto* did : GetDisplayItemDataArray(aFrame)) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (data->mDisplayItemKey == aKey && data->FirstFrame() == aFrame &&
data->mLayer->Manager() == mRetainingManager) {
return data;
}
}
return nullptr;
}
#ifdef MOZ_DUMP_PAINTING
static 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;
}
static nsACString& AppendToString(nsACString& s, const nsIntRegion& r,
const char* pfx = "", const char* sfx = "") {
s += pfx;
s += "< ";
for (auto iter = r.RectIter(); !iter.Done(); iter.Next()) {
AppendToString(s, iter.Get()) += "; ";
}
s += ">";
return s += sfx;
}
#endif // MOZ_DUMP_PAINTING
/**
* Invalidate aRegion in aLayer. aLayer is in the coordinate system
* *after* aTranslation has been applied, so we need to
* apply the inverse of that transform before calling InvalidateRegion.
*/
static void InvalidatePostTransformRegion(PaintedLayer* aLayer,
const nsIntRegion& aRegion,
const nsIntPoint& aTranslation) {
// Convert the region from the coordinates of the container layer
// (relative to the snapped top-left of the display list reference frame)
// to the PaintedLayer's own coordinates
nsIntRegion rgn = aRegion;
rgn.MoveBy(-aTranslation);
aLayer->InvalidateRegion(rgn);
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
nsAutoCString str;
AppendToString(str, rgn);
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
}
#endif
}
static PaintedDisplayItemLayerUserData* GetPaintedDisplayItemLayerUserData(
Layer* aLayer) {
return static_cast<PaintedDisplayItemLayerUserData*>(
aLayer->GetUserData(&gPaintedDisplayItemLayerUserData));
}
static nsIntPoint GetTranslationForPaintedLayer(PaintedLayer* aLayer) {
PaintedDisplayItemLayerUserData* layerData =
GetPaintedDisplayItemLayerUserData(aLayer);
NS_ASSERTION(layerData, "Must be a tracked painted layer!");
return layerData->mTranslation;
}
/**
* Get the translation transform that was in aLayer when we last painted. It's
* either the transform saved by ~FrameLayerBuilder(), or else the transform
* that's currently in the layer (which must be an integer translation).
*/
static nsIntPoint GetLastPaintOffset(PaintedLayer* aLayer) {
auto* layerData = GetPaintedDisplayItemLayerUserData(aLayer);
MOZ_ASSERT(layerData);
return layerData->mLastPaintOffset.valueOr(layerData->mTranslation);
}
static void InvalidatePreTransformRect(PaintedLayer* aLayer,
const nsRect& aRect,
const DisplayItemClip& aClip,
const nsIntPoint& aTranslation,
TransformClipNode* aTransform) {
auto* data = GetPaintedDisplayItemLayerUserData(aLayer);
nsRect rect = aClip.ApplyNonRoundedIntersection(aRect);
if (aTransform) {
rect = aTransform->TransformRect(rect, data->mAppUnitsPerDevPixel);
}
nsIntRect pixelRect = rect.ScaleToOutsidePixels(data->mXScale, data->mYScale,
data->mAppUnitsPerDevPixel);
InvalidatePostTransformRegion(aLayer, pixelRect, aTranslation);
}
/**
* 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) {
MOZ_RELEASE_ASSERT(!sDestroyedFrame);
sDestroyedFrame = aFrame;
SmallPointerArray<DisplayItemData>* array =
aFrame->TakeProperty(nsIFrame::DisplayItemDataProperty());
// Hold a reference to all the items so that they don't get
// deleted from under us.
nsTArray<RefPtr<DisplayItemData>> arrayCopy;
if (array) {
for (DisplayItemData* data : *array) {
arrayCopy.AppendElement(data);
}
}
#ifdef DEBUG_DISPLAY_ITEM_DATA
if (array && 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 (DisplayItemData* data : arrayCopy) {
PaintedLayer* t = data->mLayer ? data->mLayer->AsPaintedLayer() : nullptr;
if (t) {
auto* paintedData = GetPaintedDisplayItemLayerUserData(t);
if (paintedData && data->mGeometry) {
const int32_t appUnitsPerDevPixel = paintedData->mAppUnitsPerDevPixel;
nsRegion rgn = data->mGeometry->ComputeInvalidationRegion();
nsIntRegion pixelRgn = rgn.ToOutsidePixels(appUnitsPerDevPixel);
if (data->mTransform) {
pixelRgn = data->mTransform->TransformRegion(pixelRgn);
}
pixelRgn =
pixelRgn.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
pixelRgn.MoveBy(-GetTranslationForPaintedLayer(t));
paintedData->mRegionToInvalidate.Or(paintedData->mRegionToInvalidate,
pixelRgn);
paintedData->mRegionToInvalidate.SimplifyOutward(8);
}
}
auto it = std::find(data->mParent->mDisplayItems.begin(),
data->mParent->mDisplayItems.end(), data);
MOZ_ASSERT(it != data->mParent->mDisplayItems.end());
std::iter_swap(it, data->mParent->mDisplayItems.end() - 1);
data->mParent->mDisplayItems.pop_back();
}
if (aFrame->IsSubDocumentFrame()) {
const nsSubDocumentFrame* subdoc =
static_cast<const nsSubDocumentFrame*>(aFrame);
nsFrameLoader* frameLoader = subdoc->FrameLoader();
if (frameLoader && frameLoader->GetRemoteBrowser()) {
// This is a remote browser that is going away, notify it that it is now
// hidden
frameLoader->GetRemoteBrowser()->UpdateEffects(
mozilla::dom::EffectsInfo::FullyHidden());
}
}
arrayCopy.Clear();
delete array;
sDestroyedFrame = nullptr;
}
void FrameLayerBuilder::DidBeginRetainedLayerTransaction(
LayerManager* aManager) {
mRetainingManager = aManager;
LayerManagerData* data = static_cast<LayerManagerData*>(
aManager->GetUserData(&gLayerManagerUserData));
if (data) {
mInvalidateAllLayers = data->mInvalidateAllLayers;
} else {
data = new LayerManagerData(aManager);
aManager->SetUserData(&gLayerManagerUserData, data);
}
}
void FrameLayerBuilder::DidEndTransaction() {
GetMaskLayerImageCache()->Sweep();
}
void FrameLayerBuilder::WillEndTransaction() {
if (!mRetainingManager) {
return;
}
// We need to save the data we'll need to support retaining.
LayerManagerData* data = static_cast<LayerManagerData*>(
mRetainingManager->GetUserData(&gLayerManagerUserData));
NS_ASSERTION(data, "Must have data!");
// Update all the frames that used to have layers.
auto iter = data->mDisplayItems.begin();
while (iter != data->mDisplayItems.end()) {
DisplayItemData* did = iter->get();
if (!did->mUsed) {
// This item was visible, but isn't anymore.
PaintedLayer* t = did->mLayer->AsPaintedLayer();
if (t && did->mGeometry) {
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr(
"Invalidating unused display item (%i) belonging to "
"frame %p from layer %p\n",
did->mDisplayItemKey, did->mFrameList[0], t);
}
#endif
InvalidatePreTransformRect(
t, did->mGeometry->ComputeInvalidationRegion(), did->mClip,
GetLastPaintOffset(t), did->mTransform);
}
did->NotifyRemoved();
// Remove this item. Swapping it with the last element first is
// quicker than erasing from the middle.
if (iter != data->mDisplayItems.end() - 1) {
std::iter_swap(iter, data->mDisplayItems.end() - 1);
data->mDisplayItems.pop_back();
} else {
data->mDisplayItems.pop_back();
break;
}
// Don't increment iter because we still need to process the item which
// was moved.
} else {
ComputeGeometryChangeForItem(did);
iter++;
}
}
data->mInvalidateAllLayers = false;
}
/* static */
DisplayItemData* FrameLayerBuilder::GetDisplayItemDataForManager(
nsPaintedDisplayItem* aItem, LayerManager* aManager) {
for (auto* did : GetDisplayItemDataArray(aItem->Frame())) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (data->mDisplayItemKey == aItem->GetPerFrameKey() &&
data->mLayer->Manager() == aManager) {
return data;
}
}
return nullptr;
}
bool FrameLayerBuilder::HasRetainedDataFor(const nsIFrame* aFrame,
uint32_t aDisplayItemKey) {
for (auto* did : GetDisplayItemDataArray(aFrame)) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (data->mDisplayItemKey == aDisplayItemKey) {
return true;
}
}
if (RefPtr<WebRenderUserData> data =
GetWebRenderUserData<WebRenderFallbackData>(aFrame,
aDisplayItemKey)) {
return true;
}
return false;
}
DisplayItemData* FrameLayerBuilder::GetOldLayerForFrame(
nsIFrame* aFrame, uint32_t aDisplayItemKey,
DisplayItemData* aOldData, /* = nullptr */
LayerManager* aOldLayerManager /* = nullptr */) {
// If we need to build a new layer tree, then just refuse to recycle
// anything.
if (!mRetainingManager || mInvalidateAllLayers) {
return nullptr;
}
MOZ_ASSERT(!aOldData || aOldLayerManager,
"You must provide aOldLayerManager to check aOldData's validity.");
MOZ_ASSERT_IF(aOldData, aOldLayerManager == aOldData->mLayer->Manager());
DisplayItemData* data = aOldData;
if (!data || aOldLayerManager != mRetainingManager) {
data = GetDisplayItemData(aFrame, aDisplayItemKey);
}
MOZ_ASSERT(data == GetDisplayItemData(aFrame, aDisplayItemKey));
return data;
}
Layer* FrameLayerBuilder::GetOldLayerFor(nsDisplayItem* aItem,
nsDisplayItemGeometry** aOldGeometry,
DisplayItemClip** aOldClip) {
uint32_t key = aItem->GetPerFrameKey();
nsIFrame* frame = aItem->Frame();
DisplayItemData* oldData = GetOldLayerForFrame(frame, key);
if (oldData) {
if (aOldGeometry) {
*aOldGeometry = oldData->mGeometry.get();
}
if (aOldClip) {
*aOldClip = &oldData->mClip;
}
return oldData->mLayer;
}
return nullptr;
}
/* static */
DisplayItemData* FrameLayerBuilder::GetOldDataFor(nsDisplayItem* aItem) {
for (auto* did : GetDisplayItemDataArray(aItem->Frame())) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (data->mDisplayItemKey == aItem->GetPerFrameKey()) {
return data;
}
}
return nullptr;
}
// Reset state that should not persist when a layer is recycled.
static void ResetLayerStateForRecycling(Layer* aLayer) {
// Currently, this clears the mask layer and ancestor mask layers.
// Other cleanup may be added here.
aLayer->SetMaskLayer(nullptr);
aLayer->SetAncestorMaskLayers({});
}
already_AddRefed<ColorLayer> ContainerState::CreateOrRecycleColorLayer(
PaintedLayer* aPainted) {
auto* data = GetPaintedDisplayItemLayerUserData(aPainted);
RefPtr<ColorLayer> layer = data->mColorLayer;
if (layer) {
ResetLayerStateForRecycling(layer);
layer->ClearExtraDumpInfo();
} else {
// Create a new layer
layer = mManager->CreateColorLayer();
if (!layer) {
return nullptr;
}
// Mark this layer as being used for painting display items
data->mColorLayer = layer;
layer->SetUserData(&gColorLayerUserData, nullptr);
// Remove other layer types we might have stored for this PaintedLayer
data->mImageLayer = nullptr;
}
return layer.forget();
}
already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleImageLayer(
PaintedLayer* aPainted) {
auto* data = GetPaintedDisplayItemLayerUserData(aPainted);
RefPtr<ImageLayer> layer = data->mImageLayer;
if (layer) {
ResetLayerStateForRecycling(layer);
layer->ClearExtraDumpInfo();
} else {
// Create a new layer
layer = mManager->CreateImageLayer();
if (!layer) {
return nullptr;
}
// Mark this layer as being used for painting display items
data->mImageLayer = layer;
layer->SetUserData(&gImageLayerUserData, nullptr);
// Remove other layer types we might have stored for this PaintedLayer
data->mColorLayer = nullptr;
}
return layer.forget();
}
template <typename UserData>
already_AddRefed<ImageLayer> ContainerState::CreateOrRecycleMaskImageLayerFor(
const MaskLayerKey& aKey, UserData* (*aGetUserData)(Layer* aLayer),
void (*aSetDefaultUserData)(Layer* aLayer)) {
RefPtr<ImageLayer> result = mRecycledMaskImageLayers.Get(aKey);
if (result && aGetUserData(result.get())) {
mRecycledMaskImageLayers.Remove(aKey);
aKey.mLayer->ClearExtraDumpInfo();
// XXX if we use clip on mask layers, null it out here
} else {
// Create a new layer
result = mManager->CreateImageLayer();
if (!result) {
return nullptr;
}
aSetDefaultUserData(result);
}
return result.forget();
}
static const double SUBPIXEL_OFFSET_EPSILON = 0.02;
/**
* This normally computes NSToIntRoundUp(aValue). However, if that would
* give a residual near 0.5 while aOldResidual is near -0.5, or
* it would give a residual near -0.5 while aOldResidual is near 0.5, then
* instead we return the integer in the other direction so that the residual
* is close to aOldResidual.
*/
static int32_t RoundToMatchResidual(double aValue, double aOldResidual) {
int32_t v = NSToIntRoundUp(aValue);
double residual = aValue - v;
if (aOldResidual < 0) {
if (residual > 0 &&
fabs(residual - 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
// Round up instead
return int32_t(ceil(aValue));
}
} else if (aOldResidual > 0) {
if (residual < 0 &&
fabs(residual + 1.0 - aOldResidual) < SUBPIXEL_OFFSET_EPSILON) {
// Round down instead
return int32_t(floor(aValue));
}
}
return v;
}
static void ResetScrollPositionForLayerPixelAlignment(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
nsIScrollableFrame* sf =
nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
if (sf) {
sf->ResetScrollPositionForLayerPixelAlignment();
}
}
static void InvalidateEntirePaintedLayer(
PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
const char* aReason) {
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Invalidating entire layer %p: %s\n", aLayer, aReason);
}
#endif
aLayer->InvalidateWholeLayer();
aLayer->SetInvalidRectToVisibleRegion();
ResetScrollPositionForLayerPixelAlignment(aAnimatedGeometryRoot);
}
LayerManager::PaintedLayerCreationHint ContainerState::GetLayerCreationHint(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
// Check whether the layer will be scrollable. This is used as a hint to
// influence whether tiled layers are used or not.
// Check creation hint inherited from our parent.
if (mParameters.mLayerCreationHint == LayerManager::SCROLLABLE) {
return LayerManager::SCROLLABLE;
}
// Check whether there's any active scroll frame on the animated geometry
// root chain.
for (AnimatedGeometryRoot* agr = aAnimatedGeometryRoot;
agr && agr != mContainerAnimatedGeometryRoot; agr = agr->mParentAGR) {
nsIFrame* fParent = nsLayoutUtils::GetCrossDocParentFrameInProcess(*agr);
if (!fParent) {
break;
}
nsIScrollableFrame* scrollable = do_QueryFrame(fParent);
if (scrollable) {
return LayerManager::SCROLLABLE;
}
}
return LayerManager::NONE;
}
already_AddRefed<PaintedLayer> ContainerState::AttemptToRecyclePaintedLayer(
AnimatedGeometryRoot* aAnimatedGeometryRoot, nsDisplayItem* aItem,
const nsPoint& aTopLeft, const nsIFrame* aReferenceFrame) {
Layer* oldLayer = mLayerBuilder->GetOldLayerFor(aItem);
if (!oldLayer || !oldLayer->AsPaintedLayer()) {
return nullptr;
}
if (!mPaintedLayersAvailableForRecycling.EnsureRemoved(
oldLayer->AsPaintedLayer())) {
// Not found.
return nullptr;
}
// Try to recycle the layer.
RefPtr<PaintedLayer> layer = oldLayer->AsPaintedLayer();
// Check if the layer hint has changed and whether or not the layer should
// be recreated because of it.
if (!layer->IsOptimizedFor(GetLayerCreationHint(aAnimatedGeometryRoot))) {
return nullptr;
}
bool didResetScrollPositionForLayerPixelAlignment = false;
PaintedDisplayItemLayerUserData* data =
RecyclePaintedLayer(layer, aAnimatedGeometryRoot,
didResetScrollPositionForLayerPixelAlignment);
PreparePaintedLayerForUse(layer, data, aAnimatedGeometryRoot, aReferenceFrame,
aTopLeft,
didResetScrollPositionForLayerPixelAlignment);
return layer.forget();
}
static void ReleaseLayerUserData(void* aData) {
PaintedDisplayItemLayerUserData* userData =
static_cast<PaintedDisplayItemLayerUserData*>(aData);
userData->Release();
}
already_AddRefed<PaintedLayer> ContainerState::CreatePaintedLayer(
PaintedLayerData* aData) {
LayerManager::PaintedLayerCreationHint creationHint =
GetLayerCreationHint(aData->mAnimatedGeometryRoot);
// Create a new painted layer
RefPtr<PaintedLayer> layer =
mManager->CreatePaintedLayerWithHint(creationHint);
if (!layer) {
return nullptr;
}
// Mark this layer as being used for painting display items
RefPtr<PaintedDisplayItemLayerUserData> userData =
new PaintedDisplayItemLayerUserData();
userData->mDisabledAlpha =
mParameters.mDisableSubpixelAntialiasingInDescendants;
userData.get()->AddRef();
layer->SetUserData(&gPaintedDisplayItemLayerUserData, userData,
ReleaseLayerUserData);
ResetScrollPositionForLayerPixelAlignment(aData->mAnimatedGeometryRoot);
PreparePaintedLayerForUse(layer, userData, aData->mAnimatedGeometryRoot,
aData->mReferenceFrame,
aData->mAnimatedGeometryRootOffset, true);
return layer.forget();
}
PaintedDisplayItemLayerUserData* ContainerState::RecyclePaintedLayer(
PaintedLayer* aLayer, AnimatedGeometryRoot* aAnimatedGeometryRoot,
bool& didResetScrollPositionForLayerPixelAlignment) {
// Clear clip rect and mask layer so we don't accidentally stay clipped.
// We will reapply any necessary clipping.
ResetLayerStateForRecycling(aLayer);
aLayer->ClearExtraDumpInfo();
auto* data = GetPaintedDisplayItemLayerUserData(aLayer);
NS_ASSERTION(data, "Recycled PaintedLayers must have user data");
// This gets called on recycled PaintedLayers that are going to be in the
// final layer tree, so it's a convenient time to invalidate the
// content that changed where we don't know what PaintedLayer it belonged
// to, or if we need to invalidate the entire layer, we can do that.
// This needs to be done before we update the PaintedLayer to its new
// transform. See nsGfxScrollFrame::InvalidateInternal, where
// we ensure that mInvalidPaintedContent is updated according to the
// scroll position as of the most recent paint.
if (!FuzzyEqual(data->mXScale, mParameters.mXScale, 0.00001f) ||
!FuzzyEqual(data->mYScale, mParameters.mYScale, 0.00001f) ||
data->mAppUnitsPerDevPixel != mAppUnitsPerDevPixel) {
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Recycled layer %p changed scale\n", aLayer);
}
#endif
InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
"recycled layer changed state");
didResetScrollPositionForLayerPixelAlignment = true;
}
if (!data->mRegionToInvalidate.IsEmpty()) {
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Invalidating deleted frame content from layer %p\n",
aLayer);
}
#endif
aLayer->InvalidateRegion(data->mRegionToInvalidate);
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
nsAutoCString str;
AppendToString(str, data->mRegionToInvalidate);
printf_stderr("Invalidating layer %p: %s\n", aLayer, str.get());
}
#endif
data->mRegionToInvalidate.SetEmpty();
}
return data;
}
void ContainerState::PreparePaintedLayerForUse(
PaintedLayer* aLayer, PaintedDisplayItemLayerUserData* aData,
AnimatedGeometryRoot* aAnimatedGeometryRoot,
const nsIFrame* aReferenceFrame, const nsPoint& aTopLeft,
bool didResetScrollPositionForLayerPixelAlignment) {
aData->mXScale = mParameters.mXScale;
aData->mYScale = mParameters.mYScale;
aData->mLastAnimatedGeometryRootOrigin = aData->mAnimatedGeometryRootOrigin;
aData->mAnimatedGeometryRootOrigin = aTopLeft;
aData->mAppUnitsPerDevPixel = mAppUnitsPerDevPixel;
aLayer->SetAllowResidualTranslation(mParameters.AllowResidualTranslation());
// Set up transform so that 0,0 in the PaintedLayer corresponds to the
// (pixel-snapped) top-left of the aAnimatedGeometryRoot.
nsPoint offset =
(*aAnimatedGeometryRoot)->GetOffsetToCrossDoc(aReferenceFrame);
nscoord appUnitsPerDevPixel =
(*aAnimatedGeometryRoot)->PresContext()->AppUnitsPerDevPixel();
gfxPoint scaledOffset(
NSAppUnitsToDoublePixels(offset.x, appUnitsPerDevPixel) *
mParameters.mXScale,
NSAppUnitsToDoublePixels(offset.y, appUnitsPerDevPixel) *
mParameters.mYScale);
// We call RoundToMatchResidual here so that the residual after rounding
// is close to aData->mAnimatedGeometryRootPosition if possible.
nsIntPoint pixOffset(
RoundToMatchResidual(scaledOffset.x,
aData->mAnimatedGeometryRootPosition.x),
RoundToMatchResidual(scaledOffset.y,
aData->mAnimatedGeometryRootPosition.y));
aData->mTranslation = pixOffset;
pixOffset += mParameters.mOffset;
Matrix matrix = Matrix::Translation(pixOffset.x, pixOffset.y);
aLayer->SetBaseTransform(Matrix4x4::From2D(matrix));
aData->mVisibilityComputedRegion.SetEmpty();
// 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;
const bool disableAlpha =
mParameters.mDisableSubpixelAntialiasingInDescendants;
if (aData->mDisabledAlpha != disableAlpha) {
aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
"change of subpixel-AA");
aData->mDisabledAlpha = disableAlpha;
return;
}
// FIXME: Temporary workaround for bug 681192 and bug 724786.
#ifndef MOZ_WIDGET_ANDROID
// If it has changed, then we need to invalidate the entire layer since the
// pixels in the layer buffer have the content at a (subpixel) offset
// from what we need.
if (!animatedGeometryRootTopLeft.WithinEpsilonOf(
aData->mAnimatedGeometryRootPosition, SUBPIXEL_OFFSET_EPSILON)) {
aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
InvalidateEntirePaintedLayer(aLayer, aAnimatedGeometryRoot,
"subpixel offset");
} else if (didResetScrollPositionForLayerPixelAlignment) {
aData->mAnimatedGeometryRootPosition = animatedGeometryRootTopLeft;
}
#else
Unused << didResetScrollPositionForLayerPixelAlignment;
#endif
}
#if defined(DEBUG) || defined(MOZ_DUMP_PAINTING)
/**
* Returns the appunits per dev pixel for the item's frame
*/
static int32_t AppUnitsPerDevPixel(nsDisplayItem* aItem) {
// The underlying frame for zoom items is the root frame of the subdocument.
// But zoom display items report their bounds etc using the parent document's
// APD because zoom items act as a conversion layer between the two different
// APDs.
if (aItem->GetType() == DisplayItemType::TYPE_ZOOM) {
return static_cast<nsDisplayZoom*>(aItem)->GetParentAppUnitsPerDevPixel();
}
return aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
}
#endif
/**
* Set the visible region for aLayer.
* aOuterVisibleRegion is the visible region relative to the parent layer.
* aLayerContentsVisibleRect, if non-null, is a rectangle in the layer's
* own coordinate system to which the layer's visible region is restricted.
* Consumes *aOuterVisibleRegion.
*/
static void SetOuterVisibleRegion(
Layer* aLayer, nsIntRegion* aOuterVisibleRegion,
const nsIntRect* aLayerContentsVisibleRect = nullptr,
bool aOuterUntransformed = false) {
Matrix4x4 transform = aLayer->GetTransform();
Matrix transform2D;
if (aOuterUntransformed) {
if (aLayerContentsVisibleRect) {
aOuterVisibleRegion->And(*aOuterVisibleRegion,
*aLayerContentsVisibleRect);
}
} else if (transform.Is2D(&transform2D) &&
!transform2D.HasNonIntegerTranslation()) {
aOuterVisibleRegion->MoveBy(-int(transform2D._31), -int(transform2D._32));
if (aLayerContentsVisibleRect) {
aOuterVisibleRegion->And(*aOuterVisibleRegion,
*aLayerContentsVisibleRect);
}
} else {
nsIntRect outerRect = aOuterVisibleRegion->GetBounds();
// if 'transform' is not invertible, then nothing will be displayed
// for the layer, so it doesn't really matter what we do here
Rect outerVisible(outerRect.x, outerRect.y, outerRect.width,
outerRect.height);
transform.Invert();
Rect layerContentsVisible = Rect::MaxIntRect();
if (aLayerContentsVisibleRect) {
NS_ASSERTION(aLayerContentsVisibleRect->width >= 0 &&
aLayerContentsVisibleRect->height >= 0,
"Bad layer contents rectangle");
// restrict to aLayerContentsVisibleRect before call GfxRectToIntRect,
// in case layerVisible is extremely large (as it can be when
// projecting through the inverse of a 3D transform)
layerContentsVisible = Rect(
aLayerContentsVisibleRect->x, aLayerContentsVisibleRect->y,
aLayerContentsVisibleRect->width, aLayerContentsVisibleRect->height);
}
Rect layerVisible =
transform.ProjectRectBounds(outerVisible, layerContentsVisible);
layerVisible.RoundOut();
IntRect intRect;
if (!layerVisible.ToIntRect(&intRect)) {
intRect = IntRect::MaxIntRect();
}
*aOuterVisibleRegion = intRect;
}
aLayer->SetVisibleRegion(
LayerIntRegion::FromUnknownRegion(*aOuterVisibleRegion));
}
void ContainerState::SetOuterVisibleRegionForLayer(
Layer* aLayer, const nsIntRegion& aOuterVisibleRegion,
const nsIntRect* aLayerContentsVisibleRect,
bool aOuterUntransformed) const {
nsIntRegion visRegion = aOuterVisibleRegion;
if (!aOuterUntransformed) {
visRegion.MoveBy(mParameters.mOffset);
}
SetOuterVisibleRegion(aLayer, &visRegion, aLayerContentsVisibleRect,
aOuterUntransformed);
}
nscolor ContainerState::FindOpaqueBackgroundColorInLayer(
const PaintedLayerData* aData, const nsIntRect& aRect,
bool* aOutIntersectsLayer) const {
*aOutIntersectsLayer = true;
// Scan the candidate's display items.
nsIntRect deviceRect = aRect;
nsRect appUnitRect = ToAppUnits(deviceRect, mAppUnitsPerDevPixel);
appUnitRect.ScaleInverseRoundOut(mParameters.mXScale, mParameters.mYScale);
for (auto& assignedItem : Reversed(aData->mAssignedDisplayItems)) {
if (assignedItem.HasOpacity() || assignedItem.HasTransform()) {
// We cannot easily calculate the opaque background color for items inside
// a flattened effect.
continue;
}
if (IsEffectEndMarker(assignedItem.mType)) {
// An optimization: the underlying display item for effect markers is the
// same for both start and end markers. Skip the effect end markers.
continue;
}
nsDisplayItem* item = assignedItem.mItem;
bool snap;
nsRect bounds = item->GetBounds(mBuilder, &snap);
if (snap && mSnappingEnabled) {
nsIntRect snappedBounds = ScaleToNearestPixels(bounds);
if (!snappedBounds.Intersects(deviceRect)) continue;
if (!snappedBounds.Contains(deviceRect)) return NS_RGBA(0, 0, 0, 0);
} else {
// The layer's visible rect is already (close enough to) pixel
// aligned, so no need to round out and in here.
if (!bounds.Intersects(appUnitRect)) continue;
if (!bounds.Contains(appUnitRect)) return NS_RGBA(0, 0, 0, 0);
}
if (item->IsInvisibleInRect(appUnitRect)) {
continue;
}
if (item->GetClip().IsRectAffectedByClip(deviceRect, mParameters.mXScale,
mParameters.mYScale,
mAppUnitsPerDevPixel)) {
return NS_RGBA(0, 0, 0, 0);
}
MOZ_ASSERT(!assignedItem.HasOpacity() && !assignedItem.HasTransform());
Maybe<nscolor> color = item->IsUniform(mBuilder);
if (color && NS_GET_A(*color) == 255) {
return *color;
}
return NS_RGBA(0, 0, 0, 0);
}
*aOutIntersectsLayer = false;
return NS_RGBA(0, 0, 0, 0);
}
nscolor PaintedLayerDataNode::FindOpaqueBackgroundColor(
const nsIntRegion& aTargetVisibleRegion, int32_t aUnderIndex) const {
if (aUnderIndex == ABOVE_TOP) {
aUnderIndex = mPaintedLayerDataStack.Length();
}
for (int32_t i = aUnderIndex - 1; i >= 0; --i) {
const PaintedLayerData* candidate = &mPaintedLayerDataStack[i];
if (candidate->VisibleAboveRegionIntersects(aTargetVisibleRegion)) {
// Some non-PaintedLayer content between target and candidate; this is
// hopeless
return NS_RGBA(0, 0, 0, 0);
}
if (!candidate->VisibleRegionIntersects(aTargetVisibleRegion)) {
// The layer doesn't intersect our target, ignore it and move on
continue;
}
bool intersectsLayer = true;
nsIntRect rect = aTargetVisibleRegion.GetBounds();
nscolor color = mTree.ContState().FindOpaqueBackgroundColorInLayer(
candidate, rect, &intersectsLayer);
if (!intersectsLayer) {
continue;
}
return color;
}
if (mAllDrawingAboveBackground ||
!mVisibleAboveBackgroundRegion.Intersect(aTargetVisibleRegion)
.IsEmpty()) {
// Some non-PaintedLayer content is between this node's background and
// target.
return NS_RGBA(0, 0, 0, 0);
}
return FindOpaqueBackgroundColorInParentNode();
}
nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorCoveringEverything()
const {
if (!mPaintedLayerDataStack.IsEmpty() || mAllDrawingAboveBackground ||
!mVisibleAboveBackgroundRegion.IsEmpty()) {
return NS_RGBA(0, 0, 0, 0);
}
return FindOpaqueBackgroundColorInParentNode();
}
nscolor PaintedLayerDataNode::FindOpaqueBackgroundColorInParentNode() const {
if (mParent) {
if (mHasClip) {
// Check whether our parent node has uniform content behind our whole
// clip.
// There's one tricky case here: If our parent node is also a scrollable,
// and is currently scrolled in such a way that this inner one is
// clipped by it, then it's not really clear how we should determine
// whether we have a uniform background in the parent: There might be
// non-uniform content in the parts that our scroll port covers in the
// parent and that are currently outside the parent's clip.
// For now, we'll fail to pull a background color in that case.
return mParent->FindOpaqueBackgroundColor(mClipRect);
}
return mParent->FindOpaqueBackgroundColorCoveringEverything();
}
// We are the root.
return mTree.UniformBackgroundColor();
}
bool PaintedLayerData::CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder) {
if (!mImage) {
return false;
}
return mImage->CanOptimizeToImageLayer(mLayer->Manager(), aBuilder);
}
already_AddRefed<ImageContainer> PaintedLayerData::GetContainerForImageLayer(
nsDisplayListBuilder* aBuilder) {
if (!mImage) {
return nullptr;
}
return mImage->GetContainer(mLayer->Manager(), aBuilder);
}
PaintedLayerDataNode::PaintedLayerDataNode(
PaintedLayerDataTree& aTree, PaintedLayerDataNode* aParent,
AnimatedGeometryRoot* aAnimatedGeometryRoot)
: mTree(aTree),
mParent(aParent),
mAnimatedGeometryRoot(aAnimatedGeometryRoot),
mAllDrawingAboveBackground(false) {
MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
mTree.Builder()->RootReferenceFrame(), *mAnimatedGeometryRoot));
mHasClip = mTree.IsClippedWithRespectToParentAnimatedGeometryRoot(
mAnimatedGeometryRoot, &mClipRect);
}
PaintedLayerDataNode::~PaintedLayerDataNode() {
MOZ_ASSERT(mPaintedLayerDataStack.IsEmpty());
MOZ_ASSERT(mChildren.IsEmpty());
}
PaintedLayerDataNode* PaintedLayerDataNode::AddChildNodeFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
MOZ_ASSERT(aAnimatedGeometryRoot->mParentAGR == mAnimatedGeometryRoot);
UniquePtr<PaintedLayerDataNode> child =
MakeUnique<PaintedLayerDataNode>(mTree, this, aAnimatedGeometryRoot);
mChildren.AppendElement(std::move(child));
return mChildren.LastElement().get();
}
template <typename NewPaintedLayerCallbackType>
PaintedLayerData* PaintedLayerDataNode::FindPaintedLayerFor(
const nsIntRect& aVisibleRect, const bool aBackfaceHidden,
const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
if (!mPaintedLayerDataStack.IsEmpty()) {
PaintedLayerData* lowestUsableLayer = nullptr;
for (auto& data : Reversed(mPaintedLayerDataStack)) {
if (data.mVisibleAboveRegion.Intersects(aVisibleRect)) {
break;
}
if (data.mBackfaceHidden == aBackfaceHidden && data.mASR == aASR &&
data.mClipChain == aClipChain) {
lowestUsableLayer = &data;
}
// Also check whether the event-regions intersect the visible rect,
// unless we're in an inactive layer, in which case the event-regions
// will be hoisted out into their own layer.
// For performance reasons, we check the intersection with the bounds
// of the event-regions.
if (!mTree.ContState().IsInInactiveLayer() &&
(data.mScaledHitRegionBounds.Intersects(aVisibleRect) ||
data.mScaledMaybeHitRegionBounds.Intersects(aVisibleRect))) {
break;
}
// If the visible region intersects with the current layer then we
// can't possibly use any of the layers below it, so stop the search
// now.
//
// If we're trying to minimize painted layer size and we don't
// intersect the current visible region, then make sure we don't
// use this painted layer.
if (data.mVisibleRegion.Intersects(aVisibleRect)) {
break;
}
if (StaticPrefs::layout_smaller_painted_layers()) {
lowestUsableLayer = nullptr;
}
}
if (lowestUsableLayer) {
return lowestUsableLayer;
}
}
PaintedLayerData* data = mPaintedLayerDataStack.AppendElement();
aNewPaintedLayerCallback(data);
return data;
}
void PaintedLayerDataNode::FinishChildrenIntersecting(const nsIntRect& aRect) {
for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
if (mChildren[i]->Intersects(aRect)) {
mChildren[i]->Finish(true);
mChildren.RemoveElementAt(i);
}
}
}
void PaintedLayerDataNode::FinishAllChildren(
bool aThisNodeNeedsAccurateVisibleAboveRegion) {
for (int32_t i = mChildren.Length() - 1; i >= 0; i--) {
mChildren[i]->Finish(aThisNodeNeedsAccurateVisibleAboveRegion);
}
mChildren.Clear();
}
void PaintedLayerDataNode::Finish(bool aParentNeedsAccurateVisibleAboveRegion) {
// Skip "visible above region" maintenance, because this node is going away.
FinishAllChildren(false);
PopAllPaintedLayerData();
if (mParent && aParentNeedsAccurateVisibleAboveRegion) {
if (mHasClip) {
mParent->AddToVisibleAboveRegion(mClipRect);
} else {
mParent->SetAllDrawingAbove();
}
}
mTree.NodeWasFinished(mAnimatedGeometryRoot);
}
void PaintedLayerDataNode::AddToVisibleAboveRegion(const nsIntRect& aRect) {
nsIntRegion& visibleAboveRegion =
mPaintedLayerDataStack.IsEmpty()
? mVisibleAboveBackgroundRegion
: mPaintedLayerDataStack.LastElement().mVisibleAboveRegion;
visibleAboveRegion.Or(visibleAboveRegion, aRect);
visibleAboveRegion.SimplifyOutward(8);
}
void PaintedLayerDataNode::SetAllDrawingAbove() {
PopAllPaintedLayerData();
mAllDrawingAboveBackground = true;
mVisibleAboveBackgroundRegion.SetEmpty();
}
void PaintedLayerDataNode::PopAllPaintedLayerData() {
for (int32_t index = mPaintedLayerDataStack.Length() - 1; index >= 0;
index--) {
PaintedLayerData& data = mPaintedLayerDataStack[index];
mTree.ContState().FinishPaintedLayerData(data, [this, &data, index]() {
return this->FindOpaqueBackgroundColor(data.mVisibleRegion, index);
});
}
mPaintedLayerDataStack.Clear();
}
void PaintedLayerDataTree::InitializeForInactiveLayer(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
mForInactiveLayer = true;
mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
}
nsDisplayListBuilder* PaintedLayerDataTree::Builder() const {
return mContainerState.Builder();
}
void PaintedLayerDataTree::Finish() {
if (mRoot) {
mRoot->Finish(false);
}
MOZ_ASSERT(mNodes.Count() == 0);
mRoot.reset();
}
void PaintedLayerDataTree::NodeWasFinished(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
mNodes.Remove(aAnimatedGeometryRoot);
}
void PaintedLayerDataTree::AddingOwnLayer(
AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect,
nscolor* aOutUniformBackgroundColor) {
PaintedLayerDataNode* node = nullptr;
if (mForInactiveLayer) {
node = mRoot.ptr();
} else {
FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, aRect);
node = EnsureNodeFor(aAnimatedGeometryRoot);
}
if (aRect) {
if (aOutUniformBackgroundColor) {
*aOutUniformBackgroundColor = node->FindOpaqueBackgroundColor(*aRect);
}
node->AddToVisibleAboveRegion(*aRect);
} else {
if (aOutUniformBackgroundColor) {
*aOutUniformBackgroundColor =
node->FindOpaqueBackgroundColorCoveringEverything();
}
node->SetAllDrawingAbove();
}
}
template <typename NewPaintedLayerCallbackType>
PaintedLayerData* PaintedLayerDataTree::FindPaintedLayerFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot, const ActiveScrolledRoot* aASR,
const DisplayItemClipChain* aClipChain, const nsIntRect& aVisibleRect,
const bool aBackfaceHidden,
NewPaintedLayerCallbackType aNewPaintedLayerCallback) {
const nsIntRect* bounds = &aVisibleRect;
PaintedLayerDataNode* node = nullptr;
if (mForInactiveLayer) {
node = mRoot.ptr();
} else {
FinishPotentiallyIntersectingNodes(aAnimatedGeometryRoot, bounds);
node = EnsureNodeFor(aAnimatedGeometryRoot);
}
PaintedLayerData* data =
node->FindPaintedLayerFor(aVisibleRect, aBackfaceHidden, aASR, aClipChain,
aNewPaintedLayerCallback);
return data;
}
void PaintedLayerDataTree::FinishPotentiallyIntersectingNodes(
AnimatedGeometryRoot* aAnimatedGeometryRoot, const nsIntRect* aRect) {
AnimatedGeometryRoot* ancestorThatIsChildOfCommonAncestor = nullptr;
PaintedLayerDataNode* ancestorNode = FindNodeForAncestorAnimatedGeometryRoot(
aAnimatedGeometryRoot, &ancestorThatIsChildOfCommonAncestor);
if (!ancestorNode) {
// None of our ancestors are in the tree. This should only happen if this
// is the very first item we're looking at.
MOZ_ASSERT(!mRoot);
return;
}
if (ancestorNode->GetAnimatedGeometryRoot() == aAnimatedGeometryRoot) {
// aAnimatedGeometryRoot already has a node in the tree.
// This is the common case.
MOZ_ASSERT(!ancestorThatIsChildOfCommonAncestor);
if (aRect) {
ancestorNode->FinishChildrenIntersecting(*aRect);
} else {
ancestorNode->FinishAllChildren();
}
return;
}
// We have found an existing ancestor, but it's a proper ancestor of our
// animated geometry root.
// ancestorThatIsChildOfCommonAncestor is the last animated geometry root
// encountered on the way up from aAnimatedGeometryRoot to ancestorNode.
MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor);
MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(
*ancestorThatIsChildOfCommonAncestor, *aAnimatedGeometryRoot));
MOZ_ASSERT(ancestorThatIsChildOfCommonAncestor->mParentAGR ==
ancestorNode->GetAnimatedGeometryRoot());
// ancestorThatIsChildOfCommonAncestor is not in the tree yet!
MOZ_ASSERT(!mNodes.Get(ancestorThatIsChildOfCommonAncestor));
// We're about to add a node for ancestorThatIsChildOfCommonAncestor, so we
// finish all intersecting siblings.
nsIntRect clip;
if (IsClippedWithRespectToParentAnimatedGeometryRoot(
ancestorThatIsChildOfCommonAncestor, &clip)) {
ancestorNode->FinishChildrenIntersecting(clip);
} else {
ancestorNode->FinishAllChildren();
}
}
PaintedLayerDataNode* PaintedLayerDataTree::EnsureNodeFor(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
MOZ_ASSERT(aAnimatedGeometryRoot);
PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
if (node) {
return node;
}
AnimatedGeometryRoot* parentAnimatedGeometryRoot =
aAnimatedGeometryRoot->mParentAGR;
if (!parentAnimatedGeometryRoot) {
MOZ_ASSERT(!mRoot);
MOZ_ASSERT(*aAnimatedGeometryRoot == Builder()->RootReferenceFrame());
mRoot.emplace(*this, nullptr, aAnimatedGeometryRoot);
node = mRoot.ptr();
} else {
PaintedLayerDataNode* parentNode =
EnsureNodeFor(parentAnimatedGeometryRoot);
MOZ_ASSERT(parentNode);
node = parentNode->AddChildNodeFor(aAnimatedGeometryRoot);
}
MOZ_ASSERT(node);
mNodes.InsertOrUpdate(aAnimatedGeometryRoot, node);
return node;
}
bool PaintedLayerDataTree::IsClippedWithRespectToParentAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot, nsIntRect* aOutClip) {
if (mForInactiveLayer) {
return false;
}
nsIScrollableFrame* scrollableFrame =
nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
if (!scrollableFrame) {
return false;
}
nsIFrame* scrollFrame = do_QueryFrame(scrollableFrame);
nsRect scrollPort = scrollableFrame->GetScrollPortRect() +
Builder()->ToReferenceFrame(scrollFrame);
*aOutClip = mContainerState.ScaleToNearestPixels(scrollPort);
return true;
}
PaintedLayerDataNode*
PaintedLayerDataTree::FindNodeForAncestorAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot,
AnimatedGeometryRoot** aOutAncestorChild) {
if (!aAnimatedGeometryRoot) {
return nullptr;
}
PaintedLayerDataNode* node = mNodes.Get(aAnimatedGeometryRoot);
if (node) {
return node;
}
*aOutAncestorChild = aAnimatedGeometryRoot;
return FindNodeForAncestorAnimatedGeometryRoot(
aAnimatedGeometryRoot->mParentAGR, aOutAncestorChild);
}
static bool CanOptimizeAwayPaintedLayer(PaintedLayerData* aData,
FrameLayerBuilder* aLayerBuilder) {
if (!aLayerBuilder->IsBuildingRetainedLayers()) {
return false;
}
// If there's no painted layer with valid content in it that we can reuse,
// always create a color or image layer (and potentially throw away an
// existing completely invalid painted layer).
if (aData->mLayer->GetValidRegion().IsEmpty()) {
return true;
}
// There is an existing painted layer we can reuse. Throwing it away can make
// compositing cheaper (see bug 946952), but it might cause us to re-allocate
// the painted layer frequently due to an animation. So we only discard it if
// we're in tree compression mode, which is triggered at a low frequency.
return aLayerBuilder->CheckInLayerTreeCompressionMode();
}
#ifdef DEBUG
static int32_t FindIndexOfLayerIn(nsTArray<NewLayerEntry>& aArray,
Layer* aLayer) {
for (uint32_t i = 0; i < aArray.Length(); ++i) {
if (aArray[i].mLayer == aLayer) {
return i;
}
}
return -1;
}
#endif
already_AddRefed<Layer> ContainerState::PrepareImageLayer(
PaintedLayerData* aData) {
RefPtr<ImageContainer> imageContainer =
aData->GetContainerForImageLayer(mBuilder);
if (!imageContainer) {
return nullptr;
}
RefPtr<ImageLayer> imageLayer = CreateOrRecycleImageLayer(aData->mLayer);
imageLayer->SetContainer(imageContainer);
aData->mImage->ConfigureLayer(imageLayer, mParameters);
imageLayer->SetPostScale(mParameters.mXScale, mParameters.mYScale);
if (aData->mItemClip->HasClip()) {
ParentLayerIntRect clip = ViewAs<ParentLayerPixel>(
ScaleToNearestPixels(aData->mItemClip->GetClipRect()));
clip.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
imageLayer->SetClipRect(Some(clip));
} else {
imageLayer->SetClipRect(Nothing());
}
FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected image layer=%p\n",
imageLayer.get());
return imageLayer.forget();
}
already_AddRefed<Layer> ContainerState::PrepareColorLayer(
PaintedLayerData* aData) {
RefPtr<ColorLayer> colorLayer = CreateOrRecycleColorLayer(aData->mLayer);
colorLayer->SetColor(ToDeviceColor(aData->mSolidColor));
// Copy transform
colorLayer->SetBaseTransform(aData->mLayer->GetBaseTransform());
colorLayer->SetPostScale(aData->mLayer->GetPostXScale(),
aData->mLayer->GetPostYScale());
nsIntRect visibleRect = aData->mVisibleRegion.GetBounds();
visibleRect.MoveBy(-GetTranslationForPaintedLayer(aData->mLayer));
colorLayer->SetBounds(visibleRect);
colorLayer->SetClipRect(Nothing());
FLB_LOG_PAINTED_LAYER_DECISION(aData, " Selected color layer=%p\n",
colorLayer.get());
return colorLayer.forget();
}
static void SetBackfaceHiddenForLayer(bool aBackfaceHidden, Layer* aLayer) {
if (aBackfaceHidden) {
aLayer->SetContentFlags(aLayer->GetContentFlags() |
Layer::CONTENT_BACKFACE_HIDDEN);
} else {
aLayer->SetContentFlags(aLayer->GetContentFlags() &
~Layer::CONTENT_BACKFACE_HIDDEN);
}
}
template <typename FindOpaqueBackgroundColorCallbackType>
void ContainerState::FinishPaintedLayerData(
PaintedLayerData& aData,
FindOpaqueBackgroundColorCallbackType aFindOpaqueBackgroundColor) {
PaintedLayerData* data = &aData;
if (!data->mLayer) {
// No layer was recycled, so we create a new one.
RefPtr<PaintedLayer> paintedLayer = CreatePaintedLayer(data);
data->mLayer = paintedLayer;
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
"Layer already in list???");
mNewChildLayers[data->mNewChildLayersIndex].mLayer =
std::move(paintedLayer);
}
auto* userData = GetPaintedDisplayItemLayerUserData(data->mLayer);
NS_ASSERTION(userData, "where did our user data go?");
userData->mLastItemCount = data->mAssignedDisplayItems.size();
NewLayerEntry* newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex];
RefPtr<Layer> layer;
bool canOptimizeToImageLayer = data->CanOptimizeToImageLayer(mBuilder);
FLB_LOG_PAINTED_LAYER_DECISION(data, "Selecting layer for pld=%p\n", data);
FLB_LOG_PAINTED_LAYER_DECISION(
data, " Solid=%i, hasImage=%c, canOptimizeAwayPaintedLayer=%i\n",
data->mIsSolidColorInVisibleRegion, canOptimizeToImageLayer ? 'y' : 'n',
CanOptimizeAwayPaintedLayer(data, mLayerBuilder));
if ((data->mIsSolidColorInVisibleRegion || canOptimizeToImageLayer) &&
CanOptimizeAwayPaintedLayer(data, mLayerBuilder)) {
NS_ASSERTION(
!(data->mIsSolidColorInVisibleRegion && canOptimizeToImageLayer),
"Can't be a solid color as well as an image!");
layer = canOptimizeToImageLayer ? PrepareImageLayer(data)
: PrepareColorLayer(data);
if (layer) {
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
"Layer already in list???");
NS_ASSERTION(newLayerEntry->mLayer == data->mLayer,
"Painted layer at wrong index");
// Store optimized layer in reserved slot
NewLayerEntry* paintedLayerEntry = newLayerEntry;
newLayerEntry = &mNewChildLayers[data->mNewChildLayersIndex + 1];
NS_ASSERTION(!newLayerEntry->mLayer, "Slot already occupied?");
newLayerEntry->mLayer = layer;
newLayerEntry->mAnimatedGeometryRoot = data->mAnimatedGeometryRoot;
newLayerEntry->mASR = paintedLayerEntry->mASR;
newLayerEntry->mClipChain = paintedLayerEntry->mClipChain;
newLayerEntry->mScrollMetadataASR = paintedLayerEntry->mScrollMetadataASR;
// Hide the PaintedLayer. We leave it in the layer tree so that we
// can find and recycle it later.
ParentLayerIntRect emptyRect;
data->mLayer->SetClipRect(Some(emptyRect));
data->mLayer->SetVisibleRegion(LayerIntRegion());
data->mLayer->InvalidateWholeLayer();
data->mLayer->SetEventRegions(EventRegions());
}
}
if (!layer) {
// We couldn't optimize to an image layer or a color layer above.
layer = data->mLayer;
layer->SetClipRect(Nothing());
FLB_LOG_PAINTED_LAYER_DECISION(data, " Selected painted layer=%p\n",
layer.get());
}
for (auto& item : data->mAssignedDisplayItems) {
MOZ_ASSERT(item.mItem->GetType() !=
DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);
if (IsEffectEndMarker(item.mType)) {
// Do not invalidate for end markers.
continue;
}
InvalidateForLayerChange(item.mItem, data->mLayer, item.mDisplayItemData);
mLayerBuilder->AddPaintedDisplayItem(data, item, layer);
item.mDisplayItemData = nullptr;
}
if (mLayerBuilder->IsBuildingRetainedLayers()) {
newLayerEntry->mVisibleRegion = data->mVisibleRegion;
newLayerEntry->mOpaqueRegion = data->mOpaqueRegion;
newLayerEntry->mHideAllLayersBelow = data->mHideAllLayersBelow;
newLayerEntry->mOpaqueForAnimatedGeometryRootParent =
data->mOpaqueForAnimatedGeometryRootParent;
} else {
SetOuterVisibleRegionForLayer(layer, data->mVisibleRegion);
}
#ifdef MOZ_DUMP_PAINTING
if (!data->mLog.IsEmpty()) {
PaintedLayerData* containingPld =
mLayerBuilder->GetContainingPaintedLayerData();
if (containingPld && containingPld->mLayer) {
containingPld->mLayer->AddExtraDumpInfo(nsCString(data->mLog));
} else {
layer->AddExtraDumpInfo(nsCString(data->mLog));
}
}
#endif
mLayerBuilder->AddPaintedLayerItemsEntry(userData);
nsIntRegion transparentRegion;
transparentRegion.Sub(data->mVisibleRegion, data->mOpaqueRegion);
bool isOpaque = transparentRegion.IsEmpty();
// For translucent PaintedLayers, try to find an opaque background
// color that covers the entire area beneath it so we can pull that
// color into this layer to make it opaque.
if (layer == data->mLayer) {
nscolor backgroundColor = NS_RGBA(0, 0, 0, 0);
if (!isOpaque) {
backgroundColor = aFindOpaqueBackgroundColor();
if (NS_GET_A(backgroundColor) == 255) {
isOpaque = true;
}
}
// Store the background color
if (userData->mForcedBackgroundColor != backgroundColor) {
// Invalidate the entire target PaintedLayer since we're changing
// the background color
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr(
"Forced background color has changed from #%08X to #%08X "
"on layer %p\n",
userData->mForcedBackgroundColor, backgroundColor, data->mLayer);
nsAutoCString str;
AppendToString(str, data->mLayer->GetValidRegion());
printf_stderr("Invalidating layer %p: %s\n", data->mLayer, str.get());
}
#endif
data->mLayer->InvalidateWholeLayer();
}
userData->mForcedBackgroundColor = backgroundColor;
} 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);
userData->mItems = std::move(data->mAssignedDisplayItems);
userData->mContainerLayerFrame = GetContainerFrame();
PaintedLayerData* containingPaintedLayerData =
mLayerBuilder->GetContainingPaintedLayerData();
// If we're building layers for an inactive layer, the event regions are
// clipped to the inactive layer's clip prior to being combined into the
// event regions of the containing PLD.
// For the dispatch-to-content and maybe-hit regions, rounded corners on
// the clip are ignored, since these are approximate regions. For the
// remaining regions, rounded corners in the clip cause the region to
// be combined into the corresponding "imprecise" region of the
// containing's PLD (e.g. the maybe-hit region instead of the hit region).
const DisplayItemClip* inactiveLayerClip =
mLayerBuilder->GetInactiveLayerClip();
if (containingPaintedLayerData) {
if (!data->mDispatchToContentHitRegion.GetBounds().IsEmpty()) {
nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
mContainerReferenceFrame,
data->mDispatchToContentHitRegion.GetBounds(),
containingPaintedLayerData->mReferenceFrame);
if (inactiveLayerClip) {
rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
}
containingPaintedLayerData->mDispatchToContentHitRegion.Or(
containingPaintedLayerData->mDispatchToContentHitRegion, rect);
containingPaintedLayerData->mDispatchToContentHitRegion.SimplifyOutward(
8);
if (data->mDTCRequiresTargetConfirmation) {
containingPaintedLayerData->mDTCRequiresTargetConfirmation = true;
}
}
if (!data->mMaybeHitRegion.GetBounds().IsEmpty()) {
nsRect rect = nsLayoutUtils::TransformFrameRectToAncestor(
mContainerReferenceFrame, data->mMaybeHitRegion.GetBounds(),
containingPaintedLayerData->mReferenceFrame);
if (inactiveLayerClip) {
rect = inactiveLayerClip->ApplyNonRoundedIntersection(rect);
}
containingPaintedLayerData->mMaybeHitRegion.Or(
containingPaintedLayerData->mMaybeHitRegion, rect);
containingPaintedLayerData->mMaybeHitRegion.SimplifyOutward(8);
}
Maybe<Matrix4x4Flagged> matrixCache;
nsLayoutUtils::TransformToAncestorAndCombineRegions(
data->mHitRegion, mContainerReferenceFrame,
containingPaintedLayerData->mReferenceFrame,
&containingPaintedLayerData->mHitRegion,
&containingPaintedLayerData->mMaybeHitRegion, &matrixCache,
inactiveLayerClip);
// See the comment in nsDisplayList::AddFrame, where the touch action
// regions are handled. The same thing applies here.
bool alreadyHadRegions =
!containingPaintedLayerData->mNoActionRegion.IsEmpty() ||
!containingPaintedLayerData->mHorizontalPanRegion.IsEmpty() ||
!containingPaintedLayerData->mVerticalPanRegion.IsEmpty();
nsLayoutUtils::TransformToAncestorAndCombineRegions(
data->mNoActionRegion, mContainerReferenceFrame,
containingPaintedLayerData->mReferenceFrame,
&containingPaintedLayerData->mNoActionRegion,
&containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
inactiveLayerClip);
nsLayoutUtils::TransformToAncestorAndCombineRegions(
data->mHorizontalPanRegion, mContainerReferenceFrame,
containingPaintedLayerData->mReferenceFrame,
&containingPaintedLayerData->mHorizontalPanRegion,
&containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
inactiveLayerClip);
nsLayoutUtils::TransformToAncestorAndCombineRegions(
data->mVerticalPanRegion, mContainerReferenceFrame,
containingPaintedLayerData->mReferenceFrame,
&containingPaintedLayerData->mVerticalPanRegion,
&containingPaintedLayerData->mDispatchToContentHitRegion, &matrixCache,
inactiveLayerClip);
if (alreadyHadRegions) {
containingPaintedLayerData->mDispatchToContentHitRegion.OrWith(
containingPaintedLayerData->CombinedTouchActionRegion());
}
containingPaintedLayerData->HitRegionsUpdated();
} else {
EventRegions regions(
ScaleRegionToOutsidePixels(data->mHitRegion),
ScaleRegionToOutsidePixels(data->mMaybeHitRegion),
ScaleRegionToOutsidePixels(data->mDispatchToContentHitRegion),
ScaleRegionToOutsidePixels(data->mNoActionRegion),
ScaleRegionToOutsidePixels(data->mHorizontalPanRegion),
ScaleRegionToOutsidePixels(data->mVerticalPanRegion),
data->mDTCRequiresTargetConfirmation);
Matrix mat = layer->GetTransform().As2D();
mat.Invert();
regions.ApplyTranslationAndScale(mat._31, mat._32, mat._11, mat._22);
layer->SetEventRegions(regions);
}
SetBackfaceHiddenForLayer(data->mBackfaceHidden, data->mLayer);
if (layer != data->mLayer) {
SetBackfaceHiddenForLayer(data->mBackfaceHidden, layer);
}
}
static bool IsItemAreaInWindowOpaqueRegion(
nsDisplayListBuilder* aBuilder, nsDisplayItem* aItem,
const nsRect& aComponentAlphaBounds) {
if (!aItem->Frame()->PresContext()->IsChrome()) {
// Assume that Web content is always in the window opaque region.
return true;
}
if (aItem->ReferenceFrame() != aBuilder->RootReferenceFrame()) {
// aItem is probably in some transformed subtree.
// We're not going to bother figuring out where this landed, we're just
// going to assume it might have landed over a transparent part of
// the window.
return false;
}
return aBuilder->GetWindowOpaqueRegion().Contains(aComponentAlphaBounds);
}
void PaintedLayerData::UpdateEffectStatus(DisplayItemEntryType aType,
nsTArray<size_t>& aOpacityIndices) {
switch (aType) {
case DisplayItemEntryType::PushOpacity:
// The index of the new assigned display item in |mAssignedDisplayItems|
// array will be the current length of the array.
aOpacityIndices.AppendElement(mAssignedDisplayItems.size());
break;
case DisplayItemEntryType::PopOpacity:
MOZ_ASSERT(!aOpacityIndices.IsEmpty());
aOpacityIndices.RemoveLastElement();
break;
#ifdef DEBUG
case DisplayItemEntryType::PopTransform:
MOZ_ASSERT(mTransformLevel >= 0);
mTransformLevel--;
break;
case DisplayItemEntryType::PushTransform:
mTransformLevel++;
break;
#endif
default:
break;
}
}
bool PaintedLayerData::SetupComponentAlpha(
ContainerState* aState, nsPaintedDisplayItem* aItem,
const nsIntRect& aVisibleRect, const TransformClipNode* aTransform) {
nsRect componentAlphaBounds =
aItem->GetComponentAlphaBounds(aState->mBuilder);
if (componentAlphaBounds.IsEmpty()) {
// The item does not require component alpha, nothing do do here.
return false;
}
if (aTransform) {
componentAlphaBounds = aTransform->TransformRect(
componentAlphaBounds, aState->mAppUnitsPerDevPixel);
}
const nsIntRect pixelBounds =
aState->ScaleToOutsidePixels(componentAlphaBounds, false);
const nsIntRect visibleRect = pixelBounds.Intersect(aVisibleRect);
if (!mOpaqueRegion.Contains(visibleRect)) {
nsRect buildingRect = aItem->GetBuildingRect();
if (aTransform) {
buildingRect =
aTransform->TransformRect(buildingRect, aState->mAppUnitsPerDevPixel);
}
const nsRect tightBounds = componentAlphaBounds.Intersect(buildingRect);
if (IsItemAreaInWindowOpaqueRegion(aState->mBuilder, aItem, tightBounds)) {
mNeedComponentAlpha = true;
} else {
// There is no opaque background below the item, disable component alpha.
aItem->DisableComponentAlpha();
return false;
}
}
return true;
}
UniquePtr<InactiveLayerData> PaintedLayerData::CreateInactiveLayerData(
ContainerState* aState, nsPaintedDisplayItem* aItem,
DisplayItemData* aData) {
RefPtr<BasicLayerManager> tempManager;
if (aData) {
tempManager = aData->InactiveManager();
}
if (!tempManager) {
tempManager = new BasicLayerManager(BasicLayerManager::BLM_INACTIVE);
}
UniquePtr<InactiveLayerData> data = MakeUnique<InactiveLayerData>();
data->mLayerManager = tempManager;
FrameLayerBuilder* layerBuilder = new FrameLayerBuilder();
// Ownership of layerBuilder is passed to tempManager.
layerBuilder->Init(aState->Builder(), tempManager, this, true,
&aItem->GetClip());
tempManager->BeginTransaction();
if (aState->LayerBuilder()->GetRetainingLayerManager()) {
layerBuilder->DidBeginRetainedLayerTransaction(tempManager);
}
data->mProps = LayerProperties::CloneFrom(tempManager->GetRoot());
data->mLayer = aItem->BuildLayer(aState->Builder(), tempManager,
ContainerLayerParameters());
return data;
}
void PaintedLayerData::Accumulate(ContainerState* aState, nsDisplayItem* aItem,
const nsIntRect& aVisibleRect,
const nsRect& aContentRect,
const DisplayItemClip& aClip,
LayerState aLayerState, nsDisplayList* aList,
DisplayItemEntryType aType,
nsTArray<size_t>& aOpacityIndices,
const RefPtr<TransformClipNode>& aTransform) {
if (aItem->HasHitTestInfo()) {
AccumulateHitTestItem(aState, aItem, aClip, aTransform);
}
if (aItem->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
// These items only carry hit test information.
return;
}
nsPaintedDisplayItem* item = aItem->AsPaintedDisplayItem();
// If aItem is nullptr, the cast to nsPaintedDisplayItem failed.
MOZ_ASSERT(item, "Can only accumulate display items that are painted!");
FLB_LOG_PAINTED_LAYER_DECISION(
this, "Accumulating dp=%s(%p), f=%p against pld=%p\n", item->Name(), item,
item->Frame(), this);
const bool hasOpacity = aOpacityIndices.Length() > 0;
UpdateEffectStatus(aType, aOpacityIndices);
const DisplayItemClip* oldClip = mItemClip;
mItemClip = &aClip;
const bool isMerged =
item->AsDisplayWrapList() && item->AsDisplayWrapList()->HasMergedFrames();
if (IsEffectEndMarker(aType)) {
mAssignedDisplayItems.emplace_back(item, aLayerState, nullptr, aContentRect,
aType, hasOpacity, aTransform, isMerged);
return;
}
bool clipMatches =
(oldClip == mItemClip) || (oldClip && *oldClip == *mItemClip);
DisplayItemData* oldData = aState->mLayerBuilder->GetOldLayerForFrame(
item->Frame(), item->GetPerFrameKey());
mAssignedDisplayItems.emplace_back(item, aLayerState, oldData, aContentRect,
aType, hasOpacity, aTransform, isMerged);
if (aLayerState != LayerState::LAYER_NONE) {
FLB_LOG_PAINTED_LAYER_DECISION(this, "Creating nested FLB for item %p\n",
item);
mAssignedDisplayItems.back().mInactiveLayerData =
CreateInactiveLayerData(aState, item, oldData);
}
if (aState->mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
mForceTransparentSurface = true;
}
if (aState->mParameters.mDisableSubpixelAntialiasingInDescendants) {
// Disable component alpha.
// Note that the transform (if any) on the PaintedLayer is always an integer
// translation so we don't have to factor that in here.
item->DisableComponentAlpha();
} else {
const bool needsComponentAlpha =
SetupComponentAlpha(aState, item, aVisibleRect, aTransform);
if (needsComponentAlpha) {
// This display item needs background copy when pushing opacity group.
for (size_t i : aOpacityIndices) {
AssignedDisplayItem& item = mAssignedDisplayItems[i];
MOZ_ASSERT(item.mType == DisplayItemEntryType::PushOpacity ||
item.mType == DisplayItemEntryType::PushOpacityWithBg);
item.mType = DisplayItemEntryType::PushOpacityWithBg;
}
}
}
if (aTransform && aType == DisplayItemEntryType::Item) {
// Bounds transformed with axis-aligned transforms could be included in the
// opaque region calculations. For simplicity, this is currently not done.
return;
}
if (!mIsSolidColorInVisibleRegion && mOpaqueRegion.Contains(aVisibleRect) &&
mVisibleRegion.Contains(aVisibleRect) && !mImage) {
// A very common case! Most pages have a PaintedLayer with the page
// background (opaque) visible and most or all of the page content over the
// top of that background.
// The rest of this method won't do anything. mVisibleRegion and
// mOpaqueRegion don't need updating. mVisibleRegion contains aVisibleRect
// already, mOpaqueRegion contains aVisibleRect and therefore whatever the
// opaque region of the item is. mVisibleRegion must contain mOpaqueRegion
// and therefore aVisibleRect.
return;
}
nsIntRegion opaquePixels;
// Active opacity means no opaque pixels.
if (!hasOpacity) {
opaquePixels = aState->ComputeOpaqueRect(
item, mAnimatedGeometryRoot, mASR, aClip, aList, &mHideAllLayersBelow,
&mOpaqueForAnimatedGeometryRootParent);
opaquePixels.AndWith(aVisibleRect);
}
/* Mark as available for conversion to image layer if this is a nsDisplayImage
* and it's the only thing visible in this layer.
*/
if (nsIntRegion(aVisibleRect).Contains(mVisibleRegion) &&
opaquePixels.Contains(mVisibleRegion) &&
item->SupportsOptimizingToImage()) {
mImage = static_cast<nsDisplayImageContainer*>(item);
FLB_LOG_PAINTED_LAYER_DECISION(
this, " Tracking image: nsDisplayImageContainer covers the layer\n");
} else if (mImage) {
FLB_LOG_PAINTED_LAYER_DECISION(this, " No longer tracking image\n");
mImage = nullptr;
}
bool isFirstVisibleItem = mVisibleRegion.IsEmpty();
Maybe<nscolor> uniformColor;
if (!hasOpacity) {
uniformColor = item->IsUniform(aState->mBuilder);
}
// Some display items have to exist (so they can set forceTransparentSurface
// below) but don't draw anything. They'll return true for isUniform but
// a color with opacity 0.
if (!uniformColor || NS_GET_A(*uniformColor) > 0) {
// Make sure that the visible area is covered by uniform pixels. In
// particular this excludes cases where the edges of the item are not
// pixel-aligned (thus the item will not be truly uniform).
if (uniformColor) {
bool snap;
nsRect bounds = item->GetBounds(aState->mBuilder, &snap);
if (!aState->ScaleToInsidePixels(bounds, snap).Contains(aVisibleRect)) {
uniformColor = Nothing();
FLB_LOG_PAINTED_LAYER_DECISION(
this, " Display item does not cover the visible rect\n");
}
}
if (uniformColor) {
if (isFirstVisibleItem) {
// This color is all we have
mSolidColor = *uniformColor;
mIsSolidColorInVisibleRegion = true;
} else if (mIsSolidColorInVisibleRegion &&
mVisibleRegion.IsEqual(nsIntRegion(aVisibleRect)) &&
clipMatches) {
// we can just blend the colors together
mSolidColor = NS_ComposeColors(mSolidColor, *uniformColor);
} else {
FLB_LOG_PAINTED_LAYER_DECISION(
this, " Layer not a solid color: Can't blend colors togethers\n");
mIsSolidColorInVisibleRegion = false;
}
} else {
FLB_LOG_PAINTED_LAYER_DECISION(this,
" Layer is not a solid color: Display "
"item is not uniform over the visible "
"bound\n");
mIsSolidColorInVisibleRegion = false;
}
mVisibleRegion.Or(mVisibleRegion, aVisibleRect);
mVisibleRegion.SimplifyOutward(4);
}
if (!opaquePixels.IsEmpty()) {
for (auto iter = opaquePixels.RectIter(); !iter.Done(); iter.Next()) {
// We don't use SimplifyInward here since it's not defined exactly
// what it will discard. For our purposes the most important case
// is a large opaque background at the bottom of z-order (e.g.,
// a canvas background), so we need to make sure that the first rect
// we see doesn't get discarded.
nsIntRegion tmp;
tmp.Or(mOpaqueRegion, iter.Get());
// Opaque display items in chrome documents whose window is partially
// transparent are always added to the opaque region. This helps ensure
// that we get as much subpixel-AA as possible in the chrome.
if (tmp.GetNumRects() <= 4 || item->Frame()->PresContext()->IsChrome()) {
mOpaqueRegion = std::move(tmp);
}
}
}
}
nsRegion PaintedLayerData::CombinedTouchActionRegion() {
nsRegion result;
result.Or(mHorizontalPanRegion, mVerticalPanRegion);
result.OrWith(mNoActionRegion);
return result;
}
void PaintedLayerData::AccumulateHitTestItem(ContainerState* aState,
nsDisplayItem* aItem,
const DisplayItemClip& aClip,
TransformClipNode* aTransform) {
const auto& hitTestInfo = aItem->GetHitTestInfo();
nsRect area = hitTestInfo.Area();
const CompositorHitTestInfo& flags = hitTestInfo.Info();
FLB_LOG_PAINTED_LAYER_DECISION(
this,
"Accumulating hit test info %p against pld=%p, "
"area: [%d, %d, %d, %d], flags: 0x%x]\n",
aItem, this, area.x, area.y, area.width, area.height, flags.serialize());
area = aClip.ApplyNonRoundedIntersection(area);
if (aTransform) {
area = aTransform->TransformRect(area, aState->mAppUnitsPerDevPixel);
}
if (area.IsEmpty()) {
FLB_LOG_PAINTED_LAYER_DECISION(
this, "Discarded empty hit test info %p for pld=%p\n", aItem, this);
return;
}
bool hasRoundedCorners = aClip.GetRoundedRectCount() > 0;
// use the NS_FRAME_SIMPLE_EVENT_REGIONS to avoid calling the slightly
// expensive HasNonZeroCorner function if we know from a previous run that
// the frame has zero corners.
nsIFrame* frame = aItem->Frame();
bool simpleRegions = frame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
if (!simpleRegions) {
if (nsLayoutUtils::HasNonZeroCorner(frame->StyleBorder()->mBorderRadius)) {
hasRoundedCorners = true;
} else {
frame->AddStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
}
}
if (hasRoundedCorners || frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
mMaybeHitRegion.OrWith(area);
} else {
mHitRegion.OrWith(area);
}
const auto dtcFlags = flags & CompositorHitTestDispatchToContent;
if (!dtcFlags.isEmpty()) {
mDispatchToContentHitRegion.OrWith(area);
if (flags.contains(CompositorHitTestFlags::eRequiresTargetConfirmation)) {
mDTCRequiresTargetConfirmation = true;
}
}
const auto touchFlags = flags & CompositorHitTestTouchActionMask;
if (!touchFlags.isEmpty()) {
// If there are multiple touch-action areas, there are multiple elements
// with touch-action properties. We don't know what the relationship is
// between those elements in terms of DOM ancestry, and so we don't know how
// to combine the regions properly. Instead, we just add all the areas to
// the dispatch-to-content region, so that the APZ knows to check with the
// main thread. See bug 1286957.
if (mCollapsedTouchActions) {
mDispatchToContentHitRegion.OrWith(area);
} else if (touchFlags == CompositorHitTestTouchActionMask) {
// everything was disabled, so touch-action:none
mNoActionRegion.OrWith(area);
} else {
// The event regions code does not store enough information to actually
// represent all the different states. Prior to the introduction of
// CompositorHitTestInfo here in bug 1389149, the following two cases
// were effectively getting collapsed:
// (1) touch-action: auto
// (2) touch-action: manipulation
// In both of these cases, none of {mNoActionRegion, mHorizontalPanRegion,
// mVerticalPanRegion} were modified, and so the fact that case (2) should
// have prevented double-tap-zooming was getting lost.
// With CompositorHitTestInfo we can now represent that case correctly,
// but only if we use CompositorHitTestInfo all the way to the compositor
// (i.e. in the WebRender-enabled case). In the non-WebRender case where
// we still use the event regions, we must collapse these two cases back
// together. Or add another region to the event regions to fix this
// properly.
if (touchFlags !=
CompositorHitTestFlags::eTouchActionDoubleTapZoomDisabled) {
if (!flags.contains(CompositorHitTestFlags::eTouchActionPanXDisabled)) {
// pan-x is allowed
mHorizontalPanRegion.OrWith(area);
}
if (!flags.contains(CompositorHitTestFlags::eTouchActionPanYDisabled)) {
// pan-y is allowed
mVerticalPanRegion.OrWith(area);
}
} else {
// the touch-action: manipulation case described above. To preserve the
// existing behaviour, don't touch either mHorizontalPanRegion or
// mVerticalPanRegion
}
}
}
if (!mCollapsedTouchActions) {
// If there are multiple touch-action areas, there are multiple elements
// with touch-action properties. We don't know what the relationship is
// between those elements in terms of DOM ancestry, and so we don't know how
// to combine the regions properly. Instead, we just add all the areas to
// the dispatch-to-content region, so that the APZ knows to check with the
// main thread. See bug 1286957.
const int alreadyHadRegions = mNoActionRegion.GetNumRects() +
mHorizontalPanRegion.GetNumRects() +
mVerticalPanRegion.GetNumRects();
if (alreadyHadRegions > 1) {
mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
mNoActionRegion.SetEmpty();
mHorizontalPanRegion.SetEmpty();
mVerticalPanRegion.SetEmpty();
mCollapsedTouchActions = true;
}
}
// Avoid quadratic performance as a result of the region growing to include
// and arbitrarily large number of rects, which can happen on some pages.
mMaybeHitRegion.SimplifyOutward(8);
mDispatchToContentHitRegion.SimplifyOutward(8);
HitRegionsUpdated();
}
void PaintedLayerData::HitRegionsUpdated() {
// Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
// for quick access in FindPaintedLayerFor().
mScaledHitRegionBounds = mState->ScaleToOutsidePixels(mHitRegion.GetBounds());
mScaledMaybeHitRegionBounds =
mState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
}
void ContainerState::NewPaintedLayerData(
PaintedLayerData* aData, AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR, const DisplayItemClipChain* aClipChain,
const ActiveScrolledRoot* aScrollMetadataASR, const nsPoint& aTopLeft,
const nsIFrame* aReferenceFrame, const bool aBackfaceHidden) {
aData->mState = this;
aData->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
aData->mASR = aASR;
aData->mClipChain = aClipChain;
aData->mAnimatedGeometryRootOffset = aTopLeft;
aData->mReferenceFrame = aReferenceFrame;
aData->mBackfaceHidden = aBackfaceHidden;
aData->mNewChildLayersIndex = mNewChildLayers.Length();
NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
newLayerEntry->mAnimatedGeometryRoot = aAnimatedGeometryRoot;
newLayerEntry->mASR = aASR;
newLayerEntry->mScrollMetadataASR = aScrollMetadataASR;
newLayerEntry->mClipChain = aClipChain;
// newLayerEntry->mOpaqueRegion is filled in later from
// paintedLayerData->mOpaqueRegion, if necessary.
// Allocate another entry for this layer's optimization to
// ColorLayer/ImageLayer
mNewChildLayers.AppendElement();
}
#ifdef MOZ_DUMP_PAINTING
static void DumpPaintedImage(nsDisplayItem* aItem, SourceSurface* aSurface) {
nsCString string(aItem->Name());
string.Append('-');
string.AppendInt((uint64_t)aItem);
fprintf_stderr(gfxUtils::sDumpPaintFile, "<script>array[\"%s\"]=\"",
string.BeginReading());
gfxUtils::DumpAsDataURI(aSurface, gfxUtils::sDumpPaintFile);
fprintf_stderr(gfxUtils::sDumpPaintFile, "\";</script>\n");
}
#endif
static void PaintInactiveLayer(nsDisplayListBuilder* aBuilder,
LayerManager* aManager, nsDisplayItem* aItem,
gfxContext* aContext, gfxContext* aCtx) {
// This item has an inactive layer. Render it to a PaintedLayer
// using a temporary BasicLayerManager.
BasicLayerManager* basic = static_cast<BasicLayerManager*>(aManager);
RefPtr<gfxContext> context = aContext;
#ifdef MOZ_DUMP_PAINTING
int32_t appUnitsPerDevPixel = AppUnitsPerDevPixel(aItem);
nsIntRect itemVisibleRect =
aItem->GetPaintRect().ToOutsidePixels(appUnitsPerDevPixel);
RefPtr<DrawTarget> tempDT;
if (gfxEnv::DumpPaint()) {
tempDT = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
itemVisibleRect.Size(), SurfaceFormat::B8G8R8A8);
if (tempDT) {
context = gfxContext::CreateOrNull(tempDT);
if (!context) {
// Leave this as crash, it's in the debugging code, we want to know
gfxDevCrash(LogReason::InvalidContext)
<< "PaintInactive context problem " << gfx::hexa(tempDT);
return;
}
context->SetMatrix(
Matrix::Translation(-itemVisibleRect.x, -itemVisibleRect.y));
}
}
#endif
basic->BeginTransaction();
basic->SetTarget(context);
if (aItem->GetType() == DisplayItemType::TYPE_MASK) {
static_cast<nsDisplayMasksAndClipPaths*>(aItem)->PaintAsLayer(aBuilder,
aCtx, basic);
if (basic->InTransaction()) {
basic->AbortTransaction();
}
} else if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
static_cast<nsDisplayFilters*>(aItem)->PaintAsLayer(aBuilder, aCtx, basic);
if (basic->InTransaction()) {
basic->AbortTransaction();
}
} else {
basic->EndTransaction(FrameLayerBuilder::DrawPaintedLayer, aBuilder);
}
FrameLayerBuilder* builder = static_cast<FrameLayerBuilder*>(
basic->GetUserData(&gLayerManagerLayerBuilder));
if (builder) {
builder->DidEndTransaction();
}
basic->SetTarget(nullptr);
#ifdef MOZ_DUMP_PAINTING
if (gfxEnv::DumpPaint() && tempDT) {
RefPtr<SourceSurface> surface = tempDT->Snapshot();
DumpPaintedImage(aItem, surface);
DrawTarget* drawTarget = aContext->GetDrawTarget();
Rect rect(itemVisibleRect.x, itemVisibleRect.y, itemVisibleRect.width,
itemVisibleRect.height);
drawTarget->DrawSurface(surface, rect, Rect(Point(0, 0), rect.Size()));
aItem->SetPainted();
}
#endif
}
nsRect ContainerState::GetDisplayPortForAnimatedGeometryRoot(
AnimatedGeometryRoot* aAnimatedGeometryRoot) {
if (mLastDisplayPortAGR == aAnimatedGeometryRoot) {
return mLastDisplayPortRect;
}
mLastDisplayPortAGR = aAnimatedGeometryRoot;
nsIScrollableFrame* sf =
nsLayoutUtils::GetScrollableFrameFor(*aAnimatedGeometryRoot);
if (sf == nullptr ||
nsLayoutUtils::UsesAsyncScrolling(*aAnimatedGeometryRoot)) {
mLastDisplayPortRect = nsRect();
return mLastDisplayPortRect;
}
bool usingDisplayport = DisplayPortUtils::GetDisplayPort(
(*aAnimatedGeometryRoot)->GetContent(), &mLastDisplayPortRect,
DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
if (!usingDisplayport) {
// No async scrolling, so all that matters is that the layer contents
// cover the scrollport.
mLastDisplayPortRect = sf->GetScrollPortRect();
}
nsIFrame* scrollFrame = do_QueryFrame(sf);
mLastDisplayPortRect +=
scrollFrame->GetOffsetToCrossDoc(mContainerReferenceFrame);
return mLastDisplayPortRect;
}
nsIntRegion ContainerState::ComputeOpaqueRect(
nsDisplayItem* aItem, AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR, const DisplayItemClip& aClip,
nsDisplayList* aList, bool* aHideAllLayersBelow,
bool* aOpaqueForAnimatedGeometryRootParent) {
bool snapOpaque;
nsRegion opaque = aItem->GetOpaqueRegion(mBuilder, &snapOpaque);
MOZ_ASSERT(!opaque.IsComplex());
if (opaque.IsEmpty()) {
return nsIntRegion();
}
nsIntRegion opaquePixels;
nsRegion opaqueClipped;
for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
opaqueClipped.Or(opaqueClipped,
aClip.ApproximateIntersectInward(iter.Get()));
}
if (aAnimatedGeometryRoot == mContainerAnimatedGeometryRoot &&
aASR == mContainerASR && opaqueClipped.Contains(mContainerBounds)) {
*aHideAllLayersBelow = true;
aList->SetIsOpaque();
}
// Add opaque areas to the "exclude glass" region. Only do this when our
// container layer is going to be the rootmost layer, otherwise transforms
// etc will mess us up (and opaque contributions from other containers are
// not needed).
if (!nsLayoutUtils::GetCrossDocParentFrameInProcess(mContainerFrame)) {
mBuilder->AddWindowOpaqueRegion(aItem->Frame(), opaqueClipped.GetBounds());
}
opaquePixels = ScaleRegionToInsidePixels(opaqueClipped, snapOpaque);
if (IsInInactiveLayer()) {
return opaquePixels;
}
const nsRect& displayport =
GetDisplayPortForAnimatedGeometryRoot(aAnimatedGeometryRoot);
if (!displayport.IsEmpty() &&
opaquePixels.Contains(ScaleRegionToNearestPixels(displayport))) {
*aOpaqueForAnimatedGeometryRootParent = true;
}
return opaquePixels;
}
Maybe<size_t> ContainerState::SetupMaskLayerForScrolledClip(
Layer* aLayer, const DisplayItemClip& aClip) {
if (aClip.GetRoundedRectCount() > 0) {
Maybe<size_t> maskLayerIndex = Some(aLayer->GetAncestorMaskLayerCount());
if (RefPtr<Layer> maskLayer =
CreateMaskLayer(aLayer, aClip, maskLayerIndex)) {
aLayer->AddAncestorMaskLayer(maskLayer);
return maskLayerIndex;
}
// Fall through to |return Nothing()|.
}
return Nothing();
}
static const ActiveScrolledRoot* GetASRForPerspective(
const ActiveScrolledRoot* aASR, nsIFrame* aPerspectiveFrame) {
for (const ActiveScrolledRoot* asr = aASR; asr; asr = asr->mParent) {
nsIFrame* scrolledFrame = asr->mScrollableFrame->GetScrolledFrame();
// In OOP documents, the root scrollable frame of the in-process root
// document is always active, so using IsAncestorFrameCrossDocInProcess
// should be fine here.
if (nsLayoutUtils::IsAncestorFrameCrossDocInProcess(scrolledFrame,
aPerspectiveFrame)) {
return asr;
}
}
return nullptr;
}
static CSSMaskLayerUserData* GetCSSMaskLayerUserData(Layer* aMaskLayer) {
if (!aMaskLayer) {
return nullptr;
}
return static_cast<CSSMaskLayerUserData*>(
aMaskLayer->GetUserData(&gCSSMaskLayerUserData));
}
static void SetCSSMaskLayerUserData(Layer* aMaskLayer) {
MOZ_ASSERT(aMaskLayer);
aMaskLayer->SetUserData(&gCSSMaskLayerUserData, new CSSMaskLayerUserData());
}
void ContainerState::SetupMaskLayerForCSSMask(
Layer* aLayer, nsDisplayMasksAndClipPaths* aMaskItem) {
RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(
MaskLayerKey(aLayer, Nothing()), GetCSSMaskLayerUserData,
SetCSSMaskLayerUserData);
CSSMaskLayerUserData* oldUserData = GetCSSMaskLayerUserData(maskLayer.get());
MOZ_ASSERT(oldUserData);
bool snap;
nsRect bounds = aMaskItem->GetBounds(mBuilder, &snap);
nsIntRect itemRect = ScaleToOutsidePixels(bounds, snap);
// Setup mask layer offset.
// We do not repaint mask for mask position change, so update base transform
// each time is required.
Matrix4x4 matrix;
matrix.PreTranslate(itemRect.x, itemRect.y, 0);
matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
maskLayer->SetBaseTransform(matrix);
nsPoint maskLayerOffset = aMaskItem->ToReferenceFrame() - bounds.TopLeft();
CSSMaskLayerUserData newUserData(aMaskItem->Frame(), itemRect,
maskLayerOffset);
nsRect dirtyRect;
if (!aMaskItem->IsInvalid(dirtyRect) && *oldUserData == newUserData) {
aLayer->SetMaskLayer(maskLayer);
return;
}
int32_t maxSize = mManager->GetMaxTextureSize();
IntSize surfaceSize(std::min(itemRect.width, maxSize),
std::min(itemRect.height, maxSize));
if (surfaceSize.IsEmpty()) {
// Return early if we know that the size of this mask surface is empty.
return;
}
MaskImageData imageData(surfaceSize, mManager);
RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
if (!dt || !dt->IsValid()) {
NS_WARNING("Could not create DrawTarget for mask layer.");
return;
}
RefPtr<gfxContext> maskCtx = gfxContext::CreateOrNull(dt);
maskCtx->SetMatrix(Matrix::Translation(-itemRect.TopLeft()));
maskCtx->Multiply(
gfxMatrix::Scaling(mParameters.mXScale, mParameters.mYScale));
bool isPaintFinished = aMaskItem->PaintMask(mBuilder, maskCtx);
RefPtr<ImageContainer> imgContainer =
imageData.CreateImageAndImageContainer();
if (!imgContainer) {
return;
}
maskLayer->SetContainer(imgContainer);
if (isPaintFinished) {
*oldUserData = std::move(newUserData);
}
aLayer->SetMaskLayer(maskLayer);
}
static bool IsScrollThumbLayer(nsDisplayItem* aItem) {
return aItem->GetType() == DisplayItemType::TYPE_OWN_LAYER &&
static_cast<nsDisplayOwnLayer*>(aItem)->IsScrollThumbLayer();
}
template <typename ClearFn, typename SelectFn>
static void ProcessDisplayItemMarker(DisplayItemEntryType aMarker,
ClearFn ClearLayerSelectionIfNeeded,
SelectFn SelectLayerIfNeeded) {
switch (aMarker) {
case DisplayItemEntryType::PushTransform:
case DisplayItemEntryType::PushOpacity:
SelectLayerIfNeeded();
break;
case DisplayItemEntryType::PopTransform:
case DisplayItemEntryType::PopOpacity:
ClearLayerSelectionIfNeeded();
break;
default:
break;
}
}
/*
* Iterate through the non-clip items in aList and its descendants.
* For each item we compute the effective clip rect. Each item is assigned
* to a layer. We invalidate the areas in PaintedLayers where an item
* has moved from one PaintedLayer to another. Also,
* aState->mInvalidPaintedContent is invalidated in every PaintedLayer.
* We set the clip rect for items that generated their own layer, and
* create a mask layer to do any rounded rect clipping.
* (PaintedLayers don't need a clip rect on the layer, we clip the items
* individually when we draw them.)
* We set the visible rect for all layers, although the actual setting
* of visible rects for some PaintedLayers is deferred until the calling
* of ContainerState::Finish.
*/
void ContainerState::ProcessDisplayItems(nsDisplayList* aList) {
AUTO_PROFILER_LABEL("ContainerState::ProcessDisplayItems",
GRAPHICS_LayerBuilding);
PerfStats::AutoMetricRecording<PerfStats::Metric::LayerBuilding>
autoRecording;
nsPoint topLeft(0, 0);
int32_t maxLayers = StaticPrefs::layers_max_active();
int layerCount = 0;
if (!mManager->IsWidgetLayerManager()) {
mPaintedLayerDataTree.InitializeForInactiveLayer(
mContainerAnimatedGeometryRoot);
}
AnimatedGeometryRoot* lastAnimatedGeometryRoot = nullptr;
nsPoint lastTopLeft;
// Tracks the PaintedLayerData that the item will be accumulated in, if it is
// non-null.
PaintedLayerData* selectedLayer = nullptr;
AutoTArray<size_t, 2> opacityIndices;
// AGR and ASR for the container item that was flattened.
AnimatedGeometryRoot* containerAGR = nullptr;
const ActiveScrolledRoot* containerASR = nullptr;
nsIFrame* containerReferenceFrame = nullptr;
RefPtr<TransformClipNode> transformNode = nullptr;
const auto InTransform = [&]() { return transformNode; };
const auto InOpacity = [&]() {
return selectedLayer && opacityIndices.Length() > 0;
};
FLBDisplayListIterator iter(mBuilder, aList, this);
while (iter.HasNext()) {
DisplayItemEntry e = iter.GetNextEntry();
DisplayItemEntryType marker = e.mType;
nsDisplayItem* item = e.mItem;
MOZ_ASSERT(item);
DisplayItemType itemType = item->GetType();
const bool isHitTestItem =
itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO;
NS_ASSERTION(mAppUnitsPerDevPixel == AppUnitsPerDevPixel(item),
"items in a container layer should all have the same app "
"units per dev pixel");
if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
aList->SetNeedsTransparentSurface();
}
LayerState layerState = LayerState::LAYER_NONE;
if (marker == DisplayItemEntryType::Item) {
layerState = item->GetLayerState(mBuilder, mManager, mParameters);
if (layerState == LayerState::LAYER_INACTIVE &&
nsDisplayItem::ForceActiveLayers()) {
layerState = LayerState::LAYER_ACTIVE;
}
}
AnimatedGeometryRoot* itemAGR = item->GetAnimatedGeometryRoot();
const ActiveScrolledRoot* itemASR = item->GetActiveScrolledRoot();
const DisplayItemClipChain* itemClipChain = item->GetClipChain();
const DisplayItemClipChain* layerClipChain = nullptr;
const DisplayItemClip* itemClipPtr = &item->GetClip();
const bool inEffect = InTransform() || InOpacity();
if (mManager->IsWidgetLayerManager() && !inEffect) {
if (itemClipChain && itemClipChain->mASR == itemASR &&
itemType != DisplayItemType::TYPE_STICKY_POSITION) {
layerClipChain = itemClipChain->mParent;
} else {
layerClipChain = itemClipChain;
}
} else {
// Inside a flattened effect or inactive layer, use container AGR and ASR.
itemAGR = inEffect ? containerAGR : mContainerAnimatedGeometryRoot;
itemASR = inEffect ? containerASR : mContainerASR;
if (!IsEffectEndMarker(marker)) {
// No need to fuse clip chain for effect end markers, since it was
// already done for effect start markers.
item->FuseClipChainUpTo(mBuilder, itemASR);
itemClipChain = item->GetClipChain();
}
itemClipPtr = itemClipChain ? &itemClipChain->mClip : nullptr;
}
const DisplayItemClip& itemClip =
itemClipPtr ? *itemClipPtr : DisplayItemClip::NoClip();
if (inEffect && marker == DisplayItemEntryType::Item) {
// Fast-path for items inside flattened inactive layers. This works
// because the layer state of the item cannot be active, otherwise the
// parent item would not have been flattened.
MOZ_ASSERT(selectedLayer);
selectedLayer->Accumulate(this, item, nsIntRect(), nsRect(), itemClip,
layerState, aList, marker, opacityIndices,
transformNode);
continue;
}
// Items outside of flattened effects and non-item markers inside flattened
// effects are processed here.
MOZ_ASSERT(!inEffect || (marker != DisplayItemEntryType::Item));
if (itemAGR == lastAnimatedGeometryRoot) {
topLeft = lastTopLeft;
} else {
lastTopLeft = topLeft =
(*itemAGR)->GetOffsetToCrossDoc(mContainerReferenceFrame);
lastAnimatedGeometryRoot = itemAGR;
}
const ActiveScrolledRoot* scrollMetadataASR =
layerClipChain
? ActiveScrolledRoot::PickDescendant(itemASR, layerClipChain->mASR)
: itemASR;
const bool prerenderedTransform =
itemType == DisplayItemType::TYPE_TRANSFORM &&
static_cast<nsDisplayTransform*>(item)->MayBeAnimated(mBuilder);
bool snap = false;
nsRect itemContent = isHitTestItem ? item->GetHitTestInfo().Area()
: item->GetBounds(mBuilder, &snap);
nsIntRect itemDrawRect = ScaleToOutsidePixels(itemContent, snap);
ParentLayerIntRect clipRect;
if (itemClip.HasClip()) {
const nsRect& itemClipRect = itemClip.GetClipRect();
itemContent.IntersectRect(itemContent, itemClipRect);
clipRect = ViewAs<ParentLayerPixel>(ScaleToNearestPixels(itemClipRect));
if (!prerenderedTransform && !IsScrollThumbLayer(item)) {
itemDrawRect.IntersectRect(itemDrawRect, clipRect.ToUnknownRect());
}
clipRect.MoveBy(ViewAs<ParentLayerPixel>(mParameters.mOffset));
}
if (marker == DisplayItemEntryType::PopTransform) {
MOZ_ASSERT(transformNode);
transformNode = transformNode->Parent();
}
nsRect itemVisibleRectAu = itemContent;
if (transformNode) {
// If we are within transform, transform itemContent and itemDrawRect.
MOZ_ASSERT(transformNode);
itemContent =
transformNode->TransformRect(itemContent, mAppUnitsPerDevPixel);
itemDrawRect = transformNode->TransformRect(itemDrawRect);
}
#ifdef DEBUG
nsRect bounds = itemContent;
if (inEffect || isHitTestItem) {
bounds.SetEmpty();
}
if (!bounds.IsEmpty() && itemASR != mContainerASR) {
if (Maybe<nsRect> clip =
item->GetClipWithRespectToASR(mBuilder, mContainerASR)) {
bounds = clip.ref();
}
}
((nsRect&)mAccumulatedChildBounds)
.UnionRect(mAccumulatedChildBounds, bounds);
#endif
nsIntRect itemVisibleRect = itemDrawRect;
// We intersect the building rect with the clipped item bounds to get a
// tighter visible rect.
if (!prerenderedTransform) {
nsRect itemBuildingRect = item->GetBuildingRect();
if (transformNode) {
itemBuildingRect = transformNode->TransformRect(itemBuildingRect,
mAppUnitsPerDevPixel);
}
itemVisibleRect = itemVisibleRect.Intersect(
ScaleToOutsidePixels(itemBuildingRect, false));
}
const bool forceInactive = maxLayers != -1 && layerCount >= maxLayers;
// Assign the item to a layer
bool treatInactiveItemAsActive =
(layerState == LayerState::LAYER_INACTIVE &&
mLayerBuilder->GetContainingPaintedLayerData());
if (layerState == LayerState::LAYER_ACTIVE_FORCE ||
treatInactiveItemAsActive ||
(!forceInactive && (layerState == LayerState::LAYER_ACTIVE_EMPTY ||
layerState == LayerState::LAYER_ACTIVE))) {
layerCount++;
// Currently we do not support flattening effects within nested inactive
// layer trees.
MOZ_ASSERT(selectedLayer == nullptr);
MOZ_ASSERT(marker == DisplayItemEntryType::Item);
// LayerState::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 != LayerState::LAYER_ACTIVE_EMPTY ||
itemVisibleRect.IsEmpty(),
"State is LayerState::LAYER_ACTIVE_EMPTY but visible rect is not.");
// As long as the new layer isn't going to be a PaintedLayer,
// InvalidateForLayerChange doesn't need the new layer pointer.
// We also need to check the old data now, because BuildLayer
// can overwrite it.
DisplayItemData* oldData = mLayerBuilder->GetOldLayerForFrame(
item->Frame(), item->GetPerFrameKey());
InvalidateForLayerChange(item, nullptr, oldData);
// 3D-transformed layers don't necessarily draw in the order in which
// they're added to their parent container layer.
bool mayDrawOutOfOrder = itemType == DisplayItemType::TYPE_TRANSFORM &&
(item->Combines3DTransformWithAncestors() ||
item->Frame()->Extend3DContext());
// Let mPaintedLayerDataTree know about this item, so that
// FindPaintedLayerFor and FindOpaqueBackgroundColor are aware of this
// item, even though it's not in any PaintedLayerDataStack.
// Ideally we'd only need the "else" case here and have
// mPaintedLayerDataTree figure out the right clip from the animated
// geometry root that we give it, but it can't easily figure about
// overflow:hidden clips on ancestors just by looking at the frame.
// So we'll do a little hand holding and pass the clip instead of the
// visible rect for the two important cases.
nscolor uniformColor = NS_RGBA(0, 0, 0, 0);
nscolor* uniformColorPtr =
(mayDrawOutOfOrder || IsInInactiveLayer()) ? nullptr : &uniformColor;
nsIntRect clipRectUntyped;
nsIntRect* clipPtr = nullptr;
if (itemClip.HasClip()) {
clipRectUntyped = clipRect.ToUnknownRect();
clipPtr = &clipRectUntyped;
}
bool isStickyNotClippedToDisplayPort =
itemType == DisplayItemType::TYPE_STICKY_POSITION &&
!static_cast<nsDisplayStickyPosition*>(item)
->IsClippedToDisplayPort();
bool hasScrolledClip =
layerClipChain && layerClipChain->mClip.HasClip() &&
(!ActiveScrolledRoot::IsAncestor(layerClipChain->mASR, itemASR) ||
isStickyNotClippedToDisplayPort);
if (hasScrolledClip) {
// If the clip is scrolled, reserve just the area of the clip for
// layerization, so that elements outside the clip can still merge
// into the same layer.
const ActiveScrolledRoot* clipASR = layerClipChain->mASR;
AnimatedGeometryRoot* clipAGR =
mBuilder->AnimatedGeometryRootForASR(clipASR);
nsIntRect scrolledClipRect =
ScaleToNearestPixels(layerClipChain->mClip.GetClipRect()) +
mParameters.mOffset;
mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
uniformColorPtr);
} else if (item->ShouldFixToViewport(mBuilder) && itemClip.HasClip() &&
item->AnimatedGeometryRootForScrollMetadata() != itemAGR &&
!nsLayoutUtils::UsesAsyncScrolling(item->Frame())) {
// This is basically the same as the case above, but for the non-APZ
// case. At the moment, when APZ is off, there is only the root ASR
// (because scroll frames without display ports don't create ASRs) and
// the whole clip chain is always just one fused clip.
// Bug 1336516 aims to change that and to remove this workaround.
AnimatedGeometryRoot* clipAGR =
item->AnimatedGeometryRootForScrollMetadata();
nsIntRect scrolledClipRect =
ScaleToNearestPixels(itemClip.GetClipRect()) + mParameters.mOffset;
mPaintedLayerDataTree.AddingOwnLayer(clipAGR, &scrolledClipRect,
uniformColorPtr);
} else if (IsScrollThumbLayer(item) && mManager->IsWidgetLayerManager()) {
// For scrollbar thumbs, the clip we care about is the clip added by the
// slider frame.
mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
uniformColorPtr);
} else if (prerenderedTransform && mManager->IsWidgetLayerManager()) {
if (itemAGR->mParentAGR) {
mPaintedLayerDataTree.AddingOwnLayer(itemAGR->mParentAGR, clipPtr,
uniformColorPtr);
} else {
mPaintedLayerDataTree.AddingOwnLayer(itemAGR, nullptr,
uniformColorPtr);
}
} else {
// Using itemVisibleRect here isn't perfect. itemVisibleRect can be
// larger or smaller than the potential bounds of item's contents in
// itemAGR: It's too large if there's a clipped display
// port somewhere among item's contents (see bug 1147673), and it can
// be too small if the contents can move, because it only looks at the
// contents' current bounds and doesn't anticipate any animations.
// Time will tell whether this is good enough, or whether we need to do
// something more sophisticated here.
mPaintedLayerDataTree.AddingOwnLayer(itemAGR, &itemVisibleRect,
uniformColorPtr);
}
ContainerLayerParameters params = mParameters;
params.mBackgroundColor = uniformColor;
params.mLayerCreationHint = GetLayerCreationHint(itemAGR);
if (!transformNode) {
params.mItemVisibleRect = &itemVisibleRectAu;
} else {
// We only use mItemVisibleRect for getting the visible rect for
// remote browsers (which should never have inactive transforms), so we
// avoid doing transforms on itemVisibleRectAu above and can't report
// an accurate bounds here.
params.mItemVisibleRect = nullptr;
}
params.mScrollMetadataASR =
ActiveScrolledRoot::IsAncestor(scrollMetadataASR,
mContainerScrollMetadataASR)
? mContainerScrollMetadataASR
: scrollMetadataASR;
params.mCompositorASR =
params.mScrollMetadataASR != mContainerScrollMetadataASR
? params.mScrollMetadataASR
: mContainerCompositorASR;
if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
params.mCompositorASR = itemASR;
}
// Perspective items have a single child item, an nsDisplayTransform.
// If the perspective item is scrolled, but the perspective-inducing
// frame is outside the scroll frame (indicated by item->Frame()
// being outside that scroll frame), we have to take special care to
// make APZ scrolling work properly. APZ needs us to put the scroll
// frame's FrameMetrics on our child transform ContainerLayer instead.
// We make a similar adjustment for OwnLayer items built for frames
// with perspective transforms (e.g. when they have rounded corners).
// It's worth investigating whether this ASR adjustment can be done at
// display item creation time.
bool deferASRForPerspective =
itemType == DisplayItemType::TYPE_PERSPECTIVE ||
(itemType == DisplayItemType::TYPE_OWN_LAYER &&
item->Frame()->IsTransformed() && item->Frame()->HasPerspective());
if (deferASRForPerspective) {
scrollMetadataASR = GetASRForPerspective(
scrollMetadataASR,
item->Frame()->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME));
params.mScrollMetadataASR = scrollMetadataASR;
itemASR = scrollMetadataASR;
}
// Just use its layer.
// Set layerContentsVisibleRect.width/height to -1 to indicate we
// currently don't know. If BuildContainerLayerFor gets called by
// item->BuildLayer, this will be set to a proper rect.
nsIntRect layerContentsVisibleRect(0, 0, -1, -1);
params.mLayerContentsVisibleRect = &layerContentsVisibleRect;
// If this display item wants to build inactive layers but we are treating
// it as active because we are already inside an inactive layer tree,
// we need to make sure that the display item's clip is reflected in
// FrameLayerBuilder::mInactiveLayerClip (which is normally set in
// AddPaintedDisplayItem() when entering an inactive layer tree).
// We intersect the display item's clip into any existing inactive layer
// clip.
const DisplayItemClip* originalInactiveClip = nullptr;
DisplayItemClip combinedInactiveClip;
if (treatInactiveItemAsActive) {
originalInactiveClip = mLayerBuilder->GetInactiveLayerClip();
if (originalInactiveClip) {
combinedInactiveClip = *originalInactiveClip;
}
DisplayItemClip nestedClip = item->GetClip();
if (nestedClip.HasClip()) {
nsRect nestedClipRect = nestedClip.NonRoundedIntersection();
// Transform the nested clip to be relative to the same reference
// frame as the existing mInactiveLayerClip, so that we can intersect
// them below.
nestedClipRect = nsLayoutUtils::TransformFrameRectToAncestor(
item->ReferenceFrame(), nestedClipRect,
mLayerBuilder->GetContainingPaintedLayerData()->mReferenceFrame);
nestedClip.SetTo(nestedClipRect);
combinedInactiveClip.IntersectWith(nestedClip);
mLayerBuilder->SetInactiveLayerClip(&combinedInactiveClip);
}
}
RefPtr<Layer> ownLayer =
item->AsPaintedDisplayItem()->BuildLayer(mBuilder, mManager, params);
// If above we combined a nested clip into mInactiveLayerClip, restore
// the original inactive layer clip here.
if (treatInactiveItemAsActive) {
mLayerBuilder->SetInactiveLayerClip(originalInactiveClip);
}
if (!ownLayer) {
continue;
}
NS_ASSERTION(!ownLayer->AsPaintedLayer(),
"Should never have created a dedicated Painted layer!");
SetBackfaceHiddenForLayer(item->BackfaceIsHidden(), ownLayer);
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.
ownLayer->SetClipRect(Nothing());
ownLayer->SetScrolledClip(Nothing());
ownLayer->SetAncestorMaskLayers({});
if (itemClip.HasClip()) {
ownLayer->SetClipRect(Some(clipRect));
// rounded rectangle clipping using mask layers
// (must be done after visible rect is set on layer)
if (itemClip.GetRoundedRectCount() > 0) {
SetupMaskLayer(ownLayer, itemClip);
}
}
if (hasScrolledClip) {
const DisplayItemClip& scrolledClip = layerClipChain->mClip;
LayerClip scrolledLayerClip;
scrolledLayerClip.SetClipRect(ViewAs<ParentLayerPixel>(
ScaleToNearestPixels(scrolledClip.GetClipRect()) +
mParameters.mOffset));
if (scrolledClip.GetRoundedRectCount() > 0) {
scrolledLayerClip.SetMaskLayerIndex(
SetupMaskLayerForScrolledClip(ownLayer.get(), scrolledClip));
}
ownLayer->SetScrolledClip(Some(scrolledLayerClip));
}
if (item->GetType() == DisplayItemType::TYPE_MASK) {
MOZ_ASSERT(itemClip.GetRoundedRectCount() == 0);
nsDisplayMasksAndClipPaths* maskItem =
static_cast<nsDisplayMasksAndClipPaths*>(item);
SetupMaskLayerForCSSMask(ownLayer, maskItem);
if (iter.PeekNext() && iter.PeekNext()->GetType() ==
DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
// Since we do build a layer for mask, there is no need for this
// scroll info layer anymore.
iter.GetNextItem();
}
}
// Convert the visible rect to a region and give the item
// a chance to try restrict it further.
nsIntRegion itemVisibleRegion = itemVisibleRect;
nsRegion tightBounds = item->GetTightBounds(mBuilder, &snap);
if (!tightBounds.IsEmpty()) {
itemVisibleRegion.AndWith(
ScaleRegionToOutsidePixels(tightBounds, snap));
}
ContainerLayer* oldContainer = ownLayer->GetParent();
if (oldContainer && oldContainer != mContainerLayer) {
oldContainer->RemoveChild(ownLayer);
}
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, ownLayer) < 0,
"Layer already in list???");
// NewLayerEntry::mClipChain is used by SetupScrollingMetadata() to
// populate any scroll clips in the scroll metadata. Perspective layers
// have their ASR adjusted such that a scroll metadata that would normally
// go on the perspective layer goes on its transform layer child instead.
// However, the transform item's clip chain does not contain the
// corresponding scroll clip, so we use the perspective item's clip
// chain instead.
const DisplayItemClipChain* clipChainForScrollClips = layerClipChain;
if (itemType == DisplayItemType::TYPE_TRANSFORM && mContainerItem &&
mContainerItem->GetType() == DisplayItemType::TYPE_PERSPECTIVE) {
clipChainForScrollClips = mContainerItem->GetClipChain();
}
NewLayerEntry* newLayerEntry = mNewChildLayers.AppendElement();
newLayerEntry->mLayer = ownLayer;
newLayerEntry->mAnimatedGeometryRoot = itemAGR;
newLayerEntry->mASR = itemASR;
newLayerEntry->mScrollMetadataASR = scrollMetadataASR;
newLayerEntry->mClipChain = clipChainForScrollClips;
newLayerEntry->mLayerState = layerState;
if (itemType == DisplayItemType::TYPE_FIXED_POSITION) {
newLayerEntry->mIsFixedToRootScrollFrame =
item->Frame()->StyleDisplay()->mPosition ==
StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(item->Frame());
}
float contentXScale = 1.0f;
float contentYScale = 1.0f;
if (ContainerLayer* ownContainer = ownLayer->AsContainerLayer()) {
contentXScale = 1 / ownContainer->GetPreXScale();
contentYScale = 1 / ownContainer->GetPreYScale();
}
// nsDisplayTransform::BuildLayer must set layerContentsVisibleRect.
// We rely on this to ensure 3D transforms compute a reasonable
// layer visible region.
NS_ASSERTION(itemType != DisplayItemType::TYPE_TRANSFORM ||
layerContentsVisibleRect.width >= 0,
"Transform items must set layerContentsVisibleRect!");
if (mLayerBuilder->IsBuildingRetainedLayers()) {
newLayerEntry->mLayerContentsVisibleRect = layerContentsVisibleRect;
if (itemType == DisplayItemType::TYPE_PERSPECTIVE ||
(itemType == DisplayItemType::TYPE_TRANSFORM &&
(item->Combines3DTransformWithAncestors() ||
item->Frame()->Extend3DContext() ||
item->Frame()->HasPerspective()))) {
// Give untransformed visible region as outer visible region
// to avoid failure caused by singular transforms.
newLayerEntry->mUntransformedVisibleRegion = true;
newLayerEntry->mVisibleRegion =
item->GetBuildingRectForChildren().ScaleToOutsidePixels(
contentXScale, contentYScale, mAppUnitsPerDevPixel);
} else {
newLayerEntry->mVisibleRegion = itemVisibleRegion;
}
newLayerEntry->mOpaqueRegion = ComputeOpaqueRect(
item, itemAGR, itemASR, itemClip, aList,
&newLayerEntry->mHideAllLayersBelow,
&newLayerEntry->mOpaqueForAnimatedGeometryRootParent);
} else {
bool useChildrenVisible = itemType == DisplayItemType::TYPE_TRANSFORM &&
(item->Frame()->IsPreserve3DLeaf() ||
item->Frame()->HasPerspective());
const nsIntRegion& visible =
useChildrenVisible
? item->GetBuildingRectForChildren().ScaleToOutsidePixels(
contentXScale, contentYScale, mAppUnitsPerDevPixel)
: itemVisibleRegion;
SetOuterVisibleRegionForLayer(ownLayer, visible,
layerContentsVisibleRect.width >= 0
? &layerContentsVisibleRect
: nullptr,
useChildrenVisible);
}
if (itemType == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
nsDisplayScrollInfoLayer* scrollItem =
static_cast<nsDisplayScrollInfoLayer*>(item);
newLayerEntry->mOpaqueForAnimatedGeometryRootParent = false;
newLayerEntry->mBaseScrollMetadata = scrollItem->ComputeScrollMetadata(
mBuilder, ownLayer->Manager(), mParameters);
}
/**
* No need to allocate geometry for items that aren't
* part of a PaintedLayer.
*/
if (ownLayer->Manager() == mLayerBuilder->GetRetainingLayerManager()) {
oldData = mLayerBuilder->GetOldLayerForFrame(item->Frame(),
item->GetPerFrameKey());
mLayerBuilder->StoreDataForFrame(item->AsPaintedDisplayItem(), ownLayer,
layerState, oldData);
}
} else {
const bool backfaceHidden = item->In3DContextAndBackfaceIsHidden();
// When container item hit test info is processed, we need to use the same
// reference frame as the container children.
const nsIFrame* referenceFrame = item == mContainerItem
? mContainerReferenceFrame
: item->ReferenceFrame();
PaintedLayerData* paintedLayerData = selectedLayer;
if (!paintedLayerData) {
paintedLayerData = mPaintedLayerDataTree.FindPaintedLayerFor(
itemAGR, itemASR, layerClipChain, itemVisibleRect, backfaceHidden,
[&](PaintedLayerData* aData) {
NewPaintedLayerData(aData, itemAGR, itemASR, layerClipChain,
scrollMetadataASR, topLeft, referenceFrame,
backfaceHidden);
});
}
MOZ_ASSERT(paintedLayerData);
paintedLayerData->Accumulate(this, item, itemVisibleRect, itemContent,
itemClip, layerState, aList, marker,
opacityIndices, transformNode);
if (!paintedLayerData->mLayer) {
// Try to recycle the old layer of this display item.
RefPtr<PaintedLayer> layer = AttemptToRecyclePaintedLayer(
itemAGR, item, topLeft,
inEffect ? containerReferenceFrame : referenceFrame);
if (layer) {
paintedLayerData->mLayer = layer;
auto* userData = GetPaintedDisplayItemLayerUserData(layer);
paintedLayerData->mAssignedDisplayItems.reserve(
userData->mLastItemCount);
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, layer) < 0,
"Layer already in list???");
mNewChildLayers[paintedLayerData->mNewChildLayersIndex].mLayer =
std::move(layer);
}
}
const auto ClearLayerSelectionIfNeeded = [&]() {
if (!InOpacity() && !InTransform()) {
selectedLayer = nullptr;
containerAGR = nullptr;
containerASR = nullptr;
containerReferenceFrame = nullptr;
}
};
const auto SelectLayerIfNeeded = [&]() {
if (!selectedLayer) {
selectedLayer = paintedLayerData;
containerAGR = itemAGR;
containerASR = itemASR;
containerReferenceFrame = const_cast<nsIFrame*>(referenceFrame);
}
};
if (marker == DisplayItemEntryType::PushTransform) {
nsDisplayTransform* transform = static_cast<nsDisplayTransform*>(item);
const Matrix4x4Flagged& matrix = transform->GetTransformForRendering();
Maybe<gfx::IntRect> clip;
if (itemClip.HasClip()) {
const nsRect nonRoundedClip = itemClip.NonRoundedIntersection();
clip.emplace(nonRoundedClip.ToNearestPixels(mAppUnitsPerDevPixel));
}
transformNode = new TransformClipNode(transformNode, matrix, clip);
}
ProcessDisplayItemMarker(marker, ClearLayerSelectionIfNeeded,
SelectLayerIfNeeded);
}
nsDisplayList* childItems = item->GetSameCoordinateSystemChildren();
if (childItems && childItems->NeedsTransparentSurface()) {
aList->SetNeedsTransparentSurface();
}
}
MOZ_ASSERT(selectedLayer == nullptr);
}
void ContainerState::InvalidateForLayerChange(nsDisplayItem* aItem,
PaintedLayer* aNewLayer,
DisplayItemData* aData) {
NS_ASSERTION(aItem->GetPerFrameKey(),
"Display items that render using Thebes must have a key");
Layer* oldLayer = aData ? aData->mLayer.get() : nullptr;
if (aNewLayer != oldLayer && oldLayer) {
// The item has changed layers.
// Invalidate the old bounds in the old layer and new bounds in the new
// layer.
PaintedLayer* t = oldLayer->AsPaintedLayer();
if (t && aData->mGeometry) {
// 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
InvalidatePreTransformRect(
t, aData->mGeometry->ComputeInvalidationRegion(), aData->mClip,
GetLastPaintOffset(t), aData->mTransform);
}
// Clear the old geometry so that invalidation thinks the item has been
// added this paint.
aData->mGeometry = nullptr;
}
}
static nsRect GetInvalidationRect(nsDisplayItemGeometry* aGeometry,
const DisplayItemClip& aClip,
TransformClipNode* aTransform,
const int32_t aA2D) {
const nsRect& rect = aGeometry->ComputeInvalidationRegion();
const nsRect clipped = aClip.ApplyNonRoundedIntersection(rect);
if (aTransform) {
return aTransform->TransformRect(clipped, aA2D);
}
return clipped;
}
void FrameLayerBuilder::ComputeGeometryChangeForItem(DisplayItemData* aData) {
nsDisplayItem* item = aData->mItem;
PaintedLayer* paintedLayer = aData->mLayer->AsPaintedLayer();
// If aData->mOptLayer is presence, means this item has been optimized to the
// separate layer. Thus, skip geometry change calculation.
if (aData->mOptLayer || !item || !paintedLayer) {
aData->EndUpdate();
return;
}
// If we're a reused display item, then we can't be invalid, so no need to
// do an in-depth comparison. If we haven't previously stored geometry
// for this item (if it was an active layer), then we can't skip this
// yet.
UniquePtr<nsDisplayItemGeometry> geometry;
if (aData->mReusedItem && aData->mGeometry) {
aData->EndUpdate();
return;
}
auto* layerData = GetPaintedDisplayItemLayerUserData(aData->mLayer);
nsPoint shift = layerData->mAnimatedGeometryRootOrigin -
layerData->mLastAnimatedGeometryRootOrigin;
const DisplayItemClip& clip = item->GetClip();
const int32_t appUnitsPerDevPixel = layerData->mAppUnitsPerDevPixel;
// 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;
nsIntRegion invalidPixels;
if (!aData->mGeometry) {
// This item is being added for the first time, invalidate its entire area.
geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
const nsRect bounds = GetInvalidationRect(
geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel);
invalidPixels = bounds.ScaleToOutsidePixels(
layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr("Display item type %s(%p) added to layer %p!\n",
item->Name(), item->Frame(), aData->mLayer.get());
}
#endif
} else if (aData->mIsInvalid ||
(item->IsInvalid(invalid) && invalid.IsEmpty())) {
// Layout marked item/frame as needing repainting (without an explicit
// rect), invalidate the entire old and new areas.
geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
nsRect oldArea =
GetInvalidationRect(aData->mGeometry.get(), aData->mClip,
aData->mOldTransform, appUnitsPerDevPixel);
oldArea.MoveBy(shift);
nsRect newArea = GetInvalidationRect(
geometry.get(), clip, aData->mTransform, appUnitsPerDevPixel);
nsRegion combined;
combined.Or(oldArea, newArea);
invalidPixels = combined.ScaleToOutsidePixels(
layerData->mXScale, layerData->mYScale, appUnitsPerDevPixel);
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
printf_stderr(
"Display item type %s(%p) (in layer %p) belongs to an "
"invalidated frame!\n",
item->Name(), item->Frame(), aData->mLayer.get());
}
#endif
} else {
// Let the display item check for geometry changes and decide what needs to
// be repainted.
const nsRegion& changedFrameInvalidations =
aData->GetChangedFrameInvalidations();
if (aData->mTransform) {
// If this display item is inside a flattened transform the offset is
// already included in the root transform, so there is no need to shift.
shift = nsPoint();
}
aData->mGeometry->MoveBy(shift);
nsRegion combined;
item->ComputeInvalidationRegion(mDisplayListBuilder, aData->mGeometry.get(),
&combined);
// Only allocate a new geometry object if something actually changed,
// otherwise the existing one should be fine. We always reallocate for
// inactive layers, since these types don't implement
// ComputeInvalidateRegion (and rely on the ComputeDifferences call in
// AddPaintedDisplayItem instead).
if (!combined.IsEmpty() ||
aData->mLayerState == LayerState::LAYER_INACTIVE ||
item->NeedsGeometryUpdates()) {
geometry = WrapUnique(item->AllocateGeometry(mDisplayListBuilder));
}
aData->mClip.AddOffsetAndComputeDifference(
shift, aData->mGeometry->ComputeInvalidationRegion(), clip,
geometry ? geometry->ComputeInvalidationRegion()
: aData->mGeometry->ComputeInvalidationRegion(),
&combined);
// Add in any rect that the frame specified
combined.Or(combined, invalid);
combined.Or(combined, changedFrameInvalidations);
// Restrict invalidation to the clipped region
nsRegion clipRegion;
if (clip.ComputeRegionInClips(&aData->mClip, shift, &clipRegion)) {
combined.And(combined, clipRegion);
}
invalidPixels = combined.ToOutsidePixels(appUnitsPerDevPixel);
if (aData->mTransform) {
invalidPixels = aData->mTransform->TransformRegion(invalidPixels);
}
invalidPixels.ScaleRoundOut(layerData->mXScale, layerData->mYScale);
#ifdef MOZ_DUMP_PAINTING
if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
if (!combined.IsEmpty()) {
printf_stderr(
"Display item type %s(%p) (in layer %p) changed geometry!\n",
item->Name(), item->Frame(), aData->mLayer.get());
}
}
#endif
}
if (!invalidPixels.IsEmpty()) {
InvalidatePostTransformRegion(paintedLayer, invalidPixels,
layerData->mTranslation);
}
aData->EndUpdate(std::move(geometry));
}
void FrameLayerBuilder::AddPaintedDisplayItem(PaintedLayerData* aLayerData,
AssignedDisplayItem& aItem,
Layer* aLayer) {
PaintedLayer* layer = aLayerData->mLayer;
auto* paintedData = GetPaintedDisplayItemLayerUserData(layer);
if (layer->Manager() == mRetainingManager) {
DisplayItemData* data = aItem.mDisplayItemData;
if (data && !data->mUsed) {
data->BeginUpdate(layer, aItem.mLayerState, aItem.mItem, aItem.mReused,
aItem.mMerged);
} else {
data = StoreDataForFrame(aItem.mItem, layer, aItem.mLayerState, nullptr);
}
data->mInactiveManager = aItem.mInactiveLayerData
? aItem.mInactiveLayerData->mLayerManager
: nullptr;
// We optimized this PaintedLayer into a ColorLayer/ImageLayer. Store the
// optimized layer here.
if (aLayer != layer) {
data->mOptLayer = aLayer;
}
data->mOldTransform = data->mTransform;
data->mTransform = aItem.mTransform;
}
if (aItem.mInactiveLayerData) {
RefPtr<BasicLayerManager> tempManager =
aItem.mInactiveLayerData->mLayerManager;
FrameLayerBuilder* layerBuilder = tempManager->GetLayerBuilder();
Layer* tmpLayer = aItem.mInactiveLayerData->mLayer;
// 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);
aItem.mItem = nullptr;
return;
}
bool snap;
nsRect visibleRect = aItem.mItem->GetBuildingRect().Intersect(
aItem.mItem->GetBounds(mDisplayListBuilder, &snap));
nsIntRegion rgn =
visibleRect.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
// Convert the visible rect to a region and give the item
// a chance to try restrict it further.
nsRegion tightBounds =
aItem.mItem->GetTightBounds(mDisplayListBuilder, &snap);
if (!tightBounds.IsEmpty()) {
rgn.AndWith(
tightBounds.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel));
}
SetOuterVisibleRegion(tmpLayer, &rgn);
DisplayItemData* data = nullptr;
// If BuildLayer didn't call BuildContainerLayerFor, then our new layer
// won't have been stored in layerBuilder. Manually add it now.
if (mRetainingManager) {
#ifdef DEBUG_DISPLAY_ITEM_DATA
LayerManagerData* parentLmd = static_cast<LayerManagerData*>(
layer->Manager()->GetUserData(&gLayerManagerUserData));
LayerManagerData* lmd = static_cast<LayerManagerData*>(
tempManager->GetUserData(&gLayerManagerUserData));
lmd->mParent = parentLmd;
#endif
data =
layerBuilder->GetDisplayItemDataForManager(aItem.mItem, tempManager);
data = layerBuilder->StoreDataForFrame(aItem.mItem, tmpLayer,
LayerState::LAYER_ACTIVE, data);
data->mOldTransform = data->mTransform;
data->mTransform = aItem.mTransform;
}
tempManager->SetRoot(tmpLayer);
layerBuilder->WillEndTransaction();
tempManager->AbortTransaction();
if (gfxUtils::DumpDisplayList() || gfxEnv::DumpPaint()) {
fprintf_stderr(
gfxUtils::sDumpPaintFile,
"Basic layer tree for painting contents of display item %s(%p):\n",
aItem.mItem->Name(), aItem.mItem->Frame());
std::stringstream stream;
tempManager->Dump(stream, "", gfxEnv::DumpPaintToFile());
fprint_stderr(gfxUtils::sDumpPaintFile,
stream); // not a typo, fprint_stderr declared in nsDebug.h
}
nsIntPoint offset =
GetLastPaintOffset(layer) - GetTranslationForPaintedLayer(layer);
aItem.mInactiveLayerData->mProps->MoveBy(-offset);
// Effective transforms are needed by ComputeDifferences().
tmpLayer->ComputeEffectiveTransforms(Matrix4x4());
nsIntRegion invalid;
if (!aItem.mInactiveLayerData->mProps->ComputeDifferences(tmpLayer, invalid,
nullptr)) {
nsRect visible = aItem.mItem->Frame()->InkOverflowRect();
invalid = visible.ToOutsidePixels(paintedData->mAppUnitsPerDevPixel);
}
if (aItem.mLayerState == LayerState::LAYER_SVG_EFFECTS) {
invalid = SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
aItem.mItem->Frame(), aItem.mItem->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.mItem->Name(), aItem.mItem->Frame(),
layer);
}
#endif
if (data && data->mTransform) {
invalid = data->mTransform->TransformRegion(invalid);
}
invalid.ScaleRoundOut(paintedData->mXScale, paintedData->mYScale);
InvalidatePostTransformRegion(layer, invalid,
GetTranslationForPaintedLayer(layer));
}
}
}
DisplayItemData* FrameLayerBuilder::StoreDataForFrame(
nsPaintedDisplayItem* aItem, Layer* aLayer, LayerState aState,
DisplayItemData* aData) {
MOZ_ASSERT(aItem);
if (aData) {
if (!aData->mUsed) {
aData->BeginUpdate(aLayer, aState, false, aItem);
}
return aData;
}
LayerManagerData* lmd = static_cast<LayerManagerData*>(
mRetainingManager->GetUserData(&gLayerManagerUserData));
RefPtr<DisplayItemData> data = new (aItem->Frame()->PresContext())
DisplayItemData(lmd, aItem->GetPerFrameKey(), aLayer);
data->BeginUpdate(aLayer, aState, true, aItem);
lmd->mDisplayItems.push_back(data);
return data;
}
void FrameLayerBuilder::StoreDataForFrame(nsIFrame* aFrame,
uint32_t aDisplayItemKey,
Layer* aLayer, LayerState aState) {
DisplayItemData* oldData = GetDisplayItemData(aFrame, aDisplayItemKey);
if (oldData && oldData->mFrameList.Length() == 1) {
oldData->BeginUpdate(aLayer, aState, false);
return;
}
LayerManagerData* lmd = static_cast<LayerManagerData*>(
mRetainingManager->GetUserData(&gLayerManagerUserData));
RefPtr<DisplayItemData> data = new (aFrame->PresContext())
DisplayItemData(lmd, aDisplayItemKey, aLayer, aFrame);
data->BeginUpdate(aLayer, aState, true);
lmd->mDisplayItems.push_back(data);
}
AssignedDisplayItem::AssignedDisplayItem(
nsPaintedDisplayItem* aItem, LayerState aLayerState, DisplayItemData* aData,
const nsRect& aContentRect, DisplayItemEntryType aType,
const bool aHasOpacity, const RefPtr<TransformClipNode>& aTransform,
const bool aIsMerged)
: mItem(aItem),
mDisplayItemData(aData),
mTransform(aTransform),
mContentRect(aContentRect),
mLayerState(aLayerState),
mType(aType),
mReused(aItem->IsReused()),
mMerged(aIsMerged),
mHasOpacity(aHasOpacity),
mHasPaintRect(aItem->HasPaintRect()) {}
InactiveLayerData::~InactiveLayerData() {
if (mLayerManager) {
mLayerManager->SetUserData(&gLayerManagerLayerBuilder, nullptr);
}
}
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, false);
return false;
}
void ContainerState::CollectOldLayers() {
for (Layer* layer = mContainerLayer->GetFirstChild(); layer;
layer = layer->GetNextSibling()) {
NS_ASSERTION(!layer->HasUserData(&gMaskLayerUserData),
"Mask layers should not be part of the layer tree.");
if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
NS_ASSERTION(layer->AsPaintedLayer(), "Wrong layer type");
mPaintedLayersAvailableForRecycling.Insert(
static_cast<PaintedLayer*>(layer));
}
if (Layer* maskLayer = layer->GetMaskLayer()) {
NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
"Could not recycle mask layer, unsupported layer type.");
mRecycledMaskImageLayers.InsertOrUpdate(
MaskLayerKey(layer, Nothing()),
RefPtr{static_cast<ImageLayer*>(maskLayer)});
}
for (size_t i = 0; i < layer->GetAncestorMaskLayerCount(); i++) {
Layer* maskLayer = layer->GetAncestorMaskLayerAt(i);
NS_ASSERTION(maskLayer->GetType() == Layer::TYPE_IMAGE,
"Could not recycle mask layer, unsupported layer type.");
mRecycledMaskImageLayers.InsertOrUpdate(
MaskLayerKey(layer, Some(i)),
RefPtr{static_cast<ImageLayer*>(maskLayer)});
}
}
}
struct OpaqueRegionEntry {
AnimatedGeometryRoot* mAnimatedGeometryRoot;
const ActiveScrolledRoot* mASR;
nsIntRegion mOpaqueRegion;
};
static OpaqueRegionEntry* FindOpaqueRegionEntry(
nsTArray<OpaqueRegionEntry>& aEntries,
AnimatedGeometryRoot* aAnimatedGeometryRoot,
const ActiveScrolledRoot* aASR) {
for (uint32_t i = 0; i < aEntries.Length(); ++i) {
OpaqueRegionEntry* d = &aEntries[i];
if (d->mAnimatedGeometryRoot == aAnimatedGeometryRoot && d->mASR == aASR) {
return d;
}
}
return nullptr;
}
static const ActiveScrolledRoot* FindDirectChildASR(
const ActiveScrolledRoot* aParent, const ActiveScrolledRoot* aDescendant) {
MOZ_ASSERT(aDescendant, "can't start at the root when looking for a child");
MOZ_ASSERT(ActiveScrolledRoot::IsAncestor(aParent, aDescendant));
const ActiveScrolledRoot* directChild = aDescendant;
while (directChild->mParent != aParent) {
directChild = directChild->mParent;
MOZ_RELEASE_ASSERT(directChild, "this must not be null");
}
return directChild;
}
static void FixUpFixedPositionLayer(
Layer* aLayer, const ActiveScrolledRoot* aTargetASR,
const ActiveScrolledRoot* aLeafScrollMetadataASR,
const ActiveScrolledRoot* aContainerScrollMetadataASR,
const ActiveScrolledRoot* aContainerCompositorASR,
bool aIsFixedToRootScrollFrame) {
if (!aLayer->GetIsFixedPosition()) {
return;
}
// Analyze ASRs to figure out if we need to fix up fixedness annotations on
// the layer. Fixed annotations are required in multiple cases:
// - Sometimes we set scroll metadata on a layer for a scroll frame that we
// don't want the layer to be moved by. (We have to do this if there is a
// scrolled clip that is moved by that scroll frame.) So we set the fixed
// annotation so that the compositor knows that it should ignore that
// scroll metadata when determining the layer's position.
// - Sometimes there is a scroll meta data on aLayer's parent layer for a
// scroll frame that we don't want aLayer to be moved by. The most common
// way for this to happen is with containerful root scrolling, where the
// scroll metadata for the root scroll frame is on a container layer that
// wraps the whole document's contents.
// - Sometimes it's just needed for hit testing, i.e. figuring out what
// scroll frame should be scrolled by events over the layer.
// A fixed layer needs to be annotated with the scroll ID of the scroll frame
// that it is *fixed with respect to*, i.e. the outermost scroll frame which
// does not move the layer. nsDisplayFixedPosition only ever annotates layers
// with the scroll ID of the presshell's root scroll frame, which is
// sometimes the wrong thing to do, so we correct it here. Specifically,
// it's the wrong thing to do if the fixed frame's containing block is a
// transformed frame - in that case, the fixed frame needs to scroll along
// with the transformed frame instead of being fixed with respect to the rsf.
// (It would be nice to compute the annotation only in one place and get it
// right, instead of fixing it up after the fact like this, but this will
// need to do for now.)
// compositorASR is the ASR that the layer would move with on the compositor
// if there were no fixed annotation on it.
const ActiveScrolledRoot* compositorASR =
aLeafScrollMetadataASR == aContainerScrollMetadataASR
? aContainerCompositorASR
: aLeafScrollMetadataASR;
// The goal of the annotation is to have the layer move with aTargetASR.
if (compositorASR && aTargetASR != compositorASR) {
// Mark this layer as fixed with respect to the child scroll frame of
// aTargetASR.
aLayer->SetFixedPositionData(
FindDirectChildASR(aTargetASR, compositorASR)->GetViewId(),
aLayer->GetFixedPositionAnchor(), aLayer->GetFixedPositionSides());
} else {
// Remove the fixed annotation from the layer, unless this layers is fixed
// to the document's root scroll frame - in that case, the annotation is
// needed for hit testing, because fixed layers in iframes should scroll
// the iframe, even though their position is not affected by scrolling in
// the iframe. (The APZ hit testing code has a special case for this.)
// nsDisplayFixedPosition has annotated this layer with the document's
// root scroll frame's scroll id.
aLayer->SetIsFixedPosition(aIsFixedToRootScrollFrame);
}
}
void ContainerState::SetupScrollingMetadata(NewLayerEntry* aEntry) {
if (!mBuilder->IsPaintingToWindow()) {
// async scrolling not possible, and async scrolling info not computed
// for this paint.
return;
}
const ActiveScrolledRoot* startASR = aEntry->mScrollMetadataASR;
const ActiveScrolledRoot* stopASR = mContainerScrollMetadataASR;
if (!ActiveScrolledRoot::IsAncestor(stopASR, startASR)) {
if (ActiveScrolledRoot::IsAncestor(startASR, stopASR)) {
// startASR and stopASR are in the same branch of the ASR tree, but
// startASR is closer to the root. Just start at stopASR so that the loop
// below doesn't actually do anything.
startASR = stopASR;
} else {
// startASR and stopASR are in different branches of the
// ASR tree. Find a common ancestor and make that the stopASR.
// This can happen when there's a scrollable frame inside a fixed layer
// which has a scrolled clip. As far as scroll metadata is concerned,
// the scroll frame's scroll metadata will be a child of the scroll ID
// that scrolls the clip on the fixed layer. But as far as ASRs are
// concerned, those two ASRs are siblings, parented to the ASR of the
// fixed layer.
do {
stopASR = stopASR->mParent;
} while (!ActiveScrolledRoot::IsAncestor(stopASR, startASR));
}
}
FixUpFixedPositionLayer(aEntry->mLayer, aEntry->mASR, startASR,
mContainerScrollMetadataASR, mContainerCompositorASR,
aEntry->mIsFixedToRootScrollFrame);
AutoTArray<ScrollMetadata, 2> metricsArray;
if (aEntry->mBaseScrollMetadata) {
metricsArray.AppendElement(*aEntry->mBaseScrollMetadata);
// The base FrameMetrics was not computed by the nsIScrollableframe, so it
// should not have a mask layer.
MOZ_ASSERT(!aEntry->mBaseScrollMetadata->HasMaskLayer());
}
// Any extra mask layers we need to attach to ScrollMetadatas.
// The list may already contain an entry added for the layer's scrolled clip
// so add to it rather than overwriting it (we clear the list when recycling
// a layer).
nsTArray<RefPtr<Layer>> maskLayers(
aEntry->mLayer->GetAllAncestorMaskLayers().Clone());
// Iterate over the ASR chain and create the corresponding scroll metadatas.
// This loop is slightly tricky because the scrollframe-to-clip relationship
// is reversed between DisplayItemClipChain and ScrollMetadata:
// - DisplayItemClipChain associates the clip with the scroll frame that
// this clip is *moved by*, i.e. the clip is moving inside the scroll
// frame.
// - ScrollMetaData associates the scroll frame with the clip that's
// *just outside* the scroll frame, i.e. not moved by the scroll frame
// itself.
// This discrepancy means that the leaf clip item of the clip chain is never
// applied to any scroll meta data. Instead, it was applied earlier as the
// layer's clip (or fused with the painted layer contents), or it was applied
// as a ScrolledClip on the layer.
const DisplayItemClipChain* clipChain = aEntry->mClipChain;
for (const ActiveScrolledRoot* asr = startASR; asr != stopASR;
asr = asr->mParent) {
if (!asr) {
MOZ_ASSERT_UNREACHABLE("Should have encountered stopASR on the way up.");
break;
}
if (clipChain && clipChain->mASR == asr) {
clipChain = clipChain->mParent;
}
nsIScrollableFrame* scrollFrame = asr->mScrollableFrame;
const DisplayItemClip* clip = (clipChain && clipChain->mASR == asr->mParent)
? &clipChain->mClip
: nullptr;
scrollFrame->ClipLayerToDisplayPort(aEntry->mLayer, clip, mParameters);
Maybe<ScrollMetadata> metadata;
if (mCachedScrollMetadata.mASR == asr &&
mCachedScrollMetadata.mClip == clip) {
metadata = mCachedScrollMetadata.mMetadata;
} else {
metadata = scrollFrame->ComputeScrollMetadata(aEntry->mLayer->Manager(),
mContainerReferenceFrame,
Some(mParameters), clip);
mBuilder->AddScrollFrameToNotify(scrollFrame);
mCachedScrollMetadata.mASR = asr;
mCachedScrollMetadata.mClip = clip;
mCachedScrollMetadata.mMetadata = metadata;
}
if (!metadata) {
continue;
}
if (clip && clip->HasClip() && clip->GetRoundedRectCount() > 0) {
// The clip in between this scrollframe and its ancestor scrollframe
// requires a mask layer. Since this mask layer should not move with
// the APZC associated with this FrameMetrics, we attach the mask
// layer as an additional, separate clip.
Maybe<size_t> nextIndex = Some(maskLayers.Length());
RefPtr<Layer> maskLayer =
CreateMaskLayer(aEntry->mLayer, *clip, nextIndex);
if (maskLayer) {
MOZ_ASSERT(metadata->HasScrollClip());
metadata->ScrollClip().SetMaskLayerIndex(nextIndex);
maskLayers.AppendElement(maskLayer);
}
}
metricsArray.AppendElement(*metadata);
}
// Watch out for FrameMetrics copies in profiles
aEntry->mLayer->SetScrollMetadata(metricsArray);
aEntry->mLayer->SetAncestorMaskLayers(maskLayers);
}
static inline Maybe<ParentLayerIntRect> GetStationaryClipInContainer(
Layer* aLayer) {
if (size_t metricsCount = aLayer->GetScrollMetadataCount()) {
return aLayer->GetScrollMetadata(metricsCount - 1).GetClipRect();
}
return aLayer->GetClipRect();
}
void ContainerState::PostprocessRetainedLayers(
nsIntRegion* aOpaqueRegionForContainer) {
AutoTArray<OpaqueRegionEntry, 4> opaqueRegions;
bool hideAll = false;
int32_t opaqueRegionForContainer = -1;
for (int32_t i = mNewChildLayers.Length() - 1; i >= 0; --i) {
NewLayerEntry* e = &mNewChildLayers.ElementAt(i);
if (!e->mLayer) {
continue;
}
OpaqueRegionEntry* data =
FindOpaqueRegionEntry(opaqueRegions, e->mAnimatedGeometryRoot, e->mASR);
SetupScrollingMetadata(e);
if (hideAll) {
e->mVisibleRegion.SetEmpty();
} else if (!e->mLayer->IsScrollbarContainer()) {
Maybe<ParentLayerIntRect> clipRect =
GetStationaryClipInContainer(e->mLayer);
if (clipRect && opaqueRegionForContainer >= 0 &&
opaqueRegions[opaqueRegionForContainer].mOpaqueRegion.Contains(
clipRect->ToUnknownRect())) {
e->mVisibleRegion.SetEmpty();
} else if (data) {
e->mVisibleRegion.Sub(e->mVisibleRegion, data->mOpaqueRegion);
}
}
SetOuterVisibleRegionForLayer(e->mLayer, e->mVisibleRegion,
e->mLayerContentsVisibleRect.width >= 0
? &e->mLayerContentsVisibleRect
: nullptr,
e->mUntransformedVisibleRegion);
if (!e->mOpaqueRegion.IsEmpty()) {
AnimatedGeometryRoot* animatedGeometryRootToCover =
e->mAnimatedGeometryRoot;
const ActiveScrolledRoot* asrToCover = e->mASR;
if (e->mOpaqueForAnimatedGeometryRootParent &&
e->mAnimatedGeometryRoot->mParentAGR ==
mContainerAnimatedGeometryRoot) {
animatedGeometryRootToCover = mContainerAnimatedGeometryRoot;
asrToCover = mContainerASR;
data = FindOpaqueRegionEntry(opaqueRegions, animatedGeometryRootToCover,
asrToCover);
}
if (!data) {
if (animatedGeometryRootToCover == mContainerAnimatedGeometryRoot &&
asrToCover == mContainerASR) {
NS_ASSERTION(opaqueRegionForContainer == -1, "Already found it?");
opaqueRegionForContainer = opaqueRegions.Length();
}
data = opaqueRegions.AppendElement();
data->mAnimatedGeometryRoot = animatedGeometryRootToCover;
data->mASR = asrToCover;
}
nsIntRegion clippedOpaque = e->mOpaqueRegion;
Maybe<ParentLayerIntRect> clipRect = e->mLayer->GetCombinedClipRect();
if (clipRect) {
clippedOpaque.AndWith(clipRect->ToUnknownRect());
}
if (e->mLayer->GetScrolledClip()) {
// The clip can move asynchronously, so we can't rely on opaque parts
// staying visible.
clippedOpaque.SetEmpty();
} else if (e->mHideAllLayersBelow) {
hideAll = true;
}
data->mOpaqueRegion.Or(data->mOpaqueRegion, clippedOpaque);
}
if (e->mLayer->GetType() == Layer::TYPE_READBACK) {
// ReadbackLayers need to accurately read what's behind them. So,
// we don't want to do any occlusion culling of layers behind them.
// Theoretically we could just punch out the ReadbackLayer's rectangle
// from all mOpaqueRegions, but that's probably not worth doing.
opaqueRegions.Clear();
opaqueRegionForContainer = -1;
}
}
if (opaqueRegionForContainer >= 0) {
aOpaqueRegionForContainer->Or(
*aOpaqueRegionForContainer,
opaqueRegions[opaqueRegionForContainer].mOpaqueRegion);
}
}
void ContainerState::Finish(uint32_t* aTextContentFlags,
const nsIntRect& aContainerPixelBounds,
nsDisplayList* aChildItems) {
mPaintedLayerDataTree.Finish();
NS_ASSERTION(mContainerBounds.IsEqualInterior(mAccumulatedChildBounds),
"Bounds computation mismatch");
if (mLayerBuilder->IsBuildingRetainedLayers()) {
nsIntRegion containerOpaqueRegion;
PostprocessRetainedLayers(&containerOpaqueRegion);
if (containerOpaqueRegion.Contains(aContainerPixelBounds)) {
aChildItems->SetIsOpaque();
}
}
uint32_t textContentFlags = 0;
// Make sure that current/existing layers are added to the parent and are
// in the correct order.
Layer* layer = nullptr;
Layer* prevChild = nullptr;
for (uint32_t i = 0; i < mNewChildLayers.Length(); ++i, prevChild = layer) {
if (!mNewChildLayers[i].mLayer) {
continue;
}
layer = mNewChildLayers[i].mLayer;
if (!layer->GetVisibleRegion().IsEmpty()) {
textContentFlags |= layer->GetContentFlags() &
(Layer::CONTENT_COMPONENT_ALPHA |
Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT |
Layer::CONTENT_DISABLE_FLATTENING);
}
if (!layer->GetParent()) {
// This is not currently a child of the container, so just add it
// now.
mContainerLayer->InsertAfter(layer, prevChild);
} else {
NS_ASSERTION(layer->GetParent() == mContainerLayer,
"Layer shouldn't be the child of some other container");
if (layer->GetPrevSibling() != prevChild) {
mContainerLayer->RepositionChild(layer, prevChild);
}
}
}
// Remove old layers that have become unused.
if (!layer) {
layer = mContainerLayer->GetFirstChild();
} else {
layer = layer->GetNextSibling();
}
while (layer) {
Layer* layerToRemove = layer;
layer = layer->GetNextSibling();
mContainerLayer->RemoveChild(layerToRemove);
}
*aTextContentFlags = textContentFlags;
}
static void RestrictScaleToMaxLayerSize(Size& aScale,
const nsRect& aVisibleRect,
nsIFrame* aContainerFrame,
Layer* aContainerLayer) {
if (!aContainerLayer->Manager()->IsWidgetLayerManager()) {
return;
}
nsIntRect pixelSize = aVisibleRect.ScaleToOutsidePixels(
aScale.width, aScale.height,
aContainerFrame->PresContext()->AppUnitsPerDevPixel());
int32_t maxLayerSize = aContainerLayer->GetMaxLayerSize();
if (pixelSize.width > maxLayerSize) {
float scale = (float)pixelSize.width / maxLayerSize;
scale = gfxUtils::ClampToScaleFactor(scale);
aScale.width /= scale;
}
if (pixelSize.height > maxLayerSize) {
float scale = (float)pixelSize.height / maxLayerSize;
scale = gfxUtils::ClampToScaleFactor(scale);
aScale.height /= scale;
}
}
static nsSize ComputeDesiredDisplaySizeForAnimation(nsIFrame* aContainerFrame) {
// Use the size of the nearest widget as the maximum size. This
// is important since it might be a popup that is bigger than the
// pres context's size.
nsPresContext* presContext = aContainerFrame->PresContext();
nsIWidget* widget = aContainerFrame->GetNearestWidget();
if (widget) {
return LayoutDevicePixel::ToAppUnits(widget->GetClientSize(),
presContext->AppUnitsPerDevPixel());
}
return presContext->GetVisibleArea().Size();
}
/* static */
Size FrameLayerBuilder::ChooseScale(nsIFrame* aContainerFrame,
nsDisplayItem* aContainerItem,
const nsRect& aVisibleRect, float aXScale,
float aYScale, const Matrix& aTransform2d,
bool aCanDraw2D) {
Size scale;
// XXX Should we do something for 3D transforms?
if (aCanDraw2D && !aContainerFrame->Combines3DTransformWithAncestors() &&
!aContainerFrame->HasPerspective()) {
// If the container's transform is animated off main thread, fix a suitable
// scale size for animation
if (aContainerItem &&
aContainerItem->GetType() == DisplayItemType::TYPE_TRANSFORM &&
// FIXME: What we need is only transform, rotate, and scale, not
// translate, so it's be better to use a property set, instead of
// display item type here.
EffectCompositor::HasAnimationsForCompositor(
aContainerFrame, DisplayItemType::TYPE_TRANSFORM)) {
nsSize displaySize =
ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
// compute scale using the animation on the container, taking ancestors in
// to account
nsSize scaledVisibleSize = nsSize(aVisibleRect.Width() * aXScale,
aVisibleRect.Height() * aYScale);
scale = nsLayoutUtils::ComputeSuitableScaleForAnimation(
aContainerFrame, scaledVisibleSize, displaySize);
// multiply by the scale inherited from ancestors--we use a uniform
// scale factor to prevent blurring when the layer is rotated.
float incomingScale = std::max(aXScale, aYScale);
scale.width *= incomingScale;
scale.height *= incomingScale;
} else {
// Scale factors are normalized to a power of 2 to reduce the number of
// resolution changes
scale = aTransform2d.ScaleFactors();
// For frames with a changing scale transform round scale factors up to
// nearest power-of-2 boundary so that we don't keep having to redraw
// the content as it scales up and down. Rounding up to nearest
// power-of-2 boundary ensures we never scale up, only down --- avoiding
// jaggies. It also ensures we never scale down by more than a factor of
// 2, avoiding bad downscaling quality.
Matrix frameTransform;
if (ActiveLayerTracker::IsScaleSubjectToAnimation(aContainerFrame)) {
scale.width = gfxUtils::ClampToScaleFactor(scale.width);
scale.height = gfxUtils::ClampToScaleFactor(scale.height);
// Limit animated scale factors to not grow excessively beyond the
// display size.
nsSize maxScale(4, 4);
if (!aVisibleRect.IsEmpty()) {
nsSize displaySize =
ComputeDesiredDisplaySizeForAnimation(aContainerFrame);
maxScale = Max(maxScale, displaySize / aVisibleRect.Size());
}
if (scale.width > maxScale.width) {
scale.width = gfxUtils::ClampToScaleFactor(maxScale.width, true);
}
if (scale.height > maxScale.height) {
scale.height = gfxUtils::ClampToScaleFactor(maxScale.height, true);
}
} 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 = Size(1.0, 1.0);
}
} else {
scale = Size(1.0, 1.0);
}
// Prevent the scale from getting too large, to avoid excessive memory
// allocation. Usually memory allocation is limited by the visible region,
// which should be restricted to the display port. But at very large scales
// the visible region itself can become excessive due to rounding errors.
// Clamping the scale here prevents that.
scale =
Size(std::min(scale.width, 32768.0f), std::min(scale.height, 32768.0f));
return scale;
}
static bool ChooseScaleAndSetTransform(
FrameLayerBuilder* aLayerBuilder, nsDisplayListBuilder* aDisplayListBuilder,
nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
const nsRect& aVisibleRect, const Matrix4x4* aTransform,
const ContainerLayerParameters& aIncomingScale, ContainerLayer* aLayer,
ContainerLayerParameters& aOutgoingScale) {
nsIntPoint offset;
Matrix4x4 transform =
Matrix4x4::Scaling(aIncomingScale.mXScale, aIncomingScale.mYScale, 1.0);
if (aTransform) {
// aTransform is applied first, then the scale is applied to the result
transform = (*aTransform) * transform;
// Set any matrix entries close to integers to be those exact integers.
// This protects against floating-point inaccuracies causing problems
// in the checks below.
// We use the fixed epsilon version here because we don't want the nudging
// to depend on the scroll position.
transform.NudgeToIntegersFixedEpsilon();
}
Matrix transform2d;
if (aContainerFrame && aLayerBuilder->GetContainingPaintedLayerData() &&
(!aTransform ||
(aTransform->Is2D(&transform2d) && !transform2d.HasNonTranslation()))) {
// When we have an inactive ContainerLayer, translate the container by the
// offset to the reference frame (and offset all child layers by the
// reverse) so that the coordinate space of the child layers isn't affected
// by scrolling. This gets confusing for complicated transform (since we'd
// have to compute the scale factors for the matrix), so we don't bother.
// Any frames that are building an nsDisplayTransform for a css transform
// would have 0,0 as their offset to the reference frame, so this doesn't
// matter.
nsPoint appUnitOffset =
aDisplayListBuilder->ToReferenceFrame(aContainerFrame);
nscoord appUnitsPerDevPixel =
aContainerFrame->PresContext()->AppUnitsPerDevPixel();
offset = nsIntPoint(NS_lround(NSAppUnitsToDoublePixels(
appUnitOffset.x, appUnitsPerDevPixel) *
aIncomingScale.mXScale),
NS_lround(NSAppUnitsToDoublePixels(
appUnitOffset.y, appUnitsPerDevPixel) *
aIncomingScale.mYScale));
}
transform.PostTranslate(offset.x + aIncomingScale.mOffset.x,
offset.y + aIncomingScale.mOffset.y, 0);
if (transform.IsSingular()) {
return false;
}
bool canDraw2D = transform.CanDraw2D(&transform2d);
Size scale = FrameLayerBuilder::ChooseScale(
aContainerFrame, aContainerItem, aVisibleRect, aIncomingScale.mXScale,
aIncomingScale.mYScale, transform2d, canDraw2D);
// If this is a transform container layer, then pre-rendering might
// mean we try render a layer bigger than the max texture size. If we have
// tiling, that's not a problem, since we'll automatically choose a tiled
// layer for layers of that size. If not, we need to apply clamping to
// prevent this.
if (aTransform && !StaticPrefs::layers_enable_tiles_AtStartup()) {
RestrictScaleToMaxLayerSize(scale, aVisibleRect, aContainerFrame, aLayer);
}
// Store the inverse of our resolution-scale on the layer
aLayer->SetBaseTransform(transform);
aLayer->SetPreScale(1.0f / scale.width, 1.0f / scale.height);
aLayer->SetInheritedScale(aIncomingScale.mXScale, aIncomingScale.mYScale);
aOutgoingScale = ContainerLayerParameters(scale.width, scale.height, -offset,
aIncomingScale);
if (aTransform) {
aOutgoingScale.mInTransformedSubtree = true;
if (ActiveLayerTracker::IsTransformAnimated(aDisplayListBuilder,
aContainerFrame)) {
aOutgoingScale.mInActiveTransformedSubtree = true;
}
}
if ((aLayerBuilder->IsBuildingRetainedLayers() &&
(!canDraw2D || transform2d.HasNonIntegerTranslation())) ||
aContainerFrame->Extend3DContext() ||
aContainerFrame->Combines3DTransformWithAncestors() ||
// For async transform animation, the value would be changed at
// any time, integer translation is not always true.
aContainerFrame->HasAnimationOfTransform()) {
aOutgoingScale.mDisableSubpixelAntialiasingInDescendants = true;
}
return true;
}
already_AddRefed<ContainerLayer> FrameLayerBuilder::BuildContainerLayerFor(
nsDisplayListBuilder* aBuilder, LayerManager* aManager,
nsIFrame* aContainerFrame, nsDisplayItem* aContainerItem,
nsDisplayList* aChildren, const ContainerLayerParameters& aParameters,
const Matrix4x4* aTransform, uint32_t aFlags) {
uint32_t containerDisplayItemKey =
aContainerItem ? aContainerItem->GetPerFrameKey() : 0;
NS_ASSERTION(aContainerFrame,
"Container display items here should have a frame");
NS_ASSERTION(!aContainerItem || aContainerItem->Frame() == aContainerFrame,
"Container display item must match given frame");
if (!aParameters.mXScale || !aParameters.mYScale) {
return nullptr;
}
RefPtr<ContainerLayer> containerLayer;
if (aManager == mRetainingManager) {
// Using GetOldLayerFor will search merged frames, as well as the underlying
// frame. The underlying frame can change when a page scrolls, so this
// avoids layer recreation in the situation that a new underlying frame is
// picked for a layer.
Layer* oldLayer = nullptr;
if (aContainerItem) {
oldLayer = GetOldLayerFor(aContainerItem);
} else {
DisplayItemData* data =
GetOldLayerForFrame(aContainerFrame, containerDisplayItemKey);
if (data) {
oldLayer = data->mLayer;
}
}
if (oldLayer) {
NS_ASSERTION(oldLayer->Manager() == aManager, "Wrong manager");
if (oldLayer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
// The old layer for this item is actually our PaintedLayer
// because we rendered its layer into that PaintedLayer. So we
// don't actually have a retained container layer.
} else {
NS_ASSERTION(oldLayer->GetType() == Layer::TYPE_CONTAINER,
"Wrong layer type");
containerLayer = static_cast<ContainerLayer*>(oldLayer);
ResetLayerStateForRecycling(containerLayer);
}
}
}
if (!containerLayer) {
// No suitable existing layer was found.
containerLayer = aManager->CreateContainerLayer();
if (!containerLayer) return nullptr;
}
if (aContainerItem &&
aContainerItem->GetType() == DisplayItemType::TYPE_SCROLL_INFO_LAYER) {
// Empty layers only have metadata and should never have display items. We
// early exit because later, invalidation will walk up the frame tree to
// determine which painted layer gets invalidated. Since an empty layer
// should never have anything to paint, it should never be invalidated.
NS_ASSERTION(aChildren->IsEmpty(), "Should have no children");
return containerLayer.forget();
}
const ActiveScrolledRoot* containerASR =
aContainerItem ? aContainerItem->GetActiveScrolledRoot() : nullptr;
const ActiveScrolledRoot* containerScrollMetadataASR =
aParameters.mScrollMetadataASR;
const ActiveScrolledRoot* containerCompositorASR = aParameters.mCompositorASR;
ContainerLayerParameters scaleParameters;
nsRect bounds =
aChildren->GetClippedBoundsWithRespectToASR(aBuilder, containerASR);
nsRect childrenVisible =
aContainerItem ? aContainerItem->GetBuildingRectForChildren()
: aContainerFrame->InkOverflowRectRelativeToSelf();
if (!ChooseScaleAndSetTransform(
this, aBuilder, aContainerFrame, aContainerItem,
bounds.Intersect(childrenVisible), aTransform, aParameters,
containerLayer, scaleParameters)) {
return nullptr;
}
if (mRetainingManager) {
if (aContainerItem) {
nsPaintedDisplayItem* item = aContainerItem->AsPaintedDisplayItem();
MOZ_ASSERT(item, "Only painted display items should build layers");
DisplayItemData* data =
GetDisplayItemDataForManager(item, mRetainingManager);
StoreDataForFrame(item, containerLayer, LayerState::LAYER_ACTIVE, data);
} else {
StoreDataForFrame(aContainerFrame, containerDisplayItemKey,
containerLayer, LayerState::LAYER_ACTIVE);
}
}
nsIntRect pixBounds;
nscoord appUnitsPerDevPixel;
nscolor backgroundColor = NS_RGBA(0, 0, 0, 0);
if (aFlags & CONTAINER_ALLOW_PULL_BACKGROUND_COLOR) {
backgroundColor = aParameters.mBackgroundColor;
}
uint32_t flags;
ContainerState state(aBuilder, aManager, aManager->GetLayerBuilder(),
aContainerFrame, aContainerItem, bounds, containerLayer,
scaleParameters, backgroundColor, containerASR,
containerScrollMetadataASR, containerCompositorASR);
state.ProcessDisplayItems(aChildren);
// Set CONTENT_COMPONENT_ALPHA if any of our children have it.
// This is suboptimal ... a child could have text that's over transparent
// pixels in its own layer, but over opaque parts of previous siblings.
pixBounds = state.ScaleToOutsidePixels(bounds, false);
appUnitsPerDevPixel = state.GetAppUnitsPerDevPixel();
state.Finish(&flags, pixBounds, aChildren);
// CONTENT_COMPONENT_ALPHA is propogated up to the nearest CONTENT_OPAQUE
// ancestor so that BasicLayerManager knows when to copy the background into
// pushed groups. Accelerated layers managers can't necessarily do this (only
// when the visible region is a simple rect), so we propogate
// CONTENT_COMPONENT_ALPHA_DESCENDANT all the way to the root.
if (flags & Layer::CONTENT_COMPONENT_ALPHA) {
flags |= Layer::CONTENT_COMPONENT_ALPHA_DESCENDANT;
}
// Make sure that rounding the visible region out didn't add any area
// we won't paint
if (aChildren->IsOpaque() && !aChildren->NeedsTransparentSurface()) {
bounds.ScaleRoundIn(scaleParameters.mXScale, scaleParameters.mYScale);
if (bounds.Contains(ToAppUnits(pixBounds, appUnitsPerDevPixel))) {
// Clear CONTENT_COMPONENT_ALPHA and add CONTENT_OPAQUE instead.
flags &= ~Layer::CONTENT_COMPONENT_ALPHA;
flags |= Layer::CONTENT_OPAQUE;
}
}
if (nsLayoutUtils::ShouldSnapToGrid(aContainerFrame)) {
flags |= Layer::CONTENT_SNAP_TO_GRID;
}
containerLayer->SetContentFlags(flags);
// If aContainerItem is non-null some BuildContainerLayer further up the
// call stack is responsible for setting containerLayer's visible region.
if (!aContainerItem) {
containerLayer->SetVisibleRegion(
LayerIntRegion::FromUnknownRegion(pixBounds));
}
if (aParameters.mLayerContentsVisibleRect) {
*aParameters.mLayerContentsVisibleRect =
pixBounds + scaleParameters.mOffset;
}
nsPresContext::ClearNotifySubDocInvalidationData(containerLayer);
return containerLayer.forget();
}
Layer* FrameLayerBuilder::GetLeafLayerFor(nsDisplayListBuilder* aBuilder,
nsDisplayItem* aItem) {
Layer* layer = GetOldLayerFor(aItem);
if (!layer) {
return nullptr;
}
if (layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
// This layer was created to render Thebes-rendered content for this
// display item. The display item should not use it for its own
// layer rendering.
return nullptr;
}
ResetLayerStateForRecycling(layer);
return layer;
}
/* static */
void FrameLayerBuilder::InvalidateAllLayers(LayerManager* aManager) {
LayerManagerData* data = static_cast<LayerManagerData*>(
aManager->GetUserData(&gLayerManagerUserData));
if (data) {
data->mInvalidateAllLayers = true;
}
}
/* static */
void FrameLayerBuilder::InvalidateAllLayersForFrame(nsIFrame* aFrame) {
for (auto* did : GetDisplayItemDataArray(aFrame)) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
data->mParent->mInvalidateAllLayers = true;
}
}
/* static */
Layer* FrameLayerBuilder::GetDedicatedLayer(nsIFrame* aFrame,
DisplayItemType 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
for (auto* did : GetDisplayItemDataArray(aFrame)) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (!data->mParent->mLayerManager->IsWidgetLayerManager()) {
continue;
}
if (GetDisplayItemTypeFromKey(data->mDisplayItemKey) == aDisplayItemKey) {
if (data->mOptLayer) {
return data->mOptLayer;
}
Layer* layer = data->mLayer;
if (!layer->HasUserData(&gColorLayerUserData) &&
!layer->HasUserData(&gImageLayerUserData) &&
!layer->HasUserData(&gPaintedDisplayItemLayerUserData)) {
return layer;
}
}
}
return nullptr;
}
/* static */
void FrameLayerBuilder::EnumerateGenerationForDedicatedLayers(
const nsIFrame* aFrame, AnimationGenerationCallback aCallback) {
std::bitset<static_cast<uint32_t>(DisplayItemType::TYPE_MAX)> notFoundTypes;
for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
notFoundTypes.set(static_cast<uint32_t>(displayItemType));
}
for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
// For transform animations, the animation is on the primary frame but
// |aFrame| is the style frame.
const nsIFrame* frameToQuery =
displayItemType == DisplayItemType::TYPE_TRANSFORM
? nsLayoutUtils::GetPrimaryFrameFromStyleFrame(aFrame)
: aFrame;
for (auto* did : GetDisplayItemDataArray(frameToQuery)) {
DisplayItemData* data = DisplayItemData::AssertDisplayItemData(did);
if (!data->mParent->mLayerManager->IsWidgetLayerManager()) {
continue;
}
if (GetDisplayItemTypeFromKey(data->mDisplayItemKey) != displayItemType) {
continue;
}
notFoundTypes.reset(static_cast<uint32_t>(displayItemType));
Maybe<uint64_t> generation;
if (data->mOptLayer) {
generation = data->mOptLayer->GetAnimationGeneration();
} else if (!data->mLayer->HasUserData(&gColorLayerUserData) &&
!data->mLayer->HasUserData(&gImageLayerUserData) &&
!data->mLayer->HasUserData(
&gPaintedDisplayItemLayerUserData)) {
generation = data->mLayer->GetAnimationGeneration();
}
if (!aCallback(generation, displayItemType)) {
return;
}
break;
}
}
// Bail out if we have already enumerated all possible layers for the given
// display item types.
if (notFoundTypes.none()) {
return;
}
// If there are any display item types that the nsIFrame doesn't have, we need
// to call the callback function for them respectively.
for (auto displayItemType : LayerAnimationInfo::sDisplayItemTypes) {
if (notFoundTypes[static_cast<uint32_t>(displayItemType)] &&
!aCallback(Nothing(), displayItemType)) {
return;
}
}
}
gfxSize FrameLayerBuilder::GetPaintedLayerScaleForFrame(nsIFrame* aFrame) {
MOZ_ASSERT(aFrame, "need a frame");
nsPresContext* presCtx = aFrame->PresContext()->GetRootPresContext();
if (!presCtx) {
presCtx = aFrame->PresContext();
MOZ_ASSERT(presCtx);
}
nsIFrame* root = presCtx->PresShell()->GetRootFrame();
MOZ_ASSERT(root);
float resolution = presCtx->PresShell()->GetResolution();
Matrix4x4Flagged transform = Matrix4x4::Scaling(resolution, resolution, 1.0);
if (aFrame != root) {
// aTransform is applied first, then the scale is applied to the result
transform = nsLayoutUtils::GetTransformToAncestor(RelativeTo{aFrame},
RelativeTo{root}) *
transform;
}
Matrix transform2d;
if (transform.CanDraw2D(&transform2d)) {
return ThebesMatrix(transform2d).ScaleFactors();
}
return gfxSize(1.0, 1.0);
}
#ifdef MOZ_DUMP_PAINTING
static void DebugPaintItem(DrawTarget& aDrawTarget, nsPresContext* aPresContext,
nsPaintedDisplayItem* aItem,
nsDisplayListBuilder* aBuilder) {
bool snap;
Rect bounds = NSRectToRect(aItem->GetBounds(aBuilder, &snap),
aPresContext->AppUnitsPerDevPixel());
const IntSize size = IntSize::Truncate(bounds.width, bounds.height);
if (size.IsEmpty()) {
return;
}
RefPtr<DrawTarget> tempDT =
aDrawTarget.CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
RefPtr<gfxContext> context = gfxContext::CreateOrNull(tempDT);
if (!context) {
// Leave this as crash, it's in the debugging code, we want to know
gfxDevCrash(LogReason::InvalidContext)
<< "DebugPaintItem context problem " << gfx::hexa(tempDT);
return;
}
context->SetMatrix(Matrix::Translation(-bounds.x, -bounds.y));
aItem->Paint(aBuilder, context);
RefPtr<SourceSurface> surface = tempDT->Snapshot();
DumpPaintedImage(aItem, surface);
aDrawTarget.DrawSurface(surface, bounds, Rect(Point(0, 0), bounds.Size()));
aItem->SetPainted();
}
#endif
/* static */
void FrameLayerBuilder::RecomputeVisibilityForItems(
std::vector<AssignedDisplayItem>& aItems, nsDisplayListBuilder* aBuilder,
const nsIntRegion& aRegionToDraw, nsRect& aPreviousRectToDraw,
const nsIntPoint& aOffset, int32_t aAppUnitsPerDevPixel, float aXScale,
float aYScale) {
// Update visible regions. We perform visibility analysis to take account
// of occlusion culling.
nsRegion visible = aRegionToDraw.ToAppUnits(aAppUnitsPerDevPixel);
visible.MoveBy(NSIntPixelsToAppUnits(aOffset.x, aAppUnitsPerDevPixel),
NSIntPixelsToAppUnits(aOffset.y, aAppUnitsPerDevPixel));
visible.ScaleInverseRoundOut(aXScale, aYScale);
// We're going to read from previousRectToDraw for every iteration, let's do
// that on the stack, and just update the heap allocated one now. By the end
// of this function {visible} will have been modified by occlusion culling.
nsRect previousRectToDraw = aPreviousRectToDraw;
aPreviousRectToDraw = visible.GetBounds();
for (uint32_t i = aItems.size(); i > 0; --i) {
AssignedDisplayItem* cdi = &aItems[i - 1];
if (!cdi->mItem) {
continue;
}
if (cdi->mHasPaintRect &&
!cdi->mContentRect.Intersects(visible.GetBounds()) &&
!cdi->mContentRect.Intersects(previousRectToDraw)) {
continue;
}
if (IsEffectEndMarker(cdi->mType) || cdi->HasOpacity() ||
cdi->HasTransform()) {
// The visibility calculations are skipped when the item is an effect end
// marker, or when the display item is within a flattened effect group.
// This is because RecomputeVisibility has already been called for the
// group item, and all the children.
continue;
}
const DisplayItemClip& clip = cdi->mItem->GetClip();
NS_ASSERTION(AppUnitsPerDevPixel(cdi->mItem) == aAppUnitsPerDevPixel,
"a painted layer should contain items only at the same zoom");
MOZ_ASSERT(clip.HasClip() || clip.GetRoundedRectCount() == 0,
"If we have rounded rects, we must have a clip rect");
if (!clip.IsRectAffectedByClip(visible.GetBounds())) {
cdi->mItem->RecomputeVisibility(aBuilder, &visible);
continue;
}
// Do a little dance to account for the fact that we're clipping
// to cdi->mClipRect
nsRegion clipped;
clipped.And(visible, clip.NonRoundedIntersection());
nsRegion finalClipped = clipped;
cdi->mItem->RecomputeVisibility(aBuilder, &finalClipped);
// If we have rounded clip rects, don't subtract from the visible
// region since we aren't displaying everything inside the rect.
if (clip.GetRoundedRectCount() == 0) {
nsRegion removed;
removed.Sub(clipped, finalClipped);
nsRegion newVisible;
newVisible.Sub(visible, removed);
// Don't let the visible region get too complex.
if (newVisible.GetNumRects() <= 15) {
visible = std::move(newVisible);
}
}
}
}
/**
* Tracks and caches the item clip.
*/
struct ItemClipTracker {
explicit ItemClipTracker(gfxContext* aContext,
const int32_t aAppUnitsPerDevPixel)
: mContext(aContext),
mHasClip(false),
mAppUnitsPerDevPixel(aAppUnitsPerDevPixel) {}
/**
* Returns true if a clip is set.
*/
bool HasClip() const { return mHasClip; }
/**
* Returns true if the given |aClip| is set.
*/
bool HasClip(const DisplayItemClip* aClip) const {
MOZ_ASSERT(aClip && aClip->HasClip());
return mHasClip && mCurrentClip == *aClip;
}
/**
* Removes the clip, if there is one.
*/
void Restore() {
if (mCurrentClip.HasClip()) {
mCurrentClip = DisplayItemClip::NoClip();
}
if (!HasClip()) {
return;
}
mContext->Restore();
mHasClip = false;
};
/**
* Sets the clip to |aClip|, if it is not set already.
*/
void ChangeClipIfNeeded(const DisplayItemClip* aClip) {
MOZ_ASSERT(aClip && aClip->HasClip());
if (HasClip(aClip)) {
// Reuse the old clip.
return;
}
// Remove the previous clip and save the current state.
Restore();
mContext->Save();
// Apply the new clip.
mHasClip = true;
mCurrentClip = *aClip;
mCurrentClip.ApplyTo(mContext, mAppUnitsPerDevPixel);
mContext->NewPath();
}
private:
gfxContext* mContext;
bool mHasClip;
const int32_t mAppUnitsPerDevPixel;
DisplayItemClip mCurrentClip;
};
/**
* Tracks clips managed by |PushClip()| and |PopClip()|.
* If allowed by the caller, the top clip may be reused when a new clip that
* matches the previous one is pushed to the stack.
*/
struct ClipStack {
explicit ClipStack(gfxContext* aContext, const int32_t aAppUnitsPerDevPixel)
: mContext(aContext),
mAppUnitsPerDevPixel(aAppUnitsPerDevPixel),
mDeferredPopClip(false) {}
~ClipStack() {
MOZ_ASSERT(!mDeferredPopClip);
MOZ_ASSERT(!HasClips());
}
/**
* Returns true if there are clips set.
*/
bool HasClips() const { return mClips.Length() > 0; }
/**
* Returns the clip at the top of the stack.
*/
const DisplayItemClip& TopClip() const {
MOZ_ASSERT(HasClips());
return mClips.LastElement();
}
/**
* Returns true if the top clip matches the given |aClip|.
*/
bool TopClipMatches(const DisplayItemClip& aClip) {
return HasClips() && TopClip() == aClip;
}
/**
* Pops the current top clip. If |aDeferPopClip| is true, the top clip will
* not be popped before the next call to |PopClip(false)|.
* This allows the previously set clip to be reused during the next
* |PushClip()| call, if the new clip is identical with the top clip.
*/
void PopClip(bool aDeferPopClip) {
MOZ_ASSERT(HasClips());
if (aDeferPopClip) {
// Do not allow reusing clip with nested effects.
MOZ_ASSERT(!mDeferredPopClip);
mDeferredPopClip = true;
return;
}
if (TopClip().HasClip()) {
mContext->Restore();
}
mClips.RemoveLastElement();
mDeferredPopClip = false;
}
/**
* Pops the clip, if a call to |PopClip()| has been deferred.
*/
void PopDeferredClip() {
if (mDeferredPopClip) {
PopClip(false);
}
}
/**
* Pushes the given |aClip| to the stack.
*/
void PushClip(const DisplayItemClip& aClip) {
if (mDeferredPopClip && TopClipMatches(aClip)) {
// Reuse this clip. Defer the decision to reuse it again until the next
// call to PopClip().
mDeferredPopClip = false;
return;
}
PopDeferredClip();
mClips.AppendElement(aClip);
// Save the current state and apply new clip, if needed.
if (aClip.HasClip()) {
mContext->Save();
aClip.ApplyTo(mContext, mAppUnitsPerDevPixel);
mContext->NewPath();
}
}
private:
gfxContext* mContext;
const int32_t mAppUnitsPerDevPixel;
AutoTArray<DisplayItemClip, 2> mClips;
bool mDeferredPopClip;
};
/**
* Returns a clip for the given |aItem|. If the clip can be simplified to not
* include rounded rects, |aOutClip| is used to store the simplified clip.
*/
static const DisplayItemClip* GetItemClip(const nsDisplayItem* aItem,
DisplayItemClip& aOutClip) {
const DisplayItemClip& clip = aItem->GetClip();
if (!clip.HasClip()) {
return nullptr;
}
if (clip.GetRoundedRectCount() > 0 &&
!clip.IsRectClippedByRoundedCorner(aItem->GetPaintRect())) {
aOutClip.SetTo(clip.GetClipRect());
return &aOutClip;
}
return &clip;
}
/**
* Pushes a new opacity group for |aContext| based on |aItem|.
*/
static void PushOpacity(gfxContext* aContext, AssignedDisplayItem& aItem) {
MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushOpacity ||
aItem.mType == DisplayItemEntryType::PushOpacityWithBg);
MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_OPACITY);
nsDisplayOpacity* item = static_cast<nsDisplayOpacity*>(aItem.mItem);
const float opacity = item->GetOpacity();
if (aItem.mType == DisplayItemEntryType::PushOpacityWithBg) {
aContext->PushGroupAndCopyBackground(gfxContentType::COLOR_ALPHA, opacity);
} else {
aContext->PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, opacity);
}
}
/**
* Pushes the transformation matrix of |aItem| into |aMatrixStack| and sets the
* accumulated transform as the current transformation matrix for |aContext|.
*/
static void PushTransform(gfxContext* aContext, AssignedDisplayItem& aItem,
nsDisplayListBuilder* aBuilder,
MatrixStack4x4& aMatrixStack,
const Matrix4x4Flagged& aBaseMatrix) {
MOZ_ASSERT(aItem.mType == DisplayItemEntryType::PushTransform);
MOZ_ASSERT(aItem.mItem->GetType() == DisplayItemType::TYPE_TRANSFORM);
nsDisplayTransform* item = static_cast<nsDisplayTransform*>(aItem.mItem);
if (item->ShouldSkipTransform(aBuilder)) {
aMatrixStack.Push(Matrix4x4Flagged());
} else {
aMatrixStack.Push(item->GetTransformForRendering());
}
gfx::Matrix4x4Flagged matrix = aMatrixStack.CurrentMatrix() * aBaseMatrix;
gfx::Matrix matrix2d;
DebugOnly<bool> ok = matrix.CanDraw2D(&matrix2d);
MOZ_ASSERT(ok);
aContext->SetMatrix(matrix2d);
}
static void UpdateEffectTracking(int& aOpacityLevel, int& aTransformLevel,
const DisplayItemEntryType aType) {
switch (aType) {
case DisplayItemEntryType::PushOpacity:
case DisplayItemEntryType::PushOpacityWithBg:
aOpacityLevel++;
break;
case DisplayItemEntryType::PopOpacity:
aOpacityLevel--;
break;
case DisplayItemEntryType::PushTransform:
aTransformLevel++;
break;
case DisplayItemEntryType::PopTransform:
aTransformLevel--;
break;
default:
break;
}
MOZ_ASSERT(aOpacityLevel >= 0 && aTransformLevel >= 0);
}
void FrameLayerBuilder::PaintItems(std::vector<AssignedDisplayItem>& aItems,
const nsIntRect& aRect, gfxContext* aContext,
nsDisplayListBuilder* aBuilder,
nsPresContext* aPresContext,
const nsIntPoint& aOffset, float aXScale,
float aYScale) {
DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
nsRect boundRect = ToAppUnits(aRect, appUnitsPerDevPixel);
boundRect.MoveBy(NSIntPixelsToAppUnits(aOffset.x, appUnitsPerDevPixel),
NSIntPixelsToAppUnits(aOffset.y, appUnitsPerDevPixel));
boundRect.ScaleInverseRoundOut(aXScale, aYScale);
if (boundRect.IsEmpty()) {
// Hack! This can happen if the conversion of |aRect| to scaled and offset
// app units overflowed. Ideally the conversion would detect this and handle
// such situations gracefully. For now, do nothing.
return;
}
#ifdef DEBUG
// Tracks effect nesting level. These are used to track that every effect
// start marker has a corresponding end marker.
int opacityLevel = 0;
int transformLevel = 0;
#endif
// Tracks effect nesting level for skipping items between effect markers,
// when the effect display item does not intersect with the invalidated area.
int emptyEffectLevel = 0;
// Stores a simplified version of the item clip, if needed.
DisplayItemClip temporaryClip;
// Two types of clips are used during PaintItems(): clips for items and clips
// for effects. Item clips are always the most recent clip set, and they are
// never nested. The previous item clip is reused, if the next item has the
// same clip. Item clips are removed when an effect starts or ends.
ItemClipTracker itemClipTracker(aContext, appUnitsPerDevPixel);
// Since effects can be nested, the effect clips need to be nested as well.
// They are pushed for effect start marker, and popped for effect end marker.
// Effect clips are tracked by |effectClipStack|. If there are consecutive
// effects with the same clip, |effectClipStack| defers popping the clip for
// the first end marker, and tries to reuse the previously set clip, when
// processing the start marker for the next effect.
ClipStack effectClipStack(aContext, appUnitsPerDevPixel);
MatrixStack4x4 matrixStack;
const Matrix4x4Flagged base = Matrix4x4::From2D(aContext->CurrentMatrix());
for (uint32_t i = 0; i < aItems.size(); ++i) {
AssignedDisplayItem& cdi = aItems[i];
nsDisplayItem* item = cdi.mItem;
const auto NextItemStartsEffect = [&]() {
const uint32_t next = i + 1;
return next < aItems.size() && IsEffectStartMarker(aItems[next].mType);
};
if (!item) {
MOZ_ASSERT(cdi.mType == DisplayItemEntryType::Item);
continue;
}
nsRect visibleRect = item->GetPaintRect();
if (matrixStack.HasTransform()) {
MOZ_ASSERT(transformLevel > 0);
if (IsEffectEndMarker(cdi.mType)) {
// Always process the effect end markers.
visibleRect = boundRect;
} else {
const Matrix4x4Flagged& matrix = matrixStack.CurrentMatrix();
visibleRect = nsLayoutUtils::MatrixTransformRect(visibleRect, matrix,
appUnitsPerDevPixel);
}
}
const nsRect paintRect = visibleRect.Intersect(boundRect);
if (paintRect.IsEmpty() || emptyEffectLevel > 0) {
// In order for this branch to be hit, either this item has an empty paint
// rect and nothing would be drawn, or an effect marker before this
// item had an empty paint rect. In the latter case, the items are skipped
// until effect POP markers bring |emptyEffectLevel| back to 0.
UpdateEffectTracking(emptyEffectLevel, emptyEffectLevel, cdi.mType);
// Sometimes the item that was going to reuse the previous clip is culled.
// Since |PushClip()| is never called for culled items, pop the clip now.
effectClipStack.PopDeferredClip();
continue;
}
#ifdef MOZ_DUMP_PAINTING
AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
"FrameLayerBuilder::PaintItems", GRAPHICS_Rasterization, item->Name());
#else
AUTO_PROFILER_LABEL("FrameLayerBuilder::PaintItems",
GRAPHICS_Rasterization);
#endif
MOZ_ASSERT((opacityLevel == 0 && !cdi.HasOpacity()) ||
(opacityLevel > 0 && cdi.HasOpacity()) ||
(transformLevel == 0 && !cdi.HasTransform()) ||
(transformLevel > 0 && cdi.HasTransform()));
if (cdi.mType != DisplayItemEntryType::Item) {
// If we are processing an effect marker, remove the current item clip, if
// there is one.
itemClipTracker.Restore();
}
if (cdi.mType == DisplayItemEntryType::PushOpacity ||
cdi.mType == DisplayItemEntryType::PushOpacityWithBg) {
// To avoid pushing large temporary surfaces, it is important to clip
// opacity group with both the paint rect and the actual opacity clip.
DisplayItemClip effectClip;
effectClip.SetTo(item->GetPaintRect());
effectClip.IntersectWith(item->GetClip());
effectClipStack.PushClip(effectClip);
PushOpacity(aContext, cdi);
}
if (cdi.mType == DisplayItemEntryType::PopOpacity) {
MOZ_ASSERT(opacityLevel > 0);
aContext->PopGroupAndBlend();
}
if (cdi.mType == DisplayItemEntryType::PushTransform) {
effectClipStack.PushClip(item->GetClip());
aContext->Save();
PushTransform(aContext, cdi, aBuilder, matrixStack, base);
}
if (cdi.mType == DisplayItemEntryType::PopTransform) {
MOZ_ASSERT(transformLevel > 0);
matrixStack.Pop();
aContext->Restore();
}
if (IsEffectEndMarker(cdi.mType)) {
// Pop the clip for the effect.
MOZ_ASSERT(effectClipStack.HasClips());
// If the next item starts an effect, defer popping the current clip, and
// try to reuse it during the next call to |PushClip()|. Trying to reuse
// clips between nested effects would be difficult, for example due to
// possibly different coordinate system, so this optimization is limited
// to consecutive effects.
effectClipStack.PopClip(NextItemStartsEffect());
}
if (cdi.mType != DisplayItemEntryType::Item) {
#ifdef DEBUG
UpdateEffectTracking(opacityLevel, transformLevel, cdi.mType);
#endif
// Nothing more to do with effect markers.
continue;
}
const bool paintAsLayer = cdi.mInactiveLayerData.get();
nsPaintedDisplayItem* paintedItem = item->AsPaintedDisplayItem();
MOZ_ASSERT(paintAsLayer || paintedItem,
"The display item does not support painting");
const DisplayItemClip* itemClip = GetItemClip(item, temporaryClip);
bool itemPaintsOwnClip = false;
if (itemClip && !itemClipTracker.HasClip(itemClip)) {
// The clip has changed. Remove the previous clip.
itemClipTracker.Restore();
// Check if the item supports painting with clip.
itemPaintsOwnClip =
paintAsLayer ? false : paintedItem->CanPaintWithClip(*itemClip);
if (!itemPaintsOwnClip) {
// Item does not support painting with clip, set the clip.
itemClipTracker.ChangeClipIfNeeded(itemClip);
}
}
if (!itemClip) {
// Item does not need clipping, remove the clip if there is one.
itemClipTracker.Restore();
}
if (paintAsLayer) {
bool saved = aDrawTarget.GetPermitSubpixelAA();
PaintInactiveLayer(aBuilder, cdi.mInactiveLayerData->mLayerManager, item,
aContext, aContext);
aDrawTarget.SetPermitSubpixelAA(saved);
continue;
}
nsIFrame* frame = item->Frame();
if (aBuilder->IsPaintingToWindow()) {
frame->AddStateBits(NS_FRAME_PAINTED_THEBES);
}
#ifdef MOZ_DUMP_PAINTING
if (gfxEnv::DumpPaintItems()) {
DebugPaintItem(aDrawTarget, aPresContext, paintedItem, aBuilder);
continue;
}
#endif
if (itemPaintsOwnClip) {
MOZ_ASSERT(itemClip);
paintedItem->PaintWithClip(aBuilder, aContext, *itemClip);
} else {
paintedItem->Paint(aBuilder, aContext);
}
}
itemClipTracker.Restore();
MOZ_ASSERT(opacityLevel == 0);
MOZ_ASSERT(transformLevel == 0);
MOZ_ASSERT(emptyEffectLevel == 0);
}
/**
* Returns true if it is preferred to draw the list of display
* items separately for each rect in the visible region rather
* than clipping to a complex region.
*/
static bool ShouldDrawRectsSeparately(DrawTarget* aDrawTarget,
DrawRegionClip aClip) {
if (!StaticPrefs::layout_paint_rects_separately_AtStartup() ||
aClip == DrawRegionClip::NONE) {
return false;
}
return !aDrawTarget->SupportsRegionClipping();
}
static void DrawForcedBackgroundColor(DrawTarget& aDrawTarget,
const IntRect& aBounds,
nscolor aBackgroundColor) {
if (NS_GET_A(aBackgroundColor) > 0) {
ColorPattern color(ToDeviceColor(aBackgroundColor));
aDrawTarget.FillRect(Rect(aBounds), color);
}
}
/*
* A note on residual transforms:
*
* In a transformed subtree we sometimes apply the PaintedLayer's
* "residual transform" when drawing content into the PaintedLayer.
* This is a translation by components in the range [-0.5,0.5) provided
* by the layer system; applying the residual transform followed by the
* transforms used by layer compositing ensures that the subpixel alignment
* of the content of the PaintedLayer exactly matches what it would be if
* we used cairo/Thebes to draw directly to the screen without going through
* retained layer buffers.
*
* The visible and valid regions of the PaintedLayer are computed without
* knowing the residual transform (because we don't know what the residual
* transform is going to be until we've built the layer tree!). So we have to
* consider whether content painted in the range [x, xmost) might be painted
* outside the visible region we computed for that content. The visible region
* would be [floor(x), ceil(xmost)). The content would be rendered at
* [x + r, xmost + r), where -0.5 <= r < 0.5. So some half-rendered pixels could
* indeed fall outside the computed visible region, which is not a big deal;
* similar issues already arise when we snap cliprects to nearest pixels.
* Note that if the rendering of the content is snapped to nearest pixels ---
* which it often is --- then the content is actually rendered at
* [snap(x + r), snap(xmost + r)). It turns out that floor(x) <= snap(x + r)
* and ceil(xmost) >= snap(xmost + r), so the rendering of snapped content
* always falls within the visible region we computed.
*/
/* static */
void FrameLayerBuilder::DrawPaintedLayer(PaintedLayer* aLayer,
gfxContext* aContext,
const nsIntRegion& aRegionToDraw,
const nsIntRegion& aDirtyRegion,
DrawRegionClip aClip,
const nsIntRegion& aRegionToInvalidate,
void* aCallbackData) {
DrawTarget& aDrawTarget = *aContext->GetDrawTarget();
AUTO_PROFILER_LABEL("FrameLayerBuilder::DrawPaintedLayer",
GRAPHICS_Rasterization);
nsDisplayListBuilder* builder =
static_cast<nsDisplayListBuilder*>(aCallbackData);
FrameLayerBuilder* layerBuilder = aLayer->Manager()->GetLayerBuilder();
NS_ASSERTION(layerBuilder, "Unexpectedly null layer builder!");
auto* userData = GetPaintedDisplayItemLayerUserData(aLayer);
NS_ASSERTION(userData, "where did our user data go?");
if (!userData->mContainerLayerFrame) {
return;
}
bool shouldDrawRectsSeparately =
ShouldDrawRectsSeparately(&aDrawTarget, aClip);
if (!shouldDrawRectsSeparately) {
if (aClip == DrawRegionClip::DRAW) {
gfxUtils::ClipToRegion(aContext, aRegionToDraw);
}
DrawForcedBackgroundColor(aDrawTarget, aRegionToDraw.GetBounds(),
userData->mForcedBackgroundColor);
}
// make the origin of the context coincide with the origin of the
// PaintedLayer
gfxContextMatrixAutoSaveRestore saveMatrix(aContext);
nsIntPoint offset = GetTranslationForPaintedLayer(aLayer);
nsPresContext* presContext = userData->mContainerLayerFrame->PresContext();
if (!userData->mVisibilityComputedRegion.Contains(aDirtyRegion) &&
!layerBuilder->GetContainingPaintedLayerData()) {
// Recompute visibility of items in our PaintedLayer, if required. Note
// that this recomputes visibility for all descendants of our display
// items too, so there's no need to do this for the items in inactive
// PaintedLayers. If aDirtyRegion has not changed since the previous call
// then we can skip this.
int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
RecomputeVisibilityForItems(userData->mItems, builder, aDirtyRegion,
userData->mPreviousRecomputeVisibilityRect,
offset, appUnitsPerDevPixel, userData->mXScale,
userData->mYScale);
userData->mVisibilityComputedRegion = aDirtyRegion;
}
if (shouldDrawRectsSeparately) {
for (auto iter = aRegionToDraw.RectIter(); !iter.Done(); iter.Next()) {
const nsIntRect& iterRect = iter.Get();
gfxContextAutoSaveRestore save(aContext);
aContext->NewPath();
aContext->Rectangle(ThebesRect(iterRect));
aContext->Clip();
DrawForcedBackgroundColor(aDrawTarget, iterRect,
userData->mForcedBackgroundColor);
// Apply the residual transform if it has been enabled, to ensure that
// snapping when we draw into aContext exactly matches the ideal
// transform. See above for why this is OK.
aContext->SetMatrixDouble(
aContext->CurrentMatrixDouble()
.PreTranslate(aLayer->GetResidualTranslation() -
gfxPoint(offset.x, offset.y))
.PreScale(userData->mXScale, userData->mYScale));
layerBuilder->PaintItems(userData->mItems, iterRect, aContext, builder,
presContext, offset, userData->mXScale,
userData->mYScale);
if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) {
aLayer->Manager()->AddPaintedPixelCount(iterRect.Area());
}
}
} else {
// Apply the residual transform if it has been enabled, to ensure that
// snapping when we draw into aContext exactly matches the ideal transform.
// See above for why this is OK.
aContext->SetMatrixDouble(
aContext->CurrentMatrixDouble()
.PreTranslate(aLayer->GetResidualTranslation() -
gfxPoint(offset.x, offset.y))
.PreScale(userData->mXScale, userData->mYScale));
layerBuilder->PaintItems(userData->mItems, aRegionToDraw.GetBounds(),
aContext, builder, presContext, offset,
userData->mXScale, userData->mYScale);
if (StaticPrefs::gfx_logging_painted_pixel_count_enabled()) {
aLayer->Manager()->AddPaintedPixelCount(aRegionToDraw.GetBounds().Area());
}
}
bool isActiveLayerManager = !aLayer->Manager()->IsInactiveLayerManager();
if (presContext->GetPaintFlashing() && isActiveLayerManager) {
gfxContextAutoSaveRestore save(aContext);
if (shouldDrawRectsSeparately) {
if (aClip == DrawRegionClip::DRAW) {
gfxUtils::ClipToRegion(aContext, aRegionToDraw);
}
}
FlashPaint(aContext);
}
if (presContext->GetDocShell() && isActiveLayerManager) {
nsDocShell* docShell = static_cast<nsDocShell*>(presContext->GetDocShell());
RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
if (timelines && timelines->HasConsumer(docShell)) {
timelines->AddMarkerForDocShell(
docShell, MakeUnique<LayerTimelineMarker>(aRegionToDraw));
}
}
if (!aRegionToInvalidate.IsEmpty()) {
aLayer->AddInvalidRect(aRegionToInvalidate.GetBounds());
}
}
/* static */
void FrameLayerBuilder::DumpRetainedLayerTree(LayerManager* aManager,
std::stringstream& aStream,
bool aDumpHtml) {
aManager->Dump(aStream, "", aDumpHtml);
}
nsDisplayItemGeometry* FrameLayerBuilder::GetMostRecentGeometry(
nsDisplayItem* aItem) {
// Retrieve the array of DisplayItemData associated with our frame.
// Find our display item data, if it exists, and return its geometry.
// We first check for ones with an inactive manager, since items that
// create inactive layers will create two DisplayItemData entries,
// and we want the outer one.
DisplayItemData* firstMatching = nullptr;
uint32_t itemPerFrameKey = aItem->GetPerFrameKey();
for (auto* did : GetDisplayItemDataArray(aItem->Frame())) {
auto* data = DisplayItemData::AssertDisplayItemData(did);
if (data->GetDisplayItemKey() == itemPerFrameKey) {
if (data->InactiveManager()) {
return data->GetGeometry();
}
if (!firstMatching) {
firstMatching = data;
}
}
}
if (firstMatching) {
return firstMatching->GetGeometry();
}
if (RefPtr<WebRenderFallbackData> data =
GetWebRenderUserData<WebRenderFallbackData>(aItem->Frame(),
itemPerFrameKey)) {
return data->GetGeometry();
}
return nullptr;
}
static gfx::Rect CalculateBounds(
const nsTArray<DisplayItemClip::RoundedRect>& aRects,
int32_t aAppUnitsPerDevPixel) {
nsRect bounds = aRects[0].mRect;
for (uint32_t i = 1; i < aRects.Length(); ++i) {
bounds.UnionRect(bounds, aRects[i].mRect);
}
return gfx::Rect(bounds.ToNearestPixels(aAppUnitsPerDevPixel));
}
void ContainerState::SetupMaskLayer(Layer* aLayer,
const DisplayItemClip& aClip) {
// don't build an unnecessary mask
if (aClip.GetRoundedRectCount() == 0) {
return;
}
RefPtr<Layer> maskLayer = CreateMaskLayer(aLayer, aClip, Nothing());
if (!maskLayer) {
return;
}
aLayer->SetMaskLayer(maskLayer);
}
static MaskLayerUserData* GetMaskLayerUserData(Layer* aMaskLayer) {
if (!aMaskLayer) {
return nullptr;
}
return static_cast<MaskLayerUserData*>(
aMaskLayer->GetUserData(&gMaskLayerUserData));
}
static void SetMaskLayerUserData(Layer* aMaskLayer) {
MOZ_ASSERT(aMaskLayer);
aMaskLayer->SetUserData(&gMaskLayerUserData, new MaskLayerUserData());
}
already_AddRefed<Layer> ContainerState::CreateMaskLayer(
Layer* aLayer, const DisplayItemClip& aClip,
const Maybe<size_t>& aForAncestorMaskLayer) {
// aLayer will never be the container layer created by an
// nsDisplayMasksAndClipPaths because nsDisplayMasksAndClipPaths propagates
// the DisplayItemClip to its contents and is not clipped itself.
// This assertion will fail if that ever stops being the case.
MOZ_ASSERT(!aLayer->GetUserData(&gCSSMaskLayerUserData),
"A layer contains round clips should not have css-mask on it.");
// check if we can re-use the mask layer
RefPtr<ImageLayer> maskLayer = CreateOrRecycleMaskImageLayerFor(
MaskLayerKey(aLayer, aForAncestorMaskLayer), GetMaskLayerUserData,
SetMaskLayerUserData);
MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer.get());
int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
MaskLayerUserData newData(aClip, A2D, mParameters);
if (*userData == newData) {
return maskLayer.forget();
}
gfx::Rect boundingRect =
CalculateBounds(newData.mRoundedClipRects, newData.mAppUnitsPerDevPixel);
boundingRect.Scale(mParameters.mXScale, mParameters.mYScale);
if (boundingRect.IsEmpty()) {
// Return early if we know that there is effectively no visible data.
return nullptr;
}
uint32_t maxSize = mManager->GetMaxTextureSize();
NS_ASSERTION(maxSize > 0, "Invalid max texture size");
#ifdef MOZ_GFX_OPTIMIZE_MOBILE
// Make mask image width aligned to 4. See Bug 1245552.
gfx::Size surfaceSize(
std::min<gfx::Float>(
GetAlignedStride<4>(NSToIntCeil(boundingRect.Width()), 1), maxSize),
std::min<gfx::Float>(boundingRect.Height(), maxSize));
#else
gfx::Size surfaceSize(std::min<gfx::Float>(boundingRect.Width(), maxSize),
std::min<gfx::Float>(boundingRect.Height(), maxSize));
#endif
// maskTransform is applied to the clip when it is painted into the mask (as a
// component of imageTransform), and its inverse used when the mask is used
// for masking. It is the transform from the masked layer's space to mask
// space
gfx::Matrix maskTransform =
Matrix::Scaling(surfaceSize.width / boundingRect.Width(),
surfaceSize.height / boundingRect.Height());
if (surfaceSize.IsEmpty()) {
// Return early if we know that the size of this mask surface is empty.
return nullptr;
}
gfx::Point p = boundingRect.TopLeft();
maskTransform.PreTranslate(-p.x, -p.y);
// imageTransform is only used when the clip is painted to the mask
gfx::Matrix imageTransform = maskTransform;
imageTransform.PreScale(mParameters.mXScale, mParameters.mYScale);
UniquePtr<MaskLayerImageCache::MaskLayerImageKey> newKey(
new MaskLayerImageCache::MaskLayerImageKey());
// copy and transform the rounded rects
for (uint32_t i = 0; i < newData.mRoundedClipRects.Length(); ++i) {
newKey->mRoundedClipRects.AppendElement(
MaskLayerImageCache::PixelRoundedRect(newData.mRoundedClipRects[i],
mContainerFrame->PresContext()));
newKey->mRoundedClipRects[i].ScaleAndTranslate(imageTransform);
}
newKey->mKnowsCompositor = mManager->AsKnowsCompositor();
const MaskLayerImageCache::MaskLayerImageKey* lookupKey = newKey.get();
// check to see if we can reuse a mask image
RefPtr<ImageContainer> container =
GetMaskLayerImageCache()->FindImageFor(&lookupKey);
if (!container) {
IntSize surfaceSizeInt(NSToIntCeil(surfaceSize.width),
NSToIntCeil(surfaceSize.height));
// no existing mask image, so build a new one
MaskImageData imageData(surfaceSizeInt, mManager);
RefPtr<DrawTarget> dt = imageData.CreateDrawTarget();
// fail if we can't get the right surface
if (!dt || !dt->IsValid()) {
NS_WARNING("Could not create DrawTarget for mask layer.");
return nullptr;
}
RefPtr<gfxContext> context = gfxContext::CreateOrNull(dt);
MOZ_ASSERT(context); // already checked the draw target above
context->Multiply(ThebesMatrix(imageTransform));
// paint the clipping rects with alpha to create the mask
aClip.FillIntersectionOfRoundedRectClips(
context, DeviceColor::MaskOpaqueWhite(), newData.mAppUnitsPerDevPixel);
// build the image and container
MOZ_ASSERT(aLayer->Manager() == mManager);
container = imageData.CreateImageAndImageContainer();
NS_ASSERTION(container, "Could not create image container for mask layer.");
if (!container) {
return nullptr;
}
GetMaskLayerImageCache()->PutImage(newKey.release(), container);
}
maskLayer->SetContainer(container);
maskTransform.Invert();
Matrix4x4 matrix = Matrix4x4::From2D(maskTransform);
matrix.PreTranslate(mParameters.mOffset.x, mParameters.mOffset.y, 0);
maskLayer->SetBaseTransform(matrix);
// save the details of the clip in user data
*userData = std::move(newData);
userData->mImageKey.Reset(lookupKey);
return maskLayer.forget();
}
} // namespace mozilla