Bug 1404181 - Part 23: Only rebuild items within a displayport when the displayport changes, rather than rebuilding the whole document. r?mstange draft
authorMatt Woodrow <mwoodrow@mozilla.com>, Miko Mynttinen <mikokm@gmail.com>, Timothy Nikkel <tnikkel@gmail.com>
Fri, 29 Sep 2017 10:54:15 +1300
changeset 684534 5bb9062e8603968387f2dfe3cb65eeee032989ab
parent 684533 87b05d8c0b258eba2b31b37590d4708e6ec87041
child 684535 f9cda6f67d7d0b02a4d6db65140339c5bdd18511
push id85633
push usermwoodrow@mozilla.com
push dateSun, 22 Oct 2017 23:03:02 +0000
reviewersmstange
bugs1404181
milestone58.0a1
Bug 1404181 - Part 23: Only rebuild items within a displayport when the displayport changes, rather than rebuilding the whole document. r?mstange MozReview-Commit-ID: IYEPCKSvtBY
dom/base/nsDOMWindowUtils.cpp
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/generic/nsGfxScrollFrame.cpp
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -469,16 +469,19 @@ nsDOMWindowUtils::SetDisplayPortForEleme
         nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame))
     {
       // We are setting a root displayport for a document.
       // The pres shell needs a special flag set.
       presShell->SetIgnoreViewportScrolling(true);
     }
   }
 
+  nsLayoutUtils::InvalidateForDisplayPortChange(content, !!currentData,
+    currentData ? currentData->mRect : nsRect(), displayport);
+
   nsIFrame* rootFrame = presShell->FrameManager()->GetRootFrame();
   if (rootFrame) {
     rootFrame->SchedulePaint();
 
     // If we are hiding something that is a display root then send empty paint
     // transaction in order to release retained layers because it won't get
     // any more paint requests when it is hidden.
     if (displayport.IsEmpty() &&
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1370,16 +1370,64 @@ nsLayoutUtils::GetDisplayPortForVisibili
   bool usingDisplayPort = GetDisplayPortImpl(aContent, aResult, 1.0f,
       MaxSizeExceededBehaviour::eDrop);
   if (usingDisplayPort && aRelativeTo == RelativeTo::ScrollFrame) {
     TranslateFromScrollPortToScrollFrame(aContent, aResult);
   }
   return usingDisplayPort;
 }
 
+void
+nsLayoutUtils::InvalidateForDisplayPortChange(nsIContent* aContent,
+                                              bool aHadDisplayPort,
+                                              const nsRect& aOldDisplayPort,
+                                              const nsRect& aNewDisplayPort,
+                                              RepaintMode aRepaintMode)
+{
+  if (aRepaintMode != RepaintMode::Repaint) {
+    return;
+  }
+
+  bool changed = !aHadDisplayPort ||
+        !aOldDisplayPort.IsEqualEdges(aNewDisplayPort);
+
+  nsIFrame* frame = GetScrollFrameFromContent(aContent);
+  if (frame) {
+    frame = do_QueryFrame(frame->GetScrollTargetFrame());
+  }
+
+  if (changed && frame) {
+    // It is important to call SchedulePaint on the same frame that we set the dirty
+    // rect properties on so we can find the frame later to remove the properties.
+    frame->SchedulePaint();
+
+    nsIFrame* displayRoot = nsLayoutUtils::GetDisplayRootFrame(frame);
+    RetainedDisplayListBuilder* retainedBuilder =
+      displayRoot->GetProperty(RetainedDisplayListBuilder::Cached());
+    if (retainedBuilder) {
+      nsRect* rect =
+        frame->GetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect());
+      if (!rect) {
+        rect = new nsRect();
+        frame->SetProperty(nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
+        frame->SetHasOverrideDirtyRegion(true);
+      }
+      if (aHadDisplayPort) {
+        // We only need to build a display list for any new areas added
+        nsRegion newRegion(aNewDisplayPort);
+        newRegion.SubOut(aOldDisplayPort);
+        rect->UnionRect(*rect, newRegion.GetBounds());
+      } else {
+        rect->UnionRect(*rect, aNewDisplayPort);
+      }
+    }
+  }
+
+}
+
 bool
 nsLayoutUtils::SetDisplayPortMargins(nsIContent* aContent,
                                      nsIPresShell* aPresShell,
                                      const ScreenMargin& aMargins,
                                      uint32_t aPriority,
                                      RepaintMode aRepaintMode)
 {
   MOZ_ASSERT(aContent);
@@ -1398,38 +1446,31 @@ nsLayoutUtils::SetDisplayPortMargins(nsI
                         new DisplayPortMarginsPropertyData(
                             aMargins, aPriority),
                         nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
 
   nsRect newDisplayPort;
   DebugOnly<bool> hasDisplayPort = GetHighResolutionDisplayPort(aContent, &newDisplayPort);
   MOZ_ASSERT(hasDisplayPort);
 
-  bool changed = !hadDisplayPort ||
-        !oldDisplayPort.IsEqualEdges(newDisplayPort);
-
   if (gfxPrefs::LayoutUseContainersForRootFrames()) {
     nsIFrame* rootScrollFrame = aPresShell->GetRootScrollFrame();
     if (rootScrollFrame &&
         aContent == rootScrollFrame->GetContent() &&
         nsLayoutUtils::UsesAsyncScrolling(rootScrollFrame))
     {
       // We are setting a root displayport for a document.
       // If we have APZ, then set a special flag on the pres shell so
       // that we don't get scrollbars drawn.
       aPresShell->SetIgnoreViewportScrolling(true);
     }
   }
 
-  if (changed && aRepaintMode == RepaintMode::Repaint) {
-    nsIFrame* frame = aContent->GetPrimaryFrame();
-    if (frame) {
-      frame->SchedulePaint();
-    }
-  }
+  InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort,
+    newDisplayPort, aRepaintMode);
 
   nsIFrame* frame = GetScrollFrameFromContent(aContent);
   nsIScrollableFrame* scrollableFrame = frame ? frame->GetScrollTargetFrame() : nullptr;
   if (!scrollableFrame) {
     return true;
   }
 
   scrollableFrame->TriggerDisplayPortExpiration();
@@ -3718,17 +3759,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
     }
   }
 
   builder.ClearHaveScrollableDisplayPort();
   if (builder.IsPaintingToWindow()) {
     MaybeCreateDisplayPortInFirstScrollFrameEncountered(aFrame, builder);
   }
 
-  nsRect dirtyRect = visibleRegion.GetBounds();
+  nsRect visibleRect = visibleRegion.GetBounds();
 
   {
     AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame:BuildDisplayList",
                         GRAPHICS);
     AUTO_PROFILER_TRACING("Paint", "DisplayList");
 
     PaintTelemetry::AutoRecord record(PaintTelemetry::Metric::DisplayList);
     TimeStamp dlStart = TimeStamp::Now();
@@ -3755,34 +3796,34 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
         // nsGfxScrollFrame::BuilDisplayList will do it instead.
         if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) {
           id = nsLayoutUtils::FindOrCreateIDFor(element);
         }
       }
 
       nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
 
-      builder.SetVisibleRect(dirtyRect);
+      builder.SetVisibleRect(visibleRect);
       builder.SetIsBuilding(true);
 
       const bool paintedPreviously =
         aFrame->HasProperty(nsIFrame::ModifiedFrameList());
 
       // Attempt to do a partial build and merge into the existing list.
       // This calls BuildDisplayListForStacking context on a subset of the
       // viewport.
       bool merged = false;
       if (retainedBuilder && paintedPreviously) {
         merged = retainedBuilder->AttemptPartialUpdate(aBackstop);
       }
 
       if (!merged) {
         list.DeleteAll(&builder);
         builder.EnterPresShell(aFrame);
-        builder.SetDirtyRect(dirtyRect);
+        builder.SetDirtyRect(visibleRect);
         builder.ClearWindowDraggingRegion();
         aFrame->BuildDisplayListForStackingContext(&builder, &list);
         AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion, aBackstop);
 
         builder.LeavePresShell(aFrame, &list);
       }
     }
 
@@ -3833,17 +3874,17 @@ nsLayoutUtils::PaintFrame(gfxContext* aR
              "    image.removeAttribute('src');\n"
              "  } else {\n"
              "    image.src = array[index];\n"
              "  }\n"
              "}</script></head><body>";
     }
 #endif
     *ss << nsPrintfCString("Painting --- before optimization (dirty %d,%d,%d,%d):\n",
-            dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height).get();
+            visibleRect.x, visibleRect.y, visibleRect.width, visibleRect.height).get();
     nsFrame::PrintDisplayList(&builder, list, *ss, gfxEnv::DumpPaintToFile());
 
     if (gfxEnv::DumpPaint() || gfxEnv::DumpPaintItems()) {
       // Flush stream now to avoid reordering dump output relative to
       // messages dumped by PaintRoot below.
       fprint_stderr(gfxUtils::sDumpPaintFile, *ss);
       ss = MakeUnique<std::stringstream>();
     }
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -248,16 +248,25 @@ public:
     RelativeTo aRelativeTo = RelativeTo::ScrollPort);
 
   enum class RepaintMode : uint8_t {
     Repaint,
     DoNotRepaint
   };
 
   /**
+   * Invalidate for displayport change.
+   */
+  static void InvalidateForDisplayPortChange(nsIContent* aContent,
+                                             bool aHadDisplayPort,
+                                             const nsRect& aOldDisplayPort,
+                                             const nsRect& aNewDisplayPort,
+                                             RepaintMode aRepaintMode = RepaintMode::Repaint);
+
+  /**
    * Set the display port margins for a content element to be used with a
    * display port base (see SetDisplayPortBase()).
    * See also nsIDOMWindowUtils.setDisplayPortMargins.
    * @param aContent the content element for which to set the margins
    * @param aPresShell the pres shell for the document containing the element
    * @param aMargins the margins to set
    * @param aAlignmentX, alignmentY the amount of pixels to which to align the
    *                                displayport built by combining the base
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3297,16 +3297,19 @@ ScrollFrameHelper::BuildDisplayList(nsDi
       if (mScrollPosForLayerPixelAlignment == nsPoint(-1,-1)) {
         mScrollPosForLayerPixelAlignment = mScrollPosAtLastPaint;
       }
     } else {
       mScrollPosForLayerPixelAlignment = nsPoint(-1,-1);
     }
   }
 
+  // It's safe to get this value before the DecideScrollableLayer call below
+  // because that call cannot create a displayport for root scroll frames,
+  // and hence it cannot create an ignore scroll frame.
   bool ignoringThisScrollFrame =
     aBuilder->GetIgnoreScrollFrame() == mOuter || IsIgnoringViewportClipping();
 
   // Overflow clipping can never clip frames outside our subtree, so there
   // is no need to worry about whether we are a moving frame that might clip
   // non-moving frames.
   // Not all our descendants will be clipped by overflow clipping, but all
   // the ones that aren't clipped will be out of flow frames that have already