// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "android_webview/browser/aw_client_hints_controller_delegate.h"
#include "android_webview/browser/aw_browser_process.h"
#include "android_webview/browser/aw_contents.h"
#include "android_webview/browser/aw_cookie_access_policy.h"
#include "base/notreached.h"
#include "base/values.h"
#include "components/embedder_support/user_agent_utils.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/client_hints_controller_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "services/network/public/cpp/client_hints.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "services/network/public/cpp/network_quality_tracker.h"
#include "third_party/blink/public/common/client_hints/enabled_client_hints.h"
#include "third_party/blink/public/common/user_agent/user_agent_metadata.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace android_webview {
// Android WebView product name for building the default user agent string.
const char kAndroidWebViewProductName[] = "Android WebView";
namespace prefs {
const char kClientHintsCachedPerOriginMap[] =
"aw_client_hints_cached_per_origin_map";
} // namespace prefs
AwClientHintsControllerDelegate::AwClientHintsControllerDelegate(
PrefService* context_pref_service)
: context_pref_service_(context_pref_service) {}
AwClientHintsControllerDelegate::~AwClientHintsControllerDelegate() = default;
blink::UserAgentMetadata
AwClientHintsControllerDelegate::GetUserAgentMetadataOverrideBrand(
bool only_low_entropy_ch) {
// embedder_support::GetUserAgentMetadata() can accept a browser local_state
// PrefService argument, but doesn't need one. Either way, it shouldn't be the
// context_pref_service_ that this class holds.
auto metadata = embedder_support::GetUserAgentMetadata(only_low_entropy_ch);
std::string major_version = version_info::GetMajorVersionNumber();
// Use the major version number as a greasing seed
int major_version_number;
bool parse_result = base::StringToInt(major_version, &major_version_number);
DCHECK(parse_result);
// The old grease brand algorithm will removed soon, we should always use the
// updated algorithm.
bool enable_updated_grease_by_policy = true;
// Regenerate the brand version lists with Android WebView product name.
metadata.brand_version_list = embedder_support::GenerateBrandVersionList(
major_version_number, kAndroidWebViewProductName, major_version,
std::nullopt, std::nullopt, enable_updated_grease_by_policy,
blink::UserAgentBrandVersionType::kMajorVersion);
if (!only_low_entropy_ch) {
metadata.brand_full_version_list =
embedder_support::GenerateBrandVersionList(
major_version_number, kAndroidWebViewProductName,
metadata.full_version, std::nullopt, std::nullopt,
enable_updated_grease_by_policy,
blink::UserAgentBrandVersionType::kFullVersion);
}
return metadata;
}
network::NetworkQualityTracker*
AwClientHintsControllerDelegate::GetNetworkQualityTracker() {
// Android WebViews lack a Network Quality Tracker.
return nullptr;
}
void AwClientHintsControllerDelegate::GetAllowedClientHintsFromSource(
const url::Origin& origin,
blink::EnabledClientHints* client_hints) {
// Ensure this origin can have hints stored.
const GURL url = origin.GetURL();
if (!url.is_valid() || !network::IsUrlPotentiallyTrustworthy(url)) {
return;
}
// Add stored hints to the enabled list.
if (context_pref_service_->HasPrefPath(
prefs::kClientHintsCachedPerOriginMap)) {
auto* const client_hints_list =
context_pref_service_->GetDict(prefs::kClientHintsCachedPerOriginMap)
.FindList(origin.Serialize());
if (client_hints_list) {
for (const auto& client_hint : *client_hints_list) {
DCHECK(client_hint.is_int());
network::mojom::WebClientHintsType client_hint_mojo =
static_cast<network::mojom::WebClientHintsType>(
client_hint.GetInt());
if (network::mojom::IsKnownEnumValue(client_hint_mojo)) {
client_hints->SetIsEnabled(client_hint_mojo, true);
}
}
}
}
// Add additional hints to the enabled list.
for (auto hint : additional_hints_) {
client_hints->SetIsEnabled(hint, true);
}
}
bool AwClientHintsControllerDelegate::IsJavaScriptAllowed(
const GURL& url,
content::RenderFrameHost* parent_rfh) {
// Javascript can only be disabled per-frame, so if we're pre-loading
// and/or there is no frame Javascript is considered enabled.
if (!parent_rfh) {
return true;
}
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(
parent_rfh->GetOutermostMainFrame());
if (!web_contents) {
return true;
}
AwContents* aw_contents = AwContents::FromWebContents(web_contents);
if (!aw_contents) {
return true;
}
return aw_contents->IsJavaScriptAllowed();
}
bool AwClientHintsControllerDelegate::AreThirdPartyCookiesBlocked(
const GURL& url,
content::RenderFrameHost* rfh) {
// This function is related to an OT for the Sec-CH-UA-Reduced client hint
// and as this doesn't affect WebView at the moment, we have no reason to
// implement it.
return false;
}
blink::UserAgentMetadata
AwClientHintsControllerDelegate::GetUserAgentMetadata() {
return GetUserAgentMetadataOverrideBrand();
}
void AwClientHintsControllerDelegate::PersistClientHints(
const url::Origin& primary_origin,
content::RenderFrameHost* parent_rfh,
const std::vector<network::mojom::WebClientHintsType>& client_hints) {
// Ensure this origin can have hints stored and check the number of hints.
const GURL primary_url = primary_origin.GetURL();
if (!primary_url.is_valid() ||
!network::IsUrlPotentiallyTrustworthy(primary_url)) {
return;
}
if (!IsJavaScriptAllowed(primary_url, parent_rfh)) {
return;
}
if (client_hints.size() >
(static_cast<size_t>(network::mojom::WebClientHintsType::kMaxValue) +
1)) {
return;
}
// Assemble and store the list if no issues.
const auto& persistence_started = base::TimeTicks::Now();
base::Value::List client_hints_list;
client_hints_list.reserve(client_hints.size());
for (const auto& entry : client_hints) {
client_hints_list.Append(static_cast<int>(entry));
}
base::Value::Dict ch_per_origin;
if (context_pref_service_->HasPrefPath(
prefs::kClientHintsCachedPerOriginMap)) {
ch_per_origin =
context_pref_service_->GetDict(prefs::kClientHintsCachedPerOriginMap)
.Clone();
}
ch_per_origin.Set(primary_origin.Serialize(), std::move(client_hints_list));
context_pref_service_->SetDict(prefs::kClientHintsCachedPerOriginMap,
std::move(ch_per_origin));
network::LogClientHintsPersistenceMetrics(persistence_started,
client_hints.size());
}
void AwClientHintsControllerDelegate::SetAdditionalClientHints(
const std::vector<network::mojom::WebClientHintsType>& hints) {
additional_hints_ = hints;
}
void AwClientHintsControllerDelegate::ClearAdditionalClientHints() {
additional_hints_.clear();
}
void AwClientHintsControllerDelegate::SetMostRecentMainFrameViewportSize(
const gfx::Size& viewport_size) {
viewport_size_ = viewport_size;
}
gfx::Size
AwClientHintsControllerDelegate::GetMostRecentMainFrameViewportSize() {
return viewport_size_;
}
} // namespace android_webview