1 // Copyright 2015 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 "content/browser/frame_host/interstitial_page_impl.h"
6 
7 #include <tuple>
8 
9 #include "base/macros.h"
10 #include "base/run_loop.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "content/browser/web_contents/web_contents_impl.h"
13 #include "content/common/frame_messages.h"
14 #include "content/public/browser/browser_message_filter.h"
15 #include "content/public/browser/browser_thread.h"
16 #include "content/public/browser/interstitial_page_delegate.h"
17 #include "content/public/common/navigation_policy.h"
18 #include "content/public/test/browser_test_utils.h"
19 #include "content/public/test/content_browser_test.h"
20 #include "content/public/test/content_browser_test_utils.h"
21 #include "content/public/test/test_utils.h"
22 #include "content/shell/browser/shell.h"
23 #include "ipc/message_filter.h"
24 #include "third_party/blink/public/mojom/clipboard/clipboard.mojom.h"
25 #include "ui/base/clipboard/clipboard_monitor.h"
26 #include "ui/base/clipboard/clipboard_observer.h"
27 
28 namespace content {
29 
30 namespace {
31 
32 class TestInterstitialPageDelegate : public InterstitialPageDelegate {
33  private:
34   // InterstitialPageDelegate:
GetHTMLContents()35   std::string GetHTMLContents() override {
36     return "<html>"
37            "<head>"
38            "<script>"
39            "function create_input_and_set_text(text) {"
40            "  var input = document.createElement('input');"
41            "  input.id = 'input';"
42            "  document.body.appendChild(input);"
43            "  document.getElementById('input').value = text;"
44            "  input.addEventListener('input',"
45            "      function() { document.title='TEXT_CHANGED'; });"
46            "}"
47            "function focus_select_input() {"
48            "  document.getElementById('input').select();"
49            "}"
50            "function get_input_text() {"
51            "  window.domAutomationController.send("
52            "      document.getElementById('input').value);"
53            "}"
54            "function get_selection() {"
55            "  window.domAutomationController.send("
56            "      window.getSelection().toString());"
57            "}"
58            "function set_selection_change_listener() {"
59            "  document.addEventListener('selectionchange',"
60            "    function() { document.title='SELECTION_CHANGED'; })"
61            "}"
62            "</script>"
63            "</head>"
64            "<body>original body text</body>"
65            "</html>";
66   }
67 };
68 
69 class ClipboardChangedObserver : ui::ClipboardObserver {
70  public:
ClipboardChangedObserver()71   ClipboardChangedObserver() {
72     ui::ClipboardMonitor::GetInstance()->AddObserver(this);
73   }
74 
~ClipboardChangedObserver()75   ~ClipboardChangedObserver() override {
76     ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
77   }
78 
OnClipboardDataChanged()79   void OnClipboardDataChanged() override {
80     DCHECK(!quit_closure_.is_null());
81     std::move(quit_closure_).Run();
82   }
83 
WaitForWriteCommit()84   void WaitForWriteCommit() {
85     base::RunLoop run_loop;
86     quit_closure_ = run_loop.QuitClosure();
87     run_loop.Run();
88   }
89 
90  private:
91   base::OnceClosure quit_closure_;
92 };
93 
94 }  // namespace
95 
96 class InterstitialPageImplTest : public ContentBrowserTest {
97  public:
InterstitialPageImplTest()98   InterstitialPageImplTest() {}
99 
~InterstitialPageImplTest()100   ~InterstitialPageImplTest() override {}
101 
102  protected:
SetUpInterstitialPage()103   void SetUpInterstitialPage() {
104     WebContentsImpl* web_contents =
105         static_cast<WebContentsImpl*>(shell()->web_contents());
106 
107     // Create the interstitial page.
108     TestInterstitialPageDelegate* interstitial_delegate =
109         new TestInterstitialPageDelegate;
110     GURL url("http://interstitial");
111     interstitial_.reset(new InterstitialPageImpl(
112         web_contents, static_cast<RenderWidgetHostDelegate*>(web_contents),
113         true, url, interstitial_delegate));
114     interstitial_->Show();
115     WaitForInterstitialAttach(web_contents);
116 
117     // Focus the interstitial frame
118     FrameTree* frame_tree =
119         static_cast<RenderViewHostDelegate*>(interstitial_.get())
120             ->GetFrameTree();
121     static_cast<RenderFrameHostDelegate*>(interstitial_.get())
122         ->SetFocusedFrame(frame_tree->root(),
123                           frame_tree->GetMainFrame()->GetSiteInstance());
124 
125     // Wait until page loads completely.
126     ASSERT_TRUE(WaitForRenderFrameReady(interstitial_->GetMainFrame()));
127   }
128 
TearDownInterstitialPage()129   void TearDownInterstitialPage() {
130     // Close the interstitial.
131     interstitial_->DontProceed();
132     WaitForInterstitialDetach(shell()->web_contents());
133     interstitial_.reset();
134   }
135 
interstitial()136   InterstitialPageImpl* interstitial() { return interstitial_.get(); }
137 
FocusInputAndSelectText()138   bool FocusInputAndSelectText() {
139     return ExecuteScript(interstitial_->GetMainFrame(), "focus_select_input()");
140   }
141 
GetInputText(std::string * input_text)142   bool GetInputText(std::string* input_text) {
143     return ExecuteScriptAndExtractString(interstitial_->GetMainFrame(),
144                                          "get_input_text()", input_text);
145   }
146 
GetSelection(std::string * input_text)147   bool GetSelection(std::string* input_text) {
148     return ExecuteScriptAndExtractString(interstitial_->GetMainFrame(),
149                                          "get_selection()", input_text);
150   }
151 
CreateInputAndSetText(const std::string & text)152   bool CreateInputAndSetText(const std::string& text) {
153     return ExecuteScript(interstitial_->GetMainFrame(),
154                          "create_input_and_set_text('" + text + "')");
155   }
156 
SetSelectionChangeListener()157   bool SetSelectionChangeListener() {
158     return ExecuteScript(interstitial_->GetMainFrame(),
159                          "set_selection_change_listener()");
160   }
161 
PerformCut()162   void PerformCut() {
163     ClipboardChangedObserver clipboard_observer;
164     const base::string16 expected_title = base::UTF8ToUTF16("TEXT_CHANGED");
165     content::TitleWatcher title_watcher(shell()->web_contents(),
166                                         expected_title);
167     RenderFrameHostImpl* rfh =
168         static_cast<RenderFrameHostImpl*>(interstitial_->GetMainFrame());
169     rfh->GetRenderWidgetHost()->delegate()->Cut();
170     clipboard_observer.WaitForWriteCommit();
171     EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
172   }
173 
PerformCopy()174   void PerformCopy() {
175     ClipboardChangedObserver clipboard_observer;
176     RenderFrameHostImpl* rfh =
177         static_cast<RenderFrameHostImpl*>(interstitial_->GetMainFrame());
178     rfh->GetRenderWidgetHost()->delegate()->Copy();
179     clipboard_observer.WaitForWriteCommit();
180   }
181 
PerformPaste()182   void PerformPaste() {
183     const base::string16 expected_title = base::UTF8ToUTF16("TEXT_CHANGED");
184     content::TitleWatcher title_watcher(shell()->web_contents(),
185                                         expected_title);
186     RenderFrameHostImpl* rfh =
187         static_cast<RenderFrameHostImpl*>(interstitial_->GetMainFrame());
188     rfh->GetRenderWidgetHost()->delegate()->Paste();
189     EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
190   }
191 
PerformSelectAll()192   void PerformSelectAll() {
193     const base::string16 expected_title =
194         base::UTF8ToUTF16("SELECTION_CHANGED");
195     content::TitleWatcher title_watcher(shell()->web_contents(),
196                                         expected_title);
197     RenderFrameHostImpl* rfh =
198         static_cast<RenderFrameHostImpl*>(interstitial_->GetMainFrame());
199     rfh->GetRenderWidgetHost()->delegate()->SelectAll();
200     EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
201   }
202 
PerformBack()203   void PerformBack() {
204     RenderFrameHostImpl* rfh =
205         static_cast<RenderFrameHostImpl*>(interstitial_->GetMainFrame());
206     rfh->GetRenderWidgetHost()->ForwardMouseEvent(blink::WebMouseEvent(
207         blink::WebInputEvent::Type::kMouseUp, gfx::PointF(), gfx::PointF(),
208         blink::WebPointerProperties::Button::kBack, 0, 0,
209         base::TimeTicks::Now()));
210   }
211 
212  private:
213   std::unique_ptr<InterstitialPageImpl> interstitial_;
214 
215   DISALLOW_COPY_AND_ASSIGN(InterstitialPageImplTest);
216 };
217 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,Cut)218 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, Cut) {
219   BrowserTestClipboardScope clipboard;
220   SetUpInterstitialPage();
221 
222   ASSERT_TRUE(CreateInputAndSetText("text-to-cut"));
223   ASSERT_TRUE(FocusInputAndSelectText());
224 
225   PerformCut();
226   std::string clipboard_text;
227   clipboard.GetText(&clipboard_text);
228   EXPECT_EQ("text-to-cut", clipboard_text);
229 
230   std::string input_text;
231   ASSERT_TRUE(GetInputText(&input_text));
232   EXPECT_EQ(std::string(), input_text);
233 
234   TearDownInterstitialPage();
235 }
236 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,Copy)237 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, Copy) {
238   BrowserTestClipboardScope clipboard;
239   SetUpInterstitialPage();
240 
241   ASSERT_TRUE(CreateInputAndSetText("text-to-copy"));
242   ASSERT_TRUE(FocusInputAndSelectText());
243 
244   PerformCopy();
245   std::string clipboard_text;
246   clipboard.GetText(&clipboard_text);
247   EXPECT_EQ("text-to-copy", clipboard_text);
248 
249   std::string input_text;
250   ASSERT_TRUE(GetInputText(&input_text));
251   EXPECT_EQ("text-to-copy", input_text);
252 
253   TearDownInterstitialPage();
254 }
255 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,Paste)256 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, Paste) {
257   BrowserTestClipboardScope clipboard;
258   SetUpInterstitialPage();
259 
260   clipboard.SetText("text-to-paste");
261 
262   ASSERT_TRUE(CreateInputAndSetText(std::string()));
263   ASSERT_TRUE(FocusInputAndSelectText());
264 
265   PerformPaste();
266 
267   std::string input_text;
268   ASSERT_TRUE(GetInputText(&input_text));
269   EXPECT_EQ("text-to-paste", input_text);
270 
271   TearDownInterstitialPage();
272 }
273 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,SelectAll)274 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, SelectAll) {
275   SetUpInterstitialPage();
276   ASSERT_TRUE(SetSelectionChangeListener());
277 
278   std::string input_text;
279   ASSERT_TRUE(GetSelection(&input_text));
280   EXPECT_EQ(std::string(), input_text);
281 
282   PerformSelectAll();
283 
284   ASSERT_TRUE(GetSelection(&input_text));
285   EXPECT_EQ("original body text", input_text);
286 
287   TearDownInterstitialPage();
288 }
289 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,FocusAfterDetaching)290 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, FocusAfterDetaching) {
291   WebContentsImpl* web_contents =
292       static_cast<WebContentsImpl*>(shell()->web_contents());
293 
294   // Load something into the WebContents.
295   EXPECT_TRUE(NavigateToURL(shell(), GURL("about:blank")));
296 
297   // Blur the main frame.
298   web_contents->GetMainFrame()->GetRenderWidgetHost()->Blur();
299   EXPECT_FALSE(
300       web_contents->GetMainFrame()->GetRenderWidgetHost()->is_focused());
301 
302   // Setup the interstitial and focus it.
303   SetUpInterstitialPage();
304   interstitial()->GetView()->GetRenderWidgetHost()->Focus();
305   EXPECT_TRUE(web_contents->ShowingInterstitialPage());
306   EXPECT_TRUE(static_cast<RenderWidgetHostImpl*>(
307                   interstitial()->GetView()->GetRenderWidgetHost())
308                   ->is_focused());
309 
310   // Tear down interstitial.
311   TearDownInterstitialPage();
312 
313   // Since the interstitial was focused, the main frame should be now focused
314   // after the interstitial teardown.
315   EXPECT_TRUE(web_contents->GetRenderViewHost()->GetWidget()->is_focused());
316 }
317 
318 // Ensure that we don't show the underlying RenderWidgetHostView if a subframe
319 // commits in the original page while an interstitial is showing.
320 // See https://crbug.com/729105.
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,UnderlyingSubframeCommit)321 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, UnderlyingSubframeCommit) {
322   ASSERT_TRUE(embedded_test_server()->Start());
323 
324   // Load an initial page and inject an iframe that won't commit yet.
325   WebContentsImpl* web_contents =
326       static_cast<WebContentsImpl*>(shell()->web_contents());
327   GURL main_url(embedded_test_server()->GetURL("/title1.html"));
328   GURL slow_url(embedded_test_server()->GetURL("/title2.html"));
329   EXPECT_TRUE(NavigateToURL(shell(), main_url));
330   TestNavigationManager subframe_delayer(web_contents, slow_url);
331   {
332     std::string script =
333         "var iframe = document.createElement('iframe');"
334         "iframe.src = '" +
335         slow_url.spec() +
336         "';"
337         "document.body.appendChild(iframe);";
338     EXPECT_TRUE(ExecuteScript(web_contents->GetMainFrame(), script));
339   }
340   EXPECT_TRUE(subframe_delayer.WaitForRequestStart());
341 
342   // Show an interstitial. The underlying RenderWidgetHostView should not be
343   // showing.
344   SetUpInterstitialPage();
345   EXPECT_FALSE(web_contents->GetMainFrame()->GetView()->IsShowing());
346   EXPECT_TRUE(web_contents->GetMainFrame()->GetRenderWidgetHost()->is_hidden());
347 
348   // Allow the subframe to commit.
349   subframe_delayer.WaitForNavigationFinished();
350 
351   // The underlying RenderWidgetHostView should still not be showing.
352   EXPECT_FALSE(web_contents->GetMainFrame()->GetView()->IsShowing());
353   EXPECT_TRUE(web_contents->GetMainFrame()->GetRenderWidgetHost()->is_hidden());
354 
355   TearDownInterstitialPage();
356 }
357 
IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest,BackMouseButton)358 IN_PROC_BROWSER_TEST_F(InterstitialPageImplTest, BackMouseButton) {
359   ASSERT_TRUE(embedded_test_server()->Start());
360 
361   // Load something into the WebContents.
362   EXPECT_TRUE(NavigateToURL(
363       shell(), GURL(embedded_test_server()->GetURL("/title1.html"))));
364   SetUpInterstitialPage();
365 
366   EXPECT_TRUE(shell()->web_contents()->ShowingInterstitialPage());
367   PerformBack();
368   EXPECT_FALSE(shell()->web_contents()->ShowingInterstitialPage());
369 
370   TearDownInterstitialPage();
371 }
372 
373 }  // namespace content
374