Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(968)

Side by Side Diff: chrome/browser/autocomplete/base_search_provider.cc

Issue 131433003: Refactor search and zero suggest providers to use common base class. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: More style + zero-suggest logic fixes Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 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.
4
5 #include "chrome/browser/autocomplete/base_search_provider.h"
6
7 #include <algorithm>
8
9 #include "base/callback.h"
10 #include "base/i18n/case_conversion.h"
11 #include "base/i18n/icu_string_conversions.h"
12 #include "base/json/json_string_value_serializer.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string16.h"
15 #include "base/strings/string_util.h"
16 #include "base/strings/utf_string_conversions.h"
17 #include "chrome/browser/autocomplete/autocomplete_input.h"
18 #include "chrome/browser/autocomplete/autocomplete_match.h"
19 #include "chrome/browser/autocomplete/autocomplete_provider.h"
20 #include "chrome/browser/autocomplete/url_prefix.h"
21 #include "chrome/browser/history/history_service.h"
22 #include "chrome/browser/history/history_service_factory.h"
23 #include "chrome/browser/omnibox/omnibox_field_trial.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/search/search.h"
26 #include "chrome/browser/search_engines/template_url.h"
27 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
28 #include "chrome/browser/sync/profile_sync_service.h"
29 #include "chrome/browser/sync/profile_sync_service_factory.h"
30 #include "chrome/browser/sync/sync_prefs.h"
31 #include "chrome/browser/ui/browser.h"
32 #include "chrome/browser/ui/browser_finder.h"
33 #include "chrome/browser/ui/browser_instant_controller.h"
34 #include "chrome/browser/ui/host_desktop.h"
35 #include "chrome/common/autocomplete_match_type.h"
36 #include "chrome/common/net/url_fixer_upper.h"
37 #include "chrome/common/pref_names.h"
38 #include "chrome/common/url_constants.h"
39 #include "net/base/net_util.h"
40 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
41 #include "net/http/http_response_headers.h"
42 #include "net/url_request/url_fetcher.h"
43 #include "net/url_request/url_fetcher_delegate.h"
44 #include "net/url_request/url_request_status.h"
45
46 namespace {
47 AutocompleteMatchType::Type GetAutocompleteMatchType(const std::string& type) {
48 if (type == "ENTITY")
49 return AutocompleteMatchType::SEARCH_SUGGEST_ENTITY;
50 if (type == "INFINITE")
51 return AutocompleteMatchType::SEARCH_SUGGEST_INFINITE;
52 if (type == "PERSONALIZED")
53 return AutocompleteMatchType::SEARCH_SUGGEST_PERSONALIZED;
54 if (type == "PROFILE")
55 return AutocompleteMatchType::SEARCH_SUGGEST_PROFILE;
56 return AutocompleteMatchType::SEARCH_SUGGEST;
57 }
58 } // namespace
59
60 // SuggestionDeletionHandler --------------------------------------------------
61
62 // This class handles making requests to the server in order to delete
63 // personalized suggestions.
64 class SuggestionDeletionHandler : public net::URLFetcherDelegate {
65 public:
66 typedef base::Callback<void(bool, SuggestionDeletionHandler*)>
67 DeletionCompletedCallback;
68
69 SuggestionDeletionHandler(const std::string& deletion_url,
70 Profile* profile,
71 const DeletionCompletedCallback& callback);
72
73 virtual ~SuggestionDeletionHandler();
74
75 private:
76 // net::URLFetcherDelegate:
77 virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE;
78
79 scoped_ptr<net::URLFetcher> deletion_fetcher_;
80 DeletionCompletedCallback callback_;
81
82 DISALLOW_COPY_AND_ASSIGN(SuggestionDeletionHandler);
83 };
84
85 SuggestionDeletionHandler::SuggestionDeletionHandler(
86 const std::string& deletion_url,
87 Profile* profile,
88 const DeletionCompletedCallback& callback)
89 : callback_(callback) {
90 GURL url(deletion_url);
91 DCHECK(url.is_valid());
92
93 deletion_fetcher_.reset(
94 net::URLFetcher::Create(BaseSearchProvider::kDeletionURLFetcherID,
95 url,
96 net::URLFetcher::GET,
97 this));
98 deletion_fetcher_->SetRequestContext(profile->GetRequestContext());
99 deletion_fetcher_->Start();
100 };
101
102 SuggestionDeletionHandler::~SuggestionDeletionHandler() {}
103
104 void SuggestionDeletionHandler::OnURLFetchComplete(
105 const net::URLFetcher* source) {
106 DCHECK(source == deletion_fetcher_.get());
107 callback_.Run(
108 source->GetStatus().is_success() && (source->GetResponseCode() == 200),
109 this);
110 };
111
112 // BaseSearchProvider ---------------------------------------------------------
113
114 // Arbitrary ID needed for testing, set to 7 as to not conflict with any
115 // additions to SearchProvider.
116 const int BaseSearchProvider::kDeletionURLFetcherID = 7;
117
118 BaseSearchProvider::BaseSearchProvider(AutocompleteProviderListener* listener,
119 Profile* profile,
120 AutocompleteProvider::Type type)
121 : AutocompleteProvider(listener, profile, type),
122 field_trial_triggered_(false),
123 field_trial_triggered_in_session_(false),
124 suggest_results_pending_(0) {}
125
126 void BaseSearchProvider::Stop(bool clear_cached_results) {
127 StopSuggest();
128 done_ = true;
129
130 if (clear_cached_results)
131 ClearAllResults();
132 }
133
134 void BaseSearchProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
135 provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
136 metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
137 new_entry.set_provider(AsOmniboxEventProviderType());
138 new_entry.set_provider_done(done_);
139 std::vector<uint32> field_trial_hashes;
140 OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(&field_trial_hashes);
141 for (size_t i = 0; i < field_trial_hashes.size(); ++i) {
142 if (field_trial_triggered_)
143 new_entry.mutable_field_trial_triggered()->Add(field_trial_hashes[i]);
144 if (field_trial_triggered_in_session_) {
145 new_entry.mutable_field_trial_triggered_in_session()->Add(
146 field_trial_hashes[i]);
147 }
148 }
149 }
150
151 void BaseSearchProvider::DeleteMatch(const AutocompleteMatch& match) {
152 DCHECK(match.deletable);
153
154 deletion_handlers_.push_back(new SuggestionDeletionHandler(
155 match.GetAdditionalInfo(BaseSearchProvider::kDeletionUrlKey),
156 profile_,
157 base::Bind(&BaseSearchProvider::OnDeletionComplete,
158 base::Unretained(this))));
159
160 HistoryService* const history_service =
161 HistoryServiceFactory::GetForProfile(profile_, Profile::EXPLICIT_ACCESS);
162 TemplateURL* template_url = match.GetTemplateURL(profile_, false);
163 // This may be NULL if the template corresponding to the keyword has been
164 // deleted or there is no keyword set.
165 if (template_url != NULL) {
166 history_service->DeleteMatchingURLsForKeyword(template_url->id(),
167 match.contents);
168 }
169
170 // Immediately update the list of matches to show the match was deleted,
171 // regardless of whether the server request actually succeeds.
172 DeleteMatchFromMatches(match);
173 }
174
175 void BaseSearchProvider::ResetSession() {}
176
177 // static
178 bool BaseSearchProvider::ShouldPrefetch(const AutocompleteMatch& match) {
179 return match.GetAdditionalInfo(kShouldPrefetchKey) == kTrue;
180 }
181
182 // BaseSearchProvider::Result -------------------------------------------------
183
184 BaseSearchProvider::Result::Result(bool from_keyword_provider,
185 int relevance,
186 bool relevance_from_server)
187 : from_keyword_provider_(from_keyword_provider),
188 relevance_(relevance),
189 relevance_from_server_(relevance_from_server) {}
190
191 BaseSearchProvider::Result::~Result() {}
192
193 // BaseSearchProvider::SuggestResult ------------------------------------------
194
195 BaseSearchProvider::SuggestResult::SuggestResult(
196 const base::string16& suggestion,
197 AutocompleteMatchType::Type type,
198 const base::string16& match_contents,
199 const base::string16& annotation,
200 const std::string& suggest_query_params,
201 const std::string& deletion_url,
202 bool from_keyword_provider,
203 int relevance,
204 bool relevance_from_server,
205 bool should_prefetch,
206 const base::string16& input_text)
207 : Result(from_keyword_provider, relevance, relevance_from_server),
208 suggestion_(suggestion),
209 type_(type),
210 annotation_(annotation),
211 suggest_query_params_(suggest_query_params),
212 deletion_url_(deletion_url),
213 should_prefetch_(should_prefetch) {
214 match_contents_ = match_contents;
215 DCHECK(!match_contents_.empty());
216 ClassifyMatchContents(true, input_text);
217 }
218
219 BaseSearchProvider::SuggestResult::~SuggestResult() {}
220
221 void BaseSearchProvider::SuggestResult::ClassifyMatchContents(
222 const bool allow_bolding_all,
223 const base::string16& input_text) {
224 size_t input_position = match_contents_.find(input_text);
225 if (!allow_bolding_all && (input_position == base::string16::npos)) {
226 // Bail if the code below to update the bolding would bold the whole
227 // string. Note that the string may already be entirely bolded; if
228 // so, leave it as is.
229 return;
230 }
231 match_contents_class_.clear();
232 // We do intra-string highlighting for suggestions - the suggested segment
233 // will be highlighted, e.g. for input_text = "you" the suggestion may be
234 // "youtube", so we'll bold the "tube" section: you*tube*.
235 if (input_text != match_contents_) {
236 if (input_position == base::string16::npos) {
237 // The input text is not a substring of the query string, e.g. input
238 // text is "slasdot" and the query string is "slashdot", so we bold the
239 // whole thing.
240 match_contents_class_.push_back(
241 ACMatchClassification(0, ACMatchClassification::MATCH));
242 } else {
243 // We don't iterate over the string here annotating all matches because
244 // it looks odd to have every occurrence of a substring that may be as
245 // short as a single character highlighted in a query suggestion result,
246 // e.g. for input text "s" and query string "southwest airlines", it
247 // looks odd if both the first and last s are highlighted.
248 if (input_position != 0) {
249 match_contents_class_.push_back(
250 ACMatchClassification(0, ACMatchClassification::MATCH));
251 }
252 match_contents_class_.push_back(
253 ACMatchClassification(input_position, ACMatchClassification::NONE));
254 size_t next_fragment_position = input_position + input_text.length();
255 if (next_fragment_position < match_contents_.length()) {
256 match_contents_class_.push_back(ACMatchClassification(
257 next_fragment_position, ACMatchClassification::MATCH));
258 }
259 }
260 } else {
261 // Otherwise, match_contents_ is a verbatim (what-you-typed) match, either
262 // for the default provider or a keyword search provider.
263 match_contents_class_.push_back(
264 ACMatchClassification(0, ACMatchClassification::NONE));
265 }
266 }
267
268 bool BaseSearchProvider::SuggestResult::IsInlineable(
269 const base::string16& input) const {
270 return StartsWith(suggestion_, input, false);
271 }
272
273 int BaseSearchProvider::SuggestResult::CalculateRelevance(
274 const AutocompleteInput& input,
275 bool keyword_provider_requested) const {
276 if (!from_keyword_provider_ && keyword_provider_requested)
277 return 100;
278 return ((input.type() == AutocompleteInput::URL) ? 300 : 600);
279 }
280
281 // BaseSearchProvider::NavigationResult ---------------------------------------
282
283 BaseSearchProvider::NavigationResult::NavigationResult(
284 const AutocompleteProvider& provider,
285 const GURL& url,
286 const base::string16& description,
287 bool from_keyword_provider,
288 int relevance,
289 bool relevance_from_server,
290 const base::string16& input_text,
291 const std::string& languages)
292 : Result(from_keyword_provider, relevance, relevance_from_server),
293 url_(url),
294 formatted_url_(AutocompleteInput::FormattedStringWithEquivalentMeaning(
295 url,
296 provider.StringForURLDisplay(url, true, false))),
297 description_(description) {
298 DCHECK(url_.is_valid());
299 CalculateAndClassifyMatchContents(true, input_text, languages);
300 }
301
302 BaseSearchProvider::NavigationResult::~NavigationResult() {}
303
304 void BaseSearchProvider::NavigationResult::CalculateAndClassifyMatchContents(
305 const bool allow_bolding_nothing,
306 const base::string16& input_text,
307 const std::string& languages) {
308 // First look for the user's input inside the formatted url as it would be
309 // without trimming the scheme, so we can find matches at the beginning of
310 // the scheme.
311 const URLPrefix* prefix =
312 URLPrefix::BestURLPrefix(formatted_url_, input_text);
313 size_t match_start = (prefix == NULL) ? formatted_url_.find(input_text)
314 : prefix->prefix.length();
315 bool trim_http = !AutocompleteInput::HasHTTPScheme(input_text) &&
316 (!prefix || (match_start != 0));
317 const net::FormatUrlTypes format_types =
318 net::kFormatUrlOmitAll & ~(trim_http ? 0 : net::kFormatUrlOmitHTTP);
319
320 base::string16 match_contents = net::FormatUrl(url_,
321 languages,
322 format_types,
323 net::UnescapeRule::SPACES,
324 NULL,
325 NULL,
326 &match_start);
327 // If the first match in the untrimmed string was inside a scheme that we
328 // trimmed, look for a subsequent match.
329 if (match_start == base::string16::npos)
330 match_start = match_contents.find(input_text);
331 // Update |match_contents_| and |match_contents_class_| if it's allowed.
332 if (allow_bolding_nothing || (match_start != base::string16::npos)) {
333 match_contents_ = match_contents;
334 // Safe if |match_start| is npos; also safe if the input is longer than the
335 // remaining contents after |match_start|.
336 AutocompleteMatch::ClassifyLocationInString(match_start,
337 input_text.length(),
338 match_contents_.length(),
339 ACMatchClassification::URL,
340 &match_contents_class_);
341 }
342 }
343
344 bool BaseSearchProvider::NavigationResult::IsInlineable(
345 const base::string16& input) const {
346 return URLPrefix::BestURLPrefix(formatted_url_, input) != NULL;
347 }
348
349 int BaseSearchProvider::NavigationResult::CalculateRelevance(
350 const AutocompleteInput& input,
351 bool keyword_provider_requested) const {
352 return (from_keyword_provider_ || !keyword_provider_requested) ? 800 : 150;
353 }
354
355 // BaseSearchProvider::Results ------------------------------------------------
356
357 BaseSearchProvider::Results::Results() : verbatim_relevance(-1) {}
358
359 BaseSearchProvider::Results::~Results() {}
360
361 void BaseSearchProvider::Results::Clear() {
362 suggest_results.clear();
363 navigation_results.clear();
364 verbatim_relevance = -1;
365 metadata.clear();
366 }
367
368 bool BaseSearchProvider::Results::HasServerProvidedScores() const {
369 if (verbatim_relevance >= 0)
370 return true;
371
372 // Right now either all results of one type will be server-scored or they
373 // will all be locally scored, but in case we change this later, we'll just
374 // check them all.
375 for (SuggestResults::const_iterator i(suggest_results.begin());
376 i != suggest_results.end();
377 ++i) {
378 if (i->relevance_from_server())
379 return true;
380 }
381 for (NavigationResults::const_iterator i(navigation_results.begin());
382 i != navigation_results.end();
383 ++i) {
384 if (i->relevance_from_server())
385 return true;
386 }
387
388 return false;
389 }
390
391 // BaseSearchProvider ---------------------------------------------------------
392
393 const char BaseSearchProvider::kDeletionUrlKey[] = "deletion_url";
394 const char BaseSearchProvider::kRelevanceFromServerKey[] =
395 "relevance_from_server";
396 const char BaseSearchProvider::kShouldPrefetchKey[] = "should_prefetch";
397 const char BaseSearchProvider::kSuggestMetadataKey[] = "suggest_metadata";
398 const char BaseSearchProvider::kTrue[] = "true";
399 const char BaseSearchProvider::kFalse[] = "false";
400
401 BaseSearchProvider::~BaseSearchProvider() {}
402
403 // static
404 AutocompleteMatch BaseSearchProvider::CreateSearchSuggestion(
405 AutocompleteProvider* autocomplete_provider,
406 const AutocompleteInput& input,
407 const base::string16& input_text,
408 const SuggestResult& suggestion,
409 const TemplateURL* template_url,
410 int accepted_suggestion,
411 int omnibox_start_margin,
412 bool append_extra_query_params) {
413 AutocompleteMatch match(
414 autocomplete_provider, suggestion.relevance(), false, suggestion.type());
415
416 if (!template_url)
417 return match;
418 match.keyword = template_url->keyword();
419 match.contents = suggestion.match_contents();
420 match.contents_class = suggestion.match_contents_class();
421
422 if (!suggestion.annotation().empty())
423 match.description = suggestion.annotation();
424
425 match.allowed_to_be_default_match =
426 (input_text == suggestion.match_contents());
427
428 // When the user forced a query, we need to make sure all the fill_into_edit
429 // values preserve that property. Otherwise, if the user starts editing a
430 // suggestion, non-Search results will suddenly appear.
431 if (input.type() == AutocompleteInput::FORCED_QUERY)
432 match.fill_into_edit.assign(base::ASCIIToUTF16("?"));
433 if (suggestion.from_keyword_provider())
434 match.fill_into_edit.append(match.keyword + base::char16(' '));
435 if (!input.prevent_inline_autocomplete() &&
436 StartsWith(suggestion.suggestion(), input_text, false)) {
437 match.inline_autocompletion =
438 suggestion.suggestion().substr(input_text.length());
439 match.allowed_to_be_default_match = true;
440 }
441 match.fill_into_edit.append(suggestion.suggestion());
442
443 const TemplateURLRef& search_url = template_url->url_ref();
444 DCHECK(search_url.SupportsReplacement());
445 match.search_terms_args.reset(
446 new TemplateURLRef::SearchTermsArgs(suggestion.suggestion()));
447 match.search_terms_args->original_query = input_text;
448 match.search_terms_args->accepted_suggestion = accepted_suggestion;
449 match.search_terms_args->omnibox_start_margin = omnibox_start_margin;
450 match.search_terms_args->suggest_query_params =
451 suggestion.suggest_query_params();
452 match.search_terms_args->append_extra_query_params =
453 append_extra_query_params;
454 // This is the destination URL sans assisted query stats. This must be set
455 // so the AutocompleteController can properly de-dupe; the controller will
456 // eventually overwrite it before it reaches the user.
457 match.destination_url =
458 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get()));
459
460 // Search results don't look like URLs.
461 match.transition = suggestion.from_keyword_provider()
462 ? content::PAGE_TRANSITION_KEYWORD
463 : content::PAGE_TRANSITION_GENERATED;
464
465 return match;
466 }
467
468 // static
469 bool BaseSearchProvider::CanSendURL(
470 const GURL& current_page_url,
471 const GURL& suggest_url,
472 const TemplateURL* template_url,
473 AutocompleteInput::PageClassification page_classification,
474 Profile* profile) {
475 if (!current_page_url.is_valid())
476 return false;
477
478 // TODO(hfung): Show Most Visited on NTP with appropriate verbatim
479 // description when the user actively focuses on the omnibox as discussed in
480 // crbug/305366 if Most Visited (or something similar) will launch.
481 if ((page_classification ==
482 AutocompleteInput::INSTANT_NTP_WITH_FAKEBOX_AS_STARTING_FOCUS) ||
483 (page_classification ==
484 AutocompleteInput::INSTANT_NTP_WITH_OMNIBOX_AS_STARTING_FOCUS))
485 return false;
486
487 // Only allow HTTP URLs or HTTPS URLs for the same domain as the search
488 // provider.
489 if ((current_page_url.scheme() != content::kHttpScheme) &&
490 ((current_page_url.scheme() != content::kHttpsScheme) ||
491 !net::registry_controlled_domains::SameDomainOrHost(
492 current_page_url,
493 suggest_url,
494 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)))
495 return false;
496
497 // Make sure we are sending the suggest request through HTTPS to prevent
498 // exposing the current page URL to networks before the search provider.
499 if (!suggest_url.SchemeIs(content::kHttpsScheme))
500 return false;
501
502 // Don't run if there's no profile or in incognito mode.
503 if (profile == NULL || profile->IsOffTheRecord())
504 return false;
505
506 // Don't run if we can't get preferences or search suggest is not enabled.
507 PrefService* prefs = profile->GetPrefs();
508 if (!prefs->GetBoolean(prefs::kSearchSuggestEnabled))
509 return false;
510
511 // Only make the request if we know that the provider supports zero suggest
512 // (currently only the prepopulated Google provider).
513 if (template_url == NULL || !template_url->SupportsReplacement() ||
514 TemplateURLPrepopulateData::GetEngineType(*template_url) !=
515 SEARCH_ENGINE_GOOGLE)
516 return false;
517
518 // Check field trials and settings allow sending the URL on suggest requests.
519 ProfileSyncService* service =
520 ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
521 browser_sync::SyncPrefs sync_prefs(prefs);
522 if (!OmniboxFieldTrial::InZeroSuggestFieldTrial() || service == NULL ||
523 !service->IsSyncEnabledAndLoggedIn() ||
524 !sync_prefs.GetPreferredDataTypes(syncer::UserTypes())
525 .Has(syncer::PROXY_TABS) ||
526 service->GetEncryptedDataTypes().Has(syncer::SESSIONS))
527 return false;
528
529 return true;
530 }
531
532 void BaseSearchProvider::Start(const AutocompleteInput& input,
533 bool minimal_changes) {}
534
535 void BaseSearchProvider::OnURLFetchComplete(const net::URLFetcher* source) {
536 DCHECK(!done_);
537 suggest_results_pending_--;
538 DCHECK_GE(suggest_results_pending_, 0); // Should never go negative
539
540 LogFetchComplete(source);
541
542 bool results_updated = false;
543 if (IsRequestSuccessful(source)) {
544 const net::HttpResponseHeaders* const response_headers =
545 source->GetResponseHeaders();
546 std::string json_data;
547 source->GetResponseAsString(&json_data);
548
549 // JSON is supposed to be UTF-8, but some suggest service providers send
550 // JSON files in non-UTF-8 encodings. The actual encoding is usually
551 // specified in the Content-Type header field.
552 if (response_headers) {
553 std::string charset;
554 if (response_headers->GetCharset(&charset)) {
555 base::string16 data_16;
556 // TODO(jungshik): Switch to CodePageToUTF8 after it's added.
557 if (base::CodepageToUTF16(json_data,
558 charset.c_str(),
559 base::OnStringConversionError::FAIL,
560 &data_16))
561 json_data = base::UTF16ToUTF8(data_16);
562 }
563 }
564
565 scoped_ptr<base::Value> data(DeserializeJsonData(json_data));
566 if (data.get())
567 results_updated = ParseSuggestResults(
568 *data.get(), source, GetResultsObjectToFill(source));
569 }
570
571 UpdateMatches();
572
573 if (ShouldSendProviderUpdate(results_updated))
574 listener_->OnProviderUpdate(results_updated);
575 }
576
577 bool BaseSearchProvider::IsRequestSuccessful(const net::URLFetcher* source) {
578 return source->GetStatus().is_success() && (source->GetResponseCode() == 200);
579 }
580
581 bool BaseSearchProvider::IsKeywordRequest(const net::URLFetcher* source) {
582 return false;
583 }
584
585 bool BaseSearchProvider::ParseSuggestResults(const base::Value& root_val,
586 const net::URLFetcher* source,
587 Results* results) {
588 base::string16 query;
589 const base::ListValue* root_list = NULL;
590 const base::ListValue* results_list = NULL;
591 if (!root_val.GetAsList(&root_list) || !root_list->GetString(0, &query) ||
592 !IsValidQuery(query, source) || !root_list->GetList(1, &results_list))
593 return false;
594
595 // 3rd element: Description list.
596 const base::ListValue* descriptions = NULL;
597 root_list->GetList(2, &descriptions);
598
599 // 4th element: Disregard the query URL list for now.
600
601 // Reset suggested relevance information from the default provider.
602 results->verbatim_relevance = GetDefaultRelevance();
603
604 // 5th element: Optional key-value pairs from the Suggest server.
605 const base::ListValue* types = NULL;
606 const base::ListValue* relevances = NULL;
607 const base::ListValue* suggestion_details = NULL;
608 const base::DictionaryValue* extras = NULL;
609 int prefetch_index = -1;
610 if (root_list->GetDictionary(4, &extras)) {
611 extras->GetList("google:suggesttype", &types);
612
613 // Discard this list if its size does not match that of the suggestions.
614 if (extras->GetList("google:suggestrelevance", &relevances) &&
615 (relevances->GetSize() != results_list->GetSize()))
616 relevances = NULL;
617 extras->GetInteger("google:verbatimrelevance",
618 &results->verbatim_relevance);
619
620 // Check if the active suggest field trial (if any) has triggered.
621 bool triggered = false;
622 extras->GetBoolean("google:fieldtrialtriggered", &triggered);
623 field_trial_triggered_ |= triggered;
624 field_trial_triggered_in_session_ |= triggered;
625
626 const base::DictionaryValue* client_data = NULL;
627 if (extras->GetDictionary("google:clientdata", &client_data) && client_data)
628 client_data->GetInteger("phi", &prefetch_index);
629
630 if (extras->GetList("google:suggestdetail", &suggestion_details) &&
631 suggestion_details->GetSize() != results_list->GetSize())
632 suggestion_details = NULL;
633
634 // Store the metadata that came with the response in case we need to pass
635 // it along with the prefetch query to Instant.
636 JSONStringValueSerializer json_serializer(&results->metadata);
637 json_serializer.Serialize(*extras);
638 }
639
640 // Clear the previous results now that new results are available.
641 results->suggest_results.clear();
642 results->navigation_results.clear();
643
644 base::string16 suggestion;
645 std::string type;
646 int relevance = GetDefaultRelevance();
647 const bool allow_navsuggest = ShouldAllowNavSuggest(source);
648 const std::string languages(
649 profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
650 for (size_t index = 0; results_list->GetString(index, &suggestion); ++index) {
651 // Google search may return empty suggestions for weird input characters,
652 // they make no sense at all and can cause problems in our code.
653 if (suggestion.empty())
654 continue;
655
656 // Apply valid suggested relevance scores; discard invalid lists.
657 if (relevances != NULL && !relevances->GetInteger(index, &relevance))
658 relevances = NULL;
659 if (types && types->GetString(index, &type) && (type == "NAVIGATION")) {
660 // Do not blindly trust the URL coming from the server to be valid.
661 GURL url(URLFixerUpper::FixupURL(base::UTF16ToUTF8(suggestion),
662 std::string()));
663 if (url.is_valid() && allow_navsuggest) {
664 base::string16 title;
665 if (descriptions != NULL)
666 descriptions->GetString(index, &title);
667 results->navigation_results.push_back(
668 NavigationResult(*this,
669 url,
670 title,
671 IsKeywordRequest(source),
672 relevance,
673 relevances != NULL,
674 GetInputText(source),
675 languages));
676 }
677 } else {
678 AutocompleteMatchType::Type match_type = GetAutocompleteMatchType(type);
679 bool should_prefetch = static_cast<int>(index) == prefetch_index;
680 const base::DictionaryValue* suggestion_detail = NULL;
681 base::string16 match_contents = suggestion;
682 base::string16 annotation;
683 std::string suggest_query_params;
684 std::string deletion_url;
685
686 if (suggestion_details) {
687 suggestion_details->GetDictionary(index, &suggestion_detail);
688 if (suggestion_detail) {
689 suggestion_detail->GetString("du", &deletion_url);
690 suggestion_detail->GetString("title", &match_contents) ||
691 suggestion_detail->GetString("t", &match_contents);
692 // Error correction for bad data from server.
693 if (match_contents.empty())
694 match_contents = suggestion;
695 suggestion_detail->GetString("annotation", &annotation) ||
696 suggestion_detail->GetString("a", &annotation);
697 suggestion_detail->GetString("query_params", &suggest_query_params) ||
698 suggestion_detail->GetString("q", &suggest_query_params);
699 }
700 }
701
702 // TODO(kochi): Improve calculator suggestion presentation.
703 results->suggest_results.push_back(SuggestResult(suggestion,
704 match_type,
705 match_contents,
706 annotation,
707 suggest_query_params,
708 deletion_url,
709 IsKeywordRequest(source),
710 relevance,
711 relevances != NULL,
712 should_prefetch,
713 GetInputText(source)));
714 }
715 }
716
717 SortResults(source, relevances, results);
718
719 return true;
720 }
721
722 bool BaseSearchProvider::ShouldAllowNavSuggest(const net::URLFetcher* source) {
723 return true;
724 }
725
726 void BaseSearchProvider::SortResults(const net::URLFetcher* source,
727 const base::ListValue* relevances,
728 Results* results) {}
729
730 void BaseSearchProvider::AddMatchToMap(const SuggestResult& result,
731 const AutocompleteInput input,
732 const base::string16& input_text,
733 const TemplateURL* template_url,
734 const std::string& metadata,
735 int accepted_suggestion,
736 bool append_extra_query_params,
737 MatchMap* map) {
738 // On non-mobile, ask the instant controller for the appropriate start margin.
739 // On mobile the start margin is unused, so leave the value as default there.
740 int omnibox_start_margin = chrome::kDisableStartMargin;
741 #if !defined(OS_ANDROID) && !defined(IOS)
742 if (chrome::IsInstantExtendedAPIEnabled()) {
743 Browser* browser =
744 chrome::FindBrowserWithProfile(profile_, chrome::GetActiveDesktop());
745 if (browser && browser->instant_controller() &&
746 browser->instant_controller()->instant()) {
747 omnibox_start_margin =
748 browser->instant_controller()->instant()->omnibox_bounds().x();
749 }
750 }
751 #endif // !defined(OS_ANDROID) && !defined(IOS)
752
753 AutocompleteMatch match = CreateSearchSuggestion(this,
754 input,
755 input_text,
756 result,
757 template_url,
758 accepted_suggestion,
759 omnibox_start_margin,
760 append_extra_query_params);
761 if (!match.destination_url.is_valid())
762 return;
763 match.search_terms_args->bookmark_bar_pinned =
764 profile_->GetPrefs()->GetBoolean(prefs::kShowBookmarkBar);
765 match.RecordAdditionalInfo(kRelevanceFromServerKey,
766 result.relevance_from_server() ? kTrue : kFalse);
767 match.RecordAdditionalInfo(kShouldPrefetchKey,
768 result.should_prefetch() ? kTrue : kFalse);
769
770 if (!result.deletion_url().empty()) {
771 GURL url(match.destination_url.GetOrigin().Resolve(result.deletion_url()));
772 if (url.is_valid()) {
773 match.RecordAdditionalInfo(kDeletionUrlKey, url.spec());
774 match.deletable = true;
775 }
776 }
777
778 // Metadata is needed only for prefetching queries.
779 if (result.should_prefetch())
780 match.RecordAdditionalInfo(kSuggestMetadataKey, metadata);
781
782 // Try to add |match| to |map|. If a match for |query_string| is already in
783 // |map|, replace it if |match| is more relevant.
784 // NOTE: Keep this ToLower() call in sync with url_database.cc.
785 MatchKey match_key(
786 std::make_pair(base::i18n::ToLower(result.suggestion()),
787 match.search_terms_args->suggest_query_params));
788 const std::pair<MatchMap::iterator, bool> i(
789 map->insert(std::make_pair(match_key, match)));
790
791 bool should_prefetch = result.should_prefetch();
792 if (!i.second) {
793 // NOTE: We purposefully do a direct relevance comparison here instead of
794 // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items
795 // added first" rather than "items alphabetically first" when the scores
796 // are equal. The only case this matters is when a user has results with
797 // the same score that differ only by capitalization; because the history
798 // system returns results sorted by recency, this means we'll pick the most
799 // recent such result even if the precision of our relevance score is too
800 // low to distinguish the two.
801 if (match.relevance > i.first->second.relevance) {
802 i.first->second = match;
803 } else if (match.keyword == i.first->second.keyword) {
804 // Old and new matches are from the same search provider. It is okay to
805 // record one match's prefetch data onto a different match (for the same
806 // query string) for the following reasons:
807 // 1. Because the suggest server only sends down a query string from
808 // which we construct a URL, rather than sending a full URL, and because
809 // we construct URLs from query strings in the same way every time, the
810 // URLs for the two matches will be the same. Therefore, we won't end up
811 // prefetching something the server didn't intend.
812 // 2. Presumably the server sets the prefetch bit on a match it things is
813 // sufficiently relevant that the user is likely to choose it. Surely
814 // setting the prefetch bit on a match of even higher relevance won't
815 // violate this assumption.
816 should_prefetch |= ShouldPrefetch(i.first->second);
817 i.first->second.RecordAdditionalInfo(kShouldPrefetchKey,
818 should_prefetch ? kTrue : kFalse);
819 if (should_prefetch)
820 i.first->second.RecordAdditionalInfo(kSuggestMetadataKey, metadata);
821 }
822 }
823 }
824
825 scoped_ptr<base::Value> BaseSearchProvider::DeserializeJsonData(
826 std::string json_data) {
827 // The JSON response should be an array.
828 for (size_t response_start_index = json_data.find("["), i = 0;
829 response_start_index != std::string::npos && i < 5;
830 response_start_index = json_data.find("[", 1), ++i) {
831 // Remove any XSSI guards to allow for JSON parsing.
832 if (response_start_index > 0)
833 json_data.erase(0, response_start_index);
834
835 JSONStringValueSerializer deserializer(json_data);
836 deserializer.set_allow_trailing_comma(true);
837 int error_code = 0;
838 scoped_ptr<base::Value> data(deserializer.Deserialize(&error_code, NULL));
839 if (error_code == 0)
840 return data.Pass();
841 }
842 return scoped_ptr<base::Value>();
843 }
844
845 void BaseSearchProvider::OnDeletionComplete(
846 bool success,
847 SuggestionDeletionHandler* handler) {
848 RecordDeletionResult(success);
849 SuggestionDeletionHandlers::iterator it =
850 std::find(deletion_handlers_.begin(), deletion_handlers_.end(), handler);
851 DCHECK(it != deletion_handlers_.end());
852 deletion_handlers_.erase(it);
853 }
854
855 void BaseSearchProvider::DeleteMatchFromMatches(
856 const AutocompleteMatch& match) {
857 for (ACMatches::iterator i(matches_.begin()); i != matches_.end(); ++i) {
858 // Find the desired match to delete by checking the type and contents.
859 // We can't check the destination URL, because the autocomplete controller
860 // may have reformulated that. Not that while checking for matching
861 // contents works for personalized suggestions, if more match types gain
862 // deletion support, this algorithm may need to be re-examined.
863 if (i->contents == match.contents && i->type == match.type) {
864 matches_.erase(i);
865 break;
866 }
867 }
868 listener_->OnProviderUpdate(true);
869 }
OLDNEW
« no previous file with comments | « chrome/browser/autocomplete/base_search_provider.h ('k') | chrome/browser/autocomplete/search_provider.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698