1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "content/browser/find_request_manager.h"
6 
7 #include <algorithm>
8 #include <utility>
9 
10 #include "base/bind.h"
11 #include "base/containers/queue.h"
12 #include "content/browser/find_in_page_client.h"
13 #include "content/browser/renderer_host/render_frame_host_impl.h"
14 #include "content/browser/web_contents/web_contents_impl.h"
15 #include "content/common/frame_messages.h"
16 
17 namespace content {
18 
19 namespace {
20 
21 // The following functions allow traversal over all frames, including those
22 // across WebContentses.
23 //
24 // An inner WebContents may be embedded in an outer WebContents via an inner
25 // WebContentsTreeNode of the outer WebContents's WebContentsTreeNode.
26 
27 // Returns all child frames of |node|.
GetChildren(FrameTreeNode * node)28 std::vector<FrameTreeNode*> GetChildren(FrameTreeNode* node) {
29   std::vector<FrameTreeNode*> children;
30   children.reserve(node->child_count());
31   for (size_t i = 0; i != node->child_count(); ++i) {
32     if (auto* contents = static_cast<WebContentsImpl*>(
33             WebContentsImpl::FromOuterFrameTreeNode(node->child_at(i)))) {
34       // Portals can't receive keyboard events or be focused, so we don't return
35       // find results inside a portal.
36       if (!contents->IsPortal()) {
37         // If the child is used for an inner WebContents then add the inner
38         // WebContents.
39         children.push_back(contents->GetFrameTree()->root());
40       }
41     } else {
42       children.push_back(node->child_at(i));
43     }
44   }
45 
46   return children;
47 }
48 
49 // Returns the first child FrameTreeNode under |node|, if |node| has a child, or
50 // nullptr otherwise.
GetFirstChild(FrameTreeNode * node)51 FrameTreeNode* GetFirstChild(FrameTreeNode* node) {
52   auto children = GetChildren(node);
53   if (!children.empty())
54     return children.front();
55   return nullptr;
56 }
57 
58 // Returns the last child FrameTreeNode under |node|, if |node| has a child, or
59 // nullptr otherwise.
GetLastChild(FrameTreeNode * node)60 FrameTreeNode* GetLastChild(FrameTreeNode* node) {
61   auto children = GetChildren(node);
62   if (!children.empty())
63     return children.back();
64   return nullptr;
65 }
66 
67 // Returns the deepest last child frame under |node|/|rfh| in the frame tree.
GetDeepestLastChild(FrameTreeNode * node)68 FrameTreeNode* GetDeepestLastChild(FrameTreeNode* node) {
69   while (FrameTreeNode* last_child = GetLastChild(node))
70     node = last_child;
71   return node;
72 }
GetDeepestLastChild(RenderFrameHost * rfh)73 RenderFrameHost* GetDeepestLastChild(RenderFrameHost* rfh) {
74   FrameTreeNode* node =
75       static_cast<RenderFrameHostImpl*>(rfh)->frame_tree_node();
76   return GetDeepestLastChild(node)->current_frame_host();
77 }
78 
79 // Returns the parent FrameTreeNode of |node|, if |node| has a parent, or
80 // nullptr otherwise.
GetParent(FrameTreeNode * node)81 FrameTreeNode* GetParent(FrameTreeNode* node) {
82   if (!node)
83     return nullptr;
84   if (node->parent())
85     return node->parent()->frame_tree_node();
86 
87   auto* contents = WebContentsImpl::FromFrameTreeNode(node);
88   if (!node->IsMainFrame() || !contents->GetOuterWebContents())
89     return nullptr;
90 
91   return GetParent(FrameTreeNode::GloballyFindByID(
92       contents->GetOuterDelegateFrameTreeNodeId()));
93 }
94 
95 // Returns the previous sibling FrameTreeNode of |node|, if one exists, or
96 // nullptr otherwise.
GetPreviousSibling(FrameTreeNode * node)97 FrameTreeNode* GetPreviousSibling(FrameTreeNode* node) {
98   if (FrameTreeNode* previous_sibling = node->PreviousSibling())
99     return previous_sibling;
100 
101   // The previous sibling may be in another WebContents.
102   if (FrameTreeNode* parent = GetParent(node)) {
103     auto children = GetChildren(parent);
104     auto it = std::find(children.begin(), children.end(), node);
105     // It is odd that this node may not be a child of its parent, but this is
106     // actually possible during teardown, hence the need for the check for
107     // "it != children.end()".
108     if (it != children.end() && it != children.begin())
109       return *--it;
110   }
111 
112   return nullptr;
113 }
114 
115 // Returns the next sibling FrameTreeNode of |node|, if one exists, or nullptr
116 // otherwise.
GetNextSibling(FrameTreeNode * node)117 FrameTreeNode* GetNextSibling(FrameTreeNode* node) {
118   if (FrameTreeNode* next_sibling = node->NextSibling())
119     return next_sibling;
120 
121   // The next sibling may be in another WebContents.
122   if (FrameTreeNode* parent = GetParent(node)) {
123     auto children = GetChildren(parent);
124     auto it = std::find(children.begin(), children.end(), node);
125     // It is odd that this node may not be a child of its parent, but this is
126     // actually possible during teardown, hence the need for the check for
127     // "it != children.end()".
128     if (it != children.end() && ++it != children.end())
129       return *it;
130   }
131 
132   return nullptr;
133 }
134 
135 // Returns the FrameTreeNode directly after |node| in the frame tree in search
136 // order, or nullptr if one does not exist. If |wrap| is set, then wrapping
137 // between the first and last frames is permitted. Note that this traversal
138 // follows the same ordering as in blink::FrameTree::traverseNextWithWrap().
TraverseNext(FrameTreeNode * node,bool wrap)139 FrameTreeNode* TraverseNext(FrameTreeNode* node, bool wrap) {
140   if (FrameTreeNode* first_child = GetFirstChild(node))
141     return first_child;
142 
143   FrameTreeNode* sibling = GetNextSibling(node);
144   while (!sibling) {
145     FrameTreeNode* parent = GetParent(node);
146     if (!parent)
147       return wrap ? node : nullptr;
148     node = parent;
149     sibling = GetNextSibling(node);
150   }
151   return sibling;
152 }
153 
154 // Returns the FrameTreeNode directly before |node| in the frame tree in search
155 // order, or nullptr if one does not exist. If |wrap| is set, then wrapping
156 // between the first and last frames is permitted. Note that this traversal
157 // follows the same ordering as in blink::FrameTree::traversePreviousWithWrap().
TraversePrevious(FrameTreeNode * node,bool wrap)158 FrameTreeNode* TraversePrevious(FrameTreeNode* node, bool wrap) {
159   if (FrameTreeNode* previous_sibling = GetPreviousSibling(node))
160     return GetDeepestLastChild(previous_sibling);
161   if (FrameTreeNode* parent = GetParent(node))
162     return parent;
163   return wrap ? GetDeepestLastChild(node) : nullptr;
164 }
165 
166 // The same as either TraverseNext() or TraversePrevious(), depending on
167 // |forward|.
TraverseNode(FrameTreeNode * node,bool forward,bool wrap)168 FrameTreeNode* TraverseNode(FrameTreeNode* node, bool forward, bool wrap) {
169   return forward ? TraverseNext(node, wrap) : TraversePrevious(node, wrap);
170 }
171 
172 }  // namespace
173 
174 // Observes searched WebContentses for frame changes, including deletion,
175 // creation, and navigation.
176 class FindRequestManager::FrameObserver : public WebContentsObserver {
177  public:
FrameObserver(WebContentsImpl * web_contents,FindRequestManager * manager)178   FrameObserver(WebContentsImpl* web_contents, FindRequestManager* manager)
179       : WebContentsObserver(web_contents), manager_(manager) {}
180 
181   ~FrameObserver() override = default;
182 
DidFinishLoad(RenderFrameHost * rfh,const GURL & validated_url)183   void DidFinishLoad(RenderFrameHost* rfh, const GURL& validated_url) override {
184     if (manager_->current_session_id_ == kInvalidId)
185       return;
186 
187     manager_->RemoveFrame(rfh);
188     manager_->AddFrame(rfh, true /* force */);
189   }
190 
RenderFrameDeleted(RenderFrameHost * rfh)191   void RenderFrameDeleted(RenderFrameHost* rfh) override {
192     manager_->RemoveFrame(rfh);
193   }
194 
RenderFrameHostChanged(RenderFrameHost * old_host,RenderFrameHost * new_host)195   void RenderFrameHostChanged(RenderFrameHost* old_host,
196                               RenderFrameHost* new_host) override {
197     // The |old_host| and its children are now pending deletion. Find-in-page
198     // must not interact with them anymore.
199     if (old_host)
200       RemoveFrameRecursively(static_cast<RenderFrameHostImpl*>(old_host));
201   }
202 
FrameDeleted(RenderFrameHost * rfh)203   void FrameDeleted(RenderFrameHost* rfh) override {
204     manager_->RemoveFrame(rfh);
205   }
206 
207  private:
RemoveFrameRecursively(RenderFrameHostImpl * rfh)208   void RemoveFrameRecursively(RenderFrameHostImpl* rfh) {
209     for (size_t i = 0; i < rfh->child_count(); ++i)
210       RemoveFrameRecursively(rfh->child_at(i)->current_frame_host());
211     manager_->RemoveFrame(rfh);
212   }
213 
214   // The FindRequestManager that owns this FrameObserver.
215   FindRequestManager* const manager_;
216 
217   DISALLOW_COPY_AND_ASSIGN(FrameObserver);
218 };
219 
220 FindRequestManager::FindRequest::FindRequest() = default;
221 
FindRequest(int id,const base::string16 & search_text,blink::mojom::FindOptionsPtr options)222 FindRequestManager::FindRequest::FindRequest(
223     int id,
224     const base::string16& search_text,
225     blink::mojom::FindOptionsPtr options)
226     : id(id), search_text(search_text), options(std::move(options)) {}
227 
FindRequest(const FindRequest & request)228 FindRequestManager::FindRequest::FindRequest(const FindRequest& request)
229     : id(request.id),
230       search_text(request.search_text),
231       options(request.options.Clone()) {}
232 
233 FindRequestManager::FindRequest::~FindRequest() = default;
234 
operator =(const FindRequest & request)235 FindRequestManager::FindRequest& FindRequestManager::FindRequest::operator=(
236     const FindRequest& request) {
237   id = request.id;
238   search_text = request.search_text;
239   options = request.options.Clone();
240   return *this;
241 }
242 
243 #if defined(OS_ANDROID)
244 FindRequestManager::ActivateNearestFindResultState::
245 ActivateNearestFindResultState() = default;
246 FindRequestManager::ActivateNearestFindResultState::
ActivateNearestFindResultState(float x,float y)247     ActivateNearestFindResultState(float x, float y)
248     : current_request_id(GetNextID()), point(x, y) {}
249 FindRequestManager::ActivateNearestFindResultState::
250     ~ActivateNearestFindResultState() = default;
251 
252 FindRequestManager::FrameRects::FrameRects() = default;
FrameRects(const std::vector<gfx::RectF> & rects,int version)253 FindRequestManager::FrameRects::FrameRects(const std::vector<gfx::RectF>& rects,
254                                            int version)
255     : rects(rects), version(version) {}
256 FindRequestManager::FrameRects::~FrameRects() = default;
257 
258 FindRequestManager::FindMatchRectsState::FindMatchRectsState() = default;
259 FindRequestManager::FindMatchRectsState::~FindMatchRectsState() = default;
260 #endif
261 
262 // static
263 const int FindRequestManager::kInvalidId = -1;
264 
FindRequestManager(WebContentsImpl * web_contents)265 FindRequestManager::FindRequestManager(WebContentsImpl* web_contents)
266     : contents_(web_contents) {}
267 
268 FindRequestManager::~FindRequestManager() = default;
269 
Find(int request_id,const base::string16 & search_text,blink::mojom::FindOptionsPtr options)270 void FindRequestManager::Find(int request_id,
271                               const base::string16& search_text,
272                               blink::mojom::FindOptionsPtr options) {
273   // Every find request must have a unique ID, and these IDs must strictly
274   // increase so that newer requests always have greater IDs than older
275   // requests.
276   DCHECK_GT(request_id, current_request_.id);
277   DCHECK_GT(request_id, current_session_id_);
278 
279   // If this is a new find session, clear any queued requests from last session.
280   if (options->new_session)
281     find_request_queue_ = base::queue<FindRequest>();
282 
283   find_request_queue_.emplace(request_id, search_text, std::move(options));
284   if (find_request_queue_.size() == 1)
285     FindInternal(find_request_queue_.front());
286 }
287 
StopFinding(StopFindAction action)288 void FindRequestManager::StopFinding(StopFindAction action) {
289   for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) {
290     for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) {
291       RenderFrameHostImpl* rfh = node->current_frame_host();
292       if (!CheckFrame(rfh) || !rfh->IsRenderFrameLive())
293         continue;
294       DCHECK(
295           !static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(rfh))
296                ->IsPortal());
297       rfh->GetFindInPage()->StopFinding(
298           static_cast<blink::mojom::StopFindAction>(action));
299     }
300   }
301 
302   current_session_id_ = kInvalidId;
303 #if defined(OS_ANDROID)
304   // It is important that these pending replies are cleared whenever a find
305   // session ends, so that subsequent replies for the old session are ignored.
306   activate_.pending_replies.clear();
307   match_rects_.pending_replies.clear();
308 #endif
309 }
310 
ShouldIgnoreReply(RenderFrameHostImpl * rfh,int request_id)311 bool FindRequestManager::ShouldIgnoreReply(RenderFrameHostImpl* rfh,
312                                            int request_id) {
313   // Ignore stale replies from abandoned find sessions or dead frames.
314   return current_session_id_ == kInvalidId ||
315          request_id < current_session_id_ || !CheckFrame(rfh);
316 }
317 
HandleFinalUpdateForFrame(RenderFrameHostImpl * rfh,int request_id)318 void FindRequestManager::HandleFinalUpdateForFrame(RenderFrameHostImpl* rfh,
319                                                    int request_id) {
320   // This is the final update for this frame for the current find operation.
321   pending_initial_replies_.erase(rfh);
322   if (request_id == current_session_id_ && !pending_initial_replies_.empty()) {
323     NotifyFindReply(request_id, false /* final_update */);
324     return;
325   }
326 
327   // This is the final update for all frames for the current find operation.
328   if (request_id == current_request_.id && request_id != current_session_id_) {
329     DCHECK(!current_request_.options->new_session);
330     DCHECK_EQ(pending_find_next_reply_, rfh);
331     pending_find_next_reply_ = nullptr;
332   }
333 
334   FinalUpdateReceived(request_id, rfh);
335 }
336 
UpdatedFrameNumberOfMatches(RenderFrameHostImpl * rfh,unsigned int old_count,unsigned int new_count)337 void FindRequestManager::UpdatedFrameNumberOfMatches(RenderFrameHostImpl* rfh,
338                                                      unsigned int old_count,
339                                                      unsigned int new_count) {
340   if (old_count == new_count)
341     return;
342 
343   // Change the number of matches for this frame in the global count.
344   number_of_matches_ -= old_count;
345   number_of_matches_ += new_count;
346 
347   // All matches may have been removed since the last find reply.
348   if (rfh == active_frame_ && !new_count)
349     relative_active_match_ordinal_ = 0;
350 
351   // The active match ordinal may need updating since the number of matches
352   // before the active match may have changed.
353   UpdateActiveMatchOrdinal();
354 }
355 
SetActiveMatchRect(const gfx::Rect & active_match_rect)356 void FindRequestManager::SetActiveMatchRect(
357     const gfx::Rect& active_match_rect) {
358   selection_rect_ = active_match_rect;
359 }
360 
SetActiveMatchOrdinal(RenderFrameHostImpl * rfh,int request_id,int active_match_ordinal)361 void FindRequestManager::SetActiveMatchOrdinal(RenderFrameHostImpl* rfh,
362                                                int request_id,
363                                                int active_match_ordinal) {
364   if (active_match_ordinal > 0) {
365     // Call SetFocusedFrame on the WebContents associated with |rfh| (which
366     // might not be the same as |contents_|, as a WebContents might have
367     // inner WebContents). We need to focus on the frame where the active
368     // match is in, which should be in the |rfh|'s associated WebContents.
369     WebContentsImpl* web_contents =
370         static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(rfh));
371     web_contents->SetFocusedFrame(rfh->frame_tree_node(),
372                                   rfh->GetSiteInstance());
373   }
374   if (rfh == active_frame_) {
375     active_match_ordinal_ +=
376         active_match_ordinal - relative_active_match_ordinal_;
377     relative_active_match_ordinal_ = active_match_ordinal;
378   } else {
379     if (active_frame_) {
380       // The new active match is in a different frame than the previous, so
381       // the previous active frame needs to be informed (to clear its active
382       // match highlighting).
383       ClearActiveFindMatch();
384     }
385     active_frame_ = rfh;
386     relative_active_match_ordinal_ = active_match_ordinal;
387     UpdateActiveMatchOrdinal();
388   }
389   if (pending_active_match_ordinal_ && request_id == current_request_.id)
390     pending_active_match_ordinal_ = false;
391   AdvanceQueue(request_id);
392 }
393 
RemoveFrame(RenderFrameHost * rfh)394 void FindRequestManager::RemoveFrame(RenderFrameHost* rfh) {
395   if (current_session_id_ == kInvalidId || !CheckFrame(rfh))
396     return;
397 
398   // Make sure to always clear the highlighted selection. It is useful in case
399   // the user goes back to the same page using the BackForwardCache.
400   static_cast<RenderFrameHostImpl*>(rfh)->GetFindInPage()->StopFinding(
401       blink::mojom::StopFindAction::kStopFindActionClearSelection);
402 
403   // If matches are counted for the frame that is being removed, decrement the
404   // match total before erasing that entry.
405   auto it = find_in_page_clients_.find(rfh);
406   if (it != find_in_page_clients_.end()) {
407     number_of_matches_ -= it->second->number_of_matches();
408     find_in_page_clients_.erase(it);
409   }
410 
411   // If this is a main frame, then clear the search queue as well, since we
412   // shouldn't be dispatching any more requests. Note that if any other frame is
413   // removed, we can target any queued requests to the focused frame or main
414   // frame. However, if the main frame is removed we will not have a valid
415   // RenderFrameHost to target for the request queue.
416   if (!rfh->GetParent())
417     find_request_queue_ = base::queue<FindRequest>();
418 
419   // Update the active match ordinal, since it may have changed.
420   if (active_frame_ == rfh) {
421     active_frame_ = nullptr;
422     relative_active_match_ordinal_ = 0;
423     selection_rect_ = gfx::Rect();
424   }
425   UpdateActiveMatchOrdinal();
426 
427 #if defined(OS_ANDROID)
428   // The removed frame may contain the nearest find result known so far. Note
429   // that once all queried frames have responded, if this result was the overall
430   // nearest, then no activation will occur.
431   if (rfh == activate_.nearest_frame)
432     activate_.nearest_frame = nullptr;
433 
434   // Match rects in the removed frame are no longer relevant.
435   if (match_rects_.frame_rects.erase(rfh) != 0)
436     ++match_rects_.known_version;
437 
438   // A reply should not be expected from the removed frame.
439   RemoveNearestFindResultPendingReply(rfh);
440   RemoveFindMatchRectsPendingReply(rfh);
441 #endif
442 
443   // If no pending find replies are expected for the removed frame, then just
444   // report the updated results.
445   if (!base::Contains(pending_initial_replies_, rfh) &&
446       pending_find_next_reply_ != rfh) {
447     bool final_update =
448         pending_initial_replies_.empty() && !pending_find_next_reply_;
449     NotifyFindReply(current_session_id_, final_update);
450     return;
451   }
452 
453   if (pending_initial_replies_.erase(rfh) != 0) {
454     // A reply should not be expected from the removed frame.
455     if (pending_initial_replies_.empty()) {
456       FinalUpdateReceived(current_session_id_, rfh);
457     }
458   }
459 
460   if (pending_find_next_reply_ == rfh) {
461     // A reply should not be expected from the removed frame.
462     pending_find_next_reply_ = nullptr;
463     FinalUpdateReceived(current_request_.id, rfh);
464   }
465 }
466 
ClearActiveFindMatch()467 void FindRequestManager::ClearActiveFindMatch() {
468   active_frame_->GetFindInPage()->ClearActiveFindMatch();
469 }
470 
471 #if defined(OS_ANDROID)
ActivateNearestFindResult(float x,float y)472 void FindRequestManager::ActivateNearestFindResult(float x, float y) {
473   if (current_session_id_ == kInvalidId)
474     return;
475 
476   activate_ = ActivateNearestFindResultState(x, y);
477 
478   // Request from each frame the distance to the nearest find result (in that
479   // frame) from the point (x, y), defined in find-in-page coordinates.
480   for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) {
481     for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) {
482       RenderFrameHostImpl* rfh = node->current_frame_host();
483 
484       if (!CheckFrame(rfh) || !rfh->IsRenderFrameLive())
485         continue;
486 
487       DCHECK(
488           !static_cast<WebContentsImpl*>(WebContents::FromRenderFrameHost(rfh))
489                ->IsPortal());
490       activate_.pending_replies.insert(rfh);
491       // Lifetime of FindRequestManager > RenderFrameHost > Mojo connection,
492       // so it's safe to bind |this| and |rfh|.
493       rfh->GetFindInPage()->GetNearestFindResult(
494           activate_.point,
495           base::BindOnce(&FindRequestManager::OnGetNearestFindResultReply,
496                          base::Unretained(this), rfh,
497                          activate_.current_request_id));
498     }
499   }
500 }
501 
OnGetNearestFindResultReply(RenderFrameHostImpl * rfh,int request_id,float distance)502 void FindRequestManager::OnGetNearestFindResultReply(RenderFrameHostImpl* rfh,
503                                                      int request_id,
504                                                      float distance) {
505   if (request_id != activate_.current_request_id ||
506       !base::Contains(activate_.pending_replies, rfh)) {
507     return;
508   }
509 
510   // Check if this frame has a nearer find result than the current nearest.
511   if (distance < activate_.nearest_distance) {
512     activate_.nearest_frame = rfh;
513     activate_.nearest_distance = distance;
514   }
515 
516   RemoveNearestFindResultPendingReply(rfh);
517 }
518 
RequestFindMatchRects(int current_version)519 void FindRequestManager::RequestFindMatchRects(int current_version) {
520   match_rects_.pending_replies.clear();
521   match_rects_.request_version = current_version;
522   match_rects_.active_rect = gfx::RectF();
523 
524   // Request the latest find match rects from each frame.
525   for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) {
526     if (!contents->IsPortal()) {
527       for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) {
528         RenderFrameHostImpl* rfh = node->current_frame_host();
529 
530         if (!CheckFrame(rfh) || !rfh->IsRenderFrameLive())
531           continue;
532 
533         match_rects_.pending_replies.insert(rfh);
534         auto it = match_rects_.frame_rects.find(rfh);
535         int version = (it != match_rects_.frame_rects.end())
536                           ? it->second.version
537                           : kInvalidId;
538         rfh->GetFindInPage()->FindMatchRects(
539             version, base::BindOnce(&FindRequestManager::OnFindMatchRectsReply,
540                                     base::Unretained(this), rfh));
541       }
542     }
543   }
544 }
545 
OnFindMatchRectsReply(RenderFrameHost * rfh,int version,const std::vector<gfx::RectF> & rects,const gfx::RectF & active_rect)546 void FindRequestManager::OnFindMatchRectsReply(
547     RenderFrameHost* rfh,
548     int version,
549     const std::vector<gfx::RectF>& rects,
550     const gfx::RectF& active_rect) {
551   auto it = match_rects_.frame_rects.find(rfh);
552   if (it == match_rects_.frame_rects.end() || it->second.version != version) {
553     // New version of rects has been received, so update the data.
554     match_rects_.frame_rects[rfh] = FrameRects(rects, version);
555     ++match_rects_.known_version;
556   }
557   if (!active_rect.IsEmpty())
558     match_rects_.active_rect = active_rect;
559   RemoveFindMatchRectsPendingReply(rfh);
560 }
561 #endif
562 
Reset(const FindRequest & initial_request)563 void FindRequestManager::Reset(const FindRequest& initial_request) {
564   current_session_id_ = initial_request.id;
565   current_request_ = initial_request;
566   pending_initial_replies_.clear();
567   pending_find_next_reply_ = nullptr;
568   pending_active_match_ordinal_ = true;
569   find_in_page_clients_.clear();
570   number_of_matches_ = 0;
571   active_frame_ = nullptr;
572   relative_active_match_ordinal_ = 0;
573   active_match_ordinal_ = 0;
574   selection_rect_ = gfx::Rect();
575   last_reported_id_ = kInvalidId;
576   frame_observers_.clear();
577 #if defined(OS_ANDROID)
578   activate_ = ActivateNearestFindResultState();
579   match_rects_.pending_replies.clear();
580 #endif
581 }
582 
FindInternal(const FindRequest & request)583 void FindRequestManager::FindInternal(const FindRequest& request) {
584   DCHECK_GT(request.id, current_request_.id);
585   DCHECK_GT(request.id, current_session_id_);
586 
587   if (!request.options->new_session) {
588     // This is a find next operation.
589 
590     // This implies that there is an ongoing find session with the same search
591     // text.
592     DCHECK_GE(current_session_id_, 0);
593     DCHECK_EQ(request.search_text, current_request_.search_text);
594 
595     // The find next request will be directed at the focused frame if there is
596     // one, or the first frame with matches otherwise.
597     RenderFrameHost* target_rfh =
598         contents_->GetFocusedWebContents()->GetFocusedFrame();
599     if (!target_rfh || !CheckFrame(target_rfh))
600       target_rfh = GetInitialFrame(request.options->forward);
601 
602     SendFindRequest(request, target_rfh);
603     current_request_ = request;
604     pending_active_match_ordinal_ = true;
605     return;
606   }
607 
608   // This is an initial find operation.
609   Reset(request);
610   for (WebContentsImpl* contents : contents_->GetWebContentsAndAllInner()) {
611     // Portals can't receive keyboard events or be focused, so we don't return
612     // find results inside a portal.
613     if (!contents->IsPortal()) {
614       frame_observers_.push_back(
615           std::make_unique<FrameObserver>(contents, this));
616       for (FrameTreeNode* node : contents->GetFrameTree()->Nodes()) {
617         AddFrame(node->current_frame_host(), false /* force */);
618       }
619     }
620   }
621 }
622 
AdvanceQueue(int request_id)623 void FindRequestManager::AdvanceQueue(int request_id) {
624   if (find_request_queue_.empty() ||
625       request_id != find_request_queue_.front().id) {
626     return;
627   }
628 
629   find_request_queue_.pop();
630   if (!find_request_queue_.empty())
631     FindInternal(find_request_queue_.front());
632 }
633 
SendFindRequest(const FindRequest & request,RenderFrameHost * rfh)634 void FindRequestManager::SendFindRequest(const FindRequest& request,
635                                          RenderFrameHost* rfh) {
636   DCHECK(CheckFrame(rfh));
637   DCHECK(rfh->IsRenderFrameLive());
638 
639   if (request.options->new_session)
640     pending_initial_replies_.insert(rfh);
641   else
642     pending_find_next_reply_ = rfh;
643 
644   static_cast<RenderFrameHostImpl*>(rfh)->GetFindInPage()->Find(
645       request.id, base::UTF16ToUTF8(request.search_text),
646       request.options.Clone());
647 }
648 
NotifyFindReply(int request_id,bool final_update)649 void FindRequestManager::NotifyFindReply(int request_id, bool final_update) {
650   if (request_id == kInvalidId) {
651     NOTREACHED();
652     return;
653   }
654 
655   // Ensure that replies are not reported with IDs lower than the ID of the
656   // latest request we have results from.
657   if (request_id < last_reported_id_)
658     request_id = last_reported_id_;
659   else
660     last_reported_id_ = request_id;
661 
662   contents_->NotifyFindReply(request_id, number_of_matches_, selection_rect_,
663                              active_match_ordinal_, final_update);
664 }
665 
GetInitialFrame(bool forward) const666 RenderFrameHost* FindRequestManager::GetInitialFrame(bool forward) const {
667   RenderFrameHost* rfh = contents_->GetMainFrame();
668 
669   if (!forward)
670     rfh = GetDeepestLastChild(rfh);
671 
672   return rfh;
673 }
674 
Traverse(RenderFrameHost * from_rfh,bool forward,bool matches_only,bool wrap) const675 RenderFrameHost* FindRequestManager::Traverse(RenderFrameHost* from_rfh,
676                                               bool forward,
677                                               bool matches_only,
678                                               bool wrap) const {
679   DCHECK(from_rfh);
680   // If |from_rfh| is being detached, it might already be removed from
681   // its parent's list of children, meaning we can't traverse it correctly.
682   // We also don't traverse when |from_rfh| is in back-forward cache as we don't
683   // allow any updates in this state.
684   auto* from_rfh_impl = static_cast<RenderFrameHostImpl*>(from_rfh);
685   if (from_rfh_impl->IsPendingDeletion() ||
686       from_rfh_impl->IsInBackForwardCache()) {
687     return nullptr;
688   }
689 
690   FrameTreeNode* node = from_rfh_impl->frame_tree_node();
691   FrameTreeNode* last_node = node;
692   while ((node = TraverseNode(node, forward, wrap)) != nullptr) {
693     if (!CheckFrame(node->current_frame_host())) {
694       // If we're in the same frame as before, we might got into an infinite
695       // loop.
696       if (last_node == node)
697         break;
698       last_node = node;
699       continue;
700     }
701     RenderFrameHost* current_rfh = node->current_frame_host();
702     if (!matches_only ||
703         find_in_page_clients_.find(current_rfh)->second->number_of_matches() ||
704         base::Contains(pending_initial_replies_, current_rfh)) {
705       // Note that if there is still a pending reply expected for this frame,
706       // then it may have unaccounted matches and will not be skipped via
707       // |matches_only|.
708       return node->current_frame_host();
709     }
710     if (wrap && node->current_frame_host() == from_rfh)
711       return nullptr;
712   }
713 
714   return nullptr;
715 }
716 
AddFrame(RenderFrameHost * rfh,bool force)717 void FindRequestManager::AddFrame(RenderFrameHost* rfh, bool force) {
718   if (!rfh || !rfh->IsRenderFrameLive())
719     return;
720 
721   // A frame that is already being searched should not normally be added again.
722   DCHECK(force || !CheckFrame(rfh));
723 
724   find_in_page_clients_[rfh] = std::make_unique<FindInPageClient>(
725       this, static_cast<RenderFrameHostImpl*>(rfh));
726 
727   FindRequest request = current_request_;
728   request.id = current_session_id_;
729   request.options->new_session = true;
730   request.options->force = force;
731   SendFindRequest(request, rfh);
732 }
733 
CheckFrame(RenderFrameHost * rfh) const734 bool FindRequestManager::CheckFrame(RenderFrameHost* rfh) const {
735   return rfh && base::Contains(find_in_page_clients_, rfh);
736 }
737 
UpdateActiveMatchOrdinal()738 void FindRequestManager::UpdateActiveMatchOrdinal() {
739   active_match_ordinal_ = 0;
740 
741   if (!active_frame_ || !relative_active_match_ordinal_) {
742     DCHECK(!active_frame_ && !relative_active_match_ordinal_);
743     return;
744   }
745 
746   // Traverse the frame tree backwards (in search order) and count all of the
747   // matches in frames before the frame with the active match, in order to
748   // determine the overall active match ordinal.
749   RenderFrameHost* frame = active_frame_;
750   while ((frame = Traverse(frame,
751                            false /* forward */,
752                            true /* matches_only */,
753                            false /* wrap */)) != nullptr) {
754     active_match_ordinal_ += find_in_page_clients_[frame]->number_of_matches();
755   }
756   active_match_ordinal_ += relative_active_match_ordinal_;
757 }
758 
FinalUpdateReceived(int request_id,RenderFrameHost * rfh)759 void FindRequestManager::FinalUpdateReceived(int request_id,
760                                              RenderFrameHost* rfh) {
761   if (!number_of_matches_ ||
762       !current_request_.options->find_match ||
763       (active_match_ordinal_ && !pending_active_match_ordinal_) ||
764       pending_find_next_reply_) {
765     // All the find results for |request_id| are in and ready to report. Note
766     // that |final_update| will be set to false if there are still pending
767     // replies expected from the initial find request.
768     NotifyFindReply(request_id,
769                     pending_initial_replies_.empty() /* final_update */);
770     AdvanceQueue(request_id);
771     return;
772   }
773 
774   // There are matches, but no active match was returned, so another find next
775   // request must be sent.
776 
777   RenderFrameHost* target_rfh;
778   if (request_id == current_request_.id &&
779       !current_request_.options->new_session) {
780     // If this was a find next operation, then the active match will be in the
781     // next frame with matches after this one.
782     target_rfh = Traverse(rfh, current_request_.options->forward,
783                           true /* matches_only */, true /* wrap */);
784   } else if ((target_rfh =
785                   contents_->GetFocusedWebContents()->GetFocusedFrame()) !=
786              nullptr) {
787     // Otherwise, if there is a focused frame, then the active match will be in
788     // the next frame with matches after that one.
789     target_rfh = Traverse(target_rfh, current_request_.options->forward,
790                           true /* matches_only */, true /* wrap */);
791   } else {
792     // Otherwise, the first frame with matches will have the active match.
793     target_rfh = GetInitialFrame(current_request_.options->forward);
794     if (!CheckFrame(target_rfh) ||
795         !find_in_page_clients_[target_rfh]->number_of_matches()) {
796       target_rfh = Traverse(target_rfh, current_request_.options->forward,
797                             true /* matches_only */, false /* wrap */);
798     }
799   }
800   if (!target_rfh) {
801     // Sometimes when the WebContents is deleted/navigated, we got into this
802     // situation. We don't care about this WebContents anyways so it's ok to
803     // just not ask for the active match and return immediately.
804     // TODO(rakina): Understand what leads to this situation.
805     // See: https://crbug.com/884679.
806     return;
807   }
808 
809   // Forward the find reply without |final_update| set because the active match
810   // has not yet been found.
811   NotifyFindReply(request_id, false /* final_update */);
812 
813   current_request_.options->new_session = false;
814   SendFindRequest(current_request_, target_rfh);
815 }
816 
817 #if defined(OS_ANDROID)
RemoveNearestFindResultPendingReply(RenderFrameHost * rfh)818 void FindRequestManager::RemoveNearestFindResultPendingReply(
819     RenderFrameHost* rfh) {
820   auto it = activate_.pending_replies.find(rfh);
821   if (it == activate_.pending_replies.end())
822     return;
823 
824   activate_.pending_replies.erase(it);
825   if (activate_.pending_replies.empty() &&
826       CheckFrame(activate_.nearest_frame)) {
827     const auto client_it = find_in_page_clients_.find(activate_.nearest_frame);
828     if (client_it != find_in_page_clients_.end())
829       client_it->second->ActivateNearestFindResult(current_session_id_,
830                                                    activate_.point);
831   }
832 }
833 
RemoveFindMatchRectsPendingReply(RenderFrameHost * rfh)834 void FindRequestManager::RemoveFindMatchRectsPendingReply(
835     RenderFrameHost* rfh) {
836   auto it = match_rects_.pending_replies.find(rfh);
837   if (it == match_rects_.pending_replies.end())
838     return;
839 
840   match_rects_.pending_replies.erase(it);
841   if (!match_rects_.pending_replies.empty())
842     return;
843 
844   // All replies are in.
845   std::vector<gfx::RectF> aggregate_rects;
846   if (match_rects_.request_version != match_rects_.known_version) {
847     // Request version is stale, so aggregate and report the newer find
848     // match rects. The rects should be aggregated in search order.
849     for (RenderFrameHost* frame = GetInitialFrame(true /* forward */); frame;
850          frame = Traverse(frame, true /* forward */, true /* matches_only */,
851                           false /* wrap */)) {
852       auto frame_it = match_rects_.frame_rects.find(frame);
853       if (frame_it == match_rects_.frame_rects.end())
854         continue;
855 
856       std::vector<gfx::RectF>& frame_rects = frame_it->second.rects;
857       aggregate_rects.insert(aggregate_rects.end(), frame_rects.begin(),
858                              frame_rects.end());
859     }
860   }
861   contents_->NotifyFindMatchRectsReply(
862       match_rects_.known_version, aggregate_rects, match_rects_.active_rect);
863 }
864 #endif  // defined(OS_ANDROID)
865 
866 }  // namespace content
867