Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Internals
DescriptionUse after free in Internals
ComponentInternals
Sub-componentMac
Bug ClassUAF
Tracker441089142
Fix commit54157e0d7a49 (chromium/src) +71/-11
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-20

Changed Functions

FunctionChangeNotes
if
chrome/browser/media/webrtc/media_stream_focus_delegate.cc
modified
if
chrome/browser/permissions/BUILD.gn
modified

Files Changed

  • chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
  • content/browser/renderer_host/render_widget_host_view_mac.mm
  • chrome/browser/media/webrtc/media_stream_focus_delegate.cc
  • chrome/browser/media/webrtc/media_stream_focus_delegate.h
  • chrome/test/base/interactive_test_utils_aura.cc
  • chrome/browser/permissions/BUILD.gn
From 54157e0d7a49f477f16d6bbbe703f691e9d056ac Mon Sep 17 00:00:00 2001
From: Keren Zhu <[email protected]>
Date: Tue, 22 Sep 2026 08:50:51 -0700
Subject: [PATCH] mac: return false from RWHVMac::HasFocus() when the window is not key

This CL changes HasFocus() to return false if the window is non-key.
This aligns the behavior with Aura & Blink (that being focused implies
being in the active window).

This fixes a security issue in autofill that the autofill popup in an
inactive window sometimes occludes the active window
(crbug.com/439873332). Previously, the autofill popup closes itself iff
RWHV::HasFocus() is false, but HasFocus() does not check checks window
key status. This means the popup won't be close if the window goes
inactive but NSView does not resign first responder.

A few tests are updated to show and activate the browser window before
running the rest of the test. They were depending on HasFocus()
returning true when the window is inactive. Some tests are excluded on
Wayland because Wayland has poor support for activating background
window (https://crbug.com/40863331) and the test times out.

The change is under a kill switch,
kRWHVMacHasFocusReturnsFalseForNonKeyWindow.

Bug: 441089142, 439873332
Include-Ci-Only-Tests: chromium.mac:mac26-arm64-rel-tests|browser_tests
Change-Id: I9683d3a328d58f6a5cf76d10f53a7e170898d052
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7085807
Reviewed-by: Avi Drissman <[email protected]>
Commit-Queue: Keren Zhu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1702667}
---
diff --git a/chrome/browser/media/webrtc/media_stream_focus_delegate.cc b/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
index 04d4a8df..c7cf97a 100644
--- a/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
+++ b/chrome/browser/media/webrtc/media_stream_focus_delegate.cc
@@ -85,8 +85,8 @@
     return;  // Window of opportunity to change focus now closed - we're done.
   }
 
-  if (!IsWidgetFocused()) {
-    // Capturing window not focused - likely the user has alt-tabbed away.
+  if (!IsBrowserActive()) {
+    // Capturing window not active - likely the user has alt-tabbed away.
     return;
   }
 
@@ -105,17 +105,20 @@
   focus_window_of_opportunity_open_ = false;
 }
 
-bool MediaStreamFocusDelegate::IsWidgetFocused() const {
+bool MediaStreamFocusDelegate::IsBrowserActive() const {
   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
   DCHECK(capturing_web_contents_);  // Tested by caller.
 
-  content::RenderWidgetHostView* const rwhv =
-      capturing_web_contents_->GetRenderWidgetHostView();
-  if (!rwhv) {
-    return false;
-  }
-
-  return rwhv->HasFocus();
+  // Use BrowserWindowInterface::IsActive() rather than
+  // RenderWidgetHostView::HasFocus(). When the screen picker dialog is accepted
+  // and closing (e.g. during the asynchronous modal close animation on macOS),
+  // the child modal dialog window may still be the OS key window when
+  // SetFocus() runs, causing RenderWidgetHostView::HasFocus() to return false
+  // even though the browser window is still logically active.
+  BrowserWindowInterface* const browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          capturing_web_contents_.get());
+  return browser && browser->IsActive();
 }
 
 void MediaStreamFocusDelegate::FocusTab(
diff --git a/chrome/browser/media/webrtc/media_stream_focus_delegate.h b/chrome/browser/media/webrtc/media_stream_focus_delegate.h
index b6b79d0..024d1c8 100644
--- a/chrome/browser/media/webrtc/media_stream_focus_delegate.h
+++ b/chrome/browser/media/webrtc/media_stream_focus_delegate.h
@@ -53,7 +53,7 @@
   void ResetExplicitDecisionForTesting() { explicit_decision_ = false; }
 
  private:
-  bool IsWidgetFocused() const;
+  bool IsBrowserActive() const;
   void FocusTab(const content::DesktopMediaID& media_id);
   void FocusWindow(const content::DesktopMediaID& media_id);
 
diff --git a/content/browser/renderer_host/render_widget_host_view_mac.mm b/content/browser/renderer_host/render_widget_host_view_mac.mm
index 31561fd..3086ae7 100644
--- a/content/browser/renderer_host/render_widget_host_view_mac.mm
+++ b/content/browser/renderer_host/render_widget_host_view_mac.mm
@@ -115,6 +115,11 @@
 // resize.
 BASE_FEATURE(kThrottleResizeIpc, base::FEATURE_ENABLED_BY_DEFAULT);
 
+// If enabled, RenderWidgetHostViewMac::HasFocus() returns false if it is in
+// a non-key NSWindow. This aligns with Aura behavior. crbug.com/441089142.
+BASE_FEATURE(kRWHVMacHasFocusReturnsFalseForNonKeyWindow,
+             base::FEATURE_ENABLED_BY_DEFAULT);
+
 // Extract any events in `visible_time_request` that should go to the
 // DelegatedFrameHost and sends them to `delegated_frame_host`. Modifies
 // `visible_time_request` in place.
@@ -683,6 +688,12 @@
 }
 
 bool RenderWidgetHostViewMac::HasFocus() {
+  if (base::FeatureList::IsEnabled(
+          kRWHVMacHasFocusReturnsFalseForNonKeyWindow)) {
+    // In headless mode the NSView is not in a NSWindow.
+    return is_first_responder_ && (IsHeadless() || is_window_key_);
+  }
+
   return is_first_responder_;
 }
 
diff --git a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
index 15b55ed7..53f5410 100644
--- a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
+++ b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
@@ -22,6 +22,7 @@
 #include "chrome/test/base/chrome_test_path_utils.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chrome/test/interaction/interactive_browser_test.h"
 #include "chrome/test/permissions/permission_request_manager_test_api.h"
@@ -60,6 +61,10 @@
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_OZONE)
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
 namespace {
 
 // Helper for CreateBlobURL() and CreateFilesystemURL().
@@ -173,6 +178,7 @@
       contents, content::JsReplace("window.open($1, '', '[]');", url));
   BrowserWindowInterface* popup = ui_test_utils::WaitForBrowserToOpen();
   EXPECT_NE(popup, browser);
+  EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup));
   content::WebContents* popup_contents =
       popup->GetTabStripModel()->GetActiveWebContents();
   EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetPrimaryMainFrame()));
@@ -307,6 +313,10 @@
       permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
 
   // Move the web contents to the foreground.
+  BrowserWindowInterface* opener_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          opener_or_embedder_contents);
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(opener_browser));
   opener_rfh->GetView()->Focus();
   ASSERT_TRUE(opener_rfh->GetView()->HasFocus());
   // Request permission on the opener or embedder contents.
@@ -323,6 +333,10 @@
   EXPECT_EQ(true, content::EvalJs(test_rfh, check_permission_script));
 
   // Request permission on the test RFH.
+  BrowserWindowInterface* test_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          content::WebContents::FromRenderFrameHost(test_rfh));
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(test_browser));
   test_rfh->GetView()->Focus();
   ASSERT_TRUE(test_rfh->GetView()->HasFocus());
   EXPECT_EQ("granted", content::EvalJs(test_rfh, request_permission_script));
@@ -350,6 +364,10 @@
       permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
 
   // Move the web contents to the foreground.
+  BrowserWindowInterface* popup_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          popup_contents);
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup_browser));
   popup_rfh->GetView()->Focus();
   ASSERT_TRUE(popup_rfh->GetView()->HasFocus());
   // Request permission on the popup RenderFrameHost.
@@ -482,6 +500,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenAboutBlank) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
@@ -500,6 +523,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenAboutBlankToUseQuiet) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   browser()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kEnableQuietNotificationPermissionUi, true);
   ASSERT_TRUE(embedded_test_server()->Start());
@@ -562,6 +590,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenBlob) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   content::RenderFrameHost* main_rfh =
@@ -605,6 +638,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenFileSystemBrowserNavigation) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   content::RenderFrameHost* main_rfh =
diff --git a/chrome/test/base/interactive_test_utils_aura.cc b/chrome/test/base/interactive_test_utils_aura.cc
index 624a7a2..fb5f7ee 100644
--- a/chrome/test/base/interactive_test_utils_aura.cc
+++ b/chrome/test/base/interactive_test_utils_aura.cc
@@ -7,6 +7,7 @@
 #include "build/build_config.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
 
 namespace ui_test_utils {
 
@@ -26,6 +27,9 @@
 
 bool ShowAndFocusNativeWindowAura(gfx::NativeWindow window) {
   window->Show();
+  if (views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window)) {
+    widget->Activate();
+  }
   window->Focus();
   return true;
 }
diff --git a/chrome/browser/permissions/BUILD.gn b/chrome/browser/permissions/BUILD.gn
index b49f7df..9152679 100644
--- a/chrome/browser/permissions/BUILD.gn
+++ b/chrome/browser/permissions/BUILD.gn
@@ -445,5 +445,9 @@
       deps += [ "//chrome/browser/ui:browser_window_header" ]
       sources += [ "system/system_permission_settings_interactive_uitest.cc" ]
     }
+
+    if (use_ozone) {
+      deps += [ "//ui/ozone" ]
+    }
   }
 }
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
index 15b55ed7..53f5410 100644
--- a/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
+++ b/chrome/browser/permissions/permissions_security_model_interactive_uitest.cc
@@ -22,6 +22,7 @@
 #include "chrome/test/base/chrome_test_path_utils.h"
 #include "chrome/test/base/chrome_test_utils.h"
 #include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/interactive_test_utils.h"
 #include "chrome/test/base/ui_test_utils.h"
 #include "chrome/test/interaction/interactive_browser_test.h"
 #include "chrome/test/permissions/permission_request_manager_test_api.h"
@@ -60,6 +61,10 @@
 #include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
 #include "url/gurl.h"
 
+#if BUILDFLAG(IS_OZONE)
+#include "ui/ozone/public/ozone_platform.h"
+#endif
+
 namespace {
 
 // Helper for CreateBlobURL() and CreateFilesystemURL().
@@ -173,6 +178,7 @@
       contents, content::JsReplace("window.open($1, '', '[]');", url));
   BrowserWindowInterface* popup = ui_test_utils::WaitForBrowserToOpen();
   EXPECT_NE(popup, browser);
+  EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup));
   content::WebContents* popup_contents =
       popup->GetTabStripModel()->GetActiveWebContents();
   EXPECT_TRUE(WaitForRenderFrameReady(popup_contents->GetPrimaryMainFrame()));
@@ -307,6 +313,10 @@
       permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
 
   // Move the web contents to the foreground.
+  BrowserWindowInterface* opener_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          opener_or_embedder_contents);
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(opener_browser));
   opener_rfh->GetView()->Focus();
   ASSERT_TRUE(opener_rfh->GetView()->HasFocus());
   // Request permission on the opener or embedder contents.
@@ -323,6 +333,10 @@
   EXPECT_EQ(true, content::EvalJs(test_rfh, check_permission_script));
 
   // Request permission on the test RFH.
+  BrowserWindowInterface* test_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          content::WebContents::FromRenderFrameHost(test_rfh));
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(test_browser));
   test_rfh->GetView()->Focus();
   ASSERT_TRUE(test_rfh->GetView()->HasFocus());
   EXPECT_EQ("granted", content::EvalJs(test_rfh, request_permission_script));
@@ -350,6 +364,10 @@
       permissions::PermissionRequestManager::AutoResponseType::ACCEPT_ALL);
 
   // Move the web contents to the foreground.
+  BrowserWindowInterface* popup_browser =
+      GlobalBrowserCollection::GetInstance()->FindBrowserWithTab(
+          popup_contents);
+  ASSERT_TRUE(ui_test_utils::BringBrowserWindowToFront(popup_browser));
   popup_rfh->GetView()->Focus();
   ASSERT_TRUE(popup_rfh->GetView()->HasFocus());
   // Request permission on the popup RenderFrameHost.
@@ -482,6 +500,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenAboutBlank) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
@@ -500,6 +523,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenAboutBlankToUseQuiet) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   browser()->GetProfile()->GetPrefs()->SetBoolean(
       prefs::kEnableQuietNotificationPermissionUi, true);
   ASSERT_TRUE(embedded_test_server()->Start());
@@ -562,6 +590,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenBlob) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   content::RenderFrameHost* main_rfh =
@@ -605,6 +638,11 @@
 
 IN_PROC_BROWSER_TEST_F(PermissionsSecurityModelInteractiveUITest,
                        WindowOpenFileSystemBrowserNavigation) {
+#if BUILDFLAG(IS_OZONE)
+  if (ui::OzonePlatform::RunningOnWaylandForTest()) {
+    GTEST_SKIP() << "Wayland does not support programmatic window activation.";
+  }
+#endif
   ASSERT_TRUE(embedded_test_server()->Start());
   const GURL url(embedded_test_server()->GetURL("/empty.html"));
   content::RenderFrameHost* main_rfh =
diff --git a/chrome/test/base/interactive_test_utils_aura.cc b/chrome/test/base/interactive_test_utils_aura.cc
index 624a7a2..fb5f7ee 100644
--- a/chrome/test/base/interactive_test_utils_aura.cc
+++ b/chrome/test/base/interactive_test_utils_aura.cc
@@ -7,6 +7,7 @@
 #include "build/build_config.h"
 #include "chrome/test/base/interactive_test_utils.h"
 #include "ui/aura/window.h"
+#include "ui/views/widget/widget.h"
 
 namespace ui_test_utils {
 
@@ -26,6 +27,9 @@
 
 bool ShowAndFocusNativeWindowAura(gfx::NativeWindow window) {
   window->Show();
+  if (views::Widget* widget = views::Widget::GetWidgetForNativeWindow(window)) {
+    widget->Activate();
+  }
   window->Focus();
   return true;
 }
Loading diff…

Original Bug Report

reported by [email protected]

RWHV::HasFocus() is true after OnWebContentsLostFocus() on Mac

RenderWidgetHostView::HasFocus() in WebContentsObserver::OnWebContentsLostFocus() returns false on Linux but true on Mac.

Is that’s intended? What’s the semantics of HasFocus() then and is there an alternative way to see that a WebContents lost focus?

Setting priority to P1 because this is blocking a issue 439873332.


Example:

void Foo::OnWebContentsLostFocus(content::RenderWidgetHost* render_widget_host) {
  if (auto* rwhv = web_contents()->GetRenderWidgetHostView()) {
    LOG(ERROR) << "RWHV::HasFocus() = " << rwhv->HasFocus();
  }
}

Focusing another, non-Chrome window triggers OnWebContentsLostFocus() on Mac and Linux, but HasFocus() remains true on Mac and is false on Linux.

View on issue tracker