summaryrefslogtreecommitdiffstats
path: root/toolkit/components/printing/tests
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /toolkit/components/printing/tests
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/printing/tests')
-rw-r--r--toolkit/components/printing/tests/browser.ini90
-rw-r--r--toolkit/components/printing/tests/browser_cancel_close_print.js28
-rw-r--r--toolkit/components/printing/tests/browser_destination_change.js155
-rw-r--r--toolkit/components/printing/tests/browser_empty_paper_sizes.js97
-rw-r--r--toolkit/components/printing/tests/browser_modal_print.js309
-rw-r--r--toolkit/components/printing/tests/browser_modal_resize.js178
-rw-r--r--toolkit/components/printing/tests/browser_pdf_hidden_settings.js34
-rw-r--r--toolkit/components/printing/tests/browser_pdf_printer_settings.js125
-rw-r--r--toolkit/components/printing/tests/browser_preview_in_container.js56
-rw-r--r--toolkit/components/printing/tests/browser_preview_more_settings.js39
-rw-r--r--toolkit/components/printing/tests/browser_preview_navigation.js478
-rw-r--r--toolkit/components/printing/tests/browser_preview_print_coop.js20
-rw-r--r--toolkit/components/printing/tests/browser_preview_print_link_modulepreload.js21
-rw-r--r--toolkit/components/printing/tests/browser_print_bcg_id_overflow.js57
-rw-r--r--toolkit/components/printing/tests/browser_print_context_menu.js70
-rw-r--r--toolkit/components/printing/tests/browser_print_copies.js53
-rw-r--r--toolkit/components/printing/tests/browser_print_disabled.js43
-rw-r--r--toolkit/components/printing/tests/browser_print_duplex.js212
-rw-r--r--toolkit/components/printing/tests/browser_print_frame.js54
-rw-r--r--toolkit/components/printing/tests/browser_print_in_container.js34
-rw-r--r--toolkit/components/printing/tests/browser_print_margins.js1162
-rw-r--r--toolkit/components/printing/tests/browser_print_page_range.js540
-rw-r--r--toolkit/components/printing/tests/browser_print_page_size.js269
-rw-r--r--toolkit/components/printing/tests/browser_print_paper_sizes.js120
-rw-r--r--toolkit/components/printing/tests/browser_print_pdf_on_frame_load.js37
-rw-r--r--toolkit/components/printing/tests/browser_print_scaling.js46
-rw-r--r--toolkit/components/printing/tests/browser_print_selection.js244
-rw-r--r--toolkit/components/printing/tests/browser_print_settings_fallback.js39
-rw-r--r--toolkit/components/printing/tests/browser_print_simplified_mode.js262
-rw-r--r--toolkit/components/printing/tests/browser_print_stream.js102
-rw-r--r--toolkit/components/printing/tests/browser_sheet_count.js356
-rw-r--r--toolkit/components/printing/tests/browser_system_dialog_subdialog_hidden.js112
-rw-r--r--toolkit/components/printing/tests/browser_toolbar_button_toggle.js26
-rw-r--r--toolkit/components/printing/tests/browser_ui_labels.js23
-rw-r--r--toolkit/components/printing/tests/browser_window_print.js336
-rw-r--r--toolkit/components/printing/tests/file_coop_header.html7
-rw-r--r--toolkit/components/printing/tests/file_coop_header.html^headers^1
-rw-r--r--toolkit/components/printing/tests/file_coop_header2.html6
-rw-r--r--toolkit/components/printing/tests/file_coop_header2.html^headers^3
-rw-r--r--toolkit/components/printing/tests/file_first_landscape.html12
-rw-r--r--toolkit/components/printing/tests/file_first_portrait.html12
-rw-r--r--toolkit/components/printing/tests/file_landscape.html12
-rw-r--r--toolkit/components/printing/tests/file_link_modulepreload.html17
-rw-r--r--toolkit/components/printing/tests/file_multi_page_pdf.pdf27
-rw-r--r--toolkit/components/printing/tests/file_pdf.pdf12
-rw-r--r--toolkit/components/printing/tests/file_portrait.html12
-rw-r--r--toolkit/components/printing/tests/file_print.html6
-rw-r--r--toolkit/components/printing/tests/file_print_pdf_on_frame_load.html3
-rw-r--r--toolkit/components/printing/tests/file_window_print.html30
-rw-r--r--toolkit/components/printing/tests/file_window_print_another_iframe_and_remove.html10
-rw-r--r--toolkit/components/printing/tests/file_window_print_delayed_during_load.html14
-rw-r--r--toolkit/components/printing/tests/file_window_print_iframe_remove_on_afterprint.html31
-rw-r--r--toolkit/components/printing/tests/file_window_print_oop_iframe.html7
-rw-r--r--toolkit/components/printing/tests/file_window_print_reentrant.html12
-rw-r--r--toolkit/components/printing/tests/file_window_print_sandboxed_iframe.html8
-rw-r--r--toolkit/components/printing/tests/file_window_print_srcdoc_base_uri.html7
-rw-r--r--toolkit/components/printing/tests/head.js576
-rw-r--r--toolkit/components/printing/tests/longerArticle.html21
-rw-r--r--toolkit/components/printing/tests/page_size.html31
-rw-r--r--toolkit/components/printing/tests/page_size_a5.html31
-rw-r--r--toolkit/components/printing/tests/page_size_a5_landscape.html31
-rw-r--r--toolkit/components/printing/tests/page_size_zero.html10
-rw-r--r--toolkit/components/printing/tests/page_size_zero_width.html10
-rw-r--r--toolkit/components/printing/tests/simplifyArticleSample.html17
-rw-r--r--toolkit/components/printing/tests/simplifyNonArticleSample.html9
65 files changed, 6812 insertions, 0 deletions
diff --git a/toolkit/components/printing/tests/browser.ini b/toolkit/components/printing/tests/browser.ini
new file mode 100644
index 0000000000..e8c015601d
--- /dev/null
+++ b/toolkit/components/printing/tests/browser.ini
@@ -0,0 +1,90 @@
+[DEFAULT]
+support-files =
+ head.js
+ simplifyArticleSample.html
+
+[browser_cancel_close_print.js]
+[browser_destination_change.js]
+[browser_print_settings_fallback.js]
+[browser_empty_paper_sizes.js]
+
+[browser_modal_print.js]
+support-files =
+ file_portrait.html
+ file_landscape.html
+ file_first_portrait.html
+ file_first_landscape.html
+
+[browser_modal_resize.js]
+
+[browser_pdf_hidden_settings.js]
+support-files =
+ file_pdf.pdf
+[browser_print_copies.js]
+[browser_print_paper_sizes.js]
+[browser_pdf_printer_settings.js]
+[browser_preview_more_settings.js]
+[browser_print_bcg_id_overflow.js]
+[browser_print_context_menu.js]
+[browser_print_disabled.js]
+[browser_print_duplex.js]
+skip-if = (verify && (os == 'mac')) # bug 1675609
+[browser_print_margins.js]
+[browser_print_frame.js]
+[browser_print_selection.js]
+[browser_print_stream.js]
+[browser_print_page_range.js]
+[browser_print_page_size.js]
+support-files =
+ page_size.html
+ page_size_a5.html
+ page_size_a5_landscape.html
+ page_size_zero.html
+ page_size_zero_width.html
+[browser_print_pdf_on_frame_load.js]
+support-files =
+ file_print_pdf_on_frame_load.html
+ file_multi_page_pdf.pdf
+[browser_print_scaling.js]
+[browser_print_simplified_mode.js]
+support-files =
+ simplifyNonArticleSample.html
+[browser_sheet_count.js]
+[browser_toolbar_button_toggle.js]
+[browser_ui_labels.js]
+[browser_window_print.js]
+support-files =
+ file_window_print.html
+ file_window_print_delayed_during_load.html
+ file_window_print_oop_iframe.html
+ file_window_print_sandboxed_iframe.html
+ file_window_print_another_iframe_and_remove.html
+ file_window_print_iframe_remove_on_afterprint.html
+ file_window_print_srcdoc_base_uri.html
+ file_window_print_reentrant.html
+ file_coop_header2.html
+ file_coop_header2.html^headers^
+
+[browser_preview_in_container.js]
+support-files =
+ file_print.html
+
+[browser_preview_navigation.js]
+support-files =
+ longerArticle.html
+
+[browser_preview_print_coop.js]
+support-files =
+ file_coop_header.html
+ file_coop_header.html^headers^
+
+[browser_preview_print_link_modulepreload.js]
+support-files =
+ file_link_modulepreload.html
+
+[browser_print_in_container.js]
+skip-if =
+ tsan # Bug 1683730
+ os == "linux" && bits == 64 && debug # Bug 1683279
+ os == "linux" && asan # Bug 1683279
+[browser_system_dialog_subdialog_hidden.js]
diff --git a/toolkit/components/printing/tests/browser_cancel_close_print.js b/toolkit/components/printing/tests/browser_cancel_close_print.js
new file mode 100644
index 0000000000..7fc311a33f
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_cancel_close_print.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testCloseWhilePrinting() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.setupMockPrint();
+ helper.mockFilePicker("output.pdf");
+
+ await helper.withClosingFn(async () => {
+ let cancelButton = helper.get("cancel-button");
+ is(
+ helper.doc.l10n.getAttributes(cancelButton).id,
+ "printui-cancel-button",
+ "The cancel button is using the 'cancel' string"
+ );
+ EventUtils.sendKey("return", helper.win);
+ is(
+ helper.doc.l10n.getAttributes(cancelButton).id,
+ "printui-close-button",
+ "The cancel button is using the 'close' string"
+ );
+ helper.resolvePrint();
+ });
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_destination_change.js b/toolkit/components/printing/tests/browser_destination_change.js
new file mode 100644
index 0000000000..34d435e6df
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_destination_change.js
@@ -0,0 +1,155 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let pdfPrinterName = "Mozilla Save to PDF";
+let fastPrinterName = "Fast";
+let slowPrinterName = "Slow";
+
+async function setupPrinters(helper) {
+ helper.addMockPrinter(fastPrinterName);
+
+ let resolvePrinterInfo;
+ helper.addMockPrinter({
+ name: slowPrinterName,
+ printerInfoPromise: new Promise(resolve => {
+ resolvePrinterInfo = resolve;
+ }),
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.printer_Slow.print_orientation", 1]],
+ });
+
+ return resolvePrinterInfo;
+}
+
+async function changeDestination(helper, dir) {
+ let picker = helper.get("printer-picker");
+ let changed = BrowserTestUtils.waitForEvent(picker, "change");
+
+ let pickerOpened = BrowserTestUtils.waitForSelectPopupShown(window);
+ picker.focus();
+ EventUtils.sendKey("space", helper.win);
+ await pickerOpened;
+ EventUtils.sendKey(dir, window);
+ EventUtils.sendKey("return", window);
+ await changed;
+}
+
+function assertFormEnabled(form) {
+ for (let element of form.elements) {
+ if (element.hasAttribute("disallowed")) {
+ ok(element.disabled, `${element.id} is disallowed`);
+ } else {
+ ok(!element.disabled, `${element.id} is enabled`);
+ }
+ }
+}
+
+function assertFormDisabled(form) {
+ for (let element of form.elements) {
+ if (element.id == "printer-picker" || element.id == "cancel-button") {
+ ok(!element.disabled, `${element.id} is enabled`);
+ } else {
+ ok(element.disabled, `${element.id} is disabled`);
+ }
+ }
+}
+
+add_task(async function testSlowDestinationChange() {
+ await PrintHelper.withTestPage(async helper => {
+ let resolvePrinterInfo = await setupPrinters(helper);
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+ let printForm = helper.get("print");
+
+ info("Changing to fast printer should change settings");
+ await helper.assertSettingsChanged(
+ { printerName: pdfPrinterName, orientation: 0 },
+ { printerName: fastPrinterName, orientation: 0 },
+ async () => {
+ await changeDestination(helper, "down");
+ is(destinationPicker.value, fastPrinterName, "Fast printer selected");
+ // Wait one frame so the print settings promises resolve.
+ await helper.awaitAnimationFrame();
+ assertFormEnabled(printForm);
+ }
+ );
+
+ info("Changing to slow printer should not change settings yet");
+ await helper.assertSettingsNotChanged(
+ { printerName: fastPrinterName, orientation: 0 },
+ async () => {
+ await changeDestination(helper, "down");
+ is(destinationPicker.value, slowPrinterName, "Slow printer selected");
+ // Wait one frame, since the settings are blocked on resolvePrinterInfo
+ // the settings shouldn't change.
+ await helper.awaitAnimationFrame();
+ assertFormDisabled(printForm);
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { printerName: fastPrinterName, orientation: 0 },
+ { printerName: slowPrinterName, orientation: 1 },
+ async () => {
+ resolvePrinterInfo();
+ await helper.waitForSettingsEvent();
+ assertFormEnabled(printForm);
+ }
+ );
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testSwitchAwayFromSlowDestination() {
+ await PrintHelper.withTestPage(async helper => {
+ let resolvePrinterInfo = await setupPrinters(helper);
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+ let printForm = helper.get("print");
+
+ // Load the fast printer.
+ await helper.waitForSettingsEvent(async () => {
+ await changeDestination(helper, "down");
+ });
+ await helper.awaitAnimationFrame();
+ assertFormEnabled(printForm);
+
+ // "Load" the slow printer.
+ await changeDestination(helper, "down");
+ is(destinationPicker.value, slowPrinterName, "Slow printer selected");
+ // Wait an animation frame, since there's no settings event.
+ await helper.awaitAnimationFrame();
+ assertFormDisabled(printForm);
+
+ // Switch back to the fast printer.
+ await helper.waitForSettingsEvent(async () => {
+ await changeDestination(helper, "up");
+ });
+ helper.assertSettingsMatch({
+ printerName: fastPrinterName,
+ orientation: 0,
+ });
+
+ await helper.awaitAnimationFrame();
+ assertFormEnabled(printForm);
+
+ // Let the slow printer settings resolve, the orientation shouldn't change.
+ resolvePrinterInfo();
+ // Wait so the settings event can trigger, if this case isn't handled.
+ await helper.awaitAnimationFrame();
+ helper.assertSettingsMatch({
+ printerName: fastPrinterName,
+ orientation: 0,
+ });
+ assertFormEnabled(printForm);
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_empty_paper_sizes.js b/toolkit/components/printing/tests/browser_empty_paper_sizes.js
new file mode 100644
index 0000000000..a7ce568bb8
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_empty_paper_sizes.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testSanityCheckPaperList() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ let paperList = [
+ PrintHelper.createMockPaper({
+ id: "regular",
+ name: "Regular Paper",
+ }),
+ PrintHelper.createMockPaper({
+ id: "large",
+ name: "Large Size",
+ width: 720,
+ height: 1224,
+ }),
+ ];
+ helper.addMockPrinter({ name: mockPrinterName, paperList });
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.waitForSettingsEvent();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The Fake Printer is current printer"
+ );
+ is(
+ Object.values(helper.win.PrintSettingsViewProxy.availablePaperSizes)
+ .length,
+ 2,
+ "There are 2 paper sizes"
+ );
+ ok(
+ helper.win.PrintSettingsViewProxy.availablePaperSizes.regular,
+ "'regular' paper size is available"
+ );
+ ok(
+ helper.win.PrintSettingsViewProxy.availablePaperSizes.large,
+ "'large' paper size is available"
+ );
+ });
+});
+
+add_task(async function testEmptyPaperListGetsFallbackPaperSizes() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ is(
+ Object.values(helper.win.PrintSettingsViewProxy.availablePrinters).length,
+ 2,
+ "There are 2 available printers"
+ );
+ ok(
+ helper.win.PrintSettingsViewProxy.availablePrinters[mockPrinterName],
+ "The Fake Printer is one of our availablePrinters"
+ );
+
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.waitForSettingsEvent();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The Fake Printer is current printer"
+ );
+ is(
+ helper.get("printer-picker").value,
+ mockPrinterName,
+ "The Fake Printer is selected"
+ );
+
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].createInstance(
+ Ci.nsIPrinterList
+ );
+ let fallbackPaperList = await printerList.fallbackPaperList;
+ let paperPickerSizes = Array.from(
+ helper.get("paper-size-picker").options
+ ).map(o => o.value);
+ for (let paper of fallbackPaperList) {
+ ok(
+ helper.win.PrintSettingsViewProxy.availablePaperSizes[paper.id],
+ "Fallback paper size: " + paper.id + " is available"
+ );
+ ok(
+ paperPickerSizes.includes(paper.id),
+ "There is a paper size options for " + paper.id
+ );
+ }
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_modal_print.js b/toolkit/components/printing/tests/browser_modal_print.js
new file mode 100644
index 0000000000..c3e3f94874
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_modal_print.js
@@ -0,0 +1,309 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function assertExpectedPrintPage(helper) {
+ is(
+ helper.sourceURI,
+ PrintHelper.defaultTestPageUrlHTTPS,
+ "The URL of the browser is the one we expect"
+ );
+}
+
+add_task(async function testModalPrintDialog() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ helper.assertDialogClosed();
+
+ await helper.startPrint();
+
+ helper.assertDialogOpen();
+
+ // Check that we're printing the right page.
+ assertExpectedPrintPage(helper);
+
+ // Close the dialog with Escape.
+ await helper.withClosingFn(() => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, helper.win);
+ });
+
+ helper.assertDialogClosed();
+ });
+});
+
+add_task(async function testPrintMultiple() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ helper.assertDialogClosed();
+
+ // First print as usual.
+ await helper.startPrint();
+ helper.assertDialogOpen();
+ assertExpectedPrintPage(helper);
+
+ // Trigger the command a few more times, verify the overlay still exists.
+ ignoreAllUncaughtExceptions(true);
+ for (let i = 0; i < 3; ++i) {
+ try {
+ await helper.startPrint();
+ } finally {
+ helper.assertDialogOpen();
+ }
+ }
+ ignoreAllUncaughtExceptions(false);
+
+ // Verify it's still the correct page.
+ assertExpectedPrintPage(helper);
+
+ // Make sure we clean up, ideally this would be handled by the helper.
+ await TestUtils.waitForTick();
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testCancelButton() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ helper.assertDialogClosed();
+ await helper.startPrint();
+ helper.assertDialogOpen();
+
+ let cancelButton = helper.doc.querySelector("button[name=cancel]");
+ ok(cancelButton, "Got the cancel button");
+ await helper.withClosingFn(() =>
+ EventUtils.synthesizeMouseAtCenter(cancelButton, {}, helper.win)
+ );
+
+ helper.assertDialogClosed();
+ });
+});
+
+add_task(async function testTabOrder() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ helper.assertDialogClosed();
+ await helper.startPrint();
+ helper.assertDialogOpen();
+
+ const printerPicker = helper.doc.getElementById("printer-picker");
+ is(
+ helper.doc.activeElement,
+ printerPicker,
+ "Initial focus on printer picker"
+ );
+
+ const previewBrowser = document.querySelector(".printPreviewBrowser");
+ ok(previewBrowser, "Got the print preview browser");
+
+ let focused;
+ let navigationShadowRoot = document.querySelector(
+ ".printPreviewNavigation"
+ ).shadowRoot;
+ for (let buttonId of [
+ "navigateEnd",
+ "navigateNext",
+ "navigatePrevious",
+ "navigateHome",
+ ]) {
+ let button = navigationShadowRoot.getElementById(buttonId);
+ focused = BrowserTestUtils.waitForEvent(button, "focus");
+ await EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ await focused;
+ }
+
+ focused = BrowserTestUtils.waitForEvent(previewBrowser, "focus");
+ await EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ await focused;
+ ok(true, "Print preview focused after shift+tab through the paginator");
+
+ focused = BrowserTestUtils.waitForEvent(gNavToolbox, "focus", true);
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ await focused;
+ ok(true, "Toolbox focused after shift+tab");
+
+ focused = BrowserTestUtils.waitForEvent(previewBrowser, "focus");
+ EventUtils.synthesizeKey("KEY_Tab");
+ await focused;
+ ok(true, "Print preview focused after tab");
+
+ for (let buttonId of [
+ "navigateHome",
+ "navigatePrevious",
+ "navigateNext",
+ "navigateEnd",
+ ]) {
+ let button = navigationShadowRoot.getElementById(buttonId);
+ focused = BrowserTestUtils.waitForEvent(button, "focus");
+ await EventUtils.synthesizeKey("KEY_Tab");
+ await focused;
+ }
+ focused = BrowserTestUtils.waitForEvent(printerPicker, "focus");
+ EventUtils.synthesizeKey("KEY_Tab");
+ await focused;
+ ok(true, "Printer picker focused after tab");
+
+ const lastButtonId =
+ AppConstants.platform == "win" ? "cancel-button" : "print-button";
+ const lastButton = helper.doc.getElementById(lastButtonId);
+
+ focused = BrowserTestUtils.waitForEvent(lastButton, "focus");
+ lastButton.focus();
+ await focused;
+ ok(true, "Last button focused");
+
+ focused = BrowserTestUtils.waitForEvent(gNavToolbox, "focus", true);
+ EventUtils.synthesizeKey("KEY_Tab");
+ await focused;
+ ok(true, "Toolbox focused after tab");
+
+ focused = BrowserTestUtils.waitForEvent(lastButton, "focus");
+ EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
+ await focused;
+ ok(true, "Last button focused after shift+tab");
+
+ await helper.withClosingFn(() => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ });
+
+ helper.assertDialogClosed();
+ });
+});
+
+async function testPrintWithEnter(testFn, filename) {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ let file = helper.mockFilePicker(filename);
+ await testFn(helper);
+ await helper.assertPrintToFile(file, () => {
+ EventUtils.sendKey("return", helper.win);
+ const cancelButton = helper.doc.querySelector(`button[name="cancel"]`);
+ ok(!cancelButton.disabled, "Cancel button is not disabled");
+ const printButton = helper.doc.querySelector(`button[name="print"]`);
+ ok(printButton.disabled, "Print button is disabled");
+ });
+ });
+}
+
+add_task(async function testEnterAfterLoadPrints() {
+ info("Test print without moving focus");
+ await testPrintWithEnter(() => {}, "print_initial_focus.pdf");
+});
+
+add_task(async function testEnterPrintsFromPageRangeSelect() {
+ info("Test print from page range select");
+ await testPrintWithEnter(helper => {
+ let pageRangePicker = helper.get("range-picker");
+ pageRangePicker.focus();
+ is(
+ helper.doc.activeElement,
+ pageRangePicker,
+ "Page range select is focused"
+ );
+ }, "print_page_range_select.pdf");
+});
+
+add_task(async function testEnterPrintsFromOrientation() {
+ info("Test print on Enter from focused orientation input");
+ await testPrintWithEnter(helper => {
+ let portrait = helper.get("portrait");
+ portrait.focus();
+ is(helper.doc.activeElement, portrait, "Portrait is focused");
+ }, "print_orientation_focused.pdf");
+});
+
+add_task(async function testPrintOnNewWindowDoesntClose() {
+ info("Test that printing doesn't close a window with a single tab");
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+ let file = helper.mockFilePicker("print_new_window_close.pdf");
+ await helper.assertPrintToFile(file, () => {
+ EventUtils.sendKey("return", helper.win);
+ });
+ });
+ ok(!win.closed, "Shouldn't be closed");
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+add_task(async function testPrintProgressIndicator() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ helper.setupMockPrint();
+
+ let progressIndicator = helper.get("print-progress");
+ ok(progressIndicator.hidden, "Progress indicator is hidden");
+
+ let indicatorShown = BrowserTestUtils.waitForAttributeRemoval(
+ "hidden",
+ progressIndicator
+ );
+ helper.click(helper.get("print-button"));
+ await indicatorShown;
+
+ ok(!progressIndicator.hidden, "Progress indicator is shown on print start");
+
+ await helper.withClosingFn(async () => {
+ await helper.resolvePrint();
+ });
+ });
+});
+
+add_task(async function testPageSizePortrait() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ let orientation = helper.get("orientation");
+ ok(orientation.hidden, "Orientation selector is hidden");
+
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kPortraitOrientation,
+ "Orientation set to portrait"
+ );
+ }, "file_portrait.html");
+});
+
+add_task(async function testPageSizeLandscape() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ let orientation = helper.get("orientation");
+ ok(orientation.hidden, "Orientation selector is hidden");
+
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kLandscapeOrientation,
+ "Orientation set to landscape"
+ );
+ }, "file_landscape.html");
+});
+
+add_task(async function testFirstPageSizePortrait() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ let orientation = helper.get("orientation");
+ ok(orientation.hidden, "Orientation selector is hidden");
+
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kPortraitOrientation,
+ "Orientation set to portrait"
+ );
+ }, "file_first_portrait.html");
+});
+
+add_task(async function testFirstPageSizeLandscape() {
+ await PrintHelper.withTestPageHTTPS(async helper => {
+ await helper.startPrint();
+
+ let orientation = helper.get("orientation");
+ ok(orientation.hidden, "Orientation selector is hidden");
+
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kLandscapeOrientation,
+ "Orientation set to landscape"
+ );
+ }, "file_first_landscape.html");
+});
diff --git a/toolkit/components/printing/tests/browser_modal_resize.js b/toolkit/components/printing/tests/browser_modal_resize.js
new file mode 100644
index 0000000000..5163b5bdaf
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_modal_resize.js
@@ -0,0 +1,178 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+function waitForAnimationFrames() {
+ // Wait for 2 animation frames in hopes it's actually done resizing.
+ return new Promise(resolve =>
+ window.requestAnimationFrame(() => window.requestAnimationFrame(resolve))
+ );
+}
+
+async function mouseMoveAndWait(elem) {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(elem, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(elem, { type: "mousemove" });
+ await mouseMovePromise;
+ await TestUtils.waitForTick();
+}
+
+function closeEnough(actual, expected) {
+ return expected - 1 < actual && actual < expected + 1;
+}
+
+async function resizeWindow(x, y) {
+ let resizePromise = BrowserTestUtils.waitForEvent(window, "resize");
+ window.resizeTo(
+ x + window.outerWidth - window.innerWidth,
+ y + window.outerHeight - window.innerHeight
+ );
+ await resizePromise;
+
+ await waitForAnimationFrames();
+
+ ok(
+ closeEnough(window.innerWidth, x),
+ `Window innerWidth ${window.innerWidth} is close enough to ${x}`
+ );
+ ok(
+ closeEnough(window.innerHeight, y),
+ `Window innerHeight ${window.innerHeight} is close enough to ${y}`
+ );
+}
+
+async function waitForExpectedSize(helper, x, y) {
+ // Wait a few frames, this is generally enough for the resize to happen.
+ await waitForAnimationFrames();
+
+ let isExpectedSize = () => {
+ let box = helper.dialog._box;
+ info(`Dialog is ${box.clientWidth}x${box.clientHeight}`);
+ if (closeEnough(box.clientWidth, x) && closeEnough(box.clientHeight, y)) {
+ // Make sure there's an assertion so the test passes.
+ ok(true, `${box.clientWidth} close enough to ${x}`);
+ ok(true, `${box.clientHeight} close enough to ${y}`);
+ return true;
+ }
+ return false;
+ };
+
+ if (isExpectedSize()) {
+ // We can stop now if we hit the expected size.
+ return;
+ }
+
+ // In verify and debug runs sometimes this takes longer than expected,
+ // fallback to the slow method.
+ await TestUtils.waitForCondition(isExpectedSize, `Wait for ${x}x${y}`);
+}
+
+async function checkPreviewNavigationVisibility(expected) {
+ function isHidden(elem) {
+ // BTU.is_hidden can't handle shadow DOM elements
+ return !elem.getBoundingClientRect().height;
+ }
+
+ let previewStack = document.querySelector(".previewStack");
+ let paginationElem = document.querySelector(".printPreviewNavigation");
+ // move the mouse to a known position, then back to the preview to show the paginator
+ await mouseMoveAndWait(gURLBar.textbox);
+ await mouseMoveAndWait(previewStack);
+
+ ok(
+ BrowserTestUtils.is_visible(paginationElem),
+ "The preview pagination toolbar is visible"
+ );
+ for (let [id, visible] of Object.entries(expected)) {
+ let elem = paginationElem.shadowRoot.querySelector(`#${id}`);
+ if (visible) {
+ ok(!isHidden(elem), `Navigation element ${id} is visible`);
+ } else {
+ ok(isHidden(elem), `Navigation element ${id} is hidden`);
+ }
+ }
+}
+
+add_task(async function testResizing() {
+ if (window.windowState != window.STATE_NORMAL) {
+ todo_is(
+ window.windowState,
+ window.STATE_NORMAL,
+ "windowState should be STATE_NORMAL"
+ );
+ // On Windows the size of the window decoration depends on the size mode.
+ // Trying to set the inner size of a maximized window changes the size mode
+ // but calculates the new window size with the maximized window
+ // decorations. On Linux a maximized window can also cause problems when
+ // the window was maximized recently and the corresponding resize event is
+ // still outstanding.
+ window.restore();
+ // On Linux we would have to wait for the resize event here, but the
+ // restored and maximized size can also be equal. Brute forcing a resize
+ // to a specific size works around that.
+ await BrowserTestUtils.waitForCondition(async () => {
+ let width = window.screen.availWidth * 0.75;
+ let height = window.screen.availHeight * 0.75;
+ window.resizeTo(width, height);
+ return (
+ closeEnough(window.outerWidth, width) &&
+ closeEnough(window.outerHeight, height)
+ );
+ });
+ }
+
+ await PrintHelper.withTestPage(async helper => {
+ let { innerWidth, innerHeight } = window;
+
+ await resizeWindow(500, 400);
+
+ await helper.startPrint();
+
+ let chromeHeight = window.windowUtils.getBoundsWithoutFlushing(
+ document.getElementById("browser")
+ ).top;
+
+ let initialWidth = 500 - 8;
+ let initialHeight = 400 - 16 - chromeHeight + 5;
+
+ await waitForExpectedSize(helper, initialWidth, initialHeight);
+
+ // check the preview pagination state for this window size
+ await checkPreviewNavigationVisibility({
+ navigateHome: false,
+ navigatePrevious: false,
+ navigateNext: false,
+ navigateEnd: false,
+ sheetIndicator: true,
+ });
+
+ await resizeWindow(600, 500);
+
+ await checkPreviewNavigationVisibility({
+ navigateHome: true,
+ navigatePrevious: true,
+ navigateNext: true,
+ navigateEnd: true,
+ sheetIndicator: true,
+ });
+
+ // 100 wider for window, add back the old 4px padding, it's now 16px * 2.
+ let updatedWidth = initialWidth + 100 + 8 - 32;
+ await waitForExpectedSize(helper, updatedWidth, initialHeight + 100);
+
+ await resizeWindow(1100, 900);
+
+ await waitForExpectedSize(helper, 1000, 650);
+
+ await checkPreviewNavigationVisibility({
+ navigateHome: true,
+ navigatePrevious: true,
+ navigateNext: true,
+ navigateEnd: true,
+ sheetIndicator: true,
+ });
+
+ await helper.closeDialog();
+
+ await resizeWindow(innerWidth, innerHeight);
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_pdf_hidden_settings.js b/toolkit/components/printing/tests/browser_pdf_hidden_settings.js
new file mode 100644
index 0000000000..2967d91882
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_pdf_hidden_settings.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const hiddenPdfIds = ["backgrounds", "source-version-selection"];
+
+async function checkElements({ removed, file, testName }) {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ for (let id of hiddenPdfIds) {
+ is(
+ !helper.get(id),
+ removed,
+ `${id} is ${removed ? "" : "not "}removed (${testName})`
+ );
+ }
+
+ await helper.closeDialog();
+ }, file);
+}
+
+add_task(async function testSettingsShownForNonPdf() {
+ await checkElements({ removed: false, testName: "non-pdf" });
+});
+
+add_task(async function testSettingsHiddenForPdf() {
+ await checkElements({
+ removed: true,
+ file: "file_pdf.pdf",
+ testName: "pdf",
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_pdf_printer_settings.js b/toolkit/components/printing/tests/browser_pdf_printer_settings.js
new file mode 100644
index 0000000000..16a7d86600
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_pdf_printer_settings.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testPDFPrinterSettings() {
+ await PrintHelper.withTestPage(async helper => {
+ // Set some bad prefs
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.print_to_file", false],
+ ["print.print_in_color", false],
+ ["print.printer_Mozilla_Save_to_PDF.print_to_file", false],
+ ["print.printer_Mozilla_Save_to_PDF.print_in_color", false],
+ ],
+ });
+
+ await helper.startPrint();
+ await helper.awaitAnimationFrame();
+
+ // Verify we end up with sane settings
+ let { settings } = helper;
+
+ ok(
+ settings.outputDestination == Ci.nsIPrintSettings.kOutputDestinationFile,
+ "Check the current settings have file destination"
+ );
+ ok(
+ settings.printInColor,
+ "Check the current settings have a truthy printInColor for the PDF printer"
+ );
+ is(
+ settings.outputFormat,
+ Ci.nsIPrintSettings.kOutputFormatPDF,
+ "The PDF printer has the correct outputFormat"
+ );
+
+ await helper.closeDialog();
+ await SpecialPowers.popPrefEnv();
+ });
+});
+
+add_task(async function testPDFCancel() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ helper.mockFilePickerCancel();
+ let form = helper.doc.querySelector("#print");
+
+ // retrieve all elements other than cancel button
+ let elements = [];
+ for (let element of form.elements) {
+ if (element.name != "cancel") {
+ elements.push(element);
+ }
+ }
+ let getDisabledStates = () => elements.map(el => el.disabled);
+ let initialDisabledStates = getDisabledStates();
+
+ ok(
+ initialDisabledStates.some(disabled => !disabled),
+ "At least one enabled form element before submitting"
+ );
+ let getShownDisabledStates = new Promise(resolve => {
+ MockFilePicker.showCallback = () => resolve(getDisabledStates());
+ });
+
+ EventUtils.sendKey("return", helper.win);
+
+ let shownDisabledStates = await getShownDisabledStates;
+ ok(shownDisabledStates, "Got disabled states while shown");
+ ok(
+ shownDisabledStates.every(disabled => disabled),
+ "All elements were disabled when showing picker"
+ );
+ let cancelButton = helper.doc.querySelector(`button[name="cancel"]`);
+ ok(!cancelButton.disabled, "Cancel button is still enabled");
+
+ let saveButton = form.querySelector("#print-button");
+ await BrowserTestUtils.waitForAttributeRemoval("disabled", saveButton);
+ helper.assertDialogOpen();
+
+ is(
+ getDisabledStates().every(
+ (disabledState, index) => disabledState === initialDisabledStates[index]
+ ),
+ true,
+ "Previous disabled states match after returning to preview"
+ );
+
+ // Close the dialog with Escape.
+ await helper.withClosingFn(() => {
+ EventUtils.synthesizeKey("VK_ESCAPE", {}, helper.win);
+ });
+
+ helper.assertDialogClosed();
+ });
+});
+
+add_task(async function testPDFFile() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ helper.mockFilePicker("pdfFile.pdf");
+ let filePath = PathUtils.join(
+ Services.dirsvc.get("TmpD", Ci.nsIFile).path,
+ "pdfFile.pdf"
+ );
+
+ await helper.withClosingFn(() => {
+ EventUtils.sendKey("return", helper.win);
+ });
+
+ try {
+ Services.prefs.getStringPref(
+ "print.printer_Mozilla_Save_to_PDF.print_to_filename"
+ );
+ ok(false, "Should have cleared the filename pref");
+ } catch (ex) {
+ ok(true, "Cleared the filename pref");
+ }
+
+ is(await IOUtils.exists(filePath), true, "Saved pdf file exists");
+ ok(await IOUtils.read(filePath), "Saved pdf file is not empty");
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_preview_in_container.js b/toolkit/components/printing/tests/browser_preview_in_container.js
new file mode 100644
index 0000000000..297da14878
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_preview_in_container.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+async function runTest() {
+ is(
+ document.querySelector("print-preview"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ gBrowser.selectedTab = await BrowserTestUtils.addTab(
+ gBrowser,
+ `${TEST_PATH}file_print.html`,
+ { userContextId: 1 }
+ );
+
+ // Wait for window.print() to run and ensure we're showing the preview...
+ await waitForPreviewVisible();
+
+ let printPreviewEl = document.querySelector("print-preview");
+ await BrowserTestUtils.waitForCondition(
+ () => !!printPreviewEl.settingsBrowser.contentWindow._initialized
+ );
+ await printPreviewEl.settingsBrowser.contentWindow._initialized;
+ let contentFound = await SpecialPowers.spawn(
+ printPreviewEl.sourceBrowser,
+ [],
+ () => {
+ return !!content.document.getElementById("printed");
+ }
+ );
+ ok(contentFound, "We should find the preview content.");
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+}
+
+add_task(async function test_in_container() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate", false]],
+ });
+
+ await runTest();
+});
+
+add_task(async function test_with_fpi() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["privacy.firstparty.isolate", true]],
+ });
+ await runTest();
+});
diff --git a/toolkit/components/printing/tests/browser_preview_more_settings.js b/toolkit/components/printing/tests/browser_preview_more_settings.js
new file mode 100644
index 0000000000..3591e66164
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_preview_more_settings.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function moreSettingsHonorPref() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ ok(!helper.get("more-settings").open, "More settings is closed");
+
+ await helper.openMoreSettings();
+ ok(
+ Services.prefs.getBoolPref("print.more-settings.open"),
+ "More settings pref has been flipped to true"
+ );
+
+ await helper.closeDialog();
+
+ await helper.startPrint();
+
+ ok(helper.get("more-settings").open, "More settings is open");
+
+ helper.click(helper.get("more-settings").firstElementChild);
+ await helper.awaitAnimationFrame();
+ ok(
+ !Services.prefs.getBoolPref("print.more-settings.open"),
+ "More settings pref has been flipped to false"
+ );
+
+ await helper.closeDialog();
+
+ await helper.startPrint();
+
+ ok(!helper.get("more-settings").open, "More settings is closed");
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_preview_navigation.js b/toolkit/components/printing/tests/browser_preview_navigation.js
new file mode 100644
index 0000000000..1dc23fa9f4
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_preview_navigation.js
@@ -0,0 +1,478 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function compare10nArgs(elem, expectedValues) {
+ let l10nArgs = elem.ownerDocument.l10n.getAttributes(elem).args;
+ for (let [name, value] of Object.entries(expectedValues)) {
+ if (value !== l10nArgs[name]) {
+ info(
+ `compare10nArgs, expected ${name}: ${value}, actual: ${l10nArgs[name]}`
+ );
+ return false;
+ }
+ }
+ return true;
+}
+
+async function waitForPageStatusUpdate(elem, expected, message) {
+ await TestUtils.waitForCondition(
+ () => compare10nArgs(elem, expected),
+ message
+ );
+}
+
+async function waitUntilVisible(elem, visible = true) {
+ await TestUtils.waitForCondition(
+ () =>
+ BrowserTestUtils.is_visible(elem) &&
+ getComputedStyle(elem).opacity == "1",
+ "Waiting for element to be visible and have opacity:1"
+ );
+}
+
+async function waitUntilTransparent(elem) {
+ // Note that is_visible considers a fully transparent element "visible"
+ await TestUtils.waitForCondition(
+ () => getComputedStyle(elem).opacity == "0",
+ "Waiting for element to be have opacity:0"
+ );
+}
+
+async function mouseMoveAndWait(elem) {
+ let mouseMovePromise = BrowserTestUtils.waitForEvent(elem, "mousemove");
+ EventUtils.synthesizeMouseAtCenter(elem, { type: "mousemove" });
+ await mouseMovePromise;
+ await TestUtils.waitForTick();
+}
+
+add_task(async function testToolbarVisibility() {
+ // move the mouse to a known position
+ await mouseMoveAndWait(gURLBar.textbox);
+
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ let previewStack = document.querySelector(".previewStack");
+
+ // The toolbar has 0 opacity until we hover or focus it
+ is(
+ getComputedStyle(helper.paginationElem).opacity,
+ "0",
+ "Initially transparent"
+ );
+
+ let visiblePromise = waitUntilVisible(helper.paginationElem);
+ helper.paginationElem.shadowRoot.querySelector("#navigateEnd").focus();
+ await visiblePromise;
+ is(
+ getComputedStyle(helper.paginationElem).opacity,
+ "1",
+ "Opaque with button focused"
+ );
+
+ await EventUtils.synthesizeKey("KEY_Tab", {});
+ await waitUntilTransparent(helper.paginationElem);
+ is(
+ getComputedStyle(helper.paginationElem).opacity,
+ "0",
+ "Returns to transparent"
+ );
+
+ visiblePromise = waitUntilVisible(helper.paginationElem);
+ info("Waiting for mousemove event, and for the toolbar to become opaque");
+ await mouseMoveAndWait(previewStack);
+ await visiblePromise;
+ is(getComputedStyle(helper.paginationElem).opacity, "1", "Opaque toolbar");
+
+ // put the mouse back where it won't interfere with later tests
+ await mouseMoveAndWait(gURLBar.textbox);
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPreviewSheetCount() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ // We have to wait for the first _updatePrintPreview to get the sheet count
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ // then switch to page range 1-1 and verify page count changes
+ await helper.dispatchSettingsChange({
+ pageRanges: ["1", "1"],
+ });
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 1 },
+ "Indicates the updated number of sheets"
+ );
+
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
+
+add_task(async function testPreviewScroll() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+ let previewBrowser = helper.currentPrintPreviewBrowser;
+
+ // scroll down the document
+ // and verify the indicator is updated correctly
+ await SpecialPowers.spawn(previewBrowser, [], async function () {
+ const { ContentTaskUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/ContentTaskUtils.sys.mjs"
+ );
+ const EventUtils = ContentTaskUtils.getEventUtils(content);
+ content.focus();
+ EventUtils.synthesizeKey("VK_PAGE_DOWN", {}, content);
+ });
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 3 },
+ "Indicator updates on scroll"
+ );
+
+ // move focus before closing the dialog
+ helper.get("cancel-button").focus();
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
+
+add_task(async function testPreviewNavigationCommands() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ // click the navigation buttons
+ // and verify the indicator is updated correctly
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 3 },
+ "Indicator updates on navigation to next"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Indicator updates on navigation to previous"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateEnd"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 3, sheetCount: 3 },
+ "Indicator updates on navigation to end"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateHome"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Indicator updates on navigation to start"
+ );
+
+ // Test rapid clicks on the navigation buttons
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 3, sheetCount: 3 },
+ "2 successive 'next' clicks correctly update the sheet indicator"
+ );
+
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
+ {}
+ );
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigatePrevious"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "2 successive 'previous' clicks correctly update the sheet indicator"
+ );
+
+ // move focus before closing the dialog
+ helper.get("cancel-button").focus();
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
+
+add_task(async function testMultiplePreviewNavigation() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ const tab1 = gBrowser.selectedTab;
+
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Indicator has the correct initial sheetCount"
+ );
+
+ const tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ PrintHelper.defaultTestPageUrl
+ );
+ let helper2 = new PrintHelper(tab2.linkedBrowser);
+ await helper2.startPrint();
+
+ let [previewBrowser1, previewBrowser2] = document.querySelectorAll(
+ ".printPreviewBrowser[previewtype='source']"
+ );
+ ok(previewBrowser1 && previewBrowser2, "There are 2 preview browsers");
+
+ let [toolbar1, toolbar2] = document.querySelectorAll(
+ ".printPreviewNavigation"
+ );
+ ok(toolbar1 && toolbar2, "There are 2 preview navigation toolbars");
+ is(
+ toolbar1.previewBrowser,
+ previewBrowser1,
+ "toolbar1 has the correct previewBrowser"
+ );
+ ok(
+ compare10nArgs(helper.paginationSheetIndicator, {
+ sheetNum: 1,
+ sheetCount: 3,
+ }),
+ "First toolbar has the correct content"
+ );
+
+ is(
+ toolbar2.previewBrowser,
+ previewBrowser2,
+ "toolbar2 has the correct previewBrowser"
+ );
+ ok(
+ compare10nArgs(helper2.paginationSheetIndicator, {
+ sheetNum: 1,
+ sheetCount: 1,
+ }),
+ "2nd toolbar has the correct content"
+ );
+
+ // Switch back to the first tab and ensure the correct preview navigation is updated when clicked
+ await BrowserTestUtils.switchTab(gBrowser, tab1);
+
+ EventUtils.synthesizeMouseAtCenter(
+ toolbar1.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 3 },
+ "Indicator updates on navigation multiple"
+ );
+
+ gBrowser.removeTab(tab2);
+ }, "longerArticle.html");
+});
+
+add_task(async function testPreviewNavigationSelection() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.spawn(helper.sourceBrowser, [], async function () {
+ let element = content.document.querySelector("#page-2");
+ content.window.getSelection().selectAllChildren(element);
+ });
+
+ await helper.startPrint();
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ // click a navigation button
+ // and verify the indicator is updated correctly
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 3 },
+ "Indicator updates on navigation next selection"
+ );
+
+ await helper.openMoreSettings();
+ let printSelect = helper.get("source-version-selection-radio");
+ await helper.waitForPreview(() => helper.click(printSelect));
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 2 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ // click a navigation button
+ // and verify the indicator is updated correctly
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 2 },
+ "Indicator updates on navigation next selection 2"
+ );
+
+ // move focus before closing the dialog
+ helper.get("cancel-button").focus();
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
+
+add_task(async function testPaginatorAfterSettingsUpdate() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ // click the navigation buttons
+ // and verify the indicator is updated correctly
+ EventUtils.synthesizeMouseAtCenter(
+ helper.paginationElem.shadowRoot.querySelector("#navigateNext"),
+ {}
+ );
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 2, sheetCount: 3 },
+ "Indicator updates on navigation next after update"
+ );
+
+ // Select a new printer
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await waitForPageStatusUpdate(
+ helper.paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Indicator updates on navigation next after printer change"
+ );
+ ok(
+ compare10nArgs(helper.paginationSheetIndicator, {
+ sheetNum: 1,
+ sheetCount: 3,
+ }),
+ "Sheet indicator has correct value"
+ );
+
+ // move focus before closing the dialog
+ helper.get("cancel-button").focus();
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
+
+add_task(async function testTooltips() {
+ await SpecialPowers.pushPrefEnv({ set: [["ui.tooltipDelay", 0]] });
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ let paginationElem = document.querySelector(".printPreviewNavigation");
+ let paginationSheetIndicator =
+ paginationElem.shadowRoot.querySelector("#sheetIndicator");
+
+ // Wait for the first _updatePrintPreview before interacting with the preview
+ await waitForPageStatusUpdate(
+ paginationSheetIndicator,
+ { sheetNum: 1, sheetCount: 3 },
+ "Paginator indicates the correct number of sheets"
+ );
+
+ let awaitTooltipOpen = new Promise(resolve => {
+ window.addEventListener(
+ "popupshown",
+ function (event) {
+ resolve(event.originalTarget);
+ },
+ { once: true }
+ );
+ });
+
+ let navigateEnd = paginationElem.shadowRoot.querySelector("#navigateEnd");
+ info("Initial mouse move to end navigation button");
+ EventUtils.synthesizeMouseAtCenter(navigateEnd, { type: "mousemove" });
+ let tooltip = await awaitTooltipOpen;
+ is(tooltip.label, navigateEnd.title, "Tooltip shows correct text");
+ awaitTooltipOpen = new Promise(resolve => {
+ window.addEventListener(
+ "popupshown",
+ function (event) {
+ resolve(event.originalTarget);
+ },
+ { once: true }
+ );
+ });
+
+ let navigateNext = paginationElem.shadowRoot.querySelector("#navigateNext");
+ let navigateNextRect = navigateNext.getBoundingClientRect();
+ info("Initial mouse move to next navigation button");
+ EventUtils.synthesizeMouseAtCenter(navigateNext, { type: "mousemove" });
+ info("Waiting");
+ EventUtils.synthesizeMouse(
+ navigateNext,
+ navigateNextRect.width / 2 + 5,
+ navigateNextRect.height / 2,
+ { type: "mousemove" },
+ window
+ );
+ tooltip = await awaitTooltipOpen;
+ is(tooltip.label, navigateNext.title, "Tooltip shows correct text");
+
+ // move focus before closing the dialog
+ helper.get("cancel-button").focus();
+ await helper.awaitAnimationFrame();
+ await helper.closeDialog();
+ }, "longerArticle.html");
+});
diff --git a/toolkit/components/printing/tests/browser_preview_print_coop.js b/toolkit/components/printing/tests/browser_preview_print_coop.js
new file mode 100644
index 0000000000..433c21e7bd
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_preview_print_coop.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+/**
+ * Verify if the page with a COOP header can be used for printing preview.
+ */
+add_task(async function testTabModal() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ ok(true, "We did not crash.");
+ await helper.closeDialog();
+ }, "file_coop_header.html");
+});
diff --git a/toolkit/components/printing/tests/browser_preview_print_link_modulepreload.js b/toolkit/components/printing/tests/browser_preview_print_link_modulepreload.js
new file mode 100644
index 0000000000..84b6f2a0be
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_preview_print_link_modulepreload.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+add_task(async function testLinkModulePreload() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.importMaps.enabled", true]],
+ });
+
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ ok(true, "We did not crash.");
+ await helper.closeDialog();
+ }, "file_link_modulepreload.html");
+});
diff --git a/toolkit/components/printing/tests/browser_print_bcg_id_overflow.js b/toolkit/components/printing/tests/browser_print_bcg_id_overflow.js
new file mode 100644
index 0000000000..3053afe236
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_bcg_id_overflow.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+// The actual uri we open doesn't really matter.
+const OPENED_URI = PrintHelper.defaultTestPageUrlHTTPS;
+
+// Test for bug 1669554:
+//
+// This opens a rel=noopener window in a content process, which causes us to
+// create a browsing context with an id that likely overflows an int32_t, which
+// caused us to fail to parse the initialBrowsingContextGroupId attribute
+// (causing us to potentially clone in the wrong process, etc).
+const OPEN_NOOPENER_WINDOW = `
+ <a rel="noopener" target="_blank" href="${OPENED_URI}">Open the window</a>
+`;
+
+add_task(async function test_bc_id_overflow() {
+ is(document.querySelector(".printPreviewBrowser"), null);
+
+ await BrowserTestUtils.withNewTab(
+ `data:text/html,` + encodeURIComponent(OPEN_NOOPENER_WINDOW),
+ async function (browser) {
+ let tabOpenedPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ OPENED_URI,
+ /* waitForLoad = */ true
+ );
+ await BrowserTestUtils.synthesizeMouse("a", 0, 0, {}, browser);
+ let tab = await tabOpenedPromise;
+ let helper = new PrintHelper(tab.linkedBrowser);
+ await helper.startPrint();
+ helper.assertDialogOpen();
+
+ let previewBrowser = document.querySelector(".printPreviewBrowser");
+ is(typeof previewBrowser.browsingContext.group.id, "number", "Sanity");
+ is(
+ previewBrowser.browsingContext.group.id,
+ tab.linkedBrowser.browsingContext.group.id,
+ "Group ids should match: " + tab.linkedBrowser.browsingContext.group.id
+ );
+ is(
+ previewBrowser.browsingContext.group,
+ tab.linkedBrowser.browsingContext.group,
+ "Groups should match"
+ );
+ await helper.closeDialog();
+ await BrowserTestUtils.removeTab(tab);
+ }
+ );
+});
diff --git a/toolkit/components/printing/tests/browser_print_context_menu.js b/toolkit/components/printing/tests/browser_print_context_menu.js
new file mode 100644
index 0000000000..39cb8d3623
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_context_menu.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const frameSource = `<a href="about:mozilla">Inner frame</a>`;
+const source = `<html><h1>Top level text</h1><iframe srcdoc='${frameSource}' id="f"></iframe></html>`;
+
+add_task(async function testPrintFrame() {
+ let url = `data:text/html,${source}`;
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url },
+ async function (browser) {
+ let contentAreaContextMenuPopup = document.getElementById(
+ "contentAreaContextMenu"
+ );
+ let popupShownPromise = BrowserTestUtils.waitForEvent(
+ contentAreaContextMenuPopup,
+ "popupshown"
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#f",
+ { type: "contextmenu", button: 2 },
+ gBrowser.selectedBrowser
+ );
+ await popupShownPromise;
+
+ let frameItem = document.getElementById("frame");
+ let frameContextMenu = frameItem.menupopup;
+ popupShownPromise = BrowserTestUtils.waitForEvent(
+ frameContextMenu,
+ "popupshown"
+ );
+ frameItem.openMenu(true);
+ await popupShownPromise;
+
+ let popupHiddenPromise = BrowserTestUtils.waitForEvent(
+ frameContextMenu,
+ "popuphidden"
+ );
+ let item = document.getElementById("context-printframe");
+ frameContextMenu.activateItem(item);
+ await popupHiddenPromise;
+
+ let helper = new PrintHelper(browser);
+
+ await helper.waitForDialog();
+
+ let previewBrowser = helper.currentPrintPreviewBrowser;
+ is(
+ previewBrowser.getAttribute("previewtype"),
+ "source",
+ "Source preview was rendered"
+ );
+
+ let textContent = await SpecialPowers.spawn(
+ previewBrowser,
+ [],
+ () => content.document.body.textContent
+ );
+
+ is(textContent, "Inner frame", "Correct content loaded");
+ is(
+ helper.win.PrintEventHandler.printFrameOnly,
+ true,
+ "Print frame only is true"
+ );
+ PrintHelper.resetPrintPrefs();
+ }
+ );
+});
diff --git a/toolkit/components/printing/tests/browser_print_copies.js b/toolkit/components/printing/tests/browser_print_copies.js
new file mode 100644
index 0000000000..9a97326f18
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_copies.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testCopyError() {
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter("A printer");
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", "A printer"]],
+ });
+
+ await helper.startPrint();
+
+ let copyInput = helper.get("copies-count");
+ let destinationPicker = helper.get("printer-picker");
+ let copyError = helper.get("error-invalid-copies");
+
+ await helper.assertSettingsChanged(
+ { numCopies: 1 },
+ { numCopies: 10000 },
+ async () => {
+ await helper.waitForSettingsEvent(() => {
+ helper.text(copyInput, "10000");
+ });
+
+ is(copyError.hidden, true, "Copy error is hidden");
+ EventUtils.sendChar("0", helper.win);
+
+ // Initially, the copies will be more than the max.
+ is(copyInput.checkValidity(), false, "Copy count is invalid");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", copyError);
+ is(copyError.hidden, false, "Copy error is showing");
+ is(
+ destinationPicker.disabled,
+ false,
+ "Destination picker is still enabled"
+ );
+
+ helper.text(copyInput, "10000");
+ await helper.waitForSettingsEvent();
+ is(copyInput.value, "10000", "Copies gets set to max value");
+ is(copyInput.checkValidity(), true, "Copy count is valid again");
+ await BrowserTestUtils.waitForCondition(
+ () => copyError.hidden,
+ "Wait for copy error to be hidden"
+ );
+ }
+ );
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_disabled.js b/toolkit/components/printing/tests/browser_print_disabled.js
new file mode 100644
index 0000000000..87315d5952
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_disabled.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_setup(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.enabled", false]],
+ });
+});
+
+add_task(async function test_print_commands_disabled() {
+ let printCommand = document.getElementById(`cmd_print`);
+ let printPreview = document.getElementById(`cmd_printPreviewToggle`);
+
+ is(
+ printCommand.getAttribute("disabled"),
+ "true",
+ "print command is disabled when print.enabled=false"
+ );
+ is(
+ printPreview.getAttribute("disabled"),
+ "true",
+ "print preview command is disabled when print.enabled=false"
+ );
+});
+
+add_task(async function test_window_print_disabled() {
+ await BrowserTestUtils.withNewTab("https://example.org/", async browser => {
+ await SpecialPowers.spawn(browser, [], function () {
+ content.window.print();
+ });
+
+ // When the print dialog is open, the main thread of our tab is blocked, so
+ // the failure mode of this test is a timeout caused by the print dialog never
+ // closing.
+
+ Assert.ok(
+ true,
+ "window.print doesn't do anything when print.enabled=false"
+ );
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_duplex.js b/toolkit/components/printing/tests/browser_print_duplex.js
new file mode 100644
index 0000000000..9db33ce80e
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_duplex.js
@@ -0,0 +1,212 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function changeToOption(helper, index) {
+ return helper.waitForSettingsEvent(async function () {
+ let select = helper.get("duplex-select");
+ select.focus();
+ select.scrollIntoView({ block: "center" });
+
+ let popupOpen = BrowserTestUtils.waitForSelectPopupShown(window);
+ EventUtils.sendKey("space", helper.win);
+ await popupOpen;
+
+ let selectedIndex = select.selectedIndex;
+ info(`Looking for ${index} from ${selectedIndex}`);
+ while (selectedIndex != index) {
+ if (index > selectedIndex) {
+ EventUtils.sendKey("down", window);
+ selectedIndex++;
+ } else {
+ EventUtils.sendKey("up", window);
+ selectedIndex--;
+ }
+ }
+ EventUtils.sendKey("return", window);
+ });
+}
+
+add_task(async function testPDFPrinterIsNonDuplex() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ is(
+ helper.settings.printerName,
+ "Mozilla Save to PDF",
+ "Mozilla Save to PDF is the current printer."
+ );
+
+ const duplexSection = helper.get("two-sided-printing");
+ ok(
+ duplexSection.hidden,
+ "The two-sided printing section should be hidden when the printer does not support duplex."
+ );
+
+ helper.assertSettingsMatch({ duplex: Ci.nsIPrintSettings.kDuplexNone });
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testToggleDuplexWithPortraitOrientation() {
+ const mockPrinterName = "DuplexWithPortrait";
+ await PrintHelper.withTestPage(async helper => {
+ const printer = helper.addMockPrinter(mockPrinterName);
+ printer.supportsDuplex = Promise.resolve(true);
+
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.awaitAnimationFrame();
+ await helper.openMoreSettings();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The Fake Printer is current printer"
+ );
+
+ const duplexSection = helper.get("two-sided-printing");
+ ok(
+ !duplexSection.hidden,
+ "The two-sided printing section should not be hidden when the printer supports duplex."
+ );
+
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await changeToOption(helper, 1);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ });
+
+ await changeToOption(helper, 2);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnShortEdge,
+ });
+
+ await changeToOption(helper, 0);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testToggleDuplexWithLandscapeOrientation() {
+ const mockPrinterName = "DuplexWithLandscape";
+ await PrintHelper.withTestPage(async helper => {
+ const printer = helper.addMockPrinter(mockPrinterName);
+ printer.supportsDuplex = Promise.resolve(true);
+
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.awaitAnimationFrame();
+ await helper.openMoreSettings();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The Fake Printer is current printer"
+ );
+
+ const duplexSection = helper.get("two-sided-printing");
+ ok(
+ !duplexSection.hidden,
+ "The two-sided printing section should not be hidden when the printer supports duplex."
+ );
+
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await helper.dispatchSettingsChange({ orientation: 1 });
+ await helper.awaitAnimationFrame();
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kLandscapeOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await changeToOption(helper, 1);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kLandscapeOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ });
+
+ await changeToOption(helper, 2);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kLandscapeOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnShortEdge,
+ });
+
+ await changeToOption(helper, 0);
+ helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kLandscapeOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testSwitchOrientationWithDuplexEnabled() {
+ const mockPrinterName = "ToggleOrientationPrinter";
+ await PrintHelper.withTestPage(async helper => {
+ const printer = helper.addMockPrinter(mockPrinterName);
+ printer.supportsDuplex = Promise.resolve(true);
+
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.awaitAnimationFrame();
+ await helper.openMoreSettings();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The Fake Printer is current printer"
+ );
+
+ const duplexSection = helper.get("two-sided-printing");
+ ok(
+ !duplexSection.hidden,
+ "The two-sided printing section should not be hidden when the printer supports duplex."
+ );
+
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ });
+
+ await changeToOption(helper, 1);
+
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ });
+
+ await helper.dispatchSettingsChange({ orientation: 1 });
+ await helper.awaitAnimationFrame();
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kLandscapeOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ });
+
+ await helper.dispatchSettingsChange({ orientation: 0 });
+ await helper.awaitAnimationFrame();
+ await helper.assertSettingsMatch({
+ orientation: Ci.nsIPrintSettings.kPortraitOrientation,
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ });
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_frame.js b/toolkit/components/printing/tests/browser_print_frame.js
new file mode 100644
index 0000000000..b39925dfcd
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_frame.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const frameSource = "<a href='about:mozilla'>some text</a>";
+const SOURCES = [
+ `Something else <iframe id="f" srcdoc="${frameSource}"></iframe>`,
+ `Something else <iframe id="f" src="https://example.com/document-builder.sjs?html=${frameSource}"></iframe>`,
+];
+
+async function getPreviewText(previewBrowser) {
+ return SpecialPowers.spawn(previewBrowser, [], function () {
+ return content.document.body.textContent;
+ });
+}
+
+add_task(async function print_frame() {
+ let i = 0;
+ for (const source of SOURCES) {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "data:text/html," + source,
+ async function (browser) {
+ let frameBC = browser.browsingContext.children[0];
+ let helper = new PrintHelper(browser);
+
+ // If you change this, change nsContextMenu.printFrame() too.
+ PrintUtils.startPrintWindow(frameBC, {
+ printFrameOnly: true,
+ });
+
+ // Wait for the dialog to be fully ready. The initial preview will be
+ // done at this point.
+ await helper.waitForDialog();
+
+ let textContent = await getPreviewText(
+ helper.currentPrintPreviewBrowser
+ );
+ is(textContent, "some text", "Correct content loaded");
+
+ let file = helper.mockFilePicker(`browser_print_frame-${i++}.pdf`);
+ await helper.assertPrintToFile(file, () => {
+ helper.click(helper.get("print-button"));
+ });
+ PrintHelper.resetPrintPrefs();
+ }
+ );
+ }
+});
diff --git a/toolkit/components/printing/tests/browser_print_in_container.js b/toolkit/components/printing/tests/browser_print_in_container.js
new file mode 100644
index 0000000000..e8cec976fb
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_in_container.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+add_task(async function test() {
+ let tab = await BrowserTestUtils.switchTab(gBrowser, function () {
+ gBrowser.selectedTab = BrowserTestUtils.addTab(
+ gBrowser,
+ `${TEST_PATH}simplifyArticleSample.html`,
+ { userContextId: 1 }
+ );
+ });
+
+ const helper = new PrintHelper(tab.linkedBrowser);
+
+ helper.assertDialogClosed();
+ await helper.startPrint();
+ helper.assertDialogOpen();
+
+ let file = helper.mockFilePicker("browser_print_in_container.pdf");
+ await helper.assertPrintToFile(file, () => {
+ helper.click(helper.get("print-button"));
+ });
+
+ ok(true, "We did not crash.");
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/printing/tests/browser_print_margins.js b/toolkit/components/printing/tests/browser_print_margins.js
new file mode 100644
index 0000000000..2e1d13d0e4
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_margins.js
@@ -0,0 +1,1162 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Chaos mode slowdown causes intermittent failures - See bug 1698240.
+requestLongerTimeout(2);
+
+async function changeMargin(helper, scroll, value) {
+ let marginSelect = helper.get("margins-picker");
+
+ info(" current value is " + marginSelect.value);
+
+ marginSelect.focus();
+
+ if (scroll) {
+ marginSelect.scrollIntoView({ block: "center" });
+ }
+
+ marginSelect.value = value;
+ marginSelect.dispatchEvent(
+ new marginSelect.ownerGlobal.Event("input", {
+ bubbles: true,
+ composed: true,
+ })
+ );
+ marginSelect.dispatchEvent(
+ new marginSelect.ownerGlobal.Event("change", {
+ bubbles: true,
+ })
+ );
+}
+
+function changeDefaultToCustom(helper) {
+ info("Trying to change margin from default -> custom");
+ return changeMargin(helper, true, "custom");
+}
+
+function changeCustomToDefault(helper) {
+ info("Trying to change margin from custom -> default");
+ return changeMargin(helper, false, "default");
+}
+
+function changeCustomToNone(helper) {
+ info("Trying to change margin from custom -> none");
+ return changeMargin(helper, false, "none");
+}
+
+function assertPendingMarginsUpdate(helper) {
+ ok(
+ Object.keys(helper.win.PrintEventHandler._delayedChanges).length,
+ "At least one delayed task is added"
+ );
+ ok(
+ helper.win.PrintEventHandler._delayedSettingsChangeTask.isArmed,
+ "The update task is armed"
+ );
+}
+
+function assertNoPendingMarginsUpdate(helper) {
+ ok(
+ !helper.win.PrintEventHandler._delayedSettingsChangeTask.isArmed,
+ "The update task isn't armed"
+ );
+}
+
+async function setupLetterPaper() {
+ const INCHES_PER_POINT = 1 / 72;
+ const printerList = Cc["@mozilla.org/gfx/printerlist;1"].createInstance(
+ Ci.nsIPrinterList
+ );
+ let fallbackPaperList = await printerList.fallbackPaperList;
+ let paper = fallbackPaperList.find(
+ paper =>
+ paper.width * INCHES_PER_POINT == 8.5 &&
+ paper.height * INCHES_PER_POINT == 11
+ );
+ ok(paper, "Found a paper");
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_id", paper.id.toString()],
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_size_unit", 0],
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_paper_width",
+ (paper.width * INCHES_PER_POINT).toString(),
+ ],
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_paper_height",
+ (paper.height * INCHES_PER_POINT).toString(),
+ ],
+ ],
+ });
+}
+
+add_task(async function testCustomMarginMaxAttrsSet() {
+ await PrintHelper.withTestPage(async helper => {
+ let paperList = [
+ PrintHelper.createMockPaper({
+ id: "unwriteableMargins",
+ name: "Unwriteable Margins",
+ // Numbers here demonstrate our truncating logic doesn't round up
+ unwriteableMargin: {
+ top: 18,
+ bottom: 19,
+ left: 18,
+ right: 19,
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPaperMargin]),
+ },
+ }),
+ ];
+
+ let mockPrinterName = "Mock printer";
+ helper.addMockPrinter({ name: mockPrinterName, paperList });
+ Services.prefs.setStringPref("print_printer", mockPrinterName);
+
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+
+ let marginsSelect = helper.get("margins-select");
+ is(
+ marginsSelect._maxHeight.toFixed(2),
+ "10.49",
+ "Max height would round up"
+ );
+ is(marginsSelect._maxWidth.toFixed(2), "7.99", "Max width would round up");
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+ is(
+ helper.get("custom-margin-left").max,
+ "7.48",
+ "Left margin max attr is correct"
+ );
+ is(
+ helper.get("custom-margin-right").max,
+ "7.48",
+ "Right margin max attr is correct"
+ );
+ is(
+ helper.get("custom-margin-top").max,
+ "9.98",
+ "Top margin max attr is correct"
+ );
+ is(
+ helper.get("custom-margin-bottom").max,
+ "9.98",
+ "Bottom margin max attr is correct"
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPresetMargins() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 0.25, marginRight: 1, marginBottom: 2, marginLeft: 0.75 },
+ async () => {
+ let marginSelect = helper.get("margins-picker");
+ let customMargins = helper.get("custom-margins");
+
+ ok(customMargins.hidden, "Custom margins are hidden");
+ is(marginSelect.value, "default", "Default margins set");
+ helper.assertSettingsMatch({ honorPageRuleMargins: true });
+
+ await changeDefaultToCustom(helper);
+
+ is(marginSelect.value, "custom", "Custom margins are now set");
+ ok(!customMargins.hidden, "Custom margins are present");
+
+ // Check that values are initialized to correct values
+ is(
+ helper.get("custom-margin-top").value,
+ "0.50",
+ "Top margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-right").value,
+ "0.50",
+ "Right margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-bottom").value,
+ "0.50",
+ "Bottom margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-left").value,
+ "0.50",
+ "Left margin placeholder is correct"
+ );
+
+ await helper.awaitAnimationFrame();
+
+ await helper.text(helper.get("custom-margin-top"), "0.25");
+ await helper.text(helper.get("custom-margin-right"), "1");
+ await helper.text(helper.get("custom-margin-bottom"), "2");
+ await helper.text(helper.get("custom-margin-left"), "0.75");
+
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ }
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testHeightError() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+
+ await helper.assertSettingsNotChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ let marginError = helper.get("error-invalid-margin");
+ ok(marginError.hidden, "Margin error is hidden");
+
+ await helper.text(helper.get("custom-margin-top"), "20");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+
+ ok(!marginError.hidden, "Margin error is showing");
+ assertNoPendingMarginsUpdate(helper);
+ }
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testWidthError() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+
+ await helper.assertSettingsNotChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ let marginError = helper.get("error-invalid-margin");
+ ok(marginError.hidden, "Margin error is hidden");
+
+ await helper.text(helper.get("custom-margin-right"), "20");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+
+ ok(!marginError.hidden, "Margin error is showing");
+ assertNoPendingMarginsUpdate(helper);
+ }
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testInvalidMarginsReset() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsNotChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ ok(marginError.hidden, "Margin error is hidden");
+
+ await helper.awaitAnimationFrame();
+ await helper.text(helper.get("custom-margin-top"), "20");
+ await helper.text(helper.get("custom-margin-right"), "20");
+ assertNoPendingMarginsUpdate(helper);
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+ }
+ );
+
+ await changeCustomToDefault(helper);
+ assertNoPendingMarginsUpdate(helper);
+ await BrowserTestUtils.waitForCondition(
+ () => marginError.hidden,
+ "Wait for margin error to be hidden"
+ );
+ await changeDefaultToCustom(helper);
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+
+ is(
+ helper.get("margins-picker").value,
+ "custom",
+ "The custom option is selected"
+ );
+ is(
+ helper.get("custom-margin-top").value,
+ "0.50",
+ "Top margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-right").value,
+ "0.50",
+ "Right margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-bottom").value,
+ "0.50",
+ "Bottom margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-left").value,
+ "0.50",
+ "Left margin placeholder is correct"
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => marginError.hidden,
+ "Wait for margin error to be hidden"
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testChangeInvalidToValidUpdate() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupLetterPaper();
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.text(helper.get("custom-margin-bottom"), "11");
+ assertNoPendingMarginsUpdate(helper);
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+ ok(!marginError.hidden, "Margin error is showing");
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ paperId: "na_letter",
+ });
+
+ await helper.text(helper.get("custom-margin-top"), "1");
+ assertNoPendingMarginsUpdate(helper);
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+ ok(!marginError.hidden, "Margin error is showing");
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 1, marginRight: 0.5, marginBottom: 1, marginLeft: 0.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-bottom"), "1");
+
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+ });
+});
+
+add_task(async function testChangeInvalidCanRevalidate() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupLetterPaper();
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 5, marginRight: 0.5, marginBottom: 3, marginLeft: 0.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-top"), "5");
+ await helper.text(helper.get("custom-margin-bottom"), "3");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.text(helper.get("custom-margin-top"), "9");
+ assertNoPendingMarginsUpdate(helper);
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+ ok(!marginError.hidden, "Margin error is showing");
+ helper.assertSettingsMatch({
+ marginTop: 5,
+ marginRight: 0.5,
+ marginBottom: 3,
+ marginLeft: 0.5,
+ paperId: "na_letter",
+ });
+
+ await helper.assertSettingsChanged(
+ { marginTop: 5, marginRight: 0.5, marginBottom: 3, marginLeft: 0.5 },
+ { marginTop: 9, marginRight: 0.5, marginBottom: 2, marginLeft: 0.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-bottom"), "2");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+ });
+});
+
+add_task(async function testCustomMarginsPersist() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 0.25, marginRight: 1, marginBottom: 2, marginLeft: 0 },
+ async () => {
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+
+ await helper.text(helper.get("custom-margin-top"), "0.25");
+ await helper.text(helper.get("custom-margin-right"), "1");
+ await helper.text(helper.get("custom-margin-bottom"), "2");
+ await helper.text(helper.get("custom-margin-left"), "0");
+
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ }
+ );
+
+ await helper.closeDialog();
+
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ helper.assertSettingsMatch({
+ marginTop: 0.25,
+ marginRight: 1,
+ marginBottom: 2,
+ marginLeft: 0,
+ });
+
+ is(
+ helper.get("margins-picker").value,
+ "custom",
+ "The custom option is selected"
+ );
+ is(
+ helper.get("custom-margin-top").value,
+ "0.25",
+ "Top margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-right").value,
+ "1.00",
+ "Right margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-bottom").value,
+ "2.00",
+ "Bottom margin placeholder is correct"
+ );
+ is(
+ helper.get("custom-margin-left").value,
+ "0.00",
+ "Left margin placeholder is correct"
+ );
+ await helper.assertSettingsChanged(
+ { marginTop: 0.25, marginRight: 1, marginBottom: 2, marginLeft: 0 },
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ await helper.awaitAnimationFrame();
+
+ await helper.text(helper.get("custom-margin-top"), "0.50");
+ await helper.text(helper.get("custom-margin-right"), "0.50");
+ await helper.text(helper.get("custom-margin-bottom"), "0.50");
+ await helper.text(helper.get("custom-margin-left"), "0.50");
+
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ }
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testChangingBetweenMargins() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.printer_Mozilla_Save_to_PDF.print_margin_left", "1"]],
+ });
+
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ let marginsPicker = helper.get("margins-picker");
+ is(marginsPicker.value, "custom", "First margin is custom");
+
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 1,
+ marginRight: 0.5,
+ });
+
+ info("Switch to Default margins");
+ await helper.assertSettingsChanged(
+ { marginLeft: 1 },
+ { marginLeft: 0.5 },
+ async () => {
+ let settingsChanged = helper.waitForSettingsEvent();
+ await changeCustomToDefault(helper);
+ await settingsChanged;
+ }
+ );
+
+ is(marginsPicker.value, "default", "Default preset selected");
+
+ info("Switching back to Custom, should restore old margins");
+ await helper.assertSettingsChanged(
+ { marginLeft: 0.5 },
+ { marginLeft: 1 },
+ async () => {
+ let settingsChanged = helper.waitForSettingsEvent();
+ await changeDefaultToCustom(helper);
+ await settingsChanged;
+ }
+ );
+
+ is(marginsPicker.value, "custom", "Custom is now selected");
+
+ info("Switching back to Default, should restore 0.5");
+ await helper.assertSettingsChanged(
+ { marginLeft: 1 },
+ { marginLeft: 0.5 },
+ async () => {
+ let settingsChanged = helper.waitForSettingsEvent();
+ await changeCustomToDefault(helper);
+ await settingsChanged;
+ }
+ );
+
+ is(marginsPicker.value, "default", "Default preset is selected again");
+ });
+});
+
+add_task(async function testChangeHonoredInPrint() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.mockFilePicker("changedMargin.pdf");
+
+ await helper.openMoreSettings();
+ helper.assertSettingsMatch({ marginRight: 0.5 });
+ await changeDefaultToCustom(helper);
+
+ await helper.withClosingFn(async () => {
+ await helper.text(helper.get("custom-margin-right"), "1");
+ EventUtils.sendKey("return", helper.win);
+ helper.resolvePrint();
+ });
+ helper.assertPrintedWithSettings({ marginRight: 1 });
+ });
+});
+
+add_task(async function testInvalidPrefValueHeight() {
+ await PrintHelper.withTestPage(async helper => {
+ // Set some bad prefs
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.printer_Mozilla_Save_to_PDF.print_margin_top", "-1"]],
+ });
+ await helper.startPrint();
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testInvalidPrefValueWidth() {
+ await PrintHelper.withTestPage(async helper => {
+ // Set some bad prefs
+ await SpecialPowers.pushPrefEnv({
+ set: [["print.printer_Mozilla_Save_to_PDF.print_margin_left", "-1"]],
+ });
+ await helper.startPrint();
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testInvalidMarginStartup() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.printer_Mozilla_Save_to_PDF.print_margin_right", "4"],
+ ["print.printer_Mozilla_Save_to_PDF.print_margin_left", "5"],
+ ],
+ });
+ await setupLetterPaper();
+ await helper.startPrint();
+ helper.assertSettingsMatch({
+ paperId: "na_letter",
+ marginLeft: 0.5,
+ marginRight: 0.5,
+ });
+ helper.closeDialog();
+ });
+});
+
+add_task(async function testRevalidateSwitchToNone() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupLetterPaper();
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+
+ await helper.text(helper.get("custom-margin-bottom"), "6");
+ await helper.text(helper.get("custom-margin-top"), "6");
+ assertNoPendingMarginsUpdate(helper);
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ paperId: "na_letter",
+ });
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 6, marginRight: 0.5, marginBottom: 3, marginLeft: 0.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-bottom"), "3");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { marginTop: 6, marginRight: 0.5, marginBottom: 3, marginLeft: 0.5 },
+ { marginTop: 0, marginRight: 0, marginBottom: 0, marginLeft: 0 },
+ async () => {
+ await changeCustomToNone(helper);
+ is(
+ helper.get("margins-picker").value,
+ "none",
+ "No margins are now set"
+ );
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ }
+ );
+ });
+});
+
+add_task(async function testInvalidMarginResetAfterDestinationChange() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.printer_Fake_Printer.print_paper_id", "na_letter"],
+ ["print.printer_Fake_Printer.print_paper_size_unit", 0],
+ ["print.printer_Fake_Printer.print_paper_width", "8.5"],
+ ["print.printer_Fake_Printer.print_paper_height", "11"],
+ ],
+ });
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsNotChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ ok(marginError.hidden, "Margin error is hidden");
+
+ await helper.text(helper.get("custom-margin-top"), "20");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+
+ ok(!marginError.hidden, "Margin error is showing");
+ assertNoPendingMarginsUpdate(helper);
+ }
+ );
+
+ is(destinationPicker.disabled, false, "Destination picker is enabled");
+
+ helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await BrowserTestUtils.waitForCondition(
+ () => marginError.hidden,
+ "Wait for margin error to be hidden"
+ );
+
+ helper.assertSettingsMatch({
+ marginTop: 0.5,
+ marginRight: 0.5,
+ marginBottom: 0.5,
+ marginLeft: 0.5,
+ });
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testRevalidateCustomMarginsAfterPaperChanges() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ helper.dispatchSettingsChange({ paperId: "iso_a3" });
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 5, marginRight: 5, marginBottom: 5, marginLeft: 5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-top"), "5");
+ await helper.text(helper.get("custom-margin-bottom"), "5");
+ await helper.text(helper.get("custom-margin-right"), "5");
+ await helper.text(helper.get("custom-margin-left"), "5");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { marginTop: 5, marginRight: 5, marginBottom: 5, marginLeft: 5 },
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ helper.dispatchSettingsChange({ paperId: "iso_a5" });
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+ });
+});
+
+add_task(async function testRevalidateCustomMarginsAfterOrientationChanges() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupLetterPaper();
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 5, marginRight: 0.5, marginBottom: 5, marginLeft: 0.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-top"), "5");
+ await helper.text(helper.get("custom-margin-bottom"), "5");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { marginTop: 5, marginRight: 0.5, marginBottom: 5, marginLeft: 0.5 },
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ helper.dispatchSettingsChange({ orientation: 1 });
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+ });
+});
+
+add_task(async function testResetMarginPersists() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupLetterPaper();
+ await helper.startPrint();
+
+ await helper.openMoreSettings();
+ await changeDefaultToCustom(helper);
+ await helper.awaitAnimationFrame();
+ let marginError = helper.get("error-invalid-margin");
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ { marginTop: 0.5, marginRight: 4, marginBottom: 0.5, marginLeft: 4.5 },
+ async () => {
+ await helper.text(helper.get("custom-margin-right"), "4");
+ await helper.text(helper.get("custom-margin-left"), "4.5");
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { marginTop: 0.5, marginRight: 4, marginBottom: 0.5, marginLeft: 4.5 },
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ helper.dispatchSettingsChange({ paperId: "iso_a4" });
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.assertSettingsNotChanged(
+ { marginTop: 0.5, marginRight: 0.5, marginBottom: 0.5, marginLeft: 0.5 },
+ async () => {
+ helper.dispatchSettingsChange({ paperId: "iso_a5" });
+ await helper.waitForSettingsEvent();
+ ok(marginError.hidden, "Margin error is hidden");
+ }
+ );
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testCustomMarginUnits() {
+ const mockPrinterName = "MetricPrinter";
+ await PrintHelper.withTestPage(async helper => {
+ // Add a metric-unit printer we can test with
+ helper.addMockPrinter({
+ name: mockPrinterName,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeMillimeters,
+ paperList: [],
+ });
+
+ // settings are saved in inches
+ const persistedMargins = {
+ top: 0.5,
+ right: 5,
+ bottom: 0.5,
+ left: 1,
+ };
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_margin_right",
+ persistedMargins.right.toString(),
+ ],
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_margin_left",
+ persistedMargins.left.toString(),
+ ],
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_margin_top",
+ persistedMargins.top.toString(),
+ ],
+ [
+ "print.printer_Mozilla_Save_to_PDF.print_margin_bottom",
+ persistedMargins.bottom.toString(),
+ ],
+ ],
+ });
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ helper.assertSettingsMatch({
+ paperId: "na_letter",
+ marginTop: persistedMargins.top,
+ marginRight: persistedMargins.right,
+ marginBottom: persistedMargins.bottom,
+ marginLeft: persistedMargins.left,
+ });
+
+ is(
+ helper.settings.printerName,
+ DEFAULT_PRINTER_NAME,
+ "The PDF (inch-unit) printer is current"
+ );
+
+ is(
+ helper.get("margins-picker").value,
+ "custom",
+ "The margins picker has the expected value"
+ );
+ is(
+ helper.get("margins-picker").selectedOptions[0].dataset.l10nId,
+ "printui-margins-custom-inches",
+ "The custom margins option has correct unit string id"
+ );
+ // the unit value should be correct for inches
+ for (let edgeName of Object.keys(persistedMargins)) {
+ is(
+ helper.get(`custom-margin-${edgeName}`).value,
+ persistedMargins[edgeName].toFixed(2),
+ `Has the expected unit-converted ${edgeName}-margin value`
+ );
+ }
+
+ await helper.assertSettingsChanged(
+ { marginTop: persistedMargins.top },
+ { marginTop: 1 },
+ async () => {
+ // update the top margin to 1"
+ await helper.text(helper.get("custom-margin-top"), "1");
+ assertPendingMarginsUpdate(helper);
+
+ // Wait for the preview to update, the margin options delay updates by
+ // INPUT_DELAY_MS, which is 500ms.
+ await helper.waitForSettingsEvent();
+ // ensure any round-trip correctly re-converts the setting value back to the displayed mm value
+ is(
+ helper.get("custom-margin-top").value,
+ "1",
+ "Converted custom margin value is expected value"
+ );
+ }
+ );
+ // put it back to how it was
+ await helper.text(
+ helper.get("custom-margin-top"),
+ persistedMargins.top.toString()
+ );
+ await helper.waitForSettingsEvent();
+
+ // Now switch to the metric printer
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await helper.waitForSettingsEvent();
+
+ is(
+ helper.settings.printerName,
+ mockPrinterName,
+ "The metric printer is current"
+ );
+ is(
+ helper.get("margins-picker").value,
+ "custom",
+ "The margins picker has the expected value"
+ );
+ is(
+ helper.get("margins-picker").selectedOptions[0].dataset.l10nId,
+ "printui-margins-custom-mm",
+ "The custom margins option has correct unit string id"
+ );
+ // the unit value should be correct for mm
+ for (let edgeName of Object.keys(persistedMargins)) {
+ is(
+ helper.get(`custom-margin-${edgeName}`).value,
+ (persistedMargins[edgeName] * 25.4).toFixed(2),
+ `Has the expected unit-converted ${edgeName}-margin value`
+ );
+ }
+
+ await helper.assertSettingsChanged(
+ { marginTop: persistedMargins.top },
+ { marginTop: 1 },
+ async () => {
+ let marginError = helper.get("error-invalid-margin");
+ ok(marginError.hidden, "Margin error is hidden");
+
+ // update the top margin to 1" in mm
+ await helper.text(helper.get("custom-margin-top"), "25.4");
+ // Check the constraints validation is using the right max
+ // as 25" top margin would be an error, but 25mm is ok
+ ok(marginError.hidden, "Margin error is hidden");
+
+ assertPendingMarginsUpdate(helper);
+ // Wait for the preview to update, the margin options delay updates by INPUT_DELAY_MS
+ await helper.waitForSettingsEvent();
+ // ensure any round-trip correctly re-converts the setting value back to the displayed mm value
+ is(
+ helper.get("custom-margin-top").value,
+ "25.4",
+ "Converted custom margin value is expected value"
+ );
+ }
+ );
+
+ // check margin validation is actually working with unit-appropriate max
+ await helper.assertSettingsNotChanged({ marginTop: 1 }, async () => {
+ let marginError = helper.get("error-invalid-margin");
+ ok(marginError.hidden, "Margin error is hidden");
+
+ await helper.text(helper.get("custom-margin-top"), "300");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", marginError);
+
+ ok(!marginError.hidden, "Margin error is showing");
+ assertNoPendingMarginsUpdate(helper);
+ });
+
+ await SpecialPowers.popPrefEnv();
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testHonorPageRuleMargins() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let marginsPicker = helper.get("margins-picker");
+
+ is(marginsPicker.value, "default", "Started with default margins");
+ helper.assertSettingsMatch({ honorPageRuleMargins: true });
+
+ await helper.waitForSettingsEvent(() => changeDefaultToCustom(helper));
+
+ is(marginsPicker.value, "custom", "Changed to custom margins");
+ helper.assertSettingsMatch({ honorPageRuleMargins: false });
+
+ await helper.waitForSettingsEvent(() => changeCustomToNone(helper));
+
+ is(marginsPicker.value, "none", "Changed to no margins");
+ helper.assertSettingsMatch({ honorPageRuleMargins: false });
+
+ await helper.waitForSettingsEvent(() => changeCustomToDefault(helper));
+
+ is(marginsPicker.value, "default", "Back to default margins");
+ helper.assertSettingsMatch({ honorPageRuleMargins: true });
+ });
+});
+
+add_task(async function testIgnoreUnwriteableMargins() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let marginsPicker = helper.get("margins-picker");
+
+ is(marginsPicker.value, "default", "Started with default margins");
+ helper.assertSettingsMatch({ ignoreUnwriteableMargins: false });
+
+ await helper.waitForSettingsEvent(() => changeDefaultToCustom(helper));
+
+ is(marginsPicker.value, "custom", "Changed to custom margins");
+ helper.assertSettingsMatch({ ignoreUnwriteableMargins: false });
+
+ await helper.waitForSettingsEvent(() => changeCustomToNone(helper));
+
+ is(marginsPicker.value, "none", "Changed to no margins");
+ helper.assertSettingsMatch({ ignoreUnwriteableMargins: true });
+
+ await helper.waitForSettingsEvent(() => changeCustomToDefault(helper));
+
+ is(marginsPicker.value, "default", "Back to default margins");
+ helper.assertSettingsMatch({ ignoreUnwriteableMargins: false });
+ });
+});
+
+add_task(async function testCustomMarginZeroRespectsUnwriteableMargins() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let marginsPicker = helper.get("margins-picker");
+
+ await helper.waitForSettingsEvent(() => changeDefaultToCustom(helper));
+ is(marginsPicker.value, "custom", "Changed to custom margins");
+
+ await helper.text(helper.get("custom-margin-top"), "0");
+ await helper.text(helper.get("custom-margin-right"), "0");
+ await helper.text(helper.get("custom-margin-bottom"), "0");
+ await helper.text(helper.get("custom-margin-left"), "0");
+
+ assertPendingMarginsUpdate(helper);
+ await helper.waitForSettingsEvent();
+ helper.assertSettingsMatch({ ignoreUnwriteableMargins: false });
+ });
+});
+
+add_task(async function testDefaultMarginsInvalidStartup() {
+ await PrintHelper.withTestPage(async helper => {
+ let paperList = [
+ PrintHelper.createMockPaper({
+ id: "smallestPaper",
+ name: "Default Margins Invalid",
+ width: 50,
+ height: 50,
+ unwriteableMargin: {
+ top: 10,
+ bottom: 10,
+ left: 10,
+ right: 10,
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPaperMargin]),
+ },
+ }),
+ ];
+
+ let mockPrinterName = "Mock printer";
+ helper.addMockPrinter({ name: mockPrinterName, paperList });
+ Services.prefs.setStringPref("print_printer", mockPrinterName);
+
+ await helper.startPrint();
+
+ helper.assertSettingsMatch({
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ });
+
+ let marginSelect = helper.get("margins-picker");
+ is(marginSelect.value, "none", "Margins picker set to 'None'");
+
+ let printForm = helper.get("print");
+ ok(printForm.checkValidity(), "The print form is valid");
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_page_range.js b/toolkit/components/printing/tests/browser_print_page_range.js
new file mode 100644
index 0000000000..89ef80f3a5
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_page_range.js
@@ -0,0 +1,540 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+async function changeRangeTo(helper, destination) {
+ info(`changeRangeTo(${destination})`);
+ let rangeSelect = helper.get("range-picker");
+ let options = getRangeOptions(helper);
+ let numberMove =
+ options.indexOf(destination) - options.indexOf(rangeSelect.value);
+ let direction = numberMove > 0 ? "down" : "up";
+ if (!numberMove) {
+ return;
+ }
+
+ let input = BrowserTestUtils.waitForEvent(rangeSelect, "input");
+
+ let popupOpen = BrowserTestUtils.waitForSelectPopupShown(window);
+
+ rangeSelect.focus();
+ rangeSelect.scrollIntoView({ block: "center" });
+ EventUtils.sendKey("space", helper.win);
+
+ await popupOpen;
+ for (let i = Math.abs(numberMove); i > 0; i--) {
+ EventUtils.sendKey(direction, window);
+ }
+ EventUtils.sendKey("return", window);
+
+ await input;
+}
+
+function getRangeOptions(helper) {
+ let rangeSelect = helper.get("range-picker");
+ let options = [];
+ for (let el of rangeSelect.options) {
+ if (!el.disabled) {
+ options.push(el.value);
+ }
+ }
+ return options;
+}
+
+function getSheetCount(helper) {
+ return helper.doc.l10n.getAttributes(helper.get("sheet-count")).args
+ .sheetCount;
+}
+
+add_task(async function testRangeResetAfterScale() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.mockFilePicker("changeRangeFromScale.pdf");
+ await changeRangeTo(helper, "custom");
+
+ await helper.openMoreSettings();
+ let scaleRadio = helper.get("percent-scale-choice");
+ await helper.waitForPreview(() => helper.click(scaleRadio));
+ let percentScale = helper.get("percent-scale");
+ await helper.waitForPreview(() => helper.text(percentScale, "200"));
+
+ let customRange = helper.get("custom-range");
+ let rangeError = helper.get("error-invalid-range");
+ await helper.waitForPreview(() => {
+ helper.text(customRange, "3");
+ });
+
+ ok(rangeError.hidden, "Range error is hidden");
+
+ await helper.text(percentScale, "10");
+ EventUtils.sendKey("return", helper.win);
+
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", rangeError);
+ ok(!rangeError.hidden, "Range error is showing");
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testRangeResetAfterPaperSize() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ paperId: "iso_a5" })
+ );
+ await helper.setupMockPrint();
+
+ await helper.openMoreSettings();
+ let scaleRadio = helper.get("percent-scale-choice");
+ await helper.waitForPreview(() => helper.click(scaleRadio));
+ let percentScale = helper.get("percent-scale");
+ await helper.waitForPreview(() => helper.text(percentScale, "200"));
+
+ let customRange = helper.get("custom-range");
+ await changeRangeTo(helper, "custom");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", customRange);
+
+ let rangeError = helper.get("error-invalid-range");
+ await helper.waitForPreview(() => {
+ helper.text(customRange, "6");
+ });
+
+ ok(rangeError.hidden, "Range error is hidden");
+
+ helper.dispatchSettingsChange({ paperId: "iso_a3" });
+ await BrowserTestUtils.waitForCondition(
+ () => helper.get("paper-size-picker").value == "iso_a3",
+ "Wait for paper size select to update"
+ );
+ EventUtils.sendKey("return", helper.win);
+
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", rangeError);
+ ok(!rangeError.hidden, "Range error is showing");
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testInvalidRangeResetAfterDestinationChange() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+ let customPageRange = helper.get("custom-range");
+
+ await helper.assertSettingsNotChanged({ pageRanges: [] }, async () => {
+ await changeRangeTo(helper, "custom");
+ });
+ let rangeError = helper.get("error-invalid-range");
+
+ await helper.assertSettingsNotChanged({ pageRanges: [] }, async () => {
+ ok(rangeError.hidden, "Range error is hidden");
+ await helper.text(customPageRange, "9");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", rangeError);
+ ok(!rangeError.hidden, "Range error is showing");
+ });
+
+ is(destinationPicker.disabled, false, "Destination picker is enabled");
+
+ // Select a new printer
+ helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await BrowserTestUtils.waitForCondition(
+ () => rangeError.hidden,
+ "Wait for range error to be hidden"
+ );
+ is(customPageRange.value, "", "Page range has reset");
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPageRangeSets() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ let customRange = helper.get("custom-range");
+ let pageRangeInput = helper.get("page-range-input");
+ let invalidError = helper.get("error-invalid-range");
+ let invalidOverflowError = helper.get("error-invalid-start-range-overflow");
+
+ ok(customRange.hidden, "Custom range input is hidden");
+
+ await changeRangeTo(helper, "custom");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", customRange);
+
+ ok(!customRange.hidden, "Custom range is showing");
+ is(helper.doc.activeElement, customRange, "Custom range field is focused");
+
+ // We need to set the input to something to ensure we do not return early
+ // out of our validation function
+ helper.text(helper.get("custom-range"), ",");
+
+ let validStrings = {
+ 1: [1, 1],
+ "1,": [1, 1],
+ 2: [2, 2],
+ "1-2": [1, 2],
+ "1,2": [1, 2],
+ "1,2,": [1, 2],
+ "2,1": [1, 2],
+ "1,3": [1, 1, 3, 3],
+ "1-1,3": [1, 1, 3, 3],
+ "1,3-3": [1, 1, 3, 3],
+ "10-33": [10, 33],
+ "1-": [1, 50],
+ "-": [],
+ "-20": [1, 20],
+ "-1,1-": [1, 50],
+ "-1,1-2": [1, 2],
+ ",9": [9, 9],
+ ",": [],
+ "1,2,1,20,5": [1, 2, 5, 5, 20, 20],
+ "1-17,4,12-19": [1, 19],
+ "43-46,42,47-": [42, 50],
+ };
+
+ for (let [str, expected] of Object.entries(validStrings)) {
+ pageRangeInput._validateRangeInput(str, 50);
+ let pageRanges = pageRangeInput.formatPageRange();
+ ok(
+ expected.length == pageRanges.length &&
+ expected.every((page, index) => page === pageRanges[index]),
+ `Expected page range for "${str}" matches "${expected}"`
+ );
+
+ ok(invalidError.hidden, "Generic error message is hidden");
+ ok(invalidOverflowError.hidden, "Start overflow error message is hidden");
+ }
+
+ let invalidStrings = ["51", "1,51", "1-51", "4-1", "--", "0", "-90"];
+
+ for (let str of invalidStrings) {
+ pageRangeInput._validateRangeInput(str, 50);
+ is(pageRangeInput._pagesSet.size, 0, `There are no pages in the set`);
+ ok(!pageRangeInput.validity, "Input is invalid");
+ }
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPageRangeSelect() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ let pageRangeInput = helper.get("page-range-input");
+
+ await changeRangeTo(helper, "all");
+ let pageRanges = pageRangeInput.formatPageRange();
+ ok(!pageRanges.length, "Page range for all should be []");
+
+ await changeRangeTo(helper, "current");
+ pageRanges = pageRangeInput.formatPageRange();
+ ok(
+ pageRanges.length == 2 &&
+ [1, 1].every((page, index) => page === pageRanges[index]),
+ "The first page should be the current page"
+ );
+
+ pageRangeInput._validateRangeInput("9", 50);
+ pageRanges = pageRangeInput.formatPageRange();
+ ok(
+ pageRanges.length == 2 &&
+ [9, 9].every((page, index) => page === pageRanges[index]),
+ `Expected page range for "${pageRanges}" matches [9, 9]"`
+ );
+
+ await changeRangeTo(helper, "odd");
+ pageRanges = pageRangeInput.formatPageRange();
+ ok(
+ pageRanges.length == 4 &&
+ [1, 1, 3, 3].every((page, index) => page === pageRanges[index]),
+ "Page range for odd should be [1, 1, 3, 3]"
+ );
+
+ await changeRangeTo(helper, "even");
+ pageRanges = pageRangeInput.formatPageRange();
+ ok(
+ pageRanges.length == 2 &&
+ [2, 2].every((page, index) => page === pageRanges[index]),
+ "Page range for even should be [2, 2]"
+ );
+ }, "longerArticle.html");
+});
+
+add_task(async function testRangeError() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ await changeRangeTo(helper, "custom");
+
+ let invalidError = helper.get("error-invalid-range");
+ let invalidOverflowError = helper.get("error-invalid-start-range-overflow");
+
+ ok(invalidError.hidden, "Generic error message is hidden");
+ ok(invalidOverflowError.hidden, "Start overflow error message is hidden");
+
+ helper.text(helper.get("custom-range"), "4");
+
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", invalidError);
+
+ ok(!invalidError.hidden, "Generic error message is showing");
+ ok(invalidOverflowError.hidden, "Start overflow error message is hidden");
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testStartOverflowRangeError() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ await changeRangeTo(helper, "custom");
+
+ await helper.openMoreSettings();
+ let scaleRadio = helper.get("percent-scale-choice");
+ await helper.waitForPreview(() => helper.click(scaleRadio));
+ let percentScale = helper.get("percent-scale");
+ await helper.waitForPreview(() => helper.text(percentScale, "200"));
+
+ let invalidError = helper.get("error-invalid-range");
+ let invalidOverflowError = helper.get("error-invalid-start-range-overflow");
+
+ ok(invalidError.hidden, "Generic error message is hidden");
+ ok(invalidOverflowError.hidden, "Start overflow error message is hidden");
+
+ helper.text(helper.get("custom-range"), "2-1");
+
+ await BrowserTestUtils.waitForAttributeRemoval(
+ "hidden",
+ invalidOverflowError
+ );
+
+ ok(invalidError.hidden, "Generic error message is hidden");
+ ok(!invalidOverflowError.hidden, "Start overflow error message is showing");
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testErrorClearedAfterSwitchingToAll() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ await changeRangeTo(helper, "custom");
+
+ let customRange = helper.get("custom-range");
+ let rangeError = helper.get("error-invalid-range");
+ ok(rangeError.hidden, "Generic error message is hidden");
+
+ helper.text(customRange, "3");
+
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", rangeError);
+ ok(!rangeError.hidden, "Generic error message is showing");
+
+ await changeRangeTo(helper, "all");
+
+ await BrowserTestUtils.waitForCondition(
+ () => rangeError.hidden,
+ "Wait for range error to be hidden"
+ );
+ ok(customRange.hidden, "Custom range is hidden");
+ is(
+ helper.doc.activeElement,
+ helper.get("range-picker"),
+ "Range picker remains focused"
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPageCountChangeNoRangeNoRerender() {
+ await PrintHelper.withTestPage(async helper => {
+ let customPrinter = "A printer";
+ helper.addMockPrinter(customPrinter);
+
+ await helper.startPrint();
+
+ await helper.assertSettingsChanged(
+ { printerName: "Mozilla Save to PDF" },
+ { printerName: customPrinter },
+ async () => {
+ let destinationPicker = helper.get("printer-picker");
+ destinationPicker.focus();
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ printerName: customPrinter })
+ ),
+ helper.waitForSettingsEvent(),
+ ]);
+ }
+ );
+
+ // Change a setting that will change the number of pages. Since pageRanges
+ // is set to "all" then there shouldn't be a re-render because of it.
+ let previewUpdateCount = 0;
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ helper.doc.addEventListener("preview-updated", () => previewUpdateCount++);
+
+ // Ensure the sheet count will change.
+ let initialSheetCount = getSheetCount(helper);
+
+ await helper.assertSettingsChanged(
+ { marginLeft: 0.5, marginRight: 0.5 },
+ { marginLeft: 3, marginRight: 3 },
+ async () => {
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ marginLeft: 3, marginRight: 3 })
+ ),
+ BrowserTestUtils.waitForEvent(helper.doc, "page-count"),
+ helper.waitForSettingsEvent(),
+ ]);
+ }
+ );
+
+ let newSheetCount = getSheetCount(helper);
+ ok(
+ initialSheetCount < newSheetCount,
+ `There are more sheets now ${initialSheetCount} < ${newSheetCount}`
+ );
+
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ is(previewUpdateCount, 1, "Only one preview update fired");
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPageCountChangeRangeNoRerender() {
+ await PrintHelper.withTestPage(async helper => {
+ let customPrinter = "A printer";
+ helper.addMockPrinter(customPrinter);
+
+ await helper.startPrint();
+
+ await helper.assertSettingsChanged(
+ { printerName: "Mozilla Save to PDF", pageRanges: [] },
+ { printerName: customPrinter, pageRanges: [1, 1] },
+ async () => {
+ let destinationPicker = helper.get("printer-picker");
+ destinationPicker.focus();
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ printerName: customPrinter })
+ ),
+ helper.waitForSettingsEvent(),
+ ]);
+
+ await helper.waitForPreview(async () => {
+ await changeRangeTo(helper, "custom");
+ helper.text(helper.get("custom-range"), "1");
+ });
+ }
+ );
+
+ // Change a setting that will change the number of pages. Since pageRanges
+ // is set to a page that is in the new range, there shouldn't be a re-render.
+ let previewUpdateCount = 0;
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ helper.doc.addEventListener("preview-updated", () => previewUpdateCount++);
+
+ await helper.assertSettingsChanged(
+ { marginLeft: 0.5, marginRight: 0.5 },
+ { marginLeft: 3, marginRight: 3 },
+ async () => {
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ marginLeft: 3, marginRight: 3 })
+ ),
+ BrowserTestUtils.waitForEvent(helper.doc, "page-count"),
+ helper.waitForSettingsEvent(),
+ ]);
+ }
+ );
+
+ let newSheetCount = getSheetCount(helper);
+ is(newSheetCount, 1, "There's still only one sheet");
+
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ is(previewUpdateCount, 1, "Only one preview update fired");
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testPageCountChangeRangeRerender() {
+ await PrintHelper.withTestPage(async helper => {
+ let customPrinter = "A printer";
+ helper.addMockPrinter(customPrinter);
+
+ await helper.startPrint();
+
+ await helper.assertSettingsChanged(
+ { printerName: "Mozilla Save to PDF", pageRanges: [] },
+ { printerName: customPrinter, pageRanges: [1, 1] },
+ async () => {
+ let destinationPicker = helper.get("printer-picker");
+ destinationPicker.focus();
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ printerName: customPrinter })
+ ),
+ helper.waitForSettingsEvent(),
+ ]);
+
+ await helper.waitForPreview(async () => {
+ await changeRangeTo(helper, "custom");
+ helper.text(helper.get("custom-range"), "1-");
+ });
+ }
+ );
+
+ // Change a setting that will change the number of pages. Since pageRanges
+ // is from 1-N the calculated page range will need to be updated.
+ let previewUpdateCount = 0;
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ helper.doc.addEventListener("preview-updated", () => previewUpdateCount++);
+ let renderedTwice = BrowserTestUtils.waitForCondition(
+ () => previewUpdateCount == 2
+ );
+
+ // Ensure the sheet count will change.
+ let initialSheetCount = getSheetCount(helper);
+
+ await helper.assertSettingsChanged(
+ { marginLeft: 0.5, marginRight: 0.5 },
+ { marginLeft: 3, marginRight: 3 },
+ async () => {
+ await Promise.all([
+ helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ marginLeft: 3, marginRight: 3 })
+ ),
+ BrowserTestUtils.waitForEvent(helper.doc, "page-count"),
+ helper.waitForSettingsEvent(),
+ ]);
+ await renderedTwice;
+ }
+ );
+
+ let newSheetCount = getSheetCount(helper);
+ ok(
+ initialSheetCount < newSheetCount,
+ `There are more sheets now ${initialSheetCount} < ${newSheetCount}`
+ );
+ Assert.deepEqual(
+ helper.viewSettings.pageRanges,
+ [1, newSheetCount],
+ "The new range is the updated full page range"
+ );
+
+ ok(!helper.hasPendingPreview, "No preview is pending");
+ is(previewUpdateCount, 2, "Preview updated again to show new page range");
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_page_size.js b/toolkit/components/printing/tests/browser_print_page_size.js
new file mode 100644
index 0000000000..b9936f2150
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_page_size.js
@@ -0,0 +1,269 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const pdfPrinterName = "Mozilla Save to PDF";
+const someOtherPrinter = "Other Printer";
+
+async function changeDestination(helper, dir) {
+ let picker = helper.get("printer-picker");
+ let changed = BrowserTestUtils.waitForEvent(picker, "change");
+
+ let pickerOpened = BrowserTestUtils.waitForSelectPopupShown(window);
+ picker.focus();
+ EventUtils.sendKey("space", helper.win);
+ await pickerOpened;
+ EventUtils.sendKey(dir, window);
+ EventUtils.sendKey("return", window);
+ await changed;
+}
+
+add_task(async function testShowAndHidePaperSizeSectionWithPageSize() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.addMockPrinter(someOtherPrinter);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.assertSettingsMatch({
+ printerName: pdfPrinterName,
+ usePageRuleSizeAsPaperSize: true,
+ });
+
+ await helper.openMoreSettings();
+
+ let paperSize = helper.get("paper-size");
+
+ ok(BrowserTestUtils.is_hidden(paperSize), "Paper size section is hidden");
+
+ await helper.waitForSettingsEvent(async () => {
+ await changeDestination(helper, "down");
+ });
+ await helper.awaitAnimationFrame();
+
+ helper.assertSettingsMatch({
+ printerName: someOtherPrinter,
+ usePageRuleSizeAsPaperSize: false,
+ });
+
+ ok(BrowserTestUtils.is_visible(paperSize), "Paper size section is shown");
+
+ await helper.closeDialog();
+ }, "page_size.html");
+});
+
+add_task(async function testShowPaperSizeSectionWithoutPageSize() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.addMockPrinter(someOtherPrinter);
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.assertSettingsMatch({
+ printerName: pdfPrinterName,
+ usePageRuleSizeAsPaperSize: true,
+ });
+
+ await helper.openMoreSettings();
+
+ let paperSize = helper.get("paper-size");
+
+ ok(BrowserTestUtils.is_visible(paperSize), "Paper size section is shown");
+
+ await helper.waitForSettingsEvent(async () => {
+ await changeDestination(helper, "down");
+ });
+ await helper.awaitAnimationFrame();
+
+ helper.assertSettingsMatch({
+ printerName: someOtherPrinter,
+ usePageRuleSizeAsPaperSize: false,
+ });
+
+ ok(
+ BrowserTestUtils.is_visible(paperSize),
+ "Paper size section is still shown"
+ );
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testCustomPageSizePassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ is(helper.settings.paperWidth.toFixed(1), "4.0", "Check paperWidth");
+ is(helper.settings.paperHeight.toFixed(1), "12.0", "Check paperHeight");
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kPortraitOrientation,
+ "Check orientation"
+ );
+ is(
+ helper.settings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+
+ await helper.closeDialog();
+ }, "page_size.html");
+});
+
+add_task(async function testNamedPageSizePassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.assertSettingsMatch({
+ printerName: pdfPrinterName,
+ usePageRuleSizeAsPaperSize: true,
+ });
+
+ is(helper.settings.paperId, "iso_a5", "Check paper id is A5");
+ is(
+ helper.settings.paperWidth.toFixed(1),
+ "5.8",
+ "Check paperWidth is ~148mm (in inches)"
+ );
+ is(
+ helper.settings.paperHeight.toFixed(1),
+ "8.3",
+ "Check paperHeight is ~210mm (in inches)"
+ );
+ is(
+ helper.settings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kPortraitOrientation,
+ "Check orientation"
+ );
+ await helper.closeDialog();
+ }, "page_size_a5.html");
+});
+
+add_task(async function testLandscapePageSizePassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.assertSettingsMatch({
+ printerName: pdfPrinterName,
+ usePageRuleSizeAsPaperSize: true,
+ });
+
+ is(helper.settings.paperId, "iso_a5", "Check paper id is A5");
+ is(
+ helper.settings.orientation,
+ Ci.nsIPrintSettings.kLandscapeOrientation,
+ "Check orientation is landscape"
+ );
+ is(
+ helper.settings.paperWidth.toFixed(1),
+ "5.8",
+ "Check paperWidth is ~148mm (in inches)"
+ );
+ is(
+ helper.settings.paperHeight.toFixed(1),
+ "8.3",
+ "Check paperHeight is ~210mm (in inches)"
+ );
+ is(
+ helper.settings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+
+ await helper.closeDialog();
+ }, "page_size_a5_landscape.html");
+});
+
+add_task(async function testZeroSizePassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ let pageSize = helper.get("paper-size");
+ let orientation = helper.get("orientation");
+
+ ok(BrowserTestUtils.is_visible(pageSize), "Fallback to page size section");
+ ok(BrowserTestUtils.is_visible(orientation), "Orientation picker is shown");
+
+ await helper.closeDialog();
+ });
+}, "page_size_zero.html");
+
+add_task(async function testZeroWidthPassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ let pageSize = helper.get("paper-size");
+ let orientation = helper.get("orientation");
+
+ ok(BrowserTestUtils.is_visible(pageSize), "Fallback to page size section");
+ ok(BrowserTestUtils.is_visible(orientation), "Orientation picker is shown");
+
+ await helper.closeDialog();
+ });
+}, "page_size_zero_width.html");
+
+add_task(async function testDefaultSizePassedToPrinter() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.save_as_pdf.use_page_rule_size_as_paper_size.enabled", true],
+ ],
+ });
+ await helper.startPrint();
+ await helper.setupMockPrint();
+
+ helper.assertSettingsMatch({
+ printerName: pdfPrinterName,
+ usePageRuleSizeAsPaperSize: true,
+ });
+
+ is(helper.settings.paperWidth.toFixed(1), "8.5", "Check paperWidth");
+ is(helper.settings.paperHeight.toFixed(1), "11.0", "Check paperHeight");
+ is(
+ helper.settings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_paper_sizes.js b/toolkit/components/printing/tests/browser_print_paper_sizes.js
new file mode 100644
index 0000000000..6f81a2a0b4
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_paper_sizes.js
@@ -0,0 +1,120 @@
+"use strict";
+
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+async function selectPaperOptionWithValue(helper, value) {
+ let paperSelect = helper.get("paper-size-picker");
+ paperSelect.dispatchSettingsChange({
+ paperId: value,
+ });
+ await helper.awaitAnimationFrame();
+}
+
+add_task(async function testBadPaperSizeUnitCorrection() {
+ await PrintHelper.withTestPage(async helper => {
+ // Set prefs to select a non-default paper size
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_id", "na_letter"],
+ // paperSizeUnit is a bogus value, but the dimensions are correct for inches
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_size_unit", 99],
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_height", "11.0"],
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_width", "8.50"],
+ ],
+ });
+ await helper.startPrint();
+
+ let paperSelect = helper.get("paper-size-picker");
+ is(paperSelect.value, "na_letter", "The expected paper size is selected");
+ is(
+ helper.viewSettings.paperId,
+ "na_letter",
+ "The settings have the expected paperId"
+ );
+ is(
+ helper.viewSettings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+ is(helper.viewSettings.paperWidth.toFixed(1), "8.5", "Check paperWidth");
+ is(helper.viewSettings.paperHeight.toFixed(1), "11.0", "Check paperHeight");
+
+ await selectPaperOptionWithValue(helper, "iso_a3");
+ is(paperSelect.value, "iso_a3", "The expected paper size is selected");
+ is(
+ helper.viewSettings.paperId,
+ "iso_a3",
+ "The settings have the expected paperId"
+ );
+ is(
+ helper.viewSettings.paperSizeUnit,
+ helper.settings.kPaperSizeInches,
+ "Check paperSizeUnit"
+ );
+ is(helper.viewSettings.paperWidth.toFixed(1), "11.7", "Check paperWidth");
+ is(helper.viewSettings.paperHeight.toFixed(1), "16.5", "Check paperHeight");
+
+ await SpecialPowers.popPrefEnv();
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testMismatchedPaperSizeUnitCorrection() {
+ await PrintHelper.withTestPage(async helper => {
+ // Set prefs to select a non-default paper size
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_id", "na_ledger"],
+ // paperSizeUnit is millimeters, but the dimensions are correct for inches
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_size_unit", 1],
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_width", "11.0"],
+ ["print.printer_Mozilla_Save_to_PDF.print_paper_height", "17.0"],
+ ],
+ });
+ await helper.startPrint();
+
+ let paperSelect = helper.get("paper-size-picker");
+ is(paperSelect.value, "na_ledger", "The expected paper size is selected");
+
+ // We expect to honor the paperSizeUnit, and convert paperWidth/Height to that unit
+ is(
+ helper.viewSettings.paperId,
+ "na_ledger",
+ "The settings have the expected paperId"
+ );
+ is(
+ helper.viewSettings.paperSizeUnit,
+ helper.settings.kPaperSizeMillimeters,
+ "Check paperSizeUnit"
+ );
+ is(helper.viewSettings.paperWidth.toFixed(1), "279.4", "Check paperWidth");
+ is(
+ helper.viewSettings.paperHeight.toFixed(1),
+ "431.8",
+ "Check paperHeight"
+ );
+
+ await selectPaperOptionWithValue(helper, "iso_a3");
+ is(paperSelect.value, "iso_a3", "The expected paper size is selected");
+ is(
+ helper.viewSettings.paperId,
+ "iso_a3",
+ "The settings have the expected paperId"
+ );
+ is(
+ helper.viewSettings.paperSizeUnit,
+ helper.settings.kPaperSizeMillimeters,
+ "Check paperSizeUnit"
+ );
+ is(helper.viewSettings.paperWidth.toFixed(1), "297.0", "Check paperWidth");
+ is(
+ helper.viewSettings.paperHeight.toFixed(1),
+ "420.0",
+ "Check paperHeight"
+ );
+
+ await SpecialPowers.popPrefEnv();
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_pdf_on_frame_load.js b/toolkit/components/printing/tests/browser_print_pdf_on_frame_load.js
new file mode 100644
index 0000000000..29864e430d
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_pdf_on_frame_load.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+add_task(async function test_print_pdf_on_frame_load() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_print_pdf_on_frame_load.html`,
+ async function (browser) {
+ info(
+ "Waiting for window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await waitForPreviewVisible();
+
+ info("Preview shown, waiting for it to be updated...");
+
+ await TestUtils.waitForCondition(() => helper.sheetCount != 0);
+
+ is(helper.sheetCount, 2, "Both pages should be loaded");
+
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ }
+ );
+});
diff --git a/toolkit/components/printing/tests/browser_print_scaling.js b/toolkit/components/printing/tests/browser_print_scaling.js
new file mode 100644
index 0000000000..85b8a68449
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_scaling.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testInvalidScaleResetAfterDestinationChange() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+
+ await helper.openMoreSettings();
+ let scaleRadio = helper.get("percent-scale-choice");
+ await helper.assertSettingsChanged(
+ { shrinkToFit: true },
+ { shrinkToFit: false },
+ async () => {
+ await helper.waitForPreview(() => helper.click(scaleRadio));
+ }
+ );
+ let percentScale = helper.get("percent-scale");
+
+ let scaleError = helper.get("error-invalid-scale");
+
+ await helper.assertSettingsNotChanged({ scaling: 1 }, async () => {
+ ok(scaleError.hidden, "Scale error is hidden");
+ await helper.text(percentScale, "9");
+ await BrowserTestUtils.waitForAttributeRemoval("hidden", scaleError);
+ ok(!scaleError.hidden, "Scale error is showing");
+ });
+
+ is(destinationPicker.disabled, false, "Destination picker is enabled");
+
+ // Select a new printer
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+ await BrowserTestUtils.waitForCondition(
+ () => scaleError.hidden,
+ "Wait for scale error to be hidden"
+ );
+ is(percentScale.value, "100", "Scale has reset to 100");
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_selection.js b/toolkit/components/printing/tests/browser_print_selection.js
new file mode 100644
index 0000000000..fc62da00e2
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_selection.js
@@ -0,0 +1,244 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+const frameSource =
+ "<a href='about:mozilla'>some text</a><a id='other' href='about:about'>other text</a>";
+const sources = [
+ `<html><iframe id="f" srcdoc="${frameSource}"></iframe></html>`,
+ `<html><iframe id="f" src="https://example.com/document-builder.sjs?html=${frameSource}"></iframe></html>`,
+];
+
+async function getPreviewText(previewBrowser) {
+ return SpecialPowers.spawn(previewBrowser, [], function () {
+ return content.document.body.textContent;
+ });
+}
+
+add_task(async function print_selection() {
+ let i = 0;
+ for (let source of sources) {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ "data:text/html," + source,
+ async function (browser) {
+ let frameBC = browser.browsingContext.children[0];
+ await SpecialPowers.spawn(frameBC, [], () => {
+ let element = content.document.getElementById("other");
+ content.focus();
+ content.getSelection().selectAllChildren(element);
+ });
+
+ let helper = new PrintHelper(browser);
+
+ // If you change this, change nsContextMenu.printSelection() too.
+ PrintUtils.startPrintWindow(frameBC, {
+ printSelectionOnly: true,
+ });
+
+ await waitForPreviewVisible();
+
+ let previewBrowser = document.querySelector(
+ ".printPreviewBrowser[previewtype='selection']"
+ );
+ let previewText = () => getPreviewText(previewBrowser);
+ // The preview process is async, wait for it to not be empty.
+ let textContent = await TestUtils.waitForCondition(previewText);
+ is(textContent, "other text", "Correct content loaded");
+
+ let printSelect = document
+ .querySelector(".printSettingsBrowser")
+ .contentDocument.querySelector("#source-version-selection-radio");
+ ok(
+ BrowserTestUtils.is_visible(printSelect),
+ "Print selection checkbox is shown"
+ );
+ ok(printSelect.checked, "Print selection checkbox is checked");
+
+ let file = helper.mockFilePicker(`browser_print_selection-${i++}.pdf`);
+ await helper.assertPrintToFile(file, () => {
+ helper.click(helper.get("print-button"));
+ });
+ PrintHelper.resetPrintPrefs();
+ }
+ );
+ }
+});
+
+add_task(async function print_selection_parent_process() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab("about:support", async function (browser) {
+ ok(!browser.isRemote, "Page loaded in parent process");
+ let selectedText = await SpecialPowers.spawn(
+ browser.browsingContext,
+ [],
+ () => {
+ let element = content.document.querySelector("h1");
+ content.focus();
+ content.getSelection().selectAllChildren(element);
+ return element.textContent;
+ }
+ );
+ ok(selectedText, "There is selected text");
+
+ let helper = new PrintHelper(browser);
+
+ // If you change this, change nsContextMenu.printSelection() too.
+ PrintUtils.startPrintWindow(browser.browsingContext, {
+ printSelectionOnly: true,
+ });
+
+ await waitForPreviewVisible();
+
+ let previewBrowser = document.querySelector(
+ ".printPreviewBrowser[previewtype='selection']"
+ );
+ let previewText = () => getPreviewText(previewBrowser);
+ // The preview process is async, wait for it to not be empty.
+ let textContent = await TestUtils.waitForCondition(previewText);
+ is(textContent, selectedText, "Correct content loaded");
+
+ let printSelect = document
+ .querySelector(".printSettingsBrowser")
+ .contentDocument.querySelector("#source-version-selection-radio");
+ ok(
+ BrowserTestUtils.is_visible(printSelect),
+ "Print selection checkbox is shown"
+ );
+ ok(printSelect.checked, "Print selection checkbox is checked");
+
+ let file = helper.mockFilePicker(`browser_print_selection_parent.pdf`);
+ await helper.assertPrintToFile(file, () => {
+ helper.click(helper.get("print-button"));
+ });
+ PrintHelper.resetPrintPrefs();
+ });
+});
+
+add_task(async function no_print_selection() {
+ // Ensures the print selection checkbox is hidden if nothing is selected
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+
+ let printSelect = helper.get("source-version-selection");
+ ok(
+ BrowserTestUtils.is_hidden(printSelect),
+ "Print selection checkbox is hidden"
+ );
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function print_selection_switch() {
+ await PrintHelper.withTestPage(async helper => {
+ await SpecialPowers.spawn(helper.sourceBrowser, [], async function () {
+ let element = content.document.querySelector("h1");
+ content.window.getSelection().selectAllChildren(element);
+ });
+
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let printSource = helper.get("source-version-source-radio");
+ ok(printSource.checked, "Print source radio is checked");
+ let printSelect = helper.get("source-version-selection-radio");
+ ok(!printSelect.checked, "Print selection radio is not checked");
+
+ function getCurrentBrowser(previewType) {
+ let browser = document.querySelector(
+ `.printPreviewBrowser[previewtype="${previewType}"]`
+ );
+ is(
+ browser.parentElement.getAttribute("previewtype"),
+ previewType,
+ "Expected browser is showing"
+ );
+ return browser;
+ }
+
+ let selectedText = "Article title";
+ let fullText = await getPreviewText(getCurrentBrowser("source"));
+
+ helper.assertSettingsMatch({
+ printSelectionOnly: false,
+ });
+
+ await helper.assertSettingsChanged(
+ { printSelectionOnly: false },
+ { printSelectionOnly: true },
+ async () => {
+ await helper.waitForPreview(() => helper.click(printSelect));
+ let text = await getPreviewText(getCurrentBrowser("selection"));
+ is(text, selectedText, "Correct content loaded");
+ }
+ );
+
+ await helper.assertSettingsChanged(
+ { printSelectionOnly: true },
+ { printSelectionOnly: false },
+ async () => {
+ await helper.waitForPreview(() => helper.click(printSource));
+ let text = await getPreviewText(getCurrentBrowser("source"));
+ is(text, fullText, "Correct content loaded");
+ }
+ );
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function open_system_print_with_selection_and_pdf() {
+ await BrowserTestUtils.withNewTab(
+ "data:text/html," + sources[0],
+ async function (browser) {
+ let frameBC = browser.browsingContext.children[0];
+ await SpecialPowers.spawn(frameBC, [], () => {
+ let element = content.document.getElementById("other");
+ content.focus();
+ content.getSelection().selectAllChildren(element);
+ });
+
+ let helper = new PrintHelper(browser);
+
+ // Add another printer so the system dialog link is shown on Windows.
+ helper.addMockPrinter("A printer");
+
+ PrintUtils.startPrintWindow(frameBC, {});
+
+ await waitForPreviewVisible();
+
+ // Ensure that the PDF printer is selected since the way settings are
+ // cloned is different in this case.
+ is(
+ helper.settings.printerName,
+ "Mozilla Save to PDF",
+ "Mozilla Save to PDF is the current printer."
+ );
+
+ await helper.setupMockPrint();
+
+ helper.click(helper.get("open-dialog-link"));
+ await helper.withClosingFn(() => {
+ helper.resolveShowSystemDialog();
+ helper.resolvePrint();
+ });
+
+ ok(
+ helper.systemDialogOpenedWithSelection,
+ "Expect system print dialog to be notified of selection"
+ );
+
+ PrintHelper.resetPrintPrefs();
+ }
+ );
+});
diff --git a/toolkit/components/printing/tests/browser_print_settings_fallback.js b/toolkit/components/printing/tests/browser_print_settings_fallback.js
new file mode 100644
index 0000000000..6f30c87d8c
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_settings_fallback.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let badPrinterName = "Bad";
+let otherPrinterName = "Fallback";
+
+async function setupPrinters(helper) {
+ let badPrinter = helper.addMockPrinter({
+ name: badPrinterName,
+ });
+
+ let badPrinterInfo = await badPrinter.printerInfo;
+ badPrinterInfo.defaultSettings.printerName = "";
+
+ helper.addMockPrinter(otherPrinterName);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", badPrinterName]],
+ });
+}
+
+add_task(async function testBadPrinterSettings() {
+ await PrintHelper.withTestPage(async helper => {
+ await setupPrinters(helper);
+ await helper.startPrint();
+
+ let destinationPicker = helper.get("printer-picker");
+ // Fallback can be any other printer, the fallback or save to pdf printer.
+ isnot(
+ destinationPicker.value,
+ badPrinterName,
+ "A fallback printer is selected"
+ );
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_print_simplified_mode.js b/toolkit/components/printing/tests/browser_print_simplified_mode.js
new file mode 100644
index 0000000000..8826f9071b
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_simplified_mode.js
@@ -0,0 +1,262 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function switch_print_preview_browsers() {
+ await PrintHelper.withTestPage(async helper => {
+ // Wait for the article state to be determined.
+ await helper.waitForReaderModeReady();
+
+ // Start print preview.
+ await helper.startPrint();
+ let sourcePreviewBrowser = helper.currentPrintPreviewBrowser;
+
+ {
+ // Assert that we are showing the initial content on default print preview browser
+ let [headerText, headingText] = await SpecialPowers.spawn(
+ sourcePreviewBrowser,
+ [],
+ async function () {
+ return [
+ content.document.querySelector("header").textContent,
+ content.document.querySelector("h1").textContent,
+ ];
+ }
+ );
+ is(headerText, "Site header", "Should have initial content.");
+ is(headingText, "Article title", "Should have initial title.");
+ }
+
+ // Here we call simplified mode
+ await helper.openMoreSettings();
+ let simplifyRadio = helper.get("source-version-simplified-radio");
+ ok(!simplifyRadio.checked, "Simplify page is not checked");
+ ok(BrowserTestUtils.is_visible(simplifyRadio), "Simplify is shown");
+
+ await helper.waitForPreview(() => helper.click(simplifyRadio));
+ let simplifiedPreviewBrowser = helper.currentPrintPreviewBrowser;
+ is(
+ simplifiedPreviewBrowser.getAttribute("previewtype"),
+ "simplified",
+ "Simplified browser was rendered"
+ );
+ is(
+ simplifiedPreviewBrowser.closest("stack").getAttribute("previewtype"),
+ "simplified",
+ "Simplified browser is selected"
+ );
+ ok(
+ BrowserTestUtils.is_visible(simplifiedPreviewBrowser),
+ "Simplified browser is visible"
+ );
+ ok(simplifyRadio.checked, "Simplify page is checked");
+
+ {
+ // Assert that we are showing custom content on simplified print preview browser
+ let [hasHeader, headingText] = await SpecialPowers.spawn(
+ simplifiedPreviewBrowser,
+ [],
+ async function () {
+ return [
+ !!content.document.querySelector("header"),
+ content.document.querySelector("h1").textContent,
+ ];
+ }
+ );
+ ok(!hasHeader, "The header was simplified out");
+ is(headingText, "Article title", "The heading is still there");
+ }
+
+ // Switch back to default print preview content
+ let sourceRadio = helper.get("source-version-source-radio");
+ ok(!sourceRadio.checked, "Source is not checked");
+ await helper.waitForPreview(() => helper.click(sourceRadio));
+ is(
+ helper.currentPrintPreviewBrowser,
+ sourcePreviewBrowser,
+ "Source browser was rendered"
+ );
+ is(
+ sourcePreviewBrowser.getAttribute("previewtype"),
+ "source",
+ "Source browser was rendered"
+ );
+ is(
+ sourcePreviewBrowser.closest("stack").getAttribute("previewtype"),
+ "source",
+ "Source browser is selected"
+ );
+ ok(
+ BrowserTestUtils.is_visible(sourcePreviewBrowser),
+ "Source browser is visible"
+ );
+ ok(sourceRadio.checked, "Source version is checked");
+
+ {
+ // Assert that we are showing the initial content on default print preview browser
+ let headerText = await SpecialPowers.spawn(
+ sourcePreviewBrowser,
+ [],
+ async function () {
+ return content.document.querySelector("header").textContent;
+ }
+ );
+ is(headerText, "Site header", "Should have initial content.");
+ }
+
+ await helper.closeDialog();
+ }, "simplifyArticleSample.html");
+});
+
+add_task(async function testPrintBackgroundsDisabledSimplified() {
+ await PrintHelper.withTestPage(async helper => {
+ // Wait for the article state to be determined.
+ await helper.waitForReaderModeReady();
+ await helper.startPrint();
+
+ helper.assertPreviewedWithSettings({
+ printBGImages: false,
+ printBGColors: false,
+ });
+
+ await helper.openMoreSettings();
+
+ let printBackgrounds = helper.get("backgrounds-enabled");
+ ok(!printBackgrounds.checked, "Print backgrounds is not checked");
+ ok(!printBackgrounds.disabled, "Print backgrounds in not disabled");
+
+ await helper.assertSettingsChanged(
+ { printBGImages: false, printBGColors: false },
+ { printBGImages: true, printBGColors: true },
+ async () => {
+ await helper.waitForPreview(() => helper.click(printBackgrounds));
+ }
+ );
+
+ // Print backgrounds was enabled for preview.
+ ok(printBackgrounds.checked, "Print backgrounds is checked");
+ ok(!printBackgrounds.disabled, "Print backgrounds is not disabled");
+ helper.assertPreviewedWithSettings({
+ printBGImages: true,
+ printBGColors: true,
+ });
+
+ let simplifyRadio = helper.get("source-version-simplified-radio");
+ ok(!simplifyRadio.checked, "Simplify page is not checked");
+ ok(BrowserTestUtils.is_visible(simplifyRadio), "Simplify is shown");
+
+ // Switch to simplified mode.
+ await helper.waitForPreview(() => helper.click(simplifyRadio));
+
+ // Print backgrounds should be disabled, it's incompatible with simplified.
+ ok(!printBackgrounds.checked, "Print backgrounds is now unchecked");
+ ok(printBackgrounds.disabled, "Print backgrounds has been disabled");
+ helper.assertPreviewedWithSettings({
+ printBGImages: false,
+ printBGColors: false,
+ });
+
+ // Switch back to source, printBackgrounds is remembered.
+ let sourceRadio = helper.get("source-version-source-radio");
+ ok(!sourceRadio.checked, "Source is not checked");
+ ok(BrowserTestUtils.is_visible(sourceRadio), "Source is shown");
+
+ await helper.waitForPreview(() => helper.click(sourceRadio));
+
+ ok(printBackgrounds.checked, "Print backgrounds setting was remembered");
+ ok(!printBackgrounds.disabled, "Print backgrounds can be changed again");
+ helper.assertPreviewedWithSettings({
+ printBGImages: true,
+ printBGColors: true,
+ });
+
+ await helper.closeDialog();
+ }, "simplifyArticleSample.html");
+});
+
+add_task(async function testSimplifyHiddenNonArticle() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let sourceVersionSection = helper.get("source-version-section");
+ ok(
+ BrowserTestUtils.is_hidden(sourceVersionSection),
+ "Source version is hidden"
+ );
+ await helper.closeDialog();
+ }, "simplifyNonArticleSample.html");
+});
+
+add_task(async function testSimplifyNonArticleTabModal() {
+ await PrintHelper.withTestPage(async helper => {
+ let tab = gBrowser.selectedTab;
+
+ // Trick browser to think loaded tab has isArticle property set as true
+ tab.linkedBrowser.isArticle = true;
+
+ // Enter print preview
+ await helper.startPrint();
+
+ // Assert that we are showing the initial content on default print preview browser
+ await SpecialPowers.spawn(
+ helper.currentPrintPreviewBrowser,
+ [],
+ async () => {
+ is(
+ content.document.title,
+ "Non article title",
+ "Should have initial content."
+ );
+ }
+ );
+
+ await helper.openMoreSettings();
+
+ // Simplify the page.
+ let simplifyRadio = helper.get("source-version-simplified-radio");
+ ok(!simplifyRadio.checked, "Simplify is off");
+ await helper.waitForPreview(() => helper.click(simplifyRadio));
+ let simplifiedPreviewBrowser = helper.currentPrintPreviewBrowser;
+ is(
+ simplifiedPreviewBrowser.getAttribute("previewtype"),
+ "simplified",
+ "The simplified browser is shown"
+ );
+
+ // Assert that simplify page option is checked
+ ok(simplifyRadio.checked, "Should have simplify page option checked");
+
+ // Assert that we are showing recovery content on simplified print preview browser
+ await SpecialPowers.spawn(simplifiedPreviewBrowser, [], async () => {
+ await ContentTaskUtils.waitForCondition(
+ () => content.document.title === "Failed to load article from page",
+ "Simplified document title should be updated with recovery title."
+ );
+ });
+
+ await helper.closeDialog();
+ }, "simplifyNonArticleSample.html");
+});
+
+add_task(async function testSimplifyHiddenReaderMode() {
+ await PrintHelper.withTestPage(async helper => {
+ let tab = gBrowser.selectedTab;
+
+ // Trigger reader mode for the tab
+ let readerButton = document.getElementById("reader-mode-button");
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+ readerButton.click();
+ await BrowserTestUtils.browserLoaded(tab.linkedBrowser);
+
+ // Print from reader mode
+ await helper.startPrint();
+ await helper.openMoreSettings();
+ let sourceVersionSection = helper.get("source-version-section");
+ ok(
+ BrowserTestUtils.is_hidden(sourceVersionSection),
+ "Source version is hidden in reader mode"
+ );
+ await helper.closeDialog();
+ }, "simplifyArticleSample.html");
+});
diff --git a/toolkit/components/printing/tests/browser_print_stream.js b/toolkit/components/printing/tests/browser_print_stream.js
new file mode 100644
index 0000000000..dff1ee8762
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_print_stream.js
@@ -0,0 +1,102 @@
+//creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+);
+
+async function printToDestination(aBrowser, aDestination) {
+ let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ let fileName = `printDestinationTest-${aDestination}.pdf`;
+ let filePath = PathUtils.join(tmpDir.path, fileName);
+
+ info(`Printing to ${filePath}`);
+
+ let settings = PSSVC.createNewPrintSettings();
+ settings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
+ settings.outputDestination = aDestination;
+
+ settings.headerStrCenter = "";
+ settings.headerStrLeft = "";
+ settings.headerStrRight = "";
+ settings.footerStrCenter = "";
+ settings.footerStrLeft = "";
+ settings.footerStrRight = "";
+
+ settings.unwriteableMarginTop = 1; /* Just to ensure settings are respected on both */
+ let outStream = null;
+ if (aDestination == Ci.nsIPrintSettings.kOutputDestinationFile) {
+ settings.toFileName = PathUtils.join(tmpDir.path, fileName);
+ } else {
+ is(aDestination, Ci.nsIPrintSettings.kOutputDestinationStream);
+ outStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(
+ Ci.nsIFileOutputStream
+ );
+ let tmpFile = tmpDir.clone();
+ tmpFile.append(fileName);
+ outStream.init(tmpFile, -1, 0o666, 0);
+ settings.outputStream = outStream;
+ }
+
+ await aBrowser.browsingContext.print(settings);
+
+ return filePath;
+}
+
+add_task(async function testPrintToStream() {
+ await PrintHelper.withTestPage(async helper => {
+ let filePath = await printToDestination(
+ helper.sourceBrowser,
+ Ci.nsIPrintSettings.kOutputDestinationFile
+ );
+ let streamPath = await printToDestination(
+ helper.sourceBrowser,
+ Ci.nsIPrintSettings.kOutputDestinationStream
+ );
+
+ // In Cocoa the CGContext adds a hash, plus there are other minor
+ // non-user-visible differences, so we need to be a bit more sloppy there.
+ //
+ // We see one byte difference in Windows and Linux on automation sometimes,
+ // though files are consistently the same locally, that needs
+ // investigation, but it's probably harmless.
+ const maxSizeDifference = AppConstants.platform == "macosx" ? 100 : 2;
+
+ // Buffering shenanigans? Wait for sizes to match... There's no great
+ // IOUtils methods to force a flush without writing anything...
+ await TestUtils.waitForCondition(async function () {
+ let fileStat = await IOUtils.stat(filePath);
+ let streamStat = await IOUtils.stat(streamPath);
+
+ ok(fileStat.size > 0, "File file should not be empty: " + fileStat.size);
+ ok(
+ streamStat.size > 0,
+ "Stream file should not be empty: " + streamStat.size
+ );
+ return Math.abs(fileStat.size - streamStat.size) <= maxSizeDifference;
+ }, "Sizes should (almost) match");
+
+ if (false) {
+ // This doesn't work reliably on automation, but works locally, see
+ // above...
+ let fileData = await IOUtils.read(filePath);
+ let streamData = await IOUtils.read(streamPath);
+ ok(!!fileData.length, "File should not be empty");
+ is(fileData.length, streamData.length, "File size should be equal");
+ for (let i = 0; i < fileData.length; ++i) {
+ if (fileData[i] != streamData[i]) {
+ is(
+ fileData[i],
+ streamData[i],
+ `Files should be equal (byte ${i} different)`
+ );
+ break;
+ }
+ }
+ }
+
+ await IOUtils.remove(filePath);
+ await IOUtils.remove(streamPath);
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_sheet_count.js b/toolkit/components/printing/tests/browser_sheet_count.js
new file mode 100644
index 0000000000..d08020db18
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_sheet_count.js
@@ -0,0 +1,356 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function testSheetCount() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ let sheetCountElement = helper.get("sheet-count");
+ let { id } = helper.doc.l10n.getAttributes(sheetCountElement);
+ is(id, "printui-sheets-count", "The l10n id is correct");
+ let initialSheetCount = helper.sheetCount;
+ ok(initialSheetCount >= 1, "There is an initial sheet count");
+
+ await helper.openMoreSettings();
+
+ let scaleRadio = helper.get("percent-scale-choice");
+ await helper.waitForPreview(() => helper.click(scaleRadio));
+
+ let percentScale = helper.get("percent-scale");
+ await helper.waitForPreview(() => helper.text(percentScale, "200"));
+
+ let zoomedSheetCount = helper.sheetCount;
+ ok(zoomedSheetCount > initialSheetCount, "The sheet count increased");
+
+ // Since we're using the Save to PDF printer, the numCopies element should
+ // be hidden and its value ignored.
+ let numCopies = helper.get("copies-count");
+ ok(BrowserTestUtils.is_hidden(numCopies), "numCopies element is hidden");
+ helper.dispatchSettingsChange({
+ numCopies: 4,
+ });
+ is(
+ helper.sheetCount,
+ zoomedSheetCount,
+ "numCopies is ignored for Save to PDF printer"
+ );
+
+ is(helper.viewSettings.numCopies, 1, "numCopies is 1 in viewSettings");
+
+ // We don't have any "real" printers set up for testing yet, so insert a modified
+ // copy of the PDF printer which pretends to be real, and switch to that
+ // to triggers the component to update.
+ let realPrinterName = "My real printer";
+ let pdfPrinterInfo =
+ helper.win.PrintSettingsViewProxy.availablePrinters[
+ PrintUtils.SAVE_TO_PDF_PRINTER
+ ];
+ let mockPrinterInfo = Object.assign({}, pdfPrinterInfo, {});
+ mockPrinterInfo.settings = pdfPrinterInfo.settings.clone();
+ mockPrinterInfo.settings.outputFormat =
+ Ci.nsIPrintSettings.kOutputFormatNative;
+ mockPrinterInfo.settings.printerName = realPrinterName;
+
+ helper.win.PrintSettingsViewProxy.availablePrinters[realPrinterName] =
+ mockPrinterInfo;
+ await helper.dispatchSettingsChange({
+ printerName: realPrinterName,
+ });
+ await helper.awaitAnimationFrame();
+
+ let { settings, viewSettings } = helper;
+
+ is(
+ settings.printerName,
+ realPrinterName,
+ "Sanity check the current settings have the new printerName"
+ );
+ is(
+ settings.outputFormat,
+ Ci.nsIPrintSettings.kOutputFormatNative,
+ "The new printer has the correct outputFormat"
+ );
+ is(viewSettings.numCopies, 4, "numCopies is 4 in viewSettings");
+
+ // numCopies is now visible and sheetCount is multiplied by numCopies.
+ ok(BrowserTestUtils.is_visible(numCopies), "numCopies element is visible");
+ is(numCopies.value, "4", "numCopies displays the correct value");
+ is(
+ helper.sheetCount,
+ zoomedSheetCount * 4,
+ "numCopies is used when using a non-PDF printer"
+ );
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testSheetCountPageRange() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({
+ shrinkToFit: false,
+ scaling: 2,
+ })
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != 1,
+ "Wait for sheet count to update"
+ );
+ let sheets = helper.sheetCount;
+ ok(sheets >= 3, "There are at least 3 pages");
+
+ // Set page range to 2-3, sheet count should be 2.
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({
+ pageRanges: [2, 3],
+ })
+ );
+
+ sheets = helper.sheetCount;
+ is(sheets, 2, "There are now only 2 pages shown");
+ });
+});
+
+// Test that enabling duplex printing updates the sheet count accordingly.
+add_task(async function testSheetCountDuplex() {
+ await PrintHelper.withTestPage(async helper => {
+ const mockPrinterName = "DuplexCapablePrinter";
+ const printer = helper.addMockPrinter(mockPrinterName);
+ printer.supportsDuplex = Promise.resolve(true);
+
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+
+ // Set scale and shinkToFit to make the document
+ // bigger so that it spans multiple pages.
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({
+ shrinkToFit: false,
+ scaling: 4,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != 1,
+ "Wait for sheet count to update"
+ );
+ let singleSidedSheets = helper.sheetCount;
+ ok(singleSidedSheets >= 2, "There are at least 2 pages");
+
+ // Turn on long-edge duplex printing and ensure the sheet count is halved.
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != singleSidedSheets,
+ "Wait for sheet count to update"
+ );
+ let duplexLongEdgeSheets = helper.sheetCount;
+ is(
+ duplexLongEdgeSheets,
+ Math.ceil(singleSidedSheets / 2),
+ "Long-edge duplex printing halved the sheet count"
+ );
+
+ // Turn off duplex printing
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount == singleSidedSheets,
+ "Wait for sheet count to update"
+ );
+
+ // Turn on short-edge duplex printing and ensure the
+ // sheet count matches the long-edge duplex sheet count.
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnShortEdge,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != singleSidedSheets,
+ "Wait for sheet count to update"
+ );
+ let duplexShortEdgeSheets = helper.sheetCount;
+ is(
+ duplexShortEdgeSheets,
+ duplexLongEdgeSheets,
+ "Short-edge duplex printing halved the sheet count"
+ );
+ });
+});
+
+// Test that enabling duplex printing with multiple copies updates the
+// sheet count accordingly.
+add_task(async function testSheetCountDuplexWithCopies() {
+ // Use different scale values to exercise printing of different page counts
+ for (let scale of [2, 3, 4, 5]) {
+ await TestDuplexNumCopiesAtScale(scale);
+ }
+});
+
+// Enable duplex and numCopies=2 with the provided scale value and check
+// that the sheet count is correct.
+async function TestDuplexNumCopiesAtScale(scale) {
+ await PrintHelper.withTestPage(async helper => {
+ const mockPrinterName = "DuplexCapablePrinter";
+ const printer = helper.addMockPrinter(mockPrinterName);
+ printer.supportsDuplex = Promise.resolve(true);
+
+ await helper.startPrint();
+ await helper.dispatchSettingsChange({ printerName: mockPrinterName });
+
+ // Set scale and shinkToFit to make the document
+ // bigger so that it spans multiple pages.
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({
+ shrinkToFit: false,
+ scaling: scale,
+ duplex: Ci.nsIPrintSettings.kDuplexNone,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != 1,
+ "Wait for sheet count to update"
+ );
+ let singleSidedSheets = helper.sheetCount;
+
+ // Chnage to two copies
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({
+ numCopies: 2,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != singleSidedSheets,
+ "Wait for sheet count to update"
+ );
+ let twoCopiesSheetCount = helper.sheetCount;
+
+ // Turn on duplex printing.
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({
+ duplex: Ci.nsIPrintSettings.kDuplexFlipOnLongEdge,
+ })
+ );
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != twoCopiesSheetCount,
+ "Wait for sheet count to update"
+ );
+ let duplexTwoCopiesSheetCount = helper.sheetCount;
+
+ // Check sheet count accounts for duplex and numCopies.
+ is(
+ duplexTwoCopiesSheetCount,
+ Math.ceil(singleSidedSheets / 2) * 2,
+ "Duplex with 2 copies sheet count is correct"
+ );
+
+ await helper.closeDialog();
+ });
+}
+
+add_task(async function testPagesPerSheetCount() {
+ await PrintHelper.withTestPage(async helper => {
+ let mockPrinterName = "A real printer!";
+ helper.addMockPrinter(mockPrinterName);
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", mockPrinterName]],
+ });
+
+ await helper.startPrint();
+
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({
+ shrinkToFit: false,
+ scaling: 2,
+ })
+ );
+
+ await BrowserTestUtils.waitForCondition(
+ () => helper.sheetCount != 1,
+ "Wait for sheet count to update"
+ );
+ let sheets = helper.sheetCount;
+
+ ok(sheets > 1, "There are multiple pages");
+
+ await helper.openMoreSettings();
+ let pagesPerSheet = helper.get("pages-per-sheet-picker");
+ ok(BrowserTestUtils.is_visible(pagesPerSheet), "Pages per sheet is shown");
+ pagesPerSheet.focus();
+
+ let popupOpen = BrowserTestUtils.waitForSelectPopupShown(window);
+
+ EventUtils.sendKey("space", helper.win);
+
+ await popupOpen;
+
+ let numberMove =
+ [...pagesPerSheet.options].map(o => o.value).indexOf("16") -
+ pagesPerSheet.selectedIndex;
+
+ for (let i = 0; i < numberMove; i++) {
+ EventUtils.sendKey("down", window);
+ if (document.activeElement.value == 16) {
+ break;
+ }
+ }
+
+ await helper.waitForPreview(() => EventUtils.sendKey("return", window));
+
+ sheets = helper.sheetCount;
+ is(sheets, 1, "There's only one sheet now");
+
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({ numCopies: 5 })
+ );
+
+ sheets = helper.sheetCount;
+ is(sheets, 5, "Copies are handled with pages per sheet correctly");
+
+ await helper.closeDialog();
+ });
+});
+
+add_task(async function testUpdateCopiesNoPreviewUpdate() {
+ const mockPrinterName = "Fake Printer";
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter(mockPrinterName);
+ await helper.startPrint();
+
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({ numCopies: 5 })
+ );
+
+ ok(
+ !helper.win.PrintEventHandler._updatePrintPreviewTask.isArmed,
+ "Preview Task is not armed"
+ );
+
+ await helper.waitForPreview(() =>
+ helper.dispatchSettingsChange({ printerName: mockPrinterName })
+ );
+
+ await helper.waitForSettingsEvent(() =>
+ helper.dispatchSettingsChange({ numCopies: 2 })
+ );
+ ok(
+ !helper.win.PrintEventHandler._updatePrintPreviewTask.isArmed,
+ "Preview Task is not armed"
+ );
+
+ await helper.closeDialog();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_system_dialog_subdialog_hidden.js b/toolkit/components/printing/tests/browser_system_dialog_subdialog_hidden.js
new file mode 100644
index 0000000000..1f27916cc6
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_system_dialog_subdialog_hidden.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(async function testSystemDialogLinkState() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ is(
+ helper.get("printer-picker").options.length,
+ 1,
+ "Only the Save to PDF printer is available"
+ );
+
+ let systemLink = helper.get("open-dialog-link");
+ if (AppConstants.platform == "win") {
+ ok(
+ BrowserTestUtils.is_hidden(systemLink),
+ "Link is hidden on Windows with no extra printers"
+ );
+ } else {
+ ok(
+ BrowserTestUtils.is_visible(systemLink),
+ "Link is visible on Linux/macOS"
+ );
+ }
+ });
+});
+
+add_task(async function testModalPrintDialog() {
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter("A printer");
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", "A printer"]],
+ });
+
+ await helper.startPrint();
+
+ helper.assertDialogOpen();
+
+ helper.assertSettingsMatch({ printerName: "A printer" });
+ await helper.setupMockPrint();
+
+ helper.click(helper.get("open-dialog-link"));
+
+ helper.assertDialogHidden();
+
+ await helper.withClosingFn(() => {
+ helper.resolveShowSystemDialog();
+ helper.resolvePrint();
+ });
+
+ helper.assertDialogClosed();
+ });
+});
+
+add_task(async function testModalPrintDialogCancelled() {
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter("A printer");
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", "A printer"]],
+ });
+
+ await helper.startPrint();
+
+ helper.assertDialogOpen();
+
+ helper.assertSettingsMatch({ printerName: "A printer" });
+ await helper.setupMockPrint();
+
+ helper.click(helper.get("open-dialog-link"));
+
+ helper.assertDialogHidden();
+
+ await helper.withClosingFn(() => {
+ helper.resolveShowSystemDialog(false);
+ });
+
+ helper.assertDialogClosed();
+ });
+});
+
+add_task(async function testPrintDoesNotWaitForPreview() {
+ await PrintHelper.withTestPage(async helper => {
+ helper.addMockPrinter("A printer");
+ await SpecialPowers.pushPrefEnv({
+ set: [["print_printer", "A printer"]],
+ });
+
+ await helper.startPrint({ waitFor: "loadComplete" });
+ await helper.awaitAnimationFrame();
+
+ helper.mockFilePicker("print_does_not_wait_for_preview.pdf");
+ await helper.setupMockPrint();
+
+ let systemPrint = helper.get("system-print");
+ await BrowserTestUtils.waitForCondition(
+ () => BrowserTestUtils.is_visible(systemPrint),
+ "Wait for the system-print to be visible"
+ );
+
+ helper.click(helper.get("open-dialog-link"));
+
+ helper.assertDialogHidden();
+ await helper.withClosingFn(() => {
+ helper.resolveShowSystemDialog();
+ helper.resolvePrint();
+ });
+
+ helper.assertDialogClosed();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_toolbar_button_toggle.js b/toolkit/components/printing/tests/browser_toolbar_button_toggle.js
new file mode 100644
index 0000000000..0a644a1e57
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_toolbar_button_toggle.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+add_task(async function testToggleToolbarButton() {
+ await PrintHelper.withTestPage(async helper => {
+ CustomizableUI.addWidgetToArea("print-button", CustomizableUI.AREA_NAVBAR);
+
+ helper.assertDialogClosed();
+
+ // get the button from the toolbar
+ let button = document.getElementById("print-button");
+ // click the toolbar button
+ EventUtils.synthesizeMouseAtCenter(button, {});
+
+ await helper.waitForDialog();
+
+ // ensure dialog box is open
+ helper.assertDialogOpen();
+
+ // click toolbar button again to close dialog box
+ EventUtils.synthesizeMouseAtCenter(button, {});
+
+ helper.assertDialogClosed();
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_ui_labels.js b/toolkit/components/printing/tests/browser_ui_labels.js
new file mode 100644
index 0000000000..a107bac4d1
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_ui_labels.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function test_FormFieldLabels() {
+ await PrintHelper.withTestPage(async helper => {
+ await helper.startPrint();
+
+ let fields = Array.from(helper.get("print").elements);
+ for (let field of fields) {
+ if (field.localName == "button") {
+ continue;
+ }
+ ok(
+ field.labels.length ||
+ field.hasAttribute("aria-label") ||
+ field.hasAttribute("aria-labelledby"),
+ `Field ${field.localName}#${field.id} should be labelled`
+ );
+ }
+ });
+});
diff --git a/toolkit/components/printing/tests/browser_window_print.js b/toolkit/components/printing/tests/browser_window_print.js
new file mode 100644
index 0000000000..7d73c0616c
--- /dev/null
+++ b/toolkit/components/printing/tests/browser_window_print.js
@@ -0,0 +1,336 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+const TEST_PATH_SITE = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://test1.example.com"
+);
+
+add_task(async function test_print_blocks() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print.html`,
+ async function (browser) {
+ info(
+ "Waiting for the first window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ {
+ let [before, afterFirst] = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return [
+ !!content.document.getElementById("before-print"),
+ !!content.document.getElementById("after-first-print"),
+ ];
+ }
+ );
+
+ ok(before, "Content before printing should be in the DOM");
+ ok(!afterFirst, "Shouldn't have returned yet from window.print()");
+ }
+
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+
+ await helper.waitForDialog();
+
+ {
+ let [before, afterFirst, afterSecond] = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return [
+ !!content.document.getElementById("before-print"),
+ !!content.document.getElementById("after-first-print"),
+ !!content.document.getElementById("after-second-print"),
+ ];
+ }
+ );
+
+ ok(before, "Content before printing should be in the DOM");
+ ok(afterFirst, "Should be in the second print already");
+ ok(afterSecond, "Shouldn't have blocked if we have mozPrintCallbacks");
+ }
+ }
+ );
+});
+
+add_task(async function test_print_delayed_during_load() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print_delayed_during_load.html`,
+ async function (browser) {
+ info(
+ "Waiting for the first window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ // The print dialog is open, should be open after onload.
+ {
+ let duringLoad = await SpecialPowers.spawn(browser, [], () => {
+ return !!content.document.getElementById("added-during-load");
+ });
+ ok(duringLoad, "Print should've been delayed");
+ }
+
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+
+ is(typeof browser.isConnected, "boolean");
+ await BrowserTestUtils.waitForCondition(() => !browser.isConnected);
+ ok(true, "Tab should've been closed after printing");
+ }
+ );
+});
+
+add_task(async function test_print_on_sandboxed_frame() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print_sandboxed_iframe.html`,
+ async function (browser) {
+ info(
+ "Waiting for the first window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ isnot(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "Should open the print preview correctly"
+ );
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ }
+ );
+});
+
+add_task(async function test_print_another_iframe_and_remove() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print_another_iframe_and_remove.html`,
+ async function (browser) {
+ let firstFrame = browser.browsingContext.children[0];
+ info("Clicking on the button in the first iframe");
+ BrowserTestUtils.synthesizeMouse("button", 0, 0, {}, firstFrame);
+
+ await new PrintHelper(browser).waitForDialog();
+
+ isnot(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "Should open the print preview correctly"
+ );
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ }
+ );
+});
+
+add_task(async function test_window_print_coop_site() {
+ for (const base of [TEST_PATH, TEST_PATH_SITE]) {
+ const url = `${base}file_coop_header2.html`;
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+ await BrowserTestUtils.withNewTab(url, async function (browser) {
+ await new PrintHelper(browser).waitForDialog();
+
+ ok(true, "Shouldn't crash");
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ });
+ }
+});
+
+add_task(async function test_window_print_iframe_remove_on_afterprint() {
+ ok(
+ !document.querySelector(".printPreviewBrowser"),
+ "There shouldn't be any print preview browser"
+ );
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print_iframe_remove_on_afterprint.html`,
+ async function (browser) {
+ await new PrintHelper(browser).waitForDialog();
+ let modalBefore = await SpecialPowers.spawn(browser, [], () => {
+ return content.windowUtils.isInModalState();
+ });
+
+ ok(modalBefore, "The tab should be in modal state");
+
+ // Clear the dialog.
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+
+ let [modalAfter, hasIframe] = await SpecialPowers.spawn(
+ browser,
+ [],
+ () => {
+ return [
+ content.windowUtils.isInModalState(),
+ !!content.document.querySelector("iframe"),
+ ];
+ }
+ );
+
+ ok(!modalAfter, "Should've cleared the modal state properly");
+ ok(!hasIframe, "Iframe should've been removed from the DOM");
+ }
+ );
+});
+
+// FIXME(emilio): This test doesn't use window.print(), why is it on this file?
+add_task(async function test_focused_browsing_context() {
+ await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ `${TEST_PATH}longerArticle.html`
+ );
+
+ let tabCount = gBrowser.tabs.length;
+ document.getElementById("cmd_newNavigatorTab").doCommand();
+ await TestUtils.waitForCondition(() => gBrowser.tabs.length == tabCount + 1);
+ let newTabBrowser = gBrowser.selectedBrowser;
+ is(newTabBrowser.documentURI.spec, "about:newtab", "newtab is loaded");
+
+ let menuButton = document.getElementById("PanelUI-menu-button");
+ menuButton.click();
+ await BrowserTestUtils.waitForEvent(window.PanelUI.mainView, "ViewShown");
+
+ let printButtonID = "appMenu-print-button2";
+
+ document.getElementById(printButtonID).click();
+
+ let dialog = await TestUtils.waitForCondition(
+ () =>
+ gBrowser
+ .getTabDialogBox(newTabBrowser)
+ .getTabDialogManager()
+ ._dialogs.find(dlg => dlg._box.querySelector(".printSettingsBrowser")),
+ "Wait for dialog"
+ );
+ await dialog._dialogReady;
+ ok(dialog, "Dialog is available");
+ await dialog._frame.contentWindow._initialized;
+ await dialog.close();
+
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+ BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+add_task(async function test_print_with_oop_iframe() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ await BrowserTestUtils.withNewTab(
+ `${TEST_PATH}file_window_print_oop_iframe.html`,
+ async function (browser) {
+ info(
+ "Waiting for window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ isnot(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "Should open the print preview correctly"
+ );
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ }
+ );
+});
+
+add_task(async function test_base_uri_srcdoc() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ const PARENT_URI = `${TEST_PATH}file_window_print_srcdoc_base_uri.html`;
+ await BrowserTestUtils.withNewTab(PARENT_URI, async function (browser) {
+ info(
+ "Waiting for window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ let previewBrowser = document.querySelector(".printPreviewBrowser");
+ isnot(previewBrowser, null, "Should open the print preview correctly");
+
+ let baseURI = await SpecialPowers.spawn(previewBrowser, [], () => {
+ return content.document.baseURI;
+ });
+
+ is(baseURI, PARENT_URI, "srcdoc print document base uri should be right");
+
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+ });
+});
+
+add_task(async function test_print_reentrant() {
+ is(
+ document.querySelector(".printPreviewBrowser"),
+ null,
+ "There shouldn't be any print preview browser"
+ );
+
+ const URI = `${TEST_PATH}file_window_print_reentrant.html`;
+ await BrowserTestUtils.withNewTab(URI, async function (browser) {
+ info(
+ "Waiting for window.print() to run and ensure we're showing the preview..."
+ );
+
+ let helper = new PrintHelper(browser);
+ await helper.waitForDialog();
+
+ let previewBrowser = document.querySelector(".printPreviewBrowser");
+ isnot(previewBrowser, null, "Should open the print preview correctly");
+
+ gBrowser.getTabDialogBox(browser).abortAllDialogs();
+
+ let count = await SpecialPowers.spawn(browser, [], () => {
+ return parseInt(
+ content.document.getElementById("before-print-count").innerText
+ );
+ });
+
+ is(count, 1, "Should've fired beforeprint just once");
+ });
+});
diff --git a/toolkit/components/printing/tests/file_coop_header.html b/toolkit/components/printing/tests/file_coop_header.html
new file mode 100644
index 0000000000..4a724d5179
--- /dev/null
+++ b/toolkit/components/printing/tests/file_coop_header.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<html>
+ <body>
+ <p>Hello world</p>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_coop_header.html^headers^ b/toolkit/components/printing/tests/file_coop_header.html^headers^
new file mode 100644
index 0000000000..46ad58d83b
--- /dev/null
+++ b/toolkit/components/printing/tests/file_coop_header.html^headers^
@@ -0,0 +1 @@
+Cross-Origin-Opener-Policy: same-origin
diff --git a/toolkit/components/printing/tests/file_coop_header2.html b/toolkit/components/printing/tests/file_coop_header2.html
new file mode 100644
index 0000000000..37e2ec5051
--- /dev/null
+++ b/toolkit/components/printing/tests/file_coop_header2.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset="utf-8">
+<script>
+ if (self.crossOriginIsolated)
+ print();
+</script>
diff --git a/toolkit/components/printing/tests/file_coop_header2.html^headers^ b/toolkit/components/printing/tests/file_coop_header2.html^headers^
new file mode 100644
index 0000000000..6cabc380d9
--- /dev/null
+++ b/toolkit/components/printing/tests/file_coop_header2.html^headers^
@@ -0,0 +1,3 @@
+Cross-Origin-Embedder-Policy: require-corp
+Cross-Origin-Resource-Policy: same-origin
+Cross-Origin-Opener-Policy: same-origin
diff --git a/toolkit/components/printing/tests/file_first_landscape.html b/toolkit/components/printing/tests/file_first_landscape.html
new file mode 100644
index 0000000000..997d713861
--- /dev/null
+++ b/toolkit/components/printing/tests/file_first_landscape.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ @page a { size: landscape; }
+ </style>
+ </head>
+ <body>
+ <div style="page: a">Test</div>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_first_portrait.html b/toolkit/components/printing/tests/file_first_portrait.html
new file mode 100644
index 0000000000..720a99fb60
--- /dev/null
+++ b/toolkit/components/printing/tests/file_first_portrait.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ @page a { size: portrait; }
+ </style>
+ </head>
+ <body>
+ <div style="page: a">Test</div>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_landscape.html b/toolkit/components/printing/tests/file_landscape.html
new file mode 100644
index 0000000000..8372783174
--- /dev/null
+++ b/toolkit/components/printing/tests/file_landscape.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ @page { size: landscape; }
+ </style>
+ </head>
+ <body>
+ <div>Test</div>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_link_modulepreload.html b/toolkit/components/printing/tests/file_link_modulepreload.html
new file mode 100644
index 0000000000..ef8bc23c11
--- /dev/null
+++ b/toolkit/components/printing/tests/file_link_modulepreload.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<html>
+ <head>
+ <link rel="modulepreload" href="test.js">
+ </head>
+ <body>
+ <script type="importmap">
+ {
+ "imports": {
+ "test": "./test.js"
+ }
+ }
+ </script>
+ <p>Hello world</p>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_multi_page_pdf.pdf b/toolkit/components/printing/tests/file_multi_page_pdf.pdf
new file mode 100644
index 0000000000..4ed88753d8
--- /dev/null
+++ b/toolkit/components/printing/tests/file_multi_page_pdf.pdf
@@ -0,0 +1,27 @@
+%PDF-1.0
+%âãÏÓ
+2 0 obj
+<< /Type /Catalog /Pages 3 0 R>>
+endobj
+3 0 obj
+<< /Type /Pages /Kids [ 4 0 R 5 0 R ] /Count 2 >>
+endobj
+4 0 obj
+<< /Type /Page /MediaBox [0 0 3 3 ] /TrimBox [0 0 3 3 ] /Rotate 0 >>
+endobj
+5 0 obj
+<< /Type /Page /MediaBox [0 0 3 3 ] /TrimBox [0 0 3 3 ] /Rotate 0 >>
+endobj
+xref
+0 6
+0000000001 65535 f
+0000000000 00000 f
+0000000015 00000 n
+0000000063 00000 n
+0000000128 00000 n
+0000000216 00000 n
+trailer
+<</Size 4 /ID [(¿ßúê}Œ¸—uàÅ7!ýè) (¿ßúê}Œ¸—uàÅ7!ýè) ] /Root 2 0 R >>
+startxref
+304
+%%EOF
diff --git a/toolkit/components/printing/tests/file_pdf.pdf b/toolkit/components/printing/tests/file_pdf.pdf
new file mode 100644
index 0000000000..593558f9a4
--- /dev/null
+++ b/toolkit/components/printing/tests/file_pdf.pdf
@@ -0,0 +1,12 @@
+%PDF-1.0
+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/MediaBox[0 0 3 3]>>endobj
+xref
+0 4
+0000000000 65535 f
+0000000010 00000 n
+0000000053 00000 n
+0000000102 00000 n
+trailer<</Size 4/Root 1 0 R>>
+startxref
+149
+%EOF \ No newline at end of file
diff --git a/toolkit/components/printing/tests/file_portrait.html b/toolkit/components/printing/tests/file_portrait.html
new file mode 100644
index 0000000000..12414a1970
--- /dev/null
+++ b/toolkit/components/printing/tests/file_portrait.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ @page { size: portrait; }
+ </style>
+ </head>
+ <body>
+ <div>Test</div>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/file_print.html b/toolkit/components/printing/tests/file_print.html
new file mode 100644
index 0000000000..31deabc252
--- /dev/null
+++ b/toolkit/components/printing/tests/file_print.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+ window.print();
+</script>
+<p id="printed">I should be printed</p>
diff --git a/toolkit/components/printing/tests/file_print_pdf_on_frame_load.html b/toolkit/components/printing/tests/file_print_pdf_on_frame_load.html
new file mode 100644
index 0000000000..ac76a598f7
--- /dev/null
+++ b/toolkit/components/printing/tests/file_print_pdf_on_frame_load.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<meta charset="utf-8">
+<iframe src="file_multi_page_pdf.pdf" onload="this.contentWindow.print()"></iframe>
diff --git a/toolkit/components/printing/tests/file_window_print.html b/toolkit/components/printing/tests/file_window_print.html
new file mode 100644
index 0000000000..7a05cf4511
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<meta charset="utf-8">
+<div id="before-print">Before print</div>
+<canvas id="canvas" width="100" height="100"></canvas>
+<script>
+ onload = function() {
+ // window.print() is special until after the load event is finished firing.
+ setTimeout(function() {
+ // This fires a timer which would trigger a navigation and prevent the
+ // test from completing if it happens during window.print().
+ let meta = document.createElement("meta");
+ meta.setAttribute("http-equiv", "refresh");
+ meta.setAttribute("content", "0; url=/unlikely-to-be-found");
+ document.head.appendChild(meta);
+ // This one should block until we're done printing, and block the
+ // navigation too.
+ window.print();
+ meta.remove();
+ document.body.insertAdjacentHTML('beforeend', `<div id="after-first-print">After first print</div>`);
+
+ let canvas = document.getElementById("canvas");
+ canvas.mozPrintCallback = function() {};
+
+ // This one shouldn't, because the print callbacks need to run.
+ window.print();
+
+ document.body.insertAdjacentHTML('beforeend', `<div id="after-second-print">After second print</div>`);
+ }, 0);
+ }
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_another_iframe_and_remove.html b/toolkit/components/printing/tests/file_window_print_another_iframe_and_remove.html
new file mode 100644
index 0000000000..c4d176f39e
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_another_iframe_and_remove.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<meta charset="utf-8">
+<script>
+function handlePrint() {
+ frames[0].frameElement.style.display = "none";
+ frames[1].print();
+}
+</script>
+<iframe srcdoc='<button onclick="parent.handlePrint()">Print the other iframe, then hide me</button>'></iframe>
+<iframe id="iframe2" srcdoc="Print me"></iframe>
diff --git a/toolkit/components/printing/tests/file_window_print_delayed_during_load.html b/toolkit/components/printing/tests/file_window_print_delayed_during_load.html
new file mode 100644
index 0000000000..839005370d
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_delayed_during_load.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+<script>
+ onload = function() {
+ let div = document.createElement("div");
+ div.id = "added-during-load";
+ div.innerHTML = "I should be printed";
+ document.body.appendChild(div);
+ };
+
+ window.print(); // This should be delayed until after load.
+ window.close(); // So should this.
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_iframe_remove_on_afterprint.html b/toolkit/components/printing/tests/file_window_print_iframe_remove_on_afterprint.html
new file mode 100644
index 0000000000..471f17d89f
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_iframe_remove_on_afterprint.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<meta charset="utf-8">
+<script>
+function closePrint() {
+ document.documentElement.removeChild(this.__container__);
+}
+
+function setPrint() {
+ this.contentWindow.__container__ = this;
+ this.contentWindow.onbeforeunload = closePrint;
+ this.contentWindow.onafterprint = closePrint;
+ this.contentWindow.print();
+}
+
+function printPage(content) {
+ var frame = document.createElement("iframe");
+ frame.onload = setPrint;
+ frame.style.position = "fixed";
+ frame.style.right = "0";
+ frame.style.bottom = "0";
+ frame.style.width = "0";
+ frame.style.height = "0";
+ frame.style.border = "0";
+ frame.srcdoc = content;
+ document.documentElement.appendChild(frame);
+}
+
+onload = function() {
+ printPage("Something");
+}
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_oop_iframe.html b/toolkit/components/printing/tests/file_window_print_oop_iframe.html
new file mode 100644
index 0000000000..7f028381c3
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_oop_iframe.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<iframe src="https://example.com/document-builder.sjs?html=PASS"></iframe>
+<script>
+ onload = function() {
+ window.print();
+ };
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_reentrant.html b/toolkit/components/printing/tests/file_window_print_reentrant.html
new file mode 100644
index 0000000000..7eebd8c2fe
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_reentrant.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<div id="before-print-count"></div>
+<script>
+let count = 0;
+onload = function() {
+ window.addEventListener('beforeprint', (event) => {
+ document.getElementById("before-print-count").innerText = ++count;
+ print()
+ });
+ print()
+};
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_sandboxed_iframe.html b/toolkit/components/printing/tests/file_window_print_sandboxed_iframe.html
new file mode 100644
index 0000000000..8eb60c18b6
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_sandboxed_iframe.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset="utf-8">
+<iframe sandbox="allow-same-origin allow-scripts allow-modals" src="about:blank" width="0" height="0"></iframe>
+<script>
+ onload = function() {
+ document.querySelector("iframe").contentWindow.print();
+ };
+</script>
diff --git a/toolkit/components/printing/tests/file_window_print_srcdoc_base_uri.html b/toolkit/components/printing/tests/file_window_print_srcdoc_base_uri.html
new file mode 100644
index 0000000000..162f273206
--- /dev/null
+++ b/toolkit/components/printing/tests/file_window_print_srcdoc_base_uri.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<iframe srcdoc="Some content"></iframe>
+<script>
+window.onload = function() {
+ document.querySelector("iframe").contentWindow.print();
+};
+</script>
diff --git a/toolkit/components/printing/tests/head.js b/toolkit/components/printing/tests/head.js
new file mode 100644
index 0000000000..72033c93cb
--- /dev/null
+++ b/toolkit/components/printing/tests/head.js
@@ -0,0 +1,576 @@
+const PRINT_DOCUMENT_URI = "chrome://global/content/print.html";
+const DEFAULT_PRINTER_NAME = "Mozilla Save to PDF";
+const { MockFilePicker } = SpecialPowers;
+
+let pickerMocked = false;
+
+class PrintHelper {
+ static async withTestPage(testFn, pagePathname, useHTTPS = false) {
+ let pageUrl = "";
+ if (pagePathname) {
+ pageUrl = useHTTPS
+ ? this.getTestPageUrlHTTPS(pagePathname)
+ : this.getTestPageUrl(pagePathname);
+ } else {
+ pageUrl = useHTTPS
+ ? this.defaultTestPageUrlHTTPS
+ : this.defaultTestPageUrl;
+ }
+ info("withTestPage: " + pageUrl);
+ let isPdf = pageUrl.endsWith(".pdf");
+
+ if (isPdf) {
+ await SpecialPowers.pushPrefEnv({
+ set: [["pdfjs.eventBusDispatchToDOM", true]],
+ });
+ }
+
+ let taskReturn = await BrowserTestUtils.withNewTab(
+ isPdf ? "about:blank" : pageUrl,
+ async function (browser) {
+ if (isPdf) {
+ let loaded = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "documentloaded",
+ false,
+ null,
+ true
+ );
+ BrowserTestUtils.loadURIString(browser, pageUrl);
+ await loaded;
+ }
+ await testFn(new PrintHelper(browser));
+ }
+ );
+
+ await SpecialPowers.popPrefEnv();
+ if (isPdf) {
+ await SpecialPowers.popPrefEnv();
+ }
+
+ // Reset all of the other printing prefs to their default.
+ this.resetPrintPrefs();
+ return taskReturn;
+ }
+
+ static async withTestPageHTTPS(testFn, pagePathname) {
+ return this.withTestPage(testFn, pagePathname, /* useHttps */ true);
+ }
+
+ static resetPrintPrefs() {
+ for (let name of Services.prefs.getChildList("print.")) {
+ Services.prefs.clearUserPref(name);
+ }
+ Services.prefs.clearUserPref("print_printer");
+ Services.prefs.clearUserPref("print.more-settings.open");
+ }
+
+ static getTestPageUrl(pathName) {
+ const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+ );
+ return testPath + pathName;
+ }
+
+ static getTestPageUrlHTTPS(pathName) {
+ const testPath = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+ );
+ return testPath + pathName;
+ }
+
+ static get defaultTestPageUrl() {
+ return this.getTestPageUrl("simplifyArticleSample.html");
+ }
+
+ static get defaultTestPageUrlHTTPS() {
+ return this.getTestPageUrlHTTPS("simplifyArticleSample.html");
+ }
+
+ static createMockPaper(paperProperties = {}) {
+ return Object.assign(
+ {
+ id: "regular",
+ name: "Regular Size",
+ width: 612,
+ height: 792,
+ unwriteableMargin: Promise.resolve(
+ paperProperties.unwriteableMargin || {
+ top: 0.1,
+ bottom: 0.1,
+ left: 0.1,
+ right: 0.1,
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPaperMargin]),
+ }
+ ),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPaper]),
+ },
+ paperProperties
+ );
+ }
+
+ // This is used only for the old print preview. For tests
+ // involving the newer UI, use waitForPreview instead.
+ static waitForOldPrintPreview(expectedBrowser) {
+ const { PrintingParent } = ChromeUtils.importESModule(
+ "resource://gre/actors/PrintingParent.sys.mjs"
+ );
+
+ return new Promise(resolve => {
+ PrintingParent.setTestListener(browser => {
+ if (browser == expectedBrowser) {
+ PrintingParent.setTestListener(null);
+ resolve();
+ }
+ });
+ });
+ }
+
+ constructor(sourceBrowser) {
+ this.sourceBrowser = sourceBrowser;
+ }
+
+ async startPrint(condition = {}) {
+ this.sourceBrowser.ownerGlobal.document
+ .getElementById("cmd_print")
+ .doCommand();
+ return this.waitForDialog(condition);
+ }
+
+ async waitForDialog(condition = {}) {
+ let dialog = await TestUtils.waitForCondition(
+ () => this.dialog,
+ "Wait for dialog"
+ );
+ await dialog._dialogReady;
+
+ if (Object.keys(condition).length === 0) {
+ await this.win._initialized;
+ // Wait a frame so the rendering spinner is hidden.
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ } else if (condition.waitFor == "loadComplete") {
+ await BrowserTestUtils.waitForAttributeRemoval("loading", this.doc.body);
+ }
+ }
+
+ beforeInit(initFn) {
+ // Run a function when the print.html document is created,
+ // but before its init is called from the domcontentloaded handler
+ TestUtils.topicObserved("document-element-inserted", doc => {
+ return (
+ doc.nodePrincipal.isSystemPrincipal &&
+ doc.contentType == "text/html" &&
+ doc.URL.startsWith("chrome://global/content/print.html")
+ );
+ }).then(([doc]) => {
+ doc.addEventListener("DOMContentLoaded", () => {
+ initFn(doc.ownerGlobal);
+ });
+ });
+ }
+
+ async withClosingFn(closeFn) {
+ let { dialog } = this;
+ await closeFn();
+ if (this.dialog) {
+ await TestUtils.waitForCondition(
+ () => !this.dialog,
+ "Wait for dialog to close"
+ );
+ }
+ await dialog._closingPromise;
+ }
+
+ resetSettings() {
+ this.win.PrintEventHandler.settings =
+ this.win.PrintEventHandler.defaultSettings;
+ this.win.PrintEventHandler.saveSettingsToPrefs(
+ this.win.PrintEventHandler.kInitSaveAll
+ );
+ }
+
+ async closeDialog() {
+ this.resetSettings();
+ await this.withClosingFn(() => this.dialog.close());
+ }
+
+ assertDialogClosed() {
+ is(this._dialogs.length, 0, "There are no print dialogs");
+ }
+
+ assertDialogOpen() {
+ is(this._dialogs.length, 1, "There is one print dialog");
+ ok(BrowserTestUtils.is_visible(this.dialog._box), "The dialog is visible");
+ }
+
+ assertDialogHidden() {
+ is(this._dialogs.length, 1, "There is one print dialog");
+ ok(BrowserTestUtils.is_hidden(this.dialog._box), "The dialog is hidden");
+ ok(
+ this.dialog._box.getBoundingClientRect().width > 0,
+ "The dialog should still have boxes"
+ );
+ }
+
+ async assertPrintToFile(file, testFn) {
+ ok(!file.exists(), "File does not exist before printing");
+ await this.withClosingFn(testFn);
+ await TestUtils.waitForCondition(
+ () => file.exists() && file.fileSize > 0,
+ "Wait for target file to get created",
+ 50
+ );
+ ok(file.exists(), "Created target file");
+
+ await TestUtils.waitForCondition(
+ () => file.fileSize > 0,
+ "Wait for the print progress to run",
+ 50
+ );
+
+ ok(file.fileSize > 0, "Target file not empty");
+ }
+
+ setupMockPrint() {
+ if (this.resolveShowSystemDialog) {
+ throw new Error("Print already mocked");
+ }
+
+ // Create some Promises that we can resolve from the test.
+ let showSystemDialogPromise = new Promise(resolve => {
+ this.resolveShowSystemDialog = result => {
+ if (result !== undefined) {
+ resolve(result);
+ } else {
+ resolve(true);
+ }
+ };
+ });
+ let printPromise = new Promise((resolve, reject) => {
+ this.resolvePrint = resolve;
+ this.rejectPrint = reject;
+ });
+
+ // Mock PrintEventHandler with our Promises.
+ this.win.PrintEventHandler._showPrintDialog = (
+ window,
+ haveSelection,
+ settings
+ ) => {
+ this.systemDialogOpenedWithSelection = haveSelection;
+ return showSystemDialogPromise;
+ };
+ this.win.PrintEventHandler._doPrint = (bc, settings) => {
+ this._printedSettings = settings;
+ return printPromise;
+ };
+ }
+
+ addMockPrinter(opts = {}) {
+ if (typeof opts == "string") {
+ opts = { name: opts };
+ }
+ let {
+ name = "Mock Printer",
+ paperList,
+ printerInfoPromise = Promise.resolve(),
+ paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches,
+ paperId,
+ } = opts;
+ let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+ // Use the fallbackPaperList as the default for mock printers
+ if (!paperList) {
+ info("addMockPrinter, using the fallbackPaperList");
+ paperList = Cc["@mozilla.org/gfx/printerlist;1"].createInstance(
+ Ci.nsIPrinterList
+ ).fallbackPaperList;
+ }
+
+ let defaultSettings = PSSVC.createNewPrintSettings();
+ defaultSettings.printerName = name;
+ defaultSettings.toFileName = "";
+ defaultSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatNative;
+ defaultSettings.outputDestination =
+ Ci.nsIPrintSettings.kOutputDestinationPrinter;
+ defaultSettings.paperSizeUnit = paperSizeUnit;
+ if (paperId) {
+ defaultSettings.paperId = paperId;
+ }
+
+ if (
+ defaultSettings.paperId &&
+ Array.from(paperList).find(p => p.id == defaultSettings.paperId)
+ ) {
+ info(
+ `addMockPrinter, using paperId: ${defaultSettings.paperId} from the paperList`
+ );
+ } else if (paperList.length) {
+ defaultSettings.paperId = paperList[0].id;
+ info(
+ `addMockPrinter, corrected default paperId setting value: ${defaultSettings.paperId}`
+ );
+ }
+
+ let printer = {
+ name,
+ supportsColor: Promise.resolve(true),
+ supportsMonochrome: Promise.resolve(true),
+ printerInfo: printerInfoPromise.then(() => ({
+ paperList,
+ defaultSettings,
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPrinterInfo]),
+ })),
+ QueryInterface: ChromeUtils.generateQI([Ci.nsIPrinter]),
+ };
+
+ if (!this._mockPrinters) {
+ this._mockPrinters = [printer];
+ this.beforeInit(win => (win._mockPrinters = this._mockPrinters));
+ } else {
+ this._mockPrinters.push(printer);
+ }
+ return printer;
+ }
+
+ get _tabDialogBox() {
+ return this.sourceBrowser.ownerGlobal.gBrowser.getTabDialogBox(
+ this.sourceBrowser
+ );
+ }
+
+ get _tabDialogBoxManager() {
+ return this._tabDialogBox.getTabDialogManager();
+ }
+
+ get _dialogs() {
+ return this._tabDialogBox.getTabDialogManager()._dialogs;
+ }
+
+ get dialog() {
+ return this._dialogs.find(dlg =>
+ dlg._box.querySelector(".printSettingsBrowser")
+ );
+ }
+
+ get paginationElem() {
+ return this.dialog._box.querySelector(".printPreviewNavigation");
+ }
+
+ get paginationSheetIndicator() {
+ return this.paginationElem.shadowRoot.querySelector("#sheetIndicator");
+ }
+
+ get currentPrintPreviewBrowser() {
+ return this.win.PrintEventHandler.printPreviewEl.lastPreviewBrowser;
+ }
+
+ get _printBrowser() {
+ return this.dialog._frame;
+ }
+
+ get doc() {
+ return this._printBrowser.contentDocument;
+ }
+
+ get win() {
+ return this._printBrowser.contentWindow;
+ }
+
+ get(id) {
+ return this.doc.getElementById(id);
+ }
+
+ get sheetCount() {
+ return this.doc.l10n.getAttributes(this.get("sheet-count")).args.sheetCount;
+ }
+
+ get sourceURI() {
+ return this.win.PrintEventHandler.activeCurrentURI;
+ }
+
+ async waitForReaderModeReady() {
+ if (gBrowser.selectedBrowser.isArticle) {
+ return;
+ }
+ await new Promise(resolve => {
+ let onReaderModeChange = {
+ receiveMessage(message) {
+ if (
+ message.data &&
+ message.data.isArticle !== undefined &&
+ gBrowser.selectedBrowser.isArticle
+ ) {
+ AboutReaderParent.removeMessageListener(
+ "Reader:UpdateReaderButton",
+ onReaderModeChange
+ );
+ resolve();
+ }
+ },
+ };
+ AboutReaderParent.addMessageListener(
+ "Reader:UpdateReaderButton",
+ onReaderModeChange
+ );
+ });
+ }
+
+ async waitForPreview(changeFn) {
+ changeFn();
+ await BrowserTestUtils.waitForEvent(this.doc, "preview-updated");
+ }
+
+ async waitForSettingsEvent(changeFn) {
+ let changed = BrowserTestUtils.waitForEvent(this.doc, "print-settings");
+ await changeFn?.();
+ await BrowserTestUtils.waitForCondition(
+ () => !this.win.PrintEventHandler._delayedSettingsChangeTask.isArmed,
+ "Wait for all delayed tasks to execute"
+ );
+ await changed;
+ }
+
+ click(el, { scroll = true } = {}) {
+ if (scroll) {
+ el.scrollIntoView();
+ }
+ ok(BrowserTestUtils.is_visible(el), "Element must be visible to click");
+ EventUtils.synthesizeMouseAtCenter(el, {}, this.win);
+ }
+
+ text(el, text) {
+ this.click(el);
+ el.value = "";
+ EventUtils.sendString(text, this.win);
+ }
+
+ async openMoreSettings(options) {
+ let details = this.get("more-settings");
+ if (!details.open) {
+ this.click(details.firstElementChild, options);
+ }
+ await this.awaitAnimationFrame();
+ }
+
+ dispatchSettingsChange(settings) {
+ this.doc.dispatchEvent(
+ new CustomEvent("update-print-settings", {
+ detail: settings,
+ })
+ );
+ }
+
+ get settings() {
+ return this.win.PrintEventHandler.settings;
+ }
+
+ get viewSettings() {
+ return this.win.PrintEventHandler.viewSettings;
+ }
+
+ _assertMatches(a, b, msg) {
+ if (Array.isArray(a)) {
+ is(a.length, b.length, msg);
+ for (let i = 0; i < a.length; ++i) {
+ this._assertMatches(a[i], b[i], msg);
+ }
+ return;
+ }
+ is(a, b, msg);
+ }
+
+ assertSettingsMatch(expected) {
+ let { settings } = this;
+ for (let [setting, value] of Object.entries(expected)) {
+ this._assertMatches(settings[setting], value, `${setting} matches`);
+ }
+ }
+
+ assertPrintedWithSettings(expected) {
+ ok(this._printedSettings, "Printed settings have been recorded");
+ for (let [setting, value] of Object.entries(expected)) {
+ this._assertMatches(
+ this._printedSettings[setting],
+ value,
+ `${setting} matches printed setting`
+ );
+ }
+ }
+
+ get _lastPrintPreviewSettings() {
+ return this.win.PrintEventHandler._lastPrintPreviewSettings;
+ }
+
+ assertPreviewedWithSettings(expected) {
+ let settings = this._lastPrintPreviewSettings;
+ ok(settings, "Last preview settings are available");
+ for (let [setting, value] of Object.entries(expected)) {
+ this._assertMatches(
+ settings[setting],
+ value,
+ `${setting} matches previewed setting`
+ );
+ }
+ }
+
+ async assertSettingsChanged(from, to, changeFn) {
+ is(
+ Object.keys(from).length,
+ Object.keys(to).length,
+ "Got the same number of settings to check"
+ );
+ ok(
+ Object.keys(from).every(s => s in to),
+ "Checking the same setting names"
+ );
+ this.assertSettingsMatch(from);
+ await changeFn();
+ this.assertSettingsMatch(to);
+ }
+
+ async assertSettingsNotChanged(settings, changeFn) {
+ await this.assertSettingsChanged(settings, settings, changeFn);
+ }
+
+ awaitAnimationFrame() {
+ return new Promise(resolve => this.win.requestAnimationFrame(resolve));
+ }
+
+ mockFilePickerCancel() {
+ if (!pickerMocked) {
+ pickerMocked = true;
+ MockFilePicker.init(window);
+ registerCleanupFunction(() => MockFilePicker.cleanup());
+ }
+ MockFilePicker.returnValue = MockFilePicker.returnCancel;
+ }
+
+ mockFilePicker(filename) {
+ if (!pickerMocked) {
+ pickerMocked = true;
+ MockFilePicker.init(window);
+ registerCleanupFunction(() => MockFilePicker.cleanup());
+ }
+ MockFilePicker.returnValue = MockFilePicker.returnOK;
+ let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ file.append(filename);
+ registerCleanupFunction(() => {
+ if (file.exists()) {
+ file.remove(false);
+ }
+ });
+ MockFilePicker.setFiles([file]);
+ return file;
+ }
+}
+
+function waitForPreviewVisible() {
+ return BrowserTestUtils.waitForCondition(function () {
+ let preview = document.querySelector(".printPreviewBrowser");
+ return preview && BrowserTestUtils.is_visible(preview);
+ });
+}
diff --git a/toolkit/components/printing/tests/longerArticle.html b/toolkit/components/printing/tests/longerArticle.html
new file mode 100644
index 0000000000..4109e55959
--- /dev/null
+++ b/toolkit/components/printing/tests/longerArticle.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ @media print {
+ #page-2 {
+ page-break-before: always;
+ }
+ #page-3 {
+ page-break-before: always;
+ }
+ }
+ </style>
+ </head>
+ <body>
+ <h1 id="page-1">Page 1</h1>
+ <h1 id="page-2">Page 2</h1>
+ <h1 id="page-3">Page 3</h1>
+ </body>
+</html>
diff --git a/toolkit/components/printing/tests/page_size.html b/toolkit/components/printing/tests/page_size.html
new file mode 100644
index 0000000000..d55d9e2cff
--- /dev/null
+++ b/toolkit/components/printing/tests/page_size.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>At-Page Size</title>
+<style>
+ body {
+ margin: 0;
+ }
+ .rect {
+ /* An outlined rect that is 1inch smaller in each dimension,
+ to help visualize if the output page is the right size or not: */
+ width: calc(4in - 1in - 4px);
+ height: calc(12in - 1in - 4px);
+ border: 2px solid black;
+ }
+ @page {
+ size: 4in 12in;
+ margin: 0.5in;
+ }
+</style>
+</head>
+<body>
+ <div class="rect">
+ <h3>Expectation:</h3>
+ If you print-preview using the "Save to PDF" page target, you should see an
+ 4in-by-12in-size sheet of paper by default, just 1 inch wider (0.5in margins) than the
+ black-outlined box.
+ </div>
+</body>
+</html>
diff --git a/toolkit/components/printing/tests/page_size_a5.html b/toolkit/components/printing/tests/page_size_a5.html
new file mode 100644
index 0000000000..0f3326d691
--- /dev/null
+++ b/toolkit/components/printing/tests/page_size_a5.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>At-Page Size: A5</title>
+<style>
+ body {
+ margin: 0;
+ }
+ .rect {
+ /* An outlined rect that is 1inch smaller in each dimension,
+ to help visualize if the output page is the right size or not: */
+ width: calc(148mm - 1in - 4px);
+ height: calc(210mm - 1in - 4px);
+ border: 2px solid black;
+ }
+ @page {
+ size: A5; /* 5-7/8 x 8-1/4 in or 148 x 210 mm */
+ margin: 0.5in;
+ }
+</style>
+</head>
+<body>
+ <div class="rect">
+ <h3>Expectation:</h3>
+ If you print-preview using the "Save to PDF" page target, you should see an
+ A5-size sheet of paper by default, just 1 inch wider (0.5in margins) than the black-outlined
+ box.
+ </div>
+</body>
+</html>
diff --git a/toolkit/components/printing/tests/page_size_a5_landscape.html b/toolkit/components/printing/tests/page_size_a5_landscape.html
new file mode 100644
index 0000000000..bfbb05c69d
--- /dev/null
+++ b/toolkit/components/printing/tests/page_size_a5_landscape.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>At-Page Size: A5 Landscape</title>
+<style>
+ body {
+ margin: 0;
+ }
+ .rect {
+ /* An outlined rect that is 1inch smaller in each dimension,
+ to help visualize if the output page is the right size or not: */
+ width: calc(210mm - 1in - 4px);
+ height: calc(148mm - 1in - 4px);
+ border: 2px solid black;
+ }
+ @page {
+ size: A5 landscape; /* 8-1/4 x 5-7/8 in or 210 x 148 mm */
+ margin: 0.5in;
+ }
+</style>
+</head>
+<body>
+ <div class="rect">
+ <h3>Expectation:</h3>
+ If you print-preview using the "Save to PDF" page target, you should see an
+ A5-size sheet of paper in landscape, just 1 inch wider (0.5in margins) than the black-outlined
+ box.
+ </div>
+</body>
+</html>
diff --git a/toolkit/components/printing/tests/page_size_zero.html b/toolkit/components/printing/tests/page_size_zero.html
new file mode 100644
index 0000000000..054f9ae0e3
--- /dev/null
+++ b/toolkit/components/printing/tests/page_size_zero.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>At-Page Size: Zero</title>
+<style>
+ @page {
+ size: 0;
+ }
+</style>
+<h3>Expectation:</h3>
+A size of 0 should not be applied.
diff --git a/toolkit/components/printing/tests/page_size_zero_width.html b/toolkit/components/printing/tests/page_size_zero_width.html
new file mode 100644
index 0000000000..404edfbf06
--- /dev/null
+++ b/toolkit/components/printing/tests/page_size_zero_width.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>At-Page Size: Zero Width</title>
+<style>
+ @page {
+ size: 0 4in;
+ }
+</style>
+<h3>Expectation:</h3>
+A size including 0 should not be applied, and the orientation picker should be visible.
diff --git a/toolkit/components/printing/tests/simplifyArticleSample.html b/toolkit/components/printing/tests/simplifyArticleSample.html
new file mode 100644
index 0000000000..feff9626ab
--- /dev/null
+++ b/toolkit/components/printing/tests/simplifyArticleSample.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+</body>
+</html>
diff --git a/toolkit/components/printing/tests/simplifyNonArticleSample.html b/toolkit/components/printing/tests/simplifyNonArticleSample.html
new file mode 100644
index 0000000000..e216af3c1f
--- /dev/null
+++ b/toolkit/components/printing/tests/simplifyNonArticleSample.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Non article title</title>
+<meta name="description" content="This is the non-article description." />
+</head>
+<body>
+</body>
+</html>