Bug 1434243 - Part 2: Add nsDisplayCompositorHitTestInfo support to FrameLayerBuilder
MozReview-Commit-ID: 5RdFJDyB6RN
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -667,16 +667,17 @@ private:
DECL_GFX_PREF(Live, "layout.display-list.retain.verify", LayoutVerifyRetainDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.retain.verify.order", LayoutVerifyRetainDisplayListOrder, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.rebuild-frame-limit", LayoutRebuildFrameLimit, uint32_t, 500);
DECL_GFX_PREF(Live, "layout.display-list.dump", LayoutDumpDisplayList, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.dump-content", LayoutDumpDisplayListContent, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.dump-parent", LayoutDumpDisplayListParent, bool, false);
DECL_GFX_PREF(Live, "layout.display-list.show-rebuild-area", LayoutDisplayListShowArea, bool, false);
+ DECL_GFX_PREF(Once, "layout.simple-event-region-items", SimpleEventRegionItems, bool, true);
DECL_GFX_PREF(Live, "layout.event-regions.enabled", LayoutEventRegionsEnabledDoNotUseDirectly, bool, false);
DECL_GFX_PREF(Once, "layout.frame_rate", LayoutFrameRate, int32_t, -1);
DECL_GFX_PREF(Live, "layout.min-active-layer-size", LayoutMinActiveLayerSize, int, 64);
DECL_GFX_PREF(Once, "layout.paint_rects_separately", LayoutPaintRectsSeparately, bool, true);
// This and code dependent on it should be removed once containerless scrolling looks stable.
DECL_GFX_PREF(Once, "layout.scroll.root-frame-containers", LayoutUseContainersForRootFrames, bool, true);
// This pref is to be set by test code only.
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -483,20 +483,27 @@ public:
AnimatedGeometryRoot* GetAnimatedGeometryRoot() { return mAnimatedGeometryRoot; }
/**
* A region including the horizontal pan, vertical pan, and no action regions.
*/
nsRegion CombinedTouchActionRegion();
/**
- * Add the given hit regions to the hit regions to the hit retions for this
- * PaintedLayer.
+ * Add the given hit regions to the hit regions for this PaintedLayer.
*/
- void AccumulateEventRegions(ContainerState* aState, nsDisplayLayerEventRegions* aEventRegions);
+ void AccumulateEventRegions(ContainerState* aState,
+ nsDisplayLayerEventRegions* aEventRegions);
+
+ /**
+ * Similar to AccumulateEventRegions() but uses different display item to
+ * track hit regions.
+ */
+ void AccumulateHitTestInfo(ContainerState* aState,
+ nsDisplayCompositorHitTestInfo* aItem);
/**
* If this represents only a nsDisplayImage, and the image type supports being
* optimized to an ImageLayer, returns true.
*/
bool CanOptimizeToImageLayer(nsDisplayListBuilder* aBuilder);
/**
@@ -3140,16 +3147,17 @@ void ContainerState::FinishPaintedLayerD
NS_ASSERTION(FindIndexOfLayerIn(mNewChildLayers, paintedLayer) < 0,
"Layer already in list???");
mNewChildLayers[data->mNewChildLayersIndex].mLayer = paintedLayer.forget();
}
for (auto& item : data->mAssignedDisplayItems) {
MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_LAYER_EVENT_REGIONS);
+ MOZ_ASSERT(item.mItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO);
DisplayItemData* oldData =
mLayerBuilder->GetOldLayerForFrame(item.mItem->Frame(), item.mItem->GetPerFrameKey());
InvalidateForLayerChange(item.mItem, data->mLayer, oldData);
mLayerBuilder->AddPaintedDisplayItem(data, item.mItem, item.mClip,
*this, item.mLayerState,
data->mAnimatedGeometryRootOffset,
oldData);
@@ -3597,16 +3605,118 @@ PaintedLayerData::AccumulateEventRegions
// Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
// for quick access in FindPaintedLayerFor().
mScaledHitRegionBounds = aState->ScaleToOutsidePixels(mHitRegion.GetBounds());
mScaledMaybeHitRegionBounds = aState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
}
void
+PaintedLayerData::AccumulateHitTestInfo(ContainerState* aState,
+ nsDisplayCompositorHitTestInfo* aItem)
+{
+ FLB_LOG_PAINTED_LAYER_DECISION(this,
+ "Accumulating hit test info %p against pld=%p\n", aItem, this);
+
+ const mozilla::DisplayItemClip& clip = aItem->GetClip();
+ const nsRect area = clip.ApplyNonRoundedIntersection(aItem->Area());
+ const mozilla::gfx::CompositorHitTestInfo hitTestInfo = aItem->HitTestInfo();
+
+ bool hasRoundedCorners = clip.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->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
+ mMaybeHitRegion.OrWith(area);
+ } else {
+ mHitRegion.OrWith(area);
+ }
+
+ if (aItem->HitTestInfo() & CompositorHitTestInfo::eDispatchToContent) {
+ mDispatchToContentHitRegion.OrWith(area);
+ }
+
+ auto touchFlags = hitTestInfo & CompositorHitTestInfo::eTouchActionMask;
+ if (touchFlags) {
+ // something was disabled
+ if (touchFlags == CompositorHitTestInfo::eTouchActionMask) {
+ // 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 != CompositorHitTestInfo::eTouchActionDoubleTapZoomDisabled) {
+ if (!(hitTestInfo & CompositorHitTestInfo::eTouchActionPanXDisabled)) {
+ // pan-x is allowed
+ mHorizontalPanRegion.OrWith(area);
+ }
+ if (!(hitTestInfo & CompositorHitTestInfo::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 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());
+ }
+
+ // 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);
+
+ // Calculate scaled versions of the bounds of mHitRegion and mMaybeHitRegion
+ // for quick access in FindPaintedLayerFor().
+ mScaledHitRegionBounds =
+ aState->ScaleToOutsidePixels(mHitRegion.GetBounds());
+ mScaledMaybeHitRegionBounds =
+ aState->ScaleToOutsidePixels(mMaybeHitRegion.GetBounds());
+}
+
+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)
@@ -3970,34 +4080,58 @@ ContainerState::ProcessDisplayItems(nsDi
if (ChooseAnimatedGeometryRoot(*aList, &lastAnimatedGeometryRoot, &lastASR)) {
lastAGRTopLeft = (*lastAnimatedGeometryRoot)->GetOffsetToCrossDoc(mContainerReferenceFrame);
}
}
int32_t maxLayers = gfxPrefs::MaxActiveLayers();
int layerCount = 0;
+#ifdef DEBUG
+ bool hadLayerEventRegions = false;
+ bool hadCompositorHitTestInfo = false;
+#endif
+
FlattenedDisplayItemIterator iter(mBuilder, aList);
while (nsDisplayItem* i = iter.GetNext()) {
nsDisplayItem* item = i;
MOZ_ASSERT(item);
DisplayItemType itemType = item->GetType();
// If the item is a event regions item, but is empty (has no regions in it)
// then we should just throw it out
if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
+#ifdef DEBUG
+ hadLayerEventRegions = true;
+#endif
nsDisplayLayerEventRegions* eventRegions =
static_cast<nsDisplayLayerEventRegions*>(item);
if (eventRegions->IsEmpty()) {
continue;
}
}
+ if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+#ifdef DEBUG
+ hadCompositorHitTestInfo = true;
+#endif
+ nsDisplayCompositorHitTestInfo* hitTestInfo =
+ static_cast<nsDisplayCompositorHitTestInfo*>(item);
+
+ if (hitTestInfo->Area().IsEmpty()) {
+ continue;
+ }
+ }
+
+ // Only allow either LayerEventRegions or CompositorHitTestInfo items.
+ MOZ_ASSERT(!(hadLayerEventRegions && hadCompositorHitTestInfo));
+
+
// Peek ahead to the next item and see if it can be merged with the current
// item. We create a list of consecutive items that can be merged together.
AutoTArray<nsDisplayItem*, 1> mergedItems;
mergedItems.AppendElement(item);
while (nsDisplayItem* peek = iter.PeekNext()) {
if (!item->CanMerge(peek)) {
break;
}
@@ -4021,16 +4155,17 @@ ContainerState::ProcessDisplayItems(nsDi
"items in a container layer should all have the same app units per dev pixel");
if (mBuilder->NeedToForceTransparentSurfaceForItem(item)) {
aList->SetNeedsTransparentSurface();
}
if (mParameters.mForEventsAndPluginsOnly && !item->GetChildren() &&
(itemType != DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
+ itemType != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO &&
itemType != DisplayItemType::TYPE_PLUGIN)) {
continue;
}
LayerState layerState = item->GetLayerState(mBuilder, mManager, mParameters);
if (layerState == LAYER_INACTIVE &&
nsDisplayItem::ForceActiveLayers()) {
layerState = LAYER_ACTIVE;
@@ -4456,18 +4591,22 @@ ContainerState::ProcessDisplayItems(nsDi
[&](PaintedLayerData* aData) {
NewPaintedLayerData(aData, animatedGeometryRoot, itemASR,
layerClipChain, scrollMetadataASR, topLeft,
referenceFrame, backfaceHidden);
});
if (itemType == DisplayItemType::TYPE_LAYER_EVENT_REGIONS) {
nsDisplayLayerEventRegions* eventRegions =
- static_cast<nsDisplayLayerEventRegions*>(item);
+ static_cast<nsDisplayLayerEventRegions*>(item);
paintedLayerData->AccumulateEventRegions(this, eventRegions);
+ } else if (itemType == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ nsDisplayCompositorHitTestInfo* hitTestInfo =
+ static_cast<nsDisplayCompositorHitTestInfo*>(item);
+ paintedLayerData->AccumulateHitTestInfo(this, hitTestInfo);
} else {
// check to see if the new item has rounded rect clips in common with
// other items in the layer
if (mManager->IsWidgetLayerManager()) {
paintedLayerData->UpdateCommonClipCount(itemClip);
}
paintedLayerData->Accumulate(this, item, itemVisibleRect, itemClip, layerState, aList);
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -1023,20 +1023,21 @@ nsDisplayListBuilder::nsDisplayListBuild
mAsyncPanZoomEnabled(nsLayoutUtils::AsyncPanZoomEnabled(aReferenceFrame)),
mBuildingInvisibleItems(false),
mHitTestIsForVisibility(false),
mIsBuilding(false),
mInInvalidSubtree(false)
{
MOZ_COUNT_CTOR(nsDisplayListBuilder);
- mBuildCompositorHitTestInfo = gfxVars::UseWebRender()
- && gfxPrefs::WebRenderHitTest()
- && mAsyncPanZoomEnabled
- && mMode == nsDisplayListBuilderMode::PAINTING;
+ const bool useWRHitTest =
+ gfxPrefs::WebRenderHitTest() && gfxVars::UseWebRender();
+
+ mBuildCompositorHitTestInfo = mAsyncPanZoomEnabled && IsForPainting() &&
+ (useWRHitTest || gfxPrefs::SimpleEventRegionItems());
nsPresContext* pc = aReferenceFrame->PresContext();
nsIPresShell *shell = pc->PresShell();
if (pc->IsRenderingOnlySelection()) {
nsCOMPtr<nsISelectionController> selcon(do_QueryInterface(shell));
if (selcon) {
selcon->GetSelection(nsISelectionController::SELECTION_NORMAL,
getter_AddRefs(mBoundingSelection));
@@ -1353,16 +1354,17 @@ nsDisplayListBuilder::EnterPresShell(nsI
// A non-blank paint is a paint that does not just contain the canvas background.
static bool
DisplayListIsNonBlank(nsDisplayList* aList)
{
for (nsDisplayItem* i = aList->GetBottom(); i != nullptr; i = i->GetAbove()) {
switch (i->GetType()) {
case DisplayItemType::TYPE_LAYER_EVENT_REGIONS:
+ case DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO:
case DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR:
case DisplayItemType::TYPE_CANVAS_BACKGROUND_IMAGE:
continue;
case DisplayItemType::TYPE_SOLID_COLOR:
case DisplayItemType::TYPE_BACKGROUND:
case DisplayItemType::TYPE_BACKGROUND_COLOR:
if (i->Frame()->IsCanvasFrame()) {
continue;
@@ -2298,17 +2300,18 @@ nsDisplayList::ComputeVisibilityForRoot(
}
static nsRegion
TreatAsOpaque(nsDisplayItem* aItem, nsDisplayListBuilder* aBuilder)
{
bool snap;
nsRegion opaque = aItem->GetOpaqueRegion(aBuilder, &snap);
if (aBuilder->IsForPluginGeometry() &&
- aItem->GetType() != DisplayItemType::TYPE_LAYER_EVENT_REGIONS)
+ aItem->GetType() != DisplayItemType::TYPE_LAYER_EVENT_REGIONS &&
+ aItem->GetType() != DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO)
{
// Treat all leaf chrome items as opaque, unless their frames are opacity:0.
// Since opacity:0 frames generate an nsDisplayOpacity, that item will
// not be treated as opaque here, so opacity:0 chrome content will be
// effectively ignored, as it should be.
// We treat leaf chrome items as opaque to ensure that they cover
// content plugins, for security reasons.
// Non-leaf chrome items don't render contents of their own so shouldn't
@@ -4970,57 +4973,53 @@ nsDisplayEventReceiver::CreateWebRenderC
nsDisplayCompositorHitTestInfo::nsDisplayCompositorHitTestInfo(nsDisplayListBuilder* aBuilder,
nsIFrame* aFrame,
mozilla::gfx::CompositorHitTestInfo aHitTestInfo,
uint32_t aIndex,
const mozilla::Maybe<nsRect>& aArea)
: nsDisplayEventReceiver(aBuilder, aFrame)
, mHitTestInfo(aHitTestInfo)
, mIndex(aIndex)
+ , mAppUnitsPerDevPixel(mFrame->PresContext()->AppUnitsPerDevPixel())
{
MOZ_COUNT_CTOR(nsDisplayCompositorHitTestInfo);
// We should never even create this display item if we're not building
// compositor hit-test info or if the computed hit info indicated the
// frame is invisible to hit-testing
MOZ_ASSERT(aBuilder->BuildCompositorHitTestInfo());
MOZ_ASSERT(mHitTestInfo != mozilla::gfx::CompositorHitTestInfo::eInvisibleToHitTest);
if (aBuilder->GetCurrentScrollbarFlags() != nsDisplayOwnLayerFlags::eNone) {
// In the case of scrollbar frames, we use the scrollbar's target scrollframe
// instead of the scrollframe with which the scrollbar actually moves.
MOZ_ASSERT(mHitTestInfo & CompositorHitTestInfo::eScrollbar);
mScrollTarget = Some(aBuilder->GetCurrentScrollbarTarget());
}
- nsRect area;
if (aArea.isSome()) {
- area = *aArea;
+ mArea = *aArea;
} else {
nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(mFrame);
if (scrollFrame) {
// If the frame is content of a scrollframe, then we need to pick up the
// area corresponding to the overflow rect as well. Otherwise the parts of
// the overflow that are not occupied by descendants get skipped and the
// APZ code sends touch events to the content underneath instead.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1127773#c15.
- area = mFrame->GetScrollableOverflowRect();
+ mArea = mFrame->GetScrollableOverflowRect();
} else {
- area = nsRect(nsPoint(0, 0), mFrame->GetSize());
+ mArea = nsRect(nsPoint(0, 0), mFrame->GetSize());
}
// Note that it's important to do this call to ToReferenceFrame here in the
// nsDisplayCompositorHitTestInfo constructor, because then we'll hit the good
// fast path (because aBuilder will already have the info we want cached).
// This is as opposed to, say, calling it in CreateWebRenderCommands where
// we would not hit the fast path.
- area += aBuilder->ToReferenceFrame(mFrame);
- }
-
- if (!area.IsEmpty()) {
- mArea = LayoutDeviceRect::FromAppUnits(area, mFrame->PresContext()->AppUnitsPerDevPixel());
+ mArea += aBuilder->ToReferenceFrame(mFrame);
}
}
bool
nsDisplayCompositorHitTestInfo::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
mozilla::wr::IpcResourceUpdateQueue& aResources,
const StackingContextHelper& aSc,
mozilla::layers::WebRenderLayerManager* aManager,
@@ -5040,17 +5039,22 @@ nsDisplayCompositorHitTestInfo::CreateWe
if (const ActiveScrolledRoot* asr = GetActiveScrolledRoot()) {
return asr->GetViewId();
}
return FrameMetrics::NULL_SCROLL_ID;
});
// Insert a transparent rectangle with the hit-test info
aBuilder.SetHitTestInfo(scrollId, mHitTestInfo);
- wr::LayoutRect rect = aSc.ToRelativeLayoutRect(mArea);
+
+ const LayoutDeviceRect devRect =
+ LayoutDeviceRect::FromAppUnits(mArea, mAppUnitsPerDevPixel);
+
+ const wr::LayoutRect rect = aSc.ToRelativeLayoutRect(devRect);
+
aBuilder.PushRect(rect, rect, true, wr::ToColorF(gfx::Color()));
aBuilder.ClearHitTestInfo();
return true;
}
void
nsDisplayCompositorHitTestInfo::WriteDebugInfo(std::stringstream& aStream)
@@ -6556,16 +6560,17 @@ CollectItemsWithOpacity(nsDisplayList* a
if (type == DisplayItemType::TYPE_WRAP_LIST && children) {
// The current display item has children, process them first.
if (!CollectItemsWithOpacity(children, aArray, aMaxChildCount)) {
return false;
}
}
if (type == DisplayItemType::TYPE_LAYER_EVENT_REGIONS ||
+ type == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO ||
type == DisplayItemType::TYPE_WRAP_LIST) {
continue;
}
if (!i->CanApplyOpacity() || aArray.Length() == aMaxChildCount) {
return false;
}
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -4532,24 +4532,47 @@ public:
const StackingContextHelper& aSc,
mozilla::layers::WebRenderLayerManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) override;
void WriteDebugInfo(std::stringstream& aStream) override;
uint32_t GetPerFrameKey() const override;
int32_t ZIndex() const override;
void SetOverrideZIndex(int32_t aZIndex);
+ /**
+ * Returns the hit test area of this item.
+ */
+ const nsRect& Area() const { return mArea; }
+
+ /**
+ * ApplyOpacity() is overriden for opacity flattening.
+ */
+ void ApplyOpacity(nsDisplayListBuilder* aBuilder, float aOpacity,
+ const DisplayItemClipChain* aClip) override {}
+
+ /**
+ * CanApplyOpacity() is overriden for opacity flattening.
+ */
+ bool CanApplyOpacity() const override { return true; }
+
+ nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) const override
+ {
+ *aSnap = false;
+ return nsRect();
+ }
+
NS_DISPLAY_DECL_NAME("CompositorHitTestInfo", TYPE_COMPOSITOR_HITTEST_INFO)
private:
mozilla::gfx::CompositorHitTestInfo mHitTestInfo;
mozilla::Maybe<mozilla::layers::FrameMetrics::ViewID> mScrollTarget;
- mozilla::LayoutDeviceRect mArea;
+ nsRect mArea;
uint32_t mIndex;
mozilla::Maybe<int32_t> mOverrideZIndex;
+ int32_t mAppUnitsPerDevPixel;
};
/**
* A display item that tracks event-sensitive regions which will be set
* on the ContainerLayer that eventually contains this item.
*
* One of these is created for each stacking context and pseudo-stacking-context.
* It accumulates regions for event targets contributed by the border-boxes of