blob: 76dcf10e58846047070855359b207796cb78cc4d [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 "base/command_line.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "mojo/public/cpp/bindings/map.h"
#include "services/ui/public/cpp/property_type_converters.h"
#include "services/ui/public/interfaces/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/property_converter.h"
#include "ui/aura/mus/window_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/test/aura_mus_test_base.h"
#include "ui/aura/test/mus/test_window_tree.h"
#include "ui/aura/test/mus/window_tree_client_private.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/window.h"
#include "ui/aura/window_tracker.h"
#include "ui/aura/window_tree_host_observer.h"
#include "ui/base/class_property.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_utils.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/gfx/geometry/rect.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);
const char kTestPropertyServerKey1[] = "test-property-server1";
const char kTestPropertyServerKey2[] = "test-property-server2";
const char kTestPropertyServerKey3[] = "test-property-server3";
Id server_id(Window* window) {
return WindowMus::Get(window)->server_id();
}
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->RegisterProperty(kTestPropertyKey1, kTestPropertyServerKey1);
converter->RegisterProperty(kTestPropertyKey2, kTestPropertyServerKey2);
converter->RegisterProperty(kTestPropertyKey3, kTestPropertyServerKey3);
}
// 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);
}
} // namespace
using WindowTreeClientWmTest = test::AuraMusWmTestBase;
using WindowTreeClientClientTest = test::AuraMusClientTestBase;
// WindowTreeClientWmTest with --force-device-scale-factor=2.
class WindowTreeClientWmTestHighDPI : public WindowTreeClientWmTest {
public:
WindowTreeClientWmTestHighDPI() {}
~WindowTreeClientWmTestHighDPI() override {}
// WindowTreeClientWmTest:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kForceDeviceScaleFactor, "2");
WindowTreeClientWmTest::SetUp();
}
private:
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientWmTestHighDPI);
};
// WindowTreeClientClientTest with --force-device-scale-factor=2.
class WindowTreeClientClientTestHighDPI : public WindowTreeClientClientTest {
public:
WindowTreeClientClientTestHighDPI() {}
~WindowTreeClientClientTestHighDPI() override {}
const ui::PointerEvent* last_event_observed() const {
return last_event_observed_.get();
}
// WindowTreeClientClientTest:
void SetUp() override {
base::CommandLine::ForCurrentProcess()->AppendSwitchASCII(
switches::kForceDeviceScaleFactor, "2");
WindowTreeClientClientTest::SetUp();
}
void OnPointerEventObserved(const ui::PointerEvent& event,
Window* target) override {
last_event_observed_.reset(new ui::PointerEvent(event));
}
private:
std::unique_ptr<ui::PointerEvent> last_event_observed_;
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientClientTestHighDPI);
};
// Verifies bounds are reverted if the server replied that the change failed.
TEST_F(WindowTreeClientWmTest, 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());
}
// Verifies a new window from the server doesn't result in attempting to add
// the window back to the server.
TEST_F(WindowTreeClientWmTest, AddFromServerDoesntAddAgain) {
const Id child_window_id = server_id(root_window()) + 11;
ui::mojom::WindowDataPtr data = ui::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<ui::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(WindowTreeClientWmTest, 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<ui::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(WindowTreeClientWmTest, OnWindowHierarchyChangedWithProperties) {
RegisterTestProperties(GetPropertyConverter());
window_tree()->AckAllChanges();
const Id child_window_id = server_id(root_window()) + 11;
ui::mojom::WindowDataPtr data = ui::mojom::WindowData::New();
const uint8_t server_test_property1_value = 91;
data->properties[kTestPropertyServerKey1] =
ConvertToPropertyTransportValue(server_test_property1_value);
data->properties[ui::mojom::WindowManager::kWindowType_InitProperty] =
mojo::ConvertTo<std::vector<uint8_t>>(
static_cast<int32_t>(ui::mojom::WindowType::BUBBLE));
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<ui::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(child->type(), ui::wm::WINDOW_TYPE_POPUP);
EXPECT_EQ(ui::mojom::WindowType::BUBBLE,
child->GetProperty(client::kWindowTypeKey));
}
// Verifies a move from the server doesn't attempt signal the server.
TEST_F(WindowTreeClientWmTest, 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),
ui::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(WindowTreeClientWmTest, 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.
TEST_F(WindowTreeClientWmTest, SetBoundsFailedWithPendingChange) {
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());
// Simulate the server responding with a bounds change.
const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 101, 102));
window_tree_client()->OnWindowBoundsChanged(
server_id(root_window()), original_bounds, server_changed_bounds);
// 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());
// Simulate server changing back to original bounds. Should take immediately.
window_tree_client()->OnWindowBoundsChanged(
server_id(root_window()), server_changed_bounds, original_bounds);
EXPECT_EQ(original_bounds, root_window()->bounds());
}
TEST_F(WindowTreeClientWmTest, TwoInFlightBoundsChangesBothCanceled) {
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());
}
// Verifies properties are set if the server replied that the change succeeded.
TEST_F(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, SetStringProperty) {
std::string example = "123";
ASSERT_EQ(nullptr, root_window()->GetProperty(client::kAppIdKey));
root_window()->SetProperty(client::kAppIdKey, new std::string(example));
EXPECT_TRUE(root_window()->GetProperty(client::kAppIdKey));
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::kAppIdKey));
root_window()->SetProperty(client::kAppIdKey, new std::string());
EXPECT_EQ(std::string(), *root_window()->GetProperty(client::kAppIdKey));
ASSERT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::PROPERTY, false));
EXPECT_EQ(example, *root_window()->GetProperty(client::kAppIdKey));
}
// Verifies visible is reverted if the server replied that the change failed.
TEST_F(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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:
static uint32_t constexpr kEventId = 1;
explicit InputEventBasicTestWindowDelegate(TestWindowTree* test_window_tree)
: test_window_tree_(test_window_tree) {}
~InputEventBasicTestWindowDelegate() override {}
bool got_move() const { return got_move_; }
bool was_acked() const { return was_acked_; }
const gfx::Point& last_event_location() const { return last_event_location_; }
// TestWindowDelegate::
void OnMouseEvent(ui::MouseEvent* event) override {
was_acked_ = test_window_tree_->WasEventAcked(kEventId);
if (event->type() == ui::ET_MOUSE_MOVED)
got_move_ = true;
last_event_location_ = event->location();
event->SetHandled();
}
private:
TestWindowTree* test_window_tree_;
bool was_acked_ = false;
bool got_move_ = false;
gfx::Point last_event_location_;
DISALLOW_COPY_AND_ASSIGN(InputEventBasicTestWindowDelegate);
};
} // namespace
TEST_F(WindowTreeClientClientTest, InputEventBasic) {
InputEventBasicTestWindowDelegate window_delegate(window_tree());
WindowTreeHostMus window_tree_host(window_tree_client_impl());
Window* top_level = window_tree_host.window();
const gfx::Rect bounds(0, 0, 100, 100);
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_FALSE(window_delegate.got_move());
EXPECT_FALSE(window_delegate.was_acked());
const gfx::Point event_location_in_child(2, 3);
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(
InputEventBasicTestWindowDelegate::kEventId, server_id(&child),
window_tree_host.display_id(), ui::Event::Clone(*ui_event.get()), 0);
EXPECT_TRUE(window_tree()->WasEventAcked(
InputEventBasicTestWindowDelegate::kEventId));
EXPECT_EQ(ui::mojom::EventResult::HANDLED,
window_tree()->GetEventResult(
InputEventBasicTestWindowDelegate::kEventId));
EXPECT_TRUE(window_delegate.got_move());
EXPECT_FALSE(window_delegate.was_acked());
EXPECT_EQ(event_location_in_child, window_delegate.last_event_location());
}
class WindowTreeClientPointerObserverTest : public WindowTreeClientClientTest {
public:
WindowTreeClientPointerObserverTest() {}
~WindowTreeClientPointerObserverTest() override {}
void DeleteLastEventObserved() { last_event_observed_.reset(); }
const ui::PointerEvent* last_event_observed() const {
return last_event_observed_.get();
}
// WindowTreeClientClientTest:
void OnPointerEventObserved(const ui::PointerEvent& event,
Window* target) override {
last_event_observed_.reset(new ui::PointerEvent(event));
}
private:
std::unique_ptr<ui::PointerEvent> last_event_observed_;
DISALLOW_COPY_AND_ASSIGN(WindowTreeClientPointerObserverTest);
};
// Tests pointer watchers triggered by events that did not hit a target in this
// window tree.
TEST_F(WindowTreeClientPointerObserverTest, OnPointerEventObserved) {
std::unique_ptr<Window> top_level(base::MakeUnique<Window>(nullptr));
top_level->SetType(ui::wm::WINDOW_TYPE_NORMAL);
top_level->Init(ui::LAYER_NOT_DRAWN);
top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
top_level->Show();
// Start a pointer watcher for all events excluding move events.
window_tree_client_impl()->StartPointerWatcher(false /* want_moves */);
// Simulate the server sending an observed event.
std::unique_ptr<ui::PointerEvent> pointer_event_down(new ui::PointerEvent(
ui::ET_POINTER_DOWN, gfx::Point(), gfx::Point(), ui::EF_CONTROL_DOWN, 1,
0, ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH),
base::TimeTicks()));
window_tree_client()->OnPointerEventObserved(std::move(pointer_event_down),
0u, 0);
// Delegate sensed the event.
const ui::PointerEvent* last_event = last_event_observed();
ASSERT_TRUE(last_event);
EXPECT_EQ(ui::ET_POINTER_DOWN, last_event->type());
EXPECT_EQ(ui::EF_CONTROL_DOWN, last_event->flags());
DeleteLastEventObserved();
// Stop the pointer watcher.
window_tree_client_impl()->StopPointerWatcher();
// Simulate another event from the server.
std::unique_ptr<ui::PointerEvent> pointer_event_up(new ui::PointerEvent(
ui::ET_POINTER_UP, gfx::Point(), gfx::Point(), ui::EF_CONTROL_DOWN, 1, 0,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH),
base::TimeTicks()));
window_tree_client()->OnPointerEventObserved(std::move(pointer_event_up), 0u,
0);
// No event was sensed.
EXPECT_FALSE(last_event_observed());
}
// Tests pointer watchers triggered by events that hit this window tree.
TEST_F(WindowTreeClientPointerObserverTest,
OnWindowInputEventWithPointerWatcher) {
std::unique_ptr<Window> top_level(base::MakeUnique<Window>(nullptr));
top_level->SetType(ui::wm::WINDOW_TYPE_NORMAL);
top_level->Init(ui::LAYER_NOT_DRAWN);
top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
top_level->Show();
// Start a pointer watcher for all events excluding move events.
window_tree_client_impl()->StartPointerWatcher(false /* want_moves */);
// Simulate the server dispatching an event that also matched the observer.
std::unique_ptr<ui::PointerEvent> pointer_event_down(new ui::PointerEvent(
ui::ET_POINTER_DOWN, gfx::Point(), gfx::Point(), ui::EF_CONTROL_DOWN, 1,
0, ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH),
base::TimeTicks::Now()));
window_tree_client()->OnWindowInputEvent(1, server_id(top_level.get()), 0,
std::move(pointer_event_down), true);
// Delegate sensed the event.
const ui::Event* last_event = last_event_observed();
ASSERT_TRUE(last_event);
EXPECT_EQ(ui::ET_POINTER_DOWN, last_event->type());
EXPECT_EQ(ui::EF_CONTROL_DOWN, last_event->flags());
}
// Verifies focus is reverted if the server replied that the change failed.
TEST_F(WindowTreeClientWmTest, SetFocusFailed) {
Window child(nullptr);
child.Init(ui::LAYER_NOT_DRAWN);
root_window()->AddChild(&child);
child.Focus();
ASSERT_TRUE(child.HasFocus());
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::FOCUS, false));
EXPECT_EQ(nullptr, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, FocusOnRemovedWindowWithInFlightFocusChange) {
std::unique_ptr<Window> child1(base::MakeUnique<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();
// Destroy child1, which should set focus to null.
child1.reset(nullptr);
EXPECT_EQ(nullptr, client::GetFocusClient(root_window())->GetFocusedWindow());
// Server changes focus to 2.
window_tree_client()->OnWindowFocused(server_id(&child2));
// Shouldn't take immediately.
EXPECT_FALSE(child2.HasFocus());
// Ack both changes, focus should still be null.
ASSERT_TRUE(
window_tree()->AckFirstChangeOfType(WindowTreeChangeType::FOCUS, true));
EXPECT_EQ(nullptr, client::GetFocusClient(root_window())->GetFocusedWindow());
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::FOCUS, true));
EXPECT_EQ(nullptr, client::GetFocusClient(root_window())->GetFocusedWindow());
// Change to 2 again, this time it should take.
window_tree_client()->OnWindowFocused(server_id(&child2));
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(WindowTreeClientWmTest, ToggleVisibilityFromWindowDestroyed) {
std::unique_ptr<Window> child(base::MakeUnique<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(WindowTreeClientClientTest, NewTopLevelWindow) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
std::unique_ptr<WindowTreeHostMus> window_tree_host =
base::MakeUnique<WindowTreeHostMus>(window_tree_client_impl());
window_tree_host->InitHost();
EXPECT_FALSE(window_tree_host->window()->TargetVisibility());
aura::Window* top_level = window_tree_host->window();
EXPECT_NE(server_id(top_level), server_id(root_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));
ui::mojom::WindowDataPtr data = ui::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);
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(WindowTreeClientClientTest, NewTopLevelWindowGetsPropertiesFromData) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
WindowTreeHostMus window_tree_host(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));
ui::mojom::WindowDataPtr data = ui::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);
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(WindowTreeClientClientTest, NewWindowGetsAllChangesInFlight) {
RegisterTestProperties(GetPropertyConverter());
WindowTreeHostMus window_tree_host(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.
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.
ui::mojom::WindowDataPtr data = ui::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);
// 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::BOUNDS, false);
// The bounds of the top_level is always at the origin.
EXPECT_EQ(gfx::Rect(bounds_from_server.size()), top_level->bounds());
// But the bounds of the WindowTreeHost is display relative.
EXPECT_EQ(bounds_from_server,
top_level->GetRootWindow()->GetHost()->GetBoundsInPixels());
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(WindowTreeClientClientTest, 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<std::unordered_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::UnorderedMapToMap(*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(WindowTreeClientClientTest, Transients) {
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);
}
// Verifies adding/removing a transient child doesn't notify the server of the
// restack when the change originates from the server.
TEST_F(WindowTreeClientClientTest,
TransientChildServerMutateDoesntNotifyOfRestack) {
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]);
// No changes should be scheduled.
EXPECT_EQ(0u, window_tree()->number_of_changes());
// Make |w3| also a transient child of |w2|. Order shouldn't change.
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]);
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]);
EXPECT_EQ(0u, window_tree()->number_of_changes());
}
// Verifies adding/removing a transient child doesn't notify the server of the
// restack when the change originates from the client.
TEST_F(WindowTreeClientClientTest,
TransientChildClientMutateDoesntNotifyOfRestack) {
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|, but not
// 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]);
// Only a single add transient change should be added.
EXPECT_TRUE(window_tree()->AckSingleChangeOfType(
WindowTreeChangeType::ADD_TRANSIENT, 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));
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_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]);
// NOTE: even though the order didn't change, internally the order was
// changed and then changed back. That is the StackChildAbove() call really
// succeeded, but then TransientWindowManager reordered the windows back to
// a valid configuration. We expect only one REORDER here as the second
// results from TransientWindowManager and we assume the server applied it as
// well.
EXPECT_EQ(1u, window_tree()->number_of_changes());
window_tree()->AckAllChangesOfType(WindowTreeChangeType::REORDER, true);
EXPECT_EQ(0u, window_tree()->number_of_changes());
}
TEST_F(WindowTreeClientClientTest,
TopLevelWindowDestroyedBeforeCreateComplete) {
const size_t initial_root_count =
window_tree_client_impl()->GetRoots().size();
std::unique_ptr<WindowTreeHostMus> window_tree_host =
base::MakeUnique<WindowTreeHostMus>(window_tree_client_impl());
window_tree_host->InitHost();
EXPECT_EQ(initial_root_count + 1,
window_tree_client_impl()->GetRoots().size());
ui::mojom::WindowDataPtr data = ui::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);
EXPECT_EQ(initial_root_count, window_tree_client_impl()->GetRoots().size());
}
TEST_F(WindowTreeClientClientTest, 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);
std::unique_ptr<WindowTreeHostMus> window_tree_host =
base::MakeUnique<WindowTreeHostMus>(window_tree_client_impl(),
&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<std::unordered_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::UnorderedMapToMap(*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(const aura::WindowTreeHost* host) override {
root_destroyed_ = true;
}
private:
bool root_destroyed_ = false;
DISALLOW_COPY_AND_ASSIGN(CloseWindowWindowTreeHostObserver);
};
} // namespace
TEST_F(WindowTreeClientClientTest, CloseWindow) {
WindowTreeHostMus window_tree_host(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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, 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(WindowTreeClientWmTest, WindowDestroyedWhileTransientChildHasCapture) {
std::unique_ptr<Window> transient_parent(base::MakeUnique<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(WindowTreeClientWmTest, OnWindowTreeCaptureChanged) {
CaptureRecorder capture_recorder(root_window());
std::unique_ptr<Window> child1(base::MakeUnique<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(WindowTreeClientClientTest, TwoWindowTreesRequestCapture) {
// Creating a WindowTreeHost so we can have two root windows: top_level
// and root_window().
std::unique_ptr<WindowTreeHostMus> window_tree_host =
base::MakeUnique<WindowTreeHostMus>(window_tree_client_impl());
window_tree_host->InitHost();
Window* top_level = window_tree_host->window();
std::unique_ptr<client::DefaultCaptureClient> capture_client(
base::MakeUnique<client::DefaultCaptureClient>());
client::SetCaptureClient(top_level, capture_client.get());
window_tree_client_impl()->capture_synchronizer()->AttachToCaptureClient(
capture_client.get());
EXPECT_NE(server_id(top_level), server_id(root_window()));
// 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));
ui::mojom::WindowDataPtr data = ui::mojom::WindowData::New();
data->window_id = server_id(top_level);
data->visible = true;
const int64_t display_id = 1;
window_tree_client()->OnTopLevelCreated(change_id, std::move(data),
display_id, true);
EXPECT_EQ(
0u, window_tree()->GetChangeCountForType(WindowTreeChangeType::VISIBLE));
EXPECT_TRUE(top_level->TargetVisibility());
std::unique_ptr<CaptureRecorder> capture_recorder1(
base::MakeUnique<CaptureRecorder>(root_window()));
std::unique_ptr<CaptureRecorder> capture_recorder2(
base::MakeUnique<CaptureRecorder>(top_level));
EXPECT_NE(client::GetCaptureClient(root_window()),
client::GetCaptureClient(top_level));
EXPECT_EQ(0, capture_recorder1->capture_changed_count());
EXPECT_EQ(0, capture_recorder2->capture_changed_count());
// Give capture to top_level and ensure everyone is notified correctly.
top_level->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(top_level->id(),
capture_recorder2->last_gained_capture_window_id());
EXPECT_EQ(0, capture_recorder2->last_lost_capture_window_id());
top_level->ReleaseCapture();
capture_recorder1->reset_capture_captured_count();
capture_recorder2->reset_capture_captured_count();
// Release capture of top_level shouldn't affect the capture of root_window().
top_level->SetCapture();
root_window()->SetCapture();
top_level->ReleaseCapture();
EXPECT_EQ(1, capture_recorder1->capture_changed_count());
EXPECT_EQ(2, capture_recorder2->capture_changed_count());
EXPECT_EQ(root_window()->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(top_level->id(), capture_recorder2->last_lost_capture_window_id());
capture_recorder1->reset_capture_captured_count();
capture_recorder2->reset_capture_captured_count();
capture_recorder1.reset();
capture_recorder2.reset();
window_tree_host.reset();
capture_client.reset();
}
TEST_F(WindowTreeClientClientTest, ModalFail) {
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));
// There should be no more modal changes.
EXPECT_FALSE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, false));
}
TEST_F(WindowTreeClientClientTest, ModalSuccess) {
Window window(nullptr);
window.Init(ui::LAYER_NOT_DRAWN);
window.SetProperty(client::kModalKey, ui::MODAL_TYPE_WINDOW);
// Ack change as succeeding.
ASSERT_TRUE(
window_tree()->AckSingleChangeOfType(WindowTreeChangeType::MODAL, true));
EXPECT_EQ(ui::MODAL_TYPE_WINDOW, 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(WindowTreeClientWmTest, 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 Id server_window_id = server_id(root_window()) + 11;
ui::mojom::WindowDataPtr data1 = ui::mojom::WindowData::New();
ui::mojom::WindowDataPtr data2 = ui::mojom::WindowData::New();
ui::mojom::WindowDataPtr data3 = ui::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<ui::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(WindowTreeClientClientTest, 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(WindowTreeClientWmTest, NewWindowTreeHostIsConfiguredCorrectly) {
display::Display display(201);
display.set_bounds(gfx::Rect(1, 2, 101, 102));
ui::mojom::WindowDataPtr root_data(ui::mojom::WindowData::New());
root_data->parent_id = 0;
root_data->window_id = 101;
root_data->visible = true;
root_data->bounds = display.bounds();
const bool parent_drawn = true;
// AuraTestBase ends up owning WindowTreeHost.
WindowTreeHostMus* window_tree_host =
WindowTreeClientPrivate(window_tree_client_impl())
.CallWmNewDisplayAdded(display, std::move(root_data), parent_drawn);
EXPECT_EQ(display.bounds(), window_tree_host->GetBoundsInPixels());
// The root window of the WindowTreeHost always has an origin of 0,0.
EXPECT_EQ(gfx::Rect(display.bounds().size()),
window_tree_host->window()->bounds());
EXPECT_TRUE(window_tree_host->window()->IsVisible());
EXPECT_EQ(display.id(), window_tree_host->display_id());
}
TEST_F(WindowTreeClientWmTestHighDPI, SetBounds) {
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());
// Simulate the server responding with a bounds change. Server should operate
// in pixels.
const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 200, 200));
window_tree_client()->OnWindowBoundsChanged(
server_id(root_window()), original_bounds, server_changed_bounds);
EXPECT_EQ(new_bounds, root_window()->bounds());
}
TEST_F(WindowTreeClientClientTestHighDPI, NewTopLevelWindowBounds) {
WindowTreeHostMus window_tree_host(window_tree_client_impl());
Window* top_level = window_tree_host.window();
window_tree_host.InitHost();
ui::mojom::WindowDataPtr data = ui::mojom::WindowData::New();
data->window_id = server_id(top_level);
data->bounds.SetRect(2, 4, 6, 8);
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);
// aura::Window should operate in DIP and aura::WindowTreeHost should operate
// in pixels.
EXPECT_EQ(gfx::Rect(0, 0, 3, 4), top_level->bounds());
EXPECT_EQ(gfx::Rect(2, 4, 6, 8), top_level->GetHost()->GetBoundsInPixels());
}
TEST_F(WindowTreeClientClientTestHighDPI, PointerEventsInDips) {
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(base::MakeUnique<Window>(nullptr));
top_level->SetType(ui::wm::WINDOW_TYPE_NORMAL);
top_level->Init(ui::LAYER_NOT_DRAWN);
top_level->SetBounds(gfx::Rect(0, 0, 100, 100));
top_level->Show();
// Start a pointer watcher for all events excluding move events.
window_tree_client_impl()->StartPointerWatcher(false /* want_moves */);
// Simulate the server sending an observed event.
const gfx::Point location_pixels(10, 12);
const gfx::Point root_location_pixels(14, 16);
std::unique_ptr<ui::PointerEvent> pointer_event_down(new ui::PointerEvent(
ui::ET_POINTER_DOWN, location_pixels, root_location_pixels,
ui::EF_CONTROL_DOWN, 1, 0,
ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH),
base::TimeTicks()));
window_tree_client()->OnPointerEventObserved(std::move(pointer_event_down),
0u, primary_display.id());
// Delegate received the event in Dips.
const ui::PointerEvent* last_event = last_event_observed();
ASSERT_TRUE(last_event);
EXPECT_EQ(gfx::ConvertPointToDIP(2.0f, location_pixels),
last_event->location());
EXPECT_EQ(gfx::ConvertPointToDIP(2.0f, root_location_pixels),
last_event->root_location());
}
} // namespace aura