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