Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in UI
DescriptionUI misrepresentation in UI
ComponentUI
Bug ClassLogic Error
Tracker519246298
Fix commit5cfef9034728 (chromium/src) +151/-21
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc
modified
TEST_F
chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
modified

Files Changed

  • ash/system/media/media_notification_provider.h
  • ash/system/media/mock_media_notification_provider.h
  • chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc
  • chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h
  • chrome/browser/ui/global_media_controls/media_notification_service.cc
  • chrome/browser/ui/global_media_controls/media_notification_service.h
  • chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
From 5cfef9034728f584a838f2a93c959dd61a3a8333 Mon Sep 17 00:00:00 2001
From: Yiren Wang <yrw@chromium.org>
Date: Tue, 14 Jul 2026 16:20:47 -0700
Subject: [PATCH] Fix lifecycle of presentation context origin in GMC

We should not reset the origin for the presentation item whenever GMC
dialog is closed, since it can become out of sync with the origin in
MediaNotificationService. Instead, we always keep its lifecycle in sync
with MediaNotificationService.

- Allows resetting the origin override on a media session item by changing MediaSessionNotificationItem::UpdatePresentationRequestOrigin to accept std::optional<url::Origin>.

- Ensures the origin override persists when the notification view is temporarily set to null (e.g., when the GMC dialog is closed and reopened). It is only cleared when the presentation context itself is reset.

- Cleans up the active presentation context and clears the associated origin override in MediaNotificationService when:
  - The context is consumed by starting a cast session.
  - The initiating frame is destroyed or becomes inactive.
  - A new presentation request is initiated (including requests that delegate to a dummy presentation request notification).

Bug: 519246298
Change-Id: I5c6320e467977c2da0bdbb1e4920cd8193d4285d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8060464
Commit-Queue: Yiren Wang <yrw@chromium.org>
Reviewed-by: Ahmed Moussa <ahmedmoussa@google.com>
Cr-Commit-Position: refs/heads/main@{#1662248}
---

diff --git a/ash/system/media/media_notification_provider.h b/ash/system/media/media_notification_provider.h
index fdd43a8..93bfa86 100644
--- a/ash/system/media/media_notification_provider.h
+++ b/ash/system/media/media_notification_provider.h
@@ -98,8 +98,9 @@
 
   // Use MediaNotificationProvider as a bridge to update the source origin of a
   // media notification item.
-  virtual void UpdateMediaItemSourceOrigin(const std::string& id,
-                                           const url::Origin& origin) = 0;
+  virtual void UpdateMediaItemSourceOrigin(
+      const std::string& id,
+      const std::optional<url::Origin>& origin) = 0;
 };
 
 }  // namespace ash
diff --git a/ash/system/media/mock_media_notification_provider.h b/ash/system/media/mock_media_notification_provider.h
index 2310e70b..6eca5ed3 100644
--- a/ash/system/media/mock_media_notification_provider.h
+++ b/ash/system/media/mock_media_notification_provider.h
@@ -36,7 +36,7 @@
               ());
   MOCK_METHOD(void,
               UpdateMediaItemSourceOrigin,
-              (const std::string&, const url::Origin&),
+              (const std::string&, const std::optional<url::Origin>&),
               (override));
 
   void AddObserver(MediaNotificationProviderObserver* observer) override {}
diff --git a/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc b/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc
index ec7429d7..d8653fa 100644
--- a/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc
+++ b/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.cc
@@ -193,7 +193,7 @@
 
 void MediaNotificationProviderImpl::UpdateMediaItemSourceOrigin(
     const std::string& id,
-    const url::Origin& origin) {
+    const std::optional<url::Origin>& origin) {
   if (media_session_item_producer_) {
     media_session_item_producer_->UpdateMediaItemSourceOrigin(id, origin);
   }
diff --git a/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h b/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h
index 47a86bdc..939cca8 100644
--- a/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h
+++ b/chrome/browser/ui/ash/global_media_controls/media_notification_provider_impl.h
@@ -75,8 +75,9 @@
   std::unique_ptr<global_media_controls::MediaItemUIFooter> BuildFooterView(
       const std::string& id,
       base::WeakPtr<media_message_center::MediaNotificationItem> item) override;
-  void UpdateMediaItemSourceOrigin(const std::string& id,
-                                   const url::Origin& origin) override;
+  void UpdateMediaItemSourceOrigin(
+      const std::string& id,
+      const std::optional<url::Origin>& origin) override;
 
   // global_media_controls::MediaDialogDelegate:
   global_media_controls::MediaItemUI* ShowMediaItem(
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.cc b/chrome/browser/ui/global_media_controls/media_notification_service.cc
index fe2d536..bc3660e 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.cc
@@ -447,6 +447,8 @@
       return;
     }
 
+    ResetPresentationContext();
+
     // If there exists a media session notification associated with
     // |web_contents|, hold onto the context for later use.
     context_ = std::move(context);
@@ -456,6 +458,7 @@
     // the top frame.
     std::string item_id =
         GetActiveControllableSessionForWebContents(web_contents);
+    context_item_id_ = item_id;
     media_session_item_producer_->UpdateMediaItemSourceOrigin(
         item_id, context_->presentation_request().frame_origin);
 #if BUILDFLAG(IS_CHROMEOS)
@@ -519,8 +522,11 @@
   }
 
   if (HasPresentationContextForSession(id)) {
-    return media_router::MediaRouterUI::CreateWithStartPresentationContext(
-        web_contents, std::move(context_));
+    auto dialog_controller =
+        media_router::MediaRouterUI::CreateWithStartPresentationContext(
+            web_contents, std::move(context_));
+    ResetPresentationContext();
+    return dialog_controller;
   }
 
   auto remote_playback_metadata =
@@ -602,7 +608,7 @@
   auto* initiator_rfh = content::RenderFrameHost::FromID(
       context_->presentation_request().render_frame_host_id);
   if (!initiator_rfh || !initiator_rfh->IsActive()) {
-    context_.reset();
+    ResetPresentationContext();
     return false;
   }
   auto* web_contents =
@@ -611,6 +617,20 @@
                              initiator_rfh) == web_contents;
 }
 
+void MediaNotificationService::ResetPresentationContext() {
+  context_.reset();
+  if (!context_item_id_.empty()) {
+    media_session_item_producer_->UpdateMediaItemSourceOrigin(context_item_id_,
+                                                              std::nullopt);
+#if BUILDFLAG(IS_CHROMEOS)
+    if (auto* provider = ash::MediaNotificationProvider::Get(); provider) {
+      provider->UpdateMediaItemSourceOrigin(context_item_id_, std::nullopt);
+    }
+#endif
+    context_item_id_.clear();
+  }
+}
+
 void MediaNotificationService::set_device_provider_for_testing(
     std::unique_ptr<MediaNotificationDeviceProvider> device_provider) {
   device_provider_ = std::move(device_provider);
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service.h b/chrome/browser/ui/global_media_controls/media_notification_service.h
index 102ceba6..b0f2191 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service.h
+++ b/chrome/browser/ui/global_media_controls/media_notification_service.h
@@ -174,6 +174,9 @@
   // True if there is a presentation context for the given session ID.
   bool HasPresentationContextForSession(const std::string& session_id);
 
+  // Resets the presentation context and related states.
+  void ResetPresentationContext();
+
   // True if there are cast notifications associated with |web_contents|.
   bool HasCastNotificationsForWebContents(
       content::WebContents* web_contents) const;
@@ -212,6 +215,9 @@
   // Used to initialize a MediaRouterUI.
   std::unique_ptr<media_router::StartPresentationContext> context_;
 
+  // The ID of the media session item currently associated with `context_`.
+  std::string context_item_id_;
+
   // Generates a list of available audio devices.
   std::unique_ptr<MediaNotificationDeviceProvider> device_provider_;
 
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
index cf899d52..cddfd5b3 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
@@ -212,6 +212,10 @@
                                         url::Origin::Create(GURL()));
   }
 
+  bool HasPresentationContextForSession(const std::string& session_id) {
+    return service()->HasPresentationContextForSession(session_id);
+  }
+
   std::unique_ptr<StartPresentationContext> CreateStartPresentationContext(
       content::PresentationRequest presentation_request,
       StartPresentationContext::PresentationConnectionCallback success_cb =
@@ -765,3 +769,93 @@
   service()->OnSinksDiscovered(id.ToString());
   EXPECT_TRUE(service()->should_show_cast_local_media_iph());
 }
+
+TEST_F(MediaNotificationServiceCastTest, PresentationRequestOriginLifecycle) {
+  auto id = SimulatePlayingControllableMediaForWebContents(web_contents());
+  auto* item = GetNotificationSessionItem(id);
+  ASSERT_TRUE(item);
+  EXPECT_FALSE(item->optional_presentation_request_origin().has_value());
+
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
index cf899d52..cddfd5b3 100644
--- a/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
+++ b/chrome/browser/ui/global_media_controls/media_notification_service_unittest.cc
@@ -212,6 +212,10 @@
                                         url::Origin::Create(GURL()));
   }
 
+  bool HasPresentationContextForSession(const std::string& session_id) {
+    return service()->HasPresentationContextForSession(session_id);
+  }
+
   std::unique_ptr<StartPresentationContext> CreateStartPresentationContext(
       content::PresentationRequest presentation_request,
       StartPresentationContext::PresentationConnectionCallback success_cb =
@@ -765,3 +769,93 @@
   service()->OnSinksDiscovered(id.ToString());
   EXPECT_TRUE(service()->should_show_cast_local_media_iph());
 }
+
+TEST_F(MediaNotificationServiceCastTest, PresentationRequestOriginLifecycle) {
+  auto id = SimulatePlayingControllableMediaForWebContents(web_contents());
+  auto* item = GetNotificationSessionItem(id);
+  ASSERT_TRUE(item);
+  EXPECT_FALSE(item->optional_presentation_request_origin().has_value());
+
+  content::PresentationRequest presentation_request(
+      main_rfh()->GetGlobalId(),
+      {GURL("https://example.com"), GURL("https://example.com")},
+      url::Origin::Create(GURL("https://example.com")));
+  auto context = CreateStartPresentationContext(presentation_request);
+  auto origin = context->presentation_request().frame_origin;
+  service()->OnStartPresentationContextCreated(std::move(context));
+
+  // The origin override is set and the context is alive.
+  EXPECT_EQ(item->optional_presentation_request_origin(), origin);
+  EXPECT_TRUE(HasPresentationContextForSession(id.ToString()));
+
+  // Creating the cast dialog controller consumes the context, so the origin
+  // override is reset.
+  auto dialog_controller = CreateCastDialogControllerForSession(id.ToString());
+  EXPECT_FALSE(item->optional_presentation_request_origin().has_value());
+}
+
+TEST_F(MediaNotificationServiceCastTest,
+       PresentationRequestOriginNotResetByNewDummyRequest) {
+  auto id = SimulatePlayingControllableMediaForWebContents(web_contents());
+  auto* item = GetNotificationSessionItem(id);
+  ASSERT_TRUE(item);
+  EXPECT_FALSE(item->optional_presentation_request_origin().has_value());
+
+  content::PresentationRequest presentation_request1(
+      main_rfh()->GetGlobalId(),
+      {GURL("https://example.com"), GURL("https://example.com")},
+      url::Origin::Create(GURL("https://example.com")));
+  auto context1 = CreateStartPresentationContext(presentation_request1);
+  auto origin1 = context1->presentation_request().frame_origin;
+  service()->OnStartPresentationContextCreated(std::move(context1));
+
+  // The origin override is set and the context is alive.
+  EXPECT_EQ(item->optional_presentation_request_origin(), origin1);
+  EXPECT_TRUE(HasPresentationContextForSession(id.ToString()));
+
+  // Create a second WebContents and a second presentation request for it.
+  // There is no controllable media on this second WebContents.
+  std::unique_ptr<content::WebContents> web_contents2(CreateTestWebContents());
+  content::PresentationRequest presentation_request2(
+      web_contents2->GetPrimaryMainFrame()->GetGlobalId(),
+      {GURL("https://example2.com"), GURL("https://example2.com")},
+      url::Origin::Create(GURL("https://example2.com")));
+  auto context2 = CreateStartPresentationContext(presentation_request2);
+
+  // When we send the second request, since it doesn't have an active
+  // controllable session on web_contents2, it should go to
+  // presentation_request_notification_producer_ and we expect the previous
+  // presentation context for web_contents1 to remain intact.
+  service()->OnStartPresentationContextCreated(std::move(context2));
+
+  // The origin override on the first item should remain unchanged.
+  EXPECT_EQ(item->optional_presentation_request_origin(), origin1);
+  EXPECT_TRUE(HasPresentationContextForSession(id.ToString()));
+}
+
+TEST_F(MediaNotificationServiceCastTest,
+       HasPresentationContextForSessionWithInactiveFrame) {
+  auto id = SimulatePlayingControllableMediaForWebContents(web_contents());
+  auto* item = GetNotificationSessionItem(id);
+  ASSERT_TRUE(item);
+  EXPECT_FALSE(item->optional_presentation_request_origin().has_value());
+
+  content::PresentationRequest presentation_request(
+      main_rfh()->GetGlobalId(),
+      {GURL("https://example.com"), GURL("https://example.com")},
+      url::Origin::Create(GURL("https://example.com")));
+  auto context = CreateStartPresentationContext(presentation_request);
+  auto origin = context->presentation_request().frame_origin;
+  service()->OnStartPresentationContextCreated(std::move(context));
+
+  // The origin override is set and the context is alive.
+  EXPECT_EQ(item->optional_presentation_request_origin(), origin);
+  EXPECT_TRUE(HasPresentationContextForSession(id.ToString()));
+
+  // Simulate the WebContents being deleted.
+  DeleteContents();
+
+  // The session no longer has a presentation context because the initiating
+  // frame is deleted.
+  EXPECT_FALSE(HasPresentationContextForSession(id.ToString()));
+}
diff --git a/components/global_media_controls/public/media_session_notification_item_unittest.cc b/components/global_media_controls/public/media_session_notification_item_unittest.cc
index d6557de..7388f6a 100644
--- a/components/global_media_controls/public/media_session_notification_item_unittest.cc
+++ b/components/global_media_controls/public/media_session_notification_item_unittest.cc
@@ -119,10 +119,14 @@
   item().MediaSessionMetadataChanged(metadata);
   item().SetView(nullptr);
 
-  // Make sure that presentation request origin was reset after the view is set
-  // to null in SetView().
-  EXPECT_CALL(view(), UpdateWithMediaMetadata(metadata)).Times(1);
+  // Make sure that presentation request origin persists after the view is set
+  // to null and then set back to non-null.
+  EXPECT_CALL(view(), UpdateWithMediaMetadata(updated_metadata)).Times(1);
   item().SetView(&view());
+
+  // Make sure that presentation request origin can be reset.
+  EXPECT_CALL(view(), UpdateWithMediaMetadata(metadata)).Times(1);
+  item().UpdatePresentationRequestOrigin(std::nullopt);
 }
 
 TEST_F(MediaSessionNotificationItemTest, Freezing_DoNotUpdateImage) {
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.