| 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 AutocompleteMatch SearchProvider::CreateSearchSuggestion( |
| 129 Profile* profile, |
| 130 AutocompleteProvider* autocomplete_provider, |
| 131 const AutocompleteInput& input, |
| 132 const string16& query_string, |
| 133 const string16& input_text, |
| 134 int relevance, |
| 135 AutocompleteMatch::Type type, |
| 136 int accepted_suggestion, |
| 137 bool is_keyword, |
| 138 const string16& keyword) { |
| 139 AutocompleteMatch match(autocomplete_provider, relevance, false, type); |
| 140 |
| 141 // Bail out now if we don't actually have a valid provider. |
| 142 match.keyword = keyword; |
| 143 const TemplateURL* provider_url = match.GetTemplateURL(profile, false); |
| 144 if (provider_url == NULL) |
| 145 return match; |
| 146 |
| 147 match.contents.assign(query_string); |
| 148 // We do intra-string highlighting for suggestions - the suggested segment |
| 149 // will be highlighted, e.g. for input_text = "you" the suggestion may be |
| 150 // "youtube", so we'll bold the "tube" section: you*tube*. |
| 151 if (input_text != query_string) { |
| 152 size_t input_position = match.contents.find(input_text); |
| 153 if (input_position == string16::npos) { |
| 154 // The input text is not a substring of the query string, e.g. input |
| 155 // text is "slasdot" and the query string is "slashdot", so we bold the |
| 156 // whole thing. |
| 157 match.contents_class.push_back( |
| 158 ACMatchClassification(0, ACMatchClassification::MATCH)); |
| 159 } else { |
| 160 // TODO(beng): ACMatchClassification::MATCH now seems to just mean |
| 161 // "bold" this. Consider modifying the terminology. |
| 162 // We don't iterate over the string here annotating all matches because |
| 163 // it looks odd to have every occurrence of a substring that may be as |
| 164 // short as a single character highlighted in a query suggestion result, |
| 165 // e.g. for input text "s" and query string "southwest airlines", it |
| 166 // looks odd if both the first and last s are highlighted. |
| 167 if (input_position != 0) { |
| 168 match.contents_class.push_back( |
| 169 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 170 } |
| 171 match.contents_class.push_back( |
| 172 ACMatchClassification(input_position, ACMatchClassification::DIM)); |
| 173 size_t next_fragment_position = input_position + input_text.length(); |
| 174 if (next_fragment_position < query_string.length()) { |
| 175 match.contents_class.push_back( |
| 176 ACMatchClassification(next_fragment_position, |
| 177 ACMatchClassification::NONE)); |
| 178 } |
| 179 } |
| 180 } else { |
| 181 // Otherwise, we're dealing with the "default search" result which has no |
| 182 // completion. |
| 183 match.contents_class.push_back( |
| 184 ACMatchClassification(0, ACMatchClassification::NONE)); |
| 185 } |
| 186 |
| 187 // When the user forced a query, we need to make sure all the fill_into_edit |
| 188 // values preserve that property. Otherwise, if the user starts editing a |
| 189 // suggestion, non-Search results will suddenly appear. |
| 190 if (input.type() == AutocompleteInput::FORCED_QUERY) |
| 191 match.fill_into_edit.assign(ASCIIToUTF16("?")); |
| 192 if (is_keyword) |
| 193 match.fill_into_edit.append(match.keyword + char16(' ')); |
| 194 if (!input.prevent_inline_autocomplete() && |
| 195 StartsWith(query_string, input_text, false)) { |
| 196 match.inline_autocomplete_offset = |
| 197 match.fill_into_edit.length() + input_text.length(); |
| 198 } |
| 199 match.fill_into_edit.append(query_string); |
| 200 |
| 201 const TemplateURLRef& search_url = provider_url->url_ref(); |
| 202 DCHECK(search_url.SupportsReplacement()); |
| 203 match.search_terms_args.reset( |
| 204 new TemplateURLRef::SearchTermsArgs(query_string)); |
| 205 match.search_terms_args->original_query = input_text; |
| 206 match.search_terms_args->accepted_suggestion = accepted_suggestion; |
| 207 // This is the destination URL sans assisted query stats. This must be set |
| 208 // so the AutocompleteController can properly de-dupe; the controller will |
| 209 // eventually overwrite it before it reaches the user. |
| 210 match.destination_url = |
| 211 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); |
| 212 |
| 213 // Search results don't look like URLs. |
| 214 match.transition = is_keyword ? |
| 215 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED; |
| 216 |
| 217 return match; |
| 218 } |
| 219 |
| 127 SearchProvider::SearchProvider(AutocompleteProviderListener* listener, | 220 SearchProvider::SearchProvider(AutocompleteProviderListener* listener, |
| 128 Profile* profile) | 221 Profile* profile) |
| 129 : AutocompleteProvider(listener, profile, | 222 : AutocompleteProvider(listener, profile, |
| 130 AutocompleteProvider::TYPE_SEARCH), | 223 AutocompleteProvider::TYPE_SEARCH), |
| 131 providers_(TemplateURLServiceFactory::GetForProfile(profile)), | 224 providers_(TemplateURLServiceFactory::GetForProfile(profile)), |
| 132 suggest_results_pending_(0), | 225 suggest_results_pending_(0), |
| 133 has_default_suggested_relevance_(false), | 226 has_default_suggested_relevance_(false), |
| 134 has_keyword_suggested_relevance_(false), | 227 has_keyword_suggested_relevance_(false), |
| 135 default_verbatim_relevance_(-1), | 228 default_verbatim_relevance_(-1), |
| 136 keyword_verbatim_relevance_(-1), | 229 keyword_verbatim_relevance_(-1), |
| (...skipping 1190 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1327 bool is_keyword, | 1420 bool is_keyword, |
| 1328 MatchMap* map) { | 1421 MatchMap* map) { |
| 1329 // With Instant Extended, we never want to inline autocomplete search queries | 1422 // With Instant Extended, we never want to inline autocomplete search queries |
| 1330 // -- they should always use grey text if they are to autocomplete at all. So | 1423 // -- they should always use grey text if they are to autocomplete at all. So |
| 1331 // we clamp non-verbatim results to just below the verbatim score to ensure | 1424 // we clamp non-verbatim results to just below the verbatim score to ensure |
| 1332 // that none of them are inline autocompleted. | 1425 // that none of them are inline autocompleted. |
| 1333 if ((type != AutocompleteMatch::SEARCH_WHAT_YOU_TYPED) && | 1426 if ((type != AutocompleteMatch::SEARCH_WHAT_YOU_TYPED) && |
| 1334 chrome::IsInstantExtendedAPIEnabled()) { | 1427 chrome::IsInstantExtendedAPIEnabled()) { |
| 1335 relevance = std::min(kNonURLVerbatimRelevance - 1, relevance); | 1428 relevance = std::min(kNonURLVerbatimRelevance - 1, relevance); |
| 1336 } | 1429 } |
| 1337 AutocompleteMatch match(this, relevance, false, type); | 1430 |
| 1338 std::vector<size_t> content_param_offsets; | 1431 const string16& keyword = is_keyword ? |
| 1339 // Bail out now if we don't actually have a valid provider. | |
| 1340 match.keyword = is_keyword ? | |
| 1341 providers_.keyword_provider() : providers_.default_provider(); | 1432 providers_.keyword_provider() : providers_.default_provider(); |
| 1342 const TemplateURL* provider_url = match.GetTemplateURL(profile_, false); | 1433 AutocompleteMatch match = CreateSearchSuggestion(profile_, this, input_, |
| 1343 if (provider_url == NULL) | 1434 query_string, input_text, relevance, type, accepted_suggestion, |
| 1435 is_keyword, keyword); |
| 1436 if (!match.destination_url.is_valid()) |
| 1344 return; | 1437 return; |
| 1345 | 1438 |
| 1346 match.contents.assign(query_string); | |
| 1347 // We do intra-string highlighting for suggestions - the suggested segment | |
| 1348 // will be highlighted, e.g. for input_text = "you" the suggestion may be | |
| 1349 // "youtube", so we'll bold the "tube" section: you*tube*. | |
| 1350 if (input_text != query_string) { | |
| 1351 size_t input_position = match.contents.find(input_text); | |
| 1352 if (input_position == string16::npos) { | |
| 1353 // The input text is not a substring of the query string, e.g. input | |
| 1354 // text is "slasdot" and the query string is "slashdot", so we bold the | |
| 1355 // whole thing. | |
| 1356 match.contents_class.push_back( | |
| 1357 ACMatchClassification(0, ACMatchClassification::MATCH)); | |
| 1358 } else { | |
| 1359 // TODO(beng): ACMatchClassification::MATCH now seems to just mean | |
| 1360 // "bold" this. Consider modifying the terminology. | |
| 1361 // We don't iterate over the string here annotating all matches because | |
| 1362 // it looks odd to have every occurrence of a substring that may be as | |
| 1363 // short as a single character highlighted in a query suggestion result, | |
| 1364 // e.g. for input text "s" and query string "southwest airlines", it | |
| 1365 // looks odd if both the first and last s are highlighted. | |
| 1366 if (input_position != 0) { | |
| 1367 match.contents_class.push_back( | |
| 1368 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 1369 } | |
| 1370 match.contents_class.push_back( | |
| 1371 ACMatchClassification(input_position, ACMatchClassification::DIM)); | |
| 1372 size_t next_fragment_position = input_position + input_text.length(); | |
| 1373 if (next_fragment_position < query_string.length()) { | |
| 1374 match.contents_class.push_back( | |
| 1375 ACMatchClassification(next_fragment_position, | |
| 1376 ACMatchClassification::NONE)); | |
| 1377 } | |
| 1378 } | |
| 1379 } else { | |
| 1380 // Otherwise, we're dealing with the "default search" result which has no | |
| 1381 // completion. | |
| 1382 match.contents_class.push_back( | |
| 1383 ACMatchClassification(0, ACMatchClassification::NONE)); | |
| 1384 } | |
| 1385 | |
| 1386 // When the user forced a query, we need to make sure all the fill_into_edit | |
| 1387 // values preserve that property. Otherwise, if the user starts editing a | |
| 1388 // suggestion, non-Search results will suddenly appear. | |
| 1389 if (input_.type() == AutocompleteInput::FORCED_QUERY) | |
| 1390 match.fill_into_edit.assign(ASCIIToUTF16("?")); | |
| 1391 if (is_keyword) | |
| 1392 match.fill_into_edit.append(match.keyword + char16(' ')); | |
| 1393 if (!input_.prevent_inline_autocomplete() && | |
| 1394 StartsWith(query_string, input_text, false)) { | |
| 1395 match.inline_autocomplete_offset = | |
| 1396 match.fill_into_edit.length() + input_text.length(); | |
| 1397 } | |
| 1398 match.fill_into_edit.append(query_string); | |
| 1399 | |
| 1400 const TemplateURLRef& search_url = provider_url->url_ref(); | |
| 1401 DCHECK(search_url.SupportsReplacement()); | |
| 1402 match.search_terms_args.reset( | |
| 1403 new TemplateURLRef::SearchTermsArgs(query_string)); | |
| 1404 match.search_terms_args->original_query = input_text; | |
| 1405 match.search_terms_args->accepted_suggestion = accepted_suggestion; | |
| 1406 // This is the destination URL sans assisted query stats. This must be set | |
| 1407 // so the AutocompleteController can properly de-dupe; the controller will | |
| 1408 // eventually overwrite it before it reaches the user. | |
| 1409 match.destination_url = | |
| 1410 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
| 1411 | |
| 1412 // Search results don't look like URLs. | |
| 1413 match.transition = is_keyword ? | |
| 1414 content::PAGE_TRANSITION_KEYWORD : content::PAGE_TRANSITION_GENERATED; | |
| 1415 | |
| 1416 // Try to add |match| to |map|. If a match for |query_string| is already in | 1439 // Try to add |match| to |map|. If a match for |query_string| is already in |
| 1417 // |map|, replace it if |match| is more relevant. | 1440 // |map|, replace it if |match| is more relevant. |
| 1418 // NOTE: Keep this ToLower() call in sync with url_database.cc. | 1441 // NOTE: Keep this ToLower() call in sync with url_database.cc. |
| 1419 const std::pair<MatchMap::iterator, bool> i = map->insert( | 1442 const std::pair<MatchMap::iterator, bool> i = map->insert( |
| 1420 std::pair<string16, AutocompleteMatch>( | 1443 std::pair<string16, AutocompleteMatch>( |
| 1421 base::i18n::ToLower(query_string), match)); | 1444 base::i18n::ToLower(query_string), match)); |
| 1422 // NOTE: We purposefully do a direct relevance comparison here instead of | 1445 // NOTE: We purposefully do a direct relevance comparison here instead of |
| 1423 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added | 1446 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added |
| 1424 // first" rather than "items alphabetically first" when the scores are equal. | 1447 // first" rather than "items alphabetically first" when the scores are equal. |
| 1425 // The only case this matters is when a user has results with the same score | 1448 // 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... |
| 1523 it->set_relevance(max_query_relevance); | 1546 it->set_relevance(max_query_relevance); |
| 1524 } | 1547 } |
| 1525 } | 1548 } |
| 1526 | 1549 |
| 1527 void SearchProvider::UpdateDone() { | 1550 void SearchProvider::UpdateDone() { |
| 1528 // We're done when the timer isn't running, there are no suggest queries | 1551 // We're done when the timer isn't running, there are no suggest queries |
| 1529 // pending, and we're not waiting on Instant. | 1552 // pending, and we're not waiting on Instant. |
| 1530 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && | 1553 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && |
| 1531 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); | 1554 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); |
| 1532 } | 1555 } |
| OLD | NEW |