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