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 "base/bind.h" | |
6 #include "base/logging.h" | |
7 #include "base/run_loop.h" | |
8 #include "components/mus/public/cpp/tests/view_manager_test_base.h" | |
9 #include "components/mus/public/cpp/util.h" | |
10 #include "components/mus/public/cpp/view_observer.h" | |
11 #include "components/mus/public/cpp/view_tree_connection.h" | |
12 #include "components/mus/public/cpp/view_tree_delegate.h" | |
13 #include "mojo/application/public/cpp/application_connection.h" | |
14 #include "mojo/application/public/cpp/application_impl.h" | |
15 #include "mojo/application/public/cpp/application_test_base.h" | |
16 #include "ui/mojo/geometry/geometry_util.h" | |
17 | |
18 namespace mus { | |
19 | |
20 namespace { | |
21 | |
22 class BoundsChangeObserver : public ViewObserver { | |
23 public: | |
24 explicit BoundsChangeObserver(View* view) : view_(view) { | |
25 view_->AddObserver(this); | |
26 } | |
27 ~BoundsChangeObserver() override { view_->RemoveObserver(this); } | |
28 | |
29 private: | |
30 // Overridden from ViewObserver: | |
31 void OnViewBoundsChanged(View* view, | |
32 const mojo::Rect& old_bounds, | |
33 const mojo::Rect& new_bounds) override { | |
34 DCHECK_EQ(view, view_); | |
35 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
36 } | |
37 | |
38 View* view_; | |
39 | |
40 MOJO_DISALLOW_COPY_AND_ASSIGN(BoundsChangeObserver); | |
41 }; | |
42 | |
43 // Wait until the bounds of the supplied view change; returns false on timeout. | |
44 bool WaitForBoundsToChange(View* view) { | |
45 BoundsChangeObserver observer(view); | |
46 return ViewManagerTestBase::DoRunLoopWithTimeout(); | |
47 } | |
48 | |
49 // Spins a run loop until the tree beginning at |root| has |tree_size| views | |
50 // (including |root|). | |
51 class TreeSizeMatchesObserver : public ViewObserver { | |
52 public: | |
53 TreeSizeMatchesObserver(View* tree, size_t tree_size) | |
54 : tree_(tree), tree_size_(tree_size) { | |
55 tree_->AddObserver(this); | |
56 } | |
57 ~TreeSizeMatchesObserver() override { tree_->RemoveObserver(this); } | |
58 | |
59 bool IsTreeCorrectSize() { return CountViews(tree_) == tree_size_; } | |
60 | |
61 private: | |
62 // Overridden from ViewObserver: | |
63 void OnTreeChanged(const TreeChangeParams& params) override { | |
64 if (IsTreeCorrectSize()) | |
65 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
66 } | |
67 | |
68 size_t CountViews(const View* view) const { | |
69 size_t count = 1; | |
70 View::Children::const_iterator it = view->children().begin(); | |
71 for (; it != view->children().end(); ++it) | |
72 count += CountViews(*it); | |
73 return count; | |
74 } | |
75 | |
76 View* tree_; | |
77 size_t tree_size_; | |
78 | |
79 MOJO_DISALLOW_COPY_AND_ASSIGN(TreeSizeMatchesObserver); | |
80 }; | |
81 | |
82 // Wait until |view| has |tree_size| descendants; returns false on timeout. The | |
83 // count includes |view|. For example, if you want to wait for |view| to have | |
84 // a single child, use a |tree_size| of 2. | |
85 bool WaitForTreeSizeToMatch(View* view, size_t tree_size) { | |
86 TreeSizeMatchesObserver observer(view, tree_size); | |
87 return observer.IsTreeCorrectSize() || | |
88 ViewManagerTestBase::DoRunLoopWithTimeout(); | |
89 } | |
90 | |
91 class OrderChangeObserver : public ViewObserver { | |
92 public: | |
93 OrderChangeObserver(View* view) : view_(view) { view_->AddObserver(this); } | |
94 ~OrderChangeObserver() override { view_->RemoveObserver(this); } | |
95 | |
96 private: | |
97 // Overridden from ViewObserver: | |
98 void OnViewReordered(View* view, | |
99 View* relative_view, | |
100 mojo::OrderDirection direction) override { | |
101 DCHECK_EQ(view, view_); | |
102 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
103 } | |
104 | |
105 View* view_; | |
106 | |
107 MOJO_DISALLOW_COPY_AND_ASSIGN(OrderChangeObserver); | |
108 }; | |
109 | |
110 // Wait until |view|'s tree size matches |tree_size|; returns false on timeout. | |
111 bool WaitForOrderChange(ViewTreeConnection* connection, View* view) { | |
112 OrderChangeObserver observer(view); | |
113 return ViewManagerTestBase::DoRunLoopWithTimeout(); | |
114 } | |
115 | |
116 // Tracks a view's destruction. Query is_valid() for current state. | |
117 class ViewTracker : public ViewObserver { | |
118 public: | |
119 explicit ViewTracker(View* view) : view_(view) { view_->AddObserver(this); } | |
120 ~ViewTracker() override { | |
121 if (view_) | |
122 view_->RemoveObserver(this); | |
123 } | |
124 | |
125 bool is_valid() const { return !!view_; } | |
126 | |
127 private: | |
128 // Overridden from ViewObserver: | |
129 void OnViewDestroyed(View* view) override { | |
130 DCHECK_EQ(view, view_); | |
131 view_ = nullptr; | |
132 } | |
133 | |
134 View* view_; | |
135 | |
136 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewTracker); | |
137 }; | |
138 | |
139 } // namespace | |
140 | |
141 // ViewManager ----------------------------------------------------------------- | |
142 | |
143 struct EmbedResult { | |
144 EmbedResult(ViewTreeConnection* connection, ConnectionSpecificId id) | |
145 : connection(connection), connection_id(id) {} | |
146 EmbedResult() : connection(nullptr), connection_id(0) {} | |
147 | |
148 ViewTreeConnection* connection; | |
149 | |
150 // The id supplied to the callback from OnEmbed(). Depending upon the | |
151 // access policy this may or may not match the connection id of | |
152 // |connection|. | |
153 ConnectionSpecificId connection_id; | |
154 }; | |
155 | |
156 // These tests model synchronization of two peer connections to the view manager | |
157 // service, that are given access to some root view. | |
158 | |
159 class ViewManagerTest : public ViewManagerTestBase { | |
160 public: | |
161 ViewManagerTest() {} | |
162 | |
163 // Embeds another version of the test app @ view. This runs a run loop until | |
164 // a response is received, or a timeout. On success the new ViewManager is | |
165 // returned. | |
166 EmbedResult Embed(View* view) { | |
167 return Embed(view, mojo::ViewTree::ACCESS_POLICY_DEFAULT); | |
168 } | |
169 | |
170 EmbedResult Embed(View* view, uint32_t access_policy_bitmask) { | |
171 DCHECK(!embed_details_); | |
172 embed_details_.reset(new EmbedDetails); | |
173 view->Embed(ConnectToApplicationAndGetViewManagerClient(), | |
174 access_policy_bitmask, | |
175 base::Bind(&ViewManagerTest::EmbedCallbackImpl, | |
176 base::Unretained(this))); | |
177 embed_details_->waiting = true; | |
178 if (!ViewManagerTestBase::DoRunLoopWithTimeout()) | |
179 return EmbedResult(); | |
180 const EmbedResult result(embed_details_->connection, | |
181 embed_details_->connection_id); | |
182 embed_details_.reset(); | |
183 return result; | |
184 } | |
185 | |
186 // Establishes a connection to this application and asks for a | |
187 // ViewTreeClient. | |
188 mojo::ViewTreeClientPtr ConnectToApplicationAndGetViewManagerClient() { | |
189 mojo::URLRequestPtr request(mojo::URLRequest::New()); | |
190 request->url = mojo::String::From(application_impl()->url()); | |
191 scoped_ptr<mojo::ApplicationConnection> connection = | |
192 application_impl()->ConnectToApplication(request.Pass()); | |
193 mojo::ViewTreeClientPtr client; | |
194 connection->ConnectToService(&client); | |
195 return client.Pass(); | |
196 } | |
197 | |
198 // ViewManagerTestBase: | |
199 void OnEmbed(View* root) override { | |
200 if (!embed_details_) { | |
201 ViewManagerTestBase::OnEmbed(root); | |
202 return; | |
203 } | |
204 | |
205 embed_details_->connection = root->connection(); | |
206 if (embed_details_->callback_run) | |
207 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
208 } | |
209 | |
210 private: | |
211 // Used to track the state of a call to view->Embed(). | |
212 struct EmbedDetails { | |
213 EmbedDetails() | |
214 : callback_run(false), | |
215 result(false), | |
216 waiting(false), | |
217 connection_id(0), | |
218 connection(nullptr) {} | |
219 | |
220 // The callback supplied to Embed() was received. | |
221 bool callback_run; | |
222 | |
223 // The boolean supplied to the Embed() callback. | |
224 bool result; | |
225 | |
226 // Whether a MessageLoop is running. | |
227 bool waiting; | |
228 | |
229 // Connection id supplied to the Embed() callback. | |
230 ConnectionSpecificId connection_id; | |
231 | |
232 // The ViewTreeConnection that resulted from the Embed(). null if |result| | |
233 // is false. | |
234 ViewTreeConnection* connection; | |
235 }; | |
236 | |
237 void EmbedCallbackImpl(bool result, ConnectionSpecificId connection_id) { | |
238 embed_details_->callback_run = true; | |
239 embed_details_->result = result; | |
240 embed_details_->connection_id = connection_id; | |
241 if (embed_details_->waiting && (!result || embed_details_->connection)) | |
242 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
243 } | |
244 | |
245 scoped_ptr<EmbedDetails> embed_details_; | |
246 | |
247 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewManagerTest); | |
248 }; | |
249 | |
250 TEST_F(ViewManagerTest, RootView) { | |
251 ASSERT_NE(nullptr, window_manager()); | |
252 EXPECT_NE(nullptr, window_manager()->GetRoot()); | |
253 } | |
254 | |
255 TEST_F(ViewManagerTest, Embed) { | |
256 View* view = window_manager()->CreateView(); | |
257 ASSERT_NE(nullptr, view); | |
258 view->SetVisible(true); | |
259 window_manager()->GetRoot()->AddChild(view); | |
260 ViewTreeConnection* embedded = Embed(view).connection; | |
261 ASSERT_NE(nullptr, embedded); | |
262 | |
263 View* view_in_embedded = embedded->GetRoot(); | |
264 ASSERT_NE(nullptr, view_in_embedded); | |
265 EXPECT_EQ(view->id(), view_in_embedded->id()); | |
266 EXPECT_EQ(nullptr, view_in_embedded->parent()); | |
267 EXPECT_TRUE(view_in_embedded->children().empty()); | |
268 } | |
269 | |
270 // Window manager has two views, N1 and N11. Embeds A at N1. A should not see | |
271 // N11. | |
272 TEST_F(ViewManagerTest, EmbeddedDoesntSeeChild) { | |
273 View* view = window_manager()->CreateView(); | |
274 ASSERT_NE(nullptr, view); | |
275 view->SetVisible(true); | |
276 window_manager()->GetRoot()->AddChild(view); | |
277 View* nested = window_manager()->CreateView(); | |
278 ASSERT_NE(nullptr, nested); | |
279 nested->SetVisible(true); | |
280 view->AddChild(nested); | |
281 | |
282 ViewTreeConnection* embedded = Embed(view).connection; | |
283 ASSERT_NE(nullptr, embedded); | |
284 View* view_in_embedded = embedded->GetRoot(); | |
285 EXPECT_EQ(view->id(), view_in_embedded->id()); | |
286 EXPECT_EQ(nullptr, view_in_embedded->parent()); | |
287 EXPECT_TRUE(view_in_embedded->children().empty()); | |
288 } | |
289 | |
290 // TODO(beng): write a replacement test for the one that once existed here: | |
291 // This test validates the following scenario: | |
292 // - a view originating from one connection | |
293 // - a view originating from a second connection | |
294 // + the connection originating the view is destroyed | |
295 // -> the view should still exist (since the second connection is live) but | |
296 // should be disconnected from any views. | |
297 // http://crbug.com/396300 | |
298 // | |
299 // TODO(beng): The new test should validate the scenario as described above | |
300 // except that the second connection still has a valid tree. | |
301 | |
302 // Verifies that bounds changes applied to a view hierarchy in one connection | |
303 // are reflected to another. | |
304 TEST_F(ViewManagerTest, SetBounds) { | |
305 View* view = window_manager()->CreateView(); | |
306 view->SetVisible(true); | |
307 window_manager()->GetRoot()->AddChild(view); | |
308 ViewTreeConnection* embedded = Embed(view).connection; | |
309 ASSERT_NE(nullptr, embedded); | |
310 | |
311 View* view_in_embedded = embedded->GetViewById(view->id()); | |
312 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
313 | |
314 mojo::Rect rect; | |
315 rect.width = rect.height = 100; | |
316 view->SetBounds(rect); | |
317 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); | |
318 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
319 } | |
320 | |
321 // Verifies that bounds changes applied to a view owned by a different | |
322 // connection are refused. | |
323 TEST_F(ViewManagerTest, SetBoundsSecurity) { | |
324 View* view = window_manager()->CreateView(); | |
325 view->SetVisible(true); | |
326 window_manager()->GetRoot()->AddChild(view); | |
327 ViewTreeConnection* embedded = Embed(view).connection; | |
328 ASSERT_NE(nullptr, embedded); | |
329 | |
330 View* view_in_embedded = embedded->GetViewById(view->id()); | |
331 mojo::Rect rect; | |
332 rect.width = 800; | |
333 rect.height = 600; | |
334 view->SetBounds(rect); | |
335 ASSERT_TRUE(WaitForBoundsToChange(view_in_embedded)); | |
336 | |
337 rect.width = 1024; | |
338 rect.height = 768; | |
339 view_in_embedded->SetBounds(rect); | |
340 // Bounds change should have been rejected. | |
341 EXPECT_EQ(view->bounds(), view_in_embedded->bounds()); | |
342 } | |
343 | |
344 // Verifies that a view can only be destroyed by the connection that created it. | |
345 TEST_F(ViewManagerTest, DestroySecurity) { | |
346 View* view = window_manager()->CreateView(); | |
347 view->SetVisible(true); | |
348 window_manager()->GetRoot()->AddChild(view); | |
349 ViewTreeConnection* embedded = Embed(view).connection; | |
350 ASSERT_NE(nullptr, embedded); | |
351 | |
352 View* view_in_embedded = embedded->GetViewById(view->id()); | |
353 | |
354 ViewTracker tracker2(view_in_embedded); | |
355 view_in_embedded->Destroy(); | |
356 // View should not have been destroyed. | |
357 EXPECT_TRUE(tracker2.is_valid()); | |
358 | |
359 ViewTracker tracker1(view); | |
360 view->Destroy(); | |
361 EXPECT_FALSE(tracker1.is_valid()); | |
362 } | |
363 | |
364 TEST_F(ViewManagerTest, MultiRoots) { | |
365 View* view1 = window_manager()->CreateView(); | |
366 view1->SetVisible(true); | |
367 window_manager()->GetRoot()->AddChild(view1); | |
368 View* view2 = window_manager()->CreateView(); | |
369 view2->SetVisible(true); | |
370 window_manager()->GetRoot()->AddChild(view2); | |
371 ViewTreeConnection* embedded1 = Embed(view1).connection; | |
372 ASSERT_NE(nullptr, embedded1); | |
373 ViewTreeConnection* embedded2 = Embed(view2).connection; | |
374 ASSERT_NE(nullptr, embedded2); | |
375 EXPECT_NE(embedded1, embedded2); | |
376 } | |
377 | |
378 // TODO(alhaad): Currently, the RunLoop gets stuck waiting for order change. | |
379 // Debug and re-enable this. | |
380 TEST_F(ViewManagerTest, DISABLED_Reorder) { | |
381 View* view1 = window_manager()->CreateView(); | |
382 view1->SetVisible(true); | |
383 window_manager()->GetRoot()->AddChild(view1); | |
384 | |
385 ViewTreeConnection* embedded = Embed(view1).connection; | |
386 ASSERT_NE(nullptr, embedded); | |
387 | |
388 View* view11 = embedded->CreateView(); | |
389 view11->SetVisible(true); | |
390 embedded->GetRoot()->AddChild(view11); | |
391 View* view12 = embedded->CreateView(); | |
392 view12->SetVisible(true); | |
393 embedded->GetRoot()->AddChild(view12); | |
394 | |
395 View* root_in_embedded = embedded->GetRoot(); | |
396 | |
397 { | |
398 ASSERT_TRUE(WaitForTreeSizeToMatch(root_in_embedded, 3u)); | |
399 view11->MoveToFront(); | |
400 ASSERT_TRUE(WaitForOrderChange(embedded, root_in_embedded)); | |
401 | |
402 EXPECT_EQ(root_in_embedded->children().front(), | |
403 embedded->GetViewById(view12->id())); | |
404 EXPECT_EQ(root_in_embedded->children().back(), | |
405 embedded->GetViewById(view11->id())); | |
406 } | |
407 | |
408 { | |
409 view11->MoveToBack(); | |
410 ASSERT_TRUE( | |
411 WaitForOrderChange(embedded, embedded->GetViewById(view11->id()))); | |
412 | |
413 EXPECT_EQ(root_in_embedded->children().front(), | |
414 embedded->GetViewById(view11->id())); | |
415 EXPECT_EQ(root_in_embedded->children().back(), | |
416 embedded->GetViewById(view12->id())); | |
417 } | |
418 } | |
419 | |
420 namespace { | |
421 | |
422 class VisibilityChangeObserver : public ViewObserver { | |
423 public: | |
424 explicit VisibilityChangeObserver(View* view) : view_(view) { | |
425 view_->AddObserver(this); | |
426 } | |
427 ~VisibilityChangeObserver() override { view_->RemoveObserver(this); } | |
428 | |
429 private: | |
430 // Overridden from ViewObserver: | |
431 void OnViewVisibilityChanged(View* view) override { | |
432 EXPECT_EQ(view, view_); | |
433 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
434 } | |
435 | |
436 View* view_; | |
437 | |
438 MOJO_DISALLOW_COPY_AND_ASSIGN(VisibilityChangeObserver); | |
439 }; | |
440 | |
441 } // namespace | |
442 | |
443 TEST_F(ViewManagerTest, Visible) { | |
444 View* view1 = window_manager()->CreateView(); | |
445 view1->SetVisible(true); | |
446 window_manager()->GetRoot()->AddChild(view1); | |
447 | |
448 // Embed another app and verify initial state. | |
449 ViewTreeConnection* embedded = Embed(view1).connection; | |
450 ASSERT_NE(nullptr, embedded); | |
451 ASSERT_NE(nullptr, embedded->GetRoot()); | |
452 View* embedded_root = embedded->GetRoot(); | |
453 EXPECT_TRUE(embedded_root->visible()); | |
454 EXPECT_TRUE(embedded_root->IsDrawn()); | |
455 | |
456 // Change the visible state from the first connection and verify its mirrored | |
457 // correctly to the embedded app. | |
458 { | |
459 VisibilityChangeObserver observer(embedded_root); | |
460 view1->SetVisible(false); | |
461 ASSERT_TRUE(ViewManagerTestBase::DoRunLoopWithTimeout()); | |
462 } | |
463 | |
464 EXPECT_FALSE(view1->visible()); | |
465 EXPECT_FALSE(view1->IsDrawn()); | |
466 | |
467 EXPECT_FALSE(embedded_root->visible()); | |
468 EXPECT_FALSE(embedded_root->IsDrawn()); | |
469 | |
470 // Make the node visible again. | |
471 { | |
472 VisibilityChangeObserver observer(embedded_root); | |
473 view1->SetVisible(true); | |
474 ASSERT_TRUE(ViewManagerTestBase::DoRunLoopWithTimeout()); | |
475 } | |
476 | |
477 EXPECT_TRUE(view1->visible()); | |
478 EXPECT_TRUE(view1->IsDrawn()); | |
479 | |
480 EXPECT_TRUE(embedded_root->visible()); | |
481 EXPECT_TRUE(embedded_root->IsDrawn()); | |
482 } | |
483 | |
484 namespace { | |
485 | |
486 class DrawnChangeObserver : public ViewObserver { | |
487 public: | |
488 explicit DrawnChangeObserver(View* view) : view_(view) { | |
489 view_->AddObserver(this); | |
490 } | |
491 ~DrawnChangeObserver() override { view_->RemoveObserver(this); } | |
492 | |
493 private: | |
494 // Overridden from ViewObserver: | |
495 void OnViewDrawnChanged(View* view) override { | |
496 EXPECT_EQ(view, view_); | |
497 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
498 } | |
499 | |
500 View* view_; | |
501 | |
502 MOJO_DISALLOW_COPY_AND_ASSIGN(DrawnChangeObserver); | |
503 }; | |
504 | |
505 } // namespace | |
506 | |
507 TEST_F(ViewManagerTest, Drawn) { | |
508 View* view1 = window_manager()->CreateView(); | |
509 view1->SetVisible(true); | |
510 window_manager()->GetRoot()->AddChild(view1); | |
511 | |
512 // Embed another app and verify initial state. | |
513 ViewTreeConnection* embedded = Embed(view1).connection; | |
514 ASSERT_NE(nullptr, embedded); | |
515 ASSERT_NE(nullptr, embedded->GetRoot()); | |
516 View* embedded_root = embedded->GetRoot(); | |
517 EXPECT_TRUE(embedded_root->visible()); | |
518 EXPECT_TRUE(embedded_root->IsDrawn()); | |
519 | |
520 // Change the visibility of the root, this should propagate a drawn state | |
521 // change to |embedded|. | |
522 { | |
523 DrawnChangeObserver observer(embedded_root); | |
524 window_manager()->GetRoot()->SetVisible(false); | |
525 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
526 } | |
527 | |
528 EXPECT_TRUE(view1->visible()); | |
529 EXPECT_FALSE(view1->IsDrawn()); | |
530 | |
531 EXPECT_TRUE(embedded_root->visible()); | |
532 EXPECT_FALSE(embedded_root->IsDrawn()); | |
533 } | |
534 | |
535 // TODO(beng): tests for view event dispatcher. | |
536 // - verify that we see events for all views. | |
537 | |
538 namespace { | |
539 | |
540 class FocusChangeObserver : public ViewObserver { | |
541 public: | |
542 explicit FocusChangeObserver(View* view) | |
543 : view_(view), last_gained_focus_(nullptr), last_lost_focus_(nullptr) { | |
544 view_->AddObserver(this); | |
545 } | |
546 ~FocusChangeObserver() override { view_->RemoveObserver(this); } | |
547 | |
548 View* last_gained_focus() { return last_gained_focus_; } | |
549 | |
550 View* last_lost_focus() { return last_lost_focus_; } | |
551 | |
552 private: | |
553 // Overridden from ViewObserver. | |
554 void OnViewFocusChanged(View* gained_focus, View* lost_focus) override { | |
555 EXPECT_TRUE(!gained_focus || gained_focus->HasFocus()); | |
556 EXPECT_FALSE(lost_focus && lost_focus->HasFocus()); | |
557 last_gained_focus_ = gained_focus; | |
558 last_lost_focus_ = lost_focus; | |
559 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
560 } | |
561 | |
562 View* view_; | |
563 View* last_gained_focus_; | |
564 View* last_lost_focus_; | |
565 | |
566 MOJO_DISALLOW_COPY_AND_ASSIGN(FocusChangeObserver); | |
567 }; | |
568 | |
569 } // namespace | |
570 | |
571 TEST_F(ViewManagerTest, Focus) { | |
572 View* view1 = window_manager()->CreateView(); | |
573 view1->SetVisible(true); | |
574 window_manager()->GetRoot()->AddChild(view1); | |
575 | |
576 ViewTreeConnection* embedded = Embed(view1).connection; | |
577 ASSERT_NE(nullptr, embedded); | |
578 View* view11 = embedded->CreateView(); | |
579 view11->SetVisible(true); | |
580 embedded->GetRoot()->AddChild(view11); | |
581 | |
582 // TODO(alhaad): Figure out why switching focus between views from different | |
583 // connections is causing the tests to crash and add tests for that. | |
584 { | |
585 View* embedded_root = embedded->GetRoot(); | |
586 FocusChangeObserver observer(embedded_root); | |
587 embedded_root->SetFocus(); | |
588 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
589 ASSERT_NE(nullptr, observer.last_gained_focus()); | |
590 EXPECT_EQ(embedded_root->id(), observer.last_gained_focus()->id()); | |
591 } | |
592 { | |
593 FocusChangeObserver observer(view11); | |
594 view11->SetFocus(); | |
595 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
596 ASSERT_NE(nullptr, observer.last_gained_focus()); | |
597 ASSERT_NE(nullptr, observer.last_lost_focus()); | |
598 EXPECT_EQ(view11->id(), observer.last_gained_focus()->id()); | |
599 EXPECT_EQ(embedded->GetRoot()->id(), observer.last_lost_focus()->id()); | |
600 } | |
601 { | |
602 // Add an observer on the View that loses focus, and make sure the observer | |
603 // sees the right values. | |
604 FocusChangeObserver observer(view11); | |
605 embedded->GetRoot()->SetFocus(); | |
606 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
607 ASSERT_NE(nullptr, observer.last_gained_focus()); | |
608 ASSERT_NE(nullptr, observer.last_lost_focus()); | |
609 EXPECT_EQ(view11->id(), observer.last_lost_focus()->id()); | |
610 EXPECT_EQ(embedded->GetRoot()->id(), observer.last_gained_focus()->id()); | |
611 } | |
612 } | |
613 | |
614 namespace { | |
615 | |
616 class DestroyedChangedObserver : public ViewObserver { | |
617 public: | |
618 DestroyedChangedObserver(ViewManagerTestBase* test, | |
619 View* view, | |
620 bool* got_destroy) | |
621 : test_(test), view_(view), got_destroy_(got_destroy) { | |
622 view_->AddObserver(this); | |
623 } | |
624 ~DestroyedChangedObserver() override { | |
625 if (view_) | |
626 view_->RemoveObserver(this); | |
627 } | |
628 | |
629 private: | |
630 // Overridden from ViewObserver: | |
631 void OnViewDestroyed(View* view) override { | |
632 EXPECT_EQ(view, view_); | |
633 view_->RemoveObserver(this); | |
634 *got_destroy_ = true; | |
635 view_ = nullptr; | |
636 | |
637 // We should always get OnViewDestroyed() before OnConnectionLost(). | |
638 EXPECT_FALSE(test_->view_tree_connection_destroyed()); | |
639 } | |
640 | |
641 ViewManagerTestBase* test_; | |
642 View* view_; | |
643 bool* got_destroy_; | |
644 | |
645 MOJO_DISALLOW_COPY_AND_ASSIGN(DestroyedChangedObserver); | |
646 }; | |
647 | |
648 } // namespace | |
649 | |
650 // Verifies deleting a ViewManager sends the right notifications. | |
651 TEST_F(ViewManagerTest, DeleteViewManager) { | |
652 View* view = window_manager()->CreateView(); | |
653 ASSERT_NE(nullptr, view); | |
654 view->SetVisible(true); | |
655 window_manager()->GetRoot()->AddChild(view); | |
656 ViewTreeConnection* connection = Embed(view).connection; | |
657 ASSERT_TRUE(connection); | |
658 bool got_destroy = false; | |
659 DestroyedChangedObserver observer(this, connection->GetRoot(), &got_destroy); | |
660 delete connection; | |
661 EXPECT_TRUE(view_tree_connection_destroyed()); | |
662 EXPECT_TRUE(got_destroy); | |
663 } | |
664 | |
665 // Verifies two Embed()s in the same view trigger deletion of the first | |
666 // ViewManager. | |
667 TEST_F(ViewManagerTest, DisconnectTriggersDelete) { | |
668 View* view = window_manager()->CreateView(); | |
669 ASSERT_NE(nullptr, view); | |
670 view->SetVisible(true); | |
671 window_manager()->GetRoot()->AddChild(view); | |
672 ViewTreeConnection* connection = Embed(view).connection; | |
673 EXPECT_NE(connection, window_manager()); | |
674 View* embedded_view = connection->CreateView(); | |
675 // Embed again, this should trigger disconnect and deletion of connection. | |
676 bool got_destroy; | |
677 DestroyedChangedObserver observer(this, embedded_view, &got_destroy); | |
678 EXPECT_FALSE(view_tree_connection_destroyed()); | |
679 Embed(view); | |
680 EXPECT_TRUE(view_tree_connection_destroyed()); | |
681 } | |
682 | |
683 class ViewRemovedFromParentObserver : public ViewObserver { | |
684 public: | |
685 explicit ViewRemovedFromParentObserver(View* view) | |
686 : view_(view), was_removed_(false) { | |
687 view_->AddObserver(this); | |
688 } | |
689 ~ViewRemovedFromParentObserver() override { view_->RemoveObserver(this); } | |
690 | |
691 bool was_removed() const { return was_removed_; } | |
692 | |
693 private: | |
694 // Overridden from ViewObserver: | |
695 void OnTreeChanged(const TreeChangeParams& params) override { | |
696 if (params.target == view_ && !params.new_parent) | |
697 was_removed_ = true; | |
698 } | |
699 | |
700 View* view_; | |
701 bool was_removed_; | |
702 | |
703 MOJO_DISALLOW_COPY_AND_ASSIGN(ViewRemovedFromParentObserver); | |
704 }; | |
705 | |
706 TEST_F(ViewManagerTest, EmbedRemovesChildren) { | |
707 View* view1 = window_manager()->CreateView(); | |
708 View* view2 = window_manager()->CreateView(); | |
709 window_manager()->GetRoot()->AddChild(view1); | |
710 view1->AddChild(view2); | |
711 | |
712 ViewRemovedFromParentObserver observer(view2); | |
713 view1->Embed(ConnectToApplicationAndGetViewManagerClient()); | |
714 EXPECT_TRUE(observer.was_removed()); | |
715 EXPECT_EQ(nullptr, view2->parent()); | |
716 EXPECT_TRUE(view1->children().empty()); | |
717 | |
718 // Run the message loop so the Embed() call above completes. Without this | |
719 // we may end up reconnecting to the test and rerunning the test, which is | |
720 // problematic since the other services don't shut down. | |
721 ASSERT_TRUE(DoRunLoopWithTimeout()); | |
722 } | |
723 | |
724 namespace { | |
725 | |
726 class DestroyObserver : public ViewObserver { | |
727 public: | |
728 DestroyObserver(ViewManagerTestBase* test, | |
729 ViewTreeConnection* connection, | |
730 bool* got_destroy) | |
731 : test_(test), got_destroy_(got_destroy) { | |
732 connection->GetRoot()->AddObserver(this); | |
733 } | |
734 ~DestroyObserver() override {} | |
735 | |
736 private: | |
737 // Overridden from ViewObserver: | |
738 void OnViewDestroyed(View* view) override { | |
739 *got_destroy_ = true; | |
740 view->RemoveObserver(this); | |
741 | |
742 // We should always get OnViewDestroyed() before OnViewManagerDestroyed(). | |
743 EXPECT_FALSE(test_->view_tree_connection_destroyed()); | |
744 | |
745 EXPECT_TRUE(ViewManagerTestBase::QuitRunLoop()); | |
746 } | |
747 | |
748 ViewManagerTestBase* test_; | |
749 bool* got_destroy_; | |
750 | |
751 MOJO_DISALLOW_COPY_AND_ASSIGN(DestroyObserver); | |
752 }; | |
753 | |
754 } // namespace | |
755 | |
756 // Verifies deleting a View that is the root of another connection notifies | |
757 // observers in the right order (OnViewDestroyed() before | |
758 // OnViewManagerDestroyed()). | |
759 TEST_F(ViewManagerTest, ViewManagerDestroyedAfterRootObserver) { | |
760 View* embed_view = window_manager()->CreateView(); | |
761 window_manager()->GetRoot()->AddChild(embed_view); | |
762 | |
763 ViewTreeConnection* embedded_connection = Embed(embed_view).connection; | |
764 | |
765 bool got_destroy = false; | |
766 DestroyObserver observer(this, embedded_connection, &got_destroy); | |
767 // Delete the view |embedded_connection| is embedded in. This is async, | |
768 // but will eventually trigger deleting |embedded_connection|. | |
769 embed_view->Destroy(); | |
770 EXPECT_TRUE(DoRunLoopWithTimeout()); | |
771 EXPECT_TRUE(got_destroy); | |
772 } | |
773 | |
774 // Verifies an embed root sees views created beneath it from another | |
775 // connection. | |
776 TEST_F(ViewManagerTest, EmbedRootSeesHierarchyChanged) { | |
777 View* embed_view = window_manager()->CreateView(); | |
778 window_manager()->GetRoot()->AddChild(embed_view); | |
779 | |
780 ViewTreeConnection* vm2 = | |
781 Embed(embed_view, mojo::ViewTree::ACCESS_POLICY_EMBED_ROOT).connection; | |
782 View* vm2_v1 = vm2->CreateView(); | |
783 vm2->GetRoot()->AddChild(vm2_v1); | |
784 | |
785 ViewTreeConnection* vm3 = Embed(vm2_v1).connection; | |
786 View* vm3_v1 = vm3->CreateView(); | |
787 vm3->GetRoot()->AddChild(vm3_v1); | |
788 | |
789 // As |vm2| is an embed root it should get notified about |vm3_v1|. | |
790 ASSERT_TRUE(WaitForTreeSizeToMatch(vm2_v1, 2)); | |
791 } | |
792 | |
793 TEST_F(ViewManagerTest, EmbedFromEmbedRoot) { | |
794 View* embed_view = window_manager()->CreateView(); | |
795 window_manager()->GetRoot()->AddChild(embed_view); | |
796 | |
797 // Give the connection embedded at |embed_view| embed root powers. | |
798 const EmbedResult result1 = | |
799 Embed(embed_view, mojo::ViewTree::ACCESS_POLICY_EMBED_ROOT); | |
800 ViewTreeConnection* vm2 = result1.connection; | |
801 EXPECT_EQ(result1.connection_id, vm2->GetConnectionId()); | |
802 View* vm2_v1 = vm2->CreateView(); | |
803 vm2->GetRoot()->AddChild(vm2_v1); | |
804 | |
805 const EmbedResult result2 = Embed(vm2_v1); | |
806 ViewTreeConnection* vm3 = result2.connection; | |
807 EXPECT_EQ(result2.connection_id, vm3->GetConnectionId()); | |
808 View* vm3_v1 = vm3->CreateView(); | |
809 vm3->GetRoot()->AddChild(vm3_v1); | |
810 | |
811 // Embed from v3, the callback should not get the connection id as vm3 is not | |
812 // an embed root. | |
813 const EmbedResult result3 = Embed(vm3_v1); | |
814 ASSERT_TRUE(result3.connection); | |
815 EXPECT_EQ(0, result3.connection_id); | |
816 | |
817 // As |vm2| is an embed root it should get notified about |vm3_v1|. | |
818 ASSERT_TRUE(WaitForTreeSizeToMatch(vm2_v1, 2)); | |
819 | |
820 // Embed() from vm2 in vm3_v1. This is allowed as vm2 is an embed root, and | |
821 // further the callback should see the connection id. | |
822 ASSERT_EQ(1u, vm2_v1->children().size()); | |
823 View* vm3_v1_in_vm2 = vm2_v1->children()[0]; | |
824 const EmbedResult result4 = Embed(vm3_v1_in_vm2); | |
825 ASSERT_TRUE(result4.connection); | |
826 EXPECT_EQ(result4.connection_id, result4.connection->GetConnectionId()); | |
827 } | |
828 | |
829 } // namespace mus | |
OLD | NEW |