// Copyright (c) 2012 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/shell_window_ids.h" #include "ash/root_window_controller.h" #include "ash/shell.h" #include "ash/system/status_area_widget.h" #include "ash/system/status_area_widget_test_helper.h" #include "ash/system/unified/unified_system_tray.h" #include "ash/test/ash_test_base.h" #include "ash/test/test_widget_builder.h" #include "ash/test/test_window_builder.h" #include "ash/wm/desks/desks_util.h" #include "ash/wm/window_properties.h" #include "ash/wm/window_util.h" #include "base/run_loop.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/focus_client.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/test/window_test_api.h" #include "ui/aura/window.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/base/cursor/cursor.h" #include "ui/base/cursor/mojom/cursor_type.mojom-shared.h" #include "ui/display/display.h" #include "ui/display/display_layout.h" #include "ui/display/manager/display_manager.h" #include "ui/display/screen.h" #include "ui/events/event_handler.h" #include "ui/events/test/event_generator.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" #include "ui/wm/core/window_util.h" #include "ui/wm/public/activation_client.h" namespace ash { namespace { void SetSecondaryDisplayLayout(display::DisplayPlacement::Position position) { std::unique_ptr layout = Shell::Get()->display_manager()->GetCurrentDisplayLayout().Copy(); layout->placement_list[0].position = position; Shell::Get()->display_manager()->SetLayoutForCurrentDisplays( std::move(layout)); } // An event handler which moves the target window to the secondary root window // at pre-handle phase of a mouse release event. class MoveWindowByClickEventHandler : public ui::EventHandler { public: explicit MoveWindowByClickEventHandler(aura::Window* target) : target_(target) {} MoveWindowByClickEventHandler(const MoveWindowByClickEventHandler&) = delete; MoveWindowByClickEventHandler& operator=( const MoveWindowByClickEventHandler&) = delete; ~MoveWindowByClickEventHandler() override = default; private: // ui::EventHandler overrides: void OnMouseEvent(ui::MouseEvent* event) override { if (event->type() == ui::ET_MOUSE_RELEASED) { aura::Window::Windows root_windows = Shell::GetAllRootWindows(); DCHECK_LT(1u, root_windows.size()); root_windows[1]->AddChild(target_); } } aura::Window* target_; }; // An event handler which records the event's locations. class EventLocationRecordingEventHandler : public ui::EventHandler { public: EventLocationRecordingEventHandler() { Reset(); } EventLocationRecordingEventHandler( const EventLocationRecordingEventHandler&) = delete; EventLocationRecordingEventHandler& operator=( const EventLocationRecordingEventHandler&) = delete; ~EventLocationRecordingEventHandler() override = default; // |location_| is relative to the target window. std::string GetLocation() const { return location_.ToString(); } // |root_location_| is relative to the display where the event started. std::string GetRootLocation() const { return root_location_.ToString(); } void Reset() { location_.SetPoint(-999, -999); root_location_.SetPoint(-999, -999); } private: // ui::EventHandler overrides: void OnMouseEvent(ui::MouseEvent* event) override { if (event->type() == ui::ET_MOUSE_MOVED || event->type() == ui::ET_MOUSE_DRAGGED) { location_ = event->location(); root_location_ = event->root_location(); } } gfx::Point root_location_; gfx::Point location_; }; class EventLocationHandler : public ui::EventHandler { public: EventLocationHandler() = default; EventLocationHandler(const EventLocationHandler&) = delete; EventLocationHandler& operator=(const EventLocationHandler&) = delete; ~EventLocationHandler() override = default; const gfx::Point& press_location() const { return press_location_; } const gfx::Point& release_location() const { return release_location_; } private: // ui::EventHandler: void OnMouseEvent(ui::MouseEvent* event) override { if (event->type() == ui::ET_MOUSE_PRESSED) press_location_ = event->location(); else if (event->type() == ui::ET_MOUSE_RELEASED) release_location_ = event->location(); } gfx::Point press_location_; gfx::Point release_location_; }; } // namespace class ExtendedDesktopTest : public AshTestBase { public: ExtendedDesktopTest() = default; ExtendedDesktopTest(const ExtendedDesktopTest&) = delete; ExtendedDesktopTest& operator=(const ExtendedDesktopTest&) = delete; ~ExtendedDesktopTest() override = default; void SetUp() override { AshTestBase::SetUp(); // Shell hides the cursor by default; show it for these tests. Shell::Get()->cursor_manager()->ShowCursor(); } views::Widget* CreateTestWidget(const gfx::Rect& bounds) { return TestWidgetBuilder() .SetBounds(bounds) .SetContext(GetContext()) .BuildOwnedByNativeWidget(); } protected: bool IsBubbleShown() { return GetPrimaryUnifiedSystemTray()->IsBubbleShown(); } gfx::Rect GetSystemTrayBoundsInScreen() { return GetPrimaryUnifiedSystemTray()->GetBoundsInScreen(); } }; // Test conditions that root windows in extended desktop mode must satisfy. TEST_F(ExtendedDesktopTest, Basic) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); // All root windows must have a root window controller. ASSERT_EQ(2U, root_windows.size()); EXPECT_TRUE(RootWindowController::ForWindow(root_windows[0])); EXPECT_TRUE(RootWindowController::ForWindow(root_windows[1])); // Make sure root windows share the same controllers. EXPECT_EQ(aura::client::GetFocusClient(root_windows[0]), aura::client::GetFocusClient(root_windows[1])); EXPECT_EQ(::wm::GetActivationClient(root_windows[0]), ::wm::GetActivationClient(root_windows[1])); EXPECT_EQ(aura::client::GetCaptureClient(root_windows[0]), aura::client::GetCaptureClient(root_windows[1])); } TEST_F(ExtendedDesktopTest, Activation) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); views::Widget* widget_on_1st = CreateTestWidget(gfx::Rect(10, 10, 100, 100)); views::Widget* widget_on_2nd = CreateTestWidget(gfx::Rect(1200, 10, 100, 100)); EXPECT_EQ(root_windows[0], widget_on_1st->GetNativeView()->GetRootWindow()); EXPECT_EQ(root_windows[1], widget_on_2nd->GetNativeView()->GetRootWindow()); EXPECT_EQ(widget_on_2nd->GetNativeView(), aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow()); EXPECT_TRUE(wm::IsActiveWindow(widget_on_2nd->GetNativeView())); ui::test::EventGenerator* event_generator = GetEventGenerator(); // Clicking a window changes the active window and active root window. event_generator->MoveMouseToCenterOf(widget_on_1st->GetNativeView()); event_generator->ClickLeftButton(); EXPECT_EQ(widget_on_1st->GetNativeView(), aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow()); EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView())); event_generator->MoveMouseToCenterOf(widget_on_2nd->GetNativeView()); event_generator->ClickLeftButton(); EXPECT_EQ(widget_on_2nd->GetNativeView(), aura::client::GetFocusClient(root_windows[0])->GetFocusedWindow()); EXPECT_TRUE(wm::IsActiveWindow(widget_on_2nd->GetNativeView())); } TEST_F(ExtendedDesktopTest, SystemModal) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); views::Widget* widget_on_1st = CreateTestWidget(gfx::Rect(10, 10, 100, 100)); EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView())); EXPECT_EQ(root_windows[0], widget_on_1st->GetNativeView()->GetRootWindow()); EXPECT_EQ(root_windows[0], Shell::GetRootWindowForNewWindows()); // Open system modal. Make sure it's on 2nd root window and active. auto delegate = std::make_unique(); delegate->SetModalType(ui::MODAL_TYPE_SYSTEM); views::Widget* modal_widget = views::Widget::CreateWindowWithContext( delegate.release(), GetContext(), gfx::Rect(1200, 100, 100, 100)); modal_widget->Show(); EXPECT_TRUE(wm::IsActiveWindow(modal_widget->GetNativeView())); EXPECT_EQ(root_windows[1], modal_widget->GetNativeView()->GetRootWindow()); EXPECT_EQ(root_windows[1], Shell::GetRootWindowForNewWindows()); ui::test::EventGenerator* event_generator = GetEventGenerator(); // Clicking a widget on widget_on_1st display should not change activation. event_generator->MoveMouseToCenterOf(widget_on_1st->GetNativeView()); event_generator->ClickLeftButton(); EXPECT_TRUE(wm::IsActiveWindow(modal_widget->GetNativeView())); EXPECT_EQ(root_windows[1], Shell::GetRootWindowForNewWindows()); // Close system modal and so clicking a widget should work now. modal_widget->Close(); event_generator->MoveMouseToCenterOf(widget_on_1st->GetNativeView()); event_generator->ClickLeftButton(); EXPECT_TRUE(wm::IsActiveWindow(widget_on_1st->GetNativeView())); EXPECT_EQ(root_windows[0], Shell::GetRootWindowForNewWindows()); } TEST_F(ExtendedDesktopTest, TestCursor) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::WindowTreeHost* host0 = root_windows[0]->GetHost(); aura::WindowTreeHost* host1 = root_windows[1]->GetHost(); EXPECT_EQ(ui::mojom::CursorType::kPointer, host0->last_cursor().type()); EXPECT_EQ(ui::mojom::CursorType::kNull, host1->last_cursor().type()); Shell::Get()->cursor_manager()->SetCursor(ui::mojom::CursorType::kCopy); EXPECT_EQ(ui::mojom::CursorType::kCopy, host0->last_cursor().type()); EXPECT_EQ(ui::mojom::CursorType::kCopy, host1->last_cursor().type()); } TEST_F(ExtendedDesktopTest, TestCursorLocation) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::WindowTestApi root_window0_test_api(root_windows[0]); aura::test::WindowTestApi root_window1_test_api(root_windows[1]); root_windows[0]->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("10,10", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); EXPECT_TRUE(root_window0_test_api.ContainsMouse()); EXPECT_FALSE(root_window1_test_api.ContainsMouse()); root_windows[1]->MoveCursorTo(gfx::Point(10, 20)); EXPECT_EQ("1010,20", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); EXPECT_FALSE(root_window0_test_api.ContainsMouse()); EXPECT_TRUE(root_window1_test_api.ContainsMouse()); root_windows[0]->MoveCursorTo(gfx::Point(20, 10)); EXPECT_EQ("20,10", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); EXPECT_TRUE(root_window0_test_api.ContainsMouse()); EXPECT_FALSE(root_window1_test_api.ContainsMouse()); } TEST_F(ExtendedDesktopTest, GetRootWindowAt) { UpdateDisplay("700x500,600x500"); SetSecondaryDisplayLayout(display::DisplayPlacement::LEFT); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); using window_util::GetRootWindowAt; EXPECT_EQ(root_windows[1], GetRootWindowAt(gfx::Point(-400, 100))); EXPECT_EQ(root_windows[1], GetRootWindowAt(gfx::Point(-1, 100))); EXPECT_EQ(root_windows[0], GetRootWindowAt(gfx::Point(0, 300))); EXPECT_EQ(root_windows[0], GetRootWindowAt(gfx::Point(700, 300))); // Zero origin. EXPECT_EQ(root_windows[0], GetRootWindowAt(gfx::Point(0, 0))); // Out of range point should return the nearest root window EXPECT_EQ(root_windows[1], GetRootWindowAt(gfx::Point(-600, 0))); EXPECT_EQ(root_windows[0], GetRootWindowAt(gfx::Point(701, 100))); } TEST_F(ExtendedDesktopTest, GetRootWindowMatching) { UpdateDisplay("700x500,600x500"); SetSecondaryDisplayLayout(display::DisplayPlacement::LEFT); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); // Containing rect. using window_util::GetRootWindowMatching; EXPECT_EQ(root_windows[1], GetRootWindowMatching(gfx::Rect(-300, 10, 50, 50))); EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(100, 10, 50, 50))); // Intersecting rect. EXPECT_EQ(root_windows[1], GetRootWindowMatching(gfx::Rect(-200, 0, 300, 300))); EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(-100, 0, 300, 300))); // Zero origin. EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(0, 0, 0, 0))); EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(0, 0, 1, 1))); // Empty rect. EXPECT_EQ(root_windows[1], GetRootWindowMatching(gfx::Rect(-400, 100, 0, 0))); EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(100, 100, 0, 0))); // Out of range rect should return the primary root window. EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(-600, -300, 50, 50))); EXPECT_EQ(root_windows[0], GetRootWindowMatching(gfx::Rect(0, 1000, 50, 50))); } TEST_F(ExtendedDesktopTest, Capture) { // This test deals with input events but not visuals so don't throttle input // on visuals. aura::Env::GetInstance()->set_throttle_input_on_resize_for_testing(false); UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::EventCountDelegate r1_d1; aura::test::EventCountDelegate r1_d2; aura::test::EventCountDelegate r2_d1; std::unique_ptr r1_w1(aura::test::CreateTestWindowWithDelegate( &r1_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[0])); std::unique_ptr r1_w2(aura::test::CreateTestWindowWithDelegate( &r1_d2, 0, gfx::Rect(10, 100, 100, 100), root_windows[0])); std::unique_ptr r2_w1(aura::test::CreateTestWindowWithDelegate( &r2_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[1])); r1_w1->SetCapture(); EXPECT_EQ(r1_w1.get(), aura::client::GetCaptureWindow(r2_w1->GetRootWindow())); ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseToCenterOf(r2_w1.get()); // |r1_w1| will receive the events because it has capture. EXPECT_EQ("1 1 0", r1_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", r1_d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset()); generator->ClickLeftButton(); EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0", r2_d1.GetMouseButtonCountsAndReset()); // The mouse is outside. On chromeos, the mouse is warped to the // dest root window, but it's not implemented on Win yet, so // no mouse move event on Win. EXPECT_EQ("0 0 0", r1_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1", r1_d1.GetMouseButtonCountsAndReset()); generator->MoveMouseTo(15, 15); EXPECT_EQ("0 1 0", r1_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", r1_d2.GetMouseMotionCountsAndReset()); r1_w2->SetCapture(); EXPECT_EQ(r1_w2.get(), aura::client::GetCaptureWindow(r2_w1->GetRootWindow())); generator->MoveMouseBy(10, 10); // |r1_w2| has the capture. So it will receive the mouse-move event. EXPECT_EQ("0 0 0", r1_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 1 0", r1_d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset()); generator->ClickLeftButton(); EXPECT_EQ("0 0 0", r2_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0", r2_d1.GetMouseButtonCountsAndReset()); EXPECT_EQ("0 0 0", r1_d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1", r1_d2.GetMouseButtonCountsAndReset()); r1_w2->ReleaseCapture(); EXPECT_EQ(nullptr, aura::client::GetCaptureWindow(r2_w1->GetRootWindow())); generator->MoveMouseToCenterOf(r2_w1.get()); generator->ClickLeftButton(); EXPECT_EQ("1 1 0", r2_d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1", r2_d1.GetMouseButtonCountsAndReset()); // Make sure the mouse_moved_handler_ is properly reset. EXPECT_EQ("0 0 0", r1_d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0", r1_d2.GetMouseButtonCountsAndReset()); } TEST_F(ExtendedDesktopTest, CaptureEventLocation) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::EventCountDelegate r1_d1; aura::test::EventCountDelegate r1_d2; aura::test::EventCountDelegate r2_d1; std::unique_ptr r1_w1(aura::test::CreateTestWindowWithDelegate( &r1_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[0])); std::unique_ptr r1_w2(aura::test::CreateTestWindowWithDelegate( &r1_d2, 0, gfx::Rect(10, 100, 100, 100), root_windows[0])); std::unique_ptr r2_w1(aura::test::CreateTestWindowWithDelegate( &r2_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[1])); r1_w1->SetCapture(); ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseToCenterOf(r2_w1.get()); EXPECT_EQ(gfx::Point(1060, 60).ToString(), generator->current_screen_location().ToString()); EventLocationHandler location_handler; r1_w1->AddPreTargetHandler(&location_handler); generator->ClickLeftButton(); r1_w1->RemovePreTargetHandler(&location_handler); EXPECT_EQ(gfx::Point(1050, 50).ToString(), location_handler.press_location().ToString()); EXPECT_EQ(gfx::Point(1050, 50).ToString(), location_handler.release_location().ToString()); } TEST_F(ExtendedDesktopTest, CaptureEventLocationHighDPI) { UpdateDisplay("1000x600*2,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::EventCountDelegate r1_d1; aura::test::EventCountDelegate r1_d2; aura::test::EventCountDelegate r2_d1; std::unique_ptr r1_w1(aura::test::CreateTestWindowWithDelegate( &r1_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[0])); std::unique_ptr r1_w2(aura::test::CreateTestWindowWithDelegate( &r1_d2, 0, gfx::Rect(10, 100, 100, 100), root_windows[0])); std::unique_ptr r2_w1(aura::test::CreateTestWindowWithDelegate( &r2_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[1])); r1_w1->SetCapture(); ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseToCenterOf(r2_w1.get()); EXPECT_EQ(gfx::Point(560, 60).ToString(), generator->current_screen_location().ToString()); EventLocationHandler location_handler; r1_w1->AddPreTargetHandler(&location_handler); generator->ClickLeftButton(); r1_w1->RemovePreTargetHandler(&location_handler); EXPECT_EQ(gfx::Point(550, 50).ToString(), location_handler.press_location().ToString()); EXPECT_EQ(gfx::Point(550, 50).ToString(), location_handler.release_location().ToString()); } TEST_F(ExtendedDesktopTest, CaptureEventLocationHighDPI_2) { UpdateDisplay("1000x600,600x400*2"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::EventCountDelegate r1_d1; aura::test::EventCountDelegate r1_d2; aura::test::EventCountDelegate r2_d1; std::unique_ptr r1_w1(aura::test::CreateTestWindowWithDelegate( &r1_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[0])); std::unique_ptr r1_w2(aura::test::CreateTestWindowWithDelegate( &r1_d2, 0, gfx::Rect(10, 100, 100, 100), root_windows[0])); std::unique_ptr r2_w1(aura::test::CreateTestWindowWithDelegate( &r2_d1, 0, gfx::Rect(10, 10, 100, 100), root_windows[1])); r1_w1->SetCapture(); ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseToCenterOf(r2_w1.get()); EXPECT_EQ(gfx::Point(1060, 60).ToString(), generator->current_screen_location().ToString()); EventLocationHandler location_handler; r1_w1->AddPreTargetHandler(&location_handler); generator->ClickLeftButton(); r1_w1->RemovePreTargetHandler(&location_handler); // Event-generator dispatches the event in the primary root-window's coord // space. Since the location is (1060, 60), it goes to the secondary // root-window as (30, 30) since the secondary root-window has a device scale // factor of 2. EXPECT_EQ(gfx::Point(1020, 20).ToString(), location_handler.press_location().ToString()); EXPECT_EQ(gfx::Point(1020, 20).ToString(), location_handler.release_location().ToString()); } TEST_F(ExtendedDesktopTest, MoveWindow) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); views::Widget* d1 = CreateTestWidget(gfx::Rect(10, 10, 100, 100)); EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow()); d1->SetBounds(gfx::Rect(1010, 10, 100, 100)); EXPECT_EQ("1010,10 100x100", d1->GetWindowBoundsInScreen().ToString()); EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow()); d1->SetBounds(gfx::Rect(10, 10, 100, 100)); EXPECT_EQ("10,10 100x100", d1->GetWindowBoundsInScreen().ToString()); EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow()); // Make sure the bounds which doesn't fit to the root window // works correctly. d1->SetBounds(gfx::Rect(1560, 30, 100, 100)); EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow()); EXPECT_EQ("1560,30 100x100", d1->GetWindowBoundsInScreen().ToString()); // Setting outside of root windows will be moved to primary root window. // TODO(oshima): This one probably should pick the closest root window. d1->SetBounds(gfx::Rect(200, 10, 100, 100)); EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow()); } // Verifies if the mouse event arrives to the window even when the window // moves to another root in a pre-target handler. See: crbug.com/157583 TEST_F(ExtendedDesktopTest, MoveWindowByMouseClick) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); aura::test::EventCountDelegate delegate; std::unique_ptr window(aura::test::CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(10, 10, 100, 100), root_windows[0])); MoveWindowByClickEventHandler event_handler(window.get()); window->AddPreTargetHandler(&event_handler); ui::test::EventGenerator* event_generator = GetEventGenerator(); event_generator->MoveMouseToCenterOf(window.get()); event_generator->ClickLeftButton(); // Both mouse pressed and released arrive at the window and its delegate. EXPECT_EQ("1 1", delegate.GetMouseButtonCountsAndReset()); // Also event_handler moves the window to another root at mouse release. EXPECT_EQ(root_windows[1], window->GetRootWindow()); } TEST_F(ExtendedDesktopTest, MoveWindowToDisplay) { UpdateDisplay("1000x900,1000x900"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); display::Display display0 = display::Screen::GetScreen()->GetDisplayMatching( root_windows[0]->GetBoundsInScreen()); display::Display display1 = display::Screen::GetScreen()->GetDisplayMatching( root_windows[1]->GetBoundsInScreen()); EXPECT_NE(display0.id(), display1.id()); views::Widget* d1 = CreateTestWidget(gfx::Rect(10, 10, 1000, 100)); EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow()); // Move the window where the window spans both root windows. Since the second // parameter is |display1|, the window should be shown on the secondary root. d1->GetNativeWindow()->SetBoundsInScreen(gfx::Rect(500, 10, 1000, 100), display1); EXPECT_EQ("500,10 1000x100", d1->GetWindowBoundsInScreen().ToString()); EXPECT_EQ(root_windows[1], d1->GetNativeView()->GetRootWindow()); // Move to the primary root. d1->GetNativeWindow()->SetBoundsInScreen(gfx::Rect(500, 10, 1000, 100), display0); EXPECT_EQ("500,10 1000x100", d1->GetWindowBoundsInScreen().ToString()); EXPECT_EQ(root_windows[0], d1->GetNativeView()->GetRootWindow()); } TEST_F(ExtendedDesktopTest, MoveWindowWithTransient) { UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); // Create and activate a normal window |w1|. aura::Window* w1 = CreateTestWindowInShellWithBounds(gfx::Rect(10, 10, 100, 100)); wm::ActivateWindow(w1); // |w1_t1| is a transient child window of |w1|. std::unique_ptr w1_t1 = ChildTestWindowBuilder(w1, gfx::Rect(50, 50, 50, 50), desks_util::GetActiveDeskContainerId()) .Build(); ::wm::AddTransientChild(w1, w1_t1.get()); // |w1_t11| is a transient child window of transient child window |w1_t1|. std::unique_ptr w1_t11 = ChildTestWindowBuilder(w1_t1.get(), gfx::Rect(2, 7, 35, 35), desks_util::GetActiveDeskContainerId()) .Build(); ::wm::AddTransientChild(w1_t1.get(), w1_t11.get()); // |w11| is a non-transient child window of |w1|. std::unique_ptr w11 = ChildTestWindowBuilder(w1, gfx::Rect(10, 10, 40, 40), desks_util::GetActiveDeskContainerId()) .Build(); // |w11_t1| is a transient child window of |w11|. std::unique_ptr w11_t1 = ChildTestWindowBuilder(w11.get(), gfx::Rect(30, 10, 80, 80), desks_util::GetActiveDeskContainerId()) .Build(); ::wm::AddTransientChild(w11.get(), w11_t1.get()); EXPECT_EQ(root_windows[0], w1->GetRootWindow()); EXPECT_EQ(root_windows[0], w1_t1->GetRootWindow()); EXPECT_EQ(root_windows[0], w1_t11->GetRootWindow()); EXPECT_EQ(root_windows[0], w11->GetRootWindow()); EXPECT_EQ(root_windows[0], w11_t1->GetRootWindow()); EXPECT_EQ("10,10 100x100", w1->GetBoundsInScreen().ToString()); EXPECT_EQ("60,60 50x50", w1_t1->GetBoundsInScreen().ToString()); EXPECT_EQ("62,67 35x35", w1_t11->GetBoundsInScreen().ToString()); EXPECT_EQ("20,20 40x40", w11->GetBoundsInScreen().ToString()); EXPECT_EQ("50,30 80x80", w11_t1->GetBoundsInScreen().ToString()); w1->SetBoundsInScreen(gfx::Rect(1200, 10, 100, 100), GetSecondaryDisplay()); EXPECT_EQ(root_windows[1], w1->GetRootWindow()); EXPECT_EQ(root_windows[1], w1_t1->GetRootWindow()); EXPECT_EQ(root_windows[1], w1_t11->GetRootWindow()); EXPECT_EQ(root_windows[1], w11->GetRootWindow()); EXPECT_EQ(root_windows[1], w11_t1->GetRootWindow()); EXPECT_EQ("1200,10 100x100", w1->GetBoundsInScreen().ToString()); EXPECT_EQ("1250,60 50x50", w1_t1->GetBoundsInScreen().ToString()); EXPECT_EQ("1252,67 35x35", w1_t11->GetBoundsInScreen().ToString()); EXPECT_EQ("1210,20 40x40", w11->GetBoundsInScreen().ToString()); EXPECT_EQ("1240,30 80x80", w11_t1->GetBoundsInScreen().ToString()); } // Test transient child is parented after its transient parent moved to another // root window. TEST_F(ExtendedDesktopTest, PostMoveParentTransientChild) { UpdateDisplay("600x400,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); // Create and activate a normal window. aura::Window* window = CreateTestWindowInShellWithBounds(gfx::Rect(10, 10, 100, 100)); wm::ActivateWindow(window); // Create a transient child window of |window| without parenting to |window| // yet. std::unique_ptr child = std::make_unique(nullptr); child->SetType(aura::client::WINDOW_TYPE_NORMAL); child->Init(ui::LAYER_TEXTURED); child->SetBounds(gfx::Rect(50, 50, 50, 50)); child->Show(); ::wm::AddTransientChild(window, child.get()); // Move |window| to another display and ensure that crash doesn't happen. window->SetBoundsInScreen(gfx::Rect(610, 10, 100, 100), GetSecondaryDisplay()); // Parent |child| to |window| now. window->AddChild(child.get()); EXPECT_EQ(root_windows[1], window->GetRootWindow()); EXPECT_EQ(root_windows[1], child->GetRootWindow()); EXPECT_EQ("610,10 100x100", window->GetBoundsInScreen().ToString()); EXPECT_EQ("660,60 50x50", child->GetBoundsInScreen().ToString()); } // Test if the Window::ConvertPointToTarget works across root windows. TEST_F(ExtendedDesktopTest, ConvertPoint) { display::Screen* screen = display::Screen::GetScreen(); UpdateDisplay("1000x600,600x400"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); display::Display display_1 = screen->GetDisplayNearestWindow(root_windows[0]); EXPECT_EQ("0,0", display_1.bounds().origin().ToString()); display::Display display_2 = screen->GetDisplayNearestWindow(root_windows[1]); EXPECT_EQ("1000,0", display_2.bounds().origin().ToString()); aura::Window* d1 = CreateTestWidget(gfx::Rect(10, 10, 100, 100))->GetNativeView(); aura::Window* d2 = CreateTestWidget(gfx::Rect(1020, 20, 100, 100))->GetNativeView(); EXPECT_EQ(root_windows[0], d1->GetRootWindow()); EXPECT_EQ(root_windows[1], d2->GetRootWindow()); // Convert point in the Root2's window to the Root1's window Coord. gfx::Point p(0, 0); aura::Window::ConvertPointToTarget(root_windows[1], root_windows[0], &p); EXPECT_EQ("1000,0", p.ToString()); p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(d2, d1, &p); EXPECT_EQ("1010,10", p.ToString()); // Convert point in the Root1's window to the Root2's window Coord. p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(root_windows[0], root_windows[1], &p); EXPECT_EQ("-1000,0", p.ToString()); p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(d1, d2, &p); EXPECT_EQ("-1010,-10", p.ToString()); // Move the 2nd display to the bottom and test again. SetSecondaryDisplayLayout(display::DisplayPlacement::BOTTOM); display_2 = screen->GetDisplayNearestWindow(root_windows[1]); EXPECT_EQ("0,600", display_2.bounds().origin().ToString()); // Convert point in Root2's window to Root1's window Coord. p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(root_windows[1], root_windows[0], &p); EXPECT_EQ("0,600", p.ToString()); p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(d2, d1, &p); EXPECT_EQ("10,610", p.ToString()); // Convert point in Root1's window to Root2's window Coord. p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(root_windows[0], root_windows[1], &p); EXPECT_EQ("0,-600", p.ToString()); p.SetPoint(0, 0); aura::Window::ConvertPointToTarget(d1, d2, &p); EXPECT_EQ("-10,-610", p.ToString()); } TEST_F(ExtendedDesktopTest, OpenSystemTray) { // Two displays side by side and both are high enough for tray bubble. UpdateDisplay("500x600,600x700"); ASSERT_FALSE(IsBubbleShown()); ui::test::EventGenerator* event_generator = GetEventGenerator(); // Opens the tray by a dummy click event and makes sure that adding/removing // displays doesn't break anything. event_generator->MoveMouseTo(GetSystemTrayBoundsInScreen().CenterPoint()); event_generator->ClickLeftButton(); EXPECT_TRUE(IsBubbleShown()); // Verifies that the bubble is within the primary display. const gfx::Rect primary_display_bounds = GetPrimaryDisplay().bounds(); const gfx::Rect tray_bubble_bounds = GetPrimaryUnifiedSystemTray()->GetBubbleBoundsInScreen(); EXPECT_TRUE(primary_display_bounds.Contains(tray_bubble_bounds)) << "primary display bounds=" << primary_display_bounds.ToString() << ", tray bubble bounds=" << tray_bubble_bounds.ToString(); UpdateDisplay("500x600"); EXPECT_TRUE(IsBubbleShown()); UpdateDisplay("500x600,600x700"); EXPECT_TRUE(IsBubbleShown()); // Closes the tray and again makes sure that adding/removing displays doesn't // break anything. event_generator->ClickLeftButton(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(IsBubbleShown()); UpdateDisplay("500x600"); EXPECT_FALSE(IsBubbleShown()); UpdateDisplay("500x600,600x700"); EXPECT_FALSE(IsBubbleShown()); } TEST_F(ExtendedDesktopTest, StayInSameRootWindow) { UpdateDisplay("200x100,300x200"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); views::Widget* w1 = CreateTestWidget(gfx::Rect(10, 10, 50, 50)); EXPECT_EQ(root_windows[0], w1->GetNativeView()->GetRootWindow()); w1->SetBounds(gfx::Rect(250, 10, 50, 50)); EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow()); // The widget stays in the same root if kLockedToRootKey is set to true. w1->GetNativeView()->SetProperty(kLockedToRootKey, true); w1->SetBounds(gfx::Rect(10, 10, 50, 50)); EXPECT_EQ(root_windows[1], w1->GetNativeView()->GetRootWindow()); // The widget should now move to the 1st root window without the property. w1->GetNativeView()->ClearProperty(kLockedToRootKey); w1->SetBounds(gfx::Rect(10, 10, 50, 50)); EXPECT_EQ(root_windows[0], w1->GetNativeView()->GetRootWindow()); // a window in SettingsBubbleContainer, ShelfControlContainer and // OverviewFocusContainer should not move to another root window // regardless of the bounds specified. aura::Window* settings_bubble_container = Shell::GetPrimaryRootWindowController()->GetContainer( kShellWindowId_SettingBubbleContainer); aura::Window* window = aura::test::CreateTestWindowWithId(100, settings_bubble_container); window->SetBoundsInScreen(gfx::Rect(250, 10, 50, 50), GetSecondaryDisplay()); EXPECT_EQ(root_windows[0], window->GetRootWindow()); aura::Window* status_container = Shell::GetPrimaryRootWindowController()->GetContainer( kShellWindowId_ShelfContainer); window = aura::test::CreateTestWindowWithId(100, status_container); window->SetBoundsInScreen(gfx::Rect(250, 10, 50, 50), GetSecondaryDisplay()); EXPECT_EQ(root_windows[0], window->GetRootWindow()); } TEST_F(ExtendedDesktopTest, KeyEventsOnLockScreen) { UpdateDisplay("200x100,300x200"); aura::Window::Windows root_windows = Shell::GetAllRootWindows(); // Create normal windows on both displays. views::Widget* widget1 = CreateTestWidget( display::Screen::GetScreen()->GetPrimaryDisplay().bounds()); widget1->Show(); EXPECT_EQ(root_windows[0], widget1->GetNativeView()->GetRootWindow()); views::Widget* widget2 = CreateTestWidget(GetSecondaryDisplay().bounds()); widget2->Show(); EXPECT_EQ(root_windows[1], widget2->GetNativeView()->GetRootWindow()); // Create a LockScreen window. views::Widget* lock_widget = CreateTestWidget( display::Screen::GetScreen()->GetPrimaryDisplay().bounds()); views::Textfield* textfield = new views::Textfield; lock_widget->client_view()->AddChildView(textfield); Shell::GetContainer(Shell::GetPrimaryRootWindow(), kShellWindowId_LockScreenContainer) ->AddChild(lock_widget->GetNativeView()); lock_widget->Show(); textfield->RequestFocus(); aura::client::FocusClient* focus_client = aura::client::GetFocusClient(root_windows[0]); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); // The lock window should get events on both root windows. ui::test::EventGenerator* event_generator = GetEventGenerator(); event_generator->set_current_target(root_windows[0]); event_generator->PressAndReleaseKey(ui::VKEY_A); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); EXPECT_EQ("a", base::UTF16ToASCII(textfield->GetText())); event_generator->set_current_target(root_windows[1]); event_generator->PressAndReleaseKey(ui::VKEY_B); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); EXPECT_EQ("ab", base::UTF16ToASCII(textfield->GetText())); // Deleting 2nd display. The lock window still should get the events. UpdateDisplay("200x100"); event_generator->set_current_target(root_windows[0]); event_generator->PressAndReleaseKey(ui::VKEY_C); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); EXPECT_EQ("abc", base::UTF16ToASCII(textfield->GetText())); // Creating 2nd display again, and lock window still should get events // on both root windows. UpdateDisplay("200x100,300x200"); root_windows = Shell::GetAllRootWindows(); event_generator->set_current_target(root_windows[0]); event_generator->PressAndReleaseKey(ui::VKEY_D); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); EXPECT_EQ("abcd", base::UTF16ToASCII(textfield->GetText())); event_generator->set_current_target(root_windows[1]); event_generator->PressAndReleaseKey(ui::VKEY_E); EXPECT_EQ(lock_widget->GetNativeView(), focus_client->GetFocusedWindow()); EXPECT_EQ("abcde", base::UTF16ToASCII(textfield->GetText())); } // Verifies that clicking in the primary display and dragging to the secondary // display correctly sets the event location (window local) and root_location // (in screen coordinates) for the mouse event. https://crrev.com/11691010 TEST_F(ExtendedDesktopTest, PassiveGrab) { // This test deals with input events but not visuals so don't throttle input // on visuals. aura::Env::GetInstance()->set_throttle_input_on_resize_for_testing(false); EventLocationRecordingEventHandler event_handler; Shell::Get()->AddPreTargetHandler(&event_handler); // Create large displays so the widget won't be moved after creation. // https://crbug.com/890633 UpdateDisplay("1000x900,1000x900"); views::Widget* widget = CreateTestWidget(gfx::Rect(50, 50, 200, 200)); widget->Show(); ASSERT_EQ("50,50 200x200", widget->GetWindowBoundsInScreen().ToString()); // Move the mouse to the center of the window. ui::test::EventGenerator* generator = GetEventGenerator(); generator->MoveMouseTo(150, 150); // Location is relative to the window. EXPECT_EQ("100,100", event_handler.GetLocation()); // Root location is relative to the display. EXPECT_EQ("150,150", event_handler.GetRootLocation()); event_handler.Reset(); // Drag the mouse to the secondary display. Does not drag the window. generator->PressLeftButton(); generator->MoveMouseBy(1000, 0); // Location is relative to the original window. EXPECT_EQ("1100,100", event_handler.GetLocation()); // Root location is relative to the original display. EXPECT_EQ("1150,150", event_handler.GetRootLocation()); event_handler.Reset(); // End the drag. generator->ReleaseLeftButton(); EXPECT_EQ("-999,-999", event_handler.GetRootLocation()); EXPECT_EQ("-999,-999", event_handler.GetLocation()); event_handler.Reset(); // Move the mouse to the top-left of the secondary display. generator->MoveMouseTo(1001, 1); // Location is relative to the new display. EXPECT_EQ("1,1", event_handler.GetLocation()); // Root location is relative to the new display. EXPECT_EQ("1,1", event_handler.GetRootLocation()); event_handler.Reset(); Shell::Get()->RemovePreTargetHandler(&event_handler); } } // namespace ash