blob: 45ec0e831e8d3ea40e03941f58b9946b0f382674 [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/ui/public/cpp/window.h"
#include <limits.h>
#include <stdint.h>
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "services/ui/common/util.h"
#include "services/ui/public/cpp/property_type_converters.h"
#include "services/ui/public/cpp/tests/test_window.h"
#include "services/ui/public/cpp/window_observer.h"
#include "services/ui/public/cpp/window_private.h"
#include "services/ui/public/cpp/window_property.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect.h"
namespace ui {
namespace {
TestWindow* CreateTestWindow(TestWindow* parent) {
TestWindow* window = new TestWindow;
if (parent)
parent->AddChild(window);
return window;
}
TestWindow* CreateTestWindow(int id, TestWindow* parent) {
TestWindow* window = new TestWindow(0);
window->set_local_id(id);
if (parent)
parent->AddChild(window);
return window;
}
std::string ChildWindowIDsAsString(TestWindow* parent) {
std::string result;
for (Window* child : parent->children()) {
if (!result.empty())
result += " ";
result += base::IntToString(child->local_id());
}
return result;
}
} // namespace
// Window ---------------------------------------------------------------------
using WindowTest = testing::Test;
TEST_F(WindowTest, AddChild) {
TestWindow w1;
TestWindow w11;
w1.AddChild(&w11);
EXPECT_EQ(1U, w1.children().size());
}
TEST_F(WindowTest, RemoveChild) {
TestWindow w1;
TestWindow w11;
w1.AddChild(&w11);
EXPECT_EQ(1U, w1.children().size());
w1.RemoveChild(&w11);
EXPECT_EQ(0U, w1.children().size());
}
TEST_F(WindowTest, Reparent) {
TestWindow w1;
TestWindow w2;
TestWindow w11;
w1.AddChild(&w11);
EXPECT_EQ(1U, w1.children().size());
w2.AddChild(&w11);
EXPECT_EQ(1U, w2.children().size());
EXPECT_EQ(0U, w1.children().size());
}
TEST_F(WindowTest, Contains) {
TestWindow w1;
// Direct descendant.
TestWindow w11;
w1.AddChild(&w11);
EXPECT_TRUE(w1.Contains(&w11));
// Indirect descendant.
TestWindow w111;
w11.AddChild(&w111);
EXPECT_TRUE(w1.Contains(&w111));
}
TEST_F(WindowTest, GetChildByLocalId) {
TestWindow w1;
w1.set_local_id(0);
EXPECT_EQ(&w1, w1.GetChildByLocalId(0));
TestWindow w11;
w11.set_local_id(11);
w1.AddChild(&w11);
TestWindow w12;
w12.set_local_id(w1.local_id());
w1.AddChild(&w12);
TestWindow w111;
w111.set_local_id(111);
w11.AddChild(&w111);
// Find direct & indirect descendents.
EXPECT_EQ(&w11, w1.GetChildByLocalId(w11.local_id()));
EXPECT_EQ(&w111, w1.GetChildByLocalId(w111.local_id()));
// Verifies parent returned by child with same id.
EXPECT_EQ(&w1, w1.GetChildByLocalId(w1.local_id()));
}
TEST_F(WindowTest, DrawnAndVisible) {
TestWindow w1;
EXPECT_FALSE(w1.visible());
w1.SetVisible(true);
EXPECT_TRUE(w1.visible());
EXPECT_FALSE(w1.IsDrawn());
WindowPrivate(&w1).set_parent_drawn(true);
TestWindow w11;
w11.SetVisible(true);
w1.AddChild(&w11);
EXPECT_TRUE(w11.visible());
EXPECT_TRUE(w11.IsDrawn());
w1.RemoveChild(&w11);
EXPECT_TRUE(w11.visible());
EXPECT_FALSE(w11.IsDrawn());
}
namespace {
MUS_DEFINE_WINDOW_PROPERTY_KEY(int, kIntKey, -2);
MUS_DEFINE_WINDOW_PROPERTY_KEY(const char*, kStringKey, "squeamish");
}
TEST_F(WindowTest, Property) {
TestWindow w;
// Non-existent properties should return the default values.
EXPECT_EQ(-2, w.GetLocalProperty(kIntKey));
EXPECT_EQ(std::string("squeamish"), w.GetLocalProperty(kStringKey));
// A set property value should be returned again (even if it's the default
// value).
w.SetLocalProperty(kIntKey, INT_MAX);
EXPECT_EQ(INT_MAX, w.GetLocalProperty(kIntKey));
w.SetLocalProperty(kIntKey, -2);
EXPECT_EQ(-2, w.GetLocalProperty(kIntKey));
w.SetLocalProperty(kIntKey, INT_MIN);
EXPECT_EQ(INT_MIN, w.GetLocalProperty(kIntKey));
w.SetLocalProperty(kStringKey, static_cast<const char*>(NULL));
EXPECT_EQ(NULL, w.GetLocalProperty(kStringKey));
w.SetLocalProperty(kStringKey, "squeamish");
EXPECT_EQ(std::string("squeamish"), w.GetLocalProperty(kStringKey));
w.SetLocalProperty(kStringKey, "ossifrage");
EXPECT_EQ(std::string("ossifrage"), w.GetLocalProperty(kStringKey));
// ClearProperty should restore the default value.
w.ClearLocalProperty(kIntKey);
EXPECT_EQ(-2, w.GetLocalProperty(kIntKey));
w.ClearLocalProperty(kStringKey);
EXPECT_EQ(std::string("squeamish"), w.GetLocalProperty(kStringKey));
}
namespace {
class TestProperty {
public:
TestProperty() {}
virtual ~TestProperty() { last_deleted_ = this; }
static TestProperty* last_deleted() { return last_deleted_; }
private:
static TestProperty* last_deleted_;
DISALLOW_COPY_AND_ASSIGN(TestProperty);
};
TestProperty* TestProperty::last_deleted_ = NULL;
MUS_DEFINE_OWNED_WINDOW_PROPERTY_KEY(TestProperty, kOwnedKey, NULL);
} // namespace
TEST_F(WindowTest, OwnedProperty) {
TestProperty* p3 = NULL;
{
TestWindow w;
EXPECT_EQ(NULL, w.GetLocalProperty(kOwnedKey));
TestProperty* p1 = new TestProperty();
w.SetLocalProperty(kOwnedKey, p1);
EXPECT_EQ(p1, w.GetLocalProperty(kOwnedKey));
EXPECT_EQ(NULL, TestProperty::last_deleted());
TestProperty* p2 = new TestProperty();
w.SetLocalProperty(kOwnedKey, p2);
EXPECT_EQ(p2, w.GetLocalProperty(kOwnedKey));
EXPECT_EQ(p1, TestProperty::last_deleted());
w.ClearLocalProperty(kOwnedKey);
EXPECT_EQ(NULL, w.GetLocalProperty(kOwnedKey));
EXPECT_EQ(p2, TestProperty::last_deleted());
p3 = new TestProperty();
w.SetLocalProperty(kOwnedKey, p3);
EXPECT_EQ(p3, w.GetLocalProperty(kOwnedKey));
EXPECT_EQ(p2, TestProperty::last_deleted());
}
EXPECT_EQ(p3, TestProperty::last_deleted());
}
// WindowObserver --------------------------------------------------------
typedef testing::Test WindowObserverTest;
bool TreeChangeParamsMatch(const WindowObserver::TreeChangeParams& lhs,
const WindowObserver::TreeChangeParams& rhs) {
return lhs.target == rhs.target && lhs.old_parent == rhs.old_parent &&
lhs.new_parent == rhs.new_parent && lhs.receiver == rhs.receiver;
}
class TreeChangeObserver : public WindowObserver {
public:
explicit TreeChangeObserver(Window* observee) : observee_(observee) {
observee_->AddObserver(this);
}
~TreeChangeObserver() override { observee_->RemoveObserver(this); }
void Reset() { received_params_.clear(); }
const std::vector<TreeChangeParams>& received_params() {
return received_params_;
}
private:
// Overridden from WindowObserver:
void OnTreeChanging(const TreeChangeParams& params) override {
received_params_.push_back(params);
}
void OnTreeChanged(const TreeChangeParams& params) override {
received_params_.push_back(params);
}
Window* observee_;
std::vector<TreeChangeParams> received_params_;
DISALLOW_COPY_AND_ASSIGN(TreeChangeObserver);
};
// Adds/Removes w11 to w1.
TEST_F(WindowObserverTest, TreeChange_SimpleAddRemove) {
TestWindow w1;
w1.set_local_id(1);
TreeChangeObserver o1(&w1);
EXPECT_TRUE(o1.received_params().empty());
TestWindow w11;
w11.set_local_id(11);
TreeChangeObserver o11(&w11);
EXPECT_TRUE(o11.received_params().empty());
// Add.
w1.AddChild(&w11);
EXPECT_EQ(2U, o1.received_params().size());
WindowObserver::TreeChangeParams p1;
p1.target = &w11;
p1.receiver = &w1;
p1.old_parent = NULL;
p1.new_parent = &w1;
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
EXPECT_EQ(2U, o11.received_params().size());
WindowObserver::TreeChangeParams p11 = p1;
p11.receiver = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
o1.Reset();
o11.Reset();
EXPECT_TRUE(o1.received_params().empty());
EXPECT_TRUE(o11.received_params().empty());
// Remove.
w1.RemoveChild(&w11);
EXPECT_EQ(2U, o1.received_params().size());
p1.target = &w11;
p1.receiver = &w1;
p1.old_parent = &w1;
p1.new_parent = NULL;
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
EXPECT_EQ(2U, o11.received_params().size());
p11 = p1;
p11.receiver = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
}
// Creates these two trees:
// w1
// +- w11
// w111
// +- w1111
// +- w1112
// Then adds/removes w111 from w11.
TEST_F(WindowObserverTest, TreeChange_NestedAddRemove) {
TestWindow w1, w11, w111, w1111, w1112;
// Root tree.
w1.AddChild(&w11);
// Tree to be attached.
w111.AddChild(&w1111);
w111.AddChild(&w1112);
TreeChangeObserver o1(&w1), o11(&w11), o111(&w111), o1111(&w1111),
o1112(&w1112);
WindowObserver::TreeChangeParams p1, p11, p111, p1111, p1112;
// Add.
w11.AddChild(&w111);
EXPECT_EQ(2U, o1.received_params().size());
p1.target = &w111;
p1.receiver = &w1;
p1.old_parent = NULL;
p1.new_parent = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
EXPECT_EQ(2U, o11.received_params().size());
p11 = p1;
p11.receiver = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().back()));
EXPECT_EQ(2U, o111.received_params().size());
p111 = p11;
p111.receiver = &w111;
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
EXPECT_EQ(2U, o1111.received_params().size());
p1111 = p111;
p1111.receiver = &w1111;
EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().back()));
EXPECT_EQ(2U, o1112.received_params().size());
p1112 = p111;
p1112.receiver = &w1112;
EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().back()));
// Remove.
o1.Reset();
o11.Reset();
o111.Reset();
o1111.Reset();
o1112.Reset();
EXPECT_TRUE(o1.received_params().empty());
EXPECT_TRUE(o11.received_params().empty());
EXPECT_TRUE(o111.received_params().empty());
EXPECT_TRUE(o1111.received_params().empty());
EXPECT_TRUE(o1112.received_params().empty());
w11.RemoveChild(&w111);
EXPECT_EQ(2U, o1.received_params().size());
p1.target = &w111;
p1.receiver = &w1;
p1.old_parent = &w11;
p1.new_parent = NULL;
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
EXPECT_EQ(2U, o11.received_params().size());
p11 = p1;
p11.receiver = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
EXPECT_EQ(2U, o111.received_params().size());
p111 = p11;
p111.receiver = &w111;
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
EXPECT_EQ(2U, o1111.received_params().size());
p1111 = p111;
p1111.receiver = &w1111;
EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p1111, o1111.received_params().back()));
EXPECT_EQ(2U, o1112.received_params().size());
p1112 = p111;
p1112.receiver = &w1112;
EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p1112, o1112.received_params().back()));
}
TEST_F(WindowObserverTest, TreeChange_Reparent) {
TestWindow w1, w11, w12, w111;
w1.AddChild(&w11);
w1.AddChild(&w12);
w11.AddChild(&w111);
TreeChangeObserver o1(&w1), o11(&w11), o12(&w12), o111(&w111);
// Reparent.
w12.AddChild(&w111);
// w1 (root) should see both changing and changed notifications.
EXPECT_EQ(4U, o1.received_params().size());
WindowObserver::TreeChangeParams p1;
p1.target = &w111;
p1.receiver = &w1;
p1.old_parent = &w11;
p1.new_parent = &w12;
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p1, o1.received_params().back()));
// w11 should see changing notifications.
EXPECT_EQ(2U, o11.received_params().size());
WindowObserver::TreeChangeParams p11;
p11 = p1;
p11.receiver = &w11;
EXPECT_TRUE(TreeChangeParamsMatch(p11, o11.received_params().front()));
// w12 should see changed notifications.
EXPECT_EQ(2U, o12.received_params().size());
WindowObserver::TreeChangeParams p12;
p12 = p1;
p12.receiver = &w12;
EXPECT_TRUE(TreeChangeParamsMatch(p12, o12.received_params().back()));
// w111 should see both changing and changed notifications.
EXPECT_EQ(2U, o111.received_params().size());
WindowObserver::TreeChangeParams p111;
p111 = p1;
p111.receiver = &w111;
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().front()));
EXPECT_TRUE(TreeChangeParamsMatch(p111, o111.received_params().back()));
}
namespace {
class OrderChangeObserver : public WindowObserver {
public:
struct Change {
Window* window;
Window* relative_window;
mojom::OrderDirection direction;
};
typedef std::vector<Change> Changes;
explicit OrderChangeObserver(Window* observee) : observee_(observee) {
observee_->AddObserver(this);
}
~OrderChangeObserver() override { observee_->RemoveObserver(this); }
Changes GetAndClearChanges() {
Changes changes;
changes_.swap(changes);
return changes;
}
private:
// Overridden from WindowObserver:
void OnWindowReordering(Window* window,
Window* relative_window,
mojom::OrderDirection direction) override {
OnWindowReordered(window, relative_window, direction);
}
void OnWindowReordered(Window* window,
Window* relative_window,
mojom::OrderDirection direction) override {
Change change;
change.window = window;
change.relative_window = relative_window;
change.direction = direction;
changes_.push_back(change);
}
Window* observee_;
Changes changes_;
DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver);
};
} // namespace
TEST_F(WindowObserverTest, Order) {
TestWindow w1, w11, w12, w13;
w1.AddChild(&w11);
w1.AddChild(&w12);
w1.AddChild(&w13);
// Order: w11, w12, w13
EXPECT_EQ(3U, w1.children().size());
EXPECT_EQ(&w11, w1.children().front());
EXPECT_EQ(&w13, w1.children().back());
{
OrderChangeObserver observer(&w11);
// Move w11 to front.
// Resulting order: w12, w13, w11
w11.MoveToFront();
EXPECT_EQ(&w12, w1.children().front());
EXPECT_EQ(&w11, w1.children().back());
OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ(&w11, changes[0].window);
EXPECT_EQ(&w13, changes[0].relative_window);
EXPECT_EQ(mojom::OrderDirection::ABOVE, changes[0].direction);
EXPECT_EQ(&w11, changes[1].window);
EXPECT_EQ(&w13, changes[1].relative_window);
EXPECT_EQ(mojom::OrderDirection::ABOVE, changes[1].direction);
}
{
OrderChangeObserver observer(&w11);
// Move w11 to back.
// Resulting order: w11, w12, w13
w11.MoveToBack();
EXPECT_EQ(&w11, w1.children().front());
EXPECT_EQ(&w13, w1.children().back());
OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ(&w11, changes[0].window);
EXPECT_EQ(&w12, changes[0].relative_window);
EXPECT_EQ(mojom::OrderDirection::BELOW, changes[0].direction);
EXPECT_EQ(&w11, changes[1].window);
EXPECT_EQ(&w12, changes[1].relative_window);
EXPECT_EQ(mojom::OrderDirection::BELOW, changes[1].direction);
}
{
OrderChangeObserver observer(&w11);
// Move w11 above w12.
// Resulting order: w12. w11, w13
w11.Reorder(&w12, mojom::OrderDirection::ABOVE);
EXPECT_EQ(&w12, w1.children().front());
EXPECT_EQ(&w13, w1.children().back());
OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ(&w11, changes[0].window);
EXPECT_EQ(&w12, changes[0].relative_window);
EXPECT_EQ(mojom::OrderDirection::ABOVE, changes[0].direction);
EXPECT_EQ(&w11, changes[1].window);
EXPECT_EQ(&w12, changes[1].relative_window);
EXPECT_EQ(mojom::OrderDirection::ABOVE, changes[1].direction);
}
{
OrderChangeObserver observer(&w11);
// Move w11 below w12.
// Resulting order: w11, w12, w13
w11.Reorder(&w12, mojom::OrderDirection::BELOW);
EXPECT_EQ(&w11, w1.children().front());
EXPECT_EQ(&w13, w1.children().back());
OrderChangeObserver::Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ(&w11, changes[0].window);
EXPECT_EQ(&w12, changes[0].relative_window);
EXPECT_EQ(mojom::OrderDirection::BELOW, changes[0].direction);
EXPECT_EQ(&w11, changes[1].window);
EXPECT_EQ(&w12, changes[1].relative_window);
EXPECT_EQ(mojom::OrderDirection::BELOW, changes[1].direction);
}
}
namespace {
typedef std::vector<std::string> Changes;
std::string RectToString(const gfx::Rect& rect) {
return base::StringPrintf("%d,%d %dx%d", rect.x(), rect.y(), rect.width(),
rect.height());
}
class BoundsChangeObserver : public WindowObserver {
public:
explicit BoundsChangeObserver(Window* window) : window_(window) {
window_->AddObserver(this);
}
~BoundsChangeObserver() override { window_->RemoveObserver(this); }
Changes GetAndClearChanges() {
Changes changes;
changes.swap(changes_);
return changes;
}
private:
// Overridden from WindowObserver:
void OnWindowBoundsChanging(Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) override {
changes_.push_back(base::StringPrintf(
"window=%d old_bounds=%s new_bounds=%s phase=changing",
window->local_id(), RectToString(old_bounds).c_str(),
RectToString(new_bounds).c_str()));
}
void OnWindowBoundsChanged(Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds) override {
changes_.push_back(base::StringPrintf(
"window=%d old_bounds=%s new_bounds=%s phase=changed",
window->local_id(), RectToString(old_bounds).c_str(),
RectToString(new_bounds).c_str()));
}
Window* window_;
Changes changes_;
DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver);
};
} // namespace
TEST_F(WindowObserverTest, SetBounds) {
TestWindow w1;
w1.set_local_id(1);
{
BoundsChangeObserver observer(&w1);
w1.SetBounds(gfx::Rect(0, 0, 100, 100));
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ(
"window=1 old_bounds=0,0 0x0 new_bounds=0,0 100x100 phase=changing",
changes[0]);
EXPECT_EQ(
"window=1 old_bounds=0,0 0x0 new_bounds=0,0 100x100 phase=changed",
changes[1]);
}
}
namespace {
class VisibilityChangeObserver : public WindowObserver {
public:
explicit VisibilityChangeObserver(Window* window) : window_(window) {
window_->AddObserver(this);
}
~VisibilityChangeObserver() override { window_->RemoveObserver(this); }
Changes GetAndClearChanges() {
Changes changes;
changes.swap(changes_);
return changes;
}
private:
// Overridden from WindowObserver:
void OnWindowVisibilityChanging(Window* window, bool visible) override {
changes_.push_back(
base::StringPrintf("window=%d phase=changing visibility=%s",
window->local_id(), visible ? "true" : "false"));
}
void OnChildWindowVisibilityChanged(Window* window, bool visible) override {
changes_.push_back(
base::StringPrintf("window=%d phase=child-changed visibility=%s",
window->local_id(), visible ? "true" : "false"));
}
void OnWindowVisibilityChanged(Window* window, bool visible) override {
changes_.push_back(
base::StringPrintf("window=%d phase=changed visibility=%s",
window->local_id(), visible ? "true" : "false"));
}
Window* window_;
Changes changes_;
DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver);
};
} // namespace
TEST_F(WindowObserverTest, SetVisible) {
TestWindow w1;
EXPECT_FALSE(w1.visible());
w1.set_local_id(1);
w1.SetVisible(true);
EXPECT_TRUE(w1.visible());
{
// Change visibility from true to false and make sure we get notifications.
VisibilityChangeObserver observer(&w1);
w1.SetVisible(false);
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ("window=1 phase=changing visibility=false", changes[0]);
EXPECT_EQ("window=1 phase=changed visibility=false", changes[1]);
}
{
// Set visible to existing value and verify no notifications.
VisibilityChangeObserver observer(&w1);
w1.SetVisible(false);
EXPECT_TRUE(observer.GetAndClearChanges().empty());
}
}
TEST_F(WindowObserverTest, SetVisibleParent) {
TestWindow parent;
parent.SetVisible(true);
parent.set_local_id(1);
TestWindow child;
child.SetVisible(true);
child.set_local_id(2);
parent.AddChild(&child);
EXPECT_TRUE(parent.visible());
EXPECT_TRUE(child.visible());
{
// Change visibility from true to false and make sure we get notifications
// on the parent.
VisibilityChangeObserver observer(&parent);
child.SetVisible(false);
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(2U, changes.size());
EXPECT_EQ("window=2 phase=child-changed visibility=false", changes[0]);
EXPECT_EQ("window=2 phase=changed visibility=false", changes[1]);
}
}
TEST_F(WindowObserverTest, SetVisibleChild) {
TestWindow parent;
parent.SetVisible(true);
parent.set_local_id(1);
TestWindow child;
child.SetVisible(true);
child.set_local_id(2);
parent.AddChild(&child);
EXPECT_TRUE(parent.visible());
EXPECT_TRUE(child.visible());
{
// Change visibility from true to false and make sure we get notifications
// on the child.
VisibilityChangeObserver observer(&child);
parent.SetVisible(false);
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(1U, changes.size());
EXPECT_EQ("window=1 phase=changed visibility=false", changes[0]);
}
}
namespace {
class OpacityChangeObserver : public WindowObserver {
public:
explicit OpacityChangeObserver(Window* window) : window_(window) {
window_->AddObserver(this);
}
~OpacityChangeObserver() override { window_->RemoveObserver(this); }
Changes GetAndClearChanges() {
Changes changes;
changes.swap(changes_);
return changes;
}
private:
// WindowObserver:
void OnWindowOpacityChanged(Window* window,
float old_opacity,
float new_opacity) override {
changes_.push_back(
base::StringPrintf("window=%d old_opacity=%.2f new_opacity=%.2f",
window->local_id(), old_opacity, new_opacity));
}
Window* window_;
Changes changes_;
DISALLOW_COPY_AND_ASSIGN(OpacityChangeObserver);
};
} // namespace
// Tests that WindowObserver is only notified when opacity changes.
TEST_F(WindowObserverTest, SetOpacity) {
TestWindow w1;
w1.set_local_id(1);
EXPECT_FLOAT_EQ(1.0f, w1.opacity());
// Changing the opacity should trigger a notification.
OpacityChangeObserver observer(&w1);
w1.SetOpacity(0.5f);
EXPECT_FLOAT_EQ(0.5f, w1.opacity());
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(1u, changes.size());
EXPECT_EQ("window=1 old_opacity=1.00 new_opacity=0.50", changes[0]);
// Setting to the same opacity should be rejected, no notification.
w1.SetOpacity(0.5f);
EXPECT_FLOAT_EQ(0.5f, w1.opacity());
changes = observer.GetAndClearChanges();
EXPECT_TRUE(changes.empty());
// Alternate sources of opacity changes should trigger a notification.
WindowPrivate(&w1).LocalSetOpacity(1.0f);
EXPECT_FLOAT_EQ(1.0f, w1.opacity());
changes = observer.GetAndClearChanges();
ASSERT_EQ(1u, changes.size());
EXPECT_EQ("window=1 old_opacity=0.50 new_opacity=1.00", changes[0]);
}
namespace {
class SharedPropertyChangeObserver : public WindowObserver {
public:
explicit SharedPropertyChangeObserver(Window* window) : window_(window) {
window_->AddObserver(this);
}
~SharedPropertyChangeObserver() override { window_->RemoveObserver(this); }
Changes GetAndClearChanges() {
Changes changes;
changes.swap(changes_);
return changes;
}
private:
// Overridden from WindowObserver:
void OnWindowSharedPropertyChanged(
Window* window,
const std::string& name,
const std::vector<uint8_t>* old_data,
const std::vector<uint8_t>* new_data) override {
changes_.push_back(base::StringPrintf(
"window=%d shared property changed key=%s old_value=%s new_value=%s",
window->local_id(), name.c_str(), VectorToString(old_data).c_str(),
VectorToString(new_data).c_str()));
}
std::string VectorToString(const std::vector<uint8_t>* data) {
if (!data)
return "NULL";
gfx::Size size = mojo::ConvertTo<gfx::Size>(*data);
return base::StringPrintf("%d,%d", size.width(), size.height());
}
Window* window_;
Changes changes_;
DISALLOW_COPY_AND_ASSIGN(SharedPropertyChangeObserver);
};
} // namespace
TEST_F(WindowObserverTest, SetSharedProperty) {
TestWindow w1;
w1.set_local_id(1);
gfx::Size size(100, 100);
{
// Change visibility from true to false and make sure we get notifications.
SharedPropertyChangeObserver observer(&w1);
w1.SetSharedProperty<gfx::Size>("size", size);
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(1U, changes.size());
EXPECT_EQ(
"window=1 shared property changed key=size old_value=NULL "
"new_value=100,100",
changes[0]);
EXPECT_EQ(1U, w1.shared_properties().size());
EXPECT_EQ(w1.GetSharedProperty<gfx::Size>("size"), size);
EXPECT_TRUE(w1.HasSharedProperty("size"));
}
{
// Set visible to existing value and verify no notifications.
SharedPropertyChangeObserver observer(&w1);
w1.SetSharedProperty<gfx::Size>("size", size);
EXPECT_TRUE(observer.GetAndClearChanges().empty());
EXPECT_EQ(1U, w1.shared_properties().size());
EXPECT_TRUE(w1.HasSharedProperty("size"));
}
{
// Clear the shared property.
// Change visibility from true to false and make sure we get notifications.
SharedPropertyChangeObserver observer(&w1);
w1.ClearSharedProperty("size");
Changes changes = observer.GetAndClearChanges();
ASSERT_EQ(1U, changes.size());
EXPECT_EQ(
"window=1 shared property changed key=size old_value=100,100 "
"new_value=NULL",
changes[0]);
EXPECT_EQ(0U, w1.shared_properties().size());
EXPECT_FALSE(w1.HasSharedProperty("size"));
}
{
// Clearing a non-existent property shouldn't update us.
SharedPropertyChangeObserver observer(&w1);
w1.ClearSharedProperty("size");
EXPECT_TRUE(observer.GetAndClearChanges().empty());
EXPECT_EQ(0U, w1.shared_properties().size());
EXPECT_FALSE(w1.HasSharedProperty("size"));
}
}
namespace {
typedef std::pair<const void*, intptr_t> PropertyChangeInfo;
class LocalPropertyChangeObserver : public WindowObserver {
public:
explicit LocalPropertyChangeObserver(Window* window)
: window_(window), property_key_(nullptr), old_property_value_(-1) {
window_->AddObserver(this);
}
~LocalPropertyChangeObserver() override { window_->RemoveObserver(this); }
PropertyChangeInfo PropertyChangeInfoAndClear() {
PropertyChangeInfo result(property_key_, old_property_value_);
property_key_ = NULL;
old_property_value_ = -3;
return result;
}
private:
void OnWindowLocalPropertyChanged(Window* window,
const void* key,
intptr_t old) override {
property_key_ = key;
old_property_value_ = old;
}
Window* window_;
const void* property_key_;
intptr_t old_property_value_;
DISALLOW_COPY_AND_ASSIGN(LocalPropertyChangeObserver);
};
} // namespace
TEST_F(WindowObserverTest, LocalPropertyChanged) {
TestWindow w1;
LocalPropertyChangeObserver o(&w1);
static const WindowProperty<int> prop = {-2};
w1.SetLocalProperty(&prop, 1);
EXPECT_EQ(PropertyChangeInfo(&prop, -2), o.PropertyChangeInfoAndClear());
w1.SetLocalProperty(&prop, -2);
EXPECT_EQ(PropertyChangeInfo(&prop, 1), o.PropertyChangeInfoAndClear());
w1.SetLocalProperty(&prop, 3);
EXPECT_EQ(PropertyChangeInfo(&prop, -2), o.PropertyChangeInfoAndClear());
w1.ClearLocalProperty(&prop);
EXPECT_EQ(PropertyChangeInfo(&prop, 3), o.PropertyChangeInfoAndClear());
// Sanity check to see if |PropertyChangeInfoAndClear| really clears.
EXPECT_EQ(PropertyChangeInfo(reinterpret_cast<const void*>(NULL), -3),
o.PropertyChangeInfoAndClear());
}
TEST_F(WindowTest, RemoveTransientWindow) {
std::unique_ptr<TestWindow> w1(CreateTestWindow(nullptr));
std::unique_ptr<TestWindow> w11(CreateTestWindow(w1.get()));
TestWindow* w12 = CreateTestWindow(w1.get());
EXPECT_EQ(2u, w1->children().size());
// w12's lifetime is now tied to w11.
w11->AddTransientWindow(w12);
w11.reset();
EXPECT_EQ(0u, w1->children().size());
}
TEST_F(WindowTest, TransientWindow) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(nullptr));
std::unique_ptr<TestWindow> w1(CreateTestWindow(parent.get()));
std::unique_ptr<TestWindow> w3(CreateTestWindow(parent.get()));
Window* w2 = CreateTestWindow(parent.get());
EXPECT_EQ(w2, parent->children().back());
ASSERT_EQ(3u, parent->children().size());
w1->AddTransientWindow(w2);
// Stack w1 at the top (end). This should force w2 to be last (on top of w1).
w1->MoveToFront();
ASSERT_EQ(3u, parent->children().size());
EXPECT_EQ(w2, parent->children().back());
// Destroy w1, which should also destroy w3 (since it's a transient child).A
w1.reset();
w2 = nullptr;
ASSERT_EQ(1u, parent->children().size());
EXPECT_EQ(w3.get(), parent->children()[0]);
}
// Tests that transient windows are stacked as a unit when using order above.
TEST_F(WindowTest, TransientWindowsGroupAbove) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(0, nullptr));
std::unique_ptr<TestWindow> w1(CreateTestWindow(1, parent.get()));
TestWindow* w11 = CreateTestWindow(11, parent.get());
std::unique_ptr<TestWindow> w2(CreateTestWindow(2, parent.get()));
TestWindow* w21 = CreateTestWindow(21, parent.get());
TestWindow* w211 = CreateTestWindow(211, parent.get());
TestWindow* w212 = CreateTestWindow(212, parent.get());
TestWindow* w213 = CreateTestWindow(213, parent.get());
TestWindow* w22 = CreateTestWindow(22, parent.get());
ASSERT_EQ(8u, parent->children().size());
// w11 is now owned by w1.
w1->AddTransientWindow(w11);
// w21 is now owned by w2.
w2->AddTransientWindow(w21);
// w22 is now owned by w2.
w2->AddTransientWindow(w22);
// w211 is now owned by w21.
w21->AddTransientWindow(w211);
// w212 is now owned by w21.
w21->AddTransientWindow(w212);
// w213 is now owned by w21.
w21->AddTransientWindow(w213);
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
// Stack w1 at the top (end), this should force w11 to be last (on top of w1).
w1->MoveToFront();
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get()));
// This tests that the order in children_ array rather than in
// transient_children_ array is used when reinserting transient children.
// If transient_children_ array was used '22' would be following '21'.
w2->MoveToFront();
EXPECT_EQ(w22, parent->children().back());
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
w11->Reorder(w2.get(), mojom::OrderDirection::ABOVE);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get()));
w21->Reorder(w1.get(), mojom::OrderDirection::ABOVE);
EXPECT_EQ(w22, parent->children().back());
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
w21->Reorder(w22, mojom::OrderDirection::ABOVE);
EXPECT_EQ(w213, parent->children().back());
EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get()));
w11->Reorder(w21, mojom::OrderDirection::ABOVE);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get()));
w213->Reorder(w21, mojom::OrderDirection::ABOVE);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
// No change when stacking a transient parent above its transient child.
w21->Reorder(w211, mojom::OrderDirection::ABOVE);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
// This tests that the order in children_ array rather than in
// transient_children_ array is used when reinserting transient children.
// If transient_children_ array was used '22' would be following '21'.
w2->Reorder(w1.get(), mojom::OrderDirection::ABOVE);
EXPECT_EQ(w212, parent->children().back());
EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get()));
w11->Reorder(w213, mojom::OrderDirection::ABOVE);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
}
// Tests that transient children are stacked as a unit when using order below.
TEST_F(WindowTest, TransientWindowsGroupBelow) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(0, nullptr));
std::unique_ptr<TestWindow> w1(CreateTestWindow(1, parent.get()));
TestWindow* w11 = CreateTestWindow(11, parent.get());
std::unique_ptr<TestWindow> w2(CreateTestWindow(2, parent.get()));
TestWindow* w21 = CreateTestWindow(21, parent.get());
TestWindow* w211 = CreateTestWindow(211, parent.get());
TestWindow* w212 = CreateTestWindow(212, parent.get());
TestWindow* w213 = CreateTestWindow(213, parent.get());
TestWindow* w22 = CreateTestWindow(22, parent.get());
ASSERT_EQ(8u, parent->children().size());
// w11 is now owned by w1.
w1->AddTransientWindow(w11);
// w21 is now owned by w2.
w2->AddTransientWindow(w21);
// w22 is now owned by w2.
w2->AddTransientWindow(w22);
// w211 is now owned by w21.
w21->AddTransientWindow(w211);
// w212 is now owned by w21.
w21->AddTransientWindow(w212);
// w213 is now owned by w21.
w21->AddTransientWindow(w213);
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
// Stack w2 at the bottom, this should force w11 to be last (on top of w1).
// This also tests that the order in children_ array rather than in
// transient_children_ array is used when reinserting transient children.
// If transient_children_ array was used '22' would be following '21'.
w2->MoveToBack();
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get()));
w1->MoveToBack();
EXPECT_EQ(w22, parent->children().back());
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
w21->Reorder(w1.get(), mojom::OrderDirection::BELOW);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 21 211 212 213 22 1 11", ChildWindowIDsAsString(parent.get()));
w11->Reorder(w2.get(), mojom::OrderDirection::BELOW);
EXPECT_EQ(w22, parent->children().back());
EXPECT_EQ("1 11 2 21 211 212 213 22", ChildWindowIDsAsString(parent.get()));
w22->Reorder(w21, mojom::OrderDirection::BELOW);
EXPECT_EQ(w213, parent->children().back());
EXPECT_EQ("1 11 2 22 21 211 212 213", ChildWindowIDsAsString(parent.get()));
w21->Reorder(w11, mojom::OrderDirection::BELOW);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 211 212 213 1 11", ChildWindowIDsAsString(parent.get()));
w213->Reorder(w211, mojom::OrderDirection::BELOW);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
// No change when stacking a transient parent below its transient child.
w21->Reorder(w211, mojom::OrderDirection::BELOW);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
w1->Reorder(w2.get(), mojom::OrderDirection::BELOW);
EXPECT_EQ(w212, parent->children().back());
EXPECT_EQ("1 11 2 22 21 213 211 212", ChildWindowIDsAsString(parent.get()));
w213->Reorder(w11, mojom::OrderDirection::BELOW);
EXPECT_EQ(w11, parent->children().back());
EXPECT_EQ("2 22 21 213 211 212 1 11", ChildWindowIDsAsString(parent.get()));
}
// Tests that windows are restacked properly after a call to
// AddTransientWindow() or RemoveTransientWindow).
TEST_F(WindowTest, RestackUponAddOrRemoveTransientWindow) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(0, nullptr));
std::unique_ptr<TestWindow> windows[4];
for (int i = 0; i < 4; i++)
windows[i].reset(CreateTestWindow(i, parent.get()));
EXPECT_EQ("0 1 2 3", ChildWindowIDsAsString(parent.get()));
windows[0]->AddTransientWindow(windows[2].get());
EXPECT_EQ("0 2 1 3", ChildWindowIDsAsString(parent.get()));
windows[0]->AddTransientWindow(windows[3].get());
EXPECT_EQ("0 2 3 1", ChildWindowIDsAsString(parent.get()));
windows[0]->RemoveTransientWindow(windows[2].get());
EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get()));
windows[0]->RemoveTransientWindow(windows[3].get());
EXPECT_EQ("0 3 2 1", ChildWindowIDsAsString(parent.get()));
}
// Tests that transient windows are stacked properly when created.
TEST_F(WindowTest, StackUponCreation) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(0, nullptr));
std::unique_ptr<TestWindow> window0(CreateTestWindow(1, parent.get()));
std::unique_ptr<TestWindow> window1(CreateTestWindow(2, parent.get()));
TestWindow* window2 = CreateTestWindow(3, parent.get());
window0->AddTransientWindow(window2);
EXPECT_EQ("1 3 2", ChildWindowIDsAsString(parent.get()));
}
namespace {
class TransientWindowObserver : public WindowObserver {
public:
TransientWindowObserver() {}
~TransientWindowObserver() override {}
void ResetCounts() { added_count_ = removed_count_ = 0; }
int added_count() const { return added_count_; }
int removed_count() const { return removed_count_; }
// WindowObserver:
void OnTransientChildAdded(ui::Window* window,
ui::Window* transient) override {
added_count_++;
}
void OnTransientChildRemoved(ui::Window* window,
ui::Window* transient) override {
removed_count_++;
}
private:
int added_count_ = 0;
int removed_count_ = 0;
DISALLOW_COPY_AND_ASSIGN(TransientWindowObserver);
};
} // namespace
// Verifies WindowObserver is notified of transient windows added/removed.
TEST_F(WindowTest, TransientNotifiesObserver) {
std::unique_ptr<TestWindow> parent(CreateTestWindow(0, nullptr));
TransientWindowObserver observer;
parent->AddObserver(&observer);
std::unique_ptr<TestWindow> child(CreateTestWindow(0, nullptr));
parent->AddTransientWindow(child.get());
EXPECT_EQ(1, observer.added_count());
EXPECT_EQ(0, observer.removed_count());
observer.ResetCounts();
parent->RemoveTransientWindow(child.get());
EXPECT_EQ(0, observer.added_count());
EXPECT_EQ(1, observer.removed_count());
observer.ResetCounts();
parent->AddTransientWindow(child.get());
EXPECT_EQ(1, observer.added_count());
EXPECT_EQ(0, observer.removed_count());
observer.ResetCounts();
child.reset();
EXPECT_EQ(0, observer.added_count());
EXPECT_EQ(1, observer.removed_count());
observer.ResetCounts();
}
} // namespace ui