Rename Animate as Begin(Main)Frame
[chromium-blink-merge.git] / content / browser / cross_site_transfer_browsertest.cc
blob05837b5dcc4081b3e11157c28295631321e4d83b
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 int child_id,
42 int route_id,
43 ScopedVector<ResourceThrottle>* throttles) OVERRIDE {
44 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
45 ShellResourceDispatcherHostDelegate::RequestBeginning(
46 request, resource_context, appcache_service, resource_type, child_id,
47 route_id, throttles);
48 // Expect only a single request for the tracked url.
49 ASSERT_FALSE(throttle_created_);
50 // If this is a request for the tracked URL, add a throttle to track it.
51 if (request->url() == tracked_url_)
52 throttles->push_back(new TrackingThrottle(request, this));
55 // Starts tracking a URL. The request for previously tracked URL, if any,
56 // must have been made and deleted before calling this function.
57 void SetTrackedURL(const GURL& tracked_url) {
58 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
59 // Should not currently be tracking any URL.
60 ASSERT_FALSE(run_loop_);
62 // Create a RunLoop that will be stopped once the request for the tracked
63 // URL has been destroyed, to allow tracking the URL while also waiting for
64 // other events.
65 run_loop_.reset(new base::RunLoop());
67 BrowserThread::PostTask(
68 BrowserThread::IO, FROM_HERE,
69 base::Bind(
70 &TrackingResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
71 base::Unretained(this),
72 tracked_url));
75 // Waits until the tracked URL has been requests, and the request for it has
76 // been destroyed.
77 bool WaitForTrackedURLAndGetCompleted() {
78 CHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
79 run_loop_->Run();
80 run_loop_.reset();
81 return tracked_request_completed_;
84 private:
85 // ResourceThrottle attached to request for the tracked URL. On destruction,
86 // passes the final URLRequestStatus back to the delegate.
87 class TrackingThrottle : public ResourceThrottle {
88 public:
89 TrackingThrottle(net::URLRequest* request,
90 TrackingResourceDispatcherHostDelegate* tracker)
91 : request_(request), tracker_(tracker) {
94 virtual ~TrackingThrottle() {
95 // If the request is deleted without being cancelled, its status will
96 // indicate it succeeded, so have to check if the request is still pending
97 // as well.
98 tracker_->OnTrackedRequestDestroyed(
99 !request_->is_pending() && request_->status().is_success());
102 // ResourceThrottle implementation:
103 virtual const char* GetNameForLogging() const OVERRIDE {
104 return "TrackingThrottle";
107 private:
108 net::URLRequest* request_;
109 TrackingResourceDispatcherHostDelegate* tracker_;
111 DISALLOW_COPY_AND_ASSIGN(TrackingThrottle);
114 void SetTrackedURLOnIOThread(const GURL& tracked_url) {
115 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
116 throttle_created_ = false;
117 tracked_url_ = tracked_url;
120 void OnTrackedRequestDestroyed(bool completed) {
121 CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
122 tracked_request_completed_ = completed;
123 tracked_url_ = GURL();
125 BrowserThread::PostTask(
126 BrowserThread::UI, FROM_HERE, run_loop_->QuitClosure());
129 // These live on the IO thread.
130 GURL tracked_url_;
131 bool throttle_created_;
133 // This is created and destroyed on the UI thread, but stopped on the IO
134 // thread.
135 scoped_ptr<base::RunLoop> run_loop_;
137 // Set on the IO thread while |run_loop_| is non-NULL, read on the UI thread
138 // after deleting run_loop_.
139 bool tracked_request_completed_;
141 DISALLOW_COPY_AND_ASSIGN(TrackingResourceDispatcherHostDelegate);
144 // WebContentsDelegate that fails to open a URL when there's a request that
145 // needs to be transferred between renderers.
146 class NoTransferRequestDelegate : public WebContentsDelegate {
147 public:
148 NoTransferRequestDelegate() {}
150 virtual WebContents* OpenURLFromTab(WebContents* source,
151 const OpenURLParams& params) OVERRIDE {
152 bool is_transfer =
153 (params.transferred_global_request_id != GlobalRequestID());
154 if (is_transfer)
155 return NULL;
156 NavigationController::LoadURLParams load_url_params(params.url);
157 load_url_params.referrer = params.referrer;
158 load_url_params.frame_tree_node_id = params.frame_tree_node_id;
159 load_url_params.transition_type = params.transition;
160 load_url_params.extra_headers = params.extra_headers;
161 load_url_params.should_replace_current_entry =
162 params.should_replace_current_entry;
163 load_url_params.is_renderer_initiated = true;
164 source->GetController().LoadURLWithParams(load_url_params);
165 return source;
168 private:
169 DISALLOW_COPY_AND_ASSIGN(NoTransferRequestDelegate);
172 class CrossSiteTransferTest : public ContentBrowserTest {
173 public:
174 CrossSiteTransferTest() : old_delegate_(NULL) {
177 // ContentBrowserTest implementation:
178 virtual void SetUpOnMainThread() OVERRIDE {
179 BrowserThread::PostTask(
180 BrowserThread::IO, FROM_HERE,
181 base::Bind(
182 &CrossSiteTransferTest::InjectResourceDisptcherHostDelegate,
183 base::Unretained(this)));
186 virtual void TearDownOnMainThread() OVERRIDE {
187 BrowserThread::PostTask(
188 BrowserThread::IO, FROM_HERE,
189 base::Bind(
190 &CrossSiteTransferTest::RestoreResourceDisptcherHostDelegate,
191 base::Unretained(this)));
194 protected:
195 void NavigateToURLContentInitiated(Shell* window,
196 const GURL& url,
197 bool should_replace_current_entry,
198 bool should_wait_for_navigation) {
199 std::string script;
200 if (should_replace_current_entry)
201 script = base::StringPrintf("location.replace('%s')", url.spec().c_str());
202 else
203 script = base::StringPrintf("location.href = '%s'", url.spec().c_str());
204 TestNavigationObserver load_observer(shell()->web_contents(), 1);
205 bool result = ExecuteScript(window->web_contents(), script);
206 EXPECT_TRUE(result);
207 if (should_wait_for_navigation)
208 load_observer.Wait();
211 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
212 // Use --site-per-process to force process swaps for cross-site transfers.
213 command_line->AppendSwitch(switches::kSitePerProcess);
216 void InjectResourceDisptcherHostDelegate() {
217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
218 old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
219 ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
222 void RestoreResourceDisptcherHostDelegate() {
223 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
224 ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
225 old_delegate_ = NULL;
228 TrackingResourceDispatcherHostDelegate& tracking_delegate() {
229 return tracking_delegate_;
232 private:
233 TrackingResourceDispatcherHostDelegate tracking_delegate_;
234 ResourceDispatcherHostDelegate* old_delegate_;
237 // The following tests crash in the ThreadSanitizer runtime,
238 // http://crbug.com/356758.
239 #if defined(THREAD_SANITIZER)
240 #define MAYBE_ReplaceEntryCrossProcessThenTransfer \
241 DISABLED_ReplaceEntryCrossProcessThenTransfer
242 #define MAYBE_ReplaceEntryCrossProcessTwice \
243 DISABLED_ReplaceEntryCrossProcessTwice
244 #else
245 #define MAYBE_ReplaceEntryCrossProcessThenTransfer \
246 ReplaceEntryCrossProcessThenTransfer
247 #define MAYBE_ReplaceEntryCrossProcessTwice ReplaceEntryCrossProcessTwice
248 #endif
249 // Tests that the |should_replace_current_entry| flag persists correctly across
250 // request transfers that began with a cross-process navigation.
251 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
252 MAYBE_ReplaceEntryCrossProcessThenTransfer) {
253 const NavigationController& controller =
254 shell()->web_contents()->GetController();
255 host_resolver()->AddRule("*", "127.0.0.1");
256 ASSERT_TRUE(test_server()->Start());
258 // These must all stay in scope with replace_host.
259 GURL::Replacements replace_host;
260 std::string a_com("A.com");
261 std::string b_com("B.com");
263 // Navigate to a starting URL, so there is a history entry to replace.
264 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
265 NavigateToURL(shell(), url1);
267 // Force all future navigations to transfer. Note that this includes same-site
268 // navigiations which may cause double process swaps (via OpenURL and then via
269 // transfer). This test intentionally exercises that case.
270 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
272 // Navigate to a page on A.com with entry replacement. This navigation is
273 // cross-site, so the renderer will send it to the browser via OpenURL to give
274 // to a new process. It will then be transferred into yet another process due
275 // to the call above.
276 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
277 replace_host.SetHostStr(a_com);
278 url2 = url2.ReplaceComponents(replace_host);
279 // Used to make sure the request for url2 succeeds, and there was only one of
280 // them.
281 tracking_delegate().SetTrackedURL(url2);
282 NavigateToURLContentInitiated(shell(), url2, true, true);
284 // There should be one history entry. url2 should have replaced url1.
285 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
286 EXPECT_EQ(1, controller.GetEntryCount());
287 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
288 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
289 // Make sure the request succeeded.
290 EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
292 // Now navigate as before to a page on B.com, but normally (without
293 // replacement). This will still perform a double process-swap as above, via
294 // OpenURL and then transfer.
295 GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
296 replace_host.SetHostStr(b_com);
297 url3 = url3.ReplaceComponents(replace_host);
298 // Used to make sure the request for url3 succeeds, and there was only one of
299 // them.
300 tracking_delegate().SetTrackedURL(url3);
301 NavigateToURLContentInitiated(shell(), url3, false, true);
303 // There should be two history entries. url2 should have replaced url1. url2
304 // should not have replaced url3.
305 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
306 EXPECT_EQ(2, controller.GetEntryCount());
307 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
308 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
309 EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
311 // Make sure the request succeeded.
312 EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
315 // Tests that the |should_replace_current_entry| flag persists correctly across
316 // request transfers that began with a content-initiated in-process
317 // navigation. This test is the same as the test above, except transfering from
318 // in-process.
319 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
320 ReplaceEntryInProcessThenTranfers) {
321 const NavigationController& controller =
322 shell()->web_contents()->GetController();
323 ASSERT_TRUE(test_server()->Start());
325 // Navigate to a starting URL, so there is a history entry to replace.
326 GURL url = test_server()->GetURL("files/site_isolation/blank.html?1");
327 NavigateToURL(shell(), url);
329 // Force all future navigations to transfer. Note that this includes same-site
330 // navigiations which may cause double process swaps (via OpenURL and then via
331 // transfer). All navigations in this test are same-site, so it only swaps
332 // processes via request transfer.
333 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
335 // Navigate in-process with entry replacement. It will then be transferred
336 // into a new one due to the call above.
337 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
338 NavigateToURLContentInitiated(shell(), url2, true, true);
340 // There should be one history entry. url2 should have replaced url1.
341 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
342 EXPECT_EQ(1, controller.GetEntryCount());
343 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
344 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
346 // Now navigate as before, but without replacement.
347 GURL url3 = test_server()->GetURL("files/site_isolation/blank.html?3");
348 NavigateToURLContentInitiated(shell(), url3, false, true);
350 // There should be two history entries. url2 should have replaced url1. url2
351 // should not have replaced url3.
352 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
353 EXPECT_EQ(2, controller.GetEntryCount());
354 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
355 EXPECT_EQ(url2, controller.GetEntryAtIndex(0)->GetURL());
356 EXPECT_EQ(url3, controller.GetEntryAtIndex(1)->GetURL());
359 // Tests that the |should_replace_current_entry| flag persists correctly across
360 // request transfers that cross processes twice from renderer policy.
361 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest,
362 MAYBE_ReplaceEntryCrossProcessTwice) {
363 const NavigationController& controller =
364 shell()->web_contents()->GetController();
365 host_resolver()->AddRule("*", "127.0.0.1");
366 ASSERT_TRUE(test_server()->Start());
368 // These must all stay in scope with replace_host.
369 GURL::Replacements replace_host;
370 std::string a_com("A.com");
371 std::string b_com("B.com");
373 // Navigate to a starting URL, so there is a history entry to replace.
374 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
375 NavigateToURL(shell(), url1);
377 // Navigate to a page on A.com which redirects to B.com with entry
378 // replacement. This will switch processes via OpenURL twice. First to A.com,
379 // and second in response to the server redirect to B.com. The second swap is
380 // also renderer-initiated via OpenURL because decidePolicyForNavigation is
381 // currently applied on redirects.
382 GURL url2b = test_server()->GetURL("files/site_isolation/blank.html?2");
383 replace_host.SetHostStr(b_com);
384 url2b = url2b.ReplaceComponents(replace_host);
385 GURL url2a = test_server()->GetURL(
386 "server-redirect?" + net::EscapeQueryParamValue(url2b.spec(), false));
387 replace_host.SetHostStr(a_com);
388 url2a = url2a.ReplaceComponents(replace_host);
389 NavigateToURLContentInitiated(shell(), url2a, true, true);
391 // There should be one history entry. url2b should have replaced url1.
392 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
393 EXPECT_EQ(1, controller.GetEntryCount());
394 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
395 EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
397 // Now repeat without replacement.
398 GURL url3b = test_server()->GetURL("files/site_isolation/blank.html?3");
399 replace_host.SetHostStr(b_com);
400 url3b = url3b.ReplaceComponents(replace_host);
401 GURL url3a = test_server()->GetURL(
402 "server-redirect?" + net::EscapeQueryParamValue(url3b.spec(), false));
403 replace_host.SetHostStr(a_com);
404 url3a = url3a.ReplaceComponents(replace_host);
405 NavigateToURLContentInitiated(shell(), url3a, false, true);
407 // There should be two history entries. url2b should have replaced url1. url2b
408 // should not have replaced url3b.
409 EXPECT_TRUE(controller.GetPendingEntry() == NULL);
410 EXPECT_EQ(2, controller.GetEntryCount());
411 EXPECT_EQ(1, controller.GetCurrentEntryIndex());
412 EXPECT_EQ(url2b, controller.GetEntryAtIndex(0)->GetURL());
413 EXPECT_EQ(url3b, controller.GetEntryAtIndex(1)->GetURL());
416 // Tests that the request is destroyed when a cross process navigation is
417 // cancelled.
418 IN_PROC_BROWSER_TEST_F(CrossSiteTransferTest, NoLeakOnCrossSiteCancel) {
419 const NavigationController& controller =
420 shell()->web_contents()->GetController();
421 host_resolver()->AddRule("*", "127.0.0.1");
422 ASSERT_TRUE(test_server()->Start());
424 // These must all stay in scope with replace_host.
425 GURL::Replacements replace_host;
426 std::string a_com("A.com");
427 std::string b_com("B.com");
429 // Navigate to a starting URL, so there is a history entry to replace.
430 GURL url1 = test_server()->GetURL("files/site_isolation/blank.html?1");
431 NavigateToURL(shell(), url1);
433 // Force all future navigations to transfer.
434 ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
436 NoTransferRequestDelegate no_transfer_request_delegate;
437 WebContentsDelegate* old_delegate = shell()->web_contents()->GetDelegate();
438 shell()->web_contents()->SetDelegate(&no_transfer_request_delegate);
440 // Navigate to a page on A.com with entry replacement. This navigation is
441 // cross-site, so the renderer will send it to the browser via OpenURL to give
442 // to a new process. It will then be transferred into yet another process due
443 // to the call above.
444 GURL url2 = test_server()->GetURL("files/site_isolation/blank.html?2");
445 replace_host.SetHostStr(a_com);
446 url2 = url2.ReplaceComponents(replace_host);
447 // Used to make sure the second request is cancelled, and there is only one
448 // request for url2.
449 tracking_delegate().SetTrackedURL(url2);
451 // Don't wait for the navigation to complete, since that never happens in
452 // this case.
453 NavigateToURLContentInitiated(shell(), url2, false, false);
455 // There should be one history entry, with url1.
456 EXPECT_EQ(1, controller.GetEntryCount());
457 EXPECT_EQ(0, controller.GetCurrentEntryIndex());
458 EXPECT_EQ(url1, controller.GetEntryAtIndex(0)->GetURL());
460 // Make sure the request for url2 did not complete.
461 EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
463 shell()->web_contents()->SetDelegate(old_delegate);
466 } // namespace content