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

Side by Side Diff: mojo/application_manager/application_manager_unittest.cc

Issue 814273005: Move application_manager from /mojo to /shell. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Update include guards. Created 5 years, 11 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 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/at_exit.h"
6 #include "base/bind.h"
7 #include "base/macros.h"
8 #include "base/memory/scoped_vector.h"
9 #include "base/message_loop/message_loop.h"
10 #include "mojo/application_manager/application_loader.h"
11 #include "mojo/application_manager/application_manager.h"
12 #include "mojo/application_manager/test.mojom.h"
13 #include "mojo/public/cpp/application/application_connection.h"
14 #include "mojo/public/cpp/application/application_delegate.h"
15 #include "mojo/public/cpp/application/application_impl.h"
16 #include "mojo/public/cpp/application/interface_factory.h"
17 #include "mojo/public/interfaces/application/service_provider.mojom.h"
18 #include "testing/gtest/include/gtest/gtest.h"
19
20 namespace mojo {
21 namespace {
22
23 const char kTestURLString[] = "test:testService";
24 const char kTestAURLString[] = "test:TestA";
25 const char kTestBURLString[] = "test:TestB";
26
27 struct TestContext {
28 TestContext() : num_impls(0), num_loader_deletes(0) {}
29 std::string last_test_string;
30 int num_impls;
31 int num_loader_deletes;
32 };
33
34 class QuitMessageLoopErrorHandler : public ErrorHandler {
35 public:
36 QuitMessageLoopErrorHandler() {}
37 ~QuitMessageLoopErrorHandler() override {}
38
39 // |ErrorHandler| implementation:
40 void OnConnectionError() override {
41 base::MessageLoop::current()->QuitWhenIdle();
42 }
43
44 private:
45 DISALLOW_COPY_AND_ASSIGN(QuitMessageLoopErrorHandler);
46 };
47
48 class TestServiceImpl : public InterfaceImpl<TestService> {
49 public:
50 explicit TestServiceImpl(TestContext* context) : context_(context) {
51 ++context_->num_impls;
52 }
53
54 ~TestServiceImpl() override { --context_->num_impls; }
55
56 void OnConnectionError() override {
57 if (!base::MessageLoop::current()->is_running())
58 return;
59 base::MessageLoop::current()->Quit();
60 InterfaceImpl::OnConnectionError();
61 }
62
63 // TestService implementation:
64 void Test(const String& test_string) override {
65 context_->last_test_string = test_string;
66 client()->AckTest();
67 }
68
69 private:
70 TestContext* context_;
71 };
72
73 class TestClientImpl : public TestClient {
74 public:
75 explicit TestClientImpl(TestServicePtr service)
76 : service_(service.Pass()), quit_after_ack_(false) {
77 service_.set_client(this);
78 }
79
80 ~TestClientImpl() override { service_.reset(); }
81
82 void AckTest() override {
83 if (quit_after_ack_)
84 base::MessageLoop::current()->Quit();
85 }
86
87 void Test(const std::string& test_string) {
88 quit_after_ack_ = true;
89 service_->Test(test_string);
90 }
91
92 private:
93 TestServicePtr service_;
94 bool quit_after_ack_;
95 DISALLOW_COPY_AND_ASSIGN(TestClientImpl);
96 };
97
98 class TestApplicationLoader : public ApplicationLoader,
99 public ApplicationDelegate,
100 public InterfaceFactory<TestService> {
101 public:
102 TestApplicationLoader() : context_(NULL), num_loads_(0) {}
103
104 ~TestApplicationLoader() override {
105 if (context_)
106 ++context_->num_loader_deletes;
107 test_app_.reset(NULL);
108 }
109
110 void set_context(TestContext* context) { context_ = context; }
111 int num_loads() const { return num_loads_; }
112 const std::vector<std::string>& GetArgs() const { return test_app_->args(); }
113
114 private:
115 // ApplicationLoader implementation.
116 void Load(ApplicationManager* manager,
117 const GURL& url,
118 ScopedMessagePipeHandle shell_handle,
119 LoadCallback callback) override {
120 ++num_loads_;
121 test_app_.reset(new ApplicationImpl(this, shell_handle.Pass()));
122 }
123
124 void OnApplicationError(ApplicationManager* manager,
125 const GURL& url) override {}
126
127 // ApplicationDelegate implementation.
128 bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
129 connection->AddService(this);
130 return true;
131 }
132
133 // InterfaceFactory implementation.
134 void Create(ApplicationConnection* connection,
135 InterfaceRequest<TestService> request) override {
136 BindToRequest(new TestServiceImpl(context_), &request);
137 }
138
139 scoped_ptr<ApplicationImpl> test_app_;
140 TestContext* context_;
141 int num_loads_;
142 DISALLOW_COPY_AND_ASSIGN(TestApplicationLoader);
143 };
144
145 class TesterContext {
146 public:
147 explicit TesterContext(base::MessageLoop* loop)
148 : num_b_calls_(0),
149 num_c_calls_(0),
150 num_a_deletes_(0),
151 num_b_deletes_(0),
152 num_c_deletes_(0),
153 tester_called_quit_(false),
154 a_called_quit_(false),
155 loop_(loop) {}
156
157 void IncrementNumBCalls() {
158 base::AutoLock lock(lock_);
159 num_b_calls_++;
160 }
161
162 void IncrementNumCCalls() {
163 base::AutoLock lock(lock_);
164 num_c_calls_++;
165 }
166
167 void IncrementNumADeletes() {
168 base::AutoLock lock(lock_);
169 num_a_deletes_++;
170 }
171
172 void IncrementNumBDeletes() {
173 base::AutoLock lock(lock_);
174 num_b_deletes_++;
175 }
176
177 void IncrementNumCDeletes() {
178 base::AutoLock lock(lock_);
179 num_c_deletes_++;
180 }
181
182 void set_tester_called_quit() {
183 base::AutoLock lock(lock_);
184 tester_called_quit_ = true;
185 }
186
187 void set_a_called_quit() {
188 base::AutoLock lock(lock_);
189 a_called_quit_ = true;
190 }
191
192 int num_b_calls() {
193 base::AutoLock lock(lock_);
194 return num_b_calls_;
195 }
196 int num_c_calls() {
197 base::AutoLock lock(lock_);
198 return num_c_calls_;
199 }
200 int num_a_deletes() {
201 base::AutoLock lock(lock_);
202 return num_a_deletes_;
203 }
204 int num_b_deletes() {
205 base::AutoLock lock(lock_);
206 return num_b_deletes_;
207 }
208 int num_c_deletes() {
209 base::AutoLock lock(lock_);
210 return num_c_deletes_;
211 }
212 bool tester_called_quit() {
213 base::AutoLock lock(lock_);
214 return tester_called_quit_;
215 }
216 bool a_called_quit() {
217 base::AutoLock lock(lock_);
218 return a_called_quit_;
219 }
220
221 void QuitSoon() {
222 loop_->PostTask(FROM_HERE, base::MessageLoop::QuitWhenIdleClosure());
223 }
224
225 private:
226 // lock_ protects all members except for loop_ which must be unchanged for the
227 // lifetime of this class.
228 base::Lock lock_;
229 int num_b_calls_;
230 int num_c_calls_;
231 int num_a_deletes_;
232 int num_b_deletes_;
233 int num_c_deletes_;
234 bool tester_called_quit_;
235 bool a_called_quit_;
236
237 base::MessageLoop* loop_;
238 };
239
240 // Used to test that the requestor url will be correctly passed.
241 class TestAImpl : public TestA {
242 public:
243 TestAImpl(ApplicationConnection* connection,
244 TesterContext* test_context,
245 InterfaceRequest<TestA> request)
246 : test_context_(test_context),
247 binding_(this, request.Pass()) {
248 connection->ConnectToApplication(kTestBURLString)->ConnectToService(&b_);
249 }
250 ~TestAImpl() override {
251 test_context_->IncrementNumADeletes();
252 if (base::MessageLoop::current()->is_running())
253 Quit();
254 }
255
256 private:
257 void CallB() override {
258 b_->B(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
259 }
260
261 void CallCFromB() override {
262 b_->CallC(base::Bind(&TestAImpl::Quit, base::Unretained(this)));
263 }
264
265 void Quit() {
266 base::MessageLoop::current()->Quit();
267 test_context_->set_a_called_quit();
268 test_context_->QuitSoon();
269 }
270
271 TesterContext* test_context_;
272 TestBPtr b_;
273 Binding<TestA> binding_;
274 };
275
276 class TestBImpl : public InterfaceImpl<TestB> {
277 public:
278 TestBImpl(ApplicationConnection* connection, TesterContext* test_context)
279 : test_context_(test_context) {
280 connection->ConnectToService(&c_);
281 }
282
283 ~TestBImpl() override {
284 test_context_->IncrementNumBDeletes();
285 if (base::MessageLoop::current()->is_running())
286 base::MessageLoop::current()->Quit();
287 test_context_->QuitSoon();
288 }
289
290 private:
291 void B(const mojo::Callback<void()>& callback) override {
292 test_context_->IncrementNumBCalls();
293 callback.Run();
294 }
295
296 void CallC(const mojo::Callback<void()>& callback) override {
297 test_context_->IncrementNumBCalls();
298 c_->C(callback);
299 }
300
301 TesterContext* test_context_;
302 TestCPtr c_;
303 };
304
305 class TestCImpl : public InterfaceImpl<TestC> {
306 public:
307 TestCImpl(ApplicationConnection* connection, TesterContext* test_context)
308 : test_context_(test_context) {}
309
310 ~TestCImpl() override { test_context_->IncrementNumCDeletes(); }
311
312 private:
313 void C(const mojo::Callback<void()>& callback) override {
314 test_context_->IncrementNumCCalls();
315 callback.Run();
316 }
317 TesterContext* test_context_;
318 };
319
320 class Tester : public ApplicationDelegate,
321 public ApplicationLoader,
322 public InterfaceFactory<TestA>,
323 public InterfaceFactory<TestB>,
324 public InterfaceFactory<TestC> {
325 public:
326 Tester(TesterContext* context, const std::string& requestor_url)
327 : context_(context), requestor_url_(requestor_url) {}
328 ~Tester() override {}
329
330 private:
331 void Load(ApplicationManager* manager,
332 const GURL& url,
333 ScopedMessagePipeHandle shell_handle,
334 LoadCallback callback) override {
335 app_.reset(new ApplicationImpl(this, shell_handle.Pass()));
336 }
337
338 void OnApplicationError(ApplicationManager* manager,
339 const GURL& url) override {}
340
341 bool ConfigureIncomingConnection(ApplicationConnection* connection) override {
342 if (!requestor_url_.empty() &&
343 requestor_url_ != connection->GetRemoteApplicationURL()) {
344 context_->set_tester_called_quit();
345 context_->QuitSoon();
346 base::MessageLoop::current()->Quit();
347 return false;
348 }
349 // If we're coming from A, then add B, otherwise A.
350 if (connection->GetRemoteApplicationURL() == kTestAURLString)
351 connection->AddService<TestB>(this);
352 else
353 connection->AddService<TestA>(this);
354 return true;
355 }
356
357 bool ConfigureOutgoingConnection(ApplicationConnection* connection) override {
358 // If we're connecting to B, then add C.
359 if (connection->GetRemoteApplicationURL() == kTestBURLString)
360 connection->AddService<TestC>(this);
361 return true;
362 }
363
364 void Create(ApplicationConnection* connection,
365 InterfaceRequest<TestA> request) override {
366 a_bindings_.push_back(new TestAImpl(connection, context_, request.Pass()));
367 }
368
369 void Create(ApplicationConnection* connection,
370 InterfaceRequest<TestB> request) override {
371 BindToRequest(new TestBImpl(connection, context_), &request);
372 }
373
374 void Create(ApplicationConnection* connection,
375 InterfaceRequest<TestC> request) override {
376 BindToRequest(new TestCImpl(connection, context_), &request);
377 }
378
379 TesterContext* context_;
380 scoped_ptr<ApplicationImpl> app_;
381 std::string requestor_url_;
382 ScopedVector<TestAImpl> a_bindings_;
383 };
384
385 class TestDelegate : public ApplicationManager::Delegate {
386 public:
387 void AddMapping(const GURL& from, const GURL& to) {
388 mappings_[from] = to;
389 }
390
391 // ApplicationManager::Delegate
392 virtual GURL ResolveURL(const GURL& url) override {
393 auto it = mappings_.find(url);
394 if (it != mappings_.end())
395 return it->second;
396 return url;
397 }
398
399 virtual void OnApplicationError(const GURL& url) override {
400 }
401
402 private:
403 std::map<GURL, GURL> mappings_;
404 };
405
406 } // namespace
407
408 class ApplicationManagerTest : public testing::Test {
409 public:
410 ApplicationManagerTest() : tester_context_(&loop_) {}
411
412 ~ApplicationManagerTest() override {}
413
414 void SetUp() override {
415 application_manager_.reset(new ApplicationManager(&test_delegate_));
416 test_loader_ = new TestApplicationLoader;
417 test_loader_->set_context(&context_);
418 application_manager_->set_default_loader(
419 scoped_ptr<ApplicationLoader>(test_loader_));
420
421 TestServicePtr service_proxy;
422 application_manager_->ConnectToService(GURL(kTestURLString),
423 &service_proxy);
424 test_client_.reset(new TestClientImpl(service_proxy.Pass()));
425 }
426
427 void TearDown() override {
428 test_client_.reset(NULL);
429 application_manager_.reset(NULL);
430 }
431
432 void AddLoaderForURL(const GURL& url, const std::string& requestor_url) {
433 application_manager_->SetLoaderForURL(
434 make_scoped_ptr(new Tester(&tester_context_, requestor_url)), url);
435 }
436
437 bool HasFactoryForTestURL() {
438 ApplicationManager::TestAPI manager_test_api(application_manager_.get());
439 return manager_test_api.HasFactoryForURL(GURL(kTestURLString));
440 }
441
442 protected:
443 base::ShadowingAtExitManager at_exit_;
444 TestDelegate test_delegate_;
445 TestApplicationLoader* test_loader_;
446 TesterContext tester_context_;
447 TestContext context_;
448 base::MessageLoop loop_;
449 scoped_ptr<TestClientImpl> test_client_;
450 scoped_ptr<ApplicationManager> application_manager_;
451 DISALLOW_COPY_AND_ASSIGN(ApplicationManagerTest);
452 };
453
454 TEST_F(ApplicationManagerTest, Basic) {
455 test_client_->Test("test");
456 loop_.Run();
457 EXPECT_EQ(std::string("test"), context_.last_test_string);
458 }
459
460 // Confirm that no arguments are sent to an application by default.
461 TEST_F(ApplicationManagerTest, NoArgs) {
462 ApplicationManager am(&test_delegate_);
463 GURL test_url("test:test");
464 TestApplicationLoader* loader = new TestApplicationLoader;
465 loader->set_context(&context_);
466 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
467 TestServicePtr test_service;
468 am.ConnectToService(test_url, &test_service);
469 TestClientImpl test_client(test_service.Pass());
470 test_client.Test("test");
471 loop_.Run();
472 std::vector<std::string> app_args = loader->GetArgs();
473 EXPECT_EQ(0U, app_args.size());
474 }
475
476 // Confirm that arguments are sent to an application.
477 TEST_F(ApplicationManagerTest, Args) {
478 ApplicationManager am(&test_delegate_);
479 GURL test_url("test:test");
480 std::vector<std::string> args;
481 args.push_back("test_arg1");
482 args.push_back("test_arg2");
483 am.SetArgsForURL(args, test_url);
484 TestApplicationLoader* loader = new TestApplicationLoader;
485 loader->set_context(&context_);
486 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(loader), test_url);
487 TestServicePtr test_service;
488 am.ConnectToService(test_url, &test_service);
489 TestClientImpl test_client(test_service.Pass());
490 test_client.Test("test");
491 loop_.Run();
492 std::vector<std::string> app_args = loader->GetArgs();
493 ASSERT_EQ(args.size(), app_args.size());
494 EXPECT_EQ(args[0], app_args[0]);
495 EXPECT_EQ(args[1], app_args[1]);
496 }
497
498 TEST_F(ApplicationManagerTest, ClientError) {
499 test_client_->Test("test");
500 EXPECT_TRUE(HasFactoryForTestURL());
501 loop_.Run();
502 EXPECT_EQ(1, context_.num_impls);
503 test_client_.reset(NULL);
504 loop_.Run();
505 EXPECT_EQ(0, context_.num_impls);
506 EXPECT_TRUE(HasFactoryForTestURL());
507 }
508
509 TEST_F(ApplicationManagerTest, Deletes) {
510 {
511 ApplicationManager am(&test_delegate_);
512 TestApplicationLoader* default_loader = new TestApplicationLoader;
513 default_loader->set_context(&context_);
514 TestApplicationLoader* url_loader1 = new TestApplicationLoader;
515 TestApplicationLoader* url_loader2 = new TestApplicationLoader;
516 url_loader1->set_context(&context_);
517 url_loader2->set_context(&context_);
518 TestApplicationLoader* scheme_loader1 = new TestApplicationLoader;
519 TestApplicationLoader* scheme_loader2 = new TestApplicationLoader;
520 scheme_loader1->set_context(&context_);
521 scheme_loader2->set_context(&context_);
522 am.set_default_loader(scoped_ptr<ApplicationLoader>(default_loader));
523 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader1),
524 GURL("test:test1"));
525 am.SetLoaderForURL(scoped_ptr<ApplicationLoader>(url_loader2),
526 GURL("test:test1"));
527 am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader1),
528 "test");
529 am.SetLoaderForScheme(scoped_ptr<ApplicationLoader>(scheme_loader2),
530 "test");
531 }
532 EXPECT_EQ(5, context_.num_loader_deletes);
533 }
534
535 // Confirm that both urls and schemes can have their loaders explicitly set.
536 TEST_F(ApplicationManagerTest, SetLoaders) {
537 TestApplicationLoader* default_loader = new TestApplicationLoader;
538 TestApplicationLoader* url_loader = new TestApplicationLoader;
539 TestApplicationLoader* scheme_loader = new TestApplicationLoader;
540 application_manager_->set_default_loader(
541 scoped_ptr<ApplicationLoader>(default_loader));
542 application_manager_->SetLoaderForURL(
543 scoped_ptr<ApplicationLoader>(url_loader), GURL("test:test1"));
544 application_manager_->SetLoaderForScheme(
545 scoped_ptr<ApplicationLoader>(scheme_loader), "test");
546
547 // test::test1 should go to url_loader.
548 TestServicePtr test_service;
549 application_manager_->ConnectToService(GURL("test:test1"), &test_service);
550 EXPECT_EQ(1, url_loader->num_loads());
551 EXPECT_EQ(0, scheme_loader->num_loads());
552 EXPECT_EQ(0, default_loader->num_loads());
553
554 // test::test2 should go to scheme loader.
555 application_manager_->ConnectToService(GURL("test:test2"), &test_service);
556 EXPECT_EQ(1, url_loader->num_loads());
557 EXPECT_EQ(1, scheme_loader->num_loads());
558 EXPECT_EQ(0, default_loader->num_loads());
559
560 // http::test1 should go to default loader.
561 application_manager_->ConnectToService(GURL("http:test1"), &test_service);
562 EXPECT_EQ(1, url_loader->num_loads());
563 EXPECT_EQ(1, scheme_loader->num_loads());
564 EXPECT_EQ(1, default_loader->num_loads());
565 }
566
567 // Confirm that the url of a service is correctly passed to another service that
568 // it loads.
569 TEST_F(ApplicationManagerTest, ACallB) {
570 // Any url can load a.
571 AddLoaderForURL(GURL(kTestAURLString), std::string());
572
573 // Only a can load b.
574 AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
575
576 TestAPtr a;
577 application_manager_->ConnectToService(GURL(kTestAURLString), &a);
578 a->CallB();
579 loop_.Run();
580 EXPECT_EQ(1, tester_context_.num_b_calls());
581 EXPECT_TRUE(tester_context_.a_called_quit());
582 }
583
584 // A calls B which calls C.
585 TEST_F(ApplicationManagerTest, BCallC) {
586 // Any url can load a.
587 AddLoaderForURL(GURL(kTestAURLString), std::string());
588
589 // Only a can load b.
590 AddLoaderForURL(GURL(kTestBURLString), kTestAURLString);
591
592 TestAPtr a;
593 application_manager_->ConnectToService(GURL(kTestAURLString), &a);
594 a->CallCFromB();
595 loop_.Run();
596
597 EXPECT_EQ(1, tester_context_.num_b_calls());
598 EXPECT_EQ(1, tester_context_.num_c_calls());
599 EXPECT_TRUE(tester_context_.a_called_quit());
600 }
601
602 // Confirm that a service impl will be deleted if the app that connected to
603 // it goes away.
604 TEST_F(ApplicationManagerTest, BDeleted) {
605 AddLoaderForURL(GURL(kTestAURLString), std::string());
606 AddLoaderForURL(GURL(kTestBURLString), std::string());
607
608 TestAPtr a;
609 application_manager_->ConnectToService(GURL(kTestAURLString), &a);
610
611 a->CallB();
612 loop_.Run();
613
614 // Kills the a app.
615 application_manager_->SetLoaderForURL(scoped_ptr<ApplicationLoader>(),
616 GURL(kTestAURLString));
617 loop_.Run();
618
619 EXPECT_EQ(1, tester_context_.num_b_deletes());
620 }
621
622 // Confirm that the url of a service is correctly passed to another service that
623 // it loads, and that it can be rejected.
624 TEST_F(ApplicationManagerTest, ANoLoadB) {
625 // Any url can load a.
626 AddLoaderForURL(GURL(kTestAURLString), std::string());
627
628 // Only c can load b, so this will fail.
629 AddLoaderForURL(GURL(kTestBURLString), "test:TestC");
630
631 TestAPtr a;
632 application_manager_->ConnectToService(GURL(kTestAURLString), &a);
633 a->CallB();
634 loop_.Run();
635 EXPECT_EQ(0, tester_context_.num_b_calls());
636
637 EXPECT_FALSE(tester_context_.a_called_quit());
638 EXPECT_TRUE(tester_context_.tester_called_quit());
639 }
640
641 TEST_F(ApplicationManagerTest, NoServiceNoLoad) {
642 AddLoaderForURL(GURL(kTestAURLString), std::string());
643
644 // There is no TestC service implementation registered with
645 // ApplicationManager, so this cannot succeed (but also shouldn't crash).
646 TestCPtr c;
647 application_manager_->ConnectToService(GURL(kTestAURLString), &c);
648 QuitMessageLoopErrorHandler quitter;
649 c.set_error_handler(&quitter);
650
651 loop_.Run();
652 EXPECT_TRUE(c.encountered_error());
653 }
654
655 TEST_F(ApplicationManagerTest, MappedURLsShouldNotCauseDuplicateLoad) {
656 test_delegate_.AddMapping(GURL("foo:foo2"), GURL("foo:foo"));
657 // 1 because ApplicationManagerTest connects once at startup.
658 EXPECT_EQ(1, test_loader_->num_loads());
659
660 TestServicePtr test_service;
661 application_manager_->ConnectToService(GURL("foo:foo"), &test_service);
662 EXPECT_EQ(2, test_loader_->num_loads());
663
664 TestServicePtr test_service2;
665 application_manager_->ConnectToService(GURL("foo:foo2"), &test_service2);
666 EXPECT_EQ(2, test_loader_->num_loads());
667
668 TestServicePtr test_service3;
669 application_manager_->ConnectToService(GURL("bar:bar"), &test_service2);
670 EXPECT_EQ(3, test_loader_->num_loads());
671 }
672
673 } // namespace mojo
OLDNEW
« no previous file with comments | « mojo/application_manager/application_manager_export.h ('k') | mojo/application_manager/shell_impl.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698