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

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

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