Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(174)

Side by Side Diff: ios/chrome/browser/ui/contextual_search/contextual_search_delegate.cc

Issue 2588713002: Upstream Chrome on iOS source code [4/11]. (Closed)
Patch Set: Created 3 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2014 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 "ios/chrome/browser/ui/contextual_search/contextual_search_delegate.h"
6
7 #include <algorithm>
8 #include <utility>
9
10 #include "base/base64.h"
11 #include "base/command_line.h"
12 #include "base/json/json_string_value_serializer.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "components/google/core/browser/google_util.h"
17 #include "components/search_engines/template_url_service.h"
18 #include "components/search_engines/util.h"
19 #include "components/variations/net/variations_http_headers.h"
20 #include "components/variations/variations_associated_data.h"
21 #include "ios/chrome/browser/browser_state/chrome_browser_state.h"
22 #include "ios/chrome/browser/search_engines/template_url_service_factory.h"
23 #include "ios/chrome/browser/ui/contextual_search/protos/client_discourse_contex t.pb.h"
24 #include "ios/web/public/web_thread.h"
25 #include "net/base/escape.h"
26 #include "net/base/url_util.h"
27 #include "net/url_request/url_fetcher.h"
28 #include "url/gurl.h"
29
30 namespace {
31
32 const char kContextualSearchFieldTrialName[] = "ContextualSearch";
33 const char kContextualSearchPreventPreload[] = "prevent_preload";
34 const char kContextualSearchResolverUrl[] = "contextual-search-resolver-url";
35 const char kContextualSearchResolverURLParamName[] = "resolver_url";
36 const char kContextualSearchResponseDisplayTextParam[] = "display_text";
37 const char kContextualSearchResponseMentionsParam[] = "mentions";
38 const char kContextualSearchResponseResolvedTermParam[] = "resolved_term";
39 const char kContextualSearchResponseSelectedTextParam[] = "selected_text";
40 const char kContextualSearchResponseSearchTermParam[] = "search_term";
41 const int kContextualSearchRequestVersion = 2;
42 const char kContextualSearchServerEndpoint[] = "_/contextualsearch?";
43 const char kDiscourseContextHeaderPrefix[] = "X-Additional-Discourse-Context: ";
44 const char kDoPreventPreloadValue[] = "1";
45 const char kXssiEscape[] = ")]}'\n";
46
47 const double kMinimumDelayBetweenRequestSeconds = 1;
48
49 // Decodes the given response from the search term resolution request and sets
50 // the value of the given search-term and display_text parameters.
51 void DecodeSearchTermsFromJsonResponse(const std::string& response,
52 std::string* search_term,
53 std::string* display_text,
54 std::string* alternate_term,
55 std::string* prevent_preload,
56 int& start_offset,
57 int& end_offset) {
58 bool contains_xssi_escape = response.find(kXssiEscape) == 0;
59 const std::string& proper_json =
60 contains_xssi_escape ? response.substr(strlen(kXssiEscape)) : response;
61 JSONStringValueDeserializer deserializer(proper_json);
62 std::unique_ptr<base::Value> root(deserializer.Deserialize(NULL, NULL));
63
64 if (root.get() != NULL && root->IsType(base::Value::Type::DICTIONARY)) {
65 base::DictionaryValue* dict =
66 static_cast<base::DictionaryValue*>(root.get());
67 dict->GetString(kContextualSearchPreventPreload, prevent_preload);
68 dict->GetString(kContextualSearchResponseSearchTermParam, search_term);
69 // For the display_text, if not present fall back to the "search_term".
70 if (!dict->GetString(kContextualSearchResponseDisplayTextParam,
71 display_text)) {
72 *display_text = *search_term;
73 }
74 // If either the selected text or the resolved term is not the search term,
75 // use it as the alternate term.
76 std::string selected_text;
77 dict->GetString(kContextualSearchResponseSelectedTextParam, &selected_text);
78
79 const base::ListValue* mentionsList;
80 if (dict->GetList(kContextualSearchResponseMentionsParam, &mentionsList)) {
81 DCHECK(mentionsList->GetSize() == 2);
82 mentionsList->GetInteger(0, &start_offset);
83 mentionsList->GetInteger(1, &end_offset);
84 }
85
86 if (selected_text != *search_term) {
87 *alternate_term = selected_text;
88 } else {
89 std::string resolved_term;
90 dict->GetString(kContextualSearchResponseResolvedTermParam,
91 &resolved_term);
92 if (resolved_term != *search_term) {
93 *alternate_term = resolved_term;
94 }
95 }
96 }
97 }
98
99 } // namespace
100
101 // URLFetcher ID, only used for tests: we only have one kind of fetcher.
102 const int ContextualSearchDelegate::kContextualSearchURLFetcherID = 1;
103
104 // Handles tasks for the ContextualSearchManager in a separable, testable way.
105 ContextualSearchDelegate::ContextualSearchDelegate(
106 ios::ChromeBrowserState* browser_state,
107 const ContextualSearchDelegate::SearchTermResolutionCallback&
108 search_term_callback)
109 : template_url_service_(
110 ios::TemplateURLServiceFactory::GetForBrowserState(browser_state)),
111 browser_state_(browser_state),
112 search_term_callback_(search_term_callback),
113 weak_ptr_factory_(this) {}
114
115 ContextualSearchDelegate::~ContextualSearchDelegate() {}
116
117 void ContextualSearchDelegate::PostSearchTermRequest(
118 std::shared_ptr<ContextualSearchContext> context) {
119 context_ = context;
120 if (request_pending_) {
121 return;
122 }
123 request_pending_ = true;
124
125 base::TimeDelta interval =
126 base::TimeDelta::FromSecondsD(kMinimumDelayBetweenRequestSeconds);
127 base::Time now = base::Time::Now();
128 if (now > last_request_startup_time_ + interval) {
129 StartPendingSearchTermRequest();
130 } else {
131 base::TimeDelta delay = last_request_startup_time_ + interval - now;
132 web::WebThread::PostDelayedTask(
133 web::WebThread::UI, FROM_HERE,
134 base::Bind(&ContextualSearchDelegate::StartPendingSearchTermRequest,
135 weak_ptr_factory_.GetWeakPtr()),
136 delay);
137 }
138 }
139
140 void ContextualSearchDelegate::StartPendingSearchTermRequest() {
141 if (!request_pending_ || !context_)
142 return;
143 request_pending_ = false;
144 last_request_startup_time_ = base::Time::Now();
145 if (context_->HasSurroundingText()) {
146 RequestServerSearchTerm();
147 } else {
148 RequestLocalSearchTerm();
149 }
150 }
151
152 void ContextualSearchDelegate::RequestLocalSearchTerm() {
153 SearchResolution resolution;
154 resolution.is_invalid = false;
155 resolution.response_code = 200; // HTTP success.
156 resolution.search_term = context_->selected_text;
157 resolution.display_text = context_->selected_text;
158 resolution.alternate_term = context_->selected_text;
159 resolution.prevent_preload = false;
160 resolution.start_offset = -1;
161 resolution.end_offset = -1;
162 search_term_callback_.Run(resolution);
163 }
164
165 void ContextualSearchDelegate::RequestServerSearchTerm() {
166 GURL request_url(BuildRequestUrl());
167 DCHECK(request_url.is_valid());
168
169 // Reset will delete any previous fetcher, and we won't get any callback.
170 search_term_fetcher_ = net::URLFetcher::Create(
171 kContextualSearchURLFetcherID, request_url, net::URLFetcher::GET, this);
172 search_term_fetcher_->SetRequestContext(browser_state_->GetRequestContext());
173
174 // Add Chrome experiment state to the request headers.
175 net::HttpRequestHeaders headers;
176 // Note: It's fine to pass in |is_signed_in| false, which does not affect
177 // transmission of experiment ids coming from the variations server.
178 bool is_signed_in = false;
179 variations::AppendVariationHeaders(search_term_fetcher_->GetOriginalURL(),
180 browser_state_->IsOffTheRecord(), false,
181 is_signed_in, &headers);
182 search_term_fetcher_->SetExtraRequestHeaders(headers.ToString());
183
184 SetDiscourseContextAndAddToHeader(*context_);
185
186 search_term_fetcher_->Start();
187 }
188
189 void ContextualSearchDelegate::CancelSearchTermRequest() {
190 search_term_fetcher_.reset();
191 context_.reset();
192 }
193
194 // Adapted from /chrome/browser/search_engines/template_url_service_android.cc
195 GURL ContextualSearchDelegate::GetURLForResolvedSearch(
196 SearchResolution resolution,
197 bool should_prefetch) {
198 GURL url;
199 if (!resolution.search_term.empty()) {
200 url = GetDefaultSearchURLForSearchTerms(
201 template_url_service_, base::UTF8ToUTF16(resolution.search_term));
202 if (google_util::IsGoogleSearchUrl(url)) {
203 url = net::AppendQueryParameter(url, "ctxs", "2");
204 if (should_prefetch) {
205 // Indicate that the search page is being prefetched.
206 url = net::AppendQueryParameter(url, "pf", "c");
207 }
208
209 if (!resolution.alternate_term.empty()) {
210 url = net::AppendQueryParameter(url, "ctxsl_alternate_term",
211 resolution.alternate_term);
212 }
213 }
214 }
215 return url;
216 }
217
218 void ContextualSearchDelegate::OnURLFetchComplete(
219 const net::URLFetcher* source) {
220 DCHECK(source == search_term_fetcher_.get());
221 SearchResolution resolution;
222 std::string prevent_preload;
223 resolution.response_code = source->GetResponseCode();
224 if (source->GetStatus().is_success() && resolution.response_code == 200) {
225 std::string response;
226 bool has_string_response = source->GetResponseAsString(&response);
227 DCHECK(has_string_response);
228 if (has_string_response) {
229 resolution.start_offset = -1;
230 resolution.end_offset = -1;
231 DecodeSearchTermsFromJsonResponse(
232 response, &resolution.search_term, &resolution.display_text,
233 &resolution.alternate_term, &prevent_preload, resolution.start_offset,
234 resolution.end_offset);
235 }
236 }
237 resolution.is_invalid =
238 resolution.response_code == net::URLFetcher::RESPONSE_CODE_INVALID;
239 resolution.prevent_preload = prevent_preload == kDoPreventPreloadValue;
240
241 search_term_callback_.Run(resolution);
242 }
243
244 // TODO(donnd): use HTTP headers for the context instead of CGI params in GET.
245 // See https://code.google.com/p/chromium/issues/detail?id=341762
246 GURL ContextualSearchDelegate::BuildRequestUrl() {
247 // TODO(jeremycho): Confirm this is the right way to handle TemplateURL fails.
248 if (!template_url_service_ ||
249 !template_url_service_->GetDefaultSearchProvider()) {
250 return GURL();
251 }
252
253 std::string selected_text_escaped(
254 net::EscapeQueryParamValue(context_->selected_text, true));
255 std::string base_page_url = context_->page_url.spec();
256 bool use_resolved_search_term = context_->use_resolved_search_term;
257
258 std::string request = GetSearchTermResolutionUrlString(
259 selected_text_escaped, base_page_url, use_resolved_search_term);
260
261 return GURL(request);
262 }
263
264 std::string ContextualSearchDelegate::GetSearchTermResolutionUrlString(
265 const std::string& selected_text,
266 const std::string& base_page_url,
267 const bool use_resolved_search_term) {
268 TemplateURL* template_url = template_url_service_->GetDefaultSearchProvider();
269
270 TemplateURLRef::SearchTermsArgs search_terms_args =
271 TemplateURLRef::SearchTermsArgs(base::string16());
272
273 TemplateURLRef::SearchTermsArgs::ContextualSearchParams params(
274 kContextualSearchRequestVersion, selected_text, base_page_url,
275 use_resolved_search_term);
276
277 search_terms_args.contextual_search_params = params;
278
279 std::string request(
280 template_url->contextual_search_url_ref().ReplaceSearchTerms(
281 search_terms_args, template_url_service_->search_terms_data(), NULL));
282
283 // The switch/param should be the URL up to and including the endpoint.
284 std::string replacement_url;
285 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
286 kContextualSearchResolverUrl)) {
287 replacement_url =
288 base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
289 kContextualSearchResolverUrl);
290 } else {
291 std::string param_value = variations::GetVariationParamValue(
292 kContextualSearchFieldTrialName, kContextualSearchResolverURLParamName);
293 if (!param_value.empty())
294 replacement_url = param_value;
295 }
296
297 // If a replacement URL was specified above, do the substitution.
298 if (!replacement_url.empty()) {
299 size_t pos = request.find(kContextualSearchServerEndpoint);
300 if (pos != std::string::npos) {
301 request.replace(0, pos + strlen(kContextualSearchServerEndpoint),
302 replacement_url);
303 }
304 }
305 return request;
306 }
307
308 void ContextualSearchDelegate::SetDiscourseContextAndAddToHeader(
309 const ContextualSearchContext& context) {
310 discourse_context::ClientDiscourseContext proto;
311 discourse_context::Display* display = proto.add_display();
312 display->set_uri(context.page_url.spec());
313
314 discourse_context::Media* media = display->mutable_media();
315 media->set_mime_type(context.encoding);
316
317 discourse_context::Selection* selection = display->mutable_selection();
318 selection->set_content(
319 net::EscapeQueryParamValue(UTF16ToUTF8(context.surrounding_text), true));
320 selection->set_start(context.start_offset);
321 selection->set_end(context.end_offset);
322
323 std::string serialized;
324 proto.SerializeToString(&serialized);
325
326 std::string encoded_context;
327 base::Base64Encode(serialized, &encoded_context);
328 // The server memoizer expects a web-safe encoding.
329 std::replace(encoded_context.begin(), encoded_context.end(), '+', '-');
330 std::replace(encoded_context.begin(), encoded_context.end(), '/', '_');
331 search_term_fetcher_->AddExtraRequestHeader(kDiscourseContextHeaderPrefix +
332 encoded_context);
333 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698