searchbox.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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/omnibox_focus_state.h" 14#include "chrome/common/render_messages.h" 15#include "chrome/common/url_constants.h" 16#include "chrome/renderer/searchbox/searchbox_extension.h" 17#include "content/public/renderer/render_view.h" 18#include "grit/renderer_resources.h" 19#include "net/base/escape.h" 20#include "third_party/WebKit/public/web/WebDocument.h" 21#include "third_party/WebKit/public/web/WebFrame.h" 22#include "third_party/WebKit/public/web/WebView.h" 23#include "ui/base/resource/resource_bundle.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 |url| and fills in |id| with the InstantRestrictedID obtained from the 53// |url|. |render_view_id| is the ID of the associated RenderView. 54// 55// Valid |url| forms: 56// chrome-search://favicon/<view_id>/<restricted_id> 57// chrome-search://thumb/<view_id>/<restricted_id> 58// 59// If the |url| is valid, returns true and fills in |id| with restricted_id 60// value. If the |url| is invalid, returns false and |id| is not set. 61bool GetInstantRestrictedIDFromURL(int render_view_id, 62 const GURL& url, 63 InstantRestrictedID* id) { 64 // Strip leading path. 65 std::string path = url.path().substr(1); 66 67 // Check that the path is of Most visited item ID form. 68 std::vector<std::string> tokens; 69 if (Tokenize(path, "/", &tokens) != 2) 70 return false; 71 72 int view_id = 0; 73 if (!base::StringToInt(tokens[0], &view_id) || view_id != render_view_id) 74 return false; 75 return base::StringToInt(tokens[1], id); 76} 77 78} // namespace internal 79 80SearchBox::SearchBox(content::RenderView* render_view) 81 : content::RenderViewObserver(render_view), 82 content::RenderViewObserverTracker<SearchBox>(render_view), 83 app_launcher_enabled_(false), 84 is_focused_(false), 85 is_input_in_progress_(false), 86 is_key_capture_enabled_(false), 87 most_visited_items_cache_(kMaxInstantMostVisitedItemCacheSize), 88 omnibox_font_(), 89 omnibox_font_size_(12), 90 query_(), 91 start_margin_(0), 92 width_(0) { 93} 94 95SearchBox::~SearchBox() { 96} 97 98void SearchBox::CountMouseover() { 99 render_view()->Send(new ChromeViewHostMsg_CountMouseover( 100 render_view()->GetRoutingID(), render_view()->GetPageId())); 101} 102 103void SearchBox::DeleteMostVisitedItem( 104 InstantRestrictedID most_visited_item_id) { 105 render_view()->Send(new ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem( 106 render_view()->GetRoutingID(), render_view()->GetPageId(), 107 GetURLForMostVisitedItem(most_visited_item_id))); 108} 109 110bool SearchBox::GenerateFaviconURLFromTransientURL(const GURL& transient_url, 111 GURL* url) const { 112 InstantRestrictedID rid = 0; 113 if (!internal::GetInstantRestrictedIDFromURL(render_view()->GetRoutingID(), 114 transient_url, &rid)) { 115 return false; 116 } 117 118 GURL most_visited_item_url(GetURLForMostVisitedItem(rid)); 119 if (most_visited_item_url.is_empty()) 120 return false; 121 *url = GURL(base::StringPrintf("chrome-search://favicon/%s", 122 most_visited_item_url.spec().c_str())); 123 return true; 124} 125 126bool SearchBox::GenerateThumbnailURLFromTransientURL(const GURL& transient_url, 127 GURL* url) const { 128 InstantRestrictedID rid = 0; 129 if (!internal::GetInstantRestrictedIDFromURL(render_view()->GetRoutingID(), 130 transient_url, &rid)) { 131 return false; 132 } 133 134 GURL most_visited_item_url(GetURLForMostVisitedItem(rid)); 135 if (most_visited_item_url.is_empty()) 136 return false; 137 *url = GURL(base::StringPrintf("chrome-search://thumb/%s", 138 most_visited_item_url.spec().c_str())); 139 return true; 140} 141 142void SearchBox::GetMostVisitedItems( 143 std::vector<InstantMostVisitedItemIDPair>* items) const { 144 return most_visited_items_cache_.GetCurrentItems(items); 145} 146 147bool SearchBox::GetMostVisitedItemWithID( 148 InstantRestrictedID most_visited_item_id, 149 InstantMostVisitedItem* item) const { 150 return most_visited_items_cache_.GetItemWithRestrictedID(most_visited_item_id, 151 item); 152} 153 154const ThemeBackgroundInfo& SearchBox::GetThemeBackgroundInfo() { 155 return theme_info_; 156} 157 158void SearchBox::NavigateToURL(const GURL& url, 159 content::PageTransition transition, 160 WindowOpenDisposition disposition, 161 bool is_search_type) { 162 render_view()->Send(new ChromeViewHostMsg_SearchBoxNavigate( 163 render_view()->GetRoutingID(), render_view()->GetPageId(), 164 url, transition, disposition, is_search_type)); 165} 166 167void SearchBox::SetVoiceSearchSupported(bool supported) { 168 render_view()->Send(new ChromeViewHostMsg_SetVoiceSearchSupported( 169 render_view()->GetRoutingID(), render_view()->GetPageId(), supported)); 170} 171 172void SearchBox::StartCapturingKeyStrokes() { 173 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox( 174 render_view()->GetRoutingID(), render_view()->GetPageId(), 175 OMNIBOX_FOCUS_INVISIBLE)); 176} 177 178void SearchBox::StopCapturingKeyStrokes() { 179 render_view()->Send(new ChromeViewHostMsg_FocusOmnibox( 180 render_view()->GetRoutingID(), render_view()->GetPageId(), 181 OMNIBOX_FOCUS_NONE)); 182} 183 184void SearchBox::UndoAllMostVisitedDeletions() { 185 render_view()->Send( 186 new ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions( 187 render_view()->GetRoutingID(), render_view()->GetPageId())); 188} 189 190void SearchBox::UndoMostVisitedDeletion( 191 InstantRestrictedID most_visited_item_id) { 192 render_view()->Send(new ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion( 193 render_view()->GetRoutingID(), render_view()->GetPageId(), 194 GetURLForMostVisitedItem(most_visited_item_id))); 195} 196 197bool SearchBox::OnMessageReceived(const IPC::Message& message) { 198 bool handled = true; 199 IPC_BEGIN_MESSAGE_MAP(SearchBox, message) 200 IPC_MESSAGE_HANDLER(ChromeViewMsg_DetermineIfPageSupportsInstant, 201 OnDetermineIfPageSupportsInstant) 202 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFocusChanged, OnFocusChanged) 203 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxFontInformation, 204 OnFontInformationReceived) 205 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMarginChange, OnMarginChange) 206 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxMostVisitedItemsChanged, 207 OnMostVisitedChanged) 208 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxPromoInformation, 209 OnPromoInformationReceived) 210 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSetInputInProgress, 211 OnSetInputInProgress) 212 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxSubmit, OnSubmit) 213 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxThemeChanged, 214 OnThemeChanged) 215 IPC_MESSAGE_HANDLER(ChromeViewMsg_SearchBoxToggleVoiceSearch, 216 OnToggleVoiceSearch) 217 IPC_MESSAGE_UNHANDLED(handled = false) 218 IPC_END_MESSAGE_MAP() 219 return handled; 220} 221 222void SearchBox::OnDetermineIfPageSupportsInstant() { 223 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 224 bool result = extensions_v8::SearchBoxExtension::PageSupportsInstant( 225 render_view()->GetWebView()->mainFrame()); 226 DVLOG(1) << render_view() << " PageSupportsInstant: " << result; 227 render_view()->Send(new ChromeViewHostMsg_InstantSupportDetermined( 228 render_view()->GetRoutingID(), render_view()->GetPageId(), result)); 229 } 230} 231 232void SearchBox::OnFocusChanged(OmniboxFocusState new_focus_state, 233 OmniboxFocusChangeReason reason) { 234 bool key_capture_enabled = new_focus_state == OMNIBOX_FOCUS_INVISIBLE; 235 if (key_capture_enabled != is_key_capture_enabled_) { 236 // Tell the page if the key capture mode changed unless the focus state 237 // changed because of TYPING. This is because in that case, the browser 238 // hasn't really stopped capturing key strokes. 239 // 240 // (More practically, if we don't do this check, the page would receive 241 // onkeycapturechange before the corresponding onchange, and the page would 242 // have no way of telling whether the keycapturechange happened because of 243 // some actual user action or just because they started typing.) 244 if (reason != OMNIBOX_FOCUS_CHANGE_TYPING && 245 render_view()->GetWebView() && 246 render_view()->GetWebView()->mainFrame()) { 247 is_key_capture_enabled_ = key_capture_enabled; 248 DVLOG(1) << render_view() << " OnKeyCaptureChange"; 249 extensions_v8::SearchBoxExtension::DispatchKeyCaptureChange( 250 render_view()->GetWebView()->mainFrame()); 251 } 252 } 253 bool is_focused = new_focus_state == OMNIBOX_FOCUS_VISIBLE; 254 if (is_focused != is_focused_) { 255 is_focused_ = is_focused; 256 DVLOG(1) << render_view() << " OnFocusChange"; 257 if (render_view()->GetWebView() && 258 render_view()->GetWebView()->mainFrame()) { 259 extensions_v8::SearchBoxExtension::DispatchFocusChange( 260 render_view()->GetWebView()->mainFrame()); 261 } 262 } 263} 264 265void SearchBox::OnFontInformationReceived(const string16& omnibox_font, 266 size_t omnibox_font_size) { 267 omnibox_font_ = omnibox_font; 268 omnibox_font_size_ = omnibox_font_size; 269} 270 271void SearchBox::OnMarginChange(int margin, int width) { 272 start_margin_ = margin; 273 width_ = width; 274 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 275 extensions_v8::SearchBoxExtension::DispatchMarginChange( 276 render_view()->GetWebView()->mainFrame()); 277 } 278} 279 280void SearchBox::OnMostVisitedChanged( 281 const std::vector<InstantMostVisitedItem>& items) { 282 std::vector<InstantMostVisitedItemIDPair> last_known_items; 283 GetMostVisitedItems(&last_known_items); 284 285 if (AreMostVisitedItemsEqual(last_known_items, items)) 286 return; // Do not send duplicate onmostvisitedchange events. 287 288 most_visited_items_cache_.AddItems(items); 289 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 290 extensions_v8::SearchBoxExtension::DispatchMostVisitedChanged( 291 render_view()->GetWebView()->mainFrame()); 292 } 293} 294 295void SearchBox::OnPromoInformationReceived(bool is_app_launcher_enabled) { 296 app_launcher_enabled_ = is_app_launcher_enabled; 297} 298 299void SearchBox::OnSetInputInProgress(bool is_input_in_progress) { 300 if (is_input_in_progress_ != is_input_in_progress) { 301 is_input_in_progress_ = is_input_in_progress; 302 DVLOG(1) << render_view() << " OnSetInputInProgress"; 303 if (render_view()->GetWebView() && 304 render_view()->GetWebView()->mainFrame()) { 305 if (is_input_in_progress_) { 306 extensions_v8::SearchBoxExtension::DispatchInputStart( 307 render_view()->GetWebView()->mainFrame()); 308 } else { 309 extensions_v8::SearchBoxExtension::DispatchInputCancel( 310 render_view()->GetWebView()->mainFrame()); 311 } 312 } 313 } 314} 315 316void SearchBox::OnSubmit(const string16& query) { 317 query_ = query; 318 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 319 DVLOG(1) << render_view() << " OnSubmit"; 320 extensions_v8::SearchBoxExtension::DispatchSubmit( 321 render_view()->GetWebView()->mainFrame()); 322 } 323 if (!query.empty()) 324 Reset(); 325} 326 327void SearchBox::OnThemeChanged(const ThemeBackgroundInfo& theme_info) { 328 // Do not send duplicate notifications. 329 if (theme_info_ == theme_info) 330 return; 331 332 theme_info_ = theme_info; 333 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 334 extensions_v8::SearchBoxExtension::DispatchThemeChange( 335 render_view()->GetWebView()->mainFrame()); 336 } 337} 338 339void SearchBox::OnToggleVoiceSearch() { 340 if (render_view()->GetWebView() && render_view()->GetWebView()->mainFrame()) { 341 extensions_v8::SearchBoxExtension::DispatchToggleVoiceSearch( 342 render_view()->GetWebView()->mainFrame()); 343 } 344} 345 346GURL SearchBox::GetURLForMostVisitedItem(InstantRestrictedID item_id) const { 347 InstantMostVisitedItem item; 348 return GetMostVisitedItemWithID(item_id, &item) ? item.url : GURL(); 349} 350 351void SearchBox::Reset() { 352 query_.clear(); 353 start_margin_ = 0; 354 width_ = 0; 355 is_focused_ = false; 356 is_key_capture_enabled_ = false; 357 theme_info_ = ThemeBackgroundInfo(); 358} 359