Bug 1361268: Clear nsIDocument::mCachedRootElement *before* removing from doc's child list, rather than *after* removing. draft
authorDaniel Holbert <dholbert@cs.stanford.edu>
Tue, 02 May 2017 16:08:02 -0700
changeset 571580 30e42162949453dae93f064ccf88c8328c9eeba1
parent 571021 60ef42b05139089c278c920ebb2061bdefd32533
child 626819 31adfa4d235c322e78ba4f9981cbe07f52e4df03
push id56854
push userdholbert@mozilla.com
push dateTue, 02 May 2017 23:16:09 +0000
bugs1361268
milestone55.0a1
Bug 1361268: Clear nsIDocument::mCachedRootElement *before* removing from doc's child list, rather than *after* removing. Internally, doRemoveChildAt does quite a bit of cleanup, and that includes some calls to nsIDocument::GetRootElement(). Before this patch, those calls will misleadingly return the still-present mCachedRootElement member-var. We'd really like those invocations to find an empty cached variable, and fall back to checking the actual updated child list. MozReview-Commit-ID: 8hhKcWyUVYQ
dom/base/nsDocument.cpp
dom/base/nsINode.cpp
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -4172,18 +4172,28 @@ nsDocument::RemoveChildAt(uint32_t aInde
     return;
   }
 
   if (oldKid->IsElement()) {
     // Destroy the link map up front before we mess with the child list.
     DestroyElementMaps();
   }
 
+  // Preemptively clear mCachedRootElement, since we may be about to remove it
+  // from our child list, and we don't want to return this maybe-obsolete value
+  // from any GetRootElement() calls that happen inside of doRemoveChildAt().
+  // (NOTE: for this to be useful, doRemoveChildAt() must NOT trigger any
+  // GetRootElement() calls until after it's removed the child from mChildren.
+  // Any call before that point would restore this soon-to-be-obsolete cached
+  // answer, and our clearing here would be fruitless.)
+  mCachedRootElement = nullptr;
   doRemoveChildAt(aIndex, aNotify, oldKid, mChildren);
-  mCachedRootElement = nullptr;
+  MOZ_ASSERT(mCachedRootElement != oldKid,
+             "Stale pointer in mCachedRootElement, after we tried to clear it "
+             "(maybe somebody called GetRootElement() too early?)");
 }
 
 void
 nsDocument::EnsureOnDemandBuiltInUASheet(StyleSheet* aSheet)
 {
   if (mOnDemandBuiltInUASheets.Contains(aSheet)) {
     return;
   }
--- a/dom/base/nsINode.cpp
+++ b/dom/base/nsINode.cpp
@@ -1898,16 +1898,20 @@ nsINode::Append(const Sequence<OwningNod
 
   AppendChild(*node, aRv);
 }
 
 void
 nsINode::doRemoveChildAt(uint32_t aIndex, bool aNotify,
                          nsIContent* aKid, nsAttrAndChildArray& aChildArray)
 {
+  // NOTE: This function must not trigger any calls to
+  // nsIDocument::GetRootElement() calls until *after* it has removed aKid from
+  // aChildArray. Any calls before then could potentially restore a stale
+  // value for our cached root element, per note in nsDocument::RemoveChildAt().
   NS_PRECONDITION(aKid && aKid->GetParentNode() == this &&
                   aKid == GetChildAt(aIndex) &&
                   IndexOf(aKid) == (int32_t)aIndex, "Bogus aKid");
 
   nsMutationGuard::DidMutate();
   mozAutoDocUpdate updateBatch(GetComposedDoc(), UPDATE_CONTENT_MODEL, aNotify);
 
   nsIContent* previousSibling = aKid->GetPreviousSibling();