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