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

Side by Side Diff: chrome/browser/prerender/prerender_browsertest.cc

Issue 11571037: Pass load events from prerenders to launching elements. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: did you know prerenders can be canceled? Created 7 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 | Annotate | Revision Log
« no previous file with comments | « no previous file | chrome/browser/prerender/prerender_contents.h » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <deque> 5 #include <deque>
6 6
7 #include "base/command_line.h" 7 #include "base/command_line.h"
8 #include "base/path_service.h" 8 #include "base/path_service.h"
9 #include "base/string_util.h" 9 #include "base/string_util.h"
10 #include "base/stringprintf.h" 10 #include "base/stringprintf.h"
(...skipping 846 matching lines...) Expand 10 before | Expand all | Expand 10 after
857 "receivedPrerenderStartEvents[%d]))", index); 857 "receivedPrerenderStartEvents[%d]))", index);
858 858
859 CHECK(content::ExecuteJavaScriptAndExtractBool( 859 CHECK(content::ExecuteJavaScriptAndExtractBool(
860 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(), 860 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(),
861 "", 861 "",
862 expression, 862 expression,
863 &received_prerender_started)); 863 &received_prerender_started));
864 return received_prerender_started; 864 return received_prerender_started;
865 } 865 }
866 866
867 bool DidReceivePrerenderLoadEventForLinkNumber(int index) const {
868 bool received_prerender_loaded;
869 std::string expression = base::StringPrintf(
870 "window.domAutomationController.send(Boolean("
871 "receivedPrerenderLoadEvents[%d]))", index);
872
873 CHECK(content::ExecuteJavaScriptAndExtractBool(
874 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(),
875 "", expression,
876 &received_prerender_loaded));
877 return received_prerender_loaded;
878 }
879
867 bool DidReceivePrerenderStopEventForLinkNumber(int index) const { 880 bool DidReceivePrerenderStopEventForLinkNumber(int index) const {
868 bool received_prerender_stopped; 881 bool received_prerender_stopped;
869 std::string expression = base::StringPrintf( 882 std::string expression = base::StringPrintf(
870 "window.domAutomationController.send(Boolean(" 883 "window.domAutomationController.send(Boolean("
871 "receivedPrerenderStopEvents[%d]))", index); 884 "receivedPrerenderStopEvents[%d]))", index);
872 885
873 CHECK(content::ExecuteJavaScriptAndExtractBool( 886 CHECK(content::ExecuteJavaScriptAndExtractBool(
874 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(), 887 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(),
875 "", 888 "",
876 expression, 889 expression,
877 &received_prerender_stopped)); 890 &received_prerender_stopped));
878 return received_prerender_stopped; 891 return received_prerender_stopped;
879 } 892 }
880 893
894 bool HadPrerenderEventErrors() const {
895 bool had_prerender_event_errors;
896 CHECK(content::ExecuteJavaScriptAndExtractBool(
897 chrome::GetActiveWebContents(current_browser())->GetRenderViewHost(),
898 "", "window.domAutomationController.send(Boolean("
899 "hadPrerenderEventErrors))", &had_prerender_event_errors));
900 return had_prerender_event_errors;
901 }
902
881 // Asserting on this can result in flaky tests. PrerenderHandles are 903 // Asserting on this can result in flaky tests. PrerenderHandles are
882 // removed from the PrerenderLinkManager when the prerender is canceled from 904 // removed from the PrerenderLinkManager when the prerender is canceled from
883 // the browser, when the prerenders are cancelled from the renderer process, 905 // the browser, when the prerenders are cancelled from the renderer process,
884 // or the channel for the renderer process is closed on the IO thread. In the 906 // or the channel for the renderer process is closed on the IO thread. In the
885 // last case, the code must be careful to wait for the channel to close, as it 907 // last case, the code must be careful to wait for the channel to close, as it
886 // is done asynchronously after swapping out the old process. See 908 // is done asynchronously after swapping out the old process. See
887 // ChannelDestructionWatcher. 909 // ChannelDestructionWatcher.
888 bool IsEmptyPrerenderLinkManager() const { 910 bool IsEmptyPrerenderLinkManager() const {
889 return GetPrerenderLinkManager()->IsEmpty(); 911 return GetPrerenderLinkManager()->IsEmpty();
890 } 912 }
(...skipping 285 matching lines...) Expand 10 before | Expand all | Expand 10 after
1176 first_channel_close_watcher.WatchChannel( 1198 first_channel_close_watcher.WatchChannel(
1177 chrome::GetActiveWebContents(browser())->GetRenderProcessHost()); 1199 chrome::GetActiveWebContents(browser())->GetRenderProcessHost());
1178 NavigateToDestURL(); 1200 NavigateToDestURL();
1179 // NavigateToDestURL doesn't run a message loop. Normally that's fine, but in 1201 // NavigateToDestURL doesn't run a message loop. Normally that's fine, but in
1180 // this case, we need the pending prerenders to start. 1202 // this case, we need the pending prerenders to start.
1181 content::RunMessageLoop(); 1203 content::RunMessageLoop();
1182 first_channel_close_watcher.WaitForChannelClose(); 1204 first_channel_close_watcher.WaitForChannelClose();
1183 1205
1184 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1206 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1185 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1207 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1208 EXPECT_FALSE(HadPrerenderEventErrors());
1186 1209
1187 const GURL prerender_page_url = 1210 const GURL prerender_page_url =
1188 test_server()->GetURL("files/prerender/prerender_page.html"); 1211 test_server()->GetURL("files/prerender/prerender_page.html");
1189 EXPECT_FALSE(IsEmptyPrerenderLinkManager()); 1212 EXPECT_FALSE(IsEmptyPrerenderLinkManager());
1190 EXPECT_NE(static_cast<TestPrerenderContents*>(NULL), 1213 EXPECT_NE(static_cast<TestPrerenderContents*>(NULL),
1191 GetPrerenderContentsFor(prerender_page_url)); 1214 GetPrerenderContentsFor(prerender_page_url));
1192 1215
1193 // Now navigate to our target page. 1216 // Now navigate to our target page.
1194 ChannelDestructionWatcher second_channel_close_watcher; 1217 ChannelDestructionWatcher second_channel_close_watcher;
1195 second_channel_close_watcher.WatchChannel( 1218 second_channel_close_watcher.WatchChannel(
(...skipping 13 matching lines...) Expand all
1209 FINAL_STATUS_USED, 1); 1232 FINAL_STATUS_USED, 1);
1210 1233
1211 ChannelDestructionWatcher channel_close_watcher; 1234 ChannelDestructionWatcher channel_close_watcher;
1212 channel_close_watcher.WatchChannel( 1235 channel_close_watcher.WatchChannel(
1213 chrome::GetActiveWebContents(browser())->GetRenderProcessHost()); 1236 chrome::GetActiveWebContents(browser())->GetRenderProcessHost());
1214 NavigateToDestURL(); 1237 NavigateToDestURL();
1215 channel_close_watcher.WaitForChannelClose(); 1238 channel_close_watcher.WaitForChannelClose();
1216 1239
1217 EXPECT_FALSE(DidReceivePrerenderStartEventForLinkNumber(1)); 1240 EXPECT_FALSE(DidReceivePrerenderStartEventForLinkNumber(1));
1218 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1)); 1241 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1));
1242 EXPECT_FALSE(HadPrerenderEventErrors());
1219 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive* 1243 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive*
1220 // calls did a thread/process hop to the renderer which insured pending 1244 // calls did a thread/process hop to the renderer which insured pending
1221 // renderer events have arrived. 1245 // renderer events have arrived.
1222 ASSERT_TRUE(IsEmptyPrerenderLinkManager()); 1246 ASSERT_TRUE(IsEmptyPrerenderLinkManager());
1223 } 1247 }
1224 1248
1225 // Flaky, http://crbug.com/167340. 1249 // Flaky, http://crbug.com/167340.
1226 IN_PROC_BROWSER_TEST_F( 1250 IN_PROC_BROWSER_TEST_F(
1227 PrerenderBrowserTest, DISABLED_PrerenderPageRemovingLink) { 1251 PrerenderBrowserTest, DISABLED_PrerenderPageRemovingLink) {
1228 set_loader_path("files/prerender/prerender_loader_removing_links.html"); 1252 set_loader_path("files/prerender/prerender_loader_removing_links.html");
1229 set_loader_query_and_fragment("?links_to_insert=1"); 1253 set_loader_query_and_fragment("?links_to_insert=1");
1230 PrerenderTestURL("files/prerender/prerender_page.html", 1254 PrerenderTestURL("files/prerender/prerender_page.html",
1231 FINAL_STATUS_CANCELLED, 1); 1255 FINAL_STATUS_CANCELLED, 1);
1232 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1256 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1233 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1257 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1234 1258
1235 // No ChannelDestructionWatcher is needed here, since prerenders in the 1259 // No ChannelDestructionWatcher is needed here, since prerenders in the
1236 // PrerenderLinkManager should be deleted by removing the links, rather than 1260 // PrerenderLinkManager should be deleted by removing the links, rather than
1237 // shutting down the renderer process. 1261 // shutting down the renderer process.
1238 RemoveLinkElement(0); 1262 RemoveLinkElement(0);
1239 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1263 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1240 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1264 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1265 EXPECT_FALSE(HadPrerenderEventErrors());
1241 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive* 1266 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive*
1242 // calls did a thread/process hop to the renderer which insured pending 1267 // calls did a thread/process hop to the renderer which insured pending
1243 // renderer events have arrived. 1268 // renderer events have arrived.
1244 EXPECT_TRUE(IsEmptyPrerenderLinkManager()); 1269 EXPECT_TRUE(IsEmptyPrerenderLinkManager());
1245 } 1270 }
1246 1271
1247 // Flaky, http://crbug.com/167340. 1272 // Flaky, http://crbug.com/167340.
1248 IN_PROC_BROWSER_TEST_F( 1273 IN_PROC_BROWSER_TEST_F(
1249 PrerenderBrowserTest, DISABLED_PrerenderPageRemovingLinkWithTwoLinks) { 1274 PrerenderBrowserTest, DISABLED_PrerenderPageRemovingLinkWithTwoLinks) {
1250 GetPrerenderManager()->mutable_config().max_link_concurrency = 2; 1275 GetPrerenderManager()->mutable_config().max_link_concurrency = 2;
1251 GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2; 1276 GetPrerenderManager()->mutable_config().max_link_concurrency_per_launcher = 2;
1252 1277
1253 set_loader_path("files/prerender/prerender_loader_removing_links.html"); 1278 set_loader_path("files/prerender/prerender_loader_removing_links.html");
1254 set_loader_query_and_fragment("?links_to_insert=2"); 1279 set_loader_query_and_fragment("?links_to_insert=2");
1255 PrerenderTestURL("files/prerender/prerender_page.html", 1280 PrerenderTestURL("files/prerender/prerender_page.html",
1256 FINAL_STATUS_CANCELLED, 1); 1281 FINAL_STATUS_CANCELLED, 1);
1257 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1282 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1258 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1283 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1259 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1)); 1284 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1));
1260 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1)); 1285 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1));
1261 1286
1262 RemoveLinkElement(0); 1287 RemoveLinkElement(0);
1263 RemoveLinkElement(1); 1288 RemoveLinkElement(1);
1264 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1289 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1265 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1290 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1266 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1)); 1291 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1));
1267 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1)); 1292 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1));
1293 EXPECT_FALSE(HadPrerenderEventErrors());
1268 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive* 1294 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive*
1269 // calls did a thread/process hop to the renderer which insured pending 1295 // calls did a thread/process hop to the renderer which insured pending
1270 // renderer events have arrived. 1296 // renderer events have arrived.
1271 EXPECT_TRUE(IsEmptyPrerenderLinkManager()); 1297 EXPECT_TRUE(IsEmptyPrerenderLinkManager());
1272 } 1298 }
1273 1299
1274 #if defined(OS_WIN) 1300 #if defined(OS_WIN)
1275 // TODO(gavinp): Fails on XP Rel - http://crbug.com/128841 1301 // TODO(gavinp): Fails on XP Rel - http://crbug.com/128841
1276 #define MAYBE_PrerenderPageRemovingLinkWithTwoLinksRemovingOne \ 1302 #define MAYBE_PrerenderPageRemovingLinkWithTwoLinksRemovingOne \
1277 DISABLED_PrerenderPageRemovingLinkWithTwoLinksRemovingOne 1303 DISABLED_PrerenderPageRemovingLinkWithTwoLinksRemovingOne
(...skipping 13 matching lines...) Expand all
1291 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1317 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1292 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1318 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1293 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1)); 1319 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1));
1294 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1)); 1320 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1));
1295 1321
1296 RemoveLinkElement(0); 1322 RemoveLinkElement(0);
1297 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 1323 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
1298 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 1324 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
1299 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1)); 1325 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(1));
1300 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1)); 1326 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(1));
1327 EXPECT_FALSE(HadPrerenderEventErrors());
1301 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive* 1328 // IsEmptyPrerenderLinkManager() is not racy because the earlier DidReceive*
1302 // calls did a thread/process hop to the renderer which insured pending 1329 // calls did a thread/process hop to the renderer which insured pending
1303 // renderer events have arrived. 1330 // renderer events have arrived.
1304 EXPECT_FALSE(IsEmptyPrerenderLinkManager()); 1331 EXPECT_FALSE(IsEmptyPrerenderLinkManager());
1305 1332
1306 ChannelDestructionWatcher channel_close_watcher; 1333 ChannelDestructionWatcher channel_close_watcher;
1307 channel_close_watcher.WatchChannel( 1334 channel_close_watcher.WatchChannel(
1308 chrome::GetActiveWebContents(browser())->GetRenderProcessHost()); 1335 chrome::GetActiveWebContents(browser())->GetRenderProcessHost());
1309 NavigateToDestURL(); 1336 NavigateToDestURL();
1310 channel_close_watcher.WaitForChannelClose(); 1337 channel_close_watcher.WaitForChannelClose();
(...skipping 1091 matching lines...) Expand 10 before | Expand all | Expand 10 after
2402 MessageLoop::current()->PostTask( 2429 MessageLoop::current()->PostTask(
2403 FROM_HERE, base::Bind(&CancelAllPrerenders, GetPrerenderManager())); 2430 FROM_HERE, base::Bind(&CancelAllPrerenders, GetPrerenderManager()));
2404 content::RunMessageLoop(); 2431 content::RunMessageLoop();
2405 EXPECT_TRUE(GetPrerenderContents() == NULL); 2432 EXPECT_TRUE(GetPrerenderContents() == NULL);
2406 } 2433 }
2407 2434
2408 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderEvents) { 2435 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderEvents) {
2409 PrerenderTestURL("files/prerender/prerender_page.html", 2436 PrerenderTestURL("files/prerender/prerender_page.html",
2410 FINAL_STATUS_CANCELLED, 1); 2437 FINAL_STATUS_CANCELLED, 1);
2411 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 2438 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
2439 EXPECT_TRUE(DidReceivePrerenderLoadEventForLinkNumber(0));
2412 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0)); 2440 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
2413 2441
2414 MessageLoop::current()->PostTask( 2442 MessageLoop::current()->PostTask(
2415 FROM_HERE, base::Bind(&CancelAllPrerenders, GetPrerenderManager())); 2443 FROM_HERE, base::Bind(&CancelAllPrerenders, GetPrerenderManager()));
2416 content::RunMessageLoop(); 2444 content::RunMessageLoop();
2417 2445
2418 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0)); 2446 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
2419 EXPECT_TRUE(DidReceivePrerenderStopEventForLinkNumber(0)); 2447 EXPECT_TRUE(DidReceivePrerenderStopEventForLinkNumber(0));
2448 EXPECT_FALSE(HadPrerenderEventErrors());
2449 }
2450
2451 // PrerenderBrowserTest.PrerenderEventsNoLoad may pass flakily on regression,
2452 // so please be aggressive about filing bugs when this test is failing.
2453 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderEventsNoLoad) {
2454 // This should be canceled.
2455 PrerenderTestURL("files/prerender/prerender_http_auth_container.html",
2456 FINAL_STATUS_AUTH_NEEDED,
2457 1);
2458 EXPECT_TRUE(DidReceivePrerenderStartEventForLinkNumber(0));
2459 EXPECT_FALSE(DidReceivePrerenderLoadEventForLinkNumber(0));
2460 EXPECT_FALSE(DidReceivePrerenderStopEventForLinkNumber(0));
2461 EXPECT_FALSE(HadPrerenderEventErrors());
2420 } 2462 }
2421 2463
2422 // Prerendering and history tests. 2464 // Prerendering and history tests.
2423 // The prerendered page is navigated to in several ways [navigate via 2465 // The prerendered page is navigated to in several ways [navigate via
2424 // omnibox, click on link, key-modified click to open in background tab, etc], 2466 // omnibox, click on link, key-modified click to open in background tab, etc],
2425 // followed by a navigation to another page from the prerendered page, followed 2467 // followed by a navigation to another page from the prerendered page, followed
2426 // by a back navigation. 2468 // by a back navigation.
2427 2469
2428 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderNavigateClickGoBack) { 2470 IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, PrerenderNavigateClickGoBack) {
2429 PrerenderTestURL("files/prerender/prerender_page_with_link.html", 2471 PrerenderTestURL("files/prerender/prerender_page_with_link.html",
(...skipping 219 matching lines...) Expand 10 before | Expand all | Expand 10 after
2649 channel_close_watcher.WatchChannel( 2691 channel_close_watcher.WatchChannel(
2650 chrome::GetActiveWebContents(browser())->GetRenderProcessHost()); 2692 chrome::GetActiveWebContents(browser())->GetRenderProcessHost());
2651 NavigateToDestURL(); 2693 NavigateToDestURL();
2652 channel_close_watcher.WaitForChannelClose(); 2694 channel_close_watcher.WaitForChannelClose();
2653 2695
2654 ASSERT_TRUE(IsEmptyPrerenderLinkManager()); 2696 ASSERT_TRUE(IsEmptyPrerenderLinkManager());
2655 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message(); 2697 ASSERT_TRUE(catcher.GetNextResult()) << catcher.message();
2656 } 2698 }
2657 2699
2658 } // namespace prerender 2700 } // namespace prerender
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/prerender/prerender_contents.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698