blob: 4fe1d98a62464504ef932be96704d59cc2599df4 [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/bind.h"
#include "base/run_loop.h"
#include "base/unguessable_token.h"
#include "components/viz/common/surfaces/child_local_surface_id_allocator.h"
#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h"
#include "components/viz/host/host_frame_sink_manager.h"
#include "components/viz/test/fake_host_frame_sink_client.h"
#include "services/ws/client_root_test_helper.h"
#include "services/ws/event_test_utils.h"
#include "services/ws/proxy_window.h"
#include "services/ws/proxy_window_test_helper.h"
#include "services/ws/public/cpp/property_type_converters.h"
#include "services/ws/public/mojom/window_manager.mojom.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/env.h"
#include "ui/aura/layout_manager.h"
#include "ui/aura/mus/client_surface_embedder.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/test/window_occlusion_tracker_test_api.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
#include "ui/base/hit_test.h"
#include "ui/events/mojo/event_constants.mojom.h"
#include "ui/events/test/event_generator.h"
#include "ui/gfx/transform.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()));
ASSERT_TRUE((*setup.changes())[0].local_surface_id_allocation);
EXPECT_TRUE((*setup.changes())[0].local_surface_id_allocation->IsValid());
EXPECT_EQ(ProxyWindow::GetMayBeNull(top_level)->local_surface_id_allocation(),
(*setup.changes())[0].local_surface_id_allocation);
}
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();
ProxyWindow* top_level_proxy = ProxyWindow::GetMayBeNull(top_level);
const gfx::Rect bounds_from_client = gfx::Rect(100, 200, 300, 400);
viz::ChildLocalSurfaceIdAllocator child_allocator;
child_allocator.UpdateFromParent(
*top_level_proxy->local_surface_id_allocation());
child_allocator.GenerateId();
setup.window_tree_test_helper()->SetWindowBoundsWithAck(
top_level, bounds_from_client,
child_allocator.GetCurrentLocalSurfaceIdAllocation(), 2);
EXPECT_EQ(bounds_from_client, top_level->GetBoundsInScreen());
EXPECT_EQ("ChangeCompleted id=2 success=true",
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].bounds);
ASSERT_TRUE((*setup.changes())[0].local_surface_id_allocation);
EXPECT_NE((*setup.changes())[0].local_surface_id_allocation,
child_allocator.GetCurrentLocalSurfaceIdAllocation());
EXPECT_EQ(top_level_proxy->local_surface_id_allocation(),
(*setup.changes())[0].local_surface_id_allocation);
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);
child_allocator.GenerateId();
setup.window_tree_test_helper()->SetWindowBoundsWithAck(
top_level, bounds_from_client,
child_allocator.GetCurrentLocalSurfaceIdAllocation(), 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].bounds);
// 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.
child_allocator.GenerateId();
setup.window_tree_test_helper()->SetWindowBoundsWithAck(
top_level, bounds_from_client,
child_allocator.GetCurrentLocalSurfaceIdAllocation(), 4);
EXPECT_EQ(bounds_from_client, top_level->GetBoundsInScreen());
EXPECT_EQ(bounds_from_client - screen_offset, top_level->bounds());
EXPECT_EQ("ChangeCompleted id=4 success=true",
SingleChangeToDescription(*setup.changes()));
aura::client::SetScreenPositionClient(setup.aura_test_helper()->root_window(),
nullptr);
}
TEST(WindowTreeTest, SetTopLevelWindowBoundsSameSize) {
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();
ProxyWindow* top_level_proxy = ProxyWindow::GetMayBeNull(top_level);
ASSERT_TRUE(top_level_proxy);
ASSERT_TRUE(top_level_proxy->local_surface_id_allocation().has_value());
viz::ChildLocalSurfaceIdAllocator child_allocator;
child_allocator.UpdateFromParent(
*top_level_proxy->local_surface_id_allocation());
child_allocator.GenerateId();
// WindowTreeTestHelper::SetWindowBounds() with same bounds should succeed.
EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds(
top_level, bounds, child_allocator.GetCurrentLocalSurfaceIdAllocation()));
EXPECT_TRUE(setup.changes()->empty());
ASSERT_TRUE(top_level_proxy->local_surface_id_allocation().has_value());
EXPECT_EQ(child_allocator.GetCurrentLocalSurfaceIdAllocation(),
*top_level_proxy->local_surface_id_allocation());
}
TEST(WindowTreeTest, SetTopLevelWindowBoundsBadEmbedToken) {
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();
ProxyWindow* top_level_proxy = ProxyWindow::GetMayBeNull(top_level);
ASSERT_TRUE(top_level_proxy);
ASSERT_TRUE(top_level_proxy->local_surface_id_allocation().has_value());
const viz::LocalSurfaceId initial_lsia =
top_level_proxy->local_surface_id_allocation()->local_surface_id();
viz::LocalSurfaceIdAllocation lsia_with_different_embed_token(
viz::LocalSurfaceId(initial_lsia.parent_sequence_number(),
initial_lsia.child_sequence_number(),
base::UnguessableToken::Create()),
base::TimeTicks::Now());
// Clients are not allowed to change the embed token.
EXPECT_FALSE(setup.window_tree_test_helper()->SetWindowBounds(
top_level, gfx::Rect(1, 2, 3, 4), lsia_with_different_embed_token));
EXPECT_EQ(initial_lsia,
top_level_proxy->local_surface_id_allocation()->local_surface_id());
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_TRUE(setup.changes()->empty());
}
TEST(WindowTreeTest, UpdateLocalSurfaceIdFromChildBadEmbedToken) {
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();
ProxyWindow* top_level_proxy = ProxyWindow::GetMayBeNull(top_level);
ASSERT_TRUE(top_level_proxy);
ASSERT_TRUE(top_level_proxy->local_surface_id_allocation().has_value());
const viz::LocalSurfaceId initial_lsia =
top_level_proxy->local_surface_id_allocation()->local_surface_id();
viz::LocalSurfaceIdAllocation lsia_with_different_embed_token(
viz::LocalSurfaceId(initial_lsia.parent_sequence_number(),
initial_lsia.child_sequence_number(),
base::UnguessableToken::Create()),
base::TimeTicks::Now());
// Clients are not allowed to change the embed token.
setup.window_tree_test_helper()->window_tree()->UpdateLocalSurfaceIdFromChild(
setup.window_tree_test_helper()->TransportIdForWindow(top_level),
lsia_with_different_embed_token);
EXPECT_EQ(initial_lsia,
top_level_proxy->local_surface_id_allocation()->local_surface_id());
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_TRUE(setup.changes()->empty());
}
TEST(WindowTreeTest, UpdateLocalSurfaceIdFromScheduleEmbedWindow) {
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 a window that will serve as the parent for the remote window and
// complete the embedding.
aura::Window local_window(nullptr);
local_window.Init(ui::LAYER_NOT_DRAWN);
local_window.SetBounds(gfx::Rect(1, 2, 3, 4));
ASSERT_TRUE(setup.service()->CompleteScheduleEmbedForExistingClient(
&local_window, token, /* embed_flags */ 0));
EXPECT_TRUE(WindowService::IsProxyWindow(&local_window));
// Call UpdateLocalSurfaceIdFromScheduleEmbedWindow() for the embedded window
// and ensure the value is updated.
ProxyWindow* local_window_proxy = ProxyWindow::GetMayBeNull(&local_window);
ASSERT_TRUE(local_window_proxy);
ASSERT_TRUE(local_window_proxy->local_surface_id_allocation().has_value());
viz::ChildLocalSurfaceIdAllocator child_allocator;
child_allocator.UpdateFromParent(
*local_window_proxy->local_surface_id_allocation());
child_allocator.GenerateId();
setup.window_tree_test_helper()->window_tree()->UpdateLocalSurfaceIdFromChild(
setup.window_tree_test_helper()->TransportIdForWindow(&local_window),
child_allocator.GetCurrentLocalSurfaceIdAllocation());
EXPECT_EQ(child_allocator.GetCurrentLocalSurfaceIdAllocation(),
local_window_proxy->local_surface_id_allocation());
}
TEST(WindowTreeTest, SetTopLevelWindowBoundsNullSurfaceId) {
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();
// Server allows null LocalSurfaceIds, which is used on the initial resize
// from the client.
EXPECT_TRUE(
setup.window_tree_test_helper()->SetWindowBounds(top_level, bounds));
// The server always responds with a bounds change when the client changes the
// bounds and does not supply a LocalSurfaceId.
ASSERT_FALSE(setup.changes()->empty());
const auto& change = (*setup.changes())[0];
EXPECT_EQ(CHANGE_TYPE_NODE_BOUNDS_CHANGED, change.type);
EXPECT_EQ(setup.window_tree_test_helper()->TransportIdForWindow(top_level),
change.window_id);
EXPECT_TRUE(change.local_surface_id_allocation);
EXPECT_EQ(bounds, change.bounds);
}
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::LocalSurfaceIdAllocation> local_surface_id_allocation(
viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(1, 2, base::UnguessableToken::Create()),
base::TimeTicks::Now()));
EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds(
window, bounds2, local_surface_id_allocation));
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.bounds);
EXPECT_EQ(local_surface_id_allocation,
bounds_change.local_surface_id_allocation);
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<viz::LocalSurfaceIdAllocation> local_surface_id_allocation2(
viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(1, 3, base::UnguessableToken::Create()),
base::TimeTicks::Now()));
EXPECT_TRUE(setup.window_tree_test_helper()->SetWindowBounds(
window, bounds2, local_surface_id_allocation2));
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.bounds);
EXPECT_EQ(local_surface_id_allocation2,
bounds_change2.local_surface_id_allocation);
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_id_allocation2));
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<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;
setup.set_ack_events_immediately(false);
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 a key-press.
event_generator.PressKey(ui::VKEY_A, ui::EF_NONE);
ASSERT_EQ(1u, window_tree_client->input_events().size());
TestWindowTreeClient::InputEvent event7 = window_tree_client->PopInputEvent();
ASSERT_TRUE(event7.event->IsKeyEvent());
// Acking the wrong event should be ignored.
tree->OnWindowInputEventAck(event7.event_id + 11,
ws::mojom::EventResult::HANDLED);
EXPECT_EQ(1u, 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_screen_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_screen_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<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("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, EventObserver) {
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 observing mouse press and release.
setup.window_tree_test_helper()->window_tree()->ObserveEventTypes(
{ui::mojom::EventType::MOUSE_PRESSED_EVENT,
ui::mojom::EventType::MOUSE_RELEASED_EVENT});
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_events().empty());
event_generator.MoveMouseTo(5, 6);
ASSERT_TRUE(window_tree_client->observed_events().empty());
event_generator.PressLeftButton();
EXPECT_EQ("MOUSE_PRESSED 5,6",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopObservedEvent().get()));
event_generator.ReleaseLeftButton();
EXPECT_EQ("MOUSE_RELEASED 5,6",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopObservedEvent().get()));
// Start also observing mouse move events.
setup.window_tree_test_helper()->window_tree()->ObserveEventTypes(
{ui::mojom::EventType::MOUSE_PRESSED_EVENT,
ui::mojom::EventType::MOUSE_RELEASED_EVENT,
ui::mojom::EventType::MOUSE_MOVED_EVENT});
event_generator.MoveMouseTo(8, 9);
EXPECT_EQ("MOUSE_MOVED 8,9",
LocatedEventToEventTypeAndLocation(
window_tree_client->PopObservedEvent().get()));
}
TEST(WindowTreeTest, MatchesEventObserverSet) {
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 observing touch press and release.
setup.window_tree_test_helper()->window_tree()->ObserveEventTypes(
{ui::mojom::EventType::TOUCH_PRESSED,
ui::mojom::EventType::TOUCH_RELEASED});
ui::test::EventGenerator event_generator(setup.root());
event_generator.set_current_screen_location(gfx::Point(50, 50));
event_generator.PressTouch();
// The client should get the input event, and |matches_event_observer| should
// be true (because it also matched the event observer).
TestWindowTreeClient::InputEvent press_input =
window_tree_client->PopInputEvent();
ASSERT_TRUE(press_input.event);
EXPECT_EQ("ET_TOUCH_PRESSED 40,40",
LocatedEventToEventTypeAndLocation(press_input.event.get()));
EXPECT_TRUE(press_input.matches_event_observer);
// The event targeted the client, so there are no separately observed events.
EXPECT_TRUE(window_tree_client->observed_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("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<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(
"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<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("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();
ProxyWindow* top_level_proxy_window = ProxyWindow::GetMayBeNull(top_level);
ASSERT_TRUE(top_level_proxy_window);
ProxyWindowTestHelper top_level_proxy_window_helper(top_level_proxy_window);
EXPECT_TRUE(top_level_proxy_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_proxy_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();
ProxyWindow* top_level_proxy_window = ProxyWindow::GetMayBeNull(top_level);
ASSERT_TRUE(top_level_proxy_window);
ProxyWindowTestHelper top_level_proxy_window_helper(top_level_proxy_window);
EXPECT_TRUE(top_level_proxy_window_helper.IsHandlingPointerPress(
ui::MouseEvent::kMousePointerId));
// Hiding should implicitly cancel capture.
top_level->Hide();
EXPECT_FALSE(top_level_proxy_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(ProxyWindow::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(ProxyWindow::GetMayBeNull(window_in_parent)->HasEmbedding());
tree2.reset();
EXPECT_FALSE(ProxyWindow::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;
// This test acks its own events.
setup.set_ack_events_immediately(false);
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, VisibilityChanged) {
WindowServiceTestSetup setup;
aura::Window* window = setup.window_tree_test_helper()->NewTopLevelWindow();
setup.changes()->clear();
EXPECT_FALSE(window->IsVisible());
window->Show();
EXPECT_TRUE(window->IsVisible());
EXPECT_EQ("VisibilityChanged window=0,1 visible=true",
SingleChangeToDescription(*setup.changes()));
}
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(), HTCAPTION);
// |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.changes()->clear();
setup.window_tree_test_helper()->window_tree()->PerformWindowMove(
13, top_level_id, mojom::MoveLoopSource::TOUCH, gfx::Point(), HTCAPTION);
// 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(), HTCAPTION);
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(), HTCAPTION);
// 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(), HTCAPTION);
// 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(), HTCAPTION);
// 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();
ProxyWindow* top_level_proxy_window = ProxyWindow::GetMayBeNull(top_level);
const base::Optional<viz::LocalSurfaceIdAllocation>
initial_surface_id_allocation =
top_level_proxy_window->local_surface_id_allocation();
EXPECT_TRUE(initial_surface_id_allocation);
// Changing the scale factor should change the LocalSurfaceId.
setup.aura_test_helper()->test_screen()->SetDeviceScaleFactor(2.0f);
EXPECT_TRUE(top_level_proxy_window->local_surface_id_allocation());
EXPECT_NE(*top_level_proxy_window->local_surface_id_allocation(),
*initial_surface_id_allocation);
}
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,
viz::ReportFirstSurfaceActivation::kYes);
EXPECT_EQ(test_frame_sink_id,
ProxyWindow::GetMayBeNull(child_window)->attached_frame_sink_id());
top_level->AddChild(child_window);
EXPECT_TRUE(host_frame_sink_manager->IsFrameSinkHierarchyRegistered(
ProxyWindow::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(
ProxyWindow::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);
}
TEST(WindowTreeTest, OcclusionStateChange) {
WindowServiceTestSetup setup;
// Create |tracked| and tracks its occlusion state.
aura::Window* tracked = setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(tracked);
tracked->SetBounds(gfx::Rect(0, 0, 10, 10));
tracked->TrackOcclusionState();
// Gets HIDDEN state since |tracked| is created hidden.
EXPECT_TRUE(ContainsChange(
*setup.changes(),
"OnOcclusionStatesChanged {{window_id=0,1, state=HIDDEN}}"));
// Gets VISIBLE state when |tracked| is shown.
tracked->Show();
EXPECT_TRUE(ContainsChange(
*setup.changes(),
"OnOcclusionStatesChanged {{window_id=0,1, state=VISIBLE}}"));
// Creates |blocking_window| and make it occlude |tracked|.
aura::Window* blocking_window =
setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(blocking_window);
blocking_window->SetProperty(aura::client::kWindowLayerDrawn, true);
blocking_window->SetBounds(gfx::Rect(0, 0, 15, 15));
blocking_window->Show();
// Gets OCCLUDED state since |blocking_window| covers |tracked|.
EXPECT_TRUE(ContainsChange(
*setup.changes(),
"OnOcclusionStatesChanged {{window_id=0,1, state=OCCLUDED}}"));
}
TEST(WindowTreeTest, OcclusionStateChangeBatchSameTree) {
WindowServiceTestSetup setup;
// Create two tracked windows and tracks their occlusion state.
aura::Window* tracked_1 =
setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(tracked_1);
tracked_1->SetBounds(gfx::Rect(0, 0, 10, 10));
tracked_1->TrackOcclusionState();
tracked_1->Show();
aura::Window* tracked_2 =
setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(tracked_2);
tracked_2->SetBounds(gfx::Rect(10, 0, 10, 10));
tracked_2->TrackOcclusionState();
tracked_2->Show();
// Creates |blocking_window| and make it occlude both tracked windows.
aura::Window* blocking_window =
setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(blocking_window);
blocking_window->SetProperty(aura::client::kWindowLayerDrawn, true);
blocking_window->SetBounds(gfx::Rect(0, 0, 20, 15));
blocking_window->Show();
// Occlusion changes of windows for the same tree are sent together.
EXPECT_TRUE(
ContainsChange(*setup.changes(),
"OnOcclusionStatesChanged {{window_id=0,1, "
"state=OCCLUDED}, {window_id=0,2, state=OCCLUDED}}"));
}
TEST(WindowTreeTest, OcclusionStateChangeBatchDifferentTree) {
WindowServiceTestSetup setup;
// Create |tracked_1| from default tree.
aura::Window* tracked_1 =
setup.window_tree_test_helper()->NewTopLevelWindow(100);
ASSERT_TRUE(tracked_1);
tracked_1->SetBounds(gfx::Rect(0, 0, 10, 10));
tracked_1->TrackOcclusionState();
tracked_1->Show();
// Create |tracked_2| from a second tree.
TestWindowTreeClient client2;
std::unique_ptr<WindowTree> tree2 =
setup.service()->CreateWindowTree(&client2);
tree2->InitFromFactory();
WindowTreeTestHelper tree2_test_helper(tree2.get());
aura::Window* tracked_2 = tree2_test_helper.NewTopLevelWindow(200);
ASSERT_TRUE(tracked_2);
tracked_2->SetBounds(gfx::Rect(10, 0, 10, 10));
tracked_2->TrackOcclusionState();
tracked_2->Show();
// Creates |blocking_window| and make it occlude both tracked windows.
aura::Window* blocking_window =
setup.window_tree_test_helper()->NewTopLevelWindow();
ASSERT_TRUE(blocking_window);
blocking_window->SetProperty(aura::client::kWindowLayerDrawn, true);
blocking_window->SetBounds(gfx::Rect(0, 0, 20, 15));
blocking_window->Show();
// Occlusion changes are sent separately for different trees.
EXPECT_TRUE(ContainsChange(*setup.changes(),
"OnOcclusionStatesChanged {{window_id=0,100, "
"state=OCCLUDED}}"));
EXPECT_TRUE(ContainsChange(*client2.tracker()->changes(),
"OnOcclusionStatesChanged {{window_id=0,200, "
"state=OCCLUDED}}"));
}
TEST(WindowTreeTest, OcclusionTrackingPause) {
WindowServiceTestSetup setup;
aura::test::WindowOcclusionTrackerTestApi tracker_api(
setup.service()->env()->GetWindowOcclusionTracker());
ASSERT_FALSE(tracker_api.IsPaused());
// Simple case of one pause.
setup.window_tree_test_helper()
->window_tree()
->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
setup.window_tree_test_helper()
->window_tree()
->UnpauseWindowOcclusionTracking();
EXPECT_FALSE(tracker_api.IsPaused());
// Multiple pauses.
constexpr int kPauses = 3;
for (int i = 0; i < kPauses; ++i) {
setup.window_tree_test_helper()
->window_tree()
->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
}
for (int i = 0; i < kPauses - 1; ++i) {
setup.window_tree_test_helper()
->window_tree()
->UnpauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
}
setup.window_tree_test_helper()
->window_tree()
->UnpauseWindowOcclusionTracking();
EXPECT_FALSE(tracker_api.IsPaused());
}
TEST(WindowTreeTest, OcclusionTrackingPauseInterleaved) {
WindowServiceTestSetup setup;
aura::test::WindowOcclusionTrackerTestApi tracker_api(
setup.service()->env()->GetWindowOcclusionTracker());
ASSERT_FALSE(tracker_api.IsPaused());
// Creates a second WindowTree.
TestWindowTreeClient tree_client;
std::unique_ptr<WindowTree> tree2 =
setup.service()->CreateWindowTree(&tree_client);
tree2->InitFromFactory();
auto helper2 = std::make_unique<WindowTreeTestHelper>(tree2.get());
// Tree1 pauses.
setup.window_tree_test_helper()
->window_tree()
->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
// Tree2 pauses.
helper2->window_tree()->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
// Tree1 unpauses.
setup.window_tree_test_helper()
->window_tree()
->UnpauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
// Tree2 unpauses
helper2->window_tree()->UnpauseWindowOcclusionTracking();
EXPECT_FALSE(tracker_api.IsPaused());
}
TEST(WindowTreeTest, OcclusionTrackingPauseGoingAwayTree) {
WindowServiceTestSetup setup;
aura::test::WindowOcclusionTrackerTestApi tracker_api(
setup.service()->env()->GetWindowOcclusionTracker());
ASSERT_FALSE(tracker_api.IsPaused());
// Tree1 pauses.
setup.window_tree_test_helper()
->window_tree()
->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
// Creates a second WindowTree.
TestWindowTreeClient tree_client;
std::unique_ptr<WindowTree> tree2 =
setup.service()->CreateWindowTree(&tree_client);
tree2->InitFromFactory();
auto helper2 = std::make_unique<WindowTreeTestHelper>(tree2.get());
// Tree2 creates an outstanding pause.
helper2->window_tree()->PauseWindowOcclusionTracking();
EXPECT_TRUE(tracker_api.IsPaused());
// Tree2 goes away with the outstanding pause.
helper2.reset();
tree2.reset();
// Still paused because tree1 still holds a pause.
EXPECT_TRUE(tracker_api.IsPaused());
// Tree1 releases the pause and tracker is unpaused.
setup.window_tree_test_helper()
->window_tree()
->UnpauseWindowOcclusionTracking();
EXPECT_FALSE(tracker_api.IsPaused());
}
// Forces window visibility to a target value in OnWindowVisibilityChanged().
// This mimics MultiUserWindowManager in ash.
class WindowVisibilityEnforcer : public aura::WindowObserver {
public:
WindowVisibilityEnforcer(aura::Window* window, bool target_visibility)
: window_(window), target_visibility_(target_visibility) {
window_->AddObserver(this);
}
~WindowVisibilityEnforcer() override { StopObservering(); }
// aura::WindowObserver:
void OnWindowVisibilityChanged(aura::Window* window, bool visible) override {
if (visible == target_visibility_)
return;
if (target_visibility_)
window->Show();
else
window->Hide();
}
void OnWindowDestroying(aura::Window* window) override { StopObservering(); }
private:
void StopObservering() {
if (!window_)
return;
window_->RemoveObserver(this);
window_ = nullptr;
}
aura::Window* window_;
const bool target_visibility_;
DISALLOW_COPY_AND_ASSIGN(WindowVisibilityEnforcer);
};
TEST(WindowTreeTest, ForcedWindowVisibility) {
WindowServiceTestSetup setup;
aura::Window* top_level =
setup.window_tree_test_helper()->NewTopLevelWindow();
setup.changes()->clear();
// WindowVisibilityEnforcer ensures the window remains hidden.
std::unique_ptr<WindowVisibilityEnforcer> enforcer =
std::make_unique<WindowVisibilityEnforcer>(top_level, false);
// Attempting to show the window should fail because WindowVisibilityEnforcer
// forces the window to remain hidden.
EXPECT_FALSE(
setup.window_tree_test_helper()->SetWindowVisibility(top_level, true));
EXPECT_FALSE(top_level->IsVisible());
// The client should not be notified of anything (returning false is enough).
EXPECT_TRUE(setup.changes()->empty());
// Destroy the enforcer and make the window visible.
enforcer.reset();
EXPECT_TRUE(
setup.window_tree_test_helper()->SetWindowVisibility(top_level, true));
EXPECT_TRUE(top_level->IsVisible());
EXPECT_TRUE(setup.changes()->empty());
// Create another enforcer that forces the window to remain visible.
enforcer = std::make_unique<WindowVisibilityEnforcer>(top_level, true);
// Attempting to hide the window should fail because WindowVisibilityEnforcer
// forces the window to remain visible.
EXPECT_FALSE(
setup.window_tree_test_helper()->SetWindowVisibility(top_level, false));
EXPECT_TRUE(top_level->IsVisible());
EXPECT_TRUE(setup.changes()->empty());
}
TEST(WindowTreeTest, SetWindowTransform) {
WindowServiceTestSetup setup;
aura::Window* top_level =
setup.window_tree_test_helper()->NewTopLevelWindow();
setup.changes()->clear();
gfx::Transform scaled;
scaled.Scale(2, 2);
EXPECT_FALSE(
setup.window_tree_test_helper()->SetTransform(top_level, scaled));
EXPECT_EQ(gfx::Transform(), top_level->transform());
aura::Window* child_window = setup.window_tree_test_helper()->NewWindow();
EXPECT_TRUE(
setup.window_tree_test_helper()->SetTransform(child_window, scaled));
EXPECT_EQ(scaled, child_window->transform());
}
} // namespace
} // namespace ws