1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/autocomplete/base_search_provider.h"
7 #include "base/i18n/case_conversion.h"
8 #include "base/i18n/icu_string_conversions.h"
9 #include "base/json/json_string_value_serializer.h"
10 #include "base/json/json_writer.h"
11 #include "base/prefs/pref_registry_simple.h"
12 #include "base/prefs/pref_service.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
16 #include "chrome/browser/autocomplete/url_prefix.h"
17 #include "chrome/browser/history/history_service.h"
18 #include "chrome/browser/history/history_service_factory.h"
19 #include "chrome/browser/omnibox/omnibox_field_trial.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/search/instant_service.h"
22 #include "chrome/browser/search/instant_service_factory.h"
23 #include "chrome/browser/search/search.h"
24 #include "chrome/browser/search_engines/template_url.h"
25 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
26 #include "chrome/browser/search_engines/template_url_service.h"
27 #include "chrome/browser/search_engines/template_url_service_factory.h"
28 #include "chrome/browser/sync/profile_sync_service.h"
29 #include "chrome/browser/sync/profile_sync_service_factory.h"
30 #include "chrome/common/net/url_fixer_upper.h"
31 #include "chrome/common/pref_names.h"
32 #include "components/sync_driver/sync_prefs.h"
33 #include "content/public/common/url_constants.h"
34 #include "net/base/escape.h"
35 #include "net/base/net_util.h"
36 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
37 #include "net/http/http_response_headers.h"
38 #include "net/url_request/url_fetcher.h"
39 #include "net/url_request/url_fetcher_delegate.h"
44 AutocompleteMatchType::Type
GetAutocompleteMatchType(const std::string
& type
) {
46 return AutocompleteMatchType::SEARCH_SUGGEST_ENTITY
;
47 if (type
== "INFINITE")
48 return AutocompleteMatchType::SEARCH_SUGGEST_INFINITE
;
49 if (type
== "PERSONALIZED_QUERY")
50 return AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED
;
51 if (type
== "PROFILE")
52 return AutocompleteMatchType::SEARCH_SUGGEST_PROFILE
;
53 if (type
== "NAVIGATION")
54 return AutocompleteMatchType::NAVSUGGEST
;
55 if (type
== "PERSONALIZED_NAVIGATION")
56 return AutocompleteMatchType::NAVSUGGEST_PERSONALIZED
;
57 return AutocompleteMatchType::SEARCH_SUGGEST
;
62 // SuggestionDeletionHandler -------------------------------------------------
64 // This class handles making requests to the server in order to delete
65 // personalized suggestions.
66 class SuggestionDeletionHandler
: public net::URLFetcherDelegate
{
68 typedef base::Callback
<void(bool, SuggestionDeletionHandler
*)>
69 DeletionCompletedCallback
;
71 SuggestionDeletionHandler(
72 const std::string
& deletion_url
,
74 const DeletionCompletedCallback
& callback
);
76 virtual ~SuggestionDeletionHandler();
79 // net::URLFetcherDelegate:
80 virtual void OnURLFetchComplete(const net::URLFetcher
* source
) OVERRIDE
;
82 scoped_ptr
<net::URLFetcher
> deletion_fetcher_
;
83 DeletionCompletedCallback callback_
;
85 DISALLOW_COPY_AND_ASSIGN(SuggestionDeletionHandler
);
88 SuggestionDeletionHandler::SuggestionDeletionHandler(
89 const std::string
& deletion_url
,
91 const DeletionCompletedCallback
& callback
) : callback_(callback
) {
92 GURL
url(deletion_url
);
93 DCHECK(url
.is_valid());
95 deletion_fetcher_
.reset(net::URLFetcher::Create(
96 BaseSearchProvider::kDeletionURLFetcherID
,
100 deletion_fetcher_
->SetRequestContext(profile
->GetRequestContext());
101 deletion_fetcher_
->Start();
104 SuggestionDeletionHandler::~SuggestionDeletionHandler() {
107 void SuggestionDeletionHandler::OnURLFetchComplete(
108 const net::URLFetcher
* source
) {
109 DCHECK(source
== deletion_fetcher_
.get());
111 source
->GetStatus().is_success() && (source
->GetResponseCode() == 200),
115 // BaseSearchProvider ---------------------------------------------------------
118 const int BaseSearchProvider::kDefaultProviderURLFetcherID
= 1;
119 const int BaseSearchProvider::kKeywordProviderURLFetcherID
= 2;
120 const int BaseSearchProvider::kDeletionURLFetcherID
= 3;
122 BaseSearchProvider::BaseSearchProvider(AutocompleteProviderListener
* listener
,
124 AutocompleteProvider::Type type
)
125 : AutocompleteProvider(listener
, profile
, type
),
126 field_trial_triggered_(false),
127 field_trial_triggered_in_session_(false),
128 suggest_results_pending_(0),
129 in_app_list_(false) {
133 bool BaseSearchProvider::ShouldPrefetch(const AutocompleteMatch
& match
) {
134 return match
.GetAdditionalInfo(kShouldPrefetchKey
) == kTrue
;
138 AutocompleteMatch
BaseSearchProvider::CreateSearchSuggestion(
139 const base::string16
& suggestion
,
140 AutocompleteMatchType::Type type
,
141 bool from_keyword_provider
,
142 const TemplateURL
* template_url
) {
143 return CreateSearchSuggestion(
144 NULL
, AutocompleteInput(), BaseSearchProvider::SuggestResult(
145 suggestion
, type
, suggestion
, base::string16(), base::string16(),
146 base::string16(), base::string16(), std::string(), std::string(),
147 from_keyword_provider
, 0, false, false, base::string16()),
148 template_url
, 0, 0, false, false);
151 void BaseSearchProvider::Stop(bool clear_cached_results
) {
155 if (clear_cached_results
)
159 void BaseSearchProvider::DeleteMatch(const AutocompleteMatch
& match
) {
160 DCHECK(match
.deletable
);
161 if (!match
.GetAdditionalInfo(BaseSearchProvider::kDeletionUrlKey
).empty()) {
162 deletion_handlers_
.push_back(new SuggestionDeletionHandler(
163 match
.GetAdditionalInfo(BaseSearchProvider::kDeletionUrlKey
),
165 base::Bind(&BaseSearchProvider::OnDeletionComplete
,
166 base::Unretained(this))));
169 HistoryService
* const history_service
=
170 HistoryServiceFactory::GetForProfile(profile_
, Profile::EXPLICIT_ACCESS
);
171 TemplateURL
* template_url
= match
.GetTemplateURL(profile_
, false);
172 // This may be NULL if the template corresponding to the keyword has been
173 // deleted or there is no keyword set.
174 if (template_url
!= NULL
) {
175 history_service
->DeleteMatchingURLsForKeyword(template_url
->id(),
179 // Immediately update the list of matches to show the match was deleted,
180 // regardless of whether the server request actually succeeds.
181 DeleteMatchFromMatches(match
);
184 void BaseSearchProvider::AddProviderInfo(ProvidersInfo
* provider_info
) const {
185 provider_info
->push_back(metrics::OmniboxEventProto_ProviderInfo());
186 metrics::OmniboxEventProto_ProviderInfo
& new_entry
= provider_info
->back();
187 new_entry
.set_provider(AsOmniboxEventProviderType());
188 new_entry
.set_provider_done(done_
);
189 std::vector
<uint32
> field_trial_hashes
;
190 OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(&field_trial_hashes
);
191 for (size_t i
= 0; i
< field_trial_hashes
.size(); ++i
) {
192 if (field_trial_triggered_
)
193 new_entry
.mutable_field_trial_triggered()->Add(field_trial_hashes
[i
]);
194 if (field_trial_triggered_in_session_
) {
195 new_entry
.mutable_field_trial_triggered_in_session()->Add(
196 field_trial_hashes
[i
]);
199 ModifyProviderInfo(&new_entry
);
203 const char BaseSearchProvider::kRelevanceFromServerKey
[] =
204 "relevance_from_server";
205 const char BaseSearchProvider::kShouldPrefetchKey
[] = "should_prefetch";
206 const char BaseSearchProvider::kSuggestMetadataKey
[] = "suggest_metadata";
207 const char BaseSearchProvider::kDeletionUrlKey
[] = "deletion_url";
208 const char BaseSearchProvider::kTrue
[] = "true";
209 const char BaseSearchProvider::kFalse
[] = "false";
211 BaseSearchProvider::~BaseSearchProvider() {}
213 // BaseSearchProvider::Result --------------------------------------------------
215 BaseSearchProvider::Result::Result(bool from_keyword_provider
,
217 bool relevance_from_server
,
218 AutocompleteMatchType::Type type
,
219 const std::string
& deletion_url
)
220 : from_keyword_provider_(from_keyword_provider
),
222 relevance_(relevance
),
223 relevance_from_server_(relevance_from_server
),
224 deletion_url_(deletion_url
) {}
226 BaseSearchProvider::Result::~Result() {}
228 // BaseSearchProvider::SuggestResult -------------------------------------------
230 BaseSearchProvider::SuggestResult::SuggestResult(
231 const base::string16
& suggestion
,
232 AutocompleteMatchType::Type type
,
233 const base::string16
& match_contents
,
234 const base::string16
& match_contents_prefix
,
235 const base::string16
& annotation
,
236 const base::string16
& answer_contents
,
237 const base::string16
& answer_type
,
238 const std::string
& suggest_query_params
,
239 const std::string
& deletion_url
,
240 bool from_keyword_provider
,
242 bool relevance_from_server
,
243 bool should_prefetch
,
244 const base::string16
& input_text
)
245 : Result(from_keyword_provider
,
247 relevance_from_server
,
250 suggestion_(suggestion
),
251 match_contents_prefix_(match_contents_prefix
),
252 annotation_(annotation
),
253 suggest_query_params_(suggest_query_params
),
254 answer_contents_(answer_contents
),
255 answer_type_(answer_type
),
256 should_prefetch_(should_prefetch
) {
257 match_contents_
= match_contents
;
258 DCHECK(!match_contents_
.empty());
259 ClassifyMatchContents(true, input_text
);
262 BaseSearchProvider::SuggestResult::~SuggestResult() {}
264 void BaseSearchProvider::SuggestResult::ClassifyMatchContents(
265 const bool allow_bolding_all
,
266 const base::string16
& input_text
) {
267 if (input_text
.empty()) {
268 // In case of zero-suggest results, do not highlight matches.
269 match_contents_class_
.push_back(
270 ACMatchClassification(0, ACMatchClassification::NONE
));
274 base::string16 lookup_text
= input_text
;
275 if (type_
== AutocompleteMatchType::SEARCH_SUGGEST_INFINITE
) {
276 const size_t contents_index
=
277 suggestion_
.length() - match_contents_
.length();
278 // Ensure the query starts with the input text, and ends with the match
279 // contents, and the input text has an overlap with contents.
280 if (StartsWith(suggestion_
, input_text
, true) &&
281 EndsWith(suggestion_
, match_contents_
, true) &&
282 (input_text
.length() > contents_index
)) {
283 lookup_text
= input_text
.substr(contents_index
);
286 size_t lookup_position
= match_contents_
.find(lookup_text
);
287 if (!allow_bolding_all
&& (lookup_position
== base::string16::npos
)) {
288 // Bail if the code below to update the bolding would bold the whole
289 // string. Note that the string may already be entirely bolded; if
290 // so, leave it as is.
293 match_contents_class_
.clear();
294 // We do intra-string highlighting for suggestions - the suggested segment
295 // will be highlighted, e.g. for input_text = "you" the suggestion may be
296 // "youtube", so we'll bold the "tube" section: you*tube*.
297 if (input_text
!= match_contents_
) {
298 if (lookup_position
== base::string16::npos
) {
299 // The input text is not a substring of the query string, e.g. input
300 // text is "slasdot" and the query string is "slashdot", so we bold the
302 match_contents_class_
.push_back(
303 ACMatchClassification(0, ACMatchClassification::MATCH
));
305 // We don't iterate over the string here annotating all matches because
306 // it looks odd to have every occurrence of a substring that may be as
307 // short as a single character highlighted in a query suggestion result,
308 // e.g. for input text "s" and query string "southwest airlines", it
309 // looks odd if both the first and last s are highlighted.
310 if (lookup_position
!= 0) {
311 match_contents_class_
.push_back(
312 ACMatchClassification(0, ACMatchClassification::MATCH
));
314 match_contents_class_
.push_back(
315 ACMatchClassification(lookup_position
, ACMatchClassification::NONE
));
316 size_t next_fragment_position
= lookup_position
+ lookup_text
.length();
317 if (next_fragment_position
< match_contents_
.length()) {
318 match_contents_class_
.push_back(ACMatchClassification(
319 next_fragment_position
, ACMatchClassification::MATCH
));
323 // Otherwise, match_contents_ is a verbatim (what-you-typed) match, either
324 // for the default provider or a keyword search provider.
325 match_contents_class_
.push_back(
326 ACMatchClassification(0, ACMatchClassification::NONE
));
330 bool BaseSearchProvider::SuggestResult::IsInlineable(
331 const base::string16
& input
) const {
332 return StartsWith(suggestion_
, input
, false);
335 int BaseSearchProvider::SuggestResult::CalculateRelevance(
336 const AutocompleteInput
& input
,
337 bool keyword_provider_requested
) const {
338 if (!from_keyword_provider_
&& keyword_provider_requested
)
340 return ((input
.type() == AutocompleteInput::URL
) ? 300 : 600);
343 // BaseSearchProvider::NavigationResult ----------------------------------------
345 BaseSearchProvider::NavigationResult::NavigationResult(
346 const AutocompleteProvider
& provider
,
348 AutocompleteMatchType::Type type
,
349 const base::string16
& description
,
350 const std::string
& deletion_url
,
351 bool from_keyword_provider
,
353 bool relevance_from_server
,
354 const base::string16
& input_text
,
355 const std::string
& languages
)
356 : Result(from_keyword_provider
,
358 relevance_from_server
,
362 formatted_url_(AutocompleteInput::FormattedStringWithEquivalentMeaning(
364 provider
.StringForURLDisplay(url
, true, false))),
365 description_(description
) {
366 DCHECK(url_
.is_valid());
367 CalculateAndClassifyMatchContents(true, input_text
, languages
);
370 BaseSearchProvider::NavigationResult::~NavigationResult() {}
372 void BaseSearchProvider::NavigationResult::CalculateAndClassifyMatchContents(
373 const bool allow_bolding_nothing
,
374 const base::string16
& input_text
,
375 const std::string
& languages
) {
376 if (input_text
.empty()) {
377 // In case of zero-suggest results, do not highlight matches.
378 match_contents_class_
.push_back(
379 ACMatchClassification(0, ACMatchClassification::NONE
));
383 // First look for the user's input inside the formatted url as it would be
384 // without trimming the scheme, so we can find matches at the beginning of the
386 const URLPrefix
* prefix
=
387 URLPrefix::BestURLPrefix(formatted_url_
, input_text
);
388 size_t match_start
= (prefix
== NULL
) ?
389 formatted_url_
.find(input_text
) : prefix
->prefix
.length();
390 bool trim_http
= !AutocompleteInput::HasHTTPScheme(input_text
) &&
391 (!prefix
|| (match_start
!= 0));
392 const net::FormatUrlTypes format_types
=
393 net::kFormatUrlOmitAll
& ~(trim_http
? 0 : net::kFormatUrlOmitHTTP
);
395 base::string16 match_contents
= net::FormatUrl(url_
, languages
, format_types
,
396 net::UnescapeRule::SPACES
, NULL
, NULL
, &match_start
);
397 // If the first match in the untrimmed string was inside a scheme that we
398 // trimmed, look for a subsequent match.
399 if (match_start
== base::string16::npos
)
400 match_start
= match_contents
.find(input_text
);
401 // Update |match_contents_| and |match_contents_class_| if it's allowed.
402 if (allow_bolding_nothing
|| (match_start
!= base::string16::npos
)) {
403 match_contents_
= match_contents
;
404 // Safe if |match_start| is npos; also safe if the input is longer than the
405 // remaining contents after |match_start|.
406 AutocompleteMatch::ClassifyLocationInString(match_start
,
407 input_text
.length(), match_contents_
.length(),
408 ACMatchClassification::URL
, &match_contents_class_
);
412 bool BaseSearchProvider::NavigationResult::IsInlineable(
413 const base::string16
& input
) const {
415 URLPrefix::BestURLPrefix(base::UTF8ToUTF16(url_
.spec()), input
) != NULL
;
418 int BaseSearchProvider::NavigationResult::CalculateRelevance(
419 const AutocompleteInput
& input
,
420 bool keyword_provider_requested
) const {
421 return (from_keyword_provider_
|| !keyword_provider_requested
) ? 800 : 150;
424 // BaseSearchProvider::Results -------------------------------------------------
426 BaseSearchProvider::Results::Results() : verbatim_relevance(-1) {}
428 BaseSearchProvider::Results::~Results() {}
430 void BaseSearchProvider::Results::Clear() {
431 suggest_results
.clear();
432 navigation_results
.clear();
433 verbatim_relevance
= -1;
437 bool BaseSearchProvider::Results::HasServerProvidedScores() const {
438 if (verbatim_relevance
>= 0)
441 // Right now either all results of one type will be server-scored or they will
442 // all be locally scored, but in case we change this later, we'll just check
444 for (SuggestResults::const_iterator
i(suggest_results
.begin());
445 i
!= suggest_results
.end(); ++i
) {
446 if (i
->relevance_from_server())
449 for (NavigationResults::const_iterator
i(navigation_results
.begin());
450 i
!= navigation_results
.end(); ++i
) {
451 if (i
->relevance_from_server())
458 void BaseSearchProvider::SetDeletionURL(const std::string
& deletion_url
,
459 AutocompleteMatch
* match
) {
460 if (deletion_url
.empty())
462 TemplateURLService
* template_service
=
463 TemplateURLServiceFactory::GetForProfile(profile_
);
464 if (!template_service
)
466 GURL url
= TemplateURLService::GenerateSearchURL(
467 template_service
->GetDefaultSearchProvider());
468 url
= url
.GetOrigin().Resolve(deletion_url
);
469 if (url
.is_valid()) {
470 match
->RecordAdditionalInfo(BaseSearchProvider::kDeletionUrlKey
,
472 match
->deletable
= true;
476 // BaseSearchProvider ---------------------------------------------------------
479 AutocompleteMatch
BaseSearchProvider::CreateSearchSuggestion(
480 AutocompleteProvider
* autocomplete_provider
,
481 const AutocompleteInput
& input
,
482 const SuggestResult
& suggestion
,
483 const TemplateURL
* template_url
,
484 int accepted_suggestion
,
485 int omnibox_start_margin
,
486 bool append_extra_query_params
,
487 bool from_app_list
) {
488 AutocompleteMatch
match(autocomplete_provider
, suggestion
.relevance(), false,
493 match
.keyword
= template_url
->keyword();
494 match
.contents
= suggestion
.match_contents();
495 match
.contents_class
= suggestion
.match_contents_class();
496 match
.answer_contents
= suggestion
.answer_contents();
497 match
.answer_type
= suggestion
.answer_type();
498 if (suggestion
.type() == AutocompleteMatchType::SEARCH_SUGGEST_INFINITE
) {
499 match
.RecordAdditionalInfo(
500 kACMatchPropertyInputText
, base::UTF16ToUTF8(input
.text()));
501 match
.RecordAdditionalInfo(
502 kACMatchPropertyContentsPrefix
,
503 base::UTF16ToUTF8(suggestion
.match_contents_prefix()));
504 match
.RecordAdditionalInfo(
505 kACMatchPropertyContentsStartIndex
,
507 suggestion
.suggestion().length() - match
.contents
.length()));
510 if (!suggestion
.annotation().empty())
511 match
.description
= suggestion
.annotation();
513 // suggestion.match_contents() should have already been collapsed.
514 match
.allowed_to_be_default_match
=
515 (base::CollapseWhitespace(input
.text(), false) ==
516 suggestion
.match_contents());
518 // When the user forced a query, we need to make sure all the fill_into_edit
519 // values preserve that property. Otherwise, if the user starts editing a
520 // suggestion, non-Search results will suddenly appear.
521 if (input
.type() == AutocompleteInput::FORCED_QUERY
)
522 match
.fill_into_edit
.assign(base::ASCIIToUTF16("?"));
523 if (suggestion
.from_keyword_provider())
524 match
.fill_into_edit
.append(match
.keyword
+ base::char16(' '));
525 if (!input
.prevent_inline_autocomplete() &&
526 StartsWith(suggestion
.suggestion(), input
.text(), false)) {
527 match
.inline_autocompletion
=
528 suggestion
.suggestion().substr(input
.text().length());
529 match
.allowed_to_be_default_match
= true;
531 match
.fill_into_edit
.append(suggestion
.suggestion());
533 const TemplateURLRef
& search_url
= template_url
->url_ref();
534 DCHECK(search_url
.SupportsReplacement());
535 match
.search_terms_args
.reset(
536 new TemplateURLRef::SearchTermsArgs(suggestion
.suggestion()));
537 match
.search_terms_args
->original_query
= input
.text();
538 match
.search_terms_args
->accepted_suggestion
= accepted_suggestion
;
539 match
.search_terms_args
->omnibox_start_margin
= omnibox_start_margin
;
540 match
.search_terms_args
->suggest_query_params
=
541 suggestion
.suggest_query_params();
542 match
.search_terms_args
->append_extra_query_params
=
543 append_extra_query_params
;
544 match
.search_terms_args
->from_app_list
= from_app_list
;
545 // This is the destination URL sans assisted query stats. This must be set
546 // so the AutocompleteController can properly de-dupe; the controller will
547 // eventually overwrite it before it reaches the user.
548 match
.destination_url
=
549 GURL(search_url
.ReplaceSearchTerms(*match
.search_terms_args
.get()));
551 // Search results don't look like URLs.
552 match
.transition
= suggestion
.from_keyword_provider() ?
553 content::PAGE_TRANSITION_KEYWORD
: content::PAGE_TRANSITION_GENERATED
;
559 scoped_ptr
<base::Value
> BaseSearchProvider::DeserializeJsonData(
560 std::string json_data
) {
561 // The JSON response should be an array.
562 for (size_t response_start_index
= json_data
.find("["), i
= 0;
563 response_start_index
!= std::string::npos
&& i
< 5;
564 response_start_index
= json_data
.find("[", 1), i
++) {
565 // Remove any XSSI guards to allow for JSON parsing.
566 if (response_start_index
> 0)
567 json_data
.erase(0, response_start_index
);
569 JSONStringValueSerializer
deserializer(json_data
);
570 deserializer
.set_allow_trailing_comma(true);
572 scoped_ptr
<base::Value
> data(deserializer
.Deserialize(&error_code
, NULL
));
576 return scoped_ptr
<base::Value
>();
580 bool BaseSearchProvider::ZeroSuggestEnabled(
581 const GURL
& suggest_url
,
582 const TemplateURL
* template_url
,
583 AutocompleteInput::PageClassification page_classification
,
585 if (!OmniboxFieldTrial::InZeroSuggestFieldTrial())
588 // Make sure we are sending the suggest request through HTTPS to prevent
589 // exposing the current page URL or personalized results without encryption.
590 if (!suggest_url
.SchemeIs(url::kHttpsScheme
))
593 // Don't show zero suggest on the NTP.
594 // TODO(hfung): Experiment with showing MostVisited zero suggest on NTP
595 // under the conditions described in crbug.com/305366.
596 if ((page_classification
==
597 AutocompleteInput::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS
) ||
598 (page_classification
==
599 AutocompleteInput::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS
))
602 // Don't run if there's no profile or in incognito mode.
603 if (profile
== NULL
|| profile
->IsOffTheRecord())
606 // Don't run if we can't get preferences or search suggest is not enabled.
607 PrefService
* prefs
= profile
->GetPrefs();
608 if (!prefs
->GetBoolean(prefs::kSearchSuggestEnabled
))
611 // Only make the request if we know that the provider supports zero suggest
612 // (currently only the prepopulated Google provider).
613 if (template_url
== NULL
|| !template_url
->SupportsReplacement() ||
614 TemplateURLPrepopulateData::GetEngineType(*template_url
) !=
615 SEARCH_ENGINE_GOOGLE
)
622 bool BaseSearchProvider::CanSendURL(
623 const GURL
& current_page_url
,
624 const GURL
& suggest_url
,
625 const TemplateURL
* template_url
,
626 AutocompleteInput::PageClassification page_classification
,
628 if (!ZeroSuggestEnabled(suggest_url
, template_url
, page_classification
,
632 if (!current_page_url
.is_valid())
635 // Only allow HTTP URLs or HTTPS URLs for the same domain as the search
637 if ((current_page_url
.scheme() != url::kHttpScheme
) &&
638 ((current_page_url
.scheme() != url::kHttpsScheme
) ||
639 !net::registry_controlled_domains::SameDomainOrHost(
640 current_page_url
, suggest_url
,
641 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES
)))
644 // Check field trials and settings allow sending the URL on suggest requests.
645 ProfileSyncService
* service
=
646 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile
);
647 sync_driver::SyncPrefs
sync_prefs(profile
->GetPrefs());
648 if (service
== NULL
||
649 !service
->IsSyncEnabledAndLoggedIn() ||
650 !sync_prefs
.GetPreferredDataTypes(syncer::UserTypes()).Has(
651 syncer::PROXY_TABS
) ||
652 service
->GetEncryptedDataTypes().Has(syncer::SESSIONS
))
658 void BaseSearchProvider::OnURLFetchComplete(const net::URLFetcher
* source
) {
660 suggest_results_pending_
--;
661 DCHECK_GE(suggest_results_pending_
, 0); // Should never go negative.
663 const bool is_keyword
= IsKeywordFetcher(source
);
665 // Ensure the request succeeded and that the provider used is still available.
666 // A verbatim match cannot be generated without this provider, causing errors.
667 const bool request_succeeded
=
668 source
->GetStatus().is_success() && (source
->GetResponseCode() == 200) &&
669 GetTemplateURL(is_keyword
);
671 LogFetchComplete(request_succeeded
, is_keyword
);
673 bool results_updated
= false;
674 if (request_succeeded
) {
675 const net::HttpResponseHeaders
* const response_headers
=
676 source
->GetResponseHeaders();
677 std::string json_data
;
678 source
->GetResponseAsString(&json_data
);
680 // JSON is supposed to be UTF-8, but some suggest service providers send
681 // JSON files in non-UTF-8 encodings. The actual encoding is usually
682 // specified in the Content-Type header field.
683 if (response_headers
) {
685 if (response_headers
->GetCharset(&charset
)) {
686 base::string16 data_16
;
687 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
688 if (base::CodepageToUTF16(json_data
, charset
.c_str(),
689 base::OnStringConversionError::FAIL
,
691 json_data
= base::UTF16ToUTF8(data_16
);
695 scoped_ptr
<base::Value
> data(DeserializeJsonData(json_data
));
696 if (data
&& StoreSuggestionResponse(json_data
, *data
.get()))
699 results_updated
= data
.get() && ParseSuggestResults(
700 *data
.get(), is_keyword
, GetResultsToFill(is_keyword
));
704 if (done_
|| results_updated
)
705 listener_
->OnProviderUpdate(results_updated
);
708 void BaseSearchProvider::AddMatchToMap(const SuggestResult
& result
,
709 const std::string
& metadata
,
710 int accepted_suggestion
,
711 bool mark_as_deletable
,
713 InstantService
* instant_service
=
714 InstantServiceFactory::GetForProfile(profile_
);
715 // Android and iOS have no InstantService.
716 const int omnibox_start_margin
= instant_service
?
717 instant_service
->omnibox_start_margin() : chrome::kDisableStartMargin
;
719 AutocompleteMatch match
= CreateSearchSuggestion(
720 this, GetInput(result
.from_keyword_provider()), result
,
721 GetTemplateURL(result
.from_keyword_provider()), accepted_suggestion
,
722 omnibox_start_margin
, ShouldAppendExtraParams(result
),
724 if (!match
.destination_url
.is_valid())
726 match
.search_terms_args
->bookmark_bar_pinned
=
727 profile_
->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar
);
728 match
.RecordAdditionalInfo(kRelevanceFromServerKey
,
729 result
.relevance_from_server() ? kTrue
: kFalse
);
730 match
.RecordAdditionalInfo(kShouldPrefetchKey
,
731 result
.should_prefetch() ? kTrue
: kFalse
);
732 SetDeletionURL(result
.deletion_url(), &match
);
733 if (mark_as_deletable
)
734 match
.deletable
= true;
735 // Metadata is needed only for prefetching queries.
736 if (result
.should_prefetch())
737 match
.RecordAdditionalInfo(kSuggestMetadataKey
, metadata
);
739 // Try to add |match| to |map|. If a match for this suggestion is
740 // already in |map|, replace it if |match| is more relevant.
741 // NOTE: Keep this ToLower() call in sync with url_database.cc.
743 std::make_pair(base::i18n::ToLower(result
.suggestion()),
744 match
.search_terms_args
->suggest_query_params
));
745 const std::pair
<MatchMap::iterator
, bool> i(
746 map
->insert(std::make_pair(match_key
, match
)));
748 bool should_prefetch
= result
.should_prefetch();
750 // NOTE: We purposefully do a direct relevance comparison here instead of
751 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items
752 // added first" rather than "items alphabetically first" when the scores
753 // are equal. The only case this matters is when a user has results with
754 // the same score that differ only by capitalization; because the history
755 // system returns results sorted by recency, this means we'll pick the most
756 // recent such result even if the precision of our relevance score is too
757 // low to distinguish the two.
758 if (match
.relevance
> i
.first
->second
.relevance
) {
759 match
.duplicate_matches
.insert(match
.duplicate_matches
.end(),
760 i
.first
->second
.duplicate_matches
.begin(),
761 i
.first
->second
.duplicate_matches
.end());
762 i
.first
->second
.duplicate_matches
.clear();
763 match
.duplicate_matches
.push_back(i
.first
->second
);
764 i
.first
->second
= match
;
766 i
.first
->second
.duplicate_matches
.push_back(match
);
767 if (match
.keyword
== i
.first
->second
.keyword
) {
768 // Old and new matches are from the same search provider. It is okay to
769 // record one match's prefetch data onto a different match (for the same
770 // query string) for the following reasons:
771 // 1. Because the suggest server only sends down a query string from
772 // which we construct a URL, rather than sending a full URL, and because
773 // we construct URLs from query strings in the same way every time, the
774 // URLs for the two matches will be the same. Therefore, we won't end up
775 // prefetching something the server didn't intend.
776 // 2. Presumably the server sets the prefetch bit on a match it things
777 // is sufficiently relevant that the user is likely to choose it.
778 // Surely setting the prefetch bit on a match of even higher relevance
779 // won't violate this assumption.
780 should_prefetch
|= ShouldPrefetch(i
.first
->second
);
781 i
.first
->second
.RecordAdditionalInfo(kShouldPrefetchKey
,
782 should_prefetch
? kTrue
: kFalse
);
784 i
.first
->second
.RecordAdditionalInfo(kSuggestMetadataKey
, metadata
);
790 bool BaseSearchProvider::ParseSuggestResults(const base::Value
& root_val
,
791 bool is_keyword_result
,
793 base::string16 query
;
794 const base::ListValue
* root_list
= NULL
;
795 const base::ListValue
* results_list
= NULL
;
796 const AutocompleteInput
& input
= GetInput(is_keyword_result
);
798 if (!root_val
.GetAsList(&root_list
) || !root_list
->GetString(0, &query
) ||
799 query
!= input
.text() || !root_list
->GetList(1, &results_list
))
802 // 3rd element: Description list.
803 const base::ListValue
* descriptions
= NULL
;
804 root_list
->GetList(2, &descriptions
);
806 // 4th element: Disregard the query URL list for now.
808 // Reset suggested relevance information.
809 results
->verbatim_relevance
= -1;
811 // 5th element: Optional key-value pairs from the Suggest server.
812 const base::ListValue
* types
= NULL
;
813 const base::ListValue
* relevances
= NULL
;
814 const base::ListValue
* suggestion_details
= NULL
;
815 const base::DictionaryValue
* extras
= NULL
;
816 int prefetch_index
= -1;
817 if (root_list
->GetDictionary(4, &extras
)) {
818 extras
->GetList("google:suggesttype", &types
);
820 // Discard this list if its size does not match that of the suggestions.
821 if (extras
->GetList("google:suggestrelevance", &relevances
) &&
822 (relevances
->GetSize() != results_list
->GetSize()))
824 extras
->GetInteger("google:verbatimrelevance",
825 &results
->verbatim_relevance
);
827 // Check if the active suggest field trial (if any) has triggered either
828 // for the default provider or keyword provider.
829 bool triggered
= false;
830 extras
->GetBoolean("google:fieldtrialtriggered", &triggered
);
831 field_trial_triggered_
|= triggered
;
832 field_trial_triggered_in_session_
|= triggered
;
834 const base::DictionaryValue
* client_data
= NULL
;
835 if (extras
->GetDictionary("google:clientdata", &client_data
) && client_data
)
836 client_data
->GetInteger("phi", &prefetch_index
);
838 if (extras
->GetList("google:suggestdetail", &suggestion_details
) &&
839 suggestion_details
->GetSize() != results_list
->GetSize())
840 suggestion_details
= NULL
;
842 // Store the metadata that came with the response in case we need to pass it
843 // along with the prefetch query to Instant.
844 JSONStringValueSerializer
json_serializer(&results
->metadata
);
845 json_serializer
.Serialize(*extras
);
848 // Clear the previous results now that new results are available.
849 results
->suggest_results
.clear();
850 results
->navigation_results
.clear();
852 base::string16 suggestion
;
854 int relevance
= GetDefaultResultRelevance();
855 // Prohibit navsuggest in FORCED_QUERY mode. Users wants queries, not URLs.
856 const bool allow_navsuggest
= input
.type() != AutocompleteInput::FORCED_QUERY
;
857 const std::string
languages(
858 profile_
->GetPrefs()->GetString(prefs::kAcceptLanguages
));
859 const base::string16
& trimmed_input
=
860 base::CollapseWhitespace(input
.text(), false);
861 for (size_t index
= 0; results_list
->GetString(index
, &suggestion
); ++index
) {
862 // Google search may return empty suggestions for weird input characters,
863 // they make no sense at all and can cause problems in our code.
864 if (suggestion
.empty())
867 // Apply valid suggested relevance scores; discard invalid lists.
868 if (relevances
!= NULL
&& !relevances
->GetInteger(index
, &relevance
))
870 AutocompleteMatchType::Type match_type
=
871 AutocompleteMatchType::SEARCH_SUGGEST
;
872 if (types
&& types
->GetString(index
, &type
))
873 match_type
= GetAutocompleteMatchType(type
);
874 const base::DictionaryValue
* suggestion_detail
= NULL
;
875 std::string deletion_url
;
877 if (suggestion_details
&&
878 suggestion_details
->GetDictionary(index
, &suggestion_detail
))
879 suggestion_detail
->GetString("du", &deletion_url
);
881 if ((match_type
== AutocompleteMatchType::NAVSUGGEST
) ||
882 (match_type
== AutocompleteMatchType::NAVSUGGEST_PERSONALIZED
)) {
883 // Do not blindly trust the URL coming from the server to be valid.
884 GURL
url(URLFixerUpper::FixupURL(
885 base::UTF16ToUTF8(suggestion
), std::string()));
886 if (url
.is_valid() && allow_navsuggest
) {
887 base::string16 title
;
888 if (descriptions
!= NULL
)
889 descriptions
->GetString(index
, &title
);
890 results
->navigation_results
.push_back(NavigationResult(
891 *this, url
, match_type
, title
, deletion_url
, is_keyword_result
,
892 relevance
, relevances
!= NULL
, input
.text(), languages
));
895 base::string16 match_contents
= suggestion
;
896 base::string16 match_contents_prefix
;
897 base::string16 annotation
;
898 base::string16 answer_contents
;
899 base::string16 answer_type
;
900 std::string suggest_query_params
;
902 if (suggestion_details
) {
903 suggestion_details
->GetDictionary(index
, &suggestion_detail
);
904 if (suggestion_detail
) {
905 suggestion_detail
->GetString("t", &match_contents
);
906 suggestion_detail
->GetString("mp", &match_contents_prefix
);
907 // Error correction for bad data from server.
908 if (match_contents
.empty())
909 match_contents
= suggestion
;
910 suggestion_detail
->GetString("a", &annotation
);
911 suggestion_detail
->GetString("q", &suggest_query_params
);
913 // Extract Answers, if provided.
914 const base::DictionaryValue
* answer_json
= NULL
;
915 if (suggestion_detail
->GetDictionary("ansa", &answer_json
)) {
916 std::string contents
;
917 base::JSONWriter::Write(answer_json
, &contents
);
918 answer_contents
= base::UTF8ToUTF16(contents
);
919 suggestion_detail
->GetString("ansb", &answer_type
);
924 bool should_prefetch
= static_cast<int>(index
) == prefetch_index
;
925 // TODO(kochi): Improve calculator suggestion presentation.
926 results
->suggest_results
.push_back(SuggestResult(
927 base::CollapseWhitespace(suggestion
, false), match_type
,
928 base::CollapseWhitespace(match_contents
, false),
929 match_contents_prefix
, annotation
, answer_contents
, answer_type
,
930 suggest_query_params
, deletion_url
, is_keyword_result
, relevance
,
931 relevances
!= NULL
, should_prefetch
, trimmed_input
));
934 SortResults(is_keyword_result
, relevances
, results
);
938 void BaseSearchProvider::SortResults(bool is_keyword
,
939 const base::ListValue
* relevances
,
943 bool BaseSearchProvider::StoreSuggestionResponse(
944 const std::string
& json_data
,
945 const base::Value
& parsed_data
) {
949 void BaseSearchProvider::ModifyProviderInfo(
950 metrics::OmniboxEventProto_ProviderInfo
* provider_info
) const {
953 void BaseSearchProvider::DeleteMatchFromMatches(
954 const AutocompleteMatch
& match
) {
955 for (ACMatches::iterator
i(matches_
.begin()); i
!= matches_
.end(); ++i
) {
956 // Find the desired match to delete by checking the type and contents.
957 // We can't check the destination URL, because the autocomplete controller
958 // may have reformulated that. Not that while checking for matching
959 // contents works for personalized suggestions, if more match types gain
960 // deletion support, this algorithm may need to be re-examined.
961 if (i
->contents
== match
.contents
&& i
->type
== match
.type
) {
968 void BaseSearchProvider::OnDeletionComplete(
969 bool success
, SuggestionDeletionHandler
* handler
) {
970 RecordDeletionResult(success
);
971 SuggestionDeletionHandlers::iterator it
= std::find(
972 deletion_handlers_
.begin(), deletion_handlers_
.end(), handler
);
973 DCHECK(it
!= deletion_handlers_
.end());
974 deletion_handlers_
.erase(it
);