Medium chrome Memory Corruption 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMemory corruption in Permissions
DescriptionMemory corruption in Permissions
ComponentPermissions
Sub-componentPermissions
Bug ClassMemory Corruption
Tracker553394270
Fix commitc28e023686aa (chromium/src) +128/-394
CISA KEVNot listed
CreditedAnonymous
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
for
chrome/browser/content_settings/one_time_permission_provider.cc
modified
if
chrome/browser/content_settings/one_time_permission_provider.cc
modified
ActivePageCondition
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
MultipleTrackerCondition
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
ForegroundPageCondition
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
NewActivePage
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
NewForegroundPage
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
NewVideoCapturing
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
NewAudioCapturing
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
for
chrome/browser/permissions/one_time_permissions_tracker.cc
modified
switch
chrome/browser/permissions/one_time_permissions_tracker.cc
modified

Files Changed

  • chrome/browser/permissions/one_time_permissions_tracker_unittest.cc
  • chrome/browser/permissions/one_time_permissions_tracker.h
  • chrome/browser/permissions/one_time_permissions_tracker_helper.cc
  • chrome/browser/content_settings/one_time_permission_provider.cc
  • chrome/browser/ui/views/permissions/one_time_permission_interactive_ui_test.cc
  • chrome/browser/permissions/one_time_permissions_tracker.cc
From c28e023686aabc35230cf68c630d0c1de9bfed3b Mon Sep 17 00:00:00 2001
From: Antonio Sartori <antoniosartori@chromium.org>
Date: Wed, 16 Sep 2026 01:52:23 -0700
Subject: [PATCH] [permissions] Track OneTimePermission expiration via RAII (7/7)

This is the last of a series of CLs which refactor the logic for
expiring OneTimePermissions using a RAII pattern.

This CL tracks video and audio capturing with a scoped_refptr, which
calls the one time permission revocaiton upon destruction. This
finally allows to clean up all the per-origin counters in
OneTimePermissionsTracker.


More background on the overall refactoring:

The basic idea behind the refactoring is that if a one-time permission
was just tied to a Page, we could just expire the permission when the
Page (or an attached PageUserData) is deleted. If the permission is
tied to all Page's with a given origin, we can do the same with a
shared pointer held by all such Page's. With additional tricks we can
take care of foreground pages, used media streams, and additional
delays before expirations.

Using such patterns, instead of manually handling counters, makes it
impossible to have bugs in which such counters are not
increased/decreased correctly, and hence permissions never expired.

Bug: 498748871, 497593347, 553394270
Change-Id: Idb167f04ab39108406e84076ed87255c94925654
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8257497
Commit-Queue: Antonio Sartori <antoniosartori@chromium.org>
Reviewed-by: Thomas Nguyen <tungnh@chromium.org>
Reviewed-by: Christian Dullweber <dullweber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1698752}
---
diff --git a/chrome/browser/content_settings/one_time_permission_provider.cc b/chrome/browser/content_settings/one_time_permission_provider.cc
index 92083a7f..c168490 100644
--- a/chrome/browser/content_settings/one_time_permission_provider.cc
+++ b/chrome/browser/content_settings/one_time_permission_provider.cc
@@ -360,18 +360,6 @@
     for (const auto& pattern : entries_to_delete) {
       value_map_.DeleteValue(pattern.primary_pattern, pattern.secondary_pattern,
                              pattern.type);
-
-      // In all the observable `OneTimePermissionsTrackerObserver` events
-      // which are emitted from the tracker, the tracker knows for which
-      // origins and content settings it can halt bookkeeping. However, the
-      // tracker isn't aware of externally deleted content settings. To
-      // prevent it from triggering observers for an already deleted content
-      // setting, we need to inform it about the deletion here (and only
-      // here).
-      if (one_time_permissions_tracker_) {
-        one_time_permissions_tracker_->CleanupStateForExpiredContentSetting(
-            pattern.type, pattern.primary_pattern, pattern.secondary_pattern);
-      }
     }
   }
 
diff --git a/chrome/browser/permissions/one_time_permissions_tracker.cc b/chrome/browser/permissions/one_time_permissions_tracker.cc
index 6f3c052..0b37ae4 100644
--- a/chrome/browser/permissions/one_time_permissions_tracker.cc
+++ b/chrome/browser/permissions/one_time_permissions_tracker.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/permissions/one_time_permissions_tracker.h"
 
+#include <array>
 #include <utility>
 
 #include "base/functional/bind.h"
@@ -25,29 +26,16 @@
 
 namespace {
 
-class ActivePageCondition : public OneTimePermissionsTracker::Condition {
+template <size_t size>
+class MultipleTrackerCondition : public OneTimePermissionsTracker::Condition {
  public:
-  explicit ActivePageCondition(scoped_refptr<OneTimePermissionsConditionTracker>
-                                   internal_active_page_tracker)
-      : internal_active_page_tracker_(std::move(internal_active_page_tracker)) {
-  }
+  template <typename... Args>
+    requires(sizeof...(Args) == size)
+  explicit MultipleTrackerCondition(Args&&... trackers)
+      : trackers_{std::forward<Args>(trackers)...} {}
 
  private:
-  scoped_refptr<OneTimePermissionsConditionTracker>
-      internal_active_page_tracker_;
-};
-
-class ForegroundPageCondition : public OneTimePermissionsTracker::Condition {
- public:
-  ForegroundPageCondition(
-      scoped_refptr<OneTimePermissionsConditionTracker> short_condition_tracker,
-      scoped_refptr<OneTimePermissionsConditionTracker> long_condition_tracker)
-      : short_condition_tracker_(std::move(short_condition_tracker)),
-        long_condition_tracker_(std::move(long_condition_tracker)) {}
-
- private:
-  scoped_refptr<OneTimePermissionsConditionTracker> short_condition_tracker_;
-  scoped_refptr<OneTimePermissionsConditionTracker> long_condition_tracker_;
+  std::array<scoped_refptr<OneTimePermissionsConditionTracker>, size> trackers_;
 };
 
 }  // namespace
@@ -74,6 +62,18 @@
               OneTimePermissionsTrackerObserver::BackgroundExpiryType::
                   kLongTimeout),
           permissions::kOneTimePermissionMaximumLifetime);
+  video_capturing_tracker_factory_ =
+      std::make_unique<OneTimePermissionsConditionTracker::Factory>(
+          base::BindRepeating(
+              &OneTimePermissionsTracker::NotifyCapturingVideoExpired,
+              weak_factory_.GetWeakPtr()),
+          permissions::kOneTimePermissionTimeout);
+  audio_capturing_tracker_factory_ =
+      std::make_unique<OneTimePermissionsConditionTracker::Factory>(
+          base::BindRepeating(
+              &OneTimePermissionsTracker::NotifyCapturingAudioExpired,
+              weak_factory_.GetWeakPtr()),
+          permissions::kOneTimePermissionTimeout);
 }
 
 OneTimePermissionsTracker::~OneTimePermissionsTracker() = default;
@@ -85,20 +85,42 @@
 
 std::unique_ptr<OneTimePermissionsTracker::Condition>
 OneTimePermissionsTracker::NewActivePage(const url::Origin& origin) {
-  return std::make_unique<ActivePageCondition>(
+  return std::make_unique<MultipleTrackerCondition<1>>(
       active_page_tracker_factory_->New(origin));
 }
 
 std::unique_ptr<OneTimePermissionsTracker::Condition>
 OneTimePermissionsTracker::NewForegroundPage(const url::Origin& origin) {
-  return std::make_unique<ForegroundPageCondition>(
+  return std::make_unique<MultipleTrackerCondition<4>>(
       short_background_page_tracker_factory_->New(origin),
-      long_background_page_tracker_factory_->New(origin));
+      long_background_page_tracker_factory_->New(origin),
+      video_capturing_tracker_factory_->New(origin),
+      audio_capturing_tracker_factory_->New(origin));
 }
 
-OneTimePermissionsTracker::OriginTrackEntry::OriginTrackEntry() = default;
+std::unique_ptr<OneTimePermissionsTracker::Condition>
+OneTimePermissionsTracker::NewVideoCapturing(const url::Origin& origin) {
+  return std::make_unique<MultipleTrackerCondition<1>>(
+      video_capturing_tracker_factory_->New(origin));
+}
 
-OneTimePermissionsTracker::OriginTrackEntry::~OriginTrackEntry() = default;
+std::unique_ptr<OneTimePermissionsTracker::Condition>
+OneTimePermissionsTracker::NewAudioCapturing(const url::Origin& origin) {
+  return std::make_unique<MultipleTrackerCondition<1>>(
+      audio_capturing_tracker_factory_->New(origin));
+}
+
+void OneTimePermissionsTracker::SetTaskRunnerForTesting(
+    scoped_refptr<base::SequencedTaskRunner> task_runner) {
+  short_background_page_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
+      task_runner);
+  long_background_page_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
+      task_runner);
+  video_capturing_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
+      task_runner);
+  audio_capturing_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
+      task_runner);
+}
 
 void OneTimePermissionsTracker::Shutdown() {
   for (auto& observer : observer_list_) {
@@ -108,6 +130,8 @@
   active_page_tracker_factory_.reset();
   short_background_page_tracker_factory_.reset();
   long_background_page_tracker_factory_.reset();
+  video_capturing_tracker_factory_.reset();
+  audio_capturing_tracker_factory_.reset();
 }
 
 void OneTimePermissionsTracker::AddObserver(
@@ -120,177 +144,6 @@
   observer_list_.RemoveObserver(observer);
 }
 
-void OneTimePermissionsTracker::WebContentsBackgrounded(
-    const url::Origin& origin) {
-  // For some reason using `origin_tracker_[origin].background_tab_counter++;`
-  // on some builds leaves the value of
-  // `origin_tracker_[origin].background_tab_counter` at 0 in case of
-  // insertion. My best efforts to understand it have failed. Hence, `+=` is
-  // necessary here for the feature to work at all there.
-  origin_tracker_[origin].background_tab_counter += 1;
-
-  if (AreAllTabsToOriginBackgroundedOrDiscarded(origin)) {
-    HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_CAMERA);
-    HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_MIC);
-  }
-}
-
-void OneTimePermissionsTracker::WebContentsUnbackgrounded(
-    const url::Origin& origin) {
-  origin_tracker_[origin].background_tab_counter--;
-}
-
-void OneTimePermissionsTracker::WebContentsLoadedOrigin(
-    const url::Origin& origin) {
-  origin_tracker_[origin].undiscarded_tab_counter++;
-}
-
-void OneTimePermissionsTracker::WebContentsUnloadedOrigin(
-    const url::Origin& origin) {
-  origin_tracker_[origin].undiscarded_tab_counter--;
-  DCHECK(!(origin_tracker_[origin].undiscarded_tab_counter < 0));
-  if (AreAllTabsToOriginBackgroundedOrDiscarded(origin)) {
-    HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_CAMERA);
-    HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_MIC);
-  }
-}
-
-void OneTimePermissionsTracker::StartContentSpecificExpirationTimer(
-    const url::Origin& origin,
-    ContentSettingsType content_setting,
-    NotifyFunction notify_callback) {
-  origin_tracker_[origin].used_content_settings_set.insert(content_setting);
-  origin_tracker_[origin]
-      .content_setting_specific_expiration_timer_map[content_setting]
-      ->Start(
-          FROM_HERE, permissions::kOneTimePermissionTimeout,
-          base::BindOnce(notify_callback, weak_factory_.GetWeakPtr(), origin));
-}
-
-void OneTimePermissionsTracker::HandleUserMediaState(
-    const url::Origin& origin,
-    ContentSettingsType content_setting) {
-  NotifyFunction notify_callback;
-  switch (content_setting) {
-    case ContentSettingsType::MEDIASTREAM_CAMERA:
-      notify_callback = &OneTimePermissionsTracker::NotifyCapturingVideoExpired;
-      break;
-    case ContentSettingsType::MEDIASTREAM_MIC:
-      notify_callback = &OneTimePermissionsTracker::NotifyCapturingAudioExpired;
-      break;
-    default:
-      NOTREACHED();
-  }
-
-  if (origin_tracker_[origin].used_content_settings_set.find(content_setting) !=
-      origin_tracker_[origin].used_content_settings_set.end()) {
-    if (origin_tracker_[origin]
-            .content_setting_specific_expiration_timer_map.find(
-                content_setting) ==
-        origin_tracker_[origin]
-            .content_setting_specific_expiration_timer_map.end()) {
-      origin_tracker_[origin]
-          .content_setting_specific_expiration_timer_map[content_setting] =
-          std::make_unique<base::OneShotTimer>();
-    }
-
-    if (origin_tracker_[origin]
-                .content_setting_specific_counter_map[content_setting] == 0 &&
-        AreAllTabsToOriginBackgroundedOrDiscarded(origin)) {
-      StartContentSpecificExpirationTimer(origin, content_setting,
-                                          notify_callback);
-    } else {
-      origin_tracker_[origin]
-          .content_setting_specific_expiration_timer_map[content_setting]
-          ->Stop();
-    }
-  }
-}
-
-void OneTimePermissionsTracker::CapturingVideoChanged(const url::Origin& origin,
-                                                      bool is_capturing_video) {
-  if (is_capturing_video &&
-      origin_tracker_[origin].used_content_settings_set.find(
-          ContentSettingsType::MEDIASTREAM_CAMERA) ==
-          origin_tracker_[origin].used_content_settings_set.end()) {
-    origin_tracker_[origin].used_content_settings_set.insert(
-        ContentSettingsType::MEDIASTREAM_CAMERA);
-  }
-
-  origin_tracker_[origin].content_setting_specific_counter_map
-      [ContentSettingsType::MEDIASTREAM_CAMERA] += is_capturing_video ? 1 : -1;
-  HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_CAMERA);
-}
-
-void OneTimePermissionsTracker::CapturingAudioChanged(const url::Origin& origin,
-                                                      bool is_capturing_audio) {
-  if (is_capturing_audio &&
-      origin_tracker_[origin].used_content_settings_set.find(
-          ContentSettingsType::MEDIASTREAM_MIC) ==
-          origin_tracker_[origin].used_content_settings_set.end()) {
-    origin_tracker_[origin].used_content_settings_set.insert(
-        ContentSettingsType::MEDIASTREAM_MIC);
-  }
-
-  origin_tracker_[origin].content_setting_specific_counter_map
-      [ContentSettingsType::MEDIASTREAM_MIC] += is_capturing_audio ? 1 : -1;
-  HandleUserMediaState(origin, ContentSettingsType::MEDIASTREAM_MIC);
-}
-
-void OneTimePermissionsTracker::CleanupStateForExpiredContentSetting(
-    ContentSettingsType type,
-    ContentSettingsPattern primary_pattern,
-    ContentSettingsPattern secondary_pattern) {
-  std::vector<url::Origin> affected_origins;
-  for (const auto& entry : origin_tracker_) {
-    const GURL top_level_origin_as_gurl = entry.first.GetURL();
-    if (primary_pattern.Matches(top_level_origin_as_gurl) &&
-        secondary_pattern.Matches(top_level_origin_as_gurl)) {
-      affected_origins.push_back(entry.first);
-    }
-  }
-
-  for (const auto& origin : affected_origins) {
-    origin_tracker_[origin].content_setting_specific_expiration_timer_map.erase(
-        type);
-
-    origin_tracker_[origin].content_setting_specific_counter_map.erase(type);
-    origin_tracker_[origin].used_content_settings_set.erase(type);
-  }
-}
-
-void OneTimePermissionsTracker::FireRunningTimersForTesting() {
-  // The loops in this method require manual 'forward-looking' iteration because
-  // the methods executing upon timer expiration might erase elements from the
-  // map that is being iterated over.
-  for (auto i_outer = origin_tracker_.begin(), e_outer = origin_tracker_.end();
-       i_outer != e_outer;) {
-    auto origin_entry = i_outer++;
-    for (auto
-             i_inner =
-                 origin_entry->second
-                     .content_setting_specific_expiration_timer_map.begin(),
-             e_inner = origin_entry->second
-                           .content_setting_specific_expiration_timer_map.end();
-         i_inner != e_inner;) {
-      auto timer_entry = i_inner++;
-      if (timer_entry->second->IsRunning()) {
-        timer_entry->second->FireNow();
-      }
-    }
-  }
-}
-
-void OneTimePermissionsTracker::SetTaskRunnerForTesting(
-    scoped_refptr<base::SequencedTaskRunner> task_runner) {
-  active_page_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
-      task_runner);
-  short_background_page_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
-      task_runner);
-  long_background_page_tracker_factory_->SetTaskRunnerForTesting(  // IN-TEST
-      task_runner);
-}
-
 void OneTimePermissionsTracker::NotifyLastPageFromOriginClosed(
     const url::Origin& origin) {
   for (auto& observer : observer_list_) {
@@ -319,9 +172,3 @@
     observer.OnCapturingAudioExpired(origin);
   }
 }
-
-bool OneTimePermissionsTracker::AreAllTabsToOriginBackgroundedOrDiscarded(
-    const url::Origin& origin) {
-  return origin_tracker_[origin].background_tab_counter ==
-         origin_tracker_[origin].undiscarded_tab_counter;
-}
diff --git a/chrome/browser/permissions/one_time_permissions_tracker.h b/chrome/browser/permissions/one_time_permissions_tracker.h
index 25d7a2b..851e6d3 100644
--- a/chrome/browser/permissions/one_time_permissions_tracker.h
+++ b/chrome/browser/permissions/one_time_permissions_tracker.h
@@ -47,46 +47,17 @@
 
   std::unique_ptr<Condition> NewActivePage(const url::Origin& origin);
   std::unique_ptr<Condition> NewForegroundPage(const url::Origin& origin);
-
-  // Handles primary page changes to `origin` and pages of `origin` being
-  // undiscarded.
-  void WebContentsLoadedOrigin(const url::Origin& origin);
-
-  // Handles primary page changes from `origin`, pages of `origin` getting
-  // discarded and other WebContent destroy events.
-  void WebContentsUnloadedOrigin(const url::Origin& origin);
-
   // Adds observer implementing `OneTimePermissionsTrackerObserver`.
   void AddObserver(OneTimePermissionsTrackerObserver* observer);
 
   // Removes observer implementing `OneTimePermissionsTrackerObserver`.
   void RemoveObserver(OneTimePermissionsTrackerObserver* observer);
 
-  // Handles visibility changes.
-  void WebContentsBackgrounded(const url::Origin& origin);
-  void WebContentsUnbackgrounded(const url::Origin& origin);
-
-  // Handles changes in video capturing state.
... (file diff truncated)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/permissions/one_time_permissions_tracker_unittest.cc b/chrome/browser/permissions/one_time_permissions_tracker_unittest.cc
index 79812b20..bd4903b 100644
--- a/chrome/browser/permissions/one_time_permissions_tracker_unittest.cc
+++ b/chrome/browser/permissions/one_time_permissions_tracker_unittest.cc
@@ -136,9 +136,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountShortTimeout(), 0u);
   // Fast forward time by more than the timeout.
@@ -156,9 +154,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountShortTimeout(), 0u);
   // Fast forward time by less than the timeout.
@@ -175,9 +171,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountShortTimeout(), 0u);
   // Fast forward time by less than the timeout.
@@ -187,9 +181,7 @@
 
   // Unbackground and background the page to simulate usage.
   fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsUnbackgrounded(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   // Fast forward time by less than the timeout.
   task_environment()->FastForwardBy(permissions::kOneTimePermissionTimeout -
@@ -210,9 +202,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountLongTimeout(), 0u);
   // Fast forward time by more than the timeout.
@@ -235,9 +225,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountLongTimeout(), 0u);
   // Fast forward time by less than the timeout.
@@ -259,9 +247,7 @@
   OneTimePermissionsTrackerObserverForTesting observer;
   tracker()->AddObserver(&observer);
   auto fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsLoadedOrigin(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   ASSERT_EQ(observer.NotifiedCountLongTimeout(), 0u);
   // Fast forward time by less than the timeout.
@@ -271,9 +257,7 @@
 
   // Unbackground and background the page to simulate usage.
   fg_tracker = tracker()->NewForegroundPage(origin);
-  tracker()->WebContentsUnbackgrounded(origin);
   fg_tracker.reset();
-  tracker()->WebContentsBackgrounded(origin);
 
   // Fast forward time by less than the timeout.
   task_environment()->FastForwardBy(
@@ -409,3 +393,31 @@
 
   factory_tracker->RemoveObserver(&observer);
 }
+
+TEST_F(OneTimePermissionsTrackerTest, PageTrackerAudioCapture) {
+  OneTimePermissionsTrackerHelper::CreateForWebContents(web_contents());
+  auto* helper =
+      OneTimePermissionsTrackerHelper::FromWebContents(web_contents());
+
+  const GURL origin_url("https://example.com");
+  const url::Origin origin = url::Origin::Create(origin_url);
+
+  OneTimePermissionsTrackerObserverForTesting observer;
+  auto* factory_tracker =
+      OneTimePermissionsTrackerFactory::GetForBrowserContext(profile());
+  factory_tracker->AddObserver(&observer);
+
+  NavigateAndCommit(origin_url);
+  helper->OnVisibilityChanged(content::Visibility::HIDDEN);
+
+  helper->OnIsCapturingAudioChanged(web_contents(), true);
+  helper->OnIsCapturingAudioChanged(web_contents(), false);
+
+  EXPECT_EQ(observer.NotifiedCountCapturingAudioExpired(), 0u);
+  task_environment()->FastForwardBy(permissions::kOneTimePermissionTimeout +
+                                    base::Seconds(1));
+  EXPECT_EQ(observer.NotifiedCountCapturingAudioExpired(), 1u);
+  EXPECT_EQ(observer.LastNotifiedOrigin(), origin);
+
+  factory_tracker->RemoveObserver(&observer);
+}
diff --git a/chrome/browser/ui/views/permissions/one_time_permission_interactive_ui_test.cc b/chrome/browser/ui/views/permissions/one_time_permission_interactive_ui_test.cc
index 52ddd1fa..722f623 100644
--- a/chrome/browser/ui/views/permissions/one_time_permission_interactive_ui_test.cc
+++ b/chrome/browser/ui/views/permissions/one_time_permission_interactive_ui_test.cc
@@ -122,11 +122,13 @@
     command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
   }
 
-  // InProcessBrowserTest:
   void SetUpOnMainThread() override {
     InProcessBrowserTest::SetUpOnMainThread();
     host_resolver()->AddRule("*", "127.0.0.1");
     ASSERT_TRUE(embedded_test_server()->Start());
+    OneTimePermissionsTrackerFactory::GetForBrowserContext(
+        browser()->GetProfile())
+        ->SetTaskRunnerForTesting(task_runner_);
   }
 
   void TearDownOnMainThread() override {
@@ -178,15 +180,6 @@
     observer.Wait();
   }
 
-  void FireRunningExpirationTimers() {
-    OneTimePermissionsTrackerFactory::GetForBrowserContext(
-        current_browser()
-            ->GetTabStripModel()
-            ->GetActiveWebContents()
-            ->GetBrowserContext())
-        ->FireRunningTimersForTesting();
-  }
-
   void WatchPositionAndExpectGrantedPermission(
       permissions::PermissionRequestManager::AutoResponseType auto_response,
       bool expect_prompt) {
@@ -471,10 +464,6 @@
 
 IN_PROC_BROWSER_TEST_F(OneTimePermissionInteractiveUiTest,
                        GeolocationIsRevokedAfterFiveMinutesInBackground) {
-  auto* tracker = OneTimePermissionsTrackerFactory::GetForBrowserContext(
-      browser()->GetProfile());
-  tracker->SetTaskRunnerForTesting(task_runner_);
-
   ASSERT_NO_FATAL_FAILURE(
       Initialize(INITIALIZATION_DEFAULT, GetGeolocationGurl()));
 
@@ -503,9 +492,6 @@
   OtpEventExpectBucketCount(
       ContentSettingsType::GEOLOCATION,
       permissions::OneTimePermissionEvent::EXPIRED_IN_BACKGROUND, 1);
-
-  tracker->SetTaskRunnerForTesting(
-      base::SequencedTaskRunner::GetCurrentDefault());
 }
 
 IN_PROC_BROWSER_TEST_F(OneTimePermissionInteractiveUiTest,
@@ -520,10 +506,8 @@
   // web contents).
   CloseLastLocalStreamAt(0);
 
-  // Fire running timers. This means, that all one time permission
-  // expiration timers that are running at this point in time will fire
-  // their callbacks and are stopped.
-  FireRunningExpirationTimers();
+  // Fast forward time by the expiration timeout.
+  task_runner_->FastForwardBy(permissions::kOneTimePermissionTimeout);
 
   // Request cam/mic permission, expect no prompt is triggered.
   GetUserMediaAndExpectGrantedPermission(
@@ -553,10 +537,8 @@
   // within specified web contents).
   CloseLastLocalStreamAt(0);
 
-  // Fire running timers. This means, that all one time permission
-  // expiration timers that are running at this point in time will fire
-  // their callbacks and are stopped.
-  FireRunningExpirationTimers();
+  // Fast forward time to expire the permissions in the background.
+  task_runner_->FastForwardBy(permissions::kOneTimePermissionTimeout);
 
   // Switch back to previous tab
   browser()->GetTabStripModel()->ActivateTabAt(0);
@@ -592,10 +574,8 @@
   // Open new tab, this puts the first tab in the background.
   Initialize(INITIALIZATION_NEWTAB, GetDifferentOriginUrl());
 
-  // Fire running timers. This means, that all one time permission expiration
-  // timers that are running at this point in time will fire their callbacks and
-  // are stopped.
-  FireRunningExpirationTimers();
+  // Fast forward time by the expiration timeout.
+  task_runner_->FastForwardBy(permissions::kOneTimePermissionTimeout);
 
   // Switch back to previous tab
   browser()->GetTabStripModel()->ActivateTabAt(0);
@@ -625,10 +605,8 @@
   // web contents).
   CloseLastLocalStreamAt(0);
 
-  // Fire running timers. This means, that all one time permission
-  // expiration timers that are running at this point in time will fire
-  // their callbacks and are stopped.
-  FireRunningExpirationTimers();
+  // Fast forward time by the expiration timeout.
+  task_runner_->FastForwardBy(permissions::kOneTimePermissionTimeout);
 
   // Request cam/mic permission, expect no prompt is triggered.
   GetUserMediaAndExpectGrantedPermission(
@@ -658,9 +636,10 @@
   ASSERT_NO_FATAL_FAILURE(
       Initialize(INITIALIZATION_DEFAULT, GetDifferentOriginUrl()));
 
-  // Fire running timers. Since capturing has stopped and the remaining tab to
-  // the origin is in the background, the one-time permissions should expire.
-  FireRunningExpirationTimers();
+  // Fast forward time by the expiration timeout. Since capturing has stopped
+  // and the remaining tab to the origin is in the background, the one-time
+  // permissions should expire.
+  task_runner_->FastForwardBy(permissions::kOneTimePermissionTimeout);
 
   // Switch back to the background tab.
   browser()->GetTabStripModel()->ActivateTabAt(0);
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.