blob: 400f6db3c19fccaef736c881003f457653ece26b [file] [log] [blame]
// Copyright (c) 2012 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/views/controls/native/native_view_host.h"
#include "base/basictypes.h"
#include "base/memory/scoped_ptr.h"
#include "ui/aura/window.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/widget.h"
namespace views {
class NativeViewHostTest : public ViewsTestBase {
public:
NativeViewHostTest() {
}
virtual void SetUp() OVERRIDE {
ViewsTestBase::SetUp();
// Create the top level widget.
toplevel_.reset(new Widget);
Widget::InitParams toplevel_params =
CreateParams(Widget::InitParams::TYPE_WINDOW);
toplevel_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
toplevel_->Init(toplevel_params);
}
// Create a child widget whose native parent is |native_parent_view|, uses
// |contents_view|, and is attached to |host| which is added as a child to
// |parent_view|.
Widget* CreateChildForHost(gfx::NativeView native_parent_view,
View* parent_view,
View* contents_view,
NativeViewHost* host) {
Widget* child = new Widget;
Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL);
child_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
child_params.parent = native_parent_view;
child->Init(child_params);
child->SetContentsView(contents_view);
// Owned by |parent_view|.
parent_view->AddChildView(host);
host->Attach(child->GetNativeView());
return child;
}
Widget* toplevel() {
return toplevel_.get();
}
private:
scoped_ptr<Widget> toplevel_;
DISALLOW_COPY_AND_ASSIGN(NativeViewHostTest);
};
namespace {
// View implementation used by NativeViewHierarchyChanged to count number of
// times NativeViewHierarchyChanged() is invoked.
class NativeViewHierarchyChangedTestView : public View {
public:
NativeViewHierarchyChangedTestView() : notification_count_(0) {
}
void ResetCount() {
notification_count_ = 0;
}
int notification_count() const { return notification_count_; }
// Overriden from View:
virtual void NativeViewHierarchyChanged() OVERRIDE {
++notification_count_;
View::NativeViewHierarchyChanged();
}
private:
int notification_count_;
DISALLOW_COPY_AND_ASSIGN(NativeViewHierarchyChangedTestView);
};
aura::Window* GetNativeParent(aura::Window* window) {
return window->parent();
}
class ViewHierarchyChangedTestHost : public NativeViewHost {
public:
ViewHierarchyChangedTestHost()
: num_parent_changes_(0) {
}
void ResetParentChanges() {
num_parent_changes_ = 0;
}
int num_parent_changes() const {
return num_parent_changes_;
}
// Overriden from NativeViewHost:
virtual void ViewHierarchyChanged(
const ViewHierarchyChangedDetails& details) OVERRIDE {
gfx::NativeView parent_before = native_view() ?
GetNativeParent(native_view()) : NULL;
NativeViewHost::ViewHierarchyChanged(details);
gfx::NativeView parent_after = native_view() ?
GetNativeParent(native_view()) : NULL;
if (parent_before != parent_after)
++num_parent_changes_;
}
private:
int num_parent_changes_;
DISALLOW_COPY_AND_ASSIGN(ViewHierarchyChangedTestHost);
};
} // namespace
// Verifies NativeViewHierarchyChanged is sent.
TEST_F(NativeViewHostTest, NativeViewHierarchyChanged) {
// Create a child widget.
NativeViewHierarchyChangedTestView* test_view =
new NativeViewHierarchyChangedTestView;
NativeViewHost* host = new NativeViewHost;
scoped_ptr<Widget> child(CreateChildForHost(toplevel()->GetNativeView(),
toplevel()->GetRootView(),
test_view,
host));
#if defined(USE_AURA)
// One notification is generated from inserting the native view into the
// clipping window.
EXPECT_EQ(1, test_view->notification_count());
#else
EXPECT_EQ(0, test_view->notification_count());
#endif
test_view->ResetCount();
// Detaching should send a NativeViewHierarchyChanged() notification and
// change the parent.
host->Detach();
#if defined(USE_AURA)
// Two notifications are generated from removing the native view from the
// clipping window and then reparenting it to the root window.
EXPECT_EQ(2, test_view->notification_count());
#else
EXPECT_EQ(1, test_view->notification_count());
#endif
EXPECT_NE(toplevel()->GetNativeView(),
GetNativeParent(child->GetNativeView()));
test_view->ResetCount();
// Attaching should send a NativeViewHierarchyChanged() notification and
// reset the parent.
host->Attach(child->GetNativeView());
EXPECT_EQ(1, test_view->notification_count());
#if defined(USE_AURA)
// There is a clipping window inserted above the native view that needs to be
// accounted for when looking at the relationship between the native views.
EXPECT_EQ(toplevel()->GetNativeView(),
GetNativeParent(GetNativeParent(child->GetNativeView())));
#else
EXPECT_EQ(toplevel()->GetNativeView(),
GetNativeParent(child->GetNativeView()));
#endif
}
// Verifies ViewHierarchyChanged handles NativeViewHost remove, add and move
// (reparent) operations with correct parent changes.
// This exercises the non-recursive code paths in
// View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest, ViewHierarchyChangedForHost) {
// Original tree:
// toplevel
// +-- host0 (NativeViewHost)
// +-- child0 (Widget, attached to host0)
// +-- test_host (ViewHierarchyChangedTestHost)
// +-- test_child (Widget, attached to test_host)
// +-- host1 (NativeViewHost)
// +-- child1 (Widget, attached to host1)
// Add two children widgets attached to a NativeViewHost, and a test
// grandchild as child widget of host0.
NativeViewHost* host0 = new NativeViewHost;
scoped_ptr<Widget> child0(CreateChildForHost(toplevel()->GetNativeView(),
toplevel()->GetRootView(),
new View,
host0));
NativeViewHost* host1 = new NativeViewHost;
scoped_ptr<Widget> child1(CreateChildForHost(toplevel()->GetNativeView(),
toplevel()->GetRootView(),
new View,
host1));
ViewHierarchyChangedTestHost* test_host = new ViewHierarchyChangedTestHost;
scoped_ptr<Widget> test_child(CreateChildForHost(host0->native_view(),
host0,
new View,
test_host));
// Remove test_host from host0, expect 1 parent change.
test_host->ResetParentChanges();
EXPECT_EQ(0, test_host->num_parent_changes());
host0->RemoveChildView(test_host);
EXPECT_EQ(1, test_host->num_parent_changes());
// Add test_host back to host0, expect 1 parent change.
test_host->ResetParentChanges();
EXPECT_EQ(0, test_host->num_parent_changes());
host0->AddChildView(test_host);
EXPECT_EQ(1, test_host->num_parent_changes());
// Reparent test_host to host1, expect no parent change because the old and
// new parents, host0 and host1, belong to the same toplevel widget.
test_host->ResetParentChanges();
EXPECT_EQ(0, test_host->num_parent_changes());
host1->AddChildView(test_host);
EXPECT_EQ(0, test_host->num_parent_changes());
// Reparent test_host to contents view of child0, expect 2 parent changes
// because the old parent belongs to the toplevel widget whereas the new
// parent belongs to the child0.
test_host->ResetParentChanges();
EXPECT_EQ(0, test_host->num_parent_changes());
child0->GetContentsView()->AddChildView(test_host);
EXPECT_EQ(2, test_host->num_parent_changes());
}
// Verifies ViewHierarchyChanged handles NativeViewHost's parent remove, add and
// move (reparent) operations with correct parent changes.
// This exercises the recursive code paths in
// View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest, ViewHierarchyChangedForHostParent) {
// Original tree:
// toplevel
// +-- view0 (View)
// +-- host0 (NativeViewHierarchyChangedTestHost)
// +-- child0 (Widget, attached to host0)
// +-- view1 (View)
// +-- host1 (NativeViewHierarchyChangedTestHost)
// +-- child1 (Widget, attached to host1)
// Add two children views.
View* view0 = new View;
toplevel()->GetRootView()->AddChildView(view0);
View* view1 = new View;
toplevel()->GetRootView()->AddChildView(view1);
// To each child view, add a child widget.
ViewHierarchyChangedTestHost* host0 = new ViewHierarchyChangedTestHost;
scoped_ptr<Widget> child0(CreateChildForHost(toplevel()->GetNativeView(),
view0,
new View,
host0));
ViewHierarchyChangedTestHost* host1 = new ViewHierarchyChangedTestHost;
scoped_ptr<Widget> child1(CreateChildForHost(toplevel()->GetNativeView(),
view1,
new View,
host1));
// Remove view0 from top level, expect 1 parent change.
host0->ResetParentChanges();
EXPECT_EQ(0, host0->num_parent_changes());
toplevel()->GetRootView()->RemoveChildView(view0);
EXPECT_EQ(1, host0->num_parent_changes());
// Add view0 back to top level, expect 1 parent change.
host0->ResetParentChanges();
EXPECT_EQ(0, host0->num_parent_changes());
toplevel()->GetRootView()->AddChildView(view0);
EXPECT_EQ(1, host0->num_parent_changes());
// Reparent view0 to view1, expect no parent change because the old and new
// parents of both view0 and view1 belong to the same toplevel widget.
host0->ResetParentChanges();
host1->ResetParentChanges();
EXPECT_EQ(0, host0->num_parent_changes());
EXPECT_EQ(0, host1->num_parent_changes());
view1->AddChildView(view0);
EXPECT_EQ(0, host0->num_parent_changes());
EXPECT_EQ(0, host1->num_parent_changes());
// Restore original view hierarchy by adding back view0 to top level.
// Then, reparent view1 to contents view of child0.
// Expect 2 parent changes because the old parent belongs to the toplevel
// widget whereas the new parent belongs to the 1st child widget.
toplevel()->GetRootView()->AddChildView(view0);
host0->ResetParentChanges();
host1->ResetParentChanges();
EXPECT_EQ(0, host0->num_parent_changes());
EXPECT_EQ(0, host1->num_parent_changes());
child0->GetContentsView()->AddChildView(view1);
EXPECT_EQ(0, host0->num_parent_changes());
EXPECT_EQ(2, host1->num_parent_changes());
}
} // namespace views