blob: 70173db52d0844c2debb74204e7f85267800f733 [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_queue_manager.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_service.h"
#include "chrome/browser/privacy_sandbox/privacy_sandbox_service_factory.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h"
#include "chrome/browser/search_engine_choice/search_engine_choice_dialog_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/privacy_sandbox/privacy_sandbox_prompt_helper.h"
#include "chrome/browser/ui/views/privacy_sandbox/privacy_sandbox_dialog_view.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/privacy_sandbox/privacy_sandbox_features.h"
#include "components/regional_capabilities/regional_capabilities_switches.h"
#include "components/search_engines/search_engines_switches.h"
#include "components/user_education/test/test_product_messaging_controller.h"
#include "components/variations/service/variations_service.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_delegate.h"
DEFINE_LOCAL_REQUIRED_NOTICE_IDENTIFIER(kNoticeId);
// This file is meant to test the general code path that causes notices to show.
// Specifically triggering of the notice queue.
class PrivacySandboxQueueTestNotice : public InProcessBrowserTest {
public:
void SetUpInProcessBrowserTestFixture() override {
feature_list_.InitWithFeatures(
{privacy_sandbox::kPrivacySandboxNoticeQueue}, {});
}
void SetUpOnMainThread() override {
privacy_sandbox_service()->SetPromptDisabledForTests(false);
privacy_sandbox_service()->ForceChromeBuildForTests(true);
g_browser_process->variations_service()->OverrideStoredPermanentCountry(
"be");
base::CommandLine::ForCurrentProcess()->RemoveSwitch(switches::kNoFirstRun);
}
content::WebContents* web_contents(PrivacySandboxDialogView* dialog_widget) {
return dialog_widget->GetWebContentsForTesting();
}
privacy_sandbox::PrivacySandboxQueueManager& queue_manager() {
return privacy_sandbox_service()->GetPrivacySandboxNoticeQueueManager();
}
PrivacySandboxService* privacy_sandbox_service() {
return PrivacySandboxServiceFactory::GetForProfile(browser()->profile());
}
std::string element_path_ =
"document.querySelector('privacy-sandbox-combined-dialog-app')."
"shadowRoot.querySelector('#notice').shadowRoot.querySelector('#"
"ackButton')";
std::string wait_for_button_ =
"const element = " + element_path_ +
";"
"const style = window.getComputedStyle(element);"
"style.getPropertyValue('visibility');";
private:
base::test::ScopedFeatureList feature_list_;
};
// Navigate to a invalid then valid webpage. Ensure handle is held throughout.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice, NoPrompt) {
// Navigate to invalid page.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUINewTabPageURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// When we navigate to a page that doesn't require a prompt, we should still
// hog handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
// Navigate to valid page.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
ASSERT_TRUE(queue_manager().IsHoldingHandle());
}
// Navigate to a valid webpage (settings page) and click a notice. One window.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice, PromptShows) {
// Navigate.
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
PrivacySandboxDialogView::kViewClassName);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
auto* dialog_widget = static_cast<PrivacySandboxDialogView*>(
waiter.WaitIfNeededAndGet()->widget_delegate()->GetContentsView());
// Before we click, should still be holding handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
// Click ack button.
const std::string& code = element_path_ + ".click();";
EXPECT_TRUE(content::ExecJs(web_contents(dialog_widget), code,
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
1 /* world_id */));
// After click, should release handle.
ASSERT_FALSE(queue_manager().IsHoldingHandle());
}
// Navigate to a valid webpage (settings page) and click a notice. Two windows.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice,
PromptShowsMultipleWindows) {
// Navigate.
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
PrivacySandboxDialogView::kViewClassName);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
auto* dialog_widget = static_cast<PrivacySandboxDialogView*>(
waiter.WaitIfNeededAndGet()->widget_delegate()->GetContentsView());
// After first nav, we should be holding handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After second nav, should still be holding handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
// Click ack button on one window.
const std::string& code = element_path_ + ".click();";
EXPECT_TRUE(content::ExecJs(web_contents(dialog_widget), code,
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
1 /* world_id */));
// After click, should release handle.
ASSERT_FALSE(queue_manager().IsHoldingHandle());
}
// Browser startup assumes we don't need a notice. Then we need a notice.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice,
PromptNeededAtStartupThenNotAtNavigation) {
// Set flags incorrectly so we don't need a prompt.
privacy_sandbox_service()->SetPromptDisabledForTests(true);
// Navigate.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUINewTabPageURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After first nav, we should not be holding handle.
ASSERT_FALSE(queue_manager().IsHoldingHandle());
ASSERT_FALSE(queue_manager().IsNoticeQueued());
// Set the correct flag.
privacy_sandbox_service()->SetPromptDisabledForTests(false);
// Navigate again and now we want to queue and hold the handle.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After second nav, should have been queued and holding handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
}
// Browser startup assumes we need a notice. Then we realize we don't need it.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice,
PromptNotNeededAtStartupThenNeededAtNavigation) {
// Navigate.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After first trigger, should have been queued and holding handle.
ASSERT_TRUE(queue_manager().IsHoldingHandle());
// Change our mind about wanting a prompt.
privacy_sandbox_service()->SetPromptDisabledForTests(true);
// Navigate.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After second nav do not hold handle.
ASSERT_FALSE(queue_manager().IsNoticeQueued());
ASSERT_FALSE(queue_manager().IsHoldingHandle());
}
// Don't allow the notice to be queued, such that the handle check fails, and we
// can log a QueueState.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice,
TestNoticeQueueStateNotInQueue) {
base::HistogramTester histogram_tester;
// Suppress attempts to queue.
queue_manager().SetSuppressQueue(true);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
EXPECT_FALSE(histogram_tester
.GetTotalCountsForPrefix(
"PrivacySandbox.Notice.NotHoldingHandle.NotInQueue")
.empty());
}
// Allow the notice to be queued, but add a blocking notice that doesn't allow
// our spot in the queue to trigger. The handle check repeatedly fails, and we
// are still in the queue.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNotice,
TestNoticeQueueStateStuckInQueue) {
base::HistogramTester histogram_tester;
// Add a test notice before our notice that hogs the handle.
user_education::test::TestNotice notice(
*queue_manager().GetProductMessagingController(), kNoticeId);
// Navigate to valid page, failing the holdingHandle check.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
EXPECT_FALSE(histogram_tester
.GetTotalCountsForPrefix(
"PrivacySandbox.Notice.NotHoldingHandle.InQueue")
.empty());
}
class PrivacySandboxQueueTestNoticeWithSearchEngine
: public PrivacySandboxQueueTestNotice {
// Override the country to simulate showing the search engine choice dialog.
public:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kSearchEngineChoiceCountry, "BE");
command_line->AppendSwitch(
switches::kIgnoreNoFirstRunForSearchEngineChoiceScreen);
}
void SetUpOnMainThread() override {
PrivacySandboxQueueTestNotice::SetUpOnMainThread();
SearchEngineChoiceDialogService::SetDialogDisabledForTests(
/*dialog_disabled=*/false);
}
private:
base::AutoReset<bool> scoped_chrome_build_override_ =
SearchEngineChoiceDialogServiceFactory::
ScopedChromeBuildOverrideForTesting(
/*force_chrome_build=*/true);
};
// Navigate to a page where the DMA notice should show and ensure suppression.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNoticeWithSearchEngine,
PromptSuppressed) {
// When we navigate to valid page for SE dialog, we should unqueue.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(url::kAboutBlankURL), WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
ASSERT_FALSE(queue_manager().IsHoldingHandle());
// Navigate again to a valid notice page.
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
// After second nav do not queue or hold the handle.
ASSERT_FALSE(queue_manager().IsNoticeQueued());
ASSERT_FALSE(queue_manager().IsHoldingHandle());
}
class PrivacySandboxQueueTestNoticeFeatureDisabled
: public PrivacySandboxQueueTestNotice {
public:
void SetUpInProcessBrowserTestFixture() override {
feature_list_.InitWithFeatures(
{}, {privacy_sandbox::kPrivacySandboxNoticeQueue});
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Navigate to a page and click a button.
IN_PROC_BROWSER_TEST_F(PrivacySandboxQueueTestNoticeFeatureDisabled,
ShowAndClickPrompt) {
// Should not be queued after browser startup
ASSERT_FALSE(queue_manager().IsNoticeQueued());
ASSERT_FALSE(queue_manager().IsHoldingHandle());
// Navigate
views::NamedWidgetShownWaiter waiter(
views::test::AnyWidgetTestPasskey{},
PrivacySandboxDialogView::kViewClassName);
ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUISettingsURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP));
auto* dialog_widget = static_cast<PrivacySandboxDialogView*>(
waiter.WaitIfNeededAndGet()->widget_delegate()->GetContentsView());
// Before we click, should still be holding handle.
ASSERT_FALSE(queue_manager().IsNoticeQueued());
ASSERT_FALSE(queue_manager().IsHoldingHandle());
// Click ack button.
const std::string& code = element_path_ + ".click();";
EXPECT_TRUE(content::ExecJs(web_contents(dialog_widget), code,
content::EXECUTE_SCRIPT_DEFAULT_OPTIONS,
1 /* world_id */));
// After click, should release handle.
ASSERT_FALSE(queue_manager().IsNoticeQueued());
ASSERT_FALSE(queue_manager().IsHoldingHandle());
}