Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "chrome/browser/instant/instant_controller.h" | 5 #include "chrome/browser/instant/instant_controller.h" |
| 6 | 6 |
| 7 #include "base/command_line.h" | 7 #include "chrome/browser/instant/instant_controller_extended_impl.h" |
| 8 #include "base/metrics/histogram.h" | 8 #include "chrome/browser/instant/instant_controller_impl.h" |
| 9 #include "base/string_util.h" | |
| 10 #include "base/stringprintf.h" | |
| 11 #include "base/utf_string_conversions.h" | |
| 12 #include "chrome/browser/autocomplete/autocomplete_provider.h" | |
| 13 #include "chrome/browser/history/history_service.h" | |
| 14 #include "chrome/browser/history/history_service_factory.h" | |
| 15 #include "chrome/browser/history/history_tab_helper.h" | |
| 16 #include "chrome/browser/history/top_sites.h" | |
| 17 #include "chrome/browser/instant/instant_ntp.h" | |
| 18 #include "chrome/browser/instant/instant_overlay.h" | |
| 19 #include "chrome/browser/instant/instant_tab.h" | |
| 20 #include "chrome/browser/platform_util.h" | |
| 21 #include "chrome/browser/search_engines/template_url_service.h" | |
| 22 #include "chrome/browser/search_engines/template_url_service_factory.h" | |
| 23 #include "chrome/browser/ui/browser_instant_controller.h" | |
| 24 #include "chrome/browser/ui/search/search.h" | 9 #include "chrome/browser/ui/search/search.h" |
| 25 #include "chrome/browser/ui/search/search_tab_helper.h" | |
| 26 #include "chrome/common/chrome_notification_types.h" | |
| 27 #include "chrome/common/chrome_switches.h" | |
| 28 #include "chrome/common/url_constants.h" | |
| 29 #include "content/public/browser/navigation_entry.h" | |
| 30 #include "content/public/browser/notification_service.h" | |
| 31 #include "content/public/browser/render_widget_host_view.h" | |
| 32 #include "content/public/browser/web_contents.h" | |
| 33 #include "content/public/browser/web_contents_view.h" | |
| 34 #include "net/base/escape.h" | |
| 35 #include "third_party/icu/public/common/unicode/normalizer2.h" | |
| 36 | 10 |
| 37 #if defined(TOOLKIT_VIEWS) | 11 // static |
| 38 #include "ui/views/widget/widget.h" | 12 scoped_ptr<InstantController> InstantController::GetInstance( |
| 39 #endif | 13 chrome::BrowserInstantController* browser, |
| 40 | 14 Profile* profile) { |
| 41 namespace { | 15 InstantController* instance; |
| 42 | 16 if (chrome::search::IsInstantExtendedAPIEnabled(profile)) |
| 43 // An artificial delay (in milliseconds) we introduce before telling the Instant | 17 instance = new InstantControllerExtendedImpl(browser, profile); |
|
samarth
2013/03/01 17:59:53
Just return here (and below) instead of saving to
sreeram
2013/03/07 18:18:46
Done.
| |
| 44 // page about the new omnibox bounds, in cases where the bounds shrink. This is | 18 else |
| 45 // to avoid the page jumping up/down very fast in response to bounds changes. | 19 instance = new InstantControllerImpl(browser, profile); |
| 46 const int kUpdateBoundsDelayMS = 1000; | 20 return scoped_ptr<InstantController>(instance); |
| 47 | |
| 48 // The maximum number of times we'll load a non-Instant-supporting search engine | |
| 49 // before we give up and blacklist it for the rest of the browsing session. | |
| 50 const int kMaxInstantSupportFailures = 10; | |
| 51 | |
| 52 // For reporting events of interest. | |
| 53 enum InstantControllerEvent { | |
| 54 INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST = 0, | |
| 55 INSTANT_CONTROLLER_EVENT_URL_REMOVED_FROM_BLACKLIST = 1, | |
| 56 INSTANT_CONTROLLER_EVENT_URL_BLOCKED_BY_BLACKLIST = 2, | |
| 57 INSTANT_CONTROLLER_EVENT_MAX = 3, | |
| 58 }; | |
| 59 | |
| 60 void RecordEventHistogram(InstantControllerEvent event) { | |
| 61 UMA_HISTOGRAM_ENUMERATION("Instant.InstantControllerEvent", | |
| 62 event, | |
| 63 INSTANT_CONTROLLER_EVENT_MAX); | |
| 64 } | 21 } |
| 65 | 22 |
| 66 void AddSessionStorageHistogram(bool extended_enabled, | 23 InstantController::InstantController() { |
| 67 const content::WebContents* tab1, | |
| 68 const content::WebContents* tab2) { | |
| 69 base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet( | |
| 70 std::string("Instant.SessionStorageNamespace") + | |
| 71 (extended_enabled ? "_Extended" : "_Instant"), | |
| 72 base::HistogramBase::kUmaTargetedHistogramFlag); | |
| 73 const content::SessionStorageNamespaceMap& session_storage_map1 = | |
| 74 tab1->GetController().GetSessionStorageNamespaceMap(); | |
| 75 const content::SessionStorageNamespaceMap& session_storage_map2 = | |
| 76 tab2->GetController().GetSessionStorageNamespaceMap(); | |
| 77 bool is_session_storage_the_same = | |
| 78 session_storage_map1.size() == session_storage_map2.size(); | |
| 79 if (is_session_storage_the_same) { | |
| 80 // The size is the same, so let's check that all entries match. | |
| 81 for (content::SessionStorageNamespaceMap::const_iterator | |
| 82 it1 = session_storage_map1.begin(), | |
| 83 it2 = session_storage_map2.begin(); | |
| 84 it1 != session_storage_map1.end() && it2 != session_storage_map2.end(); | |
| 85 ++it1, ++it2) { | |
| 86 if (it1->first != it2->first || it1->second != it2->second) { | |
| 87 is_session_storage_the_same = false; | |
| 88 break; | |
| 89 } | |
| 90 } | |
| 91 } | |
| 92 histogram->AddBoolean(is_session_storage_the_same); | |
| 93 } | |
| 94 | |
| 95 string16 Normalize(const string16& str) { | |
| 96 UErrorCode status = U_ZERO_ERROR; | |
| 97 const icu::Normalizer2* normalizer = | |
| 98 icu::Normalizer2::getInstance(NULL, "nfkc_cf", UNORM2_COMPOSE, status); | |
| 99 if (normalizer == NULL || U_FAILURE(status)) | |
| 100 return str; | |
| 101 icu::UnicodeString norm_str(normalizer->normalize( | |
| 102 icu::UnicodeString(FALSE, str.c_str(), str.size()), status)); | |
| 103 if (U_FAILURE(status)) | |
| 104 return str; | |
| 105 return string16(norm_str.getBuffer(), norm_str.length()); | |
| 106 } | |
| 107 | |
| 108 bool NormalizeAndStripPrefix(string16* text, const string16& prefix) { | |
| 109 string16 norm_prefix = Normalize(prefix); | |
| 110 string16 norm_text = Normalize(*text); | |
| 111 if (norm_prefix.size() <= norm_text.size() && | |
| 112 norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) { | |
| 113 *text = norm_text.erase(0, norm_prefix.size()); | |
| 114 return true; | |
| 115 } | |
| 116 return false; | |
| 117 } | |
| 118 | |
| 119 // For TOOLKIT_VIEWS, the top level widget is always focused. If the focus | |
| 120 // change originated in views determine the child Widget from the view that is | |
| 121 // being focused. | |
| 122 gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) { | |
| 123 #if defined(TOOLKIT_VIEWS) | |
| 124 views::Widget* widget = view_gaining_focus ? | |
| 125 views::Widget::GetWidgetForNativeView(view_gaining_focus) : NULL; | |
| 126 if (widget) { | |
| 127 views::FocusManager* focus_manager = widget->GetFocusManager(); | |
| 128 if (focus_manager && focus_manager->is_changing_focus() && | |
| 129 focus_manager->GetFocusedView() && | |
| 130 focus_manager->GetFocusedView()->GetWidget()) | |
| 131 return focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | |
| 132 } | |
| 133 #endif | |
| 134 return view_gaining_focus; | |
| 135 } | |
| 136 | |
| 137 // Returns true if |view| is the top-level contents view or a child view in the | |
| 138 // view hierarchy of |contents|. | |
| 139 bool IsViewInContents(gfx::NativeView view, content::WebContents* contents) { | |
| 140 content::RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView(); | |
| 141 if (!view || !rwhv) | |
| 142 return false; | |
| 143 | |
| 144 gfx::NativeView tab_view = contents->GetView()->GetNativeView(); | |
| 145 if (view == rwhv->GetNativeView() || view == tab_view) | |
| 146 return true; | |
| 147 | |
| 148 // Walk up the view hierarchy to determine if the view is a subview of the | |
| 149 // WebContents view (such as a windowed plugin or http auth dialog). | |
| 150 while (view) { | |
| 151 view = platform_util::GetParent(view); | |
| 152 if (view == tab_view) | |
| 153 return true; | |
| 154 } | |
| 155 | |
| 156 return false; | |
| 157 } | |
| 158 | |
| 159 bool IsFullHeight(const InstantModel& model) { | |
| 160 return model.height() == 100 && model.height_units() == INSTANT_SIZE_PERCENT; | |
| 161 } | |
| 162 | |
| 163 bool IsContentsFrom(const InstantPage* page, | |
| 164 const content::WebContents* contents) { | |
| 165 return page && (page->contents() == contents); | |
| 166 } | |
| 167 | |
| 168 // Adds a transient NavigationEntry to the supplied |contents|'s | |
| 169 // NavigationController if the page's URL has not already been updated with the | |
| 170 // supplied |search_terms|. Sets the |search_terms| on the transient entry for | |
| 171 // search terms extraction to work correctly. | |
| 172 void EnsureSearchTermsAreSet(content::WebContents* contents, | |
| 173 const string16& search_terms) { | |
| 174 content::NavigationController& controller = contents->GetController(); | |
| 175 | |
| 176 // If search terms are already correct or there is already a transient entry | |
| 177 // (there shouldn't be), bail out early. | |
| 178 if (chrome::search::GetSearchTerms(contents) == search_terms || | |
| 179 controller.GetTransientEntry()) | |
| 180 return; | |
| 181 | |
| 182 content::NavigationEntry* transient = content::NavigationEntry::Create( | |
| 183 *controller.GetActiveEntry()); | |
| 184 transient->SetExtraData(chrome::search::kInstantExtendedSearchTermsKey, | |
| 185 search_terms); | |
| 186 controller.SetTransientEntry(transient); | |
| 187 | |
| 188 chrome::search::SearchTabHelper::FromWebContents(contents)-> | |
| 189 NavigationEntryUpdated(); | |
| 190 } | |
| 191 | |
| 192 } // namespace | |
| 193 | |
| 194 InstantController::InstantController(chrome::BrowserInstantController* browser, | |
| 195 bool extended_enabled) | |
| 196 : browser_(browser), | |
| 197 extended_enabled_(extended_enabled), | |
| 198 instant_enabled_(false), | |
| 199 use_local_preview_only_(true), | |
| 200 model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), | |
| 201 last_omnibox_text_has_inline_autocompletion_(false), | |
| 202 last_verbatim_(false), | |
| 203 last_transition_type_(content::PAGE_TRANSITION_LINK), | |
| 204 last_match_was_search_(false), | |
| 205 omnibox_focus_state_(OMNIBOX_FOCUS_NONE), | |
| 206 allow_preview_to_show_search_suggestions_(false), | |
| 207 ALLOW_THIS_IN_INITIALIZER_LIST(weak_ptr_factory_(this)) { | |
| 208 } | 24 } |
| 209 | 25 |
| 210 InstantController::~InstantController() { | 26 InstantController::~InstantController() { |
| 211 } | 27 } |
| 212 | |
| 213 bool InstantController::Update(const AutocompleteMatch& match, | |
| 214 const string16& user_text, | |
| 215 const string16& full_text, | |
| 216 size_t selection_start, | |
| 217 size_t selection_end, | |
| 218 bool verbatim, | |
| 219 bool user_input_in_progress, | |
| 220 bool omnibox_popup_is_open, | |
| 221 bool escape_pressed, | |
| 222 bool is_keyword_search) { | |
| 223 if (!extended_enabled_ && !instant_enabled_) | |
| 224 return false; | |
| 225 | |
| 226 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 227 "Update: %s user_text='%s' full_text='%s' selection_start=%d " | |
| 228 "selection_end=%d verbatim=%d typing=%d popup=%d escape_pressed=%d " | |
| 229 "is_keyword_search=%d", | |
| 230 AutocompleteMatch::TypeToString(match.type).c_str(), | |
| 231 UTF16ToUTF8(user_text).c_str(), UTF16ToUTF8(full_text).c_str(), | |
| 232 static_cast<int>(selection_start), static_cast<int>(selection_end), | |
| 233 verbatim, user_input_in_progress, omnibox_popup_is_open, escape_pressed, | |
| 234 is_keyword_search)); | |
| 235 | |
| 236 // TODO(dhollowa): Complete keyword match UI. For now just hide suggestions. | |
| 237 // http://crbug.com/153932. Note, this early escape is happens prior to the | |
| 238 // DCHECKs below because |user_text| and |full_text| have different semantics | |
| 239 // when keyword search is in effect. | |
| 240 if (is_keyword_search) { | |
| 241 if (instant_tab_) | |
| 242 instant_tab_->Update(string16(), 0, 0, true); | |
| 243 else | |
| 244 HideOverlay(); | |
| 245 last_match_was_search_ = false; | |
| 246 return false; | |
| 247 } | |
| 248 | |
| 249 // If the popup is open, the user has to be typing. | |
| 250 DCHECK(!omnibox_popup_is_open || user_input_in_progress); | |
| 251 | |
| 252 // If the popup is closed, there should be no inline autocompletion. | |
| 253 DCHECK(omnibox_popup_is_open || user_text.empty() || user_text == full_text) | |
| 254 << user_text << "|" << full_text; | |
| 255 | |
| 256 // If there's no text in the omnibox, the user can't have typed any. | |
| 257 DCHECK(!full_text.empty() || user_text.empty()) << user_text; | |
| 258 | |
| 259 // If the user isn't typing, and the popup is closed, there can't be any | |
| 260 // user-typed text. | |
| 261 DCHECK(user_input_in_progress || omnibox_popup_is_open || user_text.empty()) | |
| 262 << user_text; | |
| 263 | |
| 264 // The preview is being clicked and will commit soon. Don't change anything. | |
| 265 // TODO(sreeram): Add a browser test for this. | |
| 266 if (overlay_ && overlay_->is_pointer_down_from_activate()) | |
| 267 return false; | |
| 268 | |
| 269 // In non-extended mode, SearchModeChanged() is never called, so fake it. The | |
| 270 // mode is set to "disallow suggestions" here, so that if one of the early | |
| 271 // "return false" conditions is hit, suggestions will be disallowed. If the | |
| 272 // query is sent to the overlay, the mode is set to "allow" further below. | |
| 273 if (!extended_enabled_) | |
| 274 search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | |
| 275 | |
| 276 last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && | |
| 277 !user_text.empty(); | |
| 278 | |
| 279 // In non extended mode, Instant is disabled for URLs and keyword mode. | |
| 280 if (!extended_enabled_ && | |
| 281 (!last_match_was_search_ || | |
| 282 match.type == AutocompleteMatch::SEARCH_OTHER_ENGINE)) { | |
| 283 HideOverlay(); | |
| 284 return false; | |
| 285 } | |
| 286 | |
| 287 // If we have an |instant_tab_| use it, else ensure we have a overlay that is | |
| 288 // current or is using local preview. | |
| 289 if (!instant_tab_ && !(overlay_ && overlay_->IsUsingLocalPreview()) && | |
| 290 !EnsureOverlayIsCurrent(false)) { | |
| 291 HideOverlay(); | |
| 292 return false; | |
| 293 } | |
| 294 | |
| 295 if (extended_enabled_) { | |
| 296 if (!omnibox_popup_is_open) { | |
| 297 if (!user_input_in_progress) { | |
| 298 // If the user isn't typing and the omnibox popup is closed, it means a | |
| 299 // regular navigation, tab-switch or the user hitting Escape. | |
| 300 if (instant_tab_) { | |
| 301 // The user is on a search results page. It may be showing results for | |
| 302 // a partial query the user typed before they hit Escape. Send the | |
| 303 // omnibox text to the page to restore the original results. | |
| 304 // | |
| 305 // In a tab switch, |instant_tab_| won't have updated yet, so it may | |
| 306 // be pointing to the previous tab (which was a search results page). | |
| 307 // Ensure we don't send the omnibox text to a random webpage (the new | |
| 308 // tab), by comparing the old and new WebContents. | |
| 309 if (escape_pressed && | |
| 310 instant_tab_->contents() == browser_->GetActiveWebContents()) { | |
| 311 instant_tab_->Submit(full_text); | |
| 312 } | |
| 313 } else if (!full_text.empty()) { | |
| 314 // If |full_text| is empty, the user is on the NTP. The preview may | |
| 315 // be showing custom NTP content; hide only if that's not the case. | |
| 316 HideOverlay(); | |
| 317 } | |
| 318 } else if (full_text.empty()) { | |
| 319 // The user is typing, and backspaced away all omnibox text. Clear | |
| 320 // |last_omnibox_text_| so that we don't attempt to set suggestions. | |
| 321 last_omnibox_text_.clear(); | |
| 322 last_suggestion_ = InstantSuggestion(); | |
| 323 if (instant_tab_) { | |
| 324 // On a search results page, tell it to clear old results. | |
| 325 instant_tab_->Update(string16(), 0, 0, true); | |
| 326 } else if (search_mode_.is_origin_ntp()) { | |
| 327 // On the NTP, tell the preview to clear old results. Don't hide the | |
| 328 // preview so it can show a blank page or logo if it wants. | |
| 329 overlay_->Update(string16(), 0, 0, true); | |
| 330 } else { | |
| 331 HideOverlay(); | |
| 332 } | |
| 333 } else { | |
| 334 // The user switched to a tab with partial text already in the omnibox. | |
| 335 HideOverlay(); | |
| 336 | |
| 337 // The new tab may or may not be a search results page; we don't know | |
| 338 // since SearchModeChanged() hasn't been called yet. If it later turns | |
| 339 // out to be, we should store |full_text| now, so that if the user hits | |
| 340 // Enter, we'll send the correct query to instant_tab_->Submit(). If the | |
| 341 // partial text is not a query (|last_match_was_search_| is false), we | |
| 342 // won't Submit(), so no need to worry about that. | |
| 343 last_omnibox_text_ = full_text; | |
| 344 last_suggestion_ = InstantSuggestion(); | |
| 345 } | |
| 346 return false; | |
| 347 } else if (full_text.empty()) { | |
| 348 // The user typed a solitary "?". Same as the backspace case above. | |
| 349 last_omnibox_text_.clear(); | |
| 350 last_suggestion_ = InstantSuggestion(); | |
| 351 if (instant_tab_) | |
| 352 instant_tab_->Update(string16(), 0, 0, true); | |
| 353 else if (search_mode_.is_origin_ntp()) | |
| 354 overlay_->Update(string16(), 0, 0, true); | |
| 355 else | |
| 356 HideOverlay(); | |
| 357 return false; | |
| 358 } | |
| 359 } else if (!omnibox_popup_is_open || full_text.empty()) { | |
| 360 // In the non-extended case, hide the preview as long as the user isn't | |
| 361 // actively typing a non-empty query. | |
| 362 HideOverlay(); | |
| 363 return false; | |
| 364 } | |
| 365 | |
| 366 last_omnibox_text_has_inline_autocompletion_ = user_text != full_text; | |
| 367 | |
| 368 // If the user continues typing the same query as the suggested text is | |
| 369 // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER). | |
| 370 bool reused_suggestion = false; | |
| 371 if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && | |
| 372 !last_omnibox_text_has_inline_autocompletion_) { | |
| 373 if (StartsWith(last_omnibox_text_, full_text, false)) { | |
| 374 // The user is backspacing away characters. | |
| 375 last_suggestion_.text.insert(0, last_omnibox_text_, full_text.size(), | |
| 376 last_omnibox_text_.size() - full_text.size()); | |
| 377 reused_suggestion = true; | |
| 378 } else if (StartsWith(full_text, last_omnibox_text_, false)) { | |
| 379 // The user is typing forward. Normalize any added characters. | |
| 380 reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text, | |
| 381 string16(full_text, last_omnibox_text_.size())); | |
| 382 } | |
| 383 } | |
| 384 if (!reused_suggestion) | |
| 385 last_suggestion_ = InstantSuggestion(); | |
| 386 | |
| 387 last_omnibox_text_ = full_text; | |
| 388 | |
| 389 if (!extended_enabled_) { | |
| 390 // In non-extended mode, the query is verbatim if there's any selection | |
| 391 // (including inline autocompletion) or if the cursor is not at the end. | |
| 392 verbatim = verbatim || selection_start != selection_end || | |
| 393 selection_start != full_text.size(); | |
| 394 } | |
| 395 last_verbatim_ = verbatim; | |
| 396 | |
| 397 last_transition_type_ = match.transition; | |
| 398 url_for_history_ = match.destination_url; | |
| 399 | |
| 400 // Allow search suggestions. In extended mode, SearchModeChanged() will set | |
| 401 // this, but it's not called in non-extended mode, so fake it. | |
| 402 if (!extended_enabled_) | |
| 403 search_mode_.mode = chrome::search::Mode::MODE_SEARCH_SUGGESTIONS; | |
| 404 | |
| 405 if (instant_tab_) { | |
| 406 instant_tab_->Update(user_text, selection_start, selection_end, verbatim); | |
| 407 } else { | |
| 408 if (first_interaction_time_.is_null()) | |
| 409 first_interaction_time_ = base::Time::Now(); | |
| 410 allow_preview_to_show_search_suggestions_ = true; | |
| 411 | |
| 412 // For extended mode, if the loader is not ready at this point, switch over | |
| 413 // to a backup loader. | |
| 414 if (extended_enabled_ && !overlay_->supports_instant() && | |
| 415 !overlay_->IsUsingLocalPreview() && browser_->GetActiveWebContents()) { | |
| 416 CreateOverlay(chrome::search::kLocalOmniboxPopupURL, | |
| 417 browser_->GetActiveWebContents()); | |
| 418 } | |
| 419 | |
| 420 overlay_->Update(extended_enabled_ ? user_text : full_text, | |
| 421 selection_start, selection_end, verbatim); | |
| 422 } | |
| 423 | |
| 424 content::NotificationService::current()->Notify( | |
| 425 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | |
| 426 content::Source<InstantController>(this), | |
| 427 content::NotificationService::NoDetails()); | |
| 428 | |
| 429 // We don't have new suggestions yet, but we can either reuse the existing | |
| 430 // suggestion or reset the existing "gray text". | |
| 431 browser_->SetInstantSuggestion(last_suggestion_); | |
| 432 | |
| 433 return true; | |
| 434 } | |
| 435 | |
| 436 scoped_ptr<content::WebContents> InstantController::ReleaseNTPContents() { | |
| 437 if (!extended_enabled_ || !ntp_) | |
| 438 return scoped_ptr<content::WebContents>(NULL); | |
| 439 | |
| 440 LOG_INSTANT_DEBUG_EVENT(this, "ReleaseNTPContents"); | |
| 441 | |
| 442 scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents(); | |
| 443 ntp_.reset(); | |
| 444 ResetNTP(); | |
| 445 return ntp_contents.Pass(); | |
| 446 } | |
| 447 | |
| 448 // TODO(tonyg): This method only fires when the omnibox bounds change. It also | |
| 449 // needs to fire when the preview bounds change (e.g.: open/close info bar). | |
| 450 void InstantController::SetPopupBounds(const gfx::Rect& bounds) { | |
| 451 if (!extended_enabled_ && !instant_enabled_) | |
| 452 return; | |
| 453 | |
| 454 if (popup_bounds_ == bounds) | |
| 455 return; | |
| 456 | |
| 457 popup_bounds_ = bounds; | |
| 458 if (popup_bounds_.height() > last_popup_bounds_.height()) { | |
| 459 update_bounds_timer_.Stop(); | |
| 460 SendPopupBoundsToPage(); | |
| 461 } else if (!update_bounds_timer_.IsRunning()) { | |
| 462 update_bounds_timer_.Start(FROM_HERE, | |
| 463 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, | |
| 464 &InstantController::SendPopupBoundsToPage); | |
| 465 } | |
| 466 } | |
| 467 | |
| 468 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { | |
| 469 if (!extended_enabled_ || omnibox_bounds_ == bounds) | |
| 470 return; | |
| 471 | |
| 472 omnibox_bounds_ = bounds; | |
| 473 if (overlay_) | |
| 474 overlay_->SetOmniboxBounds(omnibox_bounds_); | |
| 475 if (ntp_) | |
| 476 ntp_->SetOmniboxBounds(omnibox_bounds_); | |
| 477 if (instant_tab_) | |
| 478 instant_tab_->SetOmniboxBounds(omnibox_bounds_); | |
| 479 } | |
| 480 | |
| 481 void InstantController::HandleAutocompleteResults( | |
| 482 const std::vector<AutocompleteProvider*>& providers) { | |
| 483 if (!extended_enabled_) | |
| 484 return; | |
| 485 | |
| 486 if (!instant_tab_ && !overlay_) | |
| 487 return; | |
| 488 | |
| 489 DVLOG(1) << "AutocompleteResults:"; | |
| 490 std::vector<InstantAutocompleteResult> results; | |
| 491 for (ACProviders::const_iterator provider = providers.begin(); | |
| 492 provider != providers.end(); ++provider) { | |
| 493 for (ACMatches::const_iterator match = (*provider)->matches().begin(); | |
| 494 match != (*provider)->matches().end(); ++match) { | |
| 495 InstantAutocompleteResult result; | |
| 496 result.provider = UTF8ToUTF16((*provider)->GetName()); | |
| 497 result.type = UTF8ToUTF16(AutocompleteMatch::TypeToString(match->type)); | |
| 498 result.description = match->description; | |
| 499 result.destination_url = UTF8ToUTF16(match->destination_url.spec()); | |
| 500 result.transition = match->transition; | |
| 501 result.relevance = match->relevance; | |
| 502 DVLOG(1) << " " << result.relevance << " " << result.type << " " | |
| 503 << result.provider << " " << result.destination_url << " '" | |
| 504 << result.description << "' " << result.transition; | |
| 505 results.push_back(result); | |
| 506 } | |
| 507 } | |
| 508 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 509 "HandleAutocompleteResults: total_results=%d", | |
| 510 static_cast<int>(results.size()))); | |
| 511 | |
| 512 if (instant_tab_) | |
| 513 instant_tab_->SendAutocompleteResults(results); | |
| 514 else | |
| 515 overlay_->SendAutocompleteResults(results); | |
| 516 } | |
| 517 | |
| 518 bool InstantController::OnUpOrDownKeyPressed(int count) { | |
| 519 if (!extended_enabled_) | |
| 520 return false; | |
| 521 | |
| 522 if (!instant_tab_ && !overlay_) | |
| 523 return false; | |
| 524 | |
| 525 if (instant_tab_) | |
| 526 instant_tab_->UpOrDownKeyPressed(count); | |
| 527 else | |
| 528 overlay_->UpOrDownKeyPressed(count); | |
| 529 | |
| 530 return true; | |
| 531 } | |
| 532 | |
| 533 void InstantController::OnCancel(const AutocompleteMatch& match, | |
| 534 const string16& full_text) { | |
| 535 if (!extended_enabled_) | |
| 536 return; | |
| 537 | |
| 538 if (!instant_tab_ && !overlay_) | |
| 539 return; | |
| 540 | |
| 541 // We manually reset the state here since the JS is not expected to do it. | |
| 542 // TODO(sreeram): Handle the case where user_text is now a URL | |
| 543 last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && | |
| 544 !full_text.empty(); | |
| 545 last_omnibox_text_ = full_text; | |
| 546 last_suggestion_ = InstantSuggestion(); | |
| 547 | |
| 548 if (instant_tab_) | |
| 549 instant_tab_->CancelSelection(full_text); | |
| 550 else | |
| 551 overlay_->CancelSelection(full_text); | |
| 552 } | |
| 553 | |
| 554 content::WebContents* InstantController::GetPreviewContents() const { | |
| 555 return overlay_ ? overlay_->contents() : NULL; | |
| 556 } | |
| 557 | |
| 558 bool InstantController::IsPreviewingSearchResults() const { | |
| 559 return model_.mode().is_search_suggestions() && IsFullHeight(model_) && | |
| 560 (last_match_was_search_ || | |
| 561 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER); | |
| 562 } | |
| 563 | |
| 564 bool InstantController::CommitIfPossible(InstantCommitType type) { | |
| 565 if (!extended_enabled_ && !instant_enabled_) | |
| 566 return false; | |
| 567 | |
| 568 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 569 "CommitIfPossible: type=%d last_omnibox_text_='%s' " | |
| 570 "last_match_was_search_=%d instant_tab_=%d", type, | |
| 571 UTF16ToUTF8(last_omnibox_text_).c_str(), last_match_was_search_, | |
| 572 instant_tab_ != NULL)); | |
| 573 | |
| 574 // If we are on an already committed search results page, send a submit event | |
| 575 // to the page, but otherwise, nothing else to do. | |
| 576 if (instant_tab_) { | |
| 577 if (type == INSTANT_COMMIT_PRESSED_ENTER && | |
| 578 (last_match_was_search_ || | |
| 579 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER)) { | |
| 580 EnsureSearchTermsAreSet(instant_tab_->contents(), last_omnibox_text_); | |
| 581 instant_tab_->Submit(last_omnibox_text_); | |
| 582 instant_tab_->contents()->GetView()->Focus(); | |
| 583 return true; | |
| 584 } | |
| 585 return false; | |
| 586 } | |
| 587 | |
| 588 if (!IsPreviewingSearchResults() && type != INSTANT_COMMIT_NAVIGATED) | |
| 589 return false; | |
| 590 | |
| 591 // There may re-entrance here, from the call to browser_->CommitInstant below, | |
| 592 // which can cause a TabDeactivated notification which gets back here. | |
| 593 // In this case, overlay_->ReleaseContents() was called already. | |
| 594 if (!GetPreviewContents()) | |
| 595 return false; | |
| 596 | |
| 597 // Never commit the local omnibox. | |
| 598 if (overlay_->IsUsingLocalPreview()) | |
| 599 return false; | |
| 600 | |
| 601 if (type == INSTANT_COMMIT_FOCUS_LOST) | |
| 602 overlay_->Cancel(last_omnibox_text_); | |
| 603 else if (type != INSTANT_COMMIT_NAVIGATED) | |
| 604 overlay_->Submit(last_omnibox_text_); | |
| 605 | |
| 606 scoped_ptr<content::WebContents> preview = overlay_->ReleaseContents(); | |
| 607 | |
| 608 // If the preview page has navigated since the last Update(), we need to add | |
| 609 // the navigation to history ourselves. Else, the page will navigate after | |
| 610 // commit, and it will be added to history in the usual manner. | |
| 611 const history::HistoryAddPageArgs& last_navigation = | |
| 612 overlay_->last_navigation(); | |
| 613 if (!last_navigation.url.is_empty()) { | |
| 614 content::NavigationEntry* entry = preview->GetController().GetActiveEntry(); | |
| 615 | |
| 616 // The last navigation should be the same as the active entry if the overlay | |
| 617 // is in search mode. During navigation, the active entry could have | |
| 618 // changed since DidCommitProvisionalLoadForFrame is called after the entry | |
| 619 // is changed. | |
| 620 // TODO(shishir): Should we commit the last navigation for | |
| 621 // INSTANT_COMMIT_NAVIGATED. | |
| 622 DCHECK(type == INSTANT_COMMIT_NAVIGATED || | |
| 623 last_navigation.url == entry->GetURL()); | |
| 624 | |
| 625 // Add the page to history. | |
| 626 HistoryTabHelper* history_tab_helper = | |
| 627 HistoryTabHelper::FromWebContents(preview.get()); | |
| 628 history_tab_helper->UpdateHistoryForNavigation(last_navigation); | |
| 629 | |
| 630 // Update the page title. | |
| 631 history_tab_helper->UpdateHistoryPageTitle(*entry); | |
| 632 } | |
| 633 | |
| 634 // Add a fake history entry with a non-Instant search URL, so that search | |
| 635 // terms extraction (for autocomplete history matches) works. | |
| 636 HistoryService* history = HistoryServiceFactory::GetForProfile( | |
| 637 Profile::FromBrowserContext(preview->GetBrowserContext()), | |
| 638 Profile::EXPLICIT_ACCESS); | |
| 639 if (history) { | |
| 640 history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), | |
| 641 history::RedirectList(), last_transition_type_, | |
| 642 history::SOURCE_BROWSED, false); | |
| 643 } | |
| 644 | |
| 645 if (type == INSTANT_COMMIT_PRESSED_ALT_ENTER) { | |
| 646 preview->GetController().PruneAllButActive(); | |
| 647 } else { | |
| 648 content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
| 649 AddSessionStorageHistogram(extended_enabled_, active_tab, preview.get()); | |
| 650 preview->GetController().CopyStateFromAndPrune( | |
| 651 &active_tab->GetController()); | |
| 652 } | |
| 653 | |
| 654 if (extended_enabled_) { | |
| 655 // Adjust the search terms shown in the omnibox for this query. Hitting | |
| 656 // ENTER searches for what the user typed, so use last_omnibox_text_. | |
| 657 // Clicking on the overlay commits what is currently showing, so add in the | |
| 658 // gray text in that case. | |
| 659 if (type == INSTANT_COMMIT_FOCUS_LOST && | |
| 660 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER) { | |
| 661 // Update |last_omnibox_text_| so that the controller commits the proper | |
| 662 // query if the user focuses the omnibox and presses Enter. | |
| 663 last_omnibox_text_ += last_suggestion_.text; | |
| 664 } | |
| 665 | |
| 666 EnsureSearchTermsAreSet(preview.get(), last_omnibox_text_); | |
| 667 } | |
| 668 | |
| 669 // Save notification source before we release the preview. | |
| 670 content::Source<content::WebContents> notification_source(preview.get()); | |
| 671 | |
| 672 browser_->CommitInstant(preview.Pass(), | |
| 673 type == INSTANT_COMMIT_PRESSED_ALT_ENTER); | |
| 674 | |
| 675 content::NotificationService::current()->Notify( | |
| 676 chrome::NOTIFICATION_INSTANT_COMMITTED, | |
| 677 notification_source, | |
| 678 content::NotificationService::NoDetails()); | |
| 679 | |
| 680 // Hide explicitly. See comments in HideOverlay() for why. | |
| 681 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
| 682 | |
| 683 // Delay deletion as we could've gotten here from an InstantOverlay method. | |
| 684 MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); | |
| 685 | |
| 686 // Try to create another overlay immediately so that it is ready for the next | |
| 687 // user interaction. | |
| 688 EnsureOverlayIsCurrent(false); | |
| 689 | |
| 690 LOG_INSTANT_DEBUG_EVENT(this, "Committed"); | |
| 691 return true; | |
| 692 } | |
| 693 | |
| 694 void InstantController::OmniboxFocusChanged( | |
| 695 OmniboxFocusState state, | |
| 696 OmniboxFocusChangeReason reason, | |
| 697 gfx::NativeView view_gaining_focus) { | |
| 698 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 699 "OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_, | |
| 700 state, reason)); | |
| 701 | |
| 702 OmniboxFocusState old_focus_state = omnibox_focus_state_; | |
| 703 omnibox_focus_state_ = state; | |
| 704 if (!extended_enabled_ && !instant_enabled_) | |
| 705 return; | |
| 706 | |
| 707 // Tell the page if the key capture mode changed unless the focus state | |
| 708 // changed because of TYPING. This is because in that case, the browser hasn't | |
| 709 // really stopped capturing key strokes. | |
| 710 // | |
| 711 // (More practically, if we don't do this check, the page would receive | |
| 712 // onkeycapturechange before the corresponding onchange, and the page would | |
| 713 // have no way of telling whether the keycapturechange happened because of | |
| 714 // some actual user action or just because they started typing.) | |
| 715 if (extended_enabled_ && GetPreviewContents() && | |
| 716 reason != OMNIBOX_FOCUS_CHANGE_TYPING) { | |
| 717 const bool is_key_capture_enabled = | |
| 718 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE; | |
| 719 if (overlay_) | |
| 720 overlay_->KeyCaptureChanged(is_key_capture_enabled); | |
| 721 if (instant_tab_) | |
| 722 instant_tab_->KeyCaptureChanged(is_key_capture_enabled); | |
| 723 } | |
| 724 | |
| 725 // If focus went from outside the omnibox to the omnibox, preload the default | |
| 726 // search engine, in anticipation of the user typing a query. If the reverse | |
| 727 // happened, commit or discard the preview. | |
| 728 if (state != OMNIBOX_FOCUS_NONE && old_focus_state == OMNIBOX_FOCUS_NONE) { | |
| 729 // On explicit user actions, ignore the Instant blacklist. | |
| 730 EnsureOverlayIsCurrent(reason == OMNIBOX_FOCUS_CHANGE_EXPLICIT); | |
| 731 } else if (state == OMNIBOX_FOCUS_NONE && | |
| 732 old_focus_state != OMNIBOX_FOCUS_NONE) { | |
| 733 OmniboxLostFocus(view_gaining_focus); | |
| 734 } | |
| 735 } | |
| 736 | |
| 737 void InstantController::SearchModeChanged( | |
| 738 const chrome::search::Mode& old_mode, | |
| 739 const chrome::search::Mode& new_mode) { | |
| 740 if (!extended_enabled_) | |
| 741 return; | |
| 742 | |
| 743 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 744 "SearchModeChanged: [origin:mode] %d:%d to %d:%d", old_mode.origin, | |
| 745 old_mode.mode, new_mode.origin, new_mode.mode)); | |
| 746 | |
| 747 search_mode_ = new_mode; | |
| 748 if (!new_mode.is_search_suggestions()) | |
| 749 HideOverlay(); | |
| 750 | |
| 751 ResetInstantTab(); | |
| 752 } | |
| 753 | |
| 754 void InstantController::ActiveTabChanged() { | |
| 755 if (!extended_enabled_ && !instant_enabled_) | |
| 756 return; | |
| 757 | |
| 758 LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged"); | |
| 759 | |
| 760 // When switching tabs, always hide the preview. | |
| 761 HideOverlay(); | |
| 762 | |
| 763 if (extended_enabled_) | |
| 764 ResetInstantTab(); | |
| 765 } | |
| 766 | |
| 767 void InstantController::TabDeactivated(content::WebContents* contents) { | |
| 768 LOG_INSTANT_DEBUG_EVENT(this, "TabDeactivated"); | |
| 769 if (extended_enabled_ && !contents->IsBeingDestroyed()) | |
| 770 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
| 771 } | |
| 772 | |
| 773 void InstantController::SetInstantEnabled(bool instant_enabled, | |
| 774 bool use_local_preview_only) { | |
| 775 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 776 "SetInstantEnabled: instant_enabled=%d, use_local_preview_only=%d", | |
| 777 instant_enabled, use_local_preview_only)); | |
| 778 | |
| 779 // Non extended mode does not care about |use_local_preview_only|. | |
| 780 if (instant_enabled == instant_enabled_ && | |
| 781 (!extended_enabled_ || | |
| 782 use_local_preview_only == use_local_preview_only_)) { | |
| 783 return; | |
| 784 } | |
| 785 | |
| 786 instant_enabled_ = instant_enabled; | |
| 787 use_local_preview_only_ = use_local_preview_only; | |
| 788 HideInternal(); | |
| 789 overlay_.reset(); | |
| 790 if (extended_enabled_ || instant_enabled_) | |
| 791 EnsureOverlayIsCurrent(false); | |
| 792 if (extended_enabled_) | |
| 793 ResetNTP(); | |
| 794 if (instant_tab_) | |
| 795 instant_tab_->SetDisplayInstantResults(instant_enabled_); | |
| 796 } | |
| 797 | |
| 798 void InstantController::ThemeChanged(const ThemeBackgroundInfo& theme_info) { | |
| 799 if (!extended_enabled_) | |
| 800 return; | |
| 801 | |
| 802 if (overlay_) | |
| 803 overlay_->SendThemeBackgroundInfo(theme_info); | |
| 804 if (ntp_) | |
| 805 ntp_->SendThemeBackgroundInfo(theme_info); | |
| 806 if (instant_tab_) | |
| 807 instant_tab_->SendThemeBackgroundInfo(theme_info); | |
| 808 } | |
| 809 | |
| 810 void InstantController::SwappedOverlayContents() { | |
| 811 model_.SetPreviewContents(GetPreviewContents()); | |
| 812 } | |
| 813 | |
| 814 void InstantController::FocusedOverlayContents() { | |
| 815 #if defined(USE_AURA) | |
| 816 // On aura the omnibox only receives a focus lost if we initiate the focus | |
| 817 // change. This does that. | |
| 818 if (!model_.mode().is_default()) | |
| 819 browser_->InstantPreviewFocused(); | |
| 820 #endif | |
| 821 } | |
| 822 | |
| 823 void InstantController::ReloadOverlayIfStale() { | |
| 824 // The local popup is never stale. | |
| 825 if (overlay_ && overlay_->IsUsingLocalPreview()) | |
| 826 return; | |
| 827 | |
| 828 // If the preview is showing or the omnibox has focus, don't delete the | |
| 829 // overlay. It will get refreshed the next time the preview is hidden or the | |
| 830 // omnibox loses focus. | |
| 831 if ((!overlay_ || overlay_->is_stale()) && | |
| 832 omnibox_focus_state_ == OMNIBOX_FOCUS_NONE && | |
| 833 model_.mode().is_default()) { | |
| 834 overlay_.reset(); | |
| 835 EnsureOverlayIsCurrent(false); | |
| 836 } | |
| 837 } | |
| 838 | |
| 839 void InstantController::LogDebugEvent(const std::string& info) const { | |
| 840 DVLOG(1) << info; | |
| 841 | |
| 842 debug_events_.push_front(std::make_pair( | |
| 843 base::Time::Now().ToInternalValue(), info)); | |
| 844 static const size_t kMaxDebugEventSize = 2000; | |
| 845 if (debug_events_.size() > kMaxDebugEventSize) | |
| 846 debug_events_.pop_back(); | |
| 847 } | |
| 848 | |
| 849 void InstantController::DeleteMostVisitedItem(const GURL& url) { | |
| 850 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
| 851 if (!top_sites) | |
| 852 return; | |
| 853 | |
| 854 top_sites->AddBlacklistedURL(url); | |
| 855 } | |
| 856 | |
| 857 void InstantController::UndoMostVisitedDeletion(const GURL& url) { | |
| 858 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
| 859 if (!top_sites) | |
| 860 return; | |
| 861 | |
| 862 top_sites->RemoveBlacklistedURL(url); | |
| 863 } | |
| 864 | |
| 865 void InstantController::UndoAllMostVisitedDeletions() { | |
| 866 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
| 867 if (!top_sites) | |
| 868 return; | |
| 869 | |
| 870 top_sites->ClearBlacklistedURLs(); | |
| 871 } | |
| 872 | |
| 873 void InstantController::Observe(int type, | |
| 874 const content::NotificationSource& source, | |
| 875 const content::NotificationDetails& details) { | |
| 876 DCHECK_EQ(type, chrome::NOTIFICATION_TOP_SITES_CHANGED); | |
| 877 RequestMostVisitedItems(); | |
| 878 } | |
| 879 | |
| 880 // TODO(shishir): We assume that the WebContent's current RenderViewHost is the | |
| 881 // RenderViewHost being created which is not always true. Fix this. | |
| 882 void InstantController::InstantPageRenderViewCreated( | |
| 883 const content::WebContents* contents) { | |
| 884 if (!extended_enabled_) | |
| 885 return; | |
| 886 | |
| 887 // Update theme info so that the page picks it up. | |
| 888 browser_->UpdateThemeInfo(false); | |
| 889 | |
| 890 // Ensure the searchbox API has the correct initial state. | |
| 891 if (IsContentsFrom(overlay(), contents)) { | |
| 892 overlay_->SetDisplayInstantResults(instant_enabled_); | |
| 893 overlay_->KeyCaptureChanged( | |
| 894 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE); | |
| 895 overlay_->SetOmniboxBounds(omnibox_bounds_); | |
| 896 overlay_->InitializeFonts(); | |
| 897 } else if (IsContentsFrom(ntp(), contents)) { | |
| 898 ntp_->SetDisplayInstantResults(instant_enabled_); | |
| 899 ntp_->SetOmniboxBounds(omnibox_bounds_); | |
| 900 ntp_->InitializeFonts(); | |
| 901 } else { | |
| 902 NOTREACHED(); | |
| 903 } | |
| 904 StartListeningToMostVisitedChanges(); | |
| 905 } | |
| 906 | |
| 907 void InstantController::InstantSupportDetermined( | |
| 908 const content::WebContents* contents, | |
| 909 bool supports_instant) { | |
| 910 if (IsContentsFrom(instant_tab(), contents)) { | |
| 911 if (!supports_instant) | |
| 912 MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release()); | |
| 913 } else if (IsContentsFrom(ntp(), contents)) { | |
| 914 if (supports_instant) | |
| 915 RemoveFromBlacklist(ntp_->instant_url()); | |
| 916 else | |
| 917 BlacklistAndResetNTP(); | |
| 918 | |
| 919 content::NotificationService::current()->Notify( | |
| 920 chrome::NOTIFICATION_INSTANT_NTP_SUPPORT_DETERMINED, | |
| 921 content::Source<InstantController>(this), | |
| 922 content::NotificationService::NoDetails()); | |
| 923 | |
| 924 } else if (IsContentsFrom(overlay(), contents)) { | |
| 925 if (supports_instant) | |
| 926 RemoveFromBlacklist(overlay_->instant_url()); | |
| 927 else | |
| 928 BlacklistAndResetOverlay(); | |
| 929 | |
| 930 content::NotificationService::current()->Notify( | |
| 931 chrome::NOTIFICATION_INSTANT_OVERLAY_SUPPORT_DETERMINED, | |
| 932 content::Source<InstantController>(this), | |
| 933 content::NotificationService::NoDetails()); | |
| 934 } | |
| 935 } | |
| 936 | |
| 937 void InstantController::InstantPageRenderViewGone( | |
| 938 const content::WebContents* contents) { | |
| 939 if (IsContentsFrom(overlay(), contents)) | |
| 940 BlacklistAndResetOverlay(); | |
| 941 else if (IsContentsFrom(ntp(), contents)) | |
| 942 BlacklistAndResetNTP(); | |
| 943 else | |
| 944 NOTREACHED(); | |
| 945 } | |
| 946 | |
| 947 void InstantController::InstantPageAboutToNavigateMainFrame( | |
| 948 const content::WebContents* contents, | |
| 949 const GURL& url) { | |
| 950 DCHECK(IsContentsFrom(overlay(), contents)); | |
| 951 | |
| 952 // If the page does not yet support instant, we allow redirects and other | |
| 953 // navigations to go through since the instant URL can redirect - e.g. to | |
| 954 // country specific pages. | |
| 955 if (!overlay_->supports_instant()) | |
| 956 return; | |
| 957 | |
| 958 GURL instant_url(overlay_->instant_url()); | |
| 959 | |
| 960 // If we are navigating to the instant URL, do nothing. | |
| 961 if (url == instant_url) | |
| 962 return; | |
| 963 | |
| 964 // Commit the navigation if either: | |
| 965 // - The page is in NTP mode (so it could only navigate on a user click) or | |
| 966 // - The page is not in NTP mode and we are navigating to a URL with a | |
| 967 // different host or path than the instant URL. This enables the instant | |
| 968 // page when it is showing search results to change the query parameters | |
| 969 // and fragments of the URL without it navigating. | |
| 970 if (model_.mode().is_ntp() || | |
| 971 (url.host() != instant_url.host() || url.path() != instant_url.path())) { | |
| 972 CommitIfPossible(INSTANT_COMMIT_NAVIGATED); | |
| 973 } | |
| 974 } | |
| 975 | |
| 976 void InstantController::SetSuggestions( | |
| 977 const content::WebContents* contents, | |
| 978 const std::vector<InstantSuggestion>& suggestions) { | |
| 979 LOG_INSTANT_DEBUG_EVENT(this, "SetSuggestions"); | |
| 980 | |
| 981 // Ignore if the message is from an unexpected source. | |
| 982 if (IsContentsFrom(ntp(), contents)) | |
| 983 return; | |
| 984 if (instant_tab_ && !IsContentsFrom(instant_tab(), contents)) | |
| 985 return; | |
| 986 if (IsContentsFrom(overlay(), contents) && | |
| 987 !allow_preview_to_show_search_suggestions_) | |
| 988 return; | |
| 989 | |
| 990 InstantSuggestion suggestion; | |
| 991 if (!suggestions.empty()) | |
| 992 suggestion = suggestions[0]; | |
| 993 | |
| 994 if (instant_tab_ && search_mode_.is_search_results() && | |
| 995 suggestion.behavior == INSTANT_COMPLETE_REPLACE) { | |
| 996 // Update |last_omnibox_text_| so that the controller commits the proper | |
| 997 // query if the user focuses the omnibox and presses Enter. | |
| 998 last_omnibox_text_ = suggestion.text; | |
| 999 last_suggestion_ = InstantSuggestion(); | |
| 1000 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | |
| 1001 // This means a committed page in state search called setValue(). We should | |
| 1002 // update the omnibox to reflect what the search page says. | |
| 1003 browser_->SetInstantSuggestion(suggestion); | |
| 1004 return; | |
| 1005 } | |
| 1006 | |
| 1007 // Ignore if we are not currently accepting search suggestions. | |
| 1008 if (!search_mode_.is_search_suggestions() || last_omnibox_text_.empty()) | |
| 1009 return; | |
| 1010 | |
| 1011 if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) { | |
| 1012 // We don't get an Update() when changing the omnibox due to a REPLACE | |
| 1013 // suggestion (so that we don't inadvertently cause the preview to change | |
| 1014 // what it's showing, as the user arrows up/down through the page-provided | |
| 1015 // suggestions). So, update these state variables here. | |
| 1016 last_omnibox_text_ = suggestion.text; | |
| 1017 last_suggestion_ = InstantSuggestion(); | |
| 1018 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | |
| 1019 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1020 "ReplaceSuggestion text='%s' type=%d", | |
| 1021 UTF16ToUTF8(suggestion.text).c_str(), suggestion.type)); | |
| 1022 browser_->SetInstantSuggestion(suggestion); | |
| 1023 } else { | |
| 1024 bool is_valid_suggestion = true; | |
| 1025 | |
| 1026 // If the page is trying to set inline autocompletion in verbatim mode, | |
| 1027 // instead try suggesting the exact omnibox text. This makes the omnibox | |
| 1028 // interpret user text as an URL if possible while preventing unwanted | |
| 1029 // autocompletion during backspacing. | |
| 1030 if (suggestion.behavior == INSTANT_COMPLETE_NOW && last_verbatim_) | |
| 1031 suggestion.text = last_omnibox_text_; | |
| 1032 | |
| 1033 // Suggestion text should be a full URL for URL suggestions, or the | |
| 1034 // completion of a query for query suggestions. | |
| 1035 if (suggestion.type == INSTANT_SUGGESTION_URL) { | |
| 1036 // If the suggestion is not a valid URL, perhaps it's something like | |
| 1037 // "foo.com". Try prefixing "http://". If it still isn't valid, drop it. | |
| 1038 if (!GURL(suggestion.text).is_valid()) { | |
| 1039 suggestion.text.insert(0, ASCIIToUTF16("http://")); | |
| 1040 if (!GURL(suggestion.text).is_valid()) | |
| 1041 is_valid_suggestion = false; | |
| 1042 } | |
| 1043 } else if (StartsWith(suggestion.text, last_omnibox_text_, true)) { | |
| 1044 // The user typed an exact prefix of the suggestion. | |
| 1045 suggestion.text.erase(0, last_omnibox_text_.size()); | |
| 1046 } else if (!NormalizeAndStripPrefix(&suggestion.text, last_omnibox_text_)) { | |
| 1047 // Unicode normalize and case-fold the user text and suggestion. If the | |
| 1048 // user text is a prefix, suggest the normalized, case-folded completion; | |
| 1049 // for instance, if the user types 'i' and the suggestion is 'INSTANT', | |
| 1050 // suggest 'nstant'. Otherwise, the user text really isn't a prefix, so | |
| 1051 // suggest nothing. | |
| 1052 is_valid_suggestion = false; | |
| 1053 } | |
| 1054 | |
| 1055 // Don't suggest gray text if there already was inline autocompletion. | |
| 1056 // http://crbug.com/162303 | |
| 1057 if (suggestion.behavior == INSTANT_COMPLETE_NEVER && | |
| 1058 last_omnibox_text_has_inline_autocompletion_) | |
| 1059 is_valid_suggestion = false; | |
| 1060 | |
| 1061 if (is_valid_suggestion) { | |
| 1062 last_suggestion_ = suggestion; | |
| 1063 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1064 "SetInstantSuggestion: text='%s' behavior=%d", | |
| 1065 UTF16ToUTF8(suggestion.text).c_str(), suggestion.behavior)); | |
| 1066 browser_->SetInstantSuggestion(suggestion); | |
| 1067 } else { | |
| 1068 last_suggestion_ = InstantSuggestion(); | |
| 1069 } | |
| 1070 } | |
| 1071 | |
| 1072 // Extended mode pages will call ShowOverlay() when they are ready. | |
| 1073 if (!extended_enabled_) | |
| 1074 ShowOverlay(INSTANT_SHOWN_QUERY_SUGGESTIONS, 100, INSTANT_SIZE_PERCENT); | |
| 1075 } | |
| 1076 | |
| 1077 void InstantController::ShowInstantOverlay(const content::WebContents* contents, | |
| 1078 InstantShownReason reason, | |
| 1079 int height, | |
| 1080 InstantSizeUnits units) { | |
| 1081 if (extended_enabled_ && IsContentsFrom(overlay(), contents)) | |
| 1082 ShowOverlay(reason, height, units); | |
| 1083 } | |
| 1084 | |
| 1085 void InstantController::StartCapturingKeyStrokes( | |
| 1086 const content::WebContents* contents) { | |
| 1087 if (!extended_enabled_) | |
| 1088 return; | |
| 1089 | |
| 1090 DCHECK(IsContentsFrom(instant_tab(), contents)); | |
| 1091 browser_->FocusOmniboxInvisibly(); | |
| 1092 } | |
| 1093 | |
| 1094 void InstantController::StopCapturingKeyStrokes( | |
| 1095 content::WebContents* contents) { | |
| 1096 // Nothing to do if omnibox doesn't have invisible focus. | |
| 1097 if (!extended_enabled_ || omnibox_focus_state_ != OMNIBOX_FOCUS_INVISIBLE) | |
| 1098 return; | |
| 1099 | |
| 1100 DCHECK(IsContentsFrom(instant_tab(), contents)); | |
| 1101 contents->GetView()->Focus(); | |
| 1102 } | |
| 1103 | |
| 1104 void InstantController::NavigateToURL(const content::WebContents* contents, | |
| 1105 const GURL& url, | |
| 1106 content::PageTransition transition, | |
| 1107 WindowOpenDisposition disposition) { | |
| 1108 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1109 "NavigateToURL: url='%s'", url.spec().c_str())); | |
| 1110 | |
| 1111 // TODO(samarth): handle case where contents are no longer "active" (e.g. user | |
| 1112 // has switched tabs). | |
| 1113 if (!extended_enabled_) | |
| 1114 return; | |
| 1115 if (overlay_) | |
| 1116 HideOverlay(); | |
| 1117 browser_->OpenURL(url, transition, disposition); | |
| 1118 } | |
| 1119 | |
| 1120 void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) { | |
| 1121 // If the preview is showing custom NTP content, don't hide it, commit it | |
| 1122 // (no matter where the user clicked) or try to recreate it. | |
| 1123 if (model_.mode().is_ntp()) | |
| 1124 return; | |
| 1125 | |
| 1126 if (model_.mode().is_default()) { | |
| 1127 // Correct search terms if the user clicked on the committed results page | |
| 1128 // while showing an autocomplete suggestion | |
| 1129 if (instant_tab_ && !last_suggestion_.text.empty() && | |
| 1130 last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && | |
| 1131 IsViewInContents(GetViewGainingFocus(view_gaining_focus), | |
| 1132 instant_tab_->contents())) { | |
| 1133 // Commit the omnibox's suggested grey text as if the user had typed it. | |
| 1134 browser_->CommitSuggestedText(true); | |
| 1135 | |
| 1136 // Update the state so that next query from hitting Enter from the | |
| 1137 // omnibox is correct. | |
| 1138 last_omnibox_text_ += last_suggestion_.text; | |
| 1139 last_suggestion_ = InstantSuggestion(); | |
| 1140 } | |
| 1141 // If the preview is not showing at all, recreate it if it's stale. | |
| 1142 ReloadOverlayIfStale(); | |
| 1143 MaybeSwitchToRemoteOverlay(); | |
| 1144 return; | |
| 1145 } | |
| 1146 | |
| 1147 // The preview is showing search suggestions. If GetPreviewContents() is NULL, | |
| 1148 // we are in the commit path. Don't do anything. | |
| 1149 if (!GetPreviewContents()) | |
| 1150 return; | |
| 1151 | |
| 1152 #if defined(OS_MACOSX) | |
| 1153 // TODO(sreeram): See if Mac really needs this special treatment. | |
| 1154 if (!overlay_->is_pointer_down_from_activate()) | |
| 1155 HideOverlay(); | |
| 1156 #else | |
| 1157 if (IsFullHeight(model_)) | |
| 1158 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
| 1159 else if (!IsViewInContents(GetViewGainingFocus(view_gaining_focus), | |
| 1160 overlay_->contents())) | |
| 1161 HideOverlay(); | |
| 1162 #endif | |
| 1163 } | |
| 1164 | |
| 1165 void InstantController::ResetNTP() { | |
| 1166 ntp_.reset(); | |
| 1167 std::string instant_url; | |
| 1168 if (!GetInstantURL(browser_->profile(), false, &instant_url)) | |
| 1169 return; | |
| 1170 | |
| 1171 ntp_.reset(new InstantNTP(this, instant_url)); | |
| 1172 ntp_->InitContents(browser_->profile(), browser_->GetActiveWebContents(), | |
| 1173 base::Bind(&InstantController::ResetNTP, | |
| 1174 base::Unretained(this))); | |
| 1175 } | |
| 1176 | |
| 1177 bool InstantController::EnsureOverlayIsCurrent(bool ignore_blacklist) { | |
| 1178 // If there's no active tab, the browser is closing. | |
| 1179 const content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
| 1180 if (!active_tab) | |
| 1181 return false; | |
| 1182 | |
| 1183 Profile* profile = Profile::FromBrowserContext( | |
| 1184 active_tab->GetBrowserContext()); | |
| 1185 std::string instant_url; | |
| 1186 if (!GetInstantURL(profile, ignore_blacklist, &instant_url)) { | |
| 1187 // If we are in extended mode, fallback to the local popup. | |
| 1188 if (extended_enabled_) | |
| 1189 instant_url = chrome::search::kLocalOmniboxPopupURL; | |
| 1190 else | |
| 1191 return false; | |
| 1192 } | |
| 1193 | |
| 1194 if (!overlay_ || overlay_->instant_url() != instant_url) | |
| 1195 CreateOverlay(instant_url, active_tab); | |
| 1196 | |
| 1197 return true; | |
| 1198 } | |
| 1199 | |
| 1200 void InstantController::CreateOverlay(const std::string& instant_url, | |
| 1201 const content::WebContents* active_tab) { | |
| 1202 HideInternal(); | |
| 1203 overlay_.reset(new InstantOverlay(this, instant_url)); | |
| 1204 overlay_->InitContents(browser_->profile(), active_tab); | |
| 1205 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1206 "CreateOverlay: instant_url='%s'", instant_url.c_str())); | |
| 1207 } | |
| 1208 | |
| 1209 void InstantController::MaybeSwitchToRemoteOverlay() { | |
| 1210 if (!overlay_ || omnibox_focus_state_ != OMNIBOX_FOCUS_NONE || | |
| 1211 !model_.mode().is_default()) { | |
| 1212 return; | |
| 1213 } | |
| 1214 | |
| 1215 EnsureOverlayIsCurrent(false); | |
| 1216 } | |
| 1217 | |
| 1218 void InstantController::ResetInstantTab() { | |
| 1219 // Do not wire up the InstantTab if instant should only use local previews, to | |
| 1220 // prevent it from sending data to the page. | |
| 1221 if (!search_mode_.is_origin_default() && !use_local_preview_only_) { | |
| 1222 content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
| 1223 if (!instant_tab_ || active_tab != instant_tab_->contents()) { | |
| 1224 instant_tab_.reset(new InstantTab(this)); | |
| 1225 instant_tab_->Init(active_tab); | |
| 1226 // Update theme info for this tab. | |
| 1227 browser_->UpdateThemeInfo(false); | |
| 1228 instant_tab_->SetDisplayInstantResults(instant_enabled_); | |
| 1229 instant_tab_->SetOmniboxBounds(omnibox_bounds_); | |
| 1230 instant_tab_->InitializeFonts(); | |
| 1231 StartListeningToMostVisitedChanges(); | |
| 1232 instant_tab_->KeyCaptureChanged( | |
| 1233 omnibox_focus_state_ == OMNIBOX_FOCUS_INVISIBLE); | |
| 1234 } | |
| 1235 | |
| 1236 // Hide the |overlay_| since we are now using |instant_tab_| instead. | |
| 1237 HideOverlay(); | |
| 1238 } else { | |
| 1239 instant_tab_.reset(); | |
| 1240 } | |
| 1241 } | |
| 1242 | |
| 1243 void InstantController::HideOverlay() { | |
| 1244 HideInternal(); | |
| 1245 ReloadOverlayIfStale(); | |
| 1246 MaybeSwitchToRemoteOverlay(); | |
| 1247 } | |
| 1248 | |
| 1249 void InstantController::HideInternal() { | |
| 1250 LOG_INSTANT_DEBUG_EVENT(this, "Hide"); | |
| 1251 | |
| 1252 // If GetPreviewContents() returns NULL, either we're already in the desired | |
| 1253 // MODE_DEFAULT state, or we're in the commit path. For the latter, don't | |
| 1254 // change the state just yet; else we may hide the preview unnecessarily. | |
| 1255 // Instead, the state will be set correctly after the commit is done. | |
| 1256 if (GetPreviewContents()) { | |
| 1257 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
| 1258 allow_preview_to_show_search_suggestions_ = false; | |
| 1259 | |
| 1260 // Send a message asking the preview to clear out old results. | |
| 1261 overlay_->Update(string16(), 0, 0, true); | |
| 1262 } | |
| 1263 | |
| 1264 // Clear the first interaction timestamp for later use. | |
| 1265 first_interaction_time_ = base::Time(); | |
| 1266 } | |
| 1267 | |
| 1268 void InstantController::ShowOverlay(InstantShownReason reason, | |
| 1269 int height, | |
| 1270 InstantSizeUnits units) { | |
| 1271 // If we are on a committed search results page, the |overlay_| is not in use. | |
| 1272 if (instant_tab_) | |
| 1273 return; | |
| 1274 | |
| 1275 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1276 "Show: reason=%d height=%d units=%d", reason, height, units)); | |
| 1277 | |
| 1278 // INSTANT_SHOWN_CUSTOM_NTP_CONTENT is no longer supported. | |
| 1279 // TODO(samarth): remove once the server has been updated. | |
| 1280 if (reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT) | |
| 1281 return; | |
| 1282 | |
| 1283 // Must have updated omnibox after the last HideOverlay() to show suggestions. | |
| 1284 if (reason == INSTANT_SHOWN_QUERY_SUGGESTIONS && | |
| 1285 !allow_preview_to_show_search_suggestions_) | |
| 1286 return; | |
| 1287 | |
| 1288 // The page is trying to hide itself. Hide explicitly (i.e., don't use | |
| 1289 // HideOverlay()) so that it can change its mind. | |
| 1290 if (height == 0) { | |
| 1291 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
| 1292 return; | |
| 1293 } | |
| 1294 | |
| 1295 // If the preview is being shown for the first time since the user started | |
| 1296 // typing, record a histogram value. | |
| 1297 if (!first_interaction_time_.is_null() && model_.mode().is_default()) { | |
| 1298 base::TimeDelta delta = base::Time::Now() - first_interaction_time_; | |
| 1299 UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShow", delta); | |
| 1300 } | |
| 1301 | |
| 1302 // Show at 100% height except in the following cases: | |
| 1303 // - The local omnibox popup is being loaded. | |
| 1304 // - Instant is disabled. The page needs to be able to show only a dropdown. | |
| 1305 // - The page wants to show custom NTP content. | |
| 1306 // - The page is over a website other than search or an NTP, and is not | |
| 1307 // already showing at 100% height. | |
| 1308 if (overlay_->IsUsingLocalPreview() || !instant_enabled_ || | |
| 1309 reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT || | |
| 1310 (search_mode_.is_origin_default() && !IsFullHeight(model_))) | |
| 1311 model_.SetPreviewState(search_mode_, height, units); | |
| 1312 else | |
| 1313 model_.SetPreviewState(search_mode_, 100, INSTANT_SIZE_PERCENT); | |
| 1314 | |
| 1315 // If the overlay is being shown at full height and the omnibox is not | |
| 1316 // focused, commit right away. | |
| 1317 if (IsFullHeight(model_) && omnibox_focus_state_ == OMNIBOX_FOCUS_NONE) | |
| 1318 CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST); | |
| 1319 } | |
| 1320 | |
| 1321 void InstantController::SendPopupBoundsToPage() { | |
| 1322 if (last_popup_bounds_ == popup_bounds_ || !overlay_ || | |
| 1323 overlay_->is_pointer_down_from_activate()) | |
| 1324 return; | |
| 1325 | |
| 1326 last_popup_bounds_ = popup_bounds_; | |
| 1327 gfx::Rect preview_bounds = browser_->GetInstantBounds(); | |
| 1328 gfx::Rect intersection = gfx::IntersectRects(popup_bounds_, preview_bounds); | |
| 1329 | |
| 1330 // Translate into window coordinates. | |
| 1331 if (!intersection.IsEmpty()) { | |
| 1332 intersection.Offset(-preview_bounds.origin().x(), | |
| 1333 -preview_bounds.origin().y()); | |
| 1334 } | |
| 1335 | |
| 1336 // In the current Chrome UI, these must always be true so they sanity check | |
| 1337 // the above operations. In a future UI, these may be removed or adjusted. | |
| 1338 // There is no point in sanity-checking |intersection.y()| because the omnibox | |
| 1339 // can be placed anywhere vertically relative to the preview (for example, in | |
| 1340 // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds). | |
| 1341 DCHECK_LE(0, intersection.x()); | |
| 1342 DCHECK_LE(0, intersection.width()); | |
| 1343 DCHECK_LE(0, intersection.height()); | |
| 1344 | |
| 1345 overlay_->SetPopupBounds(intersection); | |
| 1346 } | |
| 1347 | |
| 1348 bool InstantController::GetInstantURL(Profile* profile, | |
| 1349 bool ignore_blacklist, | |
| 1350 std::string* instant_url) const { | |
| 1351 DCHECK(profile); | |
| 1352 instant_url->clear(); | |
| 1353 | |
| 1354 if (extended_enabled_ && use_local_preview_only_) { | |
| 1355 *instant_url = chrome::search::kLocalOmniboxPopupURL; | |
| 1356 return true; | |
| 1357 } | |
| 1358 | |
| 1359 const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile( | |
| 1360 profile)->GetDefaultSearchProvider(); | |
| 1361 | |
| 1362 if (!template_url) { | |
| 1363 LOG_INSTANT_DEBUG_EVENT(this, "GetInstantURL: No template URL"); | |
| 1364 return false; | |
| 1365 } | |
| 1366 | |
| 1367 CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
| 1368 if (command_line->HasSwitch(switches::kInstantURL)) | |
| 1369 *instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL); | |
| 1370 | |
| 1371 if (instant_url->empty()) { | |
| 1372 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref(); | |
| 1373 if (!instant_url_ref.IsValid()) { | |
| 1374 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1375 "GetInstantURL: TemplateRef invalid: url=%s", | |
| 1376 template_url->instant_url().c_str())); | |
| 1377 return false; | |
| 1378 } | |
| 1379 | |
| 1380 // Even if the URL template doesn't have search terms, it may have other | |
| 1381 // components (such as {google:baseURL}) that need to be replaced. | |
| 1382 *instant_url = instant_url_ref.ReplaceSearchTerms( | |
| 1383 TemplateURLRef::SearchTermsArgs(string16())); | |
| 1384 | |
| 1385 // Extended mode should always use HTTPS. TODO(sreeram): This section can be | |
| 1386 // removed if TemplateURLs supported "https://{google:host}/..." instead of | |
| 1387 // only supporting "{google:baseURL}...". | |
| 1388 if (extended_enabled_) { | |
| 1389 GURL url_obj(*instant_url); | |
| 1390 if (!url_obj.is_valid()) { | |
| 1391 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1392 "GetInstantURL: Instant URL invalid: url=%s", | |
| 1393 url_obj.possibly_invalid_spec().c_str())); | |
| 1394 return false; | |
| 1395 } | |
| 1396 | |
| 1397 // Extended mode won't work properly unless the TemplateURL supports the | |
| 1398 // param to enable it on the server. | |
| 1399 if (!template_url->HasSearchTermsReplacementKey(url_obj)) { | |
| 1400 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1401 "GetInstantURL: No search terms replacement key: url=%s", | |
| 1402 url_obj.spec().c_str())); | |
| 1403 return false; | |
| 1404 } | |
| 1405 | |
| 1406 if (!url_obj.SchemeIsSecure()) { | |
| 1407 std::string new_scheme = "https"; | |
| 1408 std::string new_port = "443"; | |
| 1409 GURL::Replacements secure; | |
| 1410 secure.SetSchemeStr(new_scheme); | |
| 1411 secure.SetPortStr(new_port); | |
| 1412 url_obj = url_obj.ReplaceComponents(secure); | |
| 1413 | |
| 1414 if (!url_obj.is_valid()) { | |
| 1415 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1416 "GetInstantURL: HTTPS URL invalid: url=%s", | |
| 1417 url_obj.possibly_invalid_spec().c_str())); | |
| 1418 return false; | |
| 1419 } | |
| 1420 | |
| 1421 *instant_url = url_obj.spec(); | |
| 1422 } | |
| 1423 } | |
| 1424 } | |
| 1425 | |
| 1426 if (!ignore_blacklist) { | |
| 1427 std::map<std::string, int>::const_iterator iter = | |
| 1428 blacklisted_urls_.find(*instant_url); | |
| 1429 if (iter != blacklisted_urls_.end() && | |
| 1430 iter->second > kMaxInstantSupportFailures) { | |
| 1431 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_BLOCKED_BY_BLACKLIST); | |
| 1432 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf( | |
| 1433 "GetInstantURL: Instant URL blacklisted: url=%s", | |
| 1434 instant_url->c_str())); | |
| 1435 return false; | |
| 1436 } | |
| 1437 } | |
| 1438 | |
| 1439 return true; | |
| 1440 } | |
| 1441 | |
| 1442 void InstantController::BlacklistAndResetNTP() { | |
| 1443 ++blacklisted_urls_[ntp_->instant_url()]; | |
| 1444 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST); | |
| 1445 delete ntp_->ReleaseContents().release(); | |
| 1446 MessageLoop::current()->DeleteSoon(FROM_HERE, ntp_.release()); | |
| 1447 ResetNTP(); | |
| 1448 } | |
| 1449 | |
| 1450 void InstantController::BlacklistAndResetOverlay() { | |
| 1451 ++blacklisted_urls_[overlay_->instant_url()]; | |
| 1452 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_ADDED_TO_BLACKLIST); | |
| 1453 HideInternal(); | |
| 1454 delete overlay_->ReleaseContents().release(); | |
| 1455 MessageLoop::current()->DeleteSoon(FROM_HERE, overlay_.release()); | |
| 1456 EnsureOverlayIsCurrent(false); | |
| 1457 } | |
| 1458 | |
| 1459 void InstantController::RemoveFromBlacklist(const std::string& url) { | |
| 1460 if (blacklisted_urls_.erase(url)) { | |
| 1461 RecordEventHistogram(INSTANT_CONTROLLER_EVENT_URL_REMOVED_FROM_BLACKLIST); | |
| 1462 } | |
| 1463 } | |
| 1464 | |
| 1465 void InstantController::StartListeningToMostVisitedChanges() { | |
| 1466 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
| 1467 if (top_sites) { | |
| 1468 if (!registrar_.IsRegistered( | |
| 1469 this, chrome::NOTIFICATION_TOP_SITES_CHANGED, | |
| 1470 content::Source<history::TopSites>(top_sites))) { | |
| 1471 // TopSites updates itself after a delay. This is especially noticable | |
| 1472 // when your profile is empty. Ask TopSites to update itself when we're | |
| 1473 // about to show the new tab page. | |
| 1474 top_sites->SyncWithHistory(); | |
| 1475 | |
| 1476 RequestMostVisitedItems(); | |
| 1477 | |
| 1478 // Register for notification when TopSites changes. | |
| 1479 registrar_.Add(this, chrome::NOTIFICATION_TOP_SITES_CHANGED, | |
| 1480 content::Source<history::TopSites>(top_sites)); | |
| 1481 } else { | |
| 1482 // We are already registered, so just get and send the most visited data. | |
| 1483 RequestMostVisitedItems(); | |
| 1484 } | |
| 1485 } | |
| 1486 } | |
| 1487 | |
| 1488 void InstantController::RequestMostVisitedItems() { | |
| 1489 history::TopSites* top_sites = browser_->profile()->GetTopSites(); | |
| 1490 if (top_sites) { | |
| 1491 top_sites->GetMostVisitedURLs( | |
| 1492 base::Bind(&InstantController::OnMostVisitedItemsReceived, | |
| 1493 weak_ptr_factory_.GetWeakPtr())); | |
| 1494 } | |
| 1495 } | |
| 1496 | |
| 1497 void InstantController::OnMostVisitedItemsReceived( | |
| 1498 const history::MostVisitedURLList& data) { | |
| 1499 std::vector<MostVisitedItem> most_visited_items; | |
| 1500 for (size_t i = 0; i < data.size(); i++) { | |
| 1501 const history::MostVisitedURL& url = data[i]; | |
| 1502 | |
| 1503 MostVisitedItem item; | |
| 1504 item.url = url.url; | |
| 1505 item.title = url.title; | |
| 1506 | |
| 1507 most_visited_items.push_back(item); | |
| 1508 } | |
| 1509 SendMostVisitedItems(most_visited_items); | |
| 1510 } | |
| 1511 | |
| 1512 void InstantController::SendMostVisitedItems( | |
| 1513 const std::vector<MostVisitedItem>& items) { | |
| 1514 if (overlay_) | |
| 1515 overlay_->SendMostVisitedItems(items); | |
| 1516 if (ntp_) | |
| 1517 ntp_->SendMostVisitedItems(items); | |
| 1518 if (instant_tab_) | |
| 1519 instant_tab_->SendMostVisitedItems(items); | |
| 1520 content::NotificationService::current()->Notify( | |
| 1521 chrome::NOTIFICATION_INSTANT_SENT_MOST_VISITED_ITEMS, | |
| 1522 content::Source<InstantController>(this), | |
| 1523 content::NotificationService::NoDetails()); | |
| 1524 } | |
| OLD | NEW |