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