Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / mojo / services / network / http_server_apptest.cc
blob32c249c32bd1bce396c4cc73bc586d886bb91e91
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.
5 #include "base/logging.h"
6 #include "base/macros.h"
7 #include "base/memory/linked_ptr.h"
8 #include "base/memory/ref_counted.h"
9 #include "base/memory/scoped_ptr.h"
10 #include "base/run_loop.h"
11 #include "base/strings/string_util.h"
12 #include "base/strings/stringprintf.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 "mojo/common/data_pipe_utils.h"
17 #include "mojo/services/network/net_address_type_converters.h"
18 #include "mojo/services/network/public/cpp/web_socket_read_queue.h"
19 #include "mojo/services/network/public/cpp/web_socket_write_queue.h"
20 #include "mojo/services/network/public/interfaces/http_connection.mojom.h"
21 #include "mojo/services/network/public/interfaces/http_message.mojom.h"
22 #include "mojo/services/network/public/interfaces/http_server.mojom.h"
23 #include "mojo/services/network/public/interfaces/net_address.mojom.h"
24 #include "mojo/services/network/public/interfaces/network_service.mojom.h"
25 #include "mojo/services/network/public/interfaces/web_socket.mojom.h"
26 #include "net/base/io_buffer.h"
27 #include "net/base/net_errors.h"
28 #include "net/base/test_completion_callback.h"
29 #include "net/http/http_response_headers.h"
30 #include "net/http/http_util.h"
31 #include "net/socket/tcp_client_socket.h"
32 #include "testing/gtest/include/gtest/gtest.h"
34 namespace mojo {
35 namespace {
37 const int kMaxExpectedResponseLength = 2048;
39 NetAddressPtr GetLocalHostWithAnyPort() {
40 NetAddressPtr addr(NetAddress::New());
41 addr->family = NET_ADDRESS_FAMILY_IPV4;
42 addr->ipv4 = NetAddressIPv4::New();
43 addr->ipv4->port = 0;
44 addr->ipv4->addr.resize(4);
45 addr->ipv4->addr[0] = 127;
46 addr->ipv4->addr[1] = 0;
47 addr->ipv4->addr[2] = 0;
48 addr->ipv4->addr[3] = 1;
50 return addr.Pass();
53 using TestHeaders = std::vector<std::pair<std::string, std::string>>;
55 struct TestRequest {
56 std::string method;
57 std::string url;
58 TestHeaders headers;
59 scoped_ptr<std::string> body;
62 struct TestResponse {
63 uint32_t status_code;
64 TestHeaders headers;
65 scoped_ptr<std::string> body;
68 std::string MakeRequestMessage(const TestRequest& data) {
69 std::string message = data.method + " " + data.url + " HTTP/1.1\r\n";
70 for (const auto& item : data.headers)
71 message += item.first + ": " + item.second + "\r\n";
72 message += "\r\n";
73 if (data.body)
74 message += *data.body;
76 return message;
79 HttpResponsePtr MakeResponseStruct(const TestResponse& data) {
80 HttpResponsePtr response(HttpResponse::New());
81 response->status_code = data.status_code;
82 response->headers.resize(data.headers.size());
83 size_t index = 0;
84 for (const auto& item : data.headers) {
85 HttpHeaderPtr header(HttpHeader::New());
86 header->name = item.first;
87 header->value = item.second;
88 response->headers[index++] = header.Pass();
91 if (data.body) {
92 uint32_t num_bytes = static_cast<uint32_t>(data.body->size());
93 MojoCreateDataPipeOptions options;
94 options.struct_size = sizeof(MojoCreateDataPipeOptions);
95 options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
96 options.element_num_bytes = 1;
97 options.capacity_num_bytes = num_bytes;
98 DataPipe data_pipe(options);
99 response->body = data_pipe.consumer_handle.Pass();
100 MojoResult result =
101 WriteDataRaw(data_pipe.producer_handle.get(), data.body->data(),
102 &num_bytes, MOJO_WRITE_DATA_FLAG_ALL_OR_NONE);
103 EXPECT_EQ(MOJO_RESULT_OK, result);
106 return response.Pass();
109 void CheckHeaders(const TestHeaders& expected,
110 const Array<HttpHeaderPtr>& headers) {
111 // The server impl fiddles with Content-Length and Content-Type. So we don't
112 // do a strict check here.
113 std::map<std::string, std::string> header_map;
114 for (size_t i = 0; i < headers.size(); ++i) {
115 std::string lower_name =
116 base::StringToLowerASCII(headers[i]->name.To<std::string>());
117 header_map[lower_name] = headers[i]->value;
120 for (const auto& item : expected) {
121 std::string lower_name = base::StringToLowerASCII(item.first);
122 EXPECT_NE(header_map.end(), header_map.find(lower_name));
123 EXPECT_EQ(item.second, header_map[lower_name]);
127 void CheckRequest(const TestRequest& expected, HttpRequestPtr request) {
128 EXPECT_EQ(expected.method, request->method);
129 EXPECT_EQ(expected.url, request->url);
130 CheckHeaders(expected.headers, request->headers);
131 if (expected.body) {
132 EXPECT_TRUE(request->body.is_valid());
133 std::string body;
134 common::BlockingCopyToString(request->body.Pass(), &body);
135 EXPECT_EQ(*expected.body, body);
136 } else {
137 EXPECT_FALSE(request->body.is_valid());
141 void CheckResponse(const TestResponse& expected, const std::string& response) {
142 int header_end =
143 net::HttpUtil::LocateEndOfHeaders(response.c_str(), response.size());
144 std::string assembled_headers =
145 net::HttpUtil::AssembleRawHeaders(response.c_str(), header_end);
146 scoped_refptr<net::HttpResponseHeaders> parsed_headers(
147 new net::HttpResponseHeaders(assembled_headers));
148 EXPECT_EQ(expected.status_code,
149 static_cast<uint32_t>(parsed_headers->response_code()));
150 for (const auto& item : expected.headers)
151 EXPECT_TRUE(parsed_headers->HasHeaderValue(item.first, item.second));
153 if (expected.body) {
154 EXPECT_NE(-1, header_end);
155 std::string body(response, static_cast<size_t>(header_end));
156 EXPECT_EQ(*expected.body, body);
157 } else {
158 EXPECT_EQ(response.size(), static_cast<size_t>(header_end));
162 class TestHttpClient {
163 public:
164 TestHttpClient() : connect_result_(net::OK) {}
166 void Connect(const net::IPEndPoint& address) {
167 net::AddressList addresses(address);
168 net::NetLog::Source source;
169 socket_.reset(new net::TCPClientSocket(addresses, NULL, source));
171 base::RunLoop run_loop;
172 connect_result_ = socket_->Connect(base::Bind(&TestHttpClient::OnConnect,
173 base::Unretained(this),
174 run_loop.QuitClosure()));
175 if (connect_result_ == net::ERR_IO_PENDING)
176 run_loop.Run();
178 ASSERT_EQ(net::OK, connect_result_);
181 void Send(const std::string& data) {
182 write_buffer_ = new net::DrainableIOBuffer(new net::StringIOBuffer(data),
183 data.length());
184 Write();
187 // Note: This method determines the end of the response only by Content-Length
188 // and connection termination. Besides, it doesn't truncate at the end of the
189 // response, so |message| may return more data (e.g., part of the next
190 // response).
191 void ReadResponse(std::string* message) {
192 if (!Read(message, 1))
193 return;
194 while (!IsCompleteResponse(*message)) {
195 std::string chunk;
196 if (!Read(&chunk, 1))
197 return;
198 message->append(chunk);
200 return;
203 private:
204 void OnConnect(const base::Closure& quit_loop, int result) {
205 connect_result_ = result;
206 quit_loop.Run();
209 void Write() {
210 int result = socket_->Write(
211 write_buffer_.get(), write_buffer_->BytesRemaining(),
212 base::Bind(&TestHttpClient::OnWrite, base::Unretained(this)));
213 if (result != net::ERR_IO_PENDING)
214 OnWrite(result);
217 void OnWrite(int result) {
218 ASSERT_GT(result, 0);
219 write_buffer_->DidConsume(result);
220 if (write_buffer_->BytesRemaining())
221 Write();
224 bool Read(std::string* message, int expected_bytes) {
225 int total_bytes_received = 0;
226 message->clear();
227 while (total_bytes_received < expected_bytes) {
228 net::TestCompletionCallback callback;
229 ReadInternal(callback.callback());
230 int bytes_received = callback.WaitForResult();
231 if (bytes_received <= 0)
232 return false;
234 total_bytes_received += bytes_received;
235 message->append(read_buffer_->data(), bytes_received);
237 return true;
240 void ReadInternal(const net::CompletionCallback& callback) {
241 read_buffer_ = new net::IOBufferWithSize(kMaxExpectedResponseLength);
242 int result =
243 socket_->Read(read_buffer_.get(), kMaxExpectedResponseLength, callback);
244 if (result != net::ERR_IO_PENDING)
245 callback.Run(result);
248 bool IsCompleteResponse(const std::string& response) {
249 // Check end of headers first.
250 int end_of_headers =
251 net::HttpUtil::LocateEndOfHeaders(response.data(), response.size());
252 if (end_of_headers < 0)
253 return false;
255 // Return true if response has data equal to or more than content length.
256 int64 body_size = static_cast<int64>(response.size()) - end_of_headers;
257 DCHECK_LE(0, body_size);
258 scoped_refptr<net::HttpResponseHeaders> headers(
259 new net::HttpResponseHeaders(net::HttpUtil::AssembleRawHeaders(
260 response.data(), end_of_headers)));
261 return body_size >= headers->GetContentLength();
264 scoped_refptr<net::IOBufferWithSize> read_buffer_;
265 scoped_refptr<net::DrainableIOBuffer> write_buffer_;
266 scoped_ptr<net::TCPClientSocket> socket_;
267 int connect_result_;
269 DISALLOW_COPY_AND_ASSIGN(TestHttpClient);
272 class WebSocketClientImpl : public WebSocketClient {
273 public:
274 explicit WebSocketClientImpl()
275 : binding_(this, &client_ptr_),
276 wait_for_message_count_(0),
277 run_loop_(nullptr) {}
278 ~WebSocketClientImpl() override {}
280 // Establishes a connection from the client side.
281 void Connect(WebSocketPtr web_socket, const std::string& url) {
282 web_socket_ = web_socket.Pass();
284 DataPipe data_pipe;
285 send_stream_ = data_pipe.producer_handle.Pass();
286 write_send_stream_.reset(new WebSocketWriteQueue(send_stream_.get()));
288 web_socket_->Connect(url, Array<String>(0), "http://example.com",
289 data_pipe.consumer_handle.Pass(), client_ptr_.Pass());
292 // Establishes a connection from the server side.
293 void AcceptConnectRequest(
294 const HttpConnectionDelegate::OnReceivedWebSocketRequestCallback&
295 callback) {
296 InterfaceRequest<WebSocket> web_socket_request = GetProxy(&web_socket_);
298 DataPipe data_pipe;
299 send_stream_ = data_pipe.producer_handle.Pass();
300 write_send_stream_.reset(new WebSocketWriteQueue(send_stream_.get()));
302 callback.Run(web_socket_request.Pass(), data_pipe.consumer_handle.Pass(),
303 client_ptr_.Pass());
306 void WaitForConnectCompletion() {
307 DCHECK(!run_loop_);
309 if (receive_stream_.is_valid())
310 return;
312 base::RunLoop run_loop;
313 run_loop_ = &run_loop;
314 run_loop.Run();
315 run_loop_ = nullptr;
318 void Send(const std::string& message) {
319 DCHECK(!message.empty());
321 uint32_t size = static_cast<uint32_t>(message.size());
322 write_send_stream_->Write(
323 &message[0], size,
324 base::Bind(&WebSocketClientImpl::OnFinishedWritingSendStream,
325 base::Unretained(this), size));
328 void WaitForMessage(size_t count) {
329 DCHECK(!run_loop_);
331 if (received_messages_.size() >= count)
332 return;
333 wait_for_message_count_ = count;
334 base::RunLoop run_loop;
335 run_loop_ = &run_loop;
336 run_loop.Run();
337 run_loop_ = nullptr;
340 std::vector<std::string>& received_messages() { return received_messages_; }
342 private:
343 // WebSocketClient implementation.
344 void DidConnect(const String& selected_subprotocol,
345 const String& extensions,
346 ScopedDataPipeConsumerHandle receive_stream) override {
347 receive_stream_ = receive_stream.Pass();
348 read_receive_stream_.reset(new WebSocketReadQueue(receive_stream_.get()));
350 web_socket_->FlowControl(2048);
351 if (run_loop_)
352 run_loop_->Quit();
355 void DidReceiveData(bool fin,
356 WebSocket::MessageType type,
357 uint32_t num_bytes) override {
358 DCHECK(num_bytes > 0);
360 read_receive_stream_->Read(
361 num_bytes,
362 base::Bind(&WebSocketClientImpl::OnFinishedReadingReceiveStream,
363 base::Unretained(this), num_bytes));
366 void DidReceiveFlowControl(int64_t quota) override {}
368 void DidFail(const String& message) override {}
370 void DidClose(bool was_clean, uint16_t code, const String& reason) override {}
372 void OnFinishedWritingSendStream(uint32_t num_bytes, const char* buffer) {
373 EXPECT_TRUE(buffer);
375 web_socket_->Send(true, WebSocket::MESSAGE_TYPE_TEXT, num_bytes);
378 void OnFinishedReadingReceiveStream(uint32_t num_bytes, const char* data) {
379 EXPECT_TRUE(data);
381 received_messages_.push_back(std::string(data, num_bytes));
382 if (run_loop_ && received_messages_.size() >= wait_for_message_count_) {
383 wait_for_message_count_ = 0;
384 run_loop_->Quit();
388 WebSocketClientPtr client_ptr_;
389 Binding<WebSocketClient> binding_;
390 WebSocketPtr web_socket_;
392 ScopedDataPipeProducerHandle send_stream_;
393 scoped_ptr<WebSocketWriteQueue> write_send_stream_;
395 ScopedDataPipeConsumerHandle receive_stream_;
396 scoped_ptr<WebSocketReadQueue> read_receive_stream_;
398 std::vector<std::string> received_messages_;
399 size_t wait_for_message_count_;
401 // Pointing to a stack-allocated RunLoop instance.
402 base::RunLoop* run_loop_;
404 DISALLOW_COPY_AND_ASSIGN(WebSocketClientImpl);
407 class HttpConnectionDelegateImpl : public HttpConnectionDelegate {
408 public:
409 struct PendingRequest {
410 HttpRequestPtr request;
411 OnReceivedRequestCallback callback;
414 HttpConnectionDelegateImpl(HttpConnectionPtr connection,
415 InterfaceRequest<HttpConnectionDelegate> request)
416 : connection_(connection.Pass()),
417 binding_(this, request.Pass()),
418 wait_for_request_count_(0),
419 run_loop_(nullptr) {}
420 ~HttpConnectionDelegateImpl() override {}
422 // HttpConnectionDelegate implementation:
423 void OnReceivedRequest(HttpRequestPtr request,
424 const OnReceivedRequestCallback& callback) override {
425 linked_ptr<PendingRequest> pending_request(new PendingRequest);
426 pending_request->request = request.Pass();
427 pending_request->callback = callback;
428 pending_requests_.push_back(pending_request);
429 if (run_loop_ && pending_requests_.size() >= wait_for_request_count_) {
430 wait_for_request_count_ = 0;
431 run_loop_->Quit();
435 void OnReceivedWebSocketRequest(
436 HttpRequestPtr request,
437 const OnReceivedWebSocketRequestCallback& callback) override {
438 web_socket_.reset(new WebSocketClientImpl());
440 web_socket_->AcceptConnectRequest(callback);
442 if (run_loop_)
443 run_loop_->Quit();
446 void SendResponse(HttpResponsePtr response) {
447 ASSERT_FALSE(pending_requests_.empty());
448 linked_ptr<PendingRequest> request = pending_requests_[0];
449 pending_requests_.erase(pending_requests_.begin());
450 request->callback.Run(response.Pass());
453 void WaitForRequest(size_t count) {
454 DCHECK(!run_loop_);
456 if (pending_requests_.size() >= count)
457 return;
459 wait_for_request_count_ = count;
460 base::RunLoop run_loop;
461 run_loop_ = &run_loop;
462 run_loop.Run();
463 run_loop_ = nullptr;
466 void WaitForWebSocketRequest() {
467 DCHECK(!run_loop_);
469 if (web_socket_)
470 return;
472 base::RunLoop run_loop;
473 run_loop_ = &run_loop;
474 run_loop.Run();
475 run_loop_ = nullptr;
478 std::vector<linked_ptr<PendingRequest>>& pending_requests() {
479 return pending_requests_;
482 WebSocketClientImpl* web_socket() { return web_socket_.get(); }
484 private:
485 HttpConnectionPtr connection_;
486 Binding<HttpConnectionDelegate> binding_;
487 std::vector<linked_ptr<PendingRequest>> pending_requests_;
488 size_t wait_for_request_count_;
489 scoped_ptr<WebSocketClientImpl> web_socket_;
491 // Pointing to a stack-allocated RunLoop instance.
492 base::RunLoop* run_loop_;
494 DISALLOW_COPY_AND_ASSIGN(HttpConnectionDelegateImpl);
497 class HttpServerDelegateImpl : public HttpServerDelegate {
498 public:
499 explicit HttpServerDelegateImpl(HttpServerDelegatePtr* delegate_ptr)
500 : binding_(this, delegate_ptr),
501 wait_for_connection_count_(0),
502 run_loop_(nullptr) {}
503 ~HttpServerDelegateImpl() override {}
505 // HttpServerDelegate implementation.
506 void OnConnected(HttpConnectionPtr connection,
507 InterfaceRequest<HttpConnectionDelegate> delegate) override {
508 connections_.push_back(make_linked_ptr(
509 new HttpConnectionDelegateImpl(connection.Pass(), delegate.Pass())));
510 if (run_loop_ && connections_.size() >= wait_for_connection_count_) {
511 wait_for_connection_count_ = 0;
512 run_loop_->Quit();
516 void WaitForConnection(size_t count) {
517 DCHECK(!run_loop_);
519 if (connections_.size() >= count)
520 return;
522 wait_for_connection_count_ = count;
523 base::RunLoop run_loop;
524 run_loop_ = &run_loop;
525 run_loop.Run();
526 run_loop_ = nullptr;
529 std::vector<linked_ptr<HttpConnectionDelegateImpl>>& connections() {
530 return connections_;
533 private:
534 Binding<HttpServerDelegate> binding_;
535 std::vector<linked_ptr<HttpConnectionDelegateImpl>> connections_;
536 size_t wait_for_connection_count_;
537 // Pointing to a stack-allocated RunLoop instance.
538 base::RunLoop* run_loop_;
540 DISALLOW_COPY_AND_ASSIGN(HttpServerDelegateImpl);
543 class HttpServerAppTest : public test::ApplicationTestBase {
544 public:
545 HttpServerAppTest() : message_loop_(base::MessageLoop::TYPE_IO) {}
546 ~HttpServerAppTest() override {}
548 protected:
549 bool ShouldCreateDefaultRunLoop() override { return false; }
551 void SetUp() override {
552 ApplicationTestBase::SetUp();
554 mojo::URLRequestPtr request(mojo::URLRequest::New());
555 request->url = mojo::String::From("mojo:network_service");
556 ApplicationConnection* connection =
557 application_impl()->ConnectToApplication(request.Pass());
558 connection->ConnectToService(&network_service_);
561 void CreateHttpServer(HttpServerDelegatePtr delegate,
562 NetAddressPtr* out_bound_to) {
563 network_service_->CreateHttpServer(
564 GetLocalHostWithAnyPort(), delegate.Pass(),
565 [out_bound_to](NetworkErrorPtr result, NetAddressPtr bound_to) {
566 ASSERT_EQ(net::OK, result->code);
567 EXPECT_NE(0u, bound_to->ipv4->port);
568 *out_bound_to = bound_to.Pass();
570 network_service_.WaitForIncomingResponse();
573 NetworkServicePtr network_service_;
575 private:
576 base::MessageLoop message_loop_;
578 DISALLOW_COPY_AND_ASSIGN(HttpServerAppTest);
581 } // namespace
583 TEST_F(HttpServerAppTest, BasicHttpRequestResponse) {
584 NetAddressPtr bound_to;
585 HttpServerDelegatePtr server_delegate_ptr;
586 HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr);
587 CreateHttpServer(server_delegate_ptr.Pass(), &bound_to);
589 TestHttpClient client;
590 client.Connect(bound_to.To<net::IPEndPoint>());
592 server_delegate_impl.WaitForConnection(1);
593 HttpConnectionDelegateImpl& connection =
594 *server_delegate_impl.connections()[0];
596 TestRequest request_data = {"HEAD", "/test", {{"Hello", "World"}}, nullptr};
597 client.Send(MakeRequestMessage(request_data));
599 connection.WaitForRequest(1);
601 CheckRequest(request_data, connection.pending_requests()[0]->request.Pass());
603 TestResponse response_data = {200, {{"Content-Length", "4"}}, nullptr};
604 connection.SendResponse(MakeResponseStruct(response_data));
605 // This causes the underlying TCP connection to be closed. The client can
606 // determine the end of the response based on that.
607 server_delegate_impl.connections().clear();
609 std::string response_message;
610 client.ReadResponse(&response_message);
612 CheckResponse(response_data, response_message);
615 TEST_F(HttpServerAppTest, HttpRequestResponseWithBody) {
616 NetAddressPtr bound_to;
617 HttpServerDelegatePtr server_delegate_ptr;
618 HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr);
619 CreateHttpServer(server_delegate_ptr.Pass(), &bound_to);
621 TestHttpClient client;
622 client.Connect(bound_to.To<net::IPEndPoint>());
624 server_delegate_impl.WaitForConnection(1);
625 HttpConnectionDelegateImpl& connection =
626 *server_delegate_impl.connections()[0];
628 TestRequest request_data = {
629 "Post",
630 "/test",
631 {{"Hello", "World"},
632 {"Content-Length", "23"},
633 {"Content-Type", "text/plain"}},
634 make_scoped_ptr(new std::string("This is a test request!"))};
635 client.Send(MakeRequestMessage(request_data));
637 connection.WaitForRequest(1);
639 CheckRequest(request_data, connection.pending_requests()[0]->request.Pass());
641 TestResponse response_data = {
642 200,
643 {{"Content-Length", "26"}},
644 make_scoped_ptr(new std::string("This is a test response..."))};
645 connection.SendResponse(MakeResponseStruct(response_data));
647 std::string response_message;
648 client.ReadResponse(&response_message);
650 CheckResponse(response_data, response_message);
653 TEST_F(HttpServerAppTest, WebSocket) {
654 NetAddressPtr bound_to;
655 HttpServerDelegatePtr server_delegate_ptr;
656 HttpServerDelegateImpl server_delegate_impl(&server_delegate_ptr);
657 CreateHttpServer(server_delegate_ptr.Pass(), &bound_to);
659 WebSocketPtr web_socket_ptr;
660 network_service_->CreateWebSocket(GetProxy(&web_socket_ptr));
661 WebSocketClientImpl socket_0;
662 socket_0.Connect(
663 web_socket_ptr.Pass(),
664 base::StringPrintf("ws://127.0.0.1:%d/hello", bound_to->ipv4->port));
666 server_delegate_impl.WaitForConnection(1);
667 HttpConnectionDelegateImpl& connection =
668 *server_delegate_impl.connections()[0];
670 connection.WaitForWebSocketRequest();
671 WebSocketClientImpl& socket_1 = *connection.web_socket();
673 socket_1.WaitForConnectCompletion();
674 socket_0.WaitForConnectCompletion();
676 socket_0.Send("Hello");
677 socket_0.Send("world!");
679 socket_1.WaitForMessage(2);
680 EXPECT_EQ("Hello", socket_1.received_messages()[0]);
681 EXPECT_EQ("world!", socket_1.received_messages()[1]);
683 socket_1.Send("How do");
684 socket_1.Send("you do?");
686 socket_0.WaitForMessage(2);
687 EXPECT_EQ("How do", socket_0.received_messages()[0]);
688 EXPECT_EQ("you do?", socket_0.received_messages()[1]);
691 } // namespace mojo