1 // Copyright (c) 2012 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 "ui/events/event_dispatcher.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "ui/events/event.h"
9 #include "ui/events/event_dispatcher.h"
10 #include "ui/events/event_target.h"
11 #include "ui/events/event_target_iterator.h"
12 #include "ui/events/event_utils.h"
13
14 namespace ui {
15
16 namespace {
17
18 class TestTarget : public EventTarget {
19 public:
TestTarget()20 TestTarget() : parent_(NULL), valid_(true) {}
~TestTarget()21 virtual ~TestTarget() {}
22
set_parent(TestTarget * parent)23 void set_parent(TestTarget* parent) { parent_ = parent; }
24
valid() const25 bool valid() const { return valid_; }
set_valid(bool valid)26 void set_valid(bool valid) { valid_ = valid; }
27
AddHandlerId(int id)28 void AddHandlerId(int id) {
29 handler_list_.push_back(id);
30 }
31
handler_list() const32 const std::vector<int>& handler_list() const { return handler_list_; }
33
Reset()34 void Reset() {
35 handler_list_.clear();
36 valid_ = true;
37 }
38
39 private:
40 // Overridden from EventTarget:
CanAcceptEvent(const ui::Event & event)41 virtual bool CanAcceptEvent(const ui::Event& event) OVERRIDE {
42 return true;
43 }
44
GetParentTarget()45 virtual EventTarget* GetParentTarget() OVERRIDE {
46 return parent_;
47 }
48
GetChildIterator() const49 virtual scoped_ptr<EventTargetIterator> GetChildIterator() const OVERRIDE {
50 return scoped_ptr<EventTargetIterator>();
51 }
52
GetEventTargeter()53 virtual EventTargeter* GetEventTargeter() OVERRIDE {
54 return NULL;
55 }
56
57 TestTarget* parent_;
58 std::vector<int> handler_list_;
59 bool valid_;
60
61 DISALLOW_COPY_AND_ASSIGN(TestTarget);
62 };
63
64 class TestEventHandler : public EventHandler {
65 public:
TestEventHandler(int id)66 TestEventHandler(int id)
67 : id_(id),
68 event_result_(ER_UNHANDLED),
69 expect_pre_target_(false),
70 expect_post_target_(false),
71 received_pre_target_(false) {
72 }
73
~TestEventHandler()74 virtual ~TestEventHandler() {}
75
ReceivedEvent(Event * event)76 virtual void ReceivedEvent(Event* event) {
77 static_cast<TestTarget*>(event->target())->AddHandlerId(id_);
78 if (event->phase() == ui::EP_POSTTARGET) {
79 EXPECT_TRUE(expect_post_target_);
80 if (expect_pre_target_)
81 EXPECT_TRUE(received_pre_target_);
82 } else if (event->phase() == ui::EP_PRETARGET) {
83 EXPECT_TRUE(expect_pre_target_);
84 received_pre_target_ = true;
85 } else {
86 NOTREACHED();
87 }
88 }
89
set_event_result(EventResult result)90 void set_event_result(EventResult result) { event_result_ = result; }
91
set_expect_pre_target(bool expect)92 void set_expect_pre_target(bool expect) { expect_pre_target_ = expect; }
set_expect_post_target(bool expect)93 void set_expect_post_target(bool expect) { expect_post_target_ = expect; }
94
95 private:
96 // Overridden from EventHandler:
OnEvent(Event * event)97 virtual void OnEvent(Event* event) OVERRIDE {
98 ui::EventHandler::OnEvent(event);
99 ReceivedEvent(event);
100 SetStatusOnEvent(event);
101 }
102
SetStatusOnEvent(Event * event)103 void SetStatusOnEvent(Event* event) {
104 if (event_result_ & ui::ER_CONSUMED)
105 event->StopPropagation();
106 if (event_result_ & ui::ER_HANDLED)
107 event->SetHandled();
108 }
109
110 int id_;
111 EventResult event_result_;
112 bool expect_pre_target_;
113 bool expect_post_target_;
114 bool received_pre_target_;
115
116 DISALLOW_COPY_AND_ASSIGN(TestEventHandler);
117 };
118
119 class NonCancelableEvent : public Event {
120 public:
NonCancelableEvent()121 NonCancelableEvent()
122 : Event(ui::ET_CANCEL_MODE, ui::EventTimeForNow(), 0) {
123 set_cancelable(false);
124 }
125
~NonCancelableEvent()126 virtual ~NonCancelableEvent() {}
127
128 private:
129 DISALLOW_COPY_AND_ASSIGN(NonCancelableEvent);
130 };
131
132 // Destroys the dispatcher-delegate when it receives any event.
133 class EventHandlerDestroyDispatcherDelegate : public TestEventHandler {
134 public:
EventHandlerDestroyDispatcherDelegate(EventDispatcherDelegate * delegate,int id)135 EventHandlerDestroyDispatcherDelegate(EventDispatcherDelegate* delegate,
136 int id)
137 : TestEventHandler(id),
138 dispatcher_delegate_(delegate) {
139 }
140
~EventHandlerDestroyDispatcherDelegate()141 virtual ~EventHandlerDestroyDispatcherDelegate() {}
142
143 private:
ReceivedEvent(Event * event)144 virtual void ReceivedEvent(Event* event) OVERRIDE {
145 TestEventHandler::ReceivedEvent(event);
146 delete dispatcher_delegate_;
147 }
148
149 EventDispatcherDelegate* dispatcher_delegate_;
150
151 DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyDispatcherDelegate);
152 };
153
154 // Invalidates the target when it receives any event.
155 class InvalidateTargetEventHandler : public TestEventHandler {
156 public:
InvalidateTargetEventHandler(int id)157 explicit InvalidateTargetEventHandler(int id) : TestEventHandler(id) {}
~InvalidateTargetEventHandler()158 virtual ~InvalidateTargetEventHandler() {}
159
160 private:
ReceivedEvent(Event * event)161 virtual void ReceivedEvent(Event* event) OVERRIDE {
162 TestEventHandler::ReceivedEvent(event);
163 TestTarget* target = static_cast<TestTarget*>(event->target());
164 target->set_valid(false);
165 }
166
167 DISALLOW_COPY_AND_ASSIGN(InvalidateTargetEventHandler);
168 };
169
170 // Destroys a second event handler when this handler gets an event.
171 // Optionally also destroys the dispatcher.
172 class EventHandlerDestroyer : public TestEventHandler {
173 public:
EventHandlerDestroyer(int id,EventHandler * destroy)174 EventHandlerDestroyer(int id, EventHandler* destroy)
175 : TestEventHandler(id),
176 to_destroy_(destroy),
177 dispatcher_delegate_(NULL) {
178 }
179
~EventHandlerDestroyer()180 virtual ~EventHandlerDestroyer() {
181 CHECK(!to_destroy_);
182 }
183
set_dispatcher_delegate(EventDispatcherDelegate * dispatcher_delegate)184 void set_dispatcher_delegate(EventDispatcherDelegate* dispatcher_delegate) {
185 dispatcher_delegate_ = dispatcher_delegate;
186 }
187
188 private:
ReceivedEvent(Event * event)189 virtual void ReceivedEvent(Event* event) OVERRIDE {
190 TestEventHandler::ReceivedEvent(event);
191 delete to_destroy_;
192 to_destroy_ = NULL;
193
194 if (dispatcher_delegate_) {
195 delete dispatcher_delegate_;
196 dispatcher_delegate_ = NULL;
197 }
198 }
199
200 EventHandler* to_destroy_;
201 EventDispatcherDelegate* dispatcher_delegate_;
202
203 DISALLOW_COPY_AND_ASSIGN(EventHandlerDestroyer);
204 };
205
206 class TestEventDispatcher : public EventDispatcherDelegate {
207 public:
TestEventDispatcher()208 TestEventDispatcher() {}
209
~TestEventDispatcher()210 virtual ~TestEventDispatcher() {}
211
ProcessEvent(EventTarget * target,Event * event)212 void ProcessEvent(EventTarget* target, Event* event) {
213 EventDispatchDetails details = DispatchEvent(target, event);
214 if (details.dispatcher_destroyed)
215 return;
216 }
217
218 private:
219 // Overridden from EventDispatcherDelegate:
CanDispatchToTarget(EventTarget * target)220 virtual bool CanDispatchToTarget(EventTarget* target) OVERRIDE {
221 TestTarget* test_target = static_cast<TestTarget*>(target);
222 return test_target->valid();
223 }
224
225 DISALLOW_COPY_AND_ASSIGN(TestEventDispatcher);
226 };
227
228 } // namespace
229
TEST(EventDispatcherTest,EventDispatchOrder)230 TEST(EventDispatcherTest, EventDispatchOrder) {
231 TestEventDispatcher dispatcher;
232 TestTarget parent, child;
233 TestEventHandler h1(1), h2(2), h3(3), h4(4);
234 TestEventHandler h5(5), h6(6), h7(7), h8(8);
235
236 child.set_parent(&parent);
237
238 parent.AddPreTargetHandler(&h1);
239 parent.AddPreTargetHandler(&h2);
240
241 child.AddPreTargetHandler(&h3);
242 child.AddPreTargetHandler(&h4);
243
244 h1.set_expect_pre_target(true);
245 h2.set_expect_pre_target(true);
246 h3.set_expect_pre_target(true);
247 h4.set_expect_pre_target(true);
248
249 child.AddPostTargetHandler(&h5);
250 child.AddPostTargetHandler(&h6);
251
252 parent.AddPostTargetHandler(&h7);
253 parent.AddPostTargetHandler(&h8);
254
255 h5.set_expect_post_target(true);
256 h6.set_expect_post_target(true);
257 h7.set_expect_post_target(true);
258 h8.set_expect_post_target(true);
259
260 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
261 gfx::Point(3, 4), 0);
262 Event::DispatcherApi event_mod(&mouse);
263 dispatcher.ProcessEvent(&child, &mouse);
264 EXPECT_FALSE(mouse.stopped_propagation());
265 EXPECT_FALSE(mouse.handled());
266
267 {
268 int expected[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
269 EXPECT_EQ(
270 std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
271 child.handler_list());
272 }
273
274 child.Reset();
275 event_mod.set_phase(EP_PREDISPATCH);
276 event_mod.set_result(ER_UNHANDLED);
277
278 h1.set_event_result(ER_HANDLED);
279 dispatcher.ProcessEvent(&child, &mouse);
280 EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
281 EXPECT_FALSE(mouse.stopped_propagation());
282 EXPECT_TRUE(mouse.handled());
283 {
284 // |h1| marks the event as handled. So only the pre-target handlers should
285 // receive the event.
286 int expected[] = { 1, 2, 3, 4 };
287 EXPECT_EQ(
288 std::vector<int>(expected, expected + sizeof(expected) / sizeof(int)),
289 child.handler_list());
290 }
291
292 child.Reset();
293 event_mod.set_phase(EP_PREDISPATCH);
294 event_mod.set_result(ER_UNHANDLED);
295
296 int nexpected[] = { 1, 2, 3, 4, 5 };
297 h1.set_event_result(ER_UNHANDLED);
298 h5.set_event_result(ER_CONSUMED);
299 dispatcher.ProcessEvent(&child, &mouse);
300 EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
301 EXPECT_TRUE(mouse.stopped_propagation());
302 EXPECT_TRUE(mouse.handled());
303 EXPECT_EQ(
304 std::vector<int>(nexpected, nexpected + sizeof(nexpected) / sizeof(int)),
305 child.handler_list());
306
307 child.Reset();
308 event_mod.set_phase(EP_PREDISPATCH);
309 event_mod.set_result(ER_UNHANDLED);
310
311 int exp[] = { 1 };
312 h1.set_event_result(ER_CONSUMED);
313 dispatcher.ProcessEvent(&child, &mouse);
314 EXPECT_EQ(EP_POSTDISPATCH, mouse.phase());
315 EXPECT_TRUE(mouse.stopped_propagation());
316 EXPECT_TRUE(mouse.handled());
317 EXPECT_EQ(
318 std::vector<int>(exp, exp + sizeof(exp) / sizeof(int)),
319 child.handler_list());
320 }
321
322 // Tests that the event-phases are correct.
TEST(EventDispatcherTest,EventDispatchPhase)323 TEST(EventDispatcherTest, EventDispatchPhase) {
324 TestEventDispatcher dispatcher;
325 TestTarget target;
326
327 TestEventHandler handler(11);
328
329 target.AddPreTargetHandler(&handler);
330 target.AddPostTargetHandler(&handler);
331 handler.set_expect_pre_target(true);
332 handler.set_expect_post_target(true);
333
334 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
335 gfx::Point(3, 4), 0);
336 Event::DispatcherApi event_mod(&mouse);
337 dispatcher.ProcessEvent(&target, &mouse);
338 EXPECT_EQ(ER_UNHANDLED, mouse.result());
339
340 int handlers[] = { 11, 11 };
341 EXPECT_EQ(
342 std::vector<int>(handlers, handlers + sizeof(handlers) / sizeof(int)),
343 target.handler_list());
344 }
345
346 // Tests that if the dispatcher is destroyed in the middle of pre or post-target
347 // dispatching events, it doesn't cause a crash.
TEST(EventDispatcherTest,EventDispatcherDestroyedDuringDispatch)348 TEST(EventDispatcherTest, EventDispatcherDestroyedDuringDispatch) {
349 // Test for pre-target first.
350 {
351 TestEventDispatcher* dispatcher = new TestEventDispatcher();
352 TestTarget target;
353 EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
354 TestEventHandler h1(1), h2(2);
355
356 target.AddPreTargetHandler(&h1);
357 target.AddPreTargetHandler(&handler);
358 target.AddPreTargetHandler(&h2);
359
360 h1.set_expect_pre_target(true);
361 handler.set_expect_pre_target(true);
362 // |h2| should not receive any events at all since |handler| will have
363 // destroyed the dispatcher.
364 h2.set_expect_pre_target(false);
365
366 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
367 gfx::Point(3, 4), 0);
368 Event::DispatcherApi event_mod(&mouse);
369 dispatcher->ProcessEvent(&target, &mouse);
370 EXPECT_EQ(ER_CONSUMED, mouse.result());
371 EXPECT_EQ(2U, target.handler_list().size());
372 EXPECT_EQ(1, target.handler_list()[0]);
373 EXPECT_EQ(5, target.handler_list()[1]);
374 }
375
376 // Test for non-cancelable event.
377 {
378 TestEventDispatcher* dispatcher = new TestEventDispatcher();
379 TestTarget target;
380 EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
381 TestEventHandler h1(1), h2(2);
382
383 target.AddPreTargetHandler(&h1);
384 target.AddPreTargetHandler(&handler);
385 target.AddPreTargetHandler(&h2);
386
387 h1.set_expect_pre_target(true);
388 handler.set_expect_pre_target(true);
389 // |h2| should not receive any events at all since |handler| will have
390 // destroyed the dispatcher.
391 h2.set_expect_pre_target(false);
392
393 NonCancelableEvent event;
394 Event::DispatcherApi event_mod(&event);
395 dispatcher->ProcessEvent(&target, &event);
396 EXPECT_EQ(2U, target.handler_list().size());
397 EXPECT_EQ(1, target.handler_list()[0]);
398 EXPECT_EQ(5, target.handler_list()[1]);
399 }
400
401 // Now test for post-target.
402 {
403 TestEventDispatcher* dispatcher = new TestEventDispatcher();
404 TestTarget target;
405 EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
406 TestEventHandler h1(1), h2(2);
407
408 target.AddPostTargetHandler(&h1);
409 target.AddPostTargetHandler(&handler);
410 target.AddPostTargetHandler(&h2);
411
412 h1.set_expect_post_target(true);
413 handler.set_expect_post_target(true);
414 // |h2| should not receive any events at all since |handler| will have
415 // destroyed the dispatcher.
416 h2.set_expect_post_target(false);
417
418 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4),
419 gfx::Point(3, 4), 0);
420 Event::DispatcherApi event_mod(&mouse);
421 dispatcher->ProcessEvent(&target, &mouse);
422 EXPECT_EQ(ER_CONSUMED, mouse.result());
423 EXPECT_EQ(2U, target.handler_list().size());
424 EXPECT_EQ(1, target.handler_list()[0]);
425 EXPECT_EQ(5, target.handler_list()[1]);
426 }
427
428 // Test for non-cancelable event.
429 {
430 TestEventDispatcher* dispatcher = new TestEventDispatcher();
431 TestTarget target;
432 EventHandlerDestroyDispatcherDelegate handler(dispatcher, 5);
433 TestEventHandler h1(1), h2(2);
434
435 target.AddPostTargetHandler(&h1);
436 target.AddPostTargetHandler(&handler);
437 target.AddPostTargetHandler(&h2);
438
439 h1.set_expect_post_target(true);
440 handler.set_expect_post_target(true);
441 // |h2| should not receive any events at all since |handler| will have
442 // destroyed the dispatcher.
443 h2.set_expect_post_target(false);
444
445 NonCancelableEvent event;
446 Event::DispatcherApi event_mod(&event);
447 dispatcher->ProcessEvent(&target, &event);
448 EXPECT_EQ(2U, target.handler_list().size());
449 EXPECT_EQ(1, target.handler_list()[0]);
450 EXPECT_EQ(5, target.handler_list()[1]);
451 }
452 }
453
454 // Tests that a target becoming invalid in the middle of pre- or post-target
455 // event processing aborts processing.
TEST(EventDispatcherTest,EventDispatcherInvalidateTarget)456 TEST(EventDispatcherTest, EventDispatcherInvalidateTarget) {
457 TestEventDispatcher dispatcher;
458 TestTarget target;
459 TestEventHandler h1(1);
460 InvalidateTargetEventHandler invalidate_handler(2);
461 TestEventHandler h3(3);
462
463 target.AddPreTargetHandler(&h1);
464 target.AddPreTargetHandler(&invalidate_handler);
465 target.AddPreTargetHandler(&h3);
466
467 h1.set_expect_pre_target(true);
468 invalidate_handler.set_expect_pre_target(true);
469 // |h3| should not receive events as the target will be invalidated.
470 h3.set_expect_pre_target(false);
471
472 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0);
473 dispatcher.ProcessEvent(&target, &mouse);
474 EXPECT_FALSE(target.valid());
475 EXPECT_TRUE(mouse.stopped_propagation());
476 EXPECT_EQ(2U, target.handler_list().size());
477 EXPECT_EQ(1, target.handler_list()[0]);
478 EXPECT_EQ(2, target.handler_list()[1]);
479
480 // Test for non-cancelable event.
481 target.Reset();
482 NonCancelableEvent event;
483 dispatcher.ProcessEvent(&target, &event);
484 EXPECT_FALSE(target.valid());
485 EXPECT_TRUE(mouse.stopped_propagation());
486 EXPECT_EQ(2U, target.handler_list().size());
487 EXPECT_EQ(1, target.handler_list()[0]);
488 EXPECT_EQ(2, target.handler_list()[1]);
489 }
490
491 // Tests that if an event-handler gets destroyed during event-dispatch, it does
492 // not cause a crash.
TEST(EventDispatcherTest,EventHandlerDestroyedDuringDispatch)493 TEST(EventDispatcherTest, EventHandlerDestroyedDuringDispatch) {
494 {
495 TestEventDispatcher dispatcher;
496 TestTarget target;
497 TestEventHandler h1(1);
498 TestEventHandler* h3 = new TestEventHandler(3);
499 EventHandlerDestroyer handle_destroyer(2, h3);
500
501 target.AddPreTargetHandler(&h1);
502 target.AddPreTargetHandler(&handle_destroyer);
503 target.AddPreTargetHandler(h3);
504
505 h1.set_expect_pre_target(true);
506 handle_destroyer.set_expect_pre_target(true);
507 // |h3| should not receive events since |handle_destroyer| will have
508 // destroyed it.
509 h3->set_expect_pre_target(false);
510
511 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0);
512 dispatcher.ProcessEvent(&target, &mouse);
513 EXPECT_FALSE(mouse.stopped_propagation());
514 EXPECT_EQ(2U, target.handler_list().size());
515 EXPECT_EQ(1, target.handler_list()[0]);
516 EXPECT_EQ(2, target.handler_list()[1]);
517 }
518
519 // Test for non-cancelable events.
520 {
521 TestEventDispatcher dispatcher;
522 TestTarget target;
523 TestEventHandler h1(1);
524 TestEventHandler* h3 = new TestEventHandler(3);
525 EventHandlerDestroyer handle_destroyer(2, h3);
526
527 target.AddPreTargetHandler(&h1);
528 target.AddPreTargetHandler(&handle_destroyer);
529 target.AddPreTargetHandler(h3);
530
531 h1.set_expect_pre_target(true);
532 handle_destroyer.set_expect_pre_target(true);
533 h3->set_expect_pre_target(false);
534
535 NonCancelableEvent event;
536 dispatcher.ProcessEvent(&target, &event);
537 EXPECT_EQ(2U, target.handler_list().size());
538 EXPECT_EQ(1, target.handler_list()[0]);
539 EXPECT_EQ(2, target.handler_list()[1]);
540 }
541 }
542
543 // Tests that things work correctly if an event-handler destroys both the
544 // dispatcher and a handler.
TEST(EventDispatcherTest,EventHandlerAndDispatcherDestroyedDuringDispatch)545 TEST(EventDispatcherTest, EventHandlerAndDispatcherDestroyedDuringDispatch) {
546 {
547 TestEventDispatcher* dispatcher = new TestEventDispatcher();
548 TestTarget target;
549 TestEventHandler h1(1);
550 TestEventHandler* h3 = new TestEventHandler(3);
551 EventHandlerDestroyer destroyer(2, h3);
552
553 target.AddPreTargetHandler(&h1);
554 target.AddPreTargetHandler(&destroyer);
555 target.AddPreTargetHandler(h3);
556
557 h1.set_expect_pre_target(true);
558 destroyer.set_expect_pre_target(true);
559 destroyer.set_dispatcher_delegate(dispatcher);
560 // |h3| should not receive events since |destroyer| will have destroyed
561 // it.
562 h3->set_expect_pre_target(false);
563
564 MouseEvent mouse(ui::ET_MOUSE_MOVED, gfx::Point(3, 4), gfx::Point(3, 4), 0);
565 dispatcher->ProcessEvent(&target, &mouse);
566 EXPECT_TRUE(mouse.stopped_propagation());
567 EXPECT_EQ(2U, target.handler_list().size());
568 EXPECT_EQ(1, target.handler_list()[0]);
569 EXPECT_EQ(2, target.handler_list()[1]);
570 }
571
572 // Test for non-cancelable events.
573 {
574 TestEventDispatcher* dispatcher = new TestEventDispatcher();
575 TestTarget target;
576 TestEventHandler h1(1);
577 TestEventHandler* h3 = new TestEventHandler(3);
578 EventHandlerDestroyer destroyer(2, h3);
579
580 target.AddPreTargetHandler(&h1);
581 target.AddPreTargetHandler(&destroyer);
582 target.AddPreTargetHandler(h3);
583
584 h1.set_expect_pre_target(true);
585 destroyer.set_expect_pre_target(true);
586 destroyer.set_dispatcher_delegate(dispatcher);
587 // |h3| should not receive events since |destroyer| will have destroyed
588 // it.
589 h3->set_expect_pre_target(false);
590
591 NonCancelableEvent event;
592 dispatcher->ProcessEvent(&target, &event);
593 EXPECT_EQ(2U, target.handler_list().size());
594 EXPECT_EQ(1, target.handler_list()[0]);
595 EXPECT_EQ(2, target.handler_list()[1]);
596 }
597 }
598
599 } // namespace ui
600