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 "third_party/mojo_services/src/view_manager/public/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 "third_party/mojo_services/src/geometry/public/cpp/geometry_util.h" |
| 20 #include "third_party/mojo_services/src/view_manager/public/cpp/lib/view_manager
_client_impl.h" |
| 21 #include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_cli
ent_factory.h" |
| 22 #include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_con
text.h" |
| 23 #include "third_party/mojo_services/src/view_manager/public/cpp/view_manager_del
egate.h" |
| 24 #include "third_party/mojo_services/src/view_manager/public/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 int id_; |
| 169 View* view_; |
| 170 |
| 171 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker); |
| 172 }; |
| 173 |
| 174 } // namespace |
| 175 |
| 176 // ViewManager ----------------------------------------------------------------- |
| 177 |
| 178 // These tests model synchronization of two peer connections to the view manager |
| 179 // service, that are given access to some root view. |
| 180 |
| 181 class ViewManagerTest : public test::ApplicationTestBase, |
| 182 public ApplicationDelegate, |
| 183 public ViewManagerDelegate { |
| 184 public: |
| 185 ViewManagerTest() |
| 186 : most_recent_view_manager_(nullptr), window_manager_(nullptr) {} |
| 187 |
| 188 // Overridden from ApplicationDelegate: |
| 189 void Initialize(ApplicationImpl* app) override { |
| 190 view_manager_client_factory_.reset( |
| 191 new ViewManagerClientFactory(app->shell(), this)); |
| 192 } |
| 193 |
| 194 // ApplicationDelegate implementation. |
| 195 bool ConfigureIncomingConnection(ApplicationConnection* connection) override { |
| 196 connection->AddService(view_manager_client_factory_.get()); |
| 197 return true; |
| 198 } |
| 199 |
| 200 ViewManager* window_manager() { return window_manager_; } |
| 201 |
| 202 // Embeds another version of the test app @ view; returns nullptr on timeout. |
| 203 ViewManager* Embed(ViewManager* view_manager, View* view) { |
| 204 DCHECK_EQ(view_manager, view->view_manager()); |
| 205 most_recent_view_manager_ = nullptr; |
| 206 view->Embed(application_impl()->url()); |
| 207 if (!DoRunLoopWithTimeout()) |
| 208 return nullptr; |
| 209 ViewManager* vm = nullptr; |
| 210 std::swap(vm, most_recent_view_manager_); |
| 211 return vm; |
| 212 } |
| 213 |
| 214 ApplicationDelegate* GetApplicationDelegate() override { return this; } |
| 215 |
| 216 // Overridden from ViewManagerDelegate: |
| 217 void OnEmbed(View* root, |
| 218 InterfaceRequest<ServiceProvider> services, |
| 219 ServiceProviderPtr exposed_services) override { |
| 220 most_recent_view_manager_ = root->view_manager(); |
| 221 QuitRunLoop(); |
| 222 } |
| 223 void OnViewManagerDisconnected(ViewManager* view_manager) override {} |
| 224 |
| 225 private: |
| 226 // Overridden from testing::Test: |
| 227 void SetUp() override { |
| 228 ApplicationTestBase::SetUp(); |
| 229 |
| 230 view_manager_context_.reset(new ViewManagerContext(application_impl())); |
| 231 view_manager_context_->Embed(application_impl()->url()); |
| 232 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 233 std::swap(window_manager_, most_recent_view_manager_); |
| 234 } |
| 235 |
| 236 // Overridden from testing::Test: |
| 237 void TearDown() override { ApplicationTestBase::TearDown(); } |
| 238 |
| 239 scoped_ptr<ViewManagerClientFactory> view_manager_client_factory_; |
| 240 |
| 241 scoped_ptr<ViewManagerContext> view_manager_context_; |
| 242 |
| 243 // Used to receive the most recent view manager loaded by an embed action. |
| 244 ViewManager* most_recent_view_manager_; |
| 245 // The View Manager connection held by the window manager (app running at the |
| 246 // root view). |
| 247 ViewManager* window_manager_; |
| 248 |
| 249 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); |
| 250 }; |
| 251 |
| 252 TEST_F(ViewManagerTest, RootView) { |
| 253 ASSERT_NE(nullptr, window_manager()); |
| 254 EXPECT_NE(nullptr, window_manager()->GetRoot()); |
| 255 EXPECT_EQ("mojo:window_manager", window_manager()->GetEmbedderURL()); |
| 256 } |
| 257 |
| 258 TEST_F(ViewManagerTest, Embed) { |
| 259 View* view = window_manager()->CreateView(); |
| 260 ASSERT_NE(nullptr, view); |
| 261 view->SetVisible(true); |
| 262 window_manager()->GetRoot()->AddChild(view); |
| 263 ViewManager* embedded = Embed(window_manager(), view); |
| 264 ASSERT_NE(nullptr, embedded); |
| 265 |
| 266 View* view_in_embedded = embedded->GetRoot(); |
| 267 ASSERT_NE(nullptr, view_in_embedded); |
| 268 EXPECT_EQ(view->id(), view_in_embedded->id()); |
| 269 EXPECT_EQ(nullptr, view_in_embedded->parent()); |
| 270 EXPECT_TRUE(view_in_embedded->children().empty()); |
| 271 } |
| 272 |
| 273 // Window manager has two views, N1 and N11. Embeds A at N1. A should not see |
| 274 // N11. |
| 275 TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) { |
| 276 View* view = window_manager()->CreateView(); |
| 277 ASSERT_NE(nullptr, view); |
| 278 view->SetVisible(true); |
| 279 window_manager()->GetRoot()->AddChild(view); |
| 280 View* nested = window_manager()->CreateView(); |
| 281 ASSERT_NE(nullptr, nested); |
| 282 nested->SetVisible(true); |
| 283 view->AddChild(nested); |
| 284 |
| 285 ViewManager* embedded = Embed(window_manager(), view); |
| 286 ASSERT_NE(nullptr, embedded); |
| 287 View* view_in_embedded = embedded->GetRoot(); |
| 288 EXPECT_EQ(view->id(), view_in_embedded->id()); |
| 289 EXPECT_EQ(nullptr, view_in_embedded->parent()); |
| 290 EXPECT_TRUE(view_in_embedded->children().empty()); |
| 291 } |
| 292 |
| 293 // TODO(beng): write a replacement test for the one that once existed here: |
| 294 // This test validates the following scenario: |
| 295 // - a view originating from one connection |
| 296 // - a view originating from a second connection |
| 297 // + the connection originating the view is destroyed |
| 298 // -> the view should still exist (since the second connection is live) but |
| 299 // should be disconnected from any views. |
| 300 // http://crbug.com/396300 |
| 301 // |
| 302 // TODO(beng): The new test should validate the scenario as described above |
| 303 // except that the second connection still has a valid tree. |
| 304 |
| 305 // Verifies that bounds changes applied to a view hierarchy in one connection |
| 306 // are reflected to another. |
| 307 TEST_F(ViewManagerTest, SetBounds) { |
| 308 View* view = window_manager()->CreateView(); |
| 309 view->SetVisible(true); |
| 310 window_manager()->GetRoot()->AddChild(view); |
| 311 ViewManager* embedded = Embed(window_manager(), view); |
| 312 ASSERT_NE(nullptr, embedded); |
| 313 |
| 314 View* view_in_embedded = embedded->GetViewById(view->id()); |
| 315 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); |
| 316 |
| 317 Rect rect; |
| 318 rect.width = rect.height = 100; |
| 319 view->SetBounds(rect); |
| 320 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); |
| 321 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); |
| 322 } |
| 323 |
| 324 // Verifies that bounds changes applied to a view owned by a different |
| 325 // connection are refused. |
| 326 TEST_F(ViewManagerTest, SetBoundsSecurity) { |
| 327 View* view = window_manager()->CreateView(); |
| 328 view->SetVisible(true); |
| 329 window_manager()->GetRoot()->AddChild(view); |
| 330 ViewManager* embedded = Embed(window_manager(), view); |
| 331 ASSERT_NE(nullptr, embedded); |
| 332 |
| 333 View* view_in_embedded = embedded->GetViewById(view->id()); |
| 334 Rect rect; |
| 335 rect.width = 800; |
| 336 rect.height = 600; |
| 337 view->SetBounds(rect); |
| 338 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); |
| 339 |
| 340 rect.width = 1024; |
| 341 rect.height = 768; |
| 342 view_in_embedded->SetBounds(rect); |
| 343 // Bounds change should have been rejected. |
| 344 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); |
| 345 } |
| 346 |
| 347 // Verifies that a view can only be destroyed by the connection that created it. |
| 348 TEST_F(ViewManagerTest, DestroySecurity) { |
| 349 View* view = window_manager()->CreateView(); |
| 350 view->SetVisible(true); |
| 351 window_manager()->GetRoot()->AddChild(view); |
| 352 ViewManager* embedded = Embed(window_manager(), view); |
| 353 ASSERT_NE(nullptr, embedded); |
| 354 |
| 355 View* view_in_embedded = embedded->GetViewById(view->id()); |
| 356 |
| 357 ViewTracker tracker2(view_in_embedded); |
| 358 view_in_embedded->Destroy(); |
| 359 // View should not have been destroyed. |
| 360 EXPECT_TRUE(tracker2.is_valid()); |
| 361 |
| 362 ViewTracker tracker1(view); |
| 363 view->Destroy(); |
| 364 EXPECT_FALSE(tracker1.is_valid()); |
| 365 } |
| 366 |
| 367 TEST_F(ViewManagerTest, MultiRoots) { |
| 368 View* view1 = window_manager()->CreateView(); |
| 369 view1->SetVisible(true); |
| 370 window_manager()->GetRoot()->AddChild(view1); |
| 371 View* view2 = window_manager()->CreateView(); |
| 372 view2->SetVisible(true); |
| 373 window_manager()->GetRoot()->AddChild(view2); |
| 374 ViewManager* embedded1 = Embed(window_manager(), view1); |
| 375 ASSERT_NE(nullptr, embedded1); |
| 376 ViewManager* embedded2 = Embed(window_manager(), view2); |
| 377 ASSERT_NE(nullptr, embedded2); |
| 378 EXPECT_NE(embedded1, embedded2); |
| 379 } |
| 380 |
| 381 TEST_F(ViewManagerTest, EmbeddingIdentity) { |
| 382 View* view = window_manager()->CreateView(); |
| 383 view->SetVisible(true); |
| 384 window_manager()->GetRoot()->AddChild(view); |
| 385 ViewManager* embedded = Embed(window_manager(), view); |
| 386 ASSERT_NE(nullptr, embedded); |
| 387 EXPECT_EQ(application_impl()->url(), embedded->GetEmbedderURL()); |
| 388 } |
| 389 |
| 390 // TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change. |
| 391 // Debug and re-enable this. |
| 392 TEST_F(ViewManagerTest, DISABLED_Reorder) { |
| 393 View* view1 = window_manager()->CreateView(); |
| 394 view1->SetVisible(true); |
| 395 window_manager()->GetRoot()->AddChild(view1); |
| 396 |
| 397 ViewManager* embedded = Embed(window_manager(), view1); |
| 398 ASSERT_NE(nullptr, embedded); |
| 399 |
| 400 View* view11 = embedded->CreateView(); |
| 401 view11->SetVisible(true); |
| 402 embedded->GetRoot()->AddChild(view11); |
| 403 View* view12 = embedded->CreateView(); |
| 404 view12->SetVisible(true); |
| 405 embedded->GetRoot()->AddChild(view12); |
| 406 |
| 407 View* root_in_embedded = embedded->GetRoot(); |
| 408 |
| 409 { |
| 410 ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u)); |
| 411 view11->MoveToFront(); |
| 412 ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded)); |
| 413 |
| 414 EXPECT_EQ(root_in_embedded->children().front(), |
| 415 embedded->GetViewById(view12->id())); |
| 416 EXPECT_EQ(root_in_embedded->children().back(), |
| 417 embedded->GetViewById(view11->id())); |
| 418 } |
| 419 |
| 420 { |
| 421 view11->MoveToBack(); |
| 422 ASSERT_TRUE(WaitForOrderChange(embedded, |
| 423 embedded->GetViewById(view11->id()))); |
| 424 |
| 425 EXPECT_EQ(root_in_embedded->children().front(), |
| 426 embedded->GetViewById(view11->id())); |
| 427 EXPECT_EQ(root_in_embedded->children().back(), |
| 428 embedded->GetViewById(view12->id())); |
| 429 } |
| 430 } |
| 431 |
| 432 namespace { |
| 433 |
| 434 class VisibilityChangeObserver : public ViewObserver { |
| 435 public: |
| 436 explicit VisibilityChangeObserver(View* view) : view_(view) { |
| 437 view_->AddObserver(this); |
| 438 } |
| 439 ~VisibilityChangeObserver() override { view_->RemoveObserver(this); } |
| 440 |
| 441 private: |
| 442 // Overridden from ViewObserver: |
| 443 void OnViewVisibilityChanged(View* view) override { |
| 444 EXPECT_EQ(view, view_); |
| 445 QuitRunLoop(); |
| 446 } |
| 447 |
| 448 View* view_; |
| 449 |
| 450 MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); |
| 451 }; |
| 452 |
| 453 } // namespace |
| 454 |
| 455 TEST_F(ViewManagerTest, Visible) { |
| 456 View* view1 = window_manager()->CreateView(); |
| 457 view1->SetVisible(true); |
| 458 window_manager()->GetRoot()->AddChild(view1); |
| 459 |
| 460 // Embed another app and verify initial state. |
| 461 ViewManager* embedded = Embed(window_manager(), view1); |
| 462 ASSERT_NE(nullptr, embedded); |
| 463 ASSERT_NE(nullptr, embedded->GetRoot()); |
| 464 View* embedded_root = embedded->GetRoot(); |
| 465 EXPECT_TRUE(embedded_root->visible()); |
| 466 EXPECT_TRUE(embedded_root->IsDrawn()); |
| 467 |
| 468 // Change the visible state from the first connection and verify its mirrored |
| 469 // correctly to the embedded app. |
| 470 { |
| 471 VisibilityChangeObserver observer(embedded_root); |
| 472 view1->SetVisible(false); |
| 473 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 474 } |
| 475 |
| 476 EXPECT_FALSE(view1->visible()); |
| 477 EXPECT_FALSE(view1->IsDrawn()); |
| 478 |
| 479 EXPECT_FALSE(embedded_root->visible()); |
| 480 EXPECT_FALSE(embedded_root->IsDrawn()); |
| 481 |
| 482 // Make the node visible again. |
| 483 { |
| 484 VisibilityChangeObserver observer(embedded_root); |
| 485 view1->SetVisible(true); |
| 486 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 487 } |
| 488 |
| 489 EXPECT_TRUE(view1->visible()); |
| 490 EXPECT_TRUE(view1->IsDrawn()); |
| 491 |
| 492 EXPECT_TRUE(embedded_root->visible()); |
| 493 EXPECT_TRUE(embedded_root->IsDrawn()); |
| 494 } |
| 495 |
| 496 namespace { |
| 497 |
| 498 class DrawnChangeObserver : public ViewObserver { |
| 499 public: |
| 500 explicit DrawnChangeObserver(View* view) : view_(view) { |
| 501 view_->AddObserver(this); |
| 502 } |
| 503 ~DrawnChangeObserver() override { view_->RemoveObserver(this); } |
| 504 |
| 505 private: |
| 506 // Overridden from ViewObserver: |
| 507 void OnViewDrawnChanged(View* view) override { |
| 508 EXPECT_EQ(view, view_); |
| 509 QuitRunLoop(); |
| 510 } |
| 511 |
| 512 View* view_; |
| 513 |
| 514 MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); |
| 515 }; |
| 516 |
| 517 } // namespace |
| 518 |
| 519 TEST_F(ViewManagerTest, Drawn) { |
| 520 View* view1 = window_manager()->CreateView(); |
| 521 view1->SetVisible(true); |
| 522 window_manager()->GetRoot()->AddChild(view1); |
| 523 |
| 524 // Embed another app and verify initial state. |
| 525 ViewManager* embedded = Embed(window_manager(), view1); |
| 526 ASSERT_NE(nullptr, embedded); |
| 527 ASSERT_NE(nullptr, embedded->GetRoot()); |
| 528 View* embedded_root = embedded->GetRoot(); |
| 529 EXPECT_TRUE(embedded_root->visible()); |
| 530 EXPECT_TRUE(embedded_root->IsDrawn()); |
| 531 |
| 532 // Change the visibility of the root, this should propagate a drawn state |
| 533 // change to |embedded|. |
| 534 { |
| 535 DrawnChangeObserver observer(embedded_root); |
| 536 window_manager()->GetRoot()->SetVisible(false); |
| 537 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 538 } |
| 539 |
| 540 EXPECT_TRUE(view1->visible()); |
| 541 EXPECT_FALSE(view1->IsDrawn()); |
| 542 |
| 543 EXPECT_TRUE(embedded_root->visible()); |
| 544 EXPECT_FALSE(embedded_root->IsDrawn()); |
| 545 } |
| 546 |
| 547 // TODO(beng): tests for view event dispatcher. |
| 548 // - verify that we see events for all views. |
| 549 |
| 550 namespace { |
| 551 |
| 552 class FocusChangeObserver : public ViewObserver { |
| 553 public: |
| 554 explicit FocusChangeObserver(View* view) |
| 555 : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) { |
| 556 view_->AddObserver(this); |
| 557 } |
| 558 ~FocusChangeObserver() override { view_->RemoveObserver(this); } |
| 559 |
| 560 View* last_gained_focus() { return last_gained_focus_; } |
| 561 |
| 562 View* last_lost_focus() { return last_lost_focus_; } |
| 563 |
| 564 private: |
| 565 // Overridden from ViewObserver. |
| 566 void OnViewFocusChanged(View* gained_focus, View* lost_focus) override { |
| 567 last_gained_focus_ = gained_focus; |
| 568 last_lost_focus_ = lost_focus; |
| 569 QuitRunLoop(); |
| 570 } |
| 571 |
| 572 View* view_; |
| 573 View* last_gained_focus_; |
| 574 View* last_lost_focus_; |
| 575 |
| 576 MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); |
| 577 }; |
| 578 |
| 579 } // namespace |
| 580 |
| 581 TEST_F(ViewManagerTest, Focus) { |
| 582 View* view1 = window_manager()->CreateView(); |
| 583 view1->SetVisible(true); |
| 584 window_manager()->GetRoot()->AddChild(view1); |
| 585 |
| 586 ViewManager* embedded = Embed(window_manager(), view1); |
| 587 ASSERT_NE(nullptr, embedded); |
| 588 View* view11 = embedded->CreateView(); |
| 589 view11->SetVisible(true); |
| 590 embedded->GetRoot()->AddChild(view11); |
| 591 |
| 592 // TODO(alhaad): Figure out why switching focus between views from different |
| 593 // connections is causing the tests to crash and add tests for that. |
| 594 { |
| 595 View* embedded_root = embedded->GetRoot(); |
| 596 FocusChangeObserver observer(embedded_root); |
| 597 embedded_root->SetFocus(); |
| 598 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 599 ASSERT_NE(nullptr, observer.last_gained_focus()); |
| 600 EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id()); |
| 601 } |
| 602 { |
| 603 FocusChangeObserver observer(view11); |
| 604 view11->SetFocus(); |
| 605 ASSERT_TRUE(DoRunLoopWithTimeout()); |
| 606 ASSERT_NE(nullptr, observer.last_gained_focus()); |
| 607 ASSERT_NE(nullptr, observer.last_lost_focus()); |
| 608 EXPECT_EQ(view11->id(), observer.last_gained_focus()->id()); |
| 609 EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id()); |
| 610 } |
| 611 } |
| 612 |
| 613 } // namespace mojo |
OLD | NEW |