Medium chrome Authorization Error 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactMissing authorization in Google Lens
DescriptionMissing authorization in Google Lens
ComponentGoogle Lens
Sub-componentLens-overlay
Bug ClassAuthorization Error
Tracker502278429
Fix commita3e4912c8054 (chromium/src) +285/-8
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-06

Changed Functions

FunctionChangeNotes
LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest
chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
modified
EXPECT_TRUE
chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
modified
LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest
chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
modified

Files Changed

  • chrome/browser/ui/lens/overlay_base_controller.h
  • chrome/browser/ui/lens/overlay_base_controller.cc
  • chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
  • chrome/browser/ui/lens/lens_overlay_controller.cc
From a3e4912c80540df9646d14e377cff405d3935587 Mon Sep 17 00:00:00 2001
From: Juan Mojica <[email protected]>
Date: Wed, 09 Sep 2026 17:26:01 -0700
Subject: [PATCH] [lens-overlay] Validate user activation for privacy notice

Ensure that AcceptPrivacyNotice() and DismissPrivacyNotice() in
LensOverlayController verify that the non-blocking privacy notice
feature is enabled before processing. Additionally, require transient
user activation on the overlay RenderFrameHost prior to granting
permissions and contextualizing page content in AcceptPrivacyNotice().

If the feature flag is disabled or if AcceptPrivacyNotice() is called
without transient user activation during Mojo message dispatch, report
a bad Mojo message and return early.

Also expose GetOverlayWebView() on OverlayBaseController to access the
overlay RenderFrameHost, and add browser test coverage with
ContextualTasksSidePanel enabled verifying valid acceptance, dismissal,
and bad message reporting.

Bug: 502278429
Change-Id: Ia6ad194cc708f1c7f97f570fc39eef76678ded36
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8381945
Reviewed-by: Bryan Nguyen <[email protected]>
Commit-Queue: Juan Mojica <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1695035}
---
diff --git a/chrome/browser/ui/lens/lens_overlay_controller.cc b/chrome/browser/ui/lens/lens_overlay_controller.cc
index 6bad95a8..60e3b79 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller.cc
@@ -103,6 +103,7 @@
 #include "content/public/browser/render_widget_host_view.h"
 #include "content/public/browser/web_contents_user_data.h"
 #include "content/public/browser/web_ui.h"
+#include "mojo/public/cpp/bindings/message.h"
 #include "net/base/network_change_notifier.h"
 #include "pdf/buildflags.h"
 #include "services/metrics/public/cpp/ukm_builders.h"
@@ -2295,6 +2296,28 @@
 }
 
 void LensOverlayController::AcceptPrivacyNotice() {
+  if (!lens::features::IsLensOverlayNonBlockingPrivacyNoticeEnabled()) {
+    if (mojo::IsInMessageDispatch()) {
+      receiver_.ReportBadMessage(
+          "AcceptPrivacyNotice called when non-blocking privacy notice is not "
+          "enabled.");
+    }
+    return;
+  }
+
+  views::WebView* overlay_web_view = GetOverlayWebView();
+  content::RenderFrameHost* rfh =
+      overlay_web_view && overlay_web_view->GetWebContents()
+          ? overlay_web_view->GetWebContents()->GetPrimaryMainFrame()
+          : nullptr;
+  if (!rfh || !rfh->HasTransientUserActivation()) {
+    if (mojo::IsInMessageDispatch()) {
+      receiver_.ReportBadMessage(
+          "AcceptPrivacyNotice called without user activation.");
+    }
+    return;
+  }
+
   // Permanently grant permissions, then restart the query flow and upload page
   // content for contextualization.
   Profile* profile =
@@ -2311,6 +2334,14 @@
 }
 
 void LensOverlayController::DismissPrivacyNotice() {
+  if (!lens::features::IsLensOverlayNonBlockingPrivacyNoticeEnabled()) {
+    if (mojo::IsInMessageDispatch()) {
+      receiver_.ReportBadMessage(
+          "DismissPrivacyNotice called when non-blocking privacy notice is not "
+          "enabled.");
+    }
+    return;
+  }
   lens::RecordNonBlockingPrivacyNoticeAccepted(
       lens::LensOverlayNonBlockingPrivacyNoticeUserAction::kDismissed,
       invocation_source_);
diff --git a/chrome/browser/ui/lens/overlay_base_controller.cc b/chrome/browser/ui/lens/overlay_base_controller.cc
index c8f297e2..c7c61e4d 100644
--- a/chrome/browser/ui/lens/overlay_base_controller.cc
+++ b/chrome/browser/ui/lens/overlay_base_controller.cc
@@ -107,12 +107,20 @@
   return overlay_blur_layer_delegate_.get();
 }
 
+views::WebView* OverlayBaseController::GetOverlayWebView() const {
+  if (IsOverlayViewShared()) {
+    return overlay_web_view_.get();
+  }
+  return overlay_web_view_ ? overlay_web_view_.get()
+                           : owned_overlay_web_view_.get();
+}
+
 views::View* OverlayBaseController::GetOverlayViewForTesting() {
   return overlay_view_.get();
 }
 
 views::WebView* OverlayBaseController::GetOverlayWebViewForTesting() {
-  return overlay_web_view_.get();
+  return GetOverlayWebView();
 }
 
 void OverlayBaseController::OnViewBoundsChanged(views::View* observed_view) {
@@ -856,13 +864,7 @@
 }
 
 void OverlayBaseController::SetOverlayRoundedCorner() {
-  views::WebView* overlay_web_view = nullptr;
-  if (IsOverlayViewShared()) {
-    overlay_web_view = overlay_web_view_.get();
-  } else {
-    overlay_web_view = !!overlay_web_view_ ? overlay_web_view_.get()
-                                           : owned_overlay_web_view_.get();
-  }
+  views::WebView* overlay_web_view = GetOverlayWebView();
   CHECK(overlay_web_view);
 
   // Only tab-scoped overlays (where `!IsOverlayViewShared()`) live inside a
diff --git a/chrome/browser/ui/lens/overlay_base_controller.h b/chrome/browser/ui/lens/overlay_base_controller.h
index c7b2bb9..4925a57 100644
--- a/chrome/browser/ui/lens/overlay_base_controller.h
+++ b/chrome/browser/ui/lens/overlay_base_controller.h
@@ -358,6 +358,9 @@
   // Close the preselection bubble.
   void ClosePreselectionBubbleImpl();
 
+  // Returns the web view hosting the overlay WebUI if it exists.
+  views::WebView* GetOverlayWebView() const;
+
   // Returns the host view that the overlay should be attached to. Can be null.
   views::View* GetHostView() const;
 
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 7d93351..0e8d3981 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -110,6 +110,7 @@
 #include "components/lens/lens_overlay_dismissal_source.h"
 #include "components/lens/lens_overlay_invocation_source.h"
 #include "components/lens/lens_overlay_mime_type.h"
+#include "components/lens/lens_overlay_non_blocking_privacy_notice_user_action.h"
 #include "components/lens/lens_overlay_permission_utils.h"
 #include "components/lens/lens_overlay_side_panel_menu_option.h"
 #include "components/lens/lens_overlay_side_panel_result.h"
@@ -10028,3 +10029,243 @@
             l10n_util::GetStringUTF16(
                 IDS_LENS_OVERLAY_INITIAL_TOAST_MESSAGE_SIMPLIFIED));
 }
+
+class LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest
+    : public LensOverlayControllerBrowserTest {
+ public:
+  LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest() = default;
+
+  void SetupFeatureList() override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {lens::features::kLensOverlayNonBlockingPrivacyNotice,
+         lens::features::kLensOverlayContextualSearchbox,
+         contextual_tasks::kContextualTasksSidePanel},
+        /*disabled_features=*/{contextual_tasks::kContextualTasks});
+  }
+
+  void SetUpOnMainThread() override {
+    LensOverlayControllerBrowserTest::SetUpOnMainThread();
+    browser()->GetProfile()->GetPrefs()->SetBoolean(
+        lens::prefs::kLensSharingPageScreenshotEnabled, false);
+    browser()->GetProfile()->GetPrefs()->SetBoolean(
+        lens::prefs::kLensSharingPageContentEnabled, false);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+                       AcceptPrivacyNoticeWithoutUserActivation_Rejected) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  auto* prefs = browser()->GetProfile()->GetPrefs();
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke AcceptPrivacyNotice via JavaScript without user activation.
+  content::ExecuteScriptAsyncWithoutUserGesture(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: AcceptPrivacyNotice called without user "
+      "activation.",
+      bad_message_waiter.Wait());
+
+  // Permissions should not be granted.
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+    AcceptPrivacyNoticeWithUserActivation_GrantsPermissions) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  auto* prefs = browser()->GetProfile()->GetPrefs();
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  // Invoke AcceptPrivacyNotice with user activation via ExecJs (default option
+  // includes user gesture).
+  EXPECT_TRUE(content::ExecJs(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();"));
+
+  // Wait for permissions to be granted.
+  EXPECT_TRUE(base::test::RunUntil([&]() {
+    return prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled) &&
+           prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled);
+  }));
+
+  histogram_tester.ExpectUniqueSample(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted",
+      lens::LensOverlayNonBlockingPrivacyNoticeUserAction::kAccepted, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+                       DismissPrivacyNotice_Success) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  // Invoke DismissPrivacyNotice via JavaScript.
+  EXPECT_TRUE(content::ExecJs(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "dismissPrivacyNotice();"));
+
+  histogram_tester.ExpectUniqueSample(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted",
+      lens::LensOverlayNonBlockingPrivacyNoticeUserAction::kDismissed, 1);
+}
+
+class LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest
+    : public LensOverlayControllerBrowserTest {
+ public:
+  LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest() = default;
+
+  void SetupFeatureList() override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {lens::features::kLensOverlayContextualSearchbox,
+         contextual_tasks::kContextualTasksSidePanel},
+        /*disabled_features=*/{
+            lens::features::kLensOverlayNonBlockingPrivacyNotice,
+            contextual_tasks::kContextualTasks});
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest,
+    AcceptPrivacyNoticeWhenFeatureDisabled_ReportsBadMessage) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay with non-blocking privacy notice disabled.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke AcceptPrivacyNotice when feature is disabled.
+  content::ExecuteScriptAsync(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: AcceptPrivacyNotice called when "
+      "non-blocking privacy notice is not enabled.",
+      bad_message_waiter.Wait());
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest,
+    DismissPrivacyNoticeWhenFeatureDisabled_ReportsBadMessage) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay with non-blocking privacy notice disabled.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke DismissPrivacyNotice when feature is disabled.
+  content::ExecuteScriptAsync(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "dismissPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: DismissPrivacyNotice called when "
+      "non-blocking privacy notice is not enabled.",
+      bad_message_waiter.Wait());
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
index 7d93351..0e8d3981 100644
--- a/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
+++ b/chrome/browser/ui/lens/lens_overlay_controller_browsertest.cc
@@ -110,6 +110,7 @@
 #include "components/lens/lens_overlay_dismissal_source.h"
 #include "components/lens/lens_overlay_invocation_source.h"
 #include "components/lens/lens_overlay_mime_type.h"
+#include "components/lens/lens_overlay_non_blocking_privacy_notice_user_action.h"
 #include "components/lens/lens_overlay_permission_utils.h"
 #include "components/lens/lens_overlay_side_panel_menu_option.h"
 #include "components/lens/lens_overlay_side_panel_result.h"
@@ -10028,3 +10029,243 @@
             l10n_util::GetStringUTF16(
                 IDS_LENS_OVERLAY_INITIAL_TOAST_MESSAGE_SIMPLIFIED));
 }
+
+class LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest
+    : public LensOverlayControllerBrowserTest {
+ public:
+  LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest() = default;
+
+  void SetupFeatureList() override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {lens::features::kLensOverlayNonBlockingPrivacyNotice,
+         lens::features::kLensOverlayContextualSearchbox,
+         contextual_tasks::kContextualTasksSidePanel},
+        /*disabled_features=*/{contextual_tasks::kContextualTasks});
+  }
+
+  void SetUpOnMainThread() override {
+    LensOverlayControllerBrowserTest::SetUpOnMainThread();
+    browser()->GetProfile()->GetPrefs()->SetBoolean(
+        lens::prefs::kLensSharingPageScreenshotEnabled, false);
+    browser()->GetProfile()->GetPrefs()->SetBoolean(
+        lens::prefs::kLensSharingPageContentEnabled, false);
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+                       AcceptPrivacyNoticeWithoutUserActivation_Rejected) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  auto* prefs = browser()->GetProfile()->GetPrefs();
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+  EXPECT_FALSE(rfh->HasTransientUserActivation());
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke AcceptPrivacyNotice via JavaScript without user activation.
+  content::ExecuteScriptAsyncWithoutUserGesture(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: AcceptPrivacyNotice called without user "
+      "activation.",
+      bad_message_waiter.Wait());
+
+  // Permissions should not be granted.
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+    AcceptPrivacyNoticeWithUserActivation_GrantsPermissions) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  auto* prefs = browser()->GetProfile()->GetPrefs();
+  EXPECT_FALSE(
+      prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled));
+  EXPECT_FALSE(prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled));
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  // Invoke AcceptPrivacyNotice with user activation via ExecJs (default option
+  // includes user gesture).
+  EXPECT_TRUE(content::ExecJs(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();"));
+
+  // Wait for permissions to be granted.
+  EXPECT_TRUE(base::test::RunUntil([&]() {
+    return prefs->GetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled) &&
+           prefs->GetBoolean(lens::prefs::kLensSharingPageContentEnabled);
+  }));
+
+  histogram_tester.ExpectUniqueSample(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted",
+      lens::LensOverlayNonBlockingPrivacyNoticeUserAction::kAccepted, 1);
+}
+
+IN_PROC_BROWSER_TEST_F(LensOverlayControllerNonBlockingPrivacyNoticeBrowserTest,
+                       DismissPrivacyNotice_Success) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  // Invoke DismissPrivacyNotice via JavaScript.
+  EXPECT_TRUE(content::ExecJs(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "dismissPrivacyNotice();"));
+
+  histogram_tester.ExpectUniqueSample(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted",
+      lens::LensOverlayNonBlockingPrivacyNoticeUserAction::kDismissed, 1);
+}
+
+class LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest
+    : public LensOverlayControllerBrowserTest {
+ public:
+  LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest() = default;
+
+  void SetupFeatureList() override {
+    feature_list_.InitWithFeatures(
+        /*enabled_features=*/
+        {lens::features::kLensOverlayContextualSearchbox,
+         contextual_tasks::kContextualTasksSidePanel},
+        /*disabled_features=*/{
+            lens::features::kLensOverlayNonBlockingPrivacyNotice,
+            contextual_tasks::kContextualTasks});
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest,
+    AcceptPrivacyNoticeWhenFeatureDisabled_ReportsBadMessage) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay with non-blocking privacy notice disabled.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke AcceptPrivacyNotice when feature is disabled.
+  content::ExecuteScriptAsync(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "acceptPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: AcceptPrivacyNotice called when "
+      "non-blocking privacy notice is not enabled.",
+      bad_message_waiter.Wait());
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
+
+IN_PROC_BROWSER_TEST_F(
+    LensOverlayControllerPrivacyNoticeFeatureDisabledBrowserTest,
+    DismissPrivacyNoticeWhenFeatureDisabled_ReportsBadMessage) {
+  WaitForPaint();
+
+  base::HistogramTester histogram_tester;
+
+  auto* controller = GetLensOverlayController();
+  EXPECT_EQ(controller->state(), State::kOff);
+
+  // Open the overlay with non-blocking privacy notice disabled.
+  OpenLensOverlay(LensOverlayInvocationSource::kAppMenu);
+  ASSERT_TRUE(base::test::RunUntil(
+      [&]() { return controller->state() == State::kOverlay; }));
+
+  content::WebContents* overlay_web_contents = GetOverlayWebContents();
+  ASSERT_NE(overlay_web_contents, nullptr);
+  EXPECT_TRUE(content::WaitForLoadStop(overlay_web_contents));
+
+  content::RenderFrameHost* rfh = overlay_web_contents->GetPrimaryMainFrame();
+  ASSERT_NE(rfh, nullptr);
+
+  content::RenderProcessHostBadMojoMessageWaiter bad_message_waiter(
+      rfh->GetProcess());
+
+  // Invoke DismissPrivacyNotice when feature is disabled.
+  content::ExecuteScriptAsync(
+      overlay_web_contents,
+      "document.querySelector('lens-overlay-app').browserProxy.handler."
+      "dismissPrivacyNotice();");
+
+  EXPECT_EQ(
+      "Received bad user message: DismissPrivacyNotice called when "
+      "non-blocking privacy notice is not enabled.",
+      bad_message_waiter.Wait());
+
+  histogram_tester.ExpectTotalCount(
+      "Lens.Overlay.NonBlockingPrivacyNotice.Accepted", 0);
+}
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.