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/json/json_string_value_serializer.h" | |
9 #include "base/string16.h" | |
10 #include "base/string_util.h" | |
11 #include "base/time.h" | |
12 #include "base/utf_string_conversions.h" | |
13 #include "chrome/browser/autocomplete/autocomplete_input.h" | |
14 #include "chrome/browser/autocomplete/autocomplete_match.h" | |
15 #include "chrome/browser/autocomplete/autocomplete_provider_listener.h" | |
16 #include "chrome/browser/profiles/profile.h" | |
17 #include "chrome/browser/search_engines/template_url_service.h" | |
18 #include "chrome/browser/search_engines/template_url_service_factory.h" | |
19 #include "chrome/common/url_constants.h" | |
20 #include "googleurl/src/gurl.h" | |
21 #include "net/base/load_flags.h" | |
22 #include "net/http/http_response_headers.h" | |
23 #include "net/url_request/url_fetcher.h" | |
24 #include "net/url_request/url_request_status.h" | |
25 | |
26 ZeroSuggestProvider::ZeroSuggestProvider( | |
27 AutocompleteProviderListener* listener, | |
28 Profile* profile, | |
29 const std::string& url_prefix) | |
30 : AutocompleteProvider(listener, profile, "ZeroSuggest"), | |
31 url_prefix_(url_prefix), | |
32 template_url_service_(TemplateURLServiceFactory::GetForProfile(profile)) { | |
33 } | |
34 | |
35 void ZeroSuggestProvider::Start(const AutocompleteInput& input, | |
36 bool /*minimal_changes*/) { | |
37 UpdateMatches(input.text()); | |
38 } | |
39 | |
40 void ZeroSuggestProvider::StartZeroSuggest(const GURL& url, | |
41 const string16& user_text) { | |
42 DCHECK(url.is_valid()); | |
43 // Do not query non-http URLs. There will be no useful suggestions for https | |
44 // or chrome URLs. | |
45 if (!url.is_valid() || url.scheme() != chrome::kHttpScheme) | |
Peter Kasting
2012/08/15 21:52:44
Nit: Remove "!url.is_valid() ||", since we should
Jered
2012/08/15 23:13:00
Done.
| |
46 return; | |
47 matches_.clear(); | |
48 done_ = false; | |
49 user_text_ = user_text; | |
50 current_query_ = url.spec(); | |
51 current_query_text_ = ASCIIToUTF16(url.spec()); | |
52 // TODO(jered): Consider adding locally-sourced zero-suggestions here too. | |
53 // These may be useful on the NTP or more relevant to the user than server | |
54 // suggestions, if based on local browsing history. | |
55 Run(); | |
56 } | |
57 | |
58 void ZeroSuggestProvider::Stop(bool clear_cached_results) { | |
59 fetcher_.reset(); | |
60 done_ = true; | |
61 if (clear_cached_results) { | |
62 results_.clear(); | |
63 current_query_.clear(); | |
64 current_query_text_.clear(); | |
65 } | |
66 } | |
67 | |
68 void ZeroSuggestProvider::OnURLFetchComplete(const net::URLFetcher* source) { | |
69 std::string json_data; | |
70 source->GetResponseAsString(&json_data); | |
71 const bool request_succeeded = | |
72 source->GetStatus().is_success() && source->GetResponseCode() == 200; | |
73 | |
74 bool results_updated = false; | |
75 if (request_succeeded) { | |
76 JSONStringValueSerializer deserializer(json_data); | |
77 deserializer.set_allow_trailing_comma(true); | |
78 scoped_ptr<Value> data(deserializer.Deserialize(NULL, NULL)); | |
79 results_updated = data.get() && ParseSuggestResults(data.get()); | |
80 } | |
81 done_ = true; | |
82 | |
83 ConvertResultsToAutocompleteMatches(); | |
84 if (results_updated) | |
85 listener_->OnProviderUpdate(results_updated); | |
86 } | |
87 | |
88 ZeroSuggestProvider::~ZeroSuggestProvider() { | |
89 } | |
90 | |
91 void ZeroSuggestProvider::Run() { | |
92 const int kFetcherID = 1; | |
93 fetcher_.reset( | |
94 net::URLFetcher::Create(kFetcherID, | |
95 GURL(url_prefix_ + current_query_), | |
96 net::URLFetcher::GET, this)); | |
97 fetcher_->SetRequestContext(profile_->GetRequestContext()); | |
98 fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SAVE_COOKIES); | |
99 fetcher_->Start(); | |
100 } | |
101 | |
102 void ZeroSuggestProvider::UpdateMatches(const string16& user_text) { | |
103 user_text_ = user_text; | |
104 const size_t prev_num_matches = matches_.size(); | |
105 ConvertResultsToAutocompleteMatches(); | |
106 if (matches_.size() != prev_num_matches) | |
107 listener_->OnProviderUpdate(true); | |
108 } | |
109 | |
110 bool ZeroSuggestProvider::ParseSuggestResults(Value* root_val) { | |
111 std::string query; | |
112 ListValue* root_list = NULL; | |
113 ListValue* results = NULL; | |
114 if (!root_val->GetAsList(&root_list) || !root_list->GetString(0, &query) || | |
115 (query != current_query_) || !root_list->GetList(1, &results)) | |
116 return false; | |
117 | |
118 results_.clear(); | |
119 ListValue* one_result = NULL; | |
120 for (size_t index = 0; results->GetList(index, &one_result); ++index) { | |
121 string16 result; | |
122 one_result->GetString(0, &result); | |
123 if (result.empty()) | |
124 continue; | |
125 results_.push_back(result); | |
126 } | |
127 | |
128 return true; | |
129 } | |
130 | |
131 void ZeroSuggestProvider::ConvertResultsToAutocompleteMatches() { | |
132 const TemplateURL* search_provider = | |
133 template_url_service_->GetDefaultSearchProvider(); | |
134 // Fail if we can't set the clickthrough URL for query suggestions. | |
135 if (search_provider == NULL || !search_provider->SupportsReplacement()) | |
136 return; | |
137 matches_.clear(); | |
138 // Do not add anything if there are no results for this URL. | |
139 if (results_.empty()) | |
140 return; | |
141 AddMatchForCurrentURL(); | |
142 for (size_t i = 0; i < results_.size(); ++i) | |
143 AddMatchForResult(search_provider, i, results_[i]); | |
144 } | |
145 | |
146 // TODO(jered): Rip this out once the first match is decoupled from the current | |
147 // typing in the omnibox. | |
148 void ZeroSuggestProvider::AddMatchForCurrentURL() { | |
149 // If the user has typed something besides the current url, they probably | |
150 // don't intend to refresh it. | |
151 const bool user_text_is_url = user_text_ == current_query_text_; | |
152 if (user_text_.empty() || user_text_is_url) { | |
153 // The placeholder suggestion for the current URL has high relevance so | |
154 // that it is in the first suggestion slot and inline autocompleted. It | |
155 // gets dropped as soon as the user types something. | |
Peter Kasting
2012/08/15 21:52:44
Nit: Since the zerosuggest provider is the only th
Jered
2012/08/15 23:13:00
It seems existing providers (e.g. SearchProvider)
Peter Kasting
2012/08/16 00:24:41
I'm not really sure what you're saying here. The
| |
156 const int kPlaceholderRelevance = 2000; | |
157 AutocompleteMatch match(this, kPlaceholderRelevance, false, | |
158 AutocompleteMatch::NAVSUGGEST); | |
159 match.destination_url = GURL(current_query_); | |
160 match.contents = current_query_text_; | |
161 if (!user_text_is_url) { | |
162 match.fill_into_edit = current_query_text_; | |
163 match.inline_autocomplete_offset = 0; | |
164 } | |
165 AutocompleteMatch::ClassifyLocationInString(0, current_query_.size(), | |
166 match.contents.length(), ACMatchClassification::URL, | |
167 &match.contents_class); | |
168 matches_.push_back(match); | |
169 } | |
170 } | |
171 | |
172 void ZeroSuggestProvider::AddMatchForResult( | |
173 const TemplateURL* search_provider, | |
174 size_t result_index, | |
175 const string16& result) { | |
176 // TODO(jered): Rip out user_text_is_url logic when AddMatchForCurrentURL | |
177 // goes away. | |
178 const bool user_text_is_url = user_text_ == current_query_text_; | |
179 const bool kCaseInsensitve = false; | |
180 if (!user_text_.empty() && !user_text_is_url && | |
181 !StartsWith(result, user_text_, kCaseInsensitve)) | |
182 // This suggestion isn't relevant for the current prefix. | |
183 return; | |
184 // This bogus relevance puts suggestions below the placeholder from | |
185 // AddMatchForCurrentURL(), but very low after the user starts typing so that | |
186 // zero-suggestions go away after there are other suggestions. | |
187 // TODO(jered): Use real scores from the suggestion server. | |
188 const int suggestion_relevance = | |
189 (user_text_.empty() || user_text_is_url) ? 1999 : 100; | |
Peter Kasting
2012/08/15 21:52:44
The suggestions we add should not have the same re
Jered
2012/08/15 23:13:00
Done.
| |
190 AutocompleteMatch match(this, suggestion_relevance, false, | |
191 AutocompleteMatch::SEARCH_SUGGEST); | |
192 match.contents = result; | |
193 match.fill_into_edit = result; | |
194 if (!user_text_is_url && user_text_ != result) | |
195 match.inline_autocomplete_offset = user_text_.length(); | |
196 | |
197 // Build a URL for this query using the default search provider. | |
198 const TemplateURLRef& search_url = search_provider->url_ref(); | |
199 DCHECK(search_url.SupportsReplacement()); | |
200 match.search_terms_args.reset( | |
201 new TemplateURLRef::SearchTermsArgs(result)); | |
202 match.search_terms_args->original_query = string16(); | |
203 match.search_terms_args->accepted_suggestion = result_index; | |
204 match.destination_url = | |
205 GURL(search_url.ReplaceSearchTerms(*match.search_terms_args.get())); | |
206 | |
207 if (user_text_.empty() || user_text_is_url || user_text_ == result) { | |
208 match.contents_class.push_back( | |
209 ACMatchClassification(0, ACMatchClassification::NONE)); | |
210 } else { | |
211 // Style to look like normal search suggestions. | |
212 match.contents_class.push_back( | |
213 ACMatchClassification(0, ACMatchClassification::DIM)); | |
214 match.contents_class.push_back( | |
215 ACMatchClassification(user_text_.length(), ACMatchClassification::NONE)); | |
216 } | |
217 match.transition = content::PAGE_TRANSITION_GENERATED; | |
218 | |
219 matches_.push_back(match); | |
220 } | |
OLD | NEW |