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/zero_suggest_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 | |
27 ZeroSuggestProvider::ZeroSuggestProvider( | |
28 AutocompleteProviderListener* listener, | |
29 Profile* profile, | |
30 const std::string& url_prefix) | |
31 : AutocompleteProvider(listener, profile, "ZeroSuggest"), | |
32 url_prefix_(url_prefix), | |
33 template_url_service_(TemplateURLServiceFactory::GetForProfile(profile)) { | |
34 } | |
35 | |
36 void ZeroSuggestProvider::Start(const AutocompleteInput& input, | |
37 bool /*minimal_changes*/) { | |
38 UpdateMatches(input.text()); | |
39 } | |
40 | |
41 void ZeroSuggestProvider::StartZeroSuggest( | |
42 const GURL& url, | |
Peter Kasting
2012/08/14 19:20:02
Nit: Can fit on previous line, then indent next ar
Jered
2012/08/14 22:27:11
Done.
| |
43 const string16& user_text) { | |
44 const std::string& spec = url.possibly_invalid_spec(); | |
Peter Kasting
2012/08/14 19:20:02
Shouldn't |url| always be valid here? Seems like
Jered
2012/08/14 22:27:11
It may be empty. But invalid URLs will not be wort
| |
45 if (spec.empty() || url.scheme() != chrome::kHttpScheme) | |
Peter Kasting
2012/08/14 19:20:02
How can the spec be empty? If it can, then my com
Jered
2012/08/14 22:27:11
It's empty on initial focus. Revised.
Peter Kasting
2012/08/15 05:30:39
Wow, really? That seems odd. The caller passes i
Jered
2012/08/15 16:32:12
Yep.
Peter Kasting
2012/08/15 17:35:31
You're never at a blank URL, because that's not ac
Jered
2012/08/15 18:15:21
Done.
| |
46 // Do not query empty URLs nor non-http URLs. There will be no useful | |
47 // suggestions for https or chrome URLs. | |
48 return; | |
49 matches_.clear(); | |
50 done_ = false; | |
51 user_text_ = user_text; | |
52 current_query_ = spec; | |
53 current_query_text_ = ASCIIToUTF16(spec); | |
54 Run(); | |
55 } | |
56 | |
57 void ZeroSuggestProvider::Stop(bool clear_cached_results) { | |
58 fetcher_.reset(); | |
59 done_ = true; | |
60 if (clear_cached_results) { | |
61 results_.clear(); | |
62 current_query_.clear(); | |
63 current_query_text_.clear(); | |
64 } | |
65 } | |
66 | |
67 void ZeroSuggestProvider::OnURLFetchComplete(const net::URLFetcher* source) { | |
68 const net::HttpResponseHeaders* const response_headers = | |
69 source->GetResponseHeaders(); | |
70 std::string json_data; | |
71 source->GetResponseAsString(&json_data); | |
72 if (response_headers) { | |
73 std::string charset; | |
74 if (response_headers->GetCharset(&charset)) { | |
75 string16 data_16; | |
76 if (base::CodepageToUTF16(json_data, charset.c_str(), | |
Peter Kasting
2012/08/14 19:20:02
Why do charset -> utf16 -> utf8 instead of just ch
Jered
2012/08/14 22:27:11
This code comes from SearchProvider. Our test serv
| |
77 base::OnStringConversionError::FAIL, | |
78 &data_16)) | |
79 json_data = UTF16ToUTF8(data_16); | |
80 } | |
81 } | |
82 const bool request_succeeded = | |
83 source->GetStatus().is_success() && source->GetResponseCode() == 200; | |
84 | |
85 bool results_updated = false; | |
86 if (request_succeeded) { | |
87 JSONStringValueSerializer deserializer(json_data); | |
88 deserializer.set_allow_trailing_comma(true); | |
89 scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL)); | |
90 results_updated = data.get() && ParseSuggestResults(data.get()); | |
91 } | |
92 done_ = true; | |
93 | |
94 ConvertResultsToAutocompleteMatches(); | |
95 if (results_updated) | |
96 listener_->OnProviderUpdate(results_updated); | |
97 } | |
98 | |
99 ZeroSuggestProvider::~ZeroSuggestProvider() { | |
100 } | |
101 | |
102 void ZeroSuggestProvider::Run() { | |
103 const int kFetcherID = 1; | |
104 fetcher_.reset( | |
105 net::URLFetcher::Create(kFetcherID, | |
106 GURL(url_prefix_ + current_query_), | |
107 net::URLFetcher::GET, this)); | |
108 fetcher_->SetRequestContext(profile_->GetRequestContext()); | |
109 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); | |
110 fetcher_->Start(); | |
111 } | |
112 | |
113 void ZeroSuggestProvider::UpdateMatches(const string16& user_text) { | |
114 user_text_ = user_text; | |
115 const size_t prev_num_matches = matches_.size(); | |
116 ConvertResultsToAutocompleteMatches(); | |
117 if (matches_.size() != prev_num_matches) | |
118 listener_->OnProviderUpdate(true); | |
119 } | |
120 | |
121 bool ZeroSuggestProvider::ParseSuggestResults(Value* root_val) { | |
122 std::string query; | |
123 ListValue* root_list = NULL; | |
124 ListValue* results = NULL; | |
125 if (!root_val->GetAsList(&root_list) || !root_list->GetString(0, &query) || | |
126 (query != current_query_) || !root_list->GetList(1, &results)) | |
127 return false; | |
128 | |
129 results_.clear(); | |
130 ListValue* one_result = NULL; | |
131 for (size_t index = 0; results->GetList(index, &one_result); ++index) { | |
132 string16 result; | |
133 one_result->GetString(0, &result); | |
134 if (result.empty()) | |
135 continue; | |
136 results_.push_back(result); | |
137 } | |
138 | |
139 return true; | |
140 } | |
141 | |
142 void ZeroSuggestProvider::ConvertResultsToAutocompleteMatches() { | |
143 const TemplateURL* search_provider = | |
144 template_url_service_->GetDefaultSearchProvider(); | |
145 if (search_provider == NULL || !search_provider->SupportsReplacement()) | |
146 // Fail if we can't set the clickthrough URL for query suggestions. | |
147 return; | |
148 matches_.clear(); | |
149 if (results_.empty()) | |
150 // Do not add anything if there are no results for this URL. | |
151 return; | |
152 AddMatchForCurrentURL(); | |
153 for (size_t i = 0; i < results_.size(); ++i) | |
154 AddMatchForResult(search_provider, i, results_[i]); | |
155 } | |
156 | |
157 // TODO(jered): Rip this out once the first match is decoupled from the current | |
158 // typing in the omnibox. | |
159 void ZeroSuggestProvider::AddMatchForCurrentURL() { | |
160 // If the user has typed something besides the current url, they probably | |
161 // don't intend to refresh it. | |
162 const bool user_text_is_url = user_text_ == current_query_text_; | |
163 if (user_text_.empty() || user_text_is_url) { | |
164 // The placeholder suggestion for the current URL should have the max | |
165 // relevance score when shown so that it is above other suggestions. | |
166 const int kMaxRelevance = 2000; | |
167 AutocompleteMatch match(this, kMaxRelevance, false, | |
Peter Kasting
2012/08/14 19:20:02
The relevance scores you use here and below mean t
Jered
2012/08/14 22:27:11
This placeholder nav suggestion will get dropped w
| |
168 AutocompleteMatch::SEARCH_ZEROSUGGEST_URL); | |
169 match.destination_url = GURL(current_query_); | |
170 match.contents = current_query_text_; | |
171 if (!user_text_is_url) { | |
172 match.fill_into_edit = current_query_text_; | |
173 match.inline_autocomplete_offset = 0; | |
174 } | |
175 AutocompleteMatch::ClassifyLocationInString(0, current_query_.size(), | |
176 match.contents.length(), ACMatchClassification::URL, | |
177 &match.contents_class); | |
178 matches_.push_back(match); | |
179 } | |
180 } | |
181 | |
182 void ZeroSuggestProvider::AddMatchForResult( | |
183 const TemplateURL* search_provider, | |
184 size_t result_index, | |
185 const string16& result) { | |
186 // TODO(jered): Rip out user_text_is_url logic when AddMatchForCurrentURL | |
187 // goes away. | |
188 const bool user_text_is_url = user_text_ == current_query_text_; | |
189 const bool kCaseInsensitve = false; | |
190 if (!user_text_.empty() && !user_text_is_url && | |
191 !StartsWith(result, user_text_, kCaseInsensitve)) | |
192 // This suggestion isn't relevant for the current prefix. | |
193 return; | |
194 // This bogus relevance is just below max so that these suggestions are | |
195 // beneath the current URL match from AddMatchForCurrentURL(). | |
196 // TODO(jered): Use real scores from the suggestion server. | |
197 const int kRelevance = 1999; | |
198 AutocompleteMatch match(this, kRelevance, false, | |
199 AutocompleteMatch::SEARCH_ZEROSUGGEST); | |
200 match.contents = result; | |
201 match.fill_into_edit = result; | |
202 if (!user_text_is_url && user_text_ != result) | |
203 match.inline_autocomplete_offset = user_text_.length(); | |
204 | |
205 // Build a URL for this query using the default search provider. | |
206 const TemplateURLRef& search_url = search_provider->url_ref(); | |
207 DCHECK(search_url.SupportsReplacement()); | |
208 match.search_terms_args.reset( | |
209 new TemplateURLRef::SearchTermsArgs(result)); | |
210 match.search_terms_args->original_query = string16(); | |
211 match.search_terms_args->accepted_suggestion = result_index; | |
212 match.destination_url = | |
213 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
214 | |
215 if (user_text_.empty() || user_text_is_url || user_text_ == result) { | |
216 match.contents_class.push_back( | |
217 ACMatchClassification(0, ACMatchClassification::NONE)); | |
218 } else { | |
219 // Style to look like normal search suggestions. | |
220 match.contents_class.push_back( | |
221 ACMatchClassification(0, ACMatchClassification::DIM)); | |
222 match.contents_class.push_back( | |
223 ACMatchClassification(user_text_.length(), ACMatchClassification::NONE)); | |
224 } | |
225 match.transition = content::PAGE_TRANSITION_GENERATED; | |
226 | |
227 matches_.push_back(match); | |
228 } | |
OLD | NEW |