| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 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 "mojo/services/view_manager/cpp/view_manager.h" | |
| 6 | |
| 7 #include "base/bind.h" | |
| 8 #include "base/location.h" | |
| 9 #include "base/logging.h" | |
| 10 #include "base/memory/scoped_vector.h" | |
| 11 #include "base/message_loop/message_loop.h" | |
| 12 #include "base/run_loop.h" | |
| 13 #include "base/test/test_timeouts.h" | |
| 14 #include "mojo/public/cpp/application/application_connection.h" | |
| 15 #include "mojo/public/cpp/application/application_delegate.h" | |
| 16 #include "mojo/public/cpp/application/application_impl.h" | |
| 17 #include "mojo/public/cpp/application/application_test_base.h" | |
| 18 #include "mojo/public/cpp/application/service_provider_impl.h" | |
| 19 #include "mojo/services/geometry/cpp/geometry_util.h" | |
| 20 #include "mojo/services/view_manager/cpp/lib/view_manager_client_impl.h" | |
| 21 #include "mojo/services/view_manager/cpp/view_manager_client_factory.h" | |
| 22 #include "mojo/services/view_manager/cpp/view_manager_context.h" | |
| 23 #include "mojo/services/view_manager/cpp/view_manager_delegate.h" | |
| 24 #include "mojo/services/view_manager/cpp/view_observer.h" | |
| 25 | |
| 26 namespace mojo { | |
| 27 | |
| 28 namespace { | |
| 29 | |
| 30 base::RunLoop* current_run_loop = nullptr; | |
| 31 | |
| 32 void TimeoutRunLoop(const base::Closure& timeout_task, bool* timeout) { | |
| 33 CHECK(current_run_loop); | |
| 34 *timeout = true; | |
| 35 timeout_task.Run(); | |
| 36 } | |
| 37 | |
| 38 bool DoRunLoopWithTimeout() { | |
| 39 if (current_run_loop != nullptr) | |
| 40 return false; | |
| 41 | |
| 42 bool timeout = false; | |
| 43 base::RunLoop run_loop; | |
| 44 base::MessageLoop::current()->PostDelayedTask( | |
| 45 FROM_HERE, base::Bind(&TimeoutRunLoop, run_loop.QuitClosure(), &timeout), | |
| 46 TestTimeouts::action_timeout()); | |
| 47 | |
| 48 current_run_loop = &run_loop; | |
| 49 current_run_loop->Run(); | |
| 50 current_run_loop = nullptr; | |
| 51 return !timeout; | |
| 52 } | |
| 53 | |
| 54 void QuitRunLoop() { | |
| 55 current_run_loop->Quit(); | |
| 56 current_run_loop = nullptr; | |
| 57 } | |
| 58 | |
| 59 class BoundsChangeObserver : public ViewObserver { | |
| 60 public: | |
| 61 explicit BoundsChangeObserver(View* view) : view_(view) { | |
| 62 view_->AddObserver(this); | |
| 63 } | |
| 64 ~BoundsChangeObserver() override { view_->RemoveObserver(this); } | |
| 65 | |
| 66 private: | |
| 67 // Overridden from ViewObserver: | |
| 68 void OnViewBoundsChanged(View* view, | |
| 69 const Rect& old_bounds, | |
| 70 const Rect& new_bounds) override { | |
| 71 DCHECK_EQ(view, view_); | |
| 72 QuitRunLoop(); | |
| 73 } | |
| 74 | |
| 75 View* view_; | |
| 76 | |
| 77 MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver); | |
| 78 }; | |
| 79 | |
| 80 // Wait until the bounds of the supplied view change; returns false on timeout. | |
| 81 bool WaitForBoundsToChange(View* view) { | |
| 82 BoundsChangeObserver observer(view); | |
| 83 return DoRunLoopWithTimeout(); | |
| 84 } | |
| 85 | |
| 86 // Spins a run loop until the tree beginning at |root| has |tree_size| views | |
| 87 // (including |root|). | |
| 88 class TreeSizeMatchesObserver : public ViewObserver { | |
| 89 public: | |
| 90 TreeSizeMatchesObserver(View* tree, size_t tree_size) | |
| 91 : tree_(tree), tree_size_(tree_size) { | |
| 92 tree_->AddObserver(this); | |
| 93 } | |
| 94 ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); } | |
| 95 | |
| 96 bool IsTreeCorrectSize() { return CountViews(tree_) == tree_size_; } | |
| 97 | |
| 98 private: | |
| 99 // Overridden from ViewObserver: | |
| 100 void OnTreeChanged(const TreeChangeParams& params) override { | |
| 101 if (IsTreeCorrectSize()) | |
| 102 QuitRunLoop(); | |
| 103 } | |
| 104 | |
| 105 size_t CountViews(const View* view) const { | |
| 106 size_t count = 1; | |
| 107 View::Children::const_iterator it = view->children().begin(); | |
| 108 for (; it != view->children().end(); ++it) | |
| 109 count += CountViews(*it); | |
| 110 return count; | |
| 111 } | |
| 112 | |
| 113 View* tree_; | |
| 114 size_t tree_size_; | |
| 115 | |
| 116 MOJO_DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver); | |
| 117 }; | |
| 118 | |
| 119 // Wait until |view|'s tree size matches |tree_size|; returns false on timeout. | |
| 120 bool WaitForTreeSizeToMatch(View* view, size_t tree_size) { | |
| 121 TreeSizeMatchesObserver observer(view, tree_size); | |
| 122 return observer.IsTreeCorrectSize() || DoRunLoopWithTimeout(); | |
| 123 } | |
| 124 | |
| 125 class OrderChangeObserver : public ViewObserver { | |
| 126 public: | |
| 127 OrderChangeObserver(View* view) : view_(view) { view_->AddObserver(this); } | |
| 128 ~OrderChangeObserver() override { view_->RemoveObserver(this); } | |
| 129 | |
| 130 private: | |
| 131 // Overridden from ViewObserver: | |
| 132 void OnViewReordered(View* view, | |
| 133 View* relative_view, | |
| 134 OrderDirection direction) override { | |
| 135 DCHECK_EQ(view, view_); | |
| 136 QuitRunLoop(); | |
| 137 } | |
| 138 | |
| 139 View* view_; | |
| 140 | |
| 141 MOJO_DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver); | |
| 142 }; | |
| 143 | |
| 144 // Wait until |view|'s tree size matches |tree_size|; returns false on timeout. | |
| 145 bool WaitForOrderChange(ViewManager* view_manager, View* view) { | |
| 146 OrderChangeObserver observer(view); | |
| 147 return DoRunLoopWithTimeout(); | |
| 148 } | |
| 149 | |
| 150 // Tracks a view's destruction. Query is_valid() for current state. | |
| 151 class ViewTracker : public ViewObserver { | |
| 152 public: | |
| 153 explicit ViewTracker(View* view) : view_(view) { view_->AddObserver(this); } | |
| 154 ~ViewTracker() override { | |
| 155 if (view_) | |
| 156 view_->RemoveObserver(this); | |
| 157 } | |
| 158 | |
| 159 bool is_valid() const { return !!view_; } | |
| 160 | |
| 161 private: | |
| 162 // Overridden from ViewObserver: | |
| 163 void OnViewDestroyed(View* view) override { | |
| 164 DCHECK_EQ(view, view_); | |
| 165 view_ = nullptr; | |
| 166 } | |
| 167 | |
| 168 View* view_; | |
| 169 | |
| 170 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker); | |
| 171 }; | |
| 172 | |
| 173 } // namespace | |
| 174 | |
| 175 // ViewManager ----------------------------------------------------------------- | |
| 176 | |
| 177 // These tests model synchronization of two peer connections to the view manager | |
| 178 // service, that are given access to some root view. | |
| 179 | |
| 180 class ViewManagerTest : public test::ApplicationTestBase, | |
| 181 public ApplicationDelegate, | |
| 182 public ViewManagerDelegate { | |
| 183 public: | |
| 184 ViewManagerTest() | |
| 185 : most_recent_view_manager_(nullptr), window_manager_(nullptr) {} | |
| 186 | |
| 187 // Overridden from ApplicationDelegate: | |
| 188 void Initialize(ApplicationImpl* app) override { | |
| 189 view_manager_client_factory_.reset( | |
| 190 new ViewManagerClientFactory(app->shell(), this)); | |
| 191 } | |
| 192 | |
| 193 // ApplicationDelegate implementation. | |
| 194 bool ConfigureIncomingConnection(ApplicationConnection* connection) override { | |
| 195 connection->AddService(view_manager_client_factory_.get()); | |
| 196 return true; | |
| 197 } | |
| 198 | |
| 199 ViewManager* window_manager() { return window_manager_; } | |
| 200 | |
| 201 // Embeds another version of the test app @ view; returns nullptr on timeout. | |
| 202 ViewManager* Embed(ViewManager* view_manager, View* view) { | |
| 203 DCHECK_EQ(view_manager, view->view_manager()); | |
| 204 most_recent_view_manager_ = nullptr; | |
| 205 view->Embed(application_impl()->url()); | |
| 206 if (!DoRunLoopWithTimeout()) | |
| 207 return nullptr; | |
| 208 ViewManager* vm = nullptr; | |
| 209 std::swap(vm, most_recent_view_manager_); | |
| 210 return vm; | |
| 211 } | |
| 212 | |
| 213 ApplicationDelegate* GetApplicationDelegate() override { return this; } | |
| 214 | |
| 215 // Overridden from ViewManagerDelegate: | |
| 216 void OnEmbed(View* root, | |
| 217 InterfaceRequest<ServiceProvider> services, | |
| 218 ServiceProviderPtr exposed_services) override { | |
| 219 most_recent_view_manager_ = root->view_manager(); | |
| 220 QuitRunLoop(); | |
| 221 } | |
| 222 void OnViewManagerDisconnected(ViewManager* view_manager) override {} | |
| 223 | |
| 224 private: | |
| 225 // Overridden from testing::Test: | |
| 226 void SetUp() override { | |
| 227 ApplicationTestBase::SetUp(); | |
| 228 | |
| 229 view_manager_context_.reset(new ViewManagerContext(application_impl())); | |
| 230 view_manager_context_->Embed(application_impl()->url()); | |
| 231 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 232 std::swap(window_manager_, most_recent_view_manager_); | |
| 233 } | |
| 234 | |
| 235 // Overridden from testing::Test: | |
| 236 void TearDown() override { ApplicationTestBase::TearDown(); } | |
| 237 | |
| 238 scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; | |
| 239 | |
| 240 scoped_ptr<ViewManagerContext> view_manager_context_; | |
| 241 | |
| 242 // Used to receive the most recent view manager loaded by an embed action. | |
| 243 ViewManager* most_recent_view_manager_; | |
| 244 // The View Manager connection held by the window manager (app running at the | |
| 245 // root view). | |
| 246 ViewManager* window_manager_; | |
| 247 | |
| 248 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); | |
| 249 }; | |
| 250 | |
| 251 TEST_F(ViewManagerTest, RootView) { | |
| 252 ASSERT_NE(nullptr, window_manager()); | |
| 253 EXPECT_NE(nullptr, window_manager()->GetRoot()); | |
| 254 EXPECT_EQ("mojo:window_manager", window_manager()->GetEmbedderURL()); | |
| 255 } | |
| 256 | |
| 257 TEST_F(ViewManagerTest, Embed) { | |
| 258 View* view = window_manager()->CreateView(); | |
| 259 ASSERT_NE(nullptr, view); | |
| 260 view->SetVisible(true); | |
| 261 window_manager()->GetRoot()->AddChild(view); | |
| 262 ViewManager* embedded = Embed(window_manager(), view); | |
| 263 ASSERT_NE(nullptr, embedded); | |
| 264 | |
| 265 View* view_in_embedded = embedded->GetRoot(); | |
| 266 ASSERT_NE(nullptr, view_in_embedded); | |
| 267 EXPECT_EQ(view->id(), view_in_embedded->id()); | |
| 268 EXPECT_EQ(nullptr, view_in_embedded->parent()); | |
| 269 EXPECT_TRUE(view_in_embedded->children().empty()); | |
| 270 } | |
| 271 | |
| 272 // Window manager has two views, N1 and N11. Embeds A at N1. A should not see | |
| 273 // N11. | |
| 274 TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) { | |
| 275 View* view = window_manager()->CreateView(); | |
| 276 ASSERT_NE(nullptr, view); | |
| 277 view->SetVisible(true); | |
| 278 window_manager()->GetRoot()->AddChild(view); | |
| 279 View* nested = window_manager()->CreateView(); | |
| 280 ASSERT_NE(nullptr, nested); | |
| 281 nested->SetVisible(true); | |
| 282 view->AddChild(nested); | |
| 283 | |
| 284 ViewManager* embedded = Embed(window_manager(), view); | |
| 285 ASSERT_NE(nullptr, embedded); | |
| 286 View* view_in_embedded = embedded->GetRoot(); | |
| 287 EXPECT_EQ(view->id(), view_in_embedded->id()); | |
| 288 EXPECT_EQ(nullptr, view_in_embedded->parent()); | |
| 289 EXPECT_TRUE(view_in_embedded->children().empty()); | |
| 290 } | |
| 291 | |
| 292 // TODO(beng): write a replacement test for the one that once existed here: | |
| 293 // This test validates the following scenario: | |
| 294 // - a view originating from one connection | |
| 295 // - a view originating from a second connection | |
| 296 // + the connection originating the view is destroyed | |
| 297 // -> the view should still exist (since the second connection is live) but | |
| 298 // should be disconnected from any views. | |
| 299 // http://crbug.com/396300 | |
| 300 // | |
| 301 // TODO(beng): The new test should validate the scenario as described above | |
| 302 // except that the second connection still has a valid tree. | |
| 303 | |
| 304 // Verifies that bounds changes applied to a view hierarchy in one connection | |
| 305 // are reflected to another. | |
| 306 TEST_F(ViewManagerTest, SetBounds) { | |
| 307 View* view = window_manager()->CreateView(); | |
| 308 view->SetVisible(true); | |
| 309 window_manager()->GetRoot()->AddChild(view); | |
| 310 ViewManager* embedded = Embed(window_manager(), view); | |
| 311 ASSERT_NE(nullptr, embedded); | |
| 312 | |
| 313 View* view_in_embedded = embedded->GetViewById(view->id()); | |
| 314 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
| 315 | |
| 316 Rect rect; | |
| 317 rect.width = rect.height = 100; | |
| 318 view->SetBounds(rect); | |
| 319 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); | |
| 320 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
| 321 } | |
| 322 | |
| 323 // Verifies that bounds changes applied to a view owned by a different | |
| 324 // connection are refused. | |
| 325 TEST_F(ViewManagerTest, SetBoundsSecurity) { | |
| 326 View* view = window_manager()->CreateView(); | |
| 327 view->SetVisible(true); | |
| 328 window_manager()->GetRoot()->AddChild(view); | |
| 329 ViewManager* embedded = Embed(window_manager(), view); | |
| 330 ASSERT_NE(nullptr, embedded); | |
| 331 | |
| 332 View* view_in_embedded = embedded->GetViewById(view->id()); | |
| 333 Rect rect; | |
| 334 rect.width = 800; | |
| 335 rect.height = 600; | |
| 336 view->SetBounds(rect); | |
| 337 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); | |
| 338 | |
| 339 rect.width = 1024; | |
| 340 rect.height = 768; | |
| 341 view_in_embedded->SetBounds(rect); | |
| 342 // Bounds change should have been rejected. | |
| 343 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
| 344 } | |
| 345 | |
| 346 // Verifies that a view can only be destroyed by the connection that created it. | |
| 347 TEST_F(ViewManagerTest, DestroySecurity) { | |
| 348 View* view = window_manager()->CreateView(); | |
| 349 view->SetVisible(true); | |
| 350 window_manager()->GetRoot()->AddChild(view); | |
| 351 ViewManager* embedded = Embed(window_manager(), view); | |
| 352 ASSERT_NE(nullptr, embedded); | |
| 353 | |
| 354 View* view_in_embedded = embedded->GetViewById(view->id()); | |
| 355 | |
| 356 ViewTracker tracker2(view_in_embedded); | |
| 357 view_in_embedded->Destroy(); | |
| 358 // View should not have been destroyed. | |
| 359 EXPECT_TRUE(tracker2.is_valid()); | |
| 360 | |
| 361 ViewTracker tracker1(view); | |
| 362 view->Destroy(); | |
| 363 EXPECT_FALSE(tracker1.is_valid()); | |
| 364 } | |
| 365 | |
| 366 TEST_F(ViewManagerTest, MultiRoots) { | |
| 367 View* view1 = window_manager()->CreateView(); | |
| 368 view1->SetVisible(true); | |
| 369 window_manager()->GetRoot()->AddChild(view1); | |
| 370 View* view2 = window_manager()->CreateView(); | |
| 371 view2->SetVisible(true); | |
| 372 window_manager()->GetRoot()->AddChild(view2); | |
| 373 ViewManager* embedded1 = Embed(window_manager(), view1); | |
| 374 ASSERT_NE(nullptr, embedded1); | |
| 375 ViewManager* embedded2 = Embed(window_manager(), view2); | |
| 376 ASSERT_NE(nullptr, embedded2); | |
| 377 EXPECT_NE(embedded1, embedded2); | |
| 378 } | |
| 379 | |
| 380 TEST_F(ViewManagerTest, EmbeddingIdentity) { | |
| 381 View* view = window_manager()->CreateView(); | |
| 382 view->SetVisible(true); | |
| 383 window_manager()->GetRoot()->AddChild(view); | |
| 384 ViewManager* embedded = Embed(window_manager(), view); | |
| 385 ASSERT_NE(nullptr, embedded); | |
| 386 EXPECT_EQ(application_impl()->url(), embedded->GetEmbedderURL()); | |
| 387 } | |
| 388 | |
| 389 // TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change. | |
| 390 // Debug and re-enable this. | |
| 391 TEST_F(ViewManagerTest, DISABLED_Reorder) { | |
| 392 View* view1 = window_manager()->CreateView(); | |
| 393 view1->SetVisible(true); | |
| 394 window_manager()->GetRoot()->AddChild(view1); | |
| 395 | |
| 396 ViewManager* embedded = Embed(window_manager(), view1); | |
| 397 ASSERT_NE(nullptr, embedded); | |
| 398 | |
| 399 View* view11 = embedded->CreateView(); | |
| 400 view11->SetVisible(true); | |
| 401 embedded->GetRoot()->AddChild(view11); | |
| 402 View* view12 = embedded->CreateView(); | |
| 403 view12->SetVisible(true); | |
| 404 embedded->GetRoot()->AddChild(view12); | |
| 405 | |
| 406 View* root_in_embedded = embedded->GetRoot(); | |
| 407 | |
| 408 { | |
| 409 ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u)); | |
| 410 view11->MoveToFront(); | |
| 411 ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded)); | |
| 412 | |
| 413 EXPECT_EQ(root_in_embedded->children().front(), | |
| 414 embedded->GetViewById(view12->id())); | |
| 415 EXPECT_EQ(root_in_embedded->children().back(), | |
| 416 embedded->GetViewById(view11->id())); | |
| 417 } | |
| 418 | |
| 419 { | |
| 420 view11->MoveToBack(); | |
| 421 ASSERT_TRUE(WaitForOrderChange(embedded, | |
| 422 embedded->GetViewById(view11->id()))); | |
| 423 | |
| 424 EXPECT_EQ(root_in_embedded->children().front(), | |
| 425 embedded->GetViewById(view11->id())); | |
| 426 EXPECT_EQ(root_in_embedded->children().back(), | |
| 427 embedded->GetViewById(view12->id())); | |
| 428 } | |
| 429 } | |
| 430 | |
| 431 namespace { | |
| 432 | |
| 433 class VisibilityChangeObserver : public ViewObserver { | |
| 434 public: | |
| 435 explicit VisibilityChangeObserver(View* view) : view_(view) { | |
| 436 view_->AddObserver(this); | |
| 437 } | |
| 438 ~VisibilityChangeObserver() override { view_->RemoveObserver(this); } | |
| 439 | |
| 440 private: | |
| 441 // Overridden from ViewObserver: | |
| 442 void OnViewVisibilityChanged(View* view) override { | |
| 443 EXPECT_EQ(view, view_); | |
| 444 QuitRunLoop(); | |
| 445 } | |
| 446 | |
| 447 View* view_; | |
| 448 | |
| 449 MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); | |
| 450 }; | |
| 451 | |
| 452 } // namespace | |
| 453 | |
| 454 TEST_F(ViewManagerTest, Visible) { | |
| 455 View* view1 = window_manager()->CreateView(); | |
| 456 view1->SetVisible(true); | |
| 457 window_manager()->GetRoot()->AddChild(view1); | |
| 458 | |
| 459 // Embed another app and verify initial state. | |
| 460 ViewManager* embedded = Embed(window_manager(), view1); | |
| 461 ASSERT_NE(nullptr, embedded); | |
| 462 ASSERT_NE(nullptr, embedded->GetRoot()); | |
| 463 View* embedded_root = embedded->GetRoot(); | |
| 464 EXPECT_TRUE(embedded_root->visible()); | |
| 465 EXPECT_TRUE(embedded_root->IsDrawn()); | |
| 466 | |
| 467 // Change the visible state from the first connection and verify its mirrored | |
| 468 // correctly to the embedded app. | |
| 469 { | |
| 470 VisibilityChangeObserver observer(embedded_root); | |
| 471 view1->SetVisible(false); | |
| 472 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 473 } | |
| 474 | |
| 475 EXPECT_FALSE(view1->visible()); | |
| 476 EXPECT_FALSE(view1->IsDrawn()); | |
| 477 | |
| 478 EXPECT_FALSE(embedded_root->visible()); | |
| 479 EXPECT_FALSE(embedded_root->IsDrawn()); | |
| 480 | |
| 481 // Make the node visible again. | |
| 482 { | |
| 483 VisibilityChangeObserver observer(embedded_root); | |
| 484 view1->SetVisible(true); | |
| 485 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 486 } | |
| 487 | |
| 488 EXPECT_TRUE(view1->visible()); | |
| 489 EXPECT_TRUE(view1->IsDrawn()); | |
| 490 | |
| 491 EXPECT_TRUE(embedded_root->visible()); | |
| 492 EXPECT_TRUE(embedded_root->IsDrawn()); | |
| 493 } | |
| 494 | |
| 495 namespace { | |
| 496 | |
| 497 class DrawnChangeObserver : public ViewObserver { | |
| 498 public: | |
| 499 explicit DrawnChangeObserver(View* view) : view_(view) { | |
| 500 view_->AddObserver(this); | |
| 501 } | |
| 502 ~DrawnChangeObserver() override { view_->RemoveObserver(this); } | |
| 503 | |
| 504 private: | |
| 505 // Overridden from ViewObserver: | |
| 506 void OnViewDrawnChanged(View* view) override { | |
| 507 EXPECT_EQ(view, view_); | |
| 508 QuitRunLoop(); | |
| 509 } | |
| 510 | |
| 511 View* view_; | |
| 512 | |
| 513 MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); | |
| 514 }; | |
| 515 | |
| 516 } // namespace | |
| 517 | |
| 518 TEST_F(ViewManagerTest, Drawn) { | |
| 519 View* view1 = window_manager()->CreateView(); | |
| 520 view1->SetVisible(true); | |
| 521 window_manager()->GetRoot()->AddChild(view1); | |
| 522 | |
| 523 // Embed another app and verify initial state. | |
| 524 ViewManager* embedded = Embed(window_manager(), view1); | |
| 525 ASSERT_NE(nullptr, embedded); | |
| 526 ASSERT_NE(nullptr, embedded->GetRoot()); | |
| 527 View* embedded_root = embedded->GetRoot(); | |
| 528 EXPECT_TRUE(embedded_root->visible()); | |
| 529 EXPECT_TRUE(embedded_root->IsDrawn()); | |
| 530 | |
| 531 // Change the visibility of the root, this should propagate a drawn state | |
| 532 // change to |embedded|. | |
| 533 { | |
| 534 DrawnChangeObserver observer(embedded_root); | |
| 535 window_manager()->GetRoot()->SetVisible(false); | |
| 536 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 537 } | |
| 538 | |
| 539 EXPECT_TRUE(view1->visible()); | |
| 540 EXPECT_FALSE(view1->IsDrawn()); | |
| 541 | |
| 542 EXPECT_TRUE(embedded_root->visible()); | |
| 543 EXPECT_FALSE(embedded_root->IsDrawn()); | |
| 544 } | |
| 545 | |
| 546 // TODO(beng): tests for view event dispatcher. | |
| 547 // - verify that we see events for all views. | |
| 548 | |
| 549 namespace { | |
| 550 | |
| 551 class FocusChangeObserver : public ViewObserver { | |
| 552 public: | |
| 553 explicit FocusChangeObserver(View* view) | |
| 554 : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) { | |
| 555 view_->AddObserver(this); | |
| 556 } | |
| 557 ~FocusChangeObserver() override { view_->RemoveObserver(this); } | |
| 558 | |
| 559 View* last_gained_focus() { return last_gained_focus_; } | |
| 560 | |
| 561 View* last_lost_focus() { return last_lost_focus_; } | |
| 562 | |
| 563 private: | |
| 564 // Overridden from ViewObserver. | |
| 565 void OnViewFocusChanged(View* gained_focus, View* lost_focus) override { | |
| 566 last_gained_focus_ = gained_focus; | |
| 567 last_lost_focus_ = lost_focus; | |
| 568 QuitRunLoop(); | |
| 569 } | |
| 570 | |
| 571 View* view_; | |
| 572 View* last_gained_focus_; | |
| 573 View* last_lost_focus_; | |
| 574 | |
| 575 MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); | |
| 576 }; | |
| 577 | |
| 578 } // namespace | |
| 579 | |
| 580 TEST_F(ViewManagerTest, Focus) { | |
| 581 View* view1 = window_manager()->CreateView(); | |
| 582 view1->SetVisible(true); | |
| 583 window_manager()->GetRoot()->AddChild(view1); | |
| 584 | |
| 585 ViewManager* embedded = Embed(window_manager(), view1); | |
| 586 ASSERT_NE(nullptr, embedded); | |
| 587 View* view11 = embedded->CreateView(); | |
| 588 view11->SetVisible(true); | |
| 589 embedded->GetRoot()->AddChild(view11); | |
| 590 | |
| 591 // TODO(alhaad): Figure out why switching focus between views from different | |
| 592 // connections is causing the tests to crash and add tests for that. | |
| 593 { | |
| 594 View* embedded_root = embedded->GetRoot(); | |
| 595 FocusChangeObserver observer(embedded_root); | |
| 596 embedded_root->SetFocus(); | |
| 597 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 598 ASSERT_NE(nullptr, observer.last_gained_focus()); | |
| 599 EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id()); | |
| 600 } | |
| 601 { | |
| 602 FocusChangeObserver observer(view11); | |
| 603 view11->SetFocus(); | |
| 604 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 605 ASSERT_NE(nullptr, observer.last_gained_focus()); | |
| 606 ASSERT_NE(nullptr, observer.last_lost_focus()); | |
| 607 EXPECT_EQ(view11->id(), observer.last_gained_focus()->id()); | |
| 608 EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id()); | |
| 609 } | |
| 610 } | |
| 611 | |
| 612 class ViewRemovedFromParentObserver : public ViewObserver { | |
| 613 public: | |
| 614 explicit ViewRemovedFromParentObserver(View* view) | |
| 615 : view_(view), was_removed_(false) { | |
| 616 view_->AddObserver(this); | |
| 617 } | |
| 618 ~ViewRemovedFromParentObserver() override { view_->RemoveObserver(this); } | |
| 619 | |
| 620 bool was_removed() const { return was_removed_; } | |
| 621 | |
| 622 private: | |
| 623 // Overridden from ViewObserver: | |
| 624 void OnTreeChanged(const TreeChangeParams& params) override { | |
| 625 if (params.target == view_ && !params.new_parent) | |
| 626 was_removed_ = true; | |
| 627 } | |
| 628 | |
| 629 View* view_; | |
| 630 bool was_removed_; | |
| 631 | |
| 632 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewRemovedFromParentObserver); | |
| 633 }; | |
| 634 | |
| 635 TEST_F(ViewManagerTest, EmbedRemovesChildren) { | |
| 636 View* view1 = window_manager()->CreateView(); | |
| 637 View* view2 = window_manager()->CreateView(); | |
| 638 window_manager()->GetRoot()->AddChild(view1); | |
| 639 view1->AddChild(view2); | |
| 640 | |
| 641 ViewRemovedFromParentObserver observer(view2); | |
| 642 view1->Embed(application_impl()->url()); | |
| 643 EXPECT_TRUE(observer.was_removed()); | |
| 644 EXPECT_EQ(nullptr, view2->parent()); | |
| 645 EXPECT_TRUE(view1->children().empty()); | |
| 646 | |
| 647 // Run the message loop so the Embed() call above completes. Without this | |
| 648 // we may end up reconnecting to the test and rerunning the test, which is | |
| 649 // problematic since the other services don't shut down. | |
| 650 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
| 651 } | |
| 652 | |
| 653 } // namespace mojo | |
| OLD | NEW |