| OLD | NEW |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <map> | 5 #include <map> |
| 6 #include <utility> | 6 #include <utility> |
| 7 | 7 |
| 8 #include "base/bind.h" | 8 #include "base/bind.h" |
| 9 #include "base/bind_helpers.h" | 9 #include "base/bind_helpers.h" |
| 10 #include "base/files/file_util.h" | 10 #include "base/files/file_util.h" |
| (...skipping 14 matching lines...) Expand all Loading... |
| 25 #include "testing/gtest/include/gtest/gtest.h" | 25 #include "testing/gtest/include/gtest/gtest.h" |
| 26 | 26 |
| 27 namespace chromeos { | 27 namespace chromeos { |
| 28 namespace printing { | 28 namespace printing { |
| 29 | 29 |
| 30 namespace { | 30 namespace { |
| 31 | 31 |
| 32 // Name of the fake server we're resolving ppds from. | 32 // Name of the fake server we're resolving ppds from. |
| 33 const char kPpdServer[] = "bogus.google.com"; | 33 const char kPpdServer[] = "bogus.google.com"; |
| 34 | 34 |
| 35 // A pseudo-ppd that should get cupsFilter lines extracted from it. |
| 36 const char kCupsFilterPpdContents[] = R"( |
| 37 Other random contents that we don't care about. |
| 38 *cupsFilter: "application/vnd.cups-raster 0 my_filter" |
| 39 More random contents that we don't care about |
| 40 *cupsFilter: "application/vnd.cups-awesome 0 a_different_filter" |
| 41 *cupsFilter: "application/vnd.cups-awesomesauce 0 filter3" |
| 42 Yet more randome contents that we don't care about. |
| 43 More random contents that we don't care about. |
| 44 )"; |
| 45 |
| 46 // A pseudo-ppd that should get cupsFilter2 lines extracted from it. |
| 47 // We also have cupsFilter lines in here, but since cupsFilter2 lines |
| 48 // exist, the cupsFilter lines should be ignored. |
| 49 const char kCupsFilter2PpdContents[] = R"( |
| 50 Other random contents that we don't care about. |
| 51 *cupsFilter: "application/vnd.cups-raster 0 my_filter" |
| 52 More random contents that we don't care about |
| 53 *cupsFilter2: "foo bar 0 the_real_filter" |
| 54 *cupsFilter2: "bar baz 381 another_real_filter" |
| 55 Yet more randome contents that we don't care about. |
| 56 More random contents that we don't care about. |
| 57 )"; |
| 58 |
| 35 class PpdProviderTest : public ::testing::Test { | 59 class PpdProviderTest : public ::testing::Test { |
| 36 public: | 60 public: |
| 37 PpdProviderTest() | 61 PpdProviderTest() |
| 38 : loop_(base::MessageLoop::TYPE_IO), | 62 : loop_(base::MessageLoop::TYPE_IO), |
| 39 request_context_getter_(new net::TestURLRequestContextGetter( | 63 request_context_getter_(new net::TestURLRequestContextGetter( |
| 40 base::MessageLoop::current()->task_runner())) {} | 64 base::MessageLoop::current()->task_runner())) {} |
| 41 | 65 |
| 42 void SetUp() override { | 66 void SetUp() override { |
| 43 ASSERT_TRUE(ppd_cache_temp_dir_.CreateUniqueTempDir()); | 67 ASSERT_TRUE(ppd_cache_temp_dir_.CreateUniqueTempDir()); |
| 44 } | 68 } |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 98 ])"}, | 122 ])"}, |
| 99 {"metadata/manufacturer_a.json", | 123 {"metadata/manufacturer_a.json", |
| 100 R"([ | 124 R"([ |
| 101 ["printer_a", "printer_a_ref"], | 125 ["printer_a", "printer_a_ref"], |
| 102 ["printer_b", "printer_b_ref"] | 126 ["printer_b", "printer_b_ref"] |
| 103 ])"}, | 127 ])"}, |
| 104 {"metadata/manufacturer_b.json", | 128 {"metadata/manufacturer_b.json", |
| 105 R"([ | 129 R"([ |
| 106 ["printer_c", "printer_c_ref"] | 130 ["printer_c", "printer_c_ref"] |
| 107 ])"}, | 131 ])"}, |
| 108 {"ppds/printer_a.ppd", "a"}, | 132 {"ppds/printer_a.ppd", kCupsFilterPpdContents}, |
| 109 {"ppds/printer_b.ppd", "b"}, | 133 {"ppds/printer_b.ppd", kCupsFilter2PpdContents}, |
| 110 {"ppds/printer_c.ppd", "c"}, | 134 {"ppds/printer_c.ppd", "c"}, |
| 111 {"user_supplied_ppd_directory/user_supplied.ppd", "u"}}; | 135 {"user_supplied_ppd_directory/user_supplied.ppd", "u"}}; |
| 112 int next_file_num = 0; | 136 int next_file_num = 0; |
| 113 for (const auto& entry : server_contents) { | 137 for (const auto& entry : server_contents) { |
| 114 base::FilePath filename = interceptor_temp_dir_.GetPath().Append( | 138 base::FilePath filename = interceptor_temp_dir_.GetPath().Append( |
| 115 base::StringPrintf("%d.json", next_file_num++)); | 139 base::StringPrintf("%d.json", next_file_num++)); |
| 116 ASSERT_EQ( | 140 ASSERT_EQ( |
| 117 base::WriteFile(filename, entry.second.data(), entry.second.size()), | 141 base::WriteFile(filename, entry.second.data(), entry.second.size()), |
| 118 static_cast<int>(entry.second.size())) | 142 static_cast<int>(entry.second.size())) |
| 119 << "Failed to write temp server file"; | 143 << "Failed to write temp server file"; |
| (...skipping 23 matching lines...) Expand all Loading... |
| 143 } | 167 } |
| 144 | 168 |
| 145 // Capture the result of a ResolvePrinters() call. | 169 // Capture the result of a ResolvePrinters() call. |
| 146 void CaptureResolvePrinters(PpdProvider::CallbackResultCode code, | 170 void CaptureResolvePrinters(PpdProvider::CallbackResultCode code, |
| 147 const std::vector<std::string>& data) { | 171 const std::vector<std::string>& data) { |
| 148 captured_resolve_printers_.push_back({code, data}); | 172 captured_resolve_printers_.push_back({code, data}); |
| 149 } | 173 } |
| 150 | 174 |
| 151 // Capture the result of a ResolvePpd() call. | 175 // Capture the result of a ResolvePpd() call. |
| 152 void CaptureResolvePpd(PpdProvider::CallbackResultCode code, | 176 void CaptureResolvePpd(PpdProvider::CallbackResultCode code, |
| 153 const std::string& contents) { | 177 const std::string& ppd_contents, |
| 154 captured_resolve_ppd_.push_back({code, contents}); | 178 const std::vector<std::string>& ppd_filters) { |
| 179 CapturedResolvePpdResults results; |
| 180 results.code = code; |
| 181 results.ppd_contents = ppd_contents; |
| 182 results.ppd_filters = ppd_filters; |
| 183 captured_resolve_ppd_.push_back(results); |
| 155 } | 184 } |
| 156 | 185 |
| 157 // Capture the result of a ResolveUsbIds() call. | 186 // Capture the result of a ResolveUsbIds() call. |
| 158 void CaptureResolveUsbIds(PpdProvider::CallbackResultCode code, | 187 void CaptureResolveUsbIds(PpdProvider::CallbackResultCode code, |
| 159 const std::string& contents) { | 188 const std::string& contents) { |
| 160 captured_resolve_usb_ids_.push_back({code, contents}); | 189 captured_resolve_usb_ids_.push_back({code, contents}); |
| 161 } | 190 } |
| 162 | 191 |
| 163 // Discard the result of a ResolvePpd() call. | 192 // Discard the result of a ResolvePpd() call. |
| 164 void DiscardResolvePpd(PpdProvider::CallbackResultCode code, | 193 void DiscardResolvePpd(PpdProvider::CallbackResultCode code, |
| (...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 203 base::TestMessageLoop loop_; | 232 base::TestMessageLoop loop_; |
| 204 | 233 |
| 205 std::vector< | 234 std::vector< |
| 206 std::pair<PpdProvider::CallbackResultCode, std::vector<std::string>>> | 235 std::pair<PpdProvider::CallbackResultCode, std::vector<std::string>>> |
| 207 captured_resolve_manufacturers_; | 236 captured_resolve_manufacturers_; |
| 208 | 237 |
| 209 std::vector< | 238 std::vector< |
| 210 std::pair<PpdProvider::CallbackResultCode, std::vector<std::string>>> | 239 std::pair<PpdProvider::CallbackResultCode, std::vector<std::string>>> |
| 211 captured_resolve_printers_; | 240 captured_resolve_printers_; |
| 212 | 241 |
| 213 std::vector<std::pair<PpdProvider::CallbackResultCode, std::string>> | 242 struct CapturedResolvePpdResults { |
| 214 captured_resolve_ppd_; | 243 PpdProvider::CallbackResultCode code; |
| 244 std::string ppd_contents; |
| 245 std::vector<std::string> ppd_filters; |
| 246 }; |
| 247 std::vector<CapturedResolvePpdResults> captured_resolve_ppd_; |
| 215 | 248 |
| 216 std::vector<std::pair<PpdProvider::CallbackResultCode, std::string>> | 249 std::vector<std::pair<PpdProvider::CallbackResultCode, std::string>> |
| 217 captured_resolve_usb_ids_; | 250 captured_resolve_usb_ids_; |
| 218 | 251 |
| 219 std::unique_ptr<net::TestURLRequestInterceptor> interceptor_; | 252 std::unique_ptr<net::TestURLRequestInterceptor> interceptor_; |
| 220 | 253 |
| 221 base::ScopedTempDir ppd_cache_temp_dir_; | 254 base::ScopedTempDir ppd_cache_temp_dir_; |
| 222 base::ScopedTempDir interceptor_temp_dir_; | 255 base::ScopedTempDir interceptor_temp_dir_; |
| 223 | 256 |
| 224 // Provider to be used in the test. | 257 // Provider to be used in the test. |
| (...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 401 Printer::PpdReference ref; | 434 Printer::PpdReference ref; |
| 402 ref.effective_make_and_model = "printer_b_ref"; | 435 ref.effective_make_and_model = "printer_b_ref"; |
| 403 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 436 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 404 base::Unretained(this))); | 437 base::Unretained(this))); |
| 405 ref.effective_make_and_model = "printer_c_ref"; | 438 ref.effective_make_and_model = "printer_c_ref"; |
| 406 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 439 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 407 base::Unretained(this))); | 440 base::Unretained(this))); |
| 408 Drain(*provider); | 441 Drain(*provider); |
| 409 | 442 |
| 410 ASSERT_EQ(2UL, captured_resolve_ppd_.size()); | 443 ASSERT_EQ(2UL, captured_resolve_ppd_.size()); |
| 411 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].first); | 444 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); |
| 412 EXPECT_EQ("b", captured_resolve_ppd_[0].second); | 445 EXPECT_EQ(kCupsFilter2PpdContents, captured_resolve_ppd_[0].ppd_contents); |
| 413 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[1].first); | 446 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[1].code); |
| 414 EXPECT_EQ("c", captured_resolve_ppd_[1].second); | 447 EXPECT_EQ("c", captured_resolve_ppd_[1].ppd_contents); |
| 415 } | 448 } |
| 416 | 449 |
| 417 // Test that we *don't* resolve a ppd URL over non-file schemes. It's not clear | 450 // Test that we *don't* resolve a ppd URL over non-file schemes. It's not clear |
| 418 // whether we'll want to do this in the long term, but for now this is | 451 // whether we'll want to do this in the long term, but for now this is |
| 419 // disallowed because we're not sure we completely understand the security | 452 // disallowed because we're not sure we completely understand the security |
| 420 // implications. | 453 // implications. |
| 421 TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromNetworkFails) { | 454 TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromNetworkFails) { |
| 422 StartFakePpdServer(); | 455 StartFakePpdServer(); |
| 423 auto provider = CreateProvider("en"); | 456 auto provider = CreateProvider("en"); |
| 424 | 457 |
| 425 Printer::PpdReference ref; | 458 Printer::PpdReference ref; |
| 426 ref.user_supplied_ppd_url = base::StringPrintf( | 459 ref.user_supplied_ppd_url = base::StringPrintf( |
| 427 "https://%s/user_supplied_ppd_directory/user_supplied.ppd", kPpdServer); | 460 "https://%s/user_supplied_ppd_directory/user_supplied.ppd", kPpdServer); |
| 428 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 461 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 429 base::Unretained(this))); | 462 base::Unretained(this))); |
| 430 Drain(*provider); | 463 Drain(*provider); |
| 431 | 464 |
| 432 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); | 465 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); |
| 433 EXPECT_EQ(PpdProvider::INTERNAL_ERROR, captured_resolve_ppd_[0].first); | 466 EXPECT_EQ(PpdProvider::INTERNAL_ERROR, captured_resolve_ppd_[0].code); |
| 434 EXPECT_TRUE(captured_resolve_ppd_[0].second.empty()); | 467 EXPECT_TRUE(captured_resolve_ppd_[0].ppd_contents.empty()); |
| 435 } | 468 } |
| 436 | 469 |
| 437 // Test a successful ppd resolution from a user_supplied_url field when | 470 // Test a successful ppd resolution from a user_supplied_url field when |
| 438 // reading from a file. Note we shouldn't need the server to be up | 471 // reading from a file. Note we shouldn't need the server to be up |
| 439 // to do this successfully, as we should be able to do this offline. | 472 // to do this successfully, as we should be able to do this offline. |
| 440 TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromFile) { | 473 TEST_F(PpdProviderTest, ResolveUserSuppliedUrlPpdFromFile) { |
| 441 auto provider = CreateProvider("en"); | 474 auto provider = CreateProvider("en"); |
| 442 base::ScopedTempDir temp_dir; | 475 base::ScopedTempDir temp_dir; |
| 443 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | 476 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| 444 base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); | 477 base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); |
| 445 | 478 |
| 446 std::string user_ppd_contents = "Woohoo"; | 479 std::string user_ppd_contents = "Woohoo"; |
| 447 | 480 |
| 448 ASSERT_EQ(base::WriteFile(filename, user_ppd_contents.data(), | 481 ASSERT_EQ(base::WriteFile(filename, user_ppd_contents.data(), |
| 449 user_ppd_contents.size()), | 482 user_ppd_contents.size()), |
| 450 static_cast<int>(user_ppd_contents.size())); | 483 static_cast<int>(user_ppd_contents.size())); |
| 451 | 484 |
| 452 Printer::PpdReference ref; | 485 Printer::PpdReference ref; |
| 453 ref.user_supplied_ppd_url = | 486 ref.user_supplied_ppd_url = |
| 454 base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); | 487 base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); |
| 455 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 488 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 456 base::Unretained(this))); | 489 base::Unretained(this))); |
| 457 Drain(*provider); | 490 Drain(*provider); |
| 458 | 491 |
| 459 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); | 492 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); |
| 460 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].first); | 493 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); |
| 461 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].second); | 494 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); |
| 462 } | 495 } |
| 463 | 496 |
| 464 // Test that we cache ppd resolutions when we fetch them and that we can resolve | 497 // Test that we cache ppd resolutions when we fetch them and that we can resolve |
| 465 // from the cache without the server available. | 498 // from the cache without the server available. |
| 466 TEST_F(PpdProviderTest, ResolvedPpdsGetCached) { | 499 TEST_F(PpdProviderTest, ResolvedPpdsGetCached) { |
| 467 auto provider = CreateProvider("en"); | 500 auto provider = CreateProvider("en"); |
| 468 std::string user_ppd_contents = "Woohoo"; | 501 std::string user_ppd_contents = "Woohoo"; |
| 469 Printer::PpdReference ref; | 502 Printer::PpdReference ref; |
| 470 { | 503 { |
| 471 base::ScopedTempDir temp_dir; | 504 base::ScopedTempDir temp_dir; |
| 472 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); | 505 ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| 473 base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); | 506 base::FilePath filename = temp_dir.GetPath().Append("my_spiffy.ppd"); |
| 474 | 507 |
| 475 ASSERT_EQ(base::WriteFile(filename, user_ppd_contents.data(), | 508 ASSERT_EQ(base::WriteFile(filename, user_ppd_contents.data(), |
| 476 user_ppd_contents.size()), | 509 user_ppd_contents.size()), |
| 477 static_cast<int>(user_ppd_contents.size())); | 510 static_cast<int>(user_ppd_contents.size())); |
| 478 | 511 |
| 479 ref.user_supplied_ppd_url = | 512 ref.user_supplied_ppd_url = |
| 480 base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); | 513 base::StringPrintf("file://%s", filename.MaybeAsASCII().c_str()); |
| 481 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 514 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 482 base::Unretained(this))); | 515 base::Unretained(this))); |
| 483 Drain(*provider); | 516 Drain(*provider); |
| 484 | 517 |
| 485 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); | 518 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); |
| 486 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].first); | 519 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); |
| 487 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].second); | 520 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); |
| 488 } | 521 } |
| 489 // ScopedTempDir goes out of scope, so the source file should now be | 522 // ScopedTempDir goes out of scope, so the source file should now be |
| 490 // deleted. But if we resolve again, we should hit the cache and | 523 // deleted. But if we resolve again, we should hit the cache and |
| 491 // still be successful. | 524 // still be successful. |
| 492 | 525 |
| 493 captured_resolve_ppd_.clear(); | 526 captured_resolve_ppd_.clear(); |
| 494 | 527 |
| 495 // Recreate the provider to make sure we don't have any memory caches which | 528 // Recreate the provider to make sure we don't have any memory caches which |
| 496 // would mask problems with disk persistence. | 529 // would mask problems with disk persistence. |
| 497 provider = CreateProvider("en"); | 530 provider = CreateProvider("en"); |
| 498 | 531 |
| 499 // Re-resolve. | 532 // Re-resolve. |
| 500 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, | 533 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 501 base::Unretained(this))); | 534 base::Unretained(this))); |
| 502 Drain(*provider); | 535 Drain(*provider); |
| 503 | 536 |
| 504 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); | 537 ASSERT_EQ(1UL, captured_resolve_ppd_.size()); |
| 505 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].first); | 538 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); |
| 506 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].second); | 539 EXPECT_EQ(user_ppd_contents, captured_resolve_ppd_[0].ppd_contents); |
| 540 } |
| 541 |
| 542 // Test that the filter extraction code successfully pulls the filters |
| 543 // from the ppds resolved. |
| 544 TEST_F(PpdProviderTest, ExtractPpdFilters) { |
| 545 StartFakePpdServer(); |
| 546 auto provider = CreateProvider("en"); |
| 547 Printer::PpdReference ref; |
| 548 ref.effective_make_and_model = "printer_a_ref"; |
| 549 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 550 base::Unretained(this))); |
| 551 ref.effective_make_and_model = "printer_b_ref"; |
| 552 provider->ResolvePpd(ref, base::Bind(&PpdProviderTest::CaptureResolvePpd, |
| 553 base::Unretained(this))); |
| 554 Drain(*provider); |
| 555 |
| 556 ASSERT_EQ(2UL, captured_resolve_ppd_.size()); |
| 557 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[0].code); |
| 558 EXPECT_EQ(kCupsFilterPpdContents, captured_resolve_ppd_[0].ppd_contents); |
| 559 EXPECT_EQ( |
| 560 std::vector<std::string>({"a_different_filter", "filter3", "my_filter"}), |
| 561 captured_resolve_ppd_[0].ppd_filters); |
| 562 |
| 563 EXPECT_EQ(PpdProvider::SUCCESS, captured_resolve_ppd_[1].code); |
| 564 EXPECT_EQ(kCupsFilter2PpdContents, captured_resolve_ppd_[1].ppd_contents); |
| 565 EXPECT_EQ( |
| 566 std::vector<std::string>({"another_real_filter", "the_real_filter"}), |
| 567 captured_resolve_ppd_[1].ppd_filters); |
| 507 } | 568 } |
| 508 | 569 |
| 509 } // namespace | 570 } // namespace |
| 510 } // namespace printing | 571 } // namespace printing |
| 511 } // namespace chromeos | 572 } // namespace chromeos |
| OLD | NEW |