diff options
Diffstat (limited to 'toolkit/components/pdfjs/test')
36 files changed, 3301 insertions, 0 deletions
diff --git a/toolkit/components/pdfjs/test/browser.ini b/toolkit/components/pdfjs/test/browser.ini new file mode 100644 index 0000000000..b2292464da --- /dev/null +++ b/toolkit/components/pdfjs/test/browser.ini @@ -0,0 +1,59 @@ +[DEFAULT] +support-files = + file_pdfjs_test.pdf + head.js + +[browser_pdfjs_download_button.js] +[browser_pdfjs_editing_contextmenu.js] +skip-if = headless # the headless clipboard only recognizes unicode mime type + os == "win" && os_version == "6.1" # Skip on Azure - frequent failure +[browser_pdfjs_editing_telemetry.js] +[browser_pdfjs_fill_login.js] +support-files = + file_pdfjs_form.pdf +[browser_pdfjs_filters.js] +support-files = + file_pdfjs_transfer_map.pdf +[browser_pdfjs_find.js] +support-files = + file_pdfjs_object_stream.pdf + file_pdfjs_object_stream.pdf^headers^ +[browser_pdfjs_force_opening_files.js] +[browser_pdfjs_form.js] +support-files = + file_pdfjs_form.pdf +[browser_pdfjs_fullscreen.js] +[browser_pdfjs_hcm.js] +support-files = + file_pdfjs_hcm.pdf +[browser_pdfjs_js.js] +support-files = + file_pdfjs_js.pdf +[browser_pdfjs_load_telemetry.js] +[browser_pdfjs_main.js] +[browser_pdfjs_minimum_font_size.js] +[browser_pdfjs_navigation.js] +[browser_pdfjs_nonpdf_filename.js] +support-files = + file_pdf_download_link.html +[browser_pdfjs_not_default.js] +support-files = + file_pdfjs_object_stream.pdf + file_pdfjs_object_stream.pdf^headers^ +[browser_pdfjs_notification_close_on_navigation.js] +skip-if = true # see bug 1705327 +[browser_pdfjs_octet_stream.js] +skip-if = os == 'linux' && bits == 64 # Bug 1773830 +support-files = + file_pdfjs_object_stream.pdf + file_pdfjs_object_stream.pdf^headers^ +[browser_pdfjs_saveas.js] +support-files = + !/toolkit/content/tests/browser/common/mockTransfer.js + file_pdf_download_link.html +[browser_pdfjs_savedialog.js] +skip-if = verify +[browser_pdfjs_secondary_toolbar_telemetry.js] +[browser_pdfjs_views.js] +[browser_pdfjs_zoom.js] +skip-if = (verify && debug && (os == 'win')) diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js new file mode 100644 index 0000000000..c512c6529a --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_download_button.js @@ -0,0 +1,125 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); +MockFilePicker.returnValue = MockFilePicker.returnOK; + +var tempDir; + +async function promiseDownloadFinished(list) { + return new Promise(resolve => { + list.addView({ + onDownloadChanged(download) { + download.launchWhenSucceeded = false; + if (download.succeeded || download.error) { + list.removeView(this); + resolve(download); + } + }, + }); + }); +} + +function createPromiseForFilePicker() { + return new Promise(resolve => { + MockFilePicker.showCallback = fp => { + let destFile = tempDir.clone(); + destFile.append(fp.defaultString); + if (destFile.exists()) { + destFile.remove(false); + } + MockFilePicker.setFiles([destFile]); + MockFilePicker.filterIndex = 0; // kSaveAsType_Complete + resolve(); + }; + }); +} + +add_setup(async function () { + tempDir = createTemporarySaveDirectory(); + MockFilePicker.displayDirectory = tempDir; + + registerCleanupFunction(async function () { + MockFilePicker.cleanup(); + await cleanupDownloads(); + tempDir.remove(true); + }); +}); + +/** + * Check clicking the download button saves the file and doesn't open a new viewer + */ +add_task(async function test_downloading_pdf_nonprivate_window() { + const pdfUrl = TESTROOT + "file_pdfjs_test.pdf"; + + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.always_ask_before_handling_new_types", false]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJS(browser, pdfUrl); + + const tabCount = gBrowser.tabs.length; + info(`${tabCount} tabs are open at the start of the test`); + + let downloadList = await Downloads.getList(Downloads.PUBLIC); + + let filePickerShown = createPromiseForFilePicker(); + + let downloadFinishedPromise = promiseDownloadFinished(downloadList); + + info("Clicking on the download button..."); + await SpecialPowers.spawn(browser, [], () => { + content.document.querySelector("#download").click(); + }); + info("Waiting for a filename to be picked from the file picker"); + await filePickerShown; + + await downloadFinishedPromise; + ok(true, "A download was added when we clicked download"); + + // See bug 1739348 - don't show panel for downloads that opened dialogs + ok( + !DownloadsPanel.isPanelShowing, + "The download panel did not open, since the file picker was shown already" + ); + + const allDownloads = await downloadList.getAll(); + const dl = allDownloads.find(dl => dl.source.originalUrl === pdfUrl); + ok(!!dl, "The pdf download has the correct url in source.originalUrl"); + + SpecialPowers.clipboardCopyString(""); + DownloadsCommon.copyDownloadLink(dl); + const copiedUrl = SpecialPowers.getClipboardData("text/plain"); + is(copiedUrl, pdfUrl, "The copied url must be the original one"); + + is( + gBrowser.tabs.length, + tabCount, + "No new tab was opened to view the downloaded PDF" + ); + + SpecialPowers.clipboardCopyString(""); + const downloadsLoaded = BrowserTestUtils.waitForEvent( + browser, + "InitialDownloadsLoaded", + true + ); + BrowserTestUtils.loadURIString(browser, "about:downloads"); + await downloadsLoaded; + + info("Wait for the clipboard to contain the url of the pdf"); + await SimpleTest.promiseClipboardChange(pdfUrl, () => { + goDoCommand("cmd_copy"); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js new file mode 100644 index 0000000000..3879a9a4f2 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js @@ -0,0 +1,373 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +// This is a modified version from browser_contextmenuFillLogins.js. +async function openContextMenuAt(browser, x, y) { + const contextMenu = document.getElementById("contentAreaContextMenu"); + + const contextMenuShownPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popupshown" + ); + + // Synthesize a contextmenu event to actually open the context menu. + await BrowserTestUtils.synthesizeMouseAtPoint( + x, + y, + { + type: "contextmenu", + button: 2, + }, + browser + ); + + await contextMenuShownPromise; + return contextMenu; +} + +/** + * Open a context menu and get the pdfjs entries + * @param {Object} browser + * @param {Object} box + * @returns {Map<string,HTMLElement>} the pdfjs menu entries. + */ +async function getContextMenuItems(browser, box) { + return new Promise(resolve => { + setTimeout(async () => { + const { x, y, width, height } = box; + const menuitems = [ + "context-pdfjs-undo", + "context-pdfjs-redo", + "context-sep-pdfjs-redo", + "context-pdfjs-cut", + "context-pdfjs-copy", + "context-pdfjs-paste", + "context-pdfjs-delete", + "context-pdfjs-selectall", + "context-sep-pdfjs-selectall", + ]; + + await openContextMenuAt(browser, x + width / 2, y + height / 2); + const results = new Map(); + const doc = browser.ownerDocument; + for (const menuitem of menuitems) { + const item = doc.getElementById(menuitem); + results.set(menuitem, item || null); + } + + resolve(results); + }, 0); + }); +} + +/** + * Open a context menu on the element corresponding to the given selector + * and returs the pdfjs menu entries. + * @param {Object} browser + * @param {string} selector + * @returns {Map<string,HTMLElement>} the pdfjs menu entries. + */ +async function getContextMenuItemsOn(browser, selector) { + const box = await SpecialPowers.spawn( + browser, + [selector], + async function (selector) { + const element = content.document.querySelector(selector); + const { x, y, width, height } = element.getBoundingClientRect(); + return { x, y, width, height }; + } + ); + return getContextMenuItems(browser, box); +} + +/** + * Hide the context menu. + * @param {Object} browser + */ +async function hideContextMenu(browser) { + await new Promise(resolve => + setTimeout(async () => { + const doc = browser.ownerDocument; + const contextMenu = doc.getElementById("contentAreaContextMenu"); + + const popupHiddenPromise = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + contextMenu.hidePopup(); + await popupHiddenPromise; + resolve(); + }, 0) + ); +} + +async function clickOnItem(browser, items, entry) { + const editingPromise = BrowserTestUtils.waitForContentEvent( + browser, + "editingaction", + false, + null, + true + ); + const contextMenu = document.getElementById("contentAreaContextMenu"); + contextMenu.activateItem(items.get(entry)); + await editingPromise; +} + +/** + * Asserts that the enabled pdfjs menuitems are the expected ones. + * @param {Map<string,HTMLElement>} menuitems + * @param {Array<string>} expected + */ +function assertMenuitems(menuitems, expected) { + Assert.deepEqual( + [...menuitems.values()] + .filter( + elmt => + !elmt.id.includes("-sep-") && + !elmt.hidden && + elmt.getAttribute("disabled") === "false" + ) + .map(elmt => elmt.id), + expected + ); +} + +// Text copy, paste, undo, redo, delete and select all in using the context +// menu. +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + SpecialPowers.clipboardCopyString(""); + + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.annotationEditorMode", 0]], + }); + + // check that PDF is opened with internal viewer + await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [ + [ + "annotationEditorLayer", + "annotationLayer", + "textLayer", + "canvasWrapper", + ], + ["annotationEditorLayer", "textLayer", "canvasWrapper"], + ]); + + const spanBox = await getSpanBox(browser, "and found references"); + let menuitems = await getContextMenuItems(browser, spanBox); + + // Nothing have been edited, hence the context menu doesn't contain any + // pdf entries. + Assert.ok( + [...menuitems.values()].every(elmt => elmt.hidden), + "No visible pdf menuitem" + ); + await hideContextMenu(browser); + + await enableEditor(browser, "FreeText"); + await addFreeText(browser, "hello", spanBox); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 0 + ); + + await TestUtils.waitForTick(); + + Assert.equal(await countElements(browser, ".freeTextEditor"), 1); + + // Unselect. + await escape(browser); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".selectedEditor")) !== 1 + ); + + Assert.equal(await countElements(browser, ".selectedEditor"), 0); + + menuitems = await getContextMenuItems(browser, spanBox); + assertMenuitems(menuitems, [ + "context-pdfjs-undo", // Last created editor is undoable + "context-pdfjs-selectall", // and selectable. + ]); + // Undo. + await clickOnItem(browser, menuitems, "context-pdfjs-undo"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 1 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 0, + "The FreeText editor must have been removed" + ); + + menuitems = await getContextMenuItems(browser, spanBox); + + // The editor removed thanks to "undo" is now redoable + assertMenuitems(menuitems, ["context-pdfjs-redo"]); + await clickOnItem(browser, menuitems, "context-pdfjs-redo"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 0 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 1, + "The FreeText editor must have been added back" + ); + + await clickOn(browser, "#pdfjs_internal_editor_0"); + menuitems = await getContextMenuItemsOn( + browser, + "#pdfjs_internal_editor_0" + ); + + assertMenuitems(menuitems, [ + "context-pdfjs-undo", + "context-pdfjs-cut", + "context-pdfjs-copy", + "context-pdfjs-delete", + "context-pdfjs-selectall", + ]); + + await clickOnItem(browser, menuitems, "context-pdfjs-cut"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 1 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 0, + "The FreeText editor must have been cut" + ); + + menuitems = await getContextMenuItems(browser, spanBox); + assertMenuitems(menuitems, ["context-pdfjs-undo", "context-pdfjs-paste"]); + + await clickOnItem(browser, menuitems, "context-pdfjs-paste"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 0 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 1, + "The FreeText editor must have been pasted" + ); + + await clickOn(browser, "#pdfjs_internal_editor_1"); + menuitems = await getContextMenuItemsOn( + browser, + "#pdfjs_internal_editor_1" + ); + + assertMenuitems(menuitems, [ + "context-pdfjs-undo", + "context-pdfjs-cut", + "context-pdfjs-copy", + "context-pdfjs-paste", + "context-pdfjs-delete", + "context-pdfjs-selectall", + ]); + + await clickOnItem(browser, menuitems, "context-pdfjs-delete"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 1 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 0, + "The FreeText editor must have been deleted" + ); + + menuitems = await getContextMenuItems(browser, spanBox); + await clickOnItem(browser, menuitems, "context-pdfjs-paste"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 0 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 1, + "The FreeText editor must have been pasted" + ); + + await clickOn(browser, "#pdfjs_internal_editor_2"); + menuitems = await getContextMenuItemsOn( + browser, + "#pdfjs_internal_editor_2" + ); + + await clickOnItem(browser, menuitems, "context-pdfjs-copy"); + + menuitems = await getContextMenuItemsOn( + browser, + "#pdfjs_internal_editor_2" + ); + await clickOnItem(browser, menuitems, "context-pdfjs-paste"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 1 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 2, + "The FreeText editor must have been pasted" + ); + + menuitems = await getContextMenuItems(browser, spanBox); + await clickOnItem(browser, menuitems, "context-pdfjs-selectall"); + menuitems = await getContextMenuItems(browser, spanBox); + await clickOnItem(browser, menuitems, "context-pdfjs-delete"); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 2 + ); + + Assert.equal( + await countElements(browser, ".freeTextEditor"), + 0, + "All the FreeText editors must have been deleted" + ); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_editing_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_editing_telemetry.js new file mode 100644 index 0000000000..45d652c2c1 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_editing_telemetry.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +// Test telemetry. +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.annotationEditorMode", 0]], + }); + + Services.fog.testResetFOG(); + + // check that PDF is opened with internal viewer + await waitForPdfJSAllLayers(browser, TESTROOT + "file_pdfjs_test.pdf", [ + [ + "annotationEditorLayer", + "annotationLayer", + "textLayer", + "canvasWrapper", + ], + ["annotationEditorLayer", "textLayer", "canvasWrapper"], + ]); + + let spanBox = await getSpanBox(browser, "and found references"); + + await Services.fog.testFlushAllChildren(); + Assert.equal(Glean.pdfjs.editing.freetext.testGetValue() || 0, 0); + + await enableEditor(browser, "FreeText"); + await addFreeText(browser, "hello", spanBox); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 0 + ); + Assert.equal(await countElements(browser, ".freeTextEditor"), 1); + + await Services.fog.testFlushAllChildren(); + + Assert.equal(Glean.pdfjs.editing.freetext.testGetValue(), 1); + + spanBox = await getSpanBox(browser, "forums and ask questions"); + await addFreeText(browser, "world", spanBox); + + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) !== 1 + ); + Assert.equal(await countElements(browser, ".freeTextEditor"), 2); + + await Services.fog.testFlushAllChildren(); + + Assert.equal(Glean.pdfjs.editing.freetext.testGetValue(), 2); + + Assert.equal(Glean.pdfjs.editing.print.testGetValue() || 0, 0); + document.getElementById("cmd_print").doCommand(); + await BrowserTestUtils.waitForCondition(() => { + let preview = document.querySelector(".printPreviewBrowser"); + return preview && BrowserTestUtils.is_visible(preview); + }); + EventUtils.synthesizeKey("KEY_Escape"); + + await Services.fog.testFlushAllChildren(); + + Assert.equal(Glean.pdfjs.editing.print.testGetValue(), 1); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_fill_login.js b/toolkit/components/pdfjs/test/browser_pdfjs_fill_login.js new file mode 100644 index 0000000000..b31b9b607c --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_fill_login.js @@ -0,0 +1,108 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +// This is a modified version from browser_contextmenuFillLogins.js. +async function openContextMenuForSelector(browser, selector) { + const doc = browser.ownerDocument; + const CONTEXT_MENU = doc.getElementById("contentAreaContextMenu"); + + let contextMenuShownPromise = BrowserTestUtils.waitForEvent( + CONTEXT_MENU, + "popupshown" + ); + + let inputCoords = await SpecialPowers.spawn( + browser, + [selector], + async selector => { + let input = content.document.querySelector(selector); + input.focus(); + let inputRect = input.getBoundingClientRect(); + + // listen for the contextmenu event so we can assert on receiving it + // and examine the target + content.contextmenuPromise = new Promise(resolve => { + content.document.body.addEventListener( + "contextmenu", + event => { + info( + `Received event on target: ${event.target.nodeName}, type: ${event.target.type}` + ); + content.console.log("got contextmenu event: ", event); + resolve(event); + }, + { once: true } + ); + }); + + let coords = { + x: inputRect.x + inputRect.width / 2, + y: inputRect.y + inputRect.height / 2, + }; + return coords; + } + ); + + // add the offsets of the <browser> in the chrome window + let browserOffsets = browser.getBoundingClientRect(); + let offsetX = browserOffsets.x + inputCoords.x; + let offsetY = browserOffsets.y + inputCoords.y; + + // Synthesize a right mouse click over the input element, we have to trigger + // both events because formfill code relies on this event happening before the contextmenu + // (which it does for real user input) in order to not show the password autocomplete. + let eventDetails = { type: "mousedown", button: 2 }; + await EventUtils.synthesizeMouseAtPoint(offsetX, offsetY, eventDetails); + + // Synthesize a contextmenu event to actually open the context menu. + eventDetails = { type: "contextmenu", button: 2 }; + await EventUtils.synthesizeMouseAtPoint(offsetX, offsetY, eventDetails); + + await SpecialPowers.spawn(browser, [], async () => { + await content.contextmenuPromise; + }); + + info("waiting for contextMenuShownPromise"); + + await contextMenuShownPromise; + return CONTEXT_MENU; +} + +/** + * Ensure the fill login context menu is not shown for PDF.js forms. + */ +add_task(async function test_filllogin() { + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.renderInteractiveForms", true]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + + let contextMenu = await openContextMenuForSelector( + browser, + "#viewerContainer input" + ); + let fillItem = contextMenu.querySelector("#fill-login"); + ok(fillItem, "fill menu item exists"); + ok(fillItem && EventUtils.isHidden(fillItem), "fill menu item is hidden"); + + let promiseHidden = BrowserTestUtils.waitForEvent( + contextMenu, + "popuphidden" + ); + info("Calling hidePopup on contextMenu"); + contextMenu.hidePopup(); + info("waiting for promiseHidden"); + await promiseHidden; + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_filters.js b/toolkit/components/pdfjs/test/browser_pdfjs_filters.js new file mode 100644 index 0000000000..87d1104a84 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_filters.js @@ -0,0 +1,81 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +/** + * Get the number of red pixels in the canvas. + * @param {Object} browser + * @returns {Object} + */ +async function getRedPixels(browser) { + return SpecialPowers.spawn(browser, [], async function () { + const { document } = content; + const canvas = document.querySelector("canvas"); + + Assert.ok(!!canvas, "We must have a canvas"); + + const data = canvas + .getContext("2d") + .getImageData(0, 0, canvas.width, canvas.height).data; + + let redPixels = 0; + let total = 0; + + for (let i = 0, ii = data.length; i < ii; i += 4) { + const R = data[i]; + const G = data[i + 1]; + const B = data[i + 2]; + + if (R > 128 && R > 4 * G && R > 4 * B) { + redPixels += 1; + } + total += 1; + } + + return [redPixels, total]; + }); +} + +/** + * Test that the pdf has the correct color thanks to the SVG filters. + */ +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSCanvas( + browser, + `${TESTROOT}file_pdfjs_transfer_map.pdf#zoom=100` + ); + + const [redPixels, total] = await getRedPixels(browser); + Assert.ok( + redPixels / total >= 0.1, + `Not enough red pixels: only ${redPixels} / ${total} red pixels!` + ); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_find.js b/toolkit/components/pdfjs/test/browser_pdfjs_find.js new file mode 100644 index 0000000000..abcc89b26e --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_find.js @@ -0,0 +1,137 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TESTROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" +); + +// Get a ref to the pdfs we want to open. +const OS_PDF_URL = TESTROOT + "file_pdfjs_object_stream.pdf"; +const TEST_PDF_URL = TESTROOT + "file_pdfjs_test.pdf"; + +add_task(async function test_find_octet_stream_pdf() { + await BrowserTestUtils.withNewTab(OS_PDF_URL, async browser => { + let findEls = ["cmd_find", "cmd_findAgain", "cmd_findPrevious"].map(id => + document.getElementById(id) + ); + for (let el of findEls) { + ok(!el.hasAttribute("disabled"), `${el.id} should be enabled`); + } + }); +}); + +// This code is roughly based on `promiseFindFinished` from +// `toolkit/content/tests/browser/head.js`. +function waitForFinderResult(findbar) { + return new Promise(resolve => { + let resultListener = { + onFindResult(data) { + findbar.browser.finder.removeResultListener(resultListener); + resolve(data); + }, + onCurrentSelection() {}, + onMatchesCountResult() {}, + onHighlightFinished() {}, + }; + findbar.browser.finder.addResultListener(resultListener); + }); +} + +function waitForPdfjsResult(findbar) { + // FIXME: This is a pretty sketchy way to intercept the results from pdfjs... + return new Promise(resolve => { + let oldUpdateControlState = findbar.updateControlState; + function updateControlState(result, findPrevious) { + if (result !== Ci.nsITypeAheadFind.FIND_PENDING) { + resolve({ result, findPrevious }); + if (this.updateControlState === updateControlState) { + this.updateControlState = oldUpdateControlState; + } + } + return oldUpdateControlState.call(this, result, findPrevious); + } + findbar.updateControlState = updateControlState; + }); +} + +// This code is roughly based on `promiseFindFinished` from +// `toolkit/content/tests/browser/head.js`. +async function doFind(findbar, searchText, waitFunc) { + info(`performing a find for ${searchText}`); + findbar.startFind(findbar.FIND_NORMAL); + let highlightElement = findbar.getElement("highlight"); + if (highlightElement.checked) { + highlightElement.click(); + } + await new Promise(resolve => executeSoon(resolve)); + findbar._findField.value = searchText; + let promise = waitFunc(findbar); + findbar._find(); + return promise; +} + +add_task(async function test_findbar_in_pdf() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PDF_URL); + let findbar = await gBrowser.getFindBar(tab); + let findResult = await doFind(findbar, "Mozilla", waitForPdfjsResult); + is( + findResult.result, + Ci.nsITypeAheadFind.FIND_FOUND, + "The Mozilla string was found in the PDF document" + ); + BrowserTestUtils.removeTab(tab); +}); + +add_task(async function test_findbar_in_pdf_after_adopt() { + let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, TEST_PDF_URL); + let newWindow = await BrowserTestUtils.openNewBrowserWindow(); + + info("adopting tab into new window"); + let newTab = newWindow.gBrowser.adoptTab(tab); + + let findbar = await newWindow.gBrowser.getFindBar(newTab); + let findResult = await doFind(findbar, "Mozilla", waitForPdfjsResult); + is( + findResult.result, + Ci.nsITypeAheadFind.FIND_FOUND, + "The Mozilla string was found in the PDF document" + ); + await BrowserTestUtils.closeWindow(newWindow); +}); + +// Make sure that performing a find in the browser continues to work after +// navigating to another page (i.e. Pdfjs disables its pdfjs interception +// listeners). +add_task(async function test_findbar_after_navigate() { + await BrowserTestUtils.withNewTab(TEST_PDF_URL, async browser => { + let tab = gBrowser.getTabForBrowser(browser); + + ok( + !gBrowser.isFindBarInitialized(tab), + "Findbar shouldn't be initialized yet" + ); + + info("navigating to a webpage"); + BrowserTestUtils.loadURIString( + browser, + "http://mochi.test:8888/document-builder.sjs?html=<h1>hello, world!</h1>" + ); + await BrowserTestUtils.browserLoaded(browser); + + ok( + !gBrowser.isFindBarInitialized(tab), + "Findbar still shouldn't be initialized after navigation" + ); + + let findbar = await gBrowser.getFindBar(tab); + let findResult = await doFind(findbar, "hello", waitForFinderResult); + is( + findResult.result, + Ci.nsITypeAheadFind.FIND_FOUND, + "The hello string was found in the HTML document" + ); + }); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_force_opening_files.js b/toolkit/components/pdfjs/test/browser_pdfjs_force_opening_files.js new file mode 100644 index 0000000000..0490a6eec3 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_force_opening_files.js @@ -0,0 +1,89 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function test_file_opening() { + // Get a ref to the pdf we want to open. + let dirFileObj = getChromeDir(getResolvedURI(gTestPath)); + dirFileObj.append("file_pdfjs_test.pdf"); + + // Change the defaults. + var oldAction = changeMimeHandler(Ci.nsIHandlerInfo.useSystemDefault, true); + + // Test: "Open with" dialog should not come up, despite pdf.js not being + // the default - because files from disk should always use pdfjs, unless + // it is forcibly disabled. + let openedWindow = false; + let windowOpenedPromise = new Promise((resolve, reject) => { + addWindowListener( + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + () => { + openedWindow = true; + resolve(); + } + ); + }); + + // Open the tab with a system principal: + var tab = BrowserTestUtils.addTab(gBrowser, dirFileObj.path); + + let pdfjsLoadedPromise = TestUtils.waitForCondition(() => { + let { contentPrincipal } = tab.linkedBrowser; + return (contentPrincipal?.URI?.spec || "").endsWith("viewer.html"); + }); + await Promise.race([pdfjsLoadedPromise, windowOpenedPromise]); + ok(!openedWindow, "Shouldn't open an unknownContentType window!"); + + BrowserTestUtils.removeTab(tab); + + // Now try opening it from the file directory: + tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + dirFileObj.parent.path + ); + pdfjsLoadedPromise = BrowserTestUtils.browserLoaded( + tab.linkedBrowser, + false, + url => url.endsWith("test.pdf") + ); + await SpecialPowers.spawn(tab.linkedBrowser, [], () => { + content.document.querySelector("a[href$='test.pdf']").click(); + }); + await Promise.race([pdfjsLoadedPromise, windowOpenedPromise]); + ok( + !openedWindow, + "Shouldn't open an unknownContentType window for PDFs from file: links!" + ); + + registerCleanupFunction(function () { + if (listenerCleanup) { + listenerCleanup(); + } + changeMimeHandler(oldAction[0], oldAction[1]); + gBrowser.removeTab(tab); + }); +}); + +let listenerCleanup; +function addWindowListener(aURL, aCallback) { + let listener = { + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + listenerCleanup(); + listenerCleanup = null; + + var domwindow = aXULWindow.docShell.domWindow; + waitForFocus(function () { + is( + domwindow.document.location.href, + aURL, + "should have seen the right window open" + ); + domwindow.close(); + aCallback(); + }, domwindow); + }, + onCloseWindow(aXULWindow) {}, + }; + Services.wm.addListener(listener); + listenerCleanup = () => Services.wm.removeListener(listener); +} diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_form.js b/toolkit/components/pdfjs/test/browser_pdfjs_form.js new file mode 100644 index 0000000000..380c812b8b --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_form.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +async function checkFormState(formsEnabled) { + ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + + let formInput = content.document.querySelector("#viewerContainer input"); + + if (formsEnabled) { + ok(formInput, "Form input available"); + } else { + ok(!formInput, "Form input not available"); + } + let viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); +} + +add_task(async function test_defaults() { + // Ensure the default preference is set to the expected value. + let defaultBranch = Services.prefs.getDefaultBranch("pdfjs."); + let prefType = defaultBranch.getPrefType("annotationMode"); + let renderForms = Services.prefs.getIntPref("pdfjs.annotationMode") === 2; + + is( + prefType, + Ci.nsIPrefBranch.PREF_INT, + "The form pref is defined by default" + ); + + ok(renderForms, "Forms are enabled"); + + // Test that the forms state matches the pref. + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + + await SpecialPowers.spawn(browser, [true], checkFormState); + } + ); +}); + +// Test disabling forms with pref. +add_task(async function test_disabling() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // First, make sure they are enabled. + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.annotationMode", 2]], + }); + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + await SpecialPowers.spawn( + browser, + [true /* formsEnabled */], + checkFormState + ); + // Now disable the forms. + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.annotationMode", 1]], + }); + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + await SpecialPowers.spawn( + browser, + [false /* formsEnabled */], + checkFormState + ); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_fullscreen.js b/toolkit/components/pdfjs/test/browser_pdfjs_fullscreen.js new file mode 100644 index 0000000000..d53e4cfa74 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_fullscreen.js @@ -0,0 +1,84 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +function waitForFullScreenState(browser, state) { + return new Promise(resolve => { + let eventReceived = false; + + let observe = (subject, topic, data) => { + if (!eventReceived) { + return; + } + Services.obs.removeObserver(observe, "fullscreen-painted"); + resolve(); + }; + Services.obs.addObserver(observe, "fullscreen-painted"); + + browser.ownerGlobal.addEventListener( + `MozDOMFullscreen:${state ? "Entered" : "Exited"}`, + () => { + eventReceived = true; + }, + { once: true } + ); + }); +} + +/** + * Test that we can enter in presentation mode in using the keyboard shortcut + * without having to interact with the pdf. + */ +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSCanvas(browser, `${TESTROOT}file_pdfjs_test.pdf`); + + const fullscreenPromise = waitForFullScreenState(browser, true); + + await SpecialPowers.spawn(browser, [], async function () { + const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" + ); + const EventUtils = ContentTaskUtils.getEventUtils(content); + await EventUtils.synthesizeKey( + "p", + { ctrlKey: true, altKey: true }, + content + ); + }); + + await fullscreenPromise; + ok(true, "Should be in fullscreen"); + + await SpecialPowers.spawn(browser, [], async function () { + const viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_hcm.js b/toolkit/components/pdfjs/test/browser_pdfjs_hcm.js new file mode 100644 index 0000000000..cc5f098f1e --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_hcm.js @@ -0,0 +1,104 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +/** + * Get the first and last pixels on the drawn canvas. + * @param {Object} browser + * @returns {Object} + */ +async function getFirstLastPixels(browser) { + return SpecialPowers.spawn(browser, [], async function () { + const { document } = content; + const canvas = document.querySelector("canvas"); + + Assert.ok(!!canvas, "We must have a canvas"); + + const data = Array.from( + canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height) + .data + ); + + return { + first: data.slice(0, 3), + last: data.slice(-4, -1), + }; + }); +} + +/** + * Test that the pdf has the correct color depending on the high contrast mode. + */ +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.display.document_color_use", 0], + ["browser.display.background_color", "#ff0000"], + ["browser.display.foreground_color", "#00ff00"], + ["browser.display.use_system_colors", false], + ], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSCanvas( + browser, + `${TESTROOT}file_pdfjs_hcm.pdf#zoom=800` + ); + + const { first, last } = await getFirstLastPixels(browser); + // The first pixel must be black and not green. + Assert.deepEqual(first, [0, 0, 0]); + + // The last pixel must be white and not red. + Assert.deepEqual(last, [255, 255, 255]); + } + ); + + // Enable HCM. + await SpecialPowers.pushPrefEnv({ + set: [["ui.useAccessibilityTheme", 1]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSCanvas( + browser, + `${TESTROOT}file_pdfjs_hcm.pdf#zoom=800` + ); + + const { first, last } = await getFirstLastPixels(browser); + // The first pixel must be green. + Assert.deepEqual(first, [0, 255, 0]); + + // The last pixel must be red. + Assert.deepEqual(last, [255, 0, 0]); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_js.js b/toolkit/components/pdfjs/test/browser_pdfjs_js.js new file mode 100644 index 0000000000..67909623cd --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_js.js @@ -0,0 +1,49 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +// Test js in pdf file. +add_task(async function test_js_sandbox() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.enableScripting", true]], + }); + + await Promise.all([ + waitForPdfJSAnnotationLayer(browser, TESTROOT + "file_pdfjs_js.pdf"), + waitForPdfJSSandbox(browser), + ]); + + await SpecialPowers.spawn(browser, [], async () => { + const { PdfSandbox } = ChromeUtils.importESModule( + "resource://pdf.js/PdfSandbox.sys.mjs" + ); + + let sandboxDestroyCount = 0; + const originalDestroy = PdfSandbox.prototype.destroy; + PdfSandbox.prototype.destroy = function () { + const obj = this.sandbox.eval("({})"); + originalDestroy.apply(this, arguments); + sandboxDestroyCount++; + ok(Cu.isDeadWrapper(obj), "Sandbox must have been nuked"); + }; + + const document = content.document; + const button = document.querySelector("[data-annotation-id='16R'] a"); + button.dispatchEvent(new content.Event("click")); + + const text = document.querySelector(`[data-element-id="15R"]`); + + is(text.value, "test", "Text field must containt 'test' string"); + + content.addEventListener("unload", () => { + is(sandboxDestroyCount, 1, "Sandbox must have been destroyed"); + }); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_load_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_load_telemetry.js new file mode 100644 index 0000000000..61a4dba290 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_load_telemetry.js @@ -0,0 +1,51 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +// Test telemetry. +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + Services.fog.testResetFOG(); + + await Services.fog.testFlushAllChildren(); + Assert.equal(Glean.pdfjs.timeToView.testGetValue(), undefined); + Assert.equal(Glean.pdfjs.used.testGetValue() || 0, 0); + + // check that PDF is opened with internal viewer + await waitForPdfJSCanvas(browser, TESTROOT + "file_pdfjs_test.pdf"); + + await Services.fog.testFlushAllChildren(); + Assert.ok(Glean.pdfjs.timeToView.testGetValue().sum !== 0); + Assert.equal(Glean.pdfjs.used.testGetValue(), 1); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_main.js b/toolkit/components/pdfjs/test/browser_pdfjs_main.js new file mode 100644 index 0000000000..c2f2122268 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_main.js @@ -0,0 +1,72 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newTabBrowser) { + await waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf"); + + await SpecialPowers.spawn(newTabBrowser, [], async function () { + // Overall sanity tests + Assert.ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + + // Sidebar: open + var sidebar = content.document.querySelector("button#sidebarToggle"), + outerContainer = content.document.querySelector("div#outerContainer"); + + sidebar.click(); + Assert.ok( + outerContainer.classList.contains("sidebarOpen"), + "sidebar opens on click" + ); + + // Sidebar: close + sidebar.click(); + Assert.ok( + !outerContainer.classList.contains("sidebarOpen"), + "sidebar closes on click" + ); + + // Verify that initial page is 1 + var pgNumber = content.document.querySelector("input#pageNumber").value; + Assert.equal(parseInt(pgNumber, 10), 1, "initial page is 1"); + + // Bookmark button + var viewBookmark = content.document.querySelector("a#viewBookmark"); + viewBookmark.click(); + + Assert.ok(!!viewBookmark.href.length, "viewBookmark button has href"); + + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_minimum_font_size.js b/toolkit/components/pdfjs/test/browser_pdfjs_minimum_font_size.js new file mode 100644 index 0000000000..f85e0c3ec6 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_minimum_font_size.js @@ -0,0 +1,70 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +/** + * Test that the pdf doesn't use the minimum font size. + */ +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await SpecialPowers.pushPrefEnv({ + set: [["font.minimum-size.x-western", 56]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJSAllLayers( + browser, + `${TESTROOT}file_pdfjs_test.pdf#zoom=10`, + [] + ); + + await SpecialPowers.spawn(browser, [true], () => { + const pageNumberInput = content.document.querySelector("#pageNumber"); + const { style } = pageNumberInput; + const baseHeight = + content.document.defaultView.getComputedStyle(pageNumberInput).height; + + style.fontSize = "1px"; + const height = + content.document.defaultView.getComputedStyle(pageNumberInput).height; + + // Changing the font size should change the height of the input + // element. If the minimum font size is used, the height won't change. + Assert.notEqual( + baseHeight, + height, + "The pdf viewer musn't use the minimum font size." + ); + }); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_navigation.js b/toolkit/components/pdfjs/test/browser_pdfjs_navigation.js new file mode 100644 index 0000000000..9851150e80 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_navigation.js @@ -0,0 +1,314 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +requestLongerTimeout(2); + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +const PDF_OUTLINE_ITEMS = 17; +const TESTS = [ + { + action: { + selector: "button#next", + event: "click", + }, + expectedPage: 2, + message: "navigated to next page using NEXT button", + }, + { + action: { + selector: "button#previous", + event: "click", + }, + expectedPage: 1, + message: "navigated to previous page using PREV button", + }, + { + action: { + selector: "button#next", + event: "click", + }, + expectedPage: 2, + message: "navigated to next page using NEXT button", + }, + { + action: { + selector: "input#pageNumber", + value: 1, + event: "change", + }, + expectedPage: 1, + message: "navigated to first page using pagenumber", + }, + { + action: { + selector: "#thumbnailView a:nth-child(4)", + event: "click", + }, + expectedPage: 4, + message: "navigated to 4th page using thumbnail view", + }, + { + action: { + selector: "#thumbnailView a:nth-child(2)", + event: "click", + }, + expectedPage: 2, + message: "navigated to 2nd page using thumbnail view", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 36, + }, + expectedPage: 1, + message: "navigated to 1st page using 'home' key", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 34, + }, + expectedPage: 2, + message: "navigated to 2nd page using 'Page Down' key", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 33, + }, + expectedPage: 1, + message: "navigated to 1st page using 'Page Up' key", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 39, + }, + expectedPage: 2, + message: "navigated to 2nd page using 'right' key", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 37, + }, + expectedPage: 1, + message: "navigated to 1st page using 'left' key", + }, + { + action: { + selector: "#viewer", + event: "keydown", + keyCode: 35, + }, + expectedPage: 5, + message: "navigated to last page using 'home' key", + }, + { + action: { + selector: "#outlineView .treeItem:nth-child(1) a", + event: "click", + }, + expectedPage: 1, + message: "navigated to 1st page using outline view", + }, + { + action: { + selector: "#outlineView .treeItem:nth-child(" + PDF_OUTLINE_ITEMS + ") a", + event: "click", + }, + expectedPage: 4, + message: "navigated to 4th page using outline view", + }, + { + action: { + selector: "input#pageNumber", + value: 5, + event: "change", + }, + expectedPage: 5, + message: "navigated to 5th page using pagenumber", + }, +]; + +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newTabBrowser) { + await waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf"); + + await SpecialPowers.spawn(newTabBrowser, [], async function () { + // Check if PDF is opened with internal viewer + Assert.ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + }); + + await SpecialPowers.spawn(newTabBrowser, [], contentSetUp); + + await runTests(newTabBrowser); + + await SpecialPowers.spawn(newTabBrowser, [], async function () { + let pageNumber = content.document.querySelector("input#pageNumber"); + Assert.equal( + pageNumber.value, + pageNumber.max, + "Document is left on the last page" + ); + }); + } + ); +}); + +async function contentSetUp() { + /** + * Outline Items gets appended to the document later on we have to + * wait for them before we start to navigate though document + * + * @param document + * @returns {deferred.promise|*} + */ + function waitForOutlineItems(document) { + return new Promise((resolve, reject) => { + document.addEventListener( + "outlineloaded", + function (evt) { + var outlineCount = evt.detail.outlineCount; + + if ( + document.querySelectorAll("#outlineView .treeItem").length === + outlineCount + ) { + resolve(); + } else { + reject("Unable to find outline items."); + } + }, + { once: true } + ); + }); + } + + /** + * The key navigation has to happen in page-fit, otherwise it won't scroll + * through a complete page + * + * @param document + * @returns {deferred.promise|*} + */ + function setZoomToPageFit(document) { + return new Promise(resolve => { + document.addEventListener( + "pagerendered", + function (e) { + document.querySelector("#viewer").click(); + resolve(); + }, + { once: true } + ); + + var select = document.querySelector("select#scaleSelect"); + select.selectedIndex = 2; + select.dispatchEvent(new content.Event("change")); + }); + } + + await waitForOutlineItems(content.document); + await setZoomToPageFit(content.document); +} + +/** + * As the page changes asynchronously, we have to wait for the event after + * we trigger the action so we will be at the expected page number after each action + * + * @param document + * @param window + * @param test + * @param callback + */ +async function runTests(browser) { + await SpecialPowers.spawn(browser, [TESTS], async function (contentTESTS) { + let window = content; + let document = window.document; + + for (let test of contentTESTS) { + let deferred = {}; + deferred.promise = new Promise((resolve, reject) => { + deferred.resolve = resolve; + deferred.reject = reject; + }); + + let pageNumber = document.querySelector("input#pageNumber"); + + // Add an event-listener to wait for page to change, afterwards resolve the promise + let timeout = window.setTimeout(() => deferred.reject(), 5000); + window.addEventListener("pagechanging", function pageChange() { + if (pageNumber.value == test.expectedPage) { + window.removeEventListener("pagechanging", pageChange); + window.clearTimeout(timeout); + deferred.resolve(+pageNumber.value); + } + }); + + // Get the element and trigger the action for changing the page + var el = document.querySelector(test.action.selector); + Assert.ok(el, "Element '" + test.action.selector + "' has been found"); + + // The value option is for input case + if (test.action.value) { + el.value = test.action.value; + } + + // Dispatch the event for changing the page + var ev; + if (test.action.event == "keydown") { + ev = new window.KeyboardEvent("keydown", { + bubbles: true, + cancelable: true, + view: null, + keyCode: test.action.keyCode, + charCode: 0, + }); + el.dispatchEvent(ev); + } else { + ev = new content.Event(test.action.event); + } + el.dispatchEvent(ev); + + let pgNumber = await deferred.promise; + Assert.equal(pgNumber, test.expectedPage, test.message); + } + + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); +} diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js new file mode 100644 index 0000000000..ee08cd45d1 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TESTROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "https://example.com/" +); + +// The page we use to open the PDF. +const LINK_PAGE_URL = TESTROOT + "file_pdf_download_link.html"; + +add_task(async function test_filename_nonpdf_extension() { + var MockFilePicker = SpecialPowers.MockFilePicker; + MockFilePicker.init(window); + let filepickerNamePromise = new Promise(resolve => { + MockFilePicker.showCallback = function (fp) { + resolve(fp.defaultString); + return MockFilePicker.returnCancel; + }; + }); + registerCleanupFunction(() => MockFilePicker.cleanup()); + + await SpecialPowers.pushPrefEnv({ + set: [["browser.download.open_pdf_attachments_inline", true]], + }); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: LINK_PAGE_URL }, + async function (browser) { + await SpecialPowers.spawn(browser, [], async () => { + let link = content.document.getElementById("custom_filename"); + let response = await content.fetch(link.href, { + method: "GET", + headers: { + "Content-Type": "application/pdf", + }, + }); + let blob = await response.blob(); + const url = content.URL.createObjectURL(blob); + link.href = url; + link.download = "Fido-2022-04-28"; + link.rel = "noopener"; + }); + + let pdfLoaded = BrowserTestUtils.waitForNewTab( + gBrowser, + url => url.startsWith("blob:"), + true + ); + await BrowserTestUtils.synthesizeMouseAtCenter( + "#custom_filename", + {}, + browser + ); + let newTab = await pdfLoaded; + + info("Clicking on the download button..."); + await SpecialPowers.spawn(newTab.linkedBrowser, [], () => { + content.document.getElementById("download").click(); + }); + info("Waiting for a filename to be picked from the file picker"); + let defaultName = await filepickerNamePromise; + is( + defaultName, + "Fido-2022-04-28.pdf", + "Should have gotten the provided filename with pdf suffixed." + ); + BrowserTestUtils.removeTab(newTab); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_not_default.js b/toolkit/components/pdfjs/test/browser_pdfjs_not_default.js new file mode 100644 index 0000000000..ea05348bc9 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_not_default.js @@ -0,0 +1,26 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +add_task(async function test_pdfjs_not_default() { + var oldAction = changeMimeHandler(Ci.nsIHandlerInfo.useSystemDefault, true); + let dirFileObj = getChromeDir(getResolvedURI(gTestPath)); + dirFileObj.append("file_pdfjs_test.pdf"); + + let tab = await BrowserTestUtils.openNewForegroundTab( + gBrowser, + dirFileObj.path + ); + + // If we don't have the Pdfjs actor loaded, this will throw + await getPdfjsActor(); + + changeMimeHandler(oldAction[0], oldAction[1]); + + gBrowser.removeTab(tab); +}); + +function getPdfjsActor() { + let win = Services.wm.getMostRecentWindow("navigator:browser"); + let selectedBrowser = win.gBrowser.selectedBrowser; + return selectedBrowser.browsingContext.currentWindowGlobal.getActor("Pdfjs"); +} diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_notification_close_on_navigation.js b/toolkit/components/pdfjs/test/browser_pdfjs_notification_close_on_navigation.js new file mode 100644 index 0000000000..ac96b4418c --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_notification_close_on_navigation.js @@ -0,0 +1,43 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +add_task(async function test_notification_is_removed_upon_navigation() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + + // Trigger the PDFjs fallback notification bar + await ContentTask.spawn(browser, null, async () => { + content.wrappedJSObject.PDFViewerApplication.fallback(); + }); + + let notification = await TestUtils.waitForCondition( + () => + browser && + gBrowser.getNotificationBox(browser) && + gBrowser.getNotificationBox(browser).currentNotification, + "waiting for notification" + ); + ok(notification, "A notification should be shown"); + is( + notification.getAttribute("value"), + "pdfjs-fallback", + "Notification should be pdfjs-fallback" + ); + + BrowserTestUtils.loadURIString(browser, "https://example.com"); + + await TestUtils.waitForCondition( + () => !notification.parentNode, + "notification should be removed upon navigation to example.com" + ); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js new file mode 100644 index 0000000000..d2b4fe310f --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js @@ -0,0 +1,146 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TESTROOT = getRootDirectory(gTestPath).replace( + "chrome://mochitests/content/", + "http://mochi.test:8888/" +); + +// Get a ref to the pdf we want to open. +const PDF_URL = TESTROOT + "file_pdfjs_object_stream.pdf"; + +var gMIMEService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + +/** + * Check that if we open a PDF with octet-stream mimetype, it can load + * PDF.js . + */ +add_task(async function test_octet_stream_opens_pdfjs() { + await SpecialPowers.pushPrefEnv({ set: [["pdfjs.handleOctetStream", true]] }); + let handlerInfo = gMIMEService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newTabBrowser) { + await waitForPdfJS(newTabBrowser, PDF_URL); + is(newTabBrowser.currentURI.spec, PDF_URL, "Should load pdfjs"); + } + ); +}); + +/** + * Check that if the octet-stream thing is in a frame, we don't load it inside PDF.js + */ +add_task(async function test_octet_stream_in_frame() { + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.handleOctetStream", true]], + }); + + let handlerInfo = gMIMEService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + let downloadsPanelPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popupshown" + ); + + // Once downloaded, the PDF will be opened as a file:// URI in a new tab + let previewTabPromise = BrowserTestUtils.waitForNewTab( + gBrowser, + url => { + let uri = NetUtil.newURI(url); + return uri.scheme == "file" && uri.spec.endsWith(".pdf"); + }, + false, // dont wait for load + true // any tab, not just the next one + ); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: `data:text/html,<iframe src='${PDF_URL}'>` }, + async function (newTabBrowser) { + // wait until downloadsPanel opens before continuing with test + info("Waiting for download panel to open"); + await downloadsPanelPromise; + is( + DownloadsPanel.panel.state, + "open", + "Check the download panel state is 'open'" + ); + let downloadList = await Downloads.getList(Downloads.PUBLIC); + let [download] = downloadList._downloads; + + // Verify the downloaded PDF opened in a new tab, + // with its download file URI + info("Waiting for preview tab"); + let previewTab = await previewTabPromise; + ok(previewTab, "PDF opened in a new tab"); + + is(DownloadsPanel.isPanelShowing, true, "DownloadsPanel should be open."); + is( + downloadList._downloads.length, + 1, + "File should be successfully downloaded." + ); + + await BrowserTestUtils.removeTab(previewTab); + + info("cleaning up downloads"); + try { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (error) { + info("The file " + download.target.path + " is not removed, " + error); + } + await downloadList.remove(download); + await download.finalize(); + + if (DownloadsPanel.panel.state !== "closed") { + let hiddenPromise = BrowserTestUtils.waitForEvent( + DownloadsPanel.panel, + "popuphidden" + ); + DownloadsPanel.hidePanel(); + await hiddenPromise; + } + is( + DownloadsPanel.panel.state, + "closed", + "Check that the download panel is closed" + ); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js new file mode 100644 index 0000000000..d6c10619ec --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js @@ -0,0 +1,172 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +var MockFilePicker = SpecialPowers.MockFilePicker; +MockFilePicker.init(window); + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js", + this +); + +function createPromiseForTransferComplete(expectedFileName, destFile) { + return new Promise(resolve => { + MockFilePicker.showCallback = fp => { + info("Filepicker shown, checking filename"); + is(fp.defaultString, expectedFileName, "Filename should be correct."); + let fileName = fp.defaultString; + destFile.append(fileName); + + MockFilePicker.setFiles([destFile]); + MockFilePicker.filterIndex = 0; // kSaveAsType_Complete + + MockFilePicker.showCallback = null; + mockTransferCallback = function (downloadSuccess) { + ok(downloadSuccess, "File should have been downloaded successfully"); + mockTransferCallback = () => {}; + resolve(); + }; + }; + }); +} + +let tempDir = createTemporarySaveDirectory(); + +add_setup(async function () { + mockTransferRegisterer.register(); + registerCleanupFunction(function () { + mockTransferRegisterer.unregister(); + MockFilePicker.cleanup(); + tempDir.remove(true); + }); +}); + +/** + * Check triggering "Save Page As" on a non-forms PDF opens the "Save As" dialog + * and successfully saves the file. + */ +add_task(async function test_pdf_saveas() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf"); + let destFile = tempDir.clone(); + MockFilePicker.displayDirectory = tempDir; + let fileSavedPromise = createPromiseForTransferComplete( + "file_pdfjs_test.pdf", + destFile + ); + saveBrowser(browser); + await fileSavedPromise; + } + ); +}); + +/** + * Check triggering "Save Page As" on a PDF with forms that has been modified + * does the following: + * 1) opens the "Save As" dialog + * 2) successfully saves the file + * 3) the new file contains the new form data + */ +add_task(async function test_pdf_saveas_forms() { + await SpecialPowers.pushPrefEnv({ + set: [["pdfjs.renderInteractiveForms", true]], + }); + let destFile = tempDir.clone(); + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJSAnnotationLayer( + browser, + TESTROOT + "file_pdfjs_form.pdf" + ); + // Fill in the form input field. + await SpecialPowers.spawn(browser, [], async function () { + let formInput = content.document.querySelector( + "#viewerContainer input" + ); + ok(formInput, "PDF contains text field."); + is(formInput.value, "", "Text field is empty to start."); + formInput.value = "test"; + formInput.dispatchEvent(new content.window.Event("input")); + }); + + MockFilePicker.displayDirectory = tempDir; + let fileSavedPromise = createPromiseForTransferComplete( + "file_pdfjs_form.pdf", + destFile + ); + saveBrowser(browser); + await fileSavedPromise; + } + ); + + // Now that the file has been modified and saved, load it to verify the form + // data persisted. + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + await waitForPdfJSAnnotationLayer(browser, NetUtil.newURI(destFile).spec); + await SpecialPowers.spawn(browser, [], async function () { + let formInput = content.document.querySelector( + "#viewerContainer input" + ); + ok(formInput, "PDF contains text field."); + is(formInput.value, "test", "Text field is filled in."); + }); + } + ); +}); + +/** + * Check triggering "Save Page As" on a PDF which was loaded with a custom + * content disposition filename defaults to using the provided filename. + */ +add_task(async function test_pdf_saveas_customname() { + await SpecialPowers.pushPrefEnv({ + set: [ + ["browser.helperApps.showOpenOptionForPdfJS", true], + ["browser.helperApps.showOpenOptionForViewableInternally", true], + ["browser.download.always_ask_before_handling_new_types", false], + ["browser.download.open_pdf_attachments_inline", true], + ], + }); + await BrowserTestUtils.withNewTab( + { gBrowser, url: TESTROOT + "file_pdf_download_link.html" }, + async function (browser) { + // Click on the download link in the loaded file. This will create a new + // tab with the PDF loaded in it. + BrowserTestUtils.synthesizeMouseAtCenter("#custom_filename", {}, browser); + let tab = await BrowserTestUtils.waitForNewTab(gBrowser); + info("tab created"); + + // Wait for the PDF's metadata to be fully loaded before downloading, as + // otherwise it won't be aware of the content disposition filename yet. + await BrowserTestUtils.waitForContentEvent( + tab.linkedBrowser, + "metadataloaded", + false, + null, + true + ); + info("metadata loaded"); + + let destFile = tempDir.clone(); + MockFilePicker.displayDirectory = tempDir; + let fileSavedPromise = createPromiseForTransferComplete( + "custom_filename.pdf", + destFile + ); + saveBrowser(tab.linkedBrowser); + await fileSavedPromise; + BrowserTestUtils.removeTab(tab); + } + ); + await SpecialPowers.popPrefEnv(); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_savedialog.js b/toolkit/components/pdfjs/test/browser_pdfjs_savedialog.js new file mode 100644 index 0000000000..4f0c952ebb --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_savedialog.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +function test() { + // When the always ask pref is disabled, we expect the PDF to be simply + // downloaded without a prompt, so ensure the pref is enabled here. + Services.prefs.setBoolPref( + "browser.download.always_ask_before_handling_new_types", + true + ); + var oldAction = changeMimeHandler(Ci.nsIHandlerInfo.useSystemDefault, true); + var tab = BrowserTestUtils.addTab(gBrowser, TESTROOT + "file_pdfjs_test.pdf"); + + // Test: "Open with" dialog comes up when pdf.js is not selected as the default + // handler. + addWindowListener( + "chrome://mozapps/content/downloads/unknownContentType.xhtml", + finish + ); + + waitForExplicitFinish(); + registerCleanupFunction(function () { + Services.prefs.clearUserPref( + "browser.download.always_ask_before_handling_new_types" + ); + changeMimeHandler(oldAction[0], oldAction[1]); + gBrowser.removeTab(tab); + }); +} + +function addWindowListener(aURL, aCallback) { + Services.wm.addListener({ + onOpenWindow(aXULWindow) { + info("window opened, waiting for focus"); + Services.wm.removeListener(this); + + var domwindow = aXULWindow.docShell.domWindow; + waitForFocus(function () { + is( + domwindow.document.location.href, + aURL, + "should have seen the right window open" + ); + domwindow.close(); + aCallback(); + }, domwindow); + }, + onCloseWindow(aXULWindow) {}, + }); +} diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_secondary_toolbar_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_secondary_toolbar_telemetry.js new file mode 100644 index 0000000000..136b9c310d --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_secondary_toolbar_telemetry.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "https://example.com/browser/" + RELATIVE_DIR; + +// Test telemetry. +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + Services.fog.testResetFOG(); + + // check that PDF is opened with internal viewer + await waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf"); + + await Services.fog.testFlushAllChildren(); + Assert.equal(Glean.pdfjs.buttons.cursor_hand_tool.testGetValue() || 0, 0); + + info("Clicking on the secondary toolbar button..."); + await SpecialPowers.spawn(browser, [], () => { + content.document.querySelector("#secondaryToolbarToggle").click(); + }); + + await BrowserTestUtils.waitForCondition(async () => + SpecialPowers.spawn(browser, [], async function () { + return !content.document + .querySelector("#secondaryToolbar") + .classList.contains("hidden"); + }) + ); + + info("Clicking on the cursor handtool button..."); + await SpecialPowers.spawn(browser, [], () => { + content.document.querySelector("#cursorHandTool").click(); + }); + + await Services.fog.testFlushAllChildren(); + Assert.equal(Glean.pdfjs.buttons.cursor_hand_tool.testGetValue(), 1); + + await SpecialPowers.spawn(browser, [], async function () { + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_views.js b/toolkit/components/pdfjs/test/browser_pdfjs_views.js new file mode 100644 index 0000000000..16757aff78 --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_views.js @@ -0,0 +1,106 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (browser) { + // check that PDF is opened with internal viewer + await waitForPdfJS(browser, TESTROOT + "file_pdfjs_test.pdf"); + + await SpecialPowers.spawn(browser, [], async function () { + Assert.ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + + // open sidebar + var sidebar = content.document.querySelector("button#sidebarToggle"); + var outerContainer = + content.document.querySelector("div#outerContainer"); + + sidebar.click(); + Assert.ok( + outerContainer.classList.contains("sidebarOpen"), + "sidebar opens on click" + ); + + // check that thumbnail view is open + var thumbnailView = content.document.querySelector("div#thumbnailView"); + var outlineView = content.document.querySelector("div#outlineView"); + + Assert.equal( + thumbnailView.getAttribute("class"), + null, + "Initial view is thumbnail view" + ); + Assert.equal( + outlineView.getAttribute("class"), + "hidden", + "Outline view is hidden initially" + ); + + // switch to outline view + var viewOutlineButton = + content.document.querySelector("button#viewOutline"); + viewOutlineButton.click(); + + Assert.equal( + thumbnailView.getAttribute("class"), + "hidden", + "Thumbnail view is hidden when outline is selected" + ); + Assert.equal( + outlineView.getAttribute("class"), + "", + "Outline view is visible when selected" + ); + + // switch back to thumbnail view + var viewThumbnailButton = content.document.querySelector( + "button#viewThumbnail" + ); + viewThumbnailButton.click(); + + Assert.equal( + thumbnailView.getAttribute("class"), + "", + "Thumbnail view is visible when selected" + ); + Assert.equal( + outlineView.getAttribute("class"), + "hidden", + "Outline view is hidden when thumbnail is selected" + ); + + sidebar.click(); + + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/browser_pdfjs_zoom.js b/toolkit/components/pdfjs/test/browser_pdfjs_zoom.js new file mode 100644 index 0000000000..fd97881bfb --- /dev/null +++ b/toolkit/components/pdfjs/test/browser_pdfjs_zoom.js @@ -0,0 +1,277 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +requestLongerTimeout(2); + +const RELATIVE_DIR = "toolkit/components/pdfjs/test/"; +const TESTROOT = "http://example.com/browser/" + RELATIVE_DIR; + +const TESTS = [ + { + action: { + selector: "button#zoomIn", + event: "click", + }, + expectedZoom: 1, // 1 - zoom in + message: "Zoomed in using the '+' (zoom in) button", + }, + + { + action: { + selector: "button#zoomOut", + event: "click", + }, + expectedZoom: -1, // -1 - zoom out + message: "Zoomed out using the '-' (zoom out) button", + }, + + { + action: { + keyboard: true, + keyCode: 61, + event: "+", + }, + expectedZoom: 1, // 1 - zoom in + message: "Zoomed in using the CTRL++ keys", + }, + + { + action: { + keyboard: true, + keyCode: 109, + event: "-", + }, + expectedZoom: -1, // -1 - zoom out + message: "Zoomed out using the CTRL+- keys", + }, + + { + action: { + selector: "select#scaleSelect", + index: 5, + event: "change", + }, + expectedZoom: -1, // -1 - zoom out + message: "Zoomed using the zoom picker", + }, +]; + +add_task(async function test() { + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + + // Make sure pdf.js is the default handler. + is( + handlerInfo.alwaysAskBeforeHandling, + false, + "pdf handler defaults to always-ask is false" + ); + is( + handlerInfo.preferredAction, + Ci.nsIHandlerInfo.handleInternally, + "pdf handler defaults to internal" + ); + + info("Pref action: " + handlerInfo.preferredAction); + + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newTabBrowser) { + await waitForPdfJS( + newTabBrowser, + TESTROOT + "file_pdfjs_test.pdf#zoom=100" + ); + + await SpecialPowers.spawn( + newTabBrowser, + [TESTS], + async function (contentTESTS) { + let document = content.document; + + function waitForRender() { + return new Promise(resolve => { + document.addEventListener( + "pagerendered", + function onPageRendered(e) { + if (e.detail.pageNumber !== 1) { + return; + } + + document.removeEventListener( + "pagerendered", + onPageRendered, + true + ); + resolve(); + }, + true + ); + }); + } + + // check that PDF is opened with internal viewer + Assert.ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + + let initialWidth, previousWidth; + initialWidth = previousWidth = parseInt( + content.getComputedStyle( + content.document.querySelector("div.page[data-page-number='1']") + ).width + ); + + for (let subTest of contentTESTS) { + // We zoom using an UI element + var ev; + if (subTest.action.selector) { + // Get the element and trigger the action for changing the zoom + var el = document.querySelector(subTest.action.selector); + Assert.ok( + el, + "Element '" + subTest.action.selector + "' has been found" + ); + + if (subTest.action.index) { + el.selectedIndex = subTest.action.index; + } + + // Dispatch the event for changing the zoom + ev = new content.Event(subTest.action.event); + } else { + // We zoom using keyboard + // Simulate key press + ev = new content.KeyboardEvent("keydown", { + key: subTest.action.event, + keyCode: subTest.action.keyCode, + ctrlKey: true, + }); + el = content; + } + + el.dispatchEvent(ev); + await waitForRender(); + + var pageZoomScale = + content.document.querySelector("select#scaleSelect"); + + // The zoom value displayed in the zoom select + var zoomValue = + pageZoomScale.options[pageZoomScale.selectedIndex].innerHTML; + + let pageContainer = content.document.querySelector( + "div.page[data-page-number='1']" + ); + let actualWidth = parseInt( + content.getComputedStyle(pageContainer).width + ); + + // the actual zoom of the PDF document + let computedZoomValue = + parseInt((actualWidth / initialWidth).toFixed(2) * 100) + "%"; + Assert.equal( + computedZoomValue, + zoomValue, + "Content has correct zoom" + ); + + // Check that document zooms in the expected way (in/out) + let zoom = (actualWidth - previousWidth) * subTest.expectedZoom; + Assert.ok(zoom > 0, subTest.message); + + previousWidth = actualWidth; + } + + var viewer = content.wrappedJSObject.PDFViewerApplication; + await viewer.close(); + } + ); + } + ); +}); + +// Performs a SpecialPowers.spawn round-trip to ensure that any setup +// that needs to be done in the content process by any pending tasks has +// a chance to complete before continuing. +function waitForRoundTrip(browser) { + return SpecialPowers.spawn(browser, [], () => {}); +} + +async function waitForRenderAndGetWidth(newTabBrowser) { + return SpecialPowers.spawn(newTabBrowser, [], async function () { + function waitForRender(document) { + return new Promise(resolve => { + document.addEventListener( + "pagerendered", + function onPageRendered(e) { + if (e.detail.pageNumber !== 1) { + return; + } + + document.removeEventListener("pagerendered", onPageRendered, true); + resolve(); + }, + true + ); + }); + } + // check that PDF is opened with internal viewer + Assert.ok( + content.document.querySelector("div#viewer"), + "document content has viewer UI" + ); + + await waitForRender(content.document); + + return parseInt( + content.getComputedStyle( + content.document.querySelector("div.page[data-page-number='1']") + ).width + ); + }); +} + +add_task(async function test_browser_zoom() { + await BrowserTestUtils.withNewTab( + { gBrowser, url: "about:blank" }, + async function (newTabBrowser) { + await waitForPdfJS(newTabBrowser, TESTROOT + "file_pdfjs_test.pdf"); + + const initialWidth = await waitForRenderAndGetWidth(newTabBrowser); + + // Zoom in + let newWidthPromise = waitForRenderAndGetWidth(newTabBrowser); + await waitForRoundTrip(newTabBrowser); + FullZoom.enlarge(); + ok( + (await newWidthPromise) > initialWidth, + "Zoom in makes the page bigger." + ); + + // Reset + newWidthPromise = waitForRenderAndGetWidth(newTabBrowser); + await waitForRoundTrip(newTabBrowser); + FullZoom.reset(); + is(await newWidthPromise, initialWidth, "Zoom reset restores page."); + + // Zoom out + newWidthPromise = waitForRenderAndGetWidth(newTabBrowser); + await waitForRoundTrip(newTabBrowser); + FullZoom.reduce(); + ok( + (await newWidthPromise) < initialWidth, + "Zoom out makes the page smaller." + ); + + // Clean-up after the PDF viewer. + await SpecialPowers.spawn(newTabBrowser, [], function () { + const viewer = content.wrappedJSObject.PDFViewerApplication; + return viewer.close(); + }); + } + ); +}); diff --git a/toolkit/components/pdfjs/test/file_pdf_download_link.html b/toolkit/components/pdfjs/test/file_pdf_download_link.html new file mode 100644 index 0000000000..8b458b7a4c --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdf_download_link.html @@ -0,0 +1,4 @@ +<!DOCTYPE html> +<body> +<a id="custom_filename" href="file_pdfjs_test.pdf" download="custom_filename.pdf">custom_filename.pdf</a> +</body> diff --git a/toolkit/components/pdfjs/test/file_pdfjs_form.pdf b/toolkit/components/pdfjs/test/file_pdfjs_form.pdf Binary files differnew file mode 100644 index 0000000000..c6eff988a6 --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_form.pdf diff --git a/toolkit/components/pdfjs/test/file_pdfjs_hcm.pdf b/toolkit/components/pdfjs/test/file_pdfjs_hcm.pdf new file mode 100644 index 0000000000..611cbc6309 --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_hcm.pdf @@ -0,0 +1,24 @@ +%PDF-1.4 +1 0 obj <</Type /Catalog /Pages 2 0 R>> +endobj +2 0 obj <</Type /Pages /Kids [3 0 R] /Count 1>> +endobj +3 0 obj<</Type /Page /Parent 2 0 R /MediaBox [0 0 3 3] /Contents 4 0 R>> +endobj +4 0 obj +<</Length 12>> +stream +0.5 w 0 0 0 RG 0 2.75 m 0.5 2.75 l s +endstream +endobj +xref +0 5 +0000000000 65535 f +0000000009 00000 n +0000000056 00000 n +0000000111 00000 n +0000000191 00000 n +trailer <</Size 5/Root 1 0 R>> +startxref +275 +%%EOF diff --git a/toolkit/components/pdfjs/test/file_pdfjs_js.pdf b/toolkit/components/pdfjs/test/file_pdfjs_js.pdf Binary files differnew file mode 100644 index 0000000000..78aab95fd3 --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_js.pdf diff --git a/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf Binary files differnew file mode 100644 index 0000000000..89066463f1 --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf diff --git a/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf^headers^ b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf^headers^ new file mode 100644 index 0000000000..ff8fb3126e --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf^headers^ @@ -0,0 +1,2 @@ +Content-Type: application/octet-stream + diff --git a/toolkit/components/pdfjs/test/file_pdfjs_test.pdf b/toolkit/components/pdfjs/test/file_pdfjs_test.pdf Binary files differnew file mode 100644 index 0000000000..7ad87e3c2e --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_test.pdf diff --git a/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf b/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf Binary files differnew file mode 100644 index 0000000000..1b0c240881 --- /dev/null +++ b/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf diff --git a/toolkit/components/pdfjs/test/head.js b/toolkit/components/pdfjs/test/head.js new file mode 100644 index 0000000000..fc50e4ef4b --- /dev/null +++ b/toolkit/components/pdfjs/test/head.js @@ -0,0 +1,357 @@ +async function waitForPdfJS(browser, url) { + // Runs tests after all "load" event handlers have fired off + let loadPromise = BrowserTestUtils.waitForContentEvent( + browser, + "documentloaded", + false, + null, + true + ); + BrowserTestUtils.loadURIString(browser, url); + return loadPromise; +} + +async function waitForPdfJSAnnotationLayer(browser, url) { + let loadPromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationlayerrendered", + false, + null, + true + ); + BrowserTestUtils.loadURIString(browser, url); + return loadPromise; +} + +async function waitForPdfJSAllLayers(browser, url, layers) { + let loadPromise = BrowserTestUtils.waitForContentEvent( + browser, + "textlayerrendered", + false, + null, + true + ); + let annotationPromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationlayerrendered", + false, + null, + true + ); + let annotationEditorPromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationeditorlayerrendered", + false, + null, + true + ); + + BrowserTestUtils.loadURIString(browser, url); + await Promise.all([loadPromise, annotationPromise, annotationEditorPromise]); + + await SpecialPowers.spawn(browser, [layers], async function (layers) { + const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" + ); + const { document } = content; + + for (let i = 0; i < layers.length; i++) { + await ContentTaskUtils.waitForCondition( + () => + layers[i].every( + name => + !!document.querySelector( + `.page[data-page-number='${i + 1}'] .${name}` + ) + ), + `All the layers must be displayed on page ${i}` + ); + } + }); + + await TestUtils.waitForTick(); +} + +async function waitForPdfJSCanvas(browser, url) { + let loadPromise = BrowserTestUtils.waitForContentEvent( + browser, + "pagerendered", + false, + null, + true + ); + BrowserTestUtils.loadURIString(browser, url); + return loadPromise; +} + +async function waitForPdfJSSandbox(browser) { + let loadPromise = BrowserTestUtils.waitForContentEvent( + browser, + "sandboxcreated", + false, + null, + true + ); + return loadPromise; +} + +/** + * Enable an editor (Ink, FreeText, ...). + * @param {Object} browser + * @param {string} name + */ +async function enableEditor(browser, name) { + const editingModePromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationeditormodechanged", + false, + null, + true + ); + const editingStatePromise = BrowserTestUtils.waitForContentEvent( + browser, + "annotationeditorstateschanged", + false, + null, + true + ); + await SpecialPowers.spawn(browser, [name], async name => { + const button = content.document.querySelector(`#editor${name}`); + button.click(); + }); + await editingModePromise; + await editingStatePromise; + await TestUtils.waitForTick(); +} + +/** + * The text layer contains some spans with the text of the pdf. + * @param {Object} browser + * @param {string} text + * @returns {Object} the bbox of the span containing the text. + */ +async function getSpanBox(browser, text) { + return SpecialPowers.spawn(browser, [text], async function (text) { + const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" + ); + const { document } = content; + + await ContentTaskUtils.waitForCondition( + () => !!document.querySelector(".textLayer .endOfContent"), + "The text layer must be displayed" + ); + + let targetSpan = null; + for (const span of document.querySelectorAll( + `.textLayer span[role="presentation"]` + )) { + if (span.innerText.includes(text)) { + targetSpan = span; + break; + } + } + + Assert.ok(targetSpan, `document must have a span containing '${text}'`); + + const { x, y, width, height } = targetSpan.getBoundingClientRect(); + return { x, y, width, height }; + }); +} + +/** + * Count the number of elements corresponding to the given selector. + * @param {Object} browser + * @param {string} selector + * @returns + */ +async function countElements(browser, selector) { + return SpecialPowers.spawn(browser, [selector], async function (selector) { + return new Promise(resolve => { + content.setTimeout(() => { + resolve(content.document.querySelectorAll(selector).length); + }, 0); + }); + }); +} + +/** + * Click at the given coordinates. + * @param {Object} browser + * @param {number} x + * @param {number} y + */ +async function clickAt(browser, x, y) { + await BrowserTestUtils.synthesizeMouseAtPoint( + x, + y, + { + type: "mousedown", + button: 0, + }, + browser + ); + await BrowserTestUtils.synthesizeMouseAtPoint( + x, + y, + { + type: "mouseup", + button: 0, + }, + browser + ); + await TestUtils.waitForTick(); +} + +/** + * Click on the element corresponding to the given selector. + * @param {Object} browser + * @param {string} selector + */ +async function clickOn(browser, selector) { + const [x, y] = await SpecialPowers.spawn( + browser, + [selector], + async selector => { + const element = content.document.querySelector(selector); + const { x, y, width, height } = element.getBoundingClientRect(); + return [x + width / 2, y + height / 2]; + } + ); + await clickAt(browser, x, y); +} + +async function focusEditorLayer(browser) { + return SpecialPowers.spawn(browser, [], async function () { + const layer = content.document.querySelector(".annotationEditorLayer"); + if (layer === content.document.activeElement) { + return Promise.resolve(); + } + const promise = new Promise(resolve => { + const listener = () => { + layer.removeEventListener("focus", listener); + resolve(); + }; + layer.addEventListener("focus", listener); + }); + layer.focus(); + return promise; + }); +} + +/** + * Hit a key. + */ +async function hitKey(browser, char) { + await SpecialPowers.spawn(browser, [char], async function (char) { + const { ContentTaskUtils } = ChromeUtils.importESModule( + "resource://testing-common/ContentTaskUtils.sys.mjs" + ); + const EventUtils = ContentTaskUtils.getEventUtils(content); + await EventUtils.synthesizeKey(char, {}, content); + }); + await TestUtils.waitForTick(); +} + +/** + * Write some text using the keyboard. + * @param {Object} browser + * @param {string} text + */ +async function write(browser, text) { + for (const char of text.split("")) { + hitKey(browser, char); + } +} + +/** + * Hit escape key. + */ +async function escape(browser) { + await hitKey(browser, "KEY_Escape"); +} + +/** + * Add a FreeText annotation and write some text inside. + * @param {Object} browser + * @param {string} text + * @param {Object} box + */ +async function addFreeText(browser, text, box) { + const { x, y, width, height } = box; + const count = await countElements(browser, ".freeTextEditor"); + await focusEditorLayer(browser); + await clickAt(browser, x + 0.1 * width, y + 0.5 * height); + await BrowserTestUtils.waitForCondition( + async () => (await countElements(browser, ".freeTextEditor")) === count + 1 + ); + + await write(browser, text); + await escape(browser); +} + +function changeMimeHandler(preferredAction, alwaysAskBeforeHandling) { + let handlerService = Cc[ + "@mozilla.org/uriloader/handler-service;1" + ].getService(Ci.nsIHandlerService); + let mimeService = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService); + let handlerInfo = mimeService.getFromTypeAndExtension( + "application/pdf", + "pdf" + ); + var oldAction = [ + handlerInfo.preferredAction, + handlerInfo.alwaysAskBeforeHandling, + ]; + + // Change and save mime handler settings + handlerInfo.alwaysAskBeforeHandling = alwaysAskBeforeHandling; + handlerInfo.preferredAction = preferredAction; + handlerService.store(handlerInfo); + + // Refresh data + handlerInfo = mimeService.getFromTypeAndExtension("application/pdf", "pdf"); + + // Test: Mime handler was updated + is( + handlerInfo.alwaysAskBeforeHandling, + alwaysAskBeforeHandling, + "always-ask prompt change successful" + ); + is( + handlerInfo.preferredAction, + preferredAction, + "mime handler change successful" + ); + + return oldAction; +} + +function createTemporarySaveDirectory() { + var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile); + saveDir.append("testsavedir"); + if (!saveDir.exists()) { + saveDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755); + } + return saveDir; +} + +async function cleanupDownloads(listId = Downloads.PUBLIC) { + info("cleaning up downloads"); + let downloadList = await Downloads.getList(listId); + for (let download of await downloadList.getAll()) { + await download.finalize(true); + try { + if (Services.appinfo.OS === "WINNT") { + // We need to make the file writable to delete it on Windows. + await IOUtils.setPermissions(download.target.path, 0o600); + } + await IOUtils.remove(download.target.path); + } catch (error) { + info("The file " + download.target.path + " is not removed, " + error); + } + + await downloadList.remove(download); + await download.finalize(); + } +} diff --git a/toolkit/components/pdfjs/test/mochitest.ini b/toolkit/components/pdfjs/test/mochitest.ini new file mode 100644 index 0000000000..32e6fc9372 --- /dev/null +++ b/toolkit/components/pdfjs/test/mochitest.ini @@ -0,0 +1,4 @@ +[test_pdf_file_in_iframe.html] +skip-if = os == 'android' && !nightly_build +support-files = + file_pdfjs_test.pdf diff --git a/toolkit/components/pdfjs/test/test_pdf_file_in_iframe.html b/toolkit/components/pdfjs/test/test_pdf_file_in_iframe.html new file mode 100644 index 0000000000..81410c535f --- /dev/null +++ b/toolkit/components/pdfjs/test/test_pdf_file_in_iframe.html @@ -0,0 +1,50 @@ +<!DOCTYPE html> +<html> + +<head> + <meta charset="utf-8"> + <title>PDFjs: Load a PDF in an iframe.</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + <script src="/tests/SimpleTest/EventUtils.js"></script> + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> +</head> + +<body> + <pre id="test"> + <script type="text/javascript"> + "use strict"; + + async function test() { + await SpecialPowers.pushPrefEnv({ + "set": [ + ["pdfjs.disabled", false], + ["pdfjs.enableFloatingToolbar", false], + ], + }); + + const iframe = document.createElement("iframe"); + document.body.append(iframe); + iframe.src = "file_pdfjs_test.pdf"; + iframe.onload = async () => { + const hasViewerContainer = await SpecialPowers.spawn( + iframe.contentWindow, + [], + () => !!this.content.document.getElementById("viewerContainer") + ); + ok( + hasViewerContainer, + "The iframe with a pdf must have a `viewerContainer`" + ); + SimpleTest.finish(); + }; + } + + window.onload = () => { + SimpleTest.waitForExplicitFinish(); + test(); + }; + </script> + </pre> +</body> + +</html> |