Low chrome Spoofing 🔧 Commit mapped

Overview

Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUI misrepresentation in UI
DescriptionUI misrepresentation in UI
ComponentUI
Sub-componentMediaSession
Bug ClassSpoofing
Tracker553252261
Fix commit5d2240ac74ed (chromium/src) +229/-26
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
if
components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
modified

Files Changed

  • services/media_session/public/cpp/android/junit/BUILD.gn
  • chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java
  • components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
  • services/media_session/public/cpp/media_metadata_android.cc
  • services/media_session/public/cpp/android/junit/src/org/chromium/services/media_session/MediaMetadataUnitTest.java
  • services/media_session/public/cpp/android/java/src/org/chromium/services/media_session/MediaMetadata.java
  • components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationInfoTest.java
From 5d2240ac74ed1882652472bbb9975186740738f5 Mon Sep 17 00:00:00 2001
From: Jenna Himawan <jhimawan@google.com>
Date: Tue, 01 Sep 2026 14:56:26 -0700
Subject: [PATCH] [MediaSession] Plumb source title across JNI for Android media notifications

Previously, Android media notifications derived the origin label from
webContents.getVisibleUrl().getOrigin().getSpec() in
MediaSessionHelper. For about:blank, blob:, and opaque origins,
GURL.getOrigin() returns an empty origin, omitting origin attribution
in the media notification.

This change aligns Android with desktop by plumbing source_title
across JNI via MediaMetadata. MediaSessionImpl in C++ already
properly computes source_title from the primary main frame's
committed origin (including handling for about:blank, blob URLs, and
precursor origins). MediaSessionHelper now derives the notification
origin from metadata.getSourceTitle() instead of parsing the visible URL.

TAG=agy
CONV=01e82ac3-df9b-4deb-a74f-1da5e82b55cb

Bug: 553252261
Change-Id: I95149205d7a6621848cc18f7a31bfaff5fb419fb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8316747
Reviewed-by: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Phil Yan <philyan@chromium.org>
Commit-Queue: Jenna Himawan <jhimawan@google.com>
Cr-Commit-Position: refs/heads/main@{#1690166}
---
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java
index 766fc66..d6ada619 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/ui/MediaNotificationTitleUpdatedTest.java
@@ -317,6 +317,66 @@
         assertEquals("title2", getDisplayedTitle());
     }
 
+    @Test
+    public void testOriginUpdatedFromSourceTitle() {
+        mTabHolder.simulateNavigation("about:blank", false);
+        mTabHolder.simulateMediaSessionStateChanged(true, false);
+        mTabHolder.simulateMediaSessionMetadataChanged(
+                new MediaMetadata("title2", "artist", "album", "example.com"));
+        mTabHolder.simulateMediaSessionActionsChanged(DEFAULT_ACTIONS);
+        advanceTimeByMillis(THROTTLE_MILLIS);
+
+        assertEquals("example.com", getController().mMediaNotificationInfo.origin);
+    }
+
+    @Test
+    public void testOriginUpdatedFromSourceTitle_NullMetadata() {
+        mTabHolder.simulateNavigation("about:blank", false);
+        mTabHolder.simulateMediaSessionStateChanged(true, false);
+        mTabHolder.simulateMediaSessionMetadataChanged(null);
+        mTabHolder.simulateMediaSessionActionsChanged(DEFAULT_ACTIONS);
+        advanceTimeByMillis(THROTTLE_MILLIS);
+
+        assertEquals("", getController().mMediaNotificationInfo.origin);
+    }
+
+    @Test
+    public void testFallbackTitleFromSourceTitle_WhenPageTitleEmpty() {
+        mTabHolder.simulateNavigation("about:blank", false);
+        mTabHolder.simulateTitleUpdated("");
+        mTabHolder.simulateMediaSessionStateChanged(true, false);
+        mTabHolder.simulateMediaSessionMetadataChanged(
+                new MediaMetadata("", "artist", "album", "example.com"));
+        mTabHolder.simulateMediaSessionActionsChanged(DEFAULT_ACTIONS);
+        advanceTimeByMillis(THROTTLE_MILLIS);
+
+        assertEquals("example.com", getController().mMediaNotificationInfo.origin);
+        assertEquals("example.com", getDisplayedTitle());
+    }
+
+    @Test
+    public void testFallbackTitleUpdatesWhenSourceTitleChanges_WhenPageTitleEmpty() {
+        mTabHolder.simulateNavigation("about:blank", false);
+        mTabHolder.simulateTitleUpdated("");
+        mTabHolder.simulateMediaSessionStateChanged(true, false);
+        mTabHolder.simulateMediaSessionMetadataChanged(
+                new MediaMetadata("", "artist", "album", "first.example.com"));
+        mTabHolder.simulateMediaSessionActionsChanged(DEFAULT_ACTIONS);
+        advanceTimeByMillis(THROTTLE_MILLIS);
+
+        assertEquals("first.example.com", getController().mMediaNotificationInfo.origin);
+        assertEquals("first.example.com", getDisplayedTitle());
+
+        // Metadata updates with new sourceTitle on untitled session.
+        mTabHolder.simulateMediaSessionMetadataChanged(
+                new MediaMetadata("", "artist", "album", "second.example.com"));
+        mTabHolder.simulateMediaSessionActionsChanged(DEFAULT_ACTIONS);
+        advanceTimeByMillis(THROTTLE_MILLIS);
+
+        assertEquals("second.example.com", getController().mMediaNotificationInfo.origin);
+        assertEquals("second.example.com", getDisplayedTitle());
+    }
+
     private CharSequence getDisplayedTitle() {
         Notification notification = getController().mNotificationBuilder.build();
         ShadowNotification shadowNotification = Shadows.shadowOf(notification);
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationInfoTest.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationInfoTest.java
index c1b9a1e..d0ed5abd 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationInfoTest.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaNotificationInfoTest.java
@@ -133,4 +133,22 @@
         assertEquals(original.supportsStop(), copy.supportsStop());
         assertEquals(original.mediaSessionActions, copy.mediaSessionActions);
     }
+
+    @Test
+    public void testBuilderWithSourceTitle() {
+        MediaNotificationInfo.Builder builder = new MediaNotificationInfo.Builder();
+        builder.setOrigin("https://example.com");
+        builder.setMetadata(new MediaMetadata("title", "artist", "album", "example.com"));
+        builder.setListener(mListener);
+        builder.setInstanceId(0);
+        builder.setId(0);
+
+        MediaNotificationInfo info = builder.build();
+        assertNotNull(info);
+        assertEquals("https://example.com", info.origin);
+        assertEquals("title", info.metadata.getTitle());
+        assertEquals("artist", info.metadata.getArtist());
+        assertEquals("album", info.metadata.getAlbum());
+        assertEquals("example.com", info.metadata.getSourceTitle());
+    }
 }
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
index 6b6a5e0..623046a 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
@@ -32,7 +32,6 @@
 import org.chromium.build.annotations.RequiresNonNull;
 import org.chromium.components.browser_ui.media.MediaSessionUma.MediaSessionActionSource;
 import org.chromium.components.favicon.LargeIconBridge;
-import org.chromium.components.url_formatter.UrlFormatter;
 import org.chromium.content_public.browser.MediaSession;
 import org.chromium.content_public.browser.MediaSessionObserver;
 import org.chromium.content_public.browser.NavigationHandle;
@@ -70,7 +69,9 @@
     private @Nullable Bitmap mPageMediaImage;
     @VisibleForTesting public @Nullable Bitmap mFavicon;
     private @Nullable Bitmap mCurrentMediaImage;
-    private @Nullable String mOrigin;
+    // The origin of the media session. Defaults to an empty string to guarantee
+    // a non-null origin when constructing MediaNotificationInfo before the origin is resolved.
+    private String mOrigin = "";
     private int mPreviousVolumeControlStream = AudioManager.USE_DEFAULT_STREAM_TYPE;
     @VisibleForTesting public MediaNotificationInfo.@Nullable Builder mNotificationInfoBuilder;
     // The fallback title if |mPageMetadata| is null or its title is empty.
@@ -323,15 +324,14 @@
                     return;
                 }
                 assumeNonNull(mWebContents);
-                assumeNonNull(mOrigin);
 
-                Intent contentIntent = mDelegate.createBringTabToFrontIntent();
-
-                if (mFallbackTitle == null) mFallbackTitle = sanitizeMediaTitle(mOrigin);
+                // Initialize fallback title from origin if no page title was set yet.
+                maybeSetFallbackTitleFromOrigin(null);
 
                 mCurrentMetadata = getMetadata();
                 mCurrentMediaImage = getCachedNotificationImage();
                 rebaseMediaPosition(isPaused);
+                Intent contentIntent = mDelegate.createBringTabToFrontIntent();
                 mNotificationInfoBuilder =
                         mDelegate
                                 .createMediaNotificationInfoBuilder()
@@ -370,6 +370,11 @@
             @Override
             public void mediaSessionMetadataChanged(@Nullable MediaMetadata metadata) {
                 mPageMetadata = metadata;
+                String previousOrigin = mOrigin;
+                mOrigin = (metadata != null) ? metadata.getSourceTitle() : "";
+                // If mFallbackTitle was previously tracking the old origin, update it to
+                // the new origin without overriding any page title set via titleWasSet().
+                maybeSetFallbackTitleFromOrigin(previousOrigin);
                 updateNotificationMetadata();
             }
 
@@ -442,9 +447,7 @@
                             return;
                         }
 
-                        mOrigin =
-                                UrlFormatter.formatUrlForDisplayOmitSchemeOmitTrivialSubdomains(
-                                        webContents.getVisibleUrl().getOrigin().getSpec());
+                        mOrigin = "";
                         mFavicon = null;
                         mPageMediaImage = null;
                         mPageMetadata = null;
@@ -694,6 +697,18 @@
     }
 
     /**
+     * Updates {@link #mFallbackTitle} to track {@link #mOrigin} if uninitialized or if previously
+     * derived from {@code previousOrigin}. Preserves real page titles set via {@link #titleWasSet}.
+     */
+    private void maybeSetFallbackTitleFromOrigin(@Nullable String previousOrigin) {
+        if (TextUtils.isEmpty(mFallbackTitle)
+                || (!TextUtils.isEmpty(previousOrigin)
+                        && TextUtils.equals(mFallbackTitle, sanitizeMediaTitle(previousOrigin)))) {
+            mFallbackTitle = sanitizeMediaTitle(mOrigin);
+        }
+    }
+
+    /**
      * Updates the metadata in media notification. This method should be called whenever
      * |mPageMetadata| or |mFallbackTitle| is changed.
      */
@@ -705,6 +720,7 @@
 
         mCurrentMetadata = newMetadata;
         mNotificationInfoBuilder.setMetadata(mCurrentMetadata);
+        mNotificationInfoBuilder.setOrigin(mOrigin);
         showNotification();
     }
 
@@ -716,7 +732,11 @@
     private MediaMetadata getMetadata() {
         String artist = "";
         String album = "";
+        String sourceTitle = mOrigin;
         if (mPageMetadata != null) {
+            if (!TextUtils.isEmpty(mPageMetadata.getSourceTitle())) {
+                sourceTitle = mPageMetadata.getSourceTitle();
+            }
             if (!TextUtils.isEmpty(mPageMetadata.getTitle())) return mPageMetadata;
 
             artist = mPageMetadata.getArtist();
@@ -726,11 +746,12 @@
         if (mCurrentMetadata != null
                 && TextUtils.equals(mFallbackTitle, mCurrentMetadata.getTitle())
                 && TextUtils.equals(artist, mCurrentMetadata.getArtist())
-                && TextUtils.equals(album, mCurrentMetadata.getAlbum())) {
+                && TextUtils.equals(album, mCurrentMetadata.getAlbum())
+                && TextUtils.equals(sourceTitle, mCurrentMetadata.getSourceTitle())) {
             return mCurrentMetadata;
         }
 
-        return new MediaMetadata(mFallbackTitle, artist, album);
+        return new MediaMetadata(mFallbackTitle, artist, album, sourceTitle);
     }
 
     private void updateNotificationActions() {
diff --git a/services/media_session/public/cpp/android/java/src/org/chromium/services/media_session/MediaMetadata.java b/services/media_session/public/cpp/android/java/src/org/chromium/services/media_session/MediaMetadata.java
index c5e6c8a..5f7a650d 100644
--- a/services/media_session/public/cpp/android/java/src/org/chromium/services/media_session/MediaMetadata.java
+++ b/services/media_session/public/cpp/android/java/src/org/chromium/services/media_session/MediaMetadata.java
@@ -4,8 +4,6 @@
 
 package org.chromium.services.media_session;
 
-import static org.chromium.build.NullUtil.assumeNonNull;
-
 import android.text.TextUtils;
 
 import org.jni_zero.CalledByNative;
@@ -27,6 +25,8 @@
 
     private String mAlbum;
 
+    private final String mSourceTitle;
+
     /** Returns the title associated with the media session. */
     public @Nullable String getTitle() {
         return mTitle;
@@ -42,8 +42,14 @@
         return mAlbum;
     }
 
+    /** Returns the source title (origin/app name) associated with the media session. */
+    public String getSourceTitle() {
+        return mSourceTitle;
+    }
+
     /**
      * Sets the title associated with the media session.
+     *
      * @param title The title to use for the media session.
      */
     public void setTitle(@Nullable String title) {
@@ -51,8 +57,9 @@
     }
 
     /**
-     * Sets the arstist name associated with the media session.
-     * @param arstist The artist name to use for the media session.
+     * Sets the artist name associated with the media session.
+     *
+     * @param artist The artist name to use for the media session.
      */
     public void setArtist(String artist) {
         mArtist = artist;
@@ -67,19 +74,26 @@
     }
 
     /**
-     * Creates a new MediaMetadata from the C++ code. This is exactly like the
-     * constructor below apart that it can be called by native code.
+     * Creates a new MediaMetadata from C++ code. This is equivalent to the constructor below,
+     * except that it can be called by native code.
      */
     @CalledByNative
-    private static MediaMetadata create(@Nullable String title, String artist, String album) {
-        return new MediaMetadata(title, artist, album);
+    private static MediaMetadata create(
+            @Nullable String title, String artist, String album, String sourceTitle) {
+        return new MediaMetadata(title, artist, album, sourceTitle);
     }
 
     /** Creates a new MediaMetadata. */
     public MediaMetadata(@Nullable String title, String artist, String album) {
+        this(title, artist, album, "");
+    }
+
+    /** Creates a new MediaMetadata with source title. */
+    public MediaMetadata(@Nullable String title, String artist, String album, String sourceTitle) {
         mTitle = title;
         mArtist = artist;
         mAlbum = album;
+        mSourceTitle = sourceTitle;
     }
 
     /** Comparing MediaMetadata is expensive and should be used sparingly */
@@ -91,19 +105,20 @@
         MediaMetadata other = (MediaMetadata) obj;
         return TextUtils.equals(mTitle, other.mTitle)
                 && TextUtils.equals(mArtist, other.mArtist)
-                && TextUtils.equals(mAlbum, other.mAlbum);
+                && TextUtils.equals(mAlbum, other.mAlbum)
+                && TextUtils.equals(mSourceTitle, other.mSourceTitle);
     }
 
     /**
      * @return The hash code of this {@link MediaMetadata}. The method uses the same algorithm in
-     * {@link java.util.List} for combinine hash values.
+     *     {@link java.util.List} for combining hash values.
      */
     @Override
     public int hashCode() {
-        assumeNonNull(mTitle);
-        int result = mTitle.hashCode();
+        int result = mTitle != null ? mTitle.hashCode() : 0;
         result = 31 * result + mArtist.hashCode();
         result = 31 * result + mAlbum.hashCode();
+        result = 31 * result + mSourceTitle.hashCode();
         return result;
     }
 }
diff --git a/services/media_session/public/cpp/android/junit/src/org/chromium/services/media_session/MediaMetadataUnitTest.java b/services/media_session/public/cpp/android/junit/src/org/chromium/services/media_session/MediaMetadataUnitTest.java
new file mode 100644
index 0000000..0f31bb9
--- /dev/null
+++ b/services/media_session/public/cpp/android/junit/src/org/chromium/services/media_session/MediaMetadataUnitTest.java
@@ -0,0 +1,84 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+package org.chromium.services.media_session;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.chromium.base.test.BaseRobolectricTestRunner;
+
+/** Unit tests for {@link MediaMetadata}. */
+@RunWith(BaseRobolectricTestRunner.class)
+public class MediaMetadataUnitTest {
+    @Test
+    public void testDefaultConstructor() {
+        MediaMetadata metadata = new MediaMetadata("title", "artist", "album");
+        assertEquals("title", metadata.getTitle());
+        assertEquals("artist", metadata.getArtist());
+        assertEquals("album", metadata.getAlbum());
+        assertEquals("", metadata.getSourceTitle());
+    }
+
+    @Test
+    public void testConstructorWithSourceTitle() {
+        MediaMetadata metadata = new MediaMetadata("title", "artist", "album", "sourceTitle");
+        assertEquals("title", metadata.getTitle());
+        assertEquals("artist", metadata.getArtist());
+        assertEquals("album", metadata.getAlbum());
+        assertEquals("sourceTitle", metadata.getSourceTitle());
+    }
+
+    @Test
+    public void testSetters() {
+        MediaMetadata metadata = new MediaMetadata("title", "artist", "album", "sourceTitle");
+        metadata.setTitle("newTitle");
+        metadata.setArtist("newArtist");
+        metadata.setAlbum("newAlbum");
... (file diff truncated)
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.