summaryrefslogtreecommitdiffstats
path: root/toolkit/components/pdfjs/test
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /toolkit/components/pdfjs/test
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/pdfjs/test')
-rw-r--r--toolkit/components/pdfjs/test/browser.toml83
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_download_button.js125
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js381
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_editing_telemetry.js96
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_fill_login.js108
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_filters.js81
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_find.js137
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_force_opening_files.js89
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_form.js85
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_fullscreen.js84
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_hcm.js104
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_highlight_telemetry.js234
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_js.js49
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_load_telemetry.js51
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_main.js72
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_minimum_font_size.js70
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_navigation.js314
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_nonpdf_filename.js73
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_not_default.js26
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_octet_stream.js146
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_saveas.js216
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_savedialog.js53
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_secondary_toolbar_telemetry.js67
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js219
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_views.js106
-rw-r--r--toolkit/components/pdfjs/test/browser_pdfjs_zoom.js279
-rw-r--r--toolkit/components/pdfjs/test/file_pdf_download_link.html4
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_form.pdfbin0 -> 8586 bytes
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_hcm.pdf24
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_js.pdfbin0 -> 24153 bytes
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdfbin0 -> 1568 bytes
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf^headers^2
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_test.pdfbin0 -> 150611 bytes
-rw-r--r--toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdfbin0 -> 62324 bytes
-rw-r--r--toolkit/components/pdfjs/test/head.js428
-rw-r--r--toolkit/components/pdfjs/test/mochitest.toml7
-rw-r--r--toolkit/components/pdfjs/test/moz.pngbin0 -> 580 bytes
-rw-r--r--toolkit/components/pdfjs/test/test_pdf_file_in_iframe.html50
38 files changed, 3863 insertions, 0 deletions
diff --git a/toolkit/components/pdfjs/test/browser.toml b/toolkit/components/pdfjs/test/browser.toml
new file mode 100644
index 0000000000..4d045fd33e
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser.toml
@@ -0,0 +1,83 @@
+[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
+
+["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_highlight_telemetry.js"]
+skip-if = ["true"]
+
+["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_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_stamp_telemetry.js"]
+support-files = ["moz.png"]
+
+["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..1e3c00620c
--- /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.startLoadingURIString(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..d857bb6aac
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_editing_contextmenu.js
@@ -0,0 +1,381 @@
+/* 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();
+
+ // The PDF already has a text annotation.
+ Assert.equal(await countElements(browser, ".freeTextEditor"), 2);
+
+ // 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")) !== 2
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 1,
+ "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",
+ "context-pdfjs-selectall",
+ ]);
+ await clickOnItem(browser, menuitems, "context-pdfjs-redo");
+
+ await BrowserTestUtils.waitForCondition(
+ async () => (await countElements(browser, ".freeTextEditor")) !== 1
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 2,
+ "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")) !== 2
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 1,
+ "The FreeText editor must have been cut"
+ );
+
+ menuitems = await getContextMenuItems(browser, spanBox);
+ assertMenuitems(menuitems, [
+ "context-pdfjs-undo",
+ "context-pdfjs-paste",
+ "context-pdfjs-selectall",
+ ]);
+
+ 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"
+ );
+
+ 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")) !== 2
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 1,
+ "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")) !== 1
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 2,
+ "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")) !== 2
+ );
+
+ Assert.equal(
+ await countElements(browser, ".freeTextEditor"),
+ 3,
+ "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")) !== 3
+ );
+
+ 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..f05b28c917
--- /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"), 2);
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.pdfjs.editing.freetext.testGetValue(), 2);
+
+ 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"), 3);
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.pdfjs.editing.freetext.testGetValue(), 3);
+
+ 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.isVisible(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..24a3a50967
--- /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.startLoadingURIString(
+ 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_highlight_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_highlight_telemetry.js
new file mode 100644
index 0000000000..83b72245d8
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_highlight_telemetry.js
@@ -0,0 +1,234 @@
+/* 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) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["pdfjs.annotationEditorMode", 0],
+ ["pdfjs.enableHighlight", true],
+ ],
+ });
+
+ 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"],
+ ]);
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(
+ (Glean.pdfjsEditingHighlight.kind.freeHighlight.testGetValue() || 0) +
+ (Glean.pdfjsEditingHighlight.kind.highlight.testGetValue() || 0),
+ 0,
+ "Should have no highlight"
+ );
+
+ await enableEditor(browser, "Highlight");
+ const strs = ["In production", "buildbot automation"];
+ for (let i = 0; i < strs.length; i++) {
+ const str = strs[i];
+ const N = i + 1;
+ const spanBox = await getSpanBox(browser, str);
+ await clickAt(
+ browser,
+ spanBox.x + 0.75 * spanBox.width,
+ spanBox.y + 0.5 * spanBox.height,
+ 2
+ );
+ await waitForEditors(browser, ".highlightEditor", N);
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.kind.highlight.testGetValue(),
+ N,
+ `Should have ${N} highlights`
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.color.yellow.testGetValue(),
+ N,
+ `Should have ${N} yellow highlights`
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.method.main_toolbar.testGetValue(),
+ N,
+ `Should have ${N} highlights created from the toolbar`
+ );
+
+ await EventUtils.synthesizeKey("KEY_Escape");
+ await waitForSelector(browser, ".highlightEditor:not(.selectedEditor)");
+
+ document.getElementById("cmd_print").doCommand();
+ await BrowserTestUtils.waitForCondition(() => {
+ let preview = document.querySelector(".printPreviewBrowser");
+ return preview && BrowserTestUtils.isVisible(preview);
+ });
+ await EventUtils.synthesizeKey("KEY_Escape");
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.pdfjs.editing.print.testGetValue(), N);
+ Assert.equal(Glean.pdfjsEditingHighlight.print.testGetValue(), N);
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.numberOfColors.one.testGetValue(),
+ N
+ );
+ }
+
+ await click(
+ browser,
+ "#highlightParamsToolbarContainer button[title='Green']"
+ );
+ const spanBox = await getSpanBox(browser, "Mozilla automated testing");
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ spanBox.x - 10,
+ spanBox.y + spanBox.height / 2,
+ {
+ type: "mousedown",
+ button: 0,
+ },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ spanBox.x + spanBox.width,
+ spanBox.y + spanBox.height / 2,
+ {
+ type: "mousemove",
+ button: 0,
+ },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ spanBox.x + spanBox.width,
+ spanBox.y + spanBox.height / 2,
+ {
+ type: "mouseup",
+ button: 0,
+ },
+ browser
+ );
+ await waitForEditors(browser, ".highlightEditor", 3);
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(Glean.pdfjsEditingHighlight.color.green.testGetValue(), 1);
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.kind.free_highlight.testGetValue(),
+ 1
+ );
+
+ let telemetryPromise = waitForTelemetry(browser);
+ await focus(browser, "#editorFreeHighlightThickness");
+ await EventUtils.synthesizeKey("KEY_ArrowRight");
+
+ await telemetryPromise;
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.thickness.testGetValue().values[12],
+ 1
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.thickness.testGetValue().values[13],
+ 1
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.thicknessChanged.testGetValue(),
+ 1
+ );
+
+ document.getElementById("cmd_print").doCommand();
+ await BrowserTestUtils.waitForCondition(() => {
+ let preview = document.querySelector(".printPreviewBrowser");
+ return preview && BrowserTestUtils.isVisible(preview);
+ });
+ await EventUtils.synthesizeKey("KEY_Escape");
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(Glean.pdfjs.editing.print.testGetValue(), 3);
+ Assert.equal(Glean.pdfjsEditingHighlight.print.testGetValue(), 3);
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.numberOfColors.one.testGetValue(),
+ 2
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.numberOfColors.two.testGetValue(),
+ 1
+ );
+
+ await click(browser, ".highlightEditor.free button.colorPicker");
+ telemetryPromise = waitForTelemetry(browser);
+ await click(
+ browser,
+ ".highlightEditor.free button.colorPicker button[title='Red']"
+ );
+ await telemetryPromise;
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(Glean.pdfjsEditingHighlight.colorChanged.testGetValue(), 1);
+
+ document.getElementById("cmd_print").doCommand();
+ await BrowserTestUtils.waitForCondition(() => {
+ let preview = document.querySelector(".printPreviewBrowser");
+ return preview && BrowserTestUtils.isVisible(preview);
+ });
+ await EventUtils.synthesizeKey("KEY_Escape");
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.numberOfColors.one.testGetValue(),
+ 2
+ );
+ Assert.equal(
+ Glean.pdfjsEditingHighlight.numberOfColors.two.testGetValue(),
+ 2
+ );
+
+ telemetryPromise = waitForTelemetry(browser);
+ await EventUtils.synthesizeKey("KEY_Delete");
+ await telemetryPromise;
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(Glean.pdfjsEditingHighlight.deleted.testGetValue(), 1);
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const viewer = content.wrappedJSObject.PDFViewerApplication;
+ viewer.pdfDocument.annotationStorage.resetModified();
+ await viewer.close();
+ });
+ }
+ );
+});
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_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..a1bfc18a91
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_saveas.js
@@ -0,0 +1,216 @@
+/* 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();
+});
+
+/**
+ * Check if the directory where the pdfs are saved is based on the original
+ * domain (see bug 1768383).
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ const downloadLastDir = new DownloadLastDir(null);
+ const destDirs = [];
+ for (let i = 1; i <= 2; i++) {
+ const destDir = createTemporarySaveDirectory(i);
+ destDirs.push(destDir);
+ const url = `http://test${i}.example.com/browser/${RELATIVE_DIR}file_pdfjs_test.pdf`;
+ downloadLastDir.setFile(url, destDir);
+ await TestUtils.waitForTick();
+ }
+
+ const url = `http://test1.example.com/browser/${RELATIVE_DIR}file_pdfjs_hcm.pdf`;
+ await waitForPdfJS(browser, url);
+
+ const fileSavedPromise = new Promise(resolve => {
+ MockFilePicker.showCallback = fp => {
+ MockFilePicker.setFiles([]);
+ MockFilePicker.showCallback = null;
+ resolve(fp.displayDirectory.path);
+ };
+ });
+ registerCleanupFunction(() => {
+ for (const destDir of destDirs) {
+ destDir.remove(true);
+ }
+ });
+ saveBrowser(browser);
+ const dirPath = await fileSavedPromise;
+ is(
+ dirPath,
+ destDirs[0].path,
+ "Proposed directory must be based on the domain"
+ );
+ }
+ );
+});
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_stamp_telemetry.js b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js
new file mode 100644
index 0000000000..b8955f77e3
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_stamp_telemetry.js
@@ -0,0 +1,219 @@
+/* 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;
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/toolkit/content/tests/browser/common/mockTransfer.js",
+ this
+);
+
+const MockFilePicker = SpecialPowers.MockFilePicker;
+MockFilePicker.init(window);
+MockFilePicker.returnValue = MockFilePicker.returnOK;
+const file = new FileUtils.File(getTestFilePath("moz.png"));
+MockFilePicker.setFiles([file]);
+
+// 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"],
+ ]);
+
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(
+ Glean.pdfjs.editing.stamp.testGetValue() || 0,
+ 0,
+ "Should have no stamp"
+ );
+
+ await enableEditor(browser, "Stamp");
+ await clickOn(browser, `#editorStampAddImage`);
+ await waitForSelector(browser, ".altText");
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.editing.stamp.testGetValue(),
+ 1,
+ "Should have 1 stamp"
+ );
+ Assert.equal(
+ Glean.pdfjs.stamp.inserted_image.testGetValue(),
+ 1,
+ "Should have 1 inserted_image"
+ );
+
+ await clickOn(browser, ".altText");
+ await waitForSelector(
+ browser,
+ "#altTextDialog",
+ "Wait for the dialog to be visible"
+ );
+
+ await clickOn(browser, "#descriptionTextarea");
+ await write(browser, "Hello World");
+
+ await clickOn(browser, "#altTextSave");
+ await TestUtils.waitForTick();
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_save.testGetValue(),
+ 1,
+ "Should have 1 alt_text_save"
+ );
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_description.testGetValue(),
+ 1,
+ "Should have 1 alt_text_description"
+ );
+
+ await SpecialPowers.spawn(browser, [], async () => {
+ const altText = content.document.querySelector(".altText");
+ await EventUtils.synthesizeMouseAtCenter(
+ altText,
+ { type: "mousemove" },
+ content
+ );
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(altText.querySelector(".tooltip")),
+ "Wait for tooltip"
+ );
+ });
+
+ await TestUtils.waitForTick();
+ await Services.fog.testFlushAllChildren();
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_tooltip.testGetValue(),
+ 1,
+ "Should have 1 alt_text_tooltip"
+ );
+
+ await clickOn(browser, ".altText");
+ await waitForSelector(
+ browser,
+ "#altTextDialog",
+ "Wait for the dialog to be visible"
+ );
+ await clickOn(browser, "#altTextCancel");
+ await TestUtils.waitForTick();
+
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_cancel.testGetValue(),
+ 1,
+ "Should have 1 alt_text_cancel"
+ );
+
+ await clickOn(browser, ".altText");
+ await waitForSelector(
+ browser,
+ "#altTextDialog",
+ "Wait for the dialog to be visible"
+ );
+ await clickOn(browser, "#descriptionTextarea");
+ await write(browser, "Hello World");
+
+ await clickOn(browser, "#altTextSave");
+ await TestUtils.waitForTick();
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_save.testGetValue(),
+ 2,
+ "Should have 2 alt_text_save"
+ );
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_description.testGetValue(),
+ 2,
+ "Should have 2 alt_text_description"
+ );
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_edit.testGetValue(),
+ 1,
+ "Should have 1 alt_text_edit"
+ );
+
+ await clickOn(browser, ".altText");
+ await waitForSelector(
+ browser,
+ "#altTextDialog",
+ "Wait for the dialog to be visible"
+ );
+ await clickOn(browser, "#decorativeButton");
+ await TestUtils.waitForTick();
+ await clickOn(browser, "#altTextSave");
+ await TestUtils.waitForTick();
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_decorative.testGetValue(),
+ 1,
+ "Should have 1 alt_text_decorative"
+ );
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_keyboard.testGetValue() || 0,
+ 0,
+ "Should have 0 alt_text_keyboard"
+ );
+
+ await click(browser, ".altText");
+ await click(browser, "#descriptionButton");
+ await click(browser, "#altTextSave");
+
+ await TestUtils.waitForTick();
+ await Services.fog.testFlushAllChildren();
+
+ Assert.equal(
+ Glean.pdfjs.stamp.alt_text_keyboard.testGetValue(),
+ 1,
+ "Should have 1 alt_text_keyboard"
+ );
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ const viewer = content.wrappedJSObject.PDFViewerApplication;
+ viewer.pdfDocument.annotationStorage.resetModified();
+ 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..e00dede422
--- /dev/null
+++ b/toolkit/components/pdfjs/test/browser_pdfjs_zoom.js
@@ -0,0 +1,279 @@
+/* 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();
+ Assert.greater(
+ 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();
+ Assert.less(
+ 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
new file mode 100644
index 0000000000..c6eff988a6
--- /dev/null
+++ b/toolkit/components/pdfjs/test/file_pdfjs_form.pdf
Binary files differ
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
new file mode 100644
index 0000000000..78aab95fd3
--- /dev/null
+++ b/toolkit/components/pdfjs/test/file_pdfjs_js.pdf
Binary files differ
diff --git a/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf
new file mode 100644
index 0000000000..89066463f1
--- /dev/null
+++ b/toolkit/components/pdfjs/test/file_pdfjs_object_stream.pdf
Binary files differ
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
new file mode 100644
index 0000000000..7ad87e3c2e
--- /dev/null
+++ b/toolkit/components/pdfjs/test/file_pdfjs_test.pdf
Binary files differ
diff --git a/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf b/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf
new file mode 100644
index 0000000000..1b0c240881
--- /dev/null
+++ b/toolkit/components/pdfjs/test/file_pdfjs_transfer_map.pdf
Binary files differ
diff --git a/toolkit/components/pdfjs/test/head.js b/toolkit/components/pdfjs/test/head.js
new file mode 100644
index 0000000000..04c9543b5d
--- /dev/null
+++ b/toolkit/components/pdfjs/test/head.js
@@ -0,0 +1,428 @@
+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.startLoadingURIString(browser, url);
+ return loadPromise;
+}
+
+async function waitForPdfJSAnnotationLayer(browser, url) {
+ let loadPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "annotationlayerrendered",
+ false,
+ null,
+ true
+ );
+ BrowserTestUtils.startLoadingURIString(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.startLoadingURIString(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.startLoadingURIString(browser, url);
+ return loadPromise;
+}
+
+async function waitForPdfJSSandbox(browser) {
+ let loadPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "sandboxcreated",
+ false,
+ null,
+ true
+ );
+ return loadPromise;
+}
+
+async function waitForSelector(browser, selector, message) {
+ return SpecialPowers.spawn(
+ browser,
+ [selector, message],
+ async function (sel, msg) {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const { document } = content;
+
+ await ContentTaskUtils.waitForCondition(
+ () => !!document.querySelector(sel),
+ `${sel} must be displayed`
+ );
+
+ await ContentTaskUtils.waitForCondition(
+ () => ContentTaskUtils.isVisible(document.querySelector(sel)),
+ msg
+ );
+ }
+ );
+}
+
+async function click(browser, selector) {
+ await waitForSelector(browser, selector);
+ await SpecialPowers.spawn(browser, [selector], async function (sel) {
+ const el = content.document.querySelector(sel);
+ await new Promise(r => {
+ el.addEventListener("click", r, { once: true });
+ el.click();
+ });
+ });
+}
+
+async function waitForTelemetry(browser) {
+ await BrowserTestUtils.waitForContentEvent(
+ browser,
+ "reporttelemetry",
+ false,
+ null,
+ true
+ );
+ await TestUtils.waitForTick();
+}
+
+/**
+ * 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 clickOn(browser, `#editor${name}`);
+ 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, pageNumber = 1) {
+ return SpecialPowers.spawn(
+ browser,
+ [text, pageNumber],
+ async function (text, number) {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const { document } = content;
+
+ await ContentTaskUtils.waitForCondition(
+ () =>
+ !!document.querySelector(
+ `.page[data-page-number='${number}'] .textLayer .endOfContent`
+ ),
+ "The text layer must be displayed"
+ );
+
+ let targetSpan = null;
+ for (const span of document.querySelectorAll(
+ `.page[data-page-number='${number}'] .textLayer span`
+ )) {
+ 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
+ * @param {number} n
+ */
+async function clickAt(browser, x, y, n = 1) {
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ x,
+ y,
+ {
+ type: "mousedown",
+ button: 0,
+ clickCount: n,
+ },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouseAtPoint(
+ x,
+ y,
+ {
+ type: "mouseup",
+ button: 0,
+ clickCount: n,
+ },
+ browser
+ );
+ await TestUtils.waitForTick();
+}
+
+/**
+ * Click on the element corresponding to the given selector.
+ * @param {Object} browser
+ * @param {string} selector
+ */
+async function clickOn(browser, selector) {
+ await waitForSelector(browser, selector);
+ const [x, y] = await SpecialPowers.spawn(
+ browser,
+ [selector],
+ async selector => {
+ const element = content.document.querySelector(selector);
+ Assert.ok(
+ !!element,
+ `Element "${selector}" must be available in order to be clicked`
+ );
+ const { x, y, width, height } = element.getBoundingClientRect();
+ return [x + width / 2, y + height / 2];
+ }
+ );
+ await clickAt(browser, x, y);
+}
+
+function focusEditorLayer(browser) {
+ return focus(browser, ".annotationEditorLayer");
+}
+
+/**
+ * Focus an element corresponding to the given selector.
+ * @param {Object} browser
+ * @param {string} selector
+ * @returns
+ */
+async function focus(browser, selector) {
+ return SpecialPowers.spawn(browser, [selector], function (sel) {
+ const el = content.document.querySelector(sel);
+ if (el === content.document.activeElement) {
+ return Promise.resolve();
+ }
+ const promise = new Promise(resolve => {
+ const listener = () => {
+ el.removeEventListener("focus", listener);
+ resolve();
+ };
+ el.addEventListener("focus", listener);
+ });
+ el.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 waitForEditors(browser, ".freeTextEditor", count + 1);
+
+ await write(browser, text);
+ await escape(browser);
+}
+
+async function waitForEditors(browser, selector, count) {
+ await BrowserTestUtils.waitForCondition(
+ async () => (await countElements(browser, selector)) === count
+ );
+}
+
+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(id = "") {
+ var saveDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ saveDir.append(`testsavedir${id}`);
+ 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.toml b/toolkit/components/pdfjs/test/mochitest.toml
new file mode 100644
index 0000000000..52b40ce6f3
--- /dev/null
+++ b/toolkit/components/pdfjs/test/mochitest.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+
+["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/moz.png b/toolkit/components/pdfjs/test/moz.png
new file mode 100644
index 0000000000..769c636340
--- /dev/null
+++ b/toolkit/components/pdfjs/test/moz.png
Binary files differ
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>