Bug 1470861: Make state-passing explicit in nsFind. r?mats draft
authorEmilio Cobos Álvarez <emilio@crisal.io>
Mon, 25 Jun 2018 13:09:17 +0200
changeset 810140 302ab388085afe8f56413eecefd7514bc44536f3
parent 810139 ca39a78c24ba527236c22a175544a4f245314b24
child 810141 ed707edc3378874c36a9bbf1bfb0ea4e678b941b
push id113905
push userbmo:emilio@crisal.io
push dateMon, 25 Jun 2018 12:31:46 +0000
reviewersmats
bugs1470861
milestone62.0a1
Bug 1470861: Make state-passing explicit in nsFind. r?mats Instead of tweaking member variables and resetting them afterwards, just have an object that we pass around. This makes a bit easier to reason about nsFind IMO, and makes us able to use more complex iterators that don't keep strong references to anything and that kind of stuff, since we don't keep an iterator member around, and we don't mutate the DOM from nsFind. MozReview-Commit-ID: ERDnL6Q8QTU
toolkit/components/find/nsFind.cpp
toolkit/components/find/nsFind.h
--- a/toolkit/components/find/nsFind.cpp
+++ b/toolkit/components/find/nsFind.cpp
@@ -452,31 +452,75 @@ 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 pointer, 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;
+  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)
 
-NS_IMPL_CYCLE_COLLECTION(nsFind, mLastBlockParent, mIterNode, mIterator)
+NS_IMPL_CYCLE_COLLECTION(nsFind)
 
 nsFind::nsFind()
   : mFindBackward(false)
   , mCaseSensitive(false)
   , mWordBreaker(nullptr)
-  , mIterOffset(0)
 {
 }
 
 nsFind::~nsFind() = default;
 
 #ifdef DEBUG_FIND
 #define DEBUG_FIND_PRINTF(...) printf(__VA_ARGS__)
 #else
@@ -577,43 +621,46 @@ SkipNode(nsIContent* aContent)
 
     content = content->GetParent();
   }
 
   return false;
 }
 
 nsresult
-nsFind::InitIterator(nsINode* aStartNode, int32_t aStartOffset,
-                     nsINode* aEndNode, int32_t aEndOffset)
+nsFind::InitIterator(State& aState,
+                     nsINode* aStartNode,
+                     int32_t aStartOffset,
+                     nsINode* aEndNode,
+                     int32_t aEndOffset)
 {
-  if (!mIterator) {
-    mIterator = new nsFindContentIterator(mFindBackward);
-    NS_ENSURE_TRUE(mIterator, NS_ERROR_OUT_OF_MEMORY);
+  if (!aState.mIterator) {
+    aState.mIterator = new nsFindContentIterator(mFindBackward);
   }
 
   NS_ENSURE_ARG_POINTER(aStartNode);
   NS_ENSURE_ARG_POINTER(aEndNode);
 
 #ifdef DEBUG_FIND
   DEBUG_FIND_PRINTF("InitIterator search range:\n");
   DEBUG_FIND_PRINTF(" -- start %d, ", aStartOffset);
   nsCOMPtr<nsINode> start = do_QueryInterface(aStartNode);
   DumpNode(start);
   DEBUG_FIND_PRINTF(" -- end %d, ", aEndOffset);
   nsCOMPtr<nsINode> end = do_QueryInterface(aEndNode);
   DumpNode(end);
 #endif
 
-  nsresult rv = mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset);
+  nsresult rv =
+    aState.mIterator->Init(aStartNode, aStartOffset, aEndNode, aEndOffset);
   NS_ENSURE_SUCCESS(rv, rv);
   if (mFindBackward) {
-    mIterator->Last();
+    aState.mIterator->Last();
   } else {
-    mIterator->First();
+    aState.mIterator->First();
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFind::GetFindBackwards(bool* aFindBackward)
 {
   if (!aFindBackward) {
@@ -675,25 +722,27 @@ nsFind::SetEntireWord(bool aEntireWord)
 // "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(nsRange* aSearchRange,
-                 nsRange* aStartPoint, nsRange* aEndPoint,
+nsFind::NextNode(State& aState,
+                 nsRange* aSearchRange,
+                 nsRange* aStartPoint,
+                 nsRange* aEndPoint,
                  bool aContinueOk)
 {
   nsresult rv;
 
   nsCOMPtr<nsIContent> content;
 
-  if (!mIterator || aContinueOk) {
+  if (!aState.mIterator || aContinueOk) {
     // 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 (aContinueOk) {
       DEBUG_FIND_PRINTF("Match in progress: continuing past endpoint\n");
@@ -723,61 +772,64 @@ nsFind::NextNode(nsRange* aSearchRange,
       } else { // forward
         startNode = aStartPoint->GetStartContainer();
         startOffset = aStartPoint->StartOffset();
         endNode = aEndPoint->GetEndContainer();
         endOffset = aEndPoint->EndOffset();
       }
     }
 
-    rv = InitIterator(startNode, static_cast<int32_t>(startOffset),
-                      endNode, static_cast<int32_t>(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(mIterator->GetCurrentNode());
+    content = do_QueryInterface(aState.mIterator->GetCurrentNode());
     DEBUG_FIND_PRINTF(":::::: Got the first node ");
     DumpNode(content);
 
     if (content && content->IsText() && !SkipNode(content)) {
-      mIterNode = content;
+      aState.mIterNode = content;
       // Also set mIterOffset if appropriate:
       nsCOMPtr<nsINode> node;
       if (mFindBackward) {
         node = aStartPoint->GetEndContainer();
-        if (mIterNode == node) {
+        if (aState.mIterNode == node) {
           uint32_t endOffset = aStartPoint->EndOffset();
-          mIterOffset = static_cast<int32_t>(endOffset);
+          aState.mIterOffset = static_cast<int32_t>(endOffset);
         } else {
-          mIterOffset = -1; // sign to start from end
+          aState.mIterOffset = -1; // sign to start from end
         }
       } else {
         node = aStartPoint->GetStartContainer();
-        if (mIterNode == node) {
+        if (aState.mIterNode == node) {
           uint32_t startOffset = aStartPoint->StartOffset();
-          mIterOffset = static_cast<int32_t>(startOffset);
+          aState.mIterOffset = static_cast<int32_t>(startOffset);
         } else {
-          mIterOffset = 0;
+          aState.mIterOffset = 0;
         }
       }
-      DEBUG_FIND_PRINTF("Setting initial offset to %d\n", mIterOffset);
+      DEBUG_FIND_PRINTF("Setting initial offset to %d\n", aState.mIterOffset);
       return NS_OK;
     }
   }
 
   while (true) {
     if (mFindBackward) {
-      mIterator->Prev();
+      aState.mIterator->Prev();
     } else {
-      mIterator->Next();
+      aState.mIterator->Next();
     }
 
-    content = do_QueryInterface(mIterator->GetCurrentNode());
+    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:
@@ -796,77 +848,52 @@ nsFind::NextNode(nsRange* aSearchRange,
 
     if (content->IsText()) {
       break;
     }
     DEBUG_FIND_PRINTF("Not a text node: ");
     DumpNode(content);
   }
 
-  mIterNode = content;
-  mIterOffset = -1;
+  // 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(mIterNode);
+  DumpNode(aState.mIterNode);
   return NS_OK;
 }
 
-class MOZ_STACK_CLASS PeekNextCharRestoreState final
-{
-public:
-  explicit PeekNextCharRestoreState(nsFind* aFind)
-    : mIterOffset(aFind->mIterOffset),
-      mIterNode(aFind->mIterNode),
-      mCurrNode(aFind->mIterator->GetCurrentNode()),
-      mFind(aFind)
-  {
-  }
-
-  ~PeekNextCharRestoreState()
-  {
-    mFind->mIterOffset = mIterOffset;
-    mFind->mIterNode = mIterNode;
-    mFind->mIterator->PositionAt(mCurrNode);
-  }
-
-private:
-  int32_t mIterOffset;
-  nsCOMPtr<nsINode> mIterNode;
-  nsCOMPtr<nsINode> mCurrNode;
-  RefPtr<nsFind> mFind;
-};
-
 char16_t
-nsFind::PeekNextChar(nsRange* aSearchRange,
+nsFind::PeekNextChar(State& aState,
+                     nsRange* aSearchRange,
                      nsRange* aStartPoint,
                      nsRange* aEndPoint)
 {
-  // We need to restore the necessary member variables before this function
-  // returns.
-  PeekNextCharRestoreState restoreState(this);
+  // We need to restore the necessary state before this function returns.
+  StateRestorer restorer(aState);
 
-  nsCOMPtr<nsIContent> tc;
   const nsTextFragment *frag;
   int32_t fragLen;
 
   // Loop through non-block nodes until we find one that's not empty.
   do {
-    tc = nullptr;
-    NextNode(aSearchRange, aStartPoint, aEndPoint, false);
+    NextNode(aState, aSearchRange, aStartPoint, aEndPoint, false);
 
     // Get the text content:
-    tc = do_QueryInterface(mIterNode);
+    nsCOMPtr<nsIContent> tc = do_QueryInterface(aState.mIterNode);
 
     // Get the block parent.
-    nsIContent* blockParent = GetBlockParent(mIterNode);
+    nsIContent* blockParent = GetBlockParent(aState.mIterNode);
     if (!blockParent)
       return L'\0';
 
     // If out of nodes or in new parent.
-    if (!mIterNode || !tc || (blockParent != mLastBlockParent))
+    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;
@@ -895,26 +922,16 @@ nsFind::GetBlockParent(nsINode* aNode)
        current = current->GetParent()) {
     if (IsBlockNode(current)) {
       return current;
     }
   }
   return nullptr;
 }
 
-// Call ResetAll before returning, to remove all references to external objects.
-void
-nsFind::ResetAll()
-{
-  mIterator = nullptr;
-  mLastBlockParent = nullptr;
-  mIterNode = nullptr;
-  mIterOffset = -1;
-}
-
 #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)
 
 // Take nodes out of the tree with NextNode, until null (NextNode will return 0
 // at the end of our range).
@@ -933,18 +950,16 @@ nsFind::Find(const char16_t* aPatText, n
   NS_ENSURE_ARG(aEndPoint);
   NS_ENSURE_ARG_POINTER(aRangeRet);
   *aRangeRet = 0;
 
   if (!aPatText) {
     return NS_ERROR_NULL_POINTER;
   }
 
-  ResetAll();
-
   nsAutoString patAutoStr(aPatText);
   if (!mCaseSensitive) {
     ToLowerCase(patAutoStr);
   }
 
   // Ignore soft hyphens in the pattern
   static const char kShy[] = { char(CH_SHY), 0 };
   patAutoStr.StripChars(kShy);
@@ -982,90 +997,89 @@ nsFind::Find(const char16_t* aPatText, n
   // 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;
+
   while (1) {
     DEBUG_FIND_PRINTF("Loop ...\n");
 
     // If this is our first time on a new node, reset the pointers:
     if (!frag) {
 
       tc = nullptr;
-      NextNode(aSearchRange, aStartPoint, aEndPoint, false);
-      if (!mIterNode) { // Out of nodes
+      NextNode(state, aSearchRange, aStartPoint, aEndPoint, false);
+      if (!state.mIterNode) { // Out of nodes
         // Are we in the middle of a match? If so, try again with continuation.
+        //
+        // FIXME(emilio): If we return here unconditionally, why is this useful?
+        // Shouldn't we check `state.mIterNode` again?
         if (matchAnchorNode) {
-          NextNode(aSearchRange, aStartPoint, aEndPoint, true);
+          NextNode(state, aSearchRange, aStartPoint, aEndPoint, true);
         }
-
-        // Reset the iterator, so this nsFind will be usable if the user wants
-        // to search again (from beginning/end).
-        ResetAll();
         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(mIterNode);
+      nsIContent* blockParent = GetBlockParent(state.mIterNode);
       DEBUG_FIND_PRINTF("New node: old blockparent = %p, new = %p\n",
-                        (void*)mLastBlockParent.get(), (void*)blockParent);
-      if (blockParent != mLastBlockParent) {
+                        (void*)state.mLastBlockParent.get(), (void*)blockParent);
+      if (blockParent != state.mLastBlockParent) {
         DEBUG_FIND_PRINTF("Different block parent!\n");
-        mLastBlockParent = blockParent;
+        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(mIterNode);
+      tc = do_QueryInterface(state.mIterNode);
       if (!tc || !(frag = tc->GetText())) { // Out of nodes
-        mIterator = nullptr;
-        mLastBlockParent = nullptr;
-        ResetAll();
         return NS_OK;
       }
 
       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 (mIterNode == matchAnchorNode) {
+      if (state.mIterNode == matchAnchorNode) {
         findex = matchAnchorOffset + (mFindBackward ? 1 : 0);
       }
 
-      // mIterOffset, if set, is the range's idea of an offset, and points
+      // 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 (mIterOffset >= 0) {
-        findex = mIterOffset - (mFindBackward ? 1 : 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;
       }
 
       // Offset can only apply to the first node:
-      mIterOffset = -1;
+      state.mIterOffset = -1;
 
       // 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;
         continue;
       }
 
@@ -1096,20 +1110,19 @@ 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 (mIterNode == endNode &&
+    if (state.mIterNode == endNode &&
         ((mFindBackward && findex < static_cast<int32_t>(endOffset)) ||
          (!mFindBackward && findex > static_cast<int32_t>(endOffset)))) {
-      ResetAll();
       return NS_OK;
     }
 
     // Save the previous character for word boundary detection
     prevChar = c;
     // The two characters we'll be comparing:
     c = (t2b ? t2b[findex] : CHAR_TO_UNICHAR(t1b[findex]));
     patc = patStr[pindex];
@@ -1202,17 +1215,17 @@ 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 = mIterNode;
+        matchAnchorNode = state.mIterNode;
         matchAnchorOffset = findex;
       }
 
       // Are we done?
       if (DONE_WITH_PINDEX) {
         // Matched the whole string!
         DEBUG_FIND_PRINTF("Found a match!\n");
 
@@ -1225,17 +1238,17 @@ nsFind::Find(const char16_t* aPatText, n
           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(aSearchRange, aStartPoint, aEndPoint);
+            nextChar = PeekNextChar(state, aSearchRange, aStartPoint, aEndPoint);
 
           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;
@@ -1269,21 +1282,19 @@ nsFind::Find(const char16_t* aPatText, n
             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:
-          mIterOffset = findex + (mFindBackward ? 1 : 0);
-          DEBUG_FIND_PRINTF("mIterOffset = %d, mIterNode = ", mIterOffset);
-          DumpNode(mIterNode);
-
-          ResetAll();
+          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
@@ -1300,36 +1311,34 @@ nsFind::Find(const char16_t* aPatText, n
     }
 
     DEBUG_FIND_PRINTF("NOT: %c == %c\n", c, patc);
 
     // 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;
-      mIterOffset = matchAnchorOffset;
+      state.mIterOffset = matchAnchorOffset;
       // +incr will be added to findex when we continue
 
       // Are we going back to a previous node?
-      if (matchAnchorNode != mIterNode) {
+      if (matchAnchorNode != state.mIterNode) {
         nsCOMPtr<nsIContent> content(do_QueryInterface(matchAnchorNode));
         DebugOnly<nsresult> rv = NS_ERROR_UNEXPECTED;
         if (content) {
-          rv = mIterator->PositionAt(content);
+          rv = state.mIterator->PositionAt(content);
         }
         frag = 0;
         NS_ASSERTION(NS_SUCCEEDED(rv), "Text content wasn't nsIContent!");
         DEBUG_FIND_PRINTF("Repositioned anchor node\n");
       }
       DEBUG_FIND_PRINTF("Ending a partial match; findex -> %d, mIterOffset -> %d\n",
-                        findex, mIterOffset);
+                        findex, state.mIterOffset);
     }
     matchAnchorNode = nullptr;
     matchAnchorOffset = 0;
     inWhitespace = false;
     pindex = (mFindBackward ? patLen : 0);
     DEBUG_FIND_PRINTF("Setting findex back to %d, pindex to %d\n", findex, pindex);
   }
 
-  // Out of nodes, and didn't match.
-  ResetAll();
   return NS_OK;
 }
--- a/toolkit/components/find/nsFind.h
+++ b/toolkit/components/find/nsFind.h
@@ -40,37 +40,36 @@ protected:
   // Parameters set from the interface:
   bool mFindBackward;
   bool mCaseSensitive;
 
   // Use "find entire words" mode by setting to a word breaker or null, to
   // disable "entire words" mode.
   RefPtr<mozilla::intl::WordBreaker> mWordBreaker;
 
-  int32_t mIterOffset;
-  nsCOMPtr<nsINode> mIterNode;
-
-  // Last block parent, so that we will notice crossing block boundaries:
-  nsCOMPtr<nsIContent> mLastBlockParent;
+  struct State;
+  class StateRestorer;
   nsIContent* GetBlockParent(nsINode* aNode);
 
   // Move in the right direction for our search:
-  nsresult NextNode(nsRange* aSearchRange,
-                    nsRange* aStartPoint, nsRange* aEndPoint,
+  nsresult NextNode(State&,
+                    nsRange* aSearchRange,
+                    nsRange* aStartPoint,
+                    nsRange* aEndPoint,
                     bool aContinueOk);
 
   // Get the first character from the next node (last if mFindBackward).
-  char16_t PeekNextChar(nsRange* aSearchRange,
+  //
+  // This will mutate the state, but then restore it afterwards.
+  char16_t PeekNextChar(State&,
+                        nsRange* aSearchRange,
                         nsRange* aStartPoint,
                         nsRange* aEndPoint);
 
-  // Reset variables before returning -- don't hold any references.
-  void ResetAll();
-
   // The iterator we use to move through the document:
-  nsresult InitIterator(nsINode* aStartNode, int32_t aStartOffset,
-                        nsINode* aEndNode, int32_t aEndOffset);
-  RefPtr<nsFindContentIterator> mIterator;
-
-  friend class PeekNextCharRestoreState;
+  nsresult InitIterator(State&,
+                        nsINode* aStartNode,
+                        int32_t aStartOffset,
+                        nsINode* aEndNode,
+                        int32_t aEndOffset);
 };
 
 #endif // nsFind_h__