searchbox.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/render_messages.h"
10#include "chrome/common/url_constants.h"
11#include "chrome/renderer/searchbox/searchbox_extension.h"
12#include "content/public/renderer/render_view.h"
13#include "grit/renderer_resources.h"
14#include "net/base/escape.h"
15#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
16#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
17#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
18#include "ui/base/resource/resource_bundle.h"
19
20namespace {
21
22// Size of the results cache.
23const size_t kMaxInstantAutocompleteResultItemCacheSize = 100;
24
25// The HTML returned when an invalid or unknown restricted ID is requested.
26const char kInvalidSuggestionHtml[] =
27    "<div style=\"background:red\">invalid rid %s</div>";
28
29// Checks if the input color is in valid range.
30bool IsColorValid(int color) {
31  return color >= 0 && color <= 0xffffff;
32}
33
34// If |url| starts with |prefix|, removes |prefix|.
35void StripPrefix(string16* url, const string16& prefix) {
36  if (StartsWith(*url, prefix, true))
37    url->erase(0, prefix.length());
38}
39
40// Removes leading "http://" or "http://www." from |url| unless |userInput|
41// starts with those prefixes.
42void StripURLPrefixes(string16* url, const string16& userInput) {
43  string16 trimmedUserInput;
44  TrimWhitespace(userInput, TRIM_TRAILING, &trimmedUserInput);
45  if (StartsWith(*url, trimmedUserInput, true))
46    return;
47
48  StripPrefix(url, ASCIIToUTF16(chrome::kHttpScheme) + ASCIIToUTF16("://"));
49
50  if (StartsWith(*url, trimmedUserInput, true))
51    return;
52
53  StripPrefix(url, ASCIIToUTF16("www."));
54}
55
56}  // namespace
57
58SearchBox::SearchBox(content::RenderView* render_view)
59    : content::RenderViewObserver(render_view),
60      content::RenderViewObserverTracker<SearchBox>(render_view),
61      verbatim_(false),
62      selection_start_(0),
63      selection_end_(0),
64      start_margin_(0),
65      is_key_capture_enabled_(false),
66      display_instant_results_(false),
67      omnibox_font_size_(0),
68      autocomplete_results_cache_(kMaxInstantAutocompleteResultItemCacheSize),
69      most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize) {
70}
71
72SearchBox::~SearchBox() {
73}
74
75void SearchBox::SetSuggestions(
76    const std::vector<InstantSuggestion>& suggestions) {
77  if (!suggestions.empty() &&
78      suggestions[0].behavior == INSTANT_COMPLETE_REPLACE) {
79    query_ = suggestions[0].text;
80    verbatim_ = true;
81    selection_start_ = selection_end_ = query_.size();
82  }
83  // Explicitly allow empty vector to be sent to the browser.
84  render_view()->Send(new ChromeViewHostMsg_SetSuggestions(
85      render_view()->GetRoutingID(), render_view()->GetPageId(), suggestions));
86}
87
88void SearchBox::ClearQuery() {
89  query_.clear();
90}
91
92void SearchBox::ShowInstantOverlay(int height, InstantSizeUnits units) {
93  render_view()->Send(new ChromeViewHostMsg_ShowInstantOverlay(
94      render_view()->GetRoutingID(), render_view()->GetPageId(), height,
95      units));
96}
97
98void SearchBox::FocusOmnibox() {
99  render_view()->Send(new ChromeViewHostMsg_FocusOmnibox(
100      render_view()->GetRoutingID(), render_view()->GetPageId()));
101}
102
103void SearchBox::StartCapturingKeyStrokes() {
104  render_view()->Send(new ChromeViewHostMsg_StartCapturingKeyStrokes(
105      render_view()->GetRoutingID(), render_view()->GetPageId()));
106}
107
108void SearchBox::StopCapturingKeyStrokes() {
109  render_view()->Send(new ChromeViewHostMsg_StopCapturingKeyStrokes(
110      render_view()->GetRoutingID(), render_view()->GetPageId()));
111}
112
113void SearchBox::NavigateToURL(const GURL& url,
114                              content::PageTransition transition,
115                              WindowOpenDisposition disposition) {
116  render_view()->Send(new ChromeViewHostMsg_SearchBoxNavigate(
117      render_view()->GetRoutingID(), render_view()->GetPageId(),
118      url, transition, disposition));
119}
120
121void SearchBox::DeleteMostVisitedItem(
122    InstantRestrictedID most_visited_item_id) {
123  render_view()->Send(new ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem(
124      render_view()->GetRoutingID(), most_visited_item_id));
125}
126
127void SearchBox::UndoMostVisitedDeletion(
128    InstantRestrictedID most_visited_item_id) {
129  render_view()->Send(new ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion(
130      render_view()->GetRoutingID(), most_visited_item_id));
131}
132
133void SearchBox::UndoAllMostVisitedDeletions() {
134  render_view()->Send(
135      new ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions(
136      render_view()->GetRoutingID()));
137}
138
139void SearchBox::ShowBars() {
140  DVLOG(1) << render_view() << " ShowBars";
141  render_view()->Send(new ChromeViewHostMsg_SearchBoxShowBars(
142      render_view()->GetRoutingID(), render_view()->GetPageId()));
143}
144
145void SearchBox::HideBars() {
146  DVLOG(1) << render_view() << " HideBars";
147  render_view()->Send(new ChromeViewHostMsg_SearchBoxHideBars(
148      render_view()->GetRoutingID(), render_view()->GetPageId()));
149}
150
151int SearchBox::GetStartMargin() const {
152  return static_cast<int>(start_margin_ / GetZoom());
153}
154
155gfx::Rect SearchBox::GetPopupBounds() const {
156  double zoom = GetZoom();
157  return gfx::Rect(static_cast<int>(popup_bounds_.x() / zoom),
158                   static_cast<int>(popup_bounds_.y() / zoom),
159                   static_cast<int>(popup_bounds_.width() / zoom),
160                   static_cast<int>(popup_bounds_.height() / zoom));
161}
162
163void SearchBox::GetAutocompleteResults(
164    std::vector<InstantAutocompleteResultIDPair>* results) const {
165  autocomplete_results_cache_.GetCurrentItems(results);
166}
167
168bool SearchBox::GetAutocompleteResultWithID(
169    InstantRestrictedID autocomplete_result_id,
170    InstantAutocompleteResult* result) const {
171  return autocomplete_results_cache_.GetItemWithRestrictedID(
172      autocomplete_result_id, result);
173}
174
175const ThemeBackgroundInfo& SearchBox::GetThemeBackgroundInfo() {
176  return theme_info_;
177}
178
179bool SearchBox::OnMessageReceived(const IPC::Message& message) {
180  bool handled = true;
181  IPC_BEGIN_MESSAGE_MAP(SearchBox, message)
182    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxChange, OnChange)
183    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSubmit, OnSubmit)
184    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxCancel, OnCancel)
185    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxPopupResize, OnPopupResize)
186    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMarginChange, OnMarginChange)
187    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxBarsHidden, OnBarsHidden)
188    IPC_MESSAGE_HANDLER(ChromeViewMsg_DetermineIfPageSupportsInstant,
189                        OnDetermineIfPageSupportsInstant)
190    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxAutocompleteResults,
191                        OnAutocompleteResults)
192    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxUpOrDownKeyPressed,
193                        OnUpOrDownKeyPressed)
194    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxCancelSelection,
195                        OnCancelSelection)
196    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetDisplayInstantResults,
197                        OnSetDisplayInstantResults)
198    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxKeyCaptureChanged,
199                        OnKeyCaptureChange)
200    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxThemeChanged,
201                        OnThemeChanged)
202    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFontInformation,
203                        OnFontInformationReceived)
204    IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMostVisitedItemsChanged,
205                        OnMostVisitedChanged)
206    IPC_MESSAGE_UNHANDLED(handled = false)
207  IPC_END_MESSAGE_MAP()
208  return handled;
209}
210
211void SearchBox::DidClearWindowObject(WebKit::WebFrame* frame) {
212  extensions_v8::SearchBoxExtension::DispatchOnWindowReady(frame);
213}
214
215void SearchBox::OnChange(const string16& query,
216                         bool verbatim,
217                         size_t selection_start,
218                         size_t selection_end) {
219  query_ = query;
220  verbatim_ = verbatim;
221  selection_start_ = selection_start;
222  selection_end_ = selection_end;
223  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
224    DVLOG(1) << render_view() << " OnChange";
225    extensions_v8::SearchBoxExtension::DispatchChange(
226        render_view()->GetWebView()->mainFrame());
227  }
228}
229
230void SearchBox::OnSubmit(const string16& query) {
231  query_ = query;
232  verbatim_ = true;
233  selection_start_ = selection_end_ = query_.size();
234  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
235    DVLOG(1) << render_view() << " OnSubmit";
236    extensions_v8::SearchBoxExtension::DispatchSubmit(
237        render_view()->GetWebView()->mainFrame());
238  }
239  Reset();
240}
241
242void SearchBox::OnCancel(const string16& query) {
243  query_ = query;
244  verbatim_ = true;
245  selection_start_ = selection_end_ = query_.size();
246  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
247    DVLOG(1) << render_view() << " OnCancel";
248    extensions_v8::SearchBoxExtension::DispatchCancel(
249        render_view()->GetWebView()->mainFrame());
250  }
251  Reset();
252}
253
254void SearchBox::OnPopupResize(const gfx::Rect& bounds) {
255  popup_bounds_ = bounds;
256  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
257    DVLOG(1) << render_view() << " OnPopupResize";
258    extensions_v8::SearchBoxExtension::DispatchResize(
259        render_view()->GetWebView()->mainFrame());
260  }
261}
262
263void SearchBox::OnMarginChange(int margin, int width) {
264  start_margin_ = margin;
265
266  // Override only the width parameter of the popup bounds.
267  popup_bounds_.set_width(width);
268
269  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
270    extensions_v8::SearchBoxExtension::DispatchMarginChange(
271        render_view()->GetWebView()->mainFrame());
272  }
273}
274
275void SearchBox::OnBarsHidden() {
276  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
277    extensions_v8::SearchBoxExtension::DispatchBarsHidden(
278        render_view()->GetWebView()->mainFrame());
279  }
280}
281
282void SearchBox::OnDetermineIfPageSupportsInstant() {
283  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
284    bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant(
285        render_view()->GetWebView()->mainFrame());
286    DVLOG(1) << render_view() << " PageSupportsInstant: " << result;
287    render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined(
288        render_view()->GetRoutingID(), render_view()->GetPageId(), result));
289  }
290}
291
292void SearchBox::OnAutocompleteResults(
293    const std::vector<InstantAutocompleteResult>& results) {
294  autocomplete_results_cache_.AddItems(results);
295  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
296    DVLOG(1) << render_view() << " OnAutocompleteResults";
297    extensions_v8::SearchBoxExtension::DispatchAutocompleteResults(
298        render_view()->GetWebView()->mainFrame());
299  }
300}
301
302void SearchBox::OnUpOrDownKeyPressed(int count) {
303  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
304    DVLOG(1) << render_view() << " OnKeyPress: " << count;
305    extensions_v8::SearchBoxExtension::DispatchUpOrDownKeyPress(
306        render_view()->GetWebView()->mainFrame(), count);
307  }
308}
309
310void SearchBox::OnCancelSelection(const string16& query) {
311  // TODO(sreeram): crbug.com/176101 The state reset below are somewhat wrong.
312  query_ = query;
313  verbatim_ = true;
314  selection_start_ = selection_end_ = query_.size();
315  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
316    DVLOG(1) << render_view() << " OnKeyPress ESC";
317    extensions_v8::SearchBoxExtension::DispatchEscKeyPress(
318        render_view()->GetWebView()->mainFrame());
319  }
320}
321
322void SearchBox::OnKeyCaptureChange(bool is_key_capture_enabled) {
323  if (is_key_capture_enabled != is_key_capture_enabled_ &&
324      render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
325    is_key_capture_enabled_ = is_key_capture_enabled;
326    DVLOG(1) << render_view() << " OnKeyCaptureChange";
327    extensions_v8::SearchBoxExtension::DispatchKeyCaptureChange(
328        render_view()->GetWebView()->mainFrame());
329  }
330}
331
332void SearchBox::OnSetDisplayInstantResults(bool display_instant_results) {
333  display_instant_results_ = display_instant_results;
334}
335
336void SearchBox::OnThemeChanged(const ThemeBackgroundInfo& theme_info) {
337  theme_info_ = theme_info;
338  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
339    extensions_v8::SearchBoxExtension::DispatchThemeChange(
340        render_view()->GetWebView()->mainFrame());
341  }
342}
343
344void SearchBox::OnFontInformationReceived(const string16& omnibox_font,
345                                          size_t omnibox_font_size) {
346  omnibox_font_ = omnibox_font;
347  omnibox_font_size_ = omnibox_font_size;
348}
349
350double SearchBox::GetZoom() const {
351  WebKit::WebView* web_view = render_view()->GetWebView();
352  if (web_view) {
353    double zoom = WebKit::WebView::zoomLevelToZoomFactor(web_view->zoomLevel());
354    if (zoom != 0)
355      return zoom;
356  }
357  return 1.0;
358}
359
360void SearchBox::Reset() {
361  query_.clear();
362  verbatim_ = false;
363  selection_start_ = 0;
364  selection_end_ = 0;
365  popup_bounds_ = gfx::Rect();
366  start_margin_ = 0;
367  is_key_capture_enabled_ = false;
368  theme_info_ = ThemeBackgroundInfo();
369  // Don't reset display_instant_results_ to prevent clearing it on committed
370  // results pages in extended mode. Otherwise resetting it is a no-op because
371  // a new loader is created when it changes; see crbug.com/164662.
372  // Also don't reset omnibox_font_ or omnibox_font_size_ since it never
373  // changes.
374}
375
376void SearchBox::OnMostVisitedChanged(
377    const std::vector<InstantMostVisitedItemIDPair>& items) {
378  most_visited_items_cache_.AddItemsWithRestrictedID(items);
379
380  if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) {
381    extensions_v8::SearchBoxExtension::DispatchMostVisitedChanged(
382        render_view()->GetWebView()->mainFrame());
383  }
384}
385
386void SearchBox::GetMostVisitedItems(
387    std::vector<InstantMostVisitedItemIDPair>* items) const {
388  return most_visited_items_cache_.GetCurrentItems(items);
389}
390
391bool SearchBox::GetMostVisitedItemWithID(
392    InstantRestrictedID most_visited_item_id,
393    InstantMostVisitedItem* item) const {
394  return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id,
395                                                           item);
396}
397
398bool SearchBox::GenerateDataURLForSuggestionRequest(const GURL& request_url,
399                                                    GURL* data_url) const {
400  DCHECK(data_url);
401
402  // The origin URL is required so that the iframe knows what origin to post
403  // messages to.
404  WebKit::WebView* webview = render_view()->GetWebView();
405  if (!webview)
406    return false;
407  GURL embedder_url(webview->mainFrame()->document().url());
408  GURL embedder_origin = embedder_url.GetOrigin();
409  if (!embedder_origin.is_valid())
410    return false;
411
412  DCHECK(StartsWithASCII(request_url.path(), "/", true));
413  std::string restricted_id_str = request_url.path().substr(1);
414
415  InstantRestrictedID restricted_id = 0;
416  DCHECK_EQ(sizeof(InstantRestrictedID), sizeof(int));
417  if (!base::StringToInt(restricted_id_str, &restricted_id))
418    return false;
419
420  std::string response_html;
421  InstantAutocompleteResult result;
422  if (autocomplete_results_cache_.GetItemWithRestrictedID(
423          restricted_id, &result)) {
424    std::string template_html =
425        ResourceBundle::GetSharedInstance().GetRawDataResource(
426            IDR_OMNIBOX_RESULT).as_string();
427
428    DCHECK(IsColorValid(autocomplete_results_style_.url_color));
429    DCHECK(IsColorValid(autocomplete_results_style_.title_color));
430
431    string16 contents;
432    if (result.search_query.empty()) {
433      contents = result.destination_url;
434      FormatURLForDisplay(&contents);
435    } else {
436      contents = result.search_query;
437    }
438
439    response_html = base::StringPrintf(
440        template_html.c_str(),
441        embedder_origin.spec().c_str(),
442        UTF16ToUTF8(omnibox_font_).c_str(),
443        omnibox_font_size_,
444        autocomplete_results_style_.url_color,
445        autocomplete_results_style_.title_color,
446        net::EscapeForHTML(UTF16ToUTF8(contents)).c_str(),
447        net::EscapeForHTML(UTF16ToUTF8(result.description)).c_str());
448  } else {
449    response_html = kInvalidSuggestionHtml;
450  }
451
452  *data_url = GURL("data:text/html," + response_html);
453  return true;
454}
455
456void SearchBox::SetInstantAutocompleteResultStyle(
457    const InstantAutocompleteResultStyle& style) {
458  if (IsColorValid(style.url_color) && IsColorValid(style.title_color))
459    autocomplete_results_style_ = style;
460}
461
462void SearchBox::FormatURLForDisplay(string16* url) const {
463  StripURLPrefixes(url, query());
464
465  string16 trimmedUserInput;
466  TrimWhitespace(query(), TRIM_LEADING, &trimmedUserInput);
467  if (EndsWith(*url, trimmedUserInput, true))
468    return;
469
470  // Strip a lone trailing slash.
471  if (EndsWith(*url, ASCIIToUTF16("/"), true))
472    url->erase(url->length() - 1, 1);
473}
474