Bug 1297899 - Part 9: Move RestyleFor{InsertOrChange,Append,EmptyChange} and ContentRemoved up to RestyleManager. r?bholley draft
authorCameron McCormack <cam@mcc.id.au>
Mon, 13 Feb 2017 11:21:33 +0800
changeset 482523 178857e6a7d7b2f89fb53d10487e79c1c0f8e37d
parent 482522 1fc5541d2b2d69c602bbf11996ddfde30a32d8c0
child 482524 02efcb4c38a0370512b27e62a20b857f4de4ae55
push id45090
push userbmo:cam@mcc.id.au
push dateMon, 13 Feb 2017 03:22:33 +0000
reviewersbholley
bugs1297899
milestone54.0a1
Bug 1297899 - Part 9: Move RestyleFor{InsertOrChange,Append,EmptyChange} and ContentRemoved up to RestyleManager. r?bholley MozReview-Commit-ID: 4iK7oG20awJ
layout/base/GeckoRestyleManager.cpp
layout/base/GeckoRestyleManager.h
layout/base/RestyleManager.cpp
layout/base/RestyleManager.h
layout/base/RestyleManagerInlines.h
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -337,299 +337,16 @@ GeckoRestyleManager::AttributeChanged(El
 /* static */ uint64_t
 GeckoRestyleManager::GetAnimationGenerationForFrame(nsIFrame* aFrame)
 {
   EffectSet* effectSet = EffectSet::GetEffectSet(aFrame);
   return effectSet ? effectSet->GetAnimationGeneration() : 0;
 }
 
 void
-GeckoRestyleManager::RestyleForEmptyChange(Element* aContainer)
-{
-  // In some cases (:empty + E, :empty ~ E), a change in the content of
-  // an element requires restyling its parent's siblings.
-  nsRestyleHint hint = eRestyle_Subtree;
-  nsIContent* grandparent = aContainer->GetParent();
-  if (grandparent &&
-      (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
-    hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
-  }
-  PostRestyleEvent(aContainer, hint, nsChangeHint(0));
-}
-
-void
-GeckoRestyleManager::RestyleForAppend(nsIContent* aContainer,
-                                      nsIContent* aFirstNewContent)
-{
-  // The container cannot be a document, but might be a ShadowRoot.
-  if (!aContainer->IsElement()) {
-    return;
-  }
-  Element* container = aContainer->AsElement();
-
-#ifdef DEBUG
-  {
-    for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
-      NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(),
-                   "anonymous nodes should not be in child lists");
-    }
-  }
-#endif
-  uint32_t selectorFlags =
-    container->GetFlags() & (NODE_ALL_SELECTOR_FLAGS &
-                             ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
-  if (selectorFlags == 0)
-    return;
-
-  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
-    // see whether we need to restyle the container
-    bool wasEmpty = true; // :empty or :-moz-only-whitespace
-    for (nsIContent* cur = container->GetFirstChild();
-         cur != aFirstNewContent;
-         cur = cur->GetNextSibling()) {
-      // We don't know whether we're testing :empty or :-moz-only-whitespace,
-      // so be conservative and assume :-moz-only-whitespace (i.e., make
-      // IsSignificantChild less likely to be true, and thus make us more
-      // likely to restyle).
-      if (nsStyleUtil::IsSignificantChild(cur, true, false)) {
-        wasEmpty = false;
-        break;
-      }
-    }
-    if (wasEmpty) {
-      RestyleForEmptyChange(container);
-      return;
-    }
-  }
-
-  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
-    // Restyling the container is the most we can do here, so we're done.
-    return;
-  }
-
-  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
-    // restyle the last element child before this node
-    for (nsIContent* cur = aFirstNewContent->GetPreviousSibling();
-         cur;
-         cur = cur->GetPreviousSibling()) {
-      if (cur->IsElement()) {
-        PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
-        break;
-      }
-    }
-  }
-}
-
-// Needed since we can't use PostRestyleEvent on non-elements (with
-// eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
-// eRestyle_LaterSiblings) as appropriate).
-static void
-RestyleSiblingsStartingWith(GeckoRestyleManager* aRestyleManager,
-                            nsIContent* aStartingSibling /* may be null */)
-{
-  for (nsIContent* sibling = aStartingSibling; sibling;
-       sibling = sibling->GetNextSibling()) {
-    if (sibling->IsElement()) {
-      aRestyleManager->
-        PostRestyleEvent(sibling->AsElement(),
-                         nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
-                         nsChangeHint(0));
-      break;
-    }
-  }
-}
-
-// Restyling for a ContentInserted or CharacterDataChanged notification.
-// This could be used for ContentRemoved as well if we got the
-// notification before the removal happened (and sometimes
-// CharacterDataChanged is more like a removal than an addition).
-// The comments are written and variables are named in terms of it being
-// a ContentInserted notification.
-void
-GeckoRestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
-                                              nsIContent* aChild)
-{
-  // The container might be a document or a ShadowRoot.
-  if (!aContainer->IsElement()) {
-    return;
-  }
-  Element* container = aContainer->AsElement();
-
-  NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(),
-               "anonymous nodes should not be in child lists");
-  uint32_t selectorFlags =
-    container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
-  if (selectorFlags == 0)
-    return;
-
-  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
-    // see whether we need to restyle the container
-    bool wasEmpty = true; // :empty or :-moz-only-whitespace
-    for (nsIContent* child = container->GetFirstChild();
-         child;
-         child = child->GetNextSibling()) {
-      if (child == aChild)
-        continue;
-      // We don't know whether we're testing :empty or :-moz-only-whitespace,
-      // so be conservative and assume :-moz-only-whitespace (i.e., make
-      // IsSignificantChild less likely to be true, and thus make us more
-      // likely to restyle).
-      if (nsStyleUtil::IsSignificantChild(child, true, false)) {
-        wasEmpty = false;
-        break;
-      }
-    }
-    if (wasEmpty) {
-      RestyleForEmptyChange(container);
-      return;
-    }
-  }
-
-  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
-    // Restyling the container is the most we can do here, so we're done.
-    return;
-  }
-
-  if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
-    // Restyle all later siblings.
-    RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
-  }
-
-  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
-    // restyle the previously-first element child if it is after this node
-    bool passedChild = false;
-    for (nsIContent* content = container->GetFirstChild();
-         content;
-         content = content->GetNextSibling()) {
-      if (content == aChild) {
-        passedChild = true;
-        continue;
-      }
-      if (content->IsElement()) {
-        if (passedChild) {
-          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           nsChangeHint(0));
-        }
-        break;
-      }
-    }
-    // restyle the previously-last element child if it is before this node
-    passedChild = false;
-    for (nsIContent* content = container->GetLastChild();
-         content;
-         content = content->GetPreviousSibling()) {
-      if (content == aChild) {
-        passedChild = true;
-        continue;
-      }
-      if (content->IsElement()) {
-        if (passedChild) {
-          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           nsChangeHint(0));
-        }
-        break;
-      }
-    }
-  }
-}
-
-void
-GeckoRestyleManager::ContentRemoved(nsINode* aContainer,
-                                    nsIContent* aOldChild,
-                                    nsIContent* aFollowingSibling)
-{
-  // The container might be a document or a ShadowRoot.
-  if (!aContainer->IsElement()) {
-    return;
-  }
-  Element* container = aContainer->AsElement();
-
-  if (aOldChild->IsRootOfAnonymousSubtree()) {
-    // This should be an assert, but this is called incorrectly in
-    // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
-    // up the logs.  Make it an assert again when that's fixed.
-    MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
-               "anonymous nodes should not be in child lists (bug 439258)");
-  }
-  uint32_t selectorFlags =
-    container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
-  if (selectorFlags == 0)
-    return;
-
-  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
-    // see whether we need to restyle the container
-    bool isEmpty = true; // :empty or :-moz-only-whitespace
-    for (nsIContent* child = container->GetFirstChild();
-         child;
-         child = child->GetNextSibling()) {
-      // We don't know whether we're testing :empty or :-moz-only-whitespace,
-      // so be conservative and assume :-moz-only-whitespace (i.e., make
-      // IsSignificantChild less likely to be true, and thus make us more
-      // likely to restyle).
-      if (nsStyleUtil::IsSignificantChild(child, true, false)) {
-        isEmpty = false;
-        break;
-      }
-    }
-    if (isEmpty) {
-      RestyleForEmptyChange(container);
-      return;
-    }
-  }
-
-  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
-    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
-    // Restyling the container is the most we can do here, so we're done.
-    return;
-  }
-
-  if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
-    // Restyle all later siblings.
-    RestyleSiblingsStartingWith(this, aFollowingSibling);
-  }
-
-  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
-    // restyle the now-first element child if it was after aOldChild
-    bool reachedFollowingSibling = false;
-    for (nsIContent* content = container->GetFirstChild();
-         content;
-         content = content->GetNextSibling()) {
-      if (content == aFollowingSibling) {
-        reachedFollowingSibling = true;
-        // do NOT continue here; we might want to restyle this node
-      }
-      if (content->IsElement()) {
-        if (reachedFollowingSibling) {
-          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
-                           nsChangeHint(0));
-        }
-        break;
-      }
-    }
-    // restyle the now-last element child if it was before aOldChild
-    reachedFollowingSibling = (aFollowingSibling == nullptr);
-    for (nsIContent* content = container->GetLastChild();
-         content;
-         content = content->GetPreviousSibling()) {
-      if (content->IsElement()) {
-        if (reachedFollowingSibling) {
-          PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0));
-        }
-        break;
-      }
-      if (content == aFollowingSibling) {
-        reachedFollowingSibling = true;
-      }
-    }
-  }
-}
-
-void
 GeckoRestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
                                          nsRestyleHint aRestyleHint)
 {
   NS_ASSERTION(!(aExtraHint & nsChangeHint_ReconstructFrame),
                "Should not reconstruct the root of the frame tree.  "
                "Use ReconstructDocElementHierarchy instead.");
   MOZ_ASSERT(!(aRestyleHint & ~(eRestyle_Subtree | eRestyle_ForceDescendants)),
              "the only bits allowed in aRestyleHint are eRestyle_Subtree and "
--- a/layout/base/GeckoRestyleManager.h
+++ b/layout/base/GeckoRestyleManager.h
@@ -211,40 +211,17 @@ public:
    * For the pseudo-elements, aContent must be the anonymous content
    * that we're creating for that pseudo-element, not the real element.
    */
   static bool
   TryInitiatingTransition(nsPresContext* aPresContext, nsIContent* aContent,
                           nsStyleContext* aOldStyleContext,
                           RefPtr<nsStyleContext>* aNewStyleContext /* inout */);
 
-private:
-  void RestyleForEmptyChange(Element* aContainer);
-
 public:
-  // Handle ContentRemoved notifications.
-  //
-  // This would be have the same logic as RestyleForInsertOrChange if we got the
-  // notification before the removal.  However, we get it after, so we need the
-  // following sibling in addition to the old child.  |aContainer| must be
-  // non-null; when the container is null, no work is needed.  aFollowingSibling
-  // is the sibling that used to come after aOldChild before the removal.
-  void ContentRemoved(nsINode* aContainer, nsIContent* aOldChild,
-                      nsIContent* aFollowingSibling);
-
-  // Restyling for a ContentInserted (notification after insertion) or
-  // for a CharacterDataChanged.  |aContainer| must be non-null; when
-  // the container is null, no work is needed.
-  void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild);
-
-  // Restyling for a ContentAppended (notification after insertion) or
-  // for a CharacterDataChanged.  |aContainer| must be non-null; when
-  // the container is null, no work is needed.
-  void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent);
-
   // Process any pending restyles. This should be called after
   // CreateNeededFrames.
   // Note: It's the caller's responsibility to make sure to wrap a
   // ProcessPendingRestyles call in a view update batch and a script blocker.
   // This function does not call ProcessAttachedQueue() on the binding manager.
   // If the caller wants that to happen synchronously, it needs to handle that
   // itself.
   void ProcessPendingRestyles();
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -31,16 +31,299 @@ RestyleManager::ContentInserted(nsINode*
 }
 
 void
 RestyleManager::ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent)
 {
   RestyleForAppend(aContainer, aFirstNewContent);
 }
 
+void
+RestyleManager::RestyleForEmptyChange(Element* aContainer)
+{
+  // In some cases (:empty + E, :empty ~ E), a change in the content of
+  // an element requires restyling its parent's siblings.
+  nsRestyleHint hint = eRestyle_Subtree;
+  nsIContent* grandparent = aContainer->GetParent();
+  if (grandparent &&
+      (grandparent->GetFlags() & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS)) {
+    hint = nsRestyleHint(hint | eRestyle_LaterSiblings);
+  }
+  PostRestyleEvent(aContainer, hint, nsChangeHint(0));
+}
+
+void
+RestyleManager::RestyleForAppend(nsIContent* aContainer,
+                                 nsIContent* aFirstNewContent)
+{
+  // The container cannot be a document, but might be a ShadowRoot.
+  if (!aContainer->IsElement()) {
+    return;
+  }
+  Element* container = aContainer->AsElement();
+
+#ifdef DEBUG
+  {
+    for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+      NS_ASSERTION(!cur->IsRootOfAnonymousSubtree(),
+                   "anonymous nodes should not be in child lists");
+    }
+  }
+#endif
+  uint32_t selectorFlags =
+    container->GetFlags() & (NODE_ALL_SELECTOR_FLAGS &
+                             ~NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS);
+  if (selectorFlags == 0)
+    return;
+
+  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+    // see whether we need to restyle the container
+    bool wasEmpty = true; // :empty or :-moz-only-whitespace
+    for (nsIContent* cur = container->GetFirstChild();
+         cur != aFirstNewContent;
+         cur = cur->GetNextSibling()) {
+      // We don't know whether we're testing :empty or :-moz-only-whitespace,
+      // so be conservative and assume :-moz-only-whitespace (i.e., make
+      // IsSignificantChild less likely to be true, and thus make us more
+      // likely to restyle).
+      if (nsStyleUtil::IsSignificantChild(cur, true, false)) {
+        wasEmpty = false;
+        break;
+      }
+    }
+    if (wasEmpty) {
+      RestyleForEmptyChange(container);
+      return;
+    }
+  }
+
+  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+    // Restyling the container is the most we can do here, so we're done.
+    return;
+  }
+
+  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+    // restyle the last element child before this node
+    for (nsIContent* cur = aFirstNewContent->GetPreviousSibling();
+         cur;
+         cur = cur->GetPreviousSibling()) {
+      if (cur->IsElement()) {
+        PostRestyleEvent(cur->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+        break;
+      }
+    }
+  }
+}
+
+// Needed since we can't use PostRestyleEvent on non-elements (with
+// eRestyle_LaterSiblings or nsRestyleHint(eRestyle_Subtree |
+// eRestyle_LaterSiblings) as appropriate).
+static void
+RestyleSiblingsStartingWith(RestyleManager* aRestyleManager,
+                            nsIContent* aStartingSibling /* may be null */)
+{
+  for (nsIContent* sibling = aStartingSibling; sibling;
+       sibling = sibling->GetNextSibling()) {
+    if (sibling->IsElement()) {
+      aRestyleManager->
+        PostRestyleEvent(sibling->AsElement(),
+                         nsRestyleHint(eRestyle_Subtree | eRestyle_LaterSiblings),
+                         nsChangeHint(0));
+      break;
+    }
+  }
+}
+
+// Restyling for a ContentInserted or CharacterDataChanged notification.
+// This could be used for ContentRemoved as well if we got the
+// notification before the removal happened (and sometimes
+// CharacterDataChanged is more like a removal than an addition).
+// The comments are written and variables are named in terms of it being
+// a ContentInserted notification.
+void
+RestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
+                                         nsIContent* aChild)
+{
+  // The container might be a document or a ShadowRoot.
+  if (!aContainer->IsElement()) {
+    return;
+  }
+  Element* container = aContainer->AsElement();
+
+  NS_ASSERTION(!aChild->IsRootOfAnonymousSubtree(),
+               "anonymous nodes should not be in child lists");
+  uint32_t selectorFlags =
+    container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+  if (selectorFlags == 0)
+    return;
+
+  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+    // see whether we need to restyle the container
+    bool wasEmpty = true; // :empty or :-moz-only-whitespace
+    for (nsIContent* child = container->GetFirstChild();
+         child;
+         child = child->GetNextSibling()) {
+      if (child == aChild)
+        continue;
+      // We don't know whether we're testing :empty or :-moz-only-whitespace,
+      // so be conservative and assume :-moz-only-whitespace (i.e., make
+      // IsSignificantChild less likely to be true, and thus make us more
+      // likely to restyle).
+      if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+        wasEmpty = false;
+        break;
+      }
+    }
+    if (wasEmpty) {
+      RestyleForEmptyChange(container);
+      return;
+    }
+  }
+
+  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+    // Restyling the container is the most we can do here, so we're done.
+    return;
+  }
+
+  if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+    // Restyle all later siblings.
+    RestyleSiblingsStartingWith(this, aChild->GetNextSibling());
+  }
+
+  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+    // restyle the previously-first element child if it is after this node
+    bool passedChild = false;
+    for (nsIContent* content = container->GetFirstChild();
+         content;
+         content = content->GetNextSibling()) {
+      if (content == aChild) {
+        passedChild = true;
+        continue;
+      }
+      if (content->IsElement()) {
+        if (passedChild) {
+          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+                           nsChangeHint(0));
+        }
+        break;
+      }
+    }
+    // restyle the previously-last element child if it is before this node
+    passedChild = false;
+    for (nsIContent* content = container->GetLastChild();
+         content;
+         content = content->GetPreviousSibling()) {
+      if (content == aChild) {
+        passedChild = true;
+        continue;
+      }
+      if (content->IsElement()) {
+        if (passedChild) {
+          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+                           nsChangeHint(0));
+        }
+        break;
+      }
+    }
+  }
+}
+
+void
+RestyleManager::ContentRemoved(nsINode* aContainer,
+                               nsIContent* aOldChild,
+                               nsIContent* aFollowingSibling)
+{
+  // The container might be a document or a ShadowRoot.
+  if (!aContainer->IsElement()) {
+    return;
+  }
+  Element* container = aContainer->AsElement();
+
+  if (aOldChild->IsRootOfAnonymousSubtree()) {
+    // This should be an assert, but this is called incorrectly in
+    // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
+    // up the logs.  Make it an assert again when that's fixed.
+    MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+               "anonymous nodes should not be in child lists (bug 439258)");
+  }
+  uint32_t selectorFlags =
+    container ? (container->GetFlags() & NODE_ALL_SELECTOR_FLAGS) : 0;
+  if (selectorFlags == 0)
+    return;
+
+  if (selectorFlags & NODE_HAS_EMPTY_SELECTOR) {
+    // see whether we need to restyle the container
+    bool isEmpty = true; // :empty or :-moz-only-whitespace
+    for (nsIContent* child = container->GetFirstChild();
+         child;
+         child = child->GetNextSibling()) {
+      // We don't know whether we're testing :empty or :-moz-only-whitespace,
+      // so be conservative and assume :-moz-only-whitespace (i.e., make
+      // IsSignificantChild less likely to be true, and thus make us more
+      // likely to restyle).
+      if (nsStyleUtil::IsSignificantChild(child, true, false)) {
+        isEmpty = false;
+        break;
+      }
+    }
+    if (isEmpty) {
+      RestyleForEmptyChange(container);
+      return;
+    }
+  }
+
+  if (selectorFlags & NODE_HAS_SLOW_SELECTOR) {
+    PostRestyleEvent(container, eRestyle_Subtree, nsChangeHint(0));
+    // Restyling the container is the most we can do here, so we're done.
+    return;
+  }
+
+  if (selectorFlags & NODE_HAS_SLOW_SELECTOR_LATER_SIBLINGS) {
+    // Restyle all later siblings.
+    RestyleSiblingsStartingWith(this, aFollowingSibling);
+  }
+
+  if (selectorFlags & NODE_HAS_EDGE_CHILD_SELECTOR) {
+    // restyle the now-first element child if it was after aOldChild
+    bool reachedFollowingSibling = false;
+    for (nsIContent* content = container->GetFirstChild();
+         content;
+         content = content->GetNextSibling()) {
+      if (content == aFollowingSibling) {
+        reachedFollowingSibling = true;
+        // do NOT continue here; we might want to restyle this node
+      }
+      if (content->IsElement()) {
+        if (reachedFollowingSibling) {
+          PostRestyleEvent(content->AsElement(), eRestyle_Subtree,
+                           nsChangeHint(0));
+        }
+        break;
+      }
+    }
+    // restyle the now-last element child if it was before aOldChild
+    reachedFollowingSibling = (aFollowingSibling == nullptr);
+    for (nsIContent* content = container->GetLastChild();
+         content;
+         content = content->GetPreviousSibling()) {
+      if (content->IsElement()) {
+        if (reachedFollowingSibling) {
+          PostRestyleEvent(content->AsElement(), eRestyle_Subtree, nsChangeHint(0));
+        }
+        break;
+      }
+      if (content == aFollowingSibling) {
+        reachedFollowingSibling = true;
+      }
+    }
+  }
+}
+
 /**
  * Calculates the change hint and the restyle hint for a given content state
  * change.
  *
  * This is called from both Restyle managers.
  */
 void
 RestyleManager::ContentStateChangedInternal(Element* aElement,
--- a/layout/base/RestyleManager.h
+++ b/layout/base/RestyleManager.h
@@ -140,33 +140,45 @@ public:
     return mAnimationsWithDestroyedFrame;
   }
 
   void PostRestyleEventForLazyConstruction() { PostRestyleEventInternal(true); }
 
   void ContentInserted(nsINode* aContainer, nsIContent* aChild);
   void ContentAppended(nsIContent* aContainer, nsIContent* aFirstNewContent);
 
+  // This would be have the same logic as RestyleForInsertOrChange if we got the
+  // notification before the removal.  However, we get it after, so we need the
+  // following sibling in addition to the old child.  |aContainer| must be
+  // non-null; when the container is null, no work is needed.  aFollowingSibling
+  // is the sibling that used to come after aOldChild before the removal.
+  void ContentRemoved(nsINode* aContainer,
+                      nsIContent* aOldChild,
+                      nsIContent* aFollowingSibling);
+
+  // Restyling for a ContentInserted (notification after insertion) or
+  // for a CharacterDataChanged.  |aContainer| must be non-null; when
+  // the container is null, no work is needed.
+  void RestyleForInsertOrChange(nsINode* aContainer, nsIContent* aChild);
+
+  // Restyling for a ContentAppended (notification after insertion) or
+  // for a CharacterDataChanged.  |aContainer| must be non-null; when
+  // the container is null, no work is needed.
+  void RestyleForAppend(nsIContent* aContainer, nsIContent* aFirstNewContent);
+
   MOZ_DECL_STYLO_METHODS(GeckoRestyleManager, ServoRestyleManager)
 
   inline void PostRestyleEvent(dom::Element* aElement,
                                nsRestyleHint aRestyleHint,
                                nsChangeHint aMinChangeHint);
   inline void RebuildAllStyleData(nsChangeHint aExtraHint,
                                   nsRestyleHint aRestyleHint);
   inline void PostRebuildAllStyleDataEvent(nsChangeHint aExtraHint,
                                            nsRestyleHint aRestyleHint);
   inline void ProcessPendingRestyles();
-  inline void ContentRemoved(nsINode* aContainer,
-                             nsIContent* aOldChild,
-                             nsIContent* aFollowingSibling);
-  inline void RestyleForInsertOrChange(nsINode* aContainer,
-                                       nsIContent* aChild);
-  inline void RestyleForAppend(nsIContent* aContainer,
-                               nsIContent* aFirstNewContent);
   inline nsresult ContentStateChanged(nsIContent* aContent,
                                       EventStates aStateMask);
   inline void AttributeWillChange(dom::Element* aElement,
                                   int32_t aNameSpaceID,
                                   nsIAtom* aAttribute,
                                   int32_t aModType,
                                   const nsAttrValue* aNewValue);
   inline void AttributeChanged(dom::Element* aElement,
@@ -180,16 +192,18 @@ protected:
   RestyleManager(StyleBackendType aType, nsPresContext* aPresContext);
 
   virtual ~RestyleManager()
   {
     MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
                "leaving dangling pointers from AnimationsWithDestroyedFrame");
   }
 
+  void RestyleForEmptyChange(Element* aContainer);
+
   void ContentStateChangedInternal(Element* aElement,
                                    EventStates aStateMask,
                                    nsChangeHint* aOutChangeHint,
                                    nsRestyleHint* aOutRestyleHint);
 
   bool IsDisconnected() { return mPresContext == nullptr; }
 
   void IncrementHoverGeneration() {
--- a/layout/base/RestyleManagerInlines.h
+++ b/layout/base/RestyleManagerInlines.h
@@ -39,38 +39,16 @@ RestyleManager::PostRebuildAllStyleDataE
 }
 
 void
 RestyleManager::ProcessPendingRestyles()
 {
   MOZ_STYLO_FORWARD(ProcessPendingRestyles, ());
 }
 
-void
-RestyleManager::ContentRemoved(nsINode* aContainer,
-                               nsIContent* aOldChild,
-			       nsIContent* aFollowingSibling)
-{
-  MOZ_STYLO_FORWARD(ContentRemoved, (aContainer, aOldChild, aFollowingSibling));
-}
-
-void
-RestyleManager::RestyleForInsertOrChange(nsINode* aContainer,
-                                         nsIContent* aChild)
-{
-  MOZ_STYLO_FORWARD(RestyleForInsertOrChange, (aContainer, aChild));
-}
-
-void
-RestyleManager::RestyleForAppend(nsIContent* aContainer,
-                                 nsIContent* aFirstNewContent)
-{
-  MOZ_STYLO_FORWARD(RestyleForAppend, (aContainer, aFirstNewContent));
-}
-
 nsresult
 RestyleManager::ContentStateChanged(nsIContent* aContent,
                                     EventStates aStateMask)
 {
   MOZ_STYLO_FORWARD(ContentStateChanged, (aContent, aStateMask));
 }
 
 void