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

Side by Side Diff: chrome/browser/extensions/updater/extension_updater_unittest.cc

Issue 1549233002: Convert Pass()→std::move() in //chrome/browser/extensions (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 4 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
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 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 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 "chrome/browser/extensions/updater/extension_updater.h"
6
5 #include <stddef.h> 7 #include <stddef.h>
6 #include <stdint.h> 8 #include <stdint.h>
7
8 #include <list> 9 #include <list>
9 #include <map> 10 #include <map>
10 #include <set> 11 #include <set>
12 #include <utility>
11 #include <vector> 13 #include <vector>
12 14
13 #include "base/bind.h" 15 #include "base/bind.h"
14 #include "base/bind_helpers.h" 16 #include "base/bind_helpers.h"
15 #include "base/command_line.h" 17 #include "base/command_line.h"
16 #include "base/compiler_specific.h" 18 #include "base/compiler_specific.h"
17 #include "base/macros.h" 19 #include "base/macros.h"
18 #include "base/memory/scoped_ptr.h" 20 #include "base/memory/scoped_ptr.h"
19 #include "base/memory/weak_ptr.h" 21 #include "base/memory/weak_ptr.h"
20 #include "base/message_loop/message_loop.h" 22 #include "base/message_loop/message_loop.h"
21 #include "base/run_loop.h" 23 #include "base/run_loop.h"
22 #include "base/sequenced_task_runner.h" 24 #include "base/sequenced_task_runner.h"
23 #include "base/stl_util.h" 25 #include "base/stl_util.h"
24 #include "base/strings/string_number_conversions.h" 26 #include "base/strings/string_number_conversions.h"
25 #include "base/strings/string_split.h" 27 #include "base/strings/string_split.h"
26 #include "base/strings/string_util.h" 28 #include "base/strings/string_util.h"
27 #include "base/strings/stringprintf.h" 29 #include "base/strings/stringprintf.h"
28 #include "base/thread_task_runner_handle.h" 30 #include "base/thread_task_runner_handle.h"
29 #include "base/threading/thread.h" 31 #include "base/threading/thread.h"
30 #include "base/version.h" 32 #include "base/version.h"
31 #include "build/build_config.h" 33 #include "build/build_config.h"
32 #include "chrome/browser/chrome_notification_types.h" 34 #include "chrome/browser/chrome_notification_types.h"
33 #include "chrome/browser/extensions/crx_installer.h" 35 #include "chrome/browser/extensions/crx_installer.h"
34 #include "chrome/browser/extensions/extension_error_reporter.h" 36 #include "chrome/browser/extensions/extension_error_reporter.h"
35 #include "chrome/browser/extensions/extension_sync_data.h" 37 #include "chrome/browser/extensions/extension_sync_data.h"
36 #include "chrome/browser/extensions/test_extension_prefs.h" 38 #include "chrome/browser/extensions/test_extension_prefs.h"
37 #include "chrome/browser/extensions/test_extension_service.h" 39 #include "chrome/browser/extensions/test_extension_service.h"
38 #include "chrome/browser/extensions/test_extension_system.h" 40 #include "chrome/browser/extensions/test_extension_system.h"
39 #include "chrome/browser/extensions/updater/chrome_extension_downloader_factory. h" 41 #include "chrome/browser/extensions/updater/chrome_extension_downloader_factory. h"
40 #include "chrome/browser/extensions/updater/extension_updater.h"
41 #include "chrome/browser/google/google_brand.h" 42 #include "chrome/browser/google/google_brand.h"
42 #include "chrome/test/base/scoped_testing_local_state.h" 43 #include "chrome/test/base/scoped_testing_local_state.h"
43 #include "chrome/test/base/testing_browser_process.h" 44 #include "chrome/test/base/testing_browser_process.h"
44 #include "chrome/test/base/testing_profile.h" 45 #include "chrome/test/base/testing_profile.h"
45 #include "components/crx_file/id_util.h" 46 #include "components/crx_file/id_util.h"
46 #include "components/syncable_prefs/pref_service_syncable.h" 47 #include "components/syncable_prefs/pref_service_syncable.h"
47 #include "components/update_client/update_query_params.h" 48 #include "components/update_client/update_query_params.h"
48 #include "content/public/browser/notification_details.h" 49 #include "content/public/browser/notification_details.h"
49 #include "content/public/browser/notification_observer.h" 50 #include "content/public/browser/notification_observer.h"
50 #include "content/public/browser/notification_registrar.h" 51 #include "content/public/browser/notification_registrar.h"
(...skipping 323 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 private: 375 private:
375 scoped_ptr<ExtensionDownloader> CreateExtensionDownloader( 376 scoped_ptr<ExtensionDownloader> CreateExtensionDownloader(
376 ExtensionDownloaderDelegate* delegate) { 377 ExtensionDownloaderDelegate* delegate) {
377 scoped_ptr<ExtensionDownloader> downloader = 378 scoped_ptr<ExtensionDownloader> downloader =
378 ChromeExtensionDownloaderFactory::CreateForRequestContext( 379 ChromeExtensionDownloaderFactory::CreateForRequestContext(
379 request_context(), 380 request_context(),
380 downloader_delegate_override_ ? downloader_delegate_override_ 381 downloader_delegate_override_ ? downloader_delegate_override_
381 : delegate); 382 : delegate);
382 if (enable_metrics_) 383 if (enable_metrics_)
383 downloader->set_enable_extra_update_metrics(true); 384 downloader->set_enable_extra_update_metrics(true);
384 return downloader.Pass(); 385 return downloader;
385 } 386 }
386 387
387 scoped_ptr<ExtensionDownloader> CreateExtensionDownloaderWithIdentity( 388 scoped_ptr<ExtensionDownloader> CreateExtensionDownloaderWithIdentity(
388 ExtensionDownloaderDelegate* delegate) { 389 ExtensionDownloaderDelegate* delegate) {
389 scoped_ptr<FakeIdentityProvider> fake_identity_provider; 390 scoped_ptr<FakeIdentityProvider> fake_identity_provider;
390 fake_token_service_.reset(new FakeOAuth2TokenService()); 391 fake_token_service_.reset(new FakeOAuth2TokenService());
391 fake_identity_provider.reset(new FakeIdentityProvider( 392 fake_identity_provider.reset(new FakeIdentityProvider(
392 fake_token_service_.get())); 393 fake_token_service_.get()));
393 fake_identity_provider->LogIn(kFakeAccountId); 394 fake_identity_provider->LogIn(kFakeAccountId);
394 fake_token_service_->AddAccount(kFakeAccountId); 395 fake_token_service_->AddAccount(kFakeAccountId);
395 396
396 scoped_ptr<ExtensionDownloader> downloader( 397 scoped_ptr<ExtensionDownloader> downloader(
397 CreateExtensionDownloader(delegate)); 398 CreateExtensionDownloader(delegate));
398 downloader->SetWebstoreIdentityProvider(fake_identity_provider.Pass()); 399 downloader->SetWebstoreIdentityProvider(std::move(fake_identity_provider));
399 return downloader.Pass(); 400 return downloader;
400 } 401 }
401 402
402 scoped_ptr<FakeOAuth2TokenService> fake_token_service_; 403 scoped_ptr<FakeOAuth2TokenService> fake_token_service_;
403 404
404 ExtensionDownloaderDelegate* downloader_delegate_override_; 405 ExtensionDownloaderDelegate* downloader_delegate_override_;
405 406
406 bool enable_metrics_; 407 bool enable_metrics_;
407 408
408 DISALLOW_COPY_AND_ASSIGN(MockService); 409 DISALLOW_COPY_AND_ASSIGN(MockService);
409 }; 410 };
(...skipping 543 matching lines...) Expand 10 before | Expand all | Expand 10 after
953 fetch4->AddExtension("4444", "4.0", &zeroDays, kEmptyUpdateUrlData, 954 fetch4->AddExtension("4444", "4.0", &zeroDays, kEmptyUpdateUrlData,
954 std::string()); 955 std::string());
955 956
956 // This will start the first fetcher and queue the others. The next in queue 957 // This will start the first fetcher and queue the others. The next in queue
957 // is started as each fetcher receives its response. Note that the fetchers 958 // is started as each fetcher receives its response. Note that the fetchers
958 // don't necessarily run in the order that they are started from here. 959 // don't necessarily run in the order that they are started from here.
959 GURL fetch1_url = fetch1->full_url(); 960 GURL fetch1_url = fetch1->full_url();
960 GURL fetch2_url = fetch2->full_url(); 961 GURL fetch2_url = fetch2->full_url();
961 GURL fetch3_url = fetch3->full_url(); 962 GURL fetch3_url = fetch3->full_url();
962 GURL fetch4_url = fetch4->full_url(); 963 GURL fetch4_url = fetch4->full_url();
963 downloader.StartUpdateCheck(fetch1.Pass()); 964 downloader.StartUpdateCheck(std::move(fetch1));
964 downloader.StartUpdateCheck(fetch2.Pass()); 965 downloader.StartUpdateCheck(std::move(fetch2));
965 downloader.StartUpdateCheck(fetch3.Pass()); 966 downloader.StartUpdateCheck(std::move(fetch3));
966 downloader.StartUpdateCheck(fetch4.Pass()); 967 downloader.StartUpdateCheck(std::move(fetch4));
967 RunUntilIdle(); 968 RunUntilIdle();
968 969
969 for (int i = 0; i < 4; ++i) { 970 for (int i = 0; i < 4; ++i) {
970 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId); 971 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId);
971 ASSERT_TRUE(fetcher); 972 ASSERT_TRUE(fetcher);
972 ASSERT_TRUE(fetcher->delegate()); 973 ASSERT_TRUE(fetcher->delegate());
973 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags); 974 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags);
974 EXPECT_FALSE(fetcher->GetOriginalURL().is_empty()); 975 EXPECT_FALSE(fetcher->GetOriginalURL().is_empty());
975 976
976 if (fetcher->GetOriginalURL() == fetch1_url) { 977 if (fetcher->GetOriginalURL() == fetch1_url) {
(...skipping 101 matching lines...) Expand 10 before | Expand all | Expand 10 after
1078 downloader.manifests_queue_.set_backoff_policy(&kNoBackoffPolicy); 1079 downloader.manifests_queue_.set_backoff_policy(&kNoBackoffPolicy);
1079 1080
1080 GURL kUpdateUrl("http://localhost/manifest1"); 1081 GURL kUpdateUrl("http://localhost/manifest1");
1081 1082
1082 scoped_ptr<ManifestFetchData> fetch(CreateManifestFetchData(kUpdateUrl)); 1083 scoped_ptr<ManifestFetchData> fetch(CreateManifestFetchData(kUpdateUrl));
1083 ManifestFetchData::PingData zeroDays(0, 0, true, 0); 1084 ManifestFetchData::PingData zeroDays(0, 0, true, 0);
1084 fetch->AddExtension("1111", "1.0", &zeroDays, kEmptyUpdateUrlData, 1085 fetch->AddExtension("1111", "1.0", &zeroDays, kEmptyUpdateUrlData,
1085 std::string()); 1086 std::string());
1086 1087
1087 // This will start the first fetcher. 1088 // This will start the first fetcher.
1088 downloader.StartUpdateCheck(fetch.Pass()); 1089 downloader.StartUpdateCheck(std::move(fetch));
1089 RunUntilIdle(); 1090 RunUntilIdle();
1090 1091
1091 // ExtensionDownloader should retry kMaxRetries times and then fail. 1092 // ExtensionDownloader should retry kMaxRetries times and then fail.
1092 EXPECT_CALL(delegate, OnExtensionDownloadFailed( 1093 EXPECT_CALL(delegate, OnExtensionDownloadFailed(
1093 "1111", ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED, _, _)); 1094 "1111", ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED, _, _));
1094 for (int i = 0; i <= ExtensionDownloader::kMaxRetries; ++i) { 1095 for (int i = 0; i <= ExtensionDownloader::kMaxRetries; ++i) {
1095 // All fetches will fail. 1096 // All fetches will fail.
1096 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId); 1097 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId);
1097 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); 1098 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
1098 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags); 1099 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags);
1099 fetcher->set_url(kUpdateUrl); 1100 fetcher->set_url(kUpdateUrl);
1100 fetcher->set_status(net::URLRequestStatus()); 1101 fetcher->set_status(net::URLRequestStatus());
1101 // Code 5xx causes ExtensionDownloader to retry. 1102 // Code 5xx causes ExtensionDownloader to retry.
1102 fetcher->set_response_code(500); 1103 fetcher->set_response_code(500);
1103 fetcher->delegate()->OnURLFetchComplete(fetcher); 1104 fetcher->delegate()->OnURLFetchComplete(fetcher);
1104 RunUntilIdle(); 1105 RunUntilIdle();
1105 } 1106 }
1106 Mock::VerifyAndClearExpectations(&delegate); 1107 Mock::VerifyAndClearExpectations(&delegate);
1107 1108
1108 1109
1109 // For response codes that are not in the 5xx range ExtensionDownloader 1110 // For response codes that are not in the 5xx range ExtensionDownloader
1110 // should not retry. 1111 // should not retry.
1111 fetch.reset(CreateManifestFetchData(kUpdateUrl)); 1112 fetch.reset(CreateManifestFetchData(kUpdateUrl));
1112 fetch->AddExtension("1111", "1.0", &zeroDays, kEmptyUpdateUrlData, 1113 fetch->AddExtension("1111", "1.0", &zeroDays, kEmptyUpdateUrlData,
1113 std::string()); 1114 std::string());
1114 1115
1115 // This will start the first fetcher. 1116 // This will start the first fetcher.
1116 downloader.StartUpdateCheck(fetch.Pass()); 1117 downloader.StartUpdateCheck(std::move(fetch));
1117 RunUntilIdle(); 1118 RunUntilIdle();
1118 1119
1119 EXPECT_CALL(delegate, OnExtensionDownloadFailed( 1120 EXPECT_CALL(delegate, OnExtensionDownloadFailed(
1120 "1111", ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED, _, _)); 1121 "1111", ExtensionDownloaderDelegate::MANIFEST_FETCH_FAILED, _, _));
1121 // The first fetch will fail, and require retrying. 1122 // The first fetch will fail, and require retrying.
1122 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId); 1123 fetcher = factory.GetFetcherByID(ExtensionDownloader::kManifestFetcherId);
1123 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); 1124 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
1124 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags); 1125 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags);
1125 fetcher->set_url(kUpdateUrl); 1126 fetcher->set_url(kUpdateUrl);
1126 fetcher->set_status(net::URLRequestStatus()); 1127 fetcher->set_status(net::URLRequestStatus());
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1165 GURL test_url("http://localhost/extension.crx"); 1166 GURL test_url("http://localhost/extension.crx");
1166 1167
1167 std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 1168 std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1168 std::string hash; 1169 std::string hash;
1169 Version version("0.0.1"); 1170 Version version("0.0.1");
1170 std::set<int> requests; 1171 std::set<int> requests;
1171 requests.insert(0); 1172 requests.insert(0);
1172 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch( 1173 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch(
1173 new ExtensionDownloader::ExtensionFetch( 1174 new ExtensionDownloader::ExtensionFetch(
1174 id, test_url, hash, version.GetString(), requests)); 1175 id, test_url, hash, version.GetString(), requests));
1175 updater.downloader_->FetchUpdatedExtension(fetch.Pass()); 1176 updater.downloader_->FetchUpdatedExtension(std::move(fetch));
1176 1177
1177 if (pending) { 1178 if (pending) {
1178 const bool kIsFromSync = true; 1179 const bool kIsFromSync = true;
1179 const bool kMarkAcknowledged = false; 1180 const bool kMarkAcknowledged = false;
1180 const bool kRemoteInstall = false; 1181 const bool kRemoteInstall = false;
1181 PendingExtensionManager* pending_extension_manager = 1182 PendingExtensionManager* pending_extension_manager =
1182 service->pending_extension_manager(); 1183 service->pending_extension_manager();
1183 pending_extension_manager->AddForTesting( 1184 pending_extension_manager->AddForTesting(
1184 PendingExtensionInfo(id, 1185 PendingExtensionInfo(id,
1185 std::string(), 1186 std::string(),
(...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after
1275 1276
1276 GURL test_url(base::StringPrintf("%s/extension.crx", url_prefix.c_str())); 1277 GURL test_url(base::StringPrintf("%s/extension.crx", url_prefix.c_str()));
1277 std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 1278 std::string id = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
1278 std::string hash; 1279 std::string hash;
1279 Version version("0.0.1"); 1280 Version version("0.0.1");
1280 std::set<int> requests; 1281 std::set<int> requests;
1281 requests.insert(0); 1282 requests.insert(0);
1282 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch( 1283 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch(
1283 new ExtensionDownloader::ExtensionFetch( 1284 new ExtensionDownloader::ExtensionFetch(
1284 id, test_url, hash, version.GetString(), requests)); 1285 id, test_url, hash, version.GetString(), requests));
1285 updater.downloader_->FetchUpdatedExtension(fetch.Pass()); 1286 updater.downloader_->FetchUpdatedExtension(std::move(fetch));
1286 1287
1287 fetcher = factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId); 1288 fetcher = factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId);
1288 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); 1289 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
1289 EXPECT_EQ(kExpectedLoadFlags, fetcher->GetLoadFlags()); 1290 EXPECT_EQ(kExpectedLoadFlags, fetcher->GetLoadFlags());
1290 1291
1291 // Fake a 403 response. 1292 // Fake a 403 response.
1292 fetcher->set_url(test_url); 1293 fetcher->set_url(test_url);
1293 fetcher->set_status(net::URLRequestStatus()); 1294 fetcher->set_status(net::URLRequestStatus());
1294 fetcher->set_response_code(403); 1295 fetcher->set_response_code(403);
1295 fetcher->delegate()->OnURLFetchComplete(fetcher); 1296 fetcher->delegate()->OnURLFetchComplete(fetcher);
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after
1468 std::string version2 = "0.1"; 1469 std::string version2 = "0.1";
1469 std::set<int> requests; 1470 std::set<int> requests;
1470 requests.insert(0); 1471 requests.insert(0);
1471 // Start two fetches 1472 // Start two fetches
1472 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch1( 1473 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch1(
1473 new ExtensionDownloader::ExtensionFetch( 1474 new ExtensionDownloader::ExtensionFetch(
1474 id1, url1, hash1, version1, requests)); 1475 id1, url1, hash1, version1, requests));
1475 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch2( 1476 scoped_ptr<ExtensionDownloader::ExtensionFetch> fetch2(
1476 new ExtensionDownloader::ExtensionFetch( 1477 new ExtensionDownloader::ExtensionFetch(
1477 id2, url2, hash2, version2, requests)); 1478 id2, url2, hash2, version2, requests));
1478 updater.downloader_->FetchUpdatedExtension(fetch1.Pass()); 1479 updater.downloader_->FetchUpdatedExtension(std::move(fetch1));
1479 updater.downloader_->FetchUpdatedExtension(fetch2.Pass()); 1480 updater.downloader_->FetchUpdatedExtension(std::move(fetch2));
1480 1481
1481 // Make the first fetch complete. 1482 // Make the first fetch complete.
1482 base::FilePath extension_file_path(FILE_PATH_LITERAL("/whatever")); 1483 base::FilePath extension_file_path(FILE_PATH_LITERAL("/whatever"));
1483 1484
1484 fetcher = factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId); 1485 fetcher = factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId);
1485 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL); 1486 EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
1486 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags); 1487 EXPECT_TRUE(fetcher->GetLoadFlags() == kExpectedLoadFlags);
1487 1488
1488 // We need some CrxInstallers, and CrxInstallers require a real 1489 // We need some CrxInstallers, and CrxInstallers require a real
1489 // ExtensionService. Create one on the testing profile. Any action 1490 // ExtensionService. Create one on the testing profile. Any action
(...skipping 767 matching lines...) Expand 10 before | Expand all | Expand 10 after
2257 // -prodversionmin (shouldn't update if browser version too old) 2258 // -prodversionmin (shouldn't update if browser version too old)
2258 // -manifests & updates arriving out of order / interleaved 2259 // -manifests & updates arriving out of order / interleaved
2259 // -malformed update url (empty, file://, has query, has a # fragment, etc.) 2260 // -malformed update url (empty, file://, has query, has a # fragment, etc.)
2260 // -An extension gets uninstalled while updates are in progress (so it doesn't 2261 // -An extension gets uninstalled while updates are in progress (so it doesn't
2261 // "come back from the dead") 2262 // "come back from the dead")
2262 // -An extension gets manually updated to v3 while we're downloading v2 (ie 2263 // -An extension gets manually updated to v3 while we're downloading v2 (ie
2263 // you don't get downgraded accidentally) 2264 // you don't get downgraded accidentally)
2264 // -An update manifest mentions multiple updates 2265 // -An update manifest mentions multiple updates
2265 2266
2266 } // namespace extensions 2267 } // namespace extensions
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698