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