blob: b614be24afd424f85b800099c692ee0e37ea014d [file] [log] [blame]
// Copyright 2015 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 "ui/aura/mus/window_tree_client.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "cc/base/switches.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/common/surfaces/surface_info.h"
#include "mojo/public/cpp/bindings/map.h"
#include "services/ws/public/cpp/property_type_converters.h"
#include "services/ws/public/mojom/window_manager.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/client/aura_constants.h"
#include "ui/aura/client/capture_client.h"
#include "ui/aura/client/capture_client_observer.h"
#include "ui/aura/client/default_capture_client.h"
#include "ui/aura/client/focus_client.h"
#include "ui/aura/client/transient_window_client.h"
#include "ui/aura/mus/capture_synchronizer.h"
#include "ui/aura/mus/client_surface_embedder.h"
#include "ui/aura/mus/embed_root.h"
#include "ui/aura/mus/embed_root_delegate.h"
#include "ui/aura/mus/focus_synchronizer.h"
#include "ui/aura/mus/mus_lsi_allocator.h"
#include "ui/aura/mus/property_converter.h"
#include "ui/aura/mus/window_mus.h"
#include "ui/aura/mus/window_port_mus.h"
#include "ui/aura/mus/window_tree_client_delegate.h"
#include "ui/aura/mus/window_tree_client_observer.h"
#include "ui/aura/mus/window_tree_host_mus.h"
#include "ui/aura/mus/window_tree_host_mus_init_params.h"
#include "ui/aura/test/aura_mus_test_base.h"
#include "ui/aura/test/mus/test_window_tree.h"
#include "ui/aura/test/mus/window_port_mus_test_helper.h"
#include "ui/aura/test/mus/window_tree_client_test_api.h"
#include "ui/aura/test/test_screen.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_window_targeter.h"
#include "ui/aura/window.h"
#include "ui/aura/window_targeter.h"
#include "ui/aura/window_tracker.h"
#include "ui/aura/window_tree_host_observer.h"
#include "ui/base/class_property.h"
#include "ui/base/ui_base_features.h"
#include "ui/base/ui_base_switches.h"
#include "ui/compositor/compositor.h"
#include "ui/display/display.h"
#include "ui/display/display_switches.h"
#include "ui/display/screen.h"
#include "ui/events/event.h"
#include "ui/events/event_observer.h"
#include "ui/events/event_utils.h"
#include "ui/events/test/test_event_handler.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/transform.h"
namespace aura {
namespace {
DEFINE_UI_CLASS_PROPERTY_KEY(uint8_t, kTestPropertyKey1, 0)
DEFINE_UI_CLASS_PROPERTY_KEY(uint16_t, kTestPropertyKey2, 0)
DEFINE_UI_CLASS_PROPERTY_KEY(bool, kTestPropertyKey3, false)
DEFINE_UI_CLASS_PROPERTY_KEY(Window*, kTestPropertyKey4, nullptr)
const char kTestPropertyServerKey1[] = "test-property-server1";
const char kTestPropertyServerKey2[] = "test-property-server2";
const char kTestPropertyServerKey3[] = "test-property-server3";
const char kTestPropertyServerKey4[] = "test-property-server4";
ws::Id server_id(Window* window) {
return window ? WindowMus::Get(window)->server_id() : 0;
}
std::unique_ptr<Window> CreateWindowUsingId(
WindowTreeClient* window_tree_client,
ws::Id server_id,
Window* parent = nullptr) {
ws::mojom::WindowData window_data;
window_data.window_id = server_id;
WindowMus* window_mus =
WindowTreeClientTestApi(window_tree_client)
.NewWindowFromWindowData(WindowMus::Get(parent), window_data);
// WindowTreeClient implicitly creates the Window but doesn't own it.
// Pass ownership to the caller.
return base::WrapUnique<Window>(window_mus->GetWindow());
}
void SetWindowVisibility(Window* window, bool visible) {
if (visible)
window->Show();
else
window->Hide();
}
bool IsWindowHostVisible(Window* window) {
return window->GetRootWindow()->GetHost()->compositor()->IsVisible();
}
// Register some test window properties for aura/mus conversion.
void RegisterTestProperties(PropertyConverter* converter) {
converter->RegisterPrimitiveProperty(
kTestPropertyKey1, kTestPropertyServerKey1,
PropertyConverter::CreateAcceptAnyValueCallback());
converter->RegisterPrimitiveProperty(
kTestPropertyKey2, kTestPropertyServerKey2,
PropertyConverter::CreateAcceptAnyValueCallback());
converter->RegisterPrimitiveProperty(
kTestPropertyKey3, kTestPropertyServerKey3,
PropertyConverter::CreateAcceptAnyValueCallback());
converter->RegisterWindowPtrProperty(kTestPropertyKey4,
kTestPropertyServerKey4);
}
// Convert a primitive aura property value to a mus transport value.
// Note that this implicitly casts arguments to the aura storage type, int64_t.
std::vector<uint8_t> ConvertToPropertyTransportValue(int64_t value) {
return mojo::ConvertTo<std::vector<uint8_t>>(value);
}
void OnWindowMoveDone(int* call_count, bool* last_result, bool result) {
(*call_count)++;
*last_result = result;
}
// A simple event observer that records the last observed event.
class TestEventObserver : public ui::EventObserver {
public:
TestEventObserver() = default;
~TestEventObserver() override = default;
const ui::Event* last_event() const { return last_event_.get(); }
void ResetLastEvent() { last_event_.reset(); }
private:
// ui::EventObserver:
void OnEvent(const ui::Event& event) override {
last_event_ = ui::Event::Clone(event);
}
std::unique_ptr<ui::Event> last_event_;
DISALLOW_COPY_AND_ASSIGN(TestEventObserver);
};
} // namespace
class WindowTreeClientTest : public test::AuraMusClientTestBase {
public:
WindowTreeClientTest() = default;
~WindowTreeClientTest() override = default;
struct TopLevel {
std::unique_ptr<client::DefaultCaptureClient> capture_client;
std::unique_ptr<WindowTreeHostMus> host;
};
void CreateCaptureClientForTopLevel(TopLevel* top_level) {
top_level->capture_client =
std::make_unique<client::DefaultCaptureClient>();
client::SetCaptureClient(top_level->host->window(),
top_level->capture_client.get());
window_tree_client_impl()->capture_synchronizer()->AttachToCaptureClient(
top_level->capture_client.get());
}
viz::LocalSurfaceIdAllocation GenerateLocalSurfaceIdForNewTopLevel() {
parent_local_surface_id_allocator_.GenerateId();
return parent_local_surface_id_allocator_
.GetCurrentLocalSurfaceIdAllocation();
}
viz::LocalSurfaceIdAllocation GenerateChildLocalIdFromParentAllocator() {
viz::LocalSurfaceId current_id =
parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id();
return viz::LocalSurfaceIdAllocation(
viz::LocalSurfaceId(current_id.parent_sequence_number(),
current_id.child_sequence_number() + 1,
current_id.embed_token()),
base::TimeTicks::Now());
}
void AckNewTopLevelWindow(TopLevel* top_level, uint32_t change_id) {
Window* top_level_window = top_level->host->window();
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(top_level_window);
data->visible = true;
window_tree_client()->OnTopLevelCreated(
change_id, std::move(data), next_display_id_++, true,
GenerateLocalSurfaceIdForNewTopLevel());
EXPECT_EQ(0u, window_tree()->GetChangeCountForType(
WindowTreeChangeType::VISIBLE));
EXPECT_TRUE(top_level_window->TargetVisibility());
}
void AckNewTopLevelWindow(TopLevel* top_level) {
// Ack the request to the windowtree to create the new window.
uint32_t change_id = 0;
EXPECT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
EXPECT_EQ(window_tree()->window_id(), server_id(top_level->host->window()));
AckNewTopLevelWindow(top_level, change_id);
}
// Returns a TopLevel with the |host| and |capture_client|.
std::unique_ptr<TopLevel> CreateTopLevel() {
std::unique_ptr<TopLevel> top_level = std::make_unique<TopLevel>();
top_level->host = std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
top_level->host->InitHost();
CreateCaptureClientForTopLevel(top_level.get());
// Ack the request to the windowtree to create the new window.
AckNewTopLevelWindow(top_level.get());
return top_level;
}
protected:
viz::ParentLocalSurfaceIdAllocator parent_local_surface_id_allocator_;
private:
int64_t next_display_id_ = 1;
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientTest);
};
class WindowTreeClientTestSurfaceSync
: public WindowTreeClientTest,
public ::testing::WithParamInterface<bool> {
public:
WindowTreeClientTestSurfaceSync() {}
~WindowTreeClientTestSurfaceSync() override {}
// WindowTreeClientTest:
void SetUp() override {
if (GetParam()) {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kForceDeviceScaleFactor, "2");
}
WindowTreeClientTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientTestSurfaceSync);
};
// WindowTreeClientTest with --force-device-scale-factor=2.
class WindowTreeClientTestHighDPI : public WindowTreeClientTest {
public:
WindowTreeClientTestHighDPI() = default;
~WindowTreeClientTestHighDPI() override = default;
// WindowTreeClientTest:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kForceDeviceScaleFactor, "2");
WindowTreeClientTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientTestHighDPI);
};
// Verifies bounds are reverted if the server replied that the change failed.
TEST_F(WindowTreeClientTest, SetBoundsFailed) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
const gfx::Rect original_bounds(window.bounds());
const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
ASSERT_NE(new_bounds, window.bounds());
window.SetBounds(new_bounds);
EXPECT_EQ(new_bounds, window.bounds());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(WindowTreeChangeType::BOUNDS,
false));
EXPECT_EQ(original_bounds, window.bounds());
}
TEST_F(WindowTreeClientTest, SetBoundsFailedLocalSurfaceId) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
WindowPortMusTestHelper(&window).SimulateEmbedding();
// SimulateEmbedding() triggers a bounds change.
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::BOUNDS, true));
// Create a new LocalSurfaceIdAllocation and change the bounds.
const gfx::Rect original_bounds(window.bounds());
const viz::LocalSurfaceId original_local_surface_id(
window.GetLocalSurfaceIdAllocation().local_surface_id());
const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
ASSERT_NE(new_bounds, window.bounds());
window.SetBounds(new_bounds);
EXPECT_EQ(new_bounds, window.bounds());
WindowMus* window_mus = WindowMus::Get(&window);
ASSERT_NE(nullptr, window_mus);
ASSERT_TRUE(window_mus->GetLocalSurfaceIdAllocation().IsValid());
const viz::LocalSurfaceId new_surface_id =
window_mus->GetLocalSurfaceIdAllocation().local_surface_id();
// Return the bounds.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(WindowTreeChangeType::BOUNDS,
false));
EXPECT_EQ(original_bounds, window.bounds());
// Whenever the bounds changes a new LocalSurfaceId needs to be allocated.
EXPECT_EQ(1u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
}
INSTANTIATE_TEST_SUITE_P(/* no prefix */,
WindowTreeClientTestSurfaceSync,
::testing::Bool());
// Verifies that windows with an embedding create a ClientSurfaceEmbedder.
TEST_P(WindowTreeClientTestSurfaceSync, ClientSurfaceEmbedderCreated) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
WindowPortMusTestHelper window_test_helper(&window);
// A ClientSurfaceEmbedder is only created once there is an embedding.
ClientSurfaceEmbedder* client_surface_embedder =
window_test_helper.GetClientSurfaceEmbedder();
EXPECT_EQ(nullptr, client_surface_embedder);
window_test_helper.SimulateEmbedding();
gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
ASSERT_NE(new_bounds, window.bounds());
window.SetBounds(new_bounds);
EXPECT_EQ(new_bounds, window.bounds());
EXPECT_TRUE(WindowMus::Get(&window)->GetLocalSurfaceIdAllocation().IsValid());
// Once the bounds have been set, the ClientSurfaceEmbedder should be created.
client_surface_embedder = window_test_helper.GetClientSurfaceEmbedder();
EXPECT_TRUE(client_surface_embedder);
}
// Verifies that the viz::LocalSurfaceId generated by an embedder changes when
// the size changes, but not when the position changes.
TEST_P(WindowTreeClientTestSurfaceSync, SetBoundsLocalSurfaceIdChanges) {
ASSERT_EQ(base::nullopt, window_tree()->last_local_surface_id());
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
WindowPortMusTestHelper(&window).SimulateEmbedding();
// Resize the window and verify that we've allocated a viz::LocalSurfaceId.
const gfx::Rect new_bounds(0, 0, 100, 100);
ASSERT_NE(new_bounds, window.bounds());
window.SetBounds(new_bounds);
EXPECT_EQ(new_bounds, window.bounds());
base::Optional<viz::LocalSurfaceId> last_local_surface_id =
window_tree()->last_local_surface_id();
ASSERT_NE(base::nullopt, last_local_surface_id);
// Resize the window again and verify that the viz::LocalSurfaceId has
// changed.
const gfx::Rect new_bounds2(0, 0, 100, 102);
ASSERT_NE(new_bounds2, window.bounds());
window.SetBounds(new_bounds2);
EXPECT_EQ(new_bounds2, window.bounds());
base::Optional<viz::LocalSurfaceId> last_local_surface_id2 =
window_tree()->last_local_surface_id();
ASSERT_NE(base::nullopt, last_local_surface_id2);
EXPECT_NE(last_local_surface_id2, last_local_surface_id);
// Moving the window but not changing the size should not allocate a new
// viz::LocalSurfaceId.
const gfx::Rect new_bounds3(1337, 7331, 100, 102);
ASSERT_NE(new_bounds3, window.bounds());
window.SetBounds(new_bounds3);
EXPECT_EQ(new_bounds3, window.bounds());
base::Optional<viz::LocalSurfaceId> last_local_surface_id3 =
window_tree()->last_local_surface_id();
ASSERT_NE(base::nullopt, last_local_surface_id3);
EXPECT_EQ(last_local_surface_id2, last_local_surface_id3);
}
// Verifies a new window from the server doesn't result in attempting to add
// the window back to the server.
TEST_F(WindowTreeClientTest, AddFromServerDoesntAddAgain) {
const ws::Id child_window_id = server_id(root_window()) + 11;
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->parent_id = server_id(root_window());
data->window_id = child_window_id;
data->bounds = gfx::Rect(1, 2, 3, 4);
data->visible = false;
std::vector<ws::mojom::WindowDataPtr> data_array(1);
data_array[0] = std::move(data);
ASSERT_TRUE(root_window()->children().empty());
window_tree_client()->OnWindowHierarchyChanged(
child_window_id, 0, server_id(root_window()), std::move(data_array));
ASSERT_FALSE(window_tree()->has_change());
ASSERT_EQ(1u, root_window()->children().size());
Window* child = root_window()->children()[0];
EXPECT_FALSE(child->TargetVisibility());
}
// Verifies a reparent from the server doesn't attempt signal the server.
TEST_F(WindowTreeClientTest, ReparentFromServerDoesntAddAgain) {
Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&window1);
root_window()->AddChild(&window2);
window_tree()->AckAllChanges();
// Simulate moving |window1| to be a child of |window2| from the server.
window_tree_client()->OnWindowHierarchyChanged(
server_id(&window1), server_id(root_window()), server_id(&window2),
std::vector<ws::mojom::WindowDataPtr>());
ASSERT_FALSE(window_tree()->has_change());
EXPECT_EQ(&window2, window1.parent());
EXPECT_EQ(root_window(), window2.parent());
window1.parent()->RemoveChild(&window1);
}
// Verifies properties passed in OnWindowHierarchyChanged() make there way to
// the new window.
TEST_F(WindowTreeClientTest, OnWindowHierarchyChangedWithProperties) {
RegisterTestProperties(GetPropertyConverter());
window_tree()->AckAllChanges();
const ws::Id child_window_id = server_id(root_window()) + 11;
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
const uint8_t server_test_property1_value = 91;
data->properties[kTestPropertyServerKey1] =
ConvertToPropertyTransportValue(server_test_property1_value);
data->properties[ws::mojom::WindowManager::kWindowType_InitProperty] =
mojo::ConvertTo<std::vector<uint8_t>>(
static_cast<int32_t>(ws::mojom::WindowType::BUBBLE));
constexpr int kWindowCornerRadiusValue = 6;
data->properties[ws::mojom::WindowManager::kWindowCornerRadius_Property] =
ConvertToPropertyTransportValue(kWindowCornerRadiusValue);
data->parent_id = server_id(root_window());
data->window_id = child_window_id;
data->bounds = gfx::Rect(1, 2, 3, 4);
data->visible = false;
std::vector<ws::mojom::WindowDataPtr> data_array(1);
data_array[0] = std::move(data);
ASSERT_TRUE(root_window()->children().empty());
window_tree_client()->OnWindowHierarchyChanged(
child_window_id, 0, server_id(root_window()), std::move(data_array));
ASSERT_FALSE(window_tree()->has_change());
ASSERT_EQ(1u, root_window()->children().size());
Window* child = root_window()->children()[0];
EXPECT_FALSE(child->TargetVisibility());
EXPECT_EQ(server_test_property1_value, child->GetProperty(kTestPropertyKey1));
EXPECT_EQ(kWindowCornerRadiusValue,
child->GetProperty(client::kWindowCornerRadiusKey));
EXPECT_EQ(child->type(), client::WINDOW_TYPE_POPUP);
EXPECT_EQ(ws::mojom::WindowType::BUBBLE,
child->GetProperty(client::kWindowTypeKey));
}
// Verifies a move from the server doesn't attempt signal the server.
TEST_F(WindowTreeClientTest, MoveFromServerDoesntAddAgain) {
Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&window1);
root_window()->AddChild(&window2);
window_tree()->AckAllChanges();
// Simulate moving |window1| to be a child of |window2| from the server.
window_tree_client()->OnWindowReordered(server_id(&window2),
server_id(&window1),
ws::mojom::OrderDirection::BELOW);
ASSERT_FALSE(window_tree()->has_change());
ASSERT_EQ(2u, root_window()->children().size());
EXPECT_EQ(&window2, root_window()->children()[0]);
EXPECT_EQ(&window1, root_window()->children()[1]);
}
TEST_F(WindowTreeClientTest, FocusFromServer) {
Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&window1);
root_window()->AddChild(&window2);
ASSERT_TRUE(window1.CanFocus());
window_tree()->AckAllChanges();
EXPECT_FALSE(window1.HasFocus());
// Simulate moving |window1| to be a child of |window2| from the server.
window_tree_client()->OnWindowFocused(server_id(&window1));
ASSERT_FALSE(window_tree()->has_change());
EXPECT_TRUE(window1.HasFocus());
}
// Simulates a bounds change, and while the bounds change is in flight the
// server replies with a new bounds and the original bounds change fails.
// The server bounds change takes hold.
TEST_F(WindowTreeClientTest, SetBoundsFailedWithPendingChange) {
aura::Window root_window(nullptr);
root_window.Init(ui::LAYER_NOT_DRAWN);
const gfx::Rect original_bounds(root_window.bounds());
const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
ASSERT_NE(new_bounds, root_window.bounds());
root_window.SetBounds(new_bounds);
EXPECT_EQ(new_bounds, root_window.bounds());
const viz::LocalSurfaceIdAllocation initial_local_surface_id_allocation =
root_window.GetLocalSurfaceIdAllocation();
// Simulate the server responding with a bounds change.
const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 101, 102));
const viz::LocalSurfaceIdAllocation
server_changed_local_surface_id_allocation(
viz::LocalSurfaceId(1, base::UnguessableToken::Create()),
base::TimeTicks::Now());
window_tree_client()->OnWindowBoundsChanged(
server_id(&root_window), server_changed_bounds,
server_changed_local_surface_id_allocation);
WindowMus* root_window_mus = WindowMus::Get(&root_window);
ASSERT_NE(nullptr, root_window_mus);
// This shouldn't trigger the bounds changing yet.
EXPECT_EQ(new_bounds, root_window.bounds());
// Tell the client the change failed, which should trigger failing to the
// most recent bounds from server.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(WindowTreeChangeType::BOUNDS,
false));
EXPECT_EQ(server_changed_bounds, root_window.bounds());
// LocalSurfaceIdAllocation, for non-embed/top-levels is entirely controlled
// by the client.
EXPECT_EQ(initial_local_surface_id_allocation,
root_window_mus->GetLocalSurfaceIdAllocation());
// Simulate server changing back to original bounds. Should take immediately.
window_tree_client()->OnWindowBoundsChanged(server_id(&root_window),
original_bounds, base::nullopt);
EXPECT_EQ(original_bounds, root_window.bounds());
}
TEST_F(WindowTreeClientTest, TwoInFlightBoundsChangesBothCanceled) {
aura::Window root_window(nullptr);
root_window.Init(ui::LAYER_NOT_DRAWN);
const gfx::Rect original_bounds(root_window.bounds());
const gfx::Rect bounds1(gfx::Rect(0, 0, 100, 100));
const gfx::Rect bounds2(gfx::Rect(0, 0, 100, 102));
root_window.SetBounds(bounds1);
EXPECT_EQ(bounds1, root_window.bounds());
root_window.SetBounds(bounds2);
EXPECT_EQ(bounds2, root_window.bounds());
// Tell the client the first bounds failed. As there is a still a change in
// flight nothing should happen.
ASSERT_TRUE(
window_tree()->AckFirstChangeOfType(WindowTreeChangeType::BOUNDS, false));
EXPECT_EQ(bounds2, root_window.bounds());
// Tell the client the seconds bounds failed. Should now fallback to original
// value.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(WindowTreeChangeType::BOUNDS,
false));
EXPECT_EQ(original_bounds, root_window.bounds());
}
TEST_F(WindowTreeClientTest, TwoInFlightTransformsChangesBothCanceled) {
const gfx::Transform original_transform(root_window()->layer()->transform());
gfx::Transform transform1;
transform1.Scale(SkIntToMScalar(2), SkIntToMScalar(2));
gfx::Transform transform2;
transform2.Scale(SkIntToMScalar(3), SkIntToMScalar(3));
root_window()->SetTransform(transform1);
EXPECT_EQ(transform1, root_window()->layer()->transform());
root_window()->SetTransform(transform2);
EXPECT_EQ(transform2, root_window()->layer()->transform());
// Tell the client the first transform failed. As there is a still a change in
// flight nothing should happen.
ASSERT_TRUE(window_tree()->AckFirstChangeOfType(
WindowTreeChangeType::TRANSFORM, false));
EXPECT_EQ(transform2, root_window()->layer()->transform());
// Tell the client the seconds transform failed. Should now fallback to
// original value.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::TRANSFORM, false));
EXPECT_EQ(original_transform, root_window()->layer()->transform());
}
// Verifies properties are set if the server replied that the change succeeded.
TEST_F(WindowTreeClientTest, SetPropertySucceeded) {
ASSERT_FALSE(root_window()->GetProperty(client::kAlwaysOnTopKey));
root_window()->SetProperty(client::kAlwaysOnTopKey, true);
EXPECT_TRUE(root_window()->GetProperty(client::kAlwaysOnTopKey));
base::Optional<std::vector<uint8_t>> value =
window_tree()->GetLastPropertyValue();
ASSERT_TRUE(value.has_value());
// PropertyConverter uses int64_t values, even for smaller types, like bool.
ASSERT_EQ(8u, value->size());
EXPECT_EQ(1, mojo::ConvertTo<int64_t>(*value));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, true));
EXPECT_TRUE(root_window()->GetProperty(client::kAlwaysOnTopKey));
}
// Verifies properties are reverted if the server replied that the change
// failed.
TEST_F(WindowTreeClientTest, SetPropertyFailed) {
ASSERT_FALSE(root_window()->GetProperty(client::kAlwaysOnTopKey));
root_window()->SetProperty(client::kAlwaysOnTopKey, true);
EXPECT_TRUE(root_window()->GetProperty(client::kAlwaysOnTopKey));
base::Optional<std::vector<uint8_t>> value =
window_tree()->GetLastPropertyValue();
ASSERT_TRUE(value.has_value());
// PropertyConverter uses int64_t values, even for smaller types, like bool.
ASSERT_EQ(8u, value->size());
EXPECT_EQ(1, mojo::ConvertTo<int64_t>(*value));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_FALSE(root_window()->GetProperty(client::kAlwaysOnTopKey));
}
// Simulates a property change, and while the property change is in flight the
// server replies with a new property and the original property change fails.
TEST_F(WindowTreeClientTest, SetPropertyFailedWithPendingChange) {
RegisterTestProperties(GetPropertyConverter());
const uint8_t value1 = 11;
root_window()->SetProperty(kTestPropertyKey1, value1);
EXPECT_EQ(value1, root_window()->GetProperty(kTestPropertyKey1));
// Simulate the server responding with a different value.
const uint8_t server_value = 12;
window_tree_client()->OnWindowSharedPropertyChanged(
server_id(root_window()), kTestPropertyServerKey1,
ConvertToPropertyTransportValue(server_value));
// This shouldn't trigger the property changing yet.
EXPECT_EQ(value1, root_window()->GetProperty(kTestPropertyKey1));
// Tell the client the change failed, which should trigger failing to the
// most recent value from server.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(server_value, root_window()->GetProperty(kTestPropertyKey1));
// Simulate server changing back to value1. Should take immediately.
window_tree_client()->OnWindowSharedPropertyChanged(
server_id(root_window()), kTestPropertyServerKey1,
ConvertToPropertyTransportValue(value1));
EXPECT_EQ(value1, root_window()->GetProperty(kTestPropertyKey1));
}
// Verifies property setting behavior with failures for primitive properties.
TEST_F(WindowTreeClientTest, SetPrimitiveProperties) {
PropertyConverter* property_converter = GetPropertyConverter();
RegisterTestProperties(property_converter);
const uint8_t value1_local = UINT8_MAX / 2;
const uint8_t value1_server = UINT8_MAX / 3;
root_window()->SetProperty(kTestPropertyKey1, value1_local);
EXPECT_EQ(value1_local, root_window()->GetProperty(kTestPropertyKey1));
window_tree_client()->OnWindowSharedPropertyChanged(
server_id(root_window()), kTestPropertyServerKey1,
ConvertToPropertyTransportValue(value1_server));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(value1_server, root_window()->GetProperty(kTestPropertyKey1));
const uint16_t value2_local = UINT16_MAX / 3;
const uint16_t value2_server = UINT16_MAX / 4;
root_window()->SetProperty(kTestPropertyKey2, value2_local);
EXPECT_EQ(value2_local, root_window()->GetProperty(kTestPropertyKey2));
window_tree_client()->OnWindowSharedPropertyChanged(
server_id(root_window()), kTestPropertyServerKey2,
ConvertToPropertyTransportValue(value2_server));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(value2_server, root_window()->GetProperty(kTestPropertyKey2));
EXPECT_FALSE(root_window()->GetProperty(kTestPropertyKey3));
root_window()->SetProperty(kTestPropertyKey3, true);
EXPECT_TRUE(root_window()->GetProperty(kTestPropertyKey3));
window_tree_client()->OnWindowSharedPropertyChanged(
server_id(root_window()), kTestPropertyServerKey3,
ConvertToPropertyTransportValue(false));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_FALSE(root_window()->GetProperty(kTestPropertyKey3));
}
// Verifies property setting behavior for a gfx::Rect* property.
TEST_F(WindowTreeClientTest, SetRectProperty) {
gfx::Rect example(1, 2, 3, 4);
ASSERT_EQ(nullptr, root_window()->GetProperty(client::kRestoreBoundsKey));
root_window()->SetProperty(client::kRestoreBoundsKey, new gfx::Rect(example));
EXPECT_TRUE(root_window()->GetProperty(client::kRestoreBoundsKey));
base::Optional<std::vector<uint8_t>> value =
window_tree()->GetLastPropertyValue();
ASSERT_TRUE(value.has_value());
EXPECT_EQ(example, mojo::ConvertTo<gfx::Rect>(*value));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, true));
EXPECT_EQ(example, *root_window()->GetProperty(client::kRestoreBoundsKey));
root_window()->SetProperty(client::kRestoreBoundsKey, new gfx::Rect());
EXPECT_EQ(gfx::Rect(),
*root_window()->GetProperty(client::kRestoreBoundsKey));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(example, *root_window()->GetProperty(client::kRestoreBoundsKey));
}
// Verifies property setting behavior for a std::string* property.
TEST_F(WindowTreeClientTest, SetStringProperty) {
std::string example = "123";
ASSERT_NE(nullptr, root_window()->GetProperty(client::kNameKey));
root_window()->SetProperty(client::kNameKey, new std::string(example));
EXPECT_TRUE(root_window()->GetProperty(client::kNameKey));
base::Optional<std::vector<uint8_t>> value =
window_tree()->GetLastPropertyValue();
ASSERT_TRUE(value.has_value());
EXPECT_EQ(example, mojo::ConvertTo<std::string>(*value));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, true));
EXPECT_EQ(example, *root_window()->GetProperty(client::kNameKey));
root_window()->SetProperty(client::kNameKey, new std::string());
EXPECT_EQ(std::string(), *root_window()->GetProperty(client::kNameKey));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(example, *root_window()->GetProperty(client::kNameKey));
}
TEST_F(WindowTreeClientTest, SetWindowPointerProperty) {
PropertyConverter* property_converter = GetPropertyConverter();
RegisterTestProperties(property_converter);
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
window.Show();
root_window()->SetProperty(kTestPropertyKey4, &window);
base::Optional<std::vector<uint8_t>> value =
window_tree()->GetLastPropertyValue();
ASSERT_TRUE(value.has_value());
EXPECT_EQ(WindowMus::Get(&window)->server_id(),
mojo::ConvertTo<ws::Id>(*value));
window_tree()->AckAllChanges();
root_window()->ClearProperty(kTestPropertyKey4);
value = window_tree()->GetLastPropertyValue();
EXPECT_FALSE(value.has_value());
}
// Verifies visible is reverted if the server replied that the change failed.
TEST_F(WindowTreeClientTest, SetVisibleFailed) {
const bool original_visible = root_window()->TargetVisibility();
const bool new_visible = !original_visible;
SetWindowVisibility(root_window(), new_visible);
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::VISIBLE, false));
EXPECT_EQ(original_visible, root_window()->TargetVisibility());
}
// Simulates a visible change, and while the visible change is in flight the
// server replies with a new visible and the original visible change fails.
TEST_F(WindowTreeClientTest, SetVisibleFailedWithPendingChange) {
const bool original_visible = root_window()->TargetVisibility();
const bool new_visible = !original_visible;
SetWindowVisibility(root_window(), new_visible);
EXPECT_EQ(new_visible, root_window()->TargetVisibility());
// Simulate the server responding with a visible change.
const bool server_changed_visible = !new_visible;
window_tree_client()->OnWindowVisibilityChanged(server_id(root_window()),
server_changed_visible);
// This shouldn't trigger visible changing yet.
EXPECT_EQ(new_visible, root_window()->TargetVisibility());
// Tell the client the change failed, which should trigger failing to the
// most recent visible from server.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::VISIBLE, false));
EXPECT_EQ(server_changed_visible, root_window()->TargetVisibility());
// Simulate server changing back to original visible. Should take immediately.
window_tree_client()->OnWindowVisibilityChanged(server_id(root_window()),
original_visible);
EXPECT_EQ(original_visible, root_window()->TargetVisibility());
}
namespace {
class InputEventBasicTestWindowDelegate : public test::TestWindowDelegate {
public:
explicit InputEventBasicTestWindowDelegate(TestWindowTree* test_window_tree)
: test_window_tree_(test_window_tree) {}
~InputEventBasicTestWindowDelegate() override {}
int move_count() const { return move_count_; }
int press_count() const { return press_count_; }
int release_count() const { return release_count_; }
bool was_acked() const { return was_acked_; }
const gfx::Point& last_event_location() const { return last_event_location_; }
void set_event_id(uint32_t event_id) { event_id_ = event_id; }
bool last_mouse_event_had_native_event() const {
return last_mouse_event_had_native_event_;
}
const gfx::Point& last_native_event_location() const {
return last_native_event_location_;
}
ui::EventPointerType last_pointer_type() const { return last_pointer_type_; }
// TestWindowDelegate::
void OnMouseEvent(ui::MouseEvent* event) override {
was_acked_ = test_window_tree_->WasEventAcked(event_id_);
if (event->type() == ui::ET_MOUSE_MOVED)
++move_count_;
else if (event->type() == ui::ET_MOUSE_PRESSED)
++press_count_;
else if (event->type() == ui::ET_MOUSE_RELEASED)
++release_count_;
last_event_location_ = event->location();
last_mouse_event_had_native_event_ = event->HasNativeEvent();
if (event->HasNativeEvent()) {
last_native_event_location_ =
ui::EventSystemLocationFromNative(event->native_event());
}
last_pointer_type_ = event->pointer_details().pointer_type;
event->SetHandled();
}
void OnTouchEvent(ui::TouchEvent* event) override {
was_acked_ = test_window_tree_->WasEventAcked(event_id_);
if (event->type() == ui::ET_TOUCH_PRESSED)
++press_count_;
else if (event->type() == ui::ET_TOUCH_RELEASED)
++release_count_;
last_event_location_ = event->location();
last_pointer_type_ = event->pointer_details().pointer_type;
event->SetHandled();
}
void reset() {
was_acked_ = false;
move_count_ = 0;
press_count_ = 0;
release_count_ = 0;
last_event_location_ = gfx::Point();
event_id_ = 0;
last_mouse_event_had_native_event_ = false;
last_native_event_location_ = gfx::Point();
last_pointer_type_ = ui::EventPointerType::POINTER_TYPE_UNKNOWN;
}
private:
TestWindowTree* test_window_tree_;
bool was_acked_ = false;
int move_count_ = 0;
int press_count_ = 0;
int release_count_ = false;
gfx::Point last_event_location_;
uint32_t event_id_ = 0;
bool last_mouse_event_had_native_event_ = false;
gfx::Point last_native_event_location_;
ui::EventPointerType last_pointer_type_ =
ui::EventPointerType::POINTER_TYPE_UNKNOWN;
DISALLOW_COPY_AND_ASSIGN(InputEventBasicTestWindowDelegate);
};
class InputEventBasicTestEventHandler : public ui::test::TestEventHandler {
public:
explicit InputEventBasicTestEventHandler(Window* target_window)
: target_window_(target_window) {}
~InputEventBasicTestEventHandler() override {}
int move_count() const { return move_count_; }
const gfx::Point& last_event_location() const { return last_event_location_; }
void set_event_id(uint32_t event_id) { event_id_ = event_id; }
// ui::test::TestEventHandler overrides.
void OnMouseEvent(ui::MouseEvent* event) override {
if (event->target() == target_window_) {
if (event->type() == ui::ET_MOUSE_MOVED)
++move_count_;
last_event_location_ = event->location();
event->SetHandled();
}
}
void reset() {
move_count_ = 0;
last_event_location_ = gfx::Point();
event_id_ = 0;
}
private:
Window* target_window_ = nullptr;
int move_count_ = 0;
gfx::Point last_event_location_;
uint32_t event_id_ = 0;
DISALLOW_COPY_AND_ASSIGN(InputEventBasicTestEventHandler);
};
} // namespace
TEST_F(WindowTreeClientTest, InputEventBasic) {
InputEventBasicTestWindowDelegate window_delegate(window_tree());
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
Window child(&window_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
EXPECT_EQ(0, window_delegate.move_count());
EXPECT_FALSE(window_delegate.was_acked());
const gfx::Point event_location_in_child(2, 3);
const uint32_t event_id = 1;
window_delegate.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location_in_child,
gfx::Point(), ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate.move_count());
EXPECT_FALSE(window_delegate.was_acked());
EXPECT_EQ(event_location_in_child, window_delegate.last_event_location());
}
TEST_F(WindowTreeClientTest, InputEventMouse) {
InputEventBasicTestWindowDelegate window_delegate(window_tree());
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
Window child(&window_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
EXPECT_EQ(0, window_delegate.move_count());
const gfx::Point event_location(2, 3);
const uint32_t event_id = 1;
window_delegate.set_event_id(event_id);
ui::MouseEvent mouse_event(
ui::ET_MOUSE_MOVED, event_location, event_location, ui::EventTimeForNow(),
ui::EF_NONE, ui::EF_NONE,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE, 0));
window_tree_client()->OnWindowInputEvent(event_id, server_id(&child),
window_tree_host.display_id(),
ui::Event::Clone(mouse_event), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate.move_count());
EXPECT_EQ(event_location, window_delegate.last_event_location());
}
TEST_F(WindowTreeClientTest, InputEventPen) {
// Create a root window.
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
// Create a child window with a test delegate to sense events.
InputEventBasicTestWindowDelegate window_delegate(window_tree());
Window child(&window_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
// Dispatch a pen event to the child window.
const gfx::Point event_location(2, 3);
const uint32_t event_id = 1;
window_delegate.set_event_id(event_id);
ui::MouseEvent mouse_event(
ui::ET_MOUSE_PRESSED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_PEN, 0));
window_tree_client()->OnWindowInputEvent(event_id, server_id(&child),
window_tree_host.display_id(),
ui::Event::Clone(mouse_event), 0);
// Pen event was handled.
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(ui::EventPointerType::POINTER_TYPE_PEN,
window_delegate.last_pointer_type());
}
TEST_F(WindowTreeClientTest, InputEventFindTargetAndConversion) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
InputEventBasicTestWindowDelegate window_delegate1(window_tree());
Window child1(&window_delegate1);
child1.Init(ui::LAYER_NOT_DRAWN);
child1.SetEventTargeter(std::make_unique<WindowTargeter>());
top_level->AddChild(&child1);
child1.SetBounds(gfx::Rect(10, 10, 100, 100));
child1.Show();
InputEventBasicTestWindowDelegate window_delegate2(window_tree());
Window child2(&window_delegate2);
child2.Init(ui::LAYER_NOT_DRAWN);
child1.AddChild(&child2);
child2.SetBounds(gfx::Rect(20, 30, 100, 100));
child2.Show();
EXPECT_EQ(0, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
// child1 has a targeter set and event_location is (50, 60), child2
// should get the event even though mus-ws wants to send to child1.
const gfx::Point event_location(50, 60);
uint32_t event_id = 1;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location, gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child1), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(0, window_delegate1.move_count());
EXPECT_EQ(1, window_delegate2.move_count());
EXPECT_EQ(gfx::Point(30, 30), window_delegate2.last_event_location());
window_delegate1.reset();
window_delegate2.reset();
// Remove the targeter for child1 and specify the event to go to child1. This
// time child1 should receive the event not child2.
child1.SetEventTargeter(nullptr);
event_id = 2;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event1(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location, gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child1), window_tree_host.display_id(),
ui::Event::Clone(*ui_event1.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
EXPECT_EQ(gfx::Point(50, 60), window_delegate1.last_event_location());
}
TEST_F(WindowTreeClientTest, InputEventCustomWindowTargeter) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
InputEventBasicTestWindowDelegate window_delegate1(window_tree());
Window child1(&window_delegate1);
child1.Init(ui::LAYER_NOT_DRAWN);
child1.SetEventTargeter(std::make_unique<test::TestWindowTargeter>());
top_level->AddChild(&child1);
child1.SetBounds(gfx::Rect(10, 10, 100, 100));
child1.Show();
InputEventBasicTestWindowDelegate window_delegate2(window_tree());
Window child2(&window_delegate2);
child2.Init(ui::LAYER_NOT_DRAWN);
child1.AddChild(&child2);
child2.SetBounds(gfx::Rect(20, 30, 100, 100));
child2.Show();
EXPECT_EQ(0, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
// child1 has a custom targeter set which would always return itself as the
// target window therefore event should go to child1 unlike
// WindowTreeClientTest.InputEventFindTargetAndConversion.
const gfx::Point event_location(50, 60);
uint32_t event_id = 1;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location, gfx::Point(),
ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child1), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
EXPECT_EQ(gfx::Point(50, 60), window_delegate1.last_event_location());
window_delegate1.reset();
window_delegate2.reset();
// child1 should get the event even though mus-ws specifies child2 and it's
// actually in child2's space. Event location will be transformed.
event_id = 2;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child2), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
EXPECT_EQ(gfx::Point(70, 90), window_delegate1.last_event_location());
}
TEST_F(WindowTreeClientTest, InputEventCaptureWindow) {
std::unique_ptr<WindowTreeHostMus> window_tree_host =
std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host->window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(window_tree_host.get())
->SetBoundsInPixels(bounds);
window_tree_host->InitHost();
window_tree_host->Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host->GetBoundsInPixels());
std::unique_ptr<InputEventBasicTestWindowDelegate> window_delegate1(
std::make_unique<InputEventBasicTestWindowDelegate>(window_tree()));
std::unique_ptr<Window> child1(
std::make_unique<Window>(window_delegate1.get()));
child1->Init(ui::LAYER_NOT_DRAWN);
child1->SetEventTargeter(std::make_unique<test::TestWindowTargeter>());
top_level->AddChild(child1.get());
child1->SetBounds(gfx::Rect(10, 10, 100, 100));
child1->Show();
std::unique_ptr<InputEventBasicTestWindowDelegate> window_delegate2(
std::make_unique<InputEventBasicTestWindowDelegate>(window_tree()));
std::unique_ptr<Window> child2(
std::make_unique<Window>(window_delegate2.get()));
child2->Init(ui::LAYER_NOT_DRAWN);
child1->AddChild(child2.get());
child2->SetBounds(gfx::Rect(20, 30, 100, 100));
child2->Show();
EXPECT_EQ(0, window_delegate1->move_count());
EXPECT_EQ(0, window_delegate2->move_count());
// child1 has a custom targeter set which would always return itself as the
// target window therefore event should go to child1.
const gfx::Point event_location(50, 60);
const gfx::Point root_location;
uint32_t event_id = 1;
window_delegate1->set_event_id(event_id);
window_delegate2->set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location, root_location,
ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(child1.get()), window_tree_host->display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1->move_count());
EXPECT_EQ(0, window_delegate2->move_count());
EXPECT_EQ(gfx::Point(50, 60), window_delegate1->last_event_location());
window_delegate1->reset();
window_delegate2->reset();
// Set capture to |child2|. Capture takes precedence, and because the event is
// converted local coordinates are converted from the original target to root
// and then to capture target.
std::unique_ptr<client::DefaultCaptureClient> capture_client(
std::make_unique<client::DefaultCaptureClient>());
client::SetCaptureClient(top_level, capture_client.get());
child2->SetCapture();
EXPECT_EQ(child2.get(), client::GetCaptureWindow(child2->GetRootWindow()));
event_id = 2;
window_delegate1->set_event_id(event_id);
window_delegate2->set_event_id(event_id);
window_tree_client()->OnWindowInputEvent(
event_id, server_id(child1.get()), window_tree_host->display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(0, window_delegate1->move_count());
EXPECT_EQ(1, window_delegate2->move_count());
gfx::Point location_in_child2(event_location);
Window::ConvertPointToTarget(child1.get(), top_level, &location_in_child2);
Window::ConvertPointToTarget(top_level, child2.get(), &location_in_child2);
EXPECT_EQ(location_in_child2, window_delegate2->last_event_location());
child2.reset();
child1.reset();
window_tree_host.reset();
capture_client.reset();
}
TEST_F(WindowTreeClientTest, InputEventRootWindow) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
InputEventBasicTestEventHandler root_handler(top_level);
top_level->AddPreTargetHandler(&root_handler);
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
InputEventBasicTestWindowDelegate child_delegate(window_tree());
Window child(&child_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
EXPECT_EQ(0, root_handler.move_count());
EXPECT_EQ(0, child_delegate.move_count());
const gfx::Point event_location_in_child(20, 30);
const uint32_t event_id = 1;
root_handler.set_event_id(event_id);
child_delegate.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location_in_child,
gfx::Point(), ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(top_level), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, root_handler.move_count());
EXPECT_EQ(gfx::Point(20, 30), root_handler.last_event_location());
EXPECT_EQ(0, child_delegate.move_count());
EXPECT_EQ(gfx::Point(), child_delegate.last_event_location());
top_level->RemovePreTargetHandler(&root_handler);
}
TEST_F(WindowTreeClientTest, InputMouseEventNoWindow) {
Env* env = Env::GetInstance();
InputEventBasicTestWindowDelegate window_delegate(window_tree());
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
Window child(&window_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
EXPECT_EQ(0, window_delegate.press_count());
EXPECT_FALSE(env->IsMouseButtonDown());
EXPECT_FALSE(env->mouse_button_flags());
EXPECT_EQ(gfx::Point(), env->last_mouse_location());
const gfx::Point event_location(2, 3);
uint32_t event_id = 1;
window_delegate.set_event_id(event_id);
ui::MouseEvent mouse_event_down(
ui::ET_MOUSE_PRESSED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child), window_tree_host.display_id(),
ui::Event::Clone(mouse_event_down), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate.press_count());
EXPECT_TRUE(env->IsMouseButtonDown());
EXPECT_EQ(ui::EF_LEFT_MOUSE_BUTTON, env->mouse_button_flags());
EXPECT_EQ(event_location, env->last_mouse_location());
window_delegate.reset();
const gfx::Point event_location1(4, 5);
event_id = 2;
window_delegate.set_event_id(event_id);
ui::MouseEvent mouse_event_up(
ui::ET_MOUSE_RELEASED, event_location1, event_location,
ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_MOUSE, 0));
window_tree_client()->OnWindowInputEvent(event_id, kInvalidServerId,
window_tree_host.display_id(),
ui::Event::Clone(mouse_event_up), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
// WindowTreeClient::OnWindowInputEvent cannot find a target window with
// kInvalidServerId but should use the event to update event states kept in
// aura::Env, location shouldn't be updated.
EXPECT_EQ(ws::mojom::EventResult::UNHANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(0, window_delegate.release_count());
EXPECT_FALSE(env->IsMouseButtonDown());
EXPECT_FALSE(env->mouse_button_flags());
EXPECT_EQ(event_location, env->last_mouse_location());
}
TEST_F(WindowTreeClientTest, InputTouchEventNoWindow) {
Env* env = Env::GetInstance();
InputEventBasicTestWindowDelegate window_delegate(window_tree());
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds, top_level->bounds());
EXPECT_EQ(bounds, window_tree_host.GetBoundsInPixels());
Window child(&window_delegate);
child.Init(ui::LAYER_NOT_DRAWN);
top_level->AddChild(&child);
child.SetBounds(gfx::Rect(10, 10, 100, 100));
child.Show();
EXPECT_EQ(0, window_delegate.press_count());
EXPECT_FALSE(env->is_touch_down());
const gfx::Point event_location(2, 3);
uint32_t event_id = 1;
window_delegate.set_event_id(event_id);
ui::TouchEvent touch_event_down(
ui::ET_TOUCH_PRESSED, event_location, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child), window_tree_host.display_id(),
ui::Event::Clone(touch_event_down), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate.press_count());
EXPECT_TRUE(env->is_touch_down());
window_delegate.reset();
event_id = 2;
window_delegate.set_event_id(event_id);
ui::TouchEvent touch_event_up(
ui::ET_TOUCH_RELEASED, event_location, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0));
window_tree_client()->OnWindowInputEvent(event_id, kInvalidServerId,
window_tree_host.display_id(),
ui::Event::Clone(touch_event_up), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
// WindowTreeClient::OnWindowInputEvent cannot find a target window with
// kInvalidServerId but should use the event to update event states kept in
// aura::Env.
EXPECT_EQ(ws::mojom::EventResult::UNHANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(0, window_delegate.release_count());
EXPECT_FALSE(env->is_touch_down());
}
// Tests observation of events that did not hit a target in this window tree.
TEST_F(WindowTreeClientTest, OnObservedInputEvent) {
std::unique_ptr<Window> top_level(std::make_unique<Window>(nullptr));
top_level->SetType(client::WINDOW_TYPE_NORMAL);
top_level->Init(ui::LAYER_NOT_DRAWN);
top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
top_level->Show();
// Start observing touch press events.
TestEventObserver test_event_observer;
top_level->env()->AddEventObserver(&test_event_observer, top_level->env(),
{ui::ET_TOUCH_PRESSED});
// Simulate the server sending an observed event.
ui::TouchEvent touch_press(
ui::ET_TOUCH_PRESSED, gfx::Point(), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
window_tree_client()->OnObservedInputEvent(ui::Event::Clone(touch_press));
// The observer should have been notified of the event.
ASSERT_TRUE(test_event_observer.last_event());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, test_event_observer.last_event()->type());
test_event_observer.ResetLastEvent();
// Remove the event observer.
top_level->env()->RemoveEventObserver(&test_event_observer);
// Simulate another event from the server.
window_tree_client()->OnObservedInputEvent(ui::Event::Clone(touch_press));
// The observer should not have been notified of the event.
EXPECT_FALSE(test_event_observer.last_event());
}
// Tests observation of events that did hit a target in this window tree.
TEST_F(WindowTreeClientTest, OnWindowInputEventWithObserver) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(50, 50, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)->SetBoundsInPixels(bounds);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(bounds.size(), top_level->bounds().size());
// Start observing touch press events.
TestEventObserver test_event_observer;
top_level->env()->AddEventObserver(&test_event_observer, top_level->env(),
{ui::ET_TOUCH_PRESSED});
// Simulate the server dispatching an event that also matched the observer.
ui::TouchEvent touch_press(
ui::ET_TOUCH_PRESSED, gfx::Point(), ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
window_tree_client()->OnWindowInputEvent(1, server_id(top_level), 0,
ui::Event::Clone(touch_press), true);
// The observer should have been notified of the event.
ASSERT_TRUE(test_event_observer.last_event());
EXPECT_EQ(ui::ET_TOUCH_PRESSED, test_event_observer.last_event()->type());
top_level->env()->RemoveEventObserver(&test_event_observer);
}
// Verifies focus is reverted if the server replied that the change failed.
TEST_F(WindowTreeClientTest, SetFocusFailed) {
Window child(nullptr);
child.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child);
// AuraTestHelper::SetUp sets the active focus client and focus client root,
// root_window is assumed to have focus until we actually focus on a
// certain window.
EXPECT_EQ(WindowMus::Get(root_window()),
window_tree_client_impl()->focus_synchronizer()->focused_window());
child.Focus();
ASSERT_TRUE(child.HasFocus());
EXPECT_EQ(&child, client::GetFocusClient(&child)->GetFocusedWindow());
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::FOCUS, false));
// If the change failed, we fall back to the revert_value which is the
// current focused_window.
EXPECT_EQ(root_window(), client::GetFocusClient(&child)->GetFocusedWindow());
}
// Simulates a focus change, and while the focus change is in flight the server
// replies with a new focus and the original focus change fails.
TEST_F(WindowTreeClientTest, SetFocusFailedWithPendingChange) {
Window child1(nullptr);
child1.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child1);
Window child2(nullptr);
child2.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child2);
Window* original_focus = client::GetFocusClient(&child1)->GetFocusedWindow();
Window* new_focus = &child1;
ASSERT_NE(new_focus, original_focus);
new_focus->Focus();
ASSERT_TRUE(new_focus->HasFocus());
// Simulate the server responding with a focus change.
window_tree_client()->OnWindowFocused(server_id(&child2));
// This shouldn't trigger focus changing yet.
EXPECT_TRUE(child1.HasFocus());
// Tell the client the change failed, which should trigger failing to the
// most recent focus from server.
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::FOCUS, false));
EXPECT_FALSE(child1.HasFocus());
EXPECT_TRUE(child2.HasFocus());
EXPECT_EQ(&child2, client::GetFocusClient(&child1)->GetFocusedWindow());
// Simulate server changing focus to child1. Should take immediately.
window_tree_client()->OnWindowFocused(server_id(&child1));
EXPECT_TRUE(child1.HasFocus());
}
TEST_F(WindowTreeClientTest, FocusOnRemovedWindowWithoutInFlightFocusChange) {
std::unique_ptr<Window> child1(std::make_unique<Window>(nullptr));
child1->Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(child1.get());
Window child2(nullptr);
child2.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child2);
child1->Focus();
// Acked for the focus change.
EXPECT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::FOCUS, true));
// Destroy child1, which should set focus to null.
child1.reset(nullptr);
EXPECT_EQ(nullptr, client::GetFocusClient(root_window())->GetFocusedWindow());
// The reset of the focus shouldn't be sent to the server.
EXPECT_EQ(0u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::FOCUS));
// Server changes focus to 2.
window_tree_client()->OnWindowFocused(server_id(&child2));
// Should take effect immediately.
EXPECT_TRUE(child2.HasFocus());
}
class ToggleVisibilityFromDestroyedObserver : public WindowObserver {
public:
explicit ToggleVisibilityFromDestroyedObserver(Window* window)
: window_(window) {
window_->AddObserver(this);
}
ToggleVisibilityFromDestroyedObserver() { EXPECT_FALSE(window_); }
// WindowObserver:
void OnWindowDestroyed(Window* window) override {
SetWindowVisibility(window, !window->TargetVisibility());
window_->RemoveObserver(this);
window_ = nullptr;
}
private:
Window* window_;
DISALLOW_COPY_AND_ASSIGN(ToggleVisibilityFromDestroyedObserver);
};
TEST_F(WindowTreeClientTest, ToggleVisibilityFromWindowDestroyed) {
std::unique_ptr<Window> child(std::make_unique<Window>(nullptr));
child->Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(child.get());
ToggleVisibilityFromDestroyedObserver toggler(child.get());
// Destroying the window triggers
// ToggleVisibilityFromDestroyedObserver::OnWindowDestroyed(), which toggles
// the visibility of the window. Ack the change, which should not crash or
// trigger DCHECKs.
child.reset();
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::VISIBLE, true));
}
TEST_F(WindowTreeClientTest, NewTopLevelWindow) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
std::unique_ptr<WindowTreeHostMus> window_tree_host =
std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
window_tree_host->InitHost();
EXPECT_FALSE(window_tree_host->window()->TargetVisibility());
aura::Window* top_level = window_tree_host->window();
EXPECT_EQ(initial_root_count + 1,
window_tree_client_impl()->GetRoots().size());
EXPECT_TRUE(window_tree_client_impl()->GetRoots().count(top_level) > 0u);
// Ack the request to the windowtree to create the new window.
uint32_t change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
EXPECT_EQ(window_tree()->window_id(), server_id(top_level));
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(top_level);
const int64_t display_id = 1;
window_tree_client()->OnTopLevelCreated(
change_id, std::move(data), display_id, false,
GenerateLocalSurfaceIdForNewTopLevel());
EXPECT_FALSE(window_tree_host->window()->TargetVisibility());
// Should not be able to add a top level as a child of another window.
// TODO(sky): decide how to handle this.
// root_window()->AddChild(top_level);
// ASSERT_EQ(nullptr, top_level->parent());
// Destroy the first root, shouldn't initiate tear down.
window_tree_host.reset();
EXPECT_EQ(initial_root_count, window_tree_client_impl()->GetRoots().size());
}
TEST_F(WindowTreeClientTest, NewTopLevelWindowGetsPropertiesFromData) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
EXPECT_EQ(initial_root_count + 1,
window_tree_client_impl()->GetRoots().size());
EXPECT_FALSE(IsWindowHostVisible(top_level));
EXPECT_FALSE(top_level->TargetVisibility());
window_tree_host.InitHost();
EXPECT_FALSE(window_tree_host.window()->TargetVisibility());
// Ack the request to the windowtree to create the new window.
EXPECT_EQ(window_tree()->window_id(), server_id(top_level));
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(top_level);
data->bounds.SetRect(1, 2, 3, 4);
data->visible = true;
const int64_t display_id = 10;
uint32_t change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
window_tree_client()->OnTopLevelCreated(
change_id, std::move(data), display_id, true,
GenerateLocalSurfaceIdForNewTopLevel());
EXPECT_EQ(
0u, window_tree()->GetChangeCountForType(WindowTreeChangeType::VISIBLE));
// Make sure all the properties took.
EXPECT_TRUE(IsWindowHostVisible(top_level));
EXPECT_TRUE(top_level->TargetVisibility());
EXPECT_EQ(display_id, window_tree_host.display_id());
EXPECT_EQ(gfx::Rect(0, 0, 3, 4), top_level->bounds());
EXPECT_EQ(gfx::Rect(1, 2, 3, 4), top_level->GetHost()->GetBoundsInPixels());
}
TEST_F(WindowTreeClientTest, NewWindowGetsAllChangesInFlight) {
RegisterTestProperties(GetPropertyConverter());
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
EXPECT_FALSE(top_level->TargetVisibility());
window_tree_host.InitHost();
// Make visibility go from false->true->false. Don't ack immediately.
top_level->Show();
top_level->Hide();
// Change bounds to 5, 6, 7, 8.
static_cast<WindowTreeHost*>(&window_tree_host)
->SetBoundsInPixels(gfx::Rect(5, 6, 7, 8));
EXPECT_EQ(gfx::Rect(0, 0, 7, 8), window_tree_host.window()->bounds());
const uint8_t explicitly_set_test_property1_value = 2;
top_level->SetProperty(kTestPropertyKey1,
explicitly_set_test_property1_value);
// Ack the new window top level top_level Vis and bounds shouldn't change.
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(top_level);
const gfx::Rect bounds_from_server(1, 2, 3, 4);
data->bounds = bounds_from_server;
data->visible = true;
const uint8_t server_test_property1_value = 3;
data->properties[kTestPropertyServerKey1] =
ConvertToPropertyTransportValue(server_test_property1_value);
const uint8_t server_test_property2_value = 4;
data->properties[kTestPropertyServerKey2] =
ConvertToPropertyTransportValue(server_test_property2_value);
const int64_t display_id = 1;
// Get the id of the in flight change for creating the new top_level.
uint32_t new_window_in_flight_change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &new_window_in_flight_change_id));
window_tree_client()->OnTopLevelCreated(
new_window_in_flight_change_id, std::move(data), display_id, true,
GenerateLocalSurfaceIdForNewTopLevel());
// The only value that should take effect is the property for 'yy' as it was
// not in flight.
EXPECT_FALSE(top_level->TargetVisibility());
EXPECT_EQ(gfx::Rect(5, 6, 7, 8), window_tree_host.GetBoundsInPixels());
EXPECT_EQ(gfx::Rect(0, 0, 7, 8), top_level->bounds());
EXPECT_EQ(explicitly_set_test_property1_value,
top_level->GetProperty(kTestPropertyKey1));
EXPECT_EQ(server_test_property2_value,
top_level->GetProperty(kTestPropertyKey2));
// Tell the client the changes failed. This should cause the values to change
// to that of the server.
ASSERT_TRUE(window_tree()->AckFirstChangeOfType(WindowTreeChangeType::VISIBLE,
false));
EXPECT_FALSE(top_level->TargetVisibility());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::VISIBLE, false));
EXPECT_TRUE(top_level->TargetVisibility());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::PROPERTY, false);
EXPECT_EQ(server_test_property1_value,
top_level->GetProperty(kTestPropertyKey1));
EXPECT_EQ(server_test_property2_value,
top_level->GetProperty(kTestPropertyKey2));
}
TEST_F(WindowTreeClientTest, NewWindowGetsProperties) {
RegisterTestProperties(GetPropertyConverter());
Window window(nullptr);
const uint8_t explicitly_set_test_property1_value = 29;
window.SetProperty(kTestPropertyKey1, explicitly_set_test_property1_value);
window.Init(ui::LAYER_NOT_DRAWN);
base::Optional<base::flat_map<std::string, std::vector<uint8_t>>>
transport_properties = window_tree()->GetLastNewWindowProperties();
ASSERT_TRUE(transport_properties.has_value());
std::map<std::string, std::vector<uint8_t>> properties =
mojo::FlatMapToMap(*transport_properties);
ASSERT_EQ(1u, properties.count(kTestPropertyServerKey1));
// PropertyConverter uses int64_t values, even for smaller types like uint8_t.
ASSERT_EQ(8u, properties[kTestPropertyServerKey1].size());
EXPECT_EQ(static_cast<int64_t>(explicitly_set_test_property1_value),
mojo::ConvertTo<int64_t>(properties[kTestPropertyServerKey1]));
ASSERT_EQ(0u, properties.count(kTestPropertyServerKey2));
}
// Assertions around transient windows.
TEST_F(WindowTreeClientTest, Transients) {
aura::Window root_window(nullptr);
root_window.Init(ui::LAYER_NOT_DRAWN);
client::TransientWindowClient* transient_client =
client::GetTransientWindowClient();
Window parent(nullptr);
parent.Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(&parent);
Window transient(nullptr);
transient.Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(&transient);
window_tree()->AckAllChanges();
transient_client->AddTransientChild(&parent, &transient);
ASSERT_EQ(1u, window_tree()->GetChangeCountForType(
WindowTreeChangeType::ADD_TRANSIENT));
EXPECT_EQ(server_id(&parent), window_tree()->transient_data().parent_id);
EXPECT_EQ(server_id(&transient), window_tree()->transient_data().child_id);
// Remove from the server side.
window_tree_client()->OnTransientWindowRemoved(server_id(&parent),
server_id(&transient));
EXPECT_EQ(nullptr, transient_client->GetTransientParent(&transient));
window_tree()->AckAllChanges();
// Add from the server.
window_tree_client()->OnTransientWindowAdded(server_id(&parent),
server_id(&transient));
EXPECT_EQ(&parent, transient_client->GetTransientParent(&transient));
// Remove locally.
transient_client->RemoveTransientChild(&parent, &transient);
ASSERT_EQ(1u, window_tree()->GetChangeCountForType(
WindowTreeChangeType::REMOVE_TRANSIENT));
EXPECT_EQ(server_id(&transient), window_tree()->transient_data().child_id);
}
TEST_F(WindowTreeClientTest, DontRestackTransientsFromOtherClients) {
// Create a window from another client with 3 children.
const int32_t other_client_id = 11 << 16;
int32_t other_client_window_id = 1;
std::unique_ptr<Window> other_client_window = CreateWindowUsingId(
window_tree_client_impl(), other_client_id | other_client_window_id++,
root_window());
std::unique_ptr<Window> other_client_child_window1 = CreateWindowUsingId(
window_tree_client_impl(), other_client_id | other_client_window_id++,
other_client_window.get());
std::unique_ptr<Window> other_client_child_window2 = CreateWindowUsingId(
window_tree_client_impl(), other_client_id | other_client_window_id++,
other_client_window.get());
std::unique_ptr<Window> other_client_child_window3 = CreateWindowUsingId(
window_tree_client_impl(), other_client_id | other_client_window_id++,
other_client_window.get());
window_tree()->AckAllChanges();
// Make |other_client_child_window3| a transient child of
// |other_client_child_window1|. This should *not* reorder locally as the
// windows are parented to a window owned by another client.
window_tree_client()->OnTransientWindowAdded(
server_id(other_client_child_window1.get()),
server_id(other_client_child_window3.get()));
// There should be no changes sent to the server, and the children should
// not have been reordered.
EXPECT_EQ(0u, window_tree()->number_of_changes());
ASSERT_EQ(3u, other_client_window->children().size());
EXPECT_EQ(other_client_child_window2.get(),
other_client_window->children()[1]);
// Make sure transient was wired correctly though.
client::TransientWindowClient* transient_client =
client::GetTransientWindowClient();
EXPECT_EQ(
other_client_child_window1.get(),
transient_client->GetTransientParent(other_client_child_window3.get()));
}
// Verifies adding/removing a transient child notifies the server of the restack
// when the change originates from the server.
TEST_F(WindowTreeClientTest, TransientChildServerMutateNotifiesOfRestack) {
aura::Window root_window(nullptr);
root_window.Init(ui::LAYER_NOT_DRAWN);
Window* w1 = new Window(nullptr);
w1->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w1);
Window* w2 = new Window(nullptr);
w2->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w2);
Window* w3 = new Window(nullptr);
w3->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w3);
// Three children of root: |w1|, |w2| and |w3| (in that order). Make |w1| a
// transient child of |w2|. Should trigger moving |w1| on top of |w2|, but not
// notify the server of the reorder.
window_tree()->AckAllChanges();
window_tree_client()->OnTransientWindowAdded(server_id(w2), server_id(w1));
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w1, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
// Only reorders should be generated.
EXPECT_NE(0u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Make |w3| also a transient child of |w2|.
window_tree_client()->OnTransientWindowAdded(server_id(w2), server_id(w3));
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w1, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
// Only reorders should be generated.
EXPECT_NE(0u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Remove |w1| as a transient child, this should move |w3| on top of |w2|.
window_tree_client()->OnTransientWindowRemoved(server_id(w2), server_id(w1));
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w3, root_window.children()[1]);
EXPECT_EQ(w1, root_window.children()[2]);
// Only reorders should be generated.
EXPECT_NE(0u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
}
// Verifies adding/removing a transient child notifies the server of the
// restacks;
TEST_F(WindowTreeClientTest, TransientChildClientMutateNotifiesOfRestack) {
aura::Window root_window(nullptr);
root_window.Init(ui::LAYER_NOT_DRAWN);
client::TransientWindowClient* transient_client =
client::GetTransientWindowClient();
Window* w1 = new Window(nullptr);
w1->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w1);
Window* w2 = new Window(nullptr);
w2->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w2);
Window* w3 = new Window(nullptr);
w3->Init(ui::LAYER_NOT_DRAWN);
root_window.AddChild(w3);
// Three children of root: |w1|, |w2| and |w3| (in that order). Make |w1| a
// transient child of |w2|. Should trigger moving |w1| on top of |w2|, and
// notify notify the server of the reorder.
window_tree()->AckAllChanges();
transient_client->AddTransientChild(w2, w1);
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w1, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::ADD_TRANSIENT, true));
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::REORDER, true));
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Make |w3| also a transient child of |w2|. Order shouldn't change.
transient_client->AddTransientChild(w2, w3);
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w1, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::ADD_TRANSIENT, true));
// While the order doesn't change, internally aura shuffles things around,
// hence the REORDERs.
EXPECT_NE(0u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Remove |w1| as a transient child, this should move |w3| on top of |w2|.
transient_client->RemoveTransientChild(w2, w1);
EXPECT_EQ(w2, root_window.children()[0]);
EXPECT_EQ(w3, root_window.children()[1]);
EXPECT_EQ(w1, root_window.children()[2]);
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::REMOVE_TRANSIENT, true));
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::REORDER, true));
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Make |w1| the first child and ensure a REORDER was scheduled.
root_window.StackChildAtBottom(w1);
EXPECT_EQ(w1, root_window.children()[0]);
EXPECT_EQ(w2, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::REORDER, true));
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Try stacking |w2| above |w3|. This should be disallowed as that would
// result in placing |w2| above its transient child.
root_window.StackChildAbove(w2, w3);
EXPECT_EQ(w1, root_window.children()[0]);
EXPECT_EQ(w2, root_window.children()[1]);
EXPECT_EQ(w3, root_window.children()[2]);
// The stack above is followed by a reorder from TransientWindowManager,
// hence multiple changes.
EXPECT_NE(0u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
}
TEST_F(WindowTreeClientTest, TopLevelWindowDestroyedBeforeCreateComplete) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
std::unique_ptr<WindowTreeHostMus> window_tree_host =
std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
window_tree_host->InitHost();
EXPECT_EQ(initial_root_count + 1,
window_tree_client_impl()->GetRoots().size());
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(window_tree_host->window());
// Destroy the window before the server has a chance to ack the window
// creation.
window_tree_host.reset();
EXPECT_EQ(initial_root_count, window_tree_client_impl()->GetRoots().size());
// Get the id of the in flight change for creating the new window.
uint32_t change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
const int64_t display_id = 1;
window_tree_client()->OnTopLevelCreated(
change_id, std::move(data), display_id, true,
GenerateLocalSurfaceIdForNewTopLevel());
EXPECT_EQ(initial_root_count, window_tree_client_impl()->GetRoots().size());
}
TEST_F(WindowTreeClientTest, NewTopLevelWindowGetsProperties) {
RegisterTestProperties(GetPropertyConverter());
const uint8_t property_value = 11;
std::map<std::string, std::vector<uint8_t>> properties;
properties[kTestPropertyServerKey1] =
ConvertToPropertyTransportValue(property_value);
const char kUnknownPropertyKey[] = "unknown-property";
using UnknownPropertyType = int32_t;
const UnknownPropertyType kUnknownPropertyValue = 101;
properties[kUnknownPropertyKey] =
mojo::ConvertTo<std::vector<uint8_t>>(kUnknownPropertyValue);
WindowTreeHostMus window_tree_host(CreateInitParamsForTopLevel(
window_tree_client_impl(), std::move(properties)));
window_tree_host.InitHost();
window_tree_host.window()->Show();
// Verify the property made it to the window.
EXPECT_EQ(property_value,
window_tree_host.window()->GetProperty(kTestPropertyKey1));
// Get the id of the in flight change for creating the new top level window.
uint32_t change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
// Verify the properties were sent to the server.
base::Optional<base::flat_map<std::string, std::vector<uint8_t>>>
transport_properties = window_tree()->GetLastNewWindowProperties();
ASSERT_TRUE(transport_properties.has_value());
std::map<std::string, std::vector<uint8_t>> properties2 =
mojo::FlatMapToMap(*transport_properties);
ASSERT_EQ(1u, properties2.count(kTestPropertyServerKey1));
// PropertyConverter uses int64_t values, even for smaller types like uint8_t.
ASSERT_EQ(8u, properties2[kTestPropertyServerKey1].size());
EXPECT_EQ(static_cast<int64_t>(property_value),
mojo::ConvertTo<int64_t>(properties2[kTestPropertyServerKey1]));
ASSERT_EQ(1u, properties2.count(kUnknownPropertyKey));
ASSERT_EQ(sizeof(UnknownPropertyType),
properties2[kUnknownPropertyKey].size());
EXPECT_EQ(kUnknownPropertyValue, mojo::ConvertTo<UnknownPropertyType>(
properties2[kUnknownPropertyKey]));
}
namespace {
class CloseWindowWindowTreeHostObserver : public aura::WindowTreeHostObserver {
public:
CloseWindowWindowTreeHostObserver() {}
~CloseWindowWindowTreeHostObserver() override {}
bool root_destroyed() const { return root_destroyed_; }
// aura::WindowTreeHostObserver::
void OnHostCloseRequested(aura::WindowTreeHost* host) override {
root_destroyed_ = true;
}
private:
bool root_destroyed_ = false;
DISALLOW_COPY_AND_ASSIGN(CloseWindowWindowTreeHostObserver);
};
} // namespace
TEST_F(WindowTreeClientTest, CloseWindow) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
window_tree_host.InitHost();
CloseWindowWindowTreeHostObserver observer;
window_tree_host.AddObserver(&observer);
Window* top_level = window_tree_host.window();
// Close a root window should send close request to the observer of its
// WindowTreeHost.
EXPECT_FALSE(observer.root_destroyed());
window_tree_client()->RequestClose(server_id(top_level));
EXPECT_TRUE(observer.root_destroyed());
}
// Tests both SetCapture and ReleaseCapture, to ensure that Window is properly
// updated on failures.
TEST_F(WindowTreeClientTest, ExplicitCapture) {
root_window()->SetCapture();
EXPECT_TRUE(root_window()->HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, false));
EXPECT_FALSE(root_window()->HasCapture());
root_window()->SetCapture();
EXPECT_TRUE(root_window()->HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, true));
EXPECT_TRUE(root_window()->HasCapture());
root_window()->ReleaseCapture();
EXPECT_FALSE(root_window()->HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, false));
EXPECT_TRUE(root_window()->HasCapture());
root_window()->ReleaseCapture();
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, true));
EXPECT_FALSE(root_window()->HasCapture());
}
// Tests that when capture is lost, while there is a release capture request
// inflight, that the revert value of that request is updated correctly.
TEST_F(WindowTreeClientTest, LostCaptureDifferentInFlightChange) {
root_window()->SetCapture();
EXPECT_TRUE(root_window()->HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, true));
EXPECT_TRUE(root_window()->HasCapture());
// The ReleaseCapture should be updated to the revert of the SetCapture.
root_window()->ReleaseCapture();
window_tree_client()->OnCaptureChanged(0, server_id(root_window()));
EXPECT_FALSE(root_window()->HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, false));
EXPECT_FALSE(root_window()->HasCapture());
}
// Tests that while two windows can inflight capture requests, that the
// WindowTreeClient only identifies one as having the current capture.
TEST_F(WindowTreeClientTest, TwoWindowsRequestCapture) {
Window child(nullptr);
child.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child);
child.Show();
root_window()->SetCapture();
EXPECT_TRUE(root_window()->HasCapture());
child.SetCapture();
EXPECT_TRUE(child.HasCapture());
EXPECT_FALSE(root_window()->HasCapture());
ASSERT_TRUE(
window_tree()->AckFirstChangeOfType(WindowTreeChangeType::CAPTURE, true));
EXPECT_FALSE(root_window()->HasCapture());
EXPECT_TRUE(child.HasCapture());
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, false));
EXPECT_FALSE(child.HasCapture());
EXPECT_TRUE(root_window()->HasCapture());
window_tree_client()->OnCaptureChanged(0, server_id(root_window()));
EXPECT_FALSE(root_window()->HasCapture());
}
TEST_F(WindowTreeClientTest, WindowDestroyedWhileTransientChildHasCapture) {
std::unique_ptr<Window> transient_parent(std::make_unique<Window>(nullptr));
transient_parent->Init(ui::LAYER_NOT_DRAWN);
// Owned by |transient_parent|.
Window* transient_child = new Window(nullptr);
transient_child->Init(ui::LAYER_NOT_DRAWN);
transient_parent->Show();
transient_child->Show();
root_window()->AddChild(transient_parent.get());
root_window()->AddChild(transient_child);
client::GetTransientWindowClient()->AddTransientChild(transient_parent.get(),
transient_child);
WindowTracker tracker;
tracker.Add(transient_parent.get());
tracker.Add(transient_child);
// Request a capture on the transient child, then destroy the transient
// parent. That will destroy both windows, and should reset the capture window
// correctly.
transient_child->SetCapture();
transient_parent.reset();
EXPECT_TRUE(tracker.windows().empty());
// Create a new Window, and attempt to place capture on that.
Window child(nullptr);
child.Init(ui::LAYER_NOT_DRAWN);
child.Show();
root_window()->AddChild(&child);
child.SetCapture();
EXPECT_TRUE(child.HasCapture());
}
namespace {
class CaptureRecorder : public client::CaptureClientObserver {
public:
explicit CaptureRecorder(Window* root_window) : root_window_(root_window) {
client::GetCaptureClient(root_window)->AddObserver(this);
}
~CaptureRecorder() override {
client::GetCaptureClient(root_window_)->RemoveObserver(this);
}
void reset_capture_captured_count() { capture_changed_count_ = 0; }
int capture_changed_count() const { return capture_changed_count_; }
int last_gained_capture_window_id() const {
return last_gained_capture_window_id_;
}
int last_lost_capture_window_id() const {
return last_lost_capture_window_id_;
}
// client::CaptureClientObserver:
void OnCaptureChanged(Window* lost_capture, Window* gained_capture) override {
capture_changed_count_++;
last_gained_capture_window_id_ = gained_capture ? gained_capture->id() : 0;
last_lost_capture_window_id_ = lost_capture ? lost_capture->id() : 0;
}
private:
Window* root_window_;
int capture_changed_count_ = 0;
int last_gained_capture_window_id_ = 0;
int last_lost_capture_window_id_ = 0;
DISALLOW_COPY_AND_ASSIGN(CaptureRecorder);
};
} // namespace
TEST_F(WindowTreeClientTest, OnWindowTreeCaptureChanged) {
CaptureRecorder capture_recorder(root_window());
std::unique_ptr<Window> child1(std::make_unique<Window>(nullptr));
const int child1_id = 1;
child1->Init(ui::LAYER_NOT_DRAWN);
child1->set_id(child1_id);
child1->Show();
root_window()->AddChild(child1.get());
Window child2(nullptr);
const int child2_id = 2;
child2.Init(ui::LAYER_NOT_DRAWN);
child2.set_id(child2_id);
child2.Show();
root_window()->AddChild(&child2);
EXPECT_EQ(0, capture_recorder.capture_changed_count());
// Give capture to child1 and ensure everyone is notified correctly.
child1->SetCapture();
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, true));
EXPECT_EQ(1, capture_recorder.capture_changed_count());
EXPECT_EQ(child1_id, capture_recorder.last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder.last_lost_capture_window_id());
capture_recorder.reset_capture_captured_count();
// Deleting a window with capture should notify observers as well.
child1.reset();
// No capture change is sent during deletion (the server side sees the window
// deletion too and resets internal state).
EXPECT_EQ(
0u, window_tree()->GetChangeCountForType(WindowTreeChangeType::CAPTURE));
EXPECT_EQ(1, capture_recorder.capture_changed_count());
EXPECT_EQ(0, capture_recorder.last_gained_capture_window_id());
EXPECT_EQ(child1_id, capture_recorder.last_lost_capture_window_id());
capture_recorder.reset_capture_captured_count();
// Changes originating from server should notify observers too.
window_tree_client()->OnCaptureChanged(server_id(&child2), 0);
EXPECT_EQ(1, capture_recorder.capture_changed_count());
EXPECT_EQ(child2_id, capture_recorder.last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder.last_lost_capture_window_id());
capture_recorder.reset_capture_captured_count();
}
TEST_F(WindowTreeClientTest, TwoWindowTreesRequestCapture) {
std::unique_ptr<TopLevel> top_level1 = CreateTopLevel();
std::unique_ptr<TopLevel> top_level2 = CreateTopLevel();
aura::Window* root1 = top_level1->host->window();
aura::Window* root2 = top_level2->host->window();
auto capture_recorder1 = std::make_unique<CaptureRecorder>(root1);
auto capture_recorder2 = std::make_unique<CaptureRecorder>(root2);
EXPECT_NE(client::GetCaptureClient(root1), client::GetCaptureClient(root2));
EXPECT_EQ(0, capture_recorder1->capture_changed_count());
EXPECT_EQ(0, capture_recorder2->capture_changed_count());
// Give capture to root2 and ensure everyone is notified correctly.
root2->SetCapture();
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::CAPTURE, true));
EXPECT_EQ(0, capture_recorder1->capture_changed_count());
EXPECT_EQ(1, capture_recorder2->capture_changed_count());
EXPECT_EQ(root2->id(), capture_recorder2->last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder2->last_lost_capture_window_id());
root2->ReleaseCapture();
capture_recorder1->reset_capture_captured_count();
capture_recorder2->reset_capture_captured_count();
// Releasing capture of root2 shouldn't affect root1 capture.
root2->SetCapture();
root1->SetCapture();
root2->ReleaseCapture();
EXPECT_EQ(1, capture_recorder1->capture_changed_count());
EXPECT_EQ(2, capture_recorder2->capture_changed_count());
EXPECT_EQ(root1->id(), capture_recorder1->last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder1->last_lost_capture_window_id());
EXPECT_EQ(0, capture_recorder2->last_gained_capture_window_id());
EXPECT_EQ(root2->id(), capture_recorder2->last_lost_capture_window_id());
capture_recorder1->reset_capture_captured_count();
capture_recorder2->reset_capture_captured_count();
// Destroying top_level2 shouldn't affect root1 capture; see crbug.com/873743.
auto* synchronizer = window_tree_client_impl()->capture_synchronizer();
EXPECT_EQ(WindowMus::Get(root1), synchronizer->capture_window());
synchronizer->DetachFromCaptureClient(top_level2->capture_client.get());
capture_recorder2.reset();
top_level2->host.reset();
EXPECT_EQ(WindowMus::Get(root1), synchronizer->capture_window());
EXPECT_EQ(0, capture_recorder1->capture_changed_count());
EXPECT_EQ(root1->id(), capture_recorder1->last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder1->last_lost_capture_window_id());
}
TEST_F(WindowTreeClientTest, ModalTypeWindowFail) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
window.SetProperty(client::kModalKey, ui::MODAL_TYPE_WINDOW);
// Make sure server was told about it, and have the server say it failed.
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, false));
// Type should be back to MODAL_TYPE_NONE as the server didn't accept the
// change.
EXPECT_EQ(ui::MODAL_TYPE_NONE, window.GetProperty(client::kModalKey));
// Server is told that the type is set back to MODAL_TYPE_NONE.
EXPECT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, true));
// Type should still remain MODAL_TYPE_NONE.
EXPECT_EQ(ui::MODAL_TYPE_NONE, window.GetProperty(client::kModalKey));
}
TEST_F(WindowTreeClientTest, ModalTypeNoneFail) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
// First, set modality type to window sucessfully.
window.SetProperty(client::kModalKey, ui::MODAL_TYPE_WINDOW);
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, true));
EXPECT_EQ(ui::MODAL_TYPE_WINDOW, window.GetProperty(client::kModalKey));
// Now, set type to MODAL_TYPE_NONE, and have the server say it failed.
window.SetProperty(client::kModalKey, ui::MODAL_TYPE_NONE);
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, false));
// Type should be back to MODAL_TYPE_WINDOW as the server didn't accept the
// change.
EXPECT_EQ(ui::MODAL_TYPE_WINDOW, window.GetProperty(client::kModalKey));
// Server is told that the type is set back to MODAL_TYPE_WINDOW.
EXPECT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, true));
// Type should still remain MODAL_TYPE_WINDOW.
EXPECT_EQ(ui::MODAL_TYPE_WINDOW, window.GetProperty(client::kModalKey));
}
TEST_F(WindowTreeClientTest, ModalTypeSuccess) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
// Set modality type to MODAL_TYPE_WINDOW, MODAL_TYPE_SYSTEM, and then back to
// MODAL_TYPE_NONE, and make sure it succeeds each time.
ui::ModalType kModalTypes[] = {ui::MODAL_TYPE_WINDOW, ui::MODAL_TYPE_SYSTEM,
ui::MODAL_TYPE_NONE};
for (size_t i = 0; i < base::size(kModalTypes); i++) {
window.SetProperty(client::kModalKey, kModalTypes[i]);
// Ack change as succeeding.
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::MODAL, true));
EXPECT_EQ(kModalTypes[i], window.GetProperty(client::kModalKey));
}
// There should be no more modal changes.
EXPECT_FALSE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, false));
}
// Verifies OnWindowHierarchyChanged() deals correctly with identifying existing
// windows.
TEST_F(WindowTreeClientTest, OnWindowHierarchyChangedWithExistingWindow) {
Window* window1 = new Window(nullptr);
window1->Init(ui::LAYER_NOT_DRAWN);
Window* window2 = new Window(nullptr);
window2->Init(ui::LAYER_NOT_DRAWN);
window_tree()->AckAllChanges();
const ws::Id server_window_id = server_id(root_window()) + 11;
ws::mojom::WindowDataPtr data1 = ws::mojom::WindowData::New();
ws::mojom::WindowDataPtr data2 = ws::mojom::WindowData::New();
ws::mojom::WindowDataPtr data3 = ws::mojom::WindowData::New();
data1->parent_id = server_id(root_window());
data1->window_id = server_window_id;
data1->bounds = gfx::Rect(1, 2, 3, 4);
data2->parent_id = server_window_id;
data2->window_id = WindowMus::Get(window1)->server_id();
data2->bounds = gfx::Rect(1, 2, 3, 4);
data3->parent_id = server_window_id;
data3->window_id = WindowMus::Get(window2)->server_id();
data3->bounds = gfx::Rect(1, 2, 3, 4);
std::vector<ws::mojom::WindowDataPtr> data_array(3);
data_array[0] = std::move(data1);
data_array[1] = std::move(data2);
data_array[2] = std::move(data3);
window_tree_client()->OnWindowHierarchyChanged(
server_window_id, 0, server_id(root_window()), std::move(data_array));
ASSERT_FALSE(window_tree()->has_change());
ASSERT_EQ(1u, root_window()->children().size());
Window* server_window = root_window()->children()[0];
EXPECT_EQ(window1->parent(), server_window);
EXPECT_EQ(window2->parent(), server_window);
ASSERT_EQ(2u, server_window->children().size());
EXPECT_EQ(window1, server_window->children()[0]);
EXPECT_EQ(window2, server_window->children()[1]);
}
// Ensures when WindowTreeClient::OnWindowDeleted() is called nothing is
// scheduled on the server side.
TEST_F(WindowTreeClientTest, OnWindowDeletedDoesntNotifyServer) {
Window window1(nullptr);
window1.Init(ui::LAYER_NOT_DRAWN);
Window* window2 = new Window(nullptr);
window2->Init(ui::LAYER_NOT_DRAWN);
window1.AddChild(window2);
window_tree()->AckAllChanges();
window_tree_client()->OnWindowDeleted(server_id(window2));
EXPECT_FALSE(window_tree()->has_change());
}
TEST_F(WindowTreeClientTestHighDPI, SetBounds) {
const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
ASSERT_NE(new_bounds, root_window()->bounds());
root_window()->SetBounds(new_bounds);
EXPECT_EQ(new_bounds, root_window()->bounds());
// Simulate the server responding with a bounds change. Server operates in
// dips.
const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 200, 200));
window_tree_client()->OnWindowBoundsChanged(
server_id(root_window()), server_changed_bounds,
GenerateLocalSurfaceIdForNewTopLevel());
EXPECT_EQ(server_changed_bounds, root_window()->bounds());
}
TEST_F(WindowTreeClientTestHighDPI, NewTopLevelWindowBounds) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
Window* top_level = window_tree_host.window();
window_tree_host.InitHost();
gfx::Rect bounds(2, 4, 6, 8);
ws::mojom::WindowDataPtr data = ws::mojom::WindowData::New();
data->window_id = server_id(top_level);
data->bounds = bounds;
const int64_t display_id = 10;
uint32_t change_id;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &change_id));
window_tree_client()->OnTopLevelCreated(
change_id, std::move(data), display_id, true,
GenerateLocalSurfaceIdForNewTopLevel());
// aura::Window should operate in DIP and aura::WindowTreeHost should operate
// in pixels. Only the size is compared for |top_level| as the WindowTreeHost
// is the place that maintains the location.
EXPECT_EQ(bounds.size(), top_level->bounds().size());
const float device_scale_factor = 2.0f;
EXPECT_EQ(gfx::ConvertRectToPixel(device_scale_factor, bounds),
top_level->GetHost()->GetBoundsInPixels());
}
TEST_F(WindowTreeClientTestHighDPI, HostCtorInitializesDisplayScale) {
// WindowTreeHost's ctor attempts to initialize the display scale factor.
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
EXPECT_EQ(2.0f, window_tree_host.device_scale_factor());
// This test sets pixel bounds before calling WindowTreeHost::InitHost() to
// simulate circumstances similar to <http://crbug.com/899084>. Pixel bounds
// should still be scaled to DIP bounds for the window tree before InitHost.
gfx::Rect bounds(2, 4, 60, 80);
gfx::Rect pixel_bounds = gfx::ConvertRectToPixel(2.0f, bounds);
static_cast<WindowTreeHost*>(&window_tree_host)
->SetBoundsInPixels(pixel_bounds);
EXPECT_EQ(bounds, window_tree()->last_set_window_bounds());
}
TEST_F(WindowTreeClientTestHighDPI, ObservedInputEventsInDip) {
display::Screen* screen = display::Screen::GetScreen();
const display::Display primary_display = screen->GetPrimaryDisplay();
ASSERT_EQ(2.0f, primary_display.device_scale_factor());
std::unique_ptr<Window> top_level(std::make_unique<Window>(nullptr));
top_level->SetType(client::WINDOW_TYPE_NORMAL);
top_level->Init(ui::LAYER_NOT_DRAWN);
top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
top_level->Show();
// Start observing touch press events.
TestEventObserver test_event_observer;
top_level->env()->AddEventObserver(&test_event_observer, top_level->env(),
{ui::ET_TOUCH_PRESSED});
// Simulate the server sending an observed event in screen dip coordinates.
const gfx::Point location(10, 12);
ui::TouchEvent touch_press(
ui::ET_TOUCH_PRESSED, location, ui::EventTimeForNow(),
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 1));
window_tree_client()->OnObservedInputEvent(ui::Event::Clone(touch_press));
// The observer should have received the event in screen dip coordinates.
const ui::Event* last_event = test_event_observer.last_event();
ASSERT_TRUE(last_event);
EXPECT_EQ(location, last_event->AsLocatedEvent()->location());
EXPECT_EQ(location, last_event->AsLocatedEvent()->root_location());
}
TEST_F(WindowTreeClientTestHighDPI, InputEventsInDip) {
WindowTreeHostMus window_tree_host(
CreateInitParamsForTopLevel(window_tree_client_impl()));
display::Screen* screen = display::Screen::GetScreen();
display::Display display;
ASSERT_TRUE(
screen->GetDisplayWithDisplayId(window_tree_host.display_id(), &display));
ASSERT_EQ(2.0f, display.device_scale_factor());
Window* top_level = window_tree_host.window();
const gfx::Rect bounds_in_pixels(0, 0, 100, 100);
static_cast<WindowTreeHost*>(&window_tree_host)
->SetBoundsInPixels(bounds_in_pixels);
window_tree_host.InitHost();
window_tree_host.Show();
EXPECT_EQ(gfx::ConvertRectToDIP(2.0f, bounds_in_pixels), top_level->bounds());
EXPECT_EQ(bounds_in_pixels, window_tree_host.GetBoundsInPixels());
InputEventBasicTestWindowDelegate window_delegate1(window_tree());
Window child1(&window_delegate1);
child1.Init(ui::LAYER_NOT_DRAWN);
child1.SetEventTargeter(std::make_unique<test::TestWindowTargeter>());
top_level->AddChild(&child1);
child1.SetBounds(gfx::Rect(10, 10, 100, 100));
child1.Show();
InputEventBasicTestWindowDelegate window_delegate2(window_tree());
Window child2(&window_delegate2);
child2.Init(ui::LAYER_NOT_DRAWN);
child1.AddChild(&child2);
child2.SetBounds(gfx::Rect(20, 30, 100, 100));
child2.Show();
EXPECT_EQ(0, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
// child1 has a custom targeter set which would always return itself as the
// target window therefore event should go to child1 and should be in dip.
const gfx::Point event_location(50, 60);
uint32_t event_id = 1;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
std::unique_ptr<ui::Event> ui_event(
new ui::MouseEvent(ui::ET_MOUSE_MOVED, event_location, event_location,
ui::EventTimeForNow(), ui::EF_NONE, 0));
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child1), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
EXPECT_EQ(event_location, window_delegate1.last_event_location());
window_delegate1.reset();
window_delegate2.reset();
// Event location will be transformed and should be in dip.
event_id = 2;
window_delegate1.set_event_id(event_id);
window_delegate2.set_event_id(event_id);
window_tree_client()->OnWindowInputEvent(
event_id, server_id(&child2), window_tree_host.display_id(),
ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(event_id));
EXPECT_EQ(ws::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(event_id));
EXPECT_EQ(1, window_delegate1.move_count());
EXPECT_EQ(0, window_delegate2.move_count());
gfx::Point transformed_event_location(event_location.x() + 20,
event_location.y() + 30);
EXPECT_EQ(transformed_event_location, window_delegate1.last_event_location());
}
using WindowTreeClientDestructionTest = test::AuraTestBaseMus;
TEST_F(WindowTreeClientDestructionTest, Shutdown) {
// Windows should be able to outlive the WindowTreeClient.
aura::Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
DeleteWindowTreeClient();
// And it should be possible to create Windows after the WindowTreeClient has
// been deleted.
aura::Window window2(nullptr);
window2.Init(ui::LAYER_NOT_DRAWN);
}
TEST_F(WindowTreeClientDestructionTest, WindowsFromOtherConnectionsDeleted) {
std::unique_ptr<Window> other_client_window =
CreateWindowUsingId(window_tree_client_impl(), 10, nullptr);
WindowTracker window_tracker;
window_tracker.Add(other_client_window.get());
other_client_window.release();
DeleteWindowTreeClient();
// Deleting WindowTreeClient should delete the Window that was in
// |window_tracker|.
EXPECT_TRUE(window_tracker.windows().empty());
}
class TestEmbedRootDelegate : public EmbedRootDelegate {
public:
TestEmbedRootDelegate() = default;
~TestEmbedRootDelegate() override = default;
// EmbedRootDelegate:
void OnEmbedTokenAvailable(const base::UnguessableToken& token) override {}
void OnEmbed(Window* window) override {}
void OnUnembed() override {}
private:
DISALLOW_COPY_AND_ASSIGN(TestEmbedRootDelegate);
};
// Verifies we don't crash when focus changes to a window in an EmbedRoot.
TEST_F(WindowTreeClientTest, ChangeFocusInEmbedRootWindow) {
TestEmbedRootDelegate embed_root_delegate;
std::unique_ptr<EmbedRoot> embed_root =
window_tree_client_impl()->CreateEmbedRoot(&embed_root_delegate);
WindowTreeClientTestApi(window_tree_client_impl())
.CallOnEmbedFromToken(embed_root.get());
ASSERT_TRUE(embed_root->window());
window_tree_client()->OnWindowFocused(server_id(embed_root->window()));
}
// Verifies visibility from server is applied properly when an embed root is
// created.
TEST_F(WindowTreeClientTest, EmbedRootVisibility) {
for (bool visible : {true, false}) {
TestEmbedRootDelegate embed_root_delegate;
std::unique_ptr<EmbedRoot> embed_root =
window_tree_client_impl()->CreateEmbedRoot(&embed_root_delegate);
WindowTreeClientTestApi(window_tree_client_impl())
.CallOnEmbedFromToken(embed_root.get(), visible);
ASSERT_TRUE(embed_root->window());
EXPECT_EQ(visible, embed_root->window()->TargetVisibility());
EXPECT_EQ(visible,
embed_root->window()->GetHost()->compositor()->IsVisible());
}
}
TEST_F(WindowTreeClientTest, PerformWindowMove) {
int call_count = 0;
bool last_result = false;
WindowTreeHostMus* host_mus = static_cast<WindowTreeHostMus*>(host());
host_mus->PerformWindowMove(
host_mus->window(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
HTCAPTION, base::BindOnce(&OnWindowMoveDone, &call_count, &last_result));
EXPECT_EQ(0, call_count);
window_tree()->AckAllChanges();
EXPECT_EQ(1, call_count);
EXPECT_TRUE(last_result);
host_mus->PerformWindowMove(
host_mus->window(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
HTCAPTION, base::BindOnce(&OnWindowMoveDone, &call_count, &last_result));
window_tree()->AckAllChangesOfType(WindowTreeChangeType::OTHER, false);
EXPECT_EQ(2, call_count);
EXPECT_FALSE(last_result);
}
TEST_F(WindowTreeClientTest, PerformWindowMoveDoneAfterDelete) {
int call_count = 0;
bool last_result = false;
auto host_mus = std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
host_mus->InitHost();
window_tree()->AckAllChanges();
host_mus->PerformWindowMove(
host_mus->window(), ws::mojom::MoveLoopSource::MOUSE, gfx::Point(),
HTCAPTION, base::BindOnce(&OnWindowMoveDone, &call_count, &last_result));
EXPECT_EQ(0, call_count);
host_mus.reset();
window_tree()->AckAllChanges();
EXPECT_EQ(1, call_count);
EXPECT_TRUE(last_result);
}
TEST_F(WindowTreeClientTest, PerformWindowMoveTransferEvents) {
int call_count = 0;
bool last_result = false;
aura::Window* window = CreateNormalWindow(10, host()->window(), nullptr);
WindowTreeHostMus* host_mus = static_cast<WindowTreeHostMus*>(host());
window->SetCapture();
host_mus->PerformWindowMove(
window, ws::mojom::MoveLoopSource::TOUCH, gfx::Point(), HTCAPTION,
base::BindOnce(&OnWindowMoveDone, &call_count, &last_result));
EXPECT_EQ(0, call_count);
EXPECT_EQ(WindowPortMus::Get(window)->server_id(),
window_tree()->last_transfer_current());
EXPECT_EQ(WindowPortMus::Get(host_mus->window())->server_id(),
window_tree()->last_transfer_new());
EXPECT_FALSE(window->HasCapture());
window_tree()->AckAllChanges();
EXPECT_EQ(1, call_count);
EXPECT_TRUE(last_result);
EXPECT_EQ(WindowPortMus::Get(host_mus->window())->server_id(),
window_tree()->last_transfer_current());
EXPECT_EQ(WindowPortMus::Get(window)->server_id(),
window_tree()->last_transfer_new());
}
// Verifies occlusion state from server is applied to underlying window.
TEST_F(WindowTreeClientTest, OcclusionStateFromServer) {
struct {
const char* name;
bool window_is_visible;
ws::mojom::OcclusionState changed_state_from_server;
Window::OcclusionState expected_state;
} kTestCases[] = {
// VISIBLE is set when window is visible.
{"visible-set", true, ws::mojom::OcclusionState::kVisible,
Window::OcclusionState::VISIBLE},
// VISIBLE is not set when window hidden.
{"visible-not-set", false, ws::mojom::OcclusionState::kVisible,
Window::OcclusionState::HIDDEN},
// OCCLUDED is always set.
{"occluded-with-visible", true, ws::mojom::OcclusionState::kOccluded,
Window::OcclusionState::OCCLUDED},
{"occluded-with-invisible", false, ws::mojom::OcclusionState::kOccluded,
Window::OcclusionState::OCCLUDED},
// HIDDEN is set when window target visibility is false.
{"hidden-set", false, ws::mojom::OcclusionState::kHidden,
Window::OcclusionState::HIDDEN},
// HIDDEN is not set when window target visibility is true.
{"hidden-not-set", true, ws::mojom::OcclusionState::kHidden,
Window::OcclusionState::VISIBLE},
};
for (const auto& test : kTestCases) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
window.set_owned_by_parent(false);
root_window()->AddChild(&window);
window.TrackOcclusionState();
ASSERT_EQ(Window::OcclusionState::HIDDEN, window.occlusion_state());
if (test.window_is_visible && !window.IsVisible()) {
window.Show();
} else if (!test.window_is_visible && window.IsVisible()) {
window.Hide();
}
window_tree_client()->OnOcclusionStatesChanged(
{{server_id(&window), test.changed_state_from_server}});
EXPECT_EQ(test.expected_state, window.occlusion_state()) << test.name;
}
}
// Assertions around LocalSurfaceIds for top-levels.
TEST_F(WindowTreeClientTest, TopLevelSurfaceIds) {
std::unique_ptr<TopLevel> top_level = std::make_unique<TopLevel>();
top_level->host = std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
top_level->host->InitHost();
CreateCaptureClientForTopLevel(top_level.get());
viz::LocalSurfaceIdAllocation initial_lsia =
parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
uint32_t new_top_level_change_id = 0;
ASSERT_TRUE(window_tree()->GetAndRemoveFirstChangeOfType(
WindowTreeChangeType::NEW_TOP_LEVEL, &new_top_level_change_id));
EXPECT_EQ(0u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
static_cast<WindowTreeHost*>(top_level->host.get())
->SetBoundsInPixels(gfx::Rect(1, 2, 3, 4));
EXPECT_EQ(1u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
// As the initial LocalSurfaceId comes from the server, there shouldn't be
// a LocalSurfaceId in the client yet.
EXPECT_FALSE(window_tree()->last_local_surface_id());
EXPECT_FALSE(top_level->host->compositor()
->GetLocalSurfaceIdAllocation()
.local_surface_id()
.is_valid());
size_t number_of_changes = window_tree()->number_of_changes();
AckNewTopLevelWindow(top_level.get(), new_top_level_change_id);
// Still no surface id as bounds still in flight.
EXPECT_FALSE(window_tree()->last_local_surface_id());
// No new changes.
EXPECT_EQ(number_of_changes, window_tree()->number_of_changes());
EXPECT_EQ(
0u,
window_tree()->get_and_clear_update_local_surface_id_from_child_count());
// Ack the bounds change.
EXPECT_TRUE(
window_tree()->AckFirstChangeOfType(WindowTreeChangeType::BOUNDS, true));
EXPECT_EQ(
1u,
window_tree()->get_and_clear_update_local_surface_id_from_child_count());
viz::LocalSurfaceIdAllocation lsia =
GenerateChildLocalIdFromParentAllocator();
ASSERT_TRUE(window_tree()->last_local_surface_id());
Window* top_level_window = top_level->host->window();
EXPECT_EQ(top_level_window->GetLocalSurfaceIdAllocation().local_surface_id(),
*window_tree()->last_local_surface_id());
EXPECT_EQ(top_level->host->compositor()
->GetLocalSurfaceIdAllocation()
.local_surface_id(),
*window_tree()->last_local_surface_id());
EXPECT_EQ(lsia.local_surface_id(),
top_level_window->GetLocalSurfaceIdAllocation().local_surface_id());
}
TEST_F(WindowTreeClientTest, TopLevelBoundsChangeFails) {
std::unique_ptr<TopLevel> top_level = std::make_unique<TopLevel>();
top_level->host = std::make_unique<WindowTreeHostMus>(
CreateInitParamsForTopLevel(window_tree_client_impl()));
top_level->host->InitHost();
CreateCaptureClientForTopLevel(top_level.get());
aura::Window* root = top_level->host->window();
AckNewTopLevelWindow(top_level.get());
EXPECT_EQ(0u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
ASSERT_TRUE(root->GetLocalSurfaceIdAllocation().IsValid());
EXPECT_EQ(
parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation()
.local_surface_id()
.parent_sequence_number(),
root->GetLocalSurfaceIdAllocation()
.local_surface_id()
.parent_sequence_number());
// Resize the window.
const gfx::Rect initial_bounds(1, 2, 3, 4);
static_cast<WindowTreeHost*>(top_level->host.get())
->SetBoundsInPixels(initial_bounds);
auto initial_lsia = root->GetLocalSurfaceIdAllocation();
EXPECT_TRUE(initial_lsia.IsValid());
EXPECT_EQ(1u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
// The client should increment the child_sequence_number when asking for a new
// bounds.
auto expected_lsia = GenerateChildLocalIdFromParentAllocator();
EXPECT_EQ(expected_lsia.local_surface_id(),
window_tree()->last_local_surface_id());
parent_local_surface_id_allocator_.UpdateFromChild(expected_lsia);
// Server responds with a bounds change. This isn't immediately processed as
// client waiting for ack from SetBoundsInPixels() call above.
parent_local_surface_id_allocator_.GenerateId();
const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 200, 200));
window_tree_client()->OnWindowBoundsChanged(
server_id(root), server_changed_bounds,
parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation());
const viz::LocalSurfaceId local_surface_id1 =
root->GetLocalSurfaceIdAllocation().local_surface_id();
EXPECT_EQ(expected_lsia.local_surface_id(), local_surface_id1);
// There should still only be 1 bounds change.
EXPECT_EQ(1u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
// Server responds with false to first bounds changes. Results in reverting to
// |server_changed_bounds| *and* increasing the parent_sequence_number.
EXPECT_TRUE(
window_tree()->AckFirstChangeOfType(WindowTreeChangeType::BOUNDS, false));
expected_lsia =
parent_local_surface_id_allocator_.GetCurrentLocalSurfaceIdAllocation();
const viz::LocalSurfaceId local_surface_id2 =
root->GetLocalSurfaceIdAllocation().local_surface_id();
EXPECT_EQ(server_changed_bounds, root->bounds());
EXPECT_EQ(server_changed_bounds, top_level->host->GetBoundsInPixels());
EXPECT_EQ(expected_lsia.local_surface_id(), local_surface_id2);
// No new bounds changes should be generated.
EXPECT_EQ(0u,
window_tree()->GetChangeCountForType(WindowTreeChangeType::BOUNDS));
// As the server supplied a new LocalSurfaceId the pending LocalSurfaceId
// should be cleared.
EXPECT_FALSE(top_level->host->has_pending_local_surface_id_from_server());
}
TEST_F(WindowTreeClientTest, OnEmbedGetsLocalSurfaceId) {
viz::ParentLocalSurfaceIdAllocator parent_local_surface_id_allocator;
parent_local_surface_id_allocator.GenerateId();
TestEmbedRootDelegate embed_root_delegate;
auto initial_lsia =
parent_local_surface_id_allocator.GetCurrentLocalSurfaceIdAllocation();
std::unique_ptr<EmbedRoot> embed_root =
window_tree_client_impl()->CreateEmbedRoot(&embed_root_delegate);
WindowTreeClientTestApi(window_tree_client_impl())
.CallOnEmbedFromToken(embed_root.get(), false, initial_lsia);
ASSERT_TRUE(embed_root->window());
const viz::LocalSurfaceIdAllocation lsia =
embed_root->window()->GetLocalSurfaceIdAllocation();
EXPECT_EQ(initial_lsia, lsia);
EXPECT_EQ(initial_lsia, embed_root->window()
->GetHost()
->compositor()
->GetLocalSurfaceIdAllocation());
}
TEST_F(WindowTreeClientTest,
NotifyServerOnDidGenerateLocalSurfaceIdAllocation) {
TestEmbedRootDelegate embed_root_delegate;
std::unique_ptr<EmbedRoot> embed_root =
window_tree_client_impl()->CreateEmbedRoot(&embed_root_delegate);
WindowTreeClientTestApi(window_tree_client_impl())
.CallOnEmbedFromToken(embed_root.get());
ASSERT_TRUE(embed_root->window());
const viz::LocalSurfaceIdAllocation lsia =
embed_root->window()->GetLocalSurfaceIdAllocation();
viz::ChildLocalSurfaceIdAllocator child_allocator;
child_allocator.UpdateFromParent(lsia);
child_allocator.GenerateId();
auto updated_lsia = child_allocator.GetCurrentLocalSurfaceIdAllocation();
EmbeddedAllocator* allocator = static_cast<EmbeddedAllocator*>(
WindowPortMusTestHelper(embed_root->window()).GetAllocator());
ASSERT_TRUE(allocator);
window_tree()->get_and_clear_update_local_surface_id_from_child_count();
// This mirrors what happens when LayerTreeHostImpl generates a new id.
allocator->DidGenerateLocalSurfaceIdAllocation(
embed_root->window()->GetHost()->compositor(), updated_lsia);
EXPECT_EQ(
1u,
window_tree()->get_and_clear_update_local_surface_id_from_child_count());
EXPECT_EQ(updated_lsia.local_surface_id(),
window_tree()->last_local_surface_id());
EXPECT_EQ(updated_lsia, embed_root->window()
->GetHost()
->compositor()
->GetLocalSurfaceIdAllocation());
EXPECT_EQ(updated_lsia, embed_root->window()->GetLocalSurfaceIdAllocation());
}
} // namespace aura