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 <stddef.h>
6 
7 #include <map>
8 #include <vector>
9 
10 #include "base/base_paths.h"
11 #include "base/bind.h"
12 #include "base/files/file_enumerator.h"
13 #include "base/files/file_util.h"
14 #include "base/hash/hash.h"
15 #include "base/logging.h"
16 #include "base/macros.h"
17 #include "base/memory/ref_counted.h"
18 #include "base/path_service.h"
19 #include "base/run_loop.h"
20 #include "base/strings/pattern.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/strings/utf_string_conversions.h"
24 #include "base/synchronization/lock.h"
25 #include "base/test/bind.h"
26 #include "base/test/metrics/histogram_tester.h"
27 #include "base/test/metrics/user_action_tester.h"
28 #include "base/test/test_timeouts.h"
29 #include "base/thread_annotations.h"
30 #include "base/threading/thread_restrictions.h"
31 #include "build/branding_buildflags.h"
32 #include "build/build_config.h"
33 #include "chrome/app/chrome_command_ids.h"
34 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
35 #include "chrome/browser/extensions/component_loader.h"
36 #include "chrome/browser/extensions/extension_apitest.h"
37 #include "chrome/browser/extensions/extension_service.h"
38 #include "chrome/browser/pdf/pdf_extension_test_util.h"
39 #include "chrome/browser/pdf/pdf_extension_util.h"
40 #include "chrome/browser/plugins/plugin_prefs.h"
41 #include "chrome/browser/plugins/plugin_test_utils.h"
42 #include "chrome/browser/profiles/profile.h"
43 #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h"
44 #include "chrome/browser/ui/browser.h"
45 #include "chrome/browser/ui/browser_finder.h"
46 #include "chrome/browser/ui/tabs/tab_strip_model.h"
47 #include "chrome/common/chrome_content_client.h"
48 #include "chrome/common/chrome_paths.h"
49 #include "chrome/common/chrome_switches.h"
50 #include "chrome/common/pref_names.h"
51 #include "chrome/common/webui_url_constants.h"
52 #include "chrome/test/base/ui_test_utils.h"
53 #include "components/content_settings/core/browser/host_content_settings_map.h"
54 #include "components/download/public/common/download_item.h"
55 #include "components/guest_view/browser/guest_view_manager.h"
56 #include "components/guest_view/browser/guest_view_manager_delegate.h"
57 #include "components/guest_view/browser/test_guest_view_manager.h"
58 #include "components/metrics/content/subprocess_metrics_provider.h"
59 #include "components/viz/common/features.h"
60 #include "components/zoom/page_zoom.h"
61 #include "components/zoom/test/zoom_test_utils.h"
62 #include "components/zoom/zoom_controller.h"
63 #include "content/public/browser/accessibility_tree_formatter.h"
64 #include "content/public/browser/browser_accessibility_state.h"
65 #include "content/public/browser/browser_plugin_guest_manager.h"
66 #include "content/public/browser/browser_task_traits.h"
67 #include "content/public/browser/browser_thread.h"
68 #include "content/public/browser/download_manager.h"
69 #include "content/public/browser/plugin_service.h"
70 #include "content/public/browser/render_frame_host.h"
71 #include "content/public/browser/render_process_host.h"
72 #include "content/public/browser/render_widget_host.h"
73 #include "content/public/browser/render_widget_host_view.h"
74 #include "content/public/browser/web_contents.h"
75 #include "content/public/common/content_features.h"
76 #include "content/public/common/content_switches.h"
77 #include "content/public/common/untrustworthy_context_menu_params.h"
78 #include "content/public/common/url_constants.h"
79 #include "content/public/test/accessibility_notification_waiter.h"
80 #include "content/public/test/browser_test.h"
81 #include "content/public/test/browser_test_utils.h"
82 #include "content/public/test/dump_accessibility_test_helper.h"
83 #include "content/public/test/hit_test_region_observer.h"
84 #include "content/public/test/test_navigation_observer.h"
85 #include "content/public/test/url_loader_interceptor.h"
86 #include "extensions/browser/api/extensions_api_client.h"
87 #include "extensions/browser/extension_registry.h"
88 #include "extensions/common/manifest_handlers/mime_types_handler.h"
89 #include "extensions/test/result_catcher.h"
90 #include "net/dns/mock_host_resolver.h"
91 #include "net/test/embedded_test_server/embedded_test_server.h"
92 #include "pdf/pdf_features.h"
93 #include "services/network/public/cpp/features.h"
94 #include "testing/gmock/include/gmock/gmock.h"
95 #include "third_party/blink/public/common/context_menu_data/media_type.h"
96 #include "ui/accessibility/ax_action_data.h"
97 #include "ui/accessibility/ax_enum_util.h"
98 #include "ui/accessibility/ax_enums.mojom.h"
99 #include "ui/accessibility/ax_node.h"
100 #include "ui/accessibility/ax_tree.h"
101 #include "ui/accessibility/platform/ax_platform_node_delegate_base.h"
102 #include "ui/base/clipboard/clipboard.h"
103 #include "ui/base/clipboard/clipboard_monitor.h"
104 #include "ui/base/clipboard/clipboard_observer.h"
105 #include "ui/base/clipboard/test/test_clipboard.h"
106 #include "ui/base/resource/resource_bundle.h"
107 #include "ui/gfx/geometry/point.h"
108 #include "url/gurl.h"
109 
110 #if defined(TOOLKIT_VIEWS) && !defined(OS_MAC)
111 #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h"
112 #endif
113 
114 #if defined(TOOLKIT_VIEWS) && defined(USE_AURA)
115 #include "ui/events/base_event_utils.h"
116 #include "ui/events/event.h"
117 #include "ui/events/gesture_event_details.h"
118 #include "ui/events/types/event_type.h"
119 #include "ui/views/touchui/touch_selection_menu_views.h"
120 #include "ui/views/widget/any_widget_observer.h"
121 #endif  // defined(TOOLKIT_VIEWS) && defined(USE_AURA)
122 
123 using content::WebContents;
124 using extensions::ExtensionsAPIClient;
125 using guest_view::GuestViewManager;
126 using guest_view::TestGuestViewManager;
127 using guest_view::TestGuestViewManagerFactory;
128 using ui::AXTreeFormatter;
129 
130 const int kNumberLoadTestParts = 10;
131 
132 #if defined(OS_MAC)
133 const int kDefaultKeyModifier = blink::WebInputEvent::kMetaKey;
134 #else
135 const int kDefaultKeyModifier = blink::WebInputEvent::kControlKey;
136 #endif
137 
138 // Calling PluginService::GetPlugins ensures that LoadPlugins is called
139 // internally. This is an asynchronous task and this method uses a run loop to
140 // wait for the loading task to complete.
WaitForPluginServiceToLoad()141 void WaitForPluginServiceToLoad() {
142   base::RunLoop run_loop;
143   content::PluginService::GetPluginsCallback callback = base::BindOnce(
144       [](base::RepeatingClosure quit,
145          const std::vector<content::WebPluginInfo>& unused) { quit.Run(); },
146       run_loop.QuitClosure());
147   content::PluginService::GetInstance()->GetPlugins(std::move(callback));
148   run_loop.Run();
149 }
150 
151 // Check if the |actual| string matches the string or the string pattern in
152 // |pattern| and print a readable message if it does not match.
153 #define ASSERT_MULTILINE_STR_MATCHES(pattern, actual) \
154   ASSERT_TRUE(base::MatchPattern(actual, pattern))    \
155       << "Expected match pattern:\n"                  \
156       << pattern << "\n\nActual:\n"                   \
157       << actual
158 
GetGuestCallback(WebContents ** guest_out,WebContents * guest)159 bool GetGuestCallback(WebContents** guest_out, WebContents* guest) {
160   EXPECT_FALSE(*guest_out);
161   *guest_out = guest;
162   // Return false so that we iterate through all the guests and verify there is
163   // only one.
164   return false;
165 }
166 
167 class PDFExtensionTest : public extensions::ExtensionApiTest {
168  public:
~PDFExtensionTest()169   ~PDFExtensionTest() override {}
170 
SetUpCommandLine(base::CommandLine * command_line)171   void SetUpCommandLine(base::CommandLine* command_line) override {
172     content::IsolateAllSitesForTesting(command_line);
173 
174     feature_list_.InitWithFeatures(GetEnabledFeatures(), GetDisabledFeatures());
175   }
176 
SetUpOnMainThread()177   void SetUpOnMainThread() override {
178     extensions::ExtensionApiTest::SetUpOnMainThread();
179     host_resolver()->AddRule("*", "127.0.0.1");
180     ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
181     content::SetupCrossSiteRedirector(embedded_test_server());
182     embedded_test_server()->StartAcceptingConnections();
183   }
184 
TearDownOnMainThread()185   void TearDownOnMainThread() override {
186     ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete());
187     extensions::ExtensionApiTest::TearDownOnMainThread();
188   }
189 
190   // Serve paths prefixed with _test_resources/ from chrome/test/data.
GetTestResourcesParentDir()191   base::FilePath GetTestResourcesParentDir() override {
192     base::FilePath test_root_path;
193     base::PathService::Get(chrome::DIR_TEST_DATA, &test_root_path);
194     return test_root_path;
195   }
196 
PdfIsExpectedToLoad(const std::string & pdf_file)197   bool PdfIsExpectedToLoad(const std::string& pdf_file) {
198     const char* const kFailingPdfs[] = {
199         "pdf_private/accessibility_crash_1.pdf",
200         "pdf_private/cfuzz5.pdf",
201         "pdf_private/js.pdf",
202         "pdf_private/segv-ecx.pdf",
203         "pdf_private/tests.pdf",
204     };
205     for (const char* failing_pdf : kFailingPdfs) {
206       if (failing_pdf == pdf_file)
207         return false;
208     }
209     return true;
210   }
211 
212   // Load the PDF at the given URL and ensure it has finished loading. Return
213   // true if it loads successfully or false if it fails. If it doesn't finish
214   // loading the test will hang. This is done from outside of the BrowserPlugin
215   // guest to ensure sending messages to/from the plugin works correctly from
216   // there, since the PDFScriptingAPI relies on doing this as well.
LoadPdf(const GURL & url)217   bool LoadPdf(const GURL& url) {
218     ui_test_utils::NavigateToURL(browser(), url);
219     WebContents* web_contents = GetActiveWebContents();
220     return pdf_extension_test_util::EnsurePDFHasLoaded(web_contents);
221   }
222 
223   // Same as LoadPDF(), but loads into a new tab.
LoadPdfInNewTab(const GURL & url)224   bool LoadPdfInNewTab(const GURL& url) {
225     ui_test_utils::NavigateToURLWithDisposition(
226         browser(), url, WindowOpenDisposition::NEW_FOREGROUND_TAB,
227         ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
228     WebContents* web_contents = GetActiveWebContents();
229     return pdf_extension_test_util::EnsurePDFHasLoaded(web_contents);
230   }
231 
232   // Same as LoadPdf(), but also returns a pointer to the guest WebContents for
233   // the loaded PDF. Returns nullptr if the load fails.
LoadPdfGetGuestContents(const GURL & url)234   WebContents* LoadPdfGetGuestContents(const GURL& url) {
235     return LoadPdfGetGuestContentsHelper(url, /*new_tab=*/false);
236   }
237 
238   // Same as LoadPdf(), but also returns a pointer to the guest WebContents for
239   // the loaded PDF in a new tab. Returns nullptr if the load fails.
LoadPdfInNewTabGetGuestContents(const GURL & url)240   WebContents* LoadPdfInNewTabGetGuestContents(const GURL& url) {
241     return LoadPdfGetGuestContentsHelper(url, /*new_tab=*/true);
242   }
243 
244   // Load all the PDFs contained in chrome/test/data/<dir_name>. This only runs
245   // the test if base::PersistentHash(filename) mod kNumberLoadTestParts == k in
246   // order to shard the files evenly across values of k in [0,
247   // kNumberLoadTestParts).
LoadAllPdfsTest(const std::string & dir_name,size_t k)248   void LoadAllPdfsTest(const std::string& dir_name, size_t k) {
249     base::ScopedAllowBlockingForTesting allow_blocking;
250     base::FilePath test_data_dir;
251     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
252     base::FileEnumerator file_enumerator(test_data_dir.AppendASCII(dir_name),
253                                          false, base::FileEnumerator::FILES,
254                                          FILE_PATH_LITERAL("*.pdf"));
255 
256     size_t count = 0;
257     for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty();
258          file_path = file_enumerator.Next()) {
259       std::string filename = file_path.BaseName().MaybeAsASCII();
260       ASSERT_FALSE(filename.empty());
261 
262       std::string pdf_file = dir_name + "/" + filename;
263       SCOPED_TRACE(pdf_file);
264       if (base::PersistentHash(filename) % kNumberLoadTestParts == k) {
265         LOG(INFO) << "Loading: " << pdf_file;
266         bool success = LoadPdf(embedded_test_server()->GetURL("/" + pdf_file));
267         if (pdf_file == "pdf_private/cfuzz5.pdf")
268           continue;
269         EXPECT_EQ(PdfIsExpectedToLoad(pdf_file), success) << pdf_file;
270       }
271       ++count;
272     }
273     // Assume that there is at least 1 pdf in the directory to guard against
274     // someone deleting the directory and silently making the test pass.
275     ASSERT_GE(count, 1u);
276   }
277 
TestGetSelectedTextReply(GURL url,bool expect_success)278   void TestGetSelectedTextReply(GURL url, bool expect_success) {
279     ASSERT_TRUE(LoadPdf(url));
280 
281     // Reach into the guest and hook into it such that it posts back a 'flush'
282     // message after every getSelectedTextReply message sent.
283     WebContents* web_contents = GetActiveWebContents();
284     content::BrowserPluginGuestManager* guest_manager =
285         web_contents->GetBrowserContext()->GetGuestManager();
286     WebContents* guest_contents = nullptr;
287     ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest(
288         web_contents, base::BindRepeating(&GetGuestCallback, &guest_contents)));
289     ASSERT_TRUE(guest_contents);
290     ASSERT_TRUE(content::ExecuteScript(
291         guest_contents, "viewer.overrideSendScriptingMessageForTest();"));
292 
293     // Add an event listener for flush messages and request the selected text.
294     // If we get a flush message without receiving getSelectedText we know that
295     // the message didn't come through.
296     bool success = false;
297     ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
298         web_contents,
299         "window.addEventListener('message', function(event) {"
300         "  if (event.data == 'flush')"
301         "    window.domAutomationController.send(false);"
302         "  if (event.data.type == 'getSelectedTextReply')"
303         "    window.domAutomationController.send(true);"
304         "});"
305         "document.getElementsByTagName('embed')[0].postMessage("
306         "    {type: 'getSelectedText'});",
307         &success));
308     ASSERT_EQ(expect_success, success);
309   }
310 
ConvertPageCoordToScreenCoord(WebContents * contents,gfx::Point * point)311   void ConvertPageCoordToScreenCoord(WebContents* contents, gfx::Point* point) {
312     ASSERT_TRUE(contents);
313     ASSERT_TRUE(content::ExecuteScript(
314         contents,
315         "var visiblePage = viewer.viewport.getMostVisiblePage();"
316         "var visiblePageDimensions ="
317         "    viewer.viewport.getPageScreenRect(visiblePage);"
318         "var viewportPosition = viewer.viewport.position;"
319         "var screenOffsetX = visiblePageDimensions.x - viewportPosition.x;"
320         "var screenOffsetY = visiblePageDimensions.y - viewportPosition.y;"
321         "if (document.documentElement.hasAttribute("
322         "        'pdf-viewer-update-enabled')) {"
323         "  const scrollParent = viewer.shadowRoot.querySelector('#main');"
324         "  screenOffsetX += scrollParent.offsetLeft;"
325         "  screenOffsetY += scrollParent.offsetTop;"
326         "}"
327         "var linkScreenPositionX ="
328         "    Math.floor(" +
329             base::NumberToString(point->x()) +
330             " * viewer.viewport.internalZoom_" +
331             " + screenOffsetX);"
332             "var linkScreenPositionY ="
333             "    Math.floor(" +
334             base::NumberToString(point->y()) +
335             " * viewer.viewport.internalZoom_" +
336             " +"
337             "    screenOffsetY);"));
338 
339     int x;
340     ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
341         contents,
342         "window.domAutomationController.send(linkScreenPositionX);",
343         &x));
344 
345     int y;
346     ASSERT_TRUE(content::ExecuteScriptAndExtractInt(
347         contents,
348         "window.domAutomationController.send(linkScreenPositionY);",
349         &y));
350 
351     point->SetPoint(x, y);
352   }
353 
GetActiveWebContents()354   WebContents* GetActiveWebContents() {
355     return browser()->tab_strip_model()->GetActiveWebContents();
356   }
357 
CountPDFProcesses()358   int CountPDFProcesses() {
359     int result = -1;
360     base::RunLoop run_loop;
361     content::GetIOThreadTaskRunner({})->PostTaskAndReply(
362         FROM_HERE,
363         base::BindOnce(&PDFExtensionTest::CountPDFProcessesOnIOThread,
364                        base::Unretained(this), base::Unretained(&result)),
365         run_loop.QuitClosure());
366     run_loop.Run();
367     return result;
368   }
369 
CountPDFProcessesOnIOThread(int * result)370   void CountPDFProcessesOnIOThread(int* result) {
371     auto* service = content::PluginService::GetInstance();
372     *result = service->CountPpapiPluginProcessesForProfile(
373         base::FilePath(ChromeContentClient::kPDFPluginPath),
374         browser()->profile()->GetPath());
375   }
376 
377  protected:
378   // Hooks to set up feature flags. Defaults to setting the kPDFViewerUpdate
379   // flag based on the value returned by ShouldEnablePDFViewerUpdate().
GetEnabledFeatures() const380   virtual const std::vector<base::Feature> GetEnabledFeatures() const {
381     std::vector<base::Feature> enabled;
382     if (ShouldEnablePDFViewerUpdate()) {
383       enabled.push_back(chrome_pdf::features::kPDFViewerUpdate);
384     }
385     if (ShouldEnablePdfViewerPresentationMode()) {
386       enabled.push_back(chrome_pdf::features::kPdfViewerPresentationMode);
387     }
388     return enabled;
389   }
390 
GetDisabledFeatures() const391   virtual const std::vector<base::Feature> GetDisabledFeatures() const {
392     std::vector<base::Feature> disabled;
393     if (!ShouldEnablePDFViewerUpdate()) {
394       disabled.push_back(chrome_pdf::features::kPDFViewerUpdate);
395     }
396     if (!ShouldEnablePdfViewerPresentationMode()) {
397       disabled.push_back(chrome_pdf::features::kPdfViewerPresentationMode);
398     }
399     return disabled;
400   }
401 
402   // Hook to set up whether the PDFViewerUpdate feature is enabled.
ShouldEnablePDFViewerUpdate() const403   virtual bool ShouldEnablePDFViewerUpdate() const { return false; }
404 
405   // Hook to set up whether the PdfViewerPresentationMode feature is enabled.
ShouldEnablePdfViewerPresentationMode() const406   virtual bool ShouldEnablePdfViewerPresentationMode() const { return false; }
407 
408  private:
LoadPdfGetGuestContentsHelper(const GURL & url,bool new_tab)409   WebContents* LoadPdfGetGuestContentsHelper(const GURL& url, bool new_tab) {
410     if (new_tab) {
411       if (!LoadPdfInNewTab(url))
412         return nullptr;
413     } else if (!LoadPdf(url)) {
414       return nullptr;
415     }
416 
417     WebContents* contents = GetActiveWebContents();
418     content::BrowserPluginGuestManager* guest_manager =
419         contents->GetBrowserContext()->GetGuestManager();
420     WebContents* guest_contents = guest_manager->GetFullPageGuest(contents);
421     return guest_contents;
422   }
423 
424   base::test::ScopedFeatureList feature_list_;
425 };
426 
427 class PDFExtensionTestWithParam : public PDFExtensionTest,
428                                   public testing::WithParamInterface<bool> {
429  public:
430   ~PDFExtensionTestWithParam() override = default;
431 
432  protected:
ShouldEnablePDFViewerUpdate() const433   bool ShouldEnablePDFViewerUpdate() const override { return GetParam(); }
434 };
435 
436 class PDFExtensionTestWithTestGuestViewManager
437     : public PDFExtensionTestWithParam {
438  public:
PDFExtensionTestWithTestGuestViewManager()439   PDFExtensionTestWithTestGuestViewManager() {
440     GuestViewManager::set_factory_for_testing(&factory_);
441   }
442 
443  protected:
GetGuestViewManager()444   TestGuestViewManager* GetGuestViewManager() {
445     // TODO(wjmaclean): Re-implement FromBrowserContext in the
446     // TestGuestViewManager class to avoid all callers needing this cast.
447     auto* manager = static_cast<TestGuestViewManager*>(
448         TestGuestViewManager::FromBrowserContext(browser()->profile()));
449     // TestGuestViewManager::WaitForSingleGuestCreated can and will get called
450     // before a guest is created. Since GuestViewManager is usually not created
451     // until the first guest is created, this means that |manager| will be
452     // nullptr if trying to use the manager to wait for the first guest. Because
453     // of this, the manager must be created here if it does not already exist.
454     if (!manager) {
455       manager = static_cast<TestGuestViewManager*>(
456           GuestViewManager::CreateWithDelegate(
457               browser()->profile(),
458               ExtensionsAPIClient::Get()->CreateGuestViewManagerDelegate(
459                   browser()->profile())));
460     }
461     return manager;
462   }
463 
464  private:
465   TestGuestViewManagerFactory factory_;
466 };
467 
468 // This test is a re-implementation of
469 // WebPluginContainerTest.PluginDocumentPluginIsFocused, which was introduced
470 // for https://crbug.com/536637. The original implementation checked that the
471 // BrowserPlugin hosting the pdf extension was focused; in this re-write, we
472 // make sure the guest view's WebContents has focus.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,PdfInMainFrameHasFocus)473 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,
474                        PdfInMainFrameHasFocus) {
475   // Load test HTML, and verify the text area has focus.
476   GURL main_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
477   ui_test_utils::NavigateToURL(browser(), main_url);
478   auto* embedder_web_contents = GetActiveWebContents();
479 
480   // Verify the pdf has loaded.
481   auto* guest_web_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
482   ASSERT_TRUE(guest_web_contents);
483   EXPECT_NE(embedder_web_contents, guest_web_contents);
484   EXPECT_TRUE(content::WaitForLoadStop(guest_web_contents));
485 
486   // Make sure the guest WebContents has focus.
487   EXPECT_EQ(guest_web_contents,
488             content::GetFocusedWebContents(embedder_web_contents));
489 }
490 
491 // This test verifies that when a PDF is loaded, that (i) the embedder
492 // WebContents' html consists of a single <embed> tag with appropriate
493 // properties, and (ii) that the guest WebContents finishes loading and
494 // has the correct URL for the PDF extension.
495 // TODO(wjmaclean): Are there any attributes we can/should test with respect to
496 // the extension's loaded html?
497 // TODO(https://crbug.com/1034972): Re-enable. Flaky on all platforms.
498 // Temporarily re-enabling on all platforms to collect diagnostic data.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,PdfExtensionLoadedInGuest)499 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,
500                        PdfExtensionLoadedInGuest) {
501   // Load test HTML, and verify the text area has focus.
502   GURL main_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
503   ui_test_utils::NavigateToURL(browser(), main_url);
504   auto* embedder_web_contents = GetActiveWebContents();
505 
506   // Verify the pdf has loaded.
507   auto* guest_web_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
508   ASSERT_TRUE(guest_web_contents);
509   EXPECT_NE(embedder_web_contents, guest_web_contents);
510   EXPECT_TRUE(content::WaitForLoadStop(guest_web_contents));
511 
512   // Verify we loaded the extension.
513   const GURL extension_url(
514       "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html");
515   EXPECT_EQ(extension_url, guest_web_contents->GetURL());
516   EXPECT_EQ(main_url, embedder_web_contents->GetURL());
517 
518   // Make sure the embedder has the correct html boilerplate.
519   EXPECT_EQ(1, content::EvalJs(embedder_web_contents,
520                                "document.body.children.length;")
521                    .ExtractInt());
522   EXPECT_EQ("EMBED", content::EvalJs(embedder_web_contents,
523                                      "document.body.firstChild.tagName;")
524                          .ExtractString());
525   EXPECT_EQ("application/pdf", content::EvalJs(embedder_web_contents,
526                                                "document.body.firstChild.type;")
527                                    .ExtractString());
528   EXPECT_EQ("about:blank", content::EvalJs(embedder_web_contents,
529                                            "document.body.firstChild.src;")
530                                .ExtractString());
531   EXPECT_TRUE(
532       content::EvalJs(embedder_web_contents,
533                       "document.body.firstChild.hasAttribute('internalid');")
534           .ExtractBool());
535 }
536 
537 // This test verifies that when a PDF is served with a restrictive
538 // Content-Security-Policy, the embed tag is still sized correctly.
539 // Regression test for https://crbug.com/271452.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,CSPDoesNotBlockEmbedStyles)540 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,
541                        CSPDoesNotBlockEmbedStyles) {
542   GURL main_url(embedded_test_server()->GetURL("/pdf/test-csp.pdf"));
543   ui_test_utils::NavigateToURL(browser(), main_url);
544   auto* embedder_web_contents = GetActiveWebContents();
545   ASSERT_TRUE(embedder_web_contents);
546 
547   // Verify the pdf has loaded.
548   auto* guest_web_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
549   ASSERT_TRUE(guest_web_contents);
550   EXPECT_NE(embedder_web_contents, guest_web_contents);
551   EXPECT_TRUE(content::WaitForLoadStop(guest_web_contents));
552 
553   // Verify the extension was loaded.
554   const GURL extension_url(
555       "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html");
556   EXPECT_EQ(extension_url, guest_web_contents->GetURL());
557   EXPECT_EQ(main_url, embedder_web_contents->GetURL());
558 
559   // Verify that the plugin occupies all of the page area.
560   const gfx::Rect embedder_rect = embedder_web_contents->GetContainerBounds();
561   const gfx::Rect guest_rect = guest_web_contents->GetContainerBounds();
562   EXPECT_EQ(embedder_rect, guest_rect);
563 }
564 
565 // This test verifies that Content-Security-Policy's frame-ancestors 'none'
566 // directive is effective on a PDF response.
567 // Regression test for https://crbug.com/1107535.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,CSPFrameAncestorsCanBlockEmbedding)568 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,
569                        CSPFrameAncestorsCanBlockEmbedding) {
570   WebContents* web_contents = GetActiveWebContents();
571   content::WebContentsConsoleObserver console_observer(web_contents);
572   console_observer.SetPattern(
573       "*because an ancestor violates the following Content Security Policy "
574       "directive: \"frame-ancestors 'none'*");
575 
576   GURL main_url(embedded_test_server()->GetURL(
577       "/pdf/frame-test-csp-frame-ancestors-none.html"));
578   ui_test_utils::NavigateToURL(browser(), main_url);
579 
580   console_observer.Wait();
581 
582   // Didn't launch a PPAPI process.
583   EXPECT_EQ(0, CountPDFProcesses());
584 }
585 
586 // This test verifies that Content-Security-Policy's frame-ancestors directive
587 // overrides an X-Frame-Options header on a PDF response.
588 // Regression test for https://crbug.com/1107535.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,CSPFrameAncestorsOverridesXFrameOptions)589 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithTestGuestViewManager,
590                        CSPFrameAncestorsOverridesXFrameOptions) {
591   GURL main_url(
592       embedded_test_server()->GetURL("/pdf/frame-test-csp-and-xfo.html"));
593   ui_test_utils::NavigateToURL(browser(), main_url);
594   auto* embedder_web_contents = GetActiveWebContents();
595   ASSERT_TRUE(embedder_web_contents);
596 
597   // Verify the pdf has loaded.
598   auto* guest_web_contents = GetGuestViewManager()->WaitForSingleGuestCreated();
599   ASSERT_TRUE(guest_web_contents);
600   EXPECT_NE(embedder_web_contents, guest_web_contents);
601   EXPECT_TRUE(content::WaitForLoadStop(guest_web_contents));
602 
603   // Verify the extension was loaded.
604   const GURL extension_url(
605       "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html");
606   EXPECT_EQ(extension_url, guest_web_contents->GetURL());
607   EXPECT_EQ(main_url, embedder_web_contents->GetURL());
608 }
609 
610 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
611                          PDFExtensionTestWithTestGuestViewManager,
612                          testing::Bool());
613 
614 class PDFExtensionLoadTest
615     : public PDFExtensionTest,
616       public testing::WithParamInterface<std::pair<bool, size_t>> {
617  public:
618   PDFExtensionLoadTest() = default;
619 
620  protected:
ShouldEnablePDFViewerUpdate() const621   bool ShouldEnablePDFViewerUpdate() const override { return GetParam().first; }
622 };
623 
624 using PDFExtensionHitTestTest = PDFExtensionTestWithParam;
625 
626 // Disabled because it's flaky.
627 // See the issue for details: https://crbug.com/826055.
628 #if defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER) || \
629     defined(ADDRESS_SANITIZER)
630 #define MAYBE_Load DISABLED_Load
631 #else
632 #define MAYBE_Load Load
633 #endif
IN_PROC_BROWSER_TEST_P(PDFExtensionLoadTest,MAYBE_Load)634 IN_PROC_BROWSER_TEST_P(PDFExtensionLoadTest, MAYBE_Load) {
635 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
636   // Load private PDFs.
637   LoadAllPdfsTest("pdf_private", GetParam().second);
638 #endif
639   // Load public PDFs.
640   LoadAllPdfsTest("pdf", GetParam().second);
641 }
642 
GetTestPairValues(size_t range_max)643 const std::vector<std::pair<bool, size_t>> GetTestPairValues(size_t range_max) {
644   std::vector<std::pair<bool, size_t>> values;
645   for (size_t i = 0; i < range_max; i++) {
646     values.emplace_back(std::make_pair(true, i));
647     values.emplace_back(std::make_pair(false, i));
648   }
649   return values;
650 }
651 
652 // We break PDFExtensionLoadTest up into kNumberLoadTestParts.
653 INSTANTIATE_TEST_SUITE_P(
654     PDFTestFiles,
655     PDFExtensionLoadTest,
656     testing::ValuesIn(GetTestPairValues(kNumberLoadTestParts)));
657 
658 class DownloadAwaiter : public content::DownloadManager::Observer {
659  public:
DownloadAwaiter()660   DownloadAwaiter() {}
~DownloadAwaiter()661   ~DownloadAwaiter() override {}
662 
GetLastUrl()663   const GURL& GetLastUrl() {
664     // Wait until the download has been created.
665     download_run_loop_.Run();
666     return last_url_;
667   }
668 
669   // content::DownloadManager::Observer implementation.
OnDownloadCreated(content::DownloadManager * manager,download::DownloadItem * item)670   void OnDownloadCreated(content::DownloadManager* manager,
671                          download::DownloadItem* item) override {
672     last_url_ = item->GetURL();
673     download_run_loop_.Quit();
674   }
675 
676  private:
677   base::RunLoop download_run_loop_;
678   GURL last_url_;
679 };
680 
681 // Tests behavior when the PDF plugin is disabled in preferences.
682 class PDFPluginDisabledTest : public PDFExtensionTestWithParam {
683  public:
PDFPluginDisabledTest()684   PDFPluginDisabledTest() {}
685 
686  protected:
SetUpCommandLine(base::CommandLine * command_line)687   void SetUpCommandLine(base::CommandLine* command_line) override {
688     PDFExtensionTest::SetUpCommandLine(command_line);
689 
690     command_line->AppendSwitch(switches::kEnablePluginPlaceholderTesting);
691   }
692 
SetUpOnMainThread()693   void SetUpOnMainThread() override {
694     PDFExtensionTest::SetUpOnMainThread();
695 
696     content::BrowserContext* browser_context =
697         GetActiveWebContents()->GetBrowserContext();
698     Profile* profile = Profile::FromBrowserContext(browser_context);
699     profile->GetPrefs()->SetBoolean(prefs::kPluginsAlwaysOpenPdfExternally,
700                                     true);
701 
702     content::DownloadManager* download_manager =
703         content::BrowserContext::GetDownloadManager(browser_context);
704     download_awaiter_ = std::make_unique<DownloadAwaiter>();
705     download_manager->AddObserver(download_awaiter_.get());
706   }
707 
TearDownOnMainThread()708   void TearDownOnMainThread() override {
709     content::BrowserContext* browser_context =
710         GetActiveWebContents()->GetBrowserContext();
711     content::DownloadManager* download_manager =
712         content::BrowserContext::GetDownloadManager(browser_context);
713     download_manager->RemoveObserver(download_awaiter_.get());
714 
715     // Cancel all downloads to shut down cleanly.
716     std::vector<download::DownloadItem*> downloads;
717     download_manager->GetAllDownloads(&downloads);
718     for (auto* item : downloads) {
719       item->Cancel(false);
720     }
721 
722     PDFExtensionTest::TearDownOnMainThread();
723   }
724 
ClickOpenButtonInIframe()725   void ClickOpenButtonInIframe() {
726     int iframes_found = 0;
727     for (auto* host : GetActiveWebContents()->GetAllFrames()) {
728       if (host != GetActiveWebContents()->GetMainFrame()) {
729         ASSERT_TRUE(content::ExecJs(
730             host, "document.getElementById('open-button').click();"));
731         ++iframes_found;
732       }
733     }
734     ASSERT_EQ(1, iframes_found);
735   }
736 
ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch()737   void ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch() {
738     // Validate that we downloaded a single PDF and didn't launch the PDF
739     // plugin.
740     GURL pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
741     EXPECT_EQ(pdf_url, AwaitAndGetLastDownloadedUrl());
742     EXPECT_EQ(1u, GetNumberOfDownloads());
743     EXPECT_EQ(0, CountPDFProcesses());
744   }
745 
746  private:
GetNumberOfDownloads()747   size_t GetNumberOfDownloads() {
748     content::BrowserContext* browser_context =
749         GetActiveWebContents()->GetBrowserContext();
750     content::DownloadManager* download_manager =
751         content::BrowserContext::GetDownloadManager(browser_context);
752 
753     std::vector<download::DownloadItem*> downloads;
754     download_manager->GetAllDownloads(&downloads);
755     return downloads.size();
756   }
757 
AwaitAndGetLastDownloadedUrl()758   const GURL& AwaitAndGetLastDownloadedUrl() {
759     return download_awaiter_->GetLastUrl();
760   }
761 
762   std::unique_ptr<DownloadAwaiter> download_awaiter_;
763 };
764 
IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest,DirectNavigationToPDF)765 IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest, DirectNavigationToPDF) {
766   // Navigate to a PDF and test that it is downloaded.
767   GURL pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
768   ui_test_utils::NavigateToURL(browser(), pdf_url);
769 
770   ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch();
771 }
772 
IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest,EmbedPdfPlaceholderWithCSP)773 IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest, EmbedPdfPlaceholderWithCSP) {
774   // Navigate to a page with CSP that uses <embed> to embed a PDF as a plugin.
775   GURL embed_page_url =
776       embedded_test_server()->GetURL("/pdf/pdf_embed_csp.html");
777   ui_test_utils::NavigateToURL(browser(), embed_page_url);
778   PluginTestUtils::WaitForPlaceholderReady(GetActiveWebContents(), "pdf_embed");
779 
780   // Fake a click on the <embed>, then press Enter to trigger the download.
781   gfx::Point point_in_pdf(100, 100);
782   content::SimulateMouseClickAt(GetActiveWebContents(), kDefaultKeyModifier,
783                                 blink::WebMouseEvent::Button::kLeft,
784                                 point_in_pdf);
785   content::SimulateKeyPress(GetActiveWebContents(), ui::DomKey::ENTER,
786                             ui::DomCode::ENTER, ui::VKEY_RETURN, false, false,
787                             false, false);
788 
789   ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch();
790 }
791 
IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest,IframePdfPlaceholderWithCSP)792 IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest, IframePdfPlaceholderWithCSP) {
793   // Navigate to a page that uses <iframe> to embed a PDF as a plugin.
794   GURL iframe_page_url =
795       embedded_test_server()->GetURL("/pdf/pdf_iframe_csp.html");
796   ui_test_utils::NavigateToURL(browser(), iframe_page_url);
797 
798   ClickOpenButtonInIframe();
799   ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch();
800 }
801 
IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest,IframePlaceholderInjectedIntoNewWindow)802 IN_PROC_BROWSER_TEST_P(PDFPluginDisabledTest,
803                        IframePlaceholderInjectedIntoNewWindow) {
804   // This is an unusual test to verify crbug.com/924823. We are injecting the
805   // HTML for a PDF IFRAME into a newly created popup with an undefined URL.
806   ASSERT_TRUE(
807       content::EvalJs(
808           GetActiveWebContents(),
809           content::JsReplace(
810               "new Promise((resolve) => {"
811               "  var popup = window.open();"
812               "  popup.document.writeln("
813               "      '<iframe id=\"pdf_iframe\" src=\"' + $1 + '\"></iframe>');"
814               "  var iframe = popup.document.getElementById('pdf_iframe');"
815               "  iframe.onload = () => resolve(true);"
816               "});",
817               embedded_test_server()->GetURL("/pdf/test.pdf").spec()))
818           .ExtractBool());
819 
820   ClickOpenButtonInIframe();
821   ValidateSingleSuccessfulDownloadAndNoPDFPluginLaunch();
822 }
823 
824 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
825                          PDFPluginDisabledTest,
826                          testing::Bool());
827 
828 class PDFExtensionJSTestBase : public PDFExtensionTest {
829  public:
830   ~PDFExtensionJSTestBase() override = default;
831 
832  protected:
RunTestsInJsModule(const std::string & filename,const std::string & pdf_filename)833   void RunTestsInJsModule(const std::string& filename,
834                           const std::string& pdf_filename) {
835     RunTestsInJsModuleHelper(filename, pdf_filename, /*new_tab=*/false);
836   }
837 
RunTestsInJsModuleNewTab(const std::string & filename,const std::string & pdf_filename)838   void RunTestsInJsModuleNewTab(const std::string& filename,
839                                 const std::string& pdf_filename) {
840     RunTestsInJsModuleHelper(filename, pdf_filename, /*new_tab=*/true);
841   }
842 
843  private:
844   // Runs the extensions test at chrome/test/data/pdf/<filename> on the PDF file
845   // at chrome/test/data/pdf/<pdf_filename>, where |filename| is loaded as a JS
846   // module.
RunTestsInJsModuleHelper(const std::string & filename,const std::string & pdf_filename,bool new_tab)847   void RunTestsInJsModuleHelper(const std::string& filename,
848                                 const std::string& pdf_filename,
849                                 bool new_tab) {
850     extensions::ResultCatcher catcher;
851 
852     GURL url(embedded_test_server()->GetURL("/pdf/" + pdf_filename));
853 
854     // It should be good enough to just navigate to the URL. But loading up the
855     // BrowserPluginGuest seems to happen asynchronously as there was flakiness
856     // being seen due to the BrowserPluginGuest not being available yet (see
857     // crbug.com/498077). So instead use LoadPdf() which ensures that the PDF is
858     // loaded before continuing.
859     WebContents* guest_contents = new_tab ? LoadPdfInNewTabGetGuestContents(url)
860                                           : LoadPdfGetGuestContents(url);
861     ASSERT_TRUE(guest_contents);
862 
863     constexpr char kModuleLoaderTemplate[] =
864         R"(var s = document.createElement('script');
865            s.type = 'module';
866            s.src = '_test_resources/pdf/%s';
867            document.body.appendChild(s);)";
868 
869     ASSERT_TRUE(content::ExecuteScript(
870         guest_contents,
871         base::StringPrintf(kModuleLoaderTemplate, filename.c_str())));
872 
873     if (!catcher.GetNextResult())
874       FAIL() << catcher.message();
875   }
876 };
877 
878 class PDFExtensionJSUpdatesDisabledTest : public PDFExtensionJSTestBase {
879  public:
880   ~PDFExtensionJSUpdatesDisabledTest() override = default;
881 
882  protected:
ShouldEnablePDFViewerUpdate() const883   bool ShouldEnablePDFViewerUpdate() const override { return false; }
884 };
885 
886 // Zoom toolbar doesn't exist and the top toolbar is sticky with the new PDF
887 // viewer updates, so run this test only with the updates disabled.
IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesDisabledTest,ToolbarManager)888 IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesDisabledTest, ToolbarManager) {
889   RunTestsInJsModule("toolbar_manager_test.js", "test.pdf");
890 }
891 
892 class PDFExtensionJSUpdatesEnabledTest : public PDFExtensionJSTestBase {
893  public:
894   ~PDFExtensionJSUpdatesEnabledTest() override = default;
895 
896  protected:
ShouldEnablePDFViewerUpdate() const897   bool ShouldEnablePDFViewerUpdate() const override { return true; }
898 };
899 
900 // The following tests verify behavior of elements that are only used when the
901 // PDFViewerUpdate flag is enabled.
IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest,ViewerPdfToolbarNew)902 IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest, ViewerPdfToolbarNew) {
903   // Although this test file does not require a PDF to be loaded, loading the
904   // elements without loading a PDF is difficult.
905   RunTestsInJsModule("viewer_pdf_toolbar_new_test.js", "test.pdf");
906 }
907 
IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest,ViewerPdfSidenav)908 IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest, ViewerPdfSidenav) {
909   // Although this test file does not require a PDF to be loaded, loading the
910   // elements without loading a PDF is difficult.
911   RunTestsInJsModule("viewer_pdf_sidenav_test.js", "test.pdf");
912 }
913 
IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest,ViewerThumbnailBar)914 IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest, ViewerThumbnailBar) {
915   // Although this test file does not require a PDF to be loaded, loading the
916   // elements without loading a PDF is difficult.
917   RunTestsInJsModule("viewer_thumbnail_bar_test.js", "test.pdf");
918 }
919 
IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest,ViewerThumbnail)920 IN_PROC_BROWSER_TEST_F(PDFExtensionJSUpdatesEnabledTest, ViewerThumbnail) {
921   // Although this test file does not require a PDF to be loaded, loading the
922   // elements without loading a PDF is difficult.
923   RunTestsInJsModule("viewer_thumbnail_test.js", "test.pdf");
924 }
925 
926 class PDFExtensionPresentationModeEnabledTest : public PDFExtensionJSTestBase {
927  public:
928   ~PDFExtensionPresentationModeEnabledTest() override = default;
929 
930  protected:
ShouldEnablePDFViewerUpdate() const931   bool ShouldEnablePDFViewerUpdate() const override { return true; }
ShouldEnablePdfViewerPresentationMode() const932   bool ShouldEnablePdfViewerPresentationMode() const override { return true; }
933 };
934 
IN_PROC_BROWSER_TEST_F(PDFExtensionPresentationModeEnabledTest,Fullscreen)935 IN_PROC_BROWSER_TEST_F(PDFExtensionPresentationModeEnabledTest, Fullscreen) {
936   // Although this test file does not require a PDF to be loaded, loading the
937   // elements without loading a PDF is difficult.
938   RunTestsInJsModule("fullscreen_test.js", "test.pdf");
939 }
940 
941 class PDFExtensionJSTest : public PDFExtensionJSTestBase,
942                            public testing::WithParamInterface<bool> {
943  public:
944   ~PDFExtensionJSTest() override = default;
945 
946  protected:
ShouldEnablePDFViewerUpdate() const947   bool ShouldEnablePDFViewerUpdate() const override { return GetParam(); }
948 };
949 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Basic)950 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Basic) {
951   RunTestsInJsModule("basic_test.js", "test.pdf");
952 
953   // Ensure it loaded in a PPAPI process.
954   EXPECT_EQ(1, CountPDFProcesses());
955 }
956 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,BasicPlugin)957 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, BasicPlugin) {
958   RunTestsInJsModule("basic_plugin_test.js", "test.pdf");
959 }
960 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Viewport)961 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Viewport) {
962   RunTestsInJsModule("viewport_test.js", "test.pdf");
963 }
964 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Layout3)965 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Layout3) {
966   RunTestsInJsModule("layout_test.js", "test-layout3.pdf");
967 }
968 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Layout4)969 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Layout4) {
970   RunTestsInJsModule("layout_test.js", "test-layout4.pdf");
971 }
972 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Bookmark)973 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Bookmark) {
974   RunTestsInJsModule("bookmarks_test.js", "test-bookmarks-with-zoom.pdf");
975 }
976 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Navigator)977 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Navigator) {
978   RunTestsInJsModule("navigator_test.js", "test.pdf");
979 }
980 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,ParamsParser)981 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ParamsParser) {
982   RunTestsInJsModule("params_parser_test.js", "test.pdf");
983 }
984 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,ZoomManager)985 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ZoomManager) {
986   RunTestsInJsModule("zoom_manager_test.js", "test.pdf");
987 }
988 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,GestureDetector)989 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, GestureDetector) {
990   RunTestsInJsModule("gesture_detector_test.js", "test.pdf");
991 }
992 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,TouchHandling)993 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, TouchHandling) {
994   RunTestsInJsModule("touch_handling_test.js", "test.pdf");
995 }
996 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Elements)997 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Elements) {
998   // Although this test file does not require a PDF to be loaded, loading the
999   // elements without loading a PDF is difficult.
1000   RunTestsInJsModule("material_elements_test.js", "test.pdf");
1001 }
1002 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,DownloadControls)1003 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, DownloadControls) {
1004   // Although this test file does not require a PDF to be loaded, loading the
1005   // elements without loading a PDF is difficult.
1006   RunTestsInJsModule("download_controls_test.js", "test.pdf");
1007 }
1008 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Title)1009 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Title) {
1010   RunTestsInJsModule("title_test.js", "test-title.pdf");
1011 }
1012 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,WhitespaceTitle)1013 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, WhitespaceTitle) {
1014   RunTestsInJsModule("whitespace_title_test.js", "test-whitespace-title.pdf");
1015 }
1016 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,PageChange)1017 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, PageChange) {
1018   RunTestsInJsModule("page_change_test.js", "test-bookmarks.pdf");
1019 }
1020 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Metrics)1021 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Metrics) {
1022   RunTestsInJsModule("metrics_test.js", "test.pdf");
1023 }
1024 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,ArrayBufferAllocator)1025 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, ArrayBufferAllocator) {
1026   // Run several times to see if there are issues with unloading.
1027   RunTestsInJsModule("beep_test.js", "array_buffer.pdf");
1028   RunTestsInJsModule("beep_test.js", "array_buffer.pdf");
1029   RunTestsInJsModule("beep_test.js", "array_buffer.pdf");
1030 }
1031 
1032 // Test that if the plugin tries to load a URL that redirects then it will fail
1033 // to load. This is to avoid the source origin of the document changing during
1034 // the redirect, which can have security implications. https://crbug.com/653749.
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,RedirectsFailInPlugin)1035 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, RedirectsFailInPlugin) {
1036   RunTestsInJsModule("redirects_fail_test.js", "test.pdf");
1037 }
1038 
1039 #if defined(OS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,Printing)1040 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, Printing) {
1041   RunTestsInJsModule("printing_icon_test.js", "test.pdf");
1042 }
1043 
1044 // TODO(https://crbug.com/920684): Test times out.
1045 #if defined(MEMORY_SANITIZER) || defined(LEAK_SANITIZER) || \
1046     defined(ADDRESS_SANITIZER) || defined(_DEBUG)
1047 #define MAYBE_AnnotationsFeatureEnabled DISABLED_AnnotationsFeatureEnabled
1048 #else
1049 #define MAYBE_AnnotationsFeatureEnabled AnnotationsFeatureEnabled
1050 #endif
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,MAYBE_AnnotationsFeatureEnabled)1051 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, MAYBE_AnnotationsFeatureEnabled) {
1052   RunTestsInJsModule("annotations_feature_enabled_test.js", "test.pdf");
1053 }
1054 
IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest,AnnotationsToolbar)1055 IN_PROC_BROWSER_TEST_P(PDFExtensionJSTest, AnnotationsToolbar) {
1056   // Although this test file does not require a PDF to be loaded, loading the
1057   // elements without loading a PDF is difficult.
1058   RunTestsInJsModule("annotations_toolbar_test.js", "test.pdf");
1059 }
1060 #endif  // defined(OS_CHROMEOS)
1061 
1062 INSTANTIATE_TEST_SUITE_P(/* no prefix */, PDFExtensionJSTest, testing::Bool());
1063 
1064 class PDFExtensionContentSettingJSTest
1065     : public PDFExtensionJSTestBase,
1066       public testing::WithParamInterface<std::pair<bool, bool>> {
1067  public:
1068   ~PDFExtensionContentSettingJSTest() override = default;
1069 
1070  protected:
GetEnabledFeatures() const1071   const std::vector<base::Feature> GetEnabledFeatures() const override {
1072     std::vector<base::Feature> features;
1073     if (ShouldHonorJsContentSettings()) {
1074       features.push_back(chrome_pdf::features::kPdfHonorJsContentSettings);
1075     }
1076     if (ShouldEnablePDFViewerUpdate()) {
1077       features.push_back(chrome_pdf::features::kPDFViewerUpdate);
1078     }
1079     return features;
1080   }
1081 
GetDisabledFeatures() const1082   const std::vector<base::Feature> GetDisabledFeatures() const override {
1083     std::vector<base::Feature> features;
1084     if (!ShouldHonorJsContentSettings()) {
1085       features.push_back(chrome_pdf::features::kPdfHonorJsContentSettings);
1086     }
1087     if (!ShouldEnablePDFViewerUpdate()) {
1088       features.push_back(chrome_pdf::features::kPDFViewerUpdate);
1089     }
1090     return features;
1091   }
1092 
ShouldEnablePDFViewerUpdate() const1093   bool ShouldEnablePDFViewerUpdate() const override { return GetParam().first; }
1094 
ShouldHonorJsContentSettings() const1095   bool ShouldHonorJsContentSettings() const { return GetParam().second; }
1096 
1097   // When blocking JavaScript, block the exact query from pdf/main.js while
1098   // still allowing enough JavaScript to run in the extension for the test
1099   // harness to complete its work.
SetPdfJavaScript(bool enabled)1100   void SetPdfJavaScript(bool enabled) {
1101     auto* map =
1102         HostContentSettingsMapFactory::GetForProfile(browser()->profile());
1103     map->SetContentSettingCustomScope(
1104         ContentSettingsPattern::Wildcard(),
1105         ContentSettingsPattern::FromString(
1106             "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai"),
1107         ContentSettingsType::JAVASCRIPT,
1108         enabled ? CONTENT_SETTING_ALLOW : CONTENT_SETTING_BLOCK);
1109   }
1110 
GetDisabledJsTestFile() const1111   std::string GetDisabledJsTestFile() const {
1112     return ShouldHonorJsContentSettings() ? "nobeep_test.js" : "beep_test.js";
1113   }
1114 };
1115 
IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest,Beep)1116 IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest, Beep) {
1117   RunTestsInJsModule("beep_test.js", "test-beep.pdf");
1118 }
1119 
IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest,NoBeep)1120 IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest, NoBeep) {
1121   SetPdfJavaScript(/*enabled=*/false);
1122   RunTestsInJsModule(GetDisabledJsTestFile(), "test-beep.pdf");
1123 }
1124 
IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest,BeepThenNoBeep)1125 IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest, BeepThenNoBeep) {
1126   RunTestsInJsModule("beep_test.js", "test-beep.pdf");
1127   SetPdfJavaScript(/*enabled=*/false);
1128   RunTestsInJsModuleNewTab(GetDisabledJsTestFile(), "test-beep.pdf");
1129 
1130   // Make sure there are two PDFs in the same process.
1131   const int tab_count = browser()->tab_strip_model()->count();
1132   EXPECT_EQ(2, tab_count);
1133   EXPECT_EQ(1, CountPDFProcesses());
1134 }
1135 
IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest,NoBeepThenBeep)1136 IN_PROC_BROWSER_TEST_P(PDFExtensionContentSettingJSTest, NoBeepThenBeep) {
1137   SetPdfJavaScript(/*enabled=*/false);
1138   RunTestsInJsModule(GetDisabledJsTestFile(), "test-beep.pdf");
1139   SetPdfJavaScript(/*enabled=*/true);
1140   RunTestsInJsModuleNewTab("beep_test.js", "test-beep.pdf");
1141 
1142   // Make sure there are two PDFs in the same process.
1143   const int tab_count = browser()->tab_strip_model()->count();
1144   EXPECT_EQ(2, tab_count);
1145   EXPECT_EQ(1, CountPDFProcesses());
1146 }
1147 
1148 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
1149                          PDFExtensionContentSettingJSTest,
1150                          testing::ValuesIn({std::make_pair(true, false),
1151                                             std::make_pair(false, false),
1152                                             std::make_pair(true, true),
1153                                             std::make_pair(false, true)}));
1154 
1155 // Service worker tests are regression tests for
1156 // https://crbug.com/916514.
1157 class PDFExtensionServiceWorkerJSTest : public PDFExtensionJSTest {
1158  public:
1159   ~PDFExtensionServiceWorkerJSTest() override = default;
1160 
1161  protected:
1162   // Installs the specified service worker and tests navigating to a PDF in its
1163   // scope.
RunServiceWorkerTest(const std::string & worker_path)1164   void RunServiceWorkerTest(const std::string& worker_path) {
1165     // Install the service worker.
1166     ui_test_utils::NavigateToURL(
1167         browser(), embedded_test_server()->GetURL(
1168                        "/service_worker/create_service_worker.html"));
1169     EXPECT_EQ("DONE",
1170               EvalJs(browser()->tab_strip_model()->GetActiveWebContents(),
1171                      "register('" + worker_path + "', '/pdf');"));
1172 
1173     // Navigate to a PDF in the service worker's scope. It should load.
1174     RunTestsInJsModule("basic_test.js", "test.pdf");
1175     // Ensure it loaded in a PPAPI process.
1176     EXPECT_EQ(1, CountPDFProcesses());
1177   }
1178 };
1179 
1180 // Test navigating to a PDF in the scope of a service worker with no fetch event
1181 // handler.
IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest,NoFetchHandler)1182 IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest, NoFetchHandler) {
1183   RunServiceWorkerTest("empty.js");
1184 }
1185 
1186 // Test navigating to a PDF when a service worker intercepts the request and
1187 // then falls back to network by not calling FetchEvent.respondWith().
IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest,NetworkFallback)1188 IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest, NetworkFallback) {
1189   RunServiceWorkerTest("network_fallback_worker.js");
1190 }
1191 
1192 // Test navigating to a PDF when a service worker intercepts the request and
1193 // provides a response.
IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest,Interception)1194 IN_PROC_BROWSER_TEST_P(PDFExtensionServiceWorkerJSTest, Interception) {
1195   RunServiceWorkerTest("respond_with_fetch_worker.js");
1196 }
1197 
1198 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
1199                          PDFExtensionServiceWorkerJSTest,
1200                          testing::Bool());
1201 
1202 // Ensure that the internal PDF plugin application/x-google-chrome-pdf won't be
1203 // loaded if it's not loaded in the chrome extension page.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,EnsureInternalPluginDisabled)1204 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1205                        EnsureInternalPluginDisabled) {
1206   std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec();
1207   std::string data_url =
1208       "data:text/html,"
1209       "<html><body>"
1210       "<embed type=\"application/x-google-chrome-pdf\" src=\"" +
1211       url +
1212       "\">"
1213       "</body></html>";
1214   ui_test_utils::NavigateToURL(browser(), GURL(data_url));
1215   WebContents* web_contents = GetActiveWebContents();
1216   bool plugin_loaded = false;
1217   ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
1218       web_contents,
1219       "var plugin_loaded = "
1220       "    document.getElementsByTagName('embed')[0].postMessage !== undefined;"
1221       "window.domAutomationController.send(plugin_loaded);",
1222       &plugin_loaded));
1223   ASSERT_FALSE(plugin_loaded);
1224 }
1225 
1226 // Ensure cross-origin replies won't work for getSelectedText.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,EnsureCrossOriginRepliesBlocked)1227 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1228                        EnsureCrossOriginRepliesBlocked) {
1229   std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec();
1230   std::string data_url =
1231       "data:text/html,"
1232       "<html><body>"
1233       "<embed type=\"application/pdf\" src=\"" +
1234       url +
1235       "\">"
1236       "</body></html>";
1237   TestGetSelectedTextReply(GURL(data_url), false);
1238 }
1239 
1240 // Ensure same-origin replies do work for getSelectedText.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,EnsureSameOriginRepliesAllowed)1241 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1242                        EnsureSameOriginRepliesAllowed) {
1243   TestGetSelectedTextReply(embedded_test_server()->GetURL("/pdf/test.pdf"),
1244                            true);
1245 }
1246 
1247 // TODO(crbug.com/1004425): Should be allowed?
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,EnsureOpaqueOriginRepliesBlocked)1248 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1249                        EnsureOpaqueOriginRepliesBlocked) {
1250   TestGetSelectedTextReply(
1251       embedded_test_server()->GetURL("/pdf/data_url_rectangles.html"), false);
1252 }
1253 
1254 // Ensure that the PDF component extension cannot be loaded directly.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,BlockDirectAccess)1255 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, BlockDirectAccess) {
1256   WebContents* web_contents = GetActiveWebContents();
1257 
1258   content::WebContentsConsoleObserver console_observer(web_contents);
1259   console_observer.SetPattern(
1260       "*Streams are only available from a mime handler view guest.*");
1261   GURL forbidden_url(
1262       "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html?"
1263       "https://example.com/notrequested.pdf");
1264   ui_test_utils::NavigateToURL(browser(), forbidden_url);
1265 
1266   console_observer.Wait();
1267 
1268   // Didn't launch a PPAPI process.
1269   EXPECT_EQ(0, CountPDFProcesses());
1270 }
1271 
1272 // This test ensures that PDF can be loaded from local file
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,EnsurePDFFromLocalFileLoads)1273 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, EnsurePDFFromLocalFileLoads) {
1274   GURL test_pdf_url;
1275   {
1276     base::ScopedAllowBlockingForTesting allow_blocking;
1277     base::FilePath test_data_dir;
1278     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
1279     test_data_dir = test_data_dir.Append(FILE_PATH_LITERAL("pdf"));
1280     base::FilePath test_data_file = test_data_dir.AppendASCII("test.pdf");
1281     ASSERT_TRUE(PathExists(test_data_file));
1282     test_pdf_url = GURL("file://" + test_data_file.MaybeAsASCII());
1283   }
1284   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1285   ASSERT_TRUE(guest_contents);
1286 
1287   // Did launch a PPAPI process.
1288   EXPECT_EQ(1, CountPDFProcesses());
1289 }
1290 
1291 // Tests that PDF with no filename extension can be loaded from local file.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,ExtensionlessPDFLocalFileLoads)1292 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1293                        ExtensionlessPDFLocalFileLoads) {
1294   GURL test_pdf_url;
1295   {
1296     base::ScopedAllowBlockingForTesting allow_blocking;
1297     base::FilePath test_data_dir;
1298     ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
1299     test_data_dir = test_data_dir.AppendASCII("pdf");
1300     base::FilePath test_data_file = test_data_dir.AppendASCII("imgpdf");
1301     ASSERT_TRUE(PathExists(test_data_file));
1302     test_pdf_url = GURL("file://" + test_data_file.MaybeAsASCII());
1303   }
1304   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1305   ASSERT_TRUE(guest_contents);
1306 
1307   // Did launch a PPAPI process.
1308   EXPECT_EQ(1, CountPDFProcesses());
1309 }
1310 
1311 // This test ensures that link permissions are enforced properly in PDFs.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,LinkPermissions)1312 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, LinkPermissions) {
1313   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
1314   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1315   ASSERT_TRUE(guest_contents);
1316 
1317   // chrome://favicon links should be allowed for PDFs, while chrome://settings
1318   // links should not.
1319   GURL valid_link_url(std::string(chrome::kChromeUIFaviconURL) +
1320                       "https://www.google.ca/");
1321   GURL invalid_link_url(chrome::kChromeUISettingsURL);
1322 
1323   GURL unfiltered_valid_link_url(valid_link_url);
1324   content::RenderProcessHost* rph =
1325       guest_contents->GetMainFrame()->GetProcess();
1326   rph->FilterURL(true, &valid_link_url);
1327   rph->FilterURL(true, &invalid_link_url);
1328 
1329   // Invalid link URLs should be changed to "about:blank#blocked" when filtered.
1330   EXPECT_EQ(unfiltered_valid_link_url, valid_link_url);
1331   EXPECT_EQ(GURL(content::kBlockedURL), invalid_link_url);
1332 }
1333 
1334 // This test ensures that titles are set properly for PDFs without /Title.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,TabTitleWithNoTitle)1335 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, TabTitleWithNoTitle) {
1336   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
1337   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1338   ASSERT_TRUE(guest_contents);
1339   EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), guest_contents->GetTitle());
1340   EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), GetActiveWebContents()->GetTitle());
1341 }
1342 
1343 // This test ensures that titles are set properly for PDFs with /Title.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,TabTitleWithTitle)1344 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, TabTitleWithTitle) {
1345   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-title.pdf"));
1346   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1347   ASSERT_TRUE(guest_contents);
1348   EXPECT_EQ(base::ASCIIToUTF16("PDF title test"), guest_contents->GetTitle());
1349   EXPECT_EQ(base::ASCIIToUTF16("PDF title test"),
1350             GetActiveWebContents()->GetTitle());
1351 }
1352 
1353 // This test ensures that titles are set properly for embedded PDFs with /Title.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,TabTitleWithEmbeddedPdf)1354 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, TabTitleWithEmbeddedPdf) {
1355   std::string url =
1356       embedded_test_server()->GetURL("/pdf/test-title.pdf").spec();
1357   std::string data_url =
1358       "data:text/html,"
1359       "<html><head><title>TabTitleWithEmbeddedPdf</title></head><body>"
1360       "<embed type=\"application/pdf\" src=\"" +
1361       url +
1362       "\"></body></html>";
1363   ASSERT_TRUE(LoadPdf(GURL(data_url)));
1364   EXPECT_EQ(base::ASCIIToUTF16("TabTitleWithEmbeddedPdf"),
1365             GetActiveWebContents()->GetTitle());
1366 }
1367 
1368 // Flaky, http://crbug.com/767427
1369 #if defined(OS_WIN)
1370 #define MAYBE_PdfZoomWithoutBubble DISABLED_PdfZoomWithoutBubble
1371 #else
1372 #define MAYBE_PdfZoomWithoutBubble PdfZoomWithoutBubble
1373 #endif
IN_PROC_BROWSER_TEST_F(PDFExtensionTest,MAYBE_PdfZoomWithoutBubble)1374 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, MAYBE_PdfZoomWithoutBubble) {
1375   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
1376   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1377   ASSERT_TRUE(guest_contents);
1378   WebContents* web_contents = GetActiveWebContents();
1379 
1380   // The PDF viewer, when the update is disabled, always starts at default zoom,
1381   // which for tests is 100% or zoom level 0.0. Here we look at the presets to
1382   // find the next zoom level above 0. Ideally we should look at the zoom levels
1383   // from the PDF viewer javascript, but we assume they'll always match the
1384   // browser presets, which are easier to access. When the update is enabled,
1385   // the presence of the sidenav causes the default zoom to be just under 90%.
1386   // In this case, we add 2 additional zoom calls to match the final result.
1387   std::vector<double> preset_zoom_levels = zoom::PageZoom::PresetZoomLevels(0);
1388   auto it = std::find(preset_zoom_levels.begin(), preset_zoom_levels.end(), 0);
1389   ASSERT_TRUE(it != preset_zoom_levels.end());
1390   it++;
1391   ASSERT_TRUE(it != preset_zoom_levels.end());
1392   double new_zoom_level = *it;
1393 
1394   auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents);
1395   // We expect a ZoomChangedEvent with can_show_bubble == false if the PDF
1396   // extension behaviour is properly picked up. The test times out otherwise.
1397   zoom::ZoomChangedWatcher watcher(
1398       zoom_controller, zoom::ZoomController::ZoomChangedEventData(
1399                            web_contents, 0, new_zoom_level,
1400                            zoom::ZoomController::ZOOM_MODE_MANUAL, false));
1401 
1402   // Zoom PDF via script.
1403 #if defined(TOOLKIT_VIEWS) && !defined(OS_MAC)
1404   EXPECT_EQ(nullptr, ZoomBubbleView::GetZoomBubble());
1405 #endif
1406   ASSERT_TRUE(
1407       content::ExecuteScript(guest_contents, "viewer.viewport.zoomIn();"));
1408 
1409   // Two extra calls - the first zoomIn() takes zoom to 90%, the second to 100%,
1410   // and the third goes to the next zoom level above 100%, which is the desired
1411   // result for this test.
1412   ASSERT_TRUE(
1413       content::ExecuteScript(guest_contents,
1414                              "if (document.documentElement.hasAttribute("
1415                              "        'pdf-viewer-update-enabled')) {"
1416                              "  viewer.viewport.zoomIn();"
1417                              "  viewer.viewport.zoomIn();"
1418                              "}"));
1419 
1420   watcher.Wait();
1421 #if defined(TOOLKIT_VIEWS) && !defined(OS_MAC)
1422   EXPECT_EQ(nullptr, ZoomBubbleView::GetZoomBubble());
1423 #endif
1424 }
1425 
DumpPdfAccessibilityTree(const ui::AXTreeUpdate & ax_tree)1426 static std::string DumpPdfAccessibilityTree(const ui::AXTreeUpdate& ax_tree) {
1427   // Create a string representation of the tree starting with the embedded
1428   // object.
1429   std::string ax_tree_dump;
1430   std::map<int32_t, int> id_to_indentation;
1431   bool found_embedded_object = false;
1432   for (auto& node : ax_tree.nodes) {
1433     if (node.role == ax::mojom::Role::kEmbeddedObject)
1434       found_embedded_object = true;
1435     if (!found_embedded_object)
1436       continue;
1437 
1438     auto indent_found = id_to_indentation.find(node.id);
1439     int indent = 0;
1440     if (indent_found != id_to_indentation.end()) {
1441       indent = indent_found->second;
1442     } else if (node.role != ax::mojom::Role::kEmbeddedObject) {
1443       // If this node has no indent and isn't the embedded object, return, as
1444       // this indicates the end of the PDF.
1445       return ax_tree_dump;
1446     }
1447 
1448     ax_tree_dump += std::string(2 * indent, ' ');
1449     ax_tree_dump += ui::ToString(node.role);
1450 
1451     std::string name =
1452         node.GetStringAttribute(ax::mojom::StringAttribute::kName);
1453     base::ReplaceChars(name, "\r\n", "", &name);
1454     if (!name.empty())
1455       ax_tree_dump += " '" + name + "'";
1456     ax_tree_dump += "\n";
1457     for (size_t j = 0; j < node.child_ids.size(); ++j)
1458       id_to_indentation[node.child_ids[j]] = indent + 1;
1459   }
1460 
1461   return ax_tree_dump;
1462 }
1463 
1464 // This is a pattern with a few wildcards due to a PDF bug where the
1465 // fi ligature is not parsed correctly on some systems.
1466 // http://crbug.com/701427
1467 
1468 static const char kExpectedPDFAXTreePattern[] =
1469     "embeddedObject\n"
1470     "  document 'PDF document containing 3 pages'\n"
1471     "    region 'Page 1'\n"
1472     "      paragraph\n"
1473     "        staticText '1 First Section'\n"
1474     "          inlineTextBox '1 '\n"
1475     "          inlineTextBox 'First Section'\n"
1476     "      paragraph\n"
1477     "        staticText 'This is the *rst section.'\n"
1478     "          inlineTextBox 'This is the *rst section.'\n"
1479     "      paragraph\n"
1480     "        staticText '1'\n"
1481     "          inlineTextBox '1'\n"
1482     "    region 'Page 2'\n"
1483     "      paragraph\n"
1484     "        staticText '1.1 First Subsection'\n"
1485     "          inlineTextBox '1.1 '\n"
1486     "          inlineTextBox 'First Subsection'\n"
1487     "      paragraph\n"
1488     "        staticText 'This is the *rst subsection.'\n"
1489     "          inlineTextBox 'This is the *rst subsection.'\n"
1490     "      paragraph\n"
1491     "        staticText '2'\n"
1492     "          inlineTextBox '2'\n"
1493     "    region 'Page 3'\n"
1494     "      paragraph\n"
1495     "        staticText '2 Second Section'\n"
1496     "          inlineTextBox '2 '\n"
1497     "          inlineTextBox 'Second Section'\n"
1498     "      paragraph\n"
1499     "        staticText '3'\n"
1500     "          inlineTextBox '3'\n";
1501 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibility)1502 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, PdfAccessibility) {
1503   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
1504 
1505   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
1506   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1507   ASSERT_TRUE(guest_contents);
1508 
1509   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
1510                                                 "1 First Section\r\n");
1511   ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
1512   std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree);
1513 
1514   ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump);
1515 }
1516 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityEnableLater)1517 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, PdfAccessibilityEnableLater) {
1518   // In this test, load the PDF file first, with accessibility off.
1519   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
1520   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1521   ASSERT_TRUE(guest_contents);
1522 
1523   // Now enable accessibility globally, and assert that the PDF accessibility
1524   // tree loads.
1525   EnableAccessibilityForWebContents(guest_contents);
1526   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
1527                                                 "1 First Section\r\n");
1528   ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
1529   std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree);
1530   ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump);
1531 }
1532 
RetrieveGuestContents(WebContents ** out_guest_contents,WebContents * in_guest_contents)1533 bool RetrieveGuestContents(WebContents** out_guest_contents,
1534                            WebContents* in_guest_contents) {
1535   *out_guest_contents = in_guest_contents;
1536   return true;
1537 }
1538 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityInIframe)1539 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, PdfAccessibilityInIframe) {
1540   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
1541   GURL test_iframe_url(embedded_test_server()->GetURL("/pdf/test-iframe.html"));
1542   ui_test_utils::NavigateToURL(browser(), test_iframe_url);
1543   WebContents* contents = GetActiveWebContents();
1544   WaitForAccessibilityTreeToContainNodeWithName(contents,
1545                                                 "1 First Section\r\n");
1546 
1547   WebContents* guest_contents = nullptr;
1548   content::BrowserPluginGuestManager* guest_manager =
1549         contents->GetBrowserContext()->GetGuestManager();
1550   guest_manager->ForEachGuest(contents,
1551                               base::Bind(&RetrieveGuestContents,
1552                                          &guest_contents));
1553   ASSERT_TRUE(guest_contents);
1554 
1555   ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
1556   std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree);
1557   ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump);
1558 }
1559 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityInOOPIF)1560 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, PdfAccessibilityInOOPIF) {
1561   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
1562   GURL test_iframe_url(embedded_test_server()->GetURL(
1563       "/pdf/test-cross-site-iframe.html"));
1564   ui_test_utils::NavigateToURL(browser(), test_iframe_url);
1565   WebContents* contents = GetActiveWebContents();
1566   WaitForAccessibilityTreeToContainNodeWithName(contents,
1567                                                 "1 First Section\r\n");
1568 
1569   WebContents* guest_contents = nullptr;
1570   content::BrowserPluginGuestManager* guest_manager =
1571         contents->GetBrowserContext()->GetGuestManager();
1572   guest_manager->ForEachGuest(contents,
1573                               base::Bind(&RetrieveGuestContents,
1574                                          &guest_contents));
1575   ASSERT_TRUE(guest_contents);
1576 
1577   ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
1578   std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree);
1579   ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump);
1580 }
1581 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityWordBoundaries)1582 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1583                        PdfAccessibilityWordBoundaries) {
1584   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
1585 
1586   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
1587   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1588   ASSERT_TRUE(guest_contents);
1589 
1590   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
1591                                                 "1 First Section\r\n");
1592   ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
1593 
1594   bool found = false;
1595   for (auto& node : ax_tree.nodes) {
1596     std::string name =
1597         node.GetStringAttribute(ax::mojom::StringAttribute::kName);
1598     if (node.role == ax::mojom::Role::kInlineTextBox &&
1599         name == "First Section\r\n") {
1600       found = true;
1601       std::vector<int32_t> word_starts =
1602           node.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts);
1603       std::vector<int32_t> word_ends =
1604           node.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds);
1605       ASSERT_EQ(2U, word_starts.size());
1606       ASSERT_EQ(2U, word_ends.size());
1607       EXPECT_EQ(0, word_starts[0]);
1608       EXPECT_EQ(5, word_ends[0]);
1609       EXPECT_EQ(6, word_starts[1]);
1610       EXPECT_EQ(13, word_ends[1]);
1611     }
1612   }
1613   ASSERT_TRUE(found);
1614 }
1615 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilitySelection)1616 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, PdfAccessibilitySelection) {
1617   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
1618   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1619   ASSERT_TRUE(guest_contents);
1620 
1621   WebContents* web_contents = GetActiveWebContents();
1622   CHECK(content::ExecuteScript(
1623       web_contents,
1624       "document.getElementsByTagName('embed')[0].postMessage("
1625       "{type: 'selectAll'});"));
1626 
1627   EnableAccessibilityForWebContents(guest_contents);
1628   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
1629                                                 "1 First Section\r\n");
1630   ui::AXTreeUpdate ax_tree_update =
1631       GetAccessibilityTreeSnapshot(guest_contents);
1632   ui::AXTree ax_tree(ax_tree_update);
1633 
1634   // Ensure that the selection spans the beginning of the first text
1635   // node to the end of the last one.
1636   ui::AXNode* sel_start_node =
1637       ax_tree.GetFromId(ax_tree.data().sel_anchor_object_id);
1638   ASSERT_TRUE(sel_start_node);
1639   EXPECT_EQ(ax::mojom::Role::kStaticText, sel_start_node->data().role);
1640   std::string start_node_name = sel_start_node->data().GetStringAttribute(
1641       ax::mojom::StringAttribute::kName);
1642   EXPECT_EQ("1 First Section\r\n", start_node_name);
1643   EXPECT_EQ(0, ax_tree.data().sel_anchor_offset);
1644   ui::AXNode* para = sel_start_node->parent();
1645   EXPECT_EQ(ax::mojom::Role::kParagraph, para->data().role);
1646   ui::AXNode* region = para->parent();
1647   EXPECT_EQ(ax::mojom::Role::kRegion, region->data().role);
1648 
1649   ui::AXNode* sel_end_node =
1650       ax_tree.GetFromId(ax_tree.data().sel_focus_object_id);
1651   ASSERT_TRUE(sel_end_node);
1652   std::string end_node_name = sel_end_node->data().GetStringAttribute(
1653       ax::mojom::StringAttribute::kName);
1654   EXPECT_EQ("3", end_node_name);
1655   EXPECT_EQ(static_cast<int>(end_node_name.size()),
1656             ax_tree.data().sel_focus_offset);
1657   para = sel_end_node->parent();
1658   EXPECT_EQ(ax::mojom::Role::kParagraph, para->data().role);
1659   region = para->parent();
1660   EXPECT_EQ(ax::mojom::Role::kRegion, region->data().role);
1661 }
1662 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityContextMenuAction)1663 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1664                        PdfAccessibilityContextMenuAction) {
1665   // Validate the context menu arguments for PDF selection when context menu is
1666   // invoked via accessibility tree.
1667   const char kExepectedPDFSelection[] =
1668       "1 First Section\n"
1669       "This is the first section.\n"
1670       "1\n"
1671       "1.1 First Subsection\n"
1672       "This is the first subsection.\n"
1673       "2\n"
1674       "2 Second Section\n"
1675       "3";
1676 
1677   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf"));
1678   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1679   ASSERT_TRUE(guest_contents);
1680 
1681   CHECK(content::ExecuteScript(
1682       GetActiveWebContents(),
1683       "document.getElementsByTagName('embed')[0].postMessage("
1684       "{type: 'selectAll'});"));
1685 
1686   EnableAccessibilityForWebContents(guest_contents);
1687   WaitForAccessibilityTreeToContainNodeWithName(guest_contents,
1688                                                 "1 First Section\r\n");
1689 
1690   // Find PDF document node in the accessibility tree.
1691   content::FindAccessibilityNodeCriteria find_criteria;
1692   find_criteria.role = ax::mojom::Role::kEmbeddedObject;
1693   ui::AXPlatformNodeDelegate* pdf_root =
1694       content::FindAccessibilityNode(guest_contents, find_criteria);
1695   ASSERT_TRUE(pdf_root);
1696 
1697   find_criteria.role = ax::mojom::Role::kDocument;
1698   ui::AXPlatformNodeDelegate* pdf_doc_node =
1699       FindAccessibilityNodeInSubtree(pdf_root, find_criteria);
1700   ASSERT_TRUE(pdf_doc_node);
1701 
1702   content::RenderProcessHost* guest_process_host =
1703       guest_contents->GetMainFrame()->GetProcess();
1704   auto context_menu_filter = base::MakeRefCounted<content::ContextMenuFilter>();
1705   guest_process_host->AddFilter(context_menu_filter.get());
1706 
1707   ContextMenuWaiter menu_waiter;
1708   // Invoke kShowContextMenu accessibility action on PDF document node.
1709   ui::AXActionData data;
1710   data.action = ax::mojom::Action::kShowContextMenu;
1711   pdf_doc_node->AccessibilityPerformAction(data);
1712   menu_waiter.WaitForMenuOpenAndClose();
1713 
1714   context_menu_filter->Wait();
1715   content::UntrustworthyContextMenuParams params =
1716       context_menu_filter->get_params();
1717 
1718   // Validate the context menu params for selection.
1719   EXPECT_EQ(blink::ContextMenuDataMediaType::kPlugin, params.media_type);
1720   std::string selected_text = base::UTF16ToUTF8(params.selection_text);
1721   base::ReplaceChars(selected_text, "\r", "", &selected_text);
1722   EXPECT_EQ(kExepectedPDFSelection, selected_text);
1723 }
1724 
1725 #if BUILDFLAG(GOOGLE_CHROME_BRANDING)
1726 // Test a particular PDF encountered in the wild that triggered a crash
1727 // when accessibility is enabled.  (http://crbug.com/668724)
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfAccessibilityTextRunCrash)1728 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
1729                        PdfAccessibilityTextRunCrash) {
1730   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
1731   GURL test_pdf_url(embedded_test_server()->GetURL(
1732       "/pdf_private/accessibility_crash_2.pdf"));
1733 
1734   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1735   ASSERT_TRUE(guest_contents);
1736 
1737   WaitForAccessibilityTreeToContainNodeWithName(guest_contents, "Page 1");
1738 }
1739 #endif
1740 
1741 // Test that even if a different tab is selected when a navigation occurs,
1742 // the correct tab still gets navigated (see crbug.com/672563).
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,NavigationOnCorrectTab)1743 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, NavigationOnCorrectTab) {
1744   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
1745   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
1746   ASSERT_TRUE(guest_contents);
1747   WebContents* web_contents = GetActiveWebContents();
1748 
1749   ui_test_utils::NavigateToURLWithDisposition(
1750       browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB,
1751       ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB |
1752           ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP);
1753   WebContents* active_web_contents = GetActiveWebContents();
1754   ASSERT_NE(web_contents, active_web_contents);
1755 
1756   content::TestNavigationObserver active_navigation_observer(
1757       active_web_contents);
1758   content::TestNavigationObserver navigation_observer(web_contents);
1759   ASSERT_TRUE(
1760       content::ExecuteScript(guest_contents,
1761                              "viewer.navigator_.navigate("
1762                              "    'www.example.com',"
1763                              "    WindowOpenDisposition.CURRENT_TAB);"));
1764   navigation_observer.Wait();
1765 
1766   EXPECT_FALSE(navigation_observer.last_navigation_url().is_empty());
1767   EXPECT_TRUE(active_navigation_observer.last_navigation_url().is_empty());
1768   EXPECT_FALSE(active_web_contents->GetController().GetPendingEntry());
1769 }
1770 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,MultipleDomains)1771 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, MultipleDomains) {
1772   for (const std::string& domain : {"a.com", "b.com"}) {
1773     const GURL url = embedded_test_server()->GetURL(domain, "/pdf/test.pdf");
1774     ASSERT_TRUE(LoadPdfInNewTab(url));
1775   }
1776   EXPECT_EQ(2, CountPDFProcesses());
1777 }
1778 
1779 class PDFExtensionLinkClickTest : public PDFExtensionTestWithParam {
1780  public:
PDFExtensionLinkClickTest()1781   PDFExtensionLinkClickTest() : guest_contents_(nullptr) {}
~PDFExtensionLinkClickTest()1782   ~PDFExtensionLinkClickTest() override {}
1783 
1784  protected:
LoadTestLinkPdfGetGuestContents()1785   void LoadTestLinkPdfGetGuestContents() {
1786     GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-link.pdf"));
1787     guest_contents_ = LoadPdfGetGuestContents(test_pdf_url);
1788     ASSERT_TRUE(guest_contents_);
1789   }
1790 
1791   // The rectangle of the link in test-link.pdf is [72 706 164 719] in PDF user
1792   // space. To calculate a position inside this rectangle, several
1793   // transformations have to be applied:
1794   // [a] (110, 110) in Blink page coordinates ->
1795   // [b] (219, 169) in Blink screen coordinates ->
1796   // [c] (115, 169) in PDF Device space coordinates ->
1797   // [d] (82.5, 709.5) in PDF user space coordinates.
1798   // This performs the [a] to [b] transformation, since that is the coordinate
1799   // space content::SimulateMouseClickAt() needs.
GetLinkPosition()1800   gfx::Point GetLinkPosition() {
1801     gfx::Point link_position(110, 110);
1802     ConvertPageCoordToScreenCoord(guest_contents_, &link_position);
1803     return link_position;
1804   }
1805 
SetGuestContents(WebContents * guest_contents)1806   void SetGuestContents(WebContents* guest_contents) {
1807     ASSERT_TRUE(guest_contents);
1808     guest_contents_ = guest_contents;
1809   }
1810 
GetWebContentsForInputRouting()1811   content::WebContents* GetWebContentsForInputRouting() {
1812     return guest_contents_;
1813   }
1814 
1815  private:
1816   WebContents* guest_contents_;
1817 };
1818 
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,CtrlLeft)1819 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, CtrlLeft) {
1820   LoadTestLinkPdfGetGuestContents();
1821 
1822   WebContents* web_contents = GetActiveWebContents();
1823 
1824   content::SimulateMouseClickAt(
1825       GetWebContentsForInputRouting(), kDefaultKeyModifier,
1826       blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
1827   ui_test_utils::TabAddedWaiter(browser()).Wait();
1828 
1829   int tab_count = browser()->tab_strip_model()->count();
1830   ASSERT_EQ(2, tab_count);
1831 
1832   WebContents* active_web_contents = GetActiveWebContents();
1833   ASSERT_EQ(web_contents, active_web_contents);
1834 
1835   WebContents* new_web_contents =
1836       browser()->tab_strip_model()->GetWebContentsAt(1);
1837   ASSERT_TRUE(new_web_contents);
1838   ASSERT_NE(web_contents, new_web_contents);
1839 
1840   const GURL& url = new_web_contents->GetURL();
1841   EXPECT_EQ("http://www.example.com/", url.spec());
1842 }
1843 
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,Middle)1844 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, Middle) {
1845   LoadTestLinkPdfGetGuestContents();
1846 
1847   WebContents* web_contents = GetActiveWebContents();
1848 
1849   content::SimulateMouseClickAt(GetWebContentsForInputRouting(), 0,
1850                                 blink::WebMouseEvent::Button::kMiddle,
1851                                 GetLinkPosition());
1852   ui_test_utils::TabAddedWaiter(browser()).Wait();
1853 
1854   int tab_count = browser()->tab_strip_model()->count();
1855   ASSERT_EQ(2, tab_count);
1856 
1857   WebContents* active_web_contents = GetActiveWebContents();
1858   ASSERT_EQ(web_contents, active_web_contents);
1859 
1860   WebContents* new_web_contents =
1861       browser()->tab_strip_model()->GetWebContentsAt(1);
1862   ASSERT_TRUE(new_web_contents);
1863   ASSERT_NE(web_contents, new_web_contents);
1864 
1865   const GURL& url = new_web_contents->GetURL();
1866   EXPECT_EQ("http://www.example.com/", url.spec());
1867 }
1868 
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,CtrlShiftLeft)1869 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, CtrlShiftLeft) {
1870   LoadTestLinkPdfGetGuestContents();
1871 
1872   WebContents* web_contents = GetActiveWebContents();
1873 
1874   const int modifiers = blink::WebInputEvent::kShiftKey | kDefaultKeyModifier;
1875 
1876   content::SimulateMouseClickAt(GetWebContentsForInputRouting(), modifiers,
1877                                 blink::WebMouseEvent::Button::kLeft,
1878                                 GetLinkPosition());
1879   ui_test_utils::TabAddedWaiter(browser()).Wait();
1880 
1881   int tab_count = browser()->tab_strip_model()->count();
1882   ASSERT_EQ(2, tab_count);
1883 
1884   WebContents* active_web_contents = GetActiveWebContents();
1885   ASSERT_NE(web_contents, active_web_contents);
1886 
1887   const GURL& url = active_web_contents->GetURL();
1888   EXPECT_EQ("http://www.example.com/", url.spec());
1889 }
1890 
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,ShiftMiddle)1891 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, ShiftMiddle) {
1892   LoadTestLinkPdfGetGuestContents();
1893 
1894   WebContents* web_contents = GetActiveWebContents();
1895 
1896   content::SimulateMouseClickAt(
1897       GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
1898       blink::WebMouseEvent::Button::kMiddle, GetLinkPosition());
1899   ui_test_utils::TabAddedWaiter(browser()).Wait();
1900 
1901   int tab_count = browser()->tab_strip_model()->count();
1902   ASSERT_EQ(2, tab_count);
1903 
1904   WebContents* active_web_contents = GetActiveWebContents();
1905   ASSERT_NE(web_contents, active_web_contents);
1906 
1907   const GURL& url = active_web_contents->GetURL();
1908   EXPECT_EQ("http://www.example.com/", url.spec());
1909 }
1910 
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,ShiftLeft)1911 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, ShiftLeft) {
1912   LoadTestLinkPdfGetGuestContents();
1913 
1914   ASSERT_EQ(1U, chrome::GetTotalBrowserCount());
1915 
1916   WebContents* web_contents = GetActiveWebContents();
1917 
1918   content::SimulateMouseClickAt(
1919       GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
1920       blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
1921   ui_test_utils::WaitForBrowserToOpen();
1922 
1923   ASSERT_EQ(2U, chrome::GetTotalBrowserCount());
1924 
1925   WebContents* active_web_contents =
1926       chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents();
1927   ASSERT_NE(web_contents, active_web_contents);
1928 
1929   const GURL& url = active_web_contents->GetURL();
1930   EXPECT_EQ("http://www.example.com/", url.spec());
1931 }
1932 
1933 // This test opens a PDF by clicking a link via javascript and verifies that
1934 // the PDF is loaded and functional by clicking a link in the PDF. The link
1935 // click in the PDF opens a new tab. The main page handles the pageShow event
1936 // and updates the history state.
IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest,OpenPDFWithReplaceState)1937 IN_PROC_BROWSER_TEST_P(PDFExtensionLinkClickTest, OpenPDFWithReplaceState) {
1938   // Navigate to the main page.
1939   GURL test_url(
1940       embedded_test_server()->GetURL("/pdf/pdf_href_replace_state.html"));
1941   ui_test_utils::NavigateToURL(browser(), test_url);
1942   WebContents* web_contents = GetActiveWebContents();
1943   ASSERT_TRUE(web_contents);
1944 
1945   // Click on the link which opens the PDF via JS.
1946   content::TestNavigationObserver navigation_observer(web_contents);
1947   const char kPdfLinkClick[] = "document.getElementById('link').click();";
1948   ASSERT_TRUE(content::ExecuteScript(web_contents, kPdfLinkClick));
1949   navigation_observer.Wait();
1950   const GURL& current_url = web_contents->GetURL();
1951   ASSERT_EQ("/pdf/test-link.pdf", current_url.path());
1952 
1953   ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(web_contents));
1954 
1955   // Now click on the link to example.com in the PDF. This should open up a new
1956   // tab.
1957   content::BrowserPluginGuestManager* guest_manager =
1958       web_contents->GetBrowserContext()->GetGuestManager();
1959   SetGuestContents(guest_manager->GetFullPageGuest(web_contents));
1960 
1961   content::SimulateMouseClickAt(
1962       GetWebContentsForInputRouting(), kDefaultKeyModifier,
1963       blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
1964   ui_test_utils::TabAddedWaiter(browser()).Wait();
1965 
1966   // We should have two tabs now. One with the PDF and the second for
1967   // example.com
1968   int tab_count = browser()->tab_strip_model()->count();
1969   ASSERT_EQ(2, tab_count);
1970 
1971   WebContents* active_web_contents = GetActiveWebContents();
1972   ASSERT_EQ(web_contents, active_web_contents);
1973 
1974   WebContents* new_web_contents =
1975       browser()->tab_strip_model()->GetWebContentsAt(1);
1976   ASSERT_TRUE(new_web_contents);
1977   ASSERT_NE(web_contents, new_web_contents);
1978 
1979   const GURL& url = new_web_contents->GetURL();
1980   EXPECT_EQ("http://www.example.com/", url.spec());
1981 }
1982 
1983 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
1984                          PDFExtensionLinkClickTest,
1985                          testing::Bool());
1986 
1987 class PDFExtensionInternalLinkClickTest : public PDFExtensionTestWithParam {
1988  public:
PDFExtensionInternalLinkClickTest()1989   PDFExtensionInternalLinkClickTest() : guest_contents_(nullptr) {}
~PDFExtensionInternalLinkClickTest()1990   ~PDFExtensionInternalLinkClickTest() override {}
1991 
1992  protected:
LoadTestLinkPdfGetGuestContents()1993   void LoadTestLinkPdfGetGuestContents() {
1994     GURL test_pdf_url(
1995         embedded_test_server()->GetURL("/pdf/test-internal-link.pdf"));
1996     guest_contents_ = LoadPdfGetGuestContents(test_pdf_url);
1997     ASSERT_TRUE(guest_contents_);
1998   }
1999 
GetLinkPosition()2000   gfx::Point GetLinkPosition() {
2001     // The whole first page is a link.
2002     gfx::Point link_position(100, 100);
2003     ConvertPageCoordToScreenCoord(guest_contents_, &link_position);
2004     return link_position;
2005   }
2006 
GetWebContentsForInputRouting()2007   content::WebContents* GetWebContentsForInputRouting() {
2008     return guest_contents_;
2009   }
2010 
2011  private:
2012   WebContents* guest_contents_;
2013 };
2014 
IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest,CtrlLeft)2015 IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest, CtrlLeft) {
2016   LoadTestLinkPdfGetGuestContents();
2017 
2018   WebContents* web_contents = GetActiveWebContents();
2019 
2020   content::SimulateMouseClickAt(
2021       GetWebContentsForInputRouting(), kDefaultKeyModifier,
2022       blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
2023   ui_test_utils::TabAddedWaiter(browser()).Wait();
2024 
2025   int tab_count = browser()->tab_strip_model()->count();
2026   ASSERT_EQ(2, tab_count);
2027 
2028   WebContents* active_web_contents = GetActiveWebContents();
2029   ASSERT_EQ(web_contents, active_web_contents);
2030 
2031   WebContents* new_web_contents =
2032       browser()->tab_strip_model()->GetWebContentsAt(1);
2033   ASSERT_TRUE(new_web_contents);
2034   ASSERT_NE(web_contents, new_web_contents);
2035 
2036   const GURL& url = new_web_contents->GetURL();
2037   EXPECT_EQ("/pdf/test-internal-link.pdf", url.path());
2038   EXPECT_EQ("page=2&zoom=100,0,200", url.ref());
2039 }
2040 
IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest,Middle)2041 IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest, Middle) {
2042   LoadTestLinkPdfGetGuestContents();
2043 
2044   WebContents* web_contents = GetActiveWebContents();
2045 
2046   content::SimulateMouseClickAt(GetWebContentsForInputRouting(), 0,
2047                                 blink::WebMouseEvent::Button::kMiddle,
2048                                 GetLinkPosition());
2049   ui_test_utils::TabAddedWaiter(browser()).Wait();
2050 
2051   int tab_count = browser()->tab_strip_model()->count();
2052   ASSERT_EQ(2, tab_count);
2053 
2054   WebContents* active_web_contents = GetActiveWebContents();
2055   ASSERT_EQ(web_contents, active_web_contents);
2056 
2057   WebContents* new_web_contents =
2058       browser()->tab_strip_model()->GetWebContentsAt(1);
2059   ASSERT_TRUE(new_web_contents);
2060   ASSERT_NE(web_contents, new_web_contents);
2061 
2062   const GURL& url = new_web_contents->GetURL();
2063   EXPECT_EQ("/pdf/test-internal-link.pdf", url.path());
2064   EXPECT_EQ("page=2&zoom=100,0,200", url.ref());
2065 }
2066 
IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest,ShiftLeft)2067 IN_PROC_BROWSER_TEST_P(PDFExtensionInternalLinkClickTest, ShiftLeft) {
2068   LoadTestLinkPdfGetGuestContents();
2069 
2070   ASSERT_EQ(1U, chrome::GetTotalBrowserCount());
2071 
2072   WebContents* web_contents = GetActiveWebContents();
2073 
2074   content::SimulateMouseClickAt(
2075       GetWebContentsForInputRouting(), blink::WebInputEvent::kShiftKey,
2076       blink::WebMouseEvent::Button::kLeft, GetLinkPosition());
2077   ui_test_utils::WaitForBrowserToOpen();
2078 
2079   ASSERT_EQ(2U, chrome::GetTotalBrowserCount());
2080 
2081   WebContents* active_web_contents =
2082       chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents();
2083   ASSERT_NE(web_contents, active_web_contents);
2084 
2085   const GURL& url = active_web_contents->GetURL();
2086   EXPECT_EQ("/pdf/test-internal-link.pdf", url.path());
2087   EXPECT_EQ("page=2&zoom=100,0,200", url.ref());
2088 }
2089 
2090 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
2091                          PDFExtensionInternalLinkClickTest,
2092                          testing::Bool());
2093 
2094 class PDFExtensionClipboardTest : public PDFExtensionTestWithParam,
2095                                   public ui::ClipboardObserver {
2096  public:
PDFExtensionClipboardTest()2097   PDFExtensionClipboardTest() : guest_contents_(nullptr) {}
~PDFExtensionClipboardTest()2098   ~PDFExtensionClipboardTest() override {}
2099 
2100   // PDFExtensionTest:
SetUpOnMainThread()2101   void SetUpOnMainThread() override {
2102     PDFExtensionTest::SetUpOnMainThread();
2103     ui::TestClipboard::CreateForCurrentThread();
2104   }
TearDownOnMainThread()2105   void TearDownOnMainThread() override {
2106     ui::Clipboard::DestroyClipboardForCurrentThread();
2107     PDFExtensionTest::TearDownOnMainThread();
2108   }
2109 
2110   // ui::ClipboardObserver:
OnClipboardDataChanged()2111   void OnClipboardDataChanged() override {
2112     DCHECK(!clipboard_changed_);
2113     clipboard_changed_ = true;
2114     std::move(clipboard_quit_closure_).Run();
2115   }
2116 
LoadTestComboBoxPdfGetGuestContents()2117   void LoadTestComboBoxPdfGetGuestContents() {
2118     GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/combobox_form.pdf"));
2119     guest_contents_ = LoadPdfGetGuestContents(test_pdf_url);
2120     ASSERT_TRUE(guest_contents_);
2121   }
2122 
2123   // Returns a point near the left edge of the editable combo box in
2124   // combobox_form.pdf, inside the combo box rect. The point is in Blink screen
2125   // coordinates.
2126   //
2127   // The combo box's rect is [100 50 200 80] in PDF user space. (136, 318) in
2128   // Blink page coordinates corresponds to approximately (102, 62) in PDF user
2129   // space coordinates. See PDFExtensionLinkClickTest::GetLinkPosition() for
2130   // more information on all the coordinate systems involved.
GetEditableComboBoxLeftPosition()2131   gfx::Point GetEditableComboBoxLeftPosition() {
2132     gfx::Point position(136, 318);
2133     ConvertPageCoordToScreenCoord(guest_contents_, &position);
2134     return position;
2135   }
2136 
ClickLeftSideOfEditableComboBox()2137   void ClickLeftSideOfEditableComboBox() {
2138     content::SimulateMouseClickAt(GetWebContentsForInputRouting(), 0,
2139                                   blink::WebMouseEvent::Button::kLeft,
2140                                   GetEditableComboBoxLeftPosition());
2141   }
2142 
TypeHello()2143   void TypeHello() {
2144     auto* web_contents = GetWebContentsForInputRouting();
2145     content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('H'),
2146                               ui::DomCode::US_H, ui::VKEY_H, false, false,
2147                               false, false);
2148     content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('E'),
2149                               ui::DomCode::US_E, ui::VKEY_E, false, false,
2150                               false, false);
2151     content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('L'),
2152                               ui::DomCode::US_L, ui::VKEY_L, false, false,
2153                               false, false);
2154     content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('L'),
2155                               ui::DomCode::US_L, ui::VKEY_L, false, false,
2156                               false, false);
2157     content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('O'),
2158                               ui::DomCode::US_O, ui::VKEY_O, false, false,
2159                               false, false);
2160   }
2161 
2162   // Presses the left arrow key.
PressLeftArrow()2163   void PressLeftArrow() {
2164     content::SimulateKeyPressWithoutChar(
2165         GetWebContentsForInputRouting(), ui::DomKey::ARROW_LEFT,
2166         ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT, false, false, false, false);
2167   }
2168 
2169   // Presses down shift, presses the left arrow, and lets go of shift.
PressShiftLeftArrow()2170   void PressShiftLeftArrow() {
2171     content::SimulateKeyPressWithoutChar(GetWebContentsForInputRouting(),
2172                                          ui::DomKey::ARROW_LEFT,
2173                                          ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT,
2174                                          false, /*shift=*/true, false, false);
2175   }
2176 
2177   // Presses the right arrow key.
PressRightArrow()2178   void PressRightArrow() {
2179     content::SimulateKeyPressWithoutChar(
2180         GetWebContentsForInputRouting(), ui::DomKey::ARROW_RIGHT,
2181         ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT, false, false, false, false);
2182   }
2183 
2184   // Presses down shift, presses the right arrow, and lets go of shift.
PressShiftRightArrow()2185   void PressShiftRightArrow() {
2186     content::SimulateKeyPressWithoutChar(
2187         GetWebContentsForInputRouting(), ui::DomKey::ARROW_RIGHT,
2188         ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT, false, /*shift=*/true, false,
2189         false);
2190   }
2191 
2192   // Runs `action` and checks the Linux selection clipboard contains `expected`.
DoActionAndCheckSelectionClipboard(base::OnceClosure action,const std::string & expected)2193   void DoActionAndCheckSelectionClipboard(base::OnceClosure action,
2194                                           const std::string& expected) {
2195 #if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_BSD)
2196     DoActionAndCheckClipboard(std::move(action),
2197                               ui::ClipboardBuffer::kSelection, expected);
2198 #else
2199     // Even though there is no selection clipboard to check, `action` still
2200     // needs to run.
2201     std::move(action).Run();
2202 #endif
2203   }
2204 
2205   // Sends a copy command and checks the copy/paste clipboard.
2206   // Note: Trying to send ctrl+c does not work correctly with
2207   // SimulateKeyPress(). Using IDC_COPY does not work on Mac in browser_tests.
SendCopyCommandAndCheckCopyPasteClipboard(const std::string & expected)2208   void SendCopyCommandAndCheckCopyPasteClipboard(const std::string& expected) {
2209     DoActionAndCheckClipboard(base::BindLambdaForTesting([&]() {
2210                                 GetWebContentsForInputRouting()->Copy();
2211                               }),
2212                               ui::ClipboardBuffer::kCopyPaste, expected);
2213   }
2214 
GetWebContentsForInputRouting()2215   content::WebContents* GetWebContentsForInputRouting() {
2216     return guest_contents_;
2217   }
2218 
2219  private:
2220   // Runs `action` and checks `clipboard_buffer` contains `expected`.
DoActionAndCheckClipboard(base::OnceClosure action,ui::ClipboardBuffer clipboard_buffer,const std::string & expected)2221   void DoActionAndCheckClipboard(base::OnceClosure action,
2222                                  ui::ClipboardBuffer clipboard_buffer,
2223                                  const std::string& expected) {
2224     ui::ClipboardMonitor::GetInstance()->AddObserver(this);
2225     DCHECK(!clipboard_changed_);
2226     DCHECK(!clipboard_quit_closure_);
2227 
2228     base::RunLoop run_loop;
2229     clipboard_quit_closure_ = run_loop.QuitClosure();
2230     std::move(action).Run();
2231     run_loop.Run();
2232 
2233     EXPECT_TRUE(clipboard_changed_);
2234     clipboard_changed_ = false;
2235     ui::ClipboardMonitor::GetInstance()->RemoveObserver(this);
2236 
2237     auto* clipboard = ui::Clipboard::GetForCurrentThread();
2238     std::string clipboard_data;
2239     clipboard->ReadAsciiText(clipboard_buffer, /* data_dst=*/nullptr,
2240                              &clipboard_data);
2241     EXPECT_EQ(expected, clipboard_data);
2242   }
2243 
2244   base::RepeatingClosure clipboard_quit_closure_;
2245   WebContents* guest_contents_;
2246   bool clipboard_changed_ = false;
2247 };
2248 
IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,IndividualShiftRightArrowPresses)2249 IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,
2250                        IndividualShiftRightArrowPresses) {
2251   LoadTestComboBoxPdfGetGuestContents();
2252 
2253   // Give the editable combo box focus.
2254   ClickLeftSideOfEditableComboBox();
2255 
2256   TypeHello();
2257 
2258   // Put the cursor back to the left side of the combo box.
2259   ClickLeftSideOfEditableComboBox();
2260 
2261   // Press shift + right arrow 3 times. Letting go of shift in between.
2262   auto action = base::BindLambdaForTesting([&]() { PressShiftRightArrow(); });
2263   DoActionAndCheckSelectionClipboard(action, "H");
2264   DoActionAndCheckSelectionClipboard(action, "HE");
2265   DoActionAndCheckSelectionClipboard(action, "HEL");
2266   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
2267 }
2268 
2269 // TODO(crbug.com/897801): test is flaky.
IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,DISABLED_IndividualShiftLeftArrowPresses)2270 IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,
2271                        DISABLED_IndividualShiftLeftArrowPresses) {
2272   LoadTestComboBoxPdfGetGuestContents();
2273 
2274   // Give the editable combo box focus.
2275   ClickLeftSideOfEditableComboBox();
2276 
2277   TypeHello();
2278 
2279   // Put the cursor back to the left side of the combo box.
2280   ClickLeftSideOfEditableComboBox();
2281 
2282   for (int i = 0; i < 3; ++i)
2283     PressRightArrow();
2284 
2285   // Press shift + left arrow 2 times. Letting go of shift in between.
2286   auto action = base::BindLambdaForTesting([&]() { PressShiftLeftArrow(); });
2287   DoActionAndCheckSelectionClipboard(action, "L");
2288   DoActionAndCheckSelectionClipboard(action, "EL");
2289   SendCopyCommandAndCheckCopyPasteClipboard("EL");
2290 
2291   // Press shift + left arrow 2 times. Letting go of shift in between.
2292   DoActionAndCheckSelectionClipboard(action, "HEL");
2293   DoActionAndCheckSelectionClipboard(action, "HEL");
2294   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
2295 }
2296 
IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,CombinedShiftRightArrowPresses)2297 IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,
2298                        CombinedShiftRightArrowPresses) {
2299   LoadTestComboBoxPdfGetGuestContents();
2300 
2301   // Give the editable combo box focus.
2302   ClickLeftSideOfEditableComboBox();
2303 
2304   TypeHello();
2305 
2306   // Put the cursor back to the left side of the combo box.
2307   ClickLeftSideOfEditableComboBox();
2308 
2309   // Press shift + right arrow 3 times. Holding down shift in between.
2310   {
2311     content::ScopedSimulateModifierKeyPress hold_shift(
2312         GetWebContentsForInputRouting(), false, true, false, false);
2313     auto action = base::BindLambdaForTesting([&]() {
2314       hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT,
2315                                      ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT);
2316     });
2317     DoActionAndCheckSelectionClipboard(action, "H");
2318     DoActionAndCheckSelectionClipboard(action, "HE");
2319     DoActionAndCheckSelectionClipboard(action, "HEL");
2320   }
2321   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
2322 }
2323 
IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest,CombinedShiftArrowPresses)2324 IN_PROC_BROWSER_TEST_P(PDFExtensionClipboardTest, CombinedShiftArrowPresses) {
2325   LoadTestComboBoxPdfGetGuestContents();
2326 
2327   // Give the editable combo box focus.
2328   ClickLeftSideOfEditableComboBox();
2329 
2330   TypeHello();
2331 
2332   // Put the cursor back to the left side of the combo box.
2333   ClickLeftSideOfEditableComboBox();
2334 
2335   for (int i = 0; i < 3; ++i)
2336     PressRightArrow();
2337 
2338   // Press shift + left arrow 3 times. Holding down shift in between.
2339   {
2340     content::ScopedSimulateModifierKeyPress hold_shift(
2341         GetWebContentsForInputRouting(), false, true, false, false);
2342     auto action = base::BindLambdaForTesting([&]() {
2343       hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_LEFT,
2344                                      ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT);
2345     });
2346     DoActionAndCheckSelectionClipboard(action, "L");
2347     DoActionAndCheckSelectionClipboard(action, "EL");
2348     DoActionAndCheckSelectionClipboard(action, "HEL");
2349   }
2350   SendCopyCommandAndCheckCopyPasteClipboard("HEL");
2351 
2352   // Press shift + right arrow 2 times. Holding down shift in between.
2353   {
2354     content::ScopedSimulateModifierKeyPress hold_shift(
2355         GetWebContentsForInputRouting(), false, true, false, false);
2356     auto action = base::BindLambdaForTesting([&]() {
2357       hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT,
2358                                      ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT);
2359     });
2360     DoActionAndCheckSelectionClipboard(action, "EL");
2361     DoActionAndCheckSelectionClipboard(action, "L");
2362   }
2363   SendCopyCommandAndCheckCopyPasteClipboard("L");
2364 }
2365 
2366 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
2367                          PDFExtensionClipboardTest,
2368                          testing::Bool());
2369 
2370 // Verifies that an <embed> of size zero will still instantiate a guest and post
2371 // message to the <embed> is correctly forwarded to the extension. This is for
2372 // catching future regression in docs/ and slides/ pages (see
2373 // https://crbug.com/763812).
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PostMessageForZeroSizedEmbed)2374 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
2375                        PostMessageForZeroSizedEmbed) {
2376   content::DOMMessageQueue queue;
2377   GURL url(embedded_test_server()->GetURL(
2378       "/pdf/post_message_zero_sized_embed.html"));
2379   ui_test_utils::NavigateToURL(browser(), url);
2380   std::string message;
2381   EXPECT_TRUE(queue.WaitForMessage(&message));
2382   EXPECT_EQ("\"POST_MESSAGE_OK\"", message);
2383 }
2384 
2385 // In response to the events sent in |send_events|, ensures the PDF viewer zooms
2386 // in and that the viewer's custom pinch zooming mechanism is used to do so.
EnsureCustomPinchZoomInvoked(WebContents * guest_contents,WebContents * contents,base::OnceClosure send_events)2387 void EnsureCustomPinchZoomInvoked(WebContents* guest_contents,
2388                                   WebContents* contents,
2389                                   base::OnceClosure send_events) {
2390   ASSERT_TRUE(content::ExecuteScript(
2391       guest_contents,
2392       "var gestureDetector = new GestureDetector(viewer.plugin_); "
2393       "var updatePromise = new Promise(function(resolve) { "
2394       "  gestureDetector.getEventTarget().addEventListener('pinchupdate', "
2395       "resolve); "
2396       "});"));
2397 
2398   zoom::ZoomChangedWatcher zoom_watcher(
2399       contents,
2400       base::BindRepeating(
2401           [](const zoom::ZoomController::ZoomChangedEventData& event) {
2402             return event.new_zoom_level > event.old_zoom_level &&
2403                    event.zoom_mode == zoom::ZoomController::ZOOM_MODE_MANUAL &&
2404                    !event.can_show_bubble;
2405           }));
2406 
2407   std::move(send_events).Run();
2408 
2409   bool got_update;
2410   ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
2411       guest_contents,
2412       "updatePromise.then(function(update) { "
2413       "  window.domAutomationController.send(!!update); "
2414       "});",
2415       &got_update));
2416   EXPECT_TRUE(got_update);
2417 
2418   zoom_watcher.Wait();
2419 
2420   // Check that the browser's native pinch zoom was prevented.
2421   double scale_factor;
2422   ASSERT_TRUE(content::ExecuteScriptAndExtractDouble(
2423       contents,
2424       "window.domAutomationController.send(window.visualViewport.scale);",
2425       &scale_factor));
2426   EXPECT_DOUBLE_EQ(1.0, scale_factor);
2427 }
2428 
2429 // Ensure that touchpad pinch events are handled by the PDF viewer.
IN_PROC_BROWSER_TEST_F(PDFExtensionTest,TouchpadPinchInvokesCustomZoom)2430 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TouchpadPinchInvokesCustomZoom) {
2431   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2432   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2433   ASSERT_TRUE(guest_contents);
2434 
2435   base::OnceClosure send_pinch = base::BindOnce(
2436       [](WebContents* guest_contents) {
2437         const gfx::Rect guest_rect = guest_contents->GetContainerBounds();
2438         const gfx::Point mouse_position(guest_rect.width() / 2,
2439                                         guest_rect.height() / 2);
2440         content::SimulateGesturePinchSequence(
2441             guest_contents, mouse_position, 1.23,
2442             blink::WebGestureDevice::kTouchpad);
2443       },
2444       guest_contents);
2445 
2446   EnsureCustomPinchZoomInvoked(guest_contents, GetActiveWebContents(),
2447                                std::move(send_pinch));
2448 }
2449 
2450 #if !defined(OS_MAC)
2451 // Ensure that ctrl-wheel events are handled by the PDF viewer.
IN_PROC_BROWSER_TEST_F(PDFExtensionTest,CtrlWheelInvokesCustomZoom)2452 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, CtrlWheelInvokesCustomZoom) {
2453   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2454   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2455   ASSERT_TRUE(guest_contents);
2456 
2457   base::OnceClosure send_ctrl_wheel = base::BindOnce(
2458       [](WebContents* guest_contents) {
2459         const gfx::Rect guest_rect = guest_contents->GetContainerBounds();
2460         const gfx::Point mouse_position(guest_rect.width() / 2,
2461                                         guest_rect.height() / 2);
2462         content::SimulateMouseWheelCtrlZoomEvent(
2463             guest_contents, mouse_position, true,
2464             blink::WebMouseWheelEvent::kPhaseBegan);
2465       },
2466       guest_contents);
2467 
2468   EnsureCustomPinchZoomInvoked(guest_contents, GetActiveWebContents(),
2469                                std::move(send_ctrl_wheel));
2470 }
2471 
2472 // Flaky on ChromeOS (https://crbug.com/922974)
2473 #if defined(OS_CHROMEOS)
2474 #define MAYBE_TouchscreenPinchInvokesCustomZoom \
2475   DISABLED_TouchscreenPinchInvokesCustomZoom
2476 #else
2477 #define MAYBE_TouchscreenPinchInvokesCustomZoom \
2478   TouchscreenPinchInvokesCustomZoom
2479 #endif
IN_PROC_BROWSER_TEST_F(PDFExtensionTest,MAYBE_TouchscreenPinchInvokesCustomZoom)2480 IN_PROC_BROWSER_TEST_F(PDFExtensionTest,
2481                        MAYBE_TouchscreenPinchInvokesCustomZoom) {
2482   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2483   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2484   ASSERT_TRUE(guest_contents);
2485 
2486   base::OnceClosure send_touchscreen_pinch = base::BindOnce(
2487       [](WebContents* guest_contents) {
2488         const gfx::Rect guest_rect = guest_contents->GetContainerBounds();
2489         const gfx::PointF anchor_position(guest_rect.width() / 2,
2490                                           guest_rect.height() / 2);
2491         base::RunLoop run_loop;
2492         content::SimulateTouchscreenPinch(guest_contents, anchor_position, 1.2f,
2493                                           run_loop.QuitClosure());
2494         run_loop.Run();
2495       },
2496       guest_contents);
2497 
2498   EnsureCustomPinchZoomInvoked(guest_contents, GetActiveWebContents(),
2499                                std::move(send_touchscreen_pinch));
2500 }
2501 
2502 #endif  // !defined(OS_MAC)
2503 
2504 // Flaky in nearly all configurations; see https://crbug.com/856169.
IN_PROC_BROWSER_TEST_P(PDFExtensionHitTestTest,DISABLED_MouseLeave)2505 IN_PROC_BROWSER_TEST_P(PDFExtensionHitTestTest, DISABLED_MouseLeave) {
2506   GURL url = embedded_test_server()->GetURL("/pdf/pdf_embed.html");
2507 
2508   // Load page with embedded PDF and make sure it succeeds.
2509   ASSERT_TRUE(LoadPdf(url));
2510   WebContents* guest_contents = nullptr;
2511   WebContents* embedder_contents = GetActiveWebContents();
2512   content::BrowserPluginGuestManager* guest_manager =
2513       embedder_contents->GetBrowserContext()->GetGuestManager();
2514   ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest(
2515       embedder_contents, base::Bind(&GetGuestCallback, &guest_contents)));
2516   ASSERT_NE(nullptr, guest_contents);
2517   content::WaitForHitTestData(guest_contents);
2518 
2519   gfx::Point point_in_parent(250, 25);
2520   gfx::Point point_in_pdf(250, 250);
2521 
2522   // Inject script to count MouseLeaves in the PDF.
2523   ASSERT_TRUE(content::ExecuteScript(
2524       guest_contents,
2525       "var enter_count = 0;\n"
2526       "var leave_count = 0;\n"
2527       "document.addEventListener('mouseenter', function (){\n"
2528       "  enter_count++;"
2529       "});\n"
2530       "document.addEventListener('mouseleave', function (){\n"
2531       "  leave_count++;"
2532       "});"));
2533 
2534   // Inject some MouseMoves to invoke a MouseLeave in the PDF.
2535   content::SimulateMouseEvent(embedder_contents,
2536                               blink::WebInputEvent::Type::kMouseMove,
2537                               point_in_parent);
2538   content::SimulateMouseEvent(
2539       embedder_contents, blink::WebInputEvent::Type::kMouseMove, point_in_pdf);
2540   content::SimulateMouseEvent(embedder_contents,
2541                               blink::WebInputEvent::Type::kMouseMove,
2542                               point_in_parent);
2543 
2544   // Verify MouseEnter, MouseLeave received.
2545   int leave_count = 0;
2546   do {
2547     ASSERT_TRUE(ExecuteScriptAndExtractInt(
2548         guest_contents, "window.domAutomationController.send(leave_count);",
2549         &leave_count));
2550   } while (!leave_count);
2551   int enter_count = 0;
2552   ASSERT_TRUE(ExecuteScriptAndExtractInt(
2553       guest_contents, "window.domAutomationController.send(enter_count);",
2554       &enter_count));
2555   EXPECT_EQ(1, enter_count);
2556 }
2557 
IN_PROC_BROWSER_TEST_P(PDFExtensionHitTestTest,ContextMenuCoordinates)2558 IN_PROC_BROWSER_TEST_P(PDFExtensionHitTestTest, ContextMenuCoordinates) {
2559   GURL url = embedded_test_server()->GetURL("/pdf/pdf_embed.html");
2560 
2561   // Load page with embedded PDF and make sure it succeeds.
2562   ASSERT_TRUE(LoadPdf(url));
2563   WebContents* guest_contents = nullptr;
2564   WebContents* embedder_contents = GetActiveWebContents();
2565   content::BrowserPluginGuestManager* guest_manager =
2566       embedder_contents->GetBrowserContext()->GetGuestManager();
2567   ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest(
2568       embedder_contents, base::Bind(&GetGuestCallback, &guest_contents)));
2569   ASSERT_NE(nullptr, guest_contents);
2570   content::WaitForHitTestData(guest_contents);
2571 
2572   content::RenderProcessHost* guest_process_host =
2573       guest_contents->GetMainFrame()->GetProcess();
2574 
2575   // Get coords for mouse event.
2576   content::RenderWidgetHostView* guest_view =
2577       guest_contents->GetRenderWidgetHostView();
2578   gfx::Point local_context_menu_position(30, 80);
2579   gfx::Point root_context_menu_position =
2580       guest_view->TransformPointToRootCoordSpace(local_context_menu_position);
2581 
2582   auto context_menu_filter = base::MakeRefCounted<content::ContextMenuFilter>();
2583   guest_process_host->AddFilter(context_menu_filter.get());
2584 
2585   ContextMenuWaiter menu_observer;
2586   // Send mouse right-click to activate context menu.
2587   content::SimulateMouseClickAt(embedder_contents, kDefaultKeyModifier,
2588                                 blink::WebMouseEvent::Button::kRight,
2589                                 root_context_menu_position);
2590 
2591   // We expect the context menu, invoked via the RenderFrameHost, to be using
2592   // root view coordinates.
2593   menu_observer.WaitForMenuOpenAndClose();
2594   ASSERT_EQ(root_context_menu_position.x(), menu_observer.params().x);
2595   ASSERT_EQ(root_context_menu_position.y(), menu_observer.params().y);
2596 
2597   // We expect the IPC, received from the renderer, to be using local coords.
2598   context_menu_filter->Wait();
2599   content::UntrustworthyContextMenuParams params =
2600       context_menu_filter->get_params();
2601   EXPECT_EQ(local_context_menu_position.x(), params.x);
2602   EXPECT_EQ(local_context_menu_position.y(), params.y);
2603 
2604   // TODO(wjmaclean): If it ever becomes possible to filter outgoing IPCs from
2605   // the RenderProcessHost, we should verify the blink.mojom.PluginActionAt
2606   // message is sent with the same coordinates as in the
2607   // UntrustworthyContextMenuParams.
2608 }
2609 
2610 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
2611                          PDFExtensionHitTestTest,
2612                          testing::Bool());
2613 
2614 #if defined(TOOLKIT_VIEWS) && defined(USE_AURA)
2615 // On text selection, a touch selection menu should pop up. On clicking ellipsis
2616 // icon on the menu, the context menu should open up.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,ContextMenuOpensFromTouchSelectionMenu)2617 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
2618                        ContextMenuOpensFromTouchSelectionMenu) {
2619   const GURL url = embedded_test_server()->GetURL("/pdf/text_large.pdf");
2620   WebContents* const guest_contents = LoadPdfGetGuestContents(url);
2621   ASSERT_TRUE(guest_contents);
2622 
2623   views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{},
2624                                        "TouchSelectionMenuViews");
2625   gfx::Point text_selection_position(12, 12);
2626   ConvertPageCoordToScreenCoord(guest_contents, &text_selection_position);
2627   content::SimulateTouchEventAt(GetActiveWebContents(), ui::ET_TOUCH_PRESSED,
2628                                 text_selection_position);
2629   bool success = false;
2630   ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
2631       GetActiveWebContents(),
2632       "window.addEventListener('message', function(event) {"
2633       "  if (event.data.type == 'touchSelectionOccurred')"
2634       "    window.domAutomationController.send(true);"
2635       "});",
2636       &success));
2637   ASSERT_TRUE(success);
2638   content::SimulateTouchEventAt(GetActiveWebContents(), ui::ET_TOUCH_RELEASED,
2639                                 text_selection_position);
2640   views::Widget* widget = waiter.WaitIfNeededAndGet();
2641   ASSERT_TRUE(widget);
2642   views::View* menu = widget->GetContentsView();
2643   ASSERT_TRUE(menu);
2644   views::View* ellipsis_button = menu->GetViewByID(
2645       views::TouchSelectionMenuViews::ButtonViewId::kEllipsisButton);
2646   ASSERT_TRUE(ellipsis_button);
2647   ContextMenuWaiter context_menu_observer;
2648   ui::GestureEvent tap(0, 0, 0, ui::EventTimeForNow(),
2649                        ui::GestureEventDetails(ui::ET_GESTURE_TAP));
2650   ellipsis_button->OnGestureEvent(&tap);
2651   context_menu_observer.WaitForMenuOpenAndClose();
2652 
2653   // Verify that the expected context menu items are present.
2654   //
2655   // Note that the assertion below doesn't use exact matching via
2656   // testing::ElementsAre, because some platforms may include unexpected extra
2657   // elements (e.g. an extra separator and IDC=100 has been observed on some Mac
2658   // bots).
2659   EXPECT_THAT(
2660       context_menu_observer.GetCapturedCommandIds(),
2661       testing::IsSupersetOf(
2662           {IDC_CONTENT_CONTEXT_COPY, IDC_CONTENT_CONTEXT_SEARCHWEBFOR,
2663            IDC_PRINT, IDC_CONTENT_CONTEXT_ROTATECW,
2664            IDC_CONTENT_CONTEXT_ROTATECCW, IDC_CONTENT_CONTEXT_INSPECTELEMENT}));
2665 }
2666 #endif  // defined(TOOLKIT_VIEWS) && defined(USE_AURA)
2667 
2668 // The plugin document and the mime handler should both use the same background
2669 // color.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,BackgroundColor)2670 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, BackgroundColor) {
2671   // The background color for plugins is injected when the first response
2672   // is intercepted, at which point not all the plugins have loaded. This line
2673   // ensures that the PDF plugin has loaded and the right background color is
2674   // beign used.
2675   WaitForPluginServiceToLoad();
2676   WebContents* guest_contents =
2677       LoadPdfGetGuestContents(embedded_test_server()->GetURL("/pdf/test.pdf"));
2678   ASSERT_TRUE(guest_contents);
2679   const std::string script =
2680       "window.domAutomationController.send("
2681       "    window.getComputedStyle(document.body, null)."
2682       "    getPropertyValue('background-color'))";
2683   std::string outer;
2684   ASSERT_TRUE(content::ExecuteScriptAndExtractString(GetActiveWebContents(),
2685                                                      script, &outer));
2686   std::string inner;
2687   ASSERT_TRUE(
2688       content::ExecuteScriptAndExtractString(guest_contents, script, &inner));
2689   EXPECT_EQ(inner, outer);
2690 }
2691 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,DefaultFocusForEmbeddedPDF)2692 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, DefaultFocusForEmbeddedPDF) {
2693   GURL url = embedded_test_server()->GetURL("/pdf/pdf_embed.html");
2694 
2695   // Load page with embedded PDF and make sure it succeeds.
2696   ASSERT_TRUE(LoadPdf(url));
2697   WebContents* guest_contents = nullptr;
2698   WebContents* embedder_contents = GetActiveWebContents();
2699   content::BrowserPluginGuestManager* guest_manager =
2700       embedder_contents->GetBrowserContext()->GetGuestManager();
2701   ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest(
2702       embedder_contents,
2703       base::BindRepeating(&GetGuestCallback, &guest_contents)));
2704   ASSERT_TRUE(guest_contents);
2705 
2706   // Verify that current focus state is body element.
2707   const std::string script =
2708       "const is_plugin_focused = document.activeElement === "
2709       "document.body;"
2710       "window.domAutomationController.send(is_plugin_focused);";
2711 
2712   bool result = false;
2713   ASSERT_TRUE(
2714       content::ExecuteScriptAndExtractBool(guest_contents, script, &result));
2715   ASSERT_TRUE(result);
2716 }
2717 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,DefaultFocusForNonEmbeddedPDF)2718 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
2719                        DefaultFocusForNonEmbeddedPDF) {
2720   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2721   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2722   ASSERT_TRUE(guest_contents);
2723 
2724   // Verify that current focus state is document element.
2725   const std::string script =
2726       "const is_plugin_focused = document.activeElement === "
2727       "document.body;"
2728       "window.domAutomationController.send(is_plugin_focused);";
2729 
2730   bool result = false;
2731   ASSERT_TRUE(
2732       content::ExecuteScriptAndExtractBool(guest_contents, script, &result));
2733   ASSERT_TRUE(result);
2734 }
2735 
2736 // A helper for waiting for the first request for |url_to_intercept|.
2737 class RequestWaiter {
2738  public:
2739   // Start intercepting requests to |url_to_intercept|.
RequestWaiter(const GURL & url_to_intercept)2740   explicit RequestWaiter(const GURL& url_to_intercept)
2741       : url_to_intercept_(url_to_intercept),
2742         interceptor_(base::BindRepeating(&RequestWaiter::InterceptorCallback,
2743                                          base::Unretained(this))) {
2744     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
2745     DCHECK(url_to_intercept.is_valid());
2746   }
2747 
WaitForRequest()2748   void WaitForRequest() {
2749     DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
2750     if (!IsAlreadyIntercepted())
2751       run_loop_.Run();
2752     DCHECK(IsAlreadyIntercepted());
2753   }
2754 
2755  private:
InterceptorCallback(content::URLLoaderInterceptor::RequestParams * params)2756   bool InterceptorCallback(
2757       content::URLLoaderInterceptor::RequestParams* params) {
2758     // This method may be called either on the IO or UI thread.
2759     DCHECK(params);
2760 
2761     base::AutoLock lock(lock_);
2762     if (url_to_intercept_ != params->url_request.url || already_intercepted_)
2763       return false;
2764 
2765     already_intercepted_ = true;
2766     run_loop_.Quit();
2767     return false;
2768   }
2769 
IsAlreadyIntercepted()2770   bool IsAlreadyIntercepted() {
2771     base::AutoLock lock(lock_);
2772     return already_intercepted_;
2773   }
2774 
2775   const GURL url_to_intercept_;
2776   content::URLLoaderInterceptor interceptor_;
2777   base::RunLoop run_loop_;
2778 
2779   base::Lock lock_;
2780   bool already_intercepted_ GUARDED_BY(lock_) = false;
2781 
2782   DISALLOW_COPY_AND_ASSIGN(RequestWaiter);
2783 };
2784 
2785 // This is a regression test for a problem where DidStopLoading didn't get
2786 // propagated from a remote frame into the main frame.  See also
2787 // https://crbug.com/964364.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,DidStopLoading)2788 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, DidStopLoading) {
2789   // Prepare to wait for requests for the main page of the MimeHandlerView for
2790   // PDFs.
2791   RequestWaiter interceptor(
2792       GURL("chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html"));
2793 
2794   // Navigate to a page with:
2795   //   <embed type="application/pdf" src="test.pdf"></embed>
2796   //   <iframe src="/hung"></iframe>
2797   // Afterwards, the main page should be still loading because of the hung
2798   // subframe (but the subframe for the OOPIF-based PDF MimeHandlerView might or
2799   // might not be created at this point).
2800   GURL url = embedded_test_server()->GetURL(
2801       "/pdf/pdf_embed_with_hung_sibling_subframe.html");
2802   ui_test_utils::NavigateToURLWithDisposition(
2803       browser(), url, WindowOpenDisposition::CURRENT_TAB,
2804       ui_test_utils::BROWSER_TEST_NONE);  // Don't wait for completion.
2805 
2806   // Wait for the request for the MimeHandlerView extension.  Afterwards, the
2807   // main page should be still loading because of
2808   // 1) the MimeHandlerView frame is loading
2809   // 2) the hung subframe is loading.
2810   interceptor.WaitForRequest();
2811 
2812   // Remove the hung subframe.  Afterwards the main page should stop loading as
2813   // soon as the MimeHandlerView frame stops loading (assumming we have not bugs
2814   // similar to https://crbug.com/964364).
2815   content::WebContents* web_contents =
2816       browser()->tab_strip_model()->GetActiveWebContents();
2817   ASSERT_TRUE(content::ExecJs(
2818       web_contents, "document.getElementById('hung_subframe').remove();"));
2819 
2820   // MAIN VERIFICATION: Wait for the main frame to report that is has stopped
2821   // loading.
2822   EXPECT_TRUE(content::WaitForLoadStop(web_contents));
2823 }
2824 
2825 // This test verifies that it is possible to add an <embed src=pdf> element into
2826 // a new popup window when using document.write.  See also
2827 // https://crbug.com/1041880.
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,DocumentWriteIntoNewPopup)2828 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, DocumentWriteIntoNewPopup) {
2829   // Navigate to an empty/boring test page.
2830   GURL main_url(embedded_test_server()->GetURL("/title1.html"));
2831   ui_test_utils::NavigateToURL(browser(), main_url);
2832 
2833   // Open a new popup and call document.write to add an embedded PDF.
2834   content::WebContents* popup = nullptr;
2835   {
2836     GURL pdf_url = embedded_test_server()->GetURL("/pdf/test.pdf");
2837     const char kScriptTemplate[] = R"(
2838         const url = $1;
2839         const html = '<embed type="application/pdf" src="' + url + '">';
2840 
2841         const popup = window.open('', '_blank');
2842         popup.document.write(html);
2843     )";
2844     content::WebContentsAddedObserver popup_observer;
2845     content::WebContents* web_contents =
2846         browser()->tab_strip_model()->GetActiveWebContents();
2847     ASSERT_TRUE(content::ExecJs(web_contents,
2848                                 content::JsReplace(kScriptTemplate, pdf_url)));
2849     popup = popup_observer.GetWebContents();
2850   }
2851 
2852   // Verify the PDF loaded successfully.
2853   ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(popup));
2854 }
2855 
2856 // Tests that the PDF extension loads in the presence of an extension that, on
2857 // the completion of document loading, adds an <iframe> to the body element.
2858 // https://bugs.chromium.org/p/chromium/issues/detail?id=1046795
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,PdfLoadsWithExtensionThatInjectsFrame)2859 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,
2860                        PdfLoadsWithExtensionThatInjectsFrame) {
2861   // Load the test extension.
2862   const extensions::Extension* test_extension = LoadExtension(
2863       GetTestResourcesParentDir().AppendASCII("pdf/extension_injects_iframe"));
2864   ASSERT_TRUE(test_extension);
2865 
2866   // Load the PDF. The call to LoadPdf() will return false if the pdf extension
2867   // fails to load.
2868   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2869   ASSERT_TRUE(LoadPdf(test_pdf_url));
2870 }
2871 
IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam,Metrics)2872 IN_PROC_BROWSER_TEST_P(PDFExtensionTestWithParam, Metrics) {
2873   base::HistogramTester histograms;
2874   base::UserActionTester actions;
2875 
2876   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/combobox_form.pdf"));
2877   WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2878   ASSERT_TRUE(guest_contents);
2879 
2880   metrics::SubprocessMetricsProvider::MergeHistogramDeltasForTesting();
2881 
2882   // Histograms.
2883   // Duplicating some constants to avoid reaching into pdf/ internals.
2884   constexpr int kAcroForm = 1;
2885   constexpr int k1_7 = 8;
2886   histograms.ExpectUniqueSample("PDF.FormType", kAcroForm, 1);
2887   histograms.ExpectUniqueSample("PDF.Version", k1_7, 1);
2888   histograms.ExpectUniqueSample("PDF.IsTagged", 0, 1);
2889   histograms.ExpectUniqueSample("PDF.HasAttachment", 0, 1);
2890 
2891   // Custom histograms.
2892   histograms.ExpectUniqueSample("PDF.PageCount", 1, 1);
2893 
2894   // User actions.
2895   EXPECT_EQ(1, actions.GetActionCount("PDF.LoadSuccess"));
2896 }
2897 
2898 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
2899                          PDFExtensionTestWithParam,
2900                          testing::Bool());
2901 
2902 // Flaky. See https://crbug.com/1101514.
IN_PROC_BROWSER_TEST_F(PDFExtensionTest,DISABLED_TabInAndOutOfPDFPlugin)2903 IN_PROC_BROWSER_TEST_F(PDFExtensionTest, DISABLED_TabInAndOutOfPDFPlugin) {
2904   GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf"));
2905   content::WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2906   ASSERT_TRUE(guest_contents);
2907 
2908   // Set focus on last toolbar element (zoom-out-button).
2909   ASSERT_TRUE(
2910       content::ExecuteScript(guest_contents,
2911                              R"(viewer.shadowRoot.querySelector('#zoom-toolbar')
2912          .$['zoom-out-button']
2913          .$$('cr-icon-button')
2914          .focus();)"));
2915 
2916   // The script will ensure we return the the focused element on focus.
2917   const char kScript[] = R"(
2918     const plugin = viewer.shadowRoot.querySelector('#plugin');
2919     plugin.addEventListener('focus', () => {
2920       window.domAutomationController.send('plugin');
2921     });
2922 
2923     const button = viewer.shadowRoot.querySelector('#zoom-toolbar')
2924                    .$['zoom-out-button']
2925                    .$$('cr-icon-button');
2926     button.addEventListener('focus', () => {
2927       window.domAutomationController.send('zoom-out-button');
2928     });
2929   )";
2930   ASSERT_TRUE(content::ExecuteScript(guest_contents, kScript));
2931 
2932   // Helper to simulate a tab press and wait for a focus message.
2933   auto press_tab_and_wait_for_message = [guest_contents](bool reverse) {
2934     content::DOMMessageQueue msg_queue;
2935     std::string reply;
2936     SimulateKeyPress(guest_contents, ui::DomKey::TAB, ui::DomCode::TAB,
2937                      ui::VKEY_TAB, false, /*shift=*/reverse, false, false);
2938     EXPECT_TRUE(msg_queue.WaitForMessage(&reply));
2939     return reply;
2940   };
2941 
2942   // Press <tab> and ensure that PDF document receives focus.
2943   EXPECT_EQ("\"plugin\"", press_tab_and_wait_for_message(false));
2944   // Press <shift-tab> and ensure that last toolbar element (zoom-out-button)
2945   // receives focus.
2946   EXPECT_EQ("\"zoom-out-button\"", press_tab_and_wait_for_message(true));
2947 }
2948 
2949 // This test suite does a simple text-extraction based on the accessibility
2950 // internals, breaking lines & paragraphs where appropriate.  Unlike
2951 // TreeDumpTests, this allows us to verify the kNextOnLine and kPreviousOnLine
2952 // relationships.
2953 class PDFExtensionAccessibilityTextExtractionTest
2954     : public PDFExtensionTestWithParam {
2955  public:
2956   PDFExtensionAccessibilityTextExtractionTest() = default;
2957   ~PDFExtensionAccessibilityTextExtractionTest() override = default;
2958 
RunTextExtractionTest(const base::FilePath::CharType * pdf_file)2959   void RunTextExtractionTest(const base::FilePath::CharType* pdf_file) {
2960     base::FilePath test_path = ui_test_utils::GetTestFilePath(
2961         base::FilePath(FILE_PATH_LITERAL("pdf")),
2962         base::FilePath(FILE_PATH_LITERAL("accessibility")));
2963     {
2964       base::ScopedAllowBlockingForTesting allow_blocking;
2965       ASSERT_TRUE(base::PathExists(test_path)) << test_path.LossyDisplayName();
2966     }
2967     base::FilePath pdf_path = test_path.Append(pdf_file);
2968 
2969     RunTest(pdf_path, "pdf/accessibility");
2970   }
2971 
2972  protected:
GetEnabledFeatures() const2973   const std::vector<base::Feature> GetEnabledFeatures() const override {
2974     std::vector<base::Feature> enabled =
2975         PDFExtensionTestWithParam::GetEnabledFeatures();
2976     enabled.push_back(chrome_pdf::features::kAccessiblePDFForm);
2977     return enabled;
2978   }
2979 
2980  private:
RunTest(const base::FilePath & test_file_path,const char * file_dir)2981   void RunTest(const base::FilePath& test_file_path, const char* file_dir) {
2982     // Load the expectation file.
2983     content::DumpAccessibilityTestHelper test_helper("content");
2984     base::Optional<base::FilePath> expected_file_path =
2985         test_helper.GetExpectationFilePath(test_file_path);
2986     ASSERT_TRUE(expected_file_path) << "No expectation file present.";
2987 
2988     base::Optional<std::vector<std::string>> expected_lines =
2989         test_helper.LoadExpectationFile(*expected_file_path);
2990     ASSERT_TRUE(expected_lines) << "Couldn't load expectation file.";
2991 
2992     // Enable accessibility and load the test file.
2993     content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
2994     GURL test_pdf_url(embedded_test_server()->GetURL(
2995         "/" + std::string(file_dir) + "/" +
2996         test_file_path.BaseName().MaybeAsASCII()));
2997     WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
2998     ASSERT_TRUE(guest_contents);
2999     WaitForAccessibilityTreeToContainNodeWithName(guest_contents, "Page 1");
3000 
3001     // Extract the text content.
3002     ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents);
3003     auto actual_lines = CollectLines(ax_tree);
3004 
3005     // Validate the dump against the expectation file.
3006     EXPECT_TRUE(test_helper.ValidateAgainstExpectation(
3007         test_file_path, *expected_file_path, actual_lines, *expected_lines));
3008   }
3009 
3010  private:
CollectLines(ui::AXTreeUpdate ax_tree)3011   std::vector<std::string> CollectLines(ui::AXTreeUpdate ax_tree) {
3012     std::vector<std::string> lines;
3013 
3014     int previous_node_id = 0;
3015     int previous_node_next_id = 0;
3016     std::string line;
3017     bool found_embedded_object = false;
3018     for (const auto& node : ax_tree.nodes) {
3019       // Ignore everything before the embedded object (the root of the PDF).
3020       if (node.role == ax::mojom::Role::kEmbeddedObject)
3021         found_embedded_object = true;
3022       if (!found_embedded_object)
3023         continue;
3024 
3025       // StaticText begins a new paragraph.
3026       if (node.role == ax::mojom::Role::kStaticText && !line.empty()) {
3027         lines.push_back(line);
3028         lines.push_back("\u00b6");  // pilcrow/paragraph mark, Alt+0182
3029         line.clear();
3030       }
3031 
3032       // We collect all inline text boxes within the paragraph.
3033       if (node.role != ax::mojom::Role::kInlineTextBox)
3034         continue;
3035 
3036       std::string name =
3037           node.GetStringAttribute(ax::mojom::StringAttribute::kName);
3038       base::StringPiece trimmed_name =
3039           base::TrimString(name, "\r\n", base::TRIM_TRAILING);
3040       int prev_id =
3041           node.GetIntAttribute(ax::mojom::IntAttribute::kPreviousOnLineId);
3042       if (previous_node_next_id == node.id) {
3043         // Previous node pointed to us, so we are part of the same line.
3044         EXPECT_EQ(previous_node_id, prev_id)
3045             << "Expect this node to point to previous node.";
3046         line.append(trimmed_name.data(), trimmed_name.size());
3047       } else {
3048         // Not linked with the previous node; this is a new line.
3049         EXPECT_EQ(previous_node_next_id, 0)
3050             << "Previous node pointed to something unexpected.";
3051         EXPECT_EQ(prev_id, 0)
3052             << "Our back pointer points to something unexpected.";
3053         if (!line.empty())
3054           lines.push_back(line);
3055         line = trimmed_name.as_string();
3056       }
3057 
3058       previous_node_id = node.id;
3059       previous_node_next_id =
3060           node.GetIntAttribute(ax::mojom::IntAttribute::kNextOnLineId);
3061     }
3062     if (!line.empty())
3063       lines.push_back(line);
3064     return lines;
3065   }
3066 };
3067 
3068 // Test that Previous/NextOnLineId attributes are present and properly linked on
3069 // InlineTextBoxes within a line.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,NextOnLine)3070 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3071                        NextOnLine) {
3072   RunTextExtractionTest(FILE_PATH_LITERAL("next-on-line.pdf"));
3073 }
3074 
3075 // Test that a drop-cap is grouped with the correct line.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,DropCap)3076 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest, DropCap) {
3077   RunTextExtractionTest(FILE_PATH_LITERAL("drop-cap.pdf"));
3078 }
3079 
3080 // Test that simulated superscripts and subscripts don't cause a line break.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,SuperscriptSubscript)3081 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3082                        SuperscriptSubscript) {
3083   RunTextExtractionTest(FILE_PATH_LITERAL("superscript-subscript.pdf"));
3084 }
3085 
3086 // Test that simple font and font-size changes in the middle of a line don't
3087 // cause line breaks.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,FontChange)3088 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3089                        FontChange) {
3090   RunTextExtractionTest(FILE_PATH_LITERAL("font-change.pdf"));
3091 }
3092 
3093 // Test one property of pdf_private/accessibility_crash_2.pdf, where a page has
3094 // only whitespace characters.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,OnlyWhitespaceText)3095 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3096                        OnlyWhitespaceText) {
3097   RunTextExtractionTest(FILE_PATH_LITERAL("whitespace.pdf"));
3098 }
3099 
3100 // Test data of inline text boxes for PDF with weblinks.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,WebLinks)3101 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest, WebLinks) {
3102   RunTextExtractionTest(FILE_PATH_LITERAL("weblinks.pdf"));
3103 }
3104 
3105 // Test data of inline text boxes for PDF with highlights.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,Highlights)3106 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3107                        Highlights) {
3108   RunTextExtractionTest(FILE_PATH_LITERAL("highlights.pdf"));
3109 }
3110 
3111 // Test data of inline text boxes for PDF with text fields.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,TextFields)3112 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3113                        TextFields) {
3114   RunTextExtractionTest(FILE_PATH_LITERAL("text_fields.pdf"));
3115 }
3116 
3117 // Test data of inline text boxes for PDF with multi-line and various font-sized
3118 // text.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,ParagraphsAndHeadingUntagged)3119 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3120                        ParagraphsAndHeadingUntagged) {
3121   RunTextExtractionTest(
3122       FILE_PATH_LITERAL("paragraphs-and-heading-untagged.pdf"));
3123 }
3124 
3125 // Test data of inline text boxes for PDF with text, weblinks, images and
3126 // annotation links.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,LinksImagesAndText)3127 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3128                        LinksImagesAndText) {
3129   RunTextExtractionTest(FILE_PATH_LITERAL("text-image-link.pdf"));
3130 }
3131 
3132 // Test data of inline text boxes for PDF with overlapping annotations.
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,OverlappingAnnots)3133 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTextExtractionTest,
3134                        OverlappingAnnots) {
3135   RunTextExtractionTest(FILE_PATH_LITERAL("overlapping-annots.pdf"));
3136 }
3137 
3138 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
3139                          PDFExtensionAccessibilityTextExtractionTest,
3140                          testing::Bool());
3141 
3142 using AXTestPass = content::AccessibilityTreeFormatter::TestPass;
3143 
3144 class PDFExtensionAccessibilityTreeDumpTest
3145     : public PDFExtensionTest,
3146       public ::testing::WithParamInterface<std::pair<bool, AXTestPass>> {
3147  public:
PDFExtensionAccessibilityTreeDumpTest()3148   PDFExtensionAccessibilityTreeDumpTest() : test_pass_(GetParam().second) {}
3149   ~PDFExtensionAccessibilityTreeDumpTest() override = default;
3150 
SetUpCommandLine(base::CommandLine * command_line)3151   void SetUpCommandLine(base::CommandLine* command_line) override {
3152     PDFExtensionTest::SetUpCommandLine(command_line);
3153 
3154     // Each test pass might require custom command-line setup
3155     if (test_pass_.set_up_command_line)
3156       test_pass_.set_up_command_line(command_line);
3157   }
3158 
3159  protected:
GetEnabledFeatures() const3160   const std::vector<base::Feature> GetEnabledFeatures() const override {
3161     std::vector<base::Feature> enabled = {
3162         chrome_pdf::features::kAccessiblePDFForm};
3163     if (GetParam().first) {
3164       enabled.push_back(chrome_pdf::features::kPDFViewerUpdate);
3165     }
3166     return enabled;
3167   }
3168 
RunPDFTest(const base::FilePath::CharType * pdf_file)3169   void RunPDFTest(const base::FilePath::CharType* pdf_file) {
3170     base::FilePath test_path = ui_test_utils::GetTestFilePath(
3171         base::FilePath(FILE_PATH_LITERAL("pdf")),
3172         base::FilePath(FILE_PATH_LITERAL("accessibility")));
3173     {
3174       base::ScopedAllowBlockingForTesting allow_blocking;
3175       ASSERT_TRUE(base::PathExists(test_path)) << test_path.LossyDisplayName();
3176     }
3177     base::FilePath pdf_path = test_path.Append(pdf_file);
3178 
3179     RunTest(pdf_path, "pdf/accessibility");
3180   }
3181 
3182  private:
3183   using AXPropertyFilter = ui::AXPropertyFilter;
3184 
3185   //  See chrome/test/data/pdf/accessibility/readme.md for more info.
ParsePdfForExtraDirectives(const content::DumpAccessibilityTestHelper & test_helper,const std::string & pdf_contents,std::vector<AXPropertyFilter> * property_filters)3186   void ParsePdfForExtraDirectives(
3187       const content::DumpAccessibilityTestHelper& test_helper,
3188       const std::string& pdf_contents,
3189       std::vector<AXPropertyFilter>* property_filters) {
3190     const char kCommentMark = '%';
3191     for (const std::string& line : base::SplitString(
3192              pdf_contents, "\n", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL)) {
3193       if (line.size() > 1 && line[0] == kCommentMark) {
3194         // Remove first character since it's the comment mark.
3195         std::string trimmed_line = line.substr(1);
3196         test_helper.ParsePropertyFilter(trimmed_line, property_filters);
3197       }
3198     }
3199   }
3200 
RunTest(const base::FilePath & test_file_path,const char * file_dir)3201   void RunTest(const base::FilePath& test_file_path, const char* file_dir) {
3202     std::string pdf_contents;
3203     {
3204       base::ScopedAllowBlockingForTesting allow_blocking;
3205       ASSERT_TRUE(base::ReadFileToString(test_file_path, &pdf_contents));
3206     }
3207 
3208     // Set up the tree formatter. Parse filters and other directives in the test
3209     // file.
3210     content::DumpAccessibilityTestHelper test_helper(test_pass_.name);
3211 
3212     std::unique_ptr<AXTreeFormatter> formatter = test_pass_.create_formatter();
3213     std::vector<AXPropertyFilter> property_filters;
3214     formatter->AddDefaultFilters(&property_filters);
3215     AddDefaultFilters(&property_filters);
3216     ParsePdfForExtraDirectives(test_helper, pdf_contents, &property_filters);
3217     formatter->SetPropertyFilters(property_filters);
3218 
3219     // Exit without running the test if we can't find an expectation file or if
3220     // the expectation file contains a skip marker.
3221     // This is used to skip certain tests on certain platforms.
3222     base::FilePath expected_file_path =
3223         test_helper.GetExpectationFilePath(test_file_path);
3224     if (expected_file_path.empty()) {
3225       LOG(INFO) << "No expectation file present, ignoring test on this "
3226                    "platform.";
3227       return;
3228     }
3229 
3230     base::Optional<std::vector<std::string>> expected_lines =
3231         test_helper.LoadExpectationFile(expected_file_path);
3232     if (!expected_lines) {
3233       LOG(INFO) << "Skipping this test on this platform.";
3234       return;
3235     }
3236 
3237     // Enable accessibility and load the test file.
3238     content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
3239     GURL test_pdf_url(embedded_test_server()->GetURL(
3240         "/" + std::string(file_dir) + "/" +
3241         test_file_path.BaseName().MaybeAsASCII()));
3242     WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url);
3243     ASSERT_TRUE(guest_contents);
3244     WaitForAccessibilityTreeToContainNodeWithName(guest_contents, "Page 1");
3245 
3246     // Find the embedded PDF and dump the accessibility tree.
3247     content::FindAccessibilityNodeCriteria find_criteria;
3248     find_criteria.role = ax::mojom::Role::kEmbeddedObject;
3249     ui::AXPlatformNodeDelegate* pdf_root =
3250         content::FindAccessibilityNode(guest_contents, find_criteria);
3251     CHECK(pdf_root);
3252 
3253     std::string actual_contents;
3254     formatter->FormatAccessibilityTreeForTesting(pdf_root, &actual_contents);
3255 
3256     std::vector<std::string> actual_lines =
3257         base::SplitString(actual_contents, "\n", base::KEEP_WHITESPACE,
3258                           base::SPLIT_WANT_NONEMPTY);
3259 
3260     // Validate the dump against the expectation file.
3261     EXPECT_TRUE(test_helper.ValidateAgainstExpectation(
3262         test_file_path, expected_file_path, actual_lines, *expected_lines));
3263   }
3264 
AddDefaultFilters(std::vector<AXPropertyFilter> * property_filters)3265   void AddDefaultFilters(std::vector<AXPropertyFilter>* property_filters) {
3266     AddPropertyFilter(property_filters, "value='*'");
3267     // The value attribute on the document object contains the URL of the
3268     // current page which will not be the same every time the test is run.
3269     // The PDF plugin uses the 'chrome-extension' protocol, so block that as
3270     // well.
3271     AddPropertyFilter(property_filters, "value='http*'",
3272                       AXPropertyFilter::DENY);
3273     AddPropertyFilter(property_filters, "value='chrome-extension*'",
3274                       AXPropertyFilter::DENY);
3275     // Object attributes.value
3276     AddPropertyFilter(property_filters, "layout-guess:*",
3277                       AXPropertyFilter::ALLOW);
3278 
3279     AddPropertyFilter(property_filters, "select*");
3280     AddPropertyFilter(property_filters, "descript*");
3281     AddPropertyFilter(property_filters, "check*");
3282     AddPropertyFilter(property_filters, "horizontal");
3283     AddPropertyFilter(property_filters, "multiselectable");
3284     AddPropertyFilter(property_filters, "isPageBreakingObject*");
3285 
3286     // Deny most empty values
3287     AddPropertyFilter(property_filters, "*=''", AXPropertyFilter::DENY);
3288     // After denying empty values, because we want to allow name=''
3289     AddPropertyFilter(property_filters, "name=*",
3290                       AXPropertyFilter::ALLOW_EMPTY);
3291   }
3292 
AddPropertyFilter(std::vector<AXPropertyFilter> * property_filters,std::string filter,AXPropertyFilter::Type type=AXPropertyFilter::ALLOW)3293   void AddPropertyFilter(
3294       std::vector<AXPropertyFilter>* property_filters,
3295       std::string filter,
3296       AXPropertyFilter::Type type = AXPropertyFilter::ALLOW) {
3297     property_filters->push_back(AXPropertyFilter(filter, type));
3298   }
3299 
3300   content::AccessibilityTreeFormatter::TestPass test_pass_;
3301 };
3302 
3303 // Parameterize the tests so that each test-pass is run independently.
3304 struct DumpAccessibilityTreeTestPassToString {
operator ()DumpAccessibilityTreeTestPassToString3305   std::string operator()(
3306       const ::testing::TestParamInfo<std::pair<bool, AXTestPass>>& i) const {
3307     std::string result = i.param.second.name;
3308     return result + (i.param.first ? "_updateEnabled" : "_updateDisabled");
3309   }
3310 };
3311 
3312 // Constructs a list of accessibility tests, two for each accessibility tree
3313 // formatter testpasses: one when pdf update is enabled and the second one when
3314 // pdf update is disabled.
GetAXTestPairValues()3315 const std::vector<std::pair<bool, AXTestPass>> GetAXTestPairValues() {
3316   std::vector<std::pair<bool, AXTestPass>> values;
3317   std::vector<AXTestPass> passes =
3318       content::AccessibilityTreeFormatter::GetTestPasses();
3319   for (auto pass : passes) {
3320     values.emplace_back(std::make_pair(true, pass));
3321     values.emplace_back(std::make_pair(false, pass));
3322   }
3323   return values;
3324 }
3325 
3326 INSTANTIATE_TEST_SUITE_P(All,
3327                          PDFExtensionAccessibilityTreeDumpTest,
3328                          testing::ValuesIn(GetAXTestPairValues()),
3329                          DumpAccessibilityTreeTestPassToString());
3330 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,HelloWorld)3331 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, HelloWorld) {
3332   RunPDFTest(FILE_PATH_LITERAL("hello-world.pdf"));
3333 }
3334 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,ParagraphsAndHeadingUntagged)3335 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
3336                        ParagraphsAndHeadingUntagged) {
3337   RunPDFTest(FILE_PATH_LITERAL("paragraphs-and-heading-untagged.pdf"));
3338 }
3339 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,MultiPage)3340 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, MultiPage) {
3341   RunPDFTest(FILE_PATH_LITERAL("multi-page.pdf"));
3342 }
3343 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,DirectionalTextRuns)3344 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
3345                        DirectionalTextRuns) {
3346   RunPDFTest(FILE_PATH_LITERAL("directional-text-runs.pdf"));
3347 }
3348 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,TextDirection)3349 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextDirection) {
3350   RunPDFTest(FILE_PATH_LITERAL("text-direction.pdf"));
3351 }
3352 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,WebLinks)3353 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, WebLinks) {
3354   RunPDFTest(FILE_PATH_LITERAL("weblinks.pdf"));
3355 }
3356 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,OverlappingLinks)3357 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
3358                        OverlappingLinks) {
3359   RunPDFTest(FILE_PATH_LITERAL("overlapping-links.pdf"));
3360 }
3361 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,Highlights)3362 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, Highlights) {
3363   RunPDFTest(FILE_PATH_LITERAL("highlights.pdf"));
3364 }
3365 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,TextFields)3366 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextFields) {
3367   RunPDFTest(FILE_PATH_LITERAL("text_fields.pdf"));
3368 }
3369 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,Images)3370 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, Images) {
3371   RunPDFTest(FILE_PATH_LITERAL("image_alt_text.pdf"));
3372 }
3373 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,LinksImagesAndText)3374 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
3375                        LinksImagesAndText) {
3376   RunPDFTest(FILE_PATH_LITERAL("text-image-link.pdf"));
3377 }
3378 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,TextRunStyleHeuristic)3379 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,
3380                        TextRunStyleHeuristic) {
3381   RunPDFTest(FILE_PATH_LITERAL("text-run-style-heuristic.pdf"));
3382 }
3383 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest,TextStyle)3384 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityTreeDumpTest, TextStyle) {
3385   RunPDFTest(FILE_PATH_LITERAL("text-style.pdf"));
3386 }
3387 
3388 // This test suite validates the navigation done using the accessibility client.
3389 using PDFExtensionAccessibilityNavigationTest = PDFExtensionTestWithParam;
3390 
IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityNavigationTest,LinkNavigation)3391 IN_PROC_BROWSER_TEST_P(PDFExtensionAccessibilityNavigationTest,
3392                        LinkNavigation) {
3393   // Enable accessibility and load the test file.
3394   content::BrowserAccessibilityState::GetInstance()->EnableAccessibility();
3395   GURL url(embedded_test_server()->GetURL("/pdf/accessibility/weblinks.pdf"));
3396   WebContents* guest_contents = LoadPdfGetGuestContents(url);
3397   ASSERT_TRUE(guest_contents);
3398   WaitForAccessibilityTreeToContainNodeWithName(guest_contents, "Page 1");
3399 
3400   // Find the specific link node.
3401   content::FindAccessibilityNodeCriteria find_criteria;
3402   find_criteria.role = ax::mojom::Role::kLink;
3403   find_criteria.name = "http://bing.com";
3404   ui::AXPlatformNodeDelegate* link_node =
3405       content::FindAccessibilityNode(guest_contents, find_criteria);
3406   ASSERT_TRUE(link_node);
3407 
3408   // Invoke action on a link and wait for navigation to complete.
3409   content::AccessibilityNotificationWaiter event_waiter(
3410       GetActiveWebContents(), ui::kAXModeComplete,
3411       ax::mojom::Event::kLoadComplete);
3412   ui::AXActionData action_data;
3413   action_data.action = ax::mojom::Action::kDoDefault;
3414   action_data.target_node_id = link_node->GetData().id;
3415   link_node->AccessibilityPerformAction(action_data);
3416   event_waiter.WaitForNotification();
3417 
3418   // Test that navigation occurred correctly.
3419   const GURL& expected_url = GetActiveWebContents()->GetURL();
3420   EXPECT_EQ("https://bing.com/", expected_url.spec());
3421 }
3422 
3423 INSTANTIATE_TEST_SUITE_P(/* no prefix */,
3424                          PDFExtensionAccessibilityNavigationTest,
3425                          testing::Bool());
3426