Low chrome Logic Error 🔧 Commit mapped

Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInappropriate implementation in Split View
DescriptionInappropriate implementation in Split View
ComponentSplit View
Bug ClassLogic Error
Tracker446181124
Fix commite49ad13fa0e6 (chromium/src) +68/-19
CISA KEVNot listed
CreditedKhalil Zhani
Disclosed2025-12-02

Changed Functions

FunctionChangeNotes
if
chrome/browser/resources/tab_search/split_view/app.ts
modified
if
chrome/browser/resources/tab_search/tab_data.ts
modified
if
chrome/browser/resources/tab_search/tab_search_page.ts
modified

Files Changed

  • chrome/browser/resources/tab_search/split_view/app.ts
  • chrome/browser/resources/tab_search/tab_data.ts
  • chrome/browser/resources/tab_search/tab_search_page.ts
  • chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
  • chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
From e49ad13fa0e61b4b2acac9e524103325f10b0e21 Mon Sep 17 00:00:00 2001
From: Alison Gale <[email protected]>
Date: Wed, 08 Oct 2025 16:20:46 -0700
Subject: [PATCH] [SxS] Handle uncommitted urls from split view and tab search

For these URLs we display about:blank in the omnibox so this will be
consistent. While I'm here I also shared the display URL logic so the
split NTP will show when its a local file or something.

Split NTP: https://screenshot.googleplex.com/BdMGxsXWM6BN3xp
Tab search:
https://screenshot.googleplex.com/7GCb8RuGuY7N49u

Bug: 446181124
Change-Id: I42df102c3b3d4e51819bc89a3dc9549860178c16
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7017483
Commit-Queue: Alison Gale <[email protected]>
Reviewed-by: Yuheng Huang <[email protected]>
Reviewed-by: Chris Thompson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1527218}
---

diff --git a/chrome/browser/resources/tab_search/split_view/app.ts b/chrome/browser/resources/tab_search/split_view/app.ts
index e86a8c4..1f2af3ac 100644
--- a/chrome/browser/resources/tab_search/split_view/app.ts
+++ b/chrome/browser/resources/tab_search/split_view/app.ts
@@ -11,7 +11,7 @@
 import {CrLitElement} from 'chrome://resources/lit/v3_0/lit.rollup.js';
 
 import type {SelectableLazyListElement} from '../selectable_lazy_list.js';
-import {normalizeURL, TabData, TabItemType} from '../tab_data.js';
+import {getDisplayHostnameForUrl, normalizeURL, TabData, TabItemType} from '../tab_data.js';
 import type {ProfileData, Tab, TabsRemovedInfo, TabUpdateInfo} from '../tab_search.mojom-webui.js';
 import type {TabSearchApiProxy} from '../tab_search_api_proxy.js';
 import {TabSearchApiProxyImpl} from '../tab_search_api_proxy.js';
@@ -210,8 +210,9 @@
 
   private getTabData_(tab: Tab, inActiveWindow: boolean, type: TabItemType):
       TabData {
-    const tabData =
-        new TabData(tab, type, new URL(normalizeURL(tab.url.url)).hostname);
+    const displayUrl =
+        getDisplayHostnameForUrl(new URL(normalizeURL(tab.url.url)));
+    const tabData = new TabData(tab, type, displayUrl);
 
     if (type === TabItemType.OPEN_TAB) {
       tabData.inActiveWindow = inActiveWindow;
diff --git a/chrome/browser/resources/tab_search/tab_data.ts b/chrome/browser/resources/tab_search/tab_data.ts
index 56f4a5d..5eb3d2e 100644
--- a/chrome/browser/resources/tab_search/tab_data.ts
+++ b/chrome/browser/resources/tab_search/tab_data.ts
@@ -118,6 +118,18 @@
   return url || 'about:blank';
 }
 
+export function getDisplayHostnameForUrl(url: URL): string {
+  if (url.protocol === 'blob:') {
+    return loadTimeData.getString('blobUrlSource');
+  } else if (url.protocol === 'file:') {
+    return loadTimeData.getString('fileUrlSource');
+  } else if (url.protocol === 'about:' && url.pathname === 'blank') {
+    return 'about:blank';
+  } else {
+    return url.hostname;
+  }
+}
+
 export function getTitle(data: TabData|TabGroupData): string|undefined {
   if (data.type === TabItemType.RECENTLY_CLOSED_TAB_GROUP) {
     return undefined;
diff --git a/chrome/browser/resources/tab_search/tab_search_page.ts b/chrome/browser/resources/tab_search/tab_search_page.ts
index 3dbd50776..c45a68c 100644
--- a/chrome/browser/resources/tab_search/tab_search_page.ts
+++ b/chrome/browser/resources/tab_search/tab_search_page.ts
@@ -26,7 +26,7 @@
 import {search} from './search.js';
 import type {SelectableLazyListElement} from './selectable_lazy_list.js';
 import {NO_SELECTION, selectorNavigationKeys} from './selectable_lazy_list.js';
-import {ariaLabel, getHostname, getTabGroupTitle, getTitle, type ItemData, normalizeURL, TabData, TabGroupData, TabItemType, tokenEquals, tokenToString} from './tab_data.js';
+import {ariaLabel, getDisplayHostnameForUrl, getHostname, getTabGroupTitle, getTitle, type ItemData, normalizeURL, TabData, TabGroupData, TabItemType, tokenEquals, tokenToString} from './tab_data.js';
 import type {ProfileData, RecentlyClosedTab, Tab, TabGroup, TabsRemovedInfo, TabUpdateInfo} from './tab_search.mojom-webui.js';
 import {TabSearchSection} from './tab_search.mojom-webui.js';
 import type {TabSearchApiProxy} from './tab_search_api_proxy.js';
@@ -661,22 +661,12 @@
     return ariaLabel(tabData);
   }
 
-  private getDisplayHostnameForUrl_(url: URL): string {
-    if (url.protocol === 'blob:') {
-      return loadTimeData.getString('blobUrlSource');
-    } else if (url.protocol === 'file:') {
-      return loadTimeData.getString('fileUrlSource');
-    } else {
-      return url.hostname;
-    }
-  }
-
   private tabData_(
       tab: Tab|RecentlyClosedTab, inActiveWindow: boolean, type: TabItemType,
       tabGroupsMap: Map<string, TabGroup>): TabData {
     const tabData = new TabData(
         tab, type,
-        this.getDisplayHostnameForUrl_(new URL(normalizeURL(tab.url.url))));
+        getDisplayHostnameForUrl(new URL(normalizeURL(tab.url.url))));
 
     if (tab.groupId) {
       tabData.tabGroup = tabGroupsMap.get(tokenToString(tab.groupId));
diff --git a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
index 36955759..e4580632 100644
--- a/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
+++ b/chrome/browser/ui/webui/tab_search/tab_search_page_handler.cc
@@ -1428,10 +1428,13 @@
   // A visible URL is used when the a new tab is still loading.
   // If it is cancelled during loading the visible URL becomes empty.
   // We will display an empty URL as about:blank in Javascript.
-  tab_data->url =
-      !last_committed_url.is_valid() || last_committed_url.is_empty()
-          ? tab_renderer_data.visible_url
-          : last_committed_url;
+  if (!last_committed_url.is_valid() || last_committed_url.is_empty()) {
+    tab_data->url = tab_renderer_data.should_display_url
+                        ? tab_renderer_data.visible_url
+                        : GURL(url::kAboutBlankURL);
+  } else {
+    tab_data->url = last_committed_url;
+  }
 
   if (tab_renderer_data.favicon.IsEmpty()) {
     tab_data->is_default_favicon = true;
diff --git a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
index 2de65aa8..2ccc569 100644
--- a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
+++ b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
@@ -114,6 +114,49 @@
     assertEquals(3, tabSearchItems.length);
   });
 
+  test('Formats urls properly', async () => {
+    await splitNewTabPageSetup();
+
+    const windowData = createWindowData();
+    windowData[0]!.tabs.push(
+        createTab({
+          index: 6,
+          lastActiveTimeTicks: {internalValue: BigInt(10)},
+          tabId: 8,
+          title: '',
+          url: {url: 'about:blank'},
+        }),
+        createTab({
+          index: 7,
+          lastActiveTimeTicks: {internalValue: BigInt(11)},
+          tabId: 9,
+          title: 'file.jpg',
+          url: {url: 'file://file.jpg'},
+        }),
+        createTab({
+          index: 8,
+          lastActiveTimeTicks: {internalValue: BigInt(12)},
+          tabId: 10,
+          title: 'Data',
+          url: {url: 'blob://data'},
+        }),
+    );
+    testApiProxy.getCallbackRouterRemote().tabsChanged(createProfileData({
+      windows: windowData,
+    }));
+    await eventToPromise('viewport-filled', splitNewTabPage.$.splitTabsList);
+
+    const tabSearchItems =
+        splitNewTabPage.shadowRoot.querySelectorAll('tab-search-item');
+    assertEquals(
+        loadTimeData.getString('blobUrlSource'),
+        tabSearchItems[1]!.data.hostname);
+    assertEquals(
+        loadTimeData.getString('fileUrlSource'),
+        tabSearchItems[2]!.data.hostname);
+    assertEquals('about:blank', tabSearchItems[3]!.data.hostname);
+  });
+
   test('Sorts list', async () => {
     await splitNewTabPageSetup();
     const tabSearchItems =
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
index 2de65aa8..2ccc569 100644
--- a/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
+++ b/chrome/test/data/webui/tab_search/split_new_tab_page_test.ts
@@ -114,6 +114,49 @@
     assertEquals(3, tabSearchItems.length);
   });
 
+  test('Formats urls properly', async () => {
+    await splitNewTabPageSetup();
+
+    const windowData = createWindowData();
+    windowData[0]!.tabs.push(
+        createTab({
+          index: 6,
+          lastActiveTimeTicks: {internalValue: BigInt(10)},
+          tabId: 8,
+          title: '',
+          url: {url: 'about:blank'},
+        }),
+        createTab({
+          index: 7,
+          lastActiveTimeTicks: {internalValue: BigInt(11)},
+          tabId: 9,
+          title: 'file.jpg',
+          url: {url: 'file://file.jpg'},
+        }),
+        createTab({
+          index: 8,
+          lastActiveTimeTicks: {internalValue: BigInt(12)},
+          tabId: 10,
+          title: 'Data',
+          url: {url: 'blob://data'},
+        }),
+    );
+    testApiProxy.getCallbackRouterRemote().tabsChanged(createProfileData({
+      windows: windowData,
+    }));
+    await eventToPromise('viewport-filled', splitNewTabPage.$.splitTabsList);
+
+    const tabSearchItems =
+        splitNewTabPage.shadowRoot.querySelectorAll('tab-search-item');
+    assertEquals(
+        loadTimeData.getString('blobUrlSource'),
+        tabSearchItems[1]!.data.hostname);
+    assertEquals(
+        loadTimeData.getString('fileUrlSource'),
+        tabSearchItems[2]!.data.hostname);
+    assertEquals('about:blank', tabSearchItems[3]!.data.hostname);
+  });
+
   test('Sorts list', async () => {
     await splitNewTabPageSetup();
     const tabSearchItems =
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.