searchbox.cc revision a93a17c8d99d686bd4a1511e5504e5e6cc9fcadf
1// Copyright 2012 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 "chrome/renderer/searchbox/searchbox.h"
6
7#include "base/string_number_conversions.h"
8#include "base/utf_string_conversions.h"
9#include "chrome/common/chrome_switches.h"
10#include "chrome/common/omnibox_focus_state.h"
11#include "chrome/common/render_messages.h"
12#include "chrome/common/url_constants.h"
13#include "chrome/renderer/searchbox/searchbox_extension.h"
14#include "content/public/renderer/render_view.h"
15#include "grit/renderer_resources.h"
16#include "net/base/escape.h"
17#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
18#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
19#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
20#include "ui/base/resource/resource_bundle.h"
21
22namespace {
23
24// Size of the results cache.
25const size_t kMaxInstantAutocompleteResultItemCacheSize = 100;
26
27bool IsThemeInfoEqual(const ThemeBackgroundInfo& new_theme_info,
28    const ThemeBackgroundInfo& old_theme_info) {
29  return old_theme_info.color_r == new_theme_info.color_r &&
30      old_theme_info.color_g == new_theme_info.color_g &&
31      old_theme_info.color_b == new_theme_info.color_b &&
32      old_theme_info.color_a == new_theme_info.color_a &&
33      old_theme_info.theme_id == new_theme_info.theme_id &&
34      old_theme_info.image_horizontal_alignment ==
35          new_theme_info.image_horizontal_alignment &&
36      old_theme_info.image_vertical_alignment ==
37          new_theme_info.image_vertical_alignment &&
38      old_theme_info.image_tiling == new_theme_info.image_tiling &&
39      old_theme_info.image_height == new_theme_info.image_height &&
40      old_theme_info.has_attribution == new_theme_info.has_attribution;
41}
42
43bool AreMostVisitedItemsEqual(
44    const std::vector<InstantMostVisitedItemIDPair>& new_items,
45    const std::vector<InstantMostVisitedItemIDPair>& old_items) {
46  if (old_items.size() != new_items.size())
47    return false;
48  for (size_t i = 0; i < new_items.size(); i++) {
49    const InstantMostVisitedItem& old_item = old_items[i].second;
50    const InstantMostVisitedItem& new_item = new_items[i].second;
51    if (new_item.url != old_item.url || new_item.title != old_item.title)
52      return false;
53  }
54  return true;
55}
56
57}  // namespace
58
59SearchBox::SearchBox(content::RenderView* render_view)
60    : content::RenderViewObserver(render_view),
61      content::RenderViewObserverTracker<SearchBox>(render_view),
62      verbatim_(false),
63      query_is_restricted_(false),
64      selection_start_(0),
65      selection_end_(0),
66      start_margin_(0),
67      is_key_capture_enabled_(false),
68      display_instant_results_(false),
69      omnibox_font_size_(0),
70      autocomplete_results_cache_(kMaxInstantAutocompleteResultItemCacheSize),
71      most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize) {
72}
73
74SearchBox::~SearchBox() {
75}
76
77void SearchBox::SetSuggestions(
78    const std::vector<InstantSuggestion>& suggestions) {
79  if (!suggestions.empty() &&
80      suggestions[0].behavior == INSTANT_COMPLETE_REPLACE) {
81    SetQuery(suggestions[0].text, true);
82    selection_start_ = selection_end_ = query_.size();
83  }
84  // Explicitly allow empty vector to be sent to the browser.
85  render_view()->Send(new ChromeViewHostMsg_SetSuggestions(
86      render_view()->GetRoutingID(), render_view()->GetPageId(), suggestions));
87}
88
89void SearchBox::MarkQueryAsRestricted() {
90  query_is_restricted_ = true;
91  query_.clear();
92}
93
94void SearchBox::ShowInstantOverlay(int height, InstantSizeUnits units) {
95  render_view()->Send(new ChromeViewHostMsg_ShowInstantOverlay(
96      render_view()->GetRoutingID(), render_view()->GetPageId(), height,
97      units));
98}
99
100void SearchBox::FocusOmnibox() {
101  render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
102      render_view()->GetRoutingID(), render_view()->GetPageId(),
103      OMNIBOX_FOCUS_VISIBLE));
104}
105
106void SearchBox::StartCapturingKeyStrokes() {
107  render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
108      render_view()->GetRoutingID(), render_view()->GetPageId(),
109      OMNIBOX_FOCUS_INVISIBLE));
110}
111
112void SearchBox::StopCapturingKeyStrokes() {
113  render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
114      render_view()->GetRoutingID(), render_view()->GetPageId(),
115      OMNIBOX_FOCUS_NONE));
116}
117
118void SearchBox::NavigateToURL(const GURL& url,
119                              content::PageTransition transition,
120                              WindowOpenDisposition disposition,
121                              bool is_search_type) {
122  render_view()->Send(new ChromeViewHostMsg_SearchBoxNavigate(
123      render_view()->GetRoutingID(), render_view()->GetPageId(),
124      url, transition, disposition, is_search_type));
125}
126
127void SearchBox::DeleteMostVisitedItem(
128    InstantRestrictedID most_visited_item_id) {
129  render_view()->Send(new ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem(
130      render_view()->GetRoutingID(), most_visited_item_id));
131}
132
133void SearchBox::UndoMostVisitedDeletion(
134    InstantRestrictedID most_visited_item_id) {
135  render_view()->Send(new ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion(
136      render_view()->GetRoutingID(), most_visited_item_id));
137}
138
139void SearchBox::UndoAllMostVisitedDeletions() {
140  render_view()->Send(
141      new ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions(
142      render_view()->GetRoutingID()));
143}
144
145void SearchBox::ShowBars() {
146  DVLOG(1) << render_view() << " ShowBars";
147  render_view()->Send(new ChromeViewHostMsg_SearchBoxShowBars(
148      render_view()->GetRoutingID(), render_view()->GetPageId()));
149}
150
151void SearchBox::HideBars() {
152  DVLOG(1) << render_view() << " HideBars";
153  render_view()->Send(new ChromeViewHostMsg_SearchBoxHideBars(
154      render_view()->GetRoutingID(), render_view()->GetPageId()));
155}
156
157int SearchBox::GetStartMargin() const {
158  return static_cast<int>(start_margin_ / GetZoom());
159}
160
161gfx::Rect SearchBox::GetPopupBounds() const {
162  double zoom = GetZoom();
163  return gfx::Rect(static_cast<int>(popup_bounds_.x() / zoom),
164                   static_cast<int>(popup_bounds_.y() / zoom),
165                   static_cast<int>(popup_bounds_.width() / zoom),
166                   static_cast<int>(popup_bounds_.height() / zoom));
167}
168
169void SearchBox::GetAutocompleteResults(
170    std::vector<InstantAutocompleteResultIDPair>* results) const {
171  autocomplete_results_cache_.GetCurrentItems(results);
172}
173
174bool SearchBox::GetAutocompleteResultWithID(
175    InstantRestrictedID autocomplete_result_id,
176    InstantAutocompleteResult* result) const {
177  return autocomplete_results_cache_.GetItemWithRestrictedID(
178      autocomplete_result_id, result);
179}
180
181const ThemeBackgroundInfo& SearchBox::GetThemeBackgroundInfo() {
182  return theme_info_;
183}
184
185bool SearchBox::OnMessageReceived(const IPC::Message& message) {
186  bool handled = true;
187  IPC_BEGIN_MESSAGE_MAP(SearchBox, message)
188    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxChange, OnChange)
189    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSubmit, OnSubmit)
190    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxCancel, OnCancel)
191    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxPopupResize, OnPopupResize)
192    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMarginChange, OnMarginChange)
193    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxBarsHidden, OnBarsHidden)
194    IPC_MESSAGE_HANDLER(ChromeViewMsg_DetermineIfPageSupportsInstant,
195                        OnDetermineIfPageSupportsInstant)
196    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxAutocompleteResults,
197                        OnAutocompleteResults)
198    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxUpOrDownKeyPressed,
199                        OnUpOrDownKeyPressed)
200    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxEscKeyPressed, OnEscKeyPressed)
201    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxCancelSelection,
202                        OnCancelSelection)
203    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetDisplayInstantResults,
204                        OnSetDisplayInstantResults)
205    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxKeyCaptureChanged,
206                        OnKeyCaptureChange)
207    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxThemeChanged,
208                        OnThemeChanged)
209    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFontInformation,
210                        OnFontInformationReceived)
211    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMostVisitedItemsChanged,
212                        OnMostVisitedChanged)
213    IPC_MESSAGE_UNHANDLED(handled = false)
214  IPC_END_MESSAGE_MAP()
215  return handled;
216}
217
218void SearchBox::DidClearWindowObject(WebKit::WebFrame* frame) {
219  extensions_v8::SearchBoxExtension::DispatchOnWindowReady(frame);
220}
221
222void SearchBox::OnChange(const string16& query,
223                         bool verbatim,
224                         size_t selection_start,
225                         size_t selection_end) {
226  SetQuery(query, verbatim);
227  selection_start_ = selection_start;
228  selection_end_ = selection_end;
229
230  // If |query| is empty, this is due to the user backspacing away all the text
231  // in the omnibox, or hitting Escape to restore the "permanent URL", or
232  // switching tabs, etc. In all these cases, there will be no corresponding
233  // OnAutocompleteResults(), so clear the autocomplete results ourselves, by
234  // adding an empty set. Don't notify the page using an "onnativesuggestions"
235  // event, though.
236  if (query.empty()) {
237    autocomplete_results_cache_.AddItems(
238        std::vector<InstantAutocompleteResult>());
239  }
240
241  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
242    DVLOG(1) << render_view() << " OnChange";
243    extensions_v8::SearchBoxExtension::DispatchChange(
244        render_view()->GetWebView()->mainFrame());
245  }
246}
247
248void SearchBox::OnSubmit(const string16& query) {
249  // Submit() is called when the user hits Enter to commit the omnibox text.
250  // If |query| is non-blank, the user committed a search. If it's blank, the
251  // omnibox text was a URL, and the user is navigating to it, in which case
252  // we shouldn't update the |query_| or associated state.
253  if (!query.empty()) {
254    SetQuery(query, true);
255    selection_start_ = selection_end_ = query_.size();
256  }
257
258  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
259    DVLOG(1) << render_view() << " OnSubmit";
260    extensions_v8::SearchBoxExtension::DispatchSubmit(
261        render_view()->GetWebView()->mainFrame());
262  }
263
264  if (!query.empty())
265    Reset();
266}
267
268void SearchBox::OnCancel(const string16& query) {
269  SetQuery(query, true);
270  selection_start_ = selection_end_ = query_.size();
271  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
272    DVLOG(1) << render_view() << " OnCancel";
273    extensions_v8::SearchBoxExtension::DispatchCancel(
274        render_view()->GetWebView()->mainFrame());
275  }
276  Reset();
277}
278
279void SearchBox::OnPopupResize(const gfx::Rect& bounds) {
280  popup_bounds_ = bounds;
281  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
282    DVLOG(1) << render_view() << " OnPopupResize";
283    extensions_v8::SearchBoxExtension::DispatchResize(
284        render_view()->GetWebView()->mainFrame());
285  }
286}
287
288void SearchBox::OnMarginChange(int margin, int width) {
289  start_margin_ = margin;
290
291  // Override only the width parameter of the popup bounds.
292  popup_bounds_.set_width(width);
293
294  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
295    extensions_v8::SearchBoxExtension::DispatchMarginChange(
296        render_view()->GetWebView()->mainFrame());
297  }
298}
299
300void SearchBox::OnBarsHidden() {
301  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
302    extensions_v8::SearchBoxExtension::DispatchBarsHidden(
303        render_view()->GetWebView()->mainFrame());
304  }
305}
306
307void SearchBox::OnDetermineIfPageSupportsInstant() {
308  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
309    bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant(
310        render_view()->GetWebView()->mainFrame());
311    DVLOG(1) << render_view() << " PageSupportsInstant: " << result;
312    render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined(
313        render_view()->GetRoutingID(), render_view()->GetPageId(), result));
314  }
315}
316
317void SearchBox::OnAutocompleteResults(
318    const std::vector<InstantAutocompleteResult>& results) {
319  autocomplete_results_cache_.AddItems(results);
320  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
321    DVLOG(1) << render_view() << " OnAutocompleteResults";
322    extensions_v8::SearchBoxExtension::DispatchAutocompleteResults(
323        render_view()->GetWebView()->mainFrame());
324  }
325}
326
327void SearchBox::OnUpOrDownKeyPressed(int count) {
328  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
329    DVLOG(1) << render_view() << " OnKeyPress: " << count;
330    extensions_v8::SearchBoxExtension::DispatchUpOrDownKeyPress(
331        render_view()->GetWebView()->mainFrame(), count);
332  }
333}
334
335void SearchBox::OnEscKeyPressed() {
336  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
337    DVLOG(1) << render_view() << " OnEscKeyPressed ";
338    extensions_v8::SearchBoxExtension::DispatchEscKeyPress(
339        render_view()->GetWebView()->mainFrame());
340  }
341}
342
343void SearchBox::OnCancelSelection(const string16& query,
344                                  bool verbatim,
345                                  size_t selection_start,
346                                  size_t selection_end) {
347  SetQuery(query, verbatim);
348  selection_start_ = selection_start;
349  selection_end_ = selection_end;
350  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
351    DVLOG(1) << render_view() << " OnKeyPress ESC";
352    extensions_v8::SearchBoxExtension::DispatchEscKeyPress(
353        render_view()->GetWebView()->mainFrame());
354  }
355}
356
357void SearchBox::OnKeyCaptureChange(bool is_key_capture_enabled) {
358  if (is_key_capture_enabled != is_key_capture_enabled_ &&
359      render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
360    is_key_capture_enabled_ = is_key_capture_enabled;
361    DVLOG(1) << render_view() << " OnKeyCaptureChange";
362    extensions_v8::SearchBoxExtension::DispatchKeyCaptureChange(
363        render_view()->GetWebView()->mainFrame());
364  }
365}
366
367void SearchBox::OnSetDisplayInstantResults(bool display_instant_results) {
368  display_instant_results_ = display_instant_results;
369}
370
371void SearchBox::OnThemeChanged(const ThemeBackgroundInfo& theme_info) {
372  if (IsThemeInfoEqual(theme_info, theme_info_))
373    return;
374  theme_info_ = theme_info;
375  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
376    extensions_v8::SearchBoxExtension::DispatchThemeChange(
377        render_view()->GetWebView()->mainFrame());
378  }
379}
380
381void SearchBox::OnFontInformationReceived(const string16& omnibox_font,
382                                          size_t omnibox_font_size) {
383  omnibox_font_ = omnibox_font;
384  omnibox_font_size_ = omnibox_font_size;
385}
386
387double SearchBox::GetZoom() const {
388  WebKit::WebView* web_view = render_view()->GetWebView();
389  if (web_view) {
390    double zoom = WebKit::WebView::zoomLevelToZoomFactor(web_view->zoomLevel());
391    if (zoom != 0)
392      return zoom;
393  }
394  return 1.0;
395}
396
397void SearchBox::Reset() {
398  query_.clear();
399  verbatim_ = false;
400  query_is_restricted_ = false;
401  selection_start_ = 0;
402  selection_end_ = 0;
403  popup_bounds_ = gfx::Rect();
404  start_margin_ = 0;
405  is_key_capture_enabled_ = false;
406  theme_info_ = ThemeBackgroundInfo();
407  // Don't reset display_instant_results_ to prevent clearing it on committed
408  // results pages in extended mode. Otherwise resetting it is a no-op because
409  // a new loader is created when it changes; see crbug.com/164662.
410  // Also don't reset omnibox_font_ or omnibox_font_size_ since it never
411  // changes.
412}
413
414void SearchBox::SetQuery(const string16& query, bool verbatim) {
415  query_ = query;
416  verbatim_ = verbatim;
417  query_is_restricted_ = false;
418}
419
420void SearchBox::OnMostVisitedChanged(
421    const std::vector<InstantMostVisitedItemIDPair>& items) {
422  std::vector<InstantMostVisitedItemIDPair> old_items;
423  most_visited_items_cache_.GetCurrentItems(&old_items);
424  if (AreMostVisitedItemsEqual(items, old_items))
425    return;
426
427  most_visited_items_cache_.AddItemsWithRestrictedID(items);
428
429  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
430    extensions_v8::SearchBoxExtension::DispatchMostVisitedChanged(
431        render_view()->GetWebView()->mainFrame());
432  }
433}
434
435void SearchBox::GetMostVisitedItems(
436    std::vector<InstantMostVisitedItemIDPair>* items) const {
437  return most_visited_items_cache_.GetCurrentItems(items);
438}
439
440bool SearchBox::GetMostVisitedItemWithID(
441    InstantRestrictedID most_visited_item_id,
442    InstantMostVisitedItem* item) const {
443  return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id,
444                                                           item);
445}
446