0

[Blob URL] Add DevTools Issues for Fetching Partitioned Blob URL

This DevTools Issues indicates when a Blob URL fetch was blocked due to
storage partitioning. A separate CL will be needed to add these issues
to the DevTools issue panel in the frontend.

Bug: 372883697

Change-Id: Ia5e1a55d3d09930b0923e7ef810a2601054b4bed
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6216461
Reviewed-by: Bo Liu <boliu@chromium.org>
Reviewed-by: Ayu Ishii <ayui@chromium.org>
Reviewed-by: Dominic Farolino <dom@chromium.org>
Reviewed-by: Alex Rudenko <alexrudenko@chromium.org>
Commit-Queue: Janice Liu <janiceliu@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1418921}
This commit is contained in:
Janice Liu
2025-02-11 14:32:50 -08:00
committed by Chromium LUCI CQ
parent 05387205c6
commit 063b3d8da0
11 changed files with 209 additions and 22 deletions

@ -8,6 +8,7 @@
#include "base/test/scoped_feature_list.h"
#include "base/test/with_feature_override.h"
#include "build/build_config.h"
#include "content/browser/devtools/render_frame_devtools_agent_host.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/common/content_switches.h"
@ -17,6 +18,7 @@
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_devtools_protocol_client.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "content/test/content_browser_test_utils_internal.h"
@ -238,6 +240,83 @@ IN_PROC_BROWSER_TEST_F(BlobUrlBrowserTest,
blob_url)));
}
class BlobUrlDevToolsIssueTest : public ContentBrowserTest {
protected:
void SetUpOnMainThread() override {
ContentBrowserTest::SetUpOnMainThread();
host_resolver()->AddRule("*", "127.0.0.1");
SetupCrossSiteRedirector(embedded_test_server());
ASSERT_TRUE(embedded_test_server()->Start());
}
void WaitForIssueAndCheckUrl(const std::string& url,
TestDevToolsProtocolClient* client) {
auto is_blob_url_issue = [](const base::Value::Dict& params) {
const std::string* issue_code =
params.FindStringByDottedPath("issue.code");
return issue_code && *issue_code == "PartitioningBlobURLIssue";
};
// Wait for notification of a Partitioning Blob URL Issue.
base::Value::Dict params = client->WaitForMatchingNotification(
"Audits.issueAdded", base::BindRepeating(is_blob_url_issue));
EXPECT_EQ(*params.FindStringByDottedPath("issue.code"),
"PartitioningBlobURLIssue");
base::Value::Dict* partitioning_blob_url_issue_details =
params.FindDictByDottedPath(
"issue.details.partitioningBlobURLIssueDetails");
ASSERT_TRUE(partitioning_blob_url_issue_details);
// Verify the reported blob_url match the expected url.
std::string* blob_url_ptr =
partitioning_blob_url_issue_details->FindString("url");
EXPECT_EQ(*blob_url_ptr, url);
// Clear existing notifications so subsequent calls don't fail by checking
// `url` against old notifications.
client->ClearNotifications();
}
};
IN_PROC_BROWSER_TEST_F(BlobUrlDevToolsIssueTest, PartitioningBlobUrlIssue) {
GURL main_url = embedded_test_server()->GetURL(
"c.com", "/cross_site_iframe_factory.html?c(b(c))");
EXPECT_TRUE(NavigateToURL(shell(), main_url));
RenderFrameHost* rfh_c = shell()->web_contents()->GetPrimaryMainFrame();
std::string blob_url_string =
EvalJs(
rfh_c,
"const blob_url = URL.createObjectURL(new "
"Blob(['<!doctype html><body>potato</body>'], {type: 'text/html'}));"
"blob_url;")
.ExtractString();
GURL blob_url(blob_url_string);
RenderFrameHost* rfh_b = ChildFrameAt(rfh_c, 0);
RenderFrameHost* rfh_c_2 = ChildFrameAt(rfh_b, 0);
std::unique_ptr<content::TestDevToolsProtocolClient> client =
std::make_unique<content::TestDevToolsProtocolClient>();
client->AttachToFrameTreeHost(rfh_c_2);
client->SendCommandSync("Audits.enable");
client->ClearNotifications();
EXPECT_TRUE(ExecJs(
rfh_c_2,
JsReplace(
"async function test() {"
"const blob = await fetch($1).then(response => response.blob());"
"await blob.text();}"
"test();",
blob_url)));
WaitForIssueAndCheckUrl(blob_url_string, client.get());
client->DetachProtocolClient();
}
class BlobURLBrowserTestP : public base::test::WithFeatureOverride,
public BlobUrlBrowserTest {
public:

@ -566,6 +566,44 @@ std::unique_ptr<protocol::Audits::InspectorIssue> BuildBounceTrackingIssue(
return issue;
}
std::unique_ptr<protocol::Audits::InspectorIssue> BuildPartitioningBlobURLIssue(
const blink::mojom::PartitioningBlobURLIssueDetailsPtr& issue_details) {
protocol::String partitioning_blob_url_info_string;
switch (issue_details->partitioning_blob_url_info) {
case blink::mojom::PartitioningBlobURLInfo::kBlockedCrossPartitionFetching:
partitioning_blob_url_info_string = protocol::Audits::
PartitioningBlobURLInfoEnum::BlockedCrossPartitionFetching;
break;
case blink::mojom::PartitioningBlobURLInfo::kEnforceNoopenerForNavigation:
partitioning_blob_url_info_string = protocol::Audits::
PartitioningBlobURLInfoEnum::EnforceNoopenerForNavigation;
break;
default:
partitioning_blob_url_info_string = "Unknown";
break;
}
auto partitioning_blob_url_issue_details =
protocol::Audits::PartitioningBlobURLIssueDetails::Create()
.SetUrl(issue_details->url.spec())
.SetPartitioningBlobURLInfo(partitioning_blob_url_info_string)
.Build();
auto protocol_issue_details =
protocol::Audits::InspectorIssueDetails::Create()
.SetPartitioningBlobURLIssueDetails(
std::move(partitioning_blob_url_issue_details))
.Build();
auto issue = protocol::Audits::InspectorIssue::Create()
.SetCode(protocol::Audits::InspectorIssueCodeEnum::
PartitioningBlobURLIssue)
.SetDetails(std::move(protocol_issue_details))
.Build();
return issue;
}
void UpdateChildFrameTrees(FrameTreeNode* ftn, bool update_target_info) {
if (auto* agent_host = WebContentsDevToolsAgentHost::GetFor(
WebContentsImpl::FromFrameTreeNode(ftn))) {
@ -2102,6 +2140,10 @@ void BuildAndReportBrowserInitiatedIssue(
blink::mojom::InspectorIssueCode::kBounceTrackingIssue) {
issue =
BuildBounceTrackingIssue(info->details->bounce_tracking_issue_details);
} else if (info->code ==
blink::mojom::InspectorIssueCode::kPartitioningBlobURLIssue) {
issue = BuildPartitioningBlobURLIssue(
info->details->partitioning_blob_url_issue_details);
} else if (info->code == blink::mojom::InspectorIssueCode::
kCookieDeprecationMetadataIssue) {
issue = BuildCookieDeprecationMetadataIssue(

@ -12581,10 +12581,27 @@ void RenderFrameHostImpl::LogWebFeatureForCurrentPage(
GetContentClient()->browser()->LogWebFeatureForCurrentPage(this, feature);
}
base::RepeatingClosure RenderFrameHostImpl::CreateLogWebFeatureClosure(
blink::mojom::WebFeature feature) {
return base::BindRepeating(&RenderFrameHostImpl::LogWebFeatureForCurrentPage,
weak_ptr_factory_.GetWeakPtr(), feature);
void RenderFrameHostImpl::ReportBlockingCrossPartitionBlobURL(
const GURL& blocked_url,
blink::mojom::PartitioningBlobURLInfo info) {
// Log the use of the web feature (increment the use counter).
LogWebFeatureForCurrentPage(
blink::mojom::WebFeature::kCrossPartitionBlobURLFetch);
// Report the DevTools issue.
auto details = blink::mojom::InspectorIssueDetails::New();
auto partitioning_blob_url_issue_details =
blink::mojom::PartitioningBlobURLIssueDetails::New();
partitioning_blob_url_issue_details->url = blocked_url;
partitioning_blob_url_issue_details->partitioning_blob_url_info = info;
details->partitioning_blob_url_issue_details =
std::move(partitioning_blob_url_issue_details);
ReportInspectorIssue(blink::mojom::InspectorIssueInfo::New(
blink::mojom::InspectorIssueCode::kPartitioningBlobURLIssue,
std::move(details)));
}
void RenderFrameHostImpl::BindBlobUrlStoreAssociatedReceiver(
@ -12595,8 +12612,9 @@ void RenderFrameHostImpl::BindBlobUrlStoreAssociatedReceiver(
storage_partition_impl->GetBlobUrlRegistry()->AddReceiver(
GetStorageKey(), GetLastCommittedOrigin(),
GetProcess()->GetDeprecatedID(), std::move(receiver),
CreateLogWebFeatureClosure(
blink::mojom::WebFeature::kCrossPartitionBlobURLFetch),
base::BindRepeating(
&RenderFrameHostImpl::ReportBlockingCrossPartitionBlobURL,
weak_ptr_factory_.GetWeakPtr()),
!(GetContentClient()->browser()->IsBlobUrlPartitioningEnabled(
GetBrowserContext())));
}

@ -4198,8 +4198,12 @@ class CONTENT_EXPORT RenderFrameHostImpl
void LogWebFeatureForCurrentPage(blink::mojom::WebFeature feature);
base::RepeatingClosure CreateLogWebFeatureClosure(
blink::mojom::WebFeature feature);
// This runs when the storage_key check fails
// in `BlobURLStoreImpl::ResolveAsURLLoaderFactory` and increments the use
// counter.
void ReportBlockingCrossPartitionBlobURL(
const GURL& blocked_url,
blink::mojom::PartitioningBlobURLInfo info);
// For frames and main thread worklets we use a navigation-associated
// interface and bind `receiver` to a `BlobURLStore` instance, which

@ -11,6 +11,7 @@
#include "net/base/features.h"
#include "storage/browser/blob/blob_url_store_impl.h"
#include "storage/browser/blob/blob_url_utils.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "url/gurl.h"
namespace storage {
@ -33,13 +34,15 @@ void BlobUrlRegistry::AddReceiver(
const url::Origin& renderer_origin,
int render_process_host_id,
mojo::PendingAssociatedReceiver<blink::mojom::BlobURLStore> receiver,
base::RepeatingClosure partitioned_fetch_failure_closure,
base::RepeatingCallback<void(const GURL&,
blink::mojom::PartitioningBlobURLInfo)>
partitioning_blob_url_closure,
bool partitioning_disabled_by_policy) {
mojo::ReceiverId receiver_id = frame_receivers_.Add(
std::make_unique<storage::BlobURLStoreImpl>(
storage_key, renderer_origin, render_process_host_id, AsWeakPtr(),
storage::BlobURLValidityCheckBehavior::DEFAULT,
std::move(partitioned_fetch_failure_closure),
std::move(partitioning_blob_url_closure),
partitioning_disabled_by_policy),
std::move(receiver));

@ -20,6 +20,7 @@
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
class GURL;
@ -37,14 +38,17 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobUrlRegistry {
// Binds receivers corresponding to connections from renderer frame
// contexts and stores them in `frame_receivers_`.
// `partitioned_fetch_failure_closure` runs when the storage_key check fails
// in `BlobURLStoreImpl::ResolveAsURLLoaderFactory`.
// `partitioning_blob_url_closure` runs when the storage_key check fails
// in `BlobURLStoreImpl::ResolveAsURLLoaderFactory` and increments the use
// counter.
void AddReceiver(
const blink::StorageKey& storage_key,
const url::Origin& renderer_origin,
int render_process_host_id,
mojo::PendingAssociatedReceiver<blink::mojom::BlobURLStore> receiver,
base::RepeatingClosure partitioned_fetch_failure_closure,
base::RepeatingCallback<void(const GURL&,
blink::mojom::PartitioningBlobURLInfo)>
partitioning_blob_url_closure,
bool partitioning_disabled_by_policy = false);
// Binds receivers corresponding to connections from renderer worker

@ -16,6 +16,7 @@
#include "storage/browser/blob/blob_url_registry.h"
#include "storage/browser/blob/blob_url_utils.h"
#include "storage/browser/blob/features.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
#include "url/url_util.h"
namespace storage {
@ -71,15 +72,16 @@ BlobURLStoreImpl::BlobURLStoreImpl(
int render_process_host_id,
base::WeakPtr<BlobUrlRegistry> registry,
BlobURLValidityCheckBehavior validity_check_behavior,
base::RepeatingClosure partitioned_fetch_failure_closure,
base::RepeatingCallback<void(const GURL&,
blink::mojom::PartitioningBlobURLInfo)>
partitioning_blob_url_closure,
bool partitioning_disabled_by_policy)
: storage_key_(storage_key),
renderer_origin_(renderer_origin),
render_process_host_id_(render_process_host_id),
registry_(std::move(registry)),
validity_check_behavior_(validity_check_behavior),
partitioned_fetch_failure_closure_(
std::move(partitioned_fetch_failure_closure)),
partitioning_blob_url_closure_(std::move(partitioning_blob_url_closure)),
partitioning_disabled_by_policy_(partitioning_disabled_by_policy) {}
BlobURLStoreImpl::~BlobURLStoreImpl() {
@ -131,7 +133,9 @@ void BlobURLStoreImpl::ResolveAsURLLoaderFactory(
}
if (!registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url),
storage_key_)) {
partitioned_fetch_failure_closure_.Run();
partitioning_blob_url_closure_.Run(
url,
blink::mojom::PartitioningBlobURLInfo::kBlockedCrossPartitionFetching);
if (base::FeatureList::IsEnabled(
features::kBlockCrossPartitionBlobUrlFetching) &&
!partitioning_disabled_by_policy_) {
@ -165,7 +169,9 @@ void BlobURLStoreImpl::ResolveForNavigation(
if (!is_top_level_navigation &&
!registry_->IsUrlMapped(BlobUrlUtils::ClearUrlFragment(url),
storage_key_)) {
partitioned_fetch_failure_closure_.Run();
partitioning_blob_url_closure_.Run(
url,
blink::mojom::PartitioningBlobURLInfo::kBlockedCrossPartitionFetching);
if (base::FeatureList::IsEnabled(
features::kBlockCrossPartitionBlobUrlFetching) &&
!partitioning_disabled_by_policy_) {

@ -18,6 +18,7 @@
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "third_party/blink/public/mojom/blob/blob.mojom.h"
#include "third_party/blink/public/mojom/blob/blob_url_store.mojom.h"
#include "third_party/blink/public/mojom/devtools/inspector_issue.mojom.h"
namespace storage {
@ -26,7 +27,7 @@ class BlobUrlRegistry;
class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl
: public blink::mojom::BlobURLStore {
public:
// `partitioned_fetch_failure_closure` runs when the storage_key check fails
// `partitioning_blob_url_closure` runs when the storage_key check fails
// in `BlobURLStoreImpl::ResolveAsURLLoaderFactory`.
BlobURLStoreImpl(const blink::StorageKey& storage_key,
const url::Origin& renderer_origin,
@ -34,8 +35,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl
base::WeakPtr<BlobUrlRegistry> registry,
BlobURLValidityCheckBehavior validity_check_options =
BlobURLValidityCheckBehavior::DEFAULT,
base::RepeatingClosure partitioned_fetch_failure_closure =
base::DoNothing(),
base::RepeatingCallback<
void(const GURL&, blink::mojom::PartitioningBlobURLInfo)>
partitioning_blob_url_closure_ = base::DoNothing(),
bool partitioning_disabled_by_policy = false);
BlobURLStoreImpl(const BlobURLStoreImpl&) = delete;
@ -87,7 +89,9 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobURLStoreImpl
std::set<GURL> urls_;
base::RepeatingClosure partitioned_fetch_failure_closure_;
base::RepeatingCallback<void(const GURL&,
blink::mojom::PartitioningBlobURLInfo)>
partitioning_blob_url_closure_;
const bool partitioning_disabled_by_policy_;

@ -986,6 +986,18 @@ experimental domain Audits
string failureMessage
optional Network.RequestId requestId
type PartitioningBlobURLInfo extends string
enum
BlockedCrossPartitionFetching
EnforceNoopenerForNavigation
type PartitioningBlobURLIssueDetails extends object
properties
# The BlobURL that failed to load.
string url
# Additional information about the Partitioning Blob URL issue.
PartitioningBlobURLInfo partitioningBlobURLInfo
type SelectElementAccessibilityIssueReason extends string
enum
DisallowedSelectChild
@ -1049,6 +1061,7 @@ experimental domain Audits
CorsIssue
AttributionReportingIssue
QuirksModeIssue
PartitioningBlobURLIssue
# Deprecated
NavigatorUserAgentIssue
GenericIssue
@ -1078,6 +1091,7 @@ experimental domain Audits
optional CorsIssueDetails corsIssueDetails
optional AttributionReportingIssueDetails attributionReportingIssueDetails
optional QuirksModeIssueDetails quirksModeIssueDetails
optional PartitioningBlobURLIssueDetails partitioningBlobURLIssueDetails
deprecated optional NavigatorUserAgentIssueDetails navigatorUserAgentIssueDetails
optional GenericIssueDetails genericIssueDetails
optional DeprecationIssueDetails deprecationIssueDetails

@ -27,6 +27,7 @@ enum InspectorIssueCode {
kGenericIssue,
kDeprecationIssue,
kFederatedAuthUserInfoRequestIssue,
kPartitioningBlobURLIssue,
};
// Information about a cookie that is affected by an issue.
@ -290,6 +291,16 @@ struct BounceTrackingIssueDetails {
array<string> tracking_sites;
};
enum PartitioningBlobURLInfo {
kBlockedCrossPartitionFetching,
kEnforceNoopenerForNavigation,
};
struct PartitioningBlobURLIssueDetails {
url.mojom.Url url;
PartitioningBlobURLInfo partitioning_blob_url_info;
};
struct CookieDeprecationMetadataIssueDetails {
array<string> allowed_sites;
uint32 opt_out_percentage;
@ -341,6 +352,7 @@ struct InspectorIssueDetails {
LowTextContrastIssue? low_text_contrast_details;
FederatedAuthRequestIssueDetails? federated_auth_request_details;
BounceTrackingIssueDetails? bounce_tracking_issue_details;
PartitioningBlobURLIssueDetails? partitioning_blob_url_issue_details;
CookieDeprecationMetadataIssueDetails? cookie_deprecation_metadata_issue_details;
GenericIssueDetails? generic_issue_details;
DeprecationIssueDetails? deprecation_issue_details;

@ -59,6 +59,7 @@ blink::protocol::String InspectorIssueCodeValue(
case mojom::blink::InspectorIssueCode::kFederatedAuthRequestIssue:
case mojom::blink::InspectorIssueCode::kFederatedAuthUserInfoRequestIssue:
case mojom::blink::InspectorIssueCode::kBounceTrackingIssue:
case mojom::blink::InspectorIssueCode::kPartitioningBlobURLIssue:
case mojom::blink::InspectorIssueCode::kCookieDeprecationMetadataIssue:
case mojom::blink::InspectorIssueCode::kGenericIssue:
case mojom::blink::InspectorIssueCode::kDeprecationIssue: