Commit ee9ec691 authored by Xiyuan Xia's avatar Xiyuan Xia Committed by Commit Bot

chromeos: Add test to drag a window/tab to overview in tablet mode

- Add tests to drag window/tab to overview in tablet mode;
- Fix UIControlsOzone to use host dips instead of host pixels
  with EventInjector and use PointF to avoid rounding errors;

Bug: 942599
Change-Id: I0325749cdd95d4b0c213d09f349083336ec3f3b0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1546718
Commit-Queue: Xiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#647147}
parent b4504c65
...@@ -27,6 +27,9 @@ interface ShellTestApi { ...@@ -27,6 +27,9 @@ interface ShellTestApi {
// Enters or exits overview mode. // Enters or exits overview mode.
ToggleOverviewMode() => (); ToggleOverviewMode() => ();
// Returns true if it is in overview selecting mode.
IsOverviewSelecting() => (bool is_selecting);
// Used to emulate display change when run in a desktop environment instead // Used to emulate display change when run in a desktop environment instead
// of on a device. // of on a device.
AddRemoveDisplay(); AddRemoveDisplay();
...@@ -38,4 +41,8 @@ interface ShellTestApi { ...@@ -38,4 +41,8 @@ interface ShellTestApi {
// longer holding pointer events. See // longer holding pointer events. See
// |aura::WindowTreeHost::holding_pointer_moves_| for details. // |aura::WindowTreeHost::holding_pointer_moves_| for details.
WaitForNoPointerHoldLock() => (); WaitForNoPointerHoldLock() => ();
// Runs the callback when the compositor of the primary display has presented
// a frame on screen.
WaitForNextFrame() => ();
}; };
...@@ -167,6 +167,10 @@ void ShellTestApi::ToggleOverviewMode(ToggleOverviewModeCallback cb) { ...@@ -167,6 +167,10 @@ void ShellTestApi::ToggleOverviewMode(ToggleOverviewModeCallback cb) {
std::move(cb).Run(); std::move(cb).Run();
} }
void ShellTestApi::IsOverviewSelecting(IsOverviewSelectingCallback callback) {
std::move(callback).Run(shell_->overview_controller()->IsSelecting());
}
void ShellTestApi::AddRemoveDisplay() { void ShellTestApi::AddRemoveDisplay() {
shell_->display_manager()->AddRemoveDisplay(); shell_->display_manager()->AddRemoveDisplay();
} }
...@@ -184,4 +188,16 @@ void ShellTestApi::WaitForNoPointerHoldLock( ...@@ -184,4 +188,16 @@ void ShellTestApi::WaitForNoPointerHoldLock(
std::move(callback).Run(); std::move(callback).Run();
} }
void ShellTestApi::WaitForNextFrame(WaitForNextFrameCallback callback) {
Shell::GetPrimaryRootWindowController()
->GetHost()
->compositor()
->RequestPresentationTimeForNextFrame(base::BindOnce(
[](WaitForNextFrameCallback callback,
const gfx::PresentationFeedback& feedback) {
std::move(callback).Run();
},
std::move(callback)));
}
} // namespace ash } // namespace ash
...@@ -61,10 +61,12 @@ class ShellTestApi : public mojom::ShellTestApi { ...@@ -61,10 +61,12 @@ class ShellTestApi : public mojom::ShellTestApi {
SnapWindowInSplitViewCallback cb) override; SnapWindowInSplitViewCallback cb) override;
void ToggleFullscreen(ToggleFullscreenCallback cb) override; void ToggleFullscreen(ToggleFullscreenCallback cb) override;
void ToggleOverviewMode(ToggleOverviewModeCallback cb) override; void ToggleOverviewMode(ToggleOverviewModeCallback cb) override;
void IsOverviewSelecting(IsOverviewSelectingCallback callback) override;
void AddRemoveDisplay() override; void AddRemoveDisplay() override;
void SetMinFlingVelocity(float velocity) override; void SetMinFlingVelocity(float velocity) override;
void WaitForNoPointerHoldLock( void WaitForNoPointerHoldLock(
WaitForNoPointerHoldLockCallback callback) override; WaitForNoPointerHoldLockCallback callback) override;
void WaitForNextFrame(WaitForNextFrameCallback callback) override;
private: private:
Shell* shell_; // not owned Shell* shell_; // not owned
......
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/public/cpp/ash_switches.h"
#include "ash/public/interfaces/constants.mojom.h"
#include "ash/public/interfaces/shell_test_api.test-mojom-test-utils.h"
#include "ash/public/interfaces/shell_test_api.test-mojom.h"
#include "base/bind.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/ui/ash/tablet_mode_client_test_util.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_drag_controller.h"
#include "chrome/browser/ui/views/tabs/tab_strip.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "content/public/browser/notification_service.h"
#include "content/public/common/service_manager_connection.h"
#include "content/public/test/test_utils.h"
#include "services/service_manager/public/cpp/connector.h"
#include "ui/aura/test/mus/change_completion_waiter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h"
#include "ui/aura/window_tree_host.h"
#include "ui/base/test/ui_controls.h"
#include "ui/compositor/compositor.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/vector2d.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace {
bool IsOverviewSelecting() {
ash::mojom::ShellTestApiPtr shell_test_api;
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->BindInterface(ash::mojom::kServiceName, &shell_test_api);
ash::mojom::ShellTestApiAsyncWaiter waiter(shell_test_api.get());
bool is_selecting = false;
waiter.IsOverviewSelecting(&is_selecting);
return is_selecting;
}
int GetDetachY(TabStrip* tab_strip) {
return std::max(TabDragController::kTouchVerticalDetachMagnetism,
TabDragController::kVerticalDetachMagnetism) +
tab_strip->height() + 1;
}
// Waits for the primary display to present a frame after the object is
// constructed.
class NextFrameWaiter {
public:
NextFrameWaiter() {
content::ServiceManagerConnection::GetForProcess()
->GetConnector()
->BindInterface(ash::mojom::kServiceName, &shell_test_api_);
shell_test_api_->WaitForNextFrame(base::BindOnce(
&NextFrameWaiter::OnFramePresented, base::Unretained(this)));
// Flush to ensure the call has gone through.
shell_test_api_.FlushForTesting();
}
~NextFrameWaiter() { EXPECT_TRUE(frame_presented_); }
void WaitForDisplay() {
if (!frame_presented_) {
run_loop_ = std::make_unique<base::RunLoop>(
base::RunLoop::Type::kNestableTasksAllowed);
run_loop_->Run();
EXPECT_TRUE(frame_presented_);
}
}
private:
void OnFramePresented() {
frame_presented_ = true;
if (run_loop_)
run_loop_->Quit();
}
ash::mojom::ShellTestApiPtr shell_test_api_;
bool frame_presented_ = false;
std::unique_ptr<base::RunLoop> run_loop_;
DISALLOW_COPY_AND_ASSIGN(NextFrameWaiter);
};
} // namespace
class DragToOverviewTest : public InProcessBrowserTest {
public:
DragToOverviewTest() = default;
~DragToOverviewTest() override = default;
// InProcessBrowserTest:
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitch(ash::switches::kAshEnableTabletMode);
}
void SetUpOnMainThread() override {
test::SetAndWaitForTabletMode(true);
if (base::SysInfo::IsRunningOnChromeOS()) {
base::RunLoop run_loop;
base::PostDelayedTask(FROM_HERE, run_loop.QuitClosure(),
base::TimeDelta::FromSeconds(5));
run_loop.Run();
}
}
// Continue a drag by perform |count| steps mouse dragging with each step move
// |delta|, then moue up.
void ContinueDrag(const gfx::Point& start_position,
const gfx::Vector2d& delta,
int count) {
gfx::Point drag_position = start_position;
for (int i = 0; i < count; ++i) {
drag_position += delta;
NextFrameWaiter waiter;
ASSERT_TRUE(
ui_controls::SendMouseMove(drag_position.x(), drag_position.y()));
waiter.WaitForDisplay();
}
{
NextFrameWaiter waiter;
ASSERT_TRUE(
ui_controls::SendMouseEvents(ui_controls::LEFT, ui_controls::UP));
waiter.WaitForDisplay();
}
}
void VerifyTabDetachedAndContinueDrag(const gfx::Point& start_position,
const gfx::Vector2d delta,
int count) {
// Tab should be detached to create a new browser window.
EXPECT_EQ(2u, BrowserList::GetInstance()->size());
EXPECT_TRUE(TabDragController::IsActive());
ContinueDrag(start_position, delta, count);
}
private:
DISALLOW_COPY_AND_ASSIGN(DragToOverviewTest);
};
IN_PROC_BROWSER_TEST_F(DragToOverviewTest, DragWindow) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
aura::Window* browser_window = browser_view->GetWidget()->GetNativeWindow();
gfx::Rect browser_screen_bounds = browser_window->GetBoundsInScreen();
const gfx::Point start_position(
browser_screen_bounds.CenterPoint().x(),
browser_screen_bounds.y() + browser_view->GetTabStripHeight() / 2);
ASSERT_TRUE(
ui_test_utils::SendMouseMoveSync(start_position) &&
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::DOWN));
// One more mouse move to start drag.
ASSERT_TRUE(ui_test_utils::SendMouseMoveSync(start_position));
// Drag 25% of the work area height.
const int drag_length = display::Screen::GetScreen()
->GetDisplayNearestWindow(browser_window)
.work_area_size()
.height() /
4;
constexpr int kSteps = 20;
gfx::Vector2d delta(0, drag_length / kSteps);
ContinueDrag(start_position, delta, kSteps);
EXPECT_TRUE(IsOverviewSelecting());
}
IN_PROC_BROWSER_TEST_F(DragToOverviewTest, DragTab) {
BrowserView* browser_view = BrowserView::GetBrowserViewForBrowser(browser());
aura::Window* browser_window = browser_view->GetWidget()->GetNativeWindow();
AddBlankTabAndShow(browser());
browser_view->tabstrip()->StopAnimating(true);
aura::test::WaitForAllChangesToComplete();
gfx::Point drag_position(ui_test_utils::GetCenterInScreenCoordinates(
browser_view->tabstrip()->tab_at(0)));
ASSERT_TRUE(
ui_test_utils::SendMouseMoveSync(drag_position) &&
ui_test_utils::SendMouseEventsSync(ui_controls::LEFT, ui_controls::DOWN));
// Drag 25% of the work area height.
const int drag_length = display::Screen::GetScreen()
->GetDisplayNearestWindow(browser_window)
.work_area_size()
.height() /
4;
constexpr int kSteps = 20;
gfx::Vector2d delta(0, drag_length / kSteps);
// Drag tab far enough to detach.
drag_position.Offset(0, GetDetachY(browser_view->tabstrip()));
ui_controls::SendMouseMoveNotifyWhenDone(
drag_position.x(), drag_position.y(),
base::BindOnce(&DragToOverviewTest::VerifyTabDetachedAndContinueDrag,
base::Unretained(this), drag_position, delta, kSteps));
// Wait for the drag to finish.
content::WindowedNotificationObserver(
chrome::NOTIFICATION_TAB_DRAG_LOOP_DONE,
content::NotificationService::AllSources())
.Wait();
}
...@@ -5209,7 +5209,10 @@ if (!is_android) { ...@@ -5209,7 +5209,10 @@ if (!is_android) {
# Use only the _chromeos version on Ash / Chrome OS. # Use only the _chromeos version on Ash / Chrome OS.
"../browser/ui/views/test/view_event_test_platform_part_default.cc", "../browser/ui/views/test/view_event_test_platform_part_default.cc",
] ]
sources += [ "../browser/ui/ash/split_view_interactive_uitest.cc" ] sources += [
"../browser/ui/ash/drag_to_overview_interactive_uitest.cc",
"../browser/ui/ash/split_view_interactive_uitest.cc",
]
} else { # ! is_chromeos } else { # ! is_chromeos
# Non-ChromeOS notifications tests. # Non-ChromeOS notifications tests.
sources += [ sources += [
......
...@@ -25,6 +25,7 @@ ...@@ -25,6 +25,7 @@
#include "ui/display/screen.h" #include "ui/display/screen.h"
#include "ui/events/event_utils.h" #include "ui/events/event_utils.h"
#include "ui/events/test/events_test_utils.h" #include "ui/events/test/events_test_utils.h"
#include "ui/gfx/geometry/point_conversions.h"
namespace aura { namespace aura {
namespace test { namespace test {
...@@ -129,7 +130,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -129,7 +130,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
bool SendMouseMoveNotifyWhenDone(long screen_x, bool SendMouseMoveNotifyWhenDone(long screen_x,
long screen_y, long screen_y,
base::OnceClosure closure) override { base::OnceClosure closure) override {
gfx::Point host_location(screen_x, screen_y); gfx::PointF host_location(screen_x, screen_y);
int64_t display_id = display::kInvalidDisplayId; int64_t display_id = display::kInvalidDisplayId;
if (!ScreenDIPToHostPixels(&host_location, &display_id)) if (!ScreenDIPToHostPixels(&host_location, &display_id))
return false; return false;
...@@ -158,13 +159,14 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -158,13 +159,14 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
int button_state, int button_state,
base::OnceClosure closure, base::OnceClosure closure,
int accelerator_state) override { int accelerator_state) override {
gfx::Point host_location; gfx::PointF host_location;
int64_t display_id = display::kInvalidDisplayId; int64_t display_id = display::kInvalidDisplayId;
if (last_mouse_location_.has_value()) { if (last_mouse_location_.has_value()) {
host_location = last_mouse_location_.value(); host_location = last_mouse_location_.value();
display_id = last_mouse_display_id_; display_id = last_mouse_display_id_;
} else { } else {
host_location = host_->window()->env()->last_mouse_location(); host_location =
gfx::PointF(host_->window()->env()->last_mouse_location());
if (!ScreenDIPToHostPixels(&host_location, &display_id)) if (!ScreenDIPToHostPixels(&host_location, &display_id))
return false; return false;
} }
...@@ -228,7 +230,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -228,7 +230,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
int y, int y,
base::OnceClosure task) override { base::OnceClosure task) override {
DCHECK_NE(0, action); DCHECK_NE(0, action);
gfx::Point host_location(x, y); gfx::PointF host_location(x, y);
int64_t display_id = display::kInvalidDisplayId; int64_t display_id = display::kInvalidDisplayId;
if (!ScreenDIPToHostPixels(&host_location, &display_id)) if (!ScreenDIPToHostPixels(&host_location, &display_id))
return false; return false;
...@@ -263,8 +265,24 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -263,8 +265,24 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
int64_t display_id, int64_t display_id,
base::OnceClosure closure) { base::OnceClosure closure) {
if (event_injector_) { if (event_injector_) {
auto event_to_inject = ui::Event::Clone(*event);
if (event_to_inject->IsLocatedEvent()) {
// EventInjector expects coordinates relative to host and in DIPs.
display::Display display;
CHECK(display::Screen::GetScreen()->GetDisplayWithDisplayId(display_id,
&display));
ui::LocatedEvent* located_event = event_to_inject->AsLocatedEvent();
gfx::PointF location_in_host_dip = gfx::ScalePoint(
located_event->location_f(), 1 / display.device_scale_factor(),
1 / display.device_scale_factor());
located_event->set_location_f(location_in_host_dip);
located_event->set_root_location_f(location_in_host_dip);
}
event_injector_->InjectEvent( event_injector_->InjectEvent(
display_id, ui::Event::Clone(*event), display_id, std::move(event_to_inject),
base::BindOnce(&OnWindowServiceProcessedEvent, std::move(closure))); base::BindOnce(&OnWindowServiceProcessedEvent, std::move(closure)));
return; return;
} }
...@@ -304,7 +322,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -304,7 +322,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
} }
void PostMouseEvent(ui::EventType type, void PostMouseEvent(ui::EventType type,
const gfx::Point& host_location, const gfx::PointF& host_location,
int flags, int flags,
int changed_button_flags, int changed_button_flags,
int64_t display_id, int64_t display_id,
...@@ -317,7 +335,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -317,7 +335,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
} }
void PostMouseEventTask(ui::EventType type, void PostMouseEventTask(ui::EventType type,
const gfx::Point& host_location, const gfx::PointF& host_location,
int flags, int flags,
int changed_button_flags, int changed_button_flags,
int64_t display_id, int64_t display_id,
...@@ -333,7 +351,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -333,7 +351,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
} }
void PostTouchEvent(ui::EventType type, void PostTouchEvent(ui::EventType type,
const gfx::Point& host_location, const gfx::PointF& host_location,
int id, int id,
int64_t display_id, int64_t display_id,
base::OnceClosure closure) { base::OnceClosure closure) {
...@@ -344,14 +362,14 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -344,14 +362,14 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
} }
void PostTouchEventTask(ui::EventType type, void PostTouchEventTask(ui::EventType type,
const gfx::Point& host_location, const gfx::PointF& host_location,
int id, int id,
int64_t display_id, int64_t display_id,
base::OnceClosure closure) { base::OnceClosure closure) {
ui::PointerDetails details(ui::EventPointerType::POINTER_TYPE_TOUCH, id, ui::PointerDetails details(ui::EventPointerType::POINTER_TYPE_TOUCH, id,
1.0f, 1.0f, 0.0f); 1.0f, 1.0f, 0.0f);
ui::TouchEvent touch_event(type, host_location, ui::EventTimeForNow(), ui::TouchEvent touch_event(type, host_location, host_location,
details); ui::EventTimeForNow(), details);
SendEventToSink(&touch_event, display_id, std::move(closure)); SendEventToSink(&touch_event, display_id, std::move(closure));
} }
...@@ -367,19 +385,18 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -367,19 +385,18 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
->BindInterface(ws::mojom::kServiceName, &event_injector_); ->BindInterface(ws::mojom::kServiceName, &event_injector_);
} }
bool ScreenDIPToHostPixels(gfx::Point* location, int64_t* display_id) { bool ScreenDIPToHostPixels(gfx::PointF* location, int64_t* display_id) {
// The location needs to be in display's coordinate. // The location needs to be in display's coordinate.
display::Display display = display::Display display =
display::Screen::GetScreen()->GetDisplayNearestPoint(*location); display::Screen::GetScreen()->GetDisplayNearestPoint(
gfx::ToFlooredPoint(*location));
if (!display.is_valid()) { if (!display.is_valid()) {
LOG(ERROR) << "Failed to find the display for " << location->ToString(); LOG(ERROR) << "Failed to find the display for " << location->ToString();
return false; return false;
} }
*display_id = display.id(); *display_id = display.id();
*location -= display.bounds().OffsetFromOrigin(); *location -= display.bounds().OffsetFromOrigin();
*location = location->Scale(display.device_scale_factor());
gfx::ScaleToFlooredPoint(*location, display.device_scale_factor(),
display.device_scale_factor());
return true; return true;
} }
...@@ -389,7 +406,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura, ...@@ -389,7 +406,7 @@ class UIControlsOzone : public ui_controls::UIControlsAura,
// The mouse location for the last SendMouseEventsNotifyWhenDone call. This is // The mouse location for the last SendMouseEventsNotifyWhenDone call. This is
// used rather than Env::last_mouse_location() as Env::last_mouse_location() // used rather than Env::last_mouse_location() as Env::last_mouse_location()
// is updated asynchronously with mus. // is updated asynchronously with mus.
base::Optional<gfx::Point> last_mouse_location_; base::Optional<gfx::PointF> last_mouse_location_;
// The display ID where the last SendMouseEventsNotifyWhenDone occurred. This // The display ID where the last SendMouseEventsNotifyWhenDone occurred. This
// is used along with |last_mouse_location_| to send the mouse event to the // is used along with |last_mouse_location_| to send the mouse event to the
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment