0

Add new BFCache level for terminated DBSC sessions

A terminated Device Bound Session Credentials session can lead to
authorization changes similar to the way an expired cookie can. This CL
adds a new BFCache CacheControlNoStoreLevel for this, which will evict a
page from the cache if a DBSC session is terminated.

Bug: 353774923
Change-Id: I48e2daddb33d1102b5ccaf946eb0c541b5a07cda
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6198936
Reviewed-by: Dave Tapuska <dtapuska@chromium.org>
Reviewed-by: Eric Seckler <eseckler@chromium.org>
Commit-Queue: Daniel Rubery <drubery@chromium.org>
Reviewed-by: Fergal Daly <fergal@chromium.org>
Reviewed-by: Chris Thompson <cthomp@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1422196}
This commit is contained in:
Daniel Rubery
2025-02-19 14:03:30 -08:00
committed by Chromium LUCI CQ
parent 4143a049e5
commit 03e719955f
17 changed files with 417 additions and 13 deletions

@ -700,6 +700,7 @@ message BackForwardCacheCanStoreDocumentResult {
WEBVIEW_MESSAGE_LISTENER_INJECTED = 62;
WEBVIEW_SAFE_BROWSING_ALLOWLIST_CHANGED = 63;
WEBVIEW_DOCUMENT_START_JAVASCRIPT_CHANGED = 64;
CACHE_CONTROL_NO_STORE_DEVICE_BOUND_SESSION_TERMINATED = 65;
}
optional BackForwardCacheNotRestoredReason

@ -37,6 +37,7 @@ include_rules = [
"+components/system_media_controls",
"+components/tracing",
"+components/ukm",
"+components/unexportable_keys/features.h",
"+components/url_formatter",
"+components/viz",
"+components/web_package",

@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/back_forward_cache_browsertest.h"
#include <memory>
#include "base/test/test_future.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "build/chromecast_buildflags.h"
#include "components/unexportable_keys/features.h"
#include "content/browser/back_forward_cache_browsertest.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
@ -19,9 +20,12 @@
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "net/device_bound_sessions/test_support.h"
#include "net/net_buildflags.h"
#include "net/test/embedded_test_server/controllable_http_response.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
#include "net/test/test_data_directory.h"
#include "services/network/public/mojom/clear_data_filter.mojom.h"
#include "third_party/blink/public/common/scheduler/web_scheduler_tracked_feature.h"
// This file contains back-/forward-cache tests for the
@ -1930,4 +1934,256 @@ IN_PROC_BROWSER_TEST_F(
BlockListedFeatures()));
}
#if BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
class DeviceBoundSessionAccessObserver : public content::WebContentsObserver {
public:
DeviceBoundSessionAccessObserver(
content::WebContents* web_contents,
base::RepeatingCallback<void(
const net::device_bound_sessions::SessionAccess&)> on_access_callback)
: WebContentsObserver(web_contents),
on_access_callback_(std::move(on_access_callback)) {}
void OnDeviceBoundSessionAccessed(
content::NavigationHandle* navigation,
const net::device_bound_sessions::SessionAccess& access) override {
on_access_callback_.Run(access);
}
void OnDeviceBoundSessionAccessed(
content::RenderFrameHost* rfh,
const net::device_bound_sessions::SessionAccess& access) override {
on_access_callback_.Run(access);
}
private:
base::RepeatingCallback<void(
const net::device_bound_sessions::SessionAccess&)>
on_access_callback_;
};
class BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated
: public BackForwardCacheBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
EnableFeatureAndSetParams(features::kBackForwardCache, "", "");
EnableFeatureAndSetParams(
features::kDeviceBoundSessionTerminationEvictBackForwardCache, "", "");
EnableFeatureAndSetParams(net::features::kDeviceBoundSessions, "", "");
EnableFeatureAndSetParams(
unexportable_keys::
kEnableBoundSessionCredentialsSoftwareKeysForManualTesting,
"", "");
BackForwardCacheBrowserTest::SetUpCommandLine(command_line);
}
};
IN_PROC_BROWSER_TEST_F(
BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
NoCacheControlNoStoreButSessionTerminated) {
EXPECT_TRUE(embedded_test_server()->InitializeAndListen());
embedded_test_server()->RegisterRequestHandler(
net::device_bound_sessions::GetTestRequestHandler(
embedded_test_server()->base_url()));
embedded_test_server()->StartAcceptingConnections();
Shell* tab_to_be_bfcached = shell();
Shell* tab_to_create_session = CreateBrowser();
// 1) Load the document without cache-control:no-store
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached,
embedded_test_server()->GetURL("/set-header")));
RenderFrameHostImplWrapper cached_rfh(current_frame_host());
// 2) Navigate away. `cached_rfh` should enter bfcache.
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, embedded_test_server()->GetURL(
"b.com", "/set-header")));
EXPECT_TRUE(cached_rfh->IsInBackForwardCache());
// 3) Create a device bound session in another tab
base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
DeviceBoundSessionAccessObserver observer(
tab_to_create_session->web_contents(),
future.GetRepeatingCallback<
const net::device_bound_sessions::SessionAccess&>());
EXPECT_TRUE(NavigateToURL(tab_to_create_session,
embedded_test_server()->GetURL("/dbsc_required")));
EXPECT_EQ(future.Take().access_type,
net::device_bound_sessions::SessionAccess::AccessType::kCreation);
// 4) Terminate the session. This could happen through natural
// navigation, but it's simpler to test it through the
// `DeviceBoundSessionManager` interface.
network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
tab_to_create_session->web_contents()
->GetBrowserContext()
->GetStoragePartitionForUrl(
embedded_test_server()->GetURL("/set-header"),
/*can_create=*/true)
->GetDeviceBoundSessionManager();
ASSERT_TRUE(device_bound_session_manager);
base::RunLoop run_loop;
device_bound_session_manager->DeleteAllSessions(
/*created_after_time=*/std::nullopt,
/*created_before_time=*/std::nullopt,
/*filter=*/nullptr, run_loop.QuitClosure());
run_loop.Run();
// 5) Go back. `cached_rfh` should be restored from bfcache.
ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
ExpectRestored(FROM_HERE);
}
IN_PROC_BROWSER_TEST_F(
BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
CacheControlNoStoreSessionTerminated) {
EXPECT_TRUE(embedded_test_server()->InitializeAndListen());
embedded_test_server()->RegisterRequestHandler(
net::device_bound_sessions::GetTestRequestHandler(
embedded_test_server()->base_url()));
embedded_test_server()->StartAcceptingConnections();
Shell* tab_to_be_bfcached = shell();
Shell* tab_to_create_session = CreateBrowser();
// 1) Load the document with cache-control:no-store
EXPECT_TRUE(NavigateToURL(
tab_to_be_bfcached,
embedded_test_server()->GetURL("/set-header?Cache-Control: no-store")));
RenderFrameHostImplWrapper cached_rfh(current_frame_host());
cached_rfh->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
// 2) Navigate away. `cached_rfh` should enter bfcache.
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, embedded_test_server()->GetURL(
"b.com", "/set-header")));
EXPECT_TRUE(cached_rfh->IsInBackForwardCache());
// 3) Create a device bound session in another tab
base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
DeviceBoundSessionAccessObserver observer(
tab_to_create_session->web_contents(),
future.GetRepeatingCallback<
const net::device_bound_sessions::SessionAccess&>());
EXPECT_TRUE(NavigateToURL(tab_to_create_session,
embedded_test_server()->GetURL("/dbsc_required")));
EXPECT_EQ(future.Take().access_type,
net::device_bound_sessions::SessionAccess::AccessType::kCreation);
// 4) Terminate the session. This could happen through natural
// navigation, but it's simpler to test it through the
// `DeviceBoundSessionManager` interface.
network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
tab_to_create_session->web_contents()
->GetBrowserContext()
->GetStoragePartitionForUrl(
embedded_test_server()->GetURL("/set-header"),
/*can_create=*/true)
->GetDeviceBoundSessionManager();
ASSERT_TRUE(device_bound_session_manager);
base::RunLoop run_loop;
device_bound_session_manager->DeleteAllSessions(
/*created_after_time=*/std::nullopt,
/*created_before_time=*/std::nullopt,
/*filter=*/nullptr, run_loop.QuitClosure());
run_loop.Run();
// 5) Go back. `cached_rfh` should not be restored from bfcache.
ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kCacheControlNoStoreDeviceBoundSessionTerminated}, {},
{}, {}, {}, FROM_HERE);
EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
MatchesDocumentResult(
NotRestoredReasons(
{NotRestoredReason::
kCacheControlNoStoreDeviceBoundSessionTerminated}),
BlockListedFeatures()));
}
std::unique_ptr<net::test_server::HttpResponse> RedirectToUrl(
const GURL& gurl,
const net::test_server::HttpRequest& request) {
auto http_response = std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(net::HTTP_PERMANENT_REDIRECT);
http_response->AddCustomHeader("Location", gurl.spec());
return http_response;
}
IN_PROC_BROWSER_TEST_F(
BackForwardCacheBrowserTestRestoreUnlessDeviceBoundSessionTerminated,
CacheControlNoStoreSessionTerminatedOnRedirectedPage) {
EXPECT_TRUE(embedded_test_server()->InitializeAndListen());
embedded_test_server()->RegisterRequestHandler(
net::device_bound_sessions::GetTestRequestHandler(
embedded_test_server()->base_url()));
embedded_test_server()->StartAcceptingConnections();
GURL redirected_url =
embedded_test_server()->GetURL("/set-header?Cache-Control: no-store");
CreateHttpsServer();
https_server()->RegisterRequestHandler(
base::BindRepeating(&RedirectToUrl, redirected_url));
ASSERT_TRUE(https_server()->Start());
Shell* tab_to_be_bfcached = shell();
Shell* tab_to_create_session = CreateBrowser();
// 1) Load the document with cache-control:no-store.
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, https_server()->GetURL("/"),
redirected_url));
RenderFrameHostImplWrapper cached_rfh(current_frame_host());
cached_rfh->GetBackForwardCacheMetrics()->SetObserverForTesting(this);
// 2) Navigate away. `cached_rfh` should enter bfcache.
EXPECT_TRUE(NavigateToURL(tab_to_be_bfcached, embedded_test_server()->GetURL(
"b.com", "/set-header")));
EXPECT_TRUE(cached_rfh->IsInBackForwardCache());
// 3) Create a device bound session in another tab
base::test::TestFuture<net::device_bound_sessions::SessionAccess> future;
DeviceBoundSessionAccessObserver observer(
tab_to_create_session->web_contents(),
future.GetRepeatingCallback<
const net::device_bound_sessions::SessionAccess&>());
EXPECT_TRUE(NavigateToURL(tab_to_create_session,
embedded_test_server()->GetURL("/dbsc_required")));
EXPECT_EQ(future.Take().access_type,
net::device_bound_sessions::SessionAccess::AccessType::kCreation);
// 4) Terminate the session. This could happen through natural
// navigation, but it's simpler to test it through the
// `DeviceBoundSessionManager` interface.
network::mojom::DeviceBoundSessionManager* device_bound_session_manager =
tab_to_create_session->web_contents()
->GetBrowserContext()
->GetStoragePartitionForUrl(
embedded_test_server()->GetURL("/set-header"),
/*can_create=*/true)
->GetDeviceBoundSessionManager();
ASSERT_TRUE(device_bound_session_manager);
base::RunLoop run_loop;
device_bound_session_manager->DeleteAllSessions(
/*created_after_time=*/std::nullopt,
/*created_before_time=*/std::nullopt,
/*filter=*/nullptr, run_loop.QuitClosure());
run_loop.Run();
// 5) Go back. `cached_rfh` should not be restored from bfcache.
ASSERT_TRUE(HistoryGoBack(tab_to_be_bfcached->web_contents()));
ExpectNotRestored(
{NotRestoredReason::kCacheControlNoStoreDeviceBoundSessionTerminated}, {},
{}, {}, {}, FROM_HERE);
EXPECT_THAT(GetTreeResult()->GetDocumentResult(),
MatchesDocumentResult(
NotRestoredReasons(
{NotRestoredReason::
kCacheControlNoStoreDeviceBoundSessionTerminated}),
BlockListedFeatures()));
}
#endif // BUILDFLAG(ENABLE_DEVICE_BOUND_SESSIONS)
} // namespace content

@ -1813,6 +1813,9 @@ Page::BackForwardCacheNotRestoredReason NotRestoredReasonToProtocol(
NOTREACHED();
case Reason::kUnknown:
return Page::BackForwardCacheNotRestoredReasonEnum::Unknown;
case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
return Page::BackForwardCacheNotRestoredReasonEnum::
CacheControlNoStoreDeviceBoundSessionTerminated;
}
}
@ -2109,6 +2112,7 @@ Page::BackForwardCacheNotRestoredReasonType MapNotRestoredReasonToType(
case Reason::kCacheControlNoStoreHTTPOnlyCookieModified:
case Reason::kUnloadHandlerExistsInMainFrame:
case Reason::kUnloadHandlerExistsInSubFrame:
case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
return Page::BackForwardCacheNotRestoredReasonTypeEnum::PageSupportNeeded;
case Reason::kNetworkRequestDatapipeDrainedAsBytesConsumer:
case Reason::kUnknown:

@ -204,6 +204,8 @@ ProtoEnum::BackForwardCacheNotRestoredReason NotRestoredReasonToTraceEnum(
return ProtoEnum::BLOCKLISTED_FEATURES;
case Reason::kUnknown:
return ProtoEnum::UNKNOWN;
case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
return ProtoEnum::CACHE_CONTROL_NO_STORE_DEVICE_BOUND_SESSION_TERMINATED;
}
NOTREACHED();
}
@ -463,6 +465,9 @@ std::string BackForwardCacheCanStoreDocumentResult::NotRestoredReasonToString(
return "Android WebView safe browsing allowlist changed";
case Reason::kWebViewDocumentStartJavascriptChanged:
return "Android WebView document start script changed";
case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
return "A device bound session was terminated on a cached page with "
"Cache-Control: no-store";
}
}
@ -542,6 +547,7 @@ BackForwardCacheCanStoreDocumentResult::NotRestoredReasonToReportString(
case Reason::kCacheControlNoStore:
case Reason::kCacheControlNoStoreCookieModified:
case Reason::kCacheControlNoStoreHTTPOnlyCookieModified:
case Reason::kCacheControlNoStoreDeviceBoundSessionTerminated:
return "response-cache-control-no-store";
case Reason::kCookieDisabled:
return base::FeatureList::IsEnabled(

@ -700,8 +700,13 @@ void BackForwardCacheImpl::UpdateCanStoreToIncludeCacheControlNoStore(
// Note that kCacheControlNoStoreHTTPOnlyCookieModified,
// kCacheControlNoStoreCookieModified and kCacheControlNoStore are mutually
// exclusive.
if (render_frame_host->GetCookieChangeInfo()
.http_only_cookie_modification_count_ > 0) {
if (render_frame_host->IsDeviceBoundSessionTerminated() &&
base::FeatureList::IsEnabled(
features::kDeviceBoundSessionTerminationEvictBackForwardCache)) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kCacheControlNoStoreDeviceBoundSessionTerminated);
} else if (render_frame_host->GetCookieChangeInfo()
.http_only_cookie_modification_count_ > 0) {
result.No(BackForwardCacheMetrics::NotRestoredReason::
kCacheControlNoStoreHTTPOnlyCookieModified);
} else if (render_frame_host->GetCookieChangeInfo()

@ -2959,6 +2959,12 @@ bool NavigationRequest::ShouldAddCookieChangeListener() {
common_params_->url.SchemeIsHTTPOrHTTPS();
}
bool NavigationRequest::ShouldAddDeviceBoundSessionObserver() {
// Device bound session expiry should evict pages from the BFCache in
// the exact same circumstances as cookie expiry.
return ShouldAddCookieChangeListener();
}
void NavigationRequest::StartNavigation() {
TRACE_EVENT_WITH_FLOW0("navigation", "NavigationRequest::StartNavigation",
TRACE_ID_WITH_SCOPE(kNavigationRequestScope,
@ -2999,6 +3005,12 @@ void NavigationRequest::StartNavigation() {
GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
}
if (ShouldAddDeviceBoundSessionObserver()) {
device_bound_session_observer_ =
std::make_unique<RenderFrameHostImpl::DeviceBoundSessionObserver>(
GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
}
// Compute the redirect chain.
// TODO(clamy): Try to simplify this and have the redirects be part of
// CommonNavigationParams.
@ -3552,6 +3564,14 @@ void NavigationRequest::OnRequestRedirected(
cookie_change_listener_.reset();
}
if (ShouldAddDeviceBoundSessionObserver()) {
device_bound_session_observer_ =
std::make_unique<RenderFrameHostImpl::DeviceBoundSessionObserver>(
GetStoragePartitionWithCurrentSiteInfo(), common_params_->url);
} else {
device_bound_session_observer_.reset();
}
// Check Content Security Policy before the NavigationThrottles run. This
// gives CSP a chance to modify requests that NavigationThrottles would
// otherwise block.

@ -1292,6 +1292,11 @@ class CONTENT_EXPORT NavigationRequest
return std::move(cookie_change_listener_);
}
std::unique_ptr<RenderFrameHostImpl::DeviceBoundSessionObserver>
TakeDeviceBoundSessionObserver() {
return std::move(device_bound_session_observer_);
}
// Returns true if there is a speculative RFH that has a pending commit
// cross-document navigation, and this NavigationRequest is not a pending
// commit NavigationRequest itself. This means that this navigation should be
@ -2259,6 +2264,10 @@ class CONTENT_EXPORT NavigationRequest
// navigation.
bool ShouldAddCookieChangeListener();
// Returns if we should add/reset the `DeviceBoundSessionObserver` for
// the current navigation.
bool ShouldAddDeviceBoundSessionObserver();
// Returns the `StoragePartition` based on the config from the `site_info_`.
StoragePartition* GetStoragePartitionWithCurrentSiteInfo();
@ -3104,6 +3113,17 @@ class CONTENT_EXPORT NavigationRequest
std::unique_ptr<RenderFrameHostImpl::CookieChangeListener>
cookie_change_listener_;
// The observer that receives device bound session events and
// maintains device bound session information for the domain of the
// URL that this `NavigationRequest` is navigating to. The observer
// will observe all device bound session changes starting from the
// navigation/redirection, and it will be moved to the
// `RenderFrameHostImpl` when the navigation is committed and
// continues observing until the destructoin of the document.
// See `RenderFrameHostImpl::DeviceBoundSessionObserver`.
std::unique_ptr<RenderFrameHostImpl::DeviceBoundSessionObserver>
device_bound_session_observer_;
// LCP Critical Path Predictor managed hint data those were already available
// at the time of navigation. The hint is passed along to the renderer process
// on commit along with the other navigation params.

@ -15275,10 +15275,13 @@ bool RenderFrameHostImpl::DidCommitNavigationInternal(
RecordDocumentCreatedUkmEvent(params->origin, document_ukm_source_id,
ukm_recorder);
// We only replace the `CookieChangeListener` with the one initialized by
// the navigation request when navigating to a new document. Otherwise, the
// existing `CookieChangeListener` will be reused.
// We only replace the `CookieChangeListener` and
// `DeviceBoundSessionObserver` with the one initialized by the
// navigation request when navigating to a new document. Otherwise,
// the existing observers will be reused.
cookie_change_listener_ = navigation_request->TakeCookieChangeListener();
device_bound_session_observer_ =
navigation_request->TakeDeviceBoundSessionObserver();
}
// Note: The renderer never sets |params->is_overriding_user_agent| to true
@ -18223,6 +18226,43 @@ RenderFrameHostImpl::GetCookieChangeInfo() {
: CookieChangeListener::CookieChangeInfo{};
}
RenderFrameHostImpl::DeviceBoundSessionObserver::DeviceBoundSessionObserver(
StoragePartition* storage_partition,
GURL& url) {
DCHECK(storage_partition);
auto* device_bound_session_manager =
storage_partition->GetDeviceBoundSessionManager();
if (device_bound_session_manager) {
device_bound_session_manager->AddObserver(
url, receiver_.BindNewPipeAndPassRemote());
}
}
RenderFrameHostImpl::DeviceBoundSessionObserver::~DeviceBoundSessionObserver() =
default;
void RenderFrameHostImpl::DeviceBoundSessionObserver::
OnDeviceBoundSessionAccessed(
const net::device_bound_sessions::SessionAccess& access) {
is_terminated_ |=
access.access_type ==
net::device_bound_sessions::SessionAccess::AccessType::kTermination;
}
void RenderFrameHostImpl::DeviceBoundSessionObserver::Clone(
mojo::PendingReceiver<network::mojom::DeviceBoundSessionAccessObserver>
observer) {
// The `Clone` method is only called for the observers that are part
// of network requests, so it is not expected to be called here.
NOTREACHED();
}
bool RenderFrameHostImpl::IsDeviceBoundSessionTerminated() {
return device_bound_session_observer_
? device_bound_session_observer_->IsTerminated()
: false;
}
bool RenderFrameHostImpl::LoadedWithCacheControlNoStoreHeader() {
return GetBackForwardCacheDisablingFeatures().Has(
blink::scheduler::WebSchedulerTrackedFeature::

@ -1568,6 +1568,31 @@ class CONTENT_EXPORT RenderFrameHostImpl
CookieChangeInfo cookie_change_info_;
};
class DeviceBoundSessionObserver
: public network::mojom::DeviceBoundSessionAccessObserver {
public:
DeviceBoundSessionObserver(StoragePartition* storage_partition, GURL& url);
~DeviceBoundSessionObserver() override;
DeviceBoundSessionObserver(const DeviceBoundSessionObserver&) = delete;
DeviceBoundSessionObserver& operator=(const DeviceBoundSessionObserver&) =
delete;
bool IsTerminated() const { return is_terminated_; }
private:
// network::mojom::DeviceBoundSessionAccessObserver
void OnDeviceBoundSessionAccessed(
const net::device_bound_sessions::SessionAccess& access) override;
void Clone(
mojo::PendingReceiver<network::mojom::DeviceBoundSessionAccessObserver>
observer) override;
mojo::Receiver<network::mojom::DeviceBoundSessionAccessObserver> receiver_{
this};
bool is_terminated_ = false;
};
// Indicates that a navigation is ready to commit and can be
// handled by this RenderFrame.
// |subresource_loader_params| is used in network service land to pass
@ -3079,6 +3104,10 @@ class CONTENT_EXPORT RenderFrameHostImpl
// last committed document.
CookieChangeListener::CookieChangeInfo GetCookieChangeInfo();
// Retrieves the whether a device bound session on the last committed
// document has been terminated.
bool IsDeviceBoundSessionTerminated();
// Records metrics on sudden termination handlers found in this frame and
// subframes.
void RecordNavigationSuddenTerminationHandlers();
@ -5398,6 +5427,9 @@ class CONTENT_EXPORT RenderFrameHostImpl
// `cookie_change_listener_` in `NavigationRequest`.
std::unique_ptr<CookieChangeListener> cookie_change_listener_;
// Listens for changes to DeviceBoundSessions on this page.
std::unique_ptr<DeviceBoundSessionObserver> device_bound_session_observer_;
// If true, the renderer side widget is created after the navigation is
// committed.
bool waiting_for_renderer_widget_creation_after_commit_ = false;

@ -116,7 +116,8 @@ class CONTENT_EXPORT BackForwardCache {
kWebViewMessageListenerInjected = 66,
kWebViewSafeBrowsingAllowlistChanged = 67,
kWebViewDocumentStartJavascriptChanged = 68,
kMaxValue = kWebViewDocumentStartJavascriptChanged,
kCacheControlNoStoreDeviceBoundSessionTerminated = 69,
kMaxValue = kCacheControlNoStoreDeviceBoundSessionTerminated,
};
// LINT.ThenChange(//tools/metrics/histograms/metadata/navigation/enums.xml:BackForwardCacheNotRestoredReason)

@ -233,11 +233,6 @@ const char kCookieDeprecationTestingDisableAdsAPIsName[] = "disable_ads_apis";
// Adiitional FeatureParams for CookieDeprecationFacilitatedTesting are defined
// in chrome/browser/tpcd/experiment/tpcd_experiment_features.cc.
// When enabled, the DevTools Privacy UI is displayed.
BASE_FEATURE(kDevToolsPrivacyUI,
"DevToolsPrivacyUI",
base::FEATURE_ENABLED_BY_DEFAULT);
// Enables deferring the creation of the speculative RFH when the navigation
// starts. The creation of a speculative RFH consumes about 2ms and is blocking
// the network request. With this feature the creation will be deferred until
@ -273,6 +268,20 @@ BASE_FEATURE(kDeleteStaleSessionCookiesOnStartup,
"DeleteStaleSessionCookiesOnStartup",
base::FEATURE_DISABLED_BY_DEFAULT);
// When a device bound session
// (https://github.com/w3c/webappsec-dbsc/blob/main/README.md) is
// terminated, evict pages with cache-control:no-store from the
// BFCache. Note that if `kCacheControlNoStoreEnterBackForwardCache` is
// disabled, no such pages will be in the cache.
BASE_FEATURE(kDeviceBoundSessionTerminationEvictBackForwardCache,
"DeviceBoundSessionTerminationEvictBackForwardCache",
base::FEATURE_DISABLED_BY_DEFAULT);
// When enabled, the DevTools Privacy UI is displayed.
BASE_FEATURE(kDevToolsPrivacyUI,
"DevToolsPrivacyUI",
base::FEATURE_ENABLED_BY_DEFAULT);
// Controls whether the Digital Goods API is enabled.
// https://github.com/WICG/digital-goods/
BASE_FEATURE(kDigitalGoodsApi,

@ -82,6 +82,8 @@ CONTENT_EXPORT extern const base::FeatureParam<bool>
CONTENT_EXPORT extern const base::FeatureParam<int>
kCreateSpeculativeRFHDelayMs;
CONTENT_EXPORT BASE_DECLARE_FEATURE(kDeleteStaleSessionCookiesOnStartup);
CONTENT_EXPORT BASE_DECLARE_FEATURE(
kDeviceBoundSessionTerminationEvictBackForwardCache);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kDevToolsPrivacyUI);
CONTENT_EXPORT BASE_DECLARE_FEATURE(kDigitalGoodsApi);
// TODO(crbug.com/364900088): Refactor BTM feature flags and parameters into

@ -882,6 +882,8 @@ void ShellContentBrowserClient::ConfigureNetworkContextParamsForShell(
"cors_exempt_header_list");
if (!exempt_header.empty())
context_params->cors_exempt_header_list.push_back(exempt_header);
context_params->device_bound_sessions_enabled =
base::FeatureList::IsEnabled(net::features::kDeviceBoundSessions);
}
void ShellContentBrowserClient::GetHyphenationDictionary(

@ -1809,6 +1809,7 @@ test("content_browsertests") {
"//base/test:test_support",
"//build:chromecast_buildflags",
"//cc/slim",
"//components//unexportable_keys:unexportable_keys",
"//components/attribution_reporting:mojom",
"//components/cbor",
"//components/discardable_memory/client",

@ -9751,6 +9751,7 @@ domain Page
EmbedderExtensionSentMessageToCachedFrame
RequestedByWebViewClient
PostMessageByWebViewClient
CacheControlNoStoreDeviceBoundSessionTerminated
# Types of not restored reasons for back-forward cache.
experimental type BackForwardCacheNotRestoredReasonType extends string

@ -223,6 +223,9 @@ chromium-metrics-reviews@google.com.
<int value="66" label="WebView injected message listener"/>
<int value="67" label="WebView safe browsing allowlist changed"/>
<int value="68" label="WebView added document start javascript"/>
<int value="69"
label="CacheControlNoStore is present and Device Bound Session
terminated"/>
</enum>
<!-- LINT.ThenChange(//content/public/browser/back_forward_cache.h:NotRestoredReason) -->