1 // Copyright 2014 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 "extensions/browser/guest_view/web_view/web_view_find_helper.h"
6 
7 #include <utility>
8 
9 #include "base/memory/scoped_refptr.h"
10 #include "components/guest_view/browser/guest_view_event.h"
11 #include "extensions/browser/api/guest_view/web_view/web_view_internal_api.h"
12 #include "extensions/browser/guest_view/web_view/web_view_constants.h"
13 
14 using guest_view::GuestViewEvent;
15 
16 namespace extensions {
17 
WebViewFindHelper(WebViewGuest * webview_guest)18 WebViewFindHelper::WebViewFindHelper(WebViewGuest* webview_guest)
19     : webview_guest_(webview_guest), current_find_request_id_(0) {
20 }
21 
~WebViewFindHelper()22 WebViewFindHelper::~WebViewFindHelper() {
23 }
24 
CancelAllFindSessions()25 void WebViewFindHelper::CancelAllFindSessions() {
26   current_find_session_ = nullptr;
27   while (!find_info_map_.empty()) {
28     find_info_map_.begin()->second->SendResponse(true /* canceled */);
29     find_info_map_.erase(find_info_map_.begin());
30   }
31   if (find_update_event_)
32     DispatchFindUpdateEvent(true /* canceled */, true /* final_update */);
33   find_update_event_.reset();
34 }
35 
DispatchFindUpdateEvent(bool canceled,bool final_update)36 void WebViewFindHelper::DispatchFindUpdateEvent(bool canceled,
37                                                 bool final_update) {
38   DCHECK(find_update_event_.get());
39   std::unique_ptr<base::DictionaryValue> args(new base::DictionaryValue());
40   find_update_event_->PrepareResults(args.get());
41   args->SetBoolean(webview::kFindCanceled, canceled);
42   args->SetBoolean(webview::kFindFinalUpdate, final_update);
43   DCHECK(webview_guest_);
44   webview_guest_->DispatchEventToView(std::make_unique<GuestViewEvent>(
45       webview::kEventFindReply, std::move(args)));
46 }
47 
EndFindSession(int session_request_id,bool canceled)48 void WebViewFindHelper::EndFindSession(int session_request_id, bool canceled) {
49   auto session_iterator = find_info_map_.find(session_request_id);
50   DCHECK(session_iterator != find_info_map_.end());
51   FindInfo* find_info = session_iterator->second.get();
52 
53   // Call the callback function of the first request of the find session.
54   find_info->SendResponse(canceled);
55 
56   // For every subsequent find request of the find session.
57   for (auto i = find_info->find_next_requests_.begin();
58        i != find_info->find_next_requests_.end(); ++i) {
59     DCHECK(i->get());
60 
61     // Do not call callbacks for subsequent find requests that have not been
62     // replied to yet. These requests will get their own final updates in the
63     // same order as they appear in |find_next_requests_|, i.e. the order that
64     // the requests were made in. Once one request is found that has not been
65     // replied to, none that follow will be replied to either, and do not need
66     // to be checked.
67     if (!(*i)->replied_)
68       break;
69 
70     // Update the request's number of matches (if not canceled).
71     if (!canceled) {
72       (*i)->find_results_.number_of_matches_ =
73           find_info->find_results_.number_of_matches_;
74     }
75 
76     // Call the request's callback function with the find results, and then
77     // delete its map entry to free the WebViewInternalFindFunction object.
78     (*i)->SendResponse(canceled);
79     find_info_map_.erase((*i)->request_id_);
80   }
81 
82   // Erase the first find request's map entry to free the
83   // WebViewInternalFindFunction
84   // object.
85   find_info_map_.erase(session_request_id);
86 }
87 
Find(content::WebContents * guest_web_contents,const base::string16 & search_text,blink::mojom::FindOptionsPtr options,scoped_refptr<WebViewInternalFindFunction> find_function)88 void WebViewFindHelper::Find(
89     content::WebContents* guest_web_contents,
90     const base::string16& search_text,
91     blink::mojom::FindOptionsPtr options,
92     scoped_refptr<WebViewInternalFindFunction> find_function) {
93   // Need a new request_id for each new find request.
94   ++current_find_request_id_;
95 
96   // Stores the find request information by request_id so that its callback
97   // function can be called when the find results are available.
98   std::pair<FindInfoMap::iterator, bool> insert_result =
99       find_info_map_.insert(std::make_pair(
100           current_find_request_id_,
101           base::MakeRefCounted<FindInfo>(current_find_request_id_, search_text,
102                                          options.Clone(), find_function)));
103   // No duplicate insertions.
104   DCHECK(insert_result.second);
105 
106   // Find options including the implicit |findNext| field.
107   blink::mojom::FindOptionsPtr full_options =
108       insert_result.first->second->options().Clone();
109 
110   // Set |findNext| implicitly.
111   if (current_find_session_) {
112     const base::string16& current_search_text =
113         current_find_session_->search_text();
114     bool current_match_case = current_find_session_->options()->match_case;
115     full_options->find_next = !current_search_text.empty() &&
116                               current_search_text == search_text &&
117                               current_match_case == options->match_case;
118   } else {
119     full_options->find_next = false;
120   }
121 
122   // Link find requests that are a part of the same find session.
123   if (full_options->find_next && current_find_session_) {
124     DCHECK(current_find_request_id_ != current_find_session_->request_id());
125     current_find_session_->AddFindNextRequest(
126         insert_result.first->second->AsWeakPtr());
127   }
128 
129   // Update the current find session, if necessary.
130   if (!full_options->find_next)
131     current_find_session_ = insert_result.first->second;
132 
133   // Handle the empty |search_text| case internally.
134   if (search_text.empty()) {
135     guest_web_contents->StopFinding(content::STOP_FIND_ACTION_CLEAR_SELECTION);
136     FindReply(current_find_request_id_, 0, gfx::Rect(), 0, true);
137     return;
138   }
139 
140   guest_web_contents->Find(current_find_request_id_, search_text,
141                            std::move(full_options));
142 }
143 
FindReply(int request_id,int number_of_matches,const gfx::Rect & selection_rect,int active_match_ordinal,bool final_update)144 void WebViewFindHelper::FindReply(int request_id,
145                                   int number_of_matches,
146                                   const gfx::Rect& selection_rect,
147                                   int active_match_ordinal,
148                                   bool final_update) {
149   auto find_iterator = find_info_map_.find(request_id);
150 
151   // Ignore slow replies to canceled find requests.
152   if (find_iterator == find_info_map_.end())
153     return;
154 
155   // This find request must be a part of an existing find session.
156   DCHECK(current_find_session_);
157 
158   WebViewFindHelper::FindInfo* find_info = find_iterator->second.get();
159   // Handle canceled find requests.
160   if (!find_info->options()->find_next &&
161       find_info_map_.begin()->first < request_id) {
162     DCHECK_NE(current_find_session_->request_id(),
163               find_info_map_.begin()->first);
164     if (find_update_event_)
165       DispatchFindUpdateEvent(true /* canceled */, true /* final_update */);
166     EndFindSession(find_info_map_.begin()->first, true /* canceled */);
167   }
168 
169   // Clears the results for |findupdate| for a new find session.
170   if (!find_info->replied() && !find_info->options()->find_next)
171     find_update_event_.reset(new FindUpdateEvent(find_info->search_text()));
172 
173   // Aggregate the find results.
174   find_info->AggregateResults(number_of_matches, selection_rect,
175                               active_match_ordinal, final_update);
176   find_update_event_->AggregateResults(number_of_matches, selection_rect,
177                                       active_match_ordinal, final_update);
178 
179   // Propagate incremental results to the |findupdate| event.
180   DispatchFindUpdateEvent(false /* canceled */, final_update);
181 
182   // Call the callback functions of completed find requests.
183   if (final_update)
184     EndFindSession(request_id, false /* canceled */);
185 }
186 
FindResults()187 WebViewFindHelper::FindResults::FindResults()
188     : number_of_matches_(0), active_match_ordinal_(0) {
189 }
190 
~FindResults()191 WebViewFindHelper::FindResults::~FindResults() {
192 }
193 
AggregateResults(int number_of_matches,const gfx::Rect & selection_rect,int active_match_ordinal,bool final_update)194 void WebViewFindHelper::FindResults::AggregateResults(
195     int number_of_matches,
196     const gfx::Rect& selection_rect,
197     int active_match_ordinal,
198     bool final_update) {
199   if (number_of_matches != -1)
200     number_of_matches_ = number_of_matches;
201 
202   if (active_match_ordinal != -1)
203     active_match_ordinal_ = active_match_ordinal;
204 
205   if (final_update && active_match_ordinal_ == 0) {
206     // No match found, so the selection rectangle is empty.
207     selection_rect_ = gfx::Rect();
208   } else if (!selection_rect.IsEmpty()) {
209     selection_rect_ = selection_rect;
210   }
211 }
212 
PrepareResults(base::DictionaryValue * results)213 void WebViewFindHelper::FindResults::PrepareResults(
214     base::DictionaryValue* results) {
215   results->SetKey(webview::kFindNumberOfMatches,
216                   base::Value(number_of_matches_));
217   results->SetKey(webview::kFindActiveMatchOrdinal,
218                   base::Value(active_match_ordinal_));
219   base::Value rect(base::Value::Type::DICTIONARY);
220   rect.SetKey(webview::kFindRectLeft, base::Value(selection_rect_.x()));
221   rect.SetKey(webview::kFindRectTop, base::Value(selection_rect_.y()));
222   rect.SetKey(webview::kFindRectWidth, base::Value(selection_rect_.width()));
223   rect.SetKey(webview::kFindRectHeight, base::Value(selection_rect_.height()));
224   results->SetKey(webview::kFindSelectionRect, std::move(rect));
225 }
226 
FindUpdateEvent(const base::string16 & search_text)227 WebViewFindHelper::FindUpdateEvent::FindUpdateEvent(
228     const base::string16& search_text)
229     : search_text_(search_text) {
230 }
231 
~FindUpdateEvent()232 WebViewFindHelper::FindUpdateEvent::~FindUpdateEvent() {
233 }
234 
AggregateResults(int number_of_matches,const gfx::Rect & selection_rect,int active_match_ordinal,bool final_update)235 void WebViewFindHelper::FindUpdateEvent::AggregateResults(
236     int number_of_matches,
237     const gfx::Rect& selection_rect,
238     int active_match_ordinal,
239     bool final_update) {
240   find_results_.AggregateResults(number_of_matches, selection_rect,
241                                  active_match_ordinal, final_update);
242 }
243 
PrepareResults(base::DictionaryValue * results)244 void WebViewFindHelper::FindUpdateEvent::PrepareResults(
245     base::DictionaryValue* results) {
246   results->SetString(webview::kFindSearchText, search_text_);
247   find_results_.PrepareResults(results);
248 }
249 
FindInfo(int request_id,const base::string16 & search_text,blink::mojom::FindOptionsPtr options,scoped_refptr<WebViewInternalFindFunction> find_function)250 WebViewFindHelper::FindInfo::FindInfo(
251     int request_id,
252     const base::string16& search_text,
253     blink::mojom::FindOptionsPtr options,
254     scoped_refptr<WebViewInternalFindFunction> find_function)
255     : request_id_(request_id),
256       search_text_(search_text),
257       options_(std::move(options)),
258       find_function_(find_function),
259       replied_(false) {}
260 
AggregateResults(int number_of_matches,const gfx::Rect & selection_rect,int active_match_ordinal,bool final_update)261 void WebViewFindHelper::FindInfo::AggregateResults(
262     int number_of_matches,
263     const gfx::Rect& selection_rect,
264     int active_match_ordinal,
265     bool final_update) {
266   replied_ = true;
267   find_results_.AggregateResults(number_of_matches, selection_rect,
268                                  active_match_ordinal, final_update);
269 }
270 
271 base::WeakPtr<WebViewFindHelper::FindInfo>
AsWeakPtr()272 WebViewFindHelper::FindInfo::AsWeakPtr() {
273   return weak_ptr_factory_.GetWeakPtr();
274 }
275 
SendResponse(bool canceled)276 void WebViewFindHelper::FindInfo::SendResponse(bool canceled) {
277   // Prepare the find results to pass to the callback function.
278   base::DictionaryValue results;
279   find_results_.PrepareResults(&results);
280   results.SetBoolean(webview::kFindCanceled, canceled);
281 
282   // Call the callback.
283   find_function_->ForwardResponse(results);
284 }
285 
~FindInfo()286 WebViewFindHelper::FindInfo::~FindInfo() {}
287 
288 }  // namespace extensions
289