blob: b8cad5e851987d4c3d3f4fbb3922b3ed9aafd76b [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <memory>
#include <queue>
#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<gfx::Rect> 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<uint8_t> transport = mojo::ConvertTo<std::vector<uint8_t>>(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<uint8_t> transport = mojo::ConvertTo<std::vector<uint8_t>>(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<EmbeddingHelper> 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<viz::LocalSurfaceId> 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);
}
// 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<uint8_t> client_transport_value =
mojo::ConvertTo<std::vector<uint8_t>>(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<aura::PropertyConverter::PrimitiveType>(
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<std::vector<uint8_t>> value =
mojo::ConvertTo<std::vector<uint8_t>>(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<std::vector<uint8_t>> value =
mojo::ConvertTo<std::vector<uint8_t>>(kInvalidTransportId);
setup.window_tree_test_helper()->window_tree()->SetWindowProperty(
1, id, kTestPropertyServerKey, value);
EXPECT_FALSE(top_level->GetProperty(kTestPropertyKey));
value = mojo::ConvertTo<std::vector<uint8_t>>(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("POINTER_MOVED 40,40",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
event_generator.PressLeftButton();
EXPECT_EQ("POINTER_DOWN 40,40",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
event_generator.MoveMouseTo(0, 0);
EXPECT_EQ("POINTER_MOVED -10,-10",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
event_generator.ReleaseLeftButton();
EXPECT_EQ("POINTER_UP -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("POINTER_DOWN 40,40",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
event_generator.MoveTouch(gfx::Point(5, 6));
EXPECT_EQ("POINTER_MOVED -5,-5",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
event_generator.ReleaseTouch();
EXPECT_EQ("POINTER_UP -5,-5",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopInputEvent().event.get()));
}
class EventRecordingWindowDelegate : public aura::test::TestWindowDelegate {
public:
EventRecordingWindowDelegate() = default;
~EventRecordingWindowDelegate() override = default;
std::queue<std::unique_ptr<ui::Event>>& events() { return events_; }
std::unique_ptr<ui::Event> PopEvent() {
if (events_.empty())
return nullptr;
auto event = std::move(events_.front());
events_.pop();
return event;
}
void ClearEvents() {
std::queue<std::unique_ptr<ui::Event>> events;
std::swap(events_, events);
}
// aura::test::TestWindowDelegate:
void OnEvent(ui::Event* event) override {
events_.push(ui::Event::Clone(*event));
}
private:
std::queue<std::unique_ptr<ui::Event>> 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("POINTER_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("POINTER_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("POINTER_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("POINTER_DOWN 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("POINTER_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("POINTER_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);
setup.window_tree_test_helper()->SetEventTargetingPolicy(
top_level, mojom::EventTargetingPolicy::NONE);
EXPECT_EQ(mojom::EventTargetingPolicy::NONE,
top_level->event_targeting_policy());
// 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("POINTER_DOWN 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<EmbeddingHelper> 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("POINTER_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<EmbeddingHelper> 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(
"POINTER_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("POINTER_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<EmbeddingHelper> 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("POINTER_MOVED -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<EmbeddingHelper> 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<WindowServiceTestSetup>();
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<WindowServiceTestSetup> setup_;
TestWindowTreeClient embed_client_;
mojom::WindowTreeClientPtr embed_client_ptr_;
aura::Window* window_ = nullptr;
private:
mojo::Binding<mojom::WindowTreeClient> 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<WindowTree> 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<Id>(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<WindowTree> 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<WindowTree> 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<std::string, std::vector<uint8_t>>(), 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<viz::LocalSurfaceId> 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("POINTER_DOWN",
EventToEventType(
setup.window_tree_client()->PopInputEvent().event.get()));
EXPECT_EQ("POINTER_UP",
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