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