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

Side by Side Diff: chrome/browser/history/redirect_uitest.cc

Issue 16490: Add FTP unit test in preparation for portable FTP implementation.... (Closed) Base URL: svn://chrome-svn/chrome/trunk/src/
Patch Set: Created 11 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 | « chrome/browser/errorpage_uitest.cc ('k') | chrome/browser/interstitial_page_uitest.cc » ('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) 2006-2008 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2006-2008 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 // Navigates the browser to server and client redirect pages and makes sure 5 // Navigates the browser to server and client redirect pages and makes sure
6 // that the correct redirects are reflected in the history database. Errors 6 // that the correct redirects are reflected in the history database. Errors
7 // here might indicate that WebKit changed the calls our glue layer gets in 7 // here might indicate that WebKit changed the calls our glue layer gets in
8 // the case of redirects. It may also mean problems with the history system. 8 // the case of redirects. It may also mean problems with the history system.
9 9
10 #include "base/scoped_ptr.h" 10 #include "base/scoped_ptr.h"
(...skipping 12 matching lines...) Expand all
23 class RedirectTest : public UITest { 23 class RedirectTest : public UITest {
24 protected: 24 protected:
25 RedirectTest() : UITest() { 25 RedirectTest() : UITest() {
26 } 26 }
27 }; 27 };
28 28
29 } // namespace 29 } // namespace
30 30
31 // Tests a single server redirect 31 // Tests a single server redirect
32 TEST_F(RedirectTest, Server) { 32 TEST_F(RedirectTest, Server) {
33 TestServer server(kDocRoot); 33 scoped_refptr<HTTPTestServer> server =
34 HTTPTestServer::CreateServer(kDocRoot);
35 ASSERT_TRUE(NULL != server.get());
34 36
35 GURL final_url = server.TestServerPageW(std::wstring()); 37 GURL final_url = server->TestServerPageW(std::wstring());
36 GURL first_url = server.TestServerPageW( 38 GURL first_url = server->TestServerPageW(
37 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec())); 39 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec()));
38 40
39 NavigateToURL(first_url); 41 NavigateToURL(first_url);
40 42
41 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 43 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
42 ASSERT_TRUE(tab_proxy.get()); 44 ASSERT_TRUE(tab_proxy.get());
43 45
44 std::vector<GURL> redirects; 46 std::vector<GURL> redirects;
45 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 47 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
46 48
47 ASSERT_EQ(1, redirects.size()); 49 ASSERT_EQ(1, redirects.size());
48 EXPECT_EQ(final_url.spec(), redirects[0].spec()); 50 EXPECT_EQ(final_url.spec(), redirects[0].spec());
49 } 51 }
50 52
51 // Tests a single client redirect. 53 // Tests a single client redirect.
52 TEST_F(RedirectTest, Client) { 54 TEST_F(RedirectTest, Client) {
53 TestServer server(kDocRoot); 55 scoped_refptr<HTTPTestServer> server =
56 HTTPTestServer::CreateServer(kDocRoot);
57 ASSERT_TRUE(NULL != server.get());
54 58
55 GURL final_url = server.TestServerPageW(std::wstring()); 59 GURL final_url = server->TestServerPageW(std::wstring());
56 GURL first_url = server.TestServerPageW( 60 GURL first_url = server->TestServerPageW(
57 std::wstring(L"client-redirect?") + UTF8ToWide(final_url.spec())); 61 std::wstring(L"client-redirect?") + UTF8ToWide(final_url.spec()));
58 62
59 // We need the sleep for the client redirects, because it appears as two 63 // We need the sleep for the client redirects, because it appears as two
60 // page visits in the browser. 64 // page visits in the browser.
61 NavigateToURL(first_url); 65 NavigateToURL(first_url);
62 Sleep(action_timeout_ms()); 66 Sleep(action_timeout_ms());
63 67
64 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 68 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
65 ASSERT_TRUE(tab_proxy.get()); 69 ASSERT_TRUE(tab_proxy.get());
66 70
67 std::vector<GURL> redirects; 71 std::vector<GURL> redirects;
68 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 72 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
69 73
70 ASSERT_EQ(1, redirects.size()); 74 ASSERT_EQ(1, redirects.size());
71 EXPECT_EQ(final_url.spec(), redirects[0].spec()); 75 EXPECT_EQ(final_url.spec(), redirects[0].spec());
72 } 76 }
73 77
74 TEST_F(RedirectTest, ClientEmptyReferer) { 78 TEST_F(RedirectTest, ClientEmptyReferer) {
75 TestServer server(kDocRoot); 79 scoped_refptr<HTTPTestServer> server =
80 HTTPTestServer::CreateServer(kDocRoot);
81 ASSERT_TRUE(NULL != server.get());
76 82
77 GURL final_url = server.TestServerPageW(std::wstring()); 83 GURL final_url = server->TestServerPageW(std::wstring());
78 std::wstring test_file = test_data_directory_; 84 std::wstring test_file = test_data_directory_;
79 file_util::AppendToPath(&test_file, L"file_client_redirect.html"); 85 file_util::AppendToPath(&test_file, L"file_client_redirect.html");
80 GURL first_url = net::FilePathToFileURL(test_file); 86 GURL first_url = net::FilePathToFileURL(test_file);
81 87
82 NavigateToURL(first_url); 88 NavigateToURL(first_url);
83 std::vector<GURL> redirects; 89 std::vector<GURL> redirects;
84 // We need the sleeps for the client redirects, because it appears as two 90 // We need the sleeps for the client redirects, because it appears as two
85 // page visits in the browser. And note for this test the browser actually 91 // page visits in the browser. And note for this test the browser actually
86 // loads the html file on disk, rather than just getting a response from 92 // loads the html file on disk, rather than just getting a response from
87 // the TestServer. 93 // the TestServer.
88 for (int i = 0; i < 10; ++i) { 94 for (int i = 0; i < 10; ++i) {
89 Sleep(kWaitForActionMaxMsec / 10); 95 Sleep(kWaitForActionMaxMsec / 10);
90 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 96 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
91 ASSERT_TRUE(tab_proxy.get()); 97 ASSERT_TRUE(tab_proxy.get());
92 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 98 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
93 if (!redirects.empty()) 99 if (!redirects.empty())
94 break; 100 break;
95 } 101 }
96 102
97 EXPECT_EQ(1, redirects.size()); 103 EXPECT_EQ(1, redirects.size());
98 EXPECT_EQ(final_url.spec(), redirects[0].spec()); 104 EXPECT_EQ(final_url.spec(), redirects[0].spec());
99 } 105 }
100 106
101 // Tests to make sure a location change when a pending redirect exists isn't 107 // Tests to make sure a location change when a pending redirect exists isn't
102 // flagged as a redirect. 108 // flagged as a redirect.
103 TEST_F(RedirectTest, ClientCancelled) { 109 TEST_F(RedirectTest, ClientCancelled) {
104 std::wstring first_path = test_data_directory_; 110 std::wstring first_path = test_data_directory_;
105 file_util::AppendToPath(&first_path, L"cancelled_redirect_test.html"); 111 file_util::AppendToPath(&first_path, L"cancelled_redirect_test.html");
106 GURL first_url = net::FilePathToFileURL(first_path); 112 GURL first_url = net::FilePathToFileURL(first_path);
107 113
108 NavigateToURL(first_url); 114 NavigateToURL(first_url);
(...skipping 20 matching lines...) Expand all
129 ASSERT_TRUE(net::FileURLToFilePath(current_url, &current_path)); 135 ASSERT_TRUE(net::FileURLToFilePath(current_url, &current_path));
130 // Path should remain unchanged. 136 // Path should remain unchanged.
131 EXPECT_EQ(StringToLowerASCII(first_path), StringToLowerASCII(current_path)); 137 EXPECT_EQ(StringToLowerASCII(first_path), StringToLowerASCII(current_path));
132 EXPECT_EQ(final_ref, current_url.ref()); 138 EXPECT_EQ(final_ref, current_url.ref());
133 } 139 }
134 140
135 // Tests a client->server->server redirect 141 // Tests a client->server->server redirect
136 // TODO(creis): This is disabled temporarily while I figure out why it is 142 // TODO(creis): This is disabled temporarily while I figure out why it is
137 // failing. 143 // failing.
138 TEST_F(RedirectTest, DISABLED_ClientServerServer) { 144 TEST_F(RedirectTest, DISABLED_ClientServerServer) {
139 TestServer server(kDocRoot); 145 scoped_refptr<HTTPTestServer> server =
146 HTTPTestServer::CreateServer(kDocRoot);
147 ASSERT_TRUE(NULL != server.get());
140 148
141 GURL final_url = server.TestServerPageW(std::wstring()); 149 GURL final_url = server->TestServerPageW(std::wstring());
142 GURL next_to_last = server.TestServerPageW( 150 GURL next_to_last = server->TestServerPageW(
143 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec())); 151 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec()));
144 GURL second_url = server.TestServerPageW( 152 GURL second_url = server->TestServerPageW(
145 std::wstring(L"server-redirect?") + UTF8ToWide(next_to_last.spec())); 153 std::wstring(L"server-redirect?") + UTF8ToWide(next_to_last.spec()));
146 GURL first_url = server.TestServerPageW( 154 GURL first_url = server->TestServerPageW(
147 std::wstring(L"client-redirect?") + UTF8ToWide(second_url.spec())); 155 std::wstring(L"client-redirect?") + UTF8ToWide(second_url.spec()));
148 std::vector<GURL> redirects; 156 std::vector<GURL> redirects;
149 157
150 // We need the sleep for the client redirects, because it appears as two 158 // We need the sleep for the client redirects, because it appears as two
151 // page visits in the browser. 159 // page visits in the browser.
152 NavigateToURL(first_url); 160 NavigateToURL(first_url);
153 161
154 for (int i = 0; i < 10; ++i) { 162 for (int i = 0; i < 10; ++i) {
155 Sleep(kWaitForActionMaxMsec / 10); 163 Sleep(kWaitForActionMaxMsec / 10);
156 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 164 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
157 ASSERT_TRUE(tab_proxy.get()); 165 ASSERT_TRUE(tab_proxy.get());
158 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 166 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
159 if (!redirects.empty()) 167 if (!redirects.empty())
160 break; 168 break;
161 } 169 }
162 170
163 ASSERT_EQ(3, redirects.size()); 171 ASSERT_EQ(3, redirects.size());
164 EXPECT_EQ(second_url.spec(), redirects[0].spec()); 172 EXPECT_EQ(second_url.spec(), redirects[0].spec());
165 EXPECT_EQ(next_to_last.spec(), redirects[1].spec()); 173 EXPECT_EQ(next_to_last.spec(), redirects[1].spec());
166 EXPECT_EQ(final_url.spec(), redirects[2].spec()); 174 EXPECT_EQ(final_url.spec(), redirects[2].spec());
167 } 175 }
168 176
169 // Tests that the "#reference" gets preserved across server redirects. 177 // Tests that the "#reference" gets preserved across server redirects.
170 TEST_F(RedirectTest, ServerReference) { 178 TEST_F(RedirectTest, ServerReference) {
171 TestServer server(kDocRoot); 179 scoped_refptr<HTTPTestServer> server =
180 HTTPTestServer::CreateServer(kDocRoot);
181 ASSERT_TRUE(NULL != server.get());
172 182
173 const std::string ref("reference"); 183 const std::string ref("reference");
174 184
175 GURL final_url = server.TestServerPageW(std::wstring()); 185 GURL final_url = server->TestServerPageW(std::wstring());
176 GURL initial_url = server.TestServerPageW( 186 GURL initial_url = server->TestServerPageW(
177 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec()) + 187 std::wstring(L"server-redirect?") + UTF8ToWide(final_url.spec()) +
178 L"#" + UTF8ToWide(ref)); 188 L"#" + UTF8ToWide(ref));
179 189
180 NavigateToURL(initial_url); 190 NavigateToURL(initial_url);
181 191
182 GURL url = GetActiveTabURL(); 192 GURL url = GetActiveTabURL();
183 EXPECT_EQ(ref, url.ref()); 193 EXPECT_EQ(ref, url.ref());
184 } 194 }
185 195
186 // Test that redirect from http:// to file:// : 196 // Test that redirect from http:// to file:// :
187 // A) does not crash the browser or confuse the redirect chain, see bug 1080873 197 // A) does not crash the browser or confuse the redirect chain, see bug 1080873
188 // B) does not take place. 198 // B) does not take place.
189 TEST_F(RedirectTest, NoHttpToFile) { 199 TEST_F(RedirectTest, NoHttpToFile) {
190 TestServer server(kDocRoot); 200 scoped_refptr<HTTPTestServer> server =
201 HTTPTestServer::CreateServer(kDocRoot);
202 ASSERT_TRUE(NULL != server.get());
191 std::wstring test_file = test_data_directory_; 203 std::wstring test_file = test_data_directory_;
192 file_util::AppendToPath(&test_file, L"http_to_file.html"); 204 file_util::AppendToPath(&test_file, L"http_to_file.html");
193 GURL file_url = net::FilePathToFileURL(test_file); 205 GURL file_url = net::FilePathToFileURL(test_file);
194 206
195 GURL initial_url = server.TestServerPageW( 207 GURL initial_url = server->TestServerPageW(
196 std::wstring(L"client-redirect?") + UTF8ToWide(file_url.spec())); 208 std::wstring(L"client-redirect?") + UTF8ToWide(file_url.spec()));
197 209
198 NavigateToURL(initial_url); 210 NavigateToURL(initial_url);
199 // UITest will check for crashes. We make sure the title doesn't match the 211 // UITest will check for crashes. We make sure the title doesn't match the
200 // title from the file, because the nav should not have taken place. 212 // title from the file, because the nav should not have taken place.
201 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 213 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
202 ASSERT_TRUE(tab_proxy.get()); 214 ASSERT_TRUE(tab_proxy.get());
203 std::wstring actual_title; 215 std::wstring actual_title;
204 tab_proxy->GetTabTitle(&actual_title); 216 tab_proxy->GetTabTitle(&actual_title);
205 EXPECT_NE(L"File!", actual_title); 217 EXPECT_NE(L"File!", actual_title);
206 } 218 }
207 219
208 // Ensures that non-user initiated location changes (within page) are 220 // Ensures that non-user initiated location changes (within page) are
209 // flagged as client redirects. See bug 1139823. 221 // flagged as client redirects. See bug 1139823.
210 TEST_F(RedirectTest, ClientFragments) { 222 TEST_F(RedirectTest, ClientFragments) {
211 TestServer server(kDocRoot); 223 scoped_refptr<HTTPTestServer> server =
224 HTTPTestServer::CreateServer(kDocRoot);
225 ASSERT_TRUE(NULL != server.get());
226
212 std::wstring test_file = test_data_directory_; 227 std::wstring test_file = test_data_directory_;
213 file_util::AppendToPath(&test_file, L"ref_redirect.html"); 228 file_util::AppendToPath(&test_file, L"ref_redirect.html");
214 GURL first_url = net::FilePathToFileURL(test_file); 229 GURL first_url = net::FilePathToFileURL(test_file);
215 std::vector<GURL> redirects; 230 std::vector<GURL> redirects;
216 231
217 NavigateToURL(first_url); 232 NavigateToURL(first_url);
218 for (int i = 0; i < 10; ++i) { 233 for (int i = 0; i < 10; ++i) {
219 Sleep(kWaitForActionMaxMsec / 10); 234 Sleep(kWaitForActionMaxMsec / 10);
220 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 235 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
221 ASSERT_TRUE(tab_proxy.get()); 236 ASSERT_TRUE(tab_proxy.get());
222 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 237 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
223 if (!redirects.empty()) 238 if (!redirects.empty())
224 break; 239 break;
225 } 240 }
226 241
227 EXPECT_EQ(1, redirects.size()); 242 EXPECT_EQ(1, redirects.size());
228 EXPECT_EQ(first_url.spec() + "#myanchor", redirects[0].spec()); 243 EXPECT_EQ(first_url.spec() + "#myanchor", redirects[0].spec());
229 } 244 }
230 245
231 // TODO(timsteele): This is disabled because our current testserver can't 246 // TODO(timsteele): This is disabled because our current testserver can't
232 // handle multiple requests in parallel, making it hang on the first request to 247 // handle multiple requests in parallel, making it hang on the first request
233 // /slow?60. It's unable to serve our second request for files/title2.html until 248 // to /slow?60. It's unable to serve our second request for files/title2.html
234 // /slow? completes, which doesn't give the desired behavior. We could 249 // until /slow? completes, which doesn't give the desired behavior. We could
235 // alternatively load the second page from disk, but we would need to start the 250 // alternatively load the second page from disk, but we would need to start
236 // browser for this testcase with --process-per-tab, and I don't think we can do 251 // the browser for this testcase with --process-per-tab, and I don't think
237 // this at test-case-level granularity at the moment. 252 // we can do this at test-case-level granularity at the moment.
238 TEST_F(RedirectTest, DISABLED_ClientCancelledByNewNavigationAfterProvisionalLoad ) { 253 TEST_F(RedirectTest,
254 DISABLED_ClientCancelledByNewNavigationAfterProvisionalLoad) {
239 // We want to initiate a second navigation after the provisional load for 255 // We want to initiate a second navigation after the provisional load for
240 // the client redirect destination has started, but before this load is 256 // the client redirect destination has started, but before this load is
241 // committed. To achieve this, we tell the browser to load a slow page, 257 // committed. To achieve this, we tell the browser to load a slow page,
242 // which causes it to start a provisional load, and while it is waiting 258 // which causes it to start a provisional load, and while it is waiting
243 // for the response (which means it hasn't committed the load for the client 259 // for the response (which means it hasn't committed the load for the client
244 // redirect destination page yet), we issue a new navigation request. 260 // redirect destination page yet), we issue a new navigation request.
245 TestServer server(kDocRoot); 261 scoped_refptr<HTTPTestServer> server =
246 262 HTTPTestServer::CreateServer(kDocRoot);
247 GURL final_url = server.TestServerPageW(std::wstring(L"files/title2.html")); 263 ASSERT_TRUE(NULL != server.get());
248 GURL slow = server.TestServerPageW(std::wstring(L"slow?60")); 264
249 GURL first_url = server.TestServerPageW( 265 GURL final_url = server->TestServerPageW(std::wstring(L"files/title2.html"));
266 GURL slow = server->TestServerPageW(std::wstring(L"slow?60"));
267 GURL first_url = server->TestServerPageW(
250 std::wstring(L"client-redirect?") + UTF8ToWide(slow.spec())); 268 std::wstring(L"client-redirect?") + UTF8ToWide(slow.spec()));
251 std::vector<GURL> redirects; 269 std::vector<GURL> redirects;
252 270
253 NavigateToURL(first_url); 271 NavigateToURL(first_url);
254 // We don't sleep here - the first navigation won't have been committed yet 272 // We don't sleep here - the first navigation won't have been committed yet
255 // because we told the server to wait a minute. This means the browser has 273 // because we told the server to wait a minute. This means the browser has
256 // started it's provisional load for the client redirect destination page but 274 // started it's provisional load for the client redirect destination page but
257 // hasn't completed. Our time is now! 275 // hasn't completed. Our time is now!
258 NavigateToURL(final_url); 276 NavigateToURL(final_url);
259 277
260 std::wstring tab_title; 278 std::wstring tab_title;
261 std::wstring final_url_title = L"Title Of Awesomeness"; 279 std::wstring final_url_title = L"Title Of Awesomeness";
262 // Wait till the final page has been loaded. 280 // Wait till the final page has been loaded.
263 for (int i = 0; i < 10; ++i) { 281 for (int i = 0; i < 10; ++i) {
264 Sleep(kWaitForActionMaxMsec / 10); 282 Sleep(kWaitForActionMaxMsec / 10);
265 scoped_ptr<TabProxy> tab_proxy(GetActiveTab()); 283 scoped_ptr<TabProxy> tab_proxy(GetActiveTab());
266 ASSERT_TRUE(tab_proxy.get()); 284 ASSERT_TRUE(tab_proxy.get());
267 ASSERT_TRUE(tab_proxy->GetTabTitle(&tab_title)); 285 ASSERT_TRUE(tab_proxy->GetTabTitle(&tab_title));
268 if (tab_title == final_url_title) { 286 if (tab_title == final_url_title) {
269 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects)); 287 ASSERT_TRUE(tab_proxy->GetRedirectsFrom(first_url, &redirects));
270 break; 288 break;
271 } 289 }
272 } 290 }
273 291
274 // Check to make sure the navigation did in fact take place and we are 292 // Check to make sure the navigation did in fact take place and we are
275 // at the expected page. 293 // at the expected page.
276 EXPECT_EQ(final_url_title, tab_title); 294 EXPECT_EQ(final_url_title, tab_title);
277 295
278 bool final_navigation_not_redirect = true; 296 bool final_navigation_not_redirect = true;
279 // Check to make sure our request for files/title2.html doesn't get flagged 297 // Check to make sure our request for files/title2.html doesn't get flagged
280 // as a client redirect from the first (/client-redirect?) page. 298 // as a client redirect from the first (/client-redirect?) page.
281 for (std::vector<GURL>::iterator it = redirects.begin(); 299 for (std::vector<GURL>::iterator it = redirects.begin();
282 it != redirects.end(); ++it) { 300 it != redirects.end(); ++it) {
283 if (final_url.spec() == it->spec()) { 301 if (final_url.spec() == it->spec()) {
284 final_navigation_not_redirect = false; 302 final_navigation_not_redirect = false;
285 break; 303 break;
286 } 304 }
287 } 305 }
288 EXPECT_TRUE(final_navigation_not_redirect); 306 EXPECT_TRUE(final_navigation_not_redirect);
289 } 307 }
290 308
OLDNEW
« no previous file with comments | « chrome/browser/errorpage_uitest.cc ('k') | chrome/browser/interstitial_page_uitest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698