Bug 1404181 - Part 5: Store an annotated list of rectangles in nsDisplayLayerEventRegions so that we can remove contributions that belong to invalidated frames. r?mstange
MozReview-Commit-ID: G2kTPnrhxs4
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2675,17 +2675,17 @@ nsIFrame::BuildDisplayListForStackingCon
BuildDisplayList(aBuilder, set);
if (eventRegions) {
// If the event regions item ended up empty, throw it away rather than
// adding it to the display list.
if (!eventRegions->IsEmpty()) {
set.BorderBackground()->AppendToBottom(eventRegions);
} else {
aBuilder->SetLayerEventRegions(nullptr);
- eventRegions->~nsDisplayLayerEventRegions();
+ eventRegions->Destroy(aBuilder);
eventRegions = nullptr;
}
}
}
if (aBuilder->IsBackgroundOnly()) {
set.BlockBorderBackgrounds()->DeleteAll(aBuilder);
set.Floats()->DeleteAll(aBuilder);
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3608,18 +3608,18 @@ ScrollFrameHelper::BuildDisplayList(nsDi
if (couldBuildLayer) {
// Make sure that APZ will dispatch events back to content so we can create
// a displayport for this frame. We'll add the item later on.
nsDisplayLayerEventRegions* inactiveRegionItem = nullptr;
if (aBuilder->IsPaintingToWindow() &&
!mWillBuildScrollableLayer &&
aBuilder->IsBuildingLayerEventRegions())
{
- inactiveRegionItem = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame);
- inactiveRegionItem->AddInactiveScrollPort(mScrollPort + aBuilder->ToReferenceFrame(mOuter));
+ inactiveRegionItem = new (aBuilder) nsDisplayLayerEventRegions(aBuilder, mScrolledFrame, 1);
+ inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
}
if (inactiveRegionItem) {
int32_t zIndex =
MaxZIndexInListOfItemsContainedInFrame(scrolledContent.PositionedDescendants(), mOuter);
AppendInternalItemToTop(scrolledContent, inactiveRegionItem, zIndex);
}
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -4612,22 +4612,31 @@ nsDisplayLayerEventRegions::AddFrame(nsD
return;
}
bool simpleRegions = aFrame->HasAnyStateBits(NS_FRAME_SIMPLE_EVENT_REGIONS);
if (!simpleRegions) {
if (!aFrame->StyleVisibility()->IsVisible()) {
return;
}
}
+
+ if (aFrame != mFrame &&
+ aBuilder->IsRetainingDisplayList()) {
+ aFrame->AddDisplayItem(this);
+ }
+
+
// XXX handle other pointerEvents values for SVG
// XXX Do something clever here for the common case where the border box
// is obviously entirely inside mHitRegion.
nsRect borderBox;
- if (nsLayoutUtils::GetScrollableFrameFor(aFrame)) {
+
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(aFrame);
+ 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.
borderBox = aFrame->GetScrollableOverflowRect();
} else {
borderBox = nsRect(nsPoint(0, 0), aFrame->GetSize());
@@ -4650,89 +4659,96 @@ nsDisplayLayerEventRegions::AddFrame(nsD
borderBox = clip->ApplyNonRoundedIntersection(borderBox);
if (clip->GetRoundedRectCount() > 0) {
borderBoxHasRoundedCorners = true;
}
}
if (borderBoxHasRoundedCorners ||
(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
- mMaybeHitRegion.Or(mMaybeHitRegion, borderBox);
-
- // Avoid quadratic performance as a result of the region growing to include
- // an arbitrarily large number of rects, which can happen on some pages.
- mMaybeHitRegion.SimplifyOutward(8);
+ mMaybeHitRegion.Add(aFrame, borderBox);
} else {
- mHitRegion.Or(mHitRegion, borderBox);
+ mHitRegion.Add(aFrame, borderBox);
}
if (aBuilder->IsBuildingNonLayerizedScrollbar() ||
aBuilder->GetAncestorHasApzAwareEventHandler())
{
// Scrollbars may be painted into a layer below the actual layer they will
// scroll, and therefore wheel events may be dispatched to the outer frame
// instead of the intended scrollframe. To address this, we force a d-t-c
// region on scrollbar frames that won't be placed in their own layer. See
// bug 1213324 for details.
- mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
- mDispatchToContentHitRegion.SimplifyOutward(8);
+ mDispatchToContentHitRegion.Add(aFrame, borderBox);
} else if (aFrame->IsObjectFrame()) {
// If the frame is a plugin frame and wants to handle wheel events as
// default action, we should add the frame to dispatch-to-content region.
nsPluginFrame* pluginFrame = do_QueryFrame(aFrame);
if (pluginFrame && pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
- mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, borderBox);
- mDispatchToContentHitRegion.SimplifyOutward(8);
+ mDispatchToContentHitRegion.Add(aFrame, borderBox);
}
}
// Touch action region
nsIFrame* touchActionFrame = aFrame;
- nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetScrollableFrameFor(aFrame);
if (scrollFrame) {
touchActionFrame = do_QueryFrame(scrollFrame);
}
uint32_t touchAction = nsLayoutUtils::GetTouchActionFromFrame(touchActionFrame);
if (touchAction != NS_STYLE_TOUCH_ACTION_AUTO) {
- // If this frame has touch-action areas, and there were already
- // touch-action areas from some other element on this same event regions,
- // then all we know is that there are multiple elements with touch-action
- // properties. In particular, 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. XXX we need to come up with a better way to do this,
- // see bug 1287829.
- bool alreadyHadRegions = !mNoActionRegion.IsEmpty() ||
- !mHorizontalPanRegion.IsEmpty() ||
- !mVerticalPanRegion.IsEmpty();
if (touchAction & NS_STYLE_TOUCH_ACTION_NONE) {
- mNoActionRegion.OrWith(borderBox);
+ mNoActionRegion.Add(aFrame, borderBox);
} else {
if ((touchAction & NS_STYLE_TOUCH_ACTION_PAN_X)) {
- mHorizontalPanRegion.OrWith(borderBox);
+ mHorizontalPanRegion.Add(aFrame, borderBox);
}
if ((touchAction & NS_STYLE_TOUCH_ACTION_PAN_Y)) {
- mVerticalPanRegion.OrWith(borderBox);
+ mVerticalPanRegion.Add(aFrame, borderBox);
}
}
- if (alreadyHadRegions) {
- mDispatchToContentHitRegion.OrWith(CombinedTouchActionRegion());
- mDispatchToContentHitRegion.SimplifyOutward(8);
- }
- }
+ }
+}
+
+static void
+RemoveFrameFromFrameRects(nsDisplayLayerEventRegions::FrameRects& aFrameRects, nsIFrame* aFrame)
+{
+ uint32_t i = 0;
+ uint32_t length = aFrameRects.mFrames.Length();
+ while (i < length) {
+ if (aFrameRects.mFrames[i] == aFrame) {
+ aFrameRects.mFrames[i] = aFrameRects.mFrames[length - 1];
+ aFrameRects.mBoxes[i] = aFrameRects.mBoxes[length - 1];
+ length--;
+ } else {
+ i++;
+ }
+ }
+ aFrameRects.mFrames.SetLength(length);
+ aFrameRects.mBoxes.SetLength(length);
}
void
-nsDisplayLayerEventRegions::AddInactiveScrollPort(const nsRect& aRect)
-{
- mHitRegion.Or(mHitRegion, aRect);
- mDispatchToContentHitRegion.Or(mDispatchToContentHitRegion, aRect);
- mDispatchToContentHitRegion.SimplifyOutward(8);
+nsDisplayLayerEventRegions::RemoveFrame(nsIFrame* aFrame)
+{
+ RemoveFrameFromFrameRects(mHitRegion, aFrame);
+ RemoveFrameFromFrameRects(mMaybeHitRegion, aFrame);
+ RemoveFrameFromFrameRects(mDispatchToContentHitRegion, aFrame);
+ RemoveFrameFromFrameRects(mNoActionRegion, aFrame);
+ RemoveFrameFromFrameRects(mHorizontalPanRegion, aFrame);
+ RemoveFrameFromFrameRects(mVerticalPanRegion, aFrame);
+
+ nsDisplayItem::RemoveFrame(aFrame);
+}
+
+void
+nsDisplayLayerEventRegions::AddInactiveScrollPort(nsIFrame* aFrame, const nsRect& aRect)
+{
+ mHitRegion.Add(aFrame, aRect);
+ mDispatchToContentHitRegion.Add(aFrame, aRect);
}
bool
nsDisplayLayerEventRegions::IsEmpty() const
{
// If the hit region and maybe-hit region are empty, then the rest
// must be empty too.
if (mHitRegion.IsEmpty() && mMaybeHitRegion.IsEmpty()) {
@@ -4744,18 +4760,18 @@ nsDisplayLayerEventRegions::IsEmpty() co
}
return false;
}
nsRegion
nsDisplayLayerEventRegions::CombinedTouchActionRegion()
{
nsRegion result;
- result.Or(mHorizontalPanRegion, mVerticalPanRegion);
- result.OrWith(mNoActionRegion);
+ result.Or(HorizontalPanRegion(), VerticalPanRegion());
+ result.OrWith(NoActionRegion());
return result;
}
int32_t
nsDisplayLayerEventRegions::ZIndex() const
{
return mOverrideZIndex ? *mOverrideZIndex : nsDisplayItem::ZIndex();
}
@@ -4765,32 +4781,32 @@ nsDisplayLayerEventRegions::SetOverrideZ
{
mOverrideZIndex = Some(aZIndex);
}
void
nsDisplayLayerEventRegions::WriteDebugInfo(std::stringstream& aStream)
{
if (!mHitRegion.IsEmpty()) {
- AppendToString(aStream, mHitRegion, " (hitRegion ", ")");
+ AppendToString(aStream, HitRegion(), " (hitRegion ", ")");
}
if (!mMaybeHitRegion.IsEmpty()) {
- AppendToString(aStream, mMaybeHitRegion, " (maybeHitRegion ", ")");
+ AppendToString(aStream, MaybeHitRegion(), " (maybeHitRegion ", ")");
}
if (!mDispatchToContentHitRegion.IsEmpty()) {
- AppendToString(aStream, mDispatchToContentHitRegion, " (dispatchToContentRegion ", ")");
+ AppendToString(aStream, DispatchToContentHitRegion(), " (dispatchToContentRegion ", ")");
}
if (!mNoActionRegion.IsEmpty()) {
- AppendToString(aStream, mNoActionRegion, " (noActionRegion ", ")");
+ AppendToString(aStream, NoActionRegion(), " (noActionRegion ", ")");
}
if (!mHorizontalPanRegion.IsEmpty()) {
- AppendToString(aStream, mHorizontalPanRegion, " (horizPanRegion ", ")");
+ AppendToString(aStream, HorizontalPanRegion(), " (horizPanRegion ", ")");
}
if (!mVerticalPanRegion.IsEmpty()) {
- AppendToString(aStream, mVerticalPanRegion, " (vertPanRegion ", ")");
+ AppendToString(aStream, VerticalPanRegion(), " (vertPanRegion ", ")");
}
}
nsDisplayCaret::nsDisplayCaret(nsDisplayListBuilder* aBuilder,
nsIFrame* aCaretFrame)
: nsDisplayItem(aBuilder, aCaretFrame)
, mCaret(aBuilder->GetCaret())
, mBounds(aBuilder->GetCaretRect() + ToReferenceFrame())
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -4074,36 +4074,51 @@ public:
* For example, an event targeting a canvas or video will actually target the
* background of that element, which is logically in the PaintedLayer behind the
* CanvasFrame or ImageFrame. We only need to create a
* nsDisplayLayerEventRegions when an element's background could be in front
* of a lower z-order element with its own layer.
*/
class nsDisplayLayerEventRegions final : public nsDisplayItem {
public:
- nsDisplayLayerEventRegions(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame)
+ nsDisplayLayerEventRegions(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame, uint32_t aIndex = 0)
: nsDisplayItem(aBuilder, aFrame)
+ , mIndex(aIndex)
{
MOZ_COUNT_CTOR(nsDisplayLayerEventRegions);
}
-#ifdef NS_BUILD_REFCNT_LOGGING
- virtual ~nsDisplayLayerEventRegions() {
- MOZ_COUNT_DTOR(nsDisplayLayerEventRegions);
- }
-#endif
+
+ virtual void Destroy(nsDisplayListBuilder* aBuilder) override
+ {
+ if (!aBuilder->IsRetainingDisplayList()) {
+ nsDisplayItem::Destroy(aBuilder);
+ return;
+ }
+
+ RemoveItemFromFrames(mHitRegion);
+ RemoveItemFromFrames(mMaybeHitRegion);
+ RemoveItemFromFrames(mDispatchToContentHitRegion);
+ RemoveItemFromFrames(mNoActionRegion);
+ RemoveItemFromFrames(mHorizontalPanRegion);
+ RemoveItemFromFrames(mVerticalPanRegion);
+ nsDisplayItem::Destroy(aBuilder);
+ }
+
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap) const override
{
*aSnap = false;
return nsRect();
}
nsRect GetHitRegionBounds(nsDisplayListBuilder* aBuilder, bool* aSnap)
{
*aSnap = false;
- return mHitRegion.GetBounds().Union(mMaybeHitRegion.GetBounds());
+ // TODO: This constructs the two regions, but we're also doing the same
+ // work in AccumulateEventRegions. We should avoid doing it twice.
+ return HitRegion().GetBounds().Union(MaybeHitRegion().GetBounds());
}
virtual void ApplyOpacity(nsDisplayListBuilder* aBuilder,
float aOpacity,
const DisplayItemClipChain* aClip) override
{
NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
}
@@ -4116,57 +4131,154 @@ public:
// Indicate that aFrame's border-box contributes to the event regions for
// this layer. aFrame must have the same reference frame as mFrame.
void AddFrame(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame);
// Indicate that an inactive scrollframe's scrollport should be added to the
// dispatch-to-content region, to ensure that APZ lets content create a
// displayport.
- void AddInactiveScrollPort(const nsRect& aRect);
+ void AddInactiveScrollPort(nsIFrame* aFrame, const nsRect& aRect);
bool IsEmpty() const;
int32_t ZIndex() const override;
void SetOverrideZIndex(int32_t aZIndex);
- const nsRegion& HitRegion() { return mHitRegion; }
- const nsRegion& MaybeHitRegion() { return mMaybeHitRegion; }
- const nsRegion& DispatchToContentHitRegion() { return mDispatchToContentHitRegion; }
- const nsRegion& NoActionRegion() { return mNoActionRegion; }
- const nsRegion& HorizontalPanRegion() { return mHorizontalPanRegion; }
- const nsRegion& VerticalPanRegion() { return mVerticalPanRegion; }
+ virtual uint32_t GetPerFrameKey() const override
+ {
+ return (mIndex << TYPE_BITS) | nsDisplayItem::GetPerFrameKey();
+ }
+
+ const nsRegion HitRegion()
+ {
+ return nsRegion(mozilla::gfx::ArrayView<pixman_box32_t>(mHitRegion.mBoxes));
+ }
+ const nsRegion MaybeHitRegion()
+ {
+ nsRegion result(mozilla::gfx::ArrayView<pixman_box32_t>(mMaybeHitRegion.mBoxes));
+
+ // Avoid quadratic performance as a result of the region growing to include
+ // an arbitrarily large number of rects, which can happen on some pages.
+ // TODO: It would be nice if we could ask the initial construction above
+ // to include simplification.
+ result.SimplifyOutward(8);
+ return result;
+ }
+ const nsRegion DispatchToContentHitRegion()
+ {
+ nsRegion result(mozilla::gfx::ArrayView<pixman_box32_t>(mDispatchToContentHitRegion.mBoxes));
+
+ // If this frame has touch-action areas, and there were already
+ // touch-action areas from some other element on this same event regions,
+ // then all we know is that there are multiple elements with touch-action
+ // properties. In particular, 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. XXX we need to come up with a better way to do this,
+ // see bug 1287829.
+ uint32_t touchActionCount =
+ mNoActionRegion.mBoxes.Length() +
+ mHorizontalPanRegion.mBoxes.Length() +
+ mVerticalPanRegion.mBoxes.Length();
+ if (touchActionCount > 1) {
+ result.OrWith(NoActionRegion());
+ result.OrWith(HorizontalPanRegion());
+ result.OrWith(VerticalPanRegion());
+ }
+
+ result.SimplifyOutward(8);
+ return result;
+ }
+ const nsRegion NoActionRegion()
+ {
+ return nsRegion(mozilla::gfx::ArrayView<pixman_box32_t>(mNoActionRegion.mBoxes));
+ }
+ const nsRegion HorizontalPanRegion()
+ {
+ return nsRegion(mozilla::gfx::ArrayView<pixman_box32_t>(mHorizontalPanRegion.mBoxes));
+ }
+ const nsRegion VerticalPanRegion()
+ {
+ return nsRegion(mozilla::gfx::ArrayView<pixman_box32_t>(mVerticalPanRegion.mBoxes));
+ }
nsRegion CombinedTouchActionRegion();
virtual void WriteDebugInfo(std::stringstream& aStream) override;
+ // TODO: nsTArray (vector) might not be a great data structure
+ // choice since we need to remove elements from the middle.
+ // Should profile and try figure out the best approach
+ // here.
+ struct FrameRects {
+ void Add(nsIFrame* aFrame, const nsRect& aRect) {
+ mBoxes.AppendElement(nsRegion::RectToBox(aRect));
+ mFrames.AppendElement(aFrame);
+ }
+ void Add(nsIFrame* aFrame, const pixman_box32& aBox) {
+ mBoxes.AppendElement(aBox);
+ mFrames.AppendElement(aFrame);
+ }
+ void Add(const FrameRects& aOther) {
+ mBoxes.AppendElements(aOther.mBoxes);
+ mFrames.AppendElements(aOther.mFrames);
+ }
+
+ bool IsEmpty() const {
+ return mBoxes.IsEmpty();
+ }
+
+ nsTArray<pixman_box32_t> mBoxes;
+ nsTArray<nsIFrame*> mFrames;
+ };
+
+ virtual void RemoveFrame(nsIFrame* aFrame) override;
+
private:
+ virtual ~nsDisplayLayerEventRegions()
+ {
+ MOZ_COUNT_DTOR(nsDisplayLayerEventRegions);
+ }
+
+ void RemoveItemFromFrames(FrameRects& aFrameRects)
+ {
+ for (nsIFrame* f : aFrameRects.mFrames) {
+ if (f != mFrame) {
+ f->RemoveDisplayItem(this);
+ }
+ }
+ }
+
+ friend void MergeLayerEventRegions(nsDisplayItem*, nsDisplayItem*, bool);
+
// Relative to aFrame's reference frame.
// These are the points that are definitely in the hit region.
- nsRegion mHitRegion;
+ FrameRects mHitRegion;
// These are points that may or may not be in the hit region. Only main-thread
// event handling can tell for sure (e.g. because complex shapes are present).
- nsRegion mMaybeHitRegion;
+ FrameRects mMaybeHitRegion;
// These are points that need to be dispatched to the content thread for
// resolution. Always contained in the union of mHitRegion and mMaybeHitRegion.
- nsRegion mDispatchToContentHitRegion;
+ FrameRects mDispatchToContentHitRegion;
// These are points where panning is disabled, as determined by the touch-action
// property. Always contained in the union of mHitRegion and mMaybeHitRegion.
- nsRegion mNoActionRegion;
+ FrameRects mNoActionRegion;
// These are points where panning is horizontal, as determined by the touch-action
// property. Always contained in the union of mHitRegion and mMaybeHitRegion.
- nsRegion mHorizontalPanRegion;
+ FrameRects mHorizontalPanRegion;
// These are points where panning is vertical, as determined by the touch-action
// property. Always contained in the union of mHitRegion and mMaybeHitRegion.
- nsRegion mVerticalPanRegion;
+ FrameRects mVerticalPanRegion;
// If these event regions are for an inactive scroll frame, the z-index of
// this display item is overridden to be the largest z-index of the content
// in the scroll frame. This ensures that the event regions item remains on
// top of the content after sorting items by z-index.
mozilla::Maybe<int32_t> mOverrideZIndex;
+ uint32_t mIndex;
};
/**
* A class that lets you wrap a display list as a display item.
*
* GetUnderlyingFrame() is troublesome for wrapped lists because if the wrapped
* list has many items, it's not clear which one has the 'underlying frame'.
* Thus we force the creator to specify what the underlying frame is. The