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

Unified 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 4 years 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 side-by-side diff with in-line comments
Download patch
Index: ios/chrome/browser/ui/contextual_search/contextual_search_delegate.cc
diff --git a/ios/chrome/browser/ui/contextual_search/contextual_search_delegate.cc b/ios/chrome/browser/ui/contextual_search/contextual_search_delegate.cc
new file mode 100644
index 0000000000000000000000000000000000000000..3acaf6243c6bcf438fdbfdfa8efe1a4fa0fb8e00
--- /dev/null
+++ b/ios/chrome/browser/ui/contextual_search/contextual_search_delegate.cc
@@ -0,0 +1,333 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ios/chrome/browser/ui/contextual_search/contextual_search_delegate.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/base64.h"
+#include "base/command_line.h"
+#include "base/json/json_string_value_serializer.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "components/google/core/browser/google_util.h"
+#include "components/search_engines/template_url_service.h"
+#include "components/search_engines/util.h"
+#include "components/variations/net/variations_http_headers.h"
+#include "components/variations/variations_associated_data.h"
+#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
+#include "ios/chrome/browser/search_engines/template_url_service_factory.h"
+#include "ios/chrome/browser/ui/contextual_search/protos/client_discourse_context.pb.h"
+#include "ios/web/public/web_thread.h"
+#include "net/base/escape.h"
+#include "net/base/url_util.h"
+#include "net/url_request/url_fetcher.h"
+#include "url/gurl.h"
+
+namespace {
+
+const char kContextualSearchFieldTrialName[] = "ContextualSearch";
+const char kContextualSearchPreventPreload[] = "prevent_preload";
+const char kContextualSearchResolverUrl[] = "contextual-search-resolver-url";
+const char kContextualSearchResolverURLParamName[] = "resolver_url";
+const char kContextualSearchResponseDisplayTextParam[] = "display_text";
+const char kContextualSearchResponseMentionsParam[] = "mentions";
+const char kContextualSearchResponseResolvedTermParam[] = "resolved_term";
+const char kContextualSearchResponseSelectedTextParam[] = "selected_text";
+const char kContextualSearchResponseSearchTermParam[] = "search_term";
+const int kContextualSearchRequestVersion = 2;
+const char kContextualSearchServerEndpoint[] = "_/contextualsearch?";
+const char kDiscourseContextHeaderPrefix[] = "X-Additional-Discourse-Context: ";
+const char kDoPreventPreloadValue[] = "1";
+const char kXssiEscape[] = ")]}'\n";
+
+const double kMinimumDelayBetweenRequestSeconds = 1;
+
+// Decodes the given response from the search term resolution request and sets
+// the value of the given search-term and display_text parameters.
+void DecodeSearchTermsFromJsonResponse(const std::string& response,
+ std::string* search_term,
+ std::string* display_text,
+ std::string* alternate_term,
+ std::string* prevent_preload,
+ int& start_offset,
+ int& end_offset) {
+ bool contains_xssi_escape = response.find(kXssiEscape) == 0;
+ const std::string& proper_json =
+ contains_xssi_escape ? response.substr(strlen(kXssiEscape)) : response;
+ JSONStringValueDeserializer deserializer(proper_json);
+ std::unique_ptr<base::Value> root(deserializer.Deserialize(NULL, NULL));
+
+ if (root.get() != NULL && root->IsType(base::Value::Type::DICTIONARY)) {
+ base::DictionaryValue* dict =
+ static_cast<base::DictionaryValue*>(root.get());
+ dict->GetString(kContextualSearchPreventPreload, prevent_preload);
+ dict->GetString(kContextualSearchResponseSearchTermParam, search_term);
+ // For the display_text, if not present fall back to the "search_term".
+ if (!dict->GetString(kContextualSearchResponseDisplayTextParam,
+ display_text)) {
+ *display_text = *search_term;
+ }
+ // If either the selected text or the resolved term is not the search term,
+ // use it as the alternate term.
+ std::string selected_text;
+ dict->GetString(kContextualSearchResponseSelectedTextParam, &selected_text);
+
+ const base::ListValue* mentionsList;
+ if (dict->GetList(kContextualSearchResponseMentionsParam, &mentionsList)) {
+ DCHECK(mentionsList->GetSize() == 2);
+ mentionsList->GetInteger(0, &start_offset);
+ mentionsList->GetInteger(1, &end_offset);
+ }
+
+ if (selected_text != *search_term) {
+ *alternate_term = selected_text;
+ } else {
+ std::string resolved_term;
+ dict->GetString(kContextualSearchResponseResolvedTermParam,
+ &resolved_term);
+ if (resolved_term != *search_term) {
+ *alternate_term = resolved_term;
+ }
+ }
+ }
+}
+
+} // namespace
+
+// URLFetcher ID, only used for tests: we only have one kind of fetcher.
+const int ContextualSearchDelegate::kContextualSearchURLFetcherID = 1;
+
+// Handles tasks for the ContextualSearchManager in a separable, testable way.
+ContextualSearchDelegate::ContextualSearchDelegate(
+ ios::ChromeBrowserState* browser_state,
+ const ContextualSearchDelegate::SearchTermResolutionCallback&
+ search_term_callback)
+ : template_url_service_(
+ ios::TemplateURLServiceFactory::GetForBrowserState(browser_state)),
+ browser_state_(browser_state),
+ search_term_callback_(search_term_callback),
+ weak_ptr_factory_(this) {}
+
+ContextualSearchDelegate::~ContextualSearchDelegate() {}
+
+void ContextualSearchDelegate::PostSearchTermRequest(
+ std::shared_ptr<ContextualSearchContext> context) {
+ context_ = context;
+ if (request_pending_) {
+ return;
+ }
+ request_pending_ = true;
+
+ base::TimeDelta interval =
+ base::TimeDelta::FromSecondsD(kMinimumDelayBetweenRequestSeconds);
+ base::Time now = base::Time::Now();
+ if (now > last_request_startup_time_ + interval) {
+ StartPendingSearchTermRequest();
+ } else {
+ base::TimeDelta delay = last_request_startup_time_ + interval - now;
+ web::WebThread::PostDelayedTask(
+ web::WebThread::UI, FROM_HERE,
+ base::Bind(&ContextualSearchDelegate::StartPendingSearchTermRequest,
+ weak_ptr_factory_.GetWeakPtr()),
+ delay);
+ }
+}
+
+void ContextualSearchDelegate::StartPendingSearchTermRequest() {
+ if (!request_pending_ || !context_)
+ return;
+ request_pending_ = false;
+ last_request_startup_time_ = base::Time::Now();
+ if (context_->HasSurroundingText()) {
+ RequestServerSearchTerm();
+ } else {
+ RequestLocalSearchTerm();
+ }
+}
+
+void ContextualSearchDelegate::RequestLocalSearchTerm() {
+ SearchResolution resolution;
+ resolution.is_invalid = false;
+ resolution.response_code = 200; // HTTP success.
+ resolution.search_term = context_->selected_text;
+ resolution.display_text = context_->selected_text;
+ resolution.alternate_term = context_->selected_text;
+ resolution.prevent_preload = false;
+ resolution.start_offset = -1;
+ resolution.end_offset = -1;
+ search_term_callback_.Run(resolution);
+}
+
+void ContextualSearchDelegate::RequestServerSearchTerm() {
+ GURL request_url(BuildRequestUrl());
+ DCHECK(request_url.is_valid());
+
+ // Reset will delete any previous fetcher, and we won't get any callback.
+ search_term_fetcher_ = net::URLFetcher::Create(
+ kContextualSearchURLFetcherID, request_url, net::URLFetcher::GET, this);
+ search_term_fetcher_->SetRequestContext(browser_state_->GetRequestContext());
+
+ // Add Chrome experiment state to the request headers.
+ net::HttpRequestHeaders headers;
+ // Note: It's fine to pass in |is_signed_in| false, which does not affect
+ // transmission of experiment ids coming from the variations server.
+ bool is_signed_in = false;
+ variations::AppendVariationHeaders(search_term_fetcher_->GetOriginalURL(),
+ browser_state_->IsOffTheRecord(), false,
+ is_signed_in, &headers);
+ search_term_fetcher_->SetExtraRequestHeaders(headers.ToString());
+
+ SetDiscourseContextAndAddToHeader(*context_);
+
+ search_term_fetcher_->Start();
+}
+
+void ContextualSearchDelegate::CancelSearchTermRequest() {
+ search_term_fetcher_.reset();
+ context_.reset();
+}
+
+// Adapted from /chrome/browser/search_engines/template_url_service_android.cc
+GURL ContextualSearchDelegate::GetURLForResolvedSearch(
+ SearchResolution resolution,
+ bool should_prefetch) {
+ GURL url;
+ if (!resolution.search_term.empty()) {
+ url = GetDefaultSearchURLForSearchTerms(
+ template_url_service_, base::UTF8ToUTF16(resolution.search_term));
+ if (google_util::IsGoogleSearchUrl(url)) {
+ url = net::AppendQueryParameter(url, "ctxs", "2");
+ if (should_prefetch) {
+ // Indicate that the search page is being prefetched.
+ url = net::AppendQueryParameter(url, "pf", "c");
+ }
+
+ if (!resolution.alternate_term.empty()) {
+ url = net::AppendQueryParameter(url, "ctxsl_alternate_term",
+ resolution.alternate_term);
+ }
+ }
+ }
+ return url;
+}
+
+void ContextualSearchDelegate::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ DCHECK(source == search_term_fetcher_.get());
+ SearchResolution resolution;
+ std::string prevent_preload;
+ resolution.response_code = source->GetResponseCode();
+ if (source->GetStatus().is_success() && resolution.response_code == 200) {
+ std::string response;
+ bool has_string_response = source->GetResponseAsString(&response);
+ DCHECK(has_string_response);
+ if (has_string_response) {
+ resolution.start_offset = -1;
+ resolution.end_offset = -1;
+ DecodeSearchTermsFromJsonResponse(
+ response, &resolution.search_term, &resolution.display_text,
+ &resolution.alternate_term, &prevent_preload, resolution.start_offset,
+ resolution.end_offset);
+ }
+ }
+ resolution.is_invalid =
+ resolution.response_code == net::URLFetcher::RESPONSE_CODE_INVALID;
+ resolution.prevent_preload = prevent_preload == kDoPreventPreloadValue;
+
+ search_term_callback_.Run(resolution);
+}
+
+// TODO(donnd): use HTTP headers for the context instead of CGI params in GET.
+// See https://code.google.com/p/chromium/issues/detail?id=341762
+GURL ContextualSearchDelegate::BuildRequestUrl() {
+ // TODO(jeremycho): Confirm this is the right way to handle TemplateURL fails.
+ if (!template_url_service_ ||
+ !template_url_service_->GetDefaultSearchProvider()) {
+ return GURL();
+ }
+
+ std::string selected_text_escaped(
+ net::EscapeQueryParamValue(context_->selected_text, true));
+ std::string base_page_url = context_->page_url.spec();
+ bool use_resolved_search_term = context_->use_resolved_search_term;
+
+ std::string request = GetSearchTermResolutionUrlString(
+ selected_text_escaped, base_page_url, use_resolved_search_term);
+
+ return GURL(request);
+}
+
+std::string ContextualSearchDelegate::GetSearchTermResolutionUrlString(
+ const std::string& selected_text,
+ const std::string& base_page_url,
+ const bool use_resolved_search_term) {
+ TemplateURL* template_url = template_url_service_->GetDefaultSearchProvider();
+
+ TemplateURLRef::SearchTermsArgs search_terms_args =
+ TemplateURLRef::SearchTermsArgs(base::string16());
+
+ TemplateURLRef::SearchTermsArgs::ContextualSearchParams params(
+ kContextualSearchRequestVersion, selected_text, base_page_url,
+ use_resolved_search_term);
+
+ search_terms_args.contextual_search_params = params;
+
+ std::string request(
+ template_url->contextual_search_url_ref().ReplaceSearchTerms(
+ search_terms_args, template_url_service_->search_terms_data(), NULL));
+
+ // The switch/param should be the URL up to and including the endpoint.
+ std::string replacement_url;
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ kContextualSearchResolverUrl)) {
+ replacement_url =
+ base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kContextualSearchResolverUrl);
+ } else {
+ std::string param_value = variations::GetVariationParamValue(
+ kContextualSearchFieldTrialName, kContextualSearchResolverURLParamName);
+ if (!param_value.empty())
+ replacement_url = param_value;
+ }
+
+ // If a replacement URL was specified above, do the substitution.
+ if (!replacement_url.empty()) {
+ size_t pos = request.find(kContextualSearchServerEndpoint);
+ if (pos != std::string::npos) {
+ request.replace(0, pos + strlen(kContextualSearchServerEndpoint),
+ replacement_url);
+ }
+ }
+ return request;
+}
+
+void ContextualSearchDelegate::SetDiscourseContextAndAddToHeader(
+ const ContextualSearchContext& context) {
+ discourse_context::ClientDiscourseContext proto;
+ discourse_context::Display* display = proto.add_display();
+ display->set_uri(context.page_url.spec());
+
+ discourse_context::Media* media = display->mutable_media();
+ media->set_mime_type(context.encoding);
+
+ discourse_context::Selection* selection = display->mutable_selection();
+ selection->set_content(
+ net::EscapeQueryParamValue(UTF16ToUTF8(context.surrounding_text), true));
+ selection->set_start(context.start_offset);
+ selection->set_end(context.end_offset);
+
+ std::string serialized;
+ proto.SerializeToString(&serialized);
+
+ std::string encoded_context;
+ base::Base64Encode(serialized, &encoded_context);
+ // The server memoizer expects a web-safe encoding.
+ std::replace(encoded_context.begin(), encoded_context.end(), '+', '-');
+ std::replace(encoded_context.begin(), encoded_context.end(), '/', '_');
+ search_term_fetcher_->AddExtraRequestHeader(kDiscourseContextHeaderPrefix +
+ encoded_context);
+}

Powered by Google App Engine
This is Rietveld 408576698