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