| 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 |