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

Unified Diff: chrome/browser/android/contextualsearch/contextual_search_delegate.cc

Issue 1141283003: Upstream oodles of Chrome for Android code into Chromium. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: final patch? Created 5 years, 7 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 side-by-side diff with in-line comments
Download patch
Index: chrome/browser/android/contextualsearch/contextual_search_delegate.cc
diff --git a/chrome/browser/android/contextualsearch/contextual_search_delegate.cc b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
new file mode 100644
index 0000000000000000000000000000000000000000..332e0b36cc6e7a9d3f30272105e48c19eb0df26f
--- /dev/null
+++ b/chrome/browser/android/contextualsearch/contextual_search_delegate.cc
@@ -0,0 +1,489 @@
+// Copyright 2015 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 "chrome/browser/android/contextualsearch/contextual_search_delegate.h"
+
+#include <algorithm>
+
+#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 "chrome/browser/android/proto/client_discourse_context.pb.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "components/search_engines/template_url_service.h"
+#include "components/variations/net/variations_http_header_provider.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/android/content_view_core.h"
+#include "content/public/browser/web_contents.h"
+#include "net/base/escape.h"
+#include "net/url_request/url_fetcher.h"
+#include "url/gurl.h"
+
+using content::ContentViewCore;
+
+namespace {
+
+const char kContextualSearchFieldTrialName[] = "ContextualSearch";
+const char kContextualSearchSurroundingSizeParamName[] = "surrounding_size";
+const char kContextualSearchIcingSurroundingSizeParamName[] =
+ "icing_surrounding_size";
+const char kContextualSearchResolverURLParamName[] = "resolver_url";
+const char kContextualSearchDoNotSendURLParamName[] = "do_not_send_url";
+const char kContextualSearchResponseDisplayTextParam[] = "display_text";
+const char kContextualSearchResponseSelectedTextParam[] = "selected_text";
+const char kContextualSearchResponseSearchTermParam[] = "search_term";
+const char kContextualSearchResponseResolvedTermParam[] = "resolved_term";
+const char kContextualSearchPreventPreload[] = "prevent_preload";
+const char kContextualSearchServerEndpoint[] = "_/contextualsearch?";
+const int kContextualSearchRequestVersion = 2;
+const char kContextualSearchResolverUrl[] =
+ "contextual-search-resolver-url";
+// The default size of the content surrounding the selection to gather, allowing
+// room for other parameters.
+const int kContextualSearchDefaultContentSize = 1536;
+const int kContextualSearchDefaultIcingSurroundingSize = 400;
+// The maximum length of a URL to build.
+const int kMaxURLSize = 2048;
+const char kXssiEscape[] = ")]}'\n";
+const char kDiscourseContextHeaderPrefix[] = "X-Additional-Discourse-Context: ";
+const char kDoPreventPreloadValue[] = "1";
+
+// The number of characters that should be shown on each side of the selected
+// expression.
+const int kSurroundingSizeForUI = 30;
+
+} // 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(
+ net::URLRequestContextGetter* url_request_context,
+ TemplateURLService* template_url_service,
+ const ContextualSearchDelegate::SearchTermResolutionCallback&
+ search_term_callback,
+ const ContextualSearchDelegate::SurroundingTextCallback&
+ surrounding_callback,
+ const ContextualSearchDelegate::IcingCallback& icing_callback)
+ : url_request_context_(url_request_context),
+ template_url_service_(template_url_service),
+ search_term_callback_(search_term_callback),
+ surrounding_callback_(surrounding_callback),
+ icing_callback_(icing_callback) {
+}
+
+ContextualSearchDelegate::~ContextualSearchDelegate() {
+}
+
+void ContextualSearchDelegate::StartSearchTermResolutionRequest(
+ const std::string& selection,
+ bool use_resolved_search_term,
+ content::ContentViewCore* content_view_core) {
+ GatherSurroundingTextWithCallback(
+ selection,
+ use_resolved_search_term,
+ content_view_core,
+ base::Bind(&ContextualSearchDelegate::StartSearchTermRequestFromSelection,
+ AsWeakPtr()));
+}
+
+void ContextualSearchDelegate::GatherAndSaveSurroundingText(
+ const std::string& selection,
+ bool use_resolved_search_term,
+ content::ContentViewCore* content_view_core) {
+ GatherSurroundingTextWithCallback(
+ selection,
+ use_resolved_search_term,
+ content_view_core,
+ base::Bind(&ContextualSearchDelegate::SaveSurroundingText, AsWeakPtr()));
+ // TODO(donnd): clear the context here, since we're done with it (but risky).
+}
+
+void ContextualSearchDelegate::ContinueSearchTermResolutionRequest() {
+ DCHECK(context_.get());
+ if (!context_.get())
+ return;
+ 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_.reset(
+ net::URLFetcher::Create(kContextualSearchURLFetcherID, request_url,
+ net::URLFetcher::GET, this).release());
+ search_term_fetcher_->SetRequestContext(url_request_context_);
+
+ // Add Chrome experiment state to the request headers.
+ net::HttpRequestHeaders headers;
+ variations::VariationsHttpHeaderProvider::GetInstance()->AppendHeaders(
+ search_term_fetcher_->GetOriginalURL(),
+ false, // Impossible to be incognito at this point.
+ false,
+ &headers);
+ search_term_fetcher_->SetExtraRequestHeaders(headers.ToString());
+
+ SetDiscourseContextAndAddToHeader(*context_);
+
+ search_term_fetcher_->Start();
+}
+
+void ContextualSearchDelegate::OnURLFetchComplete(
+ const net::URLFetcher* source) {
+ DCHECK(source == search_term_fetcher_.get());
+ int response_code = source->GetResponseCode();
+ std::string search_term;
+ std::string display_text;
+ std::string alternate_term;
+ std::string prevent_preload;
+
+ if (source->GetStatus().is_success() && response_code == 200) {
+ std::string response;
+ bool has_string_response = source->GetResponseAsString(&response);
+ DCHECK(has_string_response);
+ if (has_string_response) {
+ DecodeSearchTermsFromJsonResponse(response, &search_term, &display_text,
+ &alternate_term, &prevent_preload);
+ }
+ }
+ bool is_invalid = response_code == net::URLFetcher::RESPONSE_CODE_INVALID;
+ search_term_callback_.Run(
+ is_invalid, response_code, search_term, display_text, alternate_term,
+ prevent_preload == kDoPreventPreloadValue);
+
+ // The ContextualSearchContext is consumed once the request has completed.
+ context_.reset();
+}
+
+// TODO(jeremycho): Remove selected_text and base_page_url CGI parameters.
+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_escaped(
+ net::EscapeQueryParamValue(context_->page_url.spec(), true));
+ bool use_resolved_search_term = context_->use_resolved_search_term;
+
+ // If the request is too long, don't include the base-page URL.
+ std::string request = GetSearchTermResolutionUrlString(
+ selected_text_escaped, base_page_url_escaped, use_resolved_search_term);
+ if (request.length() >= kMaxURLSize) {
+ request = GetSearchTermResolutionUrlString(
+ selected_text_escaped, "", 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::GatherSurroundingTextWithCallback(
+ const std::string& selection,
+ bool use_resolved_search_term,
+ content::ContentViewCore* content_view_core,
+ HandleSurroundingsCallback callback) {
+ // Immediately cancel any request that's in flight, since we're building a new
+ // context (and the response disposes of any existing context).
+ search_term_fetcher_.reset();
+ // Decide if the URL be sent with the context.
+ GURL page_url(content_view_core->GetWebContents()->GetURL());
+ GURL url_to_send;
+ if (CanSendPageURL(page_url,
+ ProfileManager::GetActiveUserProfile(),
+ template_url_service_)) {
+ url_to_send = page_url;
+ }
+ std::string encoding(content_view_core->GetWebContents()->GetEncoding());
+ context_.reset(new ContextualSearchContext(
+ selection, use_resolved_search_term, url_to_send, encoding));
+ content_view_core->RequestTextSurroundingSelection(
+ GetSearchTermSurroundingSize(), callback);
+}
+
+void ContextualSearchDelegate::StartSearchTermRequestFromSelection(
+ const base::string16& surrounding_text,
+ int start_offset,
+ int end_offset) {
+ // TODO(donnd): figure out how to gather text surrounding the selection
+ // for other purposes too: e.g. to determine if we should select the
+ // word where the user tapped.
+ DCHECK(context_.get());
+ SaveSurroundingText(surrounding_text, start_offset, end_offset);
+ SendSurroundingText(kSurroundingSizeForUI);
+ ContinueSearchTermResolutionRequest();
+}
+
+void ContextualSearchDelegate::SaveSurroundingText(
+ const base::string16& surrounding_text,
+ int start_offset,
+ int end_offset) {
+ DCHECK(context_.get());
+ // Sometimes the surroundings are 0, 0, '', so fall back on the selection.
+ // See crbug.com/393100.
+ if (start_offset == 0 && end_offset == 0 && surrounding_text.length() == 0) {
+ context_->surrounding_text = base::UTF8ToUTF16(context_->selected_text);
+ context_->start_offset = 0;
+ context_->end_offset = context_->selected_text.length();
+ } else {
+ context_->surrounding_text = surrounding_text;
+ context_->start_offset = start_offset;
+ context_->end_offset = end_offset;
+ }
+
+ // Call the Icing callback, unless it has been disabled.
+ int icing_surrounding_size = GetIcingSurroundingSize();
+ size_t selection_start = context_->start_offset;
+ size_t selection_end = context_->end_offset;
+ if (icing_surrounding_size >= 0 && selection_start < selection_end) {
+ int icing_padding_each_side = icing_surrounding_size / 2;
+ base::string16 icing_surrounding_text = SurroundingTextForIcing(
+ context_->surrounding_text, icing_padding_each_side, &selection_start,
+ &selection_end);
+ if (selection_start < selection_end)
+ icing_callback_.Run(context_->encoding, icing_surrounding_text,
+ selection_start, selection_end);
+ }
+}
+
+void ContextualSearchDelegate::SendSurroundingText(int max_surrounding_chars) {
+ const base::string16 surrounding = context_->surrounding_text;
+
+ // Determine the text before the selection.
+ int start_position = std::max(
+ 0, context_->start_offset - max_surrounding_chars);
+ int num_before_characters =
+ std::min(context_->start_offset, max_surrounding_chars);
+ base::string16 before_text =
+ surrounding.substr(start_position, num_before_characters);
+
+ // Determine the text after the selection.
+ int surrounding_size = surrounding.size(); // Cast to int.
+ int num_after_characters = std::min(
+ surrounding_size - context_->end_offset, max_surrounding_chars);
+ base::string16 after_text = surrounding.substr(
+ context_->end_offset, num_after_characters);
+
+ base::TrimWhitespace(before_text, base::TRIM_ALL, &before_text);
+ base::TrimWhitespace(after_text, base::TRIM_ALL, &after_text);
+ surrounding_callback_.Run(UTF16ToUTF8(before_text), UTF16ToUTF8(after_text));
+}
+
+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(UTF16ToUTF8(context.surrounding_text));
+ selection->set_start(context.start_offset);
+ selection->set_end(context.end_offset);
+ selection->set_is_uri_encoded(false);
+
+ 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);
+}
+
+bool ContextualSearchDelegate::CanSendPageURL(
+ const GURL& current_page_url,
+ Profile* profile,
+ TemplateURLService* template_url_service) {
+ // Check whether there is a Finch parameter preventing us from sending the
+ // page URL.
+ std::string param_value = variations::GetVariationParamValue(
+ kContextualSearchFieldTrialName, kContextualSearchDoNotSendURLParamName);
+ if (!param_value.empty())
+ return false;
+
+ // Ensure that the default search provider is Google.
+ TemplateURL* default_search_provider =
+ template_url_service->GetDefaultSearchProvider();
+ bool is_default_search_provider_google =
+ default_search_provider &&
+ default_search_provider->url_ref().HasGoogleBaseURLs(
+ template_url_service->search_terms_data());
+ if (!is_default_search_provider_google)
+ return false;
+
+ // Only allow HTTP URLs or HTTPS URLs.
+ if (current_page_url.scheme() != url::kHttpScheme &&
+ (current_page_url.scheme() != url::kHttpsScheme))
+ return false;
+
+ // Check that the user has sync enabled, is logged in, and syncs their Chrome
+ // History.
+ ProfileSyncService* service =
+ ProfileSyncServiceFactory::GetInstance()->GetForProfile(profile);
+ sync_driver::SyncPrefs sync_prefs(profile->GetPrefs());
+ if (service == NULL || !service->IsSyncEnabledAndLoggedIn() ||
+ !sync_prefs.GetPreferredDataTypes(syncer::UserTypes())
+ .Has(syncer::PROXY_TABS) ||
+ !service->GetActiveDataTypes().Has(syncer::HISTORY_DELETE_DIRECTIVES)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Decodes the given response from the search term resolution request and sets
+// the value of the given parameters.
+void ContextualSearchDelegate::DecodeSearchTermsFromJsonResponse(
+ const std::string& response,
+ std::string* search_term,
+ std::string* display_text,
+ std::string* alternate_term,
+ std::string* prevent_preload) {
+ 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);
+ scoped_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);
+ 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;
+ }
+ }
+ }
+}
+
+// Returns the size of the surroundings to be sent to the server for search term
+// resolution.
+int ContextualSearchDelegate::GetSearchTermSurroundingSize() {
+ const std::string param_value = variations::GetVariationParamValue(
+ kContextualSearchFieldTrialName,
+ kContextualSearchSurroundingSizeParamName);
+ int param_length;
+ if (!param_value.empty() && base::StringToInt(param_value, &param_length))
+ return param_length;
+ return kContextualSearchDefaultContentSize;
+}
+
+// Returns the size of the surroundings to be sent to Icing.
+int ContextualSearchDelegate::GetIcingSurroundingSize() {
+ std::string param_string = variations::GetVariationParamValue(
+ kContextualSearchFieldTrialName,
+ kContextualSearchIcingSurroundingSizeParamName);
+ if (base::CommandLine::ForCurrentProcess()->HasSwitch(
+ kContextualSearchIcingSurroundingSizeParamName)) {
+ param_string = base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
+ kContextualSearchIcingSurroundingSizeParamName);
+ }
+ int param_value;
+ if (!param_string.empty() && base::StringToInt(param_string, &param_value))
+ return param_value;
+ return kContextualSearchDefaultIcingSurroundingSize;
+}
+
+base::string16 ContextualSearchDelegate::SurroundingTextForIcing(
+ const base::string16& surrounding_text,
+ int padding_each_side,
+ size_t* start,
+ size_t* end) {
+ base::string16 result_text = surrounding_text;
+ size_t start_offset = *start;
+ size_t end_offset = *end;
+ size_t padding_each_side_pinned =
+ padding_each_side >= 0 ? padding_each_side : 0;
+ // Now trim the context so the portions before or after the selection
+ // are within the given limit.
+ if (start_offset > padding_each_side_pinned) {
+ // Trim the start.
+ int trim = start_offset - padding_each_side_pinned;
+ result_text = result_text.substr(trim);
+ start_offset -= trim;
+ end_offset -= trim;
+ }
+ if (result_text.length() > end_offset + padding_each_side_pinned) {
+ // Trim the end.
+ result_text = result_text.substr(0, end_offset + padding_each_side_pinned);
+ }
+ *start = start_offset;
+ *end = end_offset;
+ return result_text;
+}

Powered by Google App Engine
This is Rietveld 408576698