blob: 7ff0f205d25c22e89e77680a005502cf14f7d318 [file] [log] [blame]
// Copyright 2021 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/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_simple_task_runner.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/hit_test_region_observer.h"
#include "net/dns/mock_host_resolver.h"
#include "ui/base/test/ui_controls.h"
#include "ui/base/ui_base_features.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/corewm/tooltip_aura.h"
#include "ui/views/corewm/tooltip_controller.h"
#include "ui/views/corewm/tooltip_controller_test_helper.h"
#include "ui/views/widget/any_widget_observer.h"
#include "ui/views/widget/widget.h"
#include "ui/wm/public/tooltip_observer.h"
using content::RenderFrameHost;
using content::RenderWidgetHostView;
using content::WebContents;
using views::corewm::TooltipController;
using views::corewm::test::TooltipControllerTestHelper;
using views::corewm::TooltipAura;
class TooltipMonitor {
public:
TooltipMonitor() {
observer_ = std::make_unique<views::AnyWidgetObserver>(
views::test::AnyWidgetTestPasskey());
observer_->set_shown_callback(base::BindRepeating(
&TooltipMonitor::OnTooltipShown, base::Unretained(this)));
observer_->set_closing_callback(base::BindRepeating(
&TooltipMonitor::OnTooltipClosed, base::Unretained(this)));
}
TooltipMonitor(const TooltipMonitor&) = delete;
TooltipMonitor& operator=(const TooltipMonitor&) = delete;
~TooltipMonitor() = default;
bool IsWidgetActive() const { return active_widget_; }
void WaitUntilTooltipShown() {
if (!active_widget_) {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
}
void WaitUntilTooltipClosed() {
if (active_widget_) {
run_loop_ = std::make_unique<base::RunLoop>();
run_loop_->Run();
}
}
void OnTooltipShown(views::Widget* widget) {
if (widget->GetName() == TooltipAura::kWidgetName) {
active_widget_ = widget;
if (run_loop_ && run_loop_->running()) {
run_loop_->Quit();
}
}
}
void OnTooltipClosed(views::Widget* widget) {
if (active_widget_ == widget) {
active_widget_ = nullptr;
if (run_loop_ && run_loop_->running()) {
run_loop_->Quit();
}
}
}
private:
// This attribute will help us avoid starting the |run_loop_| when the widget
// is already shown. Otherwise, we would be waiting infinitely for an event
// that already occurred.
raw_ptr<views::Widget> active_widget_ = nullptr;
std::unique_ptr<views::AnyWidgetObserver> observer_;
std::unique_ptr<base::RunLoop> run_loop_;
}; // class TooltipMonitor
// Browser tests for tooltips on platforms that use TooltipAura to display the
// tooltip.
class TooltipBrowserTest : public InProcessBrowserTest {
public:
TooltipBrowserTest() = default;
~TooltipBrowserTest() override = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
ASSERT_TRUE(embedded_test_server()->Start());
gfx::NativeWindow root_window =
browser()->window()->GetNativeWindow()->GetRootWindow();
event_generator_ = std::make_unique<ui::test::EventGenerator>(root_window);
helper_ = std::make_unique<TooltipControllerTestHelper>(root_window);
tooltip_monitor_ = std::make_unique<TooltipMonitor>();
}
content::WebContents* web_contents() { return web_contents_; }
protected:
void NavigateToURL(const std::string& relative_url) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("a.com", relative_url)));
web_contents_ = browser()->tab_strip_model()->GetActiveWebContents();
rwhv_ = web_contents_->GetRenderWidgetHostView();
content::WaitForHitTestData(web_contents_->GetPrimaryMainFrame());
}
void LoadCrossSitePageIntoFrame(const std::string& relative_url,
const std::string& iframe_id) {
// This function makes the iframe in the web page point to a different
// origin, making it an OOPIF.
GURL frame_url(embedded_test_server()->GetURL("b.com", relative_url));
NavigateIframeToURL(web_contents_, iframe_id, frame_url);
// Verify that the child frame is an OOPIF with a different SiteInstance.
RenderFrameHost* child = GetChildRenderFrameHost(0);
EXPECT_NE(web_contents_->GetSiteInstance(), child->GetSiteInstance());
EXPECT_TRUE(child->IsCrossProcessSubframe());
content::WaitForHitTestData(child);
}
RenderFrameHost* GetChildRenderFrameHost(size_t index) {
return ChildFrameAt(web_contents_->GetPrimaryMainFrame(), index);
}
gfx::Point WebContentPositionToScreenCoordinate(int x, int y) {
return gfx::Point(x, y) + rwhv_->GetViewBounds().OffsetFromOrigin();
}
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
"KeyboardAccessibleTooltip");
scoped_feature_list_.InitWithFeatures(
{features::kKeyboardAccessibleTooltip}, {});
}
ui::test::EventGenerator* event_generator() { return event_generator_.get(); }
TooltipControllerTestHelper* helper() { return helper_.get(); }
TooltipMonitor* tooltip_monitor() { return tooltip_monitor_.get(); }
private:
std::unique_ptr<ui::test::EventGenerator> event_generator_ = nullptr;
raw_ptr<RenderWidgetHostView, AcrossTasksDanglingUntriaged> rwhv_ = nullptr;
raw_ptr<WebContents, AcrossTasksDanglingUntriaged> web_contents_ = nullptr;
std::unique_ptr<TooltipControllerTestHelper> helper_;
std::unique_ptr<TooltipMonitor> tooltip_monitor_ = nullptr;
base::test::ScopedFeatureList scoped_feature_list_;
}; // class TooltipBrowserTest
// TOOD(crbug.com/40768202): Flakily fails on Windows
#if BUILDFLAG(IS_WIN)
#define MAYBE_ShowTooltipFromWebContentWithCursor \
DISABLED_ShowTooltipFromWebContentWithCursor
#else
#define MAYBE_ShowTooltipFromWebContentWithCursor \
ShowTooltipFromWebContentWithCursor
#endif
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
MAYBE_ShowTooltipFromWebContentWithCursor) {
NavigateToURL("/tooltip.html");
std::u16string expected_text = u"my tooltip";
// Trigger the tooltip from the cursor.
// This will position the cursor right above a button with the title attribute
// set and will show the tooltip.
gfx::Point position = WebContentPositionToScreenCoordinate(10, 10);
event_generator()->MoveMouseTo(position);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
helper()->HideAndReset();
tooltip_monitor()->WaitUntilTooltipClosed();
}
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN)
// https://crbug.com/1212403. Flaky on linux-chromeos-rel and other linux bots.
#define MAYBE_ShowTooltipFromWebContentWithKeyboard \
DISABLED_ShowTooltipFromWebContentWithKeyboard
#else
#define MAYBE_ShowTooltipFromWebContentWithKeyboard \
ShowTooltipFromWebContentWithKeyboard
#endif
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
MAYBE_ShowTooltipFromWebContentWithKeyboard) {
NavigateToURL("/tooltip.html");
std::u16string expected_text = u"my tooltip";
// Trigger the tooltip from the keyboard with a TAB keypress.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
helper()->HideAndReset();
}
// https://crbug.com/1212403. Flaky on linux-chromeos-rel.
// https://crbug.com/1241736. Flaky on Win.
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
DISABLED_ShowTooltipFromIFrameWithKeyboard) {
// There are two tooltips in this file: one above the iframe and one inside
// the iframe.
NavigateToURL("/tooltip_in_iframe.html");
std::u16string expected_text = u"my tooltip";
// Make the iframe cross-origin.
LoadCrossSitePageIntoFrame("/tooltip.html", "iframe1");
// Move the focus to the button outside of the iframe to get its position.
// We'll use it in the next step to validate that the tooltip associated with
// the button inside of the iframe is positioned, well, inside the iframe.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
gfx::Point first_tooltip_anchor_point = helper()->GetTooltipPosition();
// Now that we have the anchor point of the first tooltip, move the focus to
// the tooltip inside of the iframe.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
// Validate that the tooltip is correctly positioned inside the iframe.
// Because we explicitly removed the iframe's borders and body margins, the
// buttons should be positioned at the exact same x value as the button above,
// and at exactly twice the y value as the one outside the iframe (because the
// tooltip's anchor point is at the bottom-center of the button).
int expected_y = 2 * first_tooltip_anchor_point.y();
EXPECT_EQ(gfx::Point(first_tooltip_anchor_point.x(), expected_y),
helper()->GetTooltipPosition());
helper()->HideAndReset();
tooltip_monitor()->WaitUntilTooltipClosed();
}
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
// https://crbug.com/1212403. Flaky on linux-chromeos-rel.
// https://crbug.com/1241736. Flaky on Win.
#define MAYBE_HideTooltipOnKeyPressTriggeredByCursor \
DISABLED_HideTooltipOnKeyPressTriggeredByCursor
#else
#define MAYBE_HideTooltipOnKeyPressTriggeredByCursor \
HideTooltipOnKeyPressTriggeredByCursor
#endif
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
MAYBE_HideTooltipOnKeyPressTriggeredByCursor) {
NavigateToURL("/tooltip.html");
std::u16string expected_text = u"my tooltip";
// First, trigger the tooltip from the cursor.
gfx::Point position = WebContentPositionToScreenCoordinate(10, 10);
event_generator()->MoveMouseTo(position);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
// Second, send a key press event to test whether the tooltip gets hidden.
EXPECT_TRUE(tooltip_monitor()->IsWidgetActive());
ui_controls::SendKeyPress(browser()->window()->GetNativeWindow(), ui::VKEY_A,
false, false, false, false);
tooltip_monitor()->WaitUntilTooltipClosed();
EXPECT_FALSE(helper()->IsTooltipVisible());
}
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
// https://crbug.com/1212403. Flaky on linux-chromeos-rel.
// https://crbug.com/1241736. Flaky on Win.
#define MAYBE_HideTooltipOnKeyPressTriggeredByKeyboard \
DISABLED_HideTooltipOnKeyPressTriggeredByKeyboard
#else
#define MAYBE_HideTooltipOnKeyPressTriggeredByKeyboard \
HideTooltipOnKeyPressTriggeredByKeyboard
#endif
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
MAYBE_HideTooltipOnKeyPressTriggeredByKeyboard) {
NavigateToURL("/tooltip.html");
std::u16string expected_text = u"my tooltip";
// Trigger the tooltip from the keyboard with a TAB keypress.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text, helper()->GetTooltipText());
// Send a key press event to test whether the tooltip gets hidden.
EXPECT_TRUE(tooltip_monitor()->IsWidgetActive());
event_generator()->PressKey(ui::VKEY_A, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_A, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipClosed();
EXPECT_FALSE(helper()->IsTooltipVisible());
}
#if BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
// https://crbug.com/1212403. Flaky on linux-chromeos-rel, windows, linux.
#define MAYBE_ScriptFocusHidesKeyboardTriggeredTooltip \
DISABLED_ScriptFocusHidesKeyboardTriggeredTooltip
#else
#define MAYBE_ScriptFocusHidesKeyboardTriggeredTooltip \
ScriptFocusHidesKeyboardTriggeredTooltip
#endif
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest,
MAYBE_ScriptFocusHidesKeyboardTriggeredTooltip) {
NavigateToURL("/tooltip_two_buttons.html");
std::u16string expected_text_1 = u"my tooltip 1";
std::u16string expected_text_2 = u"my tooltip 2";
// Trigger the tooltip from the keyboard with a TAB keypress.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_NONE);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_NONE);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text_1, helper()->GetTooltipText());
EXPECT_TRUE(tooltip_monitor()->IsWidgetActive());
// Validate that a blur event on another element than our focused one doesn't
// hide the tooltip.
std::string javascript = "document.getElementById('b2').blur();";
EXPECT_TRUE(content::ExecJs(web_contents(), javascript));
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text_1, helper()->GetTooltipText());
EXPECT_TRUE(tooltip_monitor()->IsWidgetActive());
// Validate that a focus on another element will hide the tooltip.
javascript = "document.getElementById('b2').focus();";
EXPECT_TRUE(content::ExecJs(web_contents(), javascript));
tooltip_monitor()->WaitUntilTooltipClosed();
EXPECT_FALSE(tooltip_monitor()->IsWidgetActive());
EXPECT_FALSE(helper()->IsTooltipVisible());
// Move the focus again to the first button to test the blur on the focused
// element.
event_generator()->PressKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
event_generator()->ReleaseKey(ui::VKEY_TAB, ui::EF_SHIFT_DOWN);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
EXPECT_EQ(expected_text_1, helper()->GetTooltipText());
EXPECT_TRUE(tooltip_monitor()->IsWidgetActive());
// Validate that the blur call hides the tooltip.
javascript = "document.getElementById('b1').blur();";
EXPECT_TRUE(content::ExecJs(web_contents(), javascript));
EXPECT_FALSE(tooltip_monitor()->IsWidgetActive());
EXPECT_FALSE(helper()->IsTooltipVisible());
}
IN_PROC_BROWSER_TEST_F(TooltipBrowserTest, ResetTooltipOnClosingWindow) {
NavigateToURL("/tooltip.html");
// Trigger the tooltip from the cursor.
gfx::Point position = WebContentPositionToScreenCoordinate(10, 10);
event_generator()->MoveMouseTo(position);
tooltip_monitor()->WaitUntilTooltipShown();
EXPECT_TRUE(helper()->IsTooltipVisible());
// Tooltip should be hidden on closing window.
chrome::CloseWindow(browser());
// Verify tooltip is closed.
tooltip_monitor()->WaitUntilTooltipClosed();
// Make sure Chrome won't crash during window destruction.
ui_test_utils::WaitForBrowserToClose();
}