Bug 1472529: More nsFind cleanup. r=mats
authorEmilio Cobos Álvarez <emilio@crisal.io>
Sun, 01 Jul 2018 02:17:10 +0200
changeset 424773 b886d32b447e6f3f999e733ab4b3ad25b529cedb
parent 424772 83489aa3a90d4106cafce8c7d36c24e8d9109707
child 424774 9136af7a5017897cf5ed371b5b595e6d864b261e
push id34223
push useraiakab@mozilla.com
push dateTue, 03 Jul 2018 08:56:10 +0000
treeherdermozilla-central@a0e47ebc4c06 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmats
bugs1472529
milestone63.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 1472529: More nsFind cleanup. r=mats The general setup is that the State struct is used to iterate over text nodes explicitly, and keeps references to the ranges so that we don't need to pass all them around everywhere. We need to teach nsFindContentIterator to rewind into NAC to be able to get rid of mIterNode, which was getting out of sync when we failed to rewind to the anchor node. MozReview-Commit-ID: 5czYADrm1WX
toolkit/components/find/nsFind.cpp
toolkit/components/find/nsFind.h
toolkit/content/tests/chrome/test_bug451540.xul
--- a/toolkit/components/find/nsFind.cpp
+++ b/toolkit/components/find/nsFind.cpp
@@ -20,18 +20,20 @@
 #include "nsServiceManagerUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
 #include "nsRange.h"
 #include "nsContentUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/TextEditor.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 // Yikes!  Casting a char to unichar can fill with ones!
 #define CHAR_TO_UNICHAR(c) ((char16_t)(unsigned char)c)
 
 static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID);
 static NS_DEFINE_CID(kCPreContentIteratorCID, NS_PRECONTENTITERATOR_CID);
 
 #define CH_QUOTE ((char16_t)0x22)
@@ -116,16 +118,17 @@ public:
   virtual void First() override;
   virtual void Last() override;
   virtual void Next() override;
   virtual void Prev() override;
   virtual nsINode* GetCurrentNode() override;
   virtual bool IsDone() override;
   virtual nsresult PositionAt(nsINode* aCurNode) override;
 
+  void Reset();
 protected:
   virtual ~nsFindContentIterator() {}
 
 private:
   static already_AddRefed<nsRange> CreateRange(nsINode* aNode)
   {
     RefPtr<nsRange> range = new nsRange(aNode);
     range->SetMaySpanAnonymousSubtrees(true);
@@ -140,17 +143,16 @@ private:
   int32_t mStartOffset;
   nsCOMPtr<nsINode> mEndNode;
   int32_t mEndOffset;
 
   nsCOMPtr<nsIContent> mStartOuterContent;
   nsCOMPtr<nsIContent> mEndOuterContent;
   bool mFindBackward;
 
-  void Reset();
   void MaybeSetupInnerIterator();
   void SetupInnerIterator(nsIContent* aContent);
 };
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFindContentIterator)
   NS_INTERFACE_MAP_ENTRY(nsIContentIterator)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -247,27 +249,49 @@ bool
 nsFindContentIterator::IsDone()
 {
   if (mInnerIterator && !mInnerIterator->IsDone()) {
     return false;
   }
   return mOuterIterator->IsDone();
 }
 
+static nsIContent&
+AnonymousSubtreeRootParent(nsINode& aNode)
+{
+  MOZ_ASSERT(aNode.IsInNativeAnonymousSubtree());
+
+  nsIContent* current = aNode.GetParent();
+  while (current->IsInNativeAnonymousSubtree()) {
+    current = current->GetParent();
+    MOZ_ASSERT(current, "huh?");
+  }
+  return *current;
+}
+
 nsresult
 nsFindContentIterator::PositionAt(nsINode* aCurNode)
 {
-  nsINode* oldNode = mOuterIterator->GetCurrentNode();
   nsresult rv = mOuterIterator->PositionAt(aCurNode);
   if (NS_SUCCEEDED(rv)) {
     MaybeSetupInnerIterator();
-  } else {
-    mOuterIterator->PositionAt(oldNode);
-    if (mInnerIterator) {
-      rv = mInnerIterator->PositionAt(aCurNode);
+    return rv;
+  }
+
+  // If this failed, it means that aCurNode is necessarily anonymous.
+  nsIContent& nonAnonNode = AnonymousSubtreeRootParent(*aCurNode);
+  SetupInnerIterator(&nonAnonNode);
+  MOZ_ASSERT(mInnerIterator, "How did we have an anonymous node otherwise?");
+  rv = mInnerIterator->PositionAt(aCurNode);
+  MOZ_ASSERT(NS_SUCCEEDED(rv));
+  if (!mOuterIterator->IsDone()) {
+    if (mFindBackward) {
+      mOuterIterator->Last();
+    } else {
+      mOuterIterator->First();
     }
   }
   return rv;
 }
 
 void
 nsFindContentIterator::Reset()
 {
@@ -452,64 +476,16 @@ NS_NewFindContentIterator(bool aFindBack
 
   nsFindContentIterator* it = new nsFindContentIterator(aFindBackward);
   if (!it) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return it->QueryInterface(NS_GET_IID(nsIContentIterator), (void**)aResult);
 }
 
-struct nsFind::State final
-{
-  // Disallow copying because copying the iterator would be a lie.
-  State(const State&) = delete;
-  State() = default;
-
-  int32_t mIterOffset = 0;
-
-  // TODO(emilio): I'm reasonably sure these can be weak pointers, since we
-  // don't mutate the DOM.
-  nsCOMPtr<nsINode> mIterNode;
-  nsCOMPtr<nsIContent> mLastBlockParent;
-
-  RefPtr<nsFindContentIterator> mIterator;
-};
-
-class MOZ_STACK_CLASS nsFind::StateRestorer final
-{
-public:
-  explicit StateRestorer(State& aState)
-    : mState(aState)
-    , mIterOffset(aState.mIterOffset)
-    , mIterNode(aState.mIterNode)
-    , mCurrNode(aState.mIterator->GetCurrentNode())
-    , mLastBlockParent(aState.mLastBlockParent)
-  {
-  }
-
-  ~StateRestorer()
-  {
-    mState.mIterOffset = mIterOffset;
-    mState.mIterNode = mIterNode;
-    mState.mLastBlockParent = mLastBlockParent;
-    mState.mIterator->PositionAt(mCurrNode);
-  }
-
-private:
-  State& mState;
-
-  int32_t mIterOffset;
-
-  // TODO(emilio): I'm reasonably sure these can be weak pointers, since we
-  // don't mutate the DOM.
-  nsCOMPtr<nsINode> mIterNode;
-  nsCOMPtr<nsINode> mCurrNode;
-  nsCOMPtr<nsIContent> mLastBlockParent;
-};
-
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFind)
   NS_INTERFACE_MAP_ENTRY(nsIFind)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFind)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFind)
 
@@ -526,17 +502,17 @@ nsFind::~nsFind() = default;
 
 #ifdef DEBUG_FIND
 #define DEBUG_FIND_PRINTF(...) printf(__VA_ARGS__)
 #else
 #define DEBUG_FIND_PRINTF(...) /* nothing */
 #endif
 
 static void
-DumpNode(nsINode* aNode)
+DumpNode(const nsINode* aNode)
 {
 #ifdef DEBUG_FIND
   if (!aNode) {
     printf(">>>> Node: NULL\n");
     return;
   }
   nsString nodeName = aNode->NodeName();
   if (aNode->IsText()) {
@@ -547,17 +523,17 @@ DumpNode(nsINode* aNode)
            NS_LossyConvertUTF16toASCII(newText).get());
   } else {
     printf(">>>> Node: %s\n", NS_LossyConvertUTF16toASCII(nodeName).get());
   }
 #endif
 }
 
 static bool
-IsBlockNode(nsIContent* aContent)
+IsBlockNode(const nsIContent* aContent)
 {
   if (aContent->IsElement() && aContent->AsElement()->IsDisplayContents()) {
     return false;
   }
 
   // FIXME(emilio): This is dubious...
   if (aContent->IsAnyOfHTMLElements(nsGkAtoms::img,
                                     nsGkAtoms::hr,
@@ -566,51 +542,50 @@ IsBlockNode(nsIContent* aContent)
     return true;
   }
 
   nsIFrame* frame = aContent->GetPrimaryFrame();
   return frame && frame->StyleDisplay()->IsBlockOutsideStyle();
 }
 
 static bool
-IsDisplayedNode(nsINode* aNode)
+IsDisplayedNode(const nsINode* aNode)
 {
   if (!aNode->IsContent()) {
     return false;
   }
 
-  nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
-  if (!frame) {
-    // No frame! Not visible then, unless it's display: contents.
-    return aNode->IsElement() && aNode->AsElement()->IsDisplayContents();
+  if (aNode->AsContent()->GetPrimaryFrame()) {
+    return true;
   }
 
-  return true;
+  // If there's no frame, it's not displayed, unless it's display: contents.
+  return aNode->IsElement() && aNode->AsElement()->IsDisplayContents();
 }
 
 static bool
-IsVisibleNode(nsINode* aNode)
+IsVisibleNode(const nsINode* aNode)
 {
   if (!IsDisplayedNode(aNode)) {
     return false;
   }
 
   nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
   if (!frame) {
     // display: contents
     return true;
   }
 
   return frame->StyleVisibility()->IsVisible();
 }
 
 static bool
-SkipNode(nsIContent* aContent)
+SkipNode(const nsIContent* aContent)
 {
-  nsIContent* content = aContent;
+  const nsIContent* content = aContent;
   while (content) {
     if (!IsDisplayedNode(content) ||
         content->IsComment() ||
         content->IsAnyOfHTMLElements(nsGkAtoms::script,
                                      nsGkAtoms::noframes,
                                      nsGkAtoms::select)) {
       DEBUG_FIND_PRINTF("Skipping node: ");
       DumpNode(content);
@@ -623,64 +598,230 @@ SkipNode(nsIContent* aContent)
     }
 
     content = content->GetParent();
   }
 
   return false;
 }
 
-static nsIContent*
-GetBlockParent(nsINode* aNode)
+static const nsIContent*
+GetBlockParent(const Text* aNode)
 {
-  if (!aNode) {
-    return nullptr;
-  }
   // FIXME(emilio): This should use GetFlattenedTreeParent instead to properly
   // handle Shadow DOM.
-  for (nsIContent* current = aNode->GetParent(); current;
+  for (const nsIContent* current = aNode->GetParent(); current;
        current = current->GetParent()) {
     if (IsBlockNode(current)) {
       return current;
     }
   }
   return nullptr;
 }
 
-nsresult
-nsFind::InitIterator(State& aState,
-                     nsINode* aStartNode,
-                     int32_t aStartOffset,
-                     nsINode* aEndNode,
-                     int32_t aEndOffset) const
+struct nsFind::State final
 {
-  if (!aState.mIterator) {
-    aState.mIterator = new nsFindContentIterator(mFindBackward);
+  // Disallow copying because copying the iterator would be a lie.
+  State(const State&) = delete;
+
+  State(bool aFindBackward,
+        const nsRange& aSearchRange,
+        const nsRange& aStartPoint,
+        const nsRange& aEndPoint)
+    : mFindBackward(aFindBackward)
+    , mInitialized(false)
+    , mIterOffset(-1)
+    , mLastBlockParent(nullptr)
+    , mSearchRange(aSearchRange)
+    , mStartPoint(aStartPoint)
+    , mEndPoint(aEndPoint)
+  {
+  }
+
+  Text* GetCurrentNode() const
+  {
+    MOZ_ASSERT(mInitialized);
+    nsINode* node = mIterator->GetCurrentNode();
+    MOZ_ASSERT(!node || node->IsText());
+    return node ? node->GetAsText() : nullptr;
+  }
+
+  Text* GetNextNode()
+  {
+    if (MOZ_UNLIKELY(!mInitialized)) {
+      Initialize();
+    } else {
+      Advance();
+      mIterOffset = -1; // mIterOffset only really applies to the first node.
+    }
+    return GetCurrentNode();
   }
 
-  NS_ENSURE_ARG_POINTER(aStartNode);
-  NS_ENSURE_ARG_POINTER(aEndNode);
+  // Gets the next non-empty text fragment in the same block, starting by the
+  // _next_ node.
+  const nsTextFragment* GetNextNonEmptyTextFragmentInSameBlock();
+
+private:
+  // Advance to the next visible text-node.
+  void Advance();
+  // Sets up the first node position and offset.
+  void Initialize();
+
+  const bool mFindBackward;
+
+  // Whether we've called GetNextNode() at least once.
+  bool mInitialized;
+
+public:
+  // An offset into the text of the first node we're starting to search at.
+  int mIterOffset;
+  const nsIContent* mLastBlockParent;
+  RefPtr<nsFindContentIterator> mIterator;
+
+  // These are only needed for the first GetNextNode() call.
+  const nsRange& mSearchRange;
+  const nsRange& mStartPoint;
+  const nsRange& mEndPoint;
+};
+
+void
+nsFind::State::Advance()
+{
+  MOZ_ASSERT(mInitialized);
+
+  while (true) {
+    if (mFindBackward) {
+      mIterator->Prev();
+    } else {
+      mIterator->Next();
+    }
 
-  DEBUG_FIND_PRINTF("InitIterator search range:\n");
-  DEBUG_FIND_PRINTF(" -- start %d, ", aStartOffset);
-  DumpNode(aStartNode);
-  DEBUG_FIND_PRINTF(" -- end %d, ", aEndOffset);
-  DumpNode(aEndNode);
+    nsINode* current = mIterator->GetCurrentNode();
+    if (!current) {
+      return;
+    }
+
+    if (!current->IsContent() || SkipNode(current->AsContent())) {
+      continue;
+    }
+
+    if (current->IsText()) {
+      return;
+    }
+  }
+}
+
+void
+nsFind::State::Initialize()
+{
+  MOZ_ASSERT(!mInitialized);
+  mInitialized = true;
+  mIterOffset = mFindBackward ? -1 : 0;
+  mIterator = new nsFindContentIterator(mFindBackward);
+
+  // Set up ourselves at the first node we want to start searching at.
+  {
+    nsINode* startNode;
+    nsINode* endNode;
+    uint32_t startOffset;
+    uint32_t endOffset;
+    if (mFindBackward) {
+      startNode = mSearchRange.GetStartContainer();
+      startOffset = mSearchRange.StartOffset();
+      endNode = mStartPoint.GetEndContainer();
+      endOffset = mStartPoint.EndOffset();
+    } else {
+      startNode = mStartPoint.GetStartContainer();
+      startOffset = mStartPoint.StartOffset();
+      endNode = mEndPoint.GetEndContainer();
+      endOffset = mEndPoint.EndOffset();
+    }
 
-  nsresult rv =
-    aState.mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (mFindBackward) {
-    aState.mIterator->Last();
-  } else {
-    aState.mIterator->First();
+    nsresult rv =
+      mIterator->Init(startNode,
+                      static_cast<int32_t>(startOffset),
+                      endNode,
+                      static_cast<int32_t>(endOffset));
+    if (NS_FAILED(rv)) {
+      return;
+    }
+
+    mIterator->Reset();
+  }
+
+  nsINode* current = mIterator->GetCurrentNode();
+  if (!current) {
+    return;
+  }
+
+  if (!current->IsText() || SkipNode(current->AsText())) {
+    Advance();
+    return;
+  }
+
+  mLastBlockParent = GetBlockParent(current->AsText());
+
+  // We found a text node at the start, find the offset if we can.
+  nsINode* beginning = mFindBackward ? mStartPoint.GetEndContainer()
+                                     : mStartPoint.GetStartContainer();
+  if (current != beginning) {
+    return;
   }
-  return NS_OK;
+
+  mIterOffset = mFindBackward ? mStartPoint.EndOffset()
+                              : mStartPoint.StartOffset();
+}
+
+const nsTextFragment*
+nsFind::State::GetNextNonEmptyTextFragmentInSameBlock()
+{
+  while (true) {
+    const Text* current = GetNextNode();
+    if (!current) {
+      return nullptr;
+    }
+
+    const nsIContent* blockParent = GetBlockParent(current);
+    if (!blockParent || blockParent != mLastBlockParent) {
+      return nullptr;
+    }
+
+    const nsTextFragment& frag = current->TextFragment();
+    if (frag.GetLength()) {
+      return &frag;
+    }
+  }
 }
 
+class MOZ_STACK_CLASS nsFind::StateRestorer final
+{
+public:
+  explicit StateRestorer(State& aState)
+    : mState(aState)
+    , mIterOffset(aState.mIterOffset)
+    , mCurrNode(aState.mIterator->GetCurrentNode())
+    , mLastBlockParent(aState.mLastBlockParent)
+  {
+  }
+
+  ~StateRestorer()
+  {
+    mState.mIterOffset = mIterOffset;
+    mState.mIterator->PositionAt(mCurrNode);
+    mState.mLastBlockParent = mLastBlockParent;
+  }
+
+private:
+  State& mState;
+
+  int32_t mIterOffset;
+  nsINode* mCurrNode;
+  const nsIContent* mLastBlockParent;
+};
+
 NS_IMETHODIMP
 nsFind::GetFindBackwards(bool* aFindBackward)
 {
   if (!aFindBackward) {
     return NS_ERROR_NULL_POINTER;
   }
 
   *aFindBackward = mFindBackward;
@@ -737,181 +878,40 @@ nsFind::SetEntireWord(bool aEntireWord)
 // iterator to go back to a previously visited node, so we always save the
 // "match anchor" node and offset.
 //
 // Text nodes store their text in an nsTextFragment, which is effectively a
 // union of a one-byte string or a two-byte string. Single and double strings
 // are intermixed in the dom. We don't have string classes which can deal with
 // intermixed strings, so all the handling is done explicitly here.
 
-nsresult
-nsFind::NextNode(State& aState,
-                 const nsRange* aSearchRange,
-                 const nsRange* aStartPoint,
-                 const nsRange* aEndPoint) const
-{
-  nsresult rv;
-
-  nsCOMPtr<nsIContent> content;
-
-  if (!aState.mIterator) {
-    // If we are continuing, that means we have a match in progress. In that
-    // case, we want to continue from the end point (where we are now) to the
-    // beginning/end of the search range.
-    nsCOMPtr<nsINode> startNode;
-    nsCOMPtr<nsINode> endNode;
-    uint32_t startOffset, endOffset;
-    if (mFindBackward) {
-      startNode = aSearchRange->GetStartContainer();
-      startOffset = aSearchRange->StartOffset();
-      endNode = aStartPoint->GetEndContainer();
-      endOffset = aStartPoint->EndOffset();
-      // XXX Needs work: Problem with this approach: if there is a match which
-      // starts just before the current selection and continues into the
-      // selection, we will miss it, because our search algorithm only starts
-      // searching from the end of the word, so we would have to search the
-      // current selection but discount any matches that fall entirely inside
-      // it.
-    } else { // forward
-      startNode = aStartPoint->GetStartContainer();
-      startOffset = aStartPoint->StartOffset();
-      endNode = aEndPoint->GetEndContainer();
-      endOffset = aEndPoint->EndOffset();
-    }
-
-    rv = InitIterator(aState,
-                      startNode,
-                      static_cast<int32_t>(startOffset),
-                      endNode,
-                      static_cast<int32_t>(endOffset));
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!aStartPoint) {
-      aStartPoint = aSearchRange;
-    }
-
-    content = do_QueryInterface(aState.mIterator->GetCurrentNode());
-    DEBUG_FIND_PRINTF(":::::: Got the first node ");
-    DumpNode(content);
-
-    if (content && content->IsText() && !SkipNode(content)) {
-      aState.mIterNode = content;
-      // Also set mIterOffset if appropriate:
-      nsCOMPtr<nsINode> node;
-      if (mFindBackward) {
-        node = aStartPoint->GetEndContainer();
-        if (aState.mIterNode == node) {
-          uint32_t endOffset = aStartPoint->EndOffset();
-          aState.mIterOffset = static_cast<int32_t>(endOffset);
-        } else {
-          aState.mIterOffset = -1; // sign to start from end
-        }
-      } else {
-        node = aStartPoint->GetStartContainer();
-        if (aState.mIterNode == node) {
-          uint32_t startOffset = aStartPoint->StartOffset();
-          aState.mIterOffset = static_cast<int32_t>(startOffset);
-        } else {
-          aState.mIterOffset = 0;
-        }
-      }
-      DEBUG_FIND_PRINTF("Setting initial offset to %d\n", aState.mIterOffset);
-      return NS_OK;
-    }
-  }
-
-  while (true) {
-    if (mFindBackward) {
-      aState.mIterator->Prev();
-    } else {
-      aState.mIterator->Next();
-    }
-
-    content = do_QueryInterface(aState.mIterator->GetCurrentNode());
-    if (!content) {
-      break;
-    }
-
-    DEBUG_FIND_PRINTF(":::::: Got another node ");
-    DumpNode(content);
-
-    // If we ever cross a block node, we might want to reset the match anchor:
-    // we don't match patterns extending across block boundaries. But we can't
-    // depend on this test here now, because the iterator doesn't give us the
-    // parent going in and going out, and we need it both times to depend on
-    // this.
-    //if (IsBlockNode(content))
-
-    // Now see if we need to skip this node -- e.g. is it part of a script or
-    // other invisible node? Note that we don't ask for CSS information; a node
-    // can be invisible due to CSS, and we'd still find it.
-    if (SkipNode(content)) {
-      continue;
-    }
-
-    if (content->IsText()) {
-      break;
-    }
-    DEBUG_FIND_PRINTF("Not a text node: ");
-    DumpNode(content);
-  }
-
-  // FIXME(emilio): Is there a case for mIterNode !=
-  // mIterator->GetCurrentNode()? If not, why does it exist?
-  aState.mIterNode = content;
-  aState.mIterOffset = -1;
-
-  DEBUG_FIND_PRINTF("Iterator gave: ");
-  DumpNode(aState.mIterNode);
-  return NS_OK;
-}
-
 char16_t
-nsFind::PeekNextChar(State& aState,
-                     const nsRange* aSearchRange,
-                     const nsRange* aStartPoint,
-                     const nsRange* aEndPoint) const
+nsFind::PeekNextChar(State& aState) const
 {
   // We need to restore the necessary state before this function returns.
   StateRestorer restorer(aState);
 
-  const nsTextFragment *frag;
-  int32_t fragLen;
-
-  // Loop through non-block nodes until we find one that's not empty.
-  do {
-    NextNode(aState, aSearchRange, aStartPoint, aEndPoint);
-
-    // Get the text content:
-    nsCOMPtr<nsIContent> tc = do_QueryInterface(aState.mIterNode);
+  const nsTextFragment* frag = aState.GetNextNonEmptyTextFragmentInSameBlock();
+  if (!frag) {
+    return L'\0';
+  }
 
-    // Get the block parent.
-    nsIContent* blockParent = GetBlockParent(aState.mIterNode);
-    if (!blockParent)
-      return L'\0';
-
-    // If out of nodes or in new parent.
-    if (!aState.mIterNode || !tc || blockParent != aState.mLastBlockParent)
-      return L'\0';
-
-    frag = tc->GetText();
-    fragLen = frag->GetLength();
-  } while (fragLen <= 0);
-
-  const char16_t *t2b = nullptr;
-  const char *t1b = nullptr;
+  const char16_t* t2b = nullptr;
+  const char* t1b = nullptr;
 
   if (frag->Is2b()) {
     t2b = frag->Get2b();
   } else {
     t1b = frag->Get1b();
   }
 
-  // Index of char to return.
-  int32_t index = mFindBackward ? fragLen - 1 : 0;
+  uint32_t len = frag->GetLength();
+  MOZ_ASSERT(len);
 
+  int32_t index = mFindBackward ? len - 1 : 0;
   return t1b ? CHAR_TO_UNICHAR(t1b[index]) : t2b[index];
 }
 
 #define NBSP_CHARCODE (CHAR_TO_UNICHAR(160))
 #define IsSpace(c) (nsCRT::IsAsciiSpace(c) || (c) == NBSP_CHARCODE)
 #define OVERFLOW_PINDEX (mFindBackward ? pindex < 0 : pindex > patLen)
 #define DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen)
 #define ALMOST_DONE_WITH_PINDEX (mFindBackward ? pindex <= 0 : pindex >= patLen - 1)
@@ -954,117 +954,103 @@ nsFind::Find(const char16_t* aPatText, n
   int32_t pindex = (mFindBackward ? patLen : 0);
 
   // Current offset into the fragment
   int32_t findex = 0;
 
   // Direction to move pindex and ptr*
   int incr = (mFindBackward ? -1 : 1);
 
-  nsCOMPtr<nsIContent> tc;
   const nsTextFragment* frag = nullptr;
   int32_t fragLen = 0;
 
   // Pointers into the current fragment:
   const char16_t* t2b = nullptr;
   const char* t1b = nullptr;
 
   // Keep track of when we're in whitespace:
   // (only matters when we're matching)
   bool inWhitespace = false;
   // Keep track of whether the previous char was a word-breaking one.
   bool wordBreakPrev = false;
 
   // Place to save the range start point in case we find a match:
-  nsCOMPtr<nsINode> matchAnchorNode;
+  Text* matchAnchorNode = nullptr;
   int32_t matchAnchorOffset = 0;
 
   // Get the end point, so we know when to end searches:
   nsCOMPtr<nsINode> endNode = aEndPoint->GetEndContainer();;
   uint32_t endOffset = aEndPoint->EndOffset();
 
   char16_t c = 0;
   char16_t patc = 0;
   char16_t prevChar = 0;
   char16_t prevCharInMatch = 0;
 
-  State state;
+  State state(mFindBackward, *aSearchRange, *aStartPoint, *aEndPoint);
+  Text* current = nullptr;
 
-  while (1) {
+  while (true) {
     DEBUG_FIND_PRINTF("Loop ...\n");
 
     // If this is our first time on a new node, reset the pointers:
     if (!frag) {
-
-      tc = nullptr;
-      NextNode(state, aSearchRange, aStartPoint, aEndPoint);
-      if (!state.mIterNode) { // Out of nodes
+      current = state.GetNextNode();
+      if (!current) {
         return NS_OK;
       }
 
       // We have a new text content. If its block parent is different from the
       // block parent of the last text content, then we need to clear the match
       // since we don't want to find across block boundaries.
-      nsIContent* blockParent = GetBlockParent(state.mIterNode);
+      const nsIContent* blockParent = GetBlockParent(current);
       DEBUG_FIND_PRINTF("New node: old blockparent = %p, new = %p\n",
-                        (void*)state.mLastBlockParent.get(), (void*)blockParent);
+                        (void*)state.mLastBlockParent, (void*)blockParent);
       if (blockParent != state.mLastBlockParent) {
         DEBUG_FIND_PRINTF("Different block parent!\n");
         state.mLastBlockParent = blockParent;
         // End any pending match:
         matchAnchorNode = nullptr;
         matchAnchorOffset = 0;
         c = 0;
         prevChar = 0;
         prevCharInMatch = 0;
         pindex = (mFindBackward ? patLen : 0);
         inWhitespace = false;
       }
 
-      // Get the text content:
-      tc = do_QueryInterface(state.mIterNode);
-      if (!tc || !(frag = tc->GetText())) { // Out of nodes
-        return NS_OK;
-      }
-
+      frag = &current->TextFragment();
       fragLen = frag->GetLength();
 
       // Set our starting point in this node. If we're going back to the anchor
       // node, which means that we just ended a partial match, use the saved
       // offset:
-      if (state.mIterNode == matchAnchorNode) {
+      //
+      // FIXME(emilio): How could current ever be the anchor node, if we had not
+      // seen current so far?
+      if (current == matchAnchorNode) {
         findex = matchAnchorOffset + (mFindBackward ? 1 : 0);
-      }
-
-      // state.mIterOffset, if set, is the range's idea of an offset, and points
-      // between characters. But when translated to a string index, it points to
-      // a character. If we're going backward, this is one character too late
-      // and we'll match part of our previous pattern.
-      else if (state.mIterOffset >= 0) {
+      } else if (state.mIterOffset >= 0) {
         findex = state.mIterOffset - (mFindBackward ? 1 : 0);
-      }
-
-      // Otherwise, just start at the appropriate end of the fragment:
-      else if (mFindBackward) {
-        findex = fragLen - 1;
       } else {
-        findex = 0;
+        findex = mFindBackward ? (fragLen - 1) : 0;
       }
 
       // Offset can only apply to the first node:
       state.mIterOffset = -1;
 
+      DEBUG_FIND_PRINTF("Starting from offset %d of %d\n", findex, fragLen);
+
       // If this is outside the bounds of the string, then skip this node:
       if (findex < 0 || findex > fragLen - 1) {
         DEBUG_FIND_PRINTF("At the end of a text node -- skipping to the next\n");
-        frag = 0;
+        frag = nullptr;
         continue;
       }
 
-      DEBUG_FIND_PRINTF("Starting from offset %d\n", findex);
       if (frag->Is2b()) {
         t2b = frag->Get2b();
         t1b = nullptr;
 #ifdef DEBUG_FIND
         nsAutoString str2(t2b, fragLen);
         DEBUG_FIND_PRINTF("2 byte, '%s'\n", NS_LossyConvertUTF16toASCII(str2).get());
 #endif
       } else {
@@ -1086,17 +1072,17 @@ nsFind::Find(const char16_t* aPatText, n
         // Done with this node.  Pull a new one.
         frag = nullptr;
         continue;
       }
     }
 
     // Have we gone past the endpoint yet? If we have, and we're not in the
     // middle of a match, return.
-    if (state.mIterNode == endNode &&
+    if (state.GetCurrentNode() == endNode &&
         ((mFindBackward && findex < static_cast<int32_t>(endOffset)) ||
          (!mFindBackward && findex > static_cast<int32_t>(endOffset)))) {
       return NS_OK;
     }
 
     // Save the previous character for word boundary detection
     prevChar = c;
     // The two characters we'll be comparing:
@@ -1191,86 +1177,79 @@ nsFind::Find(const char16_t* aPatText, n
       if (inWhitespace) {
         DEBUG_FIND_PRINTF("YES (whitespace)(%d of %d)\n", pindex, patLen);
       } else {
         DEBUG_FIND_PRINTF("YES! '%c' == '%c' (%d of %d)\n", c, patc, pindex, patLen);
       }
 
       // Save the range anchors if we haven't already:
       if (!matchAnchorNode) {
-        matchAnchorNode = state.mIterNode;
+        matchAnchorNode = state.GetCurrentNode();
         matchAnchorOffset = findex;
       }
 
       // Are we done?
       if (DONE_WITH_PINDEX) {
         // Matched the whole string!
         DEBUG_FIND_PRINTF("Found a match!\n");
 
         // Make the range:
-        nsCOMPtr<nsINode> startParent;
-        nsCOMPtr<nsINode> endParent;
-
         // Check for word break (if necessary)
         if (mWordBreaker) {
           int32_t nextfindex = findex + incr;
 
           char16_t nextChar;
           // If still in array boundaries, get nextChar.
           if (mFindBackward ? (nextfindex >= 0) : (nextfindex < fragLen))
             nextChar = (t2b ? t2b[nextfindex] : CHAR_TO_UNICHAR(t1b[nextfindex]));
           // Get next character from the next node.
           else
-            nextChar = PeekNextChar(state, aSearchRange, aStartPoint, aEndPoint);
+            nextChar = PeekNextChar(state);
 
           if (nextChar == NBSP_CHARCODE)
             nextChar = CHAR_TO_UNICHAR(' ');
 
           // If a word break isn't there when it needs to be, reset search.
           if (!mWordBreaker->BreakInBetween(&c, 1, &nextChar, 1)) {
             matchAnchorNode = nullptr;
             continue;
           }
         }
 
-        RefPtr<nsRange> range = new nsRange(tc);
-        if (range) {
-          int32_t matchStartOffset, matchEndOffset;
-          // convert char index to range point:
-          int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0);
-          if (mFindBackward) {
-            startParent = tc;
-            endParent = matchAnchorNode;
-            matchStartOffset = findex;
-            matchEndOffset = mao;
-          } else {
-            startParent = matchAnchorNode;
-            endParent = tc;
-            matchStartOffset = mao;
-            matchEndOffset = findex + 1;
-          }
-          if (startParent && endParent &&
-              IsVisibleNode(startParent) && IsVisibleNode(endParent)) {
-            range->SetStart(*startParent, matchStartOffset, IgnoreErrors());
-            range->SetEnd(*endParent, matchEndOffset, IgnoreErrors());
-            *aRangeRet = range.get();
-            NS_ADDREF(*aRangeRet);
-          } else {
-            // This match is no good -- invisible or bad range
-            startParent = nullptr;
-          }
+        RefPtr<nsRange> range = new nsRange(current);
+
+        int32_t matchStartOffset;
+        int32_t matchEndOffset;
+        // convert char index to range point:
+        int32_t mao = matchAnchorOffset + (mFindBackward ? 1 : 0);
+        Text* startParent;
+        Text* endParent;
+        if (mFindBackward) {
+          startParent = current;
+          endParent = matchAnchorNode;
+          matchStartOffset = findex;
+          matchEndOffset = mao;
+        } else {
+          startParent = matchAnchorNode;
+          endParent = current;
+          matchStartOffset = mao;
+          matchEndOffset = findex + 1;
+        }
+        if (startParent && endParent &&
+            IsVisibleNode(startParent) && IsVisibleNode(endParent)) {
+          range->SetStart(*startParent, matchStartOffset, IgnoreErrors());
+          range->SetEnd(*endParent, matchEndOffset, IgnoreErrors());
+          *aRangeRet = range.get();
+          NS_ADDREF(*aRangeRet);
+        } else {
+          // This match is no good -- invisible or bad range
+          startParent = nullptr;
         }
 
         if (startParent) {
-          // If startParent == nullptr, we didn't successfully make range
-          // or, we didn't make a range because the start or end node were
-          // invisible. Reset the offset to the other end of the found string:
-          state.mIterOffset = findex + (mFindBackward ? 1 : 0);
-          DEBUG_FIND_PRINTF("mIterOffset = %d, mIterNode = ", state.mIterOffset);
-          DumpNode(state.mIterNode);
           return NS_OK;
         }
         // This match is no good, continue on in document
         matchAnchorNode = nullptr;
       }
 
       if (matchAnchorNode) {
         // Not done, but still matching. Advance and loop around for the next
@@ -1291,30 +1270,27 @@ nsFind::Find(const char16_t* aPatText, n
     // If we didn't match, go back to the beginning of patStr, and set findex
     // back to the next char after we started the current match.
     if (matchAnchorNode) { // we're ending a partial match
       findex = matchAnchorOffset;
       state.mIterOffset = matchAnchorOffset;
       // +incr will be added to findex when we continue
 
       // Are we going back to a previous node?
-      if (matchAnchorNode != state.mIterNode) {
-        nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode));
-        DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED;
-        if (content) {
-          rv = state.mIterator->PositionAt(content);
-        }
-        frag = 0;
-        NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!");
+      if (matchAnchorNode != state.GetCurrentNode()) {
+        frag = nullptr;
+
+        DebugOnly<nsresult> rv = state.mIterator->PositionAt(matchAnchorNode);
+        MOZ_ASSERT(NS_SUCCEEDED(rv), "nsFindContentIterator failed to rewind");
         DEBUG_FIND_PRINTF("Repositioned anchor node\n");
       }
       DEBUG_FIND_PRINTF("Ending a partial match; findex -> %d, mIterOffset -> %d\n",
                         findex, state.mIterOffset);
     }
     matchAnchorNode = nullptr;
     matchAnchorOffset = 0;
     inWhitespace = false;
-    pindex = (mFindBackward ? patLen : 0);
+    pindex = mFindBackward ? patLen : 0;
     DEBUG_FIND_PRINTF("Setting findex back to %d, pindex to %d\n", findex, pindex);
   }
 
   return NS_OK;
 }
--- a/toolkit/components/find/nsFind.h
+++ b/toolkit/components/find/nsFind.h
@@ -43,31 +43,15 @@ protected:
 
   // Use "find entire words" mode by setting to a word breaker or null, to
   // disable "entire words" mode.
   RefPtr<mozilla::intl::WordBreaker> mWordBreaker;
 
   struct State;
   class StateRestorer;
 
-  // Move in the right direction for our search:
-  nsresult NextNode(State&,
-                    const nsRange* aSearchRange,
-                    const nsRange* aStartPoint,
-                    const nsRange* aEndPoint) const;
-
   // Get the first character from the next node (last if mFindBackward).
   //
   // This will mutate the state, but then restore it afterwards.
-  char16_t PeekNextChar(State&,
-                        const nsRange* aSearchRange,
-                        const nsRange* aStartPoint,
-                        const nsRange* aEndPoint) const;
-
-  // The iterator we use to move through the document:
-  nsresult InitIterator(State&,
-                        nsINode* aStartNode,
-                        int32_t aStartOffset,
-                        nsINode* aEndNode,
-                        int32_t aEndOffset) const;
+  char16_t PeekNextChar(State&) const;
 };
 
 #endif // nsFind_h__
--- a/toolkit/content/tests/chrome/test_bug451540.xul
+++ b/toolkit/content/tests/chrome/test_bug451540.xul
@@ -25,16 +25,15 @@ https://bugzilla.mozilla.org/show_bug.cg
     </pre>
   </body>
 
   <script class="testbody" type="application/javascript">
     <![CDATA[
 
       /** Test for Bug 451540 **/
       SimpleTest.waitForExplicitFinish();
-      SimpleTest.expectAssertions(18, 19); // Will be fixed as part of bug 1455891.
       window.open("bug451540_window.xul", "451540test",
                   "chrome,width=600,height=600");
 
     ]]>
   </script>
 
 </window>