8082eb239e0a15f78892944bdf884c4b4803a81f
[platform/framework/web/crosswalk.git] / src / content / browser / cross_site_transfer_browsertest.cc
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.
4
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"
25
26 namespace content {
27
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) {
34   }
35
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));
50   }
51
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_);
58
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());
63
64     BrowserThread::PostTask(
65         BrowserThread::IO, FROM_HERE,
66         base::Bind(
67             &TrackingResourceDispatcherHostDelegate::SetTrackedURLOnIOThread,
68             base::Unretained(this),
69             tracked_url));
70   }
71
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_;
79   }
80
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) {
89     }
90
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());
97     }
98
99     // ResourceThrottle implementation:
100     virtual const char* GetNameForLogging() const OVERRIDE {
101       return "TrackingThrottle";
102     }
103
104    private:
105     net::URLRequest* request_;
106     TrackingResourceDispatcherHostDelegate* tracker_;
107
108     DISALLOW_COPY_AND_ASSIGN(TrackingThrottle);
109   };
110
111   void SetTrackedURLOnIOThread(const GURL& tracked_url) {
112     CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
113     throttle_created_ = false;
114     tracked_url_ = tracked_url;
115   }
116
117   void OnTrackedRequestDestroyed(bool completed) {
118     CHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
119     tracked_request_completed_ = completed;
120     tracked_url_ = GURL();
121
122     BrowserThread::PostTask(
123         BrowserThread::UI, FROM_HERE, run_loop_->QuitClosure());
124   }
125
126   // These live on the IO thread.
127   GURL tracked_url_;
128   bool throttle_created_;
129
130   // This is created and destroyed on the UI thread, but stopped on the IO
131   // thread.
132   scoped_ptr<base::RunLoop> run_loop_;
133
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_;
137
138   DISALLOW_COPY_AND_ASSIGN(TrackingResourceDispatcherHostDelegate);
139 };
140
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() {}
146
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;
163   }
164
165  private:
166   DISALLOW_COPY_AND_ASSIGN(NoTransferRequestDelegate);
167 };
168
169 class CrossSiteTransferTest : public ContentBrowserTest {
170  public:
171   CrossSiteTransferTest() : old_delegate_(NULL) {
172   }
173
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)));
181   }
182
183   virtual void TearDownOnMainThread() OVERRIDE {
184     BrowserThread::PostTask(
185         BrowserThread::IO, FROM_HERE,
186         base::Bind(
187             &CrossSiteTransferTest::RestoreResourceDisptcherHostDelegate,
188             base::Unretained(this)));
189   }
190
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();
206   }
207
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);
211   }
212
213   void InjectResourceDisptcherHostDelegate() {
214     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
215     old_delegate_ = ResourceDispatcherHostImpl::Get()->delegate();
216     ResourceDispatcherHostImpl::Get()->SetDelegate(&tracking_delegate_);
217   }
218
219   void RestoreResourceDisptcherHostDelegate() {
220     DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
221     ResourceDispatcherHostImpl::Get()->SetDelegate(old_delegate_);
222     old_delegate_ = NULL;
223   }
224
225   TrackingResourceDispatcherHostDelegate& tracking_delegate() {
226     return tracking_delegate_;
227   }
228
229  private:
230   TrackingResourceDispatcherHostDelegate tracking_delegate_;
231   ResourceDispatcherHostDelegate* old_delegate_;
232 };
233
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());
254
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");
259
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);
263
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);
268
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);
280
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());
288
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);
299
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());
307
308   // Make sure the request succeeded.
309   EXPECT_TRUE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
310 }
311
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());
321
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);
325
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);
331
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);
336
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());
342
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);
346
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());
354 }
355
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());
364
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");
369
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);
373
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);
387
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());
393
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);
403
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());
411 }
412
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());
420
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");
425
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);
429
430   // Force all future navigations to transfer.
431   ShellContentBrowserClient::SetSwapProcessesForRedirect(true);
432
433   NoTransferRequestDelegate no_transfer_request_delegate;
434   WebContentsDelegate* old_delegate = shell()->web_contents()->GetDelegate();
435   shell()->web_contents()->SetDelegate(&no_transfer_request_delegate);
436
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);
447
448   // Don't wait for the navigation to complete, since that never happens in
449   // this case.
450   NavigateToURLContentInitiated(shell(), url2, false, false);
451
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());
456
457   // Make sure the request for url2 did not complete.
458   EXPECT_FALSE(tracking_delegate().WaitForTrackedURLAndGetCompleted());
459
460   shell()->web_contents()->SetDelegate(old_delegate);
461 }
462
463 }  // namespace content