Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in SplitView
DescriptionIncorrect security UI in SplitView
ComponentSplitView
Bug ClassLogic Error
Tracker488762971
Fix commited05567436ea (chromium/src) +48/-0
CISA KEVNot listed
CreditedKhalil Zhani
Disclosed2026-06-30

Files Changed

  • chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
  • chrome/browser/ui/views/frame/contents_web_view.cc
  • chrome/browser/ui/views/frame/contents_web_view.h
  • chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
From ed05567436ea5cb1afaf32ee255fae6a58360709 Mon Sep 17 00:00:00 2001
From: behery <behery@google.com>
Date: Mon, 01 Jun 2026 07:48:06 -0700
Subject: [PATCH] [SxS/Linux] Fix UI origin confusion during split view permission prompts

On Linux and Linux-chromeos, when Chrome is in split view and a
permission prompt is visible in one tab, the native window focus is kept
by the prompt. If the user clicks on the other side's web contents, the
OS may deliver the interaction to the background tab without
transferring window focus. Since the browser UI (Omnibox) relies on
native focus to determine the active tab, it remains associated with the
tab showing the permission prompt, leading to UI origin confusion.

This CL fixes the issue by overriding `DidGetUserInteraction` in
`ContentsWebView`. When physical input (mouse down or touch start) is
received by the unfocused WebContents, we explicitly call
`RequestFocus()`. This forces the browser to synchronize the active tab
state with the user's interaction, ensuring the Omnibox and privileged
UI correctly reflect the newly interacted-with origin.

An interactive_ui_test case was also added to verify the UI state
transition.

Additionally, this CL fixes a crash in `TwoClientSendTabToSelfSyncTest`
that was exposed by the focus changes. The altered focus behavior caused
an activation event to be triggered during browser teardown.

Bug: 488762971
Test: Manual testing done to verify that the WebContents view is correctly activated on click when switching between split view tabs
Change-Id: I2b9830c7a5c1926c99e7a63f297666de3843424d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7860845
Reviewed-by: Jood Hajeer <jood@google.com>
Reviewed-by: Caroline Rising <corising@chromium.org>
Commit-Queue: Hamzah Behery <behery@google.com>
Reviewed-by: Foromo Daniel Soromou <koretadaniel@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1639383}
---

diff --git a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
index c37926bf..1b5c33a 100644
--- a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/send_tab_to_self/page_context.h"
@@ -322,6 +323,10 @@
       ->SendEntry(kUrl, "example", target_guid, context,
                   send_tab_to_self::NavigationHistory(), base::DoNothing());
 
+  // Ensure receiver browser is active so notification is handled immediately,
+  // as opposed to getting queued and executing during teardown.
+  GetBrowser(1)->window()->Activate();
+
   // Client 1: Wait for entry and fill.
   send_tab_to_self::SendTabToSelfSyncService* service1 =
       SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1));
diff --git a/chrome/browser/ui/views/frame/contents_web_view.cc b/chrome/browser/ui/views/frame/contents_web_view.cc
index b2cbadc..a831bfd 100644
--- a/chrome/browser/ui/views/frame/contents_web_view.cc
+++ b/chrome/browser/ui/views/frame/contents_web_view.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/ui/views/frame/contents_web_view.h"
 
 #include "base/debug/dump_without_crashing.h"
+#include "build/build_config.h"
 #include "chrome/browser/ui/color/chrome_color_id.h"
 #include "chrome/browser/ui/views/frame/web_contents_close_handler.h"
 #include "chrome/browser/ui/views/status_bubble_views.h"
@@ -13,6 +14,7 @@
 #include "components/web_modal/web_contents_modal_dialog_manager.h"
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents.h"
+#include "third_party/blink/public/common/input/web_input_event.h"
 #include "third_party/skia/include/core/SkColor.h"
 #include "ui/base/metadata/metadata_impl_macros.h"
 #include "ui/color/color_provider.h"
@@ -169,6 +171,22 @@
   }
 }
 
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+void ContentsWebView::DidGetUserInteraction(const blink::WebInputEvent& event) {
+  // If the user interacts with the web contents, ensure it is activated.
+  // This handles cases where the native window does not receive a focus
+  // event, such as when a permission prompt is open in another split view.
+  if (event.GetType() == blink::WebInputEvent::Type::kMouseDown ||
+      event.GetType() == blink::WebInputEvent::Type::kTouchStart) {
+    // RequestFocus() ensures the container view receives focus,
+    // which is sufficient to update the browser UI.
+    if (!HasFocus()) {
+      RequestFocus();
+    }
+  }
+}
+#endif
+
 void ContentsWebView::UpdateBackgroundColor() {
   const SkColor color = GetColorProvider()->GetColor(
       is_letterboxing() ? kColorWebContentsBackgroundLetterboxing
diff --git a/chrome/browser/ui/views/frame/contents_web_view.h b/chrome/browser/ui/views/frame/contents_web_view.h
index 902b0fc..dba4f32 100644
--- a/chrome/browser/ui/views/frame/contents_web_view.h
+++ b/chrome/browser/ui/views/frame/contents_web_view.h
@@ -8,6 +8,7 @@
 #include <memory>
 
 #include "base/memory/raw_ptr.h"
+#include "build/build_config.h"
 #include "chrome/browser/ui/views/frame/web_contents_close_handler_delegate.h"
 #include "chrome/common/buildflags.h"
 #include "ui/base/interaction/element_identifier.h"
@@ -62,6 +63,15 @@
   void OnLetterboxingChanged() override;
   void SetWebContents(content::WebContents* web_contents) override;
 
+  // content::WebContentsObserver overrides:
+  // Overridden to track physical interactions (mouse/touch) on the WebContents.
+  // This allows the browser to force focus synchronization in split view even
+  // when native OS focus gets stuck on a different window (like a permission
+  // prompt).
+#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS)
+  void DidGetUserInteraction(const blink::WebInputEvent& event) override;
+#endif
+
   // ui::View overrides:
   std::unique_ptr<ui::Layer> RecreateLayer() override;
 
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
index 55ea2c7c..77189d5f 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/ui/views/frame/multi_contents_view_drop_target_controller.h"
 #include "chrome/browser/ui/views/frame/multi_contents_view_mini_toolbar.h"
 #include "chrome/browser/ui/views/frame/scrim_view.h"
+#include "chrome/browser/ui/views/page_info/page_info_main_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel.h"
 #include "chrome/browser/ui/views/test/split_view_interactive_test_mixin.h"
 #include "chrome/browser/ui/views/test/tab_strip_interactive_test_mixin.h"
@@ -390,6 +391,20 @@
 }
 
 // Check that MultiContentsView changes its active view when inactive view is
+// focused using mouse click while a PageInfo bubble is open in the active view.
+// This prevents UI origin confusion issues (e.g. b/488762971).
+IN_PROC_BROWSER_TEST_P(MultiContentsViewUiTest,
+                       ActivatesInactiveViewUsingMouseClickWithPageInfoOpen) {
+  RunTestSequence(CreateTabsAndEnterSplitView(), WaitForActiveTabChange(0),
+                  // Open PageInfo bubble on the active tab (0).
+                  PressButton(kLocationIconElementId),
+                  // Click the inactive tab (1).
+                  FocusInactiveTabInSplit(),
+                  // Active tab should change to 1.
+                  WaitForActiveTabChange(1), CheckActiveContentsHasFocus());
+}
+
+// Check that MultiContentsView changes its active view when inactive view is
 // focused using keyboard.
 IN_PROC_BROWSER_TEST_P(MultiContentsViewUiTest,
                        ActivatesInactiveViewUsingKeyboard) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
index c37926bf..1b5c33a 100644
--- a/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
+++ b/chrome/browser/sync/test/integration/two_client_send_tab_to_self_sync_test.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/sync/test/integration/sync_test.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/browser/ui/browser_tabstrip.h"
+#include "chrome/browser/ui/browser_window.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
 #include "components/history/core/browser/history_service.h"
 #include "components/send_tab_to_self/page_context.h"
@@ -322,6 +323,10 @@
       ->SendEntry(kUrl, "example", target_guid, context,
                   send_tab_to_self::NavigationHistory(), base::DoNothing());
 
+  // Ensure receiver browser is active so notification is handled immediately,
+  // as opposed to getting queued and executing during teardown.
+  GetBrowser(1)->window()->Activate();
+
   // Client 1: Wait for entry and fill.
   send_tab_to_self::SendTabToSelfSyncService* service1 =
       SendTabToSelfSyncServiceFactory::GetForProfile(GetProfile(1));
diff --git a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
index 55ea2c7c..77189d5f 100644
--- a/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
+++ b/chrome/browser/ui/views/frame/multi_contents_view_interactive_uitest.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/ui/views/frame/multi_contents_view_drop_target_controller.h"
 #include "chrome/browser/ui/views/frame/multi_contents_view_mini_toolbar.h"
 #include "chrome/browser/ui/views/frame/scrim_view.h"
+#include "chrome/browser/ui/views/page_info/page_info_main_view.h"
 #include "chrome/browser/ui/views/side_panel/side_panel.h"
 #include "chrome/browser/ui/views/test/split_view_interactive_test_mixin.h"
 #include "chrome/browser/ui/views/test/tab_strip_interactive_test_mixin.h"
@@ -390,6 +391,20 @@
 }
 
 // Check that MultiContentsView changes its active view when inactive view is
+// focused using mouse click while a PageInfo bubble is open in the active view.
+// This prevents UI origin confusion issues (e.g. b/488762971).
+IN_PROC_BROWSER_TEST_P(MultiContentsViewUiTest,
+                       ActivatesInactiveViewUsingMouseClickWithPageInfoOpen) {
+  RunTestSequence(CreateTabsAndEnterSplitView(), WaitForActiveTabChange(0),
+                  // Open PageInfo bubble on the active tab (0).
+                  PressButton(kLocationIconElementId),
+                  // Click the inactive tab (1).
+                  FocusInactiveTabInSplit(),
+                  // Active tab should change to 1.
+                  WaitForActiveTabChange(1), CheckActiveContentsHasFocus());
+}
+
+// Check that MultiContentsView changes its active view when inactive view is
 // focused using keyboard.
 IN_PROC_BROWSER_TEST_P(MultiContentsViewUiTest,
                        ActivatesInactiveViewUsingKeyboard) {
Loading diff…

Original Bug Report

reported by ch...@gmail.com

Omnibox shows wrong origin when clicking another page during permission prompt (Linux, split view)

Steps to reproduce the problem

When Chrome is in split view with two pages open side-by-side on Linux, the browser can lose track of the active origin while a permission prompt is visible. If the right-side page (permission.site) triggers a location permission prompt, and while that permission UI is visible the user clicks inside the left-side page (localhost:8000) which triggers the Payment Request API, Chrome does not correctly update the active origin. Even after interacting with localhost:8000, the omnibox continues to display permission.site as the active origin. As a result, user interaction occurs on localhost:8000 while the browser UI (address bar and permission context) remains associated with permission.site. This causes a mismatch between user interaction, omnibox origin, and privileged UI state, leading to UI origin confusion involving security-sensitive browser dialogs.

This issue reproduces on Linux, but does not reproduce on Windows.

Steps:

  1. Open http://localhost:8000/poc.html in Chrome.
  2. Right-click the “Go to permission.site” link.
  3. Select Location (this triggers a location permission prompt from permission.site).
  4. While the location permission prompt is visible, click anywhere inside localhost:8000/poc.html.

Problem Description

When a location permission prompt from permission.site is visible in split view on Linux, clicking inside localhost:8000 does not update the omnibox, causing the browser to show the wrong active origin and resulting in UI origin confusion.

Summary

Omnibox shows wrong origin when clicking another page during permission prompt (Linux, split view)

Additional Data

Category: Security
Chrome Channel: Not sure
Regression: N/A \

View on issue tracker
Links in the report