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