// Copyright 2018 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 "services/ws/window_tree.h" #include #include #include #include #include "base/run_loop.h" #include "base/unguessable_token.h" #include "components/viz/host/host_frame_sink_manager.h" #include "components/viz/test/fake_host_frame_sink_client.h" #include "services/ws/event_test_utils.h" #include "services/ws/public/cpp/property_type_converters.h" #include "services/ws/public/mojom/window_manager.mojom.h" #include "services/ws/server_window.h" #include "services/ws/server_window_test_helper.h" #include "services/ws/window_service.h" #include "services/ws/window_service_test_setup.h" #include "services/ws/window_tree_test_helper.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/layout_manager.h" #include "ui/aura/test/aura_test_helper.h" #include "ui/aura/test/test_screen.h" #include "ui/aura/test/test_window_delegate.h" #include "ui/aura/window.h" #include "ui/aura/window_tracker.h" #include "ui/events/test/event_generator.h" #include "ui/wm/core/capture_controller.h" #include "ui/wm/core/default_screen_position_client.h" #include "ui/wm/core/focus_controller.h" #include "ui/wm/core/window_util.h" namespace ws { namespace { DEFINE_UI_CLASS_PROPERTY_KEY(aura::Window*, kTestPropertyKey, nullptr); const char kTestPropertyServerKey[] = "test-property-server"; // Passed to Embed() to give the default behavior (see kEmbedFlag* in mojom for // details). constexpr uint32_t kDefaultEmbedFlags = 0; class TestLayoutManager : public aura::LayoutManager { public: TestLayoutManager() = default; ~TestLayoutManager() override = default; void set_next_bounds(const gfx::Rect& bounds) { next_bounds_ = bounds; } // aura::LayoutManager: void OnWindowResized() override {} void OnWindowAddedToLayout(aura::Window* child) override {} void OnWillRemoveWindowFromLayout(aura::Window* child) override {} void OnWindowRemovedFromLayout(aura::Window* child) override {} void OnChildWindowVisibilityChanged(aura::Window* child, bool visible) override {} void SetChildBounds(aura::Window* child, const gfx::Rect& requested_bounds) override { if (next_bounds_) { SetChildBoundsDirect(child, *next_bounds_); next_bounds_.reset(); } else { SetChildBoundsDirect(child, requested_bounds); } } private: base::Optional next_bounds_; DISALLOW_COPY_AND_ASSIGN(TestLayoutManager); }; // Used as callback from ScheduleEmbed(). void ScheduleEmbedCallback(base::UnguessableToken* result_token, const base::UnguessableToken& actual_token) { *result_token = actual_token; } // Used as callback to EmbedUsingToken(). void EmbedUsingTokenCallback(bool* was_called, bool* result_value, bool actual_result) { *was_called = true; *result_value = actual_result; } // A screen position client with a fixed screen offset applied via SetBounds. class TestScreenPositionClient : public wm::DefaultScreenPositionClient { public: explicit TestScreenPositionClient(const gfx::Vector2d& offset) : offset_(offset) {} ~TestScreenPositionClient() override = default; // wm::DefaultScreenPositionClient: void ConvertPointToScreen(const aura::Window* window, gfx::PointF* point) override { wm::DefaultScreenPositionClient::ConvertPointToScreen(window, point); *point += offset_; } void ConvertPointFromScreen(const aura::Window* window, gfx::PointF* point) override { *point -= offset_; wm::DefaultScreenPositionClient::ConvertPointFromScreen(window, point); } void SetBounds(aura::Window* window, const gfx::Rect& bounds, const display::Display& display) override { EXPECT_EQ(display, display::Screen::GetScreen()->GetPrimaryDisplay()); gfx::Rect offset_bounds = bounds; offset_bounds.Offset(-offset_); wm::DefaultScreenPositionClient::SetBounds(window, offset_bounds, display); } private: const gfx::Vector2d offset_; DISALLOW_COPY_AND_ASSIGN(TestScreenPositionClient); }; TEST(WindowTreeTest, NewWindow) { WindowServiceTestSetup setup; EXPECT_TRUE(setup.changes()->empty()); aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); EXPECT_EQ("ChangeCompleted id=1 success=true", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, NewWindowWithProperties) { WindowServiceTestSetup setup; EXPECT_TRUE(setup.changes()->empty()); aura::PropertyConverter::PrimitiveType value = true; std::vector transport = mojo::ConvertTo>(value); aura::Window* window = setup.window_tree_test_helper()->NewWindow( 1, {{mojom::WindowManager::kAlwaysOnTop_Property, transport}}); ASSERT_TRUE(window); EXPECT_EQ("ChangeCompleted id=1 success=true", SingleChangeToDescription(*setup.changes())); EXPECT_TRUE(window->GetProperty(aura::client::kAlwaysOnTopKey)); } TEST(WindowTreeTest, NewTopLevelWindow) { WindowServiceTestSetup setup; EXPECT_TRUE(setup.changes()->empty()); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); EXPECT_EQ("TopLevelCreated id=1 window_id=0,1 drawn=false", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, NewTopLevelWindowWithProperties) { WindowServiceTestSetup setup; EXPECT_TRUE(setup.changes()->empty()); aura::PropertyConverter::PrimitiveType value = true; std::vector transport = mojo::ConvertTo>(value); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow( 1, {{mojom::WindowManager::kAlwaysOnTop_Property, transport}}); ASSERT_TRUE(top_level); EXPECT_EQ("TopLevelCreated id=1 window_id=0,1 drawn=false", SingleChangeToDescription(*setup.changes())); EXPECT_TRUE(top_level->GetProperty(aura::client::kAlwaysOnTopKey)); } TEST(WindowTreeTest, SetTopLevelWindowBounds) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); setup.changes()->clear(); const gfx::Rect bounds_from_client = gfx::Rect(100, 200, 300, 400); setup.window_tree_test_helper()->SetWindowBoundsWithAck( top_level, bounds_from_client, 2); EXPECT_EQ(bounds_from_client, top_level->GetBoundsInScreen()); ASSERT_EQ(2u, setup.changes()->size()); { const Change& change = (*setup.changes())[0]; EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, change.type); EXPECT_EQ(top_level->GetBoundsInScreen(), change.bounds2); EXPECT_TRUE(change.local_surface_id); setup.changes()->erase(setup.changes()->begin()); } // See comments in WindowTree::SetBoundsImpl() for why this returns false. EXPECT_EQ("ChangeCompleted id=2 success=false", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); const gfx::Rect bounds_from_server = gfx::Rect(101, 102, 103, 104); top_level->SetBounds(bounds_from_server); ASSERT_EQ(1u, setup.changes()->size()); EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, (*setup.changes())[0].type); EXPECT_EQ(bounds_from_server, (*setup.changes())[0].bounds2); setup.changes()->clear(); // Set a LayoutManager so that when the client requests a bounds change the // window is resized to a different bounds. // |layout_manager| is owned by top_level->parent(); TestLayoutManager* layout_manager = new TestLayoutManager(); const gfx::Rect restricted_bounds = gfx::Rect(401, 405, 406, 407); layout_manager->set_next_bounds(restricted_bounds); top_level->parent()->SetLayoutManager(layout_manager); setup.window_tree_test_helper()->SetWindowBoundsWithAck( top_level, bounds_from_client, 3); ASSERT_EQ(2u, setup.changes()->size()); // The layout manager changes the bounds to a different value than the client // requested, so the client should get OnWindowBoundsChanged() with // |restricted_bounds|. EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, (*setup.changes())[0].type); EXPECT_EQ(restricted_bounds, (*setup.changes())[0].bounds2); // And because the layout manager changed the bounds the result is false. EXPECT_EQ("ChangeCompleted id=3 success=false", ChangeToDescription((*setup.changes())[1])); setup.changes()->clear(); // Install a screen position client with a non-zero screen bounds offset. gfx::Vector2d screen_offset(10, 20); TestScreenPositionClient screen_position_client(screen_offset); aura::client::SetScreenPositionClient(setup.aura_test_helper()->root_window(), &screen_position_client); // Tests that top-level window bounds are set in screen coordinates. setup.window_tree_test_helper()->SetWindowBoundsWithAck( top_level, bounds_from_client, 4); EXPECT_EQ(bounds_from_client, top_level->GetBoundsInScreen()); EXPECT_EQ(bounds_from_client - screen_offset, top_level->bounds()); ASSERT_EQ(2u, setup.changes()->size()); { const Change& change = (*setup.changes())[0]; EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, change.type); EXPECT_EQ(top_level->GetBoundsInScreen(), change.bounds2); EXPECT_TRUE(change.local_surface_id); setup.changes()->erase(setup.changes()->begin()); } // See comments in WindowTree::SetBoundsImpl() for why this returns false. EXPECT_EQ("ChangeCompleted id=4 success=false", SingleChangeToDescription(*setup.changes())); aura::client::SetScreenPositionClient(setup.aura_test_helper()->root_window(), nullptr); } TEST(WindowTreeTest, SetTopLevelWindowBoundsFailsForSameSize) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); setup.changes()->clear(); const gfx::Rect bounds = gfx::Rect(1, 2, 300, 400); top_level->SetBounds(bounds); setup.changes()->clear(); // WindowTreeTestHelper::SetWindowBounds() uses a null LocalSurfaceId, which // differs from the current LocalSurfaceId (assigned by ClientRoot). Because // of this, the LocalSurfaceIds differ and the call returns false. EXPECT_FALSE( setup.window_tree_test_helper()->SetWindowBounds(top_level, bounds)); EXPECT_TRUE(setup.changes()->empty()); } TEST(WindowTreeTest, SetChildWindowBounds) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); const gfx::Rect bounds = gfx::Rect(1, 2, 300, 400); EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds(window, bounds)); EXPECT_EQ(bounds, window->bounds()); // Setting to same bounds should return true. EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds(window, bounds)); EXPECT_EQ(bounds, window->bounds()); } TEST(WindowTreeTest, SetBoundsAtEmbedWindow) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); const gfx::Rect bounds1 = gfx::Rect(1, 2, 300, 400); EXPECT_TRUE( setup.window_tree_test_helper()->SetWindowBounds(window, bounds1)); std::unique_ptr embedding_helper = setup.CreateEmbedding(window); ASSERT_TRUE(embedding_helper); // Child client should not be able to change bounds of embed window. EXPECT_FALSE(embedding_helper->window_tree_test_helper->SetWindowBounds( window, gfx::Rect())); // Bounds should not have changed. EXPECT_EQ(bounds1, window->bounds()); embedding_helper->window_tree_client.tracker()->changes()->clear(); embedding_helper->window_tree_client.set_track_root_bounds_changes(true); // Set the bounds from the parent and ensure client is notified. const gfx::Rect bounds2 = gfx::Rect(1, 2, 300, 401); base::Optional local_surface_id( viz::LocalSurfaceId(1, 2, base::UnguessableToken::Create())); EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds( window, bounds2, local_surface_id)); EXPECT_EQ(bounds2, window->bounds()); ASSERT_EQ(1u, embedding_helper->window_tree_client.tracker()->changes()->size()); const Change bounds_change = (*(embedding_helper->window_tree_client.tracker()->changes()))[0]; EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, bounds_change.type); EXPECT_EQ(bounds2, bounds_change.bounds2); EXPECT_EQ(local_surface_id, bounds_change.local_surface_id); embedding_helper->window_tree_client.tracker()->changes()->clear(); // Set the bounds from the parent, only updating the LocalSurfaceId (bounds // remains the same). The client should be notified. base::Optional local_surface_id2( viz::LocalSurfaceId(1, 3, base::UnguessableToken::Create())); EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds( window, bounds2, local_surface_id2)); EXPECT_EQ(bounds2, window->bounds()); ASSERT_EQ(1u, embedding_helper->window_tree_client.tracker()->changes()->size()); const Change bounds_change2 = (*(embedding_helper->window_tree_client.tracker()->changes()))[0]; EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, bounds_change2.type); EXPECT_EQ(bounds2, bounds_change2.bounds2); EXPECT_EQ(local_surface_id2, bounds_change2.local_surface_id); embedding_helper->window_tree_client.tracker()->changes()->clear(); // Try again with the same values. This should succeed, but not notify the // client. EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds( window, bounds2, local_surface_id2)); EXPECT_TRUE( embedding_helper->window_tree_client.tracker()->changes()->empty()); } // Tests the ability of the client to change properties on the server. TEST(WindowTreeTest, SetTopLevelWindowProperty) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); setup.changes()->clear(); EXPECT_FALSE(top_level->GetProperty(aura::client::kAlwaysOnTopKey)); aura::PropertyConverter::PrimitiveType client_value = true; std::vector client_transport_value = mojo::ConvertTo>(client_value); setup.window_tree_test_helper()->SetWindowProperty( top_level, mojom::WindowManager::kAlwaysOnTop_Property, client_transport_value, 2); EXPECT_EQ("ChangeCompleted id=2 success=true", SingleChangeToDescription(*setup.changes())); EXPECT_TRUE(top_level->GetProperty(aura::client::kAlwaysOnTopKey)); setup.changes()->clear(); top_level->SetProperty(aura::client::kAlwaysOnTopKey, false); EXPECT_EQ( "PropertyChanged window=0,1 key=prop:always_on_top " "value=0000000000000000", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, WindowToWindowData) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); setup.changes()->clear(); window->SetBounds(gfx::Rect(1, 2, 300, 400)); window->SetProperty(aura::client::kAlwaysOnTopKey, true); window->Show(); // Called to make the window visible. mojom::WindowDataPtr data = setup.window_tree_test_helper()->WindowToWindowData(window); EXPECT_EQ(gfx::Rect(1, 2, 300, 400), data->bounds); EXPECT_TRUE(data->visible); EXPECT_EQ( 1u, data->properties.count(mojom::WindowManager::kAlwaysOnTop_Property)); EXPECT_EQ(aura::PropertyConverter::PrimitiveType(true), mojo::ConvertTo( data->properties[mojom::WindowManager::kAlwaysOnTop_Property])); } TEST(WindowTreeTest, SetWindowPointerProperty) { WindowServiceTestSetup setup; setup.service()->property_converter()->RegisterWindowPtrProperty( kTestPropertyKey, kTestPropertyServerKey); WindowTreeTestHelper* helper = setup.window_tree_test_helper(); aura::Window* top_level1 = helper->NewTopLevelWindow(); aura::Window* top_level2 = helper->NewTopLevelWindow(); Id id1 = helper->TransportIdForWindow(top_level1); Id id2 = helper->TransportIdForWindow(top_level2); base::Optional> value = mojo::ConvertTo>(id2); setup.window_tree_test_helper()->window_tree()->SetWindowProperty( 1, id1, kTestPropertyServerKey, value); EXPECT_EQ(top_level2, top_level1->GetProperty(kTestPropertyKey)); value.reset(); setup.window_tree_test_helper()->window_tree()->SetWindowProperty( 1, id1, kTestPropertyServerKey, value); EXPECT_FALSE(top_level1->GetProperty(kTestPropertyKey)); } TEST(WindowTreeTest, SetWindowPointerPropertyWithInvalidValues) { WindowServiceTestSetup setup; setup.service()->property_converter()->RegisterWindowPtrProperty( kTestPropertyKey, kTestPropertyServerKey); WindowTreeTestHelper* helper = setup.window_tree_test_helper(); aura::Window* top_level = helper->NewTopLevelWindow(); Id id = helper->TransportIdForWindow(top_level); base::Optional> value = mojo::ConvertTo>(kInvalidTransportId); setup.window_tree_test_helper()->window_tree()->SetWindowProperty( 1, id, kTestPropertyServerKey, value); EXPECT_FALSE(top_level->GetProperty(kTestPropertyKey)); value = mojo::ConvertTo>(10); setup.window_tree_test_helper()->window_tree()->SetWindowProperty( 1, id, kTestPropertyServerKey, value); EXPECT_FALSE(top_level->GetProperty(kTestPropertyKey)); value->clear(); value->push_back(1); setup.window_tree_test_helper()->window_tree()->SetWindowProperty( 1, id, kTestPropertyServerKey, value); EXPECT_FALSE(top_level->GetProperty(kTestPropertyKey)); } TEST(WindowTreeTest, OnWindowInputEventAck) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); WindowTreeTestHelper* tree = setup.window_tree_test_helper(); aura::Window* top_level = tree->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->Focus(); top_level->SetBounds(gfx::Rect(100, 100)); // Send a key event and a mouse event to the client. ui::test::EventGenerator event_generator(setup.root()); event_generator.PressKey(ui::VKEY_A, ui::EF_NONE); event_generator.MoveMouseTo(10, 10); ASSERT_EQ(2u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event1 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event1.event->IsKeyEvent()); TestWindowTreeClient::InputEvent event2 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event2.event->IsLocatedEvent()); // Acking the events in the order they were received works fine. EXPECT_EQ(1u, tree->in_flight_key_events().size()); tree->OnWindowInputEventAck(event1.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(0u, tree->in_flight_key_events().size()); EXPECT_EQ(1u, tree->in_flight_other_events().size()); tree->OnWindowInputEventAck(event2.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(0u, tree->in_flight_other_events().size()); // Send another key event and a mouse event. event_generator.ReleaseKey(ui::VKEY_A, ui::EF_NONE); event_generator.MoveMouseTo(11, 11); ASSERT_EQ(2u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event3 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event3.event->IsKeyEvent()); TestWindowTreeClient::InputEvent event4 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event4.event->IsLocatedEvent()); // Acking the mouse and key events out of order from one another is okay. EXPECT_EQ(1u, tree->in_flight_other_events().size()); tree->OnWindowInputEventAck(event4.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(0u, tree->in_flight_other_events().size()); EXPECT_EQ(1u, tree->in_flight_key_events().size()); tree->OnWindowInputEventAck(event3.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(0u, tree->in_flight_key_events().size()); // Send two more mouse events. event_generator.MoveMouseTo(12, 12); event_generator.MoveMouseTo(13, 13); ASSERT_EQ(2u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event5 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event5.event->IsLocatedEvent()); TestWindowTreeClient::InputEvent event6 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event6.event->IsLocatedEvent()); // The client cannot ack the second mouse event before the first. EXPECT_EQ(2u, tree->in_flight_other_events().size()); tree->OnWindowInputEventAck(event6.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(2u, tree->in_flight_other_events().size()); // Send two more key events. event_generator.PressKey(ui::VKEY_A, ui::EF_NONE); event_generator.ReleaseKey(ui::VKEY_A, ui::EF_NONE); ASSERT_EQ(2u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event7 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event7.event->IsKeyEvent()); TestWindowTreeClient::InputEvent event8 = window_tree_client->PopInputEvent(); ASSERT_TRUE(event8.event->IsKeyEvent()); // The client cannot ack the second key event before the first. EXPECT_EQ(2u, tree->in_flight_key_events().size()); tree->OnWindowInputEventAck(event8.event_id, ws::mojom::EventResult::HANDLED); EXPECT_EQ(2u, tree->in_flight_key_events().size()); } TEST(WindowTreeTest, EventLocation) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); WindowTreeTestHelper* helper = setup.window_tree_test_helper(); aura::Window* top_level = helper->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 20, 100, 100)); // Add a child Window that covers the bottom half of the top-level window. aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); window->Show(); window->SetBounds(gfx::Rect(0, 50, 100, 50)); top_level->AddChild(window); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(33, 44); ASSERT_EQ(1u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event1 = window_tree_client->PopInputEvent(); EXPECT_EQ(helper->TransportIdForWindow(top_level), event1.window_id); ASSERT_TRUE(event1.event->IsLocatedEvent()); ui::LocatedEvent* located_event1 = event1.event->AsLocatedEvent(); // The location is in the target window's coordinate system. EXPECT_EQ(gfx::Point(23, 24), located_event1->location()); // The root location is in the client-root coordinate system. EXPECT_EQ(gfx::Point(23, 24), located_event1->root_location()); event_generator.MoveMouseTo(55, 86); // 2 input events should happen -- exit on |top_level| and enter on |window|. ASSERT_EQ(2u, window_tree_client->input_events().size()); // Check the exit event on |top_level|. TestWindowTreeClient::InputEvent event2 = window_tree_client->PopInputEvent(); EXPECT_EQ(helper->TransportIdForWindow(top_level), event2.window_id); ASSERT_TRUE(event2.event->IsLocatedEvent()); ui::LocatedEvent* located_event2 = event2.event->AsLocatedEvent(); // The location is in the target window's coordinate system. EXPECT_EQ(gfx::Point(45, 66), located_event2->location()); // The root location is in the client-root coordinate system. EXPECT_EQ(gfx::Point(45, 66), located_event2->root_location()); // Check the enter event on |window|. TestWindowTreeClient::InputEvent event3 = window_tree_client->PopInputEvent(); EXPECT_EQ(helper->TransportIdForWindow(window), event3.window_id); ASSERT_TRUE(event3.event->IsLocatedEvent()); ui::LocatedEvent* located_event3 = event3.event->AsLocatedEvent(); // The location is in the target window's coordinate system. EXPECT_EQ(gfx::Point(45, 16), located_event3->location()); // The root location is in the client-root coordinate system. EXPECT_EQ(gfx::Point(45, 66), located_event3->root_location()); } TEST(WindowTreeTest, EventLocationForTransientChildWindow) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); WindowTreeTestHelper* helper = setup.window_tree_test_helper(); aura::Window* top_level = helper->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 20, 100, 100)); aura::Window* transient = helper->NewTopLevelWindow(); ASSERT_TRUE(transient); transient->Show(); transient->SetBounds(gfx::Rect(50, 30, 60, 90)); helper->window_tree()->AddTransientWindow( 10, helper->TransportIdForWindow(top_level), helper->TransportIdForWindow(transient)); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(61, 44); ASSERT_EQ(1u, window_tree_client->input_events().size()); TestWindowTreeClient::InputEvent event = window_tree_client->PopInputEvent(); EXPECT_EQ(helper->TransportIdForWindow(transient), event.window_id); ASSERT_TRUE(event.event->IsLocatedEvent()); ui::LocatedEvent* located_event = event.event->AsLocatedEvent(); // The location is in the target window's coordinate system. EXPECT_EQ(gfx::Point(11, 14), located_event->location()); // The root location is in the client-root coordinate system. Transient // parents won't affect the coordinate system. EXPECT_EQ(gfx::Point(11, 14), located_event->root_location()); } TEST(WindowTreeTest, MovePressDragRelease) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(50, 50); EXPECT_EQ("MOUSE_MOVED 40,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); event_generator.PressLeftButton(); EXPECT_EQ("MOUSE_PRESSED 40,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); event_generator.MoveMouseTo(0, 0); EXPECT_EQ("MOUSE_DRAGGED -10,-10", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); event_generator.ReleaseLeftButton(); EXPECT_EQ("MOUSE_RELEASED -10,-10", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); } // Used to verify destruction with a touch pointer down doesn't crash. TEST(WindowTreeTest, ShutdownWithTouchDown) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); ui::test::EventGenerator event_generator(setup.root()); event_generator.set_current_location(gfx::Point(50, 51)); event_generator.PressTouch(); } TEST(WindowTreeTest, TouchPressDragRelease) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 11, 100, 100)); ui::test::EventGenerator event_generator(setup.root()); event_generator.set_current_location(gfx::Point(50, 51)); event_generator.PressTouch(); EXPECT_EQ("ET_TOUCH_PRESSED 40,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); event_generator.MoveTouch(gfx::Point(5, 6)); EXPECT_EQ("ET_TOUCH_MOVED -5,-5", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); event_generator.ReleaseTouch(); EXPECT_EQ("ET_TOUCH_RELEASED -5,-5", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); } class EventRecordingWindowDelegate : public aura::test::TestWindowDelegate { public: EventRecordingWindowDelegate() = default; ~EventRecordingWindowDelegate() override = default; std::queue>& events() { return events_; } std::unique_ptr PopEvent() { if (events_.empty()) return nullptr; auto event = std::move(events_.front()); events_.pop(); return event; } void ClearEvents() { std::queue> events; std::swap(events_, events); } // aura::test::TestWindowDelegate: void OnEvent(ui::Event* event) override { events_.push(ui::Event::Clone(*event)); } private: std::queue> events_; DISALLOW_COPY_AND_ASSIGN(EventRecordingWindowDelegate); }; TEST(WindowTreeTest, MoveFromClientToNonClient) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); window_delegate.ClearEvents(); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(50, 50); EXPECT_EQ("MOUSE_MOVED 40,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); // The delegate should see the same events (but as mouse events). EXPECT_EQ("MOUSE_ENTERED 40,40", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); EXPECT_EQ("MOUSE_MOVED 40,40", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); // Move the mouse over the non-client area. // The event is still sent to the client, and the delegate. event_generator.MoveMouseTo(15, 16); EXPECT_EQ("MOUSE_MOVED 5,6", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); // Delegate should also get the events. EXPECT_EQ("MOUSE_MOVED 5,6", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); // Only the delegate should get the press in this case. event_generator.PressLeftButton(); ASSERT_FALSE(window_tree_client->PopInputEvent().event.get()); EXPECT_EQ("MOUSE_PRESSED 5,6", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); // Move mouse into client area, only the delegate should get the move (drag). event_generator.MoveMouseTo(35, 51); ASSERT_FALSE(window_tree_client->PopInputEvent().event.get()); EXPECT_EQ("MOUSE_DRAGGED 25,41", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); // Release over client area, again only delegate should get it. event_generator.ReleaseLeftButton(); ASSERT_FALSE(window_tree_client->PopInputEvent().event.get()); EXPECT_EQ("MOUSE_RELEASED", EventToEventType(window_delegate.PopEvent().get())); event_generator.MoveMouseTo(26, 50); EXPECT_EQ("MOUSE_MOVED 16,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); // Delegate should also get the events. EXPECT_EQ("MOUSE_MOVED 16,40", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); // Press in client area. Only the client should get the event. event_generator.PressLeftButton(); EXPECT_EQ("MOUSE_PRESSED 16,40", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); ASSERT_FALSE(window_delegate.PopEvent().get()); } TEST(WindowTreeTest, MouseDownInNonClientWithChildWindow) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); // Add a child Window that is sized to fill the top-level. aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); window->Show(); window->SetBounds(gfx::Rect(top_level->bounds().size())); top_level->AddChild(window); window_delegate.ClearEvents(); // Move the mouse over the non-client area. Both the client and the delegate // should get the event. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(15, 16); EXPECT_EQ("MOUSE_MOVED 5,6", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); EXPECT_TRUE(window_tree_client->input_events().empty()); EXPECT_EQ("MOUSE_ENTERED", EventToEventType(window_delegate.PopEvent().get())); EXPECT_EQ("MOUSE_MOVED", EventToEventType(window_delegate.PopEvent().get())); EXPECT_TRUE(window_delegate.events().empty()); // Press over the non-client. The client should not be notified as the event // should be handled locally. event_generator.PressLeftButton(); ASSERT_FALSE(window_tree_client->PopInputEvent().event.get()); EXPECT_EQ("MOUSE_PRESSED 5,6", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); } TEST(WindowTreeTest, MouseDownInNonClientDragToClientWithChildWindow) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); // Add a child Window that is sized to fill the top-level. aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); window->Show(); window->SetBounds(gfx::Rect(top_level->bounds().size())); top_level->AddChild(window); // Press in non-client area. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(15, 16); event_generator.PressLeftButton(); TestWindowTreeClient* window_tree_client = setup.window_tree_client(); window_tree_client->ClearInputEvents(); window_delegate.ClearEvents(); // Drag over client area, only the delegate should get it (because the press // was in the non-client area). event_generator.MoveMouseTo(15, 26); EXPECT_EQ("MOUSE_DRAGGED", EventToEventType(window_delegate.PopEvent().get())); EXPECT_TRUE(window_tree_client->input_events().empty()); } TEST(WindowTreeTest, SetHitTestInsets) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); TestWindowTreeClient* window_tree_client = setup.window_tree_client(); window_tree_client->ClearInputEvents(); window_delegate.ClearEvents(); // Set the hit test insets in the window's bounds that excludes the top half. setup.window_tree_test_helper()->SetHitTestInsets( top_level, gfx::Insets(50, 0, 0, 0), gfx::Insets(50, 0, 0, 0)); // Events outside the hit test insets are not seen by the delegate or client. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(50, 30); EXPECT_TRUE(window_tree_client->input_events().empty()); EXPECT_TRUE(window_delegate.events().empty()); // Events in the hit test insets are seen by the delegate and client. event_generator.MoveMouseTo(50, 80); EXPECT_EQ("MOUSE_MOVED 40,70", LocatedEventToEventTypeAndLocation( window_tree_client->PopInputEvent().event.get())); EXPECT_EQ("MOUSE_ENTERED 40,70", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); EXPECT_EQ("MOUSE_MOVED 40,70", LocatedEventToEventTypeAndLocation( window_delegate.PopEvent().get())); } TEST(WindowTreeTest, PointerWatcher) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); // Start the pointer watcher only for pointer down/up. setup.window_tree_test_helper()->window_tree()->StartPointerWatcher(false); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(50, 50); ASSERT_TRUE(window_tree_client->observed_pointer_events().empty()); event_generator.MoveMouseTo(5, 6); ASSERT_TRUE(window_tree_client->observed_pointer_events().empty()); event_generator.PressLeftButton(); EXPECT_EQ("POINTER_DOWN 5,6", LocatedEventToEventTypeAndLocation( window_tree_client->PopObservedPointerEvent().event.get())); event_generator.ReleaseLeftButton(); EXPECT_EQ("POINTER_UP 5,6", LocatedEventToEventTypeAndLocation( window_tree_client->PopObservedPointerEvent().event.get())); // Enable observing move events. setup.window_tree_test_helper()->window_tree()->StartPointerWatcher(true); event_generator.MoveMouseTo(8, 9); EXPECT_EQ("POINTER_MOVED 8,9", LocatedEventToEventTypeAndLocation( window_tree_client->PopObservedPointerEvent().event.get())); const int kTouchId = 11; event_generator.MoveTouchId(gfx::Point(2, 3), kTouchId); EXPECT_EQ("POINTER_MOVED 2,3", LocatedEventToEventTypeAndLocation( window_tree_client->PopObservedPointerEvent().event.get())); } TEST(WindowTreeTest, MatchesPointerWatcherSet) { WindowServiceTestSetup setup; TestWindowTreeClient* window_tree_client = setup.window_tree_client(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(1); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(10, 10, 100, 100)); // Start the pointer watcher only for pointer down/up. setup.window_tree_test_helper()->window_tree()->StartPointerWatcher(false); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(50, 50); EXPECT_TRUE(window_tree_client->observed_pointer_events().empty()); window_tree_client->ClearInputEvents(); event_generator.PressLeftButton(); // The client should get the event, and |matches_pointer_watcher| should be // true (because it matched the pointer watcher). TestWindowTreeClient::InputEvent press_input = window_tree_client->PopInputEvent(); ASSERT_TRUE(press_input.event); EXPECT_EQ("MOUSE_PRESSED 40,40", LocatedEventToEventTypeAndLocation(press_input.event.get())); EXPECT_TRUE(press_input.matches_pointer_watcher); // Because the event matches a pointer event there should be no observed // pointer events. EXPECT_TRUE(window_tree_client->observed_pointer_events().empty()); } TEST(WindowTreeTest, Capture) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); // Setting capture on |window| should fail as it's not visible. EXPECT_FALSE(setup.window_tree_test_helper()->SetCapture(window)); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); EXPECT_FALSE(setup.window_tree_test_helper()->SetCapture(top_level)); top_level->Show(); EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(top_level)); EXPECT_FALSE(setup.window_tree_test_helper()->ReleaseCapture(window)); EXPECT_TRUE(setup.window_tree_test_helper()->ReleaseCapture(top_level)); top_level->AddChild(window); window->Show(); EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_TRUE(setup.window_tree_test_helper()->ReleaseCapture(window)); } TEST(WindowTreeTest, CaptureDisallowedWhenEmbedderInterceptsEvents) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); aura::Window* window = setup.window_tree_test_helper()->NewWindow(); top_level->AddChild(window); window->Show(); std::unique_ptr embedding_helper = setup.CreateEmbedding(window, mojom::kEmbedFlagEmbedderInterceptsEvents); ASSERT_TRUE(embedding_helper); EXPECT_FALSE(embedding_helper->window_tree_test_helper->SetCapture(window)); } TEST(WindowTreeTest, TransferCaptureToClient) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); wm::CaptureController::Get()->SetCapture(top_level); ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(6, 6); setup.window_tree_client()->ClearInputEvents(); window_delegate.ClearEvents(); event_generator.MoveMouseTo(7, 7); // Because capture was initiated locally event should go to |window_delegate| // only (not the client). EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); EXPECT_EQ("MOUSE_MOVED", EventToEventType(window_delegate.PopEvent().get())); EXPECT_TRUE(window_delegate.events().empty()); // Request capture from the client. EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(top_level)); event_generator.MoveMouseTo(8, 8); // Now the event should go to the client and not local. EXPECT_TRUE(window_delegate.events().empty()); EXPECT_EQ("MOUSE_MOVED", EventToEventType( setup.window_tree_client()->PopInputEvent().event.get())); EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); } TEST(WindowTreeTest, TransferCaptureBetweenParentAndChild) { EventRecordingWindowDelegate window_delegate; WindowServiceTestSetup setup; setup.delegate()->set_delegate_for_next_top_level(&window_delegate); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); top_level->AddChild(window); window->Show(); std::unique_ptr embedding_helper = setup.CreateEmbedding(window); ASSERT_TRUE(embedding_helper); // Move the mouse and set capture from the child. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(6, 6); setup.window_tree_client()->ClearInputEvents(); window_delegate.ClearEvents(); embedding_helper->window_tree_client.ClearInputEvents(); EXPECT_TRUE(embedding_helper->window_tree_test_helper->SetCapture(window)); event_generator.MoveMouseTo(7, 7); // As capture was set from the child, only the child should get the event. EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); EXPECT_TRUE(window_delegate.events().empty()); EXPECT_EQ( "MOUSE_MOVED", EventToEventType( embedding_helper->window_tree_client.PopInputEvent().event.get())); EXPECT_TRUE(embedding_helper->window_tree_client.input_events().empty()); // Set capture from the parent, only the parent should get the event now. EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(top_level)); event_generator.MoveMouseTo(8, 8); EXPECT_EQ("MOUSE_MOVED", EventToEventType( setup.window_tree_client()->PopInputEvent().event.get())); EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); EXPECT_TRUE(window_delegate.events().empty()); EXPECT_TRUE(embedding_helper->window_tree_client.input_events().empty()); } TEST(WindowTreeTest, CaptureNotification) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); top_level->AddChild(window); ASSERT_TRUE(top_level); top_level->Show(); window->Show(); setup.changes()->clear(); EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_TRUE(setup.changes()->empty()); wm::CaptureController::Get()->ReleaseCapture(window); EXPECT_EQ("OnCaptureChanged new_window=null old_window=0,1", SingleChangeToDescription(*(setup.changes()))); } TEST(WindowTreeTest, CaptureNotificationForEmbedRoot) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); top_level->AddChild(window); ASSERT_TRUE(top_level); top_level->Show(); window->Show(); setup.changes()->clear(); EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_TRUE(setup.changes()->empty()); // Set capture on the embed-root from the embedded client. The embedder // should be notified. std::unique_ptr embedding_helper = setup.CreateEmbedding(window); ASSERT_TRUE(embedding_helper); setup.changes()->clear(); embedding_helper->changes()->clear(); EXPECT_TRUE(embedding_helper->window_tree_test_helper->SetCapture(window)); EXPECT_EQ("OnCaptureChanged new_window=null old_window=0,1", SingleChangeToDescription(*(setup.changes()))); setup.changes()->clear(); EXPECT_TRUE(embedding_helper->changes()->empty()); // Set capture from the embedder. This triggers the embedded client to lose // capture. EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_TRUE(setup.changes()->empty()); // NOTE: the '2' is because the embedded client sees the high order bits of // the root. EXPECT_EQ("OnCaptureChanged new_window=null old_window=2,1", SingleChangeToDescription(*(embedding_helper->changes()))); embedding_helper->changes()->clear(); // And release capture locally. wm::CaptureController::Get()->ReleaseCapture(window); EXPECT_EQ("OnCaptureChanged new_window=null old_window=0,1", SingleChangeToDescription(*(setup.changes()))); EXPECT_TRUE(embedding_helper->changes()->empty()); } TEST(WindowTreeTest, CaptureNotificationForTopLevel) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(11); ASSERT_TRUE(top_level); top_level->Show(); setup.changes()->clear(); EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(top_level)); EXPECT_TRUE(setup.changes()->empty()); // Release capture locally. wm::CaptureController* capture_controller = wm::CaptureController::Get(); capture_controller->ReleaseCapture(top_level); EXPECT_EQ("OnCaptureChanged new_window=null old_window=0,11", SingleChangeToDescription(*(setup.changes()))); setup.changes()->clear(); // Set capture locally. capture_controller->SetCapture(top_level); EXPECT_TRUE(setup.changes()->empty()); // Set capture from client. EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(top_level)); EXPECT_TRUE(setup.changes()->empty()); // Release locally. capture_controller->ReleaseCapture(top_level); EXPECT_EQ("OnCaptureChanged new_window=null old_window=0,11", SingleChangeToDescription(*(setup.changes()))); } TEST(WindowTreeTest, EventsGoToCaptureWindow) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); top_level->AddChild(window); ASSERT_TRUE(top_level); top_level->Show(); window->Show(); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); window->SetBounds(gfx::Rect(10, 10, 90, 90)); // Left press on the top-level, leaving mouse down. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(5, 5); event_generator.PressLeftButton(); setup.window_tree_client()->ClearInputEvents(); // Set capture on |window|. EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); // Move mouse, should go to |window|. event_generator.MoveMouseTo(6, 6); auto drag_event = setup.window_tree_client()->PopInputEvent(); EXPECT_EQ(setup.window_tree_test_helper()->TransportIdForWindow(window), drag_event.window_id); EXPECT_EQ("MOUSE_DRAGGED -4,-4", LocatedEventToEventTypeAndLocation(drag_event.event.get())); } TEST(WindowTreeTest, PointerDownResetOnCaptureChange) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->AddChild(window); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); top_level->Show(); window->Show(); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); window->SetBounds(gfx::Rect(10, 10, 90, 90)); // Left press on the top-level, leaving mouse down. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(5, 5); event_generator.PressLeftButton(); ServerWindow* top_level_server_window = ServerWindow::GetMayBeNull(top_level); ASSERT_TRUE(top_level_server_window); ServerWindowTestHelper top_level_server_window_helper( top_level_server_window); EXPECT_TRUE(top_level_server_window_helper.IsHandlingPointerPress( ui::MouseEvent::kMousePointerId)); // Set capture on |window|, top_level should no longer be in pointer-down // (because capture changed). EXPECT_TRUE(setup.window_tree_test_helper()->SetCapture(window)); EXPECT_FALSE(top_level_server_window_helper.IsHandlingPointerPress( ui::MouseEvent::kMousePointerId)); } TEST(WindowTreeTest, PointerDownResetOnHide) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); setup.window_tree_test_helper()->SetClientArea(top_level, gfx::Insets(10, 0, 0, 0)); top_level->Show(); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); // Left press on the top-level, leaving mouse down. ui::test::EventGenerator event_generator(setup.root()); event_generator.MoveMouseTo(5, 5); event_generator.PressLeftButton(); ServerWindow* top_level_server_window = ServerWindow::GetMayBeNull(top_level); ASSERT_TRUE(top_level_server_window); ServerWindowTestHelper top_level_server_window_helper( top_level_server_window); EXPECT_TRUE(top_level_server_window_helper.IsHandlingPointerPress( ui::MouseEvent::kMousePointerId)); // Hiding should implicitly cancel capture. top_level->Hide(); EXPECT_FALSE(top_level_server_window_helper.IsHandlingPointerPress( ui::MouseEvent::kMousePointerId)); } TEST(WindowTreeTest, DeleteWindow) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); aura::WindowTracker tracker; tracker.Add(window); setup.changes()->clear(); setup.window_tree_test_helper()->DeleteWindow(window); EXPECT_TRUE(tracker.windows().empty()); EXPECT_EQ("ChangeCompleted id=1 success=true", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, DeleteTopLevel) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); const ClientWindowId top_level_id = setup.window_tree_test_helper()->ClientWindowIdForWindow(top_level); ASSERT_TRUE(top_level); aura::WindowTracker tracker; tracker.Add(top_level); setup.changes()->clear(); // Ask the tree to delete the window, which should result in deleting the // Window as well responding with success. setup.window_tree_test_helper()->DeleteWindow(top_level); EXPECT_TRUE(tracker.windows().empty()); EXPECT_EQ("ChangeCompleted id=1 success=true", SingleChangeToDescription(*setup.changes())); // Make sure the WindowTree doesn't have a mapping for the id anymore. EXPECT_FALSE( setup.window_tree_test_helper()->GetWindowByClientId(top_level_id)); } TEST(WindowTreeTest, ExternalDeleteTopLevel) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); setup.changes()->clear(); ASSERT_TRUE(top_level); delete top_level; EXPECT_EQ("WindowDeleted window=0,1", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, ExternalDeleteWindow) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); setup.changes()->clear(); delete window; EXPECT_EQ("WindowDeleted window=0,1", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, Embed) { WindowServiceTestSetup setup; aura::Window* window = setup.window_tree_test_helper()->NewWindow(); aura::Window* embed_window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window); ASSERT_TRUE(embed_window); window->AddChild(embed_window); embed_window->SetBounds(gfx::Rect(1, 2, 3, 4)); setup.changes()->clear(); std::unique_ptr embedding_helper = setup.CreateEmbedding(embed_window); ASSERT_TRUE(embedding_helper); ASSERT_EQ("OnEmbed", SingleChangeToDescription(*embedding_helper->changes())); const Change& test_change = (*embedding_helper->changes())[0]; ASSERT_EQ(1u, test_change.windows.size()); EXPECT_EQ(embed_window->bounds(), test_change.windows[0].bounds); EXPECT_EQ(kInvalidTransportId, test_change.windows[0].parent_id); EXPECT_EQ(embed_window->TargetVisibility(), test_change.windows[0].visible); EXPECT_NE(kInvalidTransportId, test_change.windows[0].window_id); // OnFrameSinkIdAllocated() should called on the parent tree. ASSERT_EQ(1u, setup.changes()->size()); EXPECT_EQ(CHANGE_TYPE_FRAME_SINK_ID_ALLOCATED, (*setup.changes())[0].type); const Id embed_window_transport_id = setup.window_tree_test_helper()->TransportIdForWindow(embed_window); EXPECT_EQ(embed_window_transport_id, (*setup.changes())[0].window_id); EXPECT_EQ(ServerWindow::GetMayBeNull(embed_window)->frame_sink_id(), (*setup.changes())[0].frame_sink_id); } // Base class for ScheduleEmbed() related tests. This creates a Window and // prepares a secondary client (|embed_client_|) that is intended to be embedded // at some point. class WindowTreeScheduleEmbedTest : public testing::Test { public: WindowTreeScheduleEmbedTest() = default; ~WindowTreeScheduleEmbedTest() override = default; // testing::Test: void SetUp() override { testing::Test::SetUp(); setup_ = std::make_unique(); embed_binding_.Bind(mojo::MakeRequest(&embed_client_ptr_)); window_ = setup_->window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window_); } void TearDown() override { window_ = nullptr; embed_binding_.Close(); setup_.reset(); testing::Test::TearDown(); } protected: std::unique_ptr setup_; TestWindowTreeClient embed_client_; mojom::WindowTreeClientPtr embed_client_ptr_; aura::Window* window_ = nullptr; private: mojo::Binding embed_binding_{&embed_client_}; DISALLOW_COPY_AND_ASSIGN(WindowTreeScheduleEmbedTest); }; TEST_F(WindowTreeScheduleEmbedTest, ScheduleEmbedWithUnregisteredToken) { bool embed_result = false; bool embed_callback_called = false; setup_->window_tree_test_helper()->window_tree()->EmbedUsingToken( setup_->window_tree_test_helper()->TransportIdForWindow(window_), base::UnguessableToken::Create(), kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); // ScheduleEmbed() with an invalid token should fail. EXPECT_FALSE(embed_result); } TEST_F(WindowTreeScheduleEmbedTest, ScheduleEmbedRegisteredTokenInvalidWindow) { // Register a token for embedding. base::UnguessableToken token; setup_->window_tree_test_helper()->window_tree()->ScheduleEmbed( std::move(embed_client_ptr_), base::BindOnce(&ScheduleEmbedCallback, &token)); EXPECT_FALSE(token.is_empty()); bool embed_result = false; bool embed_callback_called = false; setup_->window_tree_test_helper()->window_tree()->EmbedUsingToken( kInvalidTransportId, token, kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); // ScheduleEmbed() with a valid token, but invalid window should fail. EXPECT_FALSE(embed_result); } TEST_F(WindowTreeScheduleEmbedTest, ScheduleEmbed) { base::UnguessableToken token; // ScheduleEmbed() with a valid token and valid window. setup_->window_tree_test_helper()->window_tree()->ScheduleEmbed( std::move(embed_client_ptr_), base::BindOnce(&ScheduleEmbedCallback, &token)); EXPECT_FALSE(token.is_empty()); bool embed_result = false; bool embed_callback_called = false; setup_->window_tree_test_helper()->window_tree()->EmbedUsingToken( setup_->window_tree_test_helper()->TransportIdForWindow(window_), token, kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); EXPECT_TRUE(embed_result); base::RunLoop().RunUntilIdle(); // The embedded client should get OnEmbed(). EXPECT_EQ("OnEmbed", SingleChangeToDescription(*embed_client_.tracker()->changes())); } TEST(WindowTreeTest, ScheduleEmbedForExistingClient) { WindowServiceTestSetup setup; // Schedule an embed in the tree created by |setup|. base::UnguessableToken token; const uint32_t window_id_in_child = 149; setup.window_tree_test_helper() ->window_tree() ->ScheduleEmbedForExistingClient( window_id_in_child, base::BindOnce(&ScheduleEmbedCallback, &token)); EXPECT_FALSE(token.is_empty()); // Create another client and a window. TestWindowTreeClient client2; std::unique_ptr tree2 = setup.service()->CreateWindowTree(&client2); ASSERT_TRUE(tree2); WindowTreeTestHelper tree2_test_helper(tree2.get()); aura::Window* window_in_parent = tree2_test_helper.NewWindow(); ASSERT_TRUE(window_in_parent); // Call EmbedUsingToken() from tree2, which should result in the tree from // |setup| getting OnEmbedFromToken(). bool embed_result = false; bool embed_callback_called = false; WindowTreeTestHelper(tree2.get()) .window_tree() ->EmbedUsingToken( tree2_test_helper.TransportIdForWindow(window_in_parent), token, kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); EXPECT_TRUE(embed_result); EXPECT_EQ("OnEmbedFromToken", SingleChangeToDescription(*setup.changes())); EXPECT_EQ( static_cast(window_id_in_child), setup.window_tree_test_helper()->TransportIdForWindow(window_in_parent)); } TEST(WindowTreeTest, DeleteRootOfEmbeddingFromScheduleEmbedForExistingClient) { WindowServiceTestSetup setup; aura::Window* window_in_parent = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window_in_parent); // Create another client. TestWindowTreeClient client2; std::unique_ptr tree2 = setup.service()->CreateWindowTree(&client2); WindowTreeTestHelper tree2_test_helper(tree2.get()); base::UnguessableToken token; tree2_test_helper.window_tree()->ScheduleEmbedForExistingClient( 11, base::BindOnce(&ScheduleEmbedCallback, &token)); EXPECT_FALSE(token.is_empty()); // Call EmbedUsingToken() from setup.window_tree(), which should result in // |tree2| getting OnEmbedFromToken(). bool embed_result = false; bool embed_callback_called = false; setup.window_tree_test_helper()->window_tree()->EmbedUsingToken( setup.window_tree_test_helper()->TransportIdForWindow(window_in_parent), token, kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); EXPECT_TRUE(embed_result); EXPECT_EQ("OnEmbedFromToken", SingleChangeToDescription(*client2.tracker()->changes())); client2.tracker()->changes()->clear(); // Delete |window_in_parent|, which should trigger notifying tree2. setup.window_tree_test_helper()->DeleteWindow(window_in_parent); window_in_parent = nullptr; // 11 is the same value supplied to ScheduleEmbedForExistingClient(). EXPECT_EQ("WindowDeleted window=0,11", SingleChangeToDescription(*client2.tracker()->changes())); } TEST(WindowTreeTest, DeleteEmbededTreeFromScheduleEmbedForExistingClient) { WindowServiceTestSetup setup; aura::Window* window_in_parent = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window_in_parent); // Create another client and call ScheduleEmbedForExistingClient() from it. TestWindowTreeClient client2; std::unique_ptr tree2 = setup.service()->CreateWindowTree(&client2); WindowTreeTestHelper tree2_test_helper(tree2.get()); base::UnguessableToken token; tree2_test_helper.window_tree()->ScheduleEmbedForExistingClient( 11, base::BindOnce(&ScheduleEmbedCallback, &token)); EXPECT_FALSE(token.is_empty()); // Call EmbedUsingToken() from setup.window_tree(), which should result in // |tree2| getting OnEmbedFromToken(). bool embed_result = false; bool embed_callback_called = false; setup.window_tree_test_helper()->window_tree()->EmbedUsingToken( setup.window_tree_test_helper()->TransportIdForWindow(window_in_parent), token, kDefaultEmbedFlags, base::BindOnce(&EmbedUsingTokenCallback, &embed_callback_called, &embed_result)); EXPECT_TRUE(embed_callback_called); EXPECT_TRUE(embed_result); EXPECT_TRUE(ServerWindow::GetMayBeNull(window_in_parent)->HasEmbedding()); tree2.reset(); EXPECT_FALSE(ServerWindow::GetMayBeNull(window_in_parent)->HasEmbedding()); } TEST(WindowTreeTest, StackAtTop) { WindowServiceTestSetup setup; aura::Window* top_level1 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level1); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->StackAtTop( 10, setup.window_tree_test_helper()->TransportIdForWindow(top_level1)); // This succeeds because |top_level1| is already at top. |10| is the value // supplied to StackAtTop(). EXPECT_EQ("ChangeCompleted id=10 success=true", SingleChangeToDescription(*setup.changes())); // Create another top-level. |top_level2| should initially be above 1. aura::Window* top_level2 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level2); ASSERT_EQ(2u, top_level1->parent()->children().size()); EXPECT_EQ(top_level2, top_level1->parent()->children()[1]); // Stack 1 at the top. EXPECT_TRUE(setup.window_tree_test_helper()->StackAtTop(top_level1)); EXPECT_EQ(top_level1, top_level1->parent()->children()[1]); // Stacking a non-toplevel window at top should fail. aura::Window* non_top_level_window = setup.window_tree_test_helper()->NewWindow(); EXPECT_FALSE( setup.window_tree_test_helper()->StackAtTop(non_top_level_window)); } TEST(WindowTreeTest, OnUnhandledKeyEvent) { // Create a top-level, show it and give it focus. WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); top_level->Focus(); ASSERT_TRUE(top_level->HasFocus()); ui::test::EventGenerator event_generator(setup.root()); // Generate a key-press. The client should get the event, but not the // delegate. event_generator.PressKey(ui::VKEY_A, ui::EF_CONTROL_DOWN); EXPECT_TRUE(setup.delegate()->unhandled_key_events()->empty()); // Respond that the event was not handled. Should result in notifying the // delegate. EXPECT_TRUE(setup.window_tree_client()->AckFirstEvent( setup.window_tree(), mojom::EventResult::UNHANDLED)); ASSERT_EQ(1u, setup.delegate()->unhandled_key_events()->size()); EXPECT_EQ(ui::VKEY_A, (*setup.delegate()->unhandled_key_events())[0].key_code()); EXPECT_EQ(ui::EF_CONTROL_DOWN, (*setup.delegate()->unhandled_key_events())[0].flags()); setup.delegate()->unhandled_key_events()->clear(); // Repeat, but respond with handled. This should not result in the delegate // being notified. event_generator.PressKey(ui::VKEY_B, ui::EF_SHIFT_DOWN); EXPECT_TRUE(setup.window_tree_client()->AckFirstEvent( setup.window_tree(), mojom::EventResult::HANDLED)); EXPECT_TRUE(setup.delegate()->unhandled_key_events()->empty()); } TEST(WindowTreeTest, ReorderWindow) { // Create a top-level and two child windows. WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); aura::Window* window1 = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window1); top_level->AddChild(window1); aura::Window* window2 = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(window2); top_level->AddChild(window2); // Reorder |window1| on top of |window2|. EXPECT_TRUE(setup.window_tree_test_helper()->ReorderWindow( window1, window2, mojom::OrderDirection::ABOVE)); EXPECT_EQ(window2, top_level->children()[0]); EXPECT_EQ(window1, top_level->children()[1]); // Reorder |window2| on top of |window1|. EXPECT_TRUE(setup.window_tree_test_helper()->ReorderWindow( window2, window1, mojom::OrderDirection::ABOVE)); EXPECT_EQ(window1, top_level->children()[0]); EXPECT_EQ(window2, top_level->children()[1]); // Repeat, but use the WindowTree interface, which should result in an ack. setup.changes()->clear(); uint32_t change_id = 101; setup.window_tree_test_helper()->window_tree()->ReorderWindow( change_id, setup.window_tree_test_helper()->TransportIdForWindow(window1), setup.window_tree_test_helper()->TransportIdForWindow(window2), mojom::OrderDirection::ABOVE); EXPECT_EQ("ChangeCompleted id=101 success=true", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); // Supply invalid window ids, which should fail. setup.window_tree_test_helper()->window_tree()->ReorderWindow( change_id, 0, 1, mojom::OrderDirection::ABOVE); EXPECT_EQ("ChangeCompleted id=101 success=false", SingleChangeToDescription(*setup.changes())); // These calls should fail as the windows are not siblings. EXPECT_FALSE(setup.window_tree_test_helper()->ReorderWindow( window1, top_level, mojom::OrderDirection::ABOVE)); EXPECT_FALSE(setup.window_tree_test_helper()->ReorderWindow( top_level, window2, mojom::OrderDirection::ABOVE)); } TEST(WindowTreeTest, StackAbove) { // Create two top-levels. WindowServiceTestSetup setup; aura::Window* top_level1 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level1); aura::Window* top_level2 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level2); ASSERT_TRUE(top_level1->parent()); ASSERT_EQ(top_level1->parent(), top_level2->parent()); ASSERT_EQ(2u, top_level2->parent()->children().size()); // 1 on top of 2. EXPECT_TRUE( setup.window_tree_test_helper()->StackAbove(top_level1, top_level2)); EXPECT_EQ(top_level2, top_level2->parent()->children()[0]); EXPECT_EQ(top_level1, top_level2->parent()->children()[1]); // Repeat, should still succeed and nothing should change. EXPECT_TRUE( setup.window_tree_test_helper()->StackAbove(top_level1, top_level2)); EXPECT_EQ(top_level2, top_level2->parent()->children()[0]); EXPECT_EQ(top_level1, top_level2->parent()->children()[1]); // 2 on top of 1. EXPECT_TRUE( setup.window_tree_test_helper()->StackAbove(top_level2, top_level1)); EXPECT_EQ(top_level1, top_level2->parent()->children()[0]); EXPECT_EQ(top_level2, top_level2->parent()->children()[1]); // 1 on top of 2, using WindowTree interface, which should result in an ack. setup.changes()->clear(); uint32_t change_id = 102; setup.window_tree_test_helper()->window_tree()->StackAbove( change_id, setup.window_tree_test_helper()->TransportIdForWindow(top_level1), setup.window_tree_test_helper()->TransportIdForWindow(top_level2)); EXPECT_EQ("ChangeCompleted id=102 success=true", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); EXPECT_EQ(top_level2, top_level2->parent()->children()[0]); EXPECT_EQ(top_level1, top_level2->parent()->children()[1]); // Using invalid id should fail. setup.window_tree_test_helper()->window_tree()->StackAbove( change_id, setup.window_tree_test_helper()->TransportIdForWindow(top_level1), kInvalidTransportId); EXPECT_EQ("ChangeCompleted id=102 success=false", SingleChangeToDescription(*setup.changes())); // Using non-top-level should fail. aura::Window* non_top_level_window = setup.window_tree_test_helper()->NewWindow(); EXPECT_FALSE(setup.window_tree_test_helper()->StackAbove( top_level1, non_top_level_window)); } TEST(WindowTreeTest, RunMoveLoopTouch) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 12, top_level_id, mojom::MoveLoopSource::TOUCH, gfx::Point()); // |top_level| isn't visible, so should fail immediately. EXPECT_EQ("ChangeCompleted id=12 success=false", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); // Make the window visible and repeat. top_level->Show(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 13, top_level_id, mojom::MoveLoopSource::TOUCH, gfx::Point()); // WindowServiceDelegate should be asked to do the move. WindowServiceDelegate::DoneCallback move_loop_callback = setup.delegate()->TakeMoveLoopCallback(); ASSERT_TRUE(move_loop_callback); // As the move is in progress, changes should be empty. EXPECT_TRUE(setup.changes()->empty()); // Respond to the callback with success, which should notify client. std::move(move_loop_callback).Run(true); EXPECT_EQ("ChangeCompleted id=13 success=true", SingleChangeToDescription(*setup.changes())); // Trying to move non-top-level should fail. aura::Window* non_top_level_window = setup.window_tree_test_helper()->NewWindow(); non_top_level_window->Show(); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 14, setup.window_tree_test_helper()->TransportIdForWindow( non_top_level_window), mojom::MoveLoopSource::TOUCH, gfx::Point()); EXPECT_EQ("ChangeCompleted id=14 success=false", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, RunMoveLoopMouse) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 12, top_level_id, mojom::MoveLoopSource::MOUSE, gfx::Point()); // The mouse isn't down, so this should fail. EXPECT_EQ("ChangeCompleted id=12 success=false", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); // Press the left button and repeat. ui::test::EventGenerator event_generator(setup.root()); event_generator.PressLeftButton(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 13, top_level_id, mojom::MoveLoopSource::MOUSE, gfx::Point()); // WindowServiceDelegate should be asked to do the move. WindowServiceDelegate::DoneCallback move_loop_callback = setup.delegate()->TakeMoveLoopCallback(); ASSERT_TRUE(move_loop_callback); // As the move is in progress, changes should be empty. EXPECT_TRUE(setup.changes()->empty()); // Respond to the callback, which should notify client. std::move(move_loop_callback).Run(true); EXPECT_EQ("ChangeCompleted id=13 success=true", SingleChangeToDescription(*setup.changes())); setup.changes()->clear(); } TEST(WindowTreeTest, CancelMoveLoop) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformWindowMove( 12, top_level_id, mojom::MoveLoopSource::TOUCH, gfx::Point()); // WindowServiceDelegate should be asked to do the move. WindowServiceDelegate::DoneCallback move_loop_callback = setup.delegate()->TakeMoveLoopCallback(); ASSERT_TRUE(move_loop_callback); // As the move is in progress, changes should be empty. EXPECT_TRUE(setup.changes()->empty()); // Cancelling with an invalid id should do nothing. EXPECT_FALSE(setup.delegate()->cancel_window_move_loop_called()); setup.window_tree_test_helper()->window_tree()->CancelWindowMove( kInvalidTransportId); EXPECT_TRUE(setup.changes()->empty()); EXPECT_FALSE(setup.delegate()->cancel_window_move_loop_called()); // Cancel with the real id should notify the delegate. EXPECT_FALSE(setup.delegate()->cancel_window_move_loop_called()); setup.window_tree_test_helper()->window_tree()->CancelWindowMove( top_level_id); EXPECT_TRUE(setup.delegate()->cancel_window_move_loop_called()); // No changes yet, because |move_loop_callback| was not run yet. EXPECT_TRUE(setup.changes()->empty()); // Run the closure, which triggers notifying the client. std::move(move_loop_callback).Run(false); EXPECT_EQ("ChangeCompleted id=12 success=false", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, CancelMode) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); EXPECT_TRUE(setup.window_tree_test_helper()->SetFocus(top_level)); // Dispatch a CancelEvent. This should go to the |top_level| as it has focus. setup.root()->GetHost()->dispatcher()->DispatchCancelModeEvent(); EXPECT_EQ("CANCEL_MODE", EventToEventType( setup.window_tree_client()->PopInputEvent().event.get())); } TEST(WindowTreeTest, PerformDragDrop) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 12, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); // Let the posted drag loop task run. base::RunLoop().RunUntilIdle(); // WindowServiceDelegate should be asked to run the drag loop. WindowServiceDelegate::DragDropCompletedCallback drag_loop_callback = setup.delegate()->TakeDragLoopCallback(); ASSERT_TRUE(drag_loop_callback); // As the drag is in progress, changes should be empty. EXPECT_TRUE(setup.changes()->empty()); // Respond with a drop operation, client should be notified with success. std::move(drag_loop_callback).Run(ui::DragDropTypes::DRAG_MOVE); EXPECT_EQ("OnPerformDragDropCompleted id=12 success=true action=1", SingleChangeToDescription(*setup.changes())); // Starts another drag and but the drag is canceled this time. setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 13, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); base::RunLoop().RunUntilIdle(); drag_loop_callback = setup.delegate()->TakeDragLoopCallback(); ASSERT_TRUE(drag_loop_callback); std::move(drag_loop_callback).Run(ui::DragDropTypes::DRAG_NONE); EXPECT_EQ("OnPerformDragDropCompleted id=13 success=false action=0", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, PerformDragDropBeforePreviousOneFinish) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 12, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); // PerformDragDrop before the drag loop task runs should fail. setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 13, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); EXPECT_EQ("OnPerformDragDropCompleted id=13 success=false action=0", SingleChangeToDescription(*setup.changes())); // Let the posted drag loop task run. base::RunLoop().RunUntilIdle(); // WindowServiceDelegate should be asked to run the drag loop. WindowServiceDelegate::DragDropCompletedCallback drag_loop_callback = setup.delegate()->TakeDragLoopCallback(); ASSERT_TRUE(drag_loop_callback); // PerformDragDrop after the drop loop task runs should fail too because // the drag is not finished. setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 14, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); EXPECT_EQ("OnPerformDragDropCompleted id=14 success=false action=0", SingleChangeToDescription(*setup.changes())); // Finish the drop operation, client should be notified with success. setup.changes()->clear(); std::move(drag_loop_callback).Run(ui::DragDropTypes::DRAG_MOVE); EXPECT_EQ("OnPerformDragDropCompleted id=12 success=true action=1", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, CancelDragDrop) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 12, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); // Let the posted drag loop task run. base::RunLoop().RunUntilIdle(); // WindowServiceDelegate should be asked to run the drag loop. WindowServiceDelegate::DragDropCompletedCallback drag_loop_callback = setup.delegate()->TakeDragLoopCallback(); ASSERT_TRUE(drag_loop_callback); // Cancelling with an invalid id should do nothing. EXPECT_FALSE(setup.delegate()->cancel_drag_loop_called()); setup.window_tree_test_helper()->window_tree()->CancelDragDrop( kInvalidTransportId); EXPECT_TRUE(setup.changes()->empty()); EXPECT_FALSE(setup.delegate()->cancel_drag_loop_called()); // Cancel with the real id should notify the delegate. EXPECT_FALSE(setup.delegate()->cancel_drag_loop_called()); setup.window_tree_test_helper()->window_tree()->CancelDragDrop(top_level_id); EXPECT_TRUE(setup.delegate()->cancel_drag_loop_called()); // No changes yet because the |drag_loop_callback| has not run. EXPECT_TRUE(setup.changes()->empty()); // Run the closure to simulate drag cancel. std::move(drag_loop_callback).Run(ui::DragDropTypes::DRAG_NONE); EXPECT_EQ("OnPerformDragDropCompleted id=12 success=false action=0", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, CancelDragDropBeforeDragLoopRun) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); const Id top_level_id = setup.window_tree_test_helper()->TransportIdForWindow(top_level); setup.changes()->clear(); setup.window_tree_test_helper()->window_tree()->PerformDragDrop( 12, top_level_id, gfx::Point(), base::flat_map>(), gfx::ImageSkia(), gfx::Vector2d(), 0, ::ui::mojom::PointerKind::MOUSE); // Cancel the drag before the drag loop task runs. EXPECT_FALSE(setup.delegate()->cancel_drag_loop_called()); setup.window_tree_test_helper()->window_tree()->CancelDragDrop(top_level_id); EXPECT_TRUE(setup.delegate()->cancel_drag_loop_called()); // Let the posted drag loop task run. base::RunLoop().RunUntilIdle(); // WindowServiceDelegate should not be notified. WindowServiceDelegate::DragDropCompletedCallback drag_loop_callback = setup.delegate()->TakeDragLoopCallback(); EXPECT_FALSE(drag_loop_callback); // The request should fail. EXPECT_EQ("OnPerformDragDropCompleted id=12 success=false action=0", SingleChangeToDescription(*setup.changes())); } TEST(WindowTreeTest, DsfChanges) { WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); ServerWindow* top_level_server_window = ServerWindow::GetMayBeNull(top_level); const base::Optional initial_surface_id = top_level_server_window->local_surface_id(); EXPECT_TRUE(initial_surface_id); // Changing the scale factor should change the LocalSurfaceId. setup.aura_test_helper()->test_screen()->SetDeviceScaleFactor(2.0f); EXPECT_TRUE(top_level_server_window->local_surface_id()); EXPECT_NE(*top_level_server_window->local_surface_id(), *initial_surface_id); } TEST(WindowTreeTest, DontSendGestures) { // Create a top-level and a child window. WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->SetBounds(gfx::Rect(0, 0, 100, 100)); top_level->Show(); aura::Window* child_window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(child_window); top_level->AddChild(child_window); child_window->SetBounds(gfx::Rect(0, 0, 100, 100)); child_window->Show(); ui::test::EventGenerator event_generator(setup.root()); // GestureTapAt() generates a touch down/up, and should not generate a gesture // because the Window Service consumes touch events (consuming touch events // results in no GestureEvents being generated). Additionally, gestures should // never be forwarded to the client, as it's assumed the client runs its own // gesture recognizer. event_generator.GestureTapAt(gfx::Point(10, 10)); EXPECT_EQ("ET_TOUCH_PRESSED", EventToEventType( setup.window_tree_client()->PopInputEvent().event.get())); EXPECT_EQ("ET_TOUCH_RELEASED", EventToEventType( setup.window_tree_client()->PopInputEvent().event.get())); EXPECT_TRUE(setup.window_tree_client()->input_events().empty()); } TEST(WindowTreeTest, DeactivateWindow) { // Create two top-levels and focuses (activates) the second. WindowServiceTestSetup setup; aura::Window* top_level1 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level1); top_level1->Show(); aura::Window* top_level2 = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level2); top_level2->Show(); EXPECT_TRUE(setup.window_tree_test_helper()->SetFocus(top_level2)); EXPECT_TRUE(wm::IsActiveWindow(top_level2)); // Attempting to deactivate |top_level1| should do nothing. setup.window_tree_test_helper()->window_tree()->DeactivateWindow( setup.window_tree_test_helper()->TransportIdForWindow(top_level1)); EXPECT_TRUE(wm::IsActiveWindow(top_level2)); // Similarly, calling Deactivate() with an invalid id should do nothing. setup.window_tree_test_helper()->window_tree()->DeactivateWindow( kInvalidTransportId); EXPECT_TRUE(wm::IsActiveWindow(top_level2)); // Deactivate() with |top_level2| should activate |top_level1|. setup.window_tree_test_helper()->window_tree()->DeactivateWindow( setup.window_tree_test_helper()->TransportIdForWindow(top_level2)); EXPECT_TRUE(wm::IsActiveWindow(top_level1)); } TEST(WindowTreeTest, AttachFrameSinkId) { // Create two top-levels and focuses (activates) the second. WindowServiceTestSetup setup; aura::Window* top_level = setup.window_tree_test_helper()->NewTopLevelWindow(); ASSERT_TRUE(top_level); top_level->Show(); aura::Window* child_window = setup.window_tree_test_helper()->NewWindow(); ASSERT_TRUE(child_window); viz::FrameSinkId test_frame_sink_id(101, 102); viz::HostFrameSinkManager* host_frame_sink_manager = child_window->env()->context_factory_private()->GetHostFrameSinkManager(); // Attach a frame sink to |child_window|. This shouldn't immediately register. setup.window_tree_test_helper()->window_tree()->AttachFrameSinkId( setup.window_tree_test_helper()->TransportIdForWindow(child_window), test_frame_sink_id); EXPECT_FALSE( host_frame_sink_manager->IsFrameSinkIdRegistered(test_frame_sink_id)); // Add the window to a parent, which should trigger registering the hierarchy. viz::FakeHostFrameSinkClient test_host_frame_sink_client; host_frame_sink_manager->RegisterFrameSinkId(test_frame_sink_id, &test_host_frame_sink_client); EXPECT_EQ(test_frame_sink_id, ServerWindow::GetMayBeNull(child_window)->attached_frame_sink_id()); top_level->AddChild(child_window); EXPECT_TRUE(host_frame_sink_manager->IsFrameSinkHierarchyRegistered( ServerWindow::GetMayBeNull(top_level)->frame_sink_id(), test_frame_sink_id)); // Removing the window should remove the association. top_level->RemoveChild(child_window); EXPECT_FALSE(host_frame_sink_manager->IsFrameSinkHierarchyRegistered( ServerWindow::GetMayBeNull(top_level)->frame_sink_id(), test_frame_sink_id)); setup.window_tree_test_helper()->DeleteWindow(child_window); host_frame_sink_manager->InvalidateFrameSinkId(test_frame_sink_id); } } // namespace } // namespace ws