Bug 1261554 (Part 1) - Prepare for implementing in-displayport visibility tracking. r=mstange
authorSeth Fowler <mark.seth.fowler@gmail.com>
Thu, 21 Apr 2016 01:21:58 -0700
changeset 332130 69abdc731a9962cb1220facb41f26bef7087b486
parent 332129 6fe85a187e707bfc3fbd6a3bba71db29aae5c71f
child 332131 82c3b4b81d82daa6cd0dcddfecdcab9e0d0d1773
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmstange
bugs1261554
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1261554 (Part 1) - Prepare for implementing in-displayport visibility tracking. r=mstange
dom/base/nsImageLoadingContent.cpp
layout/base/nsIPresShell.h
layout/base/nsPresShell.cpp
layout/base/nsPresShell.h
layout/generic/Visibility.h
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/nsImageFrame.cpp
layout/svg/SVGFEImageFrame.cpp
layout/svg/nsSVGImageFrame.cpp
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -272,32 +272,31 @@ nsImageLoadingContent::OnUnlockedDraw()
     return;
   }
 
   nsIFrame* frame = GetOurPrimaryFrame();
   if (!frame) {
     return;
   }
 
-  if (frame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE) {
-    // This frame is already marked visible; there's nothing to do.
-    return;
+  if (frame->IsVisibleOrMayBecomeVisibleSoon()) {
+    return;  // Nothing to do.
   }
 
   nsPresContext* presContext = frame->PresContext();
   if (!presContext) {
     return;
   }
 
   nsIPresShell* presShell = presContext->PresShell();
   if (!presShell) {
     return;
   }
 
-  presShell->EnsureFrameInApproximatelyVisibleList(frame);
+  presShell->MarkFrameVisibleInDisplayPort(frame);
 }
 
 nsresult
 nsImageLoadingContent::OnImageIsAnimated(imgIRequest *aRequest)
 {
   bool* requestFlag = GetRegisteredFlagForRequest(aRequest);
   if (requestFlag) {
     nsLayoutUtils::RegisterImageRequest(GetFramePresContext(),
@@ -514,17 +513,17 @@ nsImageLoadingContent::FrameDestroyed(ns
                                           &mPendingRequestRegistered);
   }
 
   UntrackImage(mCurrentRequest);
   UntrackImage(mPendingRequest);
 
   nsIPresShell* presShell = presContext ? presContext->GetPresShell() : nullptr;
   if (presShell) {
-    presShell->RemoveFrameFromApproximatelyVisibleList(aFrame);
+    presShell->MarkFrameNonvisible(aFrame);
   }
 }
 
 /* static */
 nsContentPolicyType
 nsImageLoadingContent::PolicyTypeForLoad(ImageLoadType aImageLoadType)
 {
   if (aImageLoadType == eImageLoadType_Imageset) {
@@ -1425,22 +1424,23 @@ nsImageLoadingContent::UnbindFromTree(bo
     doc->UnblockOnload(false);
 }
 
 void
 nsImageLoadingContent::OnVisibilityChange(Visibility aNewVisibility,
                                           const Maybe<OnNonvisible>& aNonvisibleAction)
 {
   switch (aNewVisibility) {
-    case Visibility::APPROXIMATELY_VISIBLE:
+    case Visibility::MAY_BECOME_VISIBLE:
+    case Visibility::IN_DISPLAYPORT:
       TrackImage(mCurrentRequest);
       TrackImage(mPendingRequest);
       break;
 
-    case Visibility::APPROXIMATELY_NONVISIBLE:
+    case Visibility::NONVISIBLE:
       UntrackImage(mCurrentRequest, aNonvisibleAction);
       UntrackImage(mPendingRequest, aNonvisibleAction);
       break;
 
     case Visibility::UNTRACKED:
       MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
       break;
   }
@@ -1456,21 +1456,21 @@ nsImageLoadingContent::TrackImage(imgIRe
              "Why haven't we heard of this request?");
 
   nsIDocument* doc = GetOurCurrentDoc();
   if (!doc) {
     return;
   }
 
   // We only want to track this request if we're visible. Ordinarily we check
-  // the visible count, but that requires a frame; in cases where
+  // whether our frame considers itself visible, but in cases where
   // GetOurPrimaryFrame() cannot obtain a frame (e.g. <feImage>), we assume
   // we're visible if FrameCreated() was called.
   nsIFrame* frame = GetOurPrimaryFrame();
-  if ((frame && frame->GetVisibility() == Visibility::APPROXIMATELY_NONVISIBLE) ||
+  if ((frame && !frame->IsVisibleOrMayBecomeVisibleSoon()) ||
       (!frame && !mFrameCreateCalled)) {
     return;
   }
 
   if (aImage == mCurrentRequest && !(mCurrentRequestFlags & REQUEST_IS_TRACKED)) {
     mCurrentRequestFlags |= REQUEST_IS_TRACKED;
     doc->AddImage(mCurrentRequest);
   }
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -40,17 +40,16 @@
 #include "nsRegionFwd.h"
 #include "mozFlushType.h"
 #include "nsWeakReference.h"
 #include <stdio.h> // for FILE definition
 #include "nsChangeHint.h"
 #include "nsRefPtrHashtable.h"
 #include "nsClassHashtable.h"
 #include "nsPresArena.h"
-#include "nsIImageLoadingContent.h"
 #include "nsMargin.h"
 #include "nsFrameState.h"
 
 #ifdef MOZ_B2G
 #include "nsIHardwareKeyHandler.h"
 #endif
 
 class nsDocShell;
@@ -1586,21 +1585,22 @@ public:
   virtual void ScheduleApproximateFrameVisibilityUpdateNow() = 0;
 
   /// Clears the current list of approximately visible frames on this pres shell
   /// and replaces it with frames that are in the display list @aList.
   virtual void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) = 0;
   virtual void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr,
                                                  bool aRemoveOnly = false) = 0;
 
-  /// Ensures @aFrame is in the list of approximately visible frames.
-  virtual void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) = 0;
+  /// Adds @aFrame to the list of frames which were visible within the
+  /// displayport during the last paint.
+  virtual void MarkFrameVisibleInDisplayPort(nsIFrame* aFrame) = 0;
 
-  /// Removes @aFrame from the list of approximately visible frames if present.
-  virtual void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) = 0;
+  /// Marks @aFrame nonvisible and removes it from all lists of visible frames.
+  virtual void MarkFrameNonvisible(nsIFrame* aFrame) = 0;
 
   /// Whether we should assume all frames are visible.
   virtual bool AssumeAllFramesVisible() = 0;
 
 
   /**
    * Returns whether the document's style set's rule processor for the
    * specified level of the cascade is shared by multiple style sets.
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -1182,21 +1182,23 @@ PresShell::Destroy()
   }
 
   mSynthMouseMoveEvent.Revoke();
 
   if (mInFrameVisibilityUpdate) {
     gfxCriticalNoteOnce << "Destroy is re-entering on "
                         << (NS_IsMainThread() ? "" : "non-") << "main thread";
   }
+
   mInFrameVisibilityUpdate = true;
 
   mUpdateApproximateFrameVisibilityEvent.Revoke();
-
-  ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES));
+  mNotifyCompositorOfVisibleRegionsChangeEvent.Revoke();
+
+  ClearVisibleFramesSets(Some(OnNonvisible::DISCARD_IMAGES));
 
   mInFrameVisibilityUpdate = false;
 
   if (mCaret) {
     mCaret->Terminate();
     mCaret = nullptr;
   }
 
@@ -4560,16 +4562,83 @@ PresShell::StyleRuleRemoved(StyleSheetHa
 }
 
 nsIFrame*
 PresShell::GetPlaceholderFrameFor(nsIFrame* aFrame) const
 {
   return mFrameConstructor->GetPlaceholderFrameFor(aFrame);
 }
 
+void
+PresShell::NotifyCompositorOfVisibleRegionsChange()
+{
+  mNotifyCompositorOfVisibleRegionsChangeEvent.Revoke();
+
+  if (!mVisibleRegions) {
+    return;
+  }
+
+  // Retrieve the layers ID and pres shell ID.
+  TabChild* tabChild = TabChild::GetFrom(this);
+  if (!tabChild) {
+    return;
+  }
+
+  const uint64_t layersId = tabChild->LayersId();
+  const uint32_t presShellId = GetPresShellId();
+
+  // Retrieve the CompositorBridgeChild.
+  LayerManager* layerManager = GetLayerManager();
+  if (!layerManager) {
+    return;
+  }
+
+  ClientLayerManager* clientLayerManager = layerManager->AsClientLayerManager();
+  if (!clientLayerManager) {
+    return;
+  }
+
+  CompositorBridgeChild* compositorChild = clientLayerManager->GetCompositorBridgeChild();
+  if (!compositorChild) {
+    return;
+  }
+
+  // XXX(seth): Right now we're just treating MAY_BECOME_VISIBLE and
+  // IN_DISPLAYPORT regions the same when visualizing them. In part 2 we'll
+  // update the visualization to display them differently.
+
+  // Clear the old visible regions associated with this document.
+  compositorChild->SendClearApproximatelyVisibleRegions(layersId, presShellId);
+
+  // Send the new visible regions to the compositor.
+  for (auto iter = mVisibleRegions->mApproximate.ConstIter();
+      !iter.Done();
+      iter.Next()) {
+    const ViewID viewId = iter.Key();
+    const CSSIntRegion* region = iter.UserData();
+    MOZ_ASSERT(region);
+
+    const ScrollableLayerGuid guid(layersId, presShellId, viewId);
+
+    compositorChild->SendNotifyApproximatelyVisibleRegion(guid, *region);
+  }
+
+  for (auto iter = mVisibleRegions->mInDisplayPort.ConstIter();
+      !iter.Done();
+      iter.Next()) {
+    const ViewID viewId = iter.Key();
+    const CSSIntRegion* region = iter.UserData();
+    MOZ_ASSERT(region);
+
+    const ScrollableLayerGuid guid(layersId, presShellId, viewId);
+
+    compositorChild->SendNotifyApproximatelyVisibleRegion(guid, *region);
+  }
+}
+
 nsresult
 PresShell::RenderDocument(const nsRect& aRect, uint32_t aFlags,
                           nscolor aBackgroundColor,
                           gfxContext* aThebesContext)
 {
   NS_ENSURE_TRUE(!(aFlags & RENDER_IS_UNTRUSTED), NS_ERROR_NOT_IMPLEMENTED);
 
   nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
@@ -5561,27 +5630,24 @@ PresShell::ProcessSynthMouseMoveEvent(bo
     shell->DispatchSynthMouseMove(&event, !aFromScroll);
   }
 
   if (!aFromScroll) {
     mSynthMouseMoveEvent.Forget();
   }
 }
 
-static void
-AddFrameToVisibleRegions(nsIFrame* aFrame,
-                         nsViewManager* aViewManager,
-                         Maybe<VisibleRegions>& aVisibleRegions)
-{
-  if (!aVisibleRegions) {
+void
+PresShell::AddFrameToVisibleRegions(nsIFrame* aFrame, VisibilityCounter aForCounter)
+{
+  if (!mVisibleRegions) {
     return;
   }
 
   MOZ_ASSERT(aFrame);
-  MOZ_ASSERT(aViewManager);
 
   // Retrieve the view ID for this frame (which we obtain from the enclosing
   // scrollable frame).
   nsIScrollableFrame* scrollableFrame =
     nsLayoutUtils::GetNearestScrollableFrame(aFrame,
                                              nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
                                              nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT);
   if (!scrollableFrame) {
@@ -5606,30 +5672,32 @@ AddFrameToVisibleRegions(nsIFrame* aFram
   nsLayoutUtils::TransformResult result =
     nsLayoutUtils::TransformRect(aFrame,
                                  scrollableFrame->GetScrolledFrame(),
                                  frameRectInScrolledFrameSpace);
   if (result != nsLayoutUtils::TransformResult::TRANSFORM_SUCCEEDED) {
     return;
   }
 
-  CSSIntRegion* regionForView = aVisibleRegions->LookupOrAdd(viewID);
+  VisibleRegions& regions = aForCounter == VisibilityCounter::MAY_BECOME_VISIBLE
+                          ? mVisibleRegions->mApproximate
+                          : mVisibleRegions->mInDisplayPort;
+  CSSIntRegion* regionForView = regions.LookupOrAdd(viewID);
   MOZ_ASSERT(regionForView);
 
   regionForView->OrWith(CSSPixel::FromAppUnitsRounded(frameRectInScrolledFrameSpace));
 }
 
 /* static */ void
-PresShell::MarkFramesInListApproximatelyVisible(const nsDisplayList& aList,
-                                                Maybe<VisibleRegions>& aVisibleRegions)
+PresShell::MarkFramesInListApproximatelyVisible(const nsDisplayList& aList)
 {
   for (nsDisplayItem* item = aList.GetBottom(); item; item = item->GetAbove()) {
     nsDisplayList* sublist = item->GetChildren();
     if (sublist) {
-      MarkFramesInListApproximatelyVisible(*sublist, aVisibleRegions);
+      MarkFramesInListApproximatelyVisible(*sublist);
       continue;
     }
 
     nsIFrame* frame = item->Frame();
     MOZ_ASSERT(frame);
 
     if (!frame->TrackingVisibility()) {
       continue;
@@ -5637,20 +5705,20 @@ PresShell::MarkFramesInListApproximately
 
     // Use the presshell containing the frame.
     auto* presShell = static_cast<PresShell*>(frame->PresContext()->PresShell());
     uint32_t count = presShell->mApproximatelyVisibleFrames.Count();
     MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
     presShell->mApproximatelyVisibleFrames.PutEntry(frame);
     if (presShell->mApproximatelyVisibleFrames.Count() > count) {
       // The frame was added to mApproximatelyVisibleFrames, so increment its visible count.
-      frame->IncApproximateVisibleCount();
-    }
-
-    AddFrameToVisibleRegions(frame, presShell->mViewManager, aVisibleRegions);
+      frame->IncVisibilityCount(VisibilityCounter::MAY_BECOME_VISIBLE);
+    }
+
+    presShell->AddFrameToVisibleRegions(frame, VisibilityCounter::MAY_BECOME_VISIBLE);
   }
 }
 
 void
 PresShell::ReportBadStateDuringVisibilityUpdate()
 {
   if (!NS_IsMainThread()) {
     gfxCriticalNote << "Got null frame in frame visibility: off-main-thread";
@@ -5682,160 +5750,143 @@ PresShell::ReportBadStateDuringVisibilit
 }
 
 void
 PresShell::SetInFrameVisibilityUpdate(bool aState)
 {
   mInFrameVisibilityUpdate = aState;
 }
 
-static void
-NotifyCompositorOfVisibleRegionsChange(PresShell* aPresShell,
-                                       const Maybe<VisibleRegions>& aRegions)
-{
-  if (!aRegions) {
-    return;
-  }
-
-  MOZ_ASSERT(aPresShell);
-
-  // Retrieve the layers ID and pres shell ID.
-  TabChild* tabChild = TabChild::GetFrom(aPresShell);
-  if (!tabChild) {
-    return;
-  }
-
-  const uint64_t layersId = tabChild->LayersId();
-  const uint32_t presShellId = aPresShell->GetPresShellId();
-
-  // Retrieve the CompositorBridgeChild.
-  LayerManager* layerManager = aPresShell->GetLayerManager();
-  if (!layerManager) {
-    return;
-  }
-
-  ClientLayerManager* clientLayerManager = layerManager->AsClientLayerManager();
-  if (!clientLayerManager) {
-    return;
-  }
-
-  CompositorBridgeChild* compositorChild = clientLayerManager->GetCompositorBridgeChild();
-  if (!compositorChild) {
-    return;
-  }
-
-  // Clear the old approximately visible regions associated with this document.
-  compositorChild->SendClearApproximatelyVisibleRegions(layersId, presShellId);
-
-  // Send the new approximately visible regions to the compositor.
-  for (auto iter = aRegions->ConstIter(); !iter.Done(); iter.Next()) {
-    const ViewID viewId = iter.Key();
-    const CSSIntRegion* region = iter.UserData();
-    MOZ_ASSERT(region);
-
-    const ScrollableLayerGuid guid(layersId, presShellId, viewId);
-
-    compositorChild->SendNotifyApproximatelyVisibleRegion(guid, *region);
-  }
-}
-
 /* static */ void
-PresShell::DecApproximateVisibleCount(VisibleFrames& aFrames,
-                                      Maybe<OnNonvisible> aNonvisibleAction
-                                        /* = Nothing() */)
-{
-  for (auto iter = aFrames.Iter(); !iter.Done(); iter.Next()) {
+PresShell::DecVisibleCount(const VisibleFrames& aFrames,
+                           VisibilityCounter aCounter,
+                           Maybe<OnNonvisible> aNonvisibleAction /* = Nothing() */)
+{
+  for (auto iter = aFrames.ConstIter(); !iter.Done(); iter.Next()) {
     nsIFrame* frame = iter.Get()->GetKey();
 
     if (MOZ_UNLIKELY(!frame)) {
       // We are about to crash, annotate crash report with some info that might
       // help debug the crash (bug 1251150)
       ReportBadStateDuringVisibilityUpdate();
     }
 
     // Decrement the frame's visible count if we're still tracking its
     // visibility. (We may not be, if the frame disabled visibility tracking
     // after we added it to the visible frames list.)
     if (frame->TrackingVisibility()) {
-      frame->DecApproximateVisibleCount(aNonvisibleAction);
-    }
-  }
+      frame->DecVisibilityCount(aCounter, aNonvisibleAction);
+    }
+  }
+}
+
+void
+PresShell::InitVisibleRegionsIfVisualizationEnabled(VisibilityCounter aForCounter)
+{
+  // If we're visualizing visible regions, initialize a
+  // VisibleRegionsContainer to store them.  Visibility-related functions we
+  // call will only do the work of populating this object and sending it to
+  // the compositor if we've created it, so we don't need to check the prefs
+  // everywhere.
+  if (!gfxPrefs::APZMinimap() ||
+      !gfxPrefs::APZMinimapVisibilityEnabled()) {
+    mVisibleRegions = nullptr;
+    return;
+  }
+
+  if (mVisibleRegions) {
+    // Clear the regions we're about to update. We don't want to clear both,
+    // or the two visibility tracking methods will interfere with each other.
+    VisibleRegions& regions = aForCounter == VisibilityCounter::MAY_BECOME_VISIBLE
+                            ? mVisibleRegions->mApproximate
+                            : mVisibleRegions->mInDisplayPort;
+    regions.Clear();
+    return;
+  }
+
+  mVisibleRegions = MakeUnique<VisibleRegionsContainer>();
 }
 
 void
 PresShell::RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList)
 {
   MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
   mApproximateFrameVisibilityVisited = true;
 
   // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
   // them in oldApproxVisibleFrames.
   VisibleFrames oldApproximatelyVisibleFrames;
   mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);
 
-  // If we're visualizing visible regions, create a VisibleRegions object to
-  // store information about them. The functions we call will populate this
-  // object and send it to the compositor only if it's Some(), so we don't
-  // need to check the prefs everywhere.
-  Maybe<VisibleRegions> visibleRegions;
-  if (gfxPrefs::APZMinimap() && gfxPrefs::APZMinimapVisibilityEnabled()) {
-    visibleRegions.emplace();
-  }
-
-  MarkFramesInListApproximatelyVisible(aList, visibleRegions);
-
-  DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
-
-  NotifyCompositorOfVisibleRegionsChange(this, visibleRegions);
+  InitVisibleRegionsIfVisualizationEnabled(VisibilityCounter::MAY_BECOME_VISIBLE);
+
+  MarkFramesInListApproximatelyVisible(aList);
+
+  DecVisibleCount(oldApproximatelyVisibleFrames,
+                  VisibilityCounter::MAY_BECOME_VISIBLE);
+
+  NotifyCompositorOfVisibleRegionsChange();
 }
 
 /* static */ void
-PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear)
+PresShell::ClearVisibleFramesForUnvisitedPresShells(nsView* aView, bool aClear)
 {
   nsViewManager* vm = aView->GetViewManager();
   if (aClear) {
     PresShell* presShell = static_cast<PresShell*>(vm->GetPresShell());
     if (!presShell->mApproximateFrameVisibilityVisited) {
-      presShell->ClearApproximatelyVisibleFramesList();
+      presShell->ClearVisibleFramesSets();
     }
     presShell->mApproximateFrameVisibilityVisited = false;
   }
   for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
-    ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
-  }
-}
-
-void
-PresShell::ClearApproximatelyVisibleFramesList(Maybe<OnNonvisible> aNonvisibleAction
-                                                 /* = Nothing() */)
-{
-  DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
+    ClearVisibleFramesForUnvisitedPresShells(v, v->GetViewManager() != vm);
+  }
+}
+
+void
+PresShell::ClearVisibleFramesSets(Maybe<OnNonvisible> aNonvisibleAction
+                                    /* = Nothing() */)
+{
+  DecVisibleCount(mApproximatelyVisibleFrames,
+                  VisibilityCounter::MAY_BECOME_VISIBLE,
+                  aNonvisibleAction);
   mApproximatelyVisibleFrames.Clear();
+
+  DecVisibleCount(mInDisplayPortFrames,
+                  VisibilityCounter::IN_DISPLAYPORT,
+                  aNonvisibleAction);
+  mInDisplayPortFrames.Clear();
+
+  if (mVisibleRegions) {
+    mVisibleRegions->mApproximate.Clear();
+    mVisibleRegions->mInDisplayPort.Clear();
+    NotifyCompositorOfVisibleRegionsChange();
+  }
 }
 
 void
 PresShell::MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame,
                                                    const nsRect& aRect,
-                                                   Maybe<VisibleRegions>& aVisibleRegions,
                                                    bool aRemoveOnly /* = false */)
 {
   MOZ_ASSERT(aFrame->PresContext()->PresShell() == this, "wrong presshell");
 
   if (aFrame->TrackingVisibility() &&
       aFrame->StyleVisibility()->IsVisible() &&
-      (!aRemoveOnly || aFrame->GetVisibility() == Visibility::APPROXIMATELY_VISIBLE)) {
+      (!aRemoveOnly || aFrame->IsVisibleOrMayBecomeVisibleSoon())) {
     MOZ_ASSERT(!AssumeAllFramesVisible());
     uint32_t count = mApproximatelyVisibleFrames.Count();
     mApproximatelyVisibleFrames.PutEntry(aFrame);
     if (mApproximatelyVisibleFrames.Count() > count) {
       // The frame was added to mApproximatelyVisibleFrames, so increment its visible count.
-      aFrame->IncApproximateVisibleCount();
-    }
-
-    AddFrameToVisibleRegions(aFrame, mViewManager, aVisibleRegions);
+      aFrame->IncVisibilityCount(VisibilityCounter::MAY_BECOME_VISIBLE);
+    }
+
+    AddFrameToVisibleRegions(aFrame, VisibilityCounter::MAY_BECOME_VISIBLE);
   }
 
   nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
   if (subdocFrame) {
     nsIPresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
       nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
     if (presShell && !presShell->AssumeAllFramesVisible()) {
       nsRect rect = aRect;
@@ -5894,17 +5945,17 @@ PresShell::MarkFramesInSubtreeApproximat
           nsRect out;
           if (nsDisplayTransform::UntransformRect(r, overflow, child, nsPoint(0,0), &out)) {
             r = out;
           } else {
             r.SetEmpty();
           }
         }
       }
-      MarkFramesInSubtreeApproximatelyVisible(child, r, aVisibleRegions);
+      MarkFramesInSubtreeApproximatelyVisible(child, r);
     }
   }
 }
 
 void
 PresShell::RebuildApproximateFrameVisibility(nsRect* aRect,
                                              bool aRemoveOnly /* = false */)
 {
@@ -5916,35 +5967,29 @@ PresShell::RebuildApproximateFrameVisibi
     return;
   }
 
   // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
   // them in oldApproximatelyVisibleFrames.
   VisibleFrames oldApproximatelyVisibleFrames;
   mApproximatelyVisibleFrames.SwapElements(oldApproximatelyVisibleFrames);
 
-  // If we're visualizing visible regions, create a VisibleRegions object to
-  // store information about them. The functions we call will populate this
-  // object and send it to the compositor only if it's Some(), so we don't
-  // need to check the prefs everywhere.
-  Maybe<VisibleRegions> visibleRegions;
-  if (gfxPrefs::APZMinimap() && gfxPrefs::APZMinimapVisibilityEnabled()) {
-    visibleRegions.emplace();
-  }
+  InitVisibleRegionsIfVisualizationEnabled(VisibilityCounter::MAY_BECOME_VISIBLE);
 
   nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
   if (aRect) {
     vis = *aRect;
   }
 
-  MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, visibleRegions, aRemoveOnly);
-
-  DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
-
-  NotifyCompositorOfVisibleRegionsChange(this, visibleRegions);
+  MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);
+
+  DecVisibleCount(oldApproximatelyVisibleFrames,
+                  VisibilityCounter::MAY_BECOME_VISIBLE);
+
+  NotifyCompositorOfVisibleRegionsChange();
 }
 
 void
 PresShell::UpdateApproximateFrameVisibility()
 {
   DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
 }
 
@@ -5965,23 +6010,23 @@ PresShell::DoUpdateApproximateFrameVisib
   if (mHaveShutDown || mIsDestroying) {
     mInFrameVisibilityUpdate = false;
     return;
   }
 
   // call update on that frame
   nsIFrame* rootFrame = GetRootFrame();
   if (!rootFrame) {
-    ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DISCARD_IMAGES));
+    ClearVisibleFramesSets(Some(OnNonvisible::DISCARD_IMAGES));
     mInFrameVisibilityUpdate = false;
     return;
   }
 
   RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
-  ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+  ClearVisibleFramesForUnvisitedPresShells(rootFrame->GetView(), true);
 
   mInFrameVisibilityUpdate = false;
 
 #ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
   // This can be used to debug the frame walker by comparing beforeFrameList
   // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see if
   // they produce the same results (mApproximatelyVisibleFrames holds the frames the
   // display list thinks are visible, beforeFrameList holds the frames the
@@ -6003,17 +6048,17 @@ PresShell::DoUpdateApproximateFrameVisib
   builder.IgnorePaintSuppression();
   builder.EnterPresShell(rootFrame, updateRect);
   nsDisplayList list;
   rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
   builder.LeavePresShell(rootFrame, updateRect);
 
   RebuildApproximateFrameVisibilityDisplayList(list);
 
-  ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+  ClearVisibleFramesForUnvisitedPresShells(rootFrame->GetView(), true);
 
   list.DeleteAll();
 #endif
 }
 
 bool
 PresShell::AssumeAllFramesVisible()
 {
@@ -6108,84 +6153,101 @@ PresShell::ScheduleApproximateFrameVisib
   RefPtr<nsRunnableMethod<PresShell> > ev =
     NS_NewRunnableMethod(this, &PresShell::UpdateApproximateFrameVisibility);
   if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
     mUpdateApproximateFrameVisibilityEvent = ev;
   }
 }
 
 void
-PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame)
+PresShell::MarkFrameVisibleInDisplayPort(nsIFrame* aFrame)
 {
   if (!aFrame->TrackingVisibility()) {
     return;
   }
 
   if (AssumeAllFramesVisible()) {
-    aFrame->IncApproximateVisibleCount();
+    if (aFrame->GetVisibility() != Visibility::IN_DISPLAYPORT) {
+      aFrame->IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
+    }
     return;
   }
 
 #ifdef DEBUG
   // Make sure it's in this pres shell.
   nsCOMPtr<nsIContent> content = aFrame->GetContent();
   if (content) {
     PresShell* shell = static_cast<PresShell*>(content->OwnerDoc()->GetShell());
     MOZ_ASSERT(!shell || shell == this, "wrong shell");
   }
 #endif
 
   if (mInFrameVisibilityUpdate) {
-    gfxCriticalNoteOnce << "EnsureFrameInApproximatelyVisibleList is re-entering on "
+    gfxCriticalNoteOnce << "MarkFrameVisibleInDisplayPort is re-entering on "
                         << (NS_IsMainThread() ? "" : "non-") << "main thread";
   }
+
   mInFrameVisibilityUpdate = true;
 
-  if (!mApproximatelyVisibleFrames.Contains(aFrame)) {
+  if (!mInDisplayPortFrames.Contains(aFrame)) {
     MOZ_ASSERT(!AssumeAllFramesVisible());
-    mApproximatelyVisibleFrames.PutEntry(aFrame);
-    aFrame->IncApproximateVisibleCount();
-  }
+    mInDisplayPortFrames.PutEntry(aFrame);
+    aFrame->IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
+  }
+
+  AddFrameToVisibleRegions(aFrame, VisibilityCounter::IN_DISPLAYPORT);
 
   mInFrameVisibilityUpdate = false;
 }
 
-void
-PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame)
+static void
+RemoveFrameFromVisibleSet(nsIFrame* aFrame,
+                          VisibleFrames& aSet,
+                          VisibilityCounter aCounter)
+{
+  uint32_t count = aSet.Count();
+  aSet.RemoveEntry(aFrame);
+
+  if (aFrame->TrackingVisibility() && aSet.Count() < count) {
+    aFrame->DecVisibilityCount(aCounter);
+  }
+
+}
+
+void
+PresShell::MarkFrameNonvisible(nsIFrame* aFrame)
 {
 #ifdef DEBUG
   // Make sure it's in this pres shell.
   nsCOMPtr<nsIContent> content = aFrame->GetContent();
   if (content) {
     PresShell* shell = static_cast<PresShell*>(content->OwnerDoc()->GetShell());
     MOZ_ASSERT(!shell || shell == this, "wrong shell");
   }
 #endif
 
   if (AssumeAllFramesVisible()) {
     MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
-               "Shouldn't have any frames in the table");
+               "Shouldn't have any frames in the approximate visibility set");
+    MOZ_ASSERT(mInDisplayPortFrames.Count() == 0,
+               "Shouldn't have any frames in the in-displayport visibility set");
     return;
   }
 
   if (mInFrameVisibilityUpdate) {
-    gfxCriticalNoteOnce << "RemoveFrameFromApproximatelyVisibleList is re-entering on "
+    gfxCriticalNoteOnce << "MarkFrameNonvisible is re-entering on "
                         << (NS_IsMainThread() ? "" : "non-") << "main thread";
   }
+
   mInFrameVisibilityUpdate = true;
 
-  uint32_t count = mApproximatelyVisibleFrames.Count();
-  mApproximatelyVisibleFrames.RemoveEntry(aFrame);
-
-  if (aFrame->TrackingVisibility() &&
-      mApproximatelyVisibleFrames.Count() < count) {
-    // aFrame was in the hashtable, and we're still tracking its visibility,
-    // so we need to decrement its visible count.
-    aFrame->DecApproximateVisibleCount();
-  }
+  RemoveFrameFromVisibleSet(aFrame, mApproximatelyVisibleFrames,
+                            VisibilityCounter::MAY_BECOME_VISIBLE);
+  RemoveFrameFromVisibleSet(aFrame, mInDisplayPortFrames,
+                            VisibilityCounter::IN_DISPLAYPORT);
 
   mInFrameVisibilityUpdate = false;
 }
 
 class nsAutoNotifyDidPaint
 {
 public:
   nsAutoNotifyDidPaint(PresShell* aShell, uint32_t aFlags)
@@ -8999,16 +9061,17 @@ FreezeSubDocument(nsIDocument *aDocument
 
   return true;
 }
 
 void
 PresShell::Freeze()
 {
   mUpdateApproximateFrameVisibilityEvent.Revoke();
+  mNotifyCompositorOfVisibleRegionsChangeEvent.Revoke();
 
   MaybeReleaseCapturingContent();
 
   mDocument->EnumerateActivityObservers(FreezeElement, nullptr);
 
   if (mCaret) {
     SetCaretEnabled(false);
   }
@@ -11067,16 +11130,21 @@ PresShell::AddSizeOfIncludingThis(Malloc
                                   size_t *aPresContextSize)
 {
   mFrameArena.AddSizeOfExcludingThis(aMallocSizeOf, aArenaObjectsSize);
   *aPresShellSize += aMallocSizeOf(this);
   if (mCaret) {
     *aPresShellSize += mCaret->SizeOfIncludingThis(aMallocSizeOf);
   }
   *aPresShellSize += mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  *aPresShellSize += mInDisplayPortFrames.ShallowSizeOfExcludingThis(aMallocSizeOf);
+  *aPresShellSize += mVisibleRegions
+                   ? mVisibleRegions->mApproximate.ShallowSizeOfExcludingThis(aMallocSizeOf) +
+                     mVisibleRegions->mInDisplayPort.ShallowSizeOfExcludingThis(aMallocSizeOf)
+                   : 0;
   *aPresShellSize += mFramesToDirty.ShallowSizeOfExcludingThis(aMallocSizeOf);
   *aPresShellSize += aArenaObjectsSize->mOther;
 
   if (nsStyleSet* styleSet = StyleSet()->GetAsGecko()) {
     *aStyleSetsSize += styleSet->SizeOfIncludingThis(aMallocSizeOf);
   } else {
     NS_WARNING("ServoStyleSets do not support memory measurements yet");
   }
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -33,16 +33,17 @@
 #include "nsRefreshDriver.h"
 #include "TouchManager.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/StyleSetHandle.h"
 #include "mozilla/UniquePtr.h"
 #include "MobileViewportManager.h"
+#include "Visibility.h"
 #include "ZoomConstraintsClient.h"
 
 class nsRange;
 
 struct RangePaintInfo;
 struct nsCallbackEventRequest;
 #ifdef MOZ_REFLOW_PERF
 class ReflowCountMgr;
@@ -74,16 +75,18 @@ class PresShell final : public nsIPresSh
                         public nsStubDocumentObserver,
                         public nsISelectionController,
                         public nsIObserver,
                         public nsSupportsWeakReference
 {
   template <typename T> using Maybe = mozilla::Maybe<T>;
   using Nothing = mozilla::Nothing;
   using OnNonvisible = mozilla::OnNonvisible;
+  template <typename T> using UniquePtr = mozilla::UniquePtr<T>;
+  using VisibilityCounter = mozilla::VisibilityCounter;
   using VisibleFrames = mozilla::VisibleFrames;
   using VisibleRegions = mozilla::VisibleRegions;
 
 public:
   PresShell();
 
   NS_DECL_AND_IMPL_ZEROING_OPERATOR_NEW
 
@@ -397,18 +400,18 @@ public:
 
   void ScheduleApproximateFrameVisibilityUpdateSoon() override;
   void ScheduleApproximateFrameVisibilityUpdateNow() override;
 
   void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList) override;
   void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr,
                                          bool aRemoveOnly = false) override;
 
-  void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) override;
-  void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) override;
+  void MarkFrameVisibleInDisplayPort(nsIFrame* aFrame) override;
+  void MarkFrameNonvisible(nsIFrame* aFrame) override;
 
   bool AssumeAllFramesVisible() override;
 
 
   virtual void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) override;
 
   virtual void DispatchAfterKeyboardEvent(nsINode* aTarget,
                                           const mozilla::WidgetKeyboardEvent& aEvent,
@@ -760,37 +763,58 @@ protected:
 
   //////////////////////////////////////////////////////////////////////////////
   // Approximate frame visibility tracking implementation.
   //////////////////////////////////////////////////////////////////////////////
 
   void UpdateApproximateFrameVisibility();
   void DoUpdateApproximateFrameVisibility(bool aRemoveOnly);
 
-  void ClearApproximatelyVisibleFramesList(Maybe<mozilla::OnNonvisible> aNonvisibleAction
-                                             = Nothing());
-  static void ClearApproximateFrameVisibilityVisited(nsView* aView, bool aClear);
-  static void MarkFramesInListApproximatelyVisible(const nsDisplayList& aList,
-                                                   Maybe<VisibleRegions>& aVisibleRegions);
+  void ClearVisibleFramesSets(Maybe<OnNonvisible> aNonvisibleAction = Nothing());
+  static void ClearVisibleFramesForUnvisitedPresShells(nsView* aView, bool aClear);
+  static void MarkFramesInListApproximatelyVisible(const nsDisplayList& aList);
   void MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame,
                                                const nsRect& aRect,
-                                               Maybe<VisibleRegions>& aVisibleRegions,
                                                bool aRemoveOnly = false);
 
-  void DecApproximateVisibleCount(VisibleFrames& aFrames,
-                                  Maybe<OnNonvisible> aNonvisibleAction = Nothing());
+  void DecVisibleCount(const VisibleFrames& aFrames,
+                       VisibilityCounter aCounter,
+                       Maybe<OnNonvisible> aNonvisibleAction = Nothing());
   void ReportBadStateDuringVisibilityUpdate();
   void SetInFrameVisibilityUpdate(bool aState);
 
+  void InitVisibleRegionsIfVisualizationEnabled(VisibilityCounter aForCounter);
+  void AddFrameToVisibleRegions(nsIFrame* aFrame, VisibilityCounter aForCounter);
+  void NotifyCompositorOfVisibleRegionsChange();
+
   nsRevocableEventPtr<nsRunnableMethod<PresShell>> mUpdateApproximateFrameVisibilityEvent;
+  nsRevocableEventPtr<nsRunnableMethod<PresShell>> mNotifyCompositorOfVisibleRegionsChangeEvent;
 
   // A set of frames that were visible or could be visible soon at the time
   // that we last did an approximate frame visibility update.
   VisibleFrames mApproximatelyVisibleFrames;
 
+  // A set of frames that were visible in the displayport the last time we painted.
+  VisibleFrames mInDisplayPortFrames;
+
+  struct VisibleRegionsContainer
+  {
+    // The approximately visible regions calculated during the last update to
+    // approximate frame visibility.
+    VisibleRegions mApproximate;
+
+    // The in-displayport visible regions calculated during the last paint.
+    VisibleRegions mInDisplayPort;
+  };
+
+  // The most recent visible regions we've computed. Only non-null if the APZ
+  // minimap visibility visualization was enabled during the last visibility
+  // update.
+  UniquePtr<VisibleRegionsContainer> mVisibleRegions;
+
 
   //////////////////////////////////////////////////////////////////////////////
   // Methods for dispatching KeyboardEvent and BeforeAfterKeyboardEvent.
   //////////////////////////////////////////////////////////////////////////////
 
   void HandleKeyboardEvent(nsINode* aTarget,
                            mozilla::WidgetKeyboardEvent& aEvent,
                            bool aEmbeddedCancelled,
--- a/layout/generic/Visibility.h
+++ b/layout/generic/Visibility.h
@@ -13,31 +13,45 @@
 #ifndef mozilla_layout_generic_Visibility_h
 #define mozilla_layout_generic_Visibility_h
 
 namespace mozilla {
 
 // Visibility states for frames.
 enum class Visibility : uint8_t
 {
-  // Indicates that we're not tracking visibility for this frame.
+  // We're not tracking visibility for this frame.
   UNTRACKED,
 
-  // Indicates that the frame is probably nonvisible. Visible frames *may* be
-  // APPROXIMATELY_NONVISIBLE because approximate visibility is not updated
-  // synchronously. Some truly nonvisible frames may be marked
-  // APPROXIMATELY_VISIBLE instead if our heuristics lead us to think they may
-  // be visible soon.
-  APPROXIMATELY_NONVISIBLE,
+  // This frame is nonvisible - i.e., it was not within the displayport as of
+  // the last paint (in which case it'd be IN_DISPLAYPORT) and our heuristics
+  // aren't telling us that it may become visible soon (in which case it'd be
+  // MAY_BECOME_VISIBLE).
+  NONVISIBLE,
+
+  // This frame is nonvisible now, but our heuristics tell us it may become
+  // visible soon. These heuristics are updated on a relatively slow timer, so a
+  // frame being marked MAY_BECOME_VISIBLE does not imply any particular
+  // relationship between the frame and the displayport.
+  MAY_BECOME_VISIBLE,
 
-  // Indicates that the frame is either visible now or is likely to be visible
-  // soon according to our heuristics. As with APPROXIMATELY_NONVISIBLE, it's
-  // important to note that approximately visibility is not updated
-  // synchronously, so this information may be out of date.
-  APPROXIMATELY_VISIBLE
+  // This frame was within the displayport as of the last paint. That doesn't
+  // necessarily mean that the frame is visible - it may still lie outside the
+  // viewport - but it does mean that the user may scroll the frame into view
+  // asynchronously at any time (due to APZ), so for most purposes such a frame
+  // should be treated as truly visible.
+  IN_DISPLAYPORT
+};
+
+// The subset of the states in @Visibility which have a per-frame counter. This
+// is used in the implementation of visibility tracking.
+enum class VisibilityCounter : uint8_t
+{
+  MAY_BECOME_VISIBLE,
+  IN_DISPLAYPORT
 };
 
 // Requested actions when frames transition to the nonvisible state.
 enum class OnNonvisible : uint8_t
 {
   DISCARD_IMAGES  // Discard images associated with the frame.
 };
 
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -406,17 +406,17 @@ nsFrame::nsFrame(nsStyleContext* aContex
   mStyleContext->FrameAddRef();
 #endif
 }
 
 nsFrame::~nsFrame()
 {
   MOZ_COUNT_DTOR(nsFrame);
 
-  MOZ_ASSERT(GetVisibility() != Visibility::APPROXIMATELY_VISIBLE,
+  MOZ_ASSERT(!IsVisibleOrMayBecomeVisibleSoon(),
              "Visible nsFrame is being destroyed");
 
   NS_IF_RELEASE(mContent);
 #ifdef DEBUG
   mStyleContext->FrameRelease();
 #endif
   mStyleContext->Release();
 }
@@ -545,17 +545,17 @@ nsFrame::Init(nsIContent*       aContent
     mState |= state & (NS_FRAME_INDEPENDENT_SELECTION |
                        NS_FRAME_GENERATED_CONTENT |
                        NS_FRAME_IS_SVG_TEXT |
                        NS_FRAME_IN_POPUP |
                        NS_FRAME_IS_NONDISPLAY);
 
     if (HasAnyStateBits(NS_FRAME_IN_POPUP) && TrackingVisibility()) {
       // Assume all frames in popups are visible.
-      IncApproximateVisibleCount();
+      IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
     }
   }
   const nsStyleDisplay *disp = StyleDisplay();
   if (disp->HasTransform(this)) {
     // The frame gets reconstructed if we toggle the -moz-transform
     // property, so we can set this bit here and then ignore it.
     mState |= NS_FRAME_MAY_BE_TRANSFORMED;
   }
@@ -598,17 +598,17 @@ nsFrame::Init(nsIContent*       aContent
   }
 
   if (aContent && aContent->GetProperty(nsGkAtoms::vr_state) != nullptr) {
     AddStateBits(NS_FRAME_HAS_VR_CONTENT);
   }
 
   if (PresContext()->PresShell()->AssumeAllFramesVisible() &&
       TrackingVisibility()) {
-    IncApproximateVisibleCount();
+    IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
   }
 
   DidSetStyleContext(nullptr);
 
   if (::IsXULBoxWrapped(this))
     ::InitBoxMetrics(this, false);
 }
 
@@ -718,18 +718,18 @@ nsFrame::DestroyFrom(nsIFrame* aDestruct
   // Disable visibility tracking. Note that we have to do this before calling
   // NotifyDestroyingFrame(), which will clear frame properties and make us lose
   // track of whether we were previously visible or not.
   // XXX(seth): It'd be ideal to assert that we're already marked nonvisible
   // here, but it's unfortunately tricky to guarantee in the face of things like
   // frame reconstruction induced by style changes.
   DisableVisibilityTracking();
 
-  // Ensure that we're not in the approximately visible list anymore.
-  PresContext()->GetPresShell()->RemoveFrameFromApproximatelyVisibleList(this);
+  // Ensure that we're not in the visible list anymore.
+  PresContext()->GetPresShell()->MarkFrameNonvisible(this);
 
   shell->NotifyDestroyingFrame(this);
 
   if (mState & NS_FRAME_EXTERNAL_REFERENCE) {
     shell->ClearFrameRefs(this);
   }
 
   if (view) {
@@ -1461,45 +1461,55 @@ nsIFrame::GetCrossDocChildLists(nsTArray
         nsFrameList(root, nsLayoutUtils::GetLastSibling(root)),
         nsIFrame::kPrincipalList));
     }
   }
 
   GetChildLists(aLists);
 }
 
+static Visibility
+VisibilityStateAsVisibility(const nsIFrame::VisibilityState& aState)
+{
+  if (aState.mInDisplayPortCounter > 0) {
+    return Visibility::IN_DISPLAYPORT;  // Takes priority over MAY_BECOME_VISIBLE.
+  }
+  if (aState.mApproximateCounter > 0) {
+    return Visibility::MAY_BECOME_VISIBLE;
+  }
+  return Visibility::NONVISIBLE;
+}
+
 Visibility
 nsIFrame::GetVisibility() const
 {
   if (!(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED)) {
     return Visibility::UNTRACKED;
   }
 
   bool isSet = false;
   FrameProperties props = Properties();
-  uint32_t visibleCount = props.Get(VisibilityStateProperty(), &isSet);
+  VisibilityState state = props.Get(VisibilityStateProperty(), &isSet);
 
   MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value "
                     "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
 
-  return visibleCount > 0
-       ? Visibility::APPROXIMATELY_VISIBLE
-       : Visibility::APPROXIMATELY_NONVISIBLE;
+  return VisibilityStateAsVisibility(state);
 }
 
 void
 nsIFrame::UpdateVisibilitySynchronously()
 {
   nsIPresShell* presShell = PresContext()->PresShell();
   if (!presShell) {
     return;
   }
 
   if (presShell->AssumeAllFramesVisible()) {
-    presShell->EnsureFrameInApproximatelyVisibleList(this);
+    presShell->MarkFrameVisibleInDisplayPort(this);
     return;
   }
 
   bool visible = true;
   nsIFrame* f = GetParent();
   nsRect rect = GetRectRelativeToSelf();
   nsIFrame* rectFrame = this;
   while (f) {
@@ -1530,19 +1540,19 @@ nsIFrame::UpdateVisibilitySynchronously(
       if (parent && parent->PresContext()->IsChrome()) {
         break;
       }
     }
     f = parent;
   }
 
   if (visible) {
-    presShell->EnsureFrameInApproximatelyVisibleList(this);
+    presShell->MarkFrameVisibleInDisplayPort(this);
   } else {
-    presShell->RemoveFrameFromApproximatelyVisibleList(this);
+    presShell->MarkFrameNonvisible(this);
   }
 }
 
 void
 nsIFrame::EnableVisibilityTracking()
 {
   if (GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED) {
     return;  // Nothing to do.
@@ -1551,17 +1561,17 @@ nsIFrame::EnableVisibilityTracking()
   FrameProperties props = Properties();
   MOZ_ASSERT(!props.Has(VisibilityStateProperty()),
              "Shouldn't have a VisibilityStateProperty value "
              "if NS_FRAME_VISIBILITY_IS_TRACKED is not set");
 
   // Add the state bit so we know to track visibility for this frame, and
   // initialize the frame property.
   AddStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
-  props.Set(VisibilityStateProperty(), 0);
+  props.Set(VisibilityStateProperty(), VisibilityState{0, 0});
 
   nsIPresShell* presShell = PresContext()->PresShell();
   if (!presShell) {
     return;
   }
 
   // Schedule a visibility update. This method will virtually always be called
   // when layout has changed anyway, so it's very unlikely that any additional
@@ -1574,76 +1584,97 @@ void
 nsIFrame::DisableVisibilityTracking()
 {
   if (!(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED)) {
     return;  // Nothing to do.
   }
 
   bool isSet = false;
   FrameProperties props = Properties();
-  uint32_t visibleCount = props.Remove(VisibilityStateProperty(), &isSet);
+  VisibilityState state = props.Remove(VisibilityStateProperty(), &isSet);
 
   MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value "
                     "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
 
   RemoveStateBits(NS_FRAME_VISIBILITY_IS_TRACKED);
 
-  if (visibleCount == 0) {
-    return;  // We were nonvisible.
+  Visibility previousVisibility = VisibilityStateAsVisibility(state);
+  if (previousVisibility == Visibility::NONVISIBLE) {
+    return;  // We were already nonvisible.
   }
 
   // We were visible, so send an OnVisibilityChange() notification.
-  OnVisibilityChange(Visibility::APPROXIMATELY_NONVISIBLE);
+  OnVisibilityChange(Visibility::NONVISIBLE);
 }
 
 void
-nsIFrame::DecApproximateVisibleCount(Maybe<OnNonvisible> aNonvisibleAction
-                                       /* = Nothing() */)
+nsIFrame::DecVisibilityCount(VisibilityCounter aCounter,
+                             Maybe<OnNonvisible> aNonvisibleAction /* = Nothing() */)
 {
   MOZ_ASSERT(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED);
 
   bool isSet = false;
   FrameProperties props = Properties();
-  uint32_t visibleCount = props.Get(VisibilityStateProperty(), &isSet);
+  VisibilityState state = props.Get(VisibilityStateProperty(), &isSet);
 
   MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value "
                     "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
-  MOZ_ASSERT(visibleCount > 0, "Frame is already nonvisible and we're "
-                               "decrementing its visible count?");
-
-  visibleCount--;
-  props.Set(VisibilityStateProperty(), visibleCount);
-  if (visibleCount > 0) {
-    return;
-  }
-
-  // We just became nonvisible, so send an OnVisibilityChange() notification.
-  OnVisibilityChange(Visibility::APPROXIMATELY_NONVISIBLE, aNonvisibleAction);
+  MOZ_ASSERT_IF(aCounter == VisibilityCounter::MAY_BECOME_VISIBLE,
+                state.mApproximateCounter > 0);
+  MOZ_ASSERT_IF(aCounter == VisibilityCounter::IN_DISPLAYPORT,
+                state.mInDisplayPortCounter > 0);
+
+  Visibility previousVisibility = VisibilityStateAsVisibility(state);
+
+  if (aCounter == VisibilityCounter::MAY_BECOME_VISIBLE) {
+    state.mApproximateCounter--;
+  } else {
+    state.mInDisplayPortCounter--;
+  }
+
+  props.Set(VisibilityStateProperty(), state);
+
+  Visibility newVisibility = VisibilityStateAsVisibility(state);
+  if (newVisibility == previousVisibility) {
+    return;  // Nothing changed.
+  }
+
+  // Our visibility just changed, so send an OnVisibilityChange() notification.
+  OnVisibilityChange(newVisibility, aNonvisibleAction);
 }
 
 void
-nsIFrame::IncApproximateVisibleCount()
+nsIFrame::IncVisibilityCount(VisibilityCounter aCounter)
 {
   MOZ_ASSERT(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED);
 
   bool isSet = false;
   FrameProperties props = Properties();
-  uint32_t visibleCount = props.Get(VisibilityStateProperty(), &isSet);
+  VisibilityState state = props.Get(VisibilityStateProperty(), &isSet);
 
   MOZ_ASSERT(isSet, "Should have a VisibilityStateProperty value "
                     "if NS_FRAME_VISIBILITY_IS_TRACKED is set");
 
-  visibleCount++;
-  props.Set(VisibilityStateProperty(), visibleCount);
-  if (visibleCount > 1) {
-    return;
-  }
-
-  // We just became visible, so send an OnVisibilityChange() notification.
-  OnVisibilityChange(Visibility::APPROXIMATELY_VISIBLE);
+  Visibility previousVisibility = VisibilityStateAsVisibility(state);
+
+  if (aCounter == VisibilityCounter::MAY_BECOME_VISIBLE) {
+    state.mApproximateCounter++;
+  } else {
+    state.mInDisplayPortCounter++;
+  }
+
+  props.Set(VisibilityStateProperty(), state);
+
+  Visibility newVisibility = VisibilityStateAsVisibility(state);
+  if (newVisibility == previousVisibility) {
+    return;  // Nothing changed.
+  }
+
+  // Our visibility just changed, so send an OnVisibilityChange() notification.
+  OnVisibilityChange(newVisibility);
 }
 
 void
 nsIFrame::OnVisibilityChange(Visibility aNewVisibility,
                              Maybe<OnNonvisible> aNonvisibleAction
                                /* = Nothing() */)
 {
   // XXX(seth): In bug 1218990 we'll implement visibility tracking for CSS
@@ -9153,17 +9184,17 @@ nsFrame::BoxMetrics() const
 }
 
 /* static */ void
 nsIFrame::AddInPopupStateBitToDescendants(nsIFrame* aFrame)
 {
   if (!aFrame->HasAnyStateBits(NS_FRAME_IN_POPUP) &&
       aFrame->TrackingVisibility()) {
     // Assume all frames in popups are visible.
-    aFrame->IncApproximateVisibleCount();
+    aFrame->IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
   }
 
   aFrame->AddStateBits(NS_FRAME_IN_POPUP);
 
   AutoTArray<nsIFrame::ChildList,4> childListArray;
   aFrame->GetCrossDocChildLists(&childListArray);
 
   nsIFrame::ChildListArrayIterator lists(childListArray);
@@ -9183,17 +9214,17 @@ nsIFrame::RemoveInPopupStateBitFromDesce
     return;
   }
 
   aFrame->RemoveStateBits(NS_FRAME_IN_POPUP);
 
   if (aFrame->TrackingVisibility()) {
     // We assume all frames in popups are visible, so this decrement balances
     // out the increment in AddInPopupStateBitToDescendants above.
-    aFrame->DecApproximateVisibleCount();
+    aFrame->DecVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
   }
 
   AutoTArray<nsIFrame::ChildList,4> childListArray;
   aFrame->GetCrossDocChildLists(&childListArray);
 
   nsIFrame::ChildListArrayIterator lists(childListArray);
   for (; !lists.IsDone(); lists.Next()) {
     nsFrameList::Enumerator childFrames(lists.CurrentList());
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -419,16 +419,17 @@ class nsIFrame : public nsQueryFrame
 {
 public:
   template <typename T> using Maybe = mozilla::Maybe<T>;
   using Nothing = mozilla::Nothing;
   using OnNonvisible = mozilla::OnNonvisible;
   template<typename T=void>
   using PropertyDescriptor = const mozilla::FramePropertyDescriptor<T>*;
   using Visibility = mozilla::Visibility;
+  using VisibilityCounter = mozilla::VisibilityCounter;
 
   typedef mozilla::FrameProperties FrameProperties;
   typedef mozilla::layers::Layer Layer;
   typedef mozilla::layout::FrameChildList ChildList;
   typedef mozilla::layout::FrameChildListID ChildListID;
   typedef mozilla::layout::FrameChildListIDs ChildListIDs;
   typedef mozilla::layout::FrameChildListIterator ChildListIterator;
   typedef mozilla::layout::FrameChildListArrayIterator ChildListArrayIterator;
@@ -1097,29 +1098,44 @@ public:
   {
     return bool(GetStateBits() & NS_FRAME_VISIBILITY_IS_TRACKED);
   }
 
   /// @return the visibility state of this frame. See the Visibility enum
   /// for the possible return values and their meanings.
   Visibility GetVisibility() const;
 
+  /// @return true if this frame is either in the displayport now or may
+  /// become visible soon.
+  bool IsVisibleOrMayBecomeVisibleSoon() const
+  {
+    Visibility visibility = GetVisibility();
+    return visibility == Visibility::MAY_BECOME_VISIBLE ||
+           visibility == Visibility::IN_DISPLAYPORT;
+  }
+
   /// Update the visibility state of this frame synchronously.
   /// XXX(seth): Avoid using this method; we should be relying on the refresh
   /// driver for visibility updates. This method, which replaces
   /// nsLayoutUtils::UpdateApproximateFrameVisibility(), exists purely as a
   /// temporary measure to avoid changing behavior during the transition from
   /// the old image visibility code.
   void UpdateVisibilitySynchronously();
 
-  // A frame property which stores the visibility state of this frame. Right
-  // now that consists of an approximate visibility counter represented as a
-  // uint32_t. When the visibility of this frame is not being tracked, this
-  // property is absent.
-  NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(VisibilityStateProperty, uint32_t);
+  struct VisibilityState
+  {
+    unsigned int mApproximateCounter : 16;
+    unsigned int mInDisplayPortCounter : 16;
+  };
+
+  // A frame property which stores the visibility state of this frame, which
+  // consists of a VisibilityState value that stores counters for each type of
+  // visibility we track. When the visibility of this frame is not being
+  // tracked, this property is absent.
+  NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(VisibilityStateProperty, VisibilityState);
 
 protected:
 
   /**
    * Subclasses can call this method to enable visibility tracking for this frame.
    *
    * If visibility tracking was previously disabled, this will schedule an
    * update an asynchronous update of visibility.
@@ -1154,31 +1170,39 @@ protected:
 
 public:
 
   ///////////////////////////////////////////////////////////////////////////////
   // Internal implementation for the approximate frame visibility API.
   ///////////////////////////////////////////////////////////////////////////////
 
   /**
-   * We track the approximate visibility of frames using a counter; if it's
-   * non-zero, then the frame is considered visible. Using a counter allows us
-   * to account for situations where the frame may be visible in more than one
-   * place (for example, via -moz-element), and it simplifies the
+   * We track the visibility of frames using counters; if any of the counters
+   * are non-zero, then the frame is considered visible. Using counters allows
+   * us to account for situations where the frame may be visible in more than
+   * one place (for example, via -moz-element), and it simplifies the
    * implementation of our approximate visibility tracking algorithms.
    *
+   * There are two visibility counters for each frame: the approximate counter
+   * (which is based on our heuristics for which frames may become visible
+   * "soon"), and the in-displayport counter (which records if the frame was
+   * within the displayport at the last paint).
+   *
+   *
+   * @param aCounter          Which counter to increment or decrement.
    * @param aNonvisibleAction A requested action if the frame has become
    *                          nonvisible. If Nothing(), no action is
    *                          requested. If DISCARD_IMAGES is specified, the
    *                          frame is requested to ask any images it's
    *                          associated with to discard their surfaces if
    *                          possible.
    */
-  void DecApproximateVisibleCount(Maybe<OnNonvisible> aNonvisibleAction = Nothing());
-  void IncApproximateVisibleCount();
+  void DecVisibilityCount(VisibilityCounter aCounter,
+                          Maybe<OnNonvisible> aNonvisibleAction = Nothing());
+  void IncVisibilityCount(VisibilityCounter aCounter);
 
 
   /**
    * Get the specified child list.
    *
    * @param   aListID identifies the requested child list.
    * @return  the child list.  If the requested list is unsupported by this
    *          frame type, an empty list will be returned.
--- a/layout/generic/nsImageFrame.cpp
+++ b/layout/generic/nsImageFrame.cpp
@@ -709,17 +709,17 @@ nsImageFrame::MaybeDecodeForPredictedSiz
   if (!mImage) {
     return;  // Nothing to do yet.
   }
 
   if (mComputedSize.IsEmpty()) {
     return;  // We won't draw anything, so no point in decoding.
   }
 
-  if (GetVisibility() != Visibility::APPROXIMATELY_VISIBLE) {
+  if (!IsVisibleOrMayBecomeVisibleSoon()) {
     return;  // We're not visible, so don't decode.
   }
 
   // OK, we're ready to decode. Compute the scale to the screen...
   nsIPresShell* presShell = PresContext()->GetPresShell();
   LayoutDeviceToScreenScale2D resolutionToScreen(
       presShell->GetCumulativeResolution()
     * nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(this));
@@ -2058,17 +2058,18 @@ nsImageFrame::OnVisibilityChange(Visibil
   if (!imageLoader) {
     MOZ_ASSERT_UNREACHABLE("Should have an nsIImageLoadingContent");
     nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
     return;
   }
 
   imageLoader->OnVisibilityChange(aNewVisibility, aNonvisibleAction);
 
-  if (aNewVisibility == Visibility::APPROXIMATELY_VISIBLE) {
+  if (aNewVisibility == Visibility::MAY_BECOME_VISIBLE ||
+      aNewVisibility == Visibility::IN_DISPLAYPORT) {
     MaybeDecodeForPredictedSize();
   }
 
   nsAtomicContainerFrame::OnVisibilityChange(aNewVisibility, aNonvisibleAction);
 }
 
 nsIAtom*
 nsImageFrame::GetType() const
--- a/layout/svg/SVGFEImageFrame.cpp
+++ b/layout/svg/SVGFEImageFrame.cpp
@@ -79,17 +79,17 @@ NS_NewSVGFEImageFrame(nsIPresShell* aPre
   return new (aPresShell) SVGFEImageFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(SVGFEImageFrame)
 
 /* virtual */ void
 SVGFEImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
-  DecApproximateVisibleCount();
+  DecVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
 
   nsCOMPtr<nsIImageLoadingContent> imageLoader =
     do_QueryInterface(nsFrame::mContent);
   if (imageLoader) {
     imageLoader->FrameDestroyed(this);
   }
 
   nsFrame::DestroyFrom(aDestructRoot);
@@ -102,17 +102,17 @@ SVGFEImageFrame::Init(nsIContent*       
 {
   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::feImage),
                "Trying to construct an SVGFEImageFrame for a "
                "content element that doesn't support the right interfaces");
 
   nsFrame::Init(aContent, aParent, aPrevInFlow);
 
   // We assume that feImage's are always visible.
-  IncApproximateVisibleCount();
+  IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
 
   nsCOMPtr<nsIImageLoadingContent> imageLoader =
     do_QueryInterface(nsFrame::mContent);
   if (imageLoader) {
     imageLoader->FrameCreated(this);
   }
 }
 
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -153,17 +153,17 @@ nsSVGImageFrame::Init(nsIContent*       
   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
                "Content is not an SVG image!");
 
   nsSVGPathGeometryFrame::Init(aContent, aParent, aPrevInFlow);
 
   if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
     // Non-display frames are likely to be patterns, masks or the like.
     // Treat them as always visible.
-    IncApproximateVisibleCount();
+    IncVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
   }
 
   mListener = new nsSVGImageListener(this);
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
   if (!imageLoader) {
     NS_RUNTIMEABORT("Why is this not an image loading content?");
   }
 
@@ -173,17 +173,17 @@ nsSVGImageFrame::Init(nsIContent*       
 
   imageLoader->AddObserver(mListener);
 }
 
 /* virtual */ void
 nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
-    DecApproximateVisibleCount();
+    DecVisibilityCount(VisibilityCounter::IN_DISPLAYPORT);
   }
 
   if (mReflowCallbackPosted) {
     PresContext()->PresShell()->CancelReflowCallback(this);
     mReflowCallbackPosted = false;
   }
 
   nsCOMPtr<nsIImageLoadingContent> imageLoader =