High chrome Logic Error 🔧 Commit mapped

Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in WebUI
DescriptionMissing authorization in WebUI
ComponentWebUI
Bug ClassLogic Error
Tracker552413517
Fix commita95d8fc58b46 (chromium/src) +236/-16
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
modified

Files Changed

  • chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
  • chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
  • chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
From a95d8fc58b4624dc999a9ac9d7079ed2ec048541 Mon Sep 17 00:00:00 2001
From: Lauren Winston <lwinston@google.com>
Date: Fri, 04 Sep 2026 15:12:59 -0700
Subject: [PATCH] [M152] Add missing user activation checks to the UntrustedPageHandler.

Original change's description:
> Add missing user activation checks to the UntrustedPageHandler.
>
> This prevents Mojo commands from being executed (e.g.
> OnLinkClicked) via a script or something other than
> user action.
>
> Fixed:552413517
>
> Change-Id: I9fac4ea6c74b4454d5fe6b2c92ddfbb0477287a9
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8282642
> Commit-Queue: Lauren Winston <lwinston@google.com>
> Reviewed-by: Kristi Saney <kristislee@google.com>
> Cr-Commit-Position: refs/heads/main@{#1686136}

(cherry picked from commit 06262fce88f70dc902d29f6be3dbb8fb140a9432)

Bug: 553316283,552413517
Change-Id: I9fac4ea6c74b4454d5fe6b2c92ddfbb0477287a9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8348823
Reviewed-by: Kristi Saney <kristislee@google.com>
Commit-Queue: Lauren Winston <lwinston@google.com>
Cr-Commit-Position: refs/branch-heads/7977@{#2439}
Cr-Branched-From: b7fe14017379ddffae396d944fb8b59a5896c261-refs/heads/main@{#1669021}
---

diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
index 59a9712..61322047 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.cc
@@ -812,6 +812,11 @@
 }
 
 void ReadAnythingUntrustedPageHandler::OnCopy() {
+  if (!HasTransientUserActivation()) {
+    VLOG(1) << "OnCopy failed with no transient user activation";
+    return;
+  }
+
   if (main_observer_ && main_observer_->web_contents()) {
     main_observer_->web_contents()->Copy();
   }
@@ -989,6 +994,11 @@
 void ReadAnythingUntrustedPageHandler::OnLinkClicked(
     const ui::AXTreeID& target_tree_id,
     ui::AXNodeID target_node_id) {
+  if (!HasTransientUserActivation()) {
+    VLOG(1) << "OnLinkClicked failed with no transient user activation";
+    return;
+  }
+
   bool is_observing_tree = IsObservingTree(target_tree_id);
   base::UmaHistogramBoolean(kRendererLinkRequestHistogram, is_observing_tree);
   if (!is_observing_tree) {
@@ -1167,6 +1177,11 @@
     int anchor_offset,
     ui::AXNodeID focus_node_id,
     int focus_offset) {
+  if (!HasTransientUserActivation()) {
+    VLOG(1) << "OnSelectionChange failed with no transient user activation";
+    return;
+  }
+
   bool is_observing_tree = IsObservingTree(target_tree_id);
   base::UmaHistogramBoolean(kRendererSelectionRequestHistogram,
                             is_observing_tree);
@@ -1197,6 +1212,11 @@
 }
 
 void ReadAnythingUntrustedPageHandler::OnCollapseSelection() {
+  if (!HasTransientUserActivation()) {
+    VLOG(1) << "OnCollapseSelection failed with no transient user activation";
+    return;
+  }
+
   if (main_observer_ && main_observer_->web_contents()) {
     main_observer_->web_contents()->CollapseSelection();
   }
@@ -1885,3 +1905,8 @@
   }
 }
 #endif
+
+bool ReadAnythingUntrustedPageHandler::HasTransientUserActivation() const {
+  return web_ui_ && web_ui_->GetRenderFrameHost() &&
+         web_ui_->GetRenderFrameHost()->HasTransientUserActivation();
+}
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
index e0b1ba585..2a15b62 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler.h
@@ -366,6 +366,8 @@
 
   content::WebContents* GetWebContents() const;
 
+  bool HasTransientUserActivation() const;
+
   void OnScreenAIServiceInitialized(bool successful);
 
   // Called to notify this instance that the dependency parser loader
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
index 0016d2c..3094dfe 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
@@ -41,6 +41,7 @@
 #include "components/prefs/pref_value_map.h"
 #include "components/tabs/public/tab_interface.h"
 #include "components/translate/core/browser/translate_manager.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -60,6 +61,9 @@
 #include "ui/accessibility/mojom/ax_tree_update.mojom.h"
 #include "ui/accessibility/platform/browser_accessibility.h"
 #include "ui/accessibility/platform/browser_accessibility_manager.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/clipboard_sequence_number_token.h"
 #include "ui/gfx/geometry/size.h"
 #if BUILDFLAG(IS_CHROMEOS)
 #include "base/test/bind.h"
@@ -184,23 +188,25 @@
 #if BUILDFLAG(IS_CHROMEOS)
   explicit TestReadAnythingUntrustedPageHandler(
       mojo::PendingRemote<read_anything::mojom::UntrustedPage> page,
+      mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>
+          receiver,
       content::WebUI* test_web_ui,
       std::unique_ptr<ChromeOsExtensionWrapper> extension_wrapper)
-      : ReadAnythingUntrustedPageHandler(
-            std::move(page),
-            mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>(),
-            test_web_ui,
-            /*use_screen_ai_service=*/false,
-            std::move(extension_wrapper)) {}
+      : ReadAnythingUntrustedPageHandler(std::move(page),
+                                         std::move(receiver),
+                                         test_web_ui,
+                                         /*use_screen_ai_service=*/false,
+                                         std::move(extension_wrapper)) {}
 #else
   explicit TestReadAnythingUntrustedPageHandler(
       mojo::PendingRemote<read_anything::mojom::UntrustedPage> page,
+      mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>
+          receiver,
       content::WebUI* test_web_ui)
-      : ReadAnythingUntrustedPageHandler(
-            std::move(page),
-            mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>(),
-            test_web_ui,
-            /*use_screen_ai_service=*/false) {}
+      : ReadAnythingUntrustedPageHandler(std::move(page),
+                                         std::move(receiver),
+                                         test_web_ui,
+                                         /*use_screen_ai_service=*/false) {}
 #endif
 };
 
@@ -315,6 +321,7 @@
 #if BUILDFLAG(IS_CHROMEOS)
     extension_wrapper_ptr_ = nullptr;
 #endif
+    handler_remote_.reset();
     handler_.reset();
     test_web_ui_.reset();
     web_contents_.reset();
@@ -322,17 +329,19 @@
   }
 
   std::unique_ptr<TestReadAnythingUntrustedPageHandler> CreateHandler() {
+    handler_remote_.reset();
 #if BUILDFLAG(IS_CHROMEOS)
     std::unique_ptr<ChromeOsExtensionWrapper> extension_wrapper_mock =
         std::make_unique<testing::NiceMock<MockChromeOsExtensionWrapper>>();
     extension_wrapper_ptr_ = static_cast<MockChromeOsExtensionWrapper*>(
         extension_wrapper_mock.get());
     return std::make_unique<TestReadAnythingUntrustedPageHandler>(
-        page_.BindAndGetRemote(), test_web_ui_.get(),
-        std::move(extension_wrapper_mock));
+        page_.BindAndGetRemote(), handler_remote_.BindNewPipeAndPassReceiver(),
+        test_web_ui_.get(), std::move(extension_wrapper_mock));
 #else
     return std::make_unique<TestReadAnythingUntrustedPageHandler>(
-        page_.BindAndGetRemote(), test_web_ui_.get());
+        page_.BindAndGetRemote(), handler_remote_.BindNewPipeAndPassReceiver(),
+        test_web_ui_.get());
 #endif
   }
 
@@ -519,6 +528,7 @@
 #endif
   testing::NiceMock<MockPage> page_;
   FakeTtsEngineDelegate engine_delegate_;
+  mojo::Remote<read_anything::mojom::UntrustedPageHandler> handler_remote_;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
index 0016d2c..3094dfe 100644
--- a/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
+++ b/chrome/browser/ui/webui/side_panel/read_anything/read_anything_untrusted_page_handler_browsertest.cc
@@ -41,6 +41,7 @@
 #include "components/prefs/pref_value_map.h"
 #include "components/tabs/public/tab_interface.h"
 #include "components/translate/core/browser/translate_manager.h"
+#include "content/public/browser/render_widget_host_view.h"
 #include "content/public/common/content_switches.h"
 #include "content/public/test/browser_test.h"
 #include "content/public/test/browser_test_utils.h"
@@ -60,6 +61,9 @@
 #include "ui/accessibility/mojom/ax_tree_update.mojom.h"
 #include "ui/accessibility/platform/browser_accessibility.h"
 #include "ui/accessibility/platform/browser_accessibility_manager.h"
+#include "ui/base/clipboard/clipboard.h"
+#include "ui/base/clipboard/clipboard_buffer.h"
+#include "ui/base/clipboard/clipboard_sequence_number_token.h"
 #include "ui/gfx/geometry/size.h"
 #if BUILDFLAG(IS_CHROMEOS)
 #include "base/test/bind.h"
@@ -184,23 +188,25 @@
 #if BUILDFLAG(IS_CHROMEOS)
   explicit TestReadAnythingUntrustedPageHandler(
       mojo::PendingRemote<read_anything::mojom::UntrustedPage> page,
+      mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>
+          receiver,
       content::WebUI* test_web_ui,
       std::unique_ptr<ChromeOsExtensionWrapper> extension_wrapper)
-      : ReadAnythingUntrustedPageHandler(
-            std::move(page),
-            mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>(),
-            test_web_ui,
-            /*use_screen_ai_service=*/false,
-            std::move(extension_wrapper)) {}
+      : ReadAnythingUntrustedPageHandler(std::move(page),
+                                         std::move(receiver),
+                                         test_web_ui,
+                                         /*use_screen_ai_service=*/false,
+                                         std::move(extension_wrapper)) {}
 #else
   explicit TestReadAnythingUntrustedPageHandler(
       mojo::PendingRemote<read_anything::mojom::UntrustedPage> page,
+      mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>
+          receiver,
       content::WebUI* test_web_ui)
-      : ReadAnythingUntrustedPageHandler(
-            std::move(page),
-            mojo::PendingReceiver<read_anything::mojom::UntrustedPageHandler>(),
-            test_web_ui,
-            /*use_screen_ai_service=*/false) {}
+      : ReadAnythingUntrustedPageHandler(std::move(page),
+                                         std::move(receiver),
+                                         test_web_ui,
+                                         /*use_screen_ai_service=*/false) {}
 #endif
 };
 
@@ -315,6 +321,7 @@
 #if BUILDFLAG(IS_CHROMEOS)
     extension_wrapper_ptr_ = nullptr;
 #endif
+    handler_remote_.reset();
     handler_.reset();
     test_web_ui_.reset();
     web_contents_.reset();
@@ -322,17 +329,19 @@
   }
 
   std::unique_ptr<TestReadAnythingUntrustedPageHandler> CreateHandler() {
+    handler_remote_.reset();
 #if BUILDFLAG(IS_CHROMEOS)
     std::unique_ptr<ChromeOsExtensionWrapper> extension_wrapper_mock =
         std::make_unique<testing::NiceMock<MockChromeOsExtensionWrapper>>();
     extension_wrapper_ptr_ = static_cast<MockChromeOsExtensionWrapper*>(
         extension_wrapper_mock.get());
     return std::make_unique<TestReadAnythingUntrustedPageHandler>(
-        page_.BindAndGetRemote(), test_web_ui_.get(),
-        std::move(extension_wrapper_mock));
+        page_.BindAndGetRemote(), handler_remote_.BindNewPipeAndPassReceiver(),
+        test_web_ui_.get(), std::move(extension_wrapper_mock));
 #else
     return std::make_unique<TestReadAnythingUntrustedPageHandler>(
-        page_.BindAndGetRemote(), test_web_ui_.get());
+        page_.BindAndGetRemote(), handler_remote_.BindNewPipeAndPassReceiver(),
+        test_web_ui_.get());
 #endif
   }
 
@@ -519,6 +528,7 @@
 #endif
   testing::NiceMock<MockPage> page_;
   FakeTtsEngineDelegate engine_delegate_;
+  mojo::Remote<read_anything::mojom::UntrustedPageHandler> handler_remote_;
   std::unique_ptr<ReadAnythingUntrustedPageHandler> handler_;
   std::unique_ptr<content::WebContents> web_contents_;
   std::unique_ptr<content::TestWebUI> test_web_ui_;
@@ -1497,8 +1507,8 @@
   EXPECT_CALL(*extension_wrapper_ptr_, ActivateSpeechEngine).Times(1);
 
   handler_ = std::make_unique<TestReadAnythingUntrustedPageHandler>(
-      page_.BindAndGetRemote(), test_web_ui_.get(),
-      std::move(extension_wrapper_mock));
+      page_.BindAndGetRemote(), handler_remote_.BindNewPipeAndPassReceiver(),
+      test_web_ui_.get(), std::move(extension_wrapper_mock));
 }
 
 IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
@@ -2397,6 +2407,189 @@
   EXPECT_FALSE(handler_->dom_distiller_content().has_value());
 }
 
+IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
+                       OnLinkClicked_RequiresTransientUserActivation) {
+  const std::string histogram_name =
+      "Accessibility.ReadAnything.RendererRequestForLinkClick."
+      "IsFromObservedTree";
+  base::HistogramTester histogram_tester;
+
+  ASSERT_TRUE(
+      content::NavigateToURL(web_contents_.get(), GURL(url::kAboutBlankURL)));
+  test_web_ui_->set_render_frame_host(web_contents_->GetPrimaryMainFrame());
+  handler_ = CreateHandler();
+
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  content::RenderFrameHost* main_rfh =
+      browser()->GetActiveTabInterface()->GetContents()->GetPrimaryMainFrame();
+  ui::AXTreeID tree_id = main_rfh->GetAXTreeID();
+  ui::AXNodeID node_id = 1;
+
+  // Initial state with no transient user activation
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+  handler_remote_->OnLinkClicked(tree_id, node_id);
+  handler_remote_.FlushForTesting();
+  histogram_tester.ExpectTotalCount(histogram_name, 0);
+
+  // Grant User Activation via simulated mouse click.
+  content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents_.get());
+  content::SimulateMouseClick(web_contents_.get(), 0,
+                              blink::WebMouseEvent::Button::kLeft);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return rfh->HasTransientUserActivation(); }));
+  EXPECT_TRUE(rfh->HasTransientUserActivation());
+  handler_remote_->OnLinkClicked(tree_id, node_id);
+  handler_remote_.FlushForTesting();
+  histogram_tester.ExpectUniqueSample(histogram_name, /*sample=*/true,
+                                      /*expected_bucket_count=*/1);
+}
+
+IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
+                       OnSelectionChange_RequiresTransientUserActivation) {
+  const std::string histogram_name =
+      "Accessibility.ReadAnything.RendererRequestForSelection."
+      "IsFromObservedTree";
+  base::HistogramTester histogram_tester;
+
+  ASSERT_TRUE(
+      content::NavigateToURL(web_contents_.get(), GURL(url::kAboutBlankURL)));
+  test_web_ui_->set_render_frame_host(web_contents_->GetPrimaryMainFrame());
+  handler_ = CreateHandler();
+
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  content::RenderFrameHost* main_rfh =
+      browser()->GetActiveTabInterface()->GetContents()->GetPrimaryMainFrame();
+  ui::AXTreeID tree_id = main_rfh->GetAXTreeID();
+
+  // Initial state with no transient user activation
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+  handler_remote_->OnSelectionChange(tree_id, /*anchor_node_id=*/1, 0,
+                                     /*focus_node_id=*/2, 5);
+  handler_remote_.FlushForTesting();
+  histogram_tester.ExpectTotalCount(histogram_name, 0);
+
+  // Grant User Activation via simulated mouse click.
+  content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents_.get());
+  content::SimulateMouseClick(web_contents_.get(), 0,
+                              blink::WebMouseEvent::Button::kLeft);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return rfh->HasTransientUserActivation(); }));
+  EXPECT_TRUE(rfh->HasTransientUserActivation());
+  handler_remote_->OnSelectionChange(tree_id, /*anchor_node_id=*/1, 0,
+                                     /*focus_node_id=*/2, 5);
+  handler_remote_.FlushForTesting();
+  histogram_tester.ExpectUniqueSample(histogram_name, /*sample=*/true,
+                                      /*expected_bucket_count=*/1);
+}
+
+IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
+                       NullRenderFrameHost_ReturnsSafely) {
+  test_web_ui_->set_render_frame_host(nullptr);
+  handler_ = CreateHandler();
+
+  content::RenderFrameHost* main_rfh =
+      browser()->GetActiveTabInterface()->GetContents()->GetPrimaryMainFrame();
+  ui::AXTreeID tree_id = main_rfh->GetAXTreeID();
+
+  handler_remote_->OnCopy();
+  handler_remote_->OnLinkClicked(tree_id, 1);
+  handler_remote_->OnSelectionChange(tree_id, 1, 0, 1, 1);
+  handler_remote_->OnCollapseSelection();
+  handler_remote_.FlushForTesting();
+}
+
+IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
+                       OnCopy_RequiresTransientUserActivation) {
+  content::WebContents* main_contents =
+      browser()->GetActiveTabInterface()->GetContents();
+  ASSERT_TRUE(content::NavigateToURL(
+      main_contents, GURL("data:text/html,<div>Hello World</div>")));
+  main_contents->Focus();
+  main_contents->SelectAll();
+
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    content::RenderWidgetHostView* view =
+        main_contents->GetRenderWidgetHostView();
+    return view && !view->GetSelectedText().empty();
+  }));
+
+  ASSERT_TRUE(
+      content::NavigateToURL(web_contents_.get(), GURL(url::kAboutBlankURL)));
+  test_web_ui_->set_render_frame_host(web_contents_->GetPrimaryMainFrame());
+  handler_ = CreateHandler();
+
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+  ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread();
+  const ui::ClipboardSequenceNumberToken initial_seq =
+      clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste);
+
+  // Initial state with no transient user activation
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+  handler_remote_->OnCopy();
+  handler_remote_.FlushForTesting();
+  EXPECT_EQ(clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste),
+            initial_seq);
+
+  // Grant User Activation via simulated mouse click.
+  content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents_.get());
+  content::SimulateMouseClick(web_contents_.get(), 0,
+                              blink::WebMouseEvent::Button::kLeft);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return rfh->HasTransientUserActivation(); }));
+
+  handler_remote_->OnCopy();
+  handler_remote_.FlushForTesting();
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    return clipboard->GetSequenceNumber(ui::ClipboardBuffer::kCopyPaste) !=
+           initial_seq;
+  }));
+}
+
+IN_PROC_BROWSER_TEST_P(ReadAnythingUntrustedPageHandlerTest,
+                       OnCollapseSelection_RequiresTransientUserActivation) {
+  content::WebContents* main_contents =
+      browser()->GetActiveTabInterface()->GetContents();
+  ASSERT_TRUE(content::NavigateToURL(
+      main_contents, GURL("data:text/html,<div>Selected Text</div>")));
+  main_contents->Focus();
+  main_contents->SelectAll();
+
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    content::RenderWidgetHostView* view =
+        main_contents->GetRenderWidgetHostView();
+    return view && !view->GetSelectedText().empty();
+  }));
+
+  ASSERT_TRUE(
+      content::NavigateToURL(web_contents_.get(), GURL(url::kAboutBlankURL)));
+  test_web_ui_->set_render_frame_host(web_contents_->GetPrimaryMainFrame());
+  handler_ = CreateHandler();
+
+  content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame();
+
+  // Initial state with no transient user activation
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+  handler_remote_->OnCollapseSelection();
+  handler_remote_.FlushForTesting();
+  EXPECT_FALSE(
+      main_contents->GetRenderWidgetHostView()->GetSelectedText().empty());
+
+  // Grant User Activation via simulated mouse click.
+  content::SimulateEndOfPaintHoldingOnPrimaryMainFrame(web_contents_.get());
+  content::SimulateMouseClick(web_contents_.get(), 0,
+                              blink::WebMouseEvent::Button::kLeft);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return rfh->HasTransientUserActivation(); }));
+
+  handler_remote_->OnCollapseSelection();
+  handler_remote_.FlushForTesting();
+  ASSERT_TRUE(base::test::RunUntil([&]() {
+    content::RenderWidgetHostView* view =
+        main_contents->GetRenderWidgetHostView();
+    return view && view->GetSelectedText().empty();
+  }));
+}
+
 }  // namespace
 INSTANTIATE_TEST_SUITE_P(All,
                          ReadAnythingUntrustedPageHandlerTest,
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.