Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / mojo / services / view_manager / view_manager_connection_unittest.cc
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 <string>
6 #include <vector>
7
8 #include "base/bind.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/message_loop/message_loop.h"
11 #include "base/run_loop.h"
12 #include "base/strings/stringprintf.h"
13 #include "mojo/public/cpp/bindings/allocation_scope.h"
14 #include "mojo/public/cpp/environment/environment.h"
15 #include "mojo/public/cpp/shell/service.h"
16 #include "mojo/services/public/cpp/view_manager/util.h"
17 #include "mojo/services/public/cpp/view_manager/view_manager_types.h"
18 #include "mojo/services/public/interfaces/view_manager/view_manager.mojom.h"
19 #include "mojo/shell/shell_test_helper.h"
20 #include "testing/gtest/include/gtest/gtest.h"
21
22 namespace mojo {
23 namespace services {
24 namespace view_manager {
25
26 namespace {
27
28 base::RunLoop* current_run_loop = NULL;
29
30 // Sets |current_run_loop| and runs it. It is expected that someone else quits
31 // the loop.
32 void DoRunLoop() {
33   base::RunLoop run_loop;
34   current_run_loop = &run_loop;
35   current_run_loop->Run();
36   current_run_loop = NULL;
37 }
38
39 // Converts |id| into a string.
40 std::string NodeIdToString(TransportNodeId id) {
41   return (id == 0) ? "null" :
42       base::StringPrintf("%d,%d", HiWord(id), LoWord(id));
43 }
44
45 // Boolean callback. Sets |result_cache| to the value of |result| and quits
46 // the run loop.
47 void BooleanCallback(bool* result_cache, bool result) {
48   *result_cache = result;
49   current_run_loop->Quit();
50 }
51
52 struct TestNode {
53   std::string ToString() const {
54     return base::StringPrintf("node=%s parent=%s view=%s",
55                               NodeIdToString(node_id).c_str(),
56                               NodeIdToString(parent_id).c_str(),
57                               NodeIdToString(view_id).c_str());
58   }
59
60   TransportNodeId parent_id;
61   TransportNodeId node_id;
62   TransportNodeId view_id;
63 };
64
65 // Callback that results in a vector of INodes. The INodes are converted to
66 // TestNodes.
67 void INodesCallback(std::vector<TestNode>* test_nodes,
68                     const mojo::Array<INode>& data) {
69   for (size_t i = 0; i < data.size(); ++i) {
70     TestNode node;
71     node.parent_id = data[i].parent_id();
72     node.node_id = data[i].node_id();
73     node.view_id = data[i].view_id();
74     test_nodes->push_back(node);
75   }
76   current_run_loop->Quit();
77 }
78
79 // Creates an id used for transport from the specified parameters.
80 TransportNodeId CreateNodeId(TransportConnectionId connection_id,
81                              TransportConnectionSpecificNodeId node_id) {
82   return (connection_id << 16) | node_id;
83 }
84
85 // Creates an id used for transport from the specified parameters.
86 TransportViewId CreateViewId(TransportConnectionId connection_id,
87                              TransportConnectionSpecificViewId view_id) {
88   return (connection_id << 16) | view_id;
89 }
90
91 // Creates a node with the specified id. Returns true on success. Blocks until
92 // we get back result from server.
93 bool CreateNode(IViewManager* view_manager,
94                 TransportConnectionSpecificNodeId id) {
95   bool result = false;
96   view_manager->CreateNode(id, base::Bind(&BooleanCallback, &result));
97   DoRunLoop();
98   return result;
99 }
100
101 // TODO(sky): make a macro for these functions, they are all the same.
102
103 // Deletes a node, blocking until done.
104 bool DeleteNode(IViewManager* view_manager,
105                 TransportNodeId node_id,
106                 TransportChangeId change_id) {
107   bool result = false;
108   view_manager->DeleteNode(node_id, change_id,
109                            base::Bind(&BooleanCallback, &result));
110   DoRunLoop();
111   return result;
112 }
113
114 // Adds a node, blocking until done.
115 bool AddNode(IViewManager* view_manager,
116              TransportNodeId parent,
117              TransportNodeId child,
118              TransportChangeId change_id) {
119   bool result = false;
120   view_manager->AddNode(parent, child, change_id,
121                         base::Bind(&BooleanCallback, &result));
122   DoRunLoop();
123   return result;
124 }
125
126 // Removes a node, blocking until done.
127 bool RemoveNodeFromParent(IViewManager* view_manager,
128                           TransportNodeId node_id,
129                           TransportChangeId change_id) {
130   bool result = false;
131   view_manager->RemoveNodeFromParent(node_id, change_id,
132                                      base::Bind(&BooleanCallback, &result));
133   DoRunLoop();
134   return result;
135 }
136
137 void GetNodeTree(IViewManager* view_manager,
138                  TransportNodeId node_id,
139                  std::vector<TestNode>* nodes) {
140   view_manager->GetNodeTree(node_id, base::Bind(&INodesCallback, nodes));
141   DoRunLoop();
142 }
143
144 // Creates a view with the specified id. Returns true on success. Blocks until
145 // we get back result from server.
146 bool CreateView(IViewManager* view_manager,
147                 TransportConnectionSpecificViewId id) {
148   bool result = false;
149   view_manager->CreateView(id, base::Bind(&BooleanCallback, &result));
150   DoRunLoop();
151   return result;
152 }
153
154 // Sets a view on the specified node. Returns true on success. Blocks until we
155 // get back result from server.
156 bool SetView(IViewManager* view_manager,
157              TransportNodeId node_id,
158              TransportViewId view_id,
159              TransportChangeId change_id) {
160   bool result = false;
161   view_manager->SetView(node_id, view_id, change_id,
162                         base::Bind(&BooleanCallback, &result));
163   DoRunLoop();
164   return result;
165 }
166
167 }  // namespace
168
169 typedef std::vector<std::string> Changes;
170
171 class ViewManagerClientImpl : public IViewManagerClient {
172  public:
173   ViewManagerClientImpl() : id_(0), quit_count_(0) {}
174
175   TransportConnectionId id() const { return id_; }
176
177   Changes GetAndClearChanges() {
178     Changes changes;
179     changes.swap(changes_);
180     return changes;
181   }
182
183   void WaitForId() {
184     if (id_ == 0)
185       DoRunLoop();
186   }
187
188   void DoRunLoopUntilChangesCount(size_t count) {
189     if (changes_.size() >= count)
190       return;
191     quit_count_ = count - changes_.size();
192     DoRunLoop();
193   }
194
195  private:
196   // IViewManagerClient overrides:
197   virtual void OnConnectionEstablished(
198       TransportConnectionId connection_id) OVERRIDE {
199     id_ = connection_id;
200     if (current_run_loop)
201       current_run_loop->Quit();
202   }
203   virtual void OnNodeHierarchyChanged(TransportNodeId node,
204                                       TransportNodeId new_parent,
205                                       TransportNodeId old_parent,
206                                       TransportChangeId change_id) OVERRIDE {
207     changes_.push_back(
208         base::StringPrintf(
209             "change_id=%d node=%s new_parent=%s old_parent=%s",
210             static_cast<int>(change_id), NodeIdToString(node).c_str(),
211             NodeIdToString(new_parent).c_str(),
212             NodeIdToString(old_parent).c_str()));
213     QuitIfNecessary();
214   }
215   virtual void OnNodeViewReplaced(TransportNodeId node,
216                                   TransportViewId new_view_id,
217                                   TransportViewId old_view_id,
218                                   TransportChangeId change_id) OVERRIDE {
219     changes_.push_back(
220         base::StringPrintf(
221             "change_id=%d node=%s new_view=%s old_view=%s",
222             static_cast<int>(change_id), NodeIdToString(node).c_str(),
223             NodeIdToString(new_view_id).c_str(),
224             NodeIdToString(old_view_id).c_str()));
225     QuitIfNecessary();
226   }
227   virtual void OnNodeDeleted(TransportNodeId node,
228                              TransportChangeId change_id) OVERRIDE {
229     changes_.push_back(
230         base::StringPrintf(
231             "change_id=%d node=%s deleted",
232             static_cast<int>(change_id), NodeIdToString(node).c_str()));
233     QuitIfNecessary();
234   }
235
236   void QuitIfNecessary() {
237     if (quit_count_ > 0 && --quit_count_ == 0)
238       current_run_loop->Quit();
239   }
240
241   TransportConnectionId id_;
242
243   // Used to determine when/if to quit the run loop.
244   size_t quit_count_;
245
246   Changes changes_;
247
248   DISALLOW_COPY_AND_ASSIGN(ViewManagerClientImpl);
249 };
250
251 class ViewManagerConnectionTest : public testing::Test {
252  public:
253   ViewManagerConnectionTest() {}
254
255   virtual void SetUp() OVERRIDE {
256     test_helper_.Init();
257
258     ConnectTo(test_helper_.shell(), "mojo:mojo_view_manager", &view_manager_);
259     view_manager_->SetClient(&client_);
260
261     client_.WaitForId();
262   }
263
264  protected:
265   // Creates a second connection to the viewmanager.
266   void EstablishSecondConnection() {
267     ConnectTo(test_helper_.shell(), "mojo:mojo_view_manager", &view_manager2_);
268     view_manager2_->SetClient(&client2_);
269
270     client2_.WaitForId();
271   }
272
273   void DestroySecondConnection() {
274     view_manager2_.reset();
275   }
276
277   base::MessageLoop loop_;
278   shell::ShellTestHelper test_helper_;
279
280   ViewManagerClientImpl client_;
281   IViewManagerPtr view_manager_;
282
283   ViewManagerClientImpl client2_;
284   IViewManagerPtr view_manager2_;
285
286   DISALLOW_COPY_AND_ASSIGN(ViewManagerConnectionTest);
287 };
288
289 // Verifies client gets a valid id.
290 TEST_F(ViewManagerConnectionTest, ValidId) {
291   // All these tests assume 1 for the client id. The only real assertion here is
292   // the client id is not zero, but adding this as rest of code here assumes 1.
293   EXPECT_EQ(1, client_.id());
294 }
295
296 // Verifies two clients/connections get different ids.
297 TEST_F(ViewManagerConnectionTest, TwoClientsGetDifferentConnectionIds) {
298   EstablishSecondConnection();
299   EXPECT_NE(0, client2_.id());
300   EXPECT_NE(client_.id(), client2_.id());
301 }
302
303 // Verifies client gets a valid id.
304 TEST_F(ViewManagerConnectionTest, CreateNode) {
305   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
306
307   // Can't create a node with the same id.
308   ASSERT_FALSE(CreateNode(view_manager_.get(), 1));
309 }
310
311 // Verifies hierarchy changes.
312 TEST_F(ViewManagerConnectionTest, AddRemoveNotify) {
313   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
314   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
315
316   EXPECT_TRUE(client_.GetAndClearChanges().empty());
317
318   // Make 2 a child of 1.
319   {
320     AllocationScope scope;
321     ASSERT_TRUE(AddNode(view_manager_.get(),
322                         CreateNodeId(client_.id(), 1),
323                         CreateNodeId(client_.id(), 2),
324                         11));
325     Changes changes(client_.GetAndClearChanges());
326     ASSERT_EQ(1u, changes.size());
327     EXPECT_EQ("change_id=11 node=1,2 new_parent=1,1 old_parent=null",
328               changes[0]);
329   }
330
331   // Remove 2 from its parent.
332   {
333     AllocationScope scope;
334     ASSERT_TRUE(RemoveNodeFromParent(view_manager_.get(),
335                                      CreateNodeId(client_.id(), 2),
336                                      101));
337     Changes changes(client_.GetAndClearChanges());
338     ASSERT_EQ(1u, changes.size());
339     EXPECT_EQ("change_id=101 node=1,2 new_parent=null old_parent=1,1",
340               changes[0]);
341   }
342 }
343
344 // Verifies hierarchy changes are sent to multiple clients.
345 TEST_F(ViewManagerConnectionTest, AddRemoveNotifyMultipleConnections) {
346   EstablishSecondConnection();
347
348   // Create two nodes in first connection.
349   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
350   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
351
352   EXPECT_TRUE(client_.GetAndClearChanges().empty());
353   EXPECT_TRUE(client2_.GetAndClearChanges().empty());
354
355   // Make 2 a child of 1.
356   {
357     AllocationScope scope;
358     ASSERT_TRUE(AddNode(view_manager_.get(),
359                         CreateNodeId(client_.id(), 1),
360                         CreateNodeId(client_.id(), 2),
361                         11));
362     Changes changes(client_.GetAndClearChanges());
363     ASSERT_EQ(1u, changes.size());
364     EXPECT_EQ("change_id=11 node=1,2 new_parent=1,1 old_parent=null",
365               changes[0]);
366   }
367
368   // Second client should also have received the change.
369   {
370     client2_.DoRunLoopUntilChangesCount(1);
371     Changes changes(client2_.GetAndClearChanges());
372     ASSERT_EQ(1u, changes.size());
373     EXPECT_EQ("change_id=0 node=1,2 new_parent=1,1 old_parent=null",
374               changes[0]);
375   }
376 }
377
378 // Verifies adding to root sends right notifications.
379 TEST_F(ViewManagerConnectionTest, AddToRoot) {
380   ASSERT_TRUE(CreateNode(view_manager_.get(), 21));
381   ASSERT_TRUE(CreateNode(view_manager_.get(), 3));
382   EXPECT_TRUE(client_.GetAndClearChanges().empty());
383
384   // Make 3 a child of 21.
385   {
386     AllocationScope scope;
387     ASSERT_TRUE(AddNode(view_manager_.get(),
388                         CreateNodeId(client_.id(), 21),
389                         CreateNodeId(client_.id(), 3),
390                         11));
391     Changes changes(client_.GetAndClearChanges());
392     ASSERT_EQ(1u, changes.size());
393     EXPECT_EQ("change_id=11 node=1,3 new_parent=1,21 old_parent=null",
394               changes[0]);
395   }
396
397   // Make 21 a child of the root.
398   {
399     AllocationScope scope;
400     ASSERT_TRUE(AddNode(view_manager_.get(),
401                         CreateNodeId(0, 1),
402                         CreateNodeId(client_.id(), 21),
403                         44));
404     Changes changes(client_.GetAndClearChanges());
405     ASSERT_EQ(1u, changes.size());
406     EXPECT_EQ("change_id=44 node=1,21 new_parent=0,1 old_parent=null",
407               changes[0]);
408   }
409 }
410
411 // Verifies DeleteNode works.
412 TEST_F(ViewManagerConnectionTest, DeleteNode) {
413   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
414   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
415   EXPECT_TRUE(client_.GetAndClearChanges().empty());
416
417   // Make 2 a child of 1.
418   {
419     AllocationScope scope;
420     ASSERT_TRUE(AddNode(view_manager_.get(),
421                         CreateNodeId(client_.id(), 1),
422                         CreateNodeId(client_.id(), 2),
423                         11));
424     Changes changes(client_.GetAndClearChanges());
425     ASSERT_EQ(1u, changes.size());
426     EXPECT_EQ("change_id=11 node=1,2 new_parent=1,1 old_parent=null",
427               changes[0]);
428   }
429
430   // Add 1 to the root
431   {
432     AllocationScope scope;
433     ASSERT_TRUE(AddNode(view_manager_.get(),
434                         CreateNodeId(0, 1),
435                         CreateNodeId(client_.id(), 1),
436                         101));
437     Changes changes(client_.GetAndClearChanges());
438     ASSERT_EQ(1u, changes.size());
439     EXPECT_EQ("change_id=101 node=1,1 new_parent=0,1 old_parent=null",
440               changes[0]);
441   }
442
443   // Delete 1.
444   {
445     AllocationScope scope;
446     ASSERT_TRUE(DeleteNode(view_manager_.get(),
447                            CreateNodeId(client_.id(), 1),
448                            121));
449     Changes changes(client_.GetAndClearChanges());
450     ASSERT_EQ(3u, changes.size());
451     EXPECT_EQ("change_id=121 node=1,1 new_parent=null old_parent=0,1",
452               changes[0]);
453     EXPECT_EQ("change_id=121 node=1,2 new_parent=null old_parent=1,1",
454               changes[1]);
455     EXPECT_EQ("change_id=121 node=1,1 deleted", changes[2]);
456   }
457 }
458
459 // Assertions around setting a view.
460 TEST_F(ViewManagerConnectionTest, SetView) {
461   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
462   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
463   ASSERT_TRUE(CreateView(view_manager_.get(), 11));
464   EXPECT_TRUE(client_.GetAndClearChanges().empty());
465
466   // Set view 11 on node 1.
467   {
468     ASSERT_TRUE(SetView(view_manager_.get(),
469                         CreateNodeId(client_.id(), 1),
470                         CreateViewId(client_.id(), 11),
471                         21));
472     Changes changes(client_.GetAndClearChanges());
473     ASSERT_EQ(1u, changes.size());
474     EXPECT_EQ("change_id=21 node=1,1 new_view=1,11 old_view=null",
475               changes[0]);
476   }
477
478   // Set view 11 on node 2.
479   {
480     ASSERT_TRUE(SetView(view_manager_.get(),
481                         CreateNodeId(client_.id(), 2),
482                         CreateViewId(client_.id(), 11),
483                         22));
484     Changes changes(client_.GetAndClearChanges());
485     ASSERT_EQ(2u, changes.size());
486     EXPECT_EQ("change_id=22 node=1,1 new_view=null old_view=1,11",
487               changes[0]);
488     EXPECT_EQ("change_id=22 node=1,2 new_view=1,11 old_view=null",
489               changes[1]);
490   }
491 }
492
493 // Verifies deleting a node with a view sends correct notifications.
494 TEST_F(ViewManagerConnectionTest, DeleteNodeWithView) {
495   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
496   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
497   ASSERT_TRUE(CreateView(view_manager_.get(), 11));
498   EXPECT_TRUE(client_.GetAndClearChanges().empty());
499
500   // Set view 11 on node 1.
501   ASSERT_TRUE(SetView(view_manager_.get(),
502                       CreateNodeId(client_.id(), 1),
503                       CreateViewId(client_.id(), 11),
504                       21));
505   client_.GetAndClearChanges();
506
507   // Delete node 1.
508   {
509     ASSERT_TRUE(DeleteNode(view_manager_.get(),
510                            CreateNodeId(client_.id(), 1),
511                            121));
512     Changes changes(client_.GetAndClearChanges());
513     ASSERT_EQ(2u, changes.size());
514     EXPECT_EQ("change_id=121 node=1,1 new_view=null old_view=1,11",
515               changes[0]);
516     EXPECT_EQ("change_id=121 node=1,1 deleted", changes[1]);
517   }
518
519   // Set view 11 on node 2.
520   {
521     ASSERT_TRUE(SetView(view_manager_.get(),
522                         CreateNodeId(client_.id(), 2),
523                         CreateViewId(client_.id(), 11),
524                         22));
525     Changes changes(client_.GetAndClearChanges());
526     ASSERT_EQ(1u, changes.size());
527     EXPECT_EQ("change_id=22 node=1,2 new_view=1,11 old_view=null", changes[0]);
528   }
529 }
530
531 // Sets view from one connection on another.
532 TEST_F(ViewManagerConnectionTest, SetViewFromSecondConnection) {
533   EstablishSecondConnection();
534
535   // Create two nodes in first connection.
536   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
537   ASSERT_TRUE(CreateNode(view_manager_.get(), 2));
538
539   EXPECT_TRUE(client_.GetAndClearChanges().empty());
540   EXPECT_TRUE(client2_.GetAndClearChanges().empty());
541
542   // Create a view in the second connection.
543   ASSERT_TRUE(CreateView(view_manager2_.get(), 51));
544
545   // Attach view to node 1 in the first connection.
546   {
547     ASSERT_TRUE(SetView(view_manager2_.get(),
548                         CreateNodeId(client_.id(), 1),
549                         CreateViewId(client2_.id(), 51),
550                         22));
551     client_.DoRunLoopUntilChangesCount(1);
552     Changes changes(client_.GetAndClearChanges());
553     ASSERT_EQ(1u, changes.size());
554     EXPECT_EQ("change_id=0 node=1,1 new_view=2,51 old_view=null", changes[0]);
555
556     client2_.DoRunLoopUntilChangesCount(1);
557     changes = client2_.GetAndClearChanges();
558     ASSERT_EQ(1u, changes.size());
559     EXPECT_EQ("change_id=22 node=1,1 new_view=2,51 old_view=null", changes[0]);
560   }
561
562   // Shutdown the second connection and verify view is removed.
563   {
564     DestroySecondConnection();
565     client_.DoRunLoopUntilChangesCount(1);
566
567     Changes changes(client_.GetAndClearChanges());
568     ASSERT_EQ(1u, changes.size());
569     EXPECT_EQ("change_id=0 node=1,1 new_view=null old_view=2,51", changes[0]);
570   }
571 }
572
573 // Assertions for GetNodeTree.
574 TEST_F(ViewManagerConnectionTest, GetNodeTree) {
575   EstablishSecondConnection();
576
577   // Create two nodes in first connection, 1 and 11 (11 is a child of 1).
578   ASSERT_TRUE(CreateNode(view_manager_.get(), 1));
579   ASSERT_TRUE(CreateNode(view_manager_.get(), 11));
580   ASSERT_TRUE(AddNode(view_manager_.get(),
581                       CreateNodeId(0, 1),
582                       CreateNodeId(client_.id(), 1),
583                       101));
584   ASSERT_TRUE(AddNode(view_manager_.get(),
585                       CreateNodeId(client_.id(), 1),
586                       CreateNodeId(client_.id(), 11),
587                       102));
588
589   // Create two nodes in second connection, 2 and 3, both children of the root.
590   ASSERT_TRUE(CreateNode(view_manager2_.get(), 2));
591   ASSERT_TRUE(CreateNode(view_manager2_.get(), 3));
592   ASSERT_TRUE(AddNode(view_manager2_.get(),
593                       CreateNodeId(0, 1),
594                       CreateNodeId(client2_.id(), 2),
595                       99));
596   ASSERT_TRUE(AddNode(view_manager2_.get(),
597                       CreateNodeId(0, 1),
598                       CreateNodeId(client2_.id(), 3),
599                       99));
600
601   // Attach view to node 11 in the first connection.
602   ASSERT_TRUE(CreateView(view_manager_.get(), 51));
603   ASSERT_TRUE(SetView(view_manager_.get(),
604                       CreateNodeId(client_.id(), 11),
605                       CreateViewId(client_.id(), 51),
606                       22));
607
608   // Verifies GetNodeTree() on the root.
609   {
610     AllocationScope scope;
611     std::vector<TestNode> nodes;
612     GetNodeTree(view_manager2_.get(), CreateNodeId(0, 1), &nodes);
613     ASSERT_EQ(5u, nodes.size());
614     EXPECT_EQ("node=0,1 parent=null view=null", nodes[0].ToString());
615     EXPECT_EQ("node=1,1 parent=0,1 view=null", nodes[1].ToString());
616     EXPECT_EQ("node=1,11 parent=1,1 view=1,51", nodes[2].ToString());
617     EXPECT_EQ("node=2,2 parent=0,1 view=null", nodes[3].ToString());
618     EXPECT_EQ("node=2,3 parent=0,1 view=null", nodes[4].ToString());
619   }
620
621   // Verifies GetNodeTree() on the node 1,1.
622   {
623     AllocationScope scope;
624     std::vector<TestNode> nodes;
625     GetNodeTree(view_manager2_.get(), CreateNodeId(1, 1), &nodes);
626     ASSERT_EQ(2u, nodes.size());
627     EXPECT_EQ("node=1,1 parent=0,1 view=null", nodes[0].ToString());
628     EXPECT_EQ("node=1,11 parent=1,1 view=1,51", nodes[1].ToString());
629   }
630 }
631
632 }  // namespace view_manager
633 }  // namespace services
634 }  // namespace mojo