| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <stddef.h> |
| |
| #include <map> |
| #include <vector> |
| |
| #include "base/base_paths.h" |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_util.h" |
| #include "base/hash.h" |
| #include "base/logging.h" |
| #include "base/macros.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/path_service.h" |
| #include "base/run_loop.h" |
| #include "base/strings/pattern.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/test/test_timeouts.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/extensions/component_loader.h" |
| #include "chrome/browser/extensions/extension_apitest.h" |
| #include "chrome/browser/extensions/extension_service.h" |
| #include "chrome/browser/pdf/pdf_extension_test_util.h" |
| #include "chrome/browser/pdf/pdf_extension_util.h" |
| #include "chrome/browser/plugins/plugin_prefs.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/renderer_context_menu/render_view_context_menu_browsertest_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/common/chrome_content_client.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/download/public/common/download_item.h" |
| #include "components/zoom/page_zoom.h" |
| #include "components/zoom/test/zoom_test_utils.h" |
| #include "components/zoom/zoom_controller.h" |
| #include "content/public/browser/browser_accessibility_state.h" |
| #include "content/public/browser/browser_plugin_guest_manager.h" |
| #include "content/public/browser/download_manager.h" |
| #include "content/public/browser/notification_observer.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/plugin_service.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/render_widget_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/context_menu_params.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/manifest_handlers/mime_types_handler.h" |
| #include "extensions/test/result_catcher.h" |
| #include "net/dns/mock_host_resolver.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "ui/accessibility/ax_enum_util.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/resource/resource_bundle.h" |
| #include "ui/base/test/test_clipboard.h" |
| #include "ui/gfx/geometry/point.h" |
| #include "url/gurl.h" |
| |
| #if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX) |
| #include "chrome/browser/ui/views/location_bar/zoom_bubble_view.h" |
| #endif |
| |
| using content::WebContents; |
| |
| const int kNumberLoadTestParts = 10; |
| |
| #if defined(OS_MACOSX) |
| const int kDefaultKeyModifier = blink::WebInputEvent::kMetaKey; |
| #else |
| const int kDefaultKeyModifier = blink::WebInputEvent::kControlKey; |
| #endif |
| |
| // Check if the |actual| string matches the string or the string pattern in |
| // |pattern| and print a readable message if it does not match. |
| #define ASSERT_MULTILINE_STR_MATCHES(pattern, actual) \ |
| ASSERT_TRUE(base::MatchPattern(actual, pattern)) \ |
| << "Expected match pattern:\n" \ |
| << pattern << "\n\nActual:\n" \ |
| << actual |
| |
| bool GetGuestCallback(WebContents** guest_out, WebContents* guest) { |
| EXPECT_FALSE(*guest_out); |
| *guest_out = guest; |
| // Return false so that we iterate through all the guests and verify there is |
| // only one. |
| return false; |
| } |
| |
| class PDFExtensionTest : public ExtensionApiTest, |
| public testing::WithParamInterface<int> { |
| public: |
| ~PDFExtensionTest() override {} |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| content::IsolateAllSitesForTesting(command_line); |
| } |
| |
| void SetUpOnMainThread() override { |
| ExtensionApiTest::SetUpOnMainThread(); |
| host_resolver()->AddRule("*", "127.0.0.1"); |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| content::SetupCrossSiteRedirector(embedded_test_server()); |
| embedded_test_server()->StartAcceptingConnections(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ASSERT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| ExtensionApiTest::TearDownOnMainThread(); |
| } |
| |
| bool PdfIsExpectedToLoad(const std::string& pdf_file) { |
| const char* const kFailingPdfs[] = { |
| "pdf_private/accessibility_crash_1.pdf", |
| "pdf_private/cfuzz5.pdf", |
| "pdf_private/cfuzz6.pdf", |
| "pdf_private/crash-11-14-44.pdf", |
| "pdf_private/js.pdf", |
| "pdf_private/segv-ecx.pdf", |
| "pdf_private/tests.pdf", |
| }; |
| for (const char* failing_pdf : kFailingPdfs) { |
| if (failing_pdf == pdf_file) |
| return false; |
| } |
| return true; |
| } |
| |
| // Runs the extensions test at chrome/test/data/pdf/<filename> on the PDF file |
| // at chrome/test/data/pdf/<pdf_filename>. |
| void RunTestsInFile(const std::string& filename, |
| const std::string& pdf_filename) { |
| extensions::ResultCatcher catcher; |
| |
| GURL url(embedded_test_server()->GetURL("/pdf/" + pdf_filename)); |
| |
| // It should be good enough to just navigate to the URL. But loading up the |
| // BrowserPluginGuest seems to happen asynchronously as there was flakiness |
| // being seen due to the BrowserPluginGuest not being available yet (see |
| // crbug.com/498077). So instead use LoadPdf() which ensures that the PDF is |
| // loaded before continuing. |
| WebContents* guest_contents = LoadPdfGetGuestContents(url); |
| ASSERT_TRUE(guest_contents); |
| std::string test_util_js; |
| std::string mock_interactions_js; |
| |
| { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath test_data_dir; |
| PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir); |
| test_data_dir = test_data_dir.Append(FILE_PATH_LITERAL("pdf")); |
| base::FilePath test_util_path = test_data_dir.AppendASCII("test_util.js"); |
| ASSERT_TRUE(base::ReadFileToString(test_util_path, &test_util_js)); |
| |
| base::FilePath source_root_dir; |
| PathService::Get(base::DIR_SOURCE_ROOT, &source_root_dir); |
| base::FilePath mock_interactions_path = source_root_dir.Append( |
| FILE_PATH_LITERAL("third_party/polymer/v1_0/components-chromium/" |
| "iron-test-helpers/mock-interactions.js")); |
| ASSERT_TRUE(base::ReadFileToString(mock_interactions_path, |
| &mock_interactions_js)); |
| test_util_js.append(mock_interactions_js); |
| |
| base::FilePath test_file_path = test_data_dir.AppendASCII(filename); |
| std::string test_js; |
| ASSERT_TRUE(base::ReadFileToString(test_file_path, &test_js)); |
| |
| test_util_js.append(test_js); |
| } |
| |
| ASSERT_TRUE(content::ExecuteScript(guest_contents, test_util_js)); |
| |
| if (!catcher.GetNextResult()) |
| FAIL() << catcher.message(); |
| } |
| |
| // Load the PDF at the given URL and use the PDFScriptingAPI to ensure it has |
| // finished loading. Return true if it loads successfully or false if it |
| // fails. If it doesn't finish loading the test will hang. This is done from |
| // outside of the BrowserPlugin guest to ensure the PDFScriptingAPI works |
| // correctly from there. |
| bool LoadPdf(const GURL& url) { |
| ui_test_utils::NavigateToURL(browser(), url); |
| WebContents* web_contents = GetActiveWebContents(); |
| return pdf_extension_test_util::EnsurePDFHasLoaded(web_contents); |
| } |
| |
| // Same as LoadPdf(), but also returns a pointer to the guest WebContents for |
| // the loaded PDF. Returns nullptr if the load fails. |
| WebContents* LoadPdfGetGuestContents(const GURL& url) { |
| if (!LoadPdf(url)) |
| return nullptr; |
| |
| WebContents* contents = GetActiveWebContents(); |
| content::BrowserPluginGuestManager* guest_manager = |
| contents->GetBrowserContext()->GetGuestManager(); |
| WebContents* guest_contents = guest_manager->GetFullPageGuest(contents); |
| return guest_contents; |
| } |
| |
| // Load all the PDFs contained in chrome/test/data/<dir_name>. This only runs |
| // the test if base::Hash(filename) mod kNumberLoadTestParts == k in order |
| // to shard the files evenly across values of k in [0, kNumberLoadTestParts). |
| void LoadAllPdfsTest(const std::string& dir_name, int k) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath test_data_dir; |
| ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); |
| base::FileEnumerator file_enumerator(test_data_dir.AppendASCII(dir_name), |
| false, base::FileEnumerator::FILES, |
| FILE_PATH_LITERAL("*.pdf")); |
| |
| size_t count = 0; |
| for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); |
| file_path = file_enumerator.Next()) { |
| std::string filename = file_path.BaseName().MaybeAsASCII(); |
| ASSERT_FALSE(filename.empty()); |
| |
| std::string pdf_file = dir_name + "/" + filename; |
| if (static_cast<int>(base::Hash(filename) % kNumberLoadTestParts) == k) { |
| LOG(INFO) << "Loading: " << pdf_file; |
| bool success = LoadPdf(embedded_test_server()->GetURL("/" + pdf_file)); |
| EXPECT_EQ(PdfIsExpectedToLoad(pdf_file), success) << pdf_file; |
| } |
| ++count; |
| } |
| // Assume that there is at least 1 pdf in the directory to guard against |
| // someone deleting the directory and silently making the test pass. |
| ASSERT_GE(count, 1u); |
| } |
| |
| void TestGetSelectedTextReply(GURL url, bool expect_success) { |
| ASSERT_TRUE(LoadPdf(url)); |
| |
| // Reach into the guest and hook into it such that it posts back a 'flush' |
| // message after every getSelectedTextReply message sent. |
| WebContents* web_contents = GetActiveWebContents(); |
| content::BrowserPluginGuestManager* guest_manager = |
| web_contents->GetBrowserContext()->GetGuestManager(); |
| WebContents* guest_contents = nullptr; |
| ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest( |
| web_contents, base::Bind(&GetGuestCallback, &guest_contents))); |
| ASSERT_TRUE(guest_contents); |
| ASSERT_TRUE(content::ExecuteScript( |
| guest_contents, |
| "var oldSendScriptingMessage = " |
| " PDFViewer.prototype.sendScriptingMessage_;" |
| "PDFViewer.prototype.sendScriptingMessage_ = function(message) {" |
| " oldSendScriptingMessage.bind(this)(message);" |
| " if (message.type == 'getSelectedTextReply')" |
| " this.parentWindow_.postMessage('flush', '*');" |
| "}")); |
| |
| // Add an event listener for flush messages and request the selected text. |
| // If we get a flush message without receiving getSelectedText we know that |
| // the message didn't come through. |
| bool success = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "window.addEventListener('message', function(event) {" |
| " if (event.data == 'flush')" |
| " window.domAutomationController.send(false);" |
| " if (event.data.type == 'getSelectedTextReply')" |
| " window.domAutomationController.send(true);" |
| "});" |
| "document.getElementsByTagName('embed')[0].postMessage(" |
| " {type: 'getSelectedText'});", |
| &success)); |
| ASSERT_EQ(expect_success, success); |
| } |
| |
| void ConvertPageCoordToScreenCoord(WebContents* contents, gfx::Point* point) { |
| ASSERT_TRUE(contents); |
| ASSERT_TRUE(content::ExecuteScript(contents, |
| "var visiblePage = viewer.viewport.getMostVisiblePage();" |
| "var visiblePageDimensions =" |
| " viewer.viewport.getPageScreenRect(visiblePage);" |
| "var viewportPosition = viewer.viewport.position;" |
| "var screenOffsetX = visiblePageDimensions.x - viewportPosition.x;" |
| "var screenOffsetY = visiblePageDimensions.y - viewportPosition.y;" |
| "var linkScreenPositionX =" |
| " Math.floor(" + base::IntToString(point->x()) + " + screenOffsetX);" |
| "var linkScreenPositionY =" |
| " Math.floor(" + base::IntToString(point->y()) + " +" |
| " screenOffsetY);")); |
| |
| int x; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| contents, |
| "window.domAutomationController.send(linkScreenPositionX);", |
| &x)); |
| |
| int y; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
| contents, |
| "window.domAutomationController.send(linkScreenPositionY);", |
| &y)); |
| |
| point->SetPoint(x, y); |
| } |
| |
| WebContents* GetActiveWebContents() { |
| return browser()->tab_strip_model()->GetActiveWebContents(); |
| } |
| |
| int CountPDFProcesses() { |
| int result = -1; |
| base::RunLoop run_loop; |
| content::BrowserThread::PostTaskAndReply( |
| content::BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&PDFExtensionTest::CountPDFProcessesOnIOThread, |
| base::Unretained(this), base::Unretained(&result)), |
| run_loop.QuitClosure()); |
| run_loop.Run(); |
| return result; |
| } |
| |
| void CountPDFProcessesOnIOThread(int* result) { |
| auto* service = content::PluginService::GetInstance(); |
| *result = service->CountPpapiPluginProcessesForProfile( |
| base::FilePath::FromUTF8Unsafe(ChromeContentClient::kPDFPluginPath), |
| browser()->profile()->GetPath()); |
| } |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(PDFExtensionTest, Load) { |
| #if defined(GOOGLE_CHROME_BUILD) |
| // Load private PDFs. |
| LoadAllPdfsTest("pdf_private", GetParam()); |
| #endif |
| // Load public PDFs. |
| LoadAllPdfsTest("pdf", GetParam()); |
| } |
| |
| class DisablePluginHelper : public content::DownloadManager::Observer { |
| public: |
| DisablePluginHelper() {} |
| ~DisablePluginHelper() override {} |
| |
| void DisablePlugin(Profile* profile) { |
| profile->GetPrefs()->SetBoolean( |
| prefs::kPluginsAlwaysOpenPdfExternally, true); |
| } |
| |
| const GURL& GetLastUrl() { |
| // Wait until the download has been created. |
| download_run_loop_.Run(); |
| return last_url_; |
| } |
| |
| // content::DownloadManager::Observer implementation. |
| void OnDownloadCreated(content::DownloadManager* manager, |
| download::DownloadItem* item) override { |
| last_url_ = item->GetURL(); |
| download_run_loop_.Quit(); |
| } |
| |
| private: |
| content::NotificationRegistrar registrar_; |
| base::RunLoop download_run_loop_; |
| GURL last_url_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, DisablePlugin) { |
| // Disable the PDF plugin. |
| WebContents* web_contents = GetActiveWebContents(); |
| content::BrowserContext* browser_context = web_contents->GetBrowserContext(); |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| DisablePluginHelper helper; |
| helper.DisablePlugin(profile); |
| |
| // Register a download observer. |
| content::DownloadManager* download_manager = |
| content::BrowserContext::GetDownloadManager(browser_context); |
| download_manager->AddObserver(&helper); |
| |
| // Navigate to a PDF and test that it is downloaded. |
| GURL url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| ui_test_utils::NavigateToURL(browser(), url); |
| ASSERT_EQ(url, helper.GetLastUrl()); |
| |
| // Didn't launch a PPAPI process. |
| EXPECT_EQ(0, CountPDFProcesses()); |
| |
| // Cancel the download to shutdown cleanly. |
| download_manager->RemoveObserver(&helper); |
| std::vector<download::DownloadItem*> downloads; |
| download_manager->GetAllDownloads(&downloads); |
| ASSERT_EQ(1u, downloads.size()); |
| downloads[0]->Cancel(false); |
| } |
| |
| // We break PDFTest.Load up into kNumberLoadTestParts. |
| INSTANTIATE_TEST_CASE_P(PDFTestFiles, |
| PDFExtensionTest, |
| testing::Range(0, kNumberLoadTestParts)); |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Basic) { |
| RunTestsInFile("basic_test.js", "test.pdf"); |
| |
| // Ensure it loaded in a PPAPI process. |
| EXPECT_EQ(1, CountPDFProcesses()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, BasicPlugin) { |
| RunTestsInFile("basic_plugin_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Viewport) { |
| RunTestsInFile("viewport_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Bookmark) { |
| RunTestsInFile("bookmarks_test.js", "test-bookmarks.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Navigator) { |
| RunTestsInFile("navigator_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ParamsParser) { |
| RunTestsInFile("params_parser_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ZoomManager) { |
| RunTestsInFile("zoom_manager_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, GestureDetector) { |
| RunTestsInFile("gesture_detector_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TouchHandling) { |
| RunTestsInFile("touch_handling_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Elements) { |
| // Although this test file does not require a PDF to be loaded, loading the |
| // elements without loading a PDF is difficult. |
| RunTestsInFile("material_elements_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ToolbarManager) { |
| RunTestsInFile("toolbar_manager_test.js", "test.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Title) { |
| RunTestsInFile("title_test.js", "test-title.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, WhitespaceTitle) { |
| RunTestsInFile("whitespace_title_test.js", "test-whitespace-title.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PageChange) { |
| RunTestsInFile("page_change_test.js", "test-bookmarks.pdf"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, Metrics) { |
| RunTestsInFile("metrics_test.js", "test.pdf"); |
| } |
| |
| // Ensure that the internal PDF plugin application/x-google-chrome-pdf won't be |
| // loaded if it's not loaded in the chrome extension page. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureInternalPluginDisabled) { |
| std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec(); |
| std::string data_url = |
| "data:text/html," |
| "<html><body>" |
| "<embed type=\"application/x-google-chrome-pdf\" src=\"" + |
| url + |
| "\">" |
| "</body></html>"; |
| ui_test_utils::NavigateToURL(browser(), GURL(data_url)); |
| WebContents* web_contents = GetActiveWebContents(); |
| bool plugin_loaded = false; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| web_contents, |
| "var plugin_loaded = " |
| " document.getElementsByTagName('embed')[0].postMessage !== undefined;" |
| "window.domAutomationController.send(plugin_loaded);", |
| &plugin_loaded)); |
| ASSERT_FALSE(plugin_loaded); |
| } |
| |
| // Ensure cross-origin replies won't work for getSelectedText. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureCrossOriginRepliesBlocked) { |
| std::string url = embedded_test_server()->GetURL("/pdf/test.pdf").spec(); |
| std::string data_url = |
| "data:text/html," |
| "<html><body>" |
| "<embed type=\"application/pdf\" src=\"" + |
| url + |
| "\">" |
| "</body></html>"; |
| TestGetSelectedTextReply(GURL(data_url), false); |
| } |
| |
| // Ensure same-origin replies do work for getSelectedText. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsureSameOriginRepliesAllowed) { |
| TestGetSelectedTextReply(embedded_test_server()->GetURL("/pdf/test.pdf"), |
| true); |
| } |
| |
| // Ensure that the PDF component extension cannot be loaded directly. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, BlockDirectAccess) { |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| std::unique_ptr<content::ConsoleObserverDelegate> console_delegate( |
| new content::ConsoleObserverDelegate( |
| web_contents, |
| "*Streams are only available from a mime handler view guest.*")); |
| web_contents->SetDelegate(console_delegate.get()); |
| GURL forbiddenUrl( |
| "chrome-extension://mhjfbmdgcfjbbpaeojofohoefgiehjai/index.html?" |
| "https://example.com/notrequested.pdf"); |
| ui_test_utils::NavigateToURL(browser(), forbiddenUrl); |
| |
| console_delegate->Wait(); |
| |
| // Didn't launch a PPAPI process. |
| EXPECT_EQ(0, CountPDFProcesses()); |
| } |
| |
| // This test ensures that PDF can be loaded from local file |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, EnsurePDFFromLocalFileLoads) { |
| GURL test_pdf_url; |
| { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::FilePath test_data_dir; |
| ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); |
| test_data_dir = test_data_dir.Append(FILE_PATH_LITERAL("pdf")); |
| base::FilePath test_data_file = test_data_dir.AppendASCII("test.pdf"); |
| ASSERT_TRUE(PathExists(test_data_file)); |
| test_pdf_url = GURL("file://" + test_data_file.MaybeAsASCII()); |
| } |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| // Did launch a PPAPI process. |
| EXPECT_EQ(1, CountPDFProcesses()); |
| } |
| |
| // This test ensures that link permissions are enforced properly in PDFs. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, LinkPermissions) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| // chrome://favicon links should be allowed for PDFs, while chrome://settings |
| // links should not. |
| GURL valid_link_url("chrome://favicon/https://www.google.ca/"); |
| GURL invalid_link_url("chrome://settings"); |
| |
| GURL unfiltered_valid_link_url(valid_link_url); |
| content::RenderProcessHost* rph = |
| guest_contents->GetMainFrame()->GetProcess(); |
| rph->FilterURL(true, &valid_link_url); |
| rph->FilterURL(true, &invalid_link_url); |
| |
| // Invalid link URLs should be changed to "about:blank" when filtered. |
| EXPECT_EQ(unfiltered_valid_link_url, valid_link_url); |
| EXPECT_EQ(GURL("about:blank"), invalid_link_url); |
| } |
| |
| // This test ensures that titles are set properly for PDFs without /Title. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TabTitleWithNoTitle) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), guest_contents->GetTitle()); |
| EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), GetActiveWebContents()->GetTitle()); |
| } |
| |
| // This test ensures that titles are set properly for PDFs with /Title. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TabTitleWithTitle) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-title.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| EXPECT_EQ(base::ASCIIToUTF16("PDF title test"), guest_contents->GetTitle()); |
| EXPECT_EQ(base::ASCIIToUTF16("PDF title test"), |
| GetActiveWebContents()->GetTitle()); |
| } |
| |
| // This test ensures that titles are set properly for embedded PDFs with /Title. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TabTitleWithEmbeddedPdf) { |
| std::string url = |
| embedded_test_server()->GetURL("/pdf/test-title.pdf").spec(); |
| std::string data_url = |
| "data:text/html," |
| "<html><head><title>TabTitleWithEmbeddedPdf</title></head><body>" |
| "<embed type=\"application/pdf\" src=\"" + |
| url + |
| "\"></body></html>"; |
| ASSERT_TRUE(LoadPdf(GURL(data_url))); |
| EXPECT_EQ(base::ASCIIToUTF16("TabTitleWithEmbeddedPdf"), |
| GetActiveWebContents()->GetTitle()); |
| } |
| |
| // Flaky, http://crbug.com/767427 |
| #if defined(OS_WIN) |
| #define MAYBE_PdfZoomWithoutBubble DISABLED_PdfZoomWithoutBubble |
| #else |
| #define MAYBE_PdfZoomWithoutBubble PdfZoomWithoutBubble |
| #endif |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, MAYBE_PdfZoomWithoutBubble) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| // The PDF viewer always starts at default zoom, which for tests is 100% or |
| // zoom level 0.0. Here we look at the presets to find the next zoom level |
| // above 0. Ideally we should look at the zoom levels from the PDF viewer |
| // javascript, but we assume they'll always match the browser presets, which |
| // are easier to access. |
| std::vector<double> preset_zoom_levels = zoom::PageZoom::PresetZoomLevels(0); |
| auto it = std::find(preset_zoom_levels.begin(), preset_zoom_levels.end(), 0); |
| ASSERT_TRUE(it != preset_zoom_levels.end()); |
| it++; |
| ASSERT_TRUE(it != preset_zoom_levels.end()); |
| double new_zoom_level = *it; |
| |
| auto* zoom_controller = zoom::ZoomController::FromWebContents(web_contents); |
| // We expect a ZoomChangedEvent with can_show_bubble == false if the PDF |
| // extension behaviour is properly picked up. The test times out otherwise. |
| zoom::ZoomChangedWatcher watcher( |
| zoom_controller, zoom::ZoomController::ZoomChangedEventData( |
| web_contents, 0, new_zoom_level, |
| zoom::ZoomController::ZOOM_MODE_MANUAL, false)); |
| |
| // Zoom PDF via script. |
| #if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX) |
| EXPECT_EQ(nullptr, ZoomBubbleView::GetZoomBubble()); |
| #endif |
| ASSERT_TRUE( |
| content::ExecuteScript(guest_contents, "viewer.viewport.zoomIn();")); |
| |
| watcher.Wait(); |
| #if defined(TOOLKIT_VIEWS) && !defined(OS_MACOSX) |
| EXPECT_EQ(nullptr, ZoomBubbleView::GetZoomBubble()); |
| #endif |
| } |
| |
| static std::string DumpPdfAccessibilityTree(const ui::AXTreeUpdate& ax_tree) { |
| // Create a string representation of the tree starting with the embedded |
| // object. |
| std::string ax_tree_dump; |
| std::map<int32_t, int> id_to_indentation; |
| bool found_embedded_object = false; |
| for (auto& node : ax_tree.nodes) { |
| if (node.role == ax::mojom::Role::kEmbeddedObject) |
| found_embedded_object = true; |
| if (!found_embedded_object) |
| continue; |
| |
| int indent = id_to_indentation[node.id]; |
| ax_tree_dump += std::string(2 * indent, ' '); |
| ax_tree_dump += ui::ToString(node.role); |
| |
| std::string name = |
| node.GetStringAttribute(ax::mojom::StringAttribute::kName); |
| base::ReplaceChars(name, "\r\n", "", &name); |
| if (!name.empty()) |
| ax_tree_dump += " '" + name + "'"; |
| ax_tree_dump += "\n"; |
| for (size_t j = 0; j < node.child_ids.size(); ++j) |
| id_to_indentation[node.child_ids[j]] = indent + 1; |
| } |
| |
| return ax_tree_dump; |
| } |
| |
| // This is a pattern with a few wildcards due to a PDF bug where the |
| // fi ligature is not parsed correctly on some systems. |
| // http://crbug.com/701427 |
| |
| static const char kExpectedPDFAXTreePattern[] = |
| "embeddedObject\n" |
| " group\n" |
| " region 'Page 1'\n" |
| " paragraph\n" |
| " staticText '1 First Section'\n" |
| " inlineTextBox '1 '\n" |
| " inlineTextBox 'First Section'\n" |
| " paragraph\n" |
| " staticText 'This is the *rst section.1'\n" |
| " inlineTextBox 'This is the *rst section.'\n" |
| " inlineTextBox '1'\n" |
| " region 'Page 2'\n" |
| " paragraph\n" |
| " staticText '1.1 First Subsection'\n" |
| " inlineTextBox '1.1 '\n" |
| " inlineTextBox 'First Subsection'\n" |
| " paragraph\n" |
| " staticText 'This is the *rst subsection.2'\n" |
| " inlineTextBox 'This is the *rst subsection.'\n" |
| " inlineTextBox '2'\n" |
| " region 'Page 3'\n" |
| " paragraph\n" |
| " staticText '2 Second Section'\n" |
| " inlineTextBox '2 '\n" |
| " inlineTextBox 'Second Section'\n" |
| " paragraph\n" |
| " staticText '3'\n" |
| " inlineTextBox '3'\n"; |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibility) { |
| content::BrowserAccessibilityState::GetInstance()->EnableAccessibility(); |
| |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(guest_contents, |
| "1 First Section\r\n"); |
| ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents); |
| std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree); |
| |
| ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityEnableLater) { |
| // In this test, load the PDF file first, with accessibility off. |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| // Now enable accessibility globally, and assert that the PDF accessibility |
| // tree loads. |
| EnableAccessibilityForWebContents(guest_contents); |
| WaitForAccessibilityTreeToContainNodeWithName(guest_contents, |
| "1 First Section\r\n"); |
| ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents); |
| std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree); |
| ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump); |
| } |
| |
| bool RetrieveGuestContents(WebContents** out_guest_contents, |
| WebContents* in_guest_contents) { |
| *out_guest_contents = in_guest_contents; |
| return true; |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityInIframe) { |
| content::BrowserAccessibilityState::GetInstance()->EnableAccessibility(); |
| GURL test_iframe_url(embedded_test_server()->GetURL("/pdf/test-iframe.html")); |
| ui_test_utils::NavigateToURL(browser(), test_iframe_url); |
| WebContents* contents = GetActiveWebContents(); |
| WaitForAccessibilityTreeToContainNodeWithName(contents, |
| "1 First Section\r\n"); |
| |
| WebContents* guest_contents = nullptr; |
| content::BrowserPluginGuestManager* guest_manager = |
| contents->GetBrowserContext()->GetGuestManager(); |
| guest_manager->ForEachGuest(contents, |
| base::Bind(&RetrieveGuestContents, |
| &guest_contents)); |
| ASSERT_TRUE(guest_contents); |
| |
| ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents); |
| std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree); |
| ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityInOOPIF) { |
| content::BrowserAccessibilityState::GetInstance()->EnableAccessibility(); |
| GURL test_iframe_url(embedded_test_server()->GetURL( |
| "/pdf/test-cross-site-iframe.html")); |
| ui_test_utils::NavigateToURL(browser(), test_iframe_url); |
| WebContents* contents = GetActiveWebContents(); |
| WaitForAccessibilityTreeToContainNodeWithName(contents, |
| "1 First Section\r\n"); |
| |
| WebContents* guest_contents = nullptr; |
| content::BrowserPluginGuestManager* guest_manager = |
| contents->GetBrowserContext()->GetGuestManager(); |
| guest_manager->ForEachGuest(contents, |
| base::Bind(&RetrieveGuestContents, |
| &guest_contents)); |
| ASSERT_TRUE(guest_contents); |
| |
| ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents); |
| std::string ax_tree_dump = DumpPdfAccessibilityTree(ax_tree); |
| ASSERT_MULTILINE_STR_MATCHES(kExpectedPDFAXTreePattern, ax_tree_dump); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityWordBoundaries) { |
| content::BrowserAccessibilityState::GetInstance()->EnableAccessibility(); |
| |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-bookmarks.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(guest_contents, |
| "1 First Section\r\n"); |
| ui::AXTreeUpdate ax_tree = GetAccessibilityTreeSnapshot(guest_contents); |
| |
| bool found = false; |
| for (auto& node : ax_tree.nodes) { |
| std::string name = |
| node.GetStringAttribute(ax::mojom::StringAttribute::kName); |
| if (node.role == ax::mojom::Role::kInlineTextBox && |
| name == "First Section\r\n") { |
| found = true; |
| std::vector<int32_t> word_starts = |
| node.GetIntListAttribute(ax::mojom::IntListAttribute::kWordStarts); |
| std::vector<int32_t> word_ends = |
| node.GetIntListAttribute(ax::mojom::IntListAttribute::kWordEnds); |
| ASSERT_EQ(2U, word_starts.size()); |
| ASSERT_EQ(2U, word_ends.size()); |
| EXPECT_EQ(0, word_starts[0]); |
| EXPECT_EQ(5, word_ends[0]); |
| EXPECT_EQ(6, word_starts[1]); |
| EXPECT_EQ(13, word_ends[1]); |
| } |
| } |
| ASSERT_TRUE(found); |
| } |
| |
| #if defined(GOOGLE_CHROME_BUILD) |
| // Test a particular PDF encountered in the wild that triggered a crash |
| // when accessibility is enabled. (http://crbug.com/668724) |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PdfAccessibilityTextRunCrash) { |
| content::BrowserAccessibilityState::GetInstance()->EnableAccessibility(); |
| GURL test_pdf_url(embedded_test_server()->GetURL( |
| "/pdf_private/accessibility_crash_2.pdf")); |
| |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| WaitForAccessibilityTreeToContainNodeWithName(guest_contents, "Page 1"); |
| } |
| #endif |
| |
| // Test that if the plugin tries to load a URL that redirects then it will fail |
| // to load. This is to avoid the source origin of the document changing during |
| // the redirect, which can have security implications. https://crbug.com/653749. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, RedirectsFailInPlugin) { |
| RunTestsInFile("redirects_fail_test.js", "test.pdf"); |
| } |
| |
| // Test that even if a different tab is selected when a navigation occurs, |
| // the correct tab still gets navigated (see crbug.com/672563). |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, NavigationOnCorrectTab) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| ui_test_utils::NavigateToURLWithDisposition( |
| browser(), GURL("about:blank"), WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_NE(web_contents, active_web_contents); |
| |
| ASSERT_TRUE(content::ExecuteScript( |
| guest_contents, |
| "viewer.navigator_.navigate(" |
| " 'www.example.com', Navigator.WindowOpenDisposition.CURRENT_TAB);")); |
| |
| EXPECT_TRUE(web_contents->GetController().GetPendingEntry()); |
| EXPECT_FALSE(active_web_contents->GetController().GetPendingEntry()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, OpenFromFTP) { |
| net::SpawnedTestServer ftp_server( |
| net::SpawnedTestServer::TYPE_FTP, |
| base::FilePath(FILE_PATH_LITERAL("chrome/test/data/pdf"))); |
| ASSERT_TRUE(ftp_server.Start()); |
| |
| GURL url(ftp_server.GetURL("/test.pdf")); |
| ASSERT_TRUE(LoadPdf(url)); |
| EXPECT_EQ(base::ASCIIToUTF16("test.pdf"), GetActiveWebContents()->GetTitle()); |
| } |
| |
| class PDFExtensionLinkClickTest : public PDFExtensionTest { |
| public: |
| PDFExtensionLinkClickTest() : guest_contents_(nullptr) {} |
| ~PDFExtensionLinkClickTest() override {} |
| |
| protected: |
| void LoadTestLinkPdfGetGuestContents() { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test-link.pdf")); |
| guest_contents_ = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents_); |
| } |
| |
| // The rectangle of the link in test-link.pdf is [72 706 164 719] in PDF user |
| // space. To calculate a position inside this rectangle, several |
| // transformations have to be applied: |
| // [a] (110, 110) in Blink page coordinates -> |
| // [b] (219, 169) in Blink screen coordinates -> |
| // [c] (115, 169) in PDF Device space coordinates -> |
| // [d] (82.5, 709.5) in PDF user space coordinates. |
| // This performs the [a] to [b] transformation, since that is the coordinate |
| // space content::SimulateMouseClickAt() needs. |
| gfx::Point GetLinkPosition() { |
| gfx::Point link_position(110, 110); |
| ConvertPageCoordToScreenCoord(guest_contents_, &link_position); |
| return link_position; |
| } |
| |
| void SetGuestContents(WebContents* guest_contents) { |
| ASSERT_TRUE(guest_contents); |
| guest_contents_ = guest_contents; |
| } |
| |
| private: |
| WebContents* guest_contents_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, CtrlLeft) { |
| LoadTestLinkPdfGetGuestContents(); |
| |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, kDefaultKeyModifier, |
| blink::WebMouseEvent::Button::kLeft, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| int tab_count = browser()->tab_strip_model()->count(); |
| ASSERT_EQ(2, tab_count); |
| |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_EQ(web_contents, active_web_contents); |
| |
| WebContents* new_web_contents = |
| browser()->tab_strip_model()->GetWebContentsAt(1); |
| ASSERT_TRUE(new_web_contents); |
| ASSERT_NE(web_contents, new_web_contents); |
| |
| const GURL& url = new_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, Middle) { |
| LoadTestLinkPdfGetGuestContents(); |
| |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, 0, |
| blink::WebMouseEvent::Button::kMiddle, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| int tab_count = browser()->tab_strip_model()->count(); |
| ASSERT_EQ(2, tab_count); |
| |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_EQ(web_contents, active_web_contents); |
| |
| WebContents* new_web_contents = |
| browser()->tab_strip_model()->GetWebContentsAt(1); |
| ASSERT_TRUE(new_web_contents); |
| ASSERT_NE(web_contents, new_web_contents); |
| |
| const GURL& url = new_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, CtrlShiftLeft) { |
| LoadTestLinkPdfGetGuestContents(); |
| |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| const int modifiers = blink::WebInputEvent::kShiftKey | kDefaultKeyModifier; |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, modifiers, |
| blink::WebMouseEvent::Button::kLeft, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| int tab_count = browser()->tab_strip_model()->count(); |
| ASSERT_EQ(2, tab_count); |
| |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_NE(web_contents, active_web_contents); |
| |
| const GURL& url = active_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, ShiftMiddle) { |
| LoadTestLinkPdfGetGuestContents(); |
| |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, blink::WebInputEvent::kShiftKey, |
| blink::WebMouseEvent::Button::kMiddle, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| int tab_count = browser()->tab_strip_model()->count(); |
| ASSERT_EQ(2, tab_count); |
| |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_NE(web_contents, active_web_contents); |
| |
| const GURL& url = active_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, ShiftLeft) { |
| LoadTestLinkPdfGetGuestContents(); |
| |
| ASSERT_EQ(1U, chrome::GetTotalBrowserCount()); |
| |
| WebContents* web_contents = GetActiveWebContents(); |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_BROWSER_WINDOW_READY, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, blink::WebInputEvent::kShiftKey, |
| blink::WebMouseEvent::Button::kLeft, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| ASSERT_EQ(2U, chrome::GetTotalBrowserCount()); |
| |
| WebContents* active_web_contents = |
| chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_NE(web_contents, active_web_contents); |
| |
| const GURL& url = active_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| // This test opens a PDF by clicking a link via javascript and verifies that |
| // the PDF is loaded and functional by clicking a link in the PDF. The link |
| // click in the PDF opens a new tab. The main page handles the pageShow event |
| // and updates the history state. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionLinkClickTest, OpenPDFWithReplaceState) { |
| // Navigate to the main page. |
| GURL test_url( |
| embedded_test_server()->GetURL("/pdf/pdf_href_replace_state.html")); |
| ui_test_utils::NavigateToURL(browser(), test_url); |
| WebContents* web_contents = GetActiveWebContents(); |
| ASSERT_TRUE(web_contents); |
| |
| // Click on the link which opens the PDF via JS. |
| content::TestNavigationObserver navigation_observer(web_contents); |
| const char kPdfLinkClick[] = "document.getElementById('link').click();"; |
| ASSERT_TRUE(content::ExecuteScript(web_contents->GetRenderViewHost(), |
| kPdfLinkClick)); |
| navigation_observer.Wait(); |
| const GURL& current_url = web_contents->GetURL(); |
| ASSERT_EQ("/pdf/test-link.pdf", current_url.path()); |
| |
| ASSERT_TRUE(pdf_extension_test_util::EnsurePDFHasLoaded(web_contents)); |
| |
| // Now click on the link to example.com in the PDF. This should open up a new |
| // tab. |
| content::BrowserPluginGuestManager* guest_manager = |
| web_contents->GetBrowserContext()->GetGuestManager(); |
| SetGuestContents(guest_manager->GetFullPageGuest(web_contents)); |
| |
| content::WindowedNotificationObserver observer( |
| chrome::NOTIFICATION_TAB_ADDED, |
| content::NotificationService::AllSources()); |
| content::SimulateMouseClickAt(web_contents, kDefaultKeyModifier, |
| blink::WebMouseEvent::Button::kLeft, |
| GetLinkPosition()); |
| observer.Wait(); |
| |
| // We should have two tabs now. One with the PDF and the second for |
| // example.com |
| int tab_count = browser()->tab_strip_model()->count(); |
| ASSERT_EQ(2, tab_count); |
| |
| WebContents* active_web_contents = GetActiveWebContents(); |
| ASSERT_EQ(web_contents, active_web_contents); |
| |
| WebContents* new_web_contents = |
| browser()->tab_strip_model()->GetWebContentsAt(1); |
| ASSERT_TRUE(new_web_contents); |
| ASSERT_NE(web_contents, new_web_contents); |
| |
| const GURL& url = new_web_contents->GetURL(); |
| EXPECT_EQ("http://www.example.com/", url.spec()); |
| } |
| |
| class PDFExtensionClipboardTest : public PDFExtensionTest { |
| public: |
| PDFExtensionClipboardTest() : guest_contents_(nullptr) {} |
| ~PDFExtensionClipboardTest() override {} |
| |
| void SetUpOnMainThread() override { |
| PDFExtensionTest::SetUpOnMainThread(); |
| ui::TestClipboard::CreateForCurrentThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| ui::Clipboard::DestroyClipboardForCurrentThread(); |
| PDFExtensionTest::TearDownOnMainThread(); |
| } |
| |
| void LoadTestComboBoxPdfGetGuestContents() { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/combobox_form.pdf")); |
| guest_contents_ = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents_); |
| } |
| |
| // Returns a point near the left edge of the editable combo box in |
| // combobox_form.pdf, inside the combo box rect. The point is in Blink screen |
| // coordinates. |
| // |
| // The combo box's rect is [100 50 200 80] in PDF user space. (136, 318) in |
| // Blink page coordinates corresponds to approximately (102, 62) in PDF user |
| // space coordinates. See PDFExtensionLinkClickTest::GetLinkPosition() for |
| // more information on all the coordinate systems involved. |
| gfx::Point GetEditableComboBoxLeftPosition() { |
| gfx::Point position(136, 318); |
| ConvertPageCoordToScreenCoord(guest_contents_, &position); |
| return position; |
| } |
| |
| void ClickLeftSideOfEditableComboBox() { |
| content::SimulateMouseClickAt(GetActiveWebContents(), 0, |
| blink::WebMouseEvent::Button::kLeft, |
| GetEditableComboBoxLeftPosition()); |
| } |
| |
| void TypeHello() { |
| auto* web_contents = GetActiveWebContents(); |
| content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('H'), |
| ui::DomCode::US_H, ui::VKEY_H, false, false, |
| false, false); |
| content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('E'), |
| ui::DomCode::US_E, ui::VKEY_E, false, false, |
| false, false); |
| content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('L'), |
| ui::DomCode::US_L, ui::VKEY_L, false, false, |
| false, false); |
| content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('L'), |
| ui::DomCode::US_L, ui::VKEY_L, false, false, |
| false, false); |
| content::SimulateKeyPress(web_contents, ui::DomKey::FromCharacter('O'), |
| ui::DomCode::US_O, ui::VKEY_O, false, false, |
| false, false); |
| } |
| |
| // Presses the left arrow key. |
| void PressLeftArrow() { |
| content::SimulateKeyPressWithoutChar( |
| GetActiveWebContents(), ui::DomKey::ARROW_LEFT, ui::DomCode::ARROW_LEFT, |
| ui::VKEY_LEFT, false, false, false, false); |
| } |
| |
| // Presses down shift, presses the left arrow, and lets go of shift. |
| void PressShiftLeftArrow() { |
| content::SimulateKeyPressWithoutChar( |
| GetActiveWebContents(), ui::DomKey::ARROW_LEFT, ui::DomCode::ARROW_LEFT, |
| ui::VKEY_LEFT, false, /*shift=*/true, false, false); |
| } |
| |
| // Presses the right arrow key. |
| void PressRightArrow() { |
| content::SimulateKeyPressWithoutChar( |
| GetActiveWebContents(), ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT, false, false, false, false); |
| } |
| |
| // Presses down shift, presses the right arrow, and lets go of shift. |
| void PressShiftRightArrow() { |
| content::SimulateKeyPressWithoutChar( |
| GetActiveWebContents(), ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT, false, /*shift=*/true, false, |
| false); |
| } |
| |
| // Checks the Linux selection clipboard by polling. |
| void CheckSelectionClipboard(const std::string& expected) { |
| #if defined(OS_LINUX) && !defined(OS_CHROMEOS) |
| CheckClipboard(ui::CLIPBOARD_TYPE_SELECTION, expected); |
| #endif |
| } |
| |
| // Sends a copy command and checks the copy/paste clipboard by |
| // polling. Note: Trying to send ctrl+c does not work correctly with |
| // SimulateKeyPress(). Using IDC_COPY does not work on Mac in browser_tests. |
| void SendCopyCommandAndCheckCopyPasteClipboard(const std::string& expected) { |
| content::RunAllPendingInMessageLoop(); |
| GetActiveWebContents()->Copy(); |
| CheckClipboard(ui::CLIPBOARD_TYPE_COPY_PASTE, expected); |
| } |
| |
| private: |
| // Waits and polls the clipboard of a given |clipboard_type| until its |
| // contents reaches the length of |expected|. Then checks and see if the |
| // clipboard contents matches |expected|. |
| // TODO(thestig): Change this to avoid polling after https://crbug.com/755826 |
| // has been fixed. |
| void CheckClipboard(ui::ClipboardType clipboard_type, |
| const std::string& expected) { |
| auto* clipboard = ui::Clipboard::GetForCurrentThread(); |
| std::string clipboard_data; |
| const std::string& last_data = last_clipboard_data_[clipboard_type]; |
| if (last_data.size() == expected.size()) { |
| DCHECK_EQ(last_data, expected); |
| clipboard->ReadAsciiText(clipboard_type, &clipboard_data); |
| EXPECT_EQ(expected, clipboard_data); |
| return; |
| } |
| |
| const bool expect_increase = last_data.size() < expected.size(); |
| while (true) { |
| clipboard->ReadAsciiText(clipboard_type, &clipboard_data); |
| if (expect_increase) { |
| if (clipboard_data.size() >= expected.size()) |
| break; |
| } else { |
| if (clipboard_data.size() <= expected.size()) |
| break; |
| } |
| |
| content::RunAllPendingInMessageLoop(); |
| } |
| EXPECT_EQ(expected, clipboard_data); |
| |
| last_clipboard_data_[clipboard_type] = clipboard_data; |
| } |
| |
| std::map<ui::ClipboardType, std::string> last_clipboard_data_; |
| WebContents* guest_contents_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionClipboardTest, |
| IndividualShiftRightArrowPresses) { |
| LoadTestComboBoxPdfGetGuestContents(); |
| |
| // Give the editable combo box focus. |
| ClickLeftSideOfEditableComboBox(); |
| |
| TypeHello(); |
| |
| // Put the cursor back to the left side of the combo box. |
| ClickLeftSideOfEditableComboBox(); |
| |
| // Press shift + right arrow 3 times. Letting go of shift in between. |
| PressShiftRightArrow(); |
| CheckSelectionClipboard("H"); |
| PressShiftRightArrow(); |
| CheckSelectionClipboard("HE"); |
| PressShiftRightArrow(); |
| CheckSelectionClipboard("HEL"); |
| SendCopyCommandAndCheckCopyPasteClipboard("HEL"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionClipboardTest, |
| IndividualShiftLeftArrowPresses) { |
| LoadTestComboBoxPdfGetGuestContents(); |
| |
| // Give the editable combo box focus. |
| ClickLeftSideOfEditableComboBox(); |
| |
| TypeHello(); |
| |
| // Put the cursor back to the left side of the combo box. |
| ClickLeftSideOfEditableComboBox(); |
| |
| for (int i = 0; i < 3; ++i) |
| PressRightArrow(); |
| |
| // Press shift + left arrow 2 times. Letting go of shift in between. |
| PressShiftLeftArrow(); |
| CheckSelectionClipboard("L"); |
| PressShiftLeftArrow(); |
| CheckSelectionClipboard("EL"); |
| SendCopyCommandAndCheckCopyPasteClipboard("EL"); |
| |
| // Press shift + left arrow 2 times. Letting go of shift in between. |
| PressShiftLeftArrow(); |
| CheckSelectionClipboard("HEL"); |
| PressShiftLeftArrow(); |
| CheckSelectionClipboard("HEL"); |
| SendCopyCommandAndCheckCopyPasteClipboard("HEL"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionClipboardTest, |
| CombinedShiftRightArrowPresses) { |
| LoadTestComboBoxPdfGetGuestContents(); |
| |
| // Give the editable combo box focus. |
| ClickLeftSideOfEditableComboBox(); |
| |
| TypeHello(); |
| |
| // Put the cursor back to the left side of the combo box. |
| ClickLeftSideOfEditableComboBox(); |
| |
| // Press shift + right arrow 3 times. Holding down shift in between. |
| { |
| content::ScopedSimulateModifierKeyPress hold_shift( |
| GetActiveWebContents(), false, true, false, false); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT); |
| CheckSelectionClipboard("H"); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT); |
| CheckSelectionClipboard("HE"); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT); |
| CheckSelectionClipboard("HEL"); |
| } |
| SendCopyCommandAndCheckCopyPasteClipboard("HEL"); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionClipboardTest, CombinedShiftArrowPresses) { |
| LoadTestComboBoxPdfGetGuestContents(); |
| |
| // Give the editable combo box focus. |
| ClickLeftSideOfEditableComboBox(); |
| |
| TypeHello(); |
| |
| // Put the cursor back to the left side of the combo box. |
| ClickLeftSideOfEditableComboBox(); |
| |
| for (int i = 0; i < 3; ++i) |
| PressRightArrow(); |
| |
| // Press shift + left arrow 3 times. Holding down shift in between. |
| { |
| content::ScopedSimulateModifierKeyPress hold_shift( |
| GetActiveWebContents(), false, true, false, false); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_LEFT, |
| ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT); |
| CheckSelectionClipboard("L"); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_LEFT, |
| ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT); |
| CheckSelectionClipboard("EL"); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_LEFT, |
| ui::DomCode::ARROW_LEFT, ui::VKEY_LEFT); |
| CheckSelectionClipboard("HEL"); |
| } |
| SendCopyCommandAndCheckCopyPasteClipboard("HEL"); |
| |
| // Press shift + right arrow 2 times. Holding down shift in between. |
| { |
| content::ScopedSimulateModifierKeyPress hold_shift( |
| GetActiveWebContents(), false, true, false, false); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT); |
| CheckSelectionClipboard("EL"); |
| hold_shift.KeyPressWithoutChar(ui::DomKey::ARROW_RIGHT, |
| ui::DomCode::ARROW_RIGHT, ui::VKEY_RIGHT); |
| CheckSelectionClipboard("L"); |
| } |
| SendCopyCommandAndCheckCopyPasteClipboard("L"); |
| } |
| |
| // Verifies that an <embed> of size zero will still instantiate a guest and post |
| // message to the <embed> is correctly forwarded to the extension. This is for |
| // catching future regression in docs/ and slides/ pages (see |
| // https://crbug.com/763812). |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, PostMessageForZeroSizedEmbed) { |
| content::DOMMessageQueue queue; |
| GURL url(embedded_test_server()->GetURL( |
| "/pdf/post_message_zero_sized_embed.html")); |
| ui_test_utils::NavigateToURL(browser(), url); |
| std::string message; |
| EXPECT_TRUE(queue.WaitForMessage(&message)); |
| EXPECT_EQ("\"POST_MESSAGE_OK\"", message); |
| } |
| |
| // In response to the events sent in |send_events|, ensures the PDF viewer zooms |
| // in and that the viewer's custom pinch zooming mechanism is used to do so. |
| void EnsureCustomPinchZoomInvoked(WebContents* guest_contents, |
| WebContents* contents, |
| base::OnceClosure send_events) { |
| ASSERT_TRUE(content::ExecuteScript( |
| guest_contents, |
| "var gestureDetector = new GestureDetector(viewer.plugin_); " |
| "var updatePromise = new Promise(function(resolve) { " |
| " gestureDetector.addEventListener('pinchupdate', resolve); " |
| "});")); |
| |
| zoom::ZoomChangedWatcher zoom_watcher( |
| contents, |
| base::BindRepeating( |
| [](const zoom::ZoomController::ZoomChangedEventData& event) { |
| return event.new_zoom_level > event.old_zoom_level && |
| event.zoom_mode == zoom::ZoomController::ZOOM_MODE_MANUAL && |
| !event.can_show_bubble; |
| })); |
| |
| std::move(send_events).Run(); |
| |
| bool got_update; |
| ASSERT_TRUE(content::ExecuteScriptAndExtractBool( |
| guest_contents, |
| "updatePromise.then(function(update) { " |
| " window.domAutomationController.send(!!update); " |
| "});", |
| &got_update)); |
| EXPECT_TRUE(got_update); |
| |
| zoom_watcher.Wait(); |
| } |
| |
| #if defined(OS_MACOSX) |
| |
| // Test that "smart zoom" (double-tap with two fingers on Mac trackpad) |
| // is disabled for the PDF viewer. This prevents the viewer's controls from |
| // being scaled off screen (see crbug.com/676668). |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, SmartZoomDisabled) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| ASSERT_TRUE(LoadPdf(test_pdf_url)); |
| |
| blink::WebGestureEvent smart_zoom_event( |
| blink::WebInputEvent::kGestureDoubleTap, |
| blink::WebInputEvent::kNoModifiers, |
| blink::WebInputEvent::GetStaticTimeStampForTests()); |
| smart_zoom_event.source_device = blink::kWebGestureDeviceTouchpad; |
| smart_zoom_event.data.tap.tap_count = 1; |
| |
| EXPECT_TRUE(browser()->PreHandleGestureEvent(GetActiveWebContents(), |
| smart_zoom_event)); |
| } |
| |
| // Ensure that Mac trackpad pinch events are handled by the PDF viewer. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, TrackpadPinchInvokesCustomZoom) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| base::OnceClosure send_pinch = base::BindOnce( |
| [](WebContents* guest_contents) { |
| const gfx::Rect guest_rect = guest_contents->GetContainerBounds(); |
| const gfx::Point mouse_position(guest_rect.width() / 2, |
| guest_rect.height() / 2); |
| content::SimulateGesturePinchSequence(guest_contents, mouse_position, |
| 1.23, |
| blink::kWebGestureDeviceTouchpad); |
| }, |
| guest_contents); |
| |
| EnsureCustomPinchZoomInvoked(guest_contents, GetActiveWebContents(), |
| std::move(send_pinch)); |
| } |
| |
| #else // !defined(OS_MACOSX) |
| |
| // Ensure that ctrl-wheel events are handled by the PDF viewer. |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, CtrlWheelInvokesCustomZoom) { |
| GURL test_pdf_url(embedded_test_server()->GetURL("/pdf/test.pdf")); |
| WebContents* guest_contents = LoadPdfGetGuestContents(test_pdf_url); |
| ASSERT_TRUE(guest_contents); |
| |
| base::OnceClosure send_ctrl_wheel = base::BindOnce( |
| [](WebContents* guest_contents) { |
| const gfx::Rect guest_rect = guest_contents->GetContainerBounds(); |
| const gfx::Point mouse_position(guest_rect.width() / 2, |
| guest_rect.height() / 2); |
| content::SimulateMouseWheelCtrlZoomEvent( |
| guest_contents, mouse_position, true, |
| blink::WebMouseWheelEvent::kPhaseBegan); |
| }, |
| guest_contents); |
| |
| EnsureCustomPinchZoomInvoked(guest_contents, GetActiveWebContents(), |
| std::move(send_ctrl_wheel)); |
| } |
| |
| #endif // defined(OS_MACOSX) |
| |
| IN_PROC_BROWSER_TEST_F(PDFExtensionTest, ContextMenuCoordinates) { |
| GURL url = embedded_test_server()->GetURL("/pdf/pdf_embed.html"); |
| |
| // Load page with embedded PDF and make sure it succeeds. |
| ASSERT_TRUE(LoadPdf(url)); |
| WebContents* guest_contents = nullptr; |
| WebContents* embedder_contents = GetActiveWebContents(); |
| content::BrowserPluginGuestManager* guest_manager = |
| embedder_contents->GetBrowserContext()->GetGuestManager(); |
| ASSERT_NO_FATAL_FAILURE(guest_manager->ForEachGuest( |
| embedder_contents, base::Bind(&GetGuestCallback, &guest_contents))); |
| ASSERT_NE(nullptr, guest_contents); |
| #if defined(USE_AURA) |
| // TODO(wjmaclean): In theory this should be used to make sure the hit testing |
| // for routing to the guest process works as intended. Not sure if not having |
| // this on Mac is an issue. |
| content::WaitForGuestSurfaceReady(guest_contents); |
| #endif |
| content::RenderProcessHost* guest_process_host = |
| guest_contents->GetMainFrame()->GetProcess(); |
| |
| // Get coords for mouse event. |
| content::RenderWidgetHostView* guest_view = |
| guest_contents->GetRenderWidgetHostView(); |
| gfx::Point local_context_menu_position(30, 80); |
| gfx::Point root_context_menu_position = |
| guest_view->TransformPointToRootCoordSpace(local_context_menu_position); |
| |
| auto context_menu_filter = base::MakeRefCounted<content::ContextMenuFilter>(); |
| guest_process_host->AddFilter(context_menu_filter.get()); |
| |
| ContextMenuWaiter menu_observer(content::NotificationService::AllSources()); |
| // Send mouse right-click to activate context menu. |
| content::SimulateRoutedMouseClickAt(embedder_contents, kDefaultKeyModifier, |
| blink::WebMouseEvent::Button::kRight, |
| root_context_menu_position); |
| |
| // We expect the context menu, invoked via the RenderFrameHost, to be using |
| // root view coordinates. |
| menu_observer.WaitForMenuOpenAndClose(); |
| ASSERT_EQ(root_context_menu_position.x(), menu_observer.params().x); |
| ASSERT_EQ(root_context_menu_position.y(), menu_observer.params().y); |
| |
| // We expect the IPC, received from the renderer, to be using local coords. |
| context_menu_filter->Wait(); |
| content::ContextMenuParams params = context_menu_filter->get_params(); |
| EXPECT_EQ(local_context_menu_position.x(), params.x); |
| EXPECT_EQ(local_context_menu_position.y(), params.y); |
| |
| // TODO(wjmaclean): If it ever becomes possible to filter outgoing IPCs |
| // from the RenderProcessHost, we should verify the ViewMsg_PluginActionAt |
| // message is sent with the same coordinates as in the ContextMenuParams. |
| } |