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 | |
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, | |
43 const string16& user_text) { | |
44 const std::string& spec = url.possibly_invalid_spec(); | |
45 if (spec.empty() || url.scheme() != chrome::kHttpScheme) | |
samarth
2012/08/13 17:42:37
You should also consider checking the scheme for u
Jered
2012/08/13 22:23:59
I'm not sure we need that for testing. Seems like
| |
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(), | |
77 base::OnStringConversionError::FAIL, | |
78 &data_16)) | |
79 json_data = UTF16ToUTF8(data_16); | |
80 } | |
81 } | |
82 const bool request_succeeded = | |
samarth
2012/08/13 17:42:37
If json_data is empty at this point, do we still c
Jered
2012/08/13 22:23:59
Yep, but results_updated will be true only if the
| |
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 AutocompleteMatch match(this, 2000, false, | |
samarth
2012/08/13 17:42:37
Make 2000 a constant, and add TODO about getting s
Jered
2012/08/13 22:23:59
Done. I added a comment about why 2000 here, and a
| |
165 AutocompleteMatch::SEARCH_ZEROSUGGEST_URL); | |
166 match.destination_url = GURL(current_query_); | |
167 match.contents = current_query_text_; | |
168 if (!user_text_is_url) { | |
169 match.fill_into_edit = current_query_text_; | |
170 match.inline_autocomplete_offset = 0; | |
171 } | |
172 AutocompleteMatch::ClassifyLocationInString(0, current_query_.size(), | |
173 match.contents.length(), ACMatchClassification::URL, | |
174 &match.contents_class); | |
175 matches_.push_back(match); | |
176 } | |
177 } | |
178 | |
179 void ZerosuggestProvider::AddMatchForResult( | |
180 const TemplateURL* search_provider, | |
181 size_t result_index, | |
182 const string16& result) { | |
183 // TODO(jered): Rip out user_text_is_url logic when AddMatchForCurrentURL goes . | |
samarth
2012/08/13 17:42:37
nit: long line
Jered
2012/08/13 22:23:59
Done.
| |
184 const bool user_text_is_url = user_text_ == current_query_text_; | |
185 const bool kCaseInsensitve = false; | |
186 if (!user_text_.empty() && !user_text_is_url && | |
187 !StartsWith(result, user_text_, kCaseInsensitve)) | |
188 // This suggestion isn't relevant for the current prefix. | |
189 return; | |
190 const int kRelevance = 1999; | |
191 AutocompleteMatch match(this, kRelevance, false, | |
192 AutocompleteMatch::SEARCH_ZEROSUGGEST); | |
193 match.contents = result; | |
194 match.fill_into_edit = result; | |
195 if (!user_text_is_url && user_text_ != result) | |
196 match.inline_autocomplete_offset = user_text_.length(); | |
197 | |
198 // Build a URL for this query using the default search provider. | |
199 const TemplateURLRef& search_url = search_provider->url_ref(); | |
200 DCHECK(search_url.SupportsReplacement()); | |
201 match.search_terms_args.reset( | |
202 new TemplateURLRef::SearchTermsArgs(result)); | |
203 match.search_terms_args->original_query = string16(); | |
204 match.search_terms_args->accepted_suggestion = result_index; | |
205 match.destination_url = | |
206 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
207 | |
208 if (user_text_.empty() || user_text_is_url || user_text_ == result) { | |
209 match.contents_class.push_back( | |
210 ACMatchClassification(0, ACMatchClassification::NONE)); | |
211 } else { | |
212 // Style to look like normal search suggestions. | |
213 match.contents_class.push_back( | |
214 ACMatchClassification(0, ACMatchClassification::DIM)); | |
215 match.contents_class.push_back( | |
216 ACMatchClassification(user_text_.length(), ACMatchClassification::NONE)); | |
217 } | |
218 match.transition = content::PAGE_TRANSITION_GENERATED; | |
219 | |
220 matches_.push_back(match); | |
221 } | |
OLD | NEW |