merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Sun, 04 Jun 2017 20:09:18 +0200
changeset 412688 8a3aa1701537ea6b8334f432cd030d260d492fa3
parent 412677 78450dd743b92638cdcf70b16c0850214782eff4 (current diff)
parent 412687 9d55c2ccf868c6f3e5c75b3bf94069c1795881a1 (diff)
child 412693 3a48edeff5afbad125b56683e95392c5b18d0936
child 412719 1002aa75bf5df4820646ddc76e39752ef2116d34
push id1490
push usermtabara@mozilla.com
push dateMon, 31 Jul 2017 14:08:16 +0000
treeherdermozilla-release@70e32e6bf15e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone55.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
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: 8W8H7Bg9Tq6
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -330,16 +330,20 @@ Accessible::VisibilityState()
   if (!frame)
     return states::INVISIBLE;
 
   // Walk the parent frame chain to see if there's invisible parent or the frame
   // is in background tab.
   if (!frame->StyleVisibility()->IsVisible())
     return states::INVISIBLE;
 
+  // Offscreen state if the document's visibility state is not visible.
+  if (Document()->IsHidden())
+    return states::OFFSCREEN;
+
   nsIFrame* curFrame = frame;
   do {
     nsView* view = curFrame->GetView();
     if (view && view->GetVisibility() == nsViewVisibility_kHide)
       return states::INVISIBLE;
 
     if (nsLayoutUtils::IsPopup(curFrame))
       return 0;
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -132,16 +132,21 @@ public:
   {
     // eDOMLoaded flag check is used for error pages as workaround to make this
     // method return correct result since error pages do not receive 'pageshow'
     // event and as consequence nsIDocument::IsShowing() returns false.
     return mDocumentNode && mDocumentNode->IsVisible() &&
       (mDocumentNode->IsShowing() || HasLoadState(eDOMLoaded));
   }
 
+  bool IsHidden() const
+  {
+    return mDocumentNode->Hidden();
+  }
+
   /**
    * Document load states.
    */
   enum LoadState {
     // initial tree construction is pending
     eTreeConstructionPending = 0,
     // initial tree construction done
     eTreeConstructed = 1,
--- a/accessible/tests/browser/head.js
+++ b/accessible/tests/browser/head.js
@@ -128,14 +128,14 @@ function waitForEvent(eventType, expecte
   });
 }
 
 /**
  * Force garbage collection.
  */
 function forceGC() {
   SpecialPowers.gc();
-  SpecialPowers.forceGC();
+  SpecialPowers.forceShrinkingGC();
   SpecialPowers.forceCC();
   SpecialPowers.gc();
-  SpecialPowers.forceGC();
+  SpecialPowers.forceShrinkingGC();
   SpecialPowers.forceCC();
 }
--- a/accessible/tests/browser/states/browser.ini
+++ b/accessible/tests/browser/states/browser.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 support-files =
   head.js
   !/accessible/tests/browser/events.js
   !/accessible/tests/browser/shared-head.js
   !/accessible/tests/mochitest/*.js
 
-[browser_test_link.js]
\ No newline at end of file
+[browser_test_link.js]
+[browser_test_visibility.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/accessible/tests/browser/states/browser_test_visibility.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+'use strict';
+
+/* import-globals-from ../../mochitest/role.js */
+/* import-globals-from ../../mochitest/states.js */
+loadScripts({ name: 'role.js', dir: MOCHITESTS_DIR },
+            { name: 'states.js', dir: MOCHITESTS_DIR });
+
+async function runTest(browser, accDoc) {
+  let getAcc = id => findAccessibleChildByID(accDoc, id);
+
+  testStates(getAcc("div"), 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
+
+  let input = getAcc("input_scrolledoff");
+  testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+  // scrolled off item (twice)
+  let lastLi = getAcc("li_last");
+  testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+  // scroll into view the item
+  await ContentTask.spawn(browser, {}, () => {
+    content.document.getElementById('li_last').scrollIntoView(true);
+  });
+  testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE);
+
+  // first item is scrolled off now (testcase for bug 768786)
+  let firstLi = getAcc("li_first");
+  testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+
+  let newTab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+  // Accessibles in background tab should have offscreen state and no
+  // invisible state.
+  testStates(getAcc("div"), STATE_OFFSCREEN, 0, STATE_INVISIBLE);
+  await BrowserTestUtils.removeTab(newTab);
+}
+
+addAccessibleTask(`
+  <div id="div" style="border:2px solid blue; width: 500px; height: 110vh;"></div>
+  <input id="input_scrolledoff">
+  <ul style="border:2px solid red; width: 100px; height: 50px; overflow: auto;">
+    <li id="li_first">item1</li><li>item2</li><li>item3</li>
+    <li>item4</li><li>item5</li><li id="li_last">item6</li>
+  </ul>`, runTest
+);
--- a/accessible/tests/mochitest/states/test_visibility.html
+++ b/accessible/tests/mochitest/states/test_visibility.html
@@ -9,139 +9,32 @@
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
-  <script type="application/javascript"
-          src="../events.js"></script>
-  <script type="application/javascript"
-          src="../browser.js"></script>
-
+  
   <script type="application/javascript">
-    ////////////////////////////////////////////////////////////////////////////
-    // Invokers
-
-    function loadURIInvoker(aURI, aFunc)
-    {
-      this.invoke = function loadURIInvoker_invoke()
-      {
-        tabBrowser().loadURI(aURI);
-      }
-
-      this.eventSeq = [
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, currentTabDocument)
-      ];
-
-      this.finalCheck = function loadURIInvoker_finalCheck()
-      {
-        aFunc.call();
-      }
-
-      this.getID = function loadURIInvoker_getID()
-      {
-        return "load uri " + aURI;
-      }
-    }
-
-    function addTabInvoker(aURL, aFunc)
-    {
-      this.eventSeq = [
-        new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, tabDocumentAt, 1)
-      ];
-
-      this.invoke = function addTabInvoker_invoke()
-      {
-        tabBrowser().loadOneTab(aURL, {
-          referrerURI: null,
-          charset: "",
-          postData: null,
-          inBackground: false,
-          triggeringPrincipal: Services.scriptSecurityManager.getSystemPrincipal(),
-        });
-      }
-
-      this.finalCheck = function addTabInvoker_finalCheck()
-      {
-        aFunc.call();
-      }
-
-      this.getID = function addTabInvoker_getID()
-      {
-        return "add tab: " + aURL;
-      }
-    }
-
-    ////////////////////////////////////////////////////////////////////////////
     // Tests
 
-    function testBackgroundTab()
-    {
-      // Accessibles in background tab should have offscreen state and no
-      // invisible state.
-      var tabDoc = tabDocumentAt(0);
-      var input = getAccessible(tabDoc.getElementById("input"));
-      testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
-    }
-
-    function testScrolledOff()
-    {
-      var tabDoc = tabDocumentAt(1);
-
-      // scrolled off
-      input = getAccessible(tabDoc.getElementById("input_scrolledoff"));
-      testStates(input, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
-
-      // scrolled off item (twice)
-      var lastLiNode = tabDoc.getElementById("li_last");
-      var lastLi = getAccessible(lastLiNode);
-      testStates(lastLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
-
-      // scroll into view the item
-      lastLiNode.scrollIntoView(true);
-      testStates(lastLi, 0, 0, STATE_OFFSCREEN | STATE_INVISIBLE);
-
-      // first item is scrolled off now (testcase for bug 768786)
-      var firstLi = getAccessible(tabDoc.getElementById("li_first"));
-      testStates(firstLi, STATE_OFFSCREEN, 0, STATE_INVISIBLE);
-    }
-
-    var gInputDocURI = "data:text/html,<html><body>";
-    gInputDocURI += "<input id='input'></body></html>";
-
-    var gDocURI = "data:text/html,<html><body>";
-    gDocURI += "<div style='border:2px solid blue; width: 500px; height: 600px;'></div>";
-    gDocURI += "<input id='input_scrolledoff'>";
-    gDocURI += "<ul style='border:2px solid red; width: 100px; height: 50px; overflow: auto;'>";
-    gDocURI += "  <li id='li_first'>item1</li><li>item2</li><li>item3</li>";
-    gDocURI += "  <li>item4</li><li>item5</li><li id='li_last'>item6</li>";
-    gDocURI += "</ul>";
-    gDocURI += "</body></html>";
-
     function doTests()
     {
       testStates("div", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
       testStates("div_off", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
       testStates("div_transformed", STATE_OFFSCREEN, 0, STATE_INVISIBLE);
       testStates("div_abschild", 0, 0, STATE_INVISIBLE | STATE_OFFSCREEN);
 
-      gQueue = new eventQueue();
-
-      gQueue.push(new addTabInvoker("about:blank", testBackgroundTab));
-      gQueue.push(new loadURIInvoker(gDocURI, testScrolledOff));
-
-      gQueue.onFinish = function() { closeBrowserWindow(); }
-      gQueue.invoke(); // Will call SimpleTest.finish();
+      SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
-    openBrowserWindow(doTests, gInputDocURI, { width: 600, height: 600 });
+    addA11yLoadEvent(doTests);
   </script>
 
 </head>
 
 <body>
 
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=591363"
--- a/js/src/jit-test/tests/self-test/inIon.js
+++ b/js/src/jit-test/tests/self-test/inIon.js
@@ -8,13 +8,13 @@ function callInIon() {
 function test() {
     // Test with OSR.
     while(!inIon());
 
     // Test with inlining.
     while(!callInIon());
 
     // Test with zealous gc preventing compilation.
-    while(!inIon()) gc();
+    while(!inIon()) gc(this, 'shrinking');
 };
 
 test();
 
--- a/js/src/jit/JitFrames.cpp
+++ b/js/src/jit/JitFrames.cpp
@@ -1527,29 +1527,18 @@ TraceJitActivation(JSTracer* trc, const 
 
 void
 TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc)
 {
     for (JitActivationIterator activations(cx, target); !activations.done(); ++activations)
         TraceJitActivation(trc, activations);
 }
 
-JSCompartment*
-TopmostIonActivationCompartment(JSContext* cx)
-{
-    for (JitActivationIterator activations(cx); !activations.done(); ++activations) {
-        for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
-            if (frames.type() == JitFrame_IonJS)
-                return activations.activation()->compartment();
-        }
-    }
-    return nullptr;
-}
-
-void UpdateJitActivationsForMinorGC(JSRuntime* rt, JSTracer* trc)
+void
+UpdateJitActivationsForMinorGC(JSRuntime* rt, JSTracer* trc)
 {
     MOZ_ASSERT(JS::CurrentThreadIsHeapMinorCollecting());
     JSContext* cx = TlsContext.get();
     for (const CooperatingContext& target : rt->cooperatingContexts()) {
         for (JitActivationIterator activations(cx, target); !activations.done(); ++activations) {
             for (JitFrameIterator frames(activations); !frames.done(); ++frames) {
                 if (frames.type() == JitFrame_IonJS)
                     UpdateIonJSFrameForMinorGC(trc, frames);
--- a/js/src/jit/JitFrames.h
+++ b/js/src/jit/JitFrames.h
@@ -282,19 +282,16 @@ struct ResumeFromException
 };
 
 void HandleException(ResumeFromException* rfe);
 
 void EnsureBareExitFrame(JSContext* cx, JitFrameLayout* frame);
 
 void TraceJitActivations(JSContext* cx, const CooperatingContext& target, JSTracer* trc);
 
-JSCompartment*
-TopmostIonActivationCompartment(JSContext* cx);
-
 void UpdateJitActivationsForMinorGC(JSRuntime* rt, JSTracer* trc);
 
 static inline uint32_t
 EncodeFrameHeaderSize(size_t headerSize)
 {
     MOZ_ASSERT((headerSize % sizeof(uintptr_t)) == 0);
 
     uint32_t headerSizeWords = headerSize / sizeof(uintptr_t);
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -3845,18 +3845,19 @@ GCRuntime::beginMarkPhase(JS::gcreason::
         c->marked = false;
         c->scheduledForDestruction = false;
         c->maybeAlive = c->hasBeenEntered() || !c->zone()->isGCScheduled();
         if (shouldPreserveJITCode(c, currentTime, reason, canAllocateMoreCode))
             c->zone()->setPreservingCode(true);
     }
 
     if (!rt->gc.cleanUpEverything && canAllocateMoreCode) {
-        if (JSCompartment* comp = jit::TopmostIonActivationCompartment(TlsContext.get()))
-            comp->zone()->setPreservingCode(true);
+        jit::JitActivationIterator activation(TlsContext.get());
+        if (!activation.done())
+            activation->compartment()->zone()->setPreservingCode(true);
     }
 
     /*
      * If keepAtoms() is true then either an instance of AutoKeepAtoms is
      * currently on the stack or parsing is currently happening on another
      * thread. In either case we don't have information about which atoms are
      * roots, so we must skip collecting atoms.
      *
--- a/js/xpconnect/tests/mochitest/test_nukeContentWindow.html
+++ b/js/xpconnect/tests/mochitest/test_nukeContentWindow.html
@@ -13,22 +13,37 @@ https://bugzilla.mozilla.org/show_bug.cg
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1322273">Mozilla Bug 1322273</a>
 
 <iframe id="subframe"></iframe>
 
 <script type="application/javascript">
 "use strict";
 
+function waitForWindowDestroyed(winID, callback) {
+  let observer = {
+    observe: function(subject, topic, data) {
+      let id = subject.QueryInterface(SpecialPowers.Ci.nsISupportsPRUint64).data;
+      if (id != winID) {
+        return;
+      }
+      SpecialPowers.removeObserver(observer, "outer-window-destroyed");
+      SpecialPowers.executeSoon(callback);
+    }
+  };
+  SpecialPowers.addObserver(observer, "outer-window-destroyed");
+}
+
 add_task(function* () {
   let frame = $('subframe');
   frame.src = "data:text/html,";
   yield new Promise(resolve => frame.addEventListener("load", resolve, {once: true}));
 
   let win = frame.contentWindow;
+  let winID = SpecialPowers.getDOMWindowUtils(win).outerWindowID;
 
   win.eval("obj = {}");
   win.obj.foo = {bar: "baz"};
 
   let obj = win.obj;
 
   let system = SpecialPowers.Services.scriptSecurityManager.getSystemPrincipal()
   let sandbox = SpecialPowers.Cu.Sandbox(system);
@@ -45,17 +60,17 @@ add_task(function* () {
   is(isWrapperDead(), false, "Sandbox wrapper for content window should not be dead");
   is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive");
 
   // Remove the frame, which should nuke the content window.
   info("Remove the content frame");
   frame.remove();
 
   // Give the nuke wrappers task a chance to run.
-  yield new Promise(SimpleTest.executeSoon);
+  yield new Promise(resolve => waitForWindowDestroyed(winID, resolve));
 
   is(isWrapperDead(), true, "Sandbox wrapper for content window should be dead");
   is(obj.foo.bar, "baz", "Content wrappers into and out of content window should be alive");
 });
 </script>
 
 </body>
 </html>
--- a/layout/base/FrameProperties.h
+++ b/layout/base/FrameProperties.h
@@ -156,16 +156,21 @@ public:
   }
 
   ~FrameProperties()
   {
     MOZ_ASSERT(mProperties.Length() == 0, "forgot to delete properties");
   }
 
   /**
+   * Return true if we have no properties, otherwise return false.
+   */
+  bool IsEmpty() const { return mProperties.IsEmpty(); }
+
+  /**
    * Set a property value. This requires a linear search through
    * the properties of the frame. Any existing value for the property
    * is destroyed.
    */
   template<typename T>
   void Set(Descriptor<T> aProperty, PropertyType<T> aValue,
            const nsIFrame* aFrame)
   {
@@ -256,16 +261,35 @@ public:
    */
   template<typename T>
   void Delete(Descriptor<T> aProperty, const nsIFrame* aFrame)
   {
     DeleteInternal(aProperty, aFrame);
   }
 
   /**
+   * Call @aFunction for each property or until @aFunction returns false.
+   */
+  template<class F>
+  void ForEach(F aFunction) const
+  {
+#ifdef DEBUG
+    size_t len = mProperties.Length();
+#endif
+    for (const auto& prop : mProperties) {
+      bool shouldContinue = aFunction(prop.mProperty, prop.mValue);
+      MOZ_ASSERT(len == mProperties.Length(),
+                 "frame property list was modified by ForEach callback!");
+      if (!shouldContinue) {
+        return;
+      }
+    }
+  }
+
+  /**
    * Remove and destroy all property values for the frame.
    */
   void DeleteAll(const nsIFrame* aFrame) {
     mozilla::DebugOnly<size_t> len = mProperties.Length();
     for (auto& prop : mProperties) {
       prop.DestroyValueFor(aFrame);
       MOZ_ASSERT(mProperties.Length() == len);
     }
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -243,34 +243,58 @@ nsContainerFrame::DestroyFrom(nsIFrame* 
     }
 
 #ifdef DEBUG
     // This is just so we can assert it's not set in nsFrame::DestroyFrom.
     RemoveStateBits(NS_FRAME_PART_OF_IBSPLIT);
 #endif
   }
 
-  // Destroy frames on the auxiliary frame lists and delete the lists.
-  nsPresContext* pc = PresContext();
-  nsIPresShell* shell = pc->PresShell();
-  SafelyDestroyFrameListProp(aDestructRoot, shell, OverflowProperty());
+  if (MOZ_UNLIKELY(!mProperties.IsEmpty())) {
+    using T = mozilla::FrameProperties::UntypedDescriptor;
+    bool hasO = false, hasOC = false, hasEOC = false, hasBackdrop = false;
+    mProperties.ForEach([&] (const T& aProp, void*) {
+      if (aProp == OverflowProperty()) {
+        hasO = true;
+      } else if (aProp == OverflowContainersProperty()) {
+        hasOC = true;
+      } else if (aProp == ExcessOverflowContainersProperty()) {
+        hasEOC = true;
+      } else if (aProp == BackdropProperty()) {
+        hasBackdrop = true;
+      }
+      return true;
+    });
 
-  MOZ_ASSERT(IsFrameOfType(nsIFrame::eCanContainOverflowContainers) ||
-             !(GetProperty(nsContainerFrame::OverflowContainersProperty()) ||
-               GetProperty(nsContainerFrame::ExcessOverflowContainersProperty())),
-             "this type of frame should't have overflow containers");
-  SafelyDestroyFrameListProp(aDestructRoot, shell,
-                             OverflowContainersProperty());
-  SafelyDestroyFrameListProp(aDestructRoot, shell,
-                             ExcessOverflowContainersProperty());
+    // Destroy frames on the auxiliary frame lists and delete the lists.
+    nsPresContext* pc = PresContext();
+    nsIPresShell* shell = pc->PresShell();
+    if (hasO) {
+      SafelyDestroyFrameListProp(aDestructRoot, shell, OverflowProperty());
+    }
 
-  MOZ_ASSERT(!GetProperty(BackdropProperty()) ||
-             StyleDisplay()->mTopLayer != NS_STYLE_TOP_LAYER_NONE,
-             "only top layer frame may have backdrop");
-  SafelyDestroyFrameListProp(aDestructRoot, shell, BackdropProperty());
+    MOZ_ASSERT(IsFrameOfType(eCanContainOverflowContainers) ||
+               !(hasOC || hasEOC),
+               "this type of frame shouldn't have overflow containers");
+    if (hasOC) {
+      SafelyDestroyFrameListProp(aDestructRoot, shell,
+                                 OverflowContainersProperty());
+    }
+    if (hasEOC) {
+      SafelyDestroyFrameListProp(aDestructRoot, shell,
+                                 ExcessOverflowContainersProperty());
+    }
+
+    MOZ_ASSERT(!GetProperty(BackdropProperty()) ||
+               StyleDisplay()->mTopLayer != NS_STYLE_TOP_LAYER_NONE,
+               "only top layer frame may have backdrop");
+    if (hasBackdrop) {
+      SafelyDestroyFrameListProp(aDestructRoot, shell, BackdropProperty());
+    }
+  }
 
   nsSplittableFrame::DestroyFrom(aDestructRoot);
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Child frame enumeration
 
 const nsFrameList&
@@ -298,46 +322,40 @@ nsContainerFrame::GetChildList(ChildList
       nsFrameList* list = GetPropTableFrames(BackdropProperty());
       return list ? *list : nsFrameList::EmptyList();
     }
     default:
       return nsSplittableFrame::GetChildList(aListID);
   }
 }
 
-static void
-AppendIfNonempty(const nsIFrame* aFrame,
-                 nsContainerFrame::FrameListPropertyDescriptor aProperty,
-                 nsTArray<nsIFrame::ChildList>* aLists,
-                 nsIFrame::ChildListID aListID)
-{
-  if (nsFrameList* list = aFrame->GetProperty(aProperty)) {
-    list->AppendIfNonempty(aLists, aListID);
-  }
-}
-
 void
 nsContainerFrame::GetChildLists(nsTArray<ChildList>* aLists) const
 {
   mFrames.AppendIfNonempty(aLists, kPrincipalList);
-  ::AppendIfNonempty(this, OverflowProperty(),
-                     aLists, kOverflowList);
-  if (IsFrameOfType(nsIFrame::eCanContainOverflowContainers)) {
-    ::AppendIfNonempty(this, OverflowContainersProperty(),
-                       aLists, kOverflowContainersList);
-    ::AppendIfNonempty(this, ExcessOverflowContainersProperty(),
-                       aLists, kExcessOverflowContainersList);
-  }
-  // Bypass BackdropProperty hashtable lookup for any in-flow frames
-  // since frames in the top layer (only which can have backdrop) are
-  // definitely out-of-flow.
-  if (GetStateBits() & NS_FRAME_OUT_OF_FLOW) {
-    ::AppendIfNonempty(this, BackdropProperty(),
-                       aLists, kBackdropList);
-  }
+
+  using T = mozilla::FrameProperties::UntypedDescriptor;
+  mProperties.ForEach([this, aLists] (const T& aProp, void* aValue) {
+    typedef const nsFrameList* L;
+    if (aProp == OverflowProperty()) {
+      L(aValue)->AppendIfNonempty(aLists, kOverflowList);
+    } else if (aProp == OverflowContainersProperty()) {
+      MOZ_ASSERT(IsFrameOfType(nsIFrame::eCanContainOverflowContainers),
+                 "found unexpected OverflowContainersProperty");
+      L(aValue)->AppendIfNonempty(aLists, kOverflowContainersList);
+    } else if (aProp == ExcessOverflowContainersProperty()) {
+      MOZ_ASSERT(IsFrameOfType(nsIFrame::eCanContainOverflowContainers),
+                 "found unexpected ExcessOverflowContainersProperty");
+      L(aValue)->AppendIfNonempty(aLists, kExcessOverflowContainersList);
+    } else if (aProp == BackdropProperty()) {
+      L(aValue)->AppendIfNonempty(aLists, kBackdropList);
+    }
+    return true;
+  });
+
   nsSplittableFrame::GetChildLists(aLists);
 }
 
 /////////////////////////////////////////////////////////////////////////////
 // Painting/Events
 
 void
 nsContainerFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
--- a/layout/tables/nsTableFrame.cpp
+++ b/layout/tables/nsTableFrame.cpp
@@ -1358,54 +1358,71 @@ nsTableFrame::GenericTraversal(nsDisplay
   }
 }
 
 static void
 PaintRowBackground(nsTableRowFrame* aRow,
                    nsIFrame* aFrame,
                    nsDisplayListBuilder* aBuilder,
                    const nsDisplayListSet& aLists,
+                   const nsRect& aDirtyRect,
                    const nsPoint& aOffset = nsPoint())
 {
   // Compute background rect by iterating all cell frame.
   for (nsTableCellFrame* cell = aRow->GetFirstCell(); cell; cell = cell->GetNextCell()) {
     auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + aOffset;
+    if (!aDirtyRect.Intersects(cellRect)) {
+      continue;
+    }
     nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
                                                          aLists.BorderBackground(),
                                                          true, nullptr,
                                                          aFrame->GetRectRelativeToSelf(),
                                                          cell);
   }
 }
 
 static void
 PaintRowGroupBackground(nsTableRowGroupFrame* aRowGroup,
                         nsIFrame* aFrame,
                         nsDisplayListBuilder* aBuilder,
-                        const nsDisplayListSet& aLists)
+                        const nsDisplayListSet& aLists,
+                        const nsRect& aDirtyRect)
 {
   for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) {
-    PaintRowBackground(row, aFrame, aBuilder, aLists, row->GetNormalPosition());
+    if (!aDirtyRect.Intersects(nsRect(row->GetNormalPosition(), row->GetSize()))) {
+      continue;
+    }
+    PaintRowBackground(row, aFrame, aBuilder, aLists, aDirtyRect, row->GetNormalPosition());
   }
 }
 
 static void
 PaintRowGroupBackgroundByColIdx(nsTableRowGroupFrame* aRowGroup,
                                 nsIFrame* aFrame,
                                 nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists,
+                                const nsRect& aDirtyRect,
                                 const nsTArray<int32_t>& aColIdx,
                                 const nsPoint& aOffset)
 {
   for (nsTableRowFrame* row = aRowGroup->GetFirstRow(); row; row = row->GetNextRow()) {
+    auto rowPos = row->GetNormalPosition() + aOffset;
+    if (!aDirtyRect.Intersects(nsRect(rowPos, row->GetSize()))) {
+      continue;
+    }
     for (nsTableCellFrame* cell = row->GetFirstCell(); cell; cell = cell->GetNextCell()) {
       int32_t curColIdx;
       cell->GetColIndex(curColIdx);
       if (aColIdx.Contains(curColIdx)) {
-        auto cellRect = cell->GetRectRelativeToSelf() + cell->GetNormalPosition() + row->GetNormalPosition() + aOffset;
+        auto cellPos = cell->GetNormalPosition() + rowPos;
+        auto cellRect = nsRect(cellPos, cell->GetSize());
+        if (!aDirtyRect.Intersects(cellRect)) {
+          continue;
+        }
         nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame, cellRect,
                                                              aLists.BorderBackground(),
                                                              true, nullptr,
                                                              aFrame->GetRectRelativeToSelf(),
                                                              cell);
       }
     }
   }
@@ -1430,50 +1447,56 @@ nsTableFrame::DisplayGenericTablePart(ns
     bool hasBoxShadow = aFrame->StyleEffects()->mBoxShadow != nullptr;
     if (hasBoxShadow) {
       aLists.BorderBackground()->AppendNewToTop(
         new (aBuilder) nsDisplayBoxShadowOuter(aBuilder, aFrame));
     }
 
     if (aFrame->IsTableRowGroupFrame()) {
       nsTableRowGroupFrame* rowGroup = static_cast<nsTableRowGroupFrame*>(aFrame);
-      PaintRowGroupBackground(rowGroup, aFrame, aBuilder, aLists);
+      PaintRowGroupBackground(rowGroup, aFrame, aBuilder, aLists, aDirtyRect);
     } else if (aFrame->IsTableRowFrame()) {
       nsTableRowFrame* row = static_cast<nsTableRowFrame*>(aFrame);
-      PaintRowBackground(row, aFrame, aBuilder, aLists);
+      PaintRowBackground(row, aFrame, aBuilder, aLists, aDirtyRect);
     } else if (aFrame->IsTableColGroupFrame()) {
       // Compute background rect by iterating all cell frame.
       nsTableColGroupFrame* colGroup = static_cast<nsTableColGroupFrame*>(aFrame);
       // Collecting column index.
       AutoTArray<int32_t, 1> colIdx;
       for (nsTableColFrame* col = colGroup->GetFirstColumn(); col; col = col->GetNextCol()) {
         colIdx.AppendElement(col->GetColIndex());
       }
 
       nsTableFrame* table = colGroup->GetTableFrame();
       RowGroupArray rowGroups;
       table->OrderRowGroups(rowGroups);
       for (nsTableRowGroupFrame* rowGroup : rowGroups) {
         auto offset = rowGroup->GetNormalPosition() - colGroup->GetNormalPosition();
-        PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset);
+        if (!aDirtyRect.Intersects(nsRect(offset, rowGroup->GetSize()))) {
+          continue;
+        }
+        PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, aDirtyRect, colIdx, offset);
       }
     } else if (aFrame->IsTableColFrame()) {
       // Compute background rect by iterating all cell frame.
       nsTableColFrame* col = static_cast<nsTableColFrame*>(aFrame);
       AutoTArray<int32_t, 1> colIdx;
       colIdx.AppendElement(col->GetColIndex());
 
       nsTableFrame* table = col->GetTableFrame();
       RowGroupArray rowGroups;
       table->OrderRowGroups(rowGroups);
       for (nsTableRowGroupFrame* rowGroup : rowGroups) {
         auto offset = rowGroup->GetNormalPosition() -
                       col->GetNormalPosition() -
                       col->GetTableColGroupFrame()->GetNormalPosition();
-        PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, colIdx, offset);
+        if (!aDirtyRect.Intersects(nsRect(offset, rowGroup->GetSize()))) {
+          continue;
+        }
+        PaintRowGroupBackgroundByColIdx(rowGroup, aFrame, aBuilder, aLists, aDirtyRect, colIdx, offset);
       }
     } else {
       nsDisplayBackgroundImage::AppendBackgroundItemsToTop(aBuilder, aFrame,
                                                            aFrame->GetRectRelativeToSelf(),
                                                            aLists.BorderBackground());
     }
 
     // Paint the inset box-shadows for the table frames
--- a/netwerk/test/unit/test_alt-data_overwrite.js
+++ b/netwerk/test/unit/test_alt-data_overwrite.js
@@ -85,46 +85,46 @@ function readServerContent(request, buff
 
     do_execute_soon(flushAndOpenAltChannel);
   });
 }
 
 function flushAndOpenAltChannel()
 {
   // We need to do a GC pass to ensure the cache entry has been freed.
-  gc();
+  Cu.forceShrinkingGC();
   Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver);
 }
 
 // needs to be rooted
 let cacheFlushObserver = { observe: function() {
   if (!cacheFlushObserver) {
     do_print("ignoring cacheFlushObserver\n");
     return;
   }
   cacheFlushObserver = null;
-  gc();
+  Cu.forceShrinkingGC();
   make_and_open_channel(URL, altContentType, readAltContent);
 }};
 
 function readAltContent(request, buffer, closure, fromCache)
 {
-  gc();
+  Cu.forceShrinkingGC();
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   do_check_eq(fromCache || servedNotModified, true);
   do_check_eq(cc.alternativeDataType, altContentType);
   do_check_eq(buffer, altContent);
 
   make_and_open_channel(URL, "dummy/null", readServerContent2);
 }
 
 function readServerContent2(request, buffer, closure, fromCache)
 {
-  gc();
+  Cu.forceShrinkingGC();
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   do_check_eq(fromCache || servedNotModified, true);
   do_check_eq(buffer, responseContent);
   do_check_eq(cc.alternativeDataType, "");
 
   do_execute_soon(() => {
     let os = cc.openAlternativeOutputStream(altContentType);
@@ -133,67 +133,67 @@ function readServerContent2(request, buf
 
     do_execute_soon(flushAndOpenAltChannel2);
   });
 }
 
 function flushAndOpenAltChannel2()
 {
   // We need to do a GC pass to ensure the cache entry has been freed.
-  gc();
+  Cu.forceShrinkingGC();
   Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver2);
 }
 
 // needs to be rooted
 let cacheFlushObserver2 = { observe: function() {
   if (!cacheFlushObserver2) {
     do_print("ignoring cacheFlushObserver2\n");
     return;
   }
   cacheFlushObserver2 = null;
-  gc();
+  Cu.forceShrinkingGC();
   make_and_open_channel(URL, altContentType, readAltContent2);
 }};
 
 function readAltContent2(request, buffer, closure, fromCache)
 {
-  gc();
+  Cu.forceShrinkingGC();
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
   do_check_eq(servedNotModified || fromCache, true);
   do_check_eq(cc.alternativeDataType, altContentType);
   do_check_eq(buffer, altContent);
 
   do_execute_soon(() => {
-    gc();
+    Cu.forceShrinkingGC();
     do_print("writing other content\n");
     let os = cc.openAlternativeOutputStream(altContentType2);
     os.write(altContent2, altContent2.length);
     os.close();
 
     do_execute_soon(flushAndOpenAltChannel3);
   });
 }
 
 function flushAndOpenAltChannel3()
 {
   // We need to do a GC pass to ensure the cache entry has been freed.
-  gc();
+  Cu.forceShrinkingGC();
   Services.cache2.QueryInterface(Ci.nsICacheTesting).flush(cacheFlushObserver3);
 }
 
 // needs to be rooted
 let cacheFlushObserver3 = { observe: function() {
   if (!cacheFlushObserver3) {
     do_print("ignoring cacheFlushObserver3\n");
     return;
   }
 
   cacheFlushObserver3 = null;
-  gc();
+  Cu.forceShrinkingGC();
   make_and_open_channel(URL, altContentType2, readAltContent3);
 }};
 
 
 function readAltContent3(request, buffer, closure, fromCache)
 {
   let cc = request.QueryInterface(Ci.nsICacheInfoChannel);
 
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -1482,16 +1482,20 @@ SpecialPowersAPI.prototype = {
   gc() {
     this.DOMWindowUtils.garbageCollect();
   },
 
   forceGC() {
     Cu.forceGC();
   },
 
+  forceShrinkingGC() {
+    Cu.forceShrinkingGC();
+  },
+
   forceCC() {
     Cu.forceCC();
   },
 
   finishCC() {
     Cu.finishCC();
   },