Bug 1459312 - Wrap the root scroll frame contents into an nsDisplayAsyncZoom when using APZ zooming and containerless scrolling. draft
authorMarkus Stange <mstange@themasta.com>
Fri, 04 May 2018 17:58:07 -0400
changeset 791689 b2cc7e09e89e82063f0f2f6f6d77d0f35a4643e5
parent 791688 c13c9b812d7a97cfb9562671dfb29e4ee7b45a92
push id108877
push userbmo:mstange@themasta.com
push dateFri, 04 May 2018 22:12:49 +0000
bugs1459312
milestone61.0a1
Bug 1459312 - Wrap the root scroll frame contents into an nsDisplayAsyncZoom when using APZ zooming and containerless scrolling. MozReview-Commit-ID: 4xnWZ4RSVKY
layout/generic/nsGfxScrollFrame.cpp
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3571,170 +3571,230 @@ ScrollFrameHelper::BuildDisplayList(nsDi
       contentBoxClipState.emplace(aBuilder);
       if (mClipAllDescendants) {
         contentBoxClipState->ClipContentDescendants(*contentBoxClip);
       } else {
         contentBoxClipState->ClipContainingBlockDescendants(*contentBoxClip);
       }
     }
 
-    nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
-    if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
-      asrSetter.EnterScrollFrame(sf);
-    }
-
-    if (mIsScrollableLayerInRootContainer) {
-      aBuilder->SetActiveScrolledRootForRootScrollframe(aBuilder->CurrentActiveScrolledRoot());
-    }
-
-    if (mWillBuildScrollableLayer) {
-      // Create a hit test info item for the scrolled content that's not
-      // clipped to the displayport. This ensures that within the bounds
-      // of the scroll frame, the scrolled content is always hit, even
-      // if we are checkerboarding.
-      if (aBuilder->BuildCompositorHitTestInfo()) {
-        CompositorHitTestInfo info = mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
-        if (info != CompositorHitTestInfo::eInvisibleToHitTest) {
-          nsDisplayCompositorHitTestInfo* hitInfo =
-              MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1);
-          aBuilder->SetCompositorHitTestInfo(hitInfo);
-          scrolledContent.BorderBackground()->AppendToTop(hitInfo);
+    nsDisplayListCollection set(aBuilder);
+    {
+      nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(aBuilder);
+      if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+        asrSetter.EnterScrollFrame(sf);
+      }
+
+      if (mIsScrollableLayerInRootContainer) {
+        aBuilder->SetActiveScrolledRootForRootScrollframe(aBuilder->CurrentActiveScrolledRoot());
+      }
+
+      if (mWillBuildScrollableLayer) {
+        // Create a hit test info item for the scrolled content that's not
+        // clipped to the displayport. This ensures that within the bounds
+        // of the scroll frame, the scrolled content is always hit, even
+        // if we are checkerboarding.
+        if (aBuilder->BuildCompositorHitTestInfo()) {
+          CompositorHitTestInfo info = mScrolledFrame->GetCompositorHitTestInfo(aBuilder);
+          if (info != CompositorHitTestInfo::eInvisibleToHitTest) {
+            nsDisplayCompositorHitTestInfo* hitInfo =
+                MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1);
+            aBuilder->SetCompositorHitTestInfo(hitInfo);
+            set.BorderBackground()->AppendToTop(hitInfo);
+          }
+        }
+      }
+
+      {
+        // Clip our contents to the unsnapped scrolled rect. This makes sure that
+        // we don't have display items over the subpixel seam at the edge of the
+        // scrolled area.
+        DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
+        nsRect scrolledRectClip =
+          GetUnsnappedScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
+                                          mScrollPort.Size()) + mScrolledFrame->GetPosition();
+        if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+          // Clip the contents to the display port.
+          // The dirty rect already acts kind of like a clip, in that
+          // FrameLayerBuilder intersects item bounds and opaque regions with
+          // it, but it doesn't have the consistent snapping behavior of a
+          // true clip.
+          // For a case where this makes a difference, imagine the following
+          // scenario: The display port has an edge that falls on a fractional
+          // layer pixel, and there's an opaque display item that covers the
+          // whole display port up until that fractional edge, and there is a
+          // transparent display item that overlaps the edge. We want to prevent
+          // this transparent item from enlarging the scrolled layer's visible
+          // region beyond its opaque region. The dirty rect doesn't do that -
+          // it gets rounded out, whereas a true clip gets rounded to nearest
+          // pixels.
+          // If there is no display port, we don't need this because the clip
+          // from the scroll port is still applied.
+          scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
+        }
+        scrolledRectClipState.ClipContainingBlockDescendants(
+          scrolledRectClip + aBuilder->ToReferenceFrame(mOuter));
+
+        nsDisplayListBuilder::AutoBuildingDisplayList
+          building(aBuilder, mOuter, visibleRect, dirtyRect, aBuilder->IsAtRootOfPseudoStackingContext());
+
+        mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, set);
+
+        if (dirtyRectHasBeenOverriden && gfxPrefs::LayoutDisplayListShowArea()) {
+          nsDisplaySolidColor* color =
+            MakeDisplayItem<nsDisplaySolidColor>(aBuilder, mOuter,
+                                                dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
+                                                NS_RGBA(0, 0, 255, 64), false);
+          color->SetOverrideZIndex(INT32_MAX);
+          set.PositionedDescendants()->AppendToTop(color);
+        }
+      }
+
+      if (extraContentBoxClipForNonCaretContent) {
+        // The items were built while the inflated content box clip was in
+        // effect, so that the caret wasn't clipped unnecessarily. We apply
+        // the non-inflated clip to the non-caret items now, by intersecting
+        // it with their existing clip.
+        ClipListsExceptCaret(&set, aBuilder, mScrolledFrame,
+                            *extraContentBoxClipForNonCaretContent);
+      }
+
+      if (aBuilder->IsPaintingToWindow()) {
+        mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
+      }
+      if (idSetter.ShouldForceLayerForScrollParent() &&
+          !gfxPrefs::LayoutUseContainersForRootFrames())
+      {
+        // Note that forcing layerization of scroll parents follows the scroll
+        // handoff chain which is subject to the out-of-flow-frames caveat noted
+        // above (where the idSetter variable is created).
+        //
+        // This is not compatible when using containes for root scrollframes.
+        MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
+          aBuilder->IsPaintingToWindow());
+        if (!mWillBuildScrollableLayer) {
+          // Set a displayport so next paint we don't have to force layerization
+          // after the fact.
+          nsLayoutUtils::SetDisplayPortMargins(mOuter->GetContent(),
+                                              mOuter->PresShell(),
+                                              ScreenMargin(),
+                                              0,
+                                              nsLayoutUtils::RepaintMode::DoNotRepaint);
+          // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer and
+          // recompute the current animated geometry root if needed.
+          // It's too late to change the dirty rect so pass a copy.
+          nsRect copyOfDirtyRect = dirtyRect;
+          nsRect copyOfVisibleRect = visibleRect;
+          Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect, &copyOfDirtyRect,
+                      /* aSetBase = */ false, nullptr);
+          if (mWillBuildScrollableLayer) {
+            asrSetter.InsertScrollFrame(sf);
+          }
         }
       }
     }
 
-    {
-      // Clip our contents to the unsnapped scrolled rect. This makes sure that
-      // we don't have display items over the subpixel seam at the edge of the
-      // scrolled area.
-      DisplayListClipState::AutoSaveRestore scrolledRectClipState(aBuilder);
-      nsRect scrolledRectClip =
-        GetUnsnappedScrolledRectInternal(mScrolledFrame->GetScrollableOverflowRect(),
-                                         mScrollPort.Size()) + mScrolledFrame->GetPosition();
-      if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
-        // Clip the contents to the display port.
-        // The dirty rect already acts kind of like a clip, in that
-        // FrameLayerBuilder intersects item bounds and opaque regions with
-        // it, but it doesn't have the consistent snapping behavior of a
-        // true clip.
-        // For a case where this makes a difference, imagine the following
-        // scenario: The display port has an edge that falls on a fractional
-        // layer pixel, and there's an opaque display item that covers the
-        // whole display port up until that fractional edge, and there is a
-        // transparent display item that overlaps the edge. We want to prevent
-        // this transparent item from enlarging the scrolled layer's visible
-        // region beyond its opaque region. The dirty rect doesn't do that -
-        // it gets rounded out, whereas a true clip gets rounded to nearest
-        // pixels.
-        // If there is no display port, we don't need this because the clip
-        // from the scroll port is still applied.
-        scrolledRectClip = scrolledRectClip.Intersect(visibleRect);
+    // scrolled ASR is no longer on the stack, viewport clip still is.
+
+    if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
+      aBuilder->ForceLayerForScrollParent();
+    }
+
+    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.
+      if (!mWillBuildScrollableLayer) {
+        if (aBuilder->BuildCompositorHitTestInfo()) {
+          CompositorHitTestInfo info = CompositorHitTestInfo::eVisibleToHitTest
+                                    | CompositorHitTestInfo::eDispatchToContent;
+          // If the scroll frame has non-default overscroll-behavior, instruct
+          // APZ to require a target confirmation before processing events that
+          // hit this scroll frame (that is, to drop the events if a confirmation
+          // does not arrive within the timeout period). Otherwise, APZ's
+          // fallback behaviour of scrolling the enclosing scroll frame would
+          // violate the specified overscroll-behavior.
+          ScrollbarStyles scrollbarStyles = GetScrollbarStylesFromFrame();
+          if (scrollbarStyles.mOverscrollBehaviorX != StyleOverscrollBehavior::Auto ||
+              scrollbarStyles.mOverscrollBehaviorY != StyleOverscrollBehavior::Auto) {
+            info |= CompositorHitTestInfo::eRequiresTargetConfirmation;
+          }
+          nsDisplayCompositorHitTestInfo* hitInfo =
+              MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1,
+                  Some(mScrollPort + aBuilder->ToReferenceFrame(mOuter)));
+          AppendInternalItemToTop(set, hitInfo, Some(INT32_MAX));
+        }
+        if (aBuilder->IsBuildingLayerEventRegions()) {
+          nsDisplayLayerEventRegions* inactiveRegionItem =
+              MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, mScrolledFrame, 1);
+          inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
+          AppendInternalItemToTop(set, inactiveRegionItem, Some(INT32_MAX));
+        }
       }
-      scrolledRectClipState.ClipContainingBlockDescendants(
-        scrolledRectClip + aBuilder->ToReferenceFrame(mOuter));
-
-      nsDisplayListBuilder::AutoBuildingDisplayList
-        building(aBuilder, mOuter, visibleRect, dirtyRect, aBuilder->IsAtRootOfPseudoStackingContext());
-
-      mOuter->BuildDisplayListForChild(aBuilder, mScrolledFrame, scrolledContent);
-
-      if (dirtyRectHasBeenOverriden && gfxPrefs::LayoutDisplayListShowArea()) {
-        nsDisplaySolidColor* color =
-          MakeDisplayItem<nsDisplaySolidColor>(aBuilder, mOuter,
-                                               dirtyRect + aBuilder->GetCurrentFrameOffsetToReferenceFrame(),
-                                               NS_RGBA(0, 0, 255, 64), false);
-        color->SetOverrideZIndex(INT32_MAX);
-        scrolledContent.PositionedDescendants()->AppendToTop(color);
+
+      if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
+        aBuilder->AppendNewScrollInfoItemForHoisting(
+          MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
+                                                  mOuter));
       }
     }
 
-    if (extraContentBoxClipForNonCaretContent) {
-      // The items were built while the inflated content box clip was in
-      // effect, so that the caret wasn't clipped unnecessarily. We apply
-      // the non-inflated clip to the non-caret items now, by intersecting
-      // it with their existing clip.
-      ClipListsExceptCaret(&scrolledContent, aBuilder, mScrolledFrame,
-                           *extraContentBoxClipForNonCaretContent);
-    }
-
-    if (aBuilder->IsPaintingToWindow()) {
-      mIsScrollParent = idSetter.ShouldForceLayerForScrollParent();
-    }
-    if (idSetter.ShouldForceLayerForScrollParent() &&
-        !gfxPrefs::LayoutUseContainersForRootFrames())
-    {
-      // Note that forcing layerization of scroll parents follows the scroll
-      // handoff chain which is subject to the out-of-flow-frames caveat noted
-      // above (where the idSetter variable is created).
-      //
-      // This is not compatible when using containes for root scrollframes.
-      MOZ_ASSERT(couldBuildLayer && mScrolledFrame->GetContent() &&
-        aBuilder->IsPaintingToWindow());
-      if (!mWillBuildScrollableLayer) {
-        // Set a displayport so next paint we don't have to force layerization
-        // after the fact.
-        nsLayoutUtils::SetDisplayPortMargins(mOuter->GetContent(),
-                                             mOuter->PresShell(),
-                                             ScreenMargin(),
-                                             0,
-                                             nsLayoutUtils::RepaintMode::DoNotRepaint);
-        // Call DecideScrollableLayer to recompute mWillBuildScrollableLayer and
-        // recompute the current animated geometry root if needed.
-        // It's too late to change the dirty rect so pass a copy.
-        nsRect copyOfDirtyRect = dirtyRect;
-        nsRect copyOfVisibleRect = visibleRect;
-        Unused << DecideScrollableLayer(aBuilder, &copyOfVisibleRect, &copyOfDirtyRect,
-                    /* aSetBase = */ false, nullptr);
-        if (mWillBuildScrollableLayer) {
-          asrSetter.InsertScrollFrame(sf);
+    if (gfxPrefs::APZAllowZooming() &&
+        !gfxPrefs::LayoutUseContainersForRootFrames() &&
+        mIsRoot && mOuter->PresContext()->IsRootContentDocument()) {
+      MOZ_ASSERT(mClipAllDescendants);
+
+      // Wrap all our scrolled contents in an nsDisplayAsyncZoom. This will be
+      // the layer that gets scaled for APZ zooming. It does not have the
+      // scrolled ASR, but it does have the viewport clip applied to it. The
+      // viewport clip is also inherited into our children; effectively we are
+      // double clipping to the viewport, at potentially different async scales.
+      // The code below follows how things are done in
+      // nsFrame::BuildDisplayListForStackingContext, in order to turn the
+      // display list set into a serial display list that has the correct z-order.
+
+      set.PositionedDescendants()->SortByZOrder();
+
+      nsDisplayList resultList;
+      // Now follow the rules of http://www.w3.org/TR/CSS21/zindex.html
+      // 1,2: backgrounds and borders
+      resultList.AppendToTop(set.BorderBackground());
+      // 3: negative z-index children.
+      for (;;) {
+        nsDisplayItem* item = set.PositionedDescendants()->GetBottom();
+        if (item && item->ZIndex() < 0) {
+          set.PositionedDescendants()->RemoveBottom();
+          resultList.AppendToTop(item);
+          continue;
         }
+        break;
       }
+      // 4: block backgrounds
+      resultList.AppendToTop(set.BlockBorderBackgrounds());
+      // 5: floats
+      resultList.AppendToTop(set.Floats());
+      // 7: general content
+      resultList.AppendToTop(set.Content());
+      // 7.5: outlines, in content tree order.
+      if (nsIContent* content = mOuter->GetContent()) {
+        set.Outlines()->SortByContentOrder(content);
+      }
+      resultList.AppendToTop(set.Outlines());
+      // 8, 9: non-negative z-index children
+      resultList.AppendToTop(set.PositionedDescendants());
+
+      mozilla::layers::FrameMetrics::ViewID viewID =
+        nsLayoutUtils::FindOrCreateIDFor(mScrolledFrame->GetContent());
+
+      set.Content()->AppendToTop(
+        MakeDisplayItem<nsDisplayAsyncZoom>(aBuilder, mOuter, &resultList,
+                                            aBuilder->CurrentActiveScrolledRoot(),
+                                            viewID));
     }
-  }
-
-  if (mWillBuildScrollableLayer && aBuilder->IsPaintingToWindow()) {
-    aBuilder->ForceLayerForScrollParent();
-  }
-
-  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.
-    if (!mWillBuildScrollableLayer) {
-      if (aBuilder->BuildCompositorHitTestInfo()) {
-        CompositorHitTestInfo info = CompositorHitTestInfo::eVisibleToHitTest
-                                   | CompositorHitTestInfo::eDispatchToContent;
-        // If the scroll frame has non-default overscroll-behavior, instruct
-        // APZ to require a target confirmation before processing events that
-        // hit this scroll frame (that is, to drop the events if a confirmation
-        // does not arrive within the timeout period). Otherwise, APZ's
-        // fallback behaviour of scrolling the enclosing scroll frame would
-        // violate the specified overscroll-behavior.
-        ScrollbarStyles scrollbarStyles = GetScrollbarStylesFromFrame();
-        if (scrollbarStyles.mOverscrollBehaviorX != StyleOverscrollBehavior::Auto ||
-            scrollbarStyles.mOverscrollBehaviorY != StyleOverscrollBehavior::Auto) {
-          info |= CompositorHitTestInfo::eRequiresTargetConfirmation;
-        }
-        nsDisplayCompositorHitTestInfo* hitInfo =
-            MakeDisplayItem<nsDisplayCompositorHitTestInfo>(aBuilder, mScrolledFrame, info, 1,
-                Some(mScrollPort + aBuilder->ToReferenceFrame(mOuter)));
-        AppendInternalItemToTop(scrolledContent, hitInfo, Some(INT32_MAX));
-      }
-      if (aBuilder->IsBuildingLayerEventRegions()) {
-        nsDisplayLayerEventRegions* inactiveRegionItem =
-            MakeDisplayItem<nsDisplayLayerEventRegions>(aBuilder, mScrolledFrame, 1);
-        inactiveRegionItem->AddInactiveScrollPort(mScrolledFrame, mScrollPort + aBuilder->ToReferenceFrame(mOuter));
-        AppendInternalItemToTop(scrolledContent, inactiveRegionItem, Some(INT32_MAX));
-      }
-    }
-
-    if (aBuilder->ShouldBuildScrollInfoItemsForHoisting()) {
-      aBuilder->AppendNewScrollInfoItemForHoisting(
-        MakeDisplayItem<nsDisplayScrollInfoLayer>(aBuilder, mScrolledFrame,
-                                                mOuter));
-    }
+
+    set.MoveTo(scrolledContent);
   }
   // Now display overlay scrollbars and the resizer, if we have one.
   AppendScrollPartsTo(aBuilder, scrolledContent, createLayersForScrollbars, true);
 
   scrolledContent.MoveTo(aLists);
 }
 
 bool