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 |