| 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 1182 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1319 return std::max(0, base_score - score_discount); | 1412 return std::max(0, base_score - score_discount); |
| 1320 } | 1413 } |
| 1321 | 1414 |
| 1322 void SearchProvider::AddMatchToMap(const string16& query_string, | 1415 void SearchProvider::AddMatchToMap(const string16& query_string, |
| 1323 const string16& input_text, | 1416 const string16& input_text, |
| 1324 int relevance, | 1417 int relevance, |
| 1325 AutocompleteMatch::Type type, | 1418 AutocompleteMatch::Type type, |
| 1326 int accepted_suggestion, | 1419 int accepted_suggestion, |
| 1327 bool is_keyword, | 1420 bool is_keyword, |
| 1328 MatchMap* map) { | 1421 MatchMap* map) { |
| 1329 AutocompleteMatch match(this, relevance, false, type); | 1422 const string16& keyword = is_keyword ? |
| 1330 std::vector<size_t> content_param_offsets; | |
| 1331 // Bail out now if we don't actually have a valid provider. | |
| 1332 match.keyword = is_keyword ? | |
| 1333 providers_.keyword_provider() : providers_.default_provider(); | 1423 providers_.keyword_provider() : providers_.default_provider(); |
| 1334 const TemplateURL* provider_url = match.GetTemplateURL(profile_, false); | 1424 AutocompleteMatch match = CreateSearchSuggestion(profile_, this, input_, |
| 1335 if (provider_url == NULL) | 1425 query_string, input_text, relevance, type, accepted_suggestion, |
| 1426 is_keyword, keyword); |
| 1427 if (!match.destination_url.is_valid()) |
| 1336 return; | 1428 return; |
| 1337 | 1429 |
| 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 | 1430 // Try to add |match| to |map|. If a match for |query_string| is already in |
| 1409 // |map|, replace it if |match| is more relevant. | 1431 // |map|, replace it if |match| is more relevant. |
| 1410 // NOTE: Keep this ToLower() call in sync with url_database.cc. | 1432 // NOTE: Keep this ToLower() call in sync with url_database.cc. |
| 1411 const std::pair<MatchMap::iterator, bool> i = map->insert( | 1433 const std::pair<MatchMap::iterator, bool> i = map->insert( |
| 1412 std::pair<string16, AutocompleteMatch>( | 1434 std::pair<string16, AutocompleteMatch>( |
| 1413 base::i18n::ToLower(query_string), match)); | 1435 base::i18n::ToLower(query_string), match)); |
| 1414 // NOTE: We purposefully do a direct relevance comparison here instead of | 1436 // NOTE: We purposefully do a direct relevance comparison here instead of |
| 1415 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added | 1437 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added |
| 1416 // first" rather than "items alphabetically first" when the scores are equal. | 1438 // 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 | 1439 // 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); | 1537 it->set_relevance(max_query_relevance); |
| 1516 } | 1538 } |
| 1517 } | 1539 } |
| 1518 | 1540 |
| 1519 void SearchProvider::UpdateDone() { | 1541 void SearchProvider::UpdateDone() { |
| 1520 // We're done when the timer isn't running, there are no suggest queries | 1542 // We're done when the timer isn't running, there are no suggest queries |
| 1521 // pending, and we're not waiting on Instant. | 1543 // pending, and we're not waiting on Instant. |
| 1522 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && | 1544 done_ = (!timer_.IsRunning() && (suggest_results_pending_ == 0) && |
| 1523 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); | 1545 (instant_finalized_ || !chrome::IsInstantEnabled(profile_))); |
| 1524 } | 1546 } |
| OLD | NEW |