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/base_paths.h"
6 #include "base/bind.h"
7 #include "base/files/file_util.h"
8 #include "base/location.h"
9 #include "base/macros.h"
10 #include "base/path_service.h"
11 #include "base/run_loop.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "base/test/scoped_feature_list.h"
15 #include "base/test/test_timeouts.h"
16 #include "base/threading/thread_task_runner_handle.h"
17 #include "build/build_config.h"
18 #include "chrome/browser/extensions/extension_apitest.h"
19 #include "chrome/browser/ui/browser.h"
20 #include "chrome/test/base/ui_test_utils.h"
21 #include "components/guest_view/browser/test_guest_view_manager.h"
22 #include "components/javascript_dialogs/app_modal_dialog_controller.h"
23 #include "components/javascript_dialogs/app_modal_dialog_view.h"
24 #include "components/printing/common/print_messages.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "content/public/browser/navigation_handle.h"
27 #include "content/public/browser/web_contents_observer.h"
28 #include "content/public/common/content_features.h"
29 #include "content/public/common/content_switches.h"
30 #include "content/public/test/browser_test_utils.h"
31 #include "content/public/test/test_renderer_host.h"
32 #include "extensions/browser/api/extensions_api_client.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/guest_view/extensions_guest_view_manager_delegate.h"
35 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_stream_manager.h"
36 #include "extensions/browser/guest_view/mime_handler_view/mime_handler_view_guest.h"
37 #include "extensions/browser/guest_view/mime_handler_view/test_mime_handler_view_guest.h"
38 #include "extensions/browser/process_manager.h"
39 #include "extensions/common/guest_view/extensions_guest_view_messages.h"
40 #include "extensions/common/mojom/guest_view.mojom.h"
41 #include "extensions/test/result_catcher.h"
42 #include "mojo/public/cpp/bindings/associated_remote.h"
43 #include "net/dns/mock_host_resolver.h"
44 #include "net/test/embedded_test_server/embedded_test_server.h"
45 #include "net/test/embedded_test_server/http_request.h"
46 #include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
47 #include "ui/base/ui_base_features.h"
48 #include "url/url_constants.h"
49
50 using extensions::ExtensionsAPIClient;
51 using extensions::MimeHandlerStreamManager;
52 using extensions::MimeHandlerViewGuest;
53 using extensions::TestMimeHandlerViewGuest;
54 using guest_view::GuestViewManager;
55 using guest_view::GuestViewManagerDelegate;
56 using guest_view::TestGuestViewManager;
57 using guest_view::TestGuestViewManagerFactory;
58
59 // The test extension id is set by the key value in the manifest.
60 const char kExtensionId[] = "oickdpebdnfbgkcaoklfcdhjniefkcji";
61
62 class MimeHandlerViewTest : public extensions::ExtensionApiTest {
63 public:
MimeHandlerViewTest()64 MimeHandlerViewTest() {
65 GuestViewManager::set_factory_for_testing(&factory_);
66 }
67
~MimeHandlerViewTest()68 ~MimeHandlerViewTest() override {}
69
SetUpOnMainThread()70 void SetUpOnMainThread() override {
71 extensions::ExtensionApiTest::SetUpOnMainThread();
72
73 embedded_test_server()->ServeFilesFromDirectory(
74 test_data_dir_.AppendASCII("mime_handler_view"));
75 embedded_test_server()->RegisterRequestMonitor(base::BindRepeating(
76 &MimeHandlerViewTest::MonitorRequest, base::Unretained(this)));
77 host_resolver()->AddRule("*", "127.0.0.1");
78 ASSERT_TRUE(StartEmbeddedTestServer());
79 }
80
81 // TODO(paulmeyer): This function is implemented over and over by the
82 // different GuestView test classes. It really needs to be refactored out to
83 // some kind of GuestViewTest base class.
GetGuestViewManager() const84 TestGuestViewManager* GetGuestViewManager() const {
85 TestGuestViewManager* manager = static_cast<TestGuestViewManager*>(
86 TestGuestViewManager::FromBrowserContext(browser()->profile()));
87 // TestGuestViewManager::WaitForSingleGuestCreated can and will get called
88 // before a guest is created. Since GuestViewManager is usually not created
89 // until the first guest is created, this means that |manager| will be
90 // nullptr if trying to use the manager to wait for the first guest. Because
91 // of this, the manager must be created here if it does not already exist.
92 if (!manager) {
93 manager = static_cast<TestGuestViewManager*>(
94 GuestViewManager::CreateWithDelegate(
95 browser()->profile(),
96 ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
97 browser()->profile())));
98 }
99 return manager;
100 }
101
GetEmbedderWebContents()102 content::WebContents* GetEmbedderWebContents() {
103 return browser()->tab_strip_model()->GetWebContentsAt(0);
104 }
105
GetLastGuestView() const106 MimeHandlerViewGuest* GetLastGuestView() const {
107 return MimeHandlerViewGuest::FromWebContents(
108 GetGuestViewManager()->GetLastGuestCreated())
109 ->As<MimeHandlerViewGuest>();
110 }
111
LoadTestExtension()112 const extensions::Extension* LoadTestExtension() {
113 const extensions::Extension* extension =
114 LoadExtension(test_data_dir_.AppendASCII("mime_handler_view"));
115 if (!extension)
116 return nullptr;
117
118 CHECK_EQ(std::string(kExtensionId), extension->id());
119
120 return extension;
121 }
122
RunTestWithUrl(const GURL & url)123 void RunTestWithUrl(const GURL& url) {
124 // Use the testing subclass of MimeHandlerViewGuest.
125 GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
126 base::Bind(&TestMimeHandlerViewGuest::Create));
127
128 const extensions::Extension* extension = LoadTestExtension();
129 ASSERT_TRUE(extension);
130
131 extensions::ResultCatcher catcher;
132
133 ui_test_utils::NavigateToURL(browser(), url);
134
135 if (!catcher.GetNextResult())
136 FAIL() << catcher.message();
137 }
138
RunTest(const std::string & path)139 void RunTest(const std::string& path) {
140 RunTestWithUrl(embedded_test_server()->GetURL("/" + path));
141 }
142
basic_count() const143 int basic_count() const { return basic_count_; }
144
145 private:
MonitorRequest(const net::test_server::HttpRequest & request)146 void MonitorRequest(const net::test_server::HttpRequest& request) {
147 if (request.relative_url == "/testBasic.csv")
148 basic_count_++;
149 }
150
151 TestGuestViewManagerFactory factory_;
152 base::test::ScopedFeatureList scoped_feature_list_;
153 int basic_count_ = 0;
154 };
155
156 class UserActivationUpdateWaiter {
157 public:
UserActivationUpdateWaiter(content::WebContents * web_contents)158 explicit UserActivationUpdateWaiter(content::WebContents* web_contents) {
159 user_activation_interceptor_.Init(web_contents->GetMainFrame());
160 }
161 ~UserActivationUpdateWaiter() = default;
162
Wait()163 void Wait() {
164 if (user_activation_interceptor_.update_user_activation_state())
165 return;
166 base::RunLoop run_loop;
167 user_activation_interceptor_.set_quit_handler(run_loop.QuitClosure());
168 run_loop.Run();
169 }
170
171 private:
172 content::UpdateUserActivationStateInterceptor user_activation_interceptor_;
173 };
174
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,Embedded)175 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Embedded) {
176 RunTest("test_embedded.html");
177 // Sanity check. Navigate the page and verify the guest goes away.
178 ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
179 auto* gv_manager = GetGuestViewManager();
180 gv_manager->WaitForAllGuestsDeleted();
181 EXPECT_EQ(1U, gv_manager->num_guests_created());
182 }
183
184 #if BUILDFLAG(ENABLE_PRINT_PREVIEW)
185 class PrintPreviewWaiter : public content::BrowserMessageFilter {
186 public:
PrintPreviewWaiter()187 PrintPreviewWaiter() : BrowserMessageFilter(PrintMsgStart) {}
188
OnMessageReceived(const IPC::Message & message)189 bool OnMessageReceived(const IPC::Message& message) override {
190 IPC_BEGIN_MESSAGE_MAP(PrintPreviewWaiter, message)
191 IPC_MESSAGE_HANDLER(PrintHostMsg_DidStartPreview, OnDidStartPreview)
192 IPC_END_MESSAGE_MAP()
193 return false;
194 }
195
OnDidStartPreview(const PrintHostMsg_DidStartPreview_Params & params,const PrintHostMsg_PreviewIds & ids)196 void OnDidStartPreview(const PrintHostMsg_DidStartPreview_Params& params,
197 const PrintHostMsg_PreviewIds& ids) {
198 // Expect that there is at least one page.
199 did_load_ = true;
200 run_loop_.Quit();
201
202 EXPECT_TRUE(params.page_count >= 1);
203 }
204
Wait()205 void Wait() {
206 if (!did_load_)
207 run_loop_.Run();
208 }
209
210 private:
211 ~PrintPreviewWaiter() override = default;
212
213 bool did_load_ = false;
214 base::RunLoop run_loop_;
215 };
216
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbeddedThenPrint)217 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedThenPrint) {
218 RunTest("test_embedded.html");
219 ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
220 auto* gv_manager = GetGuestViewManager();
221 gv_manager->WaitForAllGuestsDeleted();
222 EXPECT_EQ(1U, gv_manager->num_guests_created());
223
224 // Verify that print dialog comes up.
225 auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
226 auto* main_frame = web_contents->GetMainFrame();
227 auto print_preview_waiter = base::MakeRefCounted<PrintPreviewWaiter>();
228 web_contents->GetMainFrame()->GetProcess()->AddFilter(
229 print_preview_waiter.get());
230 // Use setTimeout() to prevent ExecuteScript() from blocking on the print
231 // dialog.
232 ASSERT_TRUE(content::ExecuteScript(
233 main_frame, "setTimeout(function() { window.print(); }, 0)"));
234 print_preview_waiter->Wait();
235 }
236 #endif // BUILDFLAG(ENABLE_PRINT_PREVIEW)
237
238 // This test start with an <object> that has a content frame. Then the content
239 // frame (plugin frame) is navigated to a cross-origin target page. After the
240 // navigation is completed, the <object> is set to render MimeHandlerView by
241 // setting its |data| and |type| attributes accordingly.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbedWithInitialCrossOriginFrame)242 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbedWithInitialCrossOriginFrame) {
243 const std::string kTestName = "test_cross_origin_frame";
244 std::string cross_origin_url =
245 embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
246 auto test_url = embedded_test_server()->GetURL(
247 "a.com",
248 base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s",
249 kTestName.c_str(), cross_origin_url.c_str(),
250 "testEmbedded.csv"));
251 RunTestWithUrl(test_url);
252 }
253
254 // This test verifies that navigations on the plugin frame before setting it to
255 // load MimeHandlerView do not race with the creation of the guest. The test
256 // loads a page with an <object> which is first navigated to some cross-origin
257 // domain and then immediately after load, the page triggers a navigation of its
258 // own to another cross-origin domain. Meanwhile the embedder sets the <object>
259 // to load a MimeHandlerView. The test passes if MHV loads. This is to catch the
260 // potential race between the cross-origin renderer initiated navigation and
261 // the navigation to "about:blank" started from the browser.
262 //
263 // Disabled due to flakiness: https://crbug.com/1002788.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,DISABLED_NavigationRaceFromEmbedder)264 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,
265 DISABLED_NavigationRaceFromEmbedder) {
266 const std::string kTestName = "test_navigation_race_embedder";
267 auto cross_origin_url =
268 embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
269 auto test_url = embedded_test_server()->GetURL(
270 "a.com",
271 base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s",
272 kTestName.c_str(), cross_origin_url.c_str(),
273 "testEmbedded.csv"));
274 RunTestWithUrl(test_url);
275 }
276
277 // TODO(ekaramad): Without proper handling of navigation to 'about:blank', this
278 // test would be flaky. Use TestNavigationManager class and possibly break the
279 // test into more sub-tests for various scenarios (https://crbug.com/659750).
280 // This test verifies that (almost) concurrent navigations in a cross-process
281 // frame inside an <embed> which is transitioning to a MimeHandlerView will
282 // not block creation of MimeHandlerView. The test will load some cross-origin
283 // content in <object> which right after loading will navigate it self to some
284 // other cross-origin content. On the embedder side, when the first page loads,
285 // the <object> loads some text/csv content to create a MimeHandlerViewGuest.
286 // The test passes if MHV loads.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,NavigationRaceFromCrossProcessRenderer)287 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,
288 NavigationRaceFromCrossProcessRenderer) {
289 const std::string kTestName = "test_navigation_race_cross_origin";
290 auto cross_origin_url =
291 embedded_test_server()->GetURL("b.com", "/test_page.html").spec();
292 auto other_cross_origin_url =
293 embedded_test_server()->GetURL("c.com", "/test_page.html").spec();
294 auto test_url = embedded_test_server()->GetURL(
295 "a.com",
296 base::StringPrintf("/test_object_with_frame.html?test_data=%s,%s,%s,%s",
297 kTestName.c_str(), cross_origin_url.c_str(),
298 other_cross_origin_url.c_str(), "testEmbedded.csv"));
299 RunTestWithUrl(test_url);
300 }
301
302 // This test verifies that removing embedder RenderFrame will not crash the
303 // renderer (for context see https://crbug.com/930803).
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbedderFrameRemovedNoCrash)304 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbedderFrameRemovedNoCrash) {
305 RunTest("test_iframe_basic.html");
306 auto* guest_view = GuestViewBase::FromWebContents(
307 GetGuestViewManager()->WaitForSingleGuestCreated());
308 ASSERT_TRUE(guest_view);
309 int32_t element_instance_id = guest_view->element_instance_id();
310 auto* embedder_web_contents = GetEmbedderWebContents();
311 auto* child_frame =
312 content::ChildFrameAt(embedder_web_contents->GetMainFrame(), 0);
313 content::RenderFrameDeletedObserver render_frame_observer(child_frame);
314 ASSERT_TRUE(
315 content::ExecJs(embedder_web_contents,
316 "document.querySelector('iframe').outerHTML = ''"));
317 render_frame_observer.WaitUntilDeleted();
318 // Send the IPC. During destruction MHVFC would cause a UaF since it was not
319 // removed from the global map.
320 mojo::AssociatedRemote<extensions::mojom::MimeHandlerViewContainerManager>
321 container_manager;
322 embedder_web_contents->GetMainFrame()
323 ->GetRemoteAssociatedInterfaces()
324 ->GetInterface(&container_manager);
325 container_manager->DestroyFrameContainer(element_instance_id);
326 // Running the following JS code fails if the renderer has crashed.
327 ASSERT_TRUE(content::ExecJs(embedder_web_contents, "window.name = 'foo'"));
328 }
329
330 // TODO(ekaramad): Somehow canceling a first dialog in a setup similar to the
331 // test below pops up another dialog. This is likely due to the navigation to
332 // about:blank from both the browser side and the embedder side in the method
333 // HTMLPlugInElement::RequestObjectInternal. Find out the issue and add another
334 // test here where the dialog is dismissed and the guest not created.
335 // (https://crbug.com/659750).
336 // This test verifies that transitioning a plugin element from text/html to
337 // application/pdf respects 'beforeunload'. The test specifically checks that
338 // 'beforeunload' dialog is shown to the user and if the user decides to
339 // proceed with the transition, MimeHandlerViewGuest is created.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbedWithInitialFrameAcceptBeforeUnloadDialog)340 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,
341 EmbedWithInitialFrameAcceptBeforeUnloadDialog) {
342 // Use the testing subclass of MimeHandlerViewGuest.
343 GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
344 base::BindRepeating(&TestMimeHandlerViewGuest::Create));
345 const extensions::Extension* extension = LoadTestExtension();
346 ASSERT_TRUE(extension);
347 ui_test_utils::NavigateToURL(
348 browser(),
349 embedded_test_server()->GetURL("a.com", "/test_object_with_frame.html"));
350 auto* main_frame =
351 browser()->tab_strip_model()->GetWebContentsAt(0)->GetMainFrame();
352 auto url_with_beforeunload =
353 embedded_test_server()->GetURL("b.com", "/test_page.html?beforeunload");
354 bool result = false;
355 ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
356 main_frame,
357 base::StringPrintf(
358 "object.data = '%s';"
359 " object.onload = () => window.domAutomationController.send(true);",
360 url_with_beforeunload.spec().c_str()),
361 &result));
362 ASSERT_TRUE(result);
363 // Give user gesture to the frame, set the <object> to text/csv resource and
364 // handle the "beforeunload" dialog.
365 content::PrepContentsForBeforeUnloadTest(
366 browser()->tab_strip_model()->GetWebContentsAt(0));
367 ASSERT_TRUE(content::ExecuteScript(main_frame,
368 "object.data = './testEmbedded.csv';"
369 "object.type = 'text/csv';"));
370 javascript_dialogs::AppModalDialogController* alert =
371 ui_test_utils::WaitForAppModalDialog();
372 ASSERT_TRUE(alert->is_before_unload_dialog());
373 alert->view()->AcceptAppModalDialog();
374
375 EXPECT_TRUE(GetGuestViewManager()->WaitForSingleGuestCreated());
376 }
377 // The following tests will eventually converted into a parametric version which
378 // will run on both BrowserPlugin-based and cross-process-frame-based
379 // MimeHandlerView (https://crbug.com/659750).
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,PostMessage)380 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, PostMessage) {
381 RunTest("test_postmessage.html");
382 }
383
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,Basic)384 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Basic) {
385 RunTest("testBasic.csv");
386 // Verify that for a navigation to a MimeHandlerView MIME type, exactly one
387 // stream is intercepted. This means :
388 // a- For BrowserPlugin-based MHV the PluginDocument passes the |view_id| to
389 // MimeHandlerViewContainer (so a new request is not sent).
390 // b- For frame-based MimeHandlerView we do not create a PluginDocument. If a
391 // PluginDocument was created here, the |view_id| associated with the
392 // stream intercepted from navigation response would be lost (
393 // PluginDocument does not talk to a MimeHandlerViewFrameContainer). Then,
394 // the newly added <embed> by the PluginDocument would send its own request
395 // leading to a total of 2 intercepted streams. The first one (from
396 // navigation) would never be released.
397 EXPECT_EQ(0U, MimeHandlerStreamManager::Get(
398 GetEmbedderWebContents()->GetBrowserContext())
399 ->streams_.size());
400 }
401
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,Iframe)402 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, Iframe) {
403 RunTest("test_iframe.html");
404 }
405
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,NonAsciiHeaders)406 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, NonAsciiHeaders) {
407 RunTest("testNonAsciiHeaders.csv");
408 }
409
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,DataUrl)410 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, DataUrl) {
411 const char* kDataUrlCsv = "data:text/csv;base64,Y29udGVudCB0byByZWFkCg==";
412 RunTestWithUrl(GURL(kDataUrlCsv));
413 }
414
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbeddedDataUrlObject)415 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlObject) {
416 RunTest("test_embedded_data_url_object.html");
417 }
418
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbeddedDataUrlEmbed)419 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlEmbed) {
420 RunTest("test_embedded_data_url_embed.html");
421 }
422
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,EmbeddedDataUrlLong)423 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, EmbeddedDataUrlLong) {
424 RunTest("test_embedded_data_url_long.html");
425 }
426
427 // Regression test for crbug.com/587709.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,SingleRequest)428 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, SingleRequest) {
429 GURL url(embedded_test_server()->GetURL("/testBasic.csv"));
430 RunTest("testBasic.csv");
431 EXPECT_EQ(1, basic_count());
432 }
433
434 // Test that a mime handler view can keep a background page alive.
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,BackgroundPage)435 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BackgroundPage) {
436 extensions::ProcessManager::SetEventPageIdleTimeForTesting(1);
437 extensions::ProcessManager::SetEventPageSuspendingTimeForTesting(1);
438 RunTest("testBackgroundPage.csv");
439 }
440
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,TargetBlankAnchor)441 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, TargetBlankAnchor) {
442 RunTest("testTargetBlankAnchor.csv");
443 ASSERT_EQ(2, browser()->tab_strip_model()->count());
444 content::WaitForLoadStop(browser()->tab_strip_model()->GetWebContentsAt(1));
445 EXPECT_EQ(
446 GURL(url::kAboutBlankURL),
447 browser()->tab_strip_model()->GetWebContentsAt(1)->GetLastCommittedURL());
448 }
449
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,BeforeUnload_NoDialog)450 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BeforeUnload_NoDialog) {
451 ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadNoDialog.csv"));
452 auto* web_contents = GetEmbedderWebContents();
453 content::PrepContentsForBeforeUnloadTest(web_contents);
454
455 // Wait for a round trip to the outer renderer to ensure any beforeunload
456 // toggle IPC has had time to reach the browser.
457 ExecuteScriptAndGetValue(web_contents->GetMainFrame(), "");
458
459 // Try to navigate away from the page. If the beforeunload listener is
460 // triggered and a dialog is shown, this navigation will never complete,
461 // causing the test to timeout and fail.
462 ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
463 }
464
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,BeforeUnload_ShowDialog)465 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, BeforeUnload_ShowDialog) {
466 ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadShowDialog.csv"));
467 auto* web_contents = GetEmbedderWebContents();
468 content::PrepContentsForBeforeUnloadTest(web_contents);
469
470 // Wait for a round trip to the outer renderer to ensure the beforeunload
471 // toggle IPC has had time to reach the browser.
472 ExecuteScriptAndGetValue(web_contents->GetMainFrame(), "");
473
474 web_contents->GetController().LoadURL(GURL(url::kAboutBlankURL), {},
475 ui::PAGE_TRANSITION_TYPED, "");
476
477 javascript_dialogs::AppModalDialogController* before_unload_dialog =
478 ui_test_utils::WaitForAppModalDialog();
479 EXPECT_TRUE(before_unload_dialog->is_before_unload_dialog());
480 EXPECT_FALSE(before_unload_dialog->is_reload());
481 before_unload_dialog->OnAccept(base::string16(), false);
482 }
483
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,BeforeUnloadEnabled_WithoutUserActivation)484 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,
485 BeforeUnloadEnabled_WithoutUserActivation) {
486 ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadWithUserActivation.csv"));
487 auto* web_contents = GetEmbedderWebContents();
488 // Prepare frames but don't trigger user activation.
489 content::PrepContentsForBeforeUnloadTest(web_contents, false);
490
491 // Even though this test's JS setup enables BeforeUnload dialogs, the dialog
492 // is still suppressed here because of lack of user activation. As a result,
493 // the following navigation away from the page works fine. If a beforeunload
494 // dialog were shown, this navigation would fail, causing the test to timeout.
495 ui_test_utils::NavigateToURL(browser(), GURL(url::kAboutBlankURL));
496 }
497
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,BeforeUnloadEnabled_WithUserActivation)498 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,
499 BeforeUnloadEnabled_WithUserActivation) {
500 ASSERT_NO_FATAL_FAILURE(RunTest("testBeforeUnloadWithUserActivation.csv"));
501 auto* web_contents = GetEmbedderWebContents();
502 // Prepare frames but don't trigger user activation across all frames.
503 content::PrepContentsForBeforeUnloadTest(web_contents, false);
504
505 // Make sure we have a guestviewmanager.
506 auto* guest_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
507 UserActivationUpdateWaiter activation_waiter(guest_contents);
508
509 // Activate |guest_contents| through a click, then wait until the activation
510 // IPC reaches the browser process.
511 SimulateMouseClick(guest_contents, 0, blink::WebMouseEvent::Button::kLeft);
512 activation_waiter.Wait();
513
514 // Wait for a round trip to the outer renderer to ensure any beforeunload
515 // toggle IPC has had time to reach the browser.
516 ExecuteScriptAndGetValue(web_contents->GetMainFrame(), "");
517
518 // Try to navigate away, this should invoke a beforeunload dialog.
519 web_contents->GetController().LoadURL(GURL(url::kAboutBlankURL), {},
520 ui::PAGE_TRANSITION_TYPED, "");
521
522 javascript_dialogs::AppModalDialogController* before_unload_dialog =
523 ui_test_utils::WaitForAppModalDialog();
524 EXPECT_TRUE(before_unload_dialog->is_before_unload_dialog());
525 EXPECT_FALSE(before_unload_dialog->is_reload());
526 before_unload_dialog->OnAccept(base::string16(), false);
527 }
528
529 // Helper class to wait for document load event in the main frame.
530 class DocumentLoadComplete : public content::WebContentsObserver {
531 public:
DocumentLoadComplete(content::WebContents * web_contents)532 explicit DocumentLoadComplete(content::WebContents* web_contents)
533 : content::WebContentsObserver(web_contents) {}
~DocumentLoadComplete()534 ~DocumentLoadComplete() override {}
535
DocumentOnLoadCompletedInMainFrame()536 void DocumentOnLoadCompletedInMainFrame() override {
537 did_load_ = true;
538 run_loop_.Quit();
539 }
540
Wait()541 void Wait() {
542 if (!did_load_)
543 run_loop_.Run();
544 }
545
546 private:
547 bool did_load_ = false;
548 base::RunLoop run_loop_;
549 };
550
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,ActivatePostMessageSupportOnce)551 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, ActivatePostMessageSupportOnce) {
552 RunTest("test_embedded.html");
553 // Attach a second <embed>.
554 ASSERT_TRUE(content::ExecJs(GetEmbedderWebContents(),
555 "const e = document.createElement('embed');"
556 "e.src = './testEmbedded.csv'; e.type='text/csv';"
557 "document.body.appendChild(e);"));
558 DocumentLoadComplete(GetGuestViewManager()->WaitForNextGuestCreated()).Wait();
559 // After load, an IPC has been sent to the renderer to update routing IDs for
560 // the guest frame and the content frame (and activate the
561 // PostMessageSupport). Run some JS to Ensure no DCHECKs have fired in the
562 // embedder process.
563 ASSERT_TRUE(content::ExecJs(GetEmbedderWebContents(), "foo = 0;"));
564 }
565
566 // This is a minimized repro for a clusterfuzz crasher and is not really related
567 // to MimeHandlerView. The test verifies that when
568 // HTMLPlugInElement::PluginWrapper is called for a plugin with no node document
569 // frame, the renderer does not crash (see https://966371).
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,AdoptNodeInOnLoadDoesNotCrash)570 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, AdoptNodeInOnLoadDoesNotCrash) {
571 ui_test_utils::NavigateToURL(
572 browser(),
573 embedded_test_server()->GetURL("/adopt_node_in_onload_no_crash.html"));
574 // Run some JavaScript in embedder and make sure it is not crashed.
575 ASSERT_TRUE(content::ExecJs(GetEmbedderWebContents(), "true"));
576 }
577
578 // Verifies that sandboxed frames do not create GuestViews (plugins are
579 // blocked in sandboxed frames).
IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest,DoNotLoadInSandboxedFrame)580 IN_PROC_BROWSER_TEST_F(MimeHandlerViewTest, DoNotLoadInSandboxedFrame) {
581 // Use the testing subclass of MimeHandlerViewGuest.
582 GetGuestViewManager()->RegisterTestGuestViewType<MimeHandlerViewGuest>(
583 base::Bind(&TestMimeHandlerViewGuest::Create));
584
585 const extensions::Extension* extension = LoadTestExtension();
586 ASSERT_TRUE(extension);
587
588 ui_test_utils::NavigateToURL(
589 browser(), embedded_test_server()->GetURL("/test_sandboxed_frame.html"));
590
591 auto* guest_view_manager = GetGuestViewManager();
592 // The page contains three <iframes> where two are sandboxed. The expectation
593 // is that the sandboxed frames do not end up creating a MimeHandlerView.
594 // Therefore, it suffices to wait for one GuestView to be created, then remove
595 // the non-sandboxed frame, and ensue there are no GuestViews left.
596 if (guest_view_manager->num_guests_created() == 0)
597 ASSERT_TRUE(guest_view_manager->WaitForNextGuestCreated());
598 ASSERT_EQ(1U, guest_view_manager->num_guests_created());
599 // Remove the non-sandboxed frame.
600 ASSERT_TRUE(content::ExecJs(GetEmbedderWebContents(),
601 "remove_frame('notsandboxed');"));
602 // The page is expected to embed only '1' GuestView. If there is GuestViews
603 // embedded inside other frames we should be timing out here.
604 guest_view_manager->WaitForAllGuestsDeleted();
605 // Sanity check: Ensure that the documents in a sandbox frame is empty.
606 auto sandbox1_document_has_contents =
607 content::EvalJs(GetEmbedderWebContents(),
608 "!!(sandbox1.contentDocument.body && "
609 "sandbox1.contentDocument.body.firstChild)")
610 .ExtractBool();
611 EXPECT_FALSE(sandbox1_document_has_contents);
612 // The document inside 'sandbox2' contains an <object> with fallback content.
613 // The expectation is that the <object> fails to load the MimeHandlerView and
614 // should show the fallback content instead, which means the width of the
615 // layout object is non-zero.
616 auto fallback_width =
617 content::EvalJs(GetEmbedderWebContents(),
618 "sandbox2.contentDocument.getElementById('fallback')."
619 "getBoundingClientRect().width")
620 .ExtractInt();
621 EXPECT_NE(0, fallback_width);
622 }
623