OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 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/zerosuggest_provider.h" | |
6 | |
7 #include "base/callback.h" | |
8 #include "base/i18n/icu_string_conversions.h" | |
9 #include "base/json/json_string_value_serializer.h" | |
10 #include "base/string16.h" | |
11 #include "base/string_util.h" | |
12 #include "base/time.h" | |
13 #include "base/utf_string_conversions.h" | |
14 #include "chrome/browser/autocomplete/autocomplete_input.h" | |
15 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
16 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h" | |
17 #include "chrome/browser/profiles/profile.h" | |
18 #include "chrome/browser/search_engines/template_url_service.h" | |
19 #include "chrome/browser/search_engines/template_url_service_factory.h" | |
20 #include "chrome/common/url_constants.h" | |
21 #include "googleurl/src/gurl.h" | |
22 #include "net/base/load_flags.h" | |
23 #include "net/http/http_response_headers.h" | |
24 #include "net/url_request/url_fetcher.h" | |
25 #include "net/url_request/url_request_status.h" | |
26 #include "ui/base/l10n/l10n_util.h" | |
27 | |
28 ZerosuggestProvider::ZerosuggestProvider( | |
29 AutocompleteProviderListener* listener, | |
30 Profile* profile, | |
31 const std::string& url_prefix) | |
32 : AutocompleteProvider(listener, profile, "Zerosuggest"), | |
33 url_prefix_(url_prefix), | |
34 template_url_service_(TemplateURLServiceFactory::GetForProfile(profile)) { | |
35 } | |
36 | |
37 void ZerosuggestProvider::Start(const AutocompleteInput& input, | |
38 bool /*minimal_changes*/) { | |
39 UpdateMatches(input.text()); | |
40 } | |
41 | |
42 void ZerosuggestProvider::StartZerosuggest( | |
43 const GURL& url, | |
44 const string16& user_text) { | |
45 const std::string& spec = url.possibly_invalid_spec(); | |
46 if (spec.empty() || url.scheme() != chrome::kHttpScheme) | |
47 // Do not query empty URLs nor non-http URLs. There will be no useful | |
48 // suggestions for https or chrome URLs. | |
49 return; | |
50 matches_.clear(); | |
51 done_ = false; | |
52 user_text_ = user_text; | |
53 current_query_ = spec; | |
54 current_query_text_ = ASCIIToUTF16(spec); | |
55 // When navigating on Mac, the omnibox first gains then immediately loses | |
56 // focus. This is difficult to distinguish from user-initiated focus. Wait | |
57 // a while before fetching, and if the omnibox is immediately unfocused, | |
58 // Stop() should cancel this timer. | |
Peter Kasting
2012/08/10 21:33:33
Find a different solution. Providers must not kno
Jered
2012/08/10 23:05:23
Done. This problem vanished after my rebase. Lucky
| |
59 const int kFetchDelayMS = 10; | |
60 timer_.Start(FROM_HERE, | |
61 base::TimeDelta::FromMilliseconds(kFetchDelayMS), | |
62 this, &ZerosuggestProvider::Run); | |
63 } | |
64 | |
65 void ZerosuggestProvider::Stop(bool clear_cached_results) { | |
66 timer_.Stop(); | |
67 fetcher_.reset(); | |
68 done_ = true; | |
69 if (clear_cached_results) { | |
70 results_.clear(); | |
71 current_query_.clear(); | |
72 current_query_text_.clear(); | |
73 } | |
74 } | |
75 | |
76 void ZerosuggestProvider::OnURLFetchComplete(const net::URLFetcher* source) { | |
77 const net::HttpResponseHeaders* const response_headers = | |
78 source->GetResponseHeaders(); | |
79 std::string json_data; | |
80 source->GetResponseAsString(&json_data); | |
81 if (response_headers) { | |
82 std::string charset; | |
83 if (response_headers->GetCharset(&charset)) { | |
84 string16 data_16; | |
85 if (base::CodepageToUTF16(json_data, charset.c_str(), | |
86 base::OnStringConversionError::FAIL, | |
87 &data_16)) | |
88 json_data = UTF16ToUTF8(data_16); | |
89 } | |
90 } | |
91 const bool request_succeeded = | |
92 source->GetStatus().is_success() && source->GetResponseCode() == 200; | |
93 | |
94 bool results_updated = false; | |
95 if (request_succeeded) { | |
96 JSONStringValueSerializer deserializer(json_data); | |
97 deserializer.set_allow_trailing_comma(true); | |
98 scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL)); | |
99 results_updated = data.get() && ParseSuggestResults(data.get()); | |
100 } | |
101 done_ = true; | |
102 | |
103 ConvertResultsToAutocompleteMatches(); | |
104 if (results_updated) | |
105 listener_->OnProviderUpdate(results_updated); | |
106 } | |
107 | |
108 ZerosuggestProvider::~ZerosuggestProvider() { | |
109 } | |
110 | |
111 void ZerosuggestProvider::Run() { | |
112 const int kFetcherID = 1; | |
113 fetcher_.reset( | |
114 net::URLFetcher::Create(kFetcherID, | |
115 GURL(url_prefix_ + current_query_), | |
116 net::URLFetcher::GET, this)); | |
117 fetcher_->SetRequestContext(profile_->GetRequestContext()); | |
118 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); | |
119 fetcher_->Start(); | |
120 } | |
121 | |
122 void ZerosuggestProvider::UpdateMatches(const string16& user_text) { | |
123 user_text_ = user_text; | |
124 const size_t prev_num_matches = matches_.size(); | |
125 ConvertResultsToAutocompleteMatches(); | |
126 if (matches_.size() != prev_num_matches) | |
127 listener_->OnProviderUpdate(true); | |
128 } | |
129 | |
130 bool ZerosuggestProvider::ParseSuggestResults(Value* root_val) { | |
131 std::string query; | |
132 ListValue* root_list = NULL; | |
133 ListValue* results = NULL; | |
134 if (!root_val->GetAsList(&root_list) || !root_list->GetString(0, &query) || | |
135 (query != current_query_) || !root_list->GetList(1, &results)) | |
136 return false; | |
137 | |
138 results_.clear(); | |
139 ListValue* one_result = NULL; | |
140 for (size_t index = 0; results->GetList(index, &one_result); ++index) { | |
141 string16 result; | |
142 one_result->GetString(0, &result); | |
143 if (result.empty()) | |
144 continue; | |
145 results_.push_back(result); | |
146 } | |
147 | |
148 return true; | |
149 } | |
150 | |
151 void ZerosuggestProvider::ConvertResultsToAutocompleteMatches() { | |
152 const TemplateURL* search_provider = | |
153 template_url_service_->GetDefaultSearchProvider(); | |
154 if (search_provider == NULL || !search_provider->SupportsReplacement()) | |
155 // Fail if we can't set the clickthrough URL for query suggestions. | |
156 return; | |
157 matches_.clear(); | |
158 if (results_.empty()) | |
159 // Do not add anything if there are no results for this URL. | |
160 return; | |
161 AddMatchForCurrentURL(); | |
162 for (size_t i = 0; i < results_.size(); ++i) | |
163 AddMatchForResult(search_provider, i, results_[i]); | |
164 } | |
165 | |
166 void ZerosuggestProvider::AddMatchForCurrentURL() { | |
167 // If the user has typed something besides the current url, they probably | |
168 // don't intend to refresh it. | |
169 const bool user_text_is_url = user_text_ == current_query_text_; | |
170 if (user_text_.empty() || user_text_is_url) { | |
171 AutocompleteMatch match(this, 2000, false, | |
172 AutocompleteMatch::SEARCH_ZEROSUGGEST_URL); | |
173 match.destination_url = GURL(current_query_); | |
174 match.contents = current_query_text_; | |
175 if (!user_text_is_url) { | |
176 match.fill_into_edit = current_query_text_; | |
177 match.inline_autocomplete_offset = 0; | |
178 } | |
179 AutocompleteMatch::ClassifyLocationInString(0, current_query_.size(), | |
180 match.contents.length(), ACMatchClassification::URL, | |
181 &match.contents_class); | |
182 matches_.push_back(match); | |
183 } | |
184 } | |
185 | |
186 void ZerosuggestProvider::AddMatchForResult( | |
187 const TemplateURL* search_provider, | |
188 size_t result_index, | |
189 const string16& result) { | |
190 const bool user_text_is_url = user_text_ == current_query_text_; | |
191 const bool kCaseInsensitve = false; | |
192 if (!user_text_.empty() && !user_text_is_url && | |
193 !StartsWith(result, user_text_, kCaseInsensitve)) | |
194 // This suggestion isn't relevant for the current prefix. | |
195 return; | |
196 const int kRelevance = 1999; | |
197 AutocompleteMatch match(this, kRelevance, false, | |
198 AutocompleteMatch::SEARCH_ZEROSUGGEST); | |
199 match.contents = result; | |
200 match.fill_into_edit = result; | |
201 if (!user_text_is_url && user_text_ != result) | |
202 match.inline_autocomplete_offset = user_text_.length(); | |
203 | |
204 // Build a URL for this query using the default search provider. | |
205 const TemplateURLRef& search_url = search_provider->url_ref(); | |
206 DCHECK(search_url.SupportsReplacement()); | |
207 match.search_terms_args.reset( | |
208 new TemplateURLRef::SearchTermsArgs(result)); | |
209 match.search_terms_args->original_query = string16(); | |
210 match.search_terms_args->accepted_suggestion = result_index; | |
211 match.destination_url = | |
212 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
213 | |
214 if (user_text_.empty() || user_text_is_url || user_text_ == result) { | |
215 match.contents_class.push_back( | |
216 ACMatchClassification(0, ACMatchClassification::NONE)); | |
217 } else { | |
218 // Style to look like normal search suggestions. | |
219 match.contents_class.push_back( | |
220 ACMatchClassification(0, ACMatchClassification::DIM)); | |
221 match.contents_class.push_back( | |
222 ACMatchClassification(user_text_.length(), ACMatchClassification::NONE)); | |
223 } | |
224 match.transition = content::PAGE_TRANSITION_GENERATED; | |
225 | |
226 matches_.push_back(match); | |
227 } | |
OLD | NEW |