CVE-2026-95326
Overview
Background
- `BluetoothAllowedDevicesMap`
- An in-memory per-
StoragePartitionstore, owned byStoragePartitionImpl, that records which Web Bluetooth devices each origin has been granted access to under the legacy permissions backend. - `HostContentSettingsMap`
- Chrome’s persistent content-settings store that backs most site permissions and is wiped by Clear Browsing Data and profile resets, but which does not hold legacy Web Bluetooth device grants.
- `DATA_TYPE_CONTENT_SETTINGS`
- The Clear Browsing Data /
ChromeBrowsingDataRemoverDelegatedata-type flag that requests removal of site settings for a profile. - `StoragePartition`
- The content-layer container for a site’s storage and network state whose public interface is where
ClearBluetoothAllowedDevicesMap()is now exposed for callers to invoke.
Root Cause Analysis
Under the shipping default (legacy) Web Bluetooth permissions backend, a user’s device chooser grant was recorded in the in-memory BluetoothAllowedDevicesMap owned by StoragePartitionImpl, entirely separate from HostContentSettingsMap. When a user cleared site settings via Clear Browsing Data (DATA_TYPE_CONTENT_SETTINGS) or reset content settings through ProfileResetter::ResetContentSettings(), only the HostContentSettingsMap-backed data was cleared, so the invariant that clearing site permissions revokes all site access was violated for Bluetooth. The stale grants survived the wipe, letting a previously authorized origin silently reconnect to devices the user believed they had de-authorized.
The fix promotes the test-only ClearBluetoothAllowedDevicesMapForTesting() into a production ClearBluetoothAllowedDevicesMap() on the StoragePartition interface and calls it from both ChromeBrowsingDataRemoverDelegate and ProfileResetter::ResetContentSettings(), iterating all loaded storage partitions (or the one matching the filter’s StoragePartitionConfig) so the map is cleared alongside the other site settings.
StoragePartition-owned map outside HostContentSettingsMap while assuming that clearing site content settings would remove them; the fix wires an explicit ClearBluetoothAllowedDevicesMap() call into every content-settings clearing path so the out-of-band store is wiped in lockstep.Attack Path
- Establish a grant
A user visits an attacker-controlled or legitimate origin and approves a device in the Web Bluetooth chooser, recording the grant in the partition-scoped
BluetoothAllowedDevicesMapviaallowed_devices().AddDevice(). - User attempts revocation
Believing they have removed the site’s access, the user runs Clear Browsing Data with Site settings selected (
DATA_TYPE_CONTENT_SETTINGS) or resets content settings. - Cleanup misses the map
The removal path clears only
HostContentSettingsMap-backed data and never touches theBluetoothAllowedDevicesMap, so the device grant persists in memory. - Silent reconnection
The origin later calls
gatt.connect()on the still-remembered device and succeeds without any new chooser prompt, retaining access the user intended to revoke.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
IN_PROC_BROWSER_TEST_Fchrome/browser/bluetooth/web_bluetooth_browsertest.cc |
modified | |
TEST_Fchrome/browser/profile_resetter/profile_resetter_unittest.cc |
modified |
Files Changed
chrome/browser/profile_resetter/profile_resetter.cccontent/public/test/test_storage_partition.ccchrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cccontent/web_test/browser/web_test_control_host.ccchrome/browser/bluetooth/BUILD.gnchrome/browser/bluetooth/web_bluetooth_browsertest.cccontent/browser/storage_partition_impl.hchrome/browser/profile_resetter/profile_resetter_unittest.cccontent/public/browser/storage_partition.hcontent/browser/storage_partition_impl.cccontent/public/test/test_storage_partition.h
Audit Directions
- Out-of-band permission storesLook for any site-scoped grant or capability kept outside
HostContentSettingsMap(inStoragePartition, other in-memory maps, or process state) and verify each is wired into every Clear Browsing Data and profile-reset path. - Test-only clearing hooksFlag
...ForTestingclearing methods that are the only way to purge real user state, since production clearing paths may be silently skipping that data. - Content-settings clearing coverageAudit
ChromeBrowsingDataRemoverDelegateandProfileResetterremoval paths for permission types that are set but never revoked whenDATA_TYPE_CONTENT_SETTINGSor a content-settings reset runs.
Patch
From 7bb4a9d06e08ea1a1c2648112229d7a0322be912 Mon Sep 17 00:00:00 2001
From: Rob Pitkin <robpitkin@google.com>
Date: Wed, 19 Aug 2026 14:17:15 -0700
Subject: [PATCH] Bluetooth: Clear legacy grants on site reset
Under the default legacy Web Bluetooth permissions backend, device
chooser grants are stored in an in-memory BluetoothAllowedDevicesMap
owned by StoragePartitionImpl, outside of HostContentSettingsMap.
Because of this separation, clearing site settings
(DATA_TYPE_CONTENT_SETTINGS) via Clear Browsing Data or resetting
profile content settings did not clear this map, allowing sites to
silently reconnect to previously granted Bluetooth devices after
permissions were wiped.
This CL exposes ClearBluetoothAllowedDevicesMap() on the
StoragePartition interface and wires it into both
ChromeBrowsingDataRemoverDelegate (when clearing
DATA_TYPE_CONTENT_SETTINGS) and ProfileResetter::ResetContentSettings().
When clearing profile-wide site settings without a specific
StoragePartitionConfig filter, all loaded storage partitions are
iterated over and cleared.
Bug: 533041383
Change-Id: Id91a7dfcd6e5376fa8b05298cbdb05a7153495cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8183886
Reviewed-by: Matt Reynolds <mattreynolds@chromium.org>
Commit-Queue: Rob Pitkin <robpitkin@chromium.org>
Reviewed-by: Avi Drissman <avi@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1682622}
---
diff --git a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
index 1cd494a..01c8b4fd 100644
--- a/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
+++ b/chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.cc
@@ -752,6 +752,19 @@
browsing_data::RemoveSiteSettingsData(delete_begin, delete_end,
host_content_settings_map_);
+ if (filter_builder->GetStoragePartitionConfig().has_value()) {
+ if (content::StoragePartition* partition = profile_->GetStoragePartition(
+ *filter_builder->GetStoragePartitionConfig(),
+ /*can_create=*/false)) {
+ partition->ClearBluetoothAllowedDevicesMap();
+ }
+ } else {
+ profile_->ForEachLoadedStoragePartition(
+ [](content::StoragePartition* partition) {
+ partition->ClearBluetoothAllowedDevicesMap();
+ });
+ }
+
// The active permission does not have timestamps, so the all active grants
// will be revoked regardless of the time range because all the are expected
// to be recent.
diff --git a/chrome/browser/profile_resetter/profile_resetter.cc b/chrome/browser/profile_resetter/profile_resetter.cc
index 882ce3b..a259507 100644
--- a/chrome/browser/profile_resetter/profile_resetter.cc
+++ b/chrome/browser/profile_resetter/profile_resetter.cc
@@ -51,6 +51,7 @@
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/storage_partition.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/management_policy.h"
@@ -325,6 +326,11 @@
permission_context->RevokeAllActiveGrants();
}
+ profile_->ForEachLoadedStoragePartition(
+ [](content::StoragePartition* partition) {
+ partition->ClearBluetoothAllowedDevicesMap();
+ });
+
MarkAsDone(CONTENT_SETTINGS);
}
diff --git a/content/browser/storage_partition_impl.cc b/content/browser/storage_partition_impl.cc
index d3fb23e..a0c2794 100644
--- a/content/browser/storage_partition_impl.cc
+++ b/content/browser/storage_partition_impl.cc
@@ -3283,8 +3283,9 @@
->Reset();
}
-void StoragePartitionImpl::ClearBluetoothAllowedDevicesMapForTesting() {
- DCHECK(initialized_);
+void StoragePartitionImpl::ClearBluetoothAllowedDevicesMap() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ CHECK(initialized_);
bluetooth_allowed_devices_map_->Clear();
}
diff --git a/content/browser/storage_partition_impl.h b/content/browser/storage_partition_impl.h
index 125c67c1..d1b7a47 100644
--- a/content/browser/storage_partition_impl.h
+++ b/content/browser/storage_partition_impl.h
@@ -237,7 +237,7 @@
base::OnceClosure callback) override;
void Flush() override;
void ResetURLLoaderFactories() override;
- void ClearBluetoothAllowedDevicesMapForTesting() override;
+ void ClearBluetoothAllowedDevicesMap() override;
void AddObserver(DataRemovalObserver* observer) override;
void RemoveObserver(DataRemovalObserver* observer) override;
void FlushNetworkInterfaceForTesting() override;
diff --git a/content/public/browser/storage_partition.h b/content/public/browser/storage_partition.h
index 4a151ba..cc13d2e3 100644
--- a/content/public/browser/storage_partition.h
+++ b/content/public/browser/storage_partition.h
@@ -340,13 +340,13 @@
// Resets all URLLoaderFactories bound to this partition's network context.
virtual void ResetURLLoaderFactories() = 0;
+ // Clears the bluetooth allowed devices map.
+ virtual void ClearBluetoothAllowedDevicesMap() = 0;
+
virtual void AddObserver(DataRemovalObserver* observer) = 0;
virtual void RemoveObserver(DataRemovalObserver* observer) = 0;
- // Clear the bluetooth allowed devices map. For test use only.
- virtual void ClearBluetoothAllowedDevicesMapForTesting() = 0;
-
// Call |FlushForTesting()| on Network Service related interfaces. For test
// use only.
virtual void FlushNetworkInterfaceForTesting() = 0;
diff --git a/content/web_test/browser/web_test_control_host.cc b/content/web_test/browser/web_test_control_host.cc
index 11536ac..0b6b030 100644
--- a/content/web_test/browser/web_test_control_host.cc
+++ b/content/web_test/browser/web_test_control_host.cc
@@ -1308,7 +1308,7 @@
browser_context->GetDefaultStoragePartition();
storage_partition->GetServiceWorkerContext()->ClearAllServiceWorkersForTest(
barrier_closure);
- storage_partition->ClearBluetoothAllowedDevicesMapForTesting();
+ storage_partition->ClearBluetoothAllowedDevicesMap();
// Clear all site-related storage APIs to ensure tests are hermetic.
// Use an "opt-out" (or "blacklist") approach for future-proofing. This
diff --git a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
index 8f17acf..64589b6 100644
--- a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
+++ b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
@@ -20,7 +20,10 @@
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/bluetooth/chrome_bluetooth_delegate_impl_client.h"
#include "chrome/browser/bluetooth/web_bluetooth_test_utils.h"
+#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/chooser_bubble_testapi.h"
@@ -28,10 +31,13 @@
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/bluetooth_delegate_impl.h"
#include "components/permissions/content_setting_permission_context_base.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
@@ -42,6 +48,7 @@
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
@@ -220,6 +227,82 @@
content::ContentMockCertVerifier mock_cert_verifier_;
};
+// Verifies that the legacy Web Bluetooth per-device grant store
+// (StoragePartitionImpl::bluetooth_allowed_devices_map_) is cleared by
+// "Clear browsing data -> Site settings" (DATA_TYPE_CONTENT_SETTINGS).
+//
+// The default (production) permissions backend records the user's chooser
+// grant in an in-memory BluetoothAllowedDevicesMap on the StoragePartition,
+// outside HostContentSettingsMap. ChromeBrowsingDataRemoverDelegate
+// (DATA_TYPE_CONTENT_SETTINGS) and ProfileResetter::ResetContentSettings()
+// clear this map so that clearing site permissions revokes Web Bluetooth
+// device access.
+IN_PROC_BROWSER_TEST_F(WebBluetoothTest,
+ LegacyGrantRevokedByClearSiteSettings) {
+ // Legacy backend must be in use (this is the shipping default).
+ ASSERT_FALSE(base::FeatureList::IsEnabled(
+ features::kWebBluetoothNewPermissionsBackend));
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kExampleUrl)));
+ web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
+ CheckLastCommitedOrigin(kExampleUrl);
+
+ AddFakeDevice(kDeviceAddress);
+ SetDeviceToSelect(kDeviceAddress);
+
+ // 1) User grants a device via the chooser. On the legacy backend this is
+ // recorded via allowed_devices().AddDevice() into the storage-partition
+ // scoped BluetoothAllowedDevicesMap.
+ EXPECT_EQ("granted", content::EvalJs(web_contents_.get(), R"((async () => {
+ window.pocDevice = await navigator.bluetooth.requestDevice(
+ {filters: [{name: 'Test Device', services: ['heart_rate']}]});
+ return window.pocDevice ? 'granted' : 'no-device';
+ })())"));
+
+ // Control: write a per-origin BLUETOOTH_GUARD exception into
+ // HostContentSettingsMap so we can prove the CBD wipe below actually ran
+ // and cleared HCSM-backed data.
+ HostContentSettingsMap* hcsm =
+ HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile());
+ hcsm->SetContentSettingDefaultScope(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD,
+ CONTENT_SETTING_BLOCK);
+ ASSERT_EQ(CONTENT_SETTING_BLOCK,
+ hcsm->GetContentSetting(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD));
+
+ // 2) User performs "Clear browsing data -> Site settings, All time"
+ // (chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS).
+ content::BrowsingDataRemover* remover =
+ browser()->GetProfile()->GetBrowsingDataRemover();
+ content::BrowsingDataRemoverCompletionObserver observer(remover);
+ remover->RemoveAndReply(
+ base::Time(), base::Time::Max(),
+ chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS,
+ chrome_browsing_data_remover::ALL_ORIGIN_TYPES, &observer);
+ observer.BlockUntilCompletion();
+
+ // Control passes: HCSM-backed BLUETOOTH_GUARD exception was wiped
+ // (back to the registry default of ASK).
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ hcsm->GetContentSetting(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD));
+
+ // 3) Verify that the grant was revoked along with all other site
+ // permissions, causing gatt.connect() to reject with SecurityError
+ // (GATT_NOT_AUTHORIZED).
+ EXPECT_EQ("SecurityError: GATT operation not authorized.",
+ content::EvalJs(web_contents_.get(), R"((async () => {
+ try {
+ const gatt = await window.pocDevice.gatt.connect();
+ const svc = await gatt.getPrimaryService('heart_rate');
+ return svc.uuid;
+ } catch (e) {
+ return e.toString();
+ }
+ })())"));
+}
+
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, WebBluetoothAfterCrash) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kExampleUrl)));
web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
diff --git a/chrome/browser/profile_resetter/profile_resetter_unittest.cc b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
index 2cda8e69..0efd90c 100644
--- a/chrome/browser/profile_resetter/profile_resetter_unittest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
@@ -51,6 +51,7 @@
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_storage_partition.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_registrar.h"
@@ -584,6 +585,17 @@
}
}
+TEST_F(ProfileResetterTest, ResetContentSettings_BluetoothAllowedDevicesMap) {
+ content::StoragePartition* partition =
+ profile()->GetDefaultStoragePartition();
+ ASSERT_TRUE(partition);
+
+ // Verifies that resetting content settings invokes
+ // ClearBluetoothAllowedDevicesMap across loaded storage partitions
+ // without crashing.
+ ResetAndWait(ProfileResetter::CONTENT_SETTINGS);
+}
+
TEST_F(ProfileResetterTest, ResetExtensionsByDisabling) {
service()->Init();
diff --git a/content/public/test/test_storage_partition.cc b/content/public/test/test_storage_partition.cc
index 50e5eb49..61d27cde 100644
--- a/content/public/test/test_storage_partition.cc
+++ b/content/public/test/test_storage_partition.cc
@@ -230,6 +230,8 @@
void TestStoragePartition::ResetURLLoaderFactories() {}
+void TestStoragePartition::ClearBluetoothAllowedDevicesMap() {}
+
void TestStoragePartition::AddObserver(DataRemovalObserver* observer) {
data_removal_observer_count_++;
}
@@ -242,8 +244,6 @@
return data_removal_observer_count_;
}
-void TestStoragePartition::ClearBluetoothAllowedDevicesMapForTesting() {}
-
void TestStoragePartition::FlushNetworkInterfaceForTesting() {}
void TestStoragePartition::FlushCertVerifierInterfaceForTesting() {}
diff --git a/content/public/test/test_storage_partition.h b/content/public/test/test_storage_partition.h
index 4419ef9..5b5e6187 100644
--- a/content/public/test/test_storage_partition.h
+++ b/content/public/test/test_storage_partition.h
@@ -232,12 +232,12 @@
void Flush() override;
void ResetURLLoaderFactories() override;
+ void ClearBluetoothAllowedDevicesMap() override;
void AddObserver(DataRemovalObserver* observer) override;
void RemoveObserver(DataRemovalObserver* observer) override;
int GetDataRemovalObserverCount();
- void ClearBluetoothAllowedDevicesMapForTesting() override;
void FlushNetworkInterfaceForTesting() override;
void FlushCertVerifierInterfaceForTesting() override;
void WaitForDeletionTasksForTesting() override;
diff --git a/chrome/browser/bluetooth/BUILD.gn b/chrome/browser/bluetooth/BUILD.gn
index 0ec41931..29ff138a 100644
--- a/chrome/browser/bluetooth/BUILD.gn
+++ b/chrome/browser/bluetooth/BUILD.gn
@@ -80,9 +80,12 @@
":bluetooth",
":test_support",
"//base/test:test_support",
+ "//chrome/browser/browsing_data:constants",
+ "//chrome/browser/content_settings:content_settings_factory",
"//chrome/browser/ui/tabs:tab_strip",
"//chrome/test:test_support",
"//chrome/test:test_support_ui",
+ "//components/content_settings/core/browser",
"//components/permissions",
"//components/variations",
"//content/public/common",
Regression Test / PoC
diff --git a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
index 8f17acf..64589b6 100644
--- a/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
+++ b/chrome/browser/bluetooth/web_bluetooth_browsertest.cc
@@ -20,7 +20,10 @@
#include "chrome/browser/bluetooth/bluetooth_chooser_context_factory.h"
#include "chrome/browser/bluetooth/chrome_bluetooth_delegate_impl_client.h"
#include "chrome/browser/bluetooth/web_bluetooth_test_utils.h"
+#include "chrome/browser/browsing_data/chrome_browsing_data_remover_constants.h"
#include "chrome/browser/chrome_content_browser_client.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/chooser_bubble_testapi.h"
@@ -28,10 +31,13 @@
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
+#include "components/content_settings/core/browser/host_content_settings_map.h"
+#include "components/content_settings/core/common/content_settings_types.h"
#include "components/permissions/bluetooth_delegate_impl.h"
#include "components/permissions/content_setting_permission_context_base.h"
#include "components/permissions/contexts/bluetooth_chooser_context.h"
#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/browsing_data_remover.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
@@ -42,6 +48,7 @@
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/browsing_data_remover_test_util.h"
#include "content/public/test/content_mock_cert_verifier.h"
#include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h"
@@ -220,6 +227,82 @@
content::ContentMockCertVerifier mock_cert_verifier_;
};
+// Verifies that the legacy Web Bluetooth per-device grant store
+// (StoragePartitionImpl::bluetooth_allowed_devices_map_) is cleared by
+// "Clear browsing data -> Site settings" (DATA_TYPE_CONTENT_SETTINGS).
+//
+// The default (production) permissions backend records the user's chooser
+// grant in an in-memory BluetoothAllowedDevicesMap on the StoragePartition,
+// outside HostContentSettingsMap. ChromeBrowsingDataRemoverDelegate
+// (DATA_TYPE_CONTENT_SETTINGS) and ProfileResetter::ResetContentSettings()
+// clear this map so that clearing site permissions revokes Web Bluetooth
+// device access.
+IN_PROC_BROWSER_TEST_F(WebBluetoothTest,
+ LegacyGrantRevokedByClearSiteSettings) {
+ // Legacy backend must be in use (this is the shipping default).
+ ASSERT_FALSE(base::FeatureList::IsEnabled(
+ features::kWebBluetoothNewPermissionsBackend));
+
+ ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kExampleUrl)));
+ web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
+ CheckLastCommitedOrigin(kExampleUrl);
+
+ AddFakeDevice(kDeviceAddress);
+ SetDeviceToSelect(kDeviceAddress);
+
+ // 1) User grants a device via the chooser. On the legacy backend this is
+ // recorded via allowed_devices().AddDevice() into the storage-partition
+ // scoped BluetoothAllowedDevicesMap.
+ EXPECT_EQ("granted", content::EvalJs(web_contents_.get(), R"((async () => {
+ window.pocDevice = await navigator.bluetooth.requestDevice(
+ {filters: [{name: 'Test Device', services: ['heart_rate']}]});
+ return window.pocDevice ? 'granted' : 'no-device';
+ })())"));
+
+ // Control: write a per-origin BLUETOOTH_GUARD exception into
+ // HostContentSettingsMap so we can prove the CBD wipe below actually ran
+ // and cleared HCSM-backed data.
+ HostContentSettingsMap* hcsm =
+ HostContentSettingsMapFactory::GetForProfile(browser()->GetProfile());
+ hcsm->SetContentSettingDefaultScope(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD,
+ CONTENT_SETTING_BLOCK);
+ ASSERT_EQ(CONTENT_SETTING_BLOCK,
+ hcsm->GetContentSetting(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD));
+
+ // 2) User performs "Clear browsing data -> Site settings, All time"
+ // (chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS).
+ content::BrowsingDataRemover* remover =
+ browser()->GetProfile()->GetBrowsingDataRemover();
+ content::BrowsingDataRemoverCompletionObserver observer(remover);
+ remover->RemoveAndReply(
+ base::Time(), base::Time::Max(),
+ chrome_browsing_data_remover::DATA_TYPE_CONTENT_SETTINGS,
+ chrome_browsing_data_remover::ALL_ORIGIN_TYPES, &observer);
+ observer.BlockUntilCompletion();
+
+ // Control passes: HCSM-backed BLUETOOTH_GUARD exception was wiped
+ // (back to the registry default of ASK).
+ EXPECT_EQ(CONTENT_SETTING_ASK,
+ hcsm->GetContentSetting(GURL(kExampleUrl), GURL(kExampleUrl),
+ ContentSettingsType::BLUETOOTH_GUARD));
+
+ // 3) Verify that the grant was revoked along with all other site
+ // permissions, causing gatt.connect() to reject with SecurityError
+ // (GATT_NOT_AUTHORIZED).
+ EXPECT_EQ("SecurityError: GATT operation not authorized.",
+ content::EvalJs(web_contents_.get(), R"((async () => {
+ try {
+ const gatt = await window.pocDevice.gatt.connect();
+ const svc = await gatt.getPrimaryService('heart_rate');
+ return svc.uuid;
+ } catch (e) {
+ return e.toString();
+ }
+ })())"));
+}
+
IN_PROC_BROWSER_TEST_F(WebBluetoothTest, WebBluetoothAfterCrash) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GURL(kExampleUrl)));
web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
diff --git a/chrome/browser/profile_resetter/profile_resetter_unittest.cc b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
index 2cda8e69..0efd90c 100644
--- a/chrome/browser/profile_resetter/profile_resetter_unittest.cc
+++ b/chrome/browser/profile_resetter/profile_resetter_unittest.cc
@@ -51,6 +51,7 @@
#include "components/search_engines/search_engines_switches.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/web_contents.h"
+#include "content/public/test/test_storage_partition.h"
#include "content/public/test/test_utils.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_registrar.h"
@@ -584,6 +585,17 @@
}
}
+TEST_F(ProfileResetterTest, ResetContentSettings_BluetoothAllowedDevicesMap) {
+ content::StoragePartition* partition =
+ profile()->GetDefaultStoragePartition();
+ ASSERT_TRUE(partition);
+
+ // Verifies that resetting content settings invokes
+ // ClearBluetoothAllowedDevicesMap across loaded storage partitions
+ // without crashing.
+ ResetAndWait(ProfileResetter::CONTENT_SETTINGS);
+}
+
TEST_F(ProfileResetterTest, ResetExtensionsByDisabling) {
service()->Init();
diff --git a/content/public/test/test_storage_partition.cc b/content/public/test/test_storage_partition.cc
index 50e5eb49..61d27cde 100644
--- a/content/public/test/test_storage_partition.cc
+++ b/content/public/test/test_storage_partition.cc
@@ -230,6 +230,8 @@
void TestStoragePartition::ResetURLLoaderFactories() {}
+void TestStoragePartition::ClearBluetoothAllowedDevicesMap() {}
+
void TestStoragePartition::AddObserver(DataRemovalObserver* observer) {
data_removal_observer_count_++;
}
@@ -242,8 +244,6 @@
return data_removal_observer_count_;
}
-void TestStoragePartition::ClearBluetoothAllowedDevicesMapForTesting() {}
-
void TestStoragePartition::FlushNetworkInterfaceForTesting() {}
void TestStoragePartition::FlushCertVerifierInterfaceForTesting() {}
diff --git a/content/public/test/test_storage_partition.h b/content/public/test/test_storage_partition.h
index 4419ef9..5b5e6187 100644
--- a/content/public/test/test_storage_partition.h
+++ b/content/public/test/test_storage_partition.h
@@ -232,12 +232,12 @@
void Flush() override;
void ResetURLLoaderFactories() override;
+ void ClearBluetoothAllowedDevicesMap() override;
void AddObserver(DataRemovalObserver* observer) override;
void RemoveObserver(DataRemovalObserver* observer) override;
int GetDataRemovalObserverCount();
- void ClearBluetoothAllowedDevicesMapForTesting() override;
void FlushNetworkInterfaceForTesting() override;
void FlushCertVerifierInterfaceForTesting() override;
void WaitForDeletionTasksForTesting() override;