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