From 16e918b99e9a2caaa9fd49050c77a7c802836126 Mon Sep 17 00:00:00 2001 From: Sebastian Hengst Date: Thu, 14 Sep 2017 14:48:50 +0200 Subject: [PATCH] Backed out changeset bc3d643c4973 (bug 1395973) for failing browser-chrome's toolkit/content/tests/browser/browser_bug982298.js and toolkit/modules/tests/browser/browser_Finder_hidden_textarea.js. r=backout on a CLOSED TREE --- dom/base/nsContentIterator.cpp | 473 +++++++++++++++++++++++++++++---- 1 file changed, 424 insertions(+), 49 deletions(-) diff --git a/dom/base/nsContentIterator.cpp b/dom/base/nsContentIterator.cpp index fc89ed1a0da1..62024317e12e 100644 --- a/dom/base/nsContentIterator.cpp +++ b/dom/base/nsContentIterator.cpp @@ -144,19 +144,28 @@ protected: // Recursively get the deepest first/last child of aRoot. This will return // aRoot itself if it has no children. - nsINode* GetDeepFirstChild(nsINode* aRoot); - nsIContent* GetDeepFirstChild(nsIContent* aRoot); - nsINode* GetDeepLastChild(nsINode* aRoot); - nsIContent* GetDeepLastChild(nsIContent* aRoot); + nsINode* GetDeepFirstChild(nsINode* aRoot, + nsTArray* aIndexes = nullptr); + nsIContent* GetDeepFirstChild(nsIContent* aRoot, + nsTArray* aIndexes = nullptr); + nsINode* GetDeepLastChild(nsINode* aRoot, + nsTArray* aIndexes = nullptr); + nsIContent* GetDeepLastChild(nsIContent* aRoot, + nsTArray* aIndexes = nullptr); // Get the next/previous sibling of aNode, or its parent's, or grandparent's, // etc. Returns null if aNode and all its ancestors have no next/previous // sibling. - nsIContent* GetNextSibling(nsINode* aNode); - nsIContent* GetPrevSibling(nsINode* aNode); + nsIContent* GetNextSibling(nsINode* aNode, + nsTArray* aIndexes = nullptr); + nsIContent* GetPrevSibling(nsINode* aNode, + nsTArray* aIndexes = nullptr); - nsINode* NextNode(nsINode* aNode); - nsINode* PrevNode(nsINode* aNode); + nsINode* NextNode(nsINode* aNode, nsTArray* aIndexes = nullptr); + nsINode* PrevNode(nsINode* aNode, nsTArray* aIndexes = nullptr); + + // WARNING: This function is expensive + nsresult RebuildIndexStack(); void MakeEmpty(); @@ -167,6 +176,30 @@ protected: nsCOMPtr mLast; nsCOMPtr mCommonParent; + // used by nsContentIterator to cache indices + AutoTArray mIndexes; + + // used by nsSubtreeIterator to cache indices. Why put them in the base + // class? Because otherwise I have to duplicate the routines GetNextSibling + // etc across both classes, with slight variations for caching. Or + // alternately, create a base class for the cache itself and have all the + // cache manipulation go through a vptr. I think this is the best space and + // speed combo, even though it's ugly. + int32_t mCachedIndex; + // another note about mCachedIndex: why should the subtree iterator use a + // trivial cached index instead of the mre robust array of indicies (which is + // what the basic content iterator uses)? The reason is that subtree + // iterators do not do much transitioning between parents and children. They + // tend to stay at the same level. In fact, you can prove (though I won't + // attempt it here) that they change levels at most n+m times, where n is the + // height of the parent hierarchy from the range start to the common + // ancestor, and m is the the height of the parent hierarchy from the range + // end to the common ancestor. If we used the index array, we would pay the + // price up front for n, and then pay the cost for m on the fly later on. + // With the simple cache, we only "pay as we go". Either way, we call + // IndexOf() once for each change of level in the hierarchy. Since a trivial + // index is much simpler, we use it for the subtree iterator. + bool mIsDone; bool mPre; @@ -232,9 +265,10 @@ nsContentIterator::LastRelease() * constructor/destructor ******************************************************/ -nsContentIterator::nsContentIterator(bool aPre) - : mIsDone(false) - , mPre(aPre) +nsContentIterator::nsContentIterator(bool aPre) : + // don't need to explicitly initialize |nsCOMPtr|s, they will automatically + // be nullptr + mCachedIndex(0), mIsDone(false), mPre(aPre) { } @@ -257,6 +291,7 @@ nsContentIterator::Init(nsINode* aRoot) } mIsDone = false; + mIndexes.Clear(); if (mPre) { mFirst = aRoot; @@ -270,6 +305,7 @@ nsContentIterator::Init(nsINode* aRoot) mCommonParent = aRoot; mCurNode = mFirst; + RebuildIndexStack(); return NS_OK; } @@ -340,6 +376,8 @@ nsContentIterator::InitInternal(nsINode* aStartContainer, uint32_t aStartOffset, mLast = mFirst; mCurNode = mFirst; + DebugOnly rv = RebuildIndexStack(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed"); return NS_OK; } } @@ -504,6 +542,13 @@ nsContentIterator::InitInternal(nsINode* aStartContainer, uint32_t aStartOffset, mCurNode = mFirst; mIsDone = !mCurNode; + if (!mCurNode) { + mIndexes.Clear(); + } else { + DebugOnly rv = RebuildIndexStack(); + NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "RebuildIndexStack failed"); + } + return NS_OK; } @@ -511,6 +556,36 @@ nsContentIterator::InitInternal(nsINode* aStartContainer, uint32_t aStartOffset, /****************************************************** * Helper routines ******************************************************/ +// WARNING: This function is expensive +nsresult +nsContentIterator::RebuildIndexStack() +{ + // Make sure we start at the right indexes on the stack! Build array up + // to common parent of start and end. Perhaps it's too many entries, but + // that's far better than too few. + nsINode* parent; + nsINode* current; + + mIndexes.Clear(); + current = mCurNode; + if (!current) { + return NS_OK; + } + + while (current != mCommonParent) { + parent = current->GetParentNode(); + + if (NS_WARN_IF(!parent)) { + return NS_ERROR_FAILURE; + } + + mIndexes.InsertElementAt(0, parent->IndexOf(current)); + + current = parent; + } + + return NS_OK; +} void nsContentIterator::MakeEmpty() @@ -520,20 +595,28 @@ nsContentIterator::MakeEmpty() mLast = nullptr; mCommonParent = nullptr; mIsDone = true; + mIndexes.Clear(); } nsINode* -nsContentIterator::GetDeepFirstChild(nsINode* aRoot) +nsContentIterator::GetDeepFirstChild(nsINode* aRoot, + nsTArray* aIndexes) { if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) { return aRoot; } - - return GetDeepFirstChild(aRoot->GetFirstChild()); + // We can't pass aRoot itself to the full GetDeepFirstChild, because that + // will only take nsIContent and aRoot might be a document. Pass aRoot's + // child, but be sure to preserve aIndexes. + if (aIndexes) { + aIndexes->AppendElement(0); + } + return GetDeepFirstChild(aRoot->GetFirstChild(), aIndexes); } nsIContent* -nsContentIterator::GetDeepFirstChild(nsIContent* aRoot) +nsContentIterator::GetDeepFirstChild(nsIContent* aRoot, + nsTArray* aIndexes) { if (NS_WARN_IF(!aRoot)) { return nullptr; @@ -543,6 +626,10 @@ nsContentIterator::GetDeepFirstChild(nsIContent* aRoot) nsIContent* child = node->GetFirstChild(); while (child) { + if (aIndexes) { + // Add this node to the stack of indexes + aIndexes->AppendElement(0); + } node = child; child = node->GetFirstChild(); } @@ -551,70 +638,164 @@ nsContentIterator::GetDeepFirstChild(nsIContent* aRoot) } nsINode* -nsContentIterator::GetDeepLastChild(nsINode* aRoot) +nsContentIterator::GetDeepLastChild(nsINode* aRoot, + nsTArray* aIndexes) { if (NS_WARN_IF(!aRoot) || !aRoot->HasChildren()) { return aRoot; } - - return GetDeepLastChild(aRoot->GetLastChild()); + // We can't pass aRoot itself to the full GetDeepLastChild, because that will + // only take nsIContent and aRoot might be a document. Pass aRoot's child, + // but be sure to preserve aIndexes. + if (aIndexes) { + aIndexes->AppendElement(aRoot->GetChildCount() - 1); + } + return GetDeepLastChild(aRoot->GetLastChild(), aIndexes); } nsIContent* -nsContentIterator::GetDeepLastChild(nsIContent* aRoot) +nsContentIterator::GetDeepLastChild(nsIContent* aRoot, + nsTArray* aIndexes) { if (NS_WARN_IF(!aRoot)) { return nullptr; } nsIContent* node = aRoot; - while (node->HasChildren()) { - nsIContent* child = node->GetLastChild(); + int32_t numChildren = node->GetChildCount(); + + while (numChildren) { + nsIContent* child = node->GetChildAt(--numChildren); + + if (aIndexes) { + // Add this node to the stack of indexes + aIndexes->AppendElement(numChildren); + } + numChildren = child->GetChildCount(); node = child; } + return node; } // Get the next sibling, or parent's next sibling, or grandpa's next sibling... nsIContent* -nsContentIterator::GetNextSibling(nsINode* aNode) +nsContentIterator::GetNextSibling(nsINode* aNode, + nsTArray* aIndexes) { if (NS_WARN_IF(!aNode)) { return nullptr; } - if (aNode->GetNextSibling()) { - return aNode->GetNextSibling(); - } - nsINode* parent = aNode->GetParentNode(); if (NS_WARN_IF(!parent)) { return nullptr; } - return GetNextSibling(parent); + + int32_t indx = 0; + + NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(), + "ContentIterator stack underflow"); + if (aIndexes && !aIndexes->IsEmpty()) { + // use the last entry on the Indexes array for the current index + indx = (*aIndexes)[aIndexes->Length()-1]; + } else { + indx = mCachedIndex; + } + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + + // reverify that the index of the current node hasn't changed. + // not super cheap, but a lot cheaper than IndexOf(), and still O(1). + // ignore result this time - the index may now be out of range. + nsIContent* sib = parent->GetChildAt(indx); + if (sib != aNode) { + // someone changed our index - find the new index the painful way + indx = parent->IndexOf(aNode); + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + } + + // indx is now canonically correct + if ((sib = parent->GetChildAt(++indx))) { + // update index cache + if (aIndexes && !aIndexes->IsEmpty()) { + aIndexes->ElementAt(aIndexes->Length()-1) = indx; + } else { + mCachedIndex = indx; + } + } else { + if (parent != mCommonParent) { + if (aIndexes) { + // pop node off the stack, go up one level and return parent or fail. + // Don't leave the index empty, especially if we're + // returning nullptr. This confuses other parts of the code. + if (aIndexes->Length() > 1) { + aIndexes->RemoveElementAt(aIndexes->Length()-1); + } + } + } + + // ok to leave cache out of date here if parent == mCommonParent? + sib = GetNextSibling(parent, aIndexes); + } + + return sib; } // Get the prev sibling, or parent's prev sibling, or grandpa's prev sibling... nsIContent* -nsContentIterator::GetPrevSibling(nsINode* aNode) +nsContentIterator::GetPrevSibling(nsINode* aNode, + nsTArray* aIndexes) { if (NS_WARN_IF(!aNode)) { return nullptr; } - if (aNode->GetPreviousSibling()) { - return aNode->GetPreviousSibling(); - } - nsINode* parent = aNode->GetParentNode(); if (NS_WARN_IF(!parent)) { return nullptr; } - return GetPrevSibling(parent); + + int32_t indx = 0; + + NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(), + "ContentIterator stack underflow"); + if (aIndexes && !aIndexes->IsEmpty()) { + // use the last entry on the Indexes array for the current index + indx = (*aIndexes)[aIndexes->Length()-1]; + } else { + indx = mCachedIndex; + } + + // reverify that the index of the current node hasn't changed + // ignore result this time - the index may now be out of range. + nsIContent* sib = parent->GetChildAt(indx); + if (sib != aNode) { + // someone changed our index - find the new index the painful way + indx = parent->IndexOf(aNode); + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + } + + // indx is now canonically correct + if (indx > 0 && (sib = parent->GetChildAt(--indx))) { + // update index cache + if (aIndexes && !aIndexes->IsEmpty()) { + aIndexes->ElementAt(aIndexes->Length()-1) = indx; + } else { + mCachedIndex = indx; + } + } else if (parent != mCommonParent) { + if (aIndexes && !aIndexes->IsEmpty()) { + // pop node off the stack, go up one level and try again. + aIndexes->RemoveElementAt(aIndexes->Length()-1); + } + return GetPrevSibling(parent, aIndexes); + } + + return sib; } nsINode* -nsContentIterator::NextNode(nsINode* aNode) +nsContentIterator::NextNode(nsINode* aNode, nsTArray* aIndexes) { nsINode* node = aNode; @@ -625,11 +806,19 @@ nsContentIterator::NextNode(nsINode* aNode) nsIContent* firstChild = node->GetFirstChild(); MOZ_ASSERT(firstChild); + // update cache + if (aIndexes) { + // push an entry on the index stack + aIndexes->AppendElement(0); + } else { + mCachedIndex = 0; + } + return firstChild; } // else next sibling is next - return GetNextSibling(node); + return GetNextSibling(node, aIndexes); } // post-order @@ -639,17 +828,64 @@ nsContentIterator::NextNode(nsINode* aNode) mIsDone = true; return node; } - nsIContent* sibling = node->GetNextSibling(); + nsIContent* sibling = nullptr; + int32_t indx = 0; + + // get the cached index + NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(), + "ContentIterator stack underflow"); + if (aIndexes && !aIndexes->IsEmpty()) { + // use the last entry on the Indexes array for the current index + indx = (*aIndexes)[aIndexes->Length()-1]; + } else { + indx = mCachedIndex; + } + + // reverify that the index of the current node hasn't changed. not super + // cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result + // this time - the index may now be out of range. + if (indx >= 0) { + sibling = parent->GetChildAt(indx); + } + if (sibling != node) { + // someone changed our index - find the new index the painful way + indx = parent->IndexOf(node); + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + } + + // indx is now canonically correct + sibling = parent->GetChildAt(++indx); if (sibling) { + // update cache + if (aIndexes && !aIndexes->IsEmpty()) { + // replace an entry on the index stack + aIndexes->ElementAt(aIndexes->Length()-1) = indx; + } else { + mCachedIndex = indx; + } + // next node is sibling's "deep left" child - return GetDeepFirstChild(sibling); + return GetDeepFirstChild(sibling, aIndexes); + } + + // else it's the parent, update cache + if (aIndexes) { + // Pop an entry off the index stack. Don't leave the index empty, + // especially if we're returning nullptr. This confuses other parts of the + // code. + if (aIndexes->Length() > 1) { + aIndexes->RemoveElementAt(aIndexes->Length()-1); + } + } else { + // this might be wrong, but we are better off guessing + mCachedIndex = 0; } return parent; } nsINode* -nsContentIterator::PrevNode(nsINode* aNode) +nsContentIterator::PrevNode(nsINode* aNode, nsTArray* aIndexes) { nsINode* node = aNode; @@ -661,22 +897,81 @@ nsContentIterator::PrevNode(nsINode* aNode) mIsDone = true; return aNode; } + nsIContent* sibling = nullptr; + int32_t indx = 0; - nsIContent* sibling = node->GetPreviousSibling(); - if (sibling) { - return GetDeepLastChild(sibling); + // get the cached index + NS_ASSERTION(!aIndexes || !aIndexes->IsEmpty(), + "ContentIterator stack underflow"); + if (aIndexes && !aIndexes->IsEmpty()) { + // use the last entry on the Indexes array for the current index + indx = (*aIndexes)[aIndexes->Length()-1]; + } else { + indx = mCachedIndex; } + // reverify that the index of the current node hasn't changed. not super + // cheap, but a lot cheaper than IndexOf(), and still O(1). ignore result + // this time - the index may now be out of range. + if (indx >= 0) { + sibling = parent->GetChildAt(indx); + NS_WARNING_ASSERTION(sibling, "GetChildAt returned null"); + } + + if (sibling != node) { + // someone changed our index - find the new index the painful way + indx = parent->IndexOf(node); + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + } + + // indx is now canonically correct + if (indx && (sibling = parent->GetChildAt(--indx))) { + // update cache + if (aIndexes && !aIndexes->IsEmpty()) { + // replace an entry on the index stack + aIndexes->ElementAt(aIndexes->Length()-1) = indx; + } else { + mCachedIndex = indx; + } + + // prev node is sibling's "deep right" child + return GetDeepLastChild(sibling, aIndexes); + } + + // else it's the parent, update cache + if (aIndexes && !aIndexes->IsEmpty()) { + // pop an entry off the index stack + aIndexes->RemoveElementAt(aIndexes->Length()-1); + } else { + // this might be wrong, but we are better off guessing + mCachedIndex = 0; + } return parent; } // post-order - if (node->HasChildren()) { - return node->GetLastChild(); + int32_t numChildren = node->GetChildCount(); + NS_WARNING_ASSERTION(numChildren >= 0, "no children"); + + // if it has children then prev node is last child + if (numChildren) { + nsIContent* lastChild = node->GetLastChild(); + NS_WARNING_ASSERTION(lastChild, "GetLastChild returned null"); + numChildren--; + + // update cache + if (aIndexes) { + // push an entry on the index stack + aIndexes->AppendElement(numChildren); + } else { + mCachedIndex = numChildren; + } + + return lastChild; } // else prev sibling is previous - return GetPrevSibling(node); + return GetPrevSibling(node, aIndexes); } /****************************************************** @@ -724,7 +1019,7 @@ nsContentIterator::Next() return; } - mCurNode = NextNode(mCurNode); + mCurNode = NextNode(mCurNode, &mIndexes); } @@ -740,7 +1035,7 @@ nsContentIterator::Prev() return; } - mCurNode = PrevNode(mCurNode); + mCurNode = PrevNode(mCurNode, &mIndexes); } @@ -750,6 +1045,7 @@ nsContentIterator::IsDone() return mIsDone; } + // Keeping arrays of indexes for the stack of nodes makes PositionAt // interesting... nsresult @@ -759,12 +1055,15 @@ nsContentIterator::PositionAt(nsINode* aCurNode) return NS_ERROR_NULL_POINTER; } + nsINode* newCurNode = aCurNode; + nsINode* tempNode = mCurNode; + + mCurNode = aCurNode; // take an early out if this doesn't actually change the position - if (mCurNode == aCurNode) { - mIsDone = false; + if (mCurNode == tempNode) { + mIsDone = false; // paranoia return NS_OK; } - mCurNode = aCurNode; // Check to see if the node falls within the traversal range. @@ -817,6 +1116,82 @@ nsContentIterator::PositionAt(nsINode* aCurNode) return NS_ERROR_FAILURE; } + // We can be at ANY node in the sequence. Need to regenerate the array of + // indexes back to the root or common parent! + AutoTArray oldParentStack; + AutoTArray newIndexes; + + // Get a list of the parents up to the root, then compare the new node with + // entries in that array until we find a match (lowest common ancestor). If + // no match, use IndexOf, take the parent, and repeat. This avoids using + // IndexOf() N times on possibly large arrays. We still end up doing it a + // fair bit. It's better to use Clone() if possible. + + // we know the depth we're down (though we may not have started at the top). + oldParentStack.SetCapacity(mIndexes.Length() + 1); + + // We want to loop mIndexes.Length() + 1 times here, because we want to make + // sure we include mCommonParent in the oldParentStack, for use in the next + // for loop, and mIndexes only has entries for nodes from tempNode up through + // an ancestor of tempNode that's a child of mCommonParent. + for (int32_t i = mIndexes.Length() + 1; i > 0 && tempNode; i--) { + // Insert at head since we're walking up + oldParentStack.InsertElementAt(0, tempNode); + + nsINode* parent = tempNode->GetParentNode(); + + if (NS_WARN_IF(!parent)) { + // this node has no parent, and thus no index + break; + } + + if (parent == mCurNode) { + // The position was moved to a parent of the current position. All we + // need to do is drop some indexes. Shortcut here. + mIndexes.RemoveElementsAt(mIndexes.Length() - oldParentStack.Length(), + oldParentStack.Length()); + mIsDone = false; + return NS_OK; + } + tempNode = parent; + } + + // Ok. We have the array of old parents. Look for a match. + while (newCurNode) { + nsINode* parent = newCurNode->GetParentNode(); + + if (NS_WARN_IF(!parent)) { + // this node has no parent, and thus no index + break; + } + + int32_t indx = parent->IndexOf(newCurNode); + NS_WARNING_ASSERTION(indx >= 0, "bad indx"); + + // insert at the head! + newIndexes.InsertElementAt(0, indx); + + // look to see if the parent is in the stack + indx = oldParentStack.IndexOf(parent); + if (indx >= 0) { + // ok, the parent IS on the old stack! Rework things. We want + // newIndexes to replace all nodes equal to or below the match. Note + // that index oldParentStack.Length() - 1 is the last node, which is one + // BELOW the last index in the mIndexes stack. In other words, we want + // to remove elements starting at index (indx + 1). + int32_t numToDrop = oldParentStack.Length() - (1 + indx); + if (numToDrop > 0) { + mIndexes.RemoveElementsAt(mIndexes.Length() - numToDrop, numToDrop); + } + mIndexes.AppendElements(newIndexes); + + break; + } + newCurNode = parent; + } + + // phew! + mIsDone = false; return NS_OK; }