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 "base/command_line.h" |
| 8 #include "base/metrics/histogram.h" | 8 #include "base/metrics/histogram.h" |
| 9 #include "base/string_util.h" | 9 #include "base/string_util.h" |
| 10 #include "base/utf_string_conversions.h" | 10 #include "base/utf_string_conversions.h" |
| 11 #include "chrome/browser/autocomplete/autocomplete_provider.h" | 11 #include "chrome/browser/autocomplete/autocomplete_provider.h" |
| 12 #include "chrome/browser/google/google_util.h" | 12 #include "chrome/browser/google/google_util.h" |
| 13 #include "chrome/browser/history/history.h" | 13 #include "chrome/browser/history/history.h" |
| 14 #include "chrome/browser/history/history_service_factory.h" | 14 #include "chrome/browser/history/history_service_factory.h" |
| 15 #include "chrome/browser/history/history_tab_helper.h" | 15 #include "chrome/browser/history/history_tab_helper.h" |
| 16 #include "chrome/browser/instant/instant_loader.h" | 16 #include "chrome/browser/instant/instant_loader.h" |
| 17 #include "chrome/browser/instant/instant_tab.h" | |
| 17 #include "chrome/browser/platform_util.h" | 18 #include "chrome/browser/platform_util.h" |
| 18 #include "chrome/browser/search_engines/template_url_service.h" | 19 #include "chrome/browser/search_engines/template_url_service.h" |
| 19 #include "chrome/browser/search_engines/template_url_service_factory.h" | 20 #include "chrome/browser/search_engines/template_url_service_factory.h" |
| 20 #include "chrome/browser/ui/browser_instant_controller.h" | 21 #include "chrome/browser/ui/browser_instant_controller.h" |
| 21 #include "chrome/browser/ui/search/search_tab_helper.h" | 22 #include "chrome/browser/ui/search/search_tab_helper.h" |
| 22 #include "chrome/browser/ui/tab_contents/tab_contents.h" | |
| 23 #include "chrome/common/chrome_notification_types.h" | 23 #include "chrome/common/chrome_notification_types.h" |
| 24 #include "chrome/common/chrome_switches.h" | 24 #include "chrome/common/chrome_switches.h" |
| 25 #include "content/public/browser/navigation_entry.h" | 25 #include "content/public/browser/navigation_entry.h" |
| 26 #include "content/public/browser/notification_service.h" | 26 #include "content/public/browser/notification_service.h" |
| 27 #include "content/public/browser/render_widget_host_view.h" | 27 #include "content/public/browser/render_widget_host_view.h" |
| 28 #include "net/base/escape.h" | 28 #include "net/base/escape.h" |
| 29 #include "unicode/normalizer2.h" | 29 #include "unicode/normalizer2.h" |
| 30 | 30 |
| 31 #if defined(TOOLKIT_VIEWS) | 31 #if defined(TOOLKIT_VIEWS) |
| 32 #include "ui/views/widget/widget.h" | 32 #include "ui/views/widget/widget.h" |
| 33 #endif | 33 #endif |
| 34 | 34 |
| 35 namespace { | 35 namespace { |
| 36 | 36 |
| 37 // An artificial delay (in milliseconds) we introduce before telling the Instant | 37 // An artificial delay (in milliseconds) we introduce before telling the Instant |
| 38 // page about the new omnibox bounds, in cases where the bounds shrink. This is | 38 // page about the new omnibox bounds, in cases where the bounds shrink. This is |
| 39 // to avoid the page jumping up/down very fast in response to bounds changes. | 39 // to avoid the page jumping up/down very fast in response to bounds changes. |
| 40 const int kUpdateBoundsDelayMS = 1000; | 40 const int kUpdateBoundsDelayMS = 1000; |
| 41 | 41 |
| 42 // The maximum number of times we'll load a non-Instant-supporting search engine | 42 // The maximum number of times we'll load a non-Instant-supporting search engine |
| 43 // before we give up and blacklist it for the rest of the browsing session. | 43 // before we give up and blacklist it for the rest of the browsing session. |
| 44 const int kMaxInstantSupportFailures = 10; | 44 const int kMaxInstantSupportFailures = 10; |
| 45 | 45 |
| 46 // If an Instant page has not been used in these many milliseconds, it is | 46 // If an Instant page has not been used in these many milliseconds, it is |
| 47 // reloaded so that the page does not become stale. | 47 // reloaded so that the page does not become stale. |
| 48 const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000; | 48 const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000; |
| 49 | 49 |
| 50 void AddSessionStorageHistogram(bool extended_enabled, | 50 void AddSessionStorageHistogram(bool extended_enabled, |
| 51 const TabContents* tab1, | 51 const content::WebContents* tab1, |
| 52 const TabContents* tab2) { | 52 const content::WebContents* tab2) { |
| 53 base::Histogram* histogram = base::BooleanHistogram::FactoryGet( | 53 base::Histogram* histogram = base::BooleanHistogram::FactoryGet( |
| 54 std::string("Instant.SessionStorageNamespace") + | 54 std::string("Instant.SessionStorageNamespace") + |
| 55 (extended_enabled ? "_Extended" : "_Instant"), | 55 (extended_enabled ? "_Extended" : "_Instant"), |
| 56 base::Histogram::kUmaTargetedHistogramFlag); | 56 base::Histogram::kUmaTargetedHistogramFlag); |
| 57 const content::SessionStorageNamespaceMap& session_storage_map1 = | 57 const content::SessionStorageNamespaceMap& session_storage_map1 = |
| 58 tab1->web_contents()->GetController().GetSessionStorageNamespaceMap(); | 58 tab1->GetController().GetSessionStorageNamespaceMap(); |
| 59 const content::SessionStorageNamespaceMap& session_storage_map2 = | 59 const content::SessionStorageNamespaceMap& session_storage_map2 = |
| 60 tab2->web_contents()->GetController().GetSessionStorageNamespaceMap(); | 60 tab2->GetController().GetSessionStorageNamespaceMap(); |
| 61 bool is_session_storage_the_same = | 61 bool is_session_storage_the_same = |
| 62 session_storage_map1.size() == session_storage_map2.size(); | 62 session_storage_map1.size() == session_storage_map2.size(); |
| 63 if (is_session_storage_the_same) { | 63 if (is_session_storage_the_same) { |
| 64 // The size is the same, so let's check that all entries match. | 64 // The size is the same, so let's check that all entries match. |
| 65 for (content::SessionStorageNamespaceMap::const_iterator | 65 for (content::SessionStorageNamespaceMap::const_iterator |
| 66 it1 = session_storage_map1.begin(), | 66 it1 = session_storage_map1.begin(), |
| 67 it2 = session_storage_map2.begin(); | 67 it2 = session_storage_map2.begin(); |
| 68 it1 != session_storage_map1.end() && it2 != session_storage_map2.end(); | 68 it1 != session_storage_map1.end() && it2 != session_storage_map2.end(); |
| 69 ++it1, ++it2) { | 69 ++it1, ++it2) { |
| 70 if (it1->first != it2->first || it1->second != it2->second) { | 70 if (it1->first != it2->first || it1->second != it2->second) { |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 83 if (normalizer == NULL || U_FAILURE(status)) | 83 if (normalizer == NULL || U_FAILURE(status)) |
| 84 return str; | 84 return str; |
| 85 icu::UnicodeString norm_str(normalizer->normalize( | 85 icu::UnicodeString norm_str(normalizer->normalize( |
| 86 icu::UnicodeString(FALSE, str.c_str(), str.size()), status)); | 86 icu::UnicodeString(FALSE, str.c_str(), str.size()), status)); |
| 87 if (U_FAILURE(status)) | 87 if (U_FAILURE(status)) |
| 88 return str; | 88 return str; |
| 89 return string16(norm_str.getBuffer(), norm_str.length()); | 89 return string16(norm_str.getBuffer(), norm_str.length()); |
| 90 } | 90 } |
| 91 | 91 |
| 92 bool NormalizeAndStripPrefix(string16* text, const string16& prefix) { | 92 bool NormalizeAndStripPrefix(string16* text, const string16& prefix) { |
| 93 const string16 norm_prefix = Normalize(prefix); | 93 string16 norm_prefix = Normalize(prefix); |
| 94 string16 norm_text = Normalize(*text); | 94 string16 norm_text = Normalize(*text); |
| 95 if (norm_prefix.size() <= norm_text.size() && | 95 if (norm_prefix.size() <= norm_text.size() && |
| 96 norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) { | 96 norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) { |
| 97 *text = norm_text.erase(0, norm_prefix.size()); | 97 *text = norm_text.erase(0, norm_prefix.size()); |
| 98 return true; | 98 return true; |
| 99 } | 99 } |
| 100 return false; | 100 return false; |
| 101 } | 101 } |
| 102 | 102 |
| 103 // For TOOLKIT_VIEWS, the top level widget is always focused. If the focus | 103 // For TOOLKIT_VIEWS, the top level widget is always focused. If the focus |
| 104 // change originated in views determine the child Widget from the view that is | 104 // change originated in views determine the child Widget from the view that is |
| 105 // being focused. | 105 // being focused. |
| 106 gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) { | 106 gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) { |
| 107 #if defined(TOOLKIT_VIEWS) | 107 #if defined(TOOLKIT_VIEWS) |
| 108 views::Widget* widget = view_gaining_focus ? | 108 views::Widget* widget = view_gaining_focus ? |
| 109 views::Widget::GetWidgetForNativeView(view_gaining_focus) : NULL; | 109 views::Widget::GetWidgetForNativeView(view_gaining_focus) : NULL; |
| 110 if (widget) { | 110 if (widget) { |
| 111 views::FocusManager* focus_manager = widget->GetFocusManager(); | 111 views::FocusManager* focus_manager = widget->GetFocusManager(); |
| 112 if (focus_manager && focus_manager->is_changing_focus() && | 112 if (focus_manager && focus_manager->is_changing_focus() && |
| 113 focus_manager->GetFocusedView() && | 113 focus_manager->GetFocusedView() && |
| 114 focus_manager->GetFocusedView()->GetWidget()) { | 114 focus_manager->GetFocusedView()->GetWidget()) |
| 115 return focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); | 115 return focus_manager->GetFocusedView()->GetWidget()->GetNativeView(); |
| 116 } | |
| 117 } | 116 } |
| 118 #endif | 117 #endif |
| 119 return view_gaining_focus; | 118 return view_gaining_focus; |
| 120 } | 119 } |
| 121 | 120 |
| 122 // Returns true if |view| is the top-level contents view or a child view in the | 121 // Returns true if |view| is the top-level contents view or a child view in the |
| 123 // view hierarchy of |contents|. | 122 // view hierarchy of |contents|. |
| 124 bool IsViewInContents(gfx::NativeView view, content::WebContents* contents) { | 123 bool IsViewInContents(gfx::NativeView view, content::WebContents* contents) { |
| 125 content::RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView(); | 124 content::RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView(); |
| 126 if (!view || !rwhv) | 125 if (!view || !rwhv) |
| (...skipping 15 matching lines...) Expand all Loading... | |
| 142 } | 141 } |
| 143 | 142 |
| 144 } // namespace | 143 } // namespace |
| 145 | 144 |
| 146 InstantController::InstantController(chrome::BrowserInstantController* browser, | 145 InstantController::InstantController(chrome::BrowserInstantController* browser, |
| 147 bool extended_enabled) | 146 bool extended_enabled) |
| 148 : browser_(browser), | 147 : browser_(browser), |
| 149 extended_enabled_(extended_enabled), | 148 extended_enabled_(extended_enabled), |
| 150 instant_enabled_(false), | 149 instant_enabled_(false), |
| 151 model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), | 150 model_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), |
| 151 last_omnibox_text_has_inline_autocompletion_(false), | |
| 152 last_verbatim_(false), | 152 last_verbatim_(false), |
| 153 last_transition_type_(content::PAGE_TRANSITION_LINK), | 153 last_transition_type_(content::PAGE_TRANSITION_LINK), |
| 154 last_match_was_search_(false), | 154 last_match_was_search_(false), |
| 155 is_omnibox_focused_(false) { | 155 is_omnibox_focused_(false), |
| 156 allow_preview_to_show_search_suggestions_(false) { | |
| 156 } | 157 } |
| 157 | 158 |
| 158 InstantController::~InstantController() { | 159 InstantController::~InstantController() { |
| 159 } | 160 } |
| 160 | 161 |
| 161 bool InstantController::Update(const AutocompleteMatch& match, | 162 bool InstantController::Update(const AutocompleteMatch& match, |
| 162 const string16& user_text, | 163 const string16& user_text, |
| 163 const string16& full_text, | 164 const string16& full_text, |
| 164 const bool verbatim, | 165 size_t selection_start, |
| 165 const bool user_input_in_progress, | 166 size_t selection_end, |
| 166 const bool omnibox_popup_is_open) { | 167 bool verbatim, |
| 168 bool user_input_in_progress, | |
| 169 bool omnibox_popup_is_open) { | |
| 167 if (!extended_enabled_ && !instant_enabled_) | 170 if (!extended_enabled_ && !instant_enabled_) |
| 168 return false; | 171 return false; |
| 169 | 172 |
| 170 DVLOG(1) << "Update: " << AutocompleteMatch::TypeToString(match.type) | 173 DVLOG(1) << "Update: " << AutocompleteMatch::TypeToString(match.type) |
| 171 << " user_text='" << user_text << "' full_text='" << full_text << "'" | 174 << " user_text='" << user_text << "' full_text='" << full_text << "'" |
| 172 << " verbatim=" << verbatim << " typing=" << user_input_in_progress | 175 << " selection_start=" << selection_start << " selection_end=" |
| 173 << " popup=" << omnibox_popup_is_open; | 176 << selection_end << " verbatim=" << verbatim << " typing=" |
| 177 << user_input_in_progress << " popup=" << omnibox_popup_is_open; | |
| 174 | 178 |
| 175 // If the popup is open, the user has to be typing. | 179 // If the popup is open, the user has to be typing. |
| 176 DCHECK(!omnibox_popup_is_open || user_input_in_progress); | 180 DCHECK(!omnibox_popup_is_open || user_input_in_progress); |
| 177 | 181 |
| 178 // If the popup is closed, there should be no inline autocompletion. | 182 // If the popup is closed, there should be no inline autocompletion. |
| 179 DCHECK(omnibox_popup_is_open || user_text == full_text) << user_text << "|" | 183 DCHECK(omnibox_popup_is_open || user_text.empty() || user_text == full_text) |
| 180 << full_text; | 184 << user_text << "|" << full_text; |
| 181 | |
| 182 // If there's inline autocompletion, the query has to be verbatim. | |
| 183 DCHECK(user_text == full_text || verbatim) << user_text << "|" << full_text; | |
| 184 | 185 |
| 185 // If there's no text in the omnibox, the user can't have typed any. | 186 // If there's no text in the omnibox, the user can't have typed any. |
| 186 DCHECK(!full_text.empty() || user_text.empty()) << user_text; | 187 DCHECK(!full_text.empty() || user_text.empty()) << user_text; |
| 187 | 188 |
| 188 // If the user isn't typing, and the popup is closed, there can't be any | 189 // If the user isn't typing, and the popup is closed, there can't be any |
| 189 // user-typed text. | 190 // user-typed text. |
| 190 DCHECK(user_input_in_progress || omnibox_popup_is_open || user_text.empty()) | 191 DCHECK(user_input_in_progress || omnibox_popup_is_open || user_text.empty()) |
| 191 << user_text; | 192 << user_text; |
| 192 | 193 |
| 193 // In non-extended mode, SearchModeChanged() is never called, so fake it. The | 194 // In non-extended mode, SearchModeChanged() is never called, so fake it. The |
| 194 // mode is set to "disallow suggestions" here, so that if one of the early | 195 // mode is set to "disallow suggestions" here, so that if one of the early |
| 195 // "return false" conditions is hit, suggestions will be disallowed. If the | 196 // "return false" conditions is hit, suggestions will be disallowed. If the |
| 196 // query is sent to the loader, the mode is set to "allow" further below. | 197 // query is sent to the loader, the mode is set to "allow" further below. |
| 197 if (!extended_enabled_) | 198 if (!extended_enabled_) |
| 198 search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | 199 search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; |
| 199 | 200 |
| 200 // If there's no active tab, the browser is closing. | 201 last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) && |
| 201 const TabContents* const active_tab = browser_->GetActiveTabContents(); | 202 !user_text.empty(); |
| 202 if (!active_tab) { | 203 |
| 203 Hide(true); | 204 if (!ResetLoaderForMatch(match)) { |
| 205 HideLoader(); | |
| 204 return false; | 206 return false; |
| 205 } | 207 } |
| 206 | 208 |
| 207 // Legend: OPIO == |omnibox_popup_is_open|, UIIP = |user_input_in_progress|. | |
| 208 // | |
| 209 // # OPIO UIIP full_text Notes | |
| 210 // - ---- ---- --------- ----- | |
| 211 // 1 no no blank } Navigation, or user hit Escape. |full_text| is | |
| 212 // 2 no no non-blank } blank if the page is NTP, non-blank otherwise. | |
| 213 // | |
| 214 // 3 no yes blank User backspaced away all omnibox text. | |
| 215 // | |
| 216 // 4 no yes non-blank User switched to a tab with a partial query. | |
| 217 // | |
| 218 // 5 yes no blank } Impossible. DCHECK()ed above. | |
| 219 // 6 yes no non-blank } | |
| 220 // | |
| 221 // 7 yes yes blank User typed a "?" into the omnibox. | |
| 222 // | |
| 223 // 8 yes yes non-blank User typed text into the omnibox. | |
| 224 // | |
| 225 // In non-extended mode, #1 to #7 call Hide(). #8 calls loader_->Update(). | |
| 226 // | |
| 227 // In extended mode, #2 and #4 call Hide(). #1 doesn't Hide() as the preview | |
| 228 // may be showing custom NTP content, but doesn't Update() either. #3 calls | |
| 229 // Hide() unless on the NTP _and_ sends a blank query; otherwise #3 and #7 | |
| 230 // don't Hide(), but send a blank query to Update(). #8 calls Update(). | |
| 231 | |
| 232 if (extended_enabled_) { | 209 if (extended_enabled_) { |
| 233 if (!omnibox_popup_is_open) { | 210 if (!omnibox_popup_is_open) { |
| 234 if (!full_text.empty() || | 211 if (!user_input_in_progress) { |
| 235 (user_input_in_progress && !search_mode_.is_origin_ntp())) { | 212 // If the user isn't typing and the omnibox popup is closed, it means a |
| 236 Hide(true); | 213 // regular navigation, tab-switch or the user hitting Escape. |
| 237 return false; | 214 if (instant_tab_) { |
| 215 // The user is on a search results page. It may be showing results for | |
| 216 // a partial query the user typed before they hit Escape. Send the | |
| 217 // omnibox text to the page to restore the original results. | |
| 218 // | |
| 219 // In a tab switch, |instant_tab_| won't have updated yet, so it may | |
| 220 // be pointing to the previous tab (which was a search results page). | |
| 221 // Ensure we don't send the omnibox text to a random webpage (the new | |
| 222 // tab), by comparing the old and new WebContents. | |
| 223 if (instant_tab_->contents() == browser_->GetActiveWebContents()) | |
| 224 instant_tab_->Submit(full_text); | |
| 225 } else if (!full_text.empty()) { | |
| 226 // If |full_text| is empty, the user is on the NTP. The preview may | |
| 227 // be showing custom NTP content; hide only if that's not the case. | |
| 228 HideLoader(); | |
| 229 } | |
| 230 } else if (full_text.empty()) { | |
| 231 // The user is typing, and backspaced away all omnibox text. Clear | |
| 232 // |last_omnibox_text_| so that we don't attempt to set suggestions. | |
| 233 last_omnibox_text_.clear(); | |
| 234 last_suggestion_ = InstantSuggestion(); | |
| 235 if (instant_tab_) { | |
| 236 // On a search results page, tell it to clear old results. | |
| 237 instant_tab_->Update(string16(), 0, 0, true); | |
| 238 } else if (search_mode_.is_origin_ntp()) { | |
| 239 // On the NTP, tell the preview to clear old results. Don't hide the | |
| 240 // preview so it can show a blank page or logo if it wants. | |
| 241 loader_->Update(string16(), 0, 0, true); | |
| 242 } else { | |
| 243 HideLoader(); | |
| 244 } | |
| 245 } else { | |
| 246 // The user switched to a tab with partial text already in the omnibox. | |
| 247 HideLoader(); | |
| 248 | |
| 249 // The new tab may or may not be a search results page; we don't know | |
| 250 // since SearchModeChanged() hasn't been called yet. If it later turns | |
| 251 // out to be, we should store |full_text| now, so that if the user hits | |
| 252 // Enter, we'll send the correct query to instant_tab_->Submit(). If the | |
| 253 // partial text is not a query (|last_match_was_search_| is false), we | |
| 254 // won't Submit(), so no need to worry about that. | |
| 255 last_omnibox_text_ = full_text; | |
| 256 last_suggestion_ = InstantSuggestion(); | |
| 238 } | 257 } |
| 239 if (!user_input_in_progress && full_text.empty()) | 258 return false; |
| 240 return false; | 259 } else if (full_text.empty()) { |
| 260 // The user typed a solitary "?". Same as the backspace case above. | |
| 261 last_omnibox_text_.clear(); | |
| 262 last_suggestion_ = InstantSuggestion(); | |
| 263 if (instant_tab_) | |
| 264 instant_tab_->Update(string16(), 0, 0, true); | |
| 265 else if (search_mode_.is_origin_ntp()) | |
| 266 loader_->Update(string16(), 0, 0, true); | |
| 267 else | |
| 268 HideLoader(); | |
| 269 return false; | |
| 241 } | 270 } |
| 242 } else if (!omnibox_popup_is_open || full_text.empty()) { | 271 } else if (!omnibox_popup_is_open || full_text.empty()) { |
| 243 // Update() can be called if the user clicks the preview while composing | 272 // In the non-extended case, hide the preview as long as the user isn't |
| 244 // text with an IME. If so, we should commit on mouse up, so don't Hide(). | 273 // actively typing a non-empty query. However, Update() can be called if the |
| 245 if (!GetPreviewContents() || !loader_->IsPointerDownFromActivate()) | 274 // user clicks the preview while composing text with an IME, so don't hide |
| 246 Hide(true); | 275 // if the mouse is down, since we'll commit on mouse up later. |
| 276 if (!loader_ || !loader_->is_pointer_down_from_activate()) | |
| 277 HideLoader(); | |
| 247 return false; | 278 return false; |
| 248 } | 279 } |
| 249 | 280 |
| 250 // Ensure we have a loader that can process this match. First, try to use the | 281 last_omnibox_text_has_inline_autocompletion_ = user_text != full_text; |
| 251 // TemplateURL of the |match|. If that's invalid, in non-extended mode, stop. | |
| 252 // In extended mode, try using the default search engine, but only when the | |
| 253 // match is for a URL (i.e., not some other kind of non-Instant search). | |
| 254 // A completely blank query shows up as a search, and we do want to allow | |
| 255 // that, hence the "!full_text.empty()" clause. | |
| 256 Profile* const profile = active_tab->profile(); | |
| 257 const bool match_is_search = AutocompleteMatch::IsSearchType(match.type); | |
| 258 if (!ResetLoader(match.GetTemplateURL(profile, false), active_tab) && | |
| 259 (!extended_enabled_ || (match_is_search && !full_text.empty()) || | |
| 260 !CreateDefaultLoader())) { | |
| 261 Hide(true); | |
| 262 return false; | |
| 263 } | |
| 264 | 282 |
| 265 // If the user continues typing the same query as the suggested text is | 283 // If the user continues typing the same query as the suggested text is |
| 266 // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER). | 284 // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER). |
| 267 bool reused_suggestion = false; | 285 bool reused_suggestion = false; |
| 268 if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER) { | 286 if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER && |
| 269 if (StartsWith(last_user_text_, user_text, false) && !user_text.empty()) { | 287 !last_omnibox_text_has_inline_autocompletion_) { |
| 288 if (StartsWith(last_omnibox_text_, full_text, false)) { | |
| 270 // The user is backspacing away characters. | 289 // The user is backspacing away characters. |
| 271 last_suggestion_.text.insert(0, last_user_text_, user_text.size(), | 290 last_suggestion_.text.insert(0, last_omnibox_text_, full_text.size(), |
| 272 last_user_text_.size() - user_text.size()); | 291 last_omnibox_text_.size() - full_text.size()); |
| 273 reused_suggestion = true; | 292 reused_suggestion = true; |
| 274 } else if (StartsWith(user_text, last_user_text_, false)) { | 293 } else if (StartsWith(full_text, last_omnibox_text_, false)) { |
| 275 // The user is typing forward. Normalize any added characters. | 294 // The user is typing forward. Normalize any added characters. |
| 276 reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text, | 295 reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text, |
| 277 string16(user_text, last_user_text_.size())); | 296 string16(full_text, last_omnibox_text_.size())); |
| 278 } | 297 } |
| 279 } | 298 } |
| 280 | |
| 281 last_user_text_ = user_text; | |
| 282 last_full_text_ = full_text; | |
| 283 last_verbatim_ = verbatim; | |
| 284 | |
| 285 if (!reused_suggestion) | 299 if (!reused_suggestion) |
| 286 last_suggestion_ = InstantSuggestion(); | 300 last_suggestion_ = InstantSuggestion(); |
| 287 | 301 |
| 302 last_omnibox_text_ = full_text; | |
| 303 | |
| 304 if (!extended_enabled_) { | |
| 305 // In non-extended mode, the query is verbatim if there's any selection | |
| 306 // (including inline autocompletion) or if the cursor is not at the end. | |
| 307 verbatim = verbatim || selection_start != selection_end || | |
| 308 selection_start != full_text.size(); | |
| 309 } | |
| 310 last_verbatim_ = verbatim; | |
| 311 | |
| 288 last_transition_type_ = match.transition; | 312 last_transition_type_ = match.transition; |
| 289 last_match_was_search_ = match_is_search; | |
| 290 url_for_history_ = match.destination_url; | 313 url_for_history_ = match.destination_url; |
| 291 | 314 |
| 292 // Store the first interaction time for use with latency histograms. | |
| 293 if (first_interaction_time_.is_null()) | |
| 294 first_interaction_time_ = base::Time::Now(); | |
| 295 | |
| 296 // Allow search suggestions. In extended mode, SearchModeChanged() will set | 315 // Allow search suggestions. In extended mode, SearchModeChanged() will set |
| 297 // this, but it's not called in non-extended mode, so fake it. | 316 // this, but it's not called in non-extended mode, so fake it. |
| 298 if (!extended_enabled_) | 317 if (!extended_enabled_) |
| 299 search_mode_.mode = chrome::search::Mode::MODE_SEARCH_SUGGESTIONS; | 318 search_mode_.mode = chrome::search::Mode::MODE_SEARCH_SUGGESTIONS; |
| 300 | 319 |
| 301 loader_->Update(extended_enabled_ ? user_text : full_text, verbatim); | 320 if (instant_tab_) { |
| 321 instant_tab_->Update(user_text, selection_start, selection_end, verbatim); | |
| 322 } else { | |
| 323 if (first_interaction_time_.is_null()) | |
| 324 first_interaction_time_ = base::Time::Now(); | |
| 325 allow_preview_to_show_search_suggestions_ = true; | |
| 326 loader_->Update(extended_enabled_ ? user_text : full_text, | |
| 327 selection_start, selection_end, verbatim); | |
| 328 } | |
| 302 | 329 |
| 303 content::NotificationService::current()->Notify( | 330 content::NotificationService::current()->Notify( |
| 304 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, | 331 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED, |
| 305 content::Source<InstantController>(this), | 332 content::Source<InstantController>(this), |
| 306 content::NotificationService::NoDetails()); | 333 content::NotificationService::NoDetails()); |
| 307 | 334 |
| 308 // We don't have new suggestions yet, but we can either reuse the existing | 335 // We don't have new suggestions yet, but we can either reuse the existing |
| 309 // suggestion or reset the existing "gray text". | 336 // suggestion or reset the existing "gray text". |
| 310 browser_->SetInstantSuggestion(last_suggestion_); | 337 browser_->SetInstantSuggestion(last_suggestion_); |
| 311 | 338 |
| 312 // Though we may have handled a URL match above, we return false here, so that | 339 // Though we may have handled a URL match above, we return false here, so that |
| 313 // omnibox prerendering can kick in. TODO(sreeram): Remove this (and always | 340 // omnibox prerendering can kick in. TODO(sreeram): Remove this (and always |
| 314 // return true) once we are able to commit URLs as well. | 341 // return true) once we are able to commit URLs as well. |
| 315 return match_is_search; | 342 return last_match_was_search_; |
| 316 } | 343 } |
| 317 | 344 |
| 318 // TODO(tonyg): This method only fires when the omnibox bounds change. It also | 345 // TODO(tonyg): This method only fires when the omnibox bounds change. It also |
| 319 // needs to fire when the preview bounds change (e.g.: open/close info bar). | 346 // needs to fire when the preview bounds change (e.g.: open/close info bar). |
| 320 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { | 347 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) { |
| 321 if (!extended_enabled_ && !instant_enabled_) | 348 if (!extended_enabled_ && !instant_enabled_) |
| 322 return; | 349 return; |
| 323 | 350 |
| 324 if (omnibox_bounds_ == bounds) | 351 if (omnibox_bounds_ == bounds) |
| 325 return; | 352 return; |
| 326 | 353 |
| 327 omnibox_bounds_ = bounds; | 354 omnibox_bounds_ = bounds; |
| 328 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { | 355 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { |
| 329 update_bounds_timer_.Stop(); | 356 update_bounds_timer_.Stop(); |
| 330 SendBoundsToPage(); | 357 SendBoundsToPage(); |
| 331 } else if (!update_bounds_timer_.IsRunning()) { | 358 } else if (!update_bounds_timer_.IsRunning()) { |
| 332 update_bounds_timer_.Start(FROM_HERE, | 359 update_bounds_timer_.Start(FROM_HERE, |
| 333 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, | 360 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, |
| 334 &InstantController::SendBoundsToPage); | 361 &InstantController::SendBoundsToPage); |
| 335 } | 362 } |
| 336 } | 363 } |
| 337 | 364 |
| 338 void InstantController::HandleAutocompleteResults( | 365 void InstantController::HandleAutocompleteResults( |
| 339 const std::vector<AutocompleteProvider*>& providers) { | 366 const std::vector<AutocompleteProvider*>& providers) { |
| 340 if (!extended_enabled_) | 367 if (!extended_enabled_) |
| 341 return; | 368 return; |
| 342 | 369 |
| 343 if (!GetPreviewContents()) | 370 if (!instant_tab_ && !loader_) |
| 344 return; | 371 return; |
| 345 | 372 |
| 346 DVLOG(1) << "AutocompleteResults:"; | 373 DVLOG(1) << "AutocompleteResults:"; |
| 347 std::vector<InstantAutocompleteResult> results; | 374 std::vector<InstantAutocompleteResult> results; |
| 348 for (ACProviders::const_iterator provider = providers.begin(); | 375 for (ACProviders::const_iterator provider = providers.begin(); |
| 349 provider != providers.end(); ++provider) { | 376 provider != providers.end(); ++provider) { |
| 350 for (ACMatches::const_iterator match = (*provider)->matches().begin(); | 377 for (ACMatches::const_iterator match = (*provider)->matches().begin(); |
| 351 match != (*provider)->matches().end(); ++match) { | 378 match != (*provider)->matches().end(); ++match) { |
| 352 InstantAutocompleteResult result; | 379 InstantAutocompleteResult result; |
| 353 result.provider = UTF8ToUTF16((*provider)->GetName()); | 380 result.provider = UTF8ToUTF16((*provider)->GetName()); |
| 354 result.type = UTF8ToUTF16(AutocompleteMatch::TypeToString(match->type)); | 381 result.type = UTF8ToUTF16(AutocompleteMatch::TypeToString(match->type)); |
| 355 result.description = match->description; | 382 result.description = match->description; |
| 356 result.destination_url = UTF8ToUTF16(match->destination_url.spec()); | 383 result.destination_url = UTF8ToUTF16(match->destination_url.spec()); |
| 357 result.relevance = match->relevance; | 384 result.relevance = match->relevance; |
| 358 DVLOG(1) << " " << result.relevance << " " << result.type << " " | 385 DVLOG(1) << " " << result.relevance << " " << result.type << " " |
| 359 << result.provider << " " << result.destination_url << " '" | 386 << result.provider << " " << result.destination_url << " '" |
| 360 << result.description << "'"; | 387 << result.description << "'"; |
| 361 results.push_back(result); | 388 results.push_back(result); |
| 362 } | 389 } |
| 363 } | 390 } |
| 364 | 391 |
| 365 loader_->SendAutocompleteResults(results); | 392 if (instant_tab_) |
| 393 instant_tab_->SendAutocompleteResults(results); | |
| 394 else | |
| 395 loader_->SendAutocompleteResults(results); | |
| 366 } | 396 } |
| 367 | 397 |
| 368 bool InstantController::OnUpOrDownKeyPressed(int count) { | 398 bool InstantController::OnUpOrDownKeyPressed(int count) { |
| 369 if (!extended_enabled_) | 399 if (!extended_enabled_) |
| 370 return false; | 400 return false; |
| 371 | 401 |
| 372 if (!GetPreviewContents()) | 402 if (!instant_tab_ && !loader_) |
| 373 return false; | 403 return false; |
| 374 | 404 |
| 375 loader_->OnUpOrDownKeyPressed(count); | 405 if (instant_tab_) |
| 406 instant_tab_->UpOrDownKeyPressed(count); | |
| 407 else | |
| 408 loader_->UpOrDownKeyPressed(count); | |
| 409 | |
| 376 return true; | 410 return true; |
| 377 } | 411 } |
| 378 | 412 |
| 379 TabContents* InstantController::GetPreviewContents() const { | 413 content::WebContents* InstantController::GetPreviewContents() const { |
| 380 return loader_ ? loader_->preview_contents() : NULL; | 414 return loader_ ? loader_->contents() : NULL; |
| 381 } | 415 } |
| 382 | 416 |
| 383 bool InstantController::IsCurrent() const { | 417 bool InstantController::IsCurrent() const { |
| 384 return model_.mode().is_search_suggestions() && last_match_was_search_; | 418 return model_.mode().is_search_suggestions() && last_match_was_search_; |
| 385 } | 419 } |
| 386 | 420 |
| 387 bool InstantController::CommitIfCurrent(InstantCommitType type) { | 421 bool InstantController::CommitIfCurrent(InstantCommitType type) { |
| 388 if (!extended_enabled_ && !instant_enabled_) | 422 if (!extended_enabled_ && !instant_enabled_) |
| 389 return false; | 423 return false; |
| 390 | 424 |
| 425 DVLOG(1) << "CommitIfCurrent: type=" << type << " last_omnibox_text_='" | |
| 426 << last_omnibox_text_ << "' last_match_was_search_=" | |
| 427 << last_match_was_search_ << " instant_tab_=" << instant_tab_; | |
| 428 | |
| 429 // If we are on an already committed search results page, send a submit event | |
| 430 // to the page, but otherwise, nothing else to do. | |
| 431 if (instant_tab_) { | |
| 432 if (last_match_was_search_ && type == INSTANT_COMMIT_PRESSED_ENTER) { | |
| 433 instant_tab_->Submit(last_omnibox_text_); | |
| 434 return true; | |
| 435 } | |
| 436 return false; | |
| 437 } | |
| 438 | |
| 391 if (!IsCurrent()) | 439 if (!IsCurrent()) |
| 392 return false; | 440 return false; |
| 393 | 441 |
| 394 DVLOG(1) << "CommitIfCurrent"; | 442 if (type == INSTANT_COMMIT_FOCUS_LOST) |
| 395 TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_); | 443 loader_->Cancel(last_omnibox_text_); |
| 444 else | |
| 445 loader_->Submit(last_omnibox_text_); | |
| 446 | |
| 447 content::WebContents* preview = loader_->ReleaseContents(); | |
| 396 | 448 |
| 397 if (extended_enabled_) { | 449 if (extended_enabled_) { |
| 398 // Consider what's happening: | 450 // Consider what's happening: |
| 399 // 1. The user has typed a query in the omnibox and committed it (either | 451 // 1. The user has typed a query in the omnibox and committed it (either |
| 400 // by pressing Enter or clicking on the preview). | 452 // by pressing Enter or clicking on the preview). |
| 401 // 2. We commit the preview to the tab strip, and tell the page. | 453 // 2. We commit the preview to the tab strip, and tell the page. |
| 402 // 3. The page will update the URL hash fragment with the query terms. | 454 // 3. The page will update the URL hash fragment with the query terms. |
| 403 // After steps 1 and 3, the omnibox will show the query terms. However, if | 455 // After steps 1 and 3, the omnibox will show the query terms. However, if |
| 404 // the URL we are committing at step 2 doesn't already have query terms, it | 456 // the URL we are committing at step 2 doesn't already have query terms, it |
| 405 // will flash for a brief moment as a plain URL. So, avoid that flicker by | 457 // will flash for a brief moment as a plain URL. So, avoid that flicker by |
| 406 // pretending that the plain URL is actually the typed query terms. | 458 // pretending that the plain URL is actually the typed query terms. |
| 407 // TODO(samarth,beaudoin): Instead of this hack, we should add a new field | 459 // TODO(samarth,beaudoin): Instead of this hack, we should add a new field |
| 408 // to NavigationEntry to keep track of what the correct query, if any, is. | 460 // to NavigationEntry to keep track of what the correct query, if any, is. |
| 409 content::NavigationEntry* entry = | 461 content::NavigationEntry* entry = |
| 410 preview->web_contents()->GetController().GetVisibleEntry(); | 462 preview->GetController().GetVisibleEntry(); |
| 411 std::string url = entry->GetVirtualURL().spec(); | 463 std::string url = entry->GetVirtualURL().spec(); |
| 412 if (!google_util::IsInstantExtendedAPIGoogleSearchUrl(url) && | 464 if (!google_util::IsInstantExtendedAPIGoogleSearchUrl(url) && |
| 413 google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN, | 465 google_util::IsGoogleDomainUrl(url, google_util::ALLOW_SUBDOMAIN, |
| 414 google_util::ALLOW_NON_STANDARD_PORTS)) { | 466 google_util::ALLOW_NON_STANDARD_PORTS)) { |
| 415 entry->SetVirtualURL(GURL( | 467 entry->SetVirtualURL(GURL( |
| 416 url + "#q=" + | 468 url + "#q=" + |
| 417 net::EscapeQueryParamValue(UTF16ToUTF8(last_full_text_), true))); | 469 net::EscapeQueryParamValue(UTF16ToUTF8(last_omnibox_text_), true))); |
| 418 chrome::search::SearchTabHelper::FromWebContents( | 470 chrome::search::SearchTabHelper::FromWebContents(preview)-> |
| 419 preview->web_contents())->NavigationEntryUpdated(); | 471 NavigationEntryUpdated(); |
| 420 } | 472 } |
| 421 } | 473 } |
| 422 | 474 |
| 423 // If the preview page has navigated since the last Update(), we need to add | 475 // If the preview page has navigated since the last Update(), we need to add |
| 424 // the navigation to history ourselves. Else, the page will navigate after | 476 // the navigation to history ourselves. Else, the page will navigate after |
| 425 // commit, and it will be added to history in the usual manner. | 477 // commit, and it will be added to history in the usual manner. |
| 426 const history::HistoryAddPageArgs& last_navigation = | 478 const history::HistoryAddPageArgs& last_navigation = |
| 427 loader_->last_navigation(); | 479 loader_->last_navigation(); |
| 428 if (!last_navigation.url.is_empty()) { | 480 if (!last_navigation.url.is_empty()) { |
| 429 content::NavigationEntry* entry = | 481 content::NavigationEntry* entry = preview->GetController().GetActiveEntry(); |
| 430 preview->web_contents()->GetController().GetActiveEntry(); | |
| 431 DCHECK_EQ(last_navigation.url, entry->GetURL()); | 482 DCHECK_EQ(last_navigation.url, entry->GetURL()); |
| 432 | 483 |
| 433 // Add the page to history. | 484 // Add the page to history. |
| 434 HistoryTabHelper* history_tab_helper = | 485 HistoryTabHelper* history_tab_helper = |
| 435 HistoryTabHelper::FromWebContents(preview->web_contents()); | 486 HistoryTabHelper::FromWebContents(preview); |
| 436 history_tab_helper->UpdateHistoryForNavigation(last_navigation); | 487 history_tab_helper->UpdateHistoryForNavigation(last_navigation); |
| 437 | 488 |
| 438 // Update the page title. | 489 // Update the page title. |
| 439 history_tab_helper->UpdateHistoryPageTitle(*entry); | 490 history_tab_helper->UpdateHistoryPageTitle(*entry); |
| 440 } | 491 } |
| 441 | 492 |
| 442 // Add a fake history entry with a non-Instant search URL, so that search | 493 // Add a fake history entry with a non-Instant search URL, so that search |
| 443 // terms extraction (for autocomplete history matches) works. | 494 // terms extraction (for autocomplete history matches) works. |
| 444 if (HistoryService* history = HistoryServiceFactory::GetForProfile( | 495 HistoryService* history = HistoryServiceFactory::GetForProfile( |
| 445 preview->profile(), Profile::EXPLICIT_ACCESS)) { | 496 Profile::FromBrowserContext(preview->GetBrowserContext()), |
| 497 Profile::EXPLICIT_ACCESS); | |
| 498 if (history) { | |
| 446 history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), | 499 history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(), |
| 447 history::RedirectList(), last_transition_type_, | 500 history::RedirectList(), last_transition_type_, |
| 448 history::SOURCE_BROWSED, false); | 501 history::SOURCE_BROWSED, false); |
| 449 } | 502 } |
| 450 | 503 |
| 451 preview->web_contents()->GetController().PruneAllButActive(); | 504 preview->GetController().PruneAllButActive(); |
| 452 | 505 |
| 453 if (type != INSTANT_COMMIT_PRESSED_ALT_ENTER) { | 506 if (type != INSTANT_COMMIT_PRESSED_ALT_ENTER) { |
| 454 const TabContents* active_tab = browser_->GetActiveTabContents(); | 507 content::WebContents* active_tab = browser_->GetActiveWebContents(); |
| 455 AddSessionStorageHistogram(extended_enabled_, active_tab, preview); | 508 AddSessionStorageHistogram(extended_enabled_, active_tab, preview); |
| 456 preview->web_contents()->GetController().CopyStateFromAndPrune( | 509 preview->GetController().CopyStateFromAndPrune( |
| 457 &active_tab->web_contents()->GetController()); | 510 &active_tab->GetController()); |
| 458 } | 511 } |
| 459 | 512 |
| 460 DeleteLoader(); | |
| 461 | |
| 462 // Browser takes ownership of the preview. | 513 // Browser takes ownership of the preview. |
| 463 browser_->CommitInstant(preview, type == INSTANT_COMMIT_PRESSED_ALT_ENTER); | 514 browser_->CommitInstant(preview, type == INSTANT_COMMIT_PRESSED_ALT_ENTER); |
| 464 | 515 |
| 465 content::NotificationService::current()->Notify( | 516 content::NotificationService::current()->Notify( |
| 466 chrome::NOTIFICATION_INSTANT_COMMITTED, | 517 chrome::NOTIFICATION_INSTANT_COMMITTED, |
| 467 content::Source<content::WebContents>(preview->web_contents()), | 518 content::Source<content::WebContents>(preview), |
| 468 content::NotificationService::NoDetails()); | 519 content::NotificationService::NoDetails()); |
| 469 | 520 |
| 521 // Hide explicitly. See comments in HideLoader() for why. | |
| 470 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 522 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); |
| 471 | 523 |
| 524 // Delay deletion as we could've gotten here from an InstantLoader method. | |
| 525 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | |
| 526 | |
| 472 // Try to create another loader immediately so that it is ready for the next | 527 // Try to create another loader immediately so that it is ready for the next |
| 473 // user interaction. | 528 // user interaction. |
| 474 CreateDefaultLoader(); | 529 CreateDefaultLoader(); |
| 475 | 530 |
| 476 return true; | 531 return true; |
| 477 } | 532 } |
| 478 | 533 |
| 479 void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) { | 534 void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) { |
| 480 DVLOG(1) << "OmniboxLostFocus"; | 535 DVLOG(1) << "OmniboxLostFocus"; |
| 481 is_omnibox_focused_ = false; | 536 is_omnibox_focused_ = false; |
| 482 | 537 |
| 483 if (!extended_enabled_ && !instant_enabled_) | 538 if (!extended_enabled_ && !instant_enabled_) |
| 484 return; | 539 return; |
| 485 | 540 |
| 486 // If the preview isn't showing search suggestions, nothing to do. The check | 541 // If the preview isn't showing search suggestions, nothing to do. The check |
| 487 // for GetPreviewContents() (which normally is redundant, given IsCurrent()) | 542 // for GetPreviewContents() (which normally is redundant, given IsCurrent()) |
| 488 // is to handle the case when we get here during a commit. | 543 // is to handle the case when we get here during a commit. |
| 489 if (!IsCurrent() || !GetPreviewContents()) { | 544 if (!IsCurrent() || !GetPreviewContents()) { |
| 490 OnStaleLoader(); | 545 OnStaleLoader(); |
| 491 return; | 546 return; |
| 492 } | 547 } |
| 493 | 548 |
| 494 #if defined(OS_MACOSX) | 549 #if defined(OS_MACOSX) |
| 495 if (!loader_->IsPointerDownFromActivate()) | 550 if (!loader_->is_pointer_down_from_activate()) |
| 496 Hide(true); | 551 HideLoader(); |
| 497 #else | 552 #else |
| 498 if (IsViewInContents(GetViewGainingFocus(view_gaining_focus), | 553 if (IsViewInContents(GetViewGainingFocus(view_gaining_focus), |
| 499 GetPreviewContents()->web_contents())) | 554 loader_->contents())) |
| 500 CommitIfCurrent(INSTANT_COMMIT_FOCUS_LOST); | 555 CommitIfCurrent(INSTANT_COMMIT_FOCUS_LOST); |
| 501 else | 556 else |
| 502 Hide(true); | 557 HideLoader(); |
| 503 #endif | 558 #endif |
| 504 } | 559 } |
| 505 | 560 |
| 506 void InstantController::OmniboxGotFocus() { | 561 void InstantController::OmniboxGotFocus() { |
| 507 DVLOG(1) << "OmniboxGotFocus"; | 562 DVLOG(1) << "OmniboxGotFocus"; |
| 508 is_omnibox_focused_ = true; | 563 is_omnibox_focused_ = true; |
| 509 | 564 |
| 510 if (!extended_enabled_ && !instant_enabled_) | 565 if (!extended_enabled_ && !instant_enabled_) |
| 511 return; | 566 return; |
| 512 | 567 |
| 513 if (!GetPreviewContents()) | 568 CreateDefaultLoader(); |
| 514 CreateDefaultLoader(); | |
| 515 } | 569 } |
| 516 | 570 |
| 517 void InstantController::SearchModeChanged( | 571 void InstantController::SearchModeChanged( |
| 518 const chrome::search::Mode& old_mode, | 572 const chrome::search::Mode& old_mode, |
| 519 const chrome::search::Mode& new_mode) { | 573 const chrome::search::Mode& new_mode) { |
| 520 if (!extended_enabled_) | 574 if (!extended_enabled_) |
| 521 return; | 575 return; |
| 522 | 576 |
| 523 DVLOG(1) << "SearchModeChanged: [origin:mode] " << old_mode.origin << ":" | 577 DVLOG(1) << "SearchModeChanged: [origin:mode] " << old_mode.origin << ":" |
| 524 << old_mode.mode << " to " << new_mode.origin << ":" | 578 << old_mode.mode << " to " << new_mode.origin << ":" |
| 525 << new_mode.mode; | 579 << new_mode.mode; |
| 580 | |
| 526 search_mode_ = new_mode; | 581 search_mode_ = new_mode; |
| 582 if (!new_mode.is_search_suggestions()) | |
| 583 HideLoader(); | |
| 527 | 584 |
| 528 if (new_mode.is_search_suggestions()) { | 585 if (loader_) |
| 529 // The preview is showing NTP content, but it's not appropriate anymore. | 586 loader_->SearchModeChanged(new_mode); |
| 530 if (model_.mode().is_ntp() && !new_mode.is_origin_ntp()) | |
| 531 Hide(false); | |
| 532 } else { | |
| 533 Hide(true); | |
| 534 } | |
| 535 | 587 |
| 536 if (GetPreviewContents()) | 588 ResetInstantTab(); |
| 537 loader_->SearchModeChanged(new_mode); | |
| 538 } | 589 } |
| 539 | 590 |
| 540 void InstantController::ActiveTabChanged() { | 591 void InstantController::ActiveTabChanged() { |
| 541 if (!extended_enabled_ && !instant_enabled_) | 592 if (!extended_enabled_ && !instant_enabled_) |
| 542 return; | 593 return; |
| 543 | 594 |
| 544 DVLOG(1) << "ActiveTabChanged"; | 595 DVLOG(1) << "ActiveTabChanged"; |
| 545 | 596 |
| 546 // By this time, SearchModeChanged() should've been called, so we only need to | 597 // When switching tabs, always hide the preview, except if it's showing NTP |
| 547 // handle the case when the search mode does NOT change, as in the case of | 598 // content, and the new tab is also an NTP. |
| 548 // going from search_suggestions to search_suggestions (i.e., partial queries | 599 if (!search_mode_.is_ntp() || !model_.mode().is_ntp()) |
| 549 // on both old and new tabs). | 600 HideLoader(); |
| 550 if (search_mode_.is_search_suggestions() && | 601 |
| 551 model_.mode().is_search_suggestions()) | 602 if (extended_enabled_) |
| 552 Hide(false); | 603 ResetInstantTab(); |
| 553 } | 604 } |
| 554 | 605 |
| 555 void InstantController::SetInstantEnabled(bool instant_enabled) { | 606 void InstantController::SetInstantEnabled(bool instant_enabled) { |
| 556 DVLOG(1) << "SetInstantEnabled: " << instant_enabled; | 607 DVLOG(1) << "SetInstantEnabled: " << instant_enabled; |
| 557 instant_enabled_ = instant_enabled; | 608 instant_enabled_ = instant_enabled; |
| 558 if (extended_enabled_) { | 609 HideInternal(); |
| 559 // Reset the loader whenever the Instant pref changes. | 610 loader_.reset(); |
| 560 DeleteLoader(); | 611 if (extended_enabled_ || instant_enabled_) |
| 561 CreateDefaultLoader(); | 612 CreateDefaultLoader(); |
| 562 } else if (!instant_enabled_) { | |
| 563 DeleteLoader(); | |
| 564 } | |
| 565 } | 613 } |
| 566 | 614 |
| 567 void InstantController::ThemeChanged(const ThemeBackgroundInfo& theme_info) { | 615 void InstantController::ThemeChanged(const ThemeBackgroundInfo& theme_info) { |
| 568 if (!extended_enabled_) | 616 if (!extended_enabled_) |
| 569 return; | 617 return; |
| 570 | 618 |
| 571 if (GetPreviewContents()) | 619 if (loader_) |
| 572 loader_->SendThemeBackgroundInfo(theme_info); | 620 loader_->SendThemeBackgroundInfo(theme_info); |
| 573 } | 621 } |
| 574 | 622 |
| 575 void InstantController::ThemeAreaHeightChanged(int height) { | 623 void InstantController::ThemeAreaHeightChanged(int height) { |
| 576 if (!extended_enabled_) | 624 if (!extended_enabled_) |
| 577 return; | 625 return; |
| 578 | 626 |
| 579 if (GetPreviewContents()) | 627 if (loader_) |
| 580 loader_->SendThemeAreaHeight(height); | 628 loader_->SendThemeAreaHeight(height); |
| 581 } | 629 } |
| 582 | 630 |
| 583 void InstantController::SetSuggestions( | 631 void InstantController::SetSuggestions( |
| 584 InstantLoader* loader, | 632 const content::WebContents* contents, |
| 585 const std::vector<InstantSuggestion>& suggestions) { | 633 const std::vector<InstantSuggestion>& suggestions) { |
| 586 DVLOG(1) << "SetSuggestions"; | 634 DVLOG(1) << "SetSuggestions"; |
| 587 if (loader_ != loader || !search_mode_.is_search_suggestions()) | 635 |
| 636 // Ignore if we are not currently accepting search suggestions. | |
| 637 if (!search_mode_.is_search_suggestions() || last_omnibox_text_.empty()) | |
| 588 return; | 638 return; |
| 589 | 639 |
| 640 // Ignore if the message is from an unexpected source. | |
| 641 if (instant_tab_) { | |
| 642 if (instant_tab_->contents() != contents) | |
| 643 return; | |
| 644 } else if (!loader_ || loader_->contents() != contents || | |
| 645 !allow_preview_to_show_search_suggestions_) { | |
| 646 return; | |
| 647 } | |
| 648 | |
| 590 InstantSuggestion suggestion; | 649 InstantSuggestion suggestion; |
| 591 if (!suggestions.empty()) | 650 if (!suggestions.empty()) |
| 592 suggestion = suggestions[0]; | 651 suggestion = suggestions[0]; |
| 593 | 652 |
| 594 if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) { | 653 if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) { |
| 595 // We don't get an Update() when changing the omnibox due to a REPLACE | 654 // We don't get an Update() when changing the omnibox due to a REPLACE |
| 596 // suggestion (so that we don't inadvertently cause the preview to change | 655 // suggestion (so that we don't inadvertently cause the preview to change |
| 597 // what it's showing, as the user arrows up/down through the page-provided | 656 // what it's showing, as the user arrows up/down through the page-provided |
| 598 // suggestions). So, update these state variables here. | 657 // suggestions). So, update these state variables here. |
| 599 last_full_text_ = suggestion.text; | 658 last_omnibox_text_ = suggestion.text; |
| 600 last_user_text_.clear(); | |
| 601 last_verbatim_ = true; | |
| 602 last_suggestion_ = InstantSuggestion(); | 659 last_suggestion_ = InstantSuggestion(); |
| 603 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; | 660 last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH; |
| 604 DVLOG(1) << "SetReplaceSuggestion: text='" << suggestion.text << "'" | 661 DVLOG(1) << "SetReplaceSuggestion: text='" << suggestion.text << "'" |
| 605 << " type=" << suggestion.type; | 662 << " type=" << suggestion.type; |
| 606 browser_->SetInstantSuggestion(suggestion); | 663 browser_->SetInstantSuggestion(suggestion); |
| 607 } else { | 664 } else { |
| 608 // Suggestion text should be a full URL for URL suggestions, or the | 665 // Suggestion text should be a full URL for URL suggestions, or the |
| 609 // completion of a query for query suggestions. | 666 // completion of a query for query suggestions. |
| 610 if (suggestion.type == INSTANT_SUGGESTION_URL) { | 667 if (suggestion.type == INSTANT_SUGGESTION_URL) { |
| 611 // If the suggestion is not a valid URL, perhaps it's something like | 668 // If the suggestion is not a valid URL, perhaps it's something like |
| 612 // "foo.com". Try prefixing "http://". If it still isn't valid, drop it. | 669 // "foo.com". Try prefixing "http://". If it still isn't valid, drop it. |
| 613 if (!GURL(suggestion.text).is_valid()) { | 670 if (!GURL(suggestion.text).is_valid()) { |
| 614 suggestion.text.insert(0, ASCIIToUTF16("http://")); | 671 suggestion.text.insert(0, ASCIIToUTF16("http://")); |
| 615 if (!GURL(suggestion.text).is_valid()) | 672 if (!GURL(suggestion.text).is_valid()) |
| 616 suggestion = InstantSuggestion(); | 673 suggestion = InstantSuggestion(); |
| 617 } | 674 } |
| 618 } else if (StartsWith(suggestion.text, last_user_text_, true)) { | 675 } else if (StartsWith(suggestion.text, last_omnibox_text_, true)) { |
| 619 // The user typed an exact prefix of the suggestion. | 676 // The user typed an exact prefix of the suggestion. |
| 620 suggestion.text.erase(0, last_user_text_.size()); | 677 suggestion.text.erase(0, last_omnibox_text_.size()); |
| 621 } else if (!NormalizeAndStripPrefix(&suggestion.text, last_user_text_)) { | 678 } else if (!NormalizeAndStripPrefix(&suggestion.text, last_omnibox_text_)) { |
| 622 // Unicode normalize and case-fold the user text and suggestion. If the | 679 // Unicode normalize and case-fold the user text and suggestion. If the |
| 623 // user text is a prefix, suggest the normalized, case-folded completion; | 680 // user text is a prefix, suggest the normalized, case-folded completion; |
| 624 // for instance, if the user types 'i' and the suggestion is 'INSTANT', | 681 // for instance, if the user types 'i' and the suggestion is 'INSTANT', |
| 625 // suggest 'nstant'. Otherwise, the user text really isn't a prefix, so | 682 // suggest 'nstant'. Otherwise, the user text really isn't a prefix, so |
| 626 // suggest nothing. | 683 // suggest nothing. |
| 627 suggestion = InstantSuggestion(); | 684 suggestion = InstantSuggestion(); |
| 628 } | 685 } |
| 629 | 686 |
| 630 // If the omnibox is blank, this suggestion is for an older query. Ignore. | |
| 631 if (last_user_text_.empty()) | |
| 632 suggestion = InstantSuggestion(); | |
| 633 | |
| 634 // Don't suggest gray text if there already was inline autocompletion. | 687 // Don't suggest gray text if there already was inline autocompletion. |
| 635 // http://crbug.com/162303 | 688 // http://crbug.com/162303 |
| 636 if (suggestion.behavior == INSTANT_COMPLETE_NEVER && | 689 if (suggestion.behavior == INSTANT_COMPLETE_NEVER && |
| 637 last_user_text_ != last_full_text_) | 690 last_omnibox_text_has_inline_autocompletion_) |
| 638 suggestion = InstantSuggestion(); | 691 suggestion = InstantSuggestion(); |
| 639 | 692 |
| 640 // Don't allow inline autocompletion if the query was verbatim. | 693 // Don't allow inline autocompletion if the query was verbatim. |
| 641 if (suggestion.behavior == INSTANT_COMPLETE_NOW && last_verbatim_) | 694 if (suggestion.behavior == INSTANT_COMPLETE_NOW && last_verbatim_) |
| 642 suggestion = InstantSuggestion(); | 695 suggestion = InstantSuggestion(); |
| 643 | 696 |
| 644 last_suggestion_ = suggestion; | 697 last_suggestion_ = suggestion; |
| 645 | 698 |
| 646 if (!suggestion.text.empty()) { | 699 if (!suggestion.text.empty()) { |
| 647 DVLOG(1) << "SetInstantSuggestion: text='" << suggestion.text << "'" | 700 DVLOG(1) << "SetInstantSuggestion: text='" << suggestion.text << "'" |
| 648 << " behavior=" << suggestion.behavior << " type=" | 701 << " behavior=" << suggestion.behavior << " type=" |
| 649 << suggestion.type; | 702 << suggestion.type; |
| 650 browser_->SetInstantSuggestion(suggestion); | 703 browser_->SetInstantSuggestion(suggestion); |
| 651 } | 704 } |
| 652 } | 705 } |
| 653 | 706 |
| 654 // Extended mode pages will show() when ready. | 707 // Extended mode pages will call ShowLoader() when they are ready. |
| 655 if (!extended_enabled_) | 708 if (!extended_enabled_) |
| 656 Show(INSTANT_SHOWN_QUERY_SUGGESTIONS, 100, INSTANT_SIZE_PERCENT); | 709 ShowLoader(INSTANT_SHOWN_QUERY_SUGGESTIONS, 100, INSTANT_SIZE_PERCENT); |
| 657 } | 710 } |
| 658 | 711 |
| 659 void InstantController::CommitInstantLoader(InstantLoader* loader) { | 712 void InstantController::InstantSupportDetermined( |
| 660 if (loader_ == loader) | 713 const content::WebContents* contents, |
| 661 CommitIfCurrent(INSTANT_COMMIT_FOCUS_LOST); | 714 bool supports_instant) { |
| 715 if (instant_tab_ && instant_tab_->contents() == contents) { | |
| 716 if (!supports_instant) | |
| 717 MessageLoop::current()->DeleteSoon(FROM_HERE, instant_tab_.release()); | |
| 718 return; | |
| 719 } | |
| 720 | |
| 721 if (loader_ && loader_->contents() == contents) { | |
| 722 if (supports_instant) { | |
| 723 blacklisted_urls_.erase(loader_->instant_url()); | |
| 724 } else { | |
| 725 ++blacklisted_urls_[loader_->instant_url()]; | |
| 726 HideInternal(); | |
| 727 delete loader_->ReleaseContents(); | |
| 728 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | |
| 729 CreateDefaultLoader(); | |
| 730 } | |
| 731 content::NotificationService::current()->Notify( | |
| 732 chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, | |
| 733 content::Source<InstantController>(this), | |
| 734 content::NotificationService::NoDetails()); | |
| 735 } | |
| 662 } | 736 } |
| 663 | 737 |
| 664 void InstantController::ShowInstantPreview(InstantLoader* loader, | 738 void InstantController::ShowInstantPreview(InstantShownReason reason, |
| 665 InstantShownReason reason, | |
| 666 int height, | 739 int height, |
| 667 InstantSizeUnits units) { | 740 InstantSizeUnits units) { |
| 668 if (loader_ == loader && extended_enabled_) | 741 if (extended_enabled_) |
| 669 Show(reason, height, units); | 742 ShowLoader(reason, height, units); |
| 670 } | 743 } |
| 671 | 744 |
| 672 void InstantController::InstantSupportDetermined(InstantLoader* loader, | 745 void InstantController::SwappedWebContents() { |
| 673 bool supports_instant) { | 746 model_.SetPreviewContents(GetPreviewContents()); |
| 674 if (supports_instant) { | |
| 675 blacklisted_urls_.erase(loader->instant_url()); | |
| 676 } else { | |
| 677 ++blacklisted_urls_[loader->instant_url()]; | |
| 678 if (loader_ == loader) | |
| 679 DeleteLoader(); | |
| 680 } | |
| 681 | |
| 682 content::NotificationService::current()->Notify( | |
| 683 chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED, | |
| 684 content::Source<InstantController>(this), | |
| 685 content::NotificationService::NoDetails()); | |
| 686 } | 747 } |
| 687 | 748 |
| 688 void InstantController::SwappedTabContents(InstantLoader* loader) { | 749 void InstantController::InstantLoaderContentsFocused() { |
| 689 if (loader_ == loader) | |
| 690 model_.SetPreviewContents(GetPreviewContents()); | |
| 691 } | |
| 692 | |
| 693 void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) { | |
| 694 #if defined(USE_AURA) | 750 #if defined(USE_AURA) |
| 695 // On aura the omnibox only receives a focus lost if we initiate the focus | 751 // On aura the omnibox only receives a focus lost if we initiate the focus |
| 696 // change. This does that. | 752 // change. This does that. |
| 697 if (loader_ == loader && !model_.mode().is_default()) | 753 if (!model_.mode().is_default()) |
| 698 browser_->InstantPreviewFocused(); | 754 browser_->InstantPreviewFocused(); |
| 699 #endif | 755 #endif |
| 700 } | 756 } |
| 701 | 757 |
| 702 bool InstantController::ResetLoader(const TemplateURL* template_url, | 758 bool InstantController::ResetLoader(const TemplateURL* template_url, |
| 703 const TabContents* active_tab) { | 759 const content::WebContents* active_tab) { |
| 704 std::string instant_url; | 760 std::string instant_url; |
| 705 if (!GetInstantURL(template_url, &instant_url)) | 761 if (!GetInstantURL(template_url, &instant_url)) |
| 706 return false; | 762 return false; |
| 707 | 763 |
| 708 if (GetPreviewContents() && loader_->instant_url() != instant_url) | 764 if (loader_ && loader_->instant_url() == instant_url) |
| 709 DeleteLoader(); | 765 return true; |
| 710 | 766 |
| 711 if (!GetPreviewContents()) { | 767 HideInternal(); |
| 712 loader_.reset(new InstantLoader(this, instant_url, active_tab)); | 768 loader_.reset(new InstantLoader(this, instant_url)); |
| 713 loader_->Init(); | 769 loader_->InitContents(active_tab); |
| 714 | 770 |
| 715 // Ensure the searchbox API has the correct initial state. | 771 // Ensure the searchbox API has the correct initial state. |
| 716 if (extended_enabled_) { | 772 if (extended_enabled_) { |
| 717 browser_->UpdateThemeInfoForPreview(); | 773 browser_->UpdateThemeInfoForPreview(); |
| 718 loader_->SetDisplayInstantResults(instant_enabled_); | 774 loader_->SetDisplayInstantResults(instant_enabled_); |
| 719 loader_->SearchModeChanged(search_mode_); | 775 loader_->SearchModeChanged(search_mode_); |
| 720 } | 776 } |
| 721 | 777 |
| 722 // Reset the loader timer. | 778 // Restart the stale loader timer. |
| 723 stale_loader_timer_.Start( | 779 stale_loader_timer_.Start(FROM_HERE, |
| 724 FROM_HERE, | 780 base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this, |
| 725 base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this, | 781 &InstantController::OnStaleLoader); |
| 726 &InstantController::OnStaleLoader); | |
| 727 } | |
| 728 | 782 |
| 729 return true; | 783 return true; |
| 730 } | 784 } |
| 731 | 785 |
| 732 bool InstantController::CreateDefaultLoader() { | 786 bool InstantController::CreateDefaultLoader() { |
| 733 // If there's no active tab, the browser is closing. | 787 // If there's no active tab, the browser is closing. |
| 734 const TabContents* active_tab = browser_->GetActiveTabContents(); | 788 const content::WebContents* active_tab = browser_->GetActiveWebContents(); |
| 735 if (!active_tab) | 789 if (!active_tab) |
| 736 return false; | 790 return false; |
| 737 | 791 |
| 738 const TemplateURL* template_url = | 792 const TemplateURL* template_url = TemplateURLServiceFactory::GetForProfile( |
| 739 TemplateURLServiceFactory::GetForProfile(active_tab->profile())-> | 793 Profile::FromBrowserContext(active_tab->GetBrowserContext()))-> |
| 740 GetDefaultSearchProvider(); | 794 GetDefaultSearchProvider(); |
| 741 | 795 |
| 742 return ResetLoader(template_url, active_tab); | 796 return ResetLoader(template_url, active_tab); |
| 743 } | 797 } |
| 744 | 798 |
| 745 void InstantController::OnStaleLoader() { | 799 void InstantController::OnStaleLoader() { |
| 746 // If the preview is showing or the omnibox has focus, don't delete the | 800 // If the preview is showing or the omnibox has focus, don't delete the |
| 747 // loader. It will get refreshed the next time the preview is hidden or the | 801 // loader. It will get refreshed the next time the preview is hidden or the |
| 748 // omnibox loses focus. | 802 // omnibox loses focus. |
| 749 if (!stale_loader_timer_.IsRunning() && !is_omnibox_focused_ && | 803 if (!stale_loader_timer_.IsRunning() && !is_omnibox_focused_ && |
| 750 model_.mode().is_default()) { | 804 model_.mode().is_default()) { |
| 751 DeleteLoader(); | 805 loader_.reset(); |
| 752 CreateDefaultLoader(); | 806 CreateDefaultLoader(); |
| 753 } | 807 } |
| 754 } | 808 } |
| 755 | 809 |
| 756 void InstantController::DeleteLoader() { | 810 void InstantController::ResetInstantTab() { |
| 757 // Clear all state, except |last_transition_type_| as it's used during commit. | 811 if (search_mode_.is_origin_search()) { |
| 758 last_user_text_.clear(); | 812 content::WebContents* active_tab = browser_->GetActiveWebContents(); |
| 759 last_full_text_.clear(); | 813 if (!instant_tab_ || active_tab != instant_tab_->contents()) { |
| 760 last_verbatim_ = false; | 814 instant_tab_.reset(new InstantTab(this, active_tab)); |
| 761 last_suggestion_ = InstantSuggestion(); | 815 instant_tab_->Init(); |
| 762 last_match_was_search_ = false; | 816 } |
| 817 | |
| 818 // Hide the |loader_| since we are now using |instant_tab_| instead. | |
| 819 HideLoader(); | |
| 820 } else { | |
| 821 instant_tab_.reset(); | |
| 822 } | |
| 823 } | |
| 824 | |
| 825 bool InstantController::ResetLoaderForMatch(const AutocompleteMatch& match) { | |
| 826 // If we are on a search results page, we'll use that instead of a loader. | |
| 827 // TODO(sreeram): If |instant_tab_|'s URL is not the same as the instant_url | |
| 828 // of |match|, we shouldn't use the committed tab. | |
| 829 if (instant_tab_) | |
| 830 return true; | |
| 831 | |
| 832 // If there's no active tab, the browser is closing. | |
| 833 const content::WebContents* active_tab = browser_->GetActiveWebContents(); | |
| 834 if (!active_tab) | |
| 835 return false; | |
| 836 | |
| 837 // Try to create a loader for the instant_url in the TemplateURL of |match|. | |
| 838 const TemplateURL* template_url = match.GetTemplateURL( | |
| 839 Profile::FromBrowserContext(active_tab->GetBrowserContext()), false); | |
| 840 if (ResetLoader(template_url, active_tab)) | |
| 841 return true; | |
| 842 | |
| 843 // In non-extended mode, stop if we couldn't get a loader for the |match|. | |
| 763 if (!extended_enabled_) | 844 if (!extended_enabled_) |
| 764 search_mode_.mode = chrome::search::Mode::MODE_DEFAULT; | 845 return false; |
| 765 omnibox_bounds_ = gfx::Rect(); | 846 |
| 766 last_omnibox_bounds_ = gfx::Rect(); | 847 // If the match is a query, it is for a non-Instant search engine; stop. |
| 767 update_bounds_timer_.Stop(); | 848 if (last_match_was_search_) |
| 768 stale_loader_timer_.Stop(); | 849 return false; |
| 769 url_for_history_ = GURL(); | 850 |
| 770 first_interaction_time_ = base::Time(); | 851 // The match is a URL, or a blank query. Try the default search engine. |
| 852 return CreateDefaultLoader(); | |
| 853 } | |
| 854 | |
| 855 void InstantController::HideLoader() { | |
| 856 HideInternal(); | |
| 857 OnStaleLoader(); | |
| 858 } | |
| 859 | |
| 860 void InstantController::HideInternal() { | |
| 861 DVLOG(1) << "Hide"; | |
| 862 | |
| 863 // If GetPreviewContents() returns NULL, either we're already in the desired | |
| 864 // MODE_DEFAULT state, or we're in the commit path. For the latter, don't | |
| 865 // change the state just yet; else we may hide the preview unnecessarily. | |
| 866 // Instead, the state will be set correctly after the commit is done. | |
| 771 if (GetPreviewContents()) { | 867 if (GetPreviewContents()) { |
| 772 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | 868 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); |
| 773 loader_->CleanupPreviewContents(); | 869 allow_preview_to_show_search_suggestions_ = false; |
| 870 | |
| 871 // Send a message asking the preview to clear out old results. | |
| 872 loader_->Update(string16(), 0, 0, true); | |
| 774 } | 873 } |
| 775 | 874 |
| 776 // Schedule the deletion for later, since we may have gotten here from a call | |
| 777 // within a |loader_| method (i.e., it's still on the stack). If we deleted | |
| 778 // the loader immediately, things would still be fine so long as the caller | |
| 779 // doesn't access any instance members after we return, but why rely on that? | |
| 780 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release()); | |
| 781 } | |
| 782 | |
| 783 void InstantController::Hide(bool clear_query) { | |
| 784 DVLOG(1) << "Hide: clear_query=" << clear_query; | |
| 785 | |
| 786 // The only time when the preview is not already in the desired MODE_DEFAULT | |
| 787 // state and GetPreviewContents() returns NULL is when we are in the commit | |
| 788 // path. In that case, don't change the state just yet; otherwise we may | |
| 789 // cause the preview to hide unnecessarily. Instead, the state will be set | |
| 790 // correctly after the commit is done. | |
| 791 if (GetPreviewContents()) | |
| 792 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
| 793 | |
| 794 // Clear the first interaction timestamp for later use. | 875 // Clear the first interaction timestamp for later use. |
| 795 first_interaction_time_ = base::Time(); | 876 first_interaction_time_ = base::Time(); |
| 796 | |
| 797 if (clear_query) { | |
| 798 if (GetPreviewContents() && !last_full_text_.empty()) | |
| 799 loader_->Update(string16(), true); | |
| 800 last_user_text_.clear(); | |
| 801 last_full_text_.clear(); | |
| 802 } | |
| 803 | |
| 804 OnStaleLoader(); | |
| 805 } | 877 } |
| 806 | 878 |
| 807 void InstantController::Show(InstantShownReason reason, | 879 void InstantController::ShowLoader(InstantShownReason reason, |
| 808 int height, | 880 int height, |
| 809 InstantSizeUnits units) { | 881 InstantSizeUnits units) { |
| 882 // If we are on a committed search results page, the |loader_| is not in use. | |
| 883 if (instant_tab_) | |
| 884 return; | |
| 885 | |
| 810 DVLOG(1) << "Show: reason=" << reason << " height=" << height << " units=" | 886 DVLOG(1) << "Show: reason=" << reason << " height=" << height << " units=" |
| 811 << units; | 887 << units; |
| 812 | 888 |
| 813 // Must be on NTP to show NTP content. | 889 // Must be on NTP to show NTP content. |
| 814 if (reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT && !search_mode_.is_ntp()) | 890 if (reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT && !search_mode_.is_ntp()) |
| 815 return; | 891 return; |
| 816 | 892 |
| 817 // Must have updated omnibox after most recent Hide() to show suggestions. | 893 // Must have updated omnibox after the last HideLoader() to show suggestions. |
| 818 if (reason == INSTANT_SHOWN_QUERY_SUGGESTIONS && | 894 if (reason == INSTANT_SHOWN_QUERY_SUGGESTIONS && |
| 819 !search_mode_.is_search_suggestions()) | 895 !allow_preview_to_show_search_suggestions_) |
|
Jered
2012/12/04 19:05:03
When is allow_preview_to_show_search_suggestions !
sreeram
2012/12/04 19:12:37
Whenever we call HideLoader() when the search_mode
| |
| 820 return; | 896 return; |
| 821 | 897 |
| 822 // If the preview is being shown because of the first set of suggestions to | 898 // The page is trying to hide itself. Hide explicitly (i.e., don't use |
| 823 // arrive for this query editing session, record a histogram value. | 899 // HideLoader()) so that it can change its mind. |
| 900 if (height == 0) { | |
| 901 model_.SetPreviewState(chrome::search::Mode(), 0, INSTANT_SIZE_PERCENT); | |
| 902 return; | |
| 903 } | |
| 904 | |
| 905 // If the preview is being shown for the first time since the user started | |
| 906 // typing, record a histogram value. | |
| 824 if (!first_interaction_time_.is_null() && model_.mode().is_default()) { | 907 if (!first_interaction_time_.is_null() && model_.mode().is_default()) { |
| 825 base::TimeDelta delta = base::Time::Now() - first_interaction_time_; | 908 base::TimeDelta delta = base::Time::Now() - first_interaction_time_; |
| 826 UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShow", delta); | 909 UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShow", delta); |
| 827 } | 910 } |
| 828 | 911 |
| 829 // Show at 100% height except in the following cases: | 912 // Show at 100% height except in the following cases: |
| 830 // - Instant is disabled. In this case the page should only ever show | 913 // - Instant is disabled. The page needs to be able to show only a dropdown. |
| 831 // a dropdown and we should always accept its height. | |
| 832 // - The page wants to hide (height=0). | |
| 833 // - The page wants to show custom NTP content. | 914 // - The page wants to show custom NTP content. |
| 834 // - The page is over a website other than search or an NTP, and is not | 915 // - The page is over a website other than search or an NTP, and is not |
| 835 // already showing at 100% height. | 916 // already showing at 100% height. |
| 836 const bool is_full_height = | 917 bool is_full_height = model_.height() == 100 && |
| 837 model_.height() == 100 && model_.height_units() == INSTANT_SIZE_PERCENT; | 918 model_.height_units() == INSTANT_SIZE_PERCENT; |
| 838 if (height == 0 || !instant_enabled_ || | 919 if (!instant_enabled_ || reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT || |
| 839 reason == INSTANT_SHOWN_CUSTOM_NTP_CONTENT || | |
| 840 (search_mode_.is_origin_default() && !is_full_height)) | 920 (search_mode_.is_origin_default() && !is_full_height)) |
| 841 model_.SetPreviewState(search_mode_, height, units); | 921 model_.SetPreviewState(search_mode_, height, units); |
| 842 else | 922 else |
| 843 model_.SetPreviewState(search_mode_, 100, INSTANT_SIZE_PERCENT); | 923 model_.SetPreviewState(search_mode_, 100, INSTANT_SIZE_PERCENT); |
| 844 } | 924 } |
| 845 | 925 |
| 846 void InstantController::SendBoundsToPage() { | 926 void InstantController::SendBoundsToPage() { |
| 847 if (last_omnibox_bounds_ == omnibox_bounds_ || | 927 if (last_omnibox_bounds_ == omnibox_bounds_ || !loader_ || |
| 848 !GetPreviewContents() || loader_->IsPointerDownFromActivate()) | 928 loader_->is_pointer_down_from_activate()) |
| 849 return; | 929 return; |
| 850 | 930 |
| 851 last_omnibox_bounds_ = omnibox_bounds_; | 931 last_omnibox_bounds_ = omnibox_bounds_; |
| 852 gfx::Rect preview_bounds = browser_->GetInstantBounds(); | 932 gfx::Rect preview_bounds = browser_->GetInstantBounds(); |
| 853 gfx::Rect intersection = gfx::IntersectRects(omnibox_bounds_, preview_bounds); | 933 gfx::Rect intersection = gfx::IntersectRects(omnibox_bounds_, preview_bounds); |
| 854 | 934 |
| 855 // Translate into window coordinates. | 935 // Translate into window coordinates. |
| 856 if (!intersection.IsEmpty()) { | 936 if (!intersection.IsEmpty()) { |
| 857 intersection.Offset(-preview_bounds.origin().x(), | 937 intersection.Offset(-preview_bounds.origin().x(), |
| 858 -preview_bounds.origin().y()); | 938 -preview_bounds.origin().y()); |
| (...skipping 33 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 892 | 972 |
| 893 // Extended mode should always use HTTPS. TODO(sreeram): This section can be | 973 // Extended mode should always use HTTPS. TODO(sreeram): This section can be |
| 894 // removed if TemplateURLs supported "https://{google:host}/..." instead of | 974 // removed if TemplateURLs supported "https://{google:host}/..." instead of |
| 895 // only supporting "{google:baseURL}...". | 975 // only supporting "{google:baseURL}...". |
| 896 if (extended_enabled_) { | 976 if (extended_enabled_) { |
| 897 GURL url_obj(*instant_url); | 977 GURL url_obj(*instant_url); |
| 898 if (!url_obj.is_valid()) | 978 if (!url_obj.is_valid()) |
| 899 return false; | 979 return false; |
| 900 | 980 |
| 901 if (!url_obj.SchemeIsSecure()) { | 981 if (!url_obj.SchemeIsSecure()) { |
| 902 const std::string new_scheme = "https"; | 982 std::string new_scheme = "https"; |
| 903 const std::string new_port = "443"; | 983 std::string new_port = "443"; |
| 904 GURL::Replacements secure; | 984 GURL::Replacements secure; |
| 905 secure.SetSchemeStr(new_scheme); | 985 secure.SetSchemeStr(new_scheme); |
| 906 secure.SetPortStr(new_port); | 986 secure.SetPortStr(new_port); |
| 907 url_obj = url_obj.ReplaceComponents(secure); | 987 url_obj = url_obj.ReplaceComponents(secure); |
| 908 | 988 |
| 909 if (!url_obj.is_valid()) | 989 if (!url_obj.is_valid()) |
| 910 return false; | 990 return false; |
| 911 | 991 |
| 912 *instant_url = url_obj.spec(); | 992 *instant_url = url_obj.spec(); |
| 913 } | 993 } |
| 914 } | 994 } |
| 915 | 995 |
| 916 std::map<std::string, int>::const_iterator iter = | 996 std::map<std::string, int>::const_iterator iter = |
| 917 blacklisted_urls_.find(*instant_url); | 997 blacklisted_urls_.find(*instant_url); |
| 918 if (iter != blacklisted_urls_.end() && | 998 if (iter != blacklisted_urls_.end() && |
| 919 iter->second > kMaxInstantSupportFailures) | 999 iter->second > kMaxInstantSupportFailures) |
| 920 return false; | 1000 return false; |
| 921 | 1001 |
| 922 return true; | 1002 return true; |
| 923 } | 1003 } |
| OLD | NEW |