blob: 9f8b21dc563e8906709fc5c872317990bec06b7d [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/test/bind.h"
#include "base/timer/timer.h"
#include "chrome/browser/lacros/browser_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/lacros/window_utility.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/crosapi/mojom/test_controller.mojom-test-utils.h"
#include "chromeos/crosapi/mojom/test_controller.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#include "content/public/test/browser_test.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/aura/window.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/views/test/widget_test.h"
namespace {
// Waits for the window identified by |window_id| to have its ash-side window
// position in DIP screen coordinates set to |target_position|.
void WaitForWindowPositionInScreen(const std::string& window_id,
const gfx::Point& target_position) {
auto* lacros_service = chromeos::LacrosService::Get();
base::RunLoop outer_loop;
auto wait_for_position = base::BindLambdaForTesting([&]() {
base::RunLoop inner_loop(base::RunLoop::Type::kNestableTasksAllowed);
absl::optional<gfx::Point> position;
lacros_service->GetRemote<crosapi::mojom::TestController>()
->GetWindowPositionInScreen(
window_id, base::BindLambdaForTesting(
[&](const absl::optional<gfx::Point>& p) {
position = p;
inner_loop.Quit();
}));
inner_loop.Run();
if (position && *position == target_position)
outer_loop.Quit();
});
base::RepeatingTimer timer;
timer.Start(FROM_HERE, base::Milliseconds(1), std::move(wait_for_position));
outer_loop.Run();
}
using PopupBrowserTest = InProcessBrowserTest;
// Regression test for https://crbug.com/1157664. Verifying that opening a
// menu via long-press on a tab does not result in a popup window with empty
// bounds. In bug caused a Wayland protocol error and lacros crash.
IN_PROC_BROWSER_TEST_F(PopupBrowserTest, LongPressOnTabOpensNonEmptyMenu) {
auto* lacros_service = chromeos::LacrosService::Get();
ASSERT_TRUE(lacros_service->IsAvailable<crosapi::mojom::TestController>());
// This test requires the tablet mode API.
if (lacros_service->GetInterfaceVersion(
crosapi::mojom::TestController::Uuid_) < 3) {
LOG(WARNING) << "Unsupported ash version.";
return;
}
// Ensure the browser is maximized. The bug only occurs when the tab strip is
// near the top of the screen.
browser()->window()->Maximize();
// Wait for the window to be created.
aura::Window* window = browser()->window()->GetNativeWindow();
std::string window_id =
lacros_window_utility::GetRootWindowUniqueId(window->GetRootWindow());
ASSERT_TRUE(browser_test_util::WaitForWindowCreation(window_id));
// Wait for the window to be globally positioned at 0,0. It will eventually
// have this position because it is maximized. We cannot assert the position
// lacros-side because Wayland clients do not know the position of their
// windows on the display.
WaitForWindowPositionInScreen(window_id, gfx::Point(0, 0));
// Precondition: The browser is the only open widget.
std::set<views::Widget*> initial_widgets =
views::test::WidgetTest::GetAllWidgets();
ASSERT_EQ(1u, initial_widgets.size());
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
views::Widget* browser_widget = browser_view->GetWidget();
ASSERT_EQ(browser_widget, *initial_widgets.begin());
// Find the center position of the first tab. We cannot use "screen"
// coordinates because the Wayland client does not know where its window is
// located on the screen. Use widget-relative position instead.
Tab* tab = browser_view->tabstrip()->tab_at(0);
gfx::Point tab_center_in_widget = tab->GetLocalBounds().CenterPoint();
views::View::ConvertPointToWidget(tab, &tab_center_in_widget);
// Generate a touch press in ash, because the bug requires lacros to receive
// events over the Wayland connection from ash.
crosapi::mojom::TestControllerAsyncWaiter waiter(
lacros_service->GetRemote<crosapi::mojom::TestController>().get());
waiter.SendTouchEvent(window_id, crosapi::mojom::TouchEventType::kPressed,
/*pointer_id=*/0u, gfx::PointF(tab_center_in_widget));
// Wait long enough that the gesture recognizer will decide this is a long
// press gesture. We cannot directly inject a long press from ash because
// Wayland only transports press/move/release events, not gestures.
base::RunLoop loop2;
base::OneShotTimer timer2;
timer2.Start(FROM_HERE, base::Seconds(1), loop2.QuitClosure());
loop2.Run();
// Release the touch in ash.
waiter.SendTouchEvent(window_id, crosapi::mojom::TouchEventType::kReleased,
/*pointer_id=*/0u, gfx::PointF(tab_center_in_widget));
// Wait for the popup menu to be created and positioned on screen.
base::RunLoop loop3;
auto wait_for_popup_on_screen = base::BindLambdaForTesting([&]() {
std::set<views::Widget*> widgets = views::test::WidgetTest::GetAllWidgets();
widgets.erase(browser_widget);
if (widgets.size() == 0u)
return;
// The popup was created.
views::Widget* popup = *widgets.begin();
// The popup's top edge may be off the top of the screen. Wait for it to be
// positioned on screen. The bug involved the repositioning code. We know
// that 0 means the top of the screen because the window is maximized.
if (popup->GetRestoredBounds().y() < 0)
return;
loop3.Quit();
});
base::RepeatingTimer timer3;
timer3.Start(FROM_HERE, base::Milliseconds(1),
std::move(wait_for_popup_on_screen));
loop3.Run();
// Find the popup.
std::set<views::Widget*> widgets = views::test::WidgetTest::GetAllWidgets();
widgets.erase(browser_widget);
ASSERT_EQ(1u, widgets.size());
views::Widget* popup = *widgets.begin();
// The popup has valid bounds.
gfx::Rect popup_bounds = popup->GetRestoredBounds();
EXPECT_FALSE(popup_bounds.IsEmpty()) << popup_bounds.ToString();
}
} // namespace