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