zero_suggest_provider.cc revision 868fa2fe829687343ffae624259930155e16dbd8
148486893f46d2e12e926682a3ecb908716bc66c4Chris Lattner// Copyright (c) 2012 The Chromium Authors. All rights reserved.
29769ab22265b313171d201b5928688524a01bd87Misha Brukman// Use of this source code is governed by a BSD-style license that can be
36fbcc26f1460eaee4e0eb8b426fc1ff0c7af11beJohn Criswell// found in the LICENSE file.
46fbcc26f1460eaee4e0eb8b426fc1ff0c7af11beJohn Criswell
56fbcc26f1460eaee4e0eb8b426fc1ff0c7af11beJohn Criswell#include "chrome/browser/autocomplete/zero_suggest_provider.h"
66fbcc26f1460eaee4e0eb8b426fc1ff0c7af11beJohn Criswell
79769ab22265b313171d201b5928688524a01bd87Misha Brukman#include "base/callback.h"
86fbcc26f1460eaee4e0eb8b426fc1ff0c7af11beJohn Criswell#include "base/i18n/case_conversion.h"
9febdf58538c2510ec1c24d3a856420792c53debeChris Lattner#include "base/json/json_string_value_serializer.h"
10febdf58538c2510ec1c24d3a856420792c53debeChris Lattner#include "base/metrics/histogram.h"
11bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer#include "base/prefs/pref_service.h"
12009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "base/strings/string16.h"
13009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "base/strings/string_util.h"
14009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "base/strings/utf_string_conversions.h"
15009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "base/time.h"
16009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "chrome/browser/autocomplete/autocomplete_input.h"
1718961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "chrome/browser/autocomplete/autocomplete_match.h"
1818961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "chrome/browser/autocomplete/autocomplete_provider_listener.h"
19551ccae044b0ff658fe629dd67edd5ffe75d10e8Reid Spencer#include "chrome/browser/autocomplete/history_url_provider.h"
20ef547ddcdd6e49bb6f019ac5fb080d2f317e43a9Chris Lattner#include "chrome/browser/autocomplete/search_provider.h"
21009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "chrome/browser/autocomplete/url_prefix.h"
22d0fde30ce850b78371fd1386338350591f9ff494Brian Gaeke#include "chrome/browser/metrics/variations/variations_http_header_provider.h"
23d0fde30ce850b78371fd1386338350591f9ff494Brian Gaeke#include "chrome/browser/net/url_fixer_upper.h"
24009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "chrome/browser/omnibox/omnibox_field_trial.h"
25caeb8b4041546bee40d8df0b7face79b9ba6dca8Chris Lattner#include "chrome/browser/profiles/profile.h"
26caeb8b4041546bee40d8df0b7face79b9ba6dca8Chris Lattner#include "chrome/browser/search/search.h"
27009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner#include "chrome/browser/search_engines/template_url_service.h"
2818961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "chrome/browser/search_engines/template_url_service_factory.h"
2917fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner#include "chrome/browser/sync/profile_sync_service.h"
30bca81448ac8e19c588c9a4ad16fc70732b76327cChris Lattner#include "chrome/browser/sync/profile_sync_service_factory.h"
31bca81448ac8e19c588c9a4ad16fc70732b76327cChris Lattner#include "chrome/common/pref_names.h"
32bca81448ac8e19c588c9a4ad16fc70732b76327cChris Lattner#include "chrome/common/url_constants.h"
3318961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "googleurl/src/gurl.h"
3417fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner#include "net/base/escape.h"
35f8dfef74376dd85f37601855f7519d8256700dabChris Lattner#include "net/base/load_flags.h"
3618961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "net/base/net_util.h"
3718961504fc2b299578dba817900a0696cf3ccc4dChris Lattner#include "net/http/http_request_headers.h"
38bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer#include "net/http/http_response_headers.h"
39bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer#include "net/url_request/url_fetcher.h"
40bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer#include "net/url_request/url_request_status.h"
41bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer
42bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencernamespace {
43bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer
44bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// TODO(hfung): The histogram code was copied and modified from
45bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// search_provider.cc.  Refactor and consolidate the code.
46bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// We keep track in a histogram how many suggest requests we send, how
47bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// many suggest requests we invalidate (e.g., due to a user typing
48bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// another character), and how many replies we receive.
49bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// *** ADD NEW ENUMS AFTER ALL PREVIOUSLY DEFINED ONES! ***
50bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer//     (excluding the end-of-list enum value)
51bddcb9427cb36ac6609fef233eaac3c9b5e5a8f4Reid Spencer// We do not want values of existing enums to change or else it screws
521fca5ff62bb2ecb5bfc8974f4dbfc56e9d3ca721Chris Lattner// up the statistics.
531fca5ff62bb2ecb5bfc8974f4dbfc56e9d3ca721Chris Lattnerenum ZeroSuggestRequestsHistogramValue {
5418961504fc2b299578dba817900a0696cf3ccc4dChris Lattner  ZERO_SUGGEST_REQUEST_SENT = 1,
55009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  ZERO_SUGGEST_REQUEST_INVALIDATED,
56009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  ZERO_SUGGEST_REPLY_RECEIVED,
5718961504fc2b299578dba817900a0696cf3ccc4dChris Lattner  ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE
5817fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner};
5918961504fc2b299578dba817900a0696cf3ccc4dChris Lattner
60fab8c796f6754962f5732145248303e3a1f7b96bChris Lattnervoid LogOmniboxZeroSuggestRequest(
6118961504fc2b299578dba817900a0696cf3ccc4dChris Lattner    ZeroSuggestRequestsHistogramValue request_value) {
6218961504fc2b299578dba817900a0696cf3ccc4dChris Lattner  UMA_HISTOGRAM_ENUMERATION("Omnibox.ZeroSuggestRequests", request_value,
6317fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner                            ZERO_SUGGEST_MAX_REQUEST_HISTOGRAM_VALUE);
64009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner}
6518961504fc2b299578dba817900a0696cf3ccc4dChris Lattner
6618961504fc2b299578dba817900a0696cf3ccc4dChris Lattner// The maximum relevance of the top match from this provider.
67009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattnerconst int kDefaultVerbatimZeroSuggestRelevance = 1300;
68009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner
69fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner// Relevance value to use if it was not set explicitly by the server.
70f40ae3cc9d7e00e8be72cc0596386af6c6925e8dMisha Brukmanconst int kDefaultZeroSuggestRelevance = 100;
71f40ae3cc9d7e00e8be72cc0596386af6c6925e8dMisha Brukman
721020b3982c9eae15844c5612b0cf251917931b1dChris Lattner}  // namespace
73fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner
743fb57b691d21e582ed18ffe6477e8f5548b4ad6aChris Lattner// static
753fb57b691d21e582ed18ffe6477e8f5548b4ad6aChris LattnerZeroSuggestProvider* ZeroSuggestProvider::Create(
76fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner    AutocompleteProviderListener* listener,
77423c2260f95883f7c84ac962e58ac66c3a11efacDan Gohman    Profile* profile) {
78423c2260f95883f7c84ac962e58ac66c3a11efacDan Gohman  return new ZeroSuggestProvider(listener, profile);
79009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner}
80009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner
81fab8c796f6754962f5732145248303e3a1f7b96bChris Lattnervoid ZeroSuggestProvider::Start(const AutocompleteInput& input,
82fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner                                bool /*minimal_changes*/) {
8317fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner  CheckIfTextModfied(input.text());
8417fcdd5e1b78b829068ca657c97357a39d6e768bChris Lattner  // Clear results only if the user text was modified.
85009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  Stop(user_text_modified_);
86cb4f10b4d5e9ba2e37e70424b290dd1187ca6ea7Chris Lattner  ConvertResultsToAutocompleteMatches(input.text(), false);
87cb4f10b4d5e9ba2e37e70424b290dd1187ca6ea7Chris Lattner  // listener_->OnProviderUpdate() does not need to be called because this
88cb4f10b4d5e9ba2e37e70424b290dd1187ca6ea7Chris Lattner  // function is only called in the synchronous pass when a user has performed
89cb4f10b4d5e9ba2e37e70424b290dd1187ca6ea7Chris Lattner  // an action (such as typing a character in the omnobox).
90cb4f10b4d5e9ba2e37e70424b290dd1187ca6ea7Chris Lattner}
9126199059268a05739c84ebf465fcdbf7ded861dfChris Lattner
9226199059268a05739c84ebf465fcdbf7ded861dfChris Lattnervoid ZeroSuggestProvider::Stop(bool clear_cached_results) {
9326199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  if (have_pending_request_)
9426199059268a05739c84ebf465fcdbf7ded861dfChris Lattner    LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_INVALIDATED);
95009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  have_pending_request_ = false;
9650cdabcfd52e88381ade61450d98a1c757195befDan Gohman  fetcher_.reset();
97dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus  done_ = true;
98dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus  if (clear_cached_results) {
99dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus    query_matches_map_.clear();
100dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus    navigation_results_.clear();
101dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus    current_query_.clear();
102dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus    matches_.clear();
103dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus  }
104dd49dbfe44098eb53b1ac29f017e422147572bbbVladimir Prus}
105b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner
106b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattnervoid ZeroSuggestProvider::AddProviderInfo(ProvidersInfo* provider_info) const {
107b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  provider_info->push_back(metrics::OmniboxEventProto_ProviderInfo());
108b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  metrics::OmniboxEventProto_ProviderInfo& new_entry = provider_info->back();
109b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  new_entry.set_provider(AsOmniboxEventProviderType());
110b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  new_entry.set_provider_done(done_);
111b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  std::vector<uint32> field_trial_hashes;
112b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  OmniboxFieldTrial::GetActiveSuggestFieldTrialHashes(&field_trial_hashes);
113b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattner  for (size_t i = 0; i < field_trial_hashes.size(); ++i) {
11407216eabb7e749dc38f521e73485db553cefc8d4Chris Lattner    if (field_trial_triggered_)
115a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner      new_entry.mutable_field_trial_triggered()->Add(field_trial_hashes[i]);
116a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner    if (field_trial_triggered_in_session_) {
11707216eabb7e749dc38f521e73485db553cefc8d4Chris Lattner      new_entry.mutable_field_trial_triggered_in_session()->Add(
118a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner          field_trial_hashes[i]);
119a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner     }
120a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner  }
121a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner}
122a71965b1adf6bfeddfd3b38fdf7df9b4412bc6c2Chris Lattner
123b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattnervoid ZeroSuggestProvider::ResetSession() {
124ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner  // The user has started editing in the omnibox, so leave
125ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner  // |field_trial_triggered_in_session_| unchanged and set
126ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner  // |field_trial_triggered_| to false since zero suggest is inactive now.
127ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner  field_trial_triggered_ = false;
128ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner}
129ad993cbb77b26b36cee938686b3377c0d92abd5eChris Lattner
130b92f50fe6091a7a12f54f9884529b1127b1a14e5Chris Lattnervoid ZeroSuggestProvider::OnURLFetchComplete(const net::URLFetcher* source) {
1311020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  have_pending_request_ = false;
132fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner  LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REPLY_RECEIVED);
133fab8c796f6754962f5732145248303e3a1f7b96bChris Lattner
1341020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  std::string json_data;
1351020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  source->GetResponseAsString(&json_data);
1361020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  const bool request_succeeded =
1371020b3982c9eae15844c5612b0cf251917931b1dChris Lattner      source->GetStatus().is_success() && source->GetResponseCode() == 200;
1381020b3982c9eae15844c5612b0cf251917931b1dChris Lattner
139c063502e326fe0206942192773b263a3d88d93f5Chris Lattner  bool have_results = false;
1401020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  if (request_succeeded) {
14118961504fc2b299578dba817900a0696cf3ccc4dChris Lattner    JSONStringValueSerializer deserializer(json_data);
14218961504fc2b299578dba817900a0696cf3ccc4dChris Lattner    deserializer.set_allow_trailing_comma(true);
143a4c6c522ee029e97aedccb6ee7cca912d52a5599Misha Brukman    scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL));
144a4c6c522ee029e97aedccb6ee7cca912d52a5599Misha Brukman    if (data.get()) {
1451020b3982c9eae15844c5612b0cf251917931b1dChris Lattner      ParseSuggestResults(*data.get());
14626199059268a05739c84ebf465fcdbf7ded861dfChris Lattner      have_results = !query_matches_map_.empty() ||
14726199059268a05739c84ebf465fcdbf7ded861dfChris Lattner          !navigation_results_.empty();
14826199059268a05739c84ebf465fcdbf7ded861dfChris Lattner    }
1491020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  }
1501020b3982c9eae15844c5612b0cf251917931b1dChris Lattner  done_ = true;
1511020b3982c9eae15844c5612b0cf251917931b1dChris Lattner
152072ddb1dac5afbf5c789a4ea7c7a2a4205508011Chris Lattner  if (have_results) {
1535c7e326585f3a543388ba871c3425f7664cd9143Bill Wendling    ConvertResultsToAutocompleteMatches(original_user_text_, true);
154072ddb1dac5afbf5c789a4ea7c7a2a4205508011Chris Lattner    listener_->OnProviderUpdate(true);
1555109f75f50501e3bd8ade66029ca485aeb359ff6Chris Lattner  }
15626199059268a05739c84ebf465fcdbf7ded861dfChris Lattner}
15770cfe13f19e91a595808ed6c6ff7e87ff0dccd64Chris Lattner
158b00c582b6d40e6b9ff2d1ed4f5eaf7930e792aceChris Lattnervoid ZeroSuggestProvider::StartZeroSuggest(const GURL& url,
159a1a702cdd23221e6e3f36632be91150138958e9dDan Gohman                                           const string16& user_text) {
1609636a91649f168f41b477cba705287665e054f79Chris Lattner  Stop(true);
1619636a91649f168f41b477cba705287665e054f79Chris Lattner  field_trial_triggered_ = false;
16226199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  field_trial_triggered_in_session_ = false;
16326199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  if (!ShouldRunZeroSuggest(url))
16426199059268a05739c84ebf465fcdbf7ded861dfChris Lattner    return;
16526199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  verbatim_relevance_ = kDefaultVerbatimZeroSuggestRelevance;
16626199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  done_ = false;
1679769ab22265b313171d201b5928688524a01bd87Misha Brukman  original_user_text_ = user_text;
16826199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  current_query_ = url.spec();
16926199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  current_url_match_ = MatchForCurrentURL();
170009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  user_text_modified_ = false;
171009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  // TODO(jered): Consider adding locally-sourced zero-suggestions here too.
17226199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  // These may be useful on the NTP or more relevant to the user than server
17326199059268a05739c84ebf465fcdbf7ded861dfChris Lattner  // suggestions, if based on local browsing history.
1749769ab22265b313171d201b5928688524a01bd87Misha Brukman  Run();
17526199059268a05739c84ebf465fcdbf7ded861dfChris Lattner}
17626199059268a05739c84ebf465fcdbf7ded861dfChris Lattner
17726199059268a05739c84ebf465fcdbf7ded861dfChris LattnerZeroSuggestProvider::ZeroSuggestProvider(
1780b2fc9b560a0cacbb9ddd8db10ef35a2ae5d334eChris Lattner  AutocompleteProviderListener* listener,
1797b6f5a3ee9a68d0f6206b1695f71551a467e33c4Chris Lattner  Profile* profile)
18026199059268a05739c84ebf465fcdbf7ded861dfChris Lattner    : AutocompleteProvider(listener, profile,
18126199059268a05739c84ebf465fcdbf7ded861dfChris Lattner          AutocompleteProvider::TYPE_ZERO_SUGGEST),
18226199059268a05739c84ebf465fcdbf7ded861dfChris Lattner      template_url_service_(TemplateURLServiceFactory::GetForProfile(profile)),
1839eb59ec548b861d6ede05b4e6dc22aabf645e665Jeff Cohen      user_text_modified_(false),
18405bb8831d3cd5299ba8e0c3fecfd7cf682f0ace1Reid Spencer      have_pending_request_(false),
18505bb8831d3cd5299ba8e0c3fecfd7cf682f0ace1Reid Spencer      verbatim_relevance_(kDefaultVerbatimZeroSuggestRelevance),
18626199059268a05739c84ebf465fcdbf7ded861dfChris Lattner      field_trial_triggered_(false),
1879769ab22265b313171d201b5928688524a01bd87Misha Brukman      field_trial_triggered_in_session_(false) {
18826199059268a05739c84ebf465fcdbf7ded861dfChris Lattner}
18926199059268a05739c84ebf465fcdbf7ded861dfChris Lattner
19026199059268a05739c84ebf465fcdbf7ded861dfChris LattnerZeroSuggestProvider::~ZeroSuggestProvider() {
19126199059268a05739c84ebf465fcdbf7ded861dfChris Lattner}
1924bd4aa5e3c41c0fc803e960252bb6fe75b804b1dChris Lattner
193f8dfef74376dd85f37601855f7519d8256700dabChris Lattnerbool ZeroSuggestProvider::ShouldRunZeroSuggest(const GURL& url) const {
194f8dfef74376dd85f37601855f7519d8256700dabChris Lattner  if (!url.is_valid())
195f8dfef74376dd85f37601855f7519d8256700dabChris Lattner    return false;
196f8dfef74376dd85f37601855f7519d8256700dabChris Lattner
197eb913b6b34a81b95ea5f692b710e5766526853c6Jeff Cohen  // Do not query non-http URLs. There will be no useful suggestions for https
198f8dfef74376dd85f37601855f7519d8256700dabChris Lattner  // or chrome URLs.
199881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner  if (url.scheme() != chrome::kHttpScheme)
200881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner    return false;
201881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner
202881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner  // Don't enable ZeroSuggest until InstantExtended works with ZeroSuggest.
203881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner  if (chrome::IsInstantExtendedAPIEnabled())
204881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner    return false;
205881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner
206881765af0acd2e05f8b01fd3b3b05a3cea03038bChris Lattner  // Don't run if there's no profile or in incognito mode.
207009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner  if (profile_ == NULL || profile_->IsOffTheRecord())
208009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner    return false;
209f8dfef74376dd85f37601855f7519d8256700dabChris Lattner
210f8dfef74376dd85f37601855f7519d8256700dabChris Lattner  // Don't run if we can't get preferences or search suggest is not enabled.
211f8dfef74376dd85f37601855f7519d8256700dabChris Lattner  PrefService* prefs = profile_->GetPrefs();
212f8dfef74376dd85f37601855f7519d8256700dabChris Lattner  if (prefs == NULL || !prefs->GetBoolean(prefs::kSearchSuggestEnabled))
213f8dfef74376dd85f37601855f7519d8256700dabChris Lattner    return false;
214d0fde30ce850b78371fd1386338350591f9ff494Brian Gaeke
215d0fde30ce850b78371fd1386338350591f9ff494Brian Gaeke  ProfileSyncService* service =
216009505452b713ed2e3a8e99c5545a6e721c65495Chris Lattner      ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile_);
217  browser_sync::SyncPrefs sync_prefs(prefs);
218  // The user has needs to have Chrome Sync enabled (for permissions to
219  // transmit their current URL) and be in the field trial.
220  if (!OmniboxFieldTrial::InZeroSuggestFieldTrial() ||
221      service == NULL ||
222      !service->IsSyncEnabledAndLoggedIn() ||
223      !sync_prefs.HasKeepEverythingSynced()) {
224    return false;
225  }
226  return true;
227}
228
229void ZeroSuggestProvider::FillResults(
230    const Value& root_val,
231    int* verbatim_relevance,
232    SearchProvider::SuggestResults* suggest_results,
233    SearchProvider::NavigationResults* navigation_results) {
234  string16 query;
235  const ListValue* root_list = NULL;
236  const ListValue* results = NULL;
237  const ListValue* relevances = NULL;
238  // The response includes the query, which should be empty for ZeroSuggest
239  // responses.
240  if (!root_val.GetAsList(&root_list) || !root_list->GetString(0, &query) ||
241      (!query.empty()) || !root_list->GetList(1, &results))
242    return;
243
244  // 3rd element: Description list.
245  const ListValue* descriptions = NULL;
246  root_list->GetList(2, &descriptions);
247
248  // 4th element: Disregard the query URL list for now.
249
250  // Reset suggested relevance information from the provider.
251  *verbatim_relevance = kDefaultVerbatimZeroSuggestRelevance;
252
253  // 5th element: Optional key-value pairs from the Suggest server.
254  const ListValue* types = NULL;
255  const DictionaryValue* extras = NULL;
256  if (root_list->GetDictionary(4, &extras)) {
257    extras->GetList("google:suggesttype", &types);
258
259    // Discard this list if its size does not match that of the suggestions.
260    if (extras->GetList("google:suggestrelevance", &relevances) &&
261        relevances->GetSize() != results->GetSize())
262      relevances = NULL;
263    extras->GetInteger("google:verbatimrelevance", verbatim_relevance);
264
265    // Check if the active suggest field trial (if any) has triggered.
266    bool triggered = false;
267    extras->GetBoolean("google:fieldtrialtriggered", &triggered);
268    field_trial_triggered_ |= triggered;
269    field_trial_triggered_in_session_ |= triggered;
270  }
271
272  // Clear the previous results now that new results are available.
273  suggest_results->clear();
274  navigation_results->clear();
275
276  string16 result, title;
277  std::string type;
278  for (size_t index = 0; results->GetString(index, &result); ++index) {
279    // Google search may return empty suggestions for weird input characters,
280    // they make no sense at all and can cause problems in our code.
281    if (result.empty())
282      continue;
283
284    int relevance = kDefaultZeroSuggestRelevance;
285
286    // Apply valid suggested relevance scores; discard invalid lists.
287    if (relevances != NULL && !relevances->GetInteger(index, &relevance))
288      relevances = NULL;
289    if (types && types->GetString(index, &type) && (type == "NAVIGATION")) {
290      // Do not blindly trust the URL coming from the server to be valid.
291      GURL url(URLFixerUpper::FixupURL(UTF16ToUTF8(result), std::string()));
292      if (url.is_valid()) {
293        if (descriptions != NULL)
294          descriptions->GetString(index, &title);
295        navigation_results->push_back(SearchProvider::NavigationResult(
296            *this, url, title, false, relevance));
297      }
298    } else {
299      suggest_results->push_back(SearchProvider::SuggestResult(
300          result, false, relevance));
301    }
302  }
303}
304
305void ZeroSuggestProvider::AddSuggestResultsToMap(
306    const SearchProvider::SuggestResults& results,
307    const string16& provider_keyword,
308    SearchProvider::MatchMap* map) {
309  for (size_t i = 0; i < results.size(); ++i) {
310    AddMatchToMap(results[i].suggestion(),
311                  provider_keyword,
312                  results[i].relevance(),
313                  AutocompleteMatchType::SEARCH_SUGGEST, i, map);
314  }
315}
316
317void ZeroSuggestProvider::AddMatchToMap(const string16& query_string,
318                                        const string16& provider_keyword,
319                                        int relevance,
320                                        AutocompleteMatch::Type type,
321                                        int accepted_suggestion,
322                                        SearchProvider::MatchMap* map) {
323  // Pass in query_string as the input_text since we don't want any bolding.
324  AutocompleteMatch match = SearchProvider::CreateSearchSuggestion(
325      profile_, this, AutocompleteInput(),
326      query_string, query_string, relevance, type, accepted_suggestion,
327      false, provider_keyword);
328  if (!match.destination_url.is_valid())
329    return;
330
331  // Try to add |match| to |map|.  If a match for |query_string| is already in
332  // |map|, replace it if |match| is more relevant.
333  // NOTE: Keep this ToLower() call in sync with url_database.cc.
334  const std::pair<SearchProvider::MatchMap::iterator, bool> i = map->insert(
335      std::pair<string16, AutocompleteMatch>(
336          base::i18n::ToLower(query_string), match));
337  // NOTE: We purposefully do a direct relevance comparison here instead of
338  // using AutocompleteMatch::MoreRelevant(), so that we'll prefer "items added
339  // first" rather than "items alphabetically first" when the scores are equal.
340  // The only case this matters is when a user has results with the same score
341  // that differ only by capitalization; because the history system returns
342  // results sorted by recency, this means we'll pick the most recent such
343  // result even if the precision of our relevance score is too low to
344  // distinguish the two.
345  if (!i.second && (match.relevance > i.first->second.relevance))
346    i.first->second = match;
347}
348
349AutocompleteMatch ZeroSuggestProvider::NavigationToMatch(
350    const SearchProvider::NavigationResult& navigation) {
351  AutocompleteMatch match(this, navigation.relevance(), false,
352                          AutocompleteMatchType::NAVSUGGEST);
353  match.destination_url = navigation.url();
354
355  const std::string languages(
356      profile_->GetPrefs()->GetString(prefs::kAcceptLanguages));
357  match.contents = net::FormatUrl(navigation.url(), languages,
358      net::kFormatUrlOmitAll, net::UnescapeRule::SPACES, NULL, NULL, NULL);
359  match.fill_into_edit +=
360      AutocompleteInput::FormattedStringWithEquivalentMeaning(navigation.url(),
361          match.contents);
362  match.inline_autocomplete_offset = string16::npos;
363
364  AutocompleteMatch::ClassifyLocationInString(string16::npos, 0,
365      match.contents.length(), ACMatchClassification::URL,
366      &match.contents_class);
367  match.description = navigation.description();
368  return match;
369}
370
371void ZeroSuggestProvider::Run() {
372  have_pending_request_ = false;
373  const int kFetcherID = 1;
374
375  const TemplateURL* default_provider =
376     template_url_service_->GetDefaultSearchProvider();
377  // TODO(hfung): Generalize if the default provider supports zero suggest.
378  // Only make the request if we know that the provider supports zero suggest
379  // (currently only the prepopulated Google provider).
380  if (default_provider == NULL || !default_provider->SupportsReplacement() ||
381      default_provider->prepopulate_id() != 1) {
382    Stop(true);
383    return;
384  }
385  string16 prefix;
386  TemplateURLRef::SearchTermsArgs search_term_args(prefix);
387  search_term_args.zero_prefix_url = current_query_;
388  std::string req_url = default_provider->suggestions_url_ref().
389      ReplaceSearchTerms(search_term_args);
390  GURL suggest_url(req_url);
391  // Make sure we are sending the suggest request through HTTPS.
392  if (!suggest_url.SchemeIs(chrome::kHttpsScheme)) {
393    Stop(true);
394    return;
395  }
396
397  fetcher_.reset(
398      net::URLFetcher::Create(kFetcherID,
399          suggest_url,
400          net::URLFetcher::GET, this));
401  fetcher_->SetRequestContext(profile_->GetRequestContext());
402  fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES);
403  // Add Chrome experiment state to the request headers.
404  net::HttpRequestHeaders headers;
405  chrome_variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
406      fetcher_->GetOriginalURL(), profile_->IsOffTheRecord(), false, &headers);
407  fetcher_->SetExtraRequestHeaders(headers.ToString());
408
409  fetcher_->Start();
410  have_pending_request_ = true;
411  LogOmniboxZeroSuggestRequest(ZERO_SUGGEST_REQUEST_SENT);
412}
413
414void ZeroSuggestProvider::CheckIfTextModfied(const string16& user_text) {
415  if (!user_text.empty() && user_text != current_url_match_.contents)
416    user_text_modified_ = true;
417}
418
419void ZeroSuggestProvider::ParseSuggestResults(const Value& root_val) {
420  SearchProvider::SuggestResults suggest_results;
421  FillResults(root_val, &verbatim_relevance_,
422              &suggest_results, &navigation_results_);
423
424  query_matches_map_.clear();
425  const TemplateURL* default_provider =
426     template_url_service_->GetDefaultSearchProvider();
427  AddSuggestResultsToMap(suggest_results, default_provider->keyword(),
428                         &query_matches_map_);
429}
430
431void ZeroSuggestProvider::ConvertResultsToAutocompleteMatches(
432    string16 user_text, bool update_histograms) {
433  matches_.clear();
434
435  const TemplateURL* default_provider =
436      template_url_service_->GetDefaultSearchProvider();
437  // Fail if we can't set the clickthrough URL for query suggestions.
438  if (default_provider == NULL || !default_provider->SupportsReplacement())
439    return;
440
441  const int num_query_results = query_matches_map_.size();
442  const int num_nav_results = navigation_results_.size();
443  const int num_results = num_query_results + num_nav_results;
444  if (update_histograms) {
445    UMA_HISTOGRAM_COUNTS("ZeroSuggest.QueryResults", num_query_results);
446    UMA_HISTOGRAM_COUNTS("ZeroSuggest.URLResults",  num_nav_results);
447    UMA_HISTOGRAM_COUNTS("ZeroSuggest.AllResults", num_results);
448  }
449
450  if (num_results == 0 || user_text_modified_)
451    return;
452
453  // TODO(jered): Rip this out once the first match is decoupled from the
454  // current typing in the omnibox.
455  // If the user text is empty, we can autocomplete to the URL.  Otherwise,
456  // don't modify the omnibox text.
457  current_url_match_.inline_autocomplete_offset = user_text.empty() ?
458      0 : string16::npos;
459  matches_.push_back(current_url_match_);
460
461  for (SearchProvider::MatchMap::const_iterator it = query_matches_map_.begin();
462       it != query_matches_map_.end(); ++it) {
463    matches_.push_back(it->second);
464  }
465
466  for (SearchProvider::NavigationResults::const_iterator it =
467           navigation_results_.begin();
468       it != navigation_results_.end(); ++it) {
469    matches_.push_back(NavigationToMatch(*it));
470  }
471}
472
473AutocompleteMatch ZeroSuggestProvider::MatchForCurrentURL() {
474  AutocompleteInput input(ASCIIToUTF16(current_query_), string16::npos,
475                          string16(), GURL(current_query_),
476                          false, false, true, AutocompleteInput::ALL_MATCHES);
477
478  AutocompleteMatch match(
479      HistoryURLProvider::SuggestExactInput(this, input, true));
480  match.is_history_what_you_typed_match = false;
481
482  // The placeholder suggestion for the current URL has high relevance so
483  // that it is in the first suggestion slot and inline autocompleted. It
484  // gets dropped as soon as the user types something.
485  match.relevance = verbatim_relevance_;
486
487  return match;
488}
489