OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 1 // Copyright 2013 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/ui/omnibox/omnibox_controller.h" | 5 #include "chrome/browser/ui/omnibox/omnibox_controller.h" |
6 | 6 |
7 #include "base/metrics/histogram.h" | 7 #include "base/metrics/histogram.h" |
8 #include "chrome/browser/autocomplete/autocomplete_classifier.h" | 8 #include "chrome/browser/autocomplete/autocomplete_classifier.h" |
9 #include "chrome/browser/autocomplete/autocomplete_match.h" | 9 #include "chrome/browser/autocomplete/autocomplete_match.h" |
10 #include "chrome/browser/autocomplete/search_provider.h" | 10 #include "chrome/browser/autocomplete/search_provider.h" |
11 #include "chrome/browser/net/predictor.h" | 11 #include "chrome/browser/net/predictor.h" |
12 #include "chrome/browser/predictors/autocomplete_action_predictor.h" | 12 #include "chrome/browser/predictors/autocomplete_action_predictor.h" |
13 #include "chrome/browser/prerender/prerender_field_trial.h" | 13 #include "chrome/browser/prerender/prerender_field_trial.h" |
14 #include "chrome/browser/prerender/prerender_manager.h" | 14 #include "chrome/browser/prerender/prerender_manager.h" |
15 #include "chrome/browser/prerender/prerender_manager_factory.h" | 15 #include "chrome/browser/prerender/prerender_manager_factory.h" |
16 #include "chrome/browser/profiles/profile.h" | 16 #include "chrome/browser/profiles/profile.h" |
17 #include "chrome/browser/search/search.h" | 17 #include "chrome/browser/search/search.h" |
| 18 #include "chrome/browser/search_engines/template_url_service.h" |
| 19 #include "chrome/browser/search_engines/template_url_service_factory.h" |
18 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h" | 20 #include "chrome/browser/ui/omnibox/omnibox_edit_controller.h" |
19 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h" | 21 #include "chrome/browser/ui/omnibox/omnibox_edit_model.h" |
20 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" | 22 #include "chrome/browser/ui/omnibox/omnibox_popup_model.h" |
21 #include "chrome/browser/ui/omnibox/omnibox_popup_view.h" | 23 #include "chrome/browser/ui/omnibox/omnibox_popup_view.h" |
22 #include "chrome/browser/ui/search/instant_controller.h" | 24 #include "chrome/browser/ui/search/instant_controller.h" |
23 #include "extensions/common/constants.h" | 25 #include "extensions/common/constants.h" |
24 #include "ui/gfx/rect.h" | 26 #include "ui/gfx/rect.h" |
25 | 27 |
26 using predictors::AutocompleteActionPredictor; | 28 using predictors::AutocompleteActionPredictor; |
27 | 29 |
| 30 namespace { |
| 31 |
| 32 string16 GetDefaultSearchProviderKeyword(Profile* profile) { |
| 33 TemplateURLService* template_url_service = |
| 34 TemplateURLServiceFactory::GetForProfile(profile); |
| 35 if (template_url_service) { |
| 36 TemplateURL* template_url = |
| 37 template_url_service->GetDefaultSearchProvider(); |
| 38 if (template_url) |
| 39 return template_url->keyword(); |
| 40 } |
| 41 return string16(); |
| 42 } |
| 43 |
| 44 } // namespace |
28 | 45 |
29 OmniboxController::OmniboxController(OmniboxEditModel* omnibox_edit_model, | 46 OmniboxController::OmniboxController(OmniboxEditModel* omnibox_edit_model, |
30 Profile* profile) | 47 Profile* profile) |
31 : omnibox_edit_model_(omnibox_edit_model), | 48 : omnibox_edit_model_(omnibox_edit_model), |
32 profile_(profile) { | 49 profile_(profile) { |
33 autocomplete_controller_.reset(new AutocompleteController(profile, this, | 50 autocomplete_controller_.reset(new AutocompleteController(profile, this, |
34 chrome::IsInstantExtendedAPIEnabled() ? | 51 chrome::IsInstantExtendedAPIEnabled() ? |
35 AutocompleteClassifier::kInstantExtendedOmniboxProviders : | 52 AutocompleteClassifier::kInstantExtendedOmniboxProviders : |
36 AutocompleteClassifier::kDefaultOmniboxProviders)); | 53 AutocompleteClassifier::kDefaultOmniboxProviders)); |
37 } | 54 } |
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
76 AutocompleteInput::ALL_MATCHES)); | 93 AutocompleteInput::ALL_MATCHES)); |
77 } | 94 } |
78 | 95 |
79 void OmniboxController::OnResultChanged(bool default_match_changed) { | 96 void OmniboxController::OnResultChanged(bool default_match_changed) { |
80 // TODO(beaudoin): There should be no need to access the popup when using | 97 // TODO(beaudoin): There should be no need to access the popup when using |
81 // instant extended, remove this reference. | 98 // instant extended, remove this reference. |
82 const bool was_open = popup_->IsOpen(); | 99 const bool was_open = popup_->IsOpen(); |
83 if (default_match_changed) { | 100 if (default_match_changed) { |
84 // The default match has changed, we need to let the OmniboxEditModel know | 101 // The default match has changed, we need to let the OmniboxEditModel know |
85 // about new inline autocomplete text (blue highlight). | 102 // about new inline autocomplete text (blue highlight). |
86 string16 inline_autocomplete_text; | |
87 string16 keyword; | |
88 bool is_keyword_hint = false; | |
89 const AutocompleteResult& result = this->result(); | 103 const AutocompleteResult& result = this->result(); |
90 const AutocompleteResult::const_iterator match(result.default_match()); | 104 const AutocompleteResult::const_iterator match(result.default_match()); |
91 if (match != result.end()) { | 105 if (match != result.end()) { |
92 if ((match->inline_autocomplete_offset != string16::npos) && | 106 current_match_ = *match; |
93 (match->inline_autocomplete_offset < | 107 // TODO(beaudoin): This code could be made simpler if AutocompleteMatch |
94 match->fill_into_edit.length())) { | 108 // kept an |inline_autocompletion| instead of a |
95 inline_autocomplete_text = | 109 // |inline_autocomplete_offset|. |
96 match->fill_into_edit.substr(match->inline_autocomplete_offset); | 110 // The |fill_into_edit| we get may not match what we have in the view at |
| 111 // that time. We're only interested in the inline_autocomplete part, so |
| 112 // update this here. |
| 113 current_match_.fill_into_edit = omnibox_edit_model_->user_text(); |
| 114 if (match->inline_autocomplete_offset < match->fill_into_edit.length()) { |
| 115 current_match_.inline_autocomplete_offset = |
| 116 current_match_.fill_into_edit.length(); |
| 117 current_match_.fill_into_edit += match->fill_into_edit.substr( |
| 118 match->inline_autocomplete_offset); |
| 119 } else { |
| 120 current_match_.inline_autocomplete_offset = string16::npos; |
97 } | 121 } |
98 | 122 |
99 if (!prerender::IsOmniboxEnabled(profile_)) | 123 if (!prerender::IsOmniboxEnabled(profile_)) |
100 DoPreconnect(*match); | 124 DoPreconnect(*match); |
101 | 125 omnibox_edit_model_->OnCurrentMatchChanged(false); |
102 // We could prefetch the alternate nav URL, if any, but because there | 126 } else { |
103 // can be many of these as a user types an initial series of characters, | 127 InvalidateCurrentMatch(); |
104 // the OS DNS cache could suffer eviction problems for minimal gain. | 128 popup_->OnResultChanged(); |
105 | 129 omnibox_edit_model_->OnPopupDataChanged(string16(), NULL, string16(), |
106 match->GetKeywordUIState(profile_, &keyword, &is_keyword_hint); | 130 false); |
107 } | 131 } |
108 | |
109 popup_->OnResultChanged(); | |
110 omnibox_edit_model_->OnPopupDataChanged(inline_autocomplete_text, NULL, | |
111 keyword, is_keyword_hint); | |
112 } else { | 132 } else { |
113 popup_->OnResultChanged(); | 133 popup_->OnResultChanged(); |
114 } | 134 } |
115 | 135 |
| 136 // TODO(beaudoin): This may no longer be needed now that instant classic is |
| 137 // gone. |
116 if (popup_->IsOpen()) { | 138 if (popup_->IsOpen()) { |
117 // The popup size may have changed, let instant know. | 139 // The popup size may have changed, let instant know. |
118 OnPopupBoundsChanged(popup_->view()->GetTargetBounds()); | 140 OnPopupBoundsChanged(popup_->view()->GetTargetBounds()); |
119 | 141 |
120 #if defined(HTML_INSTANT_EXTENDED_POPUP) | 142 #if defined(HTML_INSTANT_EXTENDED_POPUP) |
121 InstantController* instant_controller = GetInstantController(); | 143 InstantController* instant_controller = GetInstantController(); |
122 if (instant_controller && !omnibox_edit_model_->in_revert()) { | 144 if (instant_controller && !omnibox_edit_model_->in_revert()) { |
123 instant_controller->HandleAutocompleteResults( | 145 instant_controller->HandleAutocompleteResults( |
124 *autocomplete_controller_->providers(), | 146 *autocomplete_controller_->providers(), |
125 autocomplete_controller_->result()); | 147 autocomplete_controller_->result()); |
(...skipping 29 matching lines...) Expand all Loading... |
155 autocomplete_controller_->input().type(), &full_text); | 177 autocomplete_controller_->input().type(), &full_text); |
156 return instant_controller->Update( | 178 return instant_controller->Update( |
157 match, user_text, full_text, selection_start, selection_end, | 179 match, user_text, full_text, selection_start, selection_end, |
158 UseVerbatimInstant(just_deleted_text), user_input_in_progress, | 180 UseVerbatimInstant(just_deleted_text), user_input_in_progress, |
159 popup_->IsOpen(), in_escape_handler, keyword_is_selected); | 181 popup_->IsOpen(), in_escape_handler, keyword_is_selected); |
160 #else | 182 #else |
161 return false; | 183 return false; |
162 #endif | 184 #endif |
163 } | 185 } |
164 | 186 |
| 187 void OmniboxController::FinalizeInstantQuery( |
| 188 const string16& input_text, |
| 189 const InstantSuggestion& suggestion) { |
| 190 // Should only get called for the HTML popup. |
| 191 #if defined(HTML_INSTANT_EXTENDED_POPUP) |
| 192 if (!popup_model()->result().empty()) { |
| 193 // We need to finalize the instant query in all cases where the |
| 194 // |popup_model| holds some result. It is not enough to check whether the |
| 195 // popup is open, since when an IME is active the popup may be closed while |
| 196 // |popup_model| contains a non-empty result. |
| 197 SearchProvider* search_provider = |
| 198 autocomplete_controller_->search_provider(); |
| 199 // There may be no providers during testing; guard against that. |
| 200 if (search_provider) |
| 201 search_provider->FinalizeInstantQuery(input_text, suggestion); |
| 202 } |
| 203 #endif |
| 204 } |
| 205 |
| 206 void OmniboxController::SetInstantSuggestion( |
| 207 const InstantSuggestion& suggestion) { |
| 208 // Should only get called for the HTML popup. |
| 209 #if defined(HTML_INSTANT_EXTENDED_POPUP) |
| 210 switch (suggestion.behavior) { |
| 211 case INSTANT_COMPLETE_NOW: |
| 212 // Set blue suggestion text. |
| 213 // TODO(beaudoin): This currently goes to the SearchProvider. Instead we |
| 214 // should just create a valid current_match_ and call |
| 215 // omnibox_edit_model_->OnCurrentMatchChanged. This way we can get rid of |
| 216 // FinalizeInstantQuery entirely. |
| 217 if (!suggestion.text.empty()) |
| 218 FinalizeInstantQuery(omnibox_edit_model_->GetViewText(), suggestion); |
| 219 return; |
| 220 |
| 221 case INSTANT_COMPLETE_NEVER: { |
| 222 DCHECK_EQ(INSTANT_SUGGESTION_SEARCH, suggestion.type); |
| 223 |
| 224 // Set gray suggestion text. |
| 225 // Remove "?" if we're in forced query mode. |
| 226 gray_suggestion_ = suggestion.text; |
| 227 |
| 228 // TODO(beaudoin): The following should no longer be needed once the |
| 229 // instant suggestion no longer goes through the search provider. |
| 230 SearchProvider* search_provider = |
| 231 autocomplete_controller_->search_provider(); |
| 232 if (search_provider) |
| 233 search_provider->ClearInstantSuggestion(); |
| 234 |
| 235 omnibox_edit_model_->OnGrayTextChanged(); |
| 236 return; |
| 237 } |
| 238 |
| 239 case INSTANT_COMPLETE_REPLACE: |
| 240 // Replace the entire omnibox text by the suggestion the user just arrowed |
| 241 // to. |
| 242 CreateAndSetInstantMatch(suggestion.text, suggestion.text, |
| 243 suggestion.type == INSTANT_SUGGESTION_SEARCH ? |
| 244 AutocompleteMatchType::SEARCH_SUGGEST : |
| 245 AutocompleteMatchType::URL_WHAT_YOU_TYPED); |
| 246 |
| 247 omnibox_edit_model_->OnCurrentMatchChanged(true); |
| 248 return; |
| 249 } |
| 250 #endif |
| 251 } |
| 252 |
| 253 void OmniboxController::InvalidateCurrentMatch() { |
| 254 current_match_ = AutocompleteMatch(); |
| 255 } |
| 256 |
| 257 const AutocompleteMatch& OmniboxController::CurrentMatch( |
| 258 GURL* alternate_nav_url) const { |
| 259 if (alternate_nav_url && current_match_.destination_url.is_valid()) { |
| 260 *alternate_nav_url = AutocompleteResult::ComputeAlternateNavUrl( |
| 261 autocomplete_controller_->input(), current_match_); |
| 262 } |
| 263 |
| 264 return current_match_; |
| 265 } |
| 266 |
| 267 |
165 void OmniboxController::ClearPopupKeywordMode() const { | 268 void OmniboxController::ClearPopupKeywordMode() const { |
166 if (popup_->IsOpen() && | 269 if (popup_->IsOpen() && |
167 popup_->selected_line_state() == OmniboxPopupModel::KEYWORD) | 270 popup_->selected_line_state() == OmniboxPopupModel::KEYWORD) |
168 popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL); | 271 popup_->SetSelectedLineState(OmniboxPopupModel::NORMAL); |
169 } | 272 } |
170 | 273 |
171 void OmniboxController::DoPreconnect(const AutocompleteMatch& match) { | 274 void OmniboxController::DoPreconnect(const AutocompleteMatch& match) { |
172 if (!match.destination_url.SchemeIs(extensions::kExtensionScheme)) { | 275 if (!match.destination_url.SchemeIs(extensions::kExtensionScheme)) { |
173 // Warm up DNS Prefetch cache, or preconnect to a search service. | 276 // Warm up DNS Prefetch cache, or preconnect to a search service. |
174 UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type, | 277 UMA_HISTOGRAM_ENUMERATION("Autocomplete.MatchType", match.type, |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
206 // We send the caret position to Instant (so it can determine #1 itself), and | 309 // We send the caret position to Instant (so it can determine #1 itself), and |
207 // we use a separated widget for displaying the Instant suggest (so it doesn't | 310 // we use a separated widget for displaying the Instant suggest (so it doesn't |
208 // interfere with #2). So, we don't need to care about the value of | 311 // interfere with #2). So, we don't need to care about the value of |
209 // input.prevent_inline_autocomplete() here. | 312 // input.prevent_inline_autocomplete() here. |
210 return just_deleted_text || popup_->selected_line() != 0; | 313 return just_deleted_text || popup_->selected_line() != 0; |
211 } | 314 } |
212 | 315 |
213 InstantController* OmniboxController::GetInstantController() const { | 316 InstantController* OmniboxController::GetInstantController() const { |
214 return omnibox_edit_model_->GetInstantController(); | 317 return omnibox_edit_model_->GetInstantController(); |
215 } | 318 } |
| 319 |
| 320 void OmniboxController::CreateAndSetInstantMatch( |
| 321 string16 query_string, |
| 322 string16 input_text, |
| 323 AutocompleteMatchType::Type match_type) { |
| 324 string16 keyword = GetDefaultSearchProviderKeyword(profile_); |
| 325 if (keyword.empty()) |
| 326 return; // CreateSearchSuggestion needs a keyword. |
| 327 |
| 328 current_match_ = SearchProvider::CreateSearchSuggestion( |
| 329 profile_, NULL, AutocompleteInput(), query_string, input_text, 0, |
| 330 match_type, 0, false, keyword, -1); |
| 331 } |
OLD | NEW |