blob: 40085391830f4730948585f195c72bf588fd0641 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/omnibox/browser/remote_suggestions_service.h"
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "components/omnibox/browser/base_search_provider.h"
#include "components/search_engines/template_url_service.h"
#include "components/variations/net/variations_http_headers.h"
#include "net/base/load_flags.h"
#include "net/traffic_annotation/network_traffic_annotation.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
namespace {
void AddVariationHeaders(network::ResourceRequest* request) {
// Note: It's OK to pass InIncognito::kNo since we are expected to be in
// non-incognito state here (i.e. remote suggestions are not served in
// incognito mode).
variations::AppendVariationsHeaderUnknownSignedIn(
request->url, variations::InIncognito::kNo, request);
}
} // namespace
RemoteSuggestionsService::RemoteSuggestionsService(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: url_loader_factory_(url_loader_factory) {
DCHECK(url_loader_factory);
}
RemoteSuggestionsService::~RemoteSuggestionsService() = default;
// static
GURL RemoteSuggestionsService::EndpointUrl(
const TemplateURL* template_url,
TemplateURLRef::SearchTermsArgs search_terms_args,
const SearchTermsData& search_terms_data) {
const TemplateURLRef& suggestion_url_ref =
template_url->suggestions_url_ref();
// Append a specific suggest client in ChromeOS app_list launcher contexts.
BaseSearchProvider::AppendSuggestClientToAdditionalQueryParams(
template_url, search_terms_data, search_terms_args.page_classification,
&search_terms_args);
return GURL(suggestion_url_ref.ReplaceSearchTerms(search_terms_args,
search_terms_data));
}
std::unique_ptr<network::SimpleURLLoader>
RemoteSuggestionsService::StartSuggestionsRequest(
const TemplateURL* template_url,
TemplateURLRef::SearchTermsArgs search_terms_args,
const SearchTermsData& search_terms_data,
CompletionCallback completion_callback) {
DCHECK(template_url);
const GURL suggest_url =
EndpointUrl(template_url, search_terms_args, search_terms_data);
if (!suggest_url.is_valid()) {
return nullptr;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("omnibox_suggest", R"(
semantics {
sender: "Omnibox"
description:
"Chrome can provide search and navigation suggestions from the "
"currently-selected search provider in the omnibox dropdown, based "
"on user input."
trigger: "User typing in the omnibox."
data:
"The text typed into the address bar. Potentially other metadata, "
"such as the current cursor position or URL of the current page."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Users can control this feature via the 'Use a prediction service "
"to help complete searches and URLs typed in the address bar' "
"setting under 'Privacy'. The feature is enabled by default."
chrome_policy {
SearchSuggestEnabled {
policy_options {mode: MANDATORY}
SearchSuggestEnabled: false
}
}
})");
auto request = std::make_unique<network::ResourceRequest>();
request->url = suggest_url;
request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES;
// Add Chrome experiment state to the request headers.
AddVariationHeaders(request.get());
// Create a unique identifier for the request.
const base::UnguessableToken request_id = base::UnguessableToken::Create();
// Notify the observers that the transfer is about to start.
for (Observer& observer : observers_) {
observer.OnSuggestRequestStarting(request_id, request.get());
}
// Make loader and start download.
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&RemoteSuggestionsService::OnURLLoadComplete,
weak_ptr_factory_.GetWeakPtr(), request_id,
std::move(completion_callback), loader.get()));
return loader;
}
std::unique_ptr<network::SimpleURLLoader>
RemoteSuggestionsService::StartZeroPrefixSuggestionsRequest(
const TemplateURL* template_url,
TemplateURLRef::SearchTermsArgs search_terms_args,
const SearchTermsData& search_terms_data,
CompletionCallback completion_callback) {
DCHECK(template_url);
const GURL suggest_url =
EndpointUrl(template_url, search_terms_args, search_terms_data);
if (!suggest_url.is_valid()) {
return nullptr;
}
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("omnibox_zerosuggest", R"(
semantics {
sender: "Omnibox"
description:
"When the user focuses the omnibox, Chrome can provide search or "
"navigation suggestions from the default search provider in the "
"omnibox dropdown, based on the current page URL.\n"
"This is limited to users whose default search engine is Google, "
"as no other search engines currently support this kind of "
"suggestion."
trigger: "The omnibox receives focus."
data: "The URL of the current page."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Users can control this feature via the 'Use a prediction service "
"to help complete searches and URLs typed in the address bar' "
"settings under 'Privacy'. The feature is enabled by default."
chrome_policy {
SearchSuggestEnabled {
policy_options {mode: MANDATORY}
SearchSuggestEnabled: false
}
}
})");
auto request = std::make_unique<network::ResourceRequest>();
request->url = suggest_url;
request->load_flags = net::LOAD_DO_NOT_SAVE_COOKIES;
if (search_terms_args.bypass_cache) {
request->load_flags |= net::LOAD_BYPASS_CACHE;
}
// Try to attach cookies for signed in user.
request->site_for_cookies = net::SiteForCookies::FromUrl(suggest_url);
// Add Chrome experiment state to the request headers.
AddVariationHeaders(request.get());
// Create a unique identifier for the request.
const base::UnguessableToken request_id = base::UnguessableToken::Create();
// Notify the observers that the transfer is about to start.
for (Observer& observer : observers_) {
observer.OnSuggestRequestStarting(request_id, request.get());
}
// Make loader and start download.
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&RemoteSuggestionsService::OnURLLoadComplete,
weak_ptr_factory_.GetWeakPtr(), request_id,
std::move(completion_callback), loader.get()));
return loader;
}
std::unique_ptr<network::SimpleURLLoader>
RemoteSuggestionsService::StartDeletionRequest(
const std::string& deletion_url,
CompletionCallback completion_callback) {
const GURL url(deletion_url);
DCHECK(url.is_valid());
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("omnibox_suggest_deletion", R"(
semantics {
sender: "Omnibox"
description:
"When users attempt to delete server-provided personalized search "
"or navigation suggestions from the omnibox dropdown, Chrome sends "
"a message to the server requesting deletion of the suggestion."
trigger:
"A user attempt to delete a server-provided omnibox suggestion, "
"for which the server provided a custom deletion URL."
data:
"No user data is explicitly sent with the request, but because the "
"requested URL is provided by the server for each specific "
"suggestion, it necessarily uniquely identifies the suggestion the "
"user is attempting to delete."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"Since this can only be triggered on seeing server-provided "
"suggestions in the omnibox dropdown, whether it is enabled is the "
"same as whether those suggestions are enabled.\n"
"Users can control this feature via the 'Use a prediction service "
"to help complete searches and URLs typed in the address bar' "
"setting under 'Privacy'. The feature is enabled by default."
chrome_policy {
SearchSuggestEnabled {
policy_options {mode: MANDATORY}
SearchSuggestEnabled: false
}
}
})");
auto request = std::make_unique<network::ResourceRequest>();
request->url = url;
// Add Chrome experiment state to the request headers.
AddVariationHeaders(request.get());
// Create a unique identifier for the request.
const base::UnguessableToken request_id = base::UnguessableToken::Create();
// Notify the observers that the transfer is about to start.
for (Observer& observer : observers_) {
observer.OnSuggestRequestStarting(request_id, request.get());
}
// Make loader and start download.
std::unique_ptr<network::SimpleURLLoader> loader =
network::SimpleURLLoader::Create(std::move(request), traffic_annotation);
loader->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
url_loader_factory_.get(),
base::BindOnce(&RemoteSuggestionsService::OnURLLoadComplete,
weak_ptr_factory_.GetWeakPtr(), request_id,
std::move(completion_callback), loader.get()));
return loader;
}
void RemoteSuggestionsService::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void RemoteSuggestionsService::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void RemoteSuggestionsService::set_url_loader_factory_for_testing(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) {
url_loader_factory_ = std::move(url_loader_factory);
}
void RemoteSuggestionsService::OnURLLoadComplete(
const base::UnguessableToken& request_id,
CompletionCallback completion_callback,
const network::SimpleURLLoader* source,
std::unique_ptr<std::string> response_body) {
const bool response_received =
response_body && source->NetError() == net::OK &&
source->ResponseInfo() && source->ResponseInfo()->headers &&
source->ResponseInfo()->headers->response_code() == 200;
// Notify the observers that the transfer is done.
for (Observer& observer : observers_) {
observer.OnSuggestRequestCompleted(request_id, response_received,
response_body);
}
std::move(completion_callback)
.Run(source, response_received, std::move(response_body));
}