Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(116)

Side by Side Diff: components/mus/public/cpp/tests/window_tree_client_impl_unittest.cc

Issue 2018823002: Eliminate WindowTreeConnection (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@connection
Patch Set: . Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 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 "components/mus/public/cpp/lib/window_tree_client_impl.h"
6
7 #include <stdint.h>
8
9 #include "base/logging.h"
10 #include "base/macros.h"
11 #include "components/mus/common/util.h"
12 #include "components/mus/public/cpp/input_event_handler.h"
13 #include "components/mus/public/cpp/lib/window_private.h"
14 #include "components/mus/public/cpp/property_type_converters.h"
15 #include "components/mus/public/cpp/tests/test_window.h"
16 #include "components/mus/public/cpp/tests/test_window_tree.h"
17 #include "components/mus/public/cpp/tests/window_tree_client_impl_private.h"
18 #include "components/mus/public/cpp/window.h"
19 #include "components/mus/public/cpp/window_observer.h"
20 #include "components/mus/public/cpp/window_property.h"
21 #include "components/mus/public/cpp/window_tracker.h"
22 #include "components/mus/public/cpp/window_tree_delegate.h"
23 #include "mojo/common/common_type_converters.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 #include "ui/events/event.h"
26 #include "ui/events/event_utils.h"
27 #include "ui/events/mojo/input_events_type_converters.h"
28 #include "ui/gfx/geometry/rect.h"
29
30 namespace mus {
31
32 namespace {
33
34 void DoNothingWithEventResult(mojom::EventResult result) {}
35
36 Id server_id(mus::Window* window) {
37 return WindowPrivate(window).server_id();
38 }
39
40 } // namespace
41
42 mojo::Array<uint8_t> Int32ToPropertyTransportValue(int32_t value) {
43 const std::vector<uint8_t> bytes =
44 mojo::ConvertTo<std::vector<uint8_t>>(value);
45 mojo::Array<uint8_t> transport_value;
46 transport_value.resize(bytes.size());
47 memcpy(&transport_value.front(), &(bytes.front()), bytes.size());
48 return transport_value;
49 }
50
51 class TestWindowTreeDelegate : public WindowTreeDelegate {
52 public:
53 TestWindowTreeDelegate() {}
54 ~TestWindowTreeDelegate() override {}
55
56 ui::Event* last_event_observed() { return last_event_observed_.get(); }
57
58 void Reset() { last_event_observed_.reset(); }
59
60 // WindowTreeDelegate:
61 void OnEmbed(Window* root) override {}
62 void OnConnectionLost(WindowTreeConnection* connection) override {}
63 void OnEventObserved(const ui::Event& event, Window* target) override {
64 last_event_observed_ = ui::Event::Clone(event);
65 }
66
67 private:
68 std::unique_ptr<ui::Event> last_event_observed_;
69
70 DISALLOW_COPY_AND_ASSIGN(TestWindowTreeDelegate);
71 };
72
73 class WindowTreeSetup {
74 public:
75 WindowTreeSetup() : tree_client_(&window_tree_delegate_, nullptr, nullptr) {
76 WindowTreeClientImplPrivate(&tree_client_).OnEmbed(&window_tree_);
77 window_tree_.GetAndClearChangeId(nullptr);
78 }
79
80 WindowTreeConnection* window_tree_connection() {
81 return static_cast<WindowTreeConnection*>(&tree_client_);
82 }
83
84 mojom::WindowTreeClient* window_tree_client() {
85 return static_cast<mojom::WindowTreeClient*>(&tree_client_);
86 }
87
88 TestWindowTree* window_tree() { return &window_tree_; }
89
90 TestWindowTreeDelegate* window_tree_delegate() {
91 return &window_tree_delegate_;
92 }
93
94 Window* GetFirstRoot() {
95 return window_tree_connection()->GetRoots().empty()
96 ? nullptr
97 : *window_tree_connection()->GetRoots().begin();
98 }
99
100 uint32_t GetEventObserverId() {
101 return WindowTreeClientImplPrivate(&tree_client_).event_observer_id();
102 }
103
104 private:
105 TestWindowTree window_tree_;
106 TestWindowTreeDelegate window_tree_delegate_;
107 WindowTreeClientImpl tree_client_;
108
109 DISALLOW_COPY_AND_ASSIGN(WindowTreeSetup);
110 };
111
112 class TestInputEventHandler : public InputEventHandler {
113 public:
114 TestInputEventHandler()
115 : received_event_(false), should_manually_ack_(false) {}
116 ~TestInputEventHandler() override {}
117
118 void set_should_manually_ack() { should_manually_ack_ = true; }
119
120 void AckEvent() {
121 DCHECK(should_manually_ack_);
122 DCHECK(!ack_callback_.is_null());
123 ack_callback_.Run(mojom::EventResult::HANDLED);
124 ack_callback_ = base::Bind(&DoNothingWithEventResult);
125 }
126
127 void Reset() {
128 received_event_ = false;
129 ack_callback_ = base::Bind(&DoNothingWithEventResult);
130 }
131 bool received_event() const { return received_event_; }
132
133 private:
134 // InputEventHandler:
135 void OnWindowInputEvent(
136 Window* target,
137 const ui::Event& event,
138 std::unique_ptr<base::Callback<void(mojom::EventResult)>>* ack_callback)
139 override {
140 EXPECT_FALSE(received_event_)
141 << "Observer was not reset after receiving event.";
142 received_event_ = true;
143 if (should_manually_ack_) {
144 ack_callback_ = *ack_callback->get();
145 ack_callback->reset();
146 }
147 }
148
149 bool received_event_;
150 bool should_manually_ack_;
151 base::Callback<void(mojom::EventResult)> ack_callback_;
152
153 DISALLOW_COPY_AND_ASSIGN(TestInputEventHandler);
154 };
155
156 using WindowTreeClientImplTest = testing::Test;
157
158 // Verifies bounds are reverted if the server replied that the change failed.
159 TEST_F(WindowTreeClientImplTest, SetBoundsFailed) {
160 WindowTreeSetup setup;
161 Window* root = setup.GetFirstRoot();
162 ASSERT_TRUE(root);
163 const gfx::Rect original_bounds(root->bounds());
164 const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
165 ASSERT_NE(new_bounds, root->bounds());
166 root->SetBounds(new_bounds);
167 uint32_t change_id;
168 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
169 setup.window_tree_client()->OnChangeCompleted(change_id, false);
170 EXPECT_EQ(original_bounds, root->bounds());
171 }
172
173 // Simulates a bounds change, and while the bounds change is in flight the
174 // server replies with a new bounds and the original bounds change fails.
175 TEST_F(WindowTreeClientImplTest, SetBoundsFailedWithPendingChange) {
176 WindowTreeSetup setup;
177 Window* root = setup.GetFirstRoot();
178 ASSERT_TRUE(root);
179 const gfx::Rect original_bounds(root->bounds());
180 const gfx::Rect new_bounds(gfx::Rect(0, 0, 100, 100));
181 ASSERT_NE(new_bounds, root->bounds());
182 root->SetBounds(new_bounds);
183 EXPECT_EQ(new_bounds, root->bounds());
184 uint32_t change_id;
185 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
186
187 // Simulate the server responding with a bounds change.
188 const gfx::Rect server_changed_bounds(gfx::Rect(0, 0, 101, 102));
189 setup.window_tree_client()->OnWindowBoundsChanged(
190 server_id(root), original_bounds, server_changed_bounds);
191
192 // This shouldn't trigger the bounds changing yet.
193 EXPECT_EQ(new_bounds, root->bounds());
194
195 // Tell the client the change failed, which should trigger failing to the
196 // most recent bounds from server.
197 setup.window_tree_client()->OnChangeCompleted(change_id, false);
198 EXPECT_EQ(server_changed_bounds, root->bounds());
199
200 // Simulate server changing back to original bounds. Should take immediately.
201 setup.window_tree_client()->OnWindowBoundsChanged(
202 server_id(root), server_changed_bounds, original_bounds);
203 EXPECT_EQ(original_bounds, root->bounds());
204 }
205
206 TEST_F(WindowTreeClientImplTest, TwoInFlightBoundsChangesBothCanceled) {
207 WindowTreeSetup setup;
208 Window* root = setup.GetFirstRoot();
209 ASSERT_TRUE(root);
210 const gfx::Rect original_bounds(root->bounds());
211 const gfx::Rect bounds1(gfx::Rect(0, 0, 100, 100));
212 const gfx::Rect bounds2(gfx::Rect(0, 0, 100, 102));
213 root->SetBounds(bounds1);
214 EXPECT_EQ(bounds1, root->bounds());
215 uint32_t change_id1;
216 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
217
218 root->SetBounds(bounds2);
219 EXPECT_EQ(bounds2, root->bounds());
220 uint32_t change_id2;
221 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id2));
222
223 // Tell the client change 1 failed. As there is a still a change in flight
224 // nothing should happen.
225 setup.window_tree_client()->OnChangeCompleted(change_id1, false);
226 EXPECT_EQ(bounds2, root->bounds());
227
228 // And tell the client change 2 failed too. Should now fallback to original
229 // bounds.
230 setup.window_tree_client()->OnChangeCompleted(change_id2, false);
231 EXPECT_EQ(original_bounds, root->bounds());
232 }
233
234 // Verifies properties are reverted if the server replied that the change
235 // failed.
236 TEST_F(WindowTreeClientImplTest, SetPropertyFailed) {
237 WindowTreeSetup setup;
238 Window* root = setup.GetFirstRoot();
239 ASSERT_TRUE(root);
240 ASSERT_FALSE(root->HasSharedProperty("foo"));
241 const int32_t new_value = 11;
242 root->SetSharedProperty("foo", new_value);
243 ASSERT_TRUE(root->HasSharedProperty("foo"));
244 EXPECT_EQ(new_value, root->GetSharedProperty<int32_t>("foo"));
245 uint32_t change_id;
246 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
247 setup.window_tree_client()->OnChangeCompleted(change_id, false);
248 EXPECT_FALSE(root->HasSharedProperty("foo"));
249 }
250
251 // Simulates a property change, and while the property change is in flight the
252 // server replies with a new property and the original property change fails.
253 TEST_F(WindowTreeClientImplTest, SetPropertyFailedWithPendingChange) {
254 WindowTreeSetup setup;
255 Window* root = setup.GetFirstRoot();
256 ASSERT_TRUE(root);
257 const int32_t value1 = 11;
258 root->SetSharedProperty("foo", value1);
259 ASSERT_TRUE(root->HasSharedProperty("foo"));
260 EXPECT_EQ(value1, root->GetSharedProperty<int32_t>("foo"));
261 uint32_t change_id;
262 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
263
264 // Simulate the server responding with a different value.
265 const int32_t server_value = 12;
266 setup.window_tree_client()->OnWindowSharedPropertyChanged(
267 server_id(root), "foo", Int32ToPropertyTransportValue(server_value));
268
269 // This shouldn't trigger the property changing yet.
270 ASSERT_TRUE(root->HasSharedProperty("foo"));
271 EXPECT_EQ(value1, root->GetSharedProperty<int32_t>("foo"));
272
273 // Tell the client the change failed, which should trigger failing to the
274 // most recent value from server.
275 setup.window_tree_client()->OnChangeCompleted(change_id, false);
276 ASSERT_TRUE(root->HasSharedProperty("foo"));
277 EXPECT_EQ(server_value, root->GetSharedProperty<int32_t>("foo"));
278
279 // Simulate server changing back to value1. Should take immediately.
280 setup.window_tree_client()->OnWindowSharedPropertyChanged(
281 server_id(root), "foo", Int32ToPropertyTransportValue(value1));
282 ASSERT_TRUE(root->HasSharedProperty("foo"));
283 EXPECT_EQ(value1, root->GetSharedProperty<int32_t>("foo"));
284 }
285
286 // Verifies visible is reverted if the server replied that the change failed.
287 TEST_F(WindowTreeClientImplTest, SetVisibleFailed) {
288 WindowTreeSetup setup;
289 Window* root = setup.GetFirstRoot();
290 ASSERT_TRUE(root);
291 const bool original_visible = root->visible();
292 const bool new_visible = !original_visible;
293 ASSERT_NE(new_visible, root->visible());
294 root->SetVisible(new_visible);
295 uint32_t change_id;
296 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
297 setup.window_tree_client()->OnChangeCompleted(change_id, false);
298 EXPECT_EQ(original_visible, root->visible());
299 }
300
301 // Simulates a visible change, and while the visible change is in flight the
302 // server replies with a new visible and the original visible change fails.
303 TEST_F(WindowTreeClientImplTest, SetVisibleFailedWithPendingChange) {
304 WindowTreeSetup setup;
305 Window* root = setup.GetFirstRoot();
306 ASSERT_TRUE(root);
307 const bool original_visible = root->visible();
308 const bool new_visible = !original_visible;
309 ASSERT_NE(new_visible, root->visible());
310 root->SetVisible(new_visible);
311 EXPECT_EQ(new_visible, root->visible());
312 uint32_t change_id;
313 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
314
315 // Simulate the server responding with a visible change.
316 const bool server_changed_visible = !new_visible;
317 setup.window_tree_client()->OnWindowVisibilityChanged(server_id(root),
318 server_changed_visible);
319
320 // This shouldn't trigger visible changing yet.
321 EXPECT_EQ(new_visible, root->visible());
322
323 // Tell the client the change failed, which should trigger failing to the
324 // most recent visible from server.
325 setup.window_tree_client()->OnChangeCompleted(change_id, false);
326 EXPECT_EQ(server_changed_visible, root->visible());
327
328 // Simulate server changing back to original visible. Should take immediately.
329 setup.window_tree_client()->OnWindowVisibilityChanged(server_id(root),
330 original_visible);
331 EXPECT_EQ(original_visible, root->visible());
332 }
333
334 // Verifies that local opacity is not changed if the server replied that the
335 // change succeeded.
336 TEST_F(WindowTreeClientImplTest, SetOpacitySucceeds) {
337 WindowTreeSetup setup;
338 Window* root = setup.GetFirstRoot();
339 ASSERT_TRUE(root);
340 const float original_opacity = root->opacity();
341 const float new_opacity = 0.5f;
342 ASSERT_NE(new_opacity, original_opacity);
343 ASSERT_NE(new_opacity, root->opacity());
344 root->SetOpacity(new_opacity);
345 uint32_t change_id;
346 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
347 setup.window_tree_client()->OnChangeCompleted(change_id, true);
348 EXPECT_EQ(new_opacity, root->opacity());
349 }
350
351 // Verifies that opacity is reverted if the server replied that the change
352 // failed.
353 TEST_F(WindowTreeClientImplTest, SetOpacityFailed) {
354 WindowTreeSetup setup;
355 Window* root = setup.GetFirstRoot();
356 ASSERT_TRUE(root);
357 const float original_opacity = root->opacity();
358 const float new_opacity = 0.5f;
359 ASSERT_NE(new_opacity, root->opacity());
360 root->SetOpacity(new_opacity);
361 uint32_t change_id;
362 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
363 setup.window_tree_client()->OnChangeCompleted(change_id, false);
364 EXPECT_EQ(original_opacity, root->opacity());
365 }
366
367 // Simulates the server changing the opacitry while there is an opacity change
368 // in flight, causing the requested change to fail.
369 TEST_F(WindowTreeClientImplTest, SetOpacityFailedWithPendingChange) {
370 WindowTreeSetup setup;
371 Window* root = setup.GetFirstRoot();
372 ASSERT_TRUE(root);
373 const float original_opacity = root->opacity();
374 const float new_opacity = 0.5f;
375 ASSERT_NE(new_opacity, root->opacity());
376 root->SetOpacity(new_opacity);
377 EXPECT_EQ(new_opacity, root->opacity());
378 uint32_t change_id;
379 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
380
381 // Simulate the server responding with an opacity change.
382 const float server_changed_opacity = 0.75f;
383 setup.window_tree_client()->OnWindowOpacityChanged(
384 server_id(root), original_opacity, server_changed_opacity);
385
386 // This shouldn't trigger opacity changing yet.
387 EXPECT_EQ(new_opacity, root->opacity());
388
389 // Tell the client the change failed, which should trigger failing to the
390 // most recent opacity from server.
391 setup.window_tree_client()->OnChangeCompleted(change_id, false);
392 EXPECT_EQ(server_changed_opacity, root->opacity());
393
394 // Simulate server changing back to original opacity. Should take immediately.
395 setup.window_tree_client()->OnWindowOpacityChanged(
396 server_id(root), server_changed_opacity, original_opacity);
397 EXPECT_EQ(original_opacity, root->opacity());
398 }
399
400 // Tests that when there are multiple changes in flight, that failing changes
401 // update the revert state of subsequent changes.
402 TEST_F(WindowTreeClientImplTest, SetOpacityFailedWithMultiplePendingChange) {
403 WindowTreeSetup setup;
404 Window* root = setup.GetFirstRoot();
405 ASSERT_TRUE(root);
406 const float original_opacity = root->opacity();
407 const float new_opacity = 0.5f;
408 ASSERT_NE(new_opacity, root->opacity());
409 root->SetOpacity(new_opacity);
410 uint32_t change_id1;
411 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
412
413 const float second_new_opacity = 0.75f;
414 ASSERT_NE(second_new_opacity, root->opacity());
415 root->SetOpacity(second_new_opacity);
416 uint32_t change_id2;
417 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id2));
418
419 // Canceling the first one, while there is another in flight, should not
420 // change the local opacity.
421 setup.window_tree_client()->OnChangeCompleted(change_id1, false);
422 EXPECT_EQ(second_new_opacity, root->opacity());
423
424 // The previous cancelation should have updated the revert value of the in
425 // flight change.
426 setup.window_tree_client()->OnChangeCompleted(change_id2, false);
427 EXPECT_EQ(original_opacity, root->opacity());
428 }
429
430 // Verifies |is_modal| is reverted if the server replied that the change failed.
431 TEST_F(WindowTreeClientImplTest, SetModalFailed) {
432 WindowTreeSetup setup;
433 Window* root = setup.GetFirstRoot();
434 ASSERT_TRUE(root);
435 EXPECT_FALSE(root->is_modal());
436 root->SetModal();
437 uint32_t change_id;
438 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
439 EXPECT_TRUE(root->is_modal());
440 setup.window_tree_client()->OnChangeCompleted(change_id, false);
441 EXPECT_FALSE(root->is_modal());
442 }
443
444 TEST_F(WindowTreeClientImplTest, InputEventBasic) {
445 WindowTreeSetup setup;
446 Window* root = setup.GetFirstRoot();
447 ASSERT_TRUE(root);
448
449 TestInputEventHandler event_handler;
450 root->set_input_event_handler(&event_handler);
451
452 std::unique_ptr<ui::Event> ui_event(
453 new ui::MouseEvent(ui::ET_MOUSE_MOVED, gfx::Point(), gfx::Point(),
454 ui::EventTimeForNow(), ui::EF_NONE, 0));
455 setup.window_tree_client()->OnWindowInputEvent(
456 1, server_id(root), mojom::Event::From(*ui_event.get()), 0);
457 EXPECT_TRUE(event_handler.received_event());
458 EXPECT_TRUE(setup.window_tree()->WasEventAcked(1));
459 event_handler.Reset();
460
461 event_handler.set_should_manually_ack();
462 setup.window_tree_client()->OnWindowInputEvent(
463 33, server_id(root), mojom::Event::From(*ui_event.get()), 0);
464 EXPECT_TRUE(event_handler.received_event());
465 EXPECT_FALSE(setup.window_tree()->WasEventAcked(33));
466
467 event_handler.AckEvent();
468 EXPECT_TRUE(setup.window_tree()->WasEventAcked(33));
469 }
470
471 // Tests event observers triggered by events that did not hit a target in this
472 // window tree.
473 TEST_F(WindowTreeClientImplTest, OnEventObserved) {
474 WindowTreeSetup setup;
475 Window* root = setup.GetFirstRoot();
476 ASSERT_TRUE(root);
477
478 // Set up an event observer.
479 mojom::EventMatcherPtr matcher = mojom::EventMatcher::New();
480 matcher->type_matcher = mojom::EventTypeMatcher::New();
481 matcher->type_matcher->type = mojom::EventType::POINTER_DOWN;
482 setup.window_tree_connection()->SetEventObserver(std::move(matcher));
483
484 // Simulate the server sending an observed event.
485 uint32_t event_observer_id = setup.GetEventObserverId();
486 std::unique_ptr<ui::Event> ui_event(
487 new ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
488 ui::EventTimeForNow(), ui::EF_CONTROL_DOWN, 0));
489 setup.window_tree_client()->OnEventObserved(
490 mojom::Event::From(*ui_event.get()), event_observer_id);
491
492 // Delegate sensed the event.
493 ui::Event* last_event = setup.window_tree_delegate()->last_event_observed();
494 EXPECT_EQ(ui::ET_MOUSE_PRESSED, last_event->type());
495 EXPECT_EQ(ui::EF_CONTROL_DOWN, last_event->flags());
496 setup.window_tree_delegate()->Reset();
497
498 // Clear the event observer.
499 setup.window_tree_connection()->SetEventObserver(nullptr);
500
501 // Simulate another event from the server.
502 setup.window_tree_client()->OnEventObserved(
503 mojom::Event::From(*ui_event.get()), event_observer_id);
504
505 // No event was sensed.
506 EXPECT_FALSE(setup.window_tree_delegate()->last_event_observed());
507 }
508
509 // Tests event observers triggered by events that hit this window tree.
510 TEST_F(WindowTreeClientImplTest, OnWindowInputEventWithEventObserver) {
511 WindowTreeSetup setup;
512 Window* root = setup.GetFirstRoot();
513 ASSERT_TRUE(root);
514
515 // Set up an event observer.
516 mojom::EventMatcherPtr matcher = mojom::EventMatcher::New();
517 matcher->type_matcher = mojom::EventTypeMatcher::New();
518 matcher->type_matcher->type = mojom::EventType::POINTER_DOWN;
519 setup.window_tree_connection()->SetEventObserver(std::move(matcher));
520
521 // Simulate the server dispatching an event that also matched the observer.
522 uint32_t event_observer_id = setup.GetEventObserverId();
523 std::unique_ptr<ui::Event> ui_event(
524 new ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
525 ui::EventTimeForNow(), ui::EF_CONTROL_DOWN, 0));
526 setup.window_tree_client()->OnWindowInputEvent(
527 1, server_id(root), mojom::Event::From(*ui_event.get()),
528 event_observer_id);
529
530 // Delegate sensed the event.
531 ui::Event* last_event = setup.window_tree_delegate()->last_event_observed();
532 EXPECT_EQ(ui::ET_MOUSE_PRESSED, last_event->type());
533 EXPECT_EQ(ui::EF_CONTROL_DOWN, last_event->flags());
534 }
535
536 // Tests that replacing an event observer with a new one results in only new
537 // events being observed.
538 TEST_F(WindowTreeClientImplTest, EventObserverReplaced) {
539 WindowTreeSetup setup;
540 Window* root = setup.GetFirstRoot();
541 ASSERT_TRUE(root);
542
543 // Set up an event observer.
544 mojom::EventMatcherPtr matcher1 = mojom::EventMatcher::New();
545 matcher1->type_matcher = mojom::EventTypeMatcher::New();
546 matcher1->type_matcher->type = mojom::EventType::POINTER_DOWN;
547 setup.window_tree_connection()->SetEventObserver(std::move(matcher1));
548 uint32_t event_observer_id1 = setup.GetEventObserverId();
549
550 // Replace it with a second observer.
551 mojom::EventMatcherPtr matcher2 = mojom::EventMatcher::New();
552 matcher2->type_matcher = mojom::EventTypeMatcher::New();
553 matcher2->type_matcher->type = mojom::EventType::POINTER_UP;
554 setup.window_tree_connection()->SetEventObserver(std::move(matcher2));
555 uint32_t event_observer_id2 = setup.GetEventObserverId();
556
557 // Simulate the server sending an observed event that matched the old observer
558 // (e.g. that was in-flight when the observer was replaced).
559 std::unique_ptr<ui::Event> pressed_event(
560 new ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
561 ui::EventTimeForNow(), ui::EF_NONE, 0));
562 setup.window_tree_client()->OnEventObserved(
563 mojom::Event::From(*pressed_event.get()), event_observer_id1);
564
565 // The event was not sensed, because it does not match the current observer.
566 EXPECT_FALSE(setup.window_tree_delegate()->last_event_observed());
567
568 // Simulate another event that matches the new observer.
569 std::unique_ptr<ui::Event> released_event(
570 new ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(), gfx::Point(),
571 ui::EventTimeForNow(), ui::EF_CONTROL_DOWN, 0));
572 setup.window_tree_client()->OnEventObserved(
573 mojom::Event::From(*released_event.get()), event_observer_id2);
574
575 // The delegate sensed the event.
576 ui::Event* last_event = setup.window_tree_delegate()->last_event_observed();
577 EXPECT_EQ(ui::ET_MOUSE_RELEASED, last_event->type());
578 EXPECT_EQ(ui::EF_CONTROL_DOWN, last_event->flags());
579 }
580
581 // Verifies focus is reverted if the server replied that the change failed.
582 TEST_F(WindowTreeClientImplTest, SetFocusFailed) {
583 WindowTreeSetup setup;
584 Window* root = setup.GetFirstRoot();
585 ASSERT_TRUE(root);
586 root->SetVisible(true);
587 Window* child = setup.window_tree_connection()->NewWindow();
588 child->SetVisible(true);
589 root->AddChild(child);
590 Window* original_focus = setup.window_tree_connection()->GetFocusedWindow();
591 Window* new_focus = child;
592 ASSERT_NE(new_focus, original_focus);
593 new_focus->SetFocus();
594 ASSERT_TRUE(new_focus->HasFocus());
595 uint32_t change_id;
596 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
597 setup.window_tree_client()->OnChangeCompleted(change_id, false);
598 EXPECT_EQ(original_focus, setup.window_tree_connection()->GetFocusedWindow());
599 }
600
601 // Simulates a focus change, and while the focus change is in flight the server
602 // replies with a new focus and the original focus change fails.
603 TEST_F(WindowTreeClientImplTest, SetFocusFailedWithPendingChange) {
604 WindowTreeSetup setup;
605 Window* root = setup.GetFirstRoot();
606 ASSERT_TRUE(root);
607 root->SetVisible(true);
608 Window* child1 = setup.window_tree_connection()->NewWindow();
609 child1->SetVisible(true);
610 root->AddChild(child1);
611 Window* child2 = setup.window_tree_connection()->NewWindow();
612 child2->SetVisible(true);
613 root->AddChild(child2);
614 Window* original_focus = setup.window_tree_connection()->GetFocusedWindow();
615 Window* new_focus = child1;
616 ASSERT_NE(new_focus, original_focus);
617 new_focus->SetFocus();
618 ASSERT_TRUE(new_focus->HasFocus());
619 uint32_t change_id;
620 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
621
622 // Simulate the server responding with a focus change.
623 setup.window_tree_client()->OnWindowFocused(server_id(child2));
624
625 // This shouldn't trigger focus changing yet.
626 EXPECT_TRUE(child1->HasFocus());
627
628 // Tell the client the change failed, which should trigger failing to the
629 // most recent focus from server.
630 setup.window_tree_client()->OnChangeCompleted(change_id, false);
631 EXPECT_FALSE(child1->HasFocus());
632 EXPECT_TRUE(child2->HasFocus());
633 EXPECT_EQ(child2, setup.window_tree_connection()->GetFocusedWindow());
634
635 // Simulate server changing focus to child1. Should take immediately.
636 setup.window_tree_client()->OnWindowFocused(server_id(child1));
637 EXPECT_TRUE(child1->HasFocus());
638 }
639
640 TEST_F(WindowTreeClientImplTest, FocusOnRemovedWindowWithInFlightFocusChange) {
641 WindowTreeSetup setup;
642 Window* root = setup.GetFirstRoot();
643 ASSERT_TRUE(root);
644 root->SetVisible(true);
645 Window* child1 = setup.window_tree_connection()->NewWindow();
646 child1->SetVisible(true);
647 root->AddChild(child1);
648 Window* child2 = setup.window_tree_connection()->NewWindow();
649 child2->SetVisible(true);
650 root->AddChild(child2);
651
652 child1->SetFocus();
653 uint32_t change_id;
654 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
655
656 // Destroy child1, which should set focus to null.
657 child1->Destroy();
658 EXPECT_EQ(nullptr, setup.window_tree_connection()->GetFocusedWindow());
659
660 // Server changes focus to 2.
661 setup.window_tree_client()->OnWindowFocused(server_id(child2));
662 // Shouldn't take immediately.
663 EXPECT_FALSE(child2->HasFocus());
664
665 // Ack the change, focus should still be null.
666 setup.window_tree_client()->OnChangeCompleted(change_id, true);
667 EXPECT_EQ(nullptr, setup.window_tree_connection()->GetFocusedWindow());
668
669 // Change to 2 again, this time it should take.
670 setup.window_tree_client()->OnWindowFocused(server_id(child2));
671 EXPECT_TRUE(child2->HasFocus());
672 }
673
674 class ToggleVisibilityFromDestroyedObserver : public WindowObserver {
675 public:
676 explicit ToggleVisibilityFromDestroyedObserver(Window* window)
677 : window_(window) {
678 window_->AddObserver(this);
679 }
680
681 ToggleVisibilityFromDestroyedObserver() { EXPECT_FALSE(window_); }
682
683 // WindowObserver:
684 void OnWindowDestroyed(Window* window) override {
685 window_->SetVisible(!window->visible());
686 window_->RemoveObserver(this);
687 window_ = nullptr;
688 }
689
690 private:
691 Window* window_;
692
693 DISALLOW_COPY_AND_ASSIGN(ToggleVisibilityFromDestroyedObserver);
694 };
695
696 TEST_F(WindowTreeClientImplTest, ToggleVisibilityFromWindowDestroyed) {
697 WindowTreeSetup setup;
698 Window* root = setup.GetFirstRoot();
699 ASSERT_TRUE(root);
700 Window* child1 = setup.window_tree_connection()->NewWindow();
701 root->AddChild(child1);
702 ToggleVisibilityFromDestroyedObserver toggler(child1);
703 // Destroying the window triggers
704 // ToggleVisibilityFromDestroyedObserver::OnWindowDestroyed(), which toggles
705 // the visibility of the window. Ack the change, which should not crash or
706 // trigger DCHECKs.
707 child1->Destroy();
708 uint32_t change_id;
709 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
710 setup.window_tree_client()->OnChangeCompleted(change_id, true);
711 }
712
713 TEST_F(WindowTreeClientImplTest, NewTopLevelWindow) {
714 WindowTreeSetup setup;
715 Window* root1 = setup.GetFirstRoot();
716 ASSERT_TRUE(root1);
717 Window* root2 = setup.window_tree_connection()->NewTopLevelWindow(nullptr);
718 ASSERT_TRUE(root2);
719 EXPECT_TRUE(WindowPrivate(root2).parent_drawn());
720 ASSERT_NE(root2, root1);
721 EXPECT_NE(server_id(root2), server_id(root1));
722 EXPECT_EQ(2u, setup.window_tree_connection()->GetRoots().size());
723 EXPECT_TRUE(setup.window_tree_connection()->GetRoots().count(root1) > 0u);
724 EXPECT_TRUE(setup.window_tree_connection()->GetRoots().count(root2) > 0u);
725
726 // Ack the request to the windowtree to create the new window.
727 uint32_t change_id;
728 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
729 EXPECT_EQ(setup.window_tree()->window_id(), server_id(root2));
730
731 mojom::WindowDataPtr data = mojom::WindowData::New();
732 data->window_id = server_id(root2);
733 const int64_t display_id = 1;
734 setup.window_tree_client()->OnTopLevelCreated(change_id, std::move(data),
735 display_id, false);
736
737 EXPECT_FALSE(WindowPrivate(root2).parent_drawn());
738
739 // Should not be able to add a top level as a child of another window.
740 root1->AddChild(root2);
741 ASSERT_EQ(nullptr, root2->parent());
742
743 // Destroy the first root, shouldn't initiate tear down.
744 root1->Destroy();
745 root1 = nullptr;
746 EXPECT_EQ(1u, setup.window_tree_connection()->GetRoots().size());
747 EXPECT_TRUE(setup.window_tree_connection()->GetRoots().count(root2) > 0u);
748 }
749
750 TEST_F(WindowTreeClientImplTest, NewTopLevelWindowGetsPropertiesFromData) {
751 WindowTreeSetup setup;
752 Window* root1 = setup.GetFirstRoot();
753 ASSERT_TRUE(root1);
754 Window* root2 = setup.window_tree_connection()->NewTopLevelWindow(nullptr);
755 ASSERT_TRUE(root2);
756
757 EXPECT_FALSE(root2->IsDrawn());
758 EXPECT_FALSE(root2->visible());
759
760 // Ack the request to the windowtree to create the new window.
761 uint32_t change_id;
762 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
763 EXPECT_EQ(setup.window_tree()->window_id(), server_id(root2));
764
765 mojom::WindowDataPtr data = mojom::WindowData::New();
766 data->window_id = server_id(root2);
767 data->bounds.SetRect(1, 2, 3, 4);
768 data->visible = true;
769 const int64_t display_id = 1;
770 setup.window_tree_client()->OnTopLevelCreated(change_id, std::move(data),
771 display_id, true);
772
773 // Make sure all the properties took.
774 EXPECT_TRUE(root2->IsDrawn());
775 EXPECT_TRUE(root2->visible());
776 EXPECT_EQ(1, root2->display_id());
777 EXPECT_EQ(gfx::Rect(1, 2, 3, 4), root2->bounds());
778 }
779
780 TEST_F(WindowTreeClientImplTest, NewTopLevelWindowGetsAllChangesInFlight) {
781 WindowTreeSetup setup;
782 Window* root1 = setup.GetFirstRoot();
783 ASSERT_TRUE(root1);
784 Window* root2 = setup.window_tree_connection()->NewTopLevelWindow(nullptr);
785 ASSERT_TRUE(root2);
786
787 EXPECT_FALSE(root2->IsDrawn());
788 EXPECT_FALSE(root2->visible());
789
790 // Get the id of the in flight change for creating the new window.
791 uint32_t new_window_in_flight_change_id;
792 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(
793 &new_window_in_flight_change_id));
794 EXPECT_EQ(setup.window_tree()->window_id(), server_id(root2));
795
796 // Make visibility go from false->true->false. Don't ack immediately.
797 root2->SetVisible(true);
798 uint32_t vis_in_flight_change_id1;
799 ASSERT_TRUE(
800 setup.window_tree()->GetAndClearChangeId(&vis_in_flight_change_id1));
801 EXPECT_NE(new_window_in_flight_change_id, vis_in_flight_change_id1);
802 root2->SetVisible(false);
803 uint32_t vis_in_flight_change_id2;
804 ASSERT_TRUE(
805 setup.window_tree()->GetAndClearChangeId(&vis_in_flight_change_id2));
806 EXPECT_NE(vis_in_flight_change_id1, vis_in_flight_change_id2);
807
808 // Change bounds to 5, 6, 7, 8.
809 root2->SetBounds(gfx::Rect(5, 6, 7, 8));
810 uint32_t bounds_in_flight_change_id;
811 ASSERT_TRUE(
812 setup.window_tree()->GetAndClearChangeId(&bounds_in_flight_change_id));
813 EXPECT_NE(vis_in_flight_change_id2, bounds_in_flight_change_id);
814
815 root2->SetSharedProperty<std::string>("xx", "client_xx");
816 uint32_t property_in_flight_change_id;
817 ASSERT_TRUE(
818 setup.window_tree()->GetAndClearChangeId(&property_in_flight_change_id));
819 EXPECT_NE(bounds_in_flight_change_id, property_in_flight_change_id);
820
821 // Ack the new window top level window. Vis and bounds shouldn't change.
822 mojom::WindowDataPtr data = mojom::WindowData::New();
823 data->window_id = server_id(root2);
824 data->bounds.SetRect(1, 2, 3, 4);
825 data->visible = true;
826 data->properties["xx"] = mojo::Array<uint8_t>::From(std::string("server_xx"));
827 data->properties["yy"] = mojo::Array<uint8_t>::From(std::string("server_yy"));
828 const int64_t display_id = 1;
829 setup.window_tree_client()->OnTopLevelCreated(
830 new_window_in_flight_change_id, std::move(data), display_id, true);
831
832 // The only value that should take effect is the property for 'yy' as it was
833 // not in flight.
834 EXPECT_TRUE(WindowPrivate(root2).parent_drawn());
835 EXPECT_FALSE(root2->visible());
836 EXPECT_EQ(1, root2->display_id());
837 EXPECT_EQ(gfx::Rect(5, 6, 7, 8), root2->bounds());
838 EXPECT_EQ(2u, root2->shared_properties().size());
839 ASSERT_TRUE(root2->HasSharedProperty("yy"));
840 EXPECT_EQ("server_yy", root2->GetSharedProperty<std::string>("yy"));
841 ASSERT_TRUE(root2->HasSharedProperty("xx"));
842 EXPECT_EQ("client_xx", root2->GetSharedProperty<std::string>("xx"));
843
844 // Tell the client the changes failed. This should cause the values to change
845 // to that of the server.
846 setup.window_tree_client()->OnChangeCompleted(vis_in_flight_change_id1,
847 false);
848 EXPECT_FALSE(root2->visible());
849 setup.window_tree_client()->OnChangeCompleted(vis_in_flight_change_id2,
850 false);
851 EXPECT_TRUE(root2->visible());
852 setup.window_tree_client()->OnChangeCompleted(bounds_in_flight_change_id,
853 false);
854 EXPECT_EQ(gfx::Rect(1, 2, 3, 4), root2->bounds());
855 setup.window_tree_client()->OnChangeCompleted(property_in_flight_change_id,
856 false);
857 EXPECT_EQ(2u, root2->shared_properties().size());
858 ASSERT_TRUE(root2->HasSharedProperty("yy"));
859 EXPECT_EQ("server_yy", root2->GetSharedProperty<std::string>("yy"));
860 ASSERT_TRUE(root2->HasSharedProperty("xx"));
861 EXPECT_EQ("server_xx", root2->GetSharedProperty<std::string>("xx"));
862 }
863
864 // Tests that if the client has multiple unowned windows, and one of them is a
865 // transient child to another, the teardown can happen cleanly.
866 TEST_F(WindowTreeClientImplTest, MultipleUnOwnedWindowsDuringDestruction) {
867 std::unique_ptr<WindowTreeSetup> setup(new WindowTreeSetup());
868 Window* root1 = setup->GetFirstRoot();
869 ASSERT_TRUE(root1);
870 Window* root2 = setup->window_tree_connection()->NewTopLevelWindow(nullptr);
871 ASSERT_TRUE(root2);
872 root1->AddTransientWindow(root2);
873
874 WindowTracker tracker;
875 tracker.Add(root1);
876 tracker.Add(root2);
877 setup.reset();
878 EXPECT_TRUE(tracker.windows().empty());
879 }
880
881 TEST_F(WindowTreeClientImplTest, TopLevelWindowDestroyedBeforeCreateComplete) {
882 WindowTreeSetup setup;
883 Window* root1 = setup.GetFirstRoot();
884 ASSERT_TRUE(root1);
885 Window* root2 = setup.window_tree_connection()->NewTopLevelWindow(nullptr);
886 ASSERT_TRUE(root2);
887 ASSERT_EQ(2u, setup.window_tree_connection()->GetRoots().size());
888
889 // Get the id of the in flight change for creating the new window.
890 uint32_t change_id;
891 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id));
892 EXPECT_EQ(setup.window_tree()->window_id(), server_id(root2));
893
894 mojom::WindowDataPtr data = mojom::WindowData::New();
895 data->window_id = server_id(root2);
896
897 // Destroy the window before the server has a chance to ack the window
898 // creation.
899 root2->Destroy();
900 EXPECT_EQ(1u, setup.window_tree_connection()->GetRoots().size());
901
902 const int64_t display_id = 1;
903 setup.window_tree_client()->OnTopLevelCreated(change_id, std::move(data),
904 display_id, true);
905 EXPECT_EQ(1u, setup.window_tree_connection()->GetRoots().size());
906 }
907
908 // Tests both SetCapture and ReleaseCapture, to ensure that Window is properly
909 // updated on failures.
910 TEST_F(WindowTreeClientImplTest, ExplicitCapture) {
911 WindowTreeSetup setup;
912 Window* root = setup.GetFirstRoot();
913 ASSERT_TRUE(root);
914
915 root->SetCapture();
916 EXPECT_TRUE(root->HasCapture());
917 uint32_t change_id1;
918 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
919 setup.window_tree_client()->OnChangeCompleted(change_id1, false);
920 EXPECT_FALSE(root->HasCapture());
921
922 root->SetCapture();
923 EXPECT_TRUE(root->HasCapture());
924 uint32_t change_id2;
925 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id2));
926 setup.window_tree_client()->OnChangeCompleted(change_id2, true);
927 EXPECT_TRUE(root->HasCapture());
928
929 root->ReleaseCapture();
930 EXPECT_FALSE(root->HasCapture());
931 uint32_t change_id3;
932 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id3));
933 setup.window_tree_client()->OnChangeCompleted(change_id3, false);
934 EXPECT_TRUE(root->HasCapture());
935
936 root->ReleaseCapture();
937 uint32_t change_id4;
938 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id4));
939 setup.window_tree_client()->OnChangeCompleted(change_id4, true);
940 EXPECT_FALSE(root->HasCapture());
941 }
942
943 // Tests that when capture is lost, that the window tree updates properly.
944 TEST_F(WindowTreeClientImplTest, LostCapture) {
945 WindowTreeSetup setup;
946 Window* root = setup.GetFirstRoot();
947 ASSERT_TRUE(root);
948
949 root->SetCapture();
950 EXPECT_TRUE(root->HasCapture());
951 uint32_t change_id1;
952 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
953 setup.window_tree_client()->OnChangeCompleted(change_id1, true);
954 EXPECT_TRUE(root->HasCapture());
955
956 // The second SetCapture should be ignored.
957 root->SetCapture();
958 uint32_t change_id2;
959 ASSERT_FALSE(setup.window_tree()->GetAndClearChangeId(&change_id2));
960
961 setup.window_tree_client()->OnLostCapture(server_id(root));
962 EXPECT_FALSE(root->HasCapture());
963 }
964
965 // Tests that when capture is lost, while there is a release capture request
966 // inflight, that the revert value of that request is updated correctly.
967 TEST_F(WindowTreeClientImplTest, LostCaptureDifferentInFlightChange) {
968 WindowTreeSetup setup;
969 Window* root = setup.GetFirstRoot();
970 ASSERT_TRUE(root);
971
972 root->SetCapture();
973 EXPECT_TRUE(root->HasCapture());
974 uint32_t change_id1;
975 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
976 setup.window_tree_client()->OnChangeCompleted(change_id1, true);
977 EXPECT_TRUE(root->HasCapture());
978
979 // The ReleaseCapture should be updated to the revert of the SetCapture.
980 root->ReleaseCapture();
981 uint32_t change_id2;
982 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id2));
983
984 setup.window_tree_client()->OnLostCapture(server_id(root));
985 EXPECT_FALSE(root->HasCapture());
986
987 setup.window_tree_client()->OnChangeCompleted(change_id2, false);
988 EXPECT_FALSE(root->HasCapture());
989 }
990
991 // Tests that while two windows can inflight capture requests, that the
992 // WindowTreeClient only identifies one as having the current capture.
993 TEST_F(WindowTreeClientImplTest, TwoWindowsRequestCapture) {
994 WindowTreeSetup setup;
995 Window* root = setup.GetFirstRoot();
996 Window* child = setup.window_tree_connection()->NewWindow();
997 child->SetVisible(true);
998 root->AddChild(child);
999
1000 root->SetCapture();
1001 EXPECT_TRUE(root->HasCapture());
1002 uint32_t change_id1;
1003 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id1));
1004
1005 child->SetCapture();
1006 EXPECT_TRUE(child->HasCapture());
1007 EXPECT_FALSE(root->HasCapture());
1008
1009 uint32_t change_id2;
1010 ASSERT_TRUE(setup.window_tree()->GetAndClearChangeId(&change_id2));
1011
1012 setup.window_tree_client()->OnChangeCompleted(change_id1, true);
1013 EXPECT_FALSE(root->HasCapture());
1014 EXPECT_TRUE(child->HasCapture());
1015
1016 setup.window_tree_client()->OnChangeCompleted(change_id2, false);
1017 EXPECT_FALSE(child->HasCapture());
1018 EXPECT_TRUE(root->HasCapture());
1019
1020 setup.window_tree_client()->OnLostCapture(server_id(root));
1021 EXPECT_FALSE(root->HasCapture());
1022 }
1023
1024 TEST_F(WindowTreeClientImplTest, WindowDestroyedWhileTransientChildHasCapture) {
1025 WindowTreeSetup setup;
1026 Window* root = setup.GetFirstRoot();
1027 Window* transient_parent = setup.window_tree_connection()->NewWindow();
1028 Window* transient_child = setup.window_tree_connection()->NewWindow();
1029 transient_parent->SetVisible(true);
1030 transient_child->SetVisible(true);
1031 root->AddChild(transient_parent);
1032 root->AddChild(transient_child);
1033
1034 transient_parent->AddTransientWindow(transient_child);
1035
1036 WindowTracker tracker;
1037 tracker.Add(transient_parent);
1038 tracker.Add(transient_child);
1039 // Request a capture on the transient child, then destroy the transient
1040 // parent. That will destroy both windows, and should reset the capture window
1041 // correctly.
1042 transient_child->SetCapture();
1043 transient_parent->Destroy();
1044 EXPECT_TRUE(tracker.windows().empty());
1045
1046 // Create a new Window, and attempt to place capture on that.
1047 Window* child = setup.window_tree_connection()->NewWindow();
1048 child->SetVisible(true);
1049 root->AddChild(child);
1050 child->SetCapture();
1051 EXPECT_TRUE(child->HasCapture());
1052 }
1053
1054 } // namespace mus
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698