Bug 1329877: Optimize AncestorFilter usage in lazy frame construction. r=bz
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 27 Feb 2017 23:35:06 +0100
changeset 345946 ae0efe63cb9b2d5b2c2aff5b29814e37cb640aca
parent 345945 c6506764b461700b5558509b21cbfc05f1532856
child 345947 20075fb33fc76f5fba68cab086631e9043ed94a7
push id31451
push usercbook@mozilla.com
push dateMon, 06 Mar 2017 09:52:09 +0000
treeherdermozilla-central@7099e03837e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbz
bugs1329877
milestone54.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 1329877: Optimize AncestorFilter usage in lazy frame construction. r=bz MozReview-Commit-ID: ErzurfQldHf
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsCSSFrameConstructor.h
layout/style/nsRuleProcessorData.h
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -803,31 +803,33 @@ public:
   // yet.  The mCreatingExtraFrames == true mode is meant to be used for
   // construction of random "extra" frames for elements via normal frame
   // construction APIs (e.g. replication of things across pages in paginated
   // mode).
   bool                      mCreatingExtraFrames;
 
   nsCOMArray<nsIContent>    mGeneratedTextNodesWithInitializer;
 
-  TreeMatchContext          mTreeMatchContext;
+  TreeMatchContext&         mTreeMatchContext;
 
   // Constructor
   // Use the passed-in history state.
   nsFrameConstructorState(
     nsIPresShell* aPresShell,
+    TreeMatchContext& aTreeMatchContext,
     nsContainerFrame* aFixedContainingBlock,
     nsContainerFrame* aAbsoluteContainingBlock,
     nsContainerFrame* aFloatContainingBlock,
     already_AddRefed<nsILayoutHistoryState> aHistoryState);
   // Get the history state from the pres context's pres shell.
-  nsFrameConstructorState(nsIPresShell*          aPresShell,
-                          nsContainerFrame*      aFixedContainingBlock,
-                          nsContainerFrame*      aAbsoluteContainingBlock,
-                          nsContainerFrame*      aFloatContainingBlock);
+  nsFrameConstructorState(nsIPresShell* aPresShell,
+                          TreeMatchContext& aTreeMatchContext,
+                          nsContainerFrame* aFixedContainingBlock,
+                          nsContainerFrame* aAbsoluteContainingBlock,
+                          nsContainerFrame* aFloatContainingBlock);
 
   ~nsFrameConstructorState();
 
   // Function to push the existing absolute containing block state and
   // create a new scope. Code that uses this function should get matching
   // logic in GetAbsoluteContainingBlock.
   // Also makes aNewAbsoluteContainingBlock the containing block for
   // fixed-pos elements if necessary.
@@ -970,16 +972,17 @@ protected:
   // AddToAttachedQueue on all of them, in order.
   LinkedList<PendingBinding> mPendingBindings;
 
   PendingBinding* mCurrentPendingBindingInsertionPoint;
 };
 
 nsFrameConstructorState::nsFrameConstructorState(
   nsIPresShell* aPresShell,
+  TreeMatchContext& aTreeMatchContext,
   nsContainerFrame* aFixedContainingBlock,
   nsContainerFrame* aAbsoluteContainingBlock,
   nsContainerFrame* aFloatContainingBlock,
   already_AddRefed<nsILayoutHistoryState> aHistoryState)
   : mPresContext(aPresShell->GetPresContext()),
     mPresShell(aPresShell),
     mFrameManager(aPresShell->FrameManager()),
 #ifdef MOZ_XUL
@@ -996,34 +999,36 @@ nsFrameConstructorState::nsFrameConstruc
     mFrameState(aHistoryState),
     mAdditionalStateBits(nsFrameState(0)),
     // If the fixed-pos containing block is equal to the abs-pos containing
     // block, use the abs-pos containing block's abs-pos list for fixed-pos
     // frames.
     mFixedPosIsAbsPos(aFixedContainingBlock == aAbsoluteContainingBlock),
     mHavePendingPopupgroup(false),
     mCreatingExtraFrames(false),
-    mTreeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited,
-                      aPresShell->GetDocument()),
+    mTreeMatchContext(aTreeMatchContext),
     mCurrentPendingBindingInsertionPoint(nullptr)
 {
 #ifdef MOZ_XUL
   nsIRootBox* rootBox = nsIRootBox::GetRootBox(aPresShell);
   if (rootBox) {
     mPopupItems.containingBlock = rootBox->GetPopupSetFrame();
   }
 #endif
   MOZ_COUNT_CTOR(nsFrameConstructorState);
 }
 
 nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell,
+                                                 TreeMatchContext& aTreeMatchContext,
                                                  nsContainerFrame* aFixedContainingBlock,
                                                  nsContainerFrame* aAbsoluteContainingBlock,
                                                  nsContainerFrame* aFloatContainingBlock)
-  : nsFrameConstructorState(aPresShell, aFixedContainingBlock,
+  : nsFrameConstructorState(aPresShell,
+                            aTreeMatchContext,
+                            aFixedContainingBlock,
                             aAbsoluteContainingBlock,
                             aFloatContainingBlock,
                             aPresShell->GetDocument()->GetLayoutHistoryState())
 {
 }
 
 nsFrameConstructorState::~nsFrameConstructorState()
 {
@@ -2415,23 +2420,25 @@ nsCSSFrameConstructor::ConstructDocEleme
   if (nsPresContext* presContext = mPresShell->GetPresContext()) {
     propagatedScrollFrom = presContext->UpdateViewportScrollbarStylesOverride();
   }
 
   SetUpDocElementContainingBlock(aDocElement);
 
   NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
 
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  // Initialize the ancestor filter with null for now; we'll push
+  // aDocElement once we finish resolving style for it.
+  matchContext.InitAncestors(nullptr);
   nsFrameConstructorState state(mPresShell,
+                                matchContext,
                                 GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
                                 nullptr,
                                 nullptr, do_AddRef(aFrameState));
-  // Initialize the ancestor filter with null for now; we'll push
-  // aDocElement once we finish resolving style for it.
-  state.mTreeMatchContext.InitAncestors(nullptr);
 
   // XXXbz why, exactly?
   if (!mTempFrameTreeState)
     state.mPresShell->CaptureHistoryState(getter_AddRefs(mTempFrameTreeState));
 
   // Make sure that we'll handle restyles for this document element in
   // the future.  We need this, because the document element might
   // have stale restyle bits from a previous frame constructor for
@@ -2862,17 +2869,18 @@ nsCSSFrameConstructor::SetUpDocElementCo
   // propagated
   NS_ASSERTION(!isScrollable || !isXUL,
                "XUL documents should never be scrollable - see above");
 
   nsContainerFrame* newFrame = rootFrame;
   RefPtr<nsStyleContext> rootPseudoStyle;
   // we must create a state because if the scrollbars are GFX it needs the
   // state to build the scrollbar frames.
-  nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr);
 
   // Start off with the viewport as parent; we'll adjust it as needed.
   nsContainerFrame* parentFrame = viewportFrame;
 
   StyleSetHandle styleSet = mPresShell->StyleSet();
   // If paginated, make sure we don't put scrollbars in
   if (!isScrollable) {
     rootPseudoStyle = styleSet->ResolveAnonymousBoxStyle(rootPseudo,
@@ -7132,22 +7140,27 @@ nsCSSFrameConstructor::MaybeConstructLaz
     }
   }
 
   RestyleManager()->PostRestyleEventForLazyConstruction();
   return true;
 }
 
 void
-nsCSSFrameConstructor::CreateNeededFrames(nsIContent* aContent)
+nsCSSFrameConstructor::CreateNeededFrames(
+    nsIContent* aContent,
+    TreeMatchContext& aTreeMatchContext)
 {
   NS_ASSERTION(!aContent->HasFlag(NODE_NEEDS_FRAME),
     "shouldn't get here with a content node that has needs frame bit set");
   NS_ASSERTION(aContent->HasFlag(NODE_DESCENDANTS_NEED_FRAMES),
     "should only get here with a content node that has descendants needing frames");
+  MOZ_ASSERT(aTreeMatchContext.mAncestorFilter.HasFilter(),
+             "The whole point of having the tree match context is optimizing "
+             "the ancestor filter usage!");
 
   aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES);
 
   // We could either descend first (on nodes that don't have NODE_NEEDS_FRAME
   // set) or issue content notifications for our kids first. In absence of
   // anything definitive either way we'll go with the latter.
 
   // It might be better to use GetChildArray and scan it completely first and
@@ -7173,44 +7186,59 @@ nsCSSFrameConstructor::CreateNeededFrame
         inRun = true;
         firstChildInRun = child;
       }
     } else {
       if (inRun) {
         inRun = false;
         // generate a ContentRangeInserted for [startOfRun,i)
         ContentRangeInserted(aContent, firstChildInRun, child, nullptr,
-                             false);
-      }
-    }
-  }
+                             false, &aTreeMatchContext);
+      }
+    }
+  }
+
   if (inRun) {
-    ContentAppended(aContent, firstChildInRun, false);
+    ContentAppended(aContent, firstChildInRun, false, &aTreeMatchContext);
   }
 
   // Now descend.
   FlattenedChildIterator iter(aContent);
   for (nsIContent* child = iter.GetNextChild(); child; child = iter.GetNextChild()) {
     if (child->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
-      CreateNeededFrames(child);
+      TreeMatchContext::AutoAncestorPusher insertionPointPusher(
+          aTreeMatchContext);
+
+      // Handle stuff like xbl:children.
+      if (child->GetParent() != aContent && child->GetParent()->IsElement()) {
+        insertionPointPusher.PushAncestorAndStyleScope(
+            child->GetParent()->AsElement());
+      }
+
+      TreeMatchContext::AutoAncestorPusher pusher(aTreeMatchContext);
+      pusher.PushAncestorAndStyleScope(child);
+
+      CreateNeededFrames(child, aTreeMatchContext);
     }
   }
 }
 
 void nsCSSFrameConstructor::CreateNeededFrames()
 {
   NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
                "Someone forgot a script blocker");
 
   Element* rootElement = mDocument->GetRootElement();
   NS_ASSERTION(!rootElement || !rootElement->HasFlag(NODE_NEEDS_FRAME),
     "root element should not have frame created lazily");
   if (rootElement && rootElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
     BeginUpdate();
-    CreateNeededFrames(rootElement);
+    TreeMatchContext treeMatchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    treeMatchContext.InitAncestors(rootElement);
+    CreateNeededFrames(rootElement, treeMatchContext);
     EndUpdate();
   }
 }
 
 void
 nsCSSFrameConstructor::IssueSingleInsertNofications(nsIContent* aContainer,
                                                     nsIContent* aStartChild,
                                                     nsIContent* aEndChild,
@@ -7316,20 +7344,23 @@ nsCSSFrameConstructor::MaybeRecreateForF
         return true;
       }
     }
   }
   return false;
 }
 
 nsresult
-nsCSSFrameConstructor::ContentAppended(nsIContent*     aContainer,
-                                       nsIContent*     aFirstNewContent,
-                                       bool            aAllowLazyConstruction)
-{
+nsCSSFrameConstructor::ContentAppended(nsIContent* aContainer,
+                                       nsIContent* aFirstNewContent,
+                                       bool aAllowLazyConstruction,
+                                       TreeMatchContext* aProvidedTreeMatchContext)
+{
+  MOZ_ASSERT_IF(aProvidedTreeMatchContext, !aAllowLazyConstruction);
+
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while creating frames");
 
 #ifdef DEBUG
   if (gNoisyContentUpdates) {
     printf("nsCSSFrameConstructor::ContentAppended container=%p "
            "first-child=%p lazy=%d\n",
@@ -7479,21 +7510,30 @@ nsCSSFrameConstructor::ContentAppended(n
 
   // Deal with possible :after generated content on the parent
   nsIFrame* parentAfterFrame;
   parentFrame =
     ::AdjustAppendParentForAfterContent(this, insertion.mContainer, parentFrame,
                                         aFirstNewContent, &parentAfterFrame);
 
   // Create some new frames
+  //
+  // We use the provided tree match context, or create a new one on the fly
+  // otherwise.
+  Maybe<TreeMatchContext> matchContext;
+  if (!aProvidedTreeMatchContext) {
+    matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
+    matchContext->InitAncestors(aContainer->AsElement());
+  }
   nsFrameConstructorState state(mPresShell,
+                                aProvidedTreeMatchContext
+                                  ? *aProvidedTreeMatchContext : *matchContext,
                                 GetAbsoluteContainingBlock(parentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(parentFrame, ABS_POS),
                                 GetFloatContainingBlock(parentFrame));
-  state.mTreeMatchContext.InitAncestors(aContainer->AsElement());
 
   // See if the containing block has :first-letter style applied.
   bool haveFirstLetterStyle = false, haveFirstLineStyle = false;
   nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
   if (containingBlock) {
     haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
     haveFirstLineStyle =
       ShouldHaveFirstLineStyle(containingBlock->GetContent(),
@@ -7700,21 +7740,22 @@ nsCSSFrameConstructor::ContentInserted(n
 // IsValidSibling (the only place GetInsertionPrevSibling might look at the
 // passed in node itself) needs to resolve style on the node we record this and
 // return that this range needs to be split up and inserted separately. Table
 // captions need extra attention as we need to determine where to insert them
 // in the caption list, while skipping any nodes in the range being inserted
 // (because when we treat the caption frames the other nodes have had their
 // frames constructed but not yet inserted into the frame tree).
 nsresult
-nsCSSFrameConstructor::ContentRangeInserted(nsIContent*            aContainer,
-                                            nsIContent*            aStartChild,
-                                            nsIContent*            aEndChild,
+nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aContainer,
+                                            nsIContent* aStartChild,
+                                            nsIContent* aEndChild,
                                             nsILayoutHistoryState* aFrameState,
-                                            bool                   aAllowLazyConstruction)
+                                            bool aAllowLazyConstruction,
+                                            TreeMatchContext* aProvidedTreeMatchContext)
 {
   AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
   NS_PRECONDITION(mUpdateCount != 0,
                   "Should be in an update while creating frames");
 
   NS_PRECONDITION(aStartChild, "must always pass a child");
 
   // XXXldb Do we need to re-resolve style to handle the CSS2 + combinator and
@@ -7973,24 +8014,28 @@ nsCSSFrameConstructor::ContentRangeInser
   if (insertion.mParentFrame->IsFrameOfType(nsIFrame::eMathML)) {
     LAYOUT_PHASE_TEMP_EXIT();
     nsresult rv = RecreateFramesForContent(insertion.mParentFrame->GetContent(), false,
                                            REMOVE_FOR_RECONSTRUCTION, nullptr);
     LAYOUT_PHASE_TEMP_REENTER();
     return rv;
   }
 
+  Maybe<TreeMatchContext> matchContext;
+  if (!aProvidedTreeMatchContext) {
+    matchContext.emplace(mDocument, TreeMatchContext::ForFrameConstruction);
+    matchContext->InitAncestors(aContainer ? aContainer->AsElement() : nullptr);
+  }
   nsFrameConstructorState state(mPresShell,
+                                aProvidedTreeMatchContext
+                                  ? *aProvidedTreeMatchContext : *matchContext,
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
                                 GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
                                 GetFloatContainingBlock(insertion.mParentFrame),
                                 do_AddRef(aFrameState));
-  state.mTreeMatchContext.InitAncestors(aContainer ?
-                                          aContainer->AsElement() :
-                                          nullptr);
 
   // Recover state for the containing block - we need to know if
   // it has :first-letter or :first-line style applied to it. The
   // reason we care is that the internal structure in these cases
   // is not the normal structure and requires custom updating
   // logic.
   nsContainerFrame* containingBlock = state.mFloatedItems.containingBlock;
   bool haveFirstLetterStyle = false;
@@ -8912,17 +8957,20 @@ nsCSSFrameConstructor::CreateContinuingT
     nsIFrame* rgNextInFlow = rowGroupFrame->GetNextInFlow();
     if (rgNextInFlow) {
       rowGroupFrame->SetRepeatable(false);
     }
     else if (rowGroupFrame->IsRepeatable()) {
       // Replicate the header/footer frame.
       nsTableRowGroupFrame*   headerFooterFrame;
       nsFrameItems            childItems;
+
+      TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
       nsFrameConstructorState state(mPresShell,
+                                    matchContext,
                                     GetAbsoluteContainingBlock(newFrame, FIXED_POS),
                                     GetAbsoluteContainingBlock(newFrame, ABS_POS),
                                     nullptr);
       state.mCreatingExtraFrames = true;
 
       nsStyleContext* const headerFooterStyleContext = rowGroupFrame->StyleContext();
       headerFooterFrame = static_cast<nsTableRowGroupFrame*>
                                      (NS_NewTableRowGroupFrame(aPresShell, headerFooterStyleContext));
@@ -9186,17 +9234,20 @@ nsCSSFrameConstructor::ReplicateFixedFra
   if (!firstFixed) {
     return NS_OK;
   }
 
   // Don't allow abs-pos descendants of the fixed content to escape the content.
   // This should not normally be possible (because fixed-pos elements should
   // be absolute containers) but fixed-pos tables currently aren't abs-pos
   // containers.
-  nsFrameConstructorState state(mPresShell, aParentFrame,
+  TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+  nsFrameConstructorState state(mPresShell,
+                                matchContext,
+                                aParentFrame,
                                 nullptr,
                                 mRootElementFrame);
   state.mCreatingExtraFrames = true;
 
   // We can't use an ancestor filter here, because we're not going to
   // be usefully recurring down the tree.  This means that other
   // places in frame construction can't assume a filter is
   // initialized!
@@ -11486,17 +11537,20 @@ nsCSSFrameConstructor::CreateLetterFrame
     // frame.
     // XXXbz it would be really nice to destroy the old frame _first_,
     // then create the new one, so we could avoid this hack.
     aTextContent->SetPrimaryFrame(nullptr);
     nsIFrame* textFrame = NS_NewTextFrame(mPresShell, textSC);
 
     NS_ASSERTION(aBlockContinuation == GetFloatContainingBlock(aParentFrame),
                  "Containing block is confused");
+    TreeMatchContext matchContext(mDocument,
+                                  TreeMatchContext::ForFrameConstruction);
     nsFrameConstructorState state(mPresShell,
+                                  matchContext,
                                   GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
                                   GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
                                   aBlockContinuation);
 
     // Create the right type of first-letter frame
     const nsStyleDisplay* display = sc->StyleDisplay();
     if (display->IsFloatingStyle() && !aParentFrame->IsSVGText()) {
       // Make a floating first-letter frame
@@ -11872,17 +11926,20 @@ nsCSSFrameConstructor::CreateListBoxCont
                                             bool                   aIsAppend)
 {
 #ifdef MOZ_XUL
   nsresult rv = NS_OK;
 
   // Construct a new frame
   if (nullptr != aParentFrame) {
     nsFrameItems            frameItems;
-    nsFrameConstructorState state(mPresShell, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
+    TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    nsFrameConstructorState state(mPresShell,
+                                  matchContext,
+                                  GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
                                   GetAbsoluteContainingBlock(aParentFrame, ABS_POS),
                                   GetFloatContainingBlock(aParentFrame),
                                   do_AddRef(mTempFrameTreeState.get()));
 
     // If we ever initialize the ancestor filter on |state|, make sure
     // to push the right parent!
 
     RefPtr<nsStyleContext> styleContext;
@@ -12764,17 +12821,18 @@ nsCSSFrameConstructor::ReframeContaining
 nsresult
 nsCSSFrameConstructor::GenerateChildFrames(nsContainerFrame* aFrame)
 {
   {
     nsAutoScriptBlocker scriptBlocker;
     BeginUpdate();
 
     nsFrameItems childItems;
-    nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+    TreeMatchContext matchContext(mDocument, TreeMatchContext::ForFrameConstruction);
+    nsFrameConstructorState state(mPresShell, matchContext, nullptr, nullptr, nullptr);
     // We don't have a parent frame with a pending binding constructor here,
     // so no need to worry about ordering of the kids' constructors with it.
     // Pass null for the PendingBinding.
     ProcessChildren(state, aFrame->GetContent(), aFrame->StyleContext(),
                     aFrame, false, childItems, false,
                     nullptr);
 
     aFrame->SetInitialChildList(kPrincipalList, childItems);
--- a/layout/base/nsCSSFrameConstructor.h
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -82,17 +82,18 @@ public:
 
   // Create frames for content nodes that are marked as needing frames. This
   // should be called before ProcessPendingRestyles.
   // Note: It's the caller's responsibility to make sure to wrap a
   // CreateNeededFrames call in a view update batch and a script blocker.
   void CreateNeededFrames();
 
 private:
-  void CreateNeededFrames(nsIContent* aContent);
+  void CreateNeededFrames(nsIContent* aContent,
+                          TreeMatchContext& aTreeMatchContext);
 
   enum Operation {
     CONTENTAPPEND,
     CONTENTINSERT
   };
 
   // aChild is the child being inserted for inserts, and the first
   // child being appended for appends.
@@ -197,38 +198,50 @@ public:
    * quicker by processing the content tree from top down the next time we call
    * CreateNeededFrames. (We do clear the bits when BindToTree is called on any
    * nsIContent; so any nodes added to the document will not have any lazy bits
    * set.)
    */
 
   // If aAllowLazyConstruction is true then frame construction of the new
   // children can be done lazily.
+  //
+  // When constructing frames lazily, we can keep the tree match context in a
+  // much easier way than nsFrameConstructorState, and thus, we're allowed to
+  // provide a TreeMatchContext to avoid calling InitAncestors repeatedly deep
+  // in the DOM.
   nsresult ContentAppended(nsIContent* aContainer,
                            nsIContent* aFirstNewContent,
-                           bool        aAllowLazyConstruction);
+                           bool aAllowLazyConstruction,
+                           TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   // If aAllowLazyConstruction is true then frame construction of the new child
   // can be done lazily.
-  nsresult ContentInserted(nsIContent*            aContainer,
-                           nsIContent*            aChild,
+  nsresult ContentInserted(nsIContent* aContainer,
+                           nsIContent* aChild,
                            nsILayoutHistoryState* aFrameState,
-                           bool                   aAllowLazyConstruction);
+                           bool aAllowLazyConstruction);
 
   // Like ContentInserted but handles inserting the children of aContainer in
   // the range [aStartChild, aEndChild).  aStartChild must be non-null.
   // aEndChild may be null to indicate the range includes all kids after
-  // aStartChild.  If aAllowLazyConstruction is true then frame construction of
+  // aStartChild.
+  //
+  // If aAllowLazyConstruction is true then frame construction of
   // the new children can be done lazily. It is only allowed to be true when
   // inserting a single node.
-  nsresult ContentRangeInserted(nsIContent*            aContainer,
-                                nsIContent*            aStartChild,
-                                nsIContent*            aEndChild,
+  //
+  // See ContentAppended to see why we allow passing an already initialized
+  // TreeMatchContext.
+  nsresult ContentRangeInserted(nsIContent* aContainer,
+                                nsIContent* aStartChild,
+                                nsIContent* aEndChild,
                                 nsILayoutHistoryState* aFrameState,
-                                bool                   aAllowLazyConstruction);
+                                bool aAllowLazyConstruction,
+                                TreeMatchContext* aProvidedTreeMatchContext = nullptr);
 
   enum RemoveFlags {
     REMOVE_CONTENT, REMOVE_FOR_RECONSTRUCTION, REMOVE_DESTROY_FRAMES };
   /**
    * Recreate or destroy frames for aChild in aContainer.
    * aFlags == REMOVE_CONTENT means aChild has been removed from the document.
    * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the
    *   frames later.
--- a/layout/style/nsRuleProcessorData.h
+++ b/layout/style/nsRuleProcessorData.h
@@ -406,16 +406,22 @@ struct MOZ_STACK_CLASS TreeMatchContext 
   {
     if (aMatchVisited != eNeverMatchVisited) {
       nsILoadContext* loadContext = mDocument->GetLoadContext();
       if (loadContext) {
         mUsingPrivateBrowsing = loadContext->UsePrivateBrowsing();
       }
     }
   }
+
+  enum ForFrameConstructionTag { ForFrameConstruction };
+
+  TreeMatchContext(nsIDocument* aDocument, ForFrameConstructionTag)
+    : TreeMatchContext(true, nsRuleWalker::eRelevantLinkUnvisited, aDocument)
+  {}
 };
 
 struct MOZ_STACK_CLASS RuleProcessorData {
   RuleProcessorData(nsPresContext* aPresContext,
                     nsRuleWalker* aRuleWalker)
     : mPresContext(aPresContext),
       mRuleWalker(aRuleWalker),
       mScope(nullptr)