Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / content / browser / cross_site_transfer_browsertest.cc
blob8082eb239e0a15f78892944bdf884c4b4803a81f
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
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/strings/stringprintf.h"
7 #include "content/browser/loader/resource_dispatcher_host_impl.h"
8 #include "content/public/browser/navigation_entry.h"
9 #include "content/public/browser/resource_dispatcher_host_delegate.h"
10 #include "content/public/browser/resource_throttle.h"
11 #include "content/public/browser/web_contents.h"
12 #include "content/public/common/content_switches.h"
13 #include "content/public/test/browser_test_utils.h"
14 #include "content/public/test/content_browser_test.h"
15 #include "content/public/test/content_browser_test_utils.h"
16 #include "content/public/test/test_navigation_observer.h"
17 #include "content/shell/browser/shell.h"
18 #include "content/shell/browser/shell_content_browser_client.h"
19 #include "content/shell/browser/shell_resource_dispatcher_host_delegate.h"
20 #include "net/base/escape.h"
21 #include "net/dns/mock_host_resolver.h"
22 #include "net/url_request/url_request.h"
23 #include "net/url_request/url_request_status.h"
24 #include "url/gurl.h"
26 namespace content {
28 // Tracks a single request for a specified URL, and allows waiting until the
29 // request is destroyed, and then inspecting whether it completed successfully.
30 class TrackingResourceDispatcherHostDelegate
31 : public ShellResourceDispatcherHostDelegate {
32 public:
33 TrackingResourceDispatcherHostDelegate() : throttle_created_(false) {
36 virtual void RequestBeginning(
37 net::URLRequest* request,
38 ResourceContext* resource_context,
39 AppCacheService* appcache_service,
40 ResourceType resource_type,
41 ScopedVector<ResourceThrottle>* throttles) OVERRIDE {
42 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
43 ShellResourceDispatcherHostDelegate::RequestBeginning(
44 request, resource_context, appcache_service, resource_type, throttles);
45 // Expect only a single request for the tracked url.
46 ASSERT_FALSE(throttle_created_);
47 // If this is a request for the tracked URL, add a throttle to track it.
48 if (request->url() == tracked_url_)
49 throttles->push_back(new TrackingThrottle(request, this));
52 // Starts tracking a URL. The request for previously tracked URL, if any,
53 // must have been made and deleted before calling this function.
54 void SetTrackedURL(const GURL& tracked_url) {
55 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
56 // Should not currently be tracking any URL.
57 ASSERT_FALSE(run_loop_);
59 // Create a RunLoop that will be stopped once the request for the tracked
60 // URL has been destroyed, to allow tracking the URL while also waiting for
61 // other events.
62 run_loop_.reset(new base::RunLoop());
64 BrowserThread::PostTask(
65 BrowserThread::IO, FROM_HERE,
66 base::Bind(
67 &TrackingResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
68 base::Unretained(this),
69 tracked_url));
72 // Waits until the tracked URL has been requests, and the request for it has
73 // been destroyed.
74 bool WaitForTrackedURLAndGetCompleted() {
75 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
76 run_loop_->Run();
77 run_loop_.reset();
78 return tracked_request_completed_;
81 private:
82 // ResourceThrottle attached to request for the tracked URL. On destruction,
83 // passes the final URLRequestStatus back to the delegate.
84 class TrackingThrottle : public ResourceThrottle {
85 public:
86 TrackingThrottle(net::URLRequest* request,
87 TrackingResourceDispatcherHostDelegate* tracker)
88 : request_(request), tracker_(tracker) {
91 virtual ~TrackingThrottle() {
92 // If the request is deleted without being cancelled, its status will
93 // indicate it succeeded, so have to check if the request is still pending
94 // as well.
95 tracker_->OnTrackedRequestDestroyed(
96 !request_->is_pending() && request_->status().is_success());
99 // ResourceThrottle implementation:
100 virtual const char* GetNameForLogging() const OVERRIDE {
101 return "TrackingThrottle";
104 private:
105 net::URLRequest* request_;
106 TrackingResourceDispatcherHostDelegate* tracker_;
108 DISALLOW_COPY_AND_ASSIGN(TrackingThrottle);
111 void SetTrackedURLOnIOThread(const GURL& tracked_url) {
112 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
113 throttle_created_ = false;
114 tracked_url_ = tracked_url;
117 void OnTrackedRequestDestroyed(bool completed) {
118 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
119 tracked_request_completed_ = completed;
120 tracked_url_ = GURL();
122 BrowserThread::PostTask(
123 BrowserThread::UI, FROM_HERE, run_loop_->QuitClosure());
126 // These live on the IO thread.
127 GURL tracked_url_;
128 bool throttle_created_;
130 // This is created and destroyed on the UI thread, but stopped on the IO
131 // thread.
132 scoped_ptr<base::RunLoop> run_loop_;
134 // Set on the IO thread while |run_loop_| is non-NULL, read on the UI thread
135 // after deleting run_loop_.
136 bool tracked_request_completed_;
138 DISALLOW_COPY_AND_ASSIGN(TrackingResourceDispatcherHostDelegate);
141 // WebContentsDelegate that fails to open a URL when there's a request that
142 // needs to be transferred between renderers.
143 class NoTransferRequestDelegate : public WebContentsDelegate {
144 public:
145 NoTransferRequestDelegate() {}
147 virtual WebContents* OpenURLFromTab(WebContents* source,
148 const OpenURLParams& params) OVERRIDE {
149 bool is_transfer =
150 (params.transferred_global_request_id != GlobalRequestID());
151 if (is_transfer)
152 return NULL;
153 NavigationController::LoadURLParams load_url_params(params.url);
154 load_url_params.referrer = params.referrer;
155 load_url_params.frame_tree_node_id = params.frame_tree_node_id;
156 load_url_params.transition_type = params.transition;
157 load_url_params.extra_headers = params.extra_headers;
158 load_url_params.should_replace_current_entry =
159 params.should_replace_current_entry;
160 load_url_params.is_renderer_initiated = true;
161 source->GetController().LoadURLWithParams(load_url_params);
162 return source;
165 private:
166 DISALLOW_COPY_AND_ASSIGN(NoTransferRequestDelegate);
169 class CrossSiteTransferTest : public ContentBrowserTest {
170 public:
171 CrossSiteTransferTest() : old_delegate_(NULL) {
174 // ContentBrowserTest implementation:
175 virtual void SetUpOnMainThread() OVERRIDE {
176 BrowserThread::PostTask(
177 BrowserThread::IO, FROM_HERE,
178 base::Bind(
179 &CrossSiteTransferTest::InjectResourceDisptcherHostDelegate,
180 base::Unretained(this)));
183 virtual void TearDownOnMainThread() OVERRIDE {
184 BrowserThread::PostTask(
185 BrowserThread::IO, FROM_HERE,
186 base::Bind(
187 &CrossSiteTransferTest::RestoreResourceDisptcherHostDelegate,
188 base::Unretained(this)));
191 protected:
192 void NavigateToURLContentInitiated(Shell* window,
193 const GURL& url,
194 bool should_replace_current_entry,
195 bool should_wait_for_navigation) {
196 std::string script;
197 if (should_replace_current_entry)
198 script = base::StringPrintf("location.replace('%s')", url.spec().c_str());
199 else
200 script = base::StringPrintf("location.href = '%s'", url.spec().c_str());
201 TestNavigationObserver load_observer(shell()->web_contents(), 1);
202 bool result = ExecuteScript(window->web_contents(), script);
203 EXPECT_TRUE(result);
204 if (should_wait_for_navigation)
205 load_observer.Wait();
208 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
209 // Use --site-per-process to force process swaps for cross-site transfers.
210 command_line->AppendSwitch(switches::kSitePerProcess);
213 void InjectResourceDisptcherHostDelegate() {
214 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
215 old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
216 ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
219 void RestoreResourceDisptcherHostDelegate() {
220 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
221 ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
222 old_delegate_ = NULL;
225 TrackingResourceDispatcherHostDelegate& tracking_delegate() {
226 return tracking_delegate_;
229 private:
230 TrackingResourceDispatcherHostDelegate tracking_delegate_;
231 ResourceDispatcherHostDelegate* old_delegate_;
234 // The following tests crash in the ThreadSanitizer runtime,
235 // http://crbug.com/356758.
236 #if defined(THREAD_SANITIZER)
237 #define MAYBE_ReplaceEntryCrossProcessThenTransfer \
238 DISABLED_ReplaceEntryCrossProcessThenTransfer
239 #define MAYBE_ReplaceEntryCrossProcessTwice \
240 DISABLED_ReplaceEntryCrossProcessTwice
241 #else
242 #define MAYBE_ReplaceEntryCrossProcessThenTransfer \
243 ReplaceEntryCrossProcessThenTransfer
244 #define MAYBE_ReplaceEntryCrossProcessTwice ReplaceEntryCrossProcessTwice
245 #endif
246 // Tests that the |should_replace_current_entry| flag persists correctly across
247 // request transfers that began with a cross-process navigation.
248 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
249 MAYBE_ReplaceEntryCrossProcessThenTransfer) {
250 const NavigationController& controller =
251 shell()->web_contents()->GetController();
252 host_resolver()->AddRule("*", "127.0.0.1");
253 ASSERT_TRUE(test_server()->Start());
255 // These must all stay in scope with replace_host.
256 GURL::Replacements replace_host;
257 std::string a_com("A.com");
258 std::string b_com("B.com");
260 // Navigate to a starting URL, so there is a history entry to replace.
261 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
262 NavigateToURL(shell(), url1);
264 // Force all future navigations to transfer. Note that this includes same-site
265 // navigiations which may cause double process swaps (via OpenURL and then via
266 // transfer). This test intentionally exercises that case.
267 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
269 // Navigate to a page on A.com with entry replacement. This navigation is
270 // cross-site, so the renderer will send it to the browser via OpenURL to give
271 // to a new process. It will then be transferred into yet another process due
272 // to the call above.
273 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
274 replace_host.SetHostStr(a_com);
275 url2 = url2.ReplaceComponents(replace_host);
276 // Used to make sure the request for url2 succeeds, and there was only one of
277 // them.
278 tracking_delegate().SetTrackedURL(url2);
279 NavigateToURLContentInitiated(shell(), url2, true, true);
281 // There should be one history entry. url2 should have replaced url1.
282 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
283 EXPECT_EQ(1, controller.GetEntryCount());
284 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
285 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
286 // Make sure the request succeeded.
287 EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
289 // Now navigate as before to a page on B.com, but normally (without
290 // replacement). This will still perform a double process-swap as above, via
291 // OpenURL and then transfer.
292 GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
293 replace_host.SetHostStr(b_com);
294 url3 = url3.ReplaceComponents(replace_host);
295 // Used to make sure the request for url3 succeeds, and there was only one of
296 // them.
297 tracking_delegate().SetTrackedURL(url3);
298 NavigateToURLContentInitiated(shell(), url3, false, true);
300 // There should be two history entries. url2 should have replaced url1. url2
301 // should not have replaced url3.
302 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
303 EXPECT_EQ(2, controller.GetEntryCount());
304 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
305 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
306 EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
308 // Make sure the request succeeded.
309 EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
312 // Tests that the |should_replace_current_entry| flag persists correctly across
313 // request transfers that began with a content-initiated in-process
314 // navigation. This test is the same as the test above, except transfering from
315 // in-process.
316 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
317 ReplaceEntryInProcessThenTranfers) {
318 const NavigationController& controller =
319 shell()->web_contents()->GetController();
320 ASSERT_TRUE(test_server()->Start());
322 // Navigate to a starting URL, so there is a history entry to replace.
323 GURL url = test_server()->GetURL("files/site_isolation/blank.html?1");
324 NavigateToURL(shell(), url);
326 // Force all future navigations to transfer. Note that this includes same-site
327 // navigiations which may cause double process swaps (via OpenURL and then via
328 // transfer). All navigations in this test are same-site, so it only swaps
329 // processes via request transfer.
330 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
332 // Navigate in-process with entry replacement. It will then be transferred
333 // into a new one due to the call above.
334 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
335 NavigateToURLContentInitiated(shell(), url2, true, true);
337 // There should be one history entry. url2 should have replaced url1.
338 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
339 EXPECT_EQ(1, controller.GetEntryCount());
340 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
341 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
343 // Now navigate as before, but without replacement.
344 GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
345 NavigateToURLContentInitiated(shell(), url3, false, true);
347 // There should be two history entries. url2 should have replaced url1. url2
348 // should not have replaced url3.
349 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
350 EXPECT_EQ(2, controller.GetEntryCount());
351 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
352 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
353 EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
356 // Tests that the |should_replace_current_entry| flag persists correctly across
357 // request transfers that cross processes twice from renderer policy.
358 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
359 MAYBE_ReplaceEntryCrossProcessTwice) {
360 const NavigationController& controller =
361 shell()->web_contents()->GetController();
362 host_resolver()->AddRule("*", "127.0.0.1");
363 ASSERT_TRUE(test_server()->Start());
365 // These must all stay in scope with replace_host.
366 GURL::Replacements replace_host;
367 std::string a_com("A.com");
368 std::string b_com("B.com");
370 // Navigate to a starting URL, so there is a history entry to replace.
371 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
372 NavigateToURL(shell(), url1);
374 // Navigate to a page on A.com which redirects to B.com with entry
375 // replacement. This will switch processes via OpenURL twice. First to A.com,
376 // and second in response to the server redirect to B.com. The second swap is
377 // also renderer-initiated via OpenURL because decidePolicyForNavigation is
378 // currently applied on redirects.
379 GURL url2b = test_server()->GetURL("files/site_isolation/blank.html?2");
380 replace_host.SetHostStr(b_com);
381 url2b = url2b.ReplaceComponents(replace_host);
382 GURL url2a = test_server()->GetURL(
383 "server-redirect?" + net::EscapeQueryParamValue(url2b.spec(), false));
384 replace_host.SetHostStr(a_com);
385 url2a = url2a.ReplaceComponents(replace_host);
386 NavigateToURLContentInitiated(shell(), url2a, true, true);
388 // There should be one history entry. url2b should have replaced url1.
389 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
390 EXPECT_EQ(1, controller.GetEntryCount());
391 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
392 EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
394 // Now repeat without replacement.
395 GURL url3b = test_server()->GetURL("files/site_isolation/blank.html?3");
396 replace_host.SetHostStr(b_com);
397 url3b = url3b.ReplaceComponents(replace_host);
398 GURL url3a = test_server()->GetURL(
399 "server-redirect?" + net::EscapeQueryParamValue(url3b.spec(), false));
400 replace_host.SetHostStr(a_com);
401 url3a = url3a.ReplaceComponents(replace_host);
402 NavigateToURLContentInitiated(shell(), url3a, false, true);
404 // There should be two history entries. url2b should have replaced url1. url2b
405 // should not have replaced url3b.
406 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
407 EXPECT_EQ(2, controller.GetEntryCount());
408 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
409 EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
410 EXPECT_EQ(url3b, controller.GetEntryAtIndex(1)->GetURL());
413 // Tests that the request is destroyed when a cross process navigation is
414 // cancelled.
415 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest, NoLeakOnCrossSiteCancel) {
416 const NavigationController& controller =
417 shell()->web_contents()->GetController();
418 host_resolver()->AddRule("*", "127.0.0.1");
419 ASSERT_TRUE(test_server()->Start());
421 // These must all stay in scope with replace_host.
422 GURL::Replacements replace_host;
423 std::string a_com("A.com");
424 std::string b_com("B.com");
426 // Navigate to a starting URL, so there is a history entry to replace.
427 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
428 NavigateToURL(shell(), url1);
430 // Force all future navigations to transfer.
431 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
433 NoTransferRequestDelegate no_transfer_request_delegate;
434 WebContentsDelegate* old_delegate = shell()->web_contents()->GetDelegate();
435 shell()->web_contents()->SetDelegate(&no_transfer_request_delegate);
437 // Navigate to a page on A.com with entry replacement. This navigation is
438 // cross-site, so the renderer will send it to the browser via OpenURL to give
439 // to a new process. It will then be transferred into yet another process due
440 // to the call above.
441 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
442 replace_host.SetHostStr(a_com);
443 url2 = url2.ReplaceComponents(replace_host);
444 // Used to make sure the second request is cancelled, and there is only one
445 // request for url2.
446 tracking_delegate().SetTrackedURL(url2);
448 // Don't wait for the navigation to complete, since that never happens in
449 // this case.
450 NavigateToURLContentInitiated(shell(), url2, false, false);
452 // There should be one history entry, with url1.
453 EXPECT_EQ(1, controller.GetEntryCount());
454 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
455 EXPECT_EQ(url1, controller.GetEntryAtIndex(0)->GetURL());
457 // Make sure the request for url2 did not complete.
458 EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
460 shell()->web_contents()->SetDelegate(old_delegate);
463 } // namespace content