searchbox.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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 <string> 8 9#include "base/strings/string_number_conversions.h" 10#include "base/strings/string_util.h" 11#include "base/strings/utf_string_conversions.h" 12#include "chrome/common/chrome_switches.h" 13#include "chrome/common/favicon/favicon_url_parser.h" 14#include "chrome/common/omnibox_focus_state.h" 15#include "chrome/common/render_messages.h" 16#include "chrome/common/url_constants.h" 17#include "chrome/renderer/searchbox/searchbox_extension.h" 18#include "components/favicon_base/favicon_types.h" 19#include "content/public/renderer/render_view.h" 20#include "net/base/escape.h" 21#include "third_party/WebKit/public/web/WebDocument.h" 22#include "third_party/WebKit/public/web/WebFrame.h" 23#include "third_party/WebKit/public/web/WebView.h" 24#include "url/gurl.h" 25 26namespace { 27 28// The size of the InstantMostVisitedItem cache. 29const size_t kMaxInstantMostVisitedItemCacheSize = 100; 30 31// Returns true if items stored in |old_item_id_pairs| and |new_items| are 32// equal. 33bool AreMostVisitedItemsEqual( 34 const std::vector<InstantMostVisitedItemIDPair>& old_item_id_pairs, 35 const std::vector<InstantMostVisitedItem>& new_items) { 36 if (old_item_id_pairs.size() != new_items.size()) 37 return false; 38 39 for (size_t i = 0; i < new_items.size(); ++i) { 40 if (new_items[i].url != old_item_id_pairs[i].second.url || 41 new_items[i].title != old_item_id_pairs[i].second.title) { 42 return false; 43 } 44 } 45 return true; 46} 47 48} // namespace 49 50namespace internal { // for testing 51 52// Parses |path| and fills in |id| with the InstantRestrictedID obtained from 53// the |path|. |render_view_id| is the ID of the associated RenderView. 54// 55// |path| is a pair of |render_view_id| and |restricted_id|, and it is 56// contained in Instant Extended URLs. A valid |path| is in the form: 57// <render_view_id>/<restricted_id> 58// 59// If the |path| is valid, returns true and fills in |id| with restricted_id 60// value. If the |path| is invalid, returns false and |id| is not set. 61bool GetInstantRestrictedIDFromPath(int render_view_id, 62 const std::string& path, 63 InstantRestrictedID* id) { 64 // Check that the path is of Most visited item ID form. 65 std::vector<std::string> tokens; 66 if (Tokenize(path, "/", &tokens) != 2) 67 return false; 68 69 int view_id = 0; 70 if (!base::StringToInt(tokens[0], &view_id) || view_id != render_view_id) 71 return false; 72 return base::StringToInt(tokens[1], id); 73} 74 75bool GetRestrictedIDFromFaviconUrl(int render_view_id, 76 const GURL& url, 77 std::string* favicon_params, 78 InstantRestrictedID* rid) { 79 // Strip leading slash. 80 std::string raw_path = url.path(); 81 DCHECK_GT(raw_path.length(), (size_t) 0); 82 DCHECK_EQ(raw_path[0], '/'); 83 raw_path = raw_path.substr(1); 84 85 chrome::ParsedFaviconPath parsed; 86 if (!chrome::ParseFaviconPath(raw_path, favicon_base::FAVICON, &parsed)) 87 return false; 88 89 // The part of the URL which details the favicon parameters should be returned 90 // so the favicon URL can be reconstructed, by replacing the restricted_id 91 // with the actual URL from which the favicon is being requested. 92 *favicon_params = raw_path.substr(0, parsed.path_index); 93 94 // The part of the favicon URL which is supposed to contain the URL from 95 // which the favicon is being requested (i.e., the page's URL) actually 96 // contains a pair in the format "<view_id>/<restricted_id>". If the page's 97 // URL is not in the expected format then the execution must be stopped, 98 // returning |true|, indicating that the favicon URL should be translated 99 // without the page's URL part, to prevent search providers from spoofing 100 // the user's browsing history. For example, the following favicon URL 101 // "chrome-search://favicon/http://www.secretsite.com" it is not in the 102 // expected format "chrome-search://favicon/<view_id>/<restricted_id>" so 103 // the pages's URL part ("http://www.secretsite.com") should be removed 104 // entirely from the translated URL otherwise the search engine would know 105 // if the user has visited that page (by verifying whether the favicon URL 106 // returns an image for a particular page's URL); the translated URL in this 107 // case would be "chrome-search://favicon/" which would simply return the 108 // default favicon. 109 std::string id_part = raw_path.substr(parsed.path_index); 110 InstantRestrictedID id; 111 if (!GetInstantRestrictedIDFromPath(render_view_id, id_part, &id)) 112 return true; 113 114 *rid = id; 115 return true; 116} 117 118// Parses a thumbnail |url| and fills in |id| with the InstantRestrictedID 119// obtained from the |url|. |render_view_id| is the ID of the associated 120// RenderView. 121// 122// Valid |url| forms: 123// chrome-search://thumb/<view_id>/<restricted_id> 124// 125// If the |url| is valid, returns true and fills in |id| with restricted_id 126// value. If the |url| is invalid, returns false and |id| is not set. 127bool GetRestrictedIDFromThumbnailUrl(int render_view_id, 128 const GURL& url, 129 InstantRestrictedID* id) { 130 // Strip leading slash. 131 std::string path = url.path(); 132 DCHECK_GT(path.length(), (size_t) 0); 133 DCHECK_EQ(path[0], '/'); 134 path = path.substr(1); 135 136 return GetInstantRestrictedIDFromPath(render_view_id, path, id); 137} 138 139} // namespace internal 140 141SearchBox::SearchBox(content::RenderView* render_view) 142 : content::RenderViewObserver(render_view), 143 content::RenderViewObserverTracker<SearchBox>(render_view), 144 page_seq_no_(0), 145 app_launcher_enabled_(false), 146 is_focused_(false), 147 is_input_in_progress_(false), 148 is_key_capture_enabled_(false), 149 display_instant_results_(false), 150 most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize), 151 query_(), 152 start_margin_(0) { 153} 154 155SearchBox::~SearchBox() { 156} 157 158void SearchBox::LogEvent(NTPLoggingEventType event) { 159 render_view()->Send(new ChromeViewHostMsg_LogEvent( 160 render_view()->GetRoutingID(), page_seq_no_, event)); 161} 162 163void SearchBox::LogMostVisitedImpression(int position, 164 const base::string16& provider) { 165 render_view()->Send(new ChromeViewHostMsg_LogMostVisitedImpression( 166 render_view()->GetRoutingID(), page_seq_no_, position, provider)); 167} 168 169void SearchBox::LogMostVisitedNavigation(int position, 170 const base::string16& provider) { 171 render_view()->Send(new ChromeViewHostMsg_LogMostVisitedNavigation( 172 render_view()->GetRoutingID(), page_seq_no_, position, provider)); 173} 174 175void SearchBox::CheckIsUserSignedInToChromeAs(const base::string16& identity) { 176 render_view()->Send(new ChromeViewHostMsg_ChromeIdentityCheck( 177 render_view()->GetRoutingID(), page_seq_no_, identity)); 178} 179 180void SearchBox::DeleteMostVisitedItem( 181 InstantRestrictedID most_visited_item_id) { 182 render_view()->Send(new ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem( 183 render_view()->GetRoutingID(), 184 page_seq_no_, 185 GetURLForMostVisitedItem(most_visited_item_id))); 186} 187 188bool SearchBox::GenerateFaviconURLFromTransientURL(const GURL& transient_url, 189 GURL* url) const { 190 std::string favicon_params; 191 InstantRestrictedID rid = -1; 192 bool success = internal::GetRestrictedIDFromFaviconUrl( 193 render_view()->GetRoutingID(), transient_url, &favicon_params, &rid); 194 if (!success) 195 return false; 196 197 InstantMostVisitedItem item; 198 std::string item_url; 199 if (rid != -1 && GetMostVisitedItemWithID(rid, &item)) 200 item_url = item.url.spec(); 201 202 *url = GURL(base::StringPrintf("chrome-search://favicon/%s%s", 203 favicon_params.c_str(), 204 item_url.c_str())); 205 return true; 206} 207 208bool SearchBox::GenerateThumbnailURLFromTransientURL(const GURL& transient_url, 209 GURL* url) const { 210 InstantRestrictedID rid = 0; 211 if (!internal::GetRestrictedIDFromThumbnailUrl(render_view()->GetRoutingID(), 212 transient_url, &rid)) { 213 return false; 214 } 215 216 GURL most_visited_item_url(GetURLForMostVisitedItem(rid)); 217 if (most_visited_item_url.is_empty()) 218 return false; 219 *url = GURL(base::StringPrintf("chrome-search://thumb/%s", 220 most_visited_item_url.spec().c_str())); 221 return true; 222} 223 224void SearchBox::GetMostVisitedItems( 225 std::vector<InstantMostVisitedItemIDPair>* items) const { 226 return most_visited_items_cache_.GetCurrentItems(items); 227} 228 229bool SearchBox::GetMostVisitedItemWithID( 230 InstantRestrictedID most_visited_item_id, 231 InstantMostVisitedItem* item) const { 232 return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id, 233 item); 234} 235 236const ThemeBackgroundInfo& SearchBox::GetThemeBackgroundInfo() { 237 return theme_info_; 238} 239 240void SearchBox::Focus() { 241 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox( 242 render_view()->GetRoutingID(), page_seq_no_, OMNIBOX_FOCUS_VISIBLE)); 243} 244 245void SearchBox::NavigateToURL(const GURL& url, 246 WindowOpenDisposition disposition, 247 bool is_most_visited_item_url) { 248 render_view()->Send(new ChromeViewHostMsg_SearchBoxNavigate( 249 render_view()->GetRoutingID(), page_seq_no_, url, 250 disposition, is_most_visited_item_url)); 251} 252 253void SearchBox::Paste(const base::string16& text) { 254 render_view()->Send(new ChromeViewHostMsg_PasteAndOpenDropdown( 255 render_view()->GetRoutingID(), page_seq_no_, text)); 256} 257 258void SearchBox::SetVoiceSearchSupported(bool supported) { 259 render_view()->Send(new ChromeViewHostMsg_SetVoiceSearchSupported( 260 render_view()->GetRoutingID(), page_seq_no_, supported)); 261} 262 263void SearchBox::StartCapturingKeyStrokes() { 264 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox( 265 render_view()->GetRoutingID(), page_seq_no_, OMNIBOX_FOCUS_INVISIBLE)); 266} 267 268void SearchBox::StopCapturingKeyStrokes() { 269 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox( 270 render_view()->GetRoutingID(), page_seq_no_, OMNIBOX_FOCUS_NONE)); 271} 272 273void SearchBox::UndoAllMostVisitedDeletions() { 274 render_view()->Send( 275 new ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions( 276 render_view()->GetRoutingID(), page_seq_no_)); 277} 278 279void SearchBox::UndoMostVisitedDeletion( 280 InstantRestrictedID most_visited_item_id) { 281 render_view()->Send(new ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion( 282 render_view()->GetRoutingID(), page_seq_no_, 283 GetURLForMostVisitedItem(most_visited_item_id))); 284} 285 286bool SearchBox::OnMessageReceived(const IPC::Message& message) { 287 bool handled = true; 288 IPC_BEGIN_MESSAGE_MAP(SearchBox, message) 289 IPC_MESSAGE_HANDLER(ChromeViewMsg_SetPageSequenceNumber, 290 OnSetPageSequenceNumber) 291 IPC_MESSAGE_HANDLER(ChromeViewMsg_ChromeIdentityCheckResult, 292 OnChromeIdentityCheckResult) 293 IPC_MESSAGE_HANDLER(ChromeViewMsg_DetermineIfPageSupportsInstant, 294 OnDetermineIfPageSupportsInstant) 295 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFocusChanged, OnFocusChanged) 296 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMarginChange, OnMarginChange) 297 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMostVisitedItemsChanged, 298 OnMostVisitedChanged) 299 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxPromoInformation, 300 OnPromoInformationReceived) 301 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetDisplayInstantResults, 302 OnSetDisplayInstantResults) 303 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetInputInProgress, 304 OnSetInputInProgress) 305 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetSuggestionToPrefetch, 306 OnSetSuggestionToPrefetch) 307 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSubmit, OnSubmit) 308 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxThemeChanged, 309 OnThemeChanged) 310 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxToggleVoiceSearch, 311 OnToggleVoiceSearch) 312 IPC_MESSAGE_UNHANDLED(handled = false) 313 IPC_END_MESSAGE_MAP() 314 return handled; 315} 316 317void SearchBox::OnSetPageSequenceNumber(int page_seq_no) { 318 page_seq_no_ = page_seq_no; 319} 320 321void SearchBox::OnChromeIdentityCheckResult(const base::string16& identity, 322 bool identity_match) { 323 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 324 extensions_v8::SearchBoxExtension::DispatchChromeIdentityCheckResult( 325 render_view()->GetWebView()->mainFrame(), identity, identity_match); 326 } 327} 328 329void SearchBox::OnDetermineIfPageSupportsInstant() { 330 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 331 bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant( 332 render_view()->GetWebView()->mainFrame()); 333 DVLOG(1) << render_view() << " PageSupportsInstant: " << result; 334 render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined( 335 render_view()->GetRoutingID(), page_seq_no_, result)); 336 } 337} 338 339void SearchBox::OnFocusChanged(OmniboxFocusState new_focus_state, 340 OmniboxFocusChangeReason reason) { 341 bool key_capture_enabled = new_focus_state == OMNIBOX_FOCUS_INVISIBLE; 342 if (key_capture_enabled != is_key_capture_enabled_) { 343 // Tell the page if the key capture mode changed unless the focus state 344 // changed because of TYPING. This is because in that case, the browser 345 // hasn't really stopped capturing key strokes. 346 // 347 // (More practically, if we don't do this check, the page would receive 348 // onkeycapturechange before the corresponding onchange, and the page would 349 // have no way of telling whether the keycapturechange happened because of 350 // some actual user action or just because they started typing.) 351 if (reason != OMNIBOX_FOCUS_CHANGE_TYPING && 352 render_view()->GetWebView() && 353 render_view()->GetWebView()->mainFrame()) { 354 is_key_capture_enabled_ = key_capture_enabled; 355 DVLOG(1) << render_view() << " OnKeyCaptureChange"; 356 extensions_v8::SearchBoxExtension::DispatchKeyCaptureChange( 357 render_view()->GetWebView()->mainFrame()); 358 } 359 } 360 bool is_focused = new_focus_state == OMNIBOX_FOCUS_VISIBLE; 361 if (is_focused != is_focused_) { 362 is_focused_ = is_focused; 363 DVLOG(1) << render_view() << " OnFocusChange"; 364 if (render_view()->GetWebView() && 365 render_view()->GetWebView()->mainFrame()) { 366 extensions_v8::SearchBoxExtension::DispatchFocusChange( 367 render_view()->GetWebView()->mainFrame()); 368 } 369 } 370} 371 372void SearchBox::OnMarginChange(int margin) { 373 start_margin_ = margin; 374 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 375 extensions_v8::SearchBoxExtension::DispatchMarginChange( 376 render_view()->GetWebView()->mainFrame()); 377 } 378} 379 380void SearchBox::OnMostVisitedChanged( 381 const std::vector<InstantMostVisitedItem>& items) { 382 std::vector<InstantMostVisitedItemIDPair> last_known_items; 383 GetMostVisitedItems(&last_known_items); 384 385 if (AreMostVisitedItemsEqual(last_known_items, items)) 386 return; // Do not send duplicate onmostvisitedchange events. 387 388 most_visited_items_cache_.AddItems(items); 389 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 390 extensions_v8::SearchBoxExtension::DispatchMostVisitedChanged( 391 render_view()->GetWebView()->mainFrame()); 392 } 393} 394 395void SearchBox::OnPromoInformationReceived(bool is_app_launcher_enabled) { 396 app_launcher_enabled_ = is_app_launcher_enabled; 397} 398 399void SearchBox::OnSetDisplayInstantResults(bool display_instant_results) { 400 display_instant_results_ = display_instant_results; 401} 402 403void SearchBox::OnSetInputInProgress(bool is_input_in_progress) { 404 if (is_input_in_progress_ != is_input_in_progress) { 405 is_input_in_progress_ = is_input_in_progress; 406 DVLOG(1) << render_view() << " OnSetInputInProgress"; 407 if (render_view()->GetWebView() && 408 render_view()->GetWebView()->mainFrame()) { 409 if (is_input_in_progress_) { 410 extensions_v8::SearchBoxExtension::DispatchInputStart( 411 render_view()->GetWebView()->mainFrame()); 412 } else { 413 extensions_v8::SearchBoxExtension::DispatchInputCancel( 414 render_view()->GetWebView()->mainFrame()); 415 } 416 } 417 } 418} 419 420void SearchBox::OnSetSuggestionToPrefetch(const InstantSuggestion& suggestion) { 421 suggestion_ = suggestion; 422 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 423 DVLOG(1) << render_view() << " OnSetSuggestionToPrefetch"; 424 extensions_v8::SearchBoxExtension::DispatchSuggestionChange( 425 render_view()->GetWebView()->mainFrame()); 426 } 427} 428 429void SearchBox::OnSubmit(const base::string16& query) { 430 query_ = query; 431 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 432 DVLOG(1) << render_view() << " OnSubmit"; 433 extensions_v8::SearchBoxExtension::DispatchSubmit( 434 render_view()->GetWebView()->mainFrame()); 435 } 436 if (!query.empty()) 437 Reset(); 438} 439 440void SearchBox::OnThemeChanged(const ThemeBackgroundInfo& theme_info) { 441 // Do not send duplicate notifications. 442 if (theme_info_ == theme_info) 443 return; 444 445 theme_info_ = theme_info; 446 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 447 extensions_v8::SearchBoxExtension::DispatchThemeChange( 448 render_view()->GetWebView()->mainFrame()); 449 } 450} 451 452void SearchBox::OnToggleVoiceSearch() { 453 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 454 extensions_v8::SearchBoxExtension::DispatchToggleVoiceSearch( 455 render_view()->GetWebView()->mainFrame()); 456 } 457} 458 459GURL SearchBox::GetURLForMostVisitedItem(InstantRestrictedID item_id) const { 460 InstantMostVisitedItem item; 461 return GetMostVisitedItemWithID(item_id, &item) ? item.url : GURL(); 462} 463 464void SearchBox::Reset() { 465 query_.clear(); 466 suggestion_ = InstantSuggestion(); 467 start_margin_ = 0; 468 is_focused_ = false; 469 is_key_capture_enabled_ = false; 470 theme_info_ = ThemeBackgroundInfo(); 471} 472