Low watch CVSS 4.3 chrome Info Disclosure 🔧 Commit mapped

Overview

Low
Severity
4.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInformation leak in Core
DescriptionInformation leak in Core
ComponentCore
Bug ClassInfo Disclosure
Action tierwatch — information disclosure — security-relevant, below the RCE-class bar
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker537470182
Fix commit6e43fb358cfb (chromium/src) +223/-2
CWECWE-200 (Information exposure)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-07-21 (per vendor advisory)
Fix landed2026-07-31 (public git)
Disclosed2026-09-08
Patch gap39 days — fix public in source before the advisory

Background

`NavigationRequest`
the browser-process object that tracks a single navigation through its lifecycle states, from creation up through WILL_START_REQUEST and commit.
`site_info_` / `GetStoragePartitionWithCurrentSiteInfo()`
site_info_ holds the target frame’s StoragePartition configuration, but only reflects the real destination partition after StartNavigation() has run; before that it holds a default configuration, which GetStoragePartitionWithCurrentSiteInfo() resolves against.
`StoragePartition` / guest `<webview>`
an isolation boundary for a browsing context’s storage and network state; non-default partitions back guest content such as <webview>, and data must not cross between partitions.
`DeclarativePerformanceObserver::RecordEarlyNavigationFailure`
records an early-failure telemetry report into the DeclarativePerformanceObserverStore of the resolved StoragePartition, keyed by origin.

Root Cause Analysis

When a primary-main-frame navigation failed early (aborted with net::ERR_ABORTED, or any error before a response), NavigationRequest unconditionally called RecordEarlyNavigationFailure(this, GetStoragePartitionWithCurrentSiteInfo(), ...). If the navigation was discarded before StartNavigation() advanced state_ to WILL_START_REQUEST (for example, aborted during a beforeunload handler), site_info_ still held the default configuration, so GetStoragePartitionWithCurrentSiteInfo() resolved to the default StoragePartition rather than the frame’s actual (non-default) partition. For a frame living in a non-default partition such as a guest <webview>, this wrote a failure report keyed by the frame’s origin into the default partition’s store, violating the invariant that a non-default partition’s activity stays confined to that partition.

The fix adds CanRecordEarlyNavigationFailure() as a guard on both recording sites: it permits recording only when state_ >= WILL_START_REQUEST (so site_info_ is trustworthy) or when the current frame already belongs to the default StoragePartition (so a mis-resolution is harmless). The regression tests confirm that in a non-default partition an early abort now records into neither the default nor the frame store, while a default-partition early abort still records normally.

Key insight
The code read the partition destination from site_info_ before that field was populated by StartNavigation(), so early-failure telemetry for a non-default partition was misrouted into the default partition; the fix gates recording on state_ >= WILL_START_REQUEST or an already-default current partition, ensuring the destination is only trusted once it is known.

Attack Path

  1. Set up a non-default partition Host attacker-controlled content inside a guest <webview> (or other non-default StoragePartition) whose origin has an early-failure reporting policy registered.
  2. Trigger an early-aborted navigation Cause a primary-main-frame navigation in that partition to be discarded before it reaches WILL_START_REQUEST (e.g., via a beforeunload handler that stalls the navigation, then discarding it), so net_error_ == net::ERR_ABORTED while site_info_ still holds the default configuration.
  3. Misrouted recording RecordEarlyNavigationFailure runs with GetStoragePartitionWithCurrentSiteInfo() resolving to the default StoragePartition, writing an origin-keyed early-failure report into the default partition’s DeclarativePerformanceObserverStore instead of the guest partition’s store.
  4. Cross-partition observation (inferred) Content in the default partition that can read early-failure reports for that origin observes navigation-failure information about the isolated guest partition, leaking cross-StoragePartition navigation state.

Impact Assessment

An attacker gains a cross-StoragePartition information leak: early navigation-failure telemetry for a frame in a non-default partition (e.g., a guest <webview>) is recorded into the default partition where it should not be visible. The precondition is a primary-main-frame navigation in a non-default partition that is aborted before StartNavigation() runs. The advisory classifies this as an information leak in Core; no in-the-wild exploitation is stated, and any further use of the leaked data by default-partition content is (inferred).

Changed Functions

FunctionChangeNotes
NavigationRequestPartitionBrowserTest
content/browser/renderer_host/navigation_request_browsertest.cc
modified
NavigationRequestPartitionBrowserTest
content/browser/renderer_host/navigation_request_browsertest.cc
modified
if
content/browser/renderer_host/navigation_request_browsertest.cc
modified

Files Changed

  • content/browser/renderer_host/navigation_request.cc
  • content/browser/renderer_host/navigation_request_browsertest.cc
  • content/browser/renderer_host/navigation_request.h

Audit Directions

  • Reading destination state before it is populated
    Look for other consumers of site_info_, GetStoragePartitionWithCurrentSiteInfo(), or similar destination-derived data on paths that can run before StartNavigation() / WILL_START_REQUEST, where a default fallback silently substitutes for the real target.
  • Partition-keyed side effects on early/aborted navigations
    Audit any recording, caching, or reporting keyed by StoragePartition that fires on early-failure or abort paths, ensuring it resolves the partition only when the target is known.
  • Guest / non-default partition confinement
    Review features that assume the default StoragePartition and verify they behave correctly for guest <webview> and other non-default partitions, especially where data written to one partition could be read from another.
From 6e43fb358cfb4ad3c72af10ad00d840b92460f63 Mon Sep 17 00:00:00 2001
From: Shunya Shishido <sisidovski@chromium.org>
Date: Thu, 30 Jul 2026 23:33:31 -0700
Subject: [PATCH] Fix DeclarativePerformanceObserver early failure partition check

NavigationRequest::site_info_ only reflects the target frame's
StoragePartition once StartNavigation() has run; before that it holds a
default configuration.

To prevent cross-StoragePartition information leaks from non-default
partitions (e.g., guest <webview>) while preserving early-failure
telemetry for standard main frames in the default StoragePartition, this
CL requires that either StartNavigation() has run (`state_ >=
WILL_START_REQUEST`) or the current frame already belongs to the default
StoragePartition.

This CL also introduces CanRecordEarlyNavigationFailure() to avoid code
duplication and adds comprehensive browsertests to verify both
cross-partition leak prevention and standard early-failure recording.

Bug: 537470182
Change-Id: I4c9c19a0bb7e45a726c083b74c201e1a31331d39
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8173103
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Commit-Queue: Shunya Shishido <sisidovski@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1671650}
---
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 6a62ef7..76ed6e5 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -2245,7 +2245,8 @@
   }
 
   if (!early_navigation_failure_recorded_ && !response() &&
-      IsInPrimaryMainFrame() && net_error_ == net::ERR_ABORTED) {
+      IsInPrimaryMainFrame() && net_error_ == net::ERR_ABORTED &&
+      CanRecordEarlyNavigationFailure()) {
     DeclarativePerformanceObserver::RecordEarlyNavigationFailure(
         this, GetStoragePartitionWithCurrentSiteInfo(), net::ERR_ABORTED);
   }
@@ -5592,7 +5593,8 @@
     fast_fetch_manager_->OnRequestFailed(*this, status, skip_throttles);
   }
 
-  if (!response() && IsInPrimaryMainFrame() && status.error_code != net::OK) {
+  if (!response() && IsInPrimaryMainFrame() && status.error_code != net::OK &&
+      CanRecordEarlyNavigationFailure()) {
     DeclarativePerformanceObserver::RecordEarlyNavigationFailure(
         this, GetStoragePartitionWithCurrentSiteInfo(), status.error_code);
     early_navigation_failure_recorded_ = true;
@@ -12572,6 +12574,21 @@
          !IsPrerenderedPageActivation();
 }
 
+bool NavigationRequest::CanRecordEarlyNavigationFailure() const {
+  // `site_info_` reflects the target frame's StoragePartition only after
+  // StartNavigation() has run; before that it holds a default configuration.
+  // To prevent cross-partition leaks from non-default partitions (e.g., guest
+  // <webview>) while keeping metrics for standard primary main frames, require
+  // that StartNavigation() has run (`state_ >= WILL_START_REQUEST`) or that
+  // the current frame already belongs to the default StoragePartition.
+  return state_ >= WILL_START_REQUEST ||
+         frame_tree_node_->current_frame_host()
+             ->GetSiteInstance()
+             ->GetSecurityPrincipal()
+             .GetStoragePartitionConfig()
+             .is_default();
+}
+
 void NavigationRequest::MaybeRecordTraceEventsAndHistograms() {
   if (navigation_handle_timing_.navigation_commit_sent_time.is_null()) {
     return;
diff --git a/content/browser/renderer_host/navigation_request.h b/content/browser/renderer_host/navigation_request.h
index 192469b..c645e7913 100644
--- a/content/browser/renderer_host/navigation_request.h
+++ b/content/browser/renderer_host/navigation_request.h
@@ -2712,6 +2712,10 @@
   // eventually be replaced with the navigation timeline metrics.
   bool ShouldRecordNavigationTimelineUkm() const;
 
+  // Returns true if early navigation failure can be safely recorded without
+  // risking cross-StoragePartition information leakage.
+  bool CanRecordEarlyNavigationFailure() const;
+
   // Given the known destination origin, this updates the view transition state
   // and resources. Namely, it clears it if the view transition state and
   // resources were generated from a different origin with the given origin.
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index 1b0fdf6..197641d 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -11,11 +11,13 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "components/history/core/browser/features.h"
 #include "content/browser/process_lock.h"
@@ -25,19 +27,24 @@
 #include "content/browser/renderer_host/process_selection_deferring_condition_runner.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/site_instance_impl.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_discard_reason.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/runtime_feature_state/runtime_feature_state_document_data.h"
 #include "content/public/browser/security_principal.h"
 #include "content/public/browser/site_isolation_policy.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/isolated_world_ids.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/back_forward_cache_util.h"
@@ -69,6 +76,7 @@
 #include "services/network/public/cpp/loading_params.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/common/chrome_debug_urls.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h"
 #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_read_context.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
@@ -5854,4 +5862,196 @@
             EvalJs(popup_shell->web_contents(), "document.body.textContent"));
 }
 
+namespace {
+
+base::ListValue TakeEarlyFailureReportsForTesting(
+    DeclarativePerformanceObserverStore* store,
+    const url::Origin& origin) {
+  base::ListValue out;
+  base::RunLoop loop;
+  store->TakeEarlyFailureReports(
+      origin, base::BindLambdaForTesting([&](base::ListValue res) {
+        out = std::move(res);
+        loop.Quit();
+      }));
+  loop.Run();
+  return out;
+}
+
+class NavigationRequestPartitionBrowserTest
+    : public NavigationRequestBrowserTest {
+ public:
+  NavigationRequestPartitionBrowserTest() {
+    feature_list_.InitWithFeatures(
+        {blink::features::kDeclarativePerformanceObserver,
+         features::kAbortNavigationsFromTabClosures},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+}  // namespace
+
+// Verifies that when a navigation request in a non-default StoragePartition is
+// aborted before reaching WILL_START_REQUEST, early failure reports are not
+// recorded in either the default StoragePartition or the frame's partition.
+IN_PROC_BROWSER_TEST_F(
+    NavigationRequestPartitionBrowserTest,
+    EarlyFailureInNonDefaultPartitionAbortedBeforeStartNavigation) {
+  CustomStoragePartitionBrowserClient modified_client(GURL("http://b.com/"));
+
+  GURL url1(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  const url::Origin origin = url::Origin::Create(url1);
+
+  EXPECT_TRUE(NavigateToURL(shell(), url1));
+
+  RenderFrameHostImpl* rfh = contents()->GetPrimaryMainFrame();
+  SiteInstanceImpl* si = rfh->GetSiteInstance();
+  EXPECT_FALSE(
+      si->GetSecurityPrincipal().GetStoragePartitionConfig().is_default());
+
+  BrowserContext* browser_context = contents()->GetBrowserContext();
+  auto* frame_partition =
+      static_cast<StoragePartitionImpl*>(rfh->GetStoragePartition());
+  auto* default_partition =
+      static_cast<StoragePartitionImpl*>(browser_context->GetStoragePartition(
+          StoragePartitionConfig::CreateDefault(browser_context)));
+  EXPECT_NE(frame_partition, default_partition);
+
+  DeclarativePerformanceObserverStore* frame_store =
+      frame_partition->GetDeclarativePerformanceObserverStore();
+  DeclarativePerformanceObserverStore* default_store =
+      default_partition->GetDeclarativePerformanceObserverStore();
+  EXPECT_TRUE(frame_store);
+  EXPECT_TRUE(default_store);
+
+  {
+    base::RunLoop loop;
+    default_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  {
+    base::RunLoop loop;
+    frame_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  EXPECT_TRUE(default_store->HasEarlyFailurePolicy(origin));
+  EXPECT_TRUE(frame_store->HasEarlyFailurePolicy(origin));
+
+  EXPECT_TRUE(ExecJs(rfh, R"(
+      window.addEventListener('beforeunload', e => {
+        e.preventDefault();
+        e.returnValue = 'blocked';
+      });
+  )"));
+  rfh->ExecuteJavaScriptWithUserGestureForTests(
+      std::u16string(), base::NullCallback(), ISOLATED_WORLD_ID_GLOBAL);
+  rfh->DisableBeforeUnloadHangMonitorForTesting();
+
+  FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
+  EXPECT_EQ(FrameType::kPrimaryMainFrame, root->GetFrameType());
+
+  {
+    BeforeUnloadBlockingDelegate beforeunload_pauser(contents());
+
+    shell()->LoadURL(url2);
+    beforeunload_pauser.Wait();
+
+    NavigationRequest* pending = root->navigation_request();
+    EXPECT_TRUE(pending);
+    if (pending) {
+      EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
+                pending->state());
+      EXPECT_TRUE(pending->IsInPrimaryMainFrame());
+    }
+
+    root->ResetNavigationRequest(
+        NavigationDiscardReason::kNewOtherNavigationBrowserInitiated);
+    EXPECT_FALSE(root->navigation_request());
+  }
+
+  base::ListValue default_reports =
+      TakeEarlyFailureReportsForTesting(default_store, origin);
+  base::ListValue frame_reports =
+      TakeEarlyFailureReportsForTesting(frame_store, origin);
+
+  EXPECT_EQ(0u, default_reports.size());
+  EXPECT_EQ(0u, frame_reports.size());
+}
+
+// Verifies that when a standard navigation request in the default
+// StoragePartition is aborted before reaching WILL_START_REQUEST, early failure
+// reports are correctly recorded in the default StoragePartition.
+// Note: This test fails under the original fix CL that unconditionally
+// required `state_ >= WILL_START_REQUEST`.
+IN_PROC_BROWSER_TEST_F(
+    NavigationRequestPartitionBrowserTest,
+    EarlyFailureInDefaultPartitionAbortedBeforeStartNavigation) {
+  GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url2(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  const url::Origin origin = url::Origin::Create(url1);
+
+  EXPECT_TRUE(NavigateToURL(shell(), url1));
+
+  RenderFrameHostImpl* rfh = contents()->GetPrimaryMainFrame();
+  SiteInstanceImpl* si = rfh->GetSiteInstance();
+  EXPECT_TRUE(
+      si->GetSecurityPrincipal().GetStoragePartitionConfig().is_default());
+
+  BrowserContext* browser_context = contents()->GetBrowserContext();
+  auto* default_partition =
+      static_cast<StoragePartitionImpl*>(browser_context->GetDefaultStoragePartition());
+
+  DeclarativePerformanceObserverStore* default_store =
+      default_partition->GetDeclarativePerformanceObserverStore();
+  EXPECT_TRUE(default_store);
+
+  {
+    base::RunLoop loop;
+    default_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  EXPECT_TRUE(default_store->HasEarlyFailurePolicy(origin));
+
+  EXPECT_TRUE(ExecJs(rfh, R"(
+      window.addEventListener('beforeunload', e => {
+        e.preventDefault();
+        e.returnValue = 'blocked';
+      });
+  )"));
+  rfh->ExecuteJavaScriptWithUserGestureForTests(
+      std::u16string(), base::NullCallback(), ISOLATED_WORLD_ID_GLOBAL);
+  rfh->DisableBeforeUnloadHangMonitorForTesting();
+
+  FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
+  EXPECT_EQ(FrameType::kPrimaryMainFrame, root->GetFrameType());
+
+  {
+    BeforeUnloadBlockingDelegate beforeunload_pauser(contents());
+
+    shell()->LoadURL(url2);
+    beforeunload_pauser.Wait();
+
+    NavigationRequest* pending = root->navigation_request();
+    EXPECT_TRUE(pending);
+    if (pending) {
+      EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
+                pending->state());
+      EXPECT_TRUE(pending->IsInPrimaryMainFrame());
+    }
+
+    root->ResetNavigationRequest(
+        NavigationDiscardReason::kNewOtherNavigationBrowserInitiated);
+    EXPECT_FALSE(root->navigation_request());
+  }
+
+  base::ListValue default_reports =
+      TakeEarlyFailureReportsForTesting(default_store, origin);
+
+  EXPECT_EQ(1u, default_reports.size());
+}
+
 }  // namespace content
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/renderer_host/navigation_request_browsertest.cc b/content/browser/renderer_host/navigation_request_browsertest.cc
index 1b0fdf6..197641d 100644
--- a/content/browser/renderer_host/navigation_request_browsertest.cc
+++ b/content/browser/renderer_host/navigation_request_browsertest.cc
@@ -11,11 +11,13 @@
 #include "base/functional/bind.h"
 #include "base/memory/raw_ptr.h"
 #include "base/memory/weak_ptr.h"
+#include "base/run_loop.h"
 #include "base/strings/strcat.h"
 #include "base/strings/string_number_conversions.h"
 #include "base/strings/stringprintf.h"
 #include "base/test/bind.h"
 #include "base/test/metrics/histogram_tester.h"
+#include "base/values.h"
 #include "build/build_config.h"
 #include "components/history/core/browser/features.h"
 #include "content/browser/process_lock.h"
@@ -25,19 +27,24 @@
 #include "content/browser/renderer_host/process_selection_deferring_condition_runner.h"
 #include "content/browser/renderer_host/render_frame_host_impl.h"
 #include "content/browser/site_instance_impl.h"
+#include "content/browser/storage_partition_impl.h"
 #include "content/browser/web_contents/web_contents_impl.h"
 #include "content/common/content_navigation_policy.h"
 #include "content/public/browser/browser_task_traits.h"
 #include "content/public/browser/browser_thread.h"
+#include "content/public/browser/navigation_discard_reason.h"
 #include "content/public/browser/navigation_throttle.h"
 #include "content/public/browser/runtime_feature_state/runtime_feature_state_document_data.h"
 #include "content/public/browser/security_principal.h"
 #include "content/public/browser/site_isolation_policy.h"
+#include "content/public/browser/storage_partition.h"
+#include "content/public/browser/storage_partition_config.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/browser/web_contents_observer.h"
 #include "content/public/common/bindings_policy.h"
 #include "content/public/common/content_features.h"
 #include "content/public/common/content_switches.h"
+#include "content/public/common/isolated_world_ids.h"
 #include "content/public/common/result_codes.h"
 #include "content/public/common/url_constants.h"
 #include "content/public/test/back_forward_cache_util.h"
@@ -69,6 +76,7 @@
 #include "services/network/public/cpp/loading_params.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "third_party/blink/public/common/chrome_debug_urls.h"
+#include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_context.h"
 #include "third_party/blink/public/common/runtime_feature_state/runtime_feature_state_read_context.h"
 #include "third_party/blink/public/mojom/fetch/fetch_api_request.mojom.h"
@@ -5854,4 +5862,196 @@
             EvalJs(popup_shell->web_contents(), "document.body.textContent"));
 }
 
+namespace {
+
+base::ListValue TakeEarlyFailureReportsForTesting(
+    DeclarativePerformanceObserverStore* store,
+    const url::Origin& origin) {
+  base::ListValue out;
+  base::RunLoop loop;
+  store->TakeEarlyFailureReports(
+      origin, base::BindLambdaForTesting([&](base::ListValue res) {
+        out = std::move(res);
+        loop.Quit();
+      }));
+  loop.Run();
+  return out;
+}
+
+class NavigationRequestPartitionBrowserTest
+    : public NavigationRequestBrowserTest {
+ public:
+  NavigationRequestPartitionBrowserTest() {
+    feature_list_.InitWithFeatures(
+        {blink::features::kDeclarativePerformanceObserver,
+         features::kAbortNavigationsFromTabClosures},
+        {});
+  }
+
+ private:
+  base::test::ScopedFeatureList feature_list_;
+};
+
+}  // namespace
+
+// Verifies that when a navigation request in a non-default StoragePartition is
+// aborted before reaching WILL_START_REQUEST, early failure reports are not
+// recorded in either the default StoragePartition or the frame's partition.
+IN_PROC_BROWSER_TEST_F(
+    NavigationRequestPartitionBrowserTest,
+    EarlyFailureInNonDefaultPartitionAbortedBeforeStartNavigation) {
+  CustomStoragePartitionBrowserClient modified_client(GURL("http://b.com/"));
+
+  GURL url1(embedded_test_server()->GetURL("b.com", "/title1.html"));
+  GURL url2(embedded_test_server()->GetURL("b.com", "/title2.html"));
+  const url::Origin origin = url::Origin::Create(url1);
+
+  EXPECT_TRUE(NavigateToURL(shell(), url1));
+
+  RenderFrameHostImpl* rfh = contents()->GetPrimaryMainFrame();
+  SiteInstanceImpl* si = rfh->GetSiteInstance();
+  EXPECT_FALSE(
+      si->GetSecurityPrincipal().GetStoragePartitionConfig().is_default());
+
+  BrowserContext* browser_context = contents()->GetBrowserContext();
+  auto* frame_partition =
+      static_cast<StoragePartitionImpl*>(rfh->GetStoragePartition());
+  auto* default_partition =
+      static_cast<StoragePartitionImpl*>(browser_context->GetStoragePartition(
+          StoragePartitionConfig::CreateDefault(browser_context)));
+  EXPECT_NE(frame_partition, default_partition);
+
+  DeclarativePerformanceObserverStore* frame_store =
+      frame_partition->GetDeclarativePerformanceObserverStore();
+  DeclarativePerformanceObserverStore* default_store =
+      default_partition->GetDeclarativePerformanceObserverStore();
+  EXPECT_TRUE(frame_store);
+  EXPECT_TRUE(default_store);
+
+  {
+    base::RunLoop loop;
+    default_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  {
+    base::RunLoop loop;
+    frame_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  EXPECT_TRUE(default_store->HasEarlyFailurePolicy(origin));
+  EXPECT_TRUE(frame_store->HasEarlyFailurePolicy(origin));
+
+  EXPECT_TRUE(ExecJs(rfh, R"(
+      window.addEventListener('beforeunload', e => {
+        e.preventDefault();
+        e.returnValue = 'blocked';
+      });
+  )"));
+  rfh->ExecuteJavaScriptWithUserGestureForTests(
+      std::u16string(), base::NullCallback(), ISOLATED_WORLD_ID_GLOBAL);
+  rfh->DisableBeforeUnloadHangMonitorForTesting();
+
+  FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
+  EXPECT_EQ(FrameType::kPrimaryMainFrame, root->GetFrameType());
+
+  {
+    BeforeUnloadBlockingDelegate beforeunload_pauser(contents());
+
+    shell()->LoadURL(url2);
+    beforeunload_pauser.Wait();
+
+    NavigationRequest* pending = root->navigation_request();
+    EXPECT_TRUE(pending);
+    if (pending) {
+      EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
+                pending->state());
+      EXPECT_TRUE(pending->IsInPrimaryMainFrame());
+    }
+
+    root->ResetNavigationRequest(
+        NavigationDiscardReason::kNewOtherNavigationBrowserInitiated);
+    EXPECT_FALSE(root->navigation_request());
+  }
+
+  base::ListValue default_reports =
+      TakeEarlyFailureReportsForTesting(default_store, origin);
+  base::ListValue frame_reports =
+      TakeEarlyFailureReportsForTesting(frame_store, origin);
+
+  EXPECT_EQ(0u, default_reports.size());
+  EXPECT_EQ(0u, frame_reports.size());
+}
+
+// Verifies that when a standard navigation request in the default
+// StoragePartition is aborted before reaching WILL_START_REQUEST, early failure
+// reports are correctly recorded in the default StoragePartition.
+// Note: This test fails under the original fix CL that unconditionally
+// required `state_ >= WILL_START_REQUEST`.
+IN_PROC_BROWSER_TEST_F(
+    NavigationRequestPartitionBrowserTest,
+    EarlyFailureInDefaultPartitionAbortedBeforeStartNavigation) {
+  GURL url1(embedded_test_server()->GetURL("a.com", "/title1.html"));
+  GURL url2(embedded_test_server()->GetURL("a.com", "/title2.html"));
+  const url::Origin origin = url::Origin::Create(url1);
+
+  EXPECT_TRUE(NavigateToURL(shell(), url1));
+
+  RenderFrameHostImpl* rfh = contents()->GetPrimaryMainFrame();
+  SiteInstanceImpl* si = rfh->GetSiteInstance();
+  EXPECT_TRUE(
+      si->GetSecurityPrincipal().GetStoragePartitionConfig().is_default());
+
+  BrowserContext* browser_context = contents()->GetBrowserContext();
+  auto* default_partition =
+      static_cast<StoragePartitionImpl*>(browser_context->GetDefaultStoragePartition());
+
+  DeclarativePerformanceObserverStore* default_store =
+      default_partition->GetDeclarativePerformanceObserverStore();
+  EXPECT_TRUE(default_store);
+
+  {
+    base::RunLoop loop;
+    default_store->SetEarlyFailurePolicy(origin, true, loop.QuitClosure());
+    loop.Run();
+  }
+  EXPECT_TRUE(default_store->HasEarlyFailurePolicy(origin));
+
+  EXPECT_TRUE(ExecJs(rfh, R"(
+      window.addEventListener('beforeunload', e => {
+        e.preventDefault();
+        e.returnValue = 'blocked';
+      });
+  )"));
+  rfh->ExecuteJavaScriptWithUserGestureForTests(
+      std::u16string(), base::NullCallback(), ISOLATED_WORLD_ID_GLOBAL);
+  rfh->DisableBeforeUnloadHangMonitorForTesting();
+
+  FrameTreeNode* root = contents()->GetPrimaryFrameTree().root();
+  EXPECT_EQ(FrameType::kPrimaryMainFrame, root->GetFrameType());
+
+  {
+    BeforeUnloadBlockingDelegate beforeunload_pauser(contents());
+
+    shell()->LoadURL(url2);
+    beforeunload_pauser.Wait();
+
+    NavigationRequest* pending = root->navigation_request();
+    EXPECT_TRUE(pending);
+    if (pending) {
+      EXPECT_EQ(NavigationRequest::WAITING_FOR_RENDERER_RESPONSE,
+                pending->state());
+      EXPECT_TRUE(pending->IsInPrimaryMainFrame());
+    }
+
+    root->ResetNavigationRequest(
+        NavigationDiscardReason::kNewOtherNavigationBrowserInitiated);
+    EXPECT_FALSE(root->navigation_request());
+  }
+
+  base::ListValue default_reports =
+      TakeEarlyFailureReportsForTesting(default_store, origin);
+
+  EXPECT_EQ(1u, default_reports.size());
+}
+
 }  // namespace content
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.