1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "ui/views/controls/native/native_view_host.h"
6
7 #include <memory>
8
9 #include "base/macros.h"
10 #include "ui/aura/window.h"
11 #include "ui/views/controls/native/native_view_host_test_base.h"
12 #include "ui/views/test/views_test_base.h"
13 #include "ui/views/widget/widget.h"
14
15 namespace views {
16
17 class NativeViewHostTest : public test::NativeViewHostTestBase {
18 public:
19 NativeViewHostTest() = default;
20
SetUp()21 void SetUp() override {
22 ViewsTestBase::SetUp();
23 CreateTopLevel();
24 }
25
26 private:
27 DISALLOW_COPY_AND_ASSIGN(NativeViewHostTest);
28 };
29
30 namespace {
31
32 // View implementation used by NativeViewHierarchyChanged to count number of
33 // times NativeViewHierarchyChanged() is invoked.
34 class NativeViewHierarchyChangedTestView : public View {
35 public:
36 NativeViewHierarchyChangedTestView() = default;
37
ResetCount()38 void ResetCount() { notification_count_ = 0; }
39
notification_count() const40 int notification_count() const { return notification_count_; }
41
42 // Overriden from View:
NativeViewHierarchyChanged()43 void NativeViewHierarchyChanged() override {
44 ++notification_count_;
45 View::NativeViewHierarchyChanged();
46 }
47
48 private:
49 int notification_count_ = 0;
50
51 DISALLOW_COPY_AND_ASSIGN(NativeViewHierarchyChangedTestView);
52 };
53
GetNativeParent(aura::Window * window)54 aura::Window* GetNativeParent(aura::Window* window) {
55 return window->parent();
56 }
57
58 class ViewHierarchyChangedTestHost : public NativeViewHost {
59 public:
60 ViewHierarchyChangedTestHost() = default;
61
ResetParentChanges()62 void ResetParentChanges() { num_parent_changes_ = 0; }
63
num_parent_changes() const64 int num_parent_changes() const { return num_parent_changes_; }
65
66 // NativeViewHost:
ViewHierarchyChanged(const ViewHierarchyChangedDetails & details)67 void ViewHierarchyChanged(
68 const ViewHierarchyChangedDetails& details) override {
69 gfx::NativeView parent_before =
70 native_view() ? GetNativeParent(native_view()) : nullptr;
71 NativeViewHost::ViewHierarchyChanged(details);
72 gfx::NativeView parent_after =
73 native_view() ? GetNativeParent(native_view()) : nullptr;
74 if (parent_before != parent_after)
75 ++num_parent_changes_;
76 }
77
78 private:
79 int num_parent_changes_ = 0;
80
81 DISALLOW_COPY_AND_ASSIGN(ViewHierarchyChangedTestHost);
82 };
83
84 } // namespace
85
86 // Verifies NativeViewHierarchyChanged is sent.
TEST_F(NativeViewHostTest,NativeViewHierarchyChanged)87 TEST_F(NativeViewHostTest, NativeViewHierarchyChanged) {
88 // Create a child widget.
89 NativeViewHierarchyChangedTestView* test_view =
90 new NativeViewHierarchyChangedTestView;
91 NativeViewHost* host = new NativeViewHost;
92 std::unique_ptr<Widget> child(CreateChildForHost(
93 toplevel()->GetNativeView(), toplevel()->GetRootView(), test_view, host));
94 #if defined(USE_AURA)
95 // Two notifications are generated from inserting the native view into the
96 // clipping window and then inserting the clipping window into the root
97 // window.
98 EXPECT_EQ(2, test_view->notification_count());
99 #else
100 EXPECT_EQ(0, test_view->notification_count());
101 #endif
102 test_view->ResetCount();
103
104 // Detaching should send a NativeViewHierarchyChanged() notification and
105 // change the parent.
106 host->Detach();
107 #if defined(USE_AURA)
108 // Two notifications are generated from removing the native view from the
109 // clipping window and then reparenting it to the root window.
110 EXPECT_EQ(2, test_view->notification_count());
111 #else
112 EXPECT_EQ(1, test_view->notification_count());
113 #endif
114 EXPECT_NE(toplevel()->GetNativeView(),
115 GetNativeParent(child->GetNativeView()));
116 test_view->ResetCount();
117
118 // Attaching should send a NativeViewHierarchyChanged() notification and
119 // reset the parent.
120 host->Attach(child->GetNativeView());
121 #if defined(USE_AURA)
122 // There is a clipping window inserted above the native view that needs to be
123 // accounted for when looking at the relationship between the native views.
124 EXPECT_EQ(2, test_view->notification_count());
125 EXPECT_EQ(toplevel()->GetNativeView(),
126 GetNativeParent(GetNativeParent(child->GetNativeView())));
127 #else
128 EXPECT_EQ(1, test_view->notification_count());
129 EXPECT_EQ(toplevel()->GetNativeView(),
130 GetNativeParent(child->GetNativeView()));
131 #endif
132 }
133
134 // Verifies ViewHierarchyChanged handles NativeViewHost remove, add and move
135 // (reparent) operations with correct parent changes.
136 // This exercises the non-recursive code paths in
137 // View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest,ViewHierarchyChangedForHost)138 TEST_F(NativeViewHostTest, ViewHierarchyChangedForHost) {
139 // Original tree:
140 // toplevel
141 // +-- host0 (NativeViewHost)
142 // +-- child0 (Widget, attached to host0)
143 // +-- test_host (ViewHierarchyChangedTestHost)
144 // +-- test_child (Widget, attached to test_host)
145 // +-- host1 (NativeViewHost)
146 // +-- child1 (Widget, attached to host1)
147
148 // Add two children widgets attached to a NativeViewHost, and a test
149 // grandchild as child widget of host0.
150 NativeViewHost* host0 = new NativeViewHost;
151 std::unique_ptr<Widget> child0(CreateChildForHost(
152 toplevel()->GetNativeView(), toplevel()->GetRootView(), new View, host0));
153 NativeViewHost* host1 = new NativeViewHost;
154 std::unique_ptr<Widget> child1(CreateChildForHost(
155 toplevel()->GetNativeView(), toplevel()->GetRootView(), new View, host1));
156 ViewHierarchyChangedTestHost* test_host = new ViewHierarchyChangedTestHost;
157 std::unique_ptr<Widget> test_child(
158 CreateChildForHost(host0->native_view(), host0, new View, test_host));
159
160 // Remove test_host from host0, expect 1 parent change.
161 test_host->ResetParentChanges();
162 EXPECT_EQ(0, test_host->num_parent_changes());
163 host0->RemoveChildView(test_host);
164 EXPECT_EQ(1, test_host->num_parent_changes());
165
166 // Add test_host back to host0, expect 1 parent change.
167 test_host->ResetParentChanges();
168 EXPECT_EQ(0, test_host->num_parent_changes());
169 host0->AddChildView(test_host);
170 EXPECT_EQ(1, test_host->num_parent_changes());
171
172 // Reparent test_host to host1, expect no parent change because the old and
173 // new parents, host0 and host1, belong to the same toplevel widget.
174 test_host->ResetParentChanges();
175 EXPECT_EQ(0, test_host->num_parent_changes());
176 host1->AddChildView(test_host);
177 EXPECT_EQ(0, test_host->num_parent_changes());
178
179 // Reparent test_host to contents view of child0, expect 2 parent changes
180 // because the old parent belongs to the toplevel widget whereas the new
181 // parent belongs to the child0.
182 test_host->ResetParentChanges();
183 EXPECT_EQ(0, test_host->num_parent_changes());
184 child0->GetContentsView()->AddChildView(test_host);
185 EXPECT_EQ(2, test_host->num_parent_changes());
186 }
187
188 // Verifies ViewHierarchyChanged handles NativeViewHost's parent remove, add and
189 // move (reparent) operations with correct parent changes.
190 // This exercises the recursive code paths in
191 // View::PropagateRemoveNotifications() and View::PropagateAddNotifications().
TEST_F(NativeViewHostTest,ViewHierarchyChangedForHostParent)192 TEST_F(NativeViewHostTest, ViewHierarchyChangedForHostParent) {
193 // Original tree:
194 // toplevel
195 // +-- view0 (View)
196 // +-- host0 (NativeViewHierarchyChangedTestHost)
197 // +-- child0 (Widget, attached to host0)
198 // +-- view1 (View)
199 // +-- host1 (NativeViewHierarchyChangedTestHost)
200 // +-- child1 (Widget, attached to host1)
201
202 // Add two children views.
203 View* view0 = new View;
204 toplevel()->GetRootView()->AddChildView(view0);
205 View* view1 = new View;
206 toplevel()->GetRootView()->AddChildView(view1);
207
208 // To each child view, add a child widget.
209 ViewHierarchyChangedTestHost* host0 = new ViewHierarchyChangedTestHost;
210 std::unique_ptr<Widget> child0(
211 CreateChildForHost(toplevel()->GetNativeView(), view0, new View, host0));
212 ViewHierarchyChangedTestHost* host1 = new ViewHierarchyChangedTestHost;
213 std::unique_ptr<Widget> child1(
214 CreateChildForHost(toplevel()->GetNativeView(), view1, new View, host1));
215
216 // Remove view0 from top level, expect 1 parent change.
217 host0->ResetParentChanges();
218 EXPECT_EQ(0, host0->num_parent_changes());
219 toplevel()->GetRootView()->RemoveChildView(view0);
220 EXPECT_EQ(1, host0->num_parent_changes());
221
222 // Add view0 back to top level, expect 1 parent change.
223 host0->ResetParentChanges();
224 EXPECT_EQ(0, host0->num_parent_changes());
225 toplevel()->GetRootView()->AddChildView(view0);
226 EXPECT_EQ(1, host0->num_parent_changes());
227
228 // Reparent view0 to view1, expect no parent change because the old and new
229 // parents of both view0 and view1 belong to the same toplevel widget.
230 host0->ResetParentChanges();
231 host1->ResetParentChanges();
232 EXPECT_EQ(0, host0->num_parent_changes());
233 EXPECT_EQ(0, host1->num_parent_changes());
234 view1->AddChildView(view0);
235 EXPECT_EQ(0, host0->num_parent_changes());
236 EXPECT_EQ(0, host1->num_parent_changes());
237
238 // Restore original view hierarchy by adding back view0 to top level.
239 // Then, reparent view1 to contents view of child0.
240 // Expect 2 parent changes because the old parent belongs to the toplevel
241 // widget whereas the new parent belongs to the 1st child widget.
242 toplevel()->GetRootView()->AddChildView(view0);
243 host0->ResetParentChanges();
244 host1->ResetParentChanges();
245 EXPECT_EQ(0, host0->num_parent_changes());
246 EXPECT_EQ(0, host1->num_parent_changes());
247 child0->GetContentsView()->AddChildView(view1);
248 EXPECT_EQ(0, host0->num_parent_changes());
249 EXPECT_EQ(2, host1->num_parent_changes());
250 }
251
252 } // namespace views
253