Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper privilege management in Actor
DescriptionImproper privilege management in Actor
ComponentActor
Bug ClassLogic Error
Tracker517673944
Fix commit01dc0c8102c0 (chromium/src) +46/-28
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
IN_PROC_BROWSER_TEST_F
chrome/browser/actor/tools/media_control_tool_browsertest.cc
modified
IN_PROC_BROWSER_TEST_F
chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
modified
if
chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc
modified
if
chrome/test/data/actor/media.html
modified

Files Changed

  • chrome/browser/actor/tools/media_control_tool.cc
  • chrome/browser/actor/tools/media_control_tool_browsertest.cc
  • chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
  • chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc
  • chrome/test/data/actor/media.html
From 01dc0c8102c05be7b6fb72a311513dfb2d3959d7 Mon Sep 17 00:00:00 2001
From: Yiren Wang <[email protected]>
Date: Mon, 29 Jun 2026 17:02:30 -0700
Subject: [PATCH] Use SuspendType of kSystem for media session

MediaControlTool and AI Overlay WebUI tools currently control media
sessions using SuspendType::kUI, but SuspendType::kUI is reserved for
physical human actions and unconditionally grants user activation
gesture on the page.

This CL switches to use SuspendType::kSystem to suppress transient user
activation, and updates the corresponding tests. The downside of using
SuspendType::kSystem is that it will skip the media action handlers
registered by the sites.

Bug: 517673944
Change-Id: I6197826843c79962bb351a843b518cd47d1d308a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8013147
Commit-Queue: Yiren Wang <[email protected]>
Reviewed-by: David Bokan <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1654478}
---

diff --git a/chrome/browser/actor/tools/media_control_tool.cc b/chrome/browser/actor/tools/media_control_tool.cc
index 8129df27..27968a4 100644
--- a/chrome/browser/actor/tools/media_control_tool.cc
+++ b/chrome/browser/actor/tools/media_control_tool.cc
@@ -61,11 +61,11 @@
       absl::Overload(
           [media_session](const PlayMedia& arg) {
             // Resume media playback.
-            media_session->Resume(content::MediaSession::SuspendType::kUI);
+            media_session->Resume(content::MediaSession::SuspendType::kSystem);
           },
           [media_session](const PauseMedia& arg) {
             // Suspend media playback.
-            media_session->Suspend(content::MediaSession::SuspendType::kUI);
+            media_session->Suspend(content::MediaSession::SuspendType::kSystem);
           },
           [media_session](const SeekMedia& arg) {
             // Seek to a specific time in the media.
diff --git a/chrome/browser/actor/tools/media_control_tool_browsertest.cc b/chrome/browser/actor/tools/media_control_tool_browsertest.cc
index 426383f8..5a58a90 100644
--- a/chrome/browser/actor/tools/media_control_tool_browsertest.cc
+++ b/chrome/browser/actor/tools/media_control_tool_browsertest.cc
@@ -61,7 +61,7 @@
       MakeMediaControlRequest(*active_tab(), PauseMedia());
   actor_task().Act(ToRequestList(pause_request), pause_result.GetCallback());
   ExpectOkResult(pause_result);
-  EXPECT_EQ("pause", content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('pause')"));
 
   // Play the media.
   ActResultFuture play_result;
@@ -69,8 +69,7 @@
       MakeMediaControlRequest(*active_tab(), PlayMedia());
   actor_task().Act(ToRequestList(play_request), play_result.GetCallback());
   ExpectOkResult(play_result);
-  EXPECT_EQ("pause,play",
-            content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('play')"));
 }
 
 IN_PROC_BROWSER_TEST_F(ActorMediaControlToolBrowserTest, SeekMedia) {
@@ -78,8 +77,13 @@
   ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
   ASSERT_TRUE(WaitForLoadStop(web_contents()));
 
-  // Start playback.
+  // Start playback to initialize media session.
   ASSERT_TRUE(content::ExecJs(web_contents(), "play()"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('play')"));
+
+  // Pause it so the currentTime doesn't drift during seek.
+  ASSERT_TRUE(content::ExecJs(web_contents(), "video.pause()"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('pause')"));
 
   // Seek the media.
   ActResultFuture result;
@@ -93,7 +97,10 @@
       ToRequestList(request, request_negative_time, request_unreachable_time),
       result.GetCallback());
   ExpectOkResult(result);
-  EXPECT_EQ("seek 1", content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForSeek(1.0)"));
+  EXPECT_EQ(
+      1.0,
+      content::EvalJs(web_contents(), "video.currentTime").ExtractDouble());
 }
 
 }  // namespace
diff --git a/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc b/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
index 7d448ae..86ef34f9 100644
--- a/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
@@ -50,12 +50,11 @@
       InitializeWithOpenGlicWindow(),
       StartActorTaskInNewTab(url, kNewActorTabId),
       ExecuteJs(kNewActorTabId, "play"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"),
       MediaControlAction(actor::PauseMedia()),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "pause"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('pause')"),
       MediaControlAction(actor::PlayMedia()),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "pause,play"));
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"));
 }
 
 IN_PROC_BROWSER_TEST_F(GlicActorMediaControlToolUiTest, SeekMedia) {
@@ -65,9 +64,11 @@
       InitializeWithOpenGlicWindow(),
       StartActorTaskInNewTab(url, kNewActorTabId),
       ExecuteJs(kNewActorTabId, "play"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"),
+      ExecuteJs(kNewActorTabId, "() => { video.pause(); }"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('pause')"),
       MediaControlAction(actor::SeekMedia{.seek_time_milliseconds = 1000}),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "seek 1"));
+      WaitForJsResult(kNewActorTabId, "() => waitForSeek(1.0)"));
 }
 
 }  //  namespace
diff --git a/chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc b/chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc
index aa2a688..34d34d01 100644
--- a/chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc
+++ b/chrome/browser/ui/webui/ai_overlay_dialog/tools/tools.cc
@@ -350,7 +350,7 @@
   content::MediaSession* media_session =
       content::MediaSession::GetIfExists(contents);
   if (media_session) {
-    media_session->Resume(content::MediaSession::SuspendType::kUI);
+    media_session->Resume(content::MediaSession::SuspendType::kSystem);
     std::move(callback).Run(std::monostate());
   } else {
     std::move(callback).Run(base::unexpected("No active media session"));
@@ -369,7 +369,7 @@
   content::MediaSession* media_session =
       content::MediaSession::GetIfExists(contents);
   if (media_session) {
-    media_session->Suspend(content::MediaSession::SuspendType::kUI);
+    media_session->Suspend(content::MediaSession::SuspendType::kSystem);
     std::move(callback).Run(std::monostate());
   } else {
     std::move(callback).Run(base::unexpected("No active media session"));
diff --git a/chrome/test/data/actor/media.html b/chrome/test/data/actor/media.html
index 118ab9d..ff120f87 100644
--- a/chrome/test/data/actor/media.html
+++ b/chrome/test/data/actor/media.html
@@ -9,26 +9,36 @@
   </video>
   <script>
     const video = document.getElementById('video');
-    let event_log = [];
-    setupActionHandlers();
 
     // Called by the test to start media playback.
     function play() {
       video.play();
     }
 
-    function setupActionHandlers() {
-      navigator.mediaSession.setActionHandler('play', _ => {
-        video.play();
-        event_log.push('play');
+    // Helper function used by tests to wait for playback state transitions.
+    // Returns `true` immediately if the video is already in the expected state.
+    // Otherwise, returns a Promise that resolves when the corresponding event is fired.
+    function waitForEvent(eventName) {
+      if (eventName === 'pause' && video.paused) {
+        return true;
+      }
+      if (eventName === 'play' && !video.paused) {
+        return true;
+      }
+      return new Promise(resolve => {
+        video.addEventListener(eventName, () => resolve(true), {once: true});
       });
-      navigator.mediaSession.setActionHandler('pause',  _ => {
-        video.pause();
-        event_log.push('pause');
-      });
-      navigator.mediaSession.setActionHandler('seekto', (details) => {
-        video.currentTime = details.seekTime;
-        event_log.push('seek ' + details.seekTime);
+    }
+
+    // Helper function used by tests to wait for seek transitions.
+    // Returns `true` immediately if the video has already seeked to `targetTime`.
+    // Otherwise, returns a Promise that resolves when the next native 'seeked' event fires.
+    function waitForSeek(targetTime) {
+      if (Math.abs(video.currentTime - targetTime) < 0.1) {
+        return true;
+      }
+      return new Promise(resolve => {
+        video.addEventListener('seeked', () => resolve(true), {once: true});
       });
     }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/actor/tools/media_control_tool_browsertest.cc b/chrome/browser/actor/tools/media_control_tool_browsertest.cc
index 426383f8..5a58a90 100644
--- a/chrome/browser/actor/tools/media_control_tool_browsertest.cc
+++ b/chrome/browser/actor/tools/media_control_tool_browsertest.cc
@@ -61,7 +61,7 @@
       MakeMediaControlRequest(*active_tab(), PauseMedia());
   actor_task().Act(ToRequestList(pause_request), pause_result.GetCallback());
   ExpectOkResult(pause_result);
-  EXPECT_EQ("pause", content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('pause')"));
 
   // Play the media.
   ActResultFuture play_result;
@@ -69,8 +69,7 @@
       MakeMediaControlRequest(*active_tab(), PlayMedia());
   actor_task().Act(ToRequestList(play_request), play_result.GetCallback());
   ExpectOkResult(play_result);
-  EXPECT_EQ("pause,play",
-            content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('play')"));
 }
 
 IN_PROC_BROWSER_TEST_F(ActorMediaControlToolBrowserTest, SeekMedia) {
@@ -78,8 +77,13 @@
   ASSERT_TRUE(content::NavigateToURL(web_contents(), url));
   ASSERT_TRUE(WaitForLoadStop(web_contents()));
 
-  // Start playback.
+  // Start playback to initialize media session.
   ASSERT_TRUE(content::ExecJs(web_contents(), "play()"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('play')"));
+
+  // Pause it so the currentTime doesn't drift during seek.
+  ASSERT_TRUE(content::ExecJs(web_contents(), "video.pause()"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForEvent('pause')"));
 
   // Seek the media.
   ActResultFuture result;
@@ -93,7 +97,10 @@
       ToRequestList(request, request_negative_time, request_unreachable_time),
       result.GetCallback());
   ExpectOkResult(result);
-  EXPECT_EQ("seek 1", content::EvalJs(web_contents(), "event_log.join(',')"));
+  EXPECT_EQ(true, content::EvalJs(web_contents(), "waitForSeek(1.0)"));
+  EXPECT_EQ(
+      1.0,
+      content::EvalJs(web_contents(), "video.currentTime").ExtractDouble());
 }
 
 }  // namespace
diff --git a/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc b/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
index 7d448ae..86ef34f9 100644
--- a/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
+++ b/chrome/browser/glic/host/glic_actor_media_control_tool_interactive_uitest.cc
@@ -50,12 +50,11 @@
       InitializeWithOpenGlicWindow(),
       StartActorTaskInNewTab(url, kNewActorTabId),
       ExecuteJs(kNewActorTabId, "play"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"),
       MediaControlAction(actor::PauseMedia()),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "pause"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('pause')"),
       MediaControlAction(actor::PlayMedia()),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "pause,play"));
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"));
 }
 
 IN_PROC_BROWSER_TEST_F(GlicActorMediaControlToolUiTest, SeekMedia) {
@@ -65,9 +64,11 @@
       InitializeWithOpenGlicWindow(),
       StartActorTaskInNewTab(url, kNewActorTabId),
       ExecuteJs(kNewActorTabId, "play"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('play')"),
+      ExecuteJs(kNewActorTabId, "() => { video.pause(); }"),
+      WaitForJsResult(kNewActorTabId, "() => waitForEvent('pause')"),
       MediaControlAction(actor::SeekMedia{.seek_time_milliseconds = 1000}),
-      WaitForJsResult(kNewActorTabId, "() => { return event_log.join(','); }",
-                      "seek 1"));
+      WaitForJsResult(kNewActorTabId, "() => waitForSeek(1.0)"));
 }
 
 }  //  namespace
diff --git a/chrome/test/data/actor/media.html b/chrome/test/data/actor/media.html
index 118ab9d..ff120f87 100644
--- a/chrome/test/data/actor/media.html
+++ b/chrome/test/data/actor/media.html
@@ -9,26 +9,36 @@
   </video>
   <script>
     const video = document.getElementById('video');
-    let event_log = [];
-    setupActionHandlers();
 
     // Called by the test to start media playback.
     function play() {
       video.play();
     }
 
-    function setupActionHandlers() {
-      navigator.mediaSession.setActionHandler('play', _ => {
-        video.play();
-        event_log.push('play');
+    // Helper function used by tests to wait for playback state transitions.
+    // Returns `true` immediately if the video is already in the expected state.
+    // Otherwise, returns a Promise that resolves when the corresponding event is fired.
+    function waitForEvent(eventName) {
+      if (eventName === 'pause' && video.paused) {
+        return true;
+      }
+      if (eventName === 'play' && !video.paused) {
+        return true;
+      }
+      return new Promise(resolve => {
+        video.addEventListener(eventName, () => resolve(true), {once: true});
       });
-      navigator.mediaSession.setActionHandler('pause',  _ => {
-        video.pause();
-        event_log.push('pause');
-      });
-      navigator.mediaSession.setActionHandler('seekto', (details) => {
-        video.currentTime = details.seekTime;
-        event_log.push('seek ' + details.seekTime);
+    }
+
+    // Helper function used by tests to wait for seek transitions.
+    // Returns `true` immediately if the video has already seeked to `targetTime`.
+    // Otherwise, returns a Promise that resolves when the next native 'seeked' event fires.
+    function waitForSeek(targetTime) {
+      if (Math.abs(video.currentTime - targetTime) < 0.1) {
+        return true;
+      }
+      return new Promise(resolve => {
+        video.addEventListener('seeked', () => resolve(true), {once: true});
       });
     }
   </script>
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.