1 // Copyright 2014 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/macros.h"
7 #include "base/strings/stringprintf.h"
8 #include "content/public/browser/web_contents.h"
9 #include "content/public/browser/web_contents_delegate.h"
10 #include "content/public/test/browser_test_utils.h"
11 #include "content/public/test/content_browser_test.h"
12 #include "content/public/test/content_browser_test_utils.h"
13 #include "content/public/test/test_navigation_observer.h"
14 #include "content/shell/browser/shell.h"
15 #include "net/dns/mock_host_resolver.h"
16 #include "url/gurl.h"
17 
18 namespace content {
19 
20 namespace {
21 
22 // A dummy WebContentsDelegate which tracks whether CloseContents() has been
23 // called. It refuses the actual close but keeps track of whether the renderer
24 // requested it.
25 class CloseTrackingDelegate : public WebContentsDelegate {
26  public:
CloseTrackingDelegate()27   CloseTrackingDelegate() : close_contents_called_(false) {}
28 
close_contents_called() const29   bool close_contents_called() const { return close_contents_called_; }
30 
CloseContents(WebContents * source)31   void CloseContents(WebContents* source) override {
32     close_contents_called_ = true;
33   }
34 
35  private:
36   bool close_contents_called_;
37 
38   DISALLOW_COPY_AND_ASSIGN(CloseTrackingDelegate);
39 };
40 
41 }  // namespace
42 
43 class OpenedByDOMTest : public ContentBrowserTest {
44  protected:
SetUpCommandLine(base::CommandLine * command_line)45   void SetUpCommandLine(base::CommandLine* command_line) override {
46     // Use --site-per-process to force process swaps on cross-site navigations.
47     IsolateAllSitesForTesting(command_line);
48   }
49 
SetUpOnMainThread()50   void SetUpOnMainThread() override {
51     host_resolver()->AddRule("*", "127.0.0.1");
52   }
53 
AttemptCloseFromJavaScript(WebContents * web_contents)54   bool AttemptCloseFromJavaScript(WebContents* web_contents) {
55     CloseTrackingDelegate close_tracking_delegate;
56     WebContentsDelegate* old_delegate = web_contents->GetDelegate();
57     web_contents->SetDelegate(&close_tracking_delegate);
58 
59     const char kCloseWindowScript[] =
60         // Close the window.
61         "window.close();"
62         // Report back after an event loop iteration; the close IPC isn't sent
63         // immediately.
64         "setTimeout(function() {"
65         "window.domAutomationController.send(0);"
66         "});";
67     int dummy;
68     CHECK(ExecuteScriptAndExtractInt(web_contents, kCloseWindowScript, &dummy));
69 
70     web_contents->SetDelegate(old_delegate);
71     return close_tracking_delegate.close_contents_called();
72   }
73 
OpenWindowFromJavaScript(Shell * shell,const GURL & url)74   Shell* OpenWindowFromJavaScript(Shell* shell, const GURL& url) {
75     // Wait for the popup to be created and for it to have navigated.
76     ShellAddedObserver new_shell_observer;
77     TestNavigationObserver nav_observer(nullptr);
78     nav_observer.StartWatchingNewWebContents();
79     CHECK(ExecuteScript(
80         shell, base::StringPrintf("window.open('%s')", url.spec().c_str())));
81     nav_observer.Wait();
82     return new_shell_observer.GetShell();
83   }
84 };
85 
86 // Tests that window.close() does not work on a normal window that has navigated
87 // a few times.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest,NormalWindow)88 IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, NormalWindow) {
89   ASSERT_TRUE(embedded_test_server()->Start());
90 
91   // window.close is allowed if the window was opened by DOM OR the back/forward
92   // list has only one element. Navigate a bit so the second condition is false.
93   GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");
94   GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
95   EXPECT_TRUE(NavigateToURL(shell(), url1));
96   EXPECT_TRUE(NavigateToURL(shell(), url2));
97 
98   // This window was not opened by DOM, so close does not reach the browser
99   // process.
100   EXPECT_FALSE(AttemptCloseFromJavaScript(shell()->web_contents()));
101 }
102 
103 // Tests that window.close() works in a popup window that has navigated a few
104 // times.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest,Popup)105 IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, Popup) {
106   ASSERT_TRUE(embedded_test_server()->Start());
107 
108   GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");
109   GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
110   GURL url3 = embedded_test_server()->GetURL("/site_isolation/blank.html?3");
111   EXPECT_TRUE(NavigateToURL(shell(), url1));
112 
113   Shell* popup = OpenWindowFromJavaScript(shell(), url2);
114   EXPECT_TRUE(NavigateToURL(popup, url3));
115   EXPECT_TRUE(AttemptCloseFromJavaScript(popup->web_contents()));
116 }
117 
118 // Tests that window.close() works in a popup window that has navigated a few
119 // times and swapped processes.
IN_PROC_BROWSER_TEST_F(OpenedByDOMTest,CrossProcessPopup)120 IN_PROC_BROWSER_TEST_F(OpenedByDOMTest, CrossProcessPopup) {
121   ASSERT_TRUE(embedded_test_server()->Start());
122 
123   GURL url1 = embedded_test_server()->GetURL("/site_isolation/blank.html?1");
124 
125   GURL url2 = embedded_test_server()->GetURL("/site_isolation/blank.html?2");
126   GURL::Replacements replace_host;
127   replace_host.SetHostStr("foo.com");
128   url2 = url2.ReplaceComponents(replace_host);
129 
130   GURL url3 = embedded_test_server()->GetURL("/site_isolation/blank.html?3");
131   url3 = url3.ReplaceComponents(replace_host);
132 
133   EXPECT_TRUE(NavigateToURL(shell(), url1));
134 
135   Shell* popup = OpenWindowFromJavaScript(shell(), url2);
136   EXPECT_TRUE(NavigateToURL(popup, url3));
137   EXPECT_TRUE(AttemptCloseFromJavaScript(popup->web_contents()));
138 }
139 
140 }  // namespace content
141