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/autocomplete/search_provider.h" | 5 #include "chrome/browser/autocomplete/search_provider.h" |
| 6 | 6 |
| 7 #include <algorithm> | 7 #include <algorithm> |
| 8 #include <cmath> | 8 #include <cmath> |
| 9 | 9 |
| 10 #include "base/auto_reset.h" | 10 #include "base/auto_reset.h" |
| (...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 117 | 117 |
| 118 // SearchProvider ------------------------------------------------------------- | 118 // SearchProvider ------------------------------------------------------------- |
| 119 | 119 |
| 120 // static | 120 // static |
| 121 const int SearchProvider::kDefaultProviderURLFetcherID = 1; | 121 const int SearchProvider::kDefaultProviderURLFetcherID = 1; |
| 122 // static | 122 // static |
| 123 const int SearchProvider::kKeywordProviderURLFetcherID = 2; | 123 const int SearchProvider::kKeywordProviderURLFetcherID = 2; |
| 124 // static | 124 // static |
| 125 int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs = 100; | 125 int SearchProvider::kMinimumTimeBetweenSuggestQueriesMs = 100; |
| 126 | 126 |
| 127 // static | |
| 128 SearchProvider::Providers SearchProvider::GetProviders( | |
| 129 Profile* profile, | |
| 130 const AutocompleteInput& input, | |
| 131 AutocompleteInput* keyword_input) { | |
| 132 TemplateURLService* model = TemplateURLServiceFactory::GetForProfile(profile); | |
| 133 Providers providers(model); | |
| 134 | |
| 135 const TemplateURL* keyword_provider = NULL; | |
| 136 if (keyword_input) { | |
| 137 keyword_provider = KeywordProvider::GetSubstitutingTemplateURLForInput( | |
| 138 model, keyword_input); | |
| 139 if (keyword_provider == NULL) | |
| 140 keyword_input->Clear(); | |
| 141 else if (keyword_input->text().empty()) | |
| 142 keyword_provider = NULL; // No text after the keyword. | |
| 143 } | |
| 144 | |
| 145 const TemplateURL* default_provider = model->GetDefaultSearchProvider(); | |
| 146 if (default_provider && !default_provider->SupportsReplacement()) | |
| 147 default_provider = NULL; | |
| 148 | |
| 149 if (keyword_provider == default_provider) | |
| 150 default_provider = NULL; // No use in querying the same provider twice. | |
| 151 | |
| 152 providers.set( | |
| 153 default_provider ? default_provider->keyword() : string16(), | |
| 154 keyword_provider ? keyword_provider->keyword() : string16()); | |
| 155 | |
| 156 return providers; | |
| 157 } | |
| 158 | |
| 159 // static | |
| 160 AutocompleteMatch SearchProvider::CreateSearchSuggestion( | |
| 161 Profile* profile, | |
| 162 AutocompleteProvider* autocomplete_provider, | |
| 163 const Providers& search_providers, | |
| 164 const AutocompleteInput& input, | |
| 165 const string16& query_string, | |
| 166 const string16& input_text, | |
| 167 int relevance, | |
| 168 AutocompleteMatch::Type type, | |
| 169 int accepted_suggestion, | |
| 170 bool is_keyword) { | |
| 171 AutocompleteMatch match(autocomplete_provider, relevance, false, type); | |
| 172 | |
| 173 // Bail out now if we don't actually have a valid provider. | |
| 174 match.keyword = is_keyword ? | |
|
Peter Kasting
2013/04/27 01:21:21
The only reason this function needs |search_provid
sreeram
2013/04/27 01:25:54
But I do want to be able to support keyword mode.
Peter Kasting
2013/04/27 01:37:48
I would say, let's worry about that when we get th
| |
| 175 search_providers.keyword_provider() : search_providers.default_provider(); | |
| 176 const TemplateURL* provider_url = match.GetTemplateURL(profile, false); | |
| 177 if (provider_url == NULL) | |
| 178 return match; | |
| 179 | |
| 180 match.contents.assign(query_string); | |
| 181 // We do intra-string highlighting for suggestions - the suggested segment | |
| 182 // will be highlighted, e.g. for input_text = "you" the suggestion may be | |
| 183 // "youtube", so we'll bold the "tube" section: you*tube*. | |
| 184 if (input_text != query_string) { | |
| 185 size_t input_position = match.contents.find(input_text); | |
| 186 if (input_position == string16::npos) { | |
| 187 // The input text is not a substring of the query string, e.g. input | |
| 188 // text is "slasdot" and the query string is "slashdot", so we bold the | |
| 189 // whole thing. | |
| 190 match.contents_class.push_back( | |
| 191 ACMatchClassification(0, ACMatchClassification::MATCH)); | |
| 192 } else { | |
| 193 // TODO(beng): ACMatchClassification::MATCH now seems to just mean | |
| 194 // "bold" this. Consider modifying the terminology. | |
| 195 // We don't iterate over the string here annotating all matches because | |
| 196 // it looks odd to have every occurrence of a substring that may be as | |
| 197 // short as a single character highlighted in a query suggestion result, | |
| 198 // e.g. for input text "s" and query string "southwest airlines", it | |
| 199 // looks odd if both the first and last s are highlighted. | |
| 200 if (input_position != 0) { | |
| 201 match.contents_class.push_back( | |
| 202 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 203 } | |
| 204 match.contents_class.push_back( | |
| 205 ACMatchClassification(input_position, ACMatchClassification::DIM)); | |
| 206 size_t next_fragment_position = input_position + input_text.length(); | |
| 207 if (next_fragment_position < query_string.length()) { | |
| 208 match.contents_class.push_back( | |
| 209 ACMatchClassification(next_fragment_position, | |
| 210 ACMatchClassification::NONE)); | |
| 211 } | |
| 212 } | |
| 213 } else { | |
| 214 // Otherwise, we're dealing with the "default search" result which has no | |
| 215 // completion. | |
| 216 match.contents_class.push_back( | |
| 217 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 218 } | |
| 219 | |
| 220 // When the user forced a query, we need to make sure all the fill_into_edit | |
| 221 // values preserve that property. Otherwise, if the user starts editing a | |
| 222 // suggestion, non-Search results will suddenly appear. | |
| 223 if (input.type() == AutocompleteInput::FORCED_QUERY) | |
| 224 match.fill_into_edit.assign(ASCIIToUTF16("?")); | |
| 225 if (is_keyword) | |
| 226 match.fill_into_edit.append(match.keyword + char16(' ')); | |
| 227 if (!input.prevent_inline_autocomplete() && | |
| 228 StartsWith(query_string, input_text, false)) { | |
| 229 match.inline_autocomplete_offset = | |
| 230 match.fill_into_edit.length() + input_text.length(); | |
| 231 } | |
| 232 match.fill_into_edit.append(query_string); | |
| 233 | |
| 234 const TemplateURLRef& search_url = provider_url->url_ref(); | |
| 235 DCHECK(search_url.SupportsReplacement()); | |
| 236 match.search_terms_args.reset( | |
| 237 new TemplateURLRef::SearchTermsArgs(query_string)); | |
| 238 match.search_terms_args->original_query = input_text; | |
| 239 match.search_terms_args->accepted_suggestion = accepted_suggestion; | |
| 240 // This is the destination URL sans assisted query stats. This must be set | |
| 241 // so the AutocompleteController can properly de-dupe; the controller will | |
| 242 // eventually overwrite it before it reaches the user. | |
| 243 match.destination_url = | |
| 244 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
| 245 | |
| 246 // Search results don't look like URLs. | |
| 247 match.transition = is_keyword ? | |
| 248 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED; | |
| 249 | |
| 250 return match; | |
| 251 } | |
| 252 | |
| 127 SearchProvider::SearchProvider(AutocompleteProviderListener* listener, | 253 SearchProvider::SearchProvider(AutocompleteProviderListener* listener, |
| 128 Profile* profile) | 254 Profile* profile) |
| 129 : AutocompleteProvider(listener, profile, | 255 : AutocompleteProvider(listener, profile, |
| 130 AutocompleteProvider::TYPE_SEARCH), | 256 AutocompleteProvider::TYPE_SEARCH), |
| 131 providers_(TemplateURLServiceFactory::GetForProfile(profile)), | 257 providers_(TemplateURLServiceFactory::GetForProfile(profile)), |
| 132 suggest_results_pending_(0), | 258 suggest_results_pending_(0), |
| 133 has_default_suggested_relevance_(false), | 259 has_default_suggested_relevance_(false), |
| 134 has_keyword_suggested_relevance_(false), | 260 has_keyword_suggested_relevance_(false), |
| 135 default_verbatim_relevance_(-1), | 261 default_verbatim_relevance_(-1), |
| 136 keyword_verbatim_relevance_(-1), | 262 keyword_verbatim_relevance_(-1), |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 234 instant_finalized_ = | 360 instant_finalized_ = |
| 235 (input.matches_requested() != AutocompleteInput::ALL_MATCHES); | 361 (input.matches_requested() != AutocompleteInput::ALL_MATCHES); |
| 236 | 362 |
| 237 // Can't return search/suggest results for bogus input or without a profile. | 363 // Can't return search/suggest results for bogus input or without a profile. |
| 238 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) { | 364 if (!profile_ || (input.type() == AutocompleteInput::INVALID)) { |
| 239 Stop(false); | 365 Stop(false); |
| 240 return; | 366 return; |
| 241 } | 367 } |
| 242 | 368 |
| 243 keyword_input_ = input; | 369 keyword_input_ = input; |
| 244 const TemplateURL* keyword_provider = | 370 Providers providers = GetProviders(profile_, input, &keyword_input_); |
| 245 KeywordProvider::GetSubstitutingTemplateURLForInput(model, | |
| 246 &keyword_input_); | |
| 247 if (keyword_provider == NULL) | |
| 248 keyword_input_.Clear(); | |
| 249 else if (keyword_input_.text().empty()) | |
| 250 keyword_provider = NULL; | |
| 251 | 371 |
| 252 const TemplateURL* default_provider = model->GetDefaultSearchProvider(); | 372 if (providers.default_provider().empty() && |
| 253 if (default_provider && !default_provider->SupportsReplacement()) | 373 providers.keyword_provider().empty()) { |
| 254 default_provider = NULL; | |
| 255 | |
| 256 if (keyword_provider == default_provider) | |
| 257 default_provider = NULL; // No use in querying the same provider twice. | |
| 258 | |
| 259 if (!default_provider && !keyword_provider) { | |
| 260 // No valid providers. | 374 // No valid providers. |
| 261 Stop(false); | 375 Stop(false); |
| 262 return; | 376 return; |
| 263 } | 377 } |
| 264 | 378 |
| 265 // If we're still running an old query but have since changed the query text | 379 // If we're still running an old query but have since changed the query text |
| 266 // or the providers, abort the query. | 380 // or the providers, abort the query. |
| 267 string16 default_provider_keyword(default_provider ? | |
| 268 default_provider->keyword() : string16()); | |
| 269 string16 keyword_provider_keyword(keyword_provider ? | |
| 270 keyword_provider->keyword() : string16()); | |
| 271 if (!minimal_changes || | 381 if (!minimal_changes || |
| 272 !providers_.equal(default_provider_keyword, keyword_provider_keyword)) { | 382 !providers_.equal(providers.default_provider(), |
| 383 providers.keyword_provider())) { | |
| 273 // If Instant has not come back with a suggestion, adjust the previous | 384 // If Instant has not come back with a suggestion, adjust the previous |
| 274 // suggestion if possible. If |instant_finalized| is true, we are looking | 385 // suggestion if possible. If |instant_finalized| is true, we are looking |
| 275 // for synchronous matches only, so the suggestion is cleared. | 386 // for synchronous matches only, so the suggestion is cleared. |
| 276 if (instant_finalized_) | 387 if (instant_finalized_) |
| 277 default_provider_suggestion_ = InstantSuggestion(); | 388 default_provider_suggestion_ = InstantSuggestion(); |
| 278 else | 389 else |
| 279 AdjustDefaultProviderSuggestion(input_.text(), input.text()); | 390 AdjustDefaultProviderSuggestion(input_.text(), input.text()); |
| 280 | 391 |
| 281 // Cancel any in-flight suggest requests. | 392 // Cancel any in-flight suggest requests. |
| 282 if (!done_) { | 393 if (!done_) { |
| 283 // The Stop(false) call below clears |default_provider_suggestion_|, but | 394 // The Stop(false) call below clears |default_provider_suggestion_|, but |
| 284 // in this instance we do not want to clear cached results, so we | 395 // in this instance we do not want to clear cached results, so we |
| 285 // restore it. | 396 // restore it. |
| 286 base::AutoReset<InstantSuggestion> reset(&default_provider_suggestion_, | 397 base::AutoReset<InstantSuggestion> reset(&default_provider_suggestion_, |
| 287 InstantSuggestion()); | 398 InstantSuggestion()); |
| 288 Stop(false); | 399 Stop(false); |
| 289 } | 400 } |
| 290 } | 401 } |
| 291 | 402 |
| 292 providers_.set(default_provider_keyword, keyword_provider_keyword); | 403 providers_ = providers; |
| 293 | 404 |
| 294 if (input.text().empty()) { | 405 if (input.text().empty()) { |
| 295 // User typed "?" alone. Give them a placeholder result indicating what | 406 // User typed "?" alone. Give them a placeholder result indicating what |
| 296 // this syntax does. | 407 // this syntax does. |
| 297 if (default_provider) { | 408 if (!providers_.default_provider().empty()) { |
| 298 AutocompleteMatch match; | 409 AutocompleteMatch match; |
| 299 match.provider = this; | 410 match.provider = this; |
| 300 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)); | 411 match.contents.assign(l10n_util::GetStringUTF16(IDS_EMPTY_KEYWORD_VALUE)); |
| 301 match.contents_class.push_back( | 412 match.contents_class.push_back( |
| 302 ACMatchClassification(0, ACMatchClassification::NONE)); | 413 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 303 match.keyword = providers_.default_provider(); | 414 match.keyword = providers_.default_provider(); |
| 304 matches_.push_back(match); | 415 matches_.push_back(match); |
| 305 } | 416 } |
| 306 Stop(false); | 417 Stop(false); |
| 307 return; | 418 return; |
| (...skipping 1011 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1319 return std::max(0, base_score - score_discount); | 1430 return std::max(0, base_score - score_discount); |
| 1320 } | 1431 } |
| 1321 | 1432 |
| 1322 void SearchProvider::AddMatchToMap(const string16& query_string, | 1433 void SearchProvider::AddMatchToMap(const string16& query_string, |
| 1323 const string16& input_text, | 1434 const string16& input_text, |
| 1324 int relevance, | 1435 int relevance, |
| 1325 AutocompleteMatch::Type type, | 1436 AutocompleteMatch::Type type, |
| 1326 int accepted_suggestion, | 1437 int accepted_suggestion, |
| 1327 bool is_keyword, | 1438 bool is_keyword, |
| 1328 MatchMap* map) { | 1439 MatchMap* map) { |
| 1329 AutocompleteMatch match(this, relevance, false, type); | 1440 AutocompleteMatch match = CreateSearchSuggestion(profile_, this, providers_, |
| 1330 std::vector<size_t> content_param_offsets; | 1441 input_, query_string, input_text, relevance, type, accepted_suggestion, |
| 1331 // Bail out now if we don't actually have a valid provider. | 1442 is_keyword); |
| 1332 match.keyword = is_keyword ? | 1443 if (!match.destination_url.is_valid()) |
| 1333 providers_.keyword_provider() : providers_.default_provider(); | |
| 1334 const TemplateURL* provider_url = match.GetTemplateURL(profile_, false); | |
| 1335 if (provider_url == NULL) | |
| 1336 return; | 1444 return; |
| 1337 | 1445 |
| 1338 match.contents.assign(query_string); | |
| 1339 // We do intra-string highlighting for suggestions - the suggested segment | |
| 1340 // will be highlighted, e.g. for input_text = "you" the suggestion may be | |
| 1341 // "youtube", so we'll bold the "tube" section: you*tube*. | |
| 1342 if (input_text != query_string) { | |
| 1343 size_t input_position = match.contents.find(input_text); | |
| 1344 if (input_position == string16::npos) { | |
| 1345 // The input text is not a substring of the query string, e.g. input | |
| 1346 // text is "slasdot" and the query string is "slashdot", so we bold the | |
| 1347 // whole thing. | |
| 1348 match.contents_class.push_back( | |
| 1349 ACMatchClassification(0, ACMatchClassification::MATCH)); | |
| 1350 } else { | |
| 1351 // TODO(beng): ACMatchClassification::MATCH now seems to just mean | |
| 1352 // "bold" this. Consider modifying the terminology. | |
| 1353 // We don't iterate over the string here annotating all matches because | |
| 1354 // it looks odd to have every occurrence of a substring that may be as | |
| 1355 // short as a single character highlighted in a query suggestion result, | |
| 1356 // e.g. for input text "s" and query string "southwest airlines", it | |
| 1357 // looks odd if both the first and last s are highlighted. | |
| 1358 if (input_position != 0) { | |
| 1359 match.contents_class.push_back( | |
| 1360 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 1361 } | |
| 1362 match.contents_class.push_back( | |
| 1363 ACMatchClassification(input_position, ACMatchClassification::DIM)); | |
| 1364 size_t next_fragment_position = input_position + input_text.length(); | |
| 1365 if (next_fragment_position < query_string.length()) { | |
| 1366 match.contents_class.push_back( | |
| 1367 ACMatchClassification(next_fragment_position, | |
| 1368 ACMatchClassification::NONE)); | |
| 1369 } | |
| 1370 } | |
| 1371 } else { | |
| 1372 // Otherwise, we're dealing with the "default search" result which has no | |
| 1373 // completion. | |
| 1374 match.contents_class.push_back( | |
| 1375 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 1376 } | |
| 1377 | |
| 1378 // When the user forced a query, we need to make sure all the fill_into_edit | |
| 1379 // values preserve that property. Otherwise, if the user starts editing a | |
| 1380 // suggestion, non-Search results will suddenly appear. | |
| 1381 if (input_.type() == AutocompleteInput::FORCED_QUERY) | |
| 1382 match.fill_into_edit.assign(ASCIIToUTF16("?")); | |
| 1383 if (is_keyword) | |
| 1384 match.fill_into_edit.append(match.keyword + char16(' ')); | |
| 1385 if (!input_.prevent_inline_autocomplete() && | |
| 1386 StartsWith(query_string, input_text, false)) { | |
| 1387 match.inline_autocomplete_offset = | |
| 1388 match.fill_into_edit.length() + input_text.length(); | |
| 1389 } | |
| 1390 match.fill_into_edit.append(query_string); | |
| 1391 | |
| 1392 const TemplateURLRef& search_url = provider_url->url_ref(); | |
| 1393 DCHECK(search_url.SupportsReplacement()); | |
| 1394 match.search_terms_args.reset( | |
| 1395 new TemplateURLRef::SearchTermsArgs(query_string)); | |
| 1396 match.search_terms_args->original_query = input_text; | |
| 1397 match.search_terms_args->accepted_suggestion = accepted_suggestion; | |
| 1398 // This is the destination URL sans assisted query stats. This must be set | |
| 1399 // so the AutocompleteController can properly de-dupe; the controller will | |
| 1400 // eventually overwrite it before it reaches the user. | |
| 1401 match.destination_url = | |
| 1402 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
| 1403 | |
| 1404 // Search results don't look like URLs. | |
| 1405 match.transition = is_keyword ? | |
| 1406 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED; | |
| 1407 | |
| 1408 // Try to add |match| to |map|. If a match for |query_string| is already in | 1446 // Try to add |match| to |map|. If a match for |query_string| is already in |
| 1409 // |map|, replace it if |match| is more relevant. | 1447 // |map|, replace it if |match| is more relevant. |
| 1410 // NOTE: Keep this ToLower() call in sync with url_database.cc. | 1448 // NOTE: Keep this ToLower() call in sync with url_database.cc. |
| 1411 const std::pair<MatchMap::iterator, bool> i = map->insert( | 1449 const std::pair<MatchMap::iterator, bool> i = map->insert( |
| 1412 std::pair<string16, AutocompleteMatch>( | 1450 std::pair<string16, AutocompleteMatch>( |
| 1413 base::i18n::ToLower(query_string), match)); | 1451 base::i18n::ToLower(query_string), match)); |
| 1414 // NOTE: We purposefully do a direct relevance comparison here instead of | 1452 // NOTE: We purposefully do a direct relevance comparison here instead of |
| 1415 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added | 1453 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added |
| 1416 // first" rather than "items alphabetically first" when the scores are equal. | 1454 // first" rather than "items alphabetically first" when the scores are equal. |
| 1417 // The only case this matters is when a user has results with the same score | 1455 // The only case this matters is when a user has results with the same score |
| (...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1515 it->set_relevance(max_query_relevance); | 1553 it->set_relevance(max_query_relevance); |
| 1516 } | 1554 } |
| 1517 } | 1555 } |
| 1518 | 1556 |
| 1519 void SearchProvider::UpdateDone() { | 1557 void SearchProvider::UpdateDone() { |
| 1520 // We're done when the timer isn't running, there are no suggest queries | 1558 // We're done when the timer isn't running, there are no suggest queries |
| 1521 // pending, and we're not waiting on Instant. | 1559 // pending, and we're not waiting on Instant. |
| 1522 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && | 1560 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && |
| 1523 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); | 1561 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); |
| 1524 } | 1562 } |
| OLD | NEW |