summaryrefslogtreecommitdiffstats
path: root/remote/shared/PDF.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'remote/shared/PDF.sys.mjs')
-rw-r--r--remote/shared/PDF.sys.mjs222
1 files changed, 222 insertions, 0 deletions
diff --git a/remote/shared/PDF.sys.mjs b/remote/shared/PDF.sys.mjs
new file mode 100644
index 0000000000..a171fef590
--- /dev/null
+++ b/remote/shared/PDF.sys.mjs
@@ -0,0 +1,222 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ clearInterval: "resource://gre/modules/Timer.sys.mjs",
+ setInterval: "resource://gre/modules/Timer.sys.mjs",
+
+ assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
+ Log: "chrome://remote/content/shared/Log.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
+
+export const print = {
+ maxScaleValue: 2.0,
+ minScaleValue: 0.1,
+ letterPaperSizeCm: {
+ width: 21.59,
+ height: 27.94,
+ },
+};
+
+print.addDefaultSettings = function(settings) {
+ const {
+ landscape = false,
+ margin = {
+ top: 1,
+ bottom: 1,
+ left: 1,
+ right: 1,
+ },
+ page = print.letterPaperSizeCm,
+ shrinkToFit = true,
+ printBackground = false,
+ scale = 1.0,
+ pageRanges = [],
+ } = settings;
+
+ return {
+ landscape,
+ margin,
+ page,
+ shrinkToFit,
+ printBackground,
+ scale,
+ pageRanges,
+ };
+};
+
+function getPrintSettings(settings, filePath) {
+ const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+
+ let cmToInches = cm => cm / 2.54;
+ const printSettings = psService.createNewPrintSettings();
+ printSettings.isInitializedFromPrinter = true;
+ printSettings.isInitializedFromPrefs = true;
+ printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
+ printSettings.printerName = "marionette";
+ printSettings.printSilent = true;
+ printSettings.outputDestination = Ci.nsIPrintSettings.kOutputDestinationFile;
+ printSettings.toFileName = filePath;
+
+ // Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac
+ printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
+ printSettings.paperWidth = cmToInches(settings.page.width);
+ printSettings.paperHeight = cmToInches(settings.page.height);
+
+ printSettings.marginBottom = cmToInches(settings.margin.bottom);
+ printSettings.marginLeft = cmToInches(settings.margin.left);
+ printSettings.marginRight = cmToInches(settings.margin.right);
+ printSettings.marginTop = cmToInches(settings.margin.top);
+
+ printSettings.printBGColors = settings.printBackground;
+ printSettings.printBGImages = settings.printBackground;
+ printSettings.scaling = settings.scale;
+ printSettings.shrinkToFit = settings.shrinkToFit;
+
+ printSettings.headerStrCenter = "";
+ printSettings.headerStrLeft = "";
+ printSettings.headerStrRight = "";
+ printSettings.footerStrCenter = "";
+ printSettings.footerStrLeft = "";
+ printSettings.footerStrRight = "";
+
+ // Override any os-specific unwriteable margins
+ printSettings.unwriteableMarginTop = 0;
+ printSettings.unwriteableMarginLeft = 0;
+ printSettings.unwriteableMarginBottom = 0;
+ printSettings.unwriteableMarginRight = 0;
+
+ if (settings.landscape) {
+ printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
+ }
+
+ if (settings.pageRanges?.length) {
+ printSettings.pageRanges = parseRanges(settings.pageRanges);
+ }
+
+ return printSettings;
+}
+
+/**
+ * Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of
+ * limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end)
+ *
+ * @param {Array.<string|number>} ranges
+ * Page ranges to print, e.g., ['1-5', '8', '11-13'].
+ * Defaults to the empty string, which means print all pages.
+ *
+ * @return {Array.<number>}
+ * Even-length array containing page range limits
+ */
+function parseRanges(ranges) {
+ const MAX_PAGES = 0x7fffffff;
+
+ if (ranges.length === 0) {
+ return [];
+ }
+
+ let allLimits = [];
+
+ for (let range of ranges) {
+ let limits;
+ if (typeof range !== "string") {
+ // We got a single integer so the limits are just that page
+ lazy.assert.positiveInteger(range);
+ limits = [range, range];
+ } else {
+ // We got a string presumably of the form <int> | <int>? "-" <int>?
+ const msg = `Expected a range of the form <int> or <int>-<int>, got ${range}`;
+
+ limits = range.split("-").map(x => x.trim());
+ lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);
+
+ // Single numbers map to a range with that page at the start and the end
+ if (limits.length == 1) {
+ limits.push(limits[0]);
+ }
+
+ // Need to check that both limits are strings conisting only of
+ // decimal digits (or empty strings)
+ const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg);
+ limits.every(x => assertNumeric(x));
+
+ // Convert from strings representing numbers to actual numbers
+ // If we don't have an upper bound, choose something very large;
+ // the print code will later truncate this to the number of pages
+ limits = limits.map((limitStr, i) => {
+ if (limitStr == "") {
+ return i == 0 ? 1 : MAX_PAGES;
+ }
+ return parseInt(limitStr);
+ });
+ }
+ lazy.assert.that(
+ x => x[0] <= x[1],
+ "Lower limit ${parts[0]} is higher than upper limit ${parts[1]}"
+ )(limits);
+
+ allLimits.push(limits);
+ }
+ // Order by lower limit
+ allLimits.sort((a, b) => a[0] - b[0]);
+ let parsedRanges = [allLimits.shift()];
+ for (let limits of allLimits) {
+ let prev = parsedRanges[parsedRanges.length - 1];
+ let prevMax = prev[1];
+ let [min, max] = limits;
+ if (min <= prevMax) {
+ // min is inside previous range, so extend the max if needed
+ if (max > prevMax) {
+ prev[1] = max;
+ }
+ } else {
+ // Otherwise we have a new range
+ parsedRanges.push(limits);
+ }
+ }
+
+ let rv = parsedRanges.flat();
+ lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
+ return rv;
+}
+
+print.printToFile = async function(browser, settings) {
+ // Create a unique filename for the temporary PDF file
+ const filePath = await IOUtils.createUniqueFile(
+ PathUtils.tempDir,
+ "marionette.pdf",
+ 0o600
+ );
+
+ let printSettings = getPrintSettings(settings, filePath);
+
+ await browser.browsingContext.print(printSettings);
+
+ // Bug 1603739 - With e10s enabled the promise returned by print() resolves
+ // too early, which means the file hasn't been completely written.
+ await new Promise(resolve => {
+ const DELAY_CHECK_FILE_COMPLETELY_WRITTEN = 100;
+
+ let lastSize = 0;
+ const timerId = lazy.setInterval(async () => {
+ const fileInfo = await IOUtils.stat(filePath);
+ if (lastSize > 0 && fileInfo.size == lastSize) {
+ lazy.clearInterval(timerId);
+ resolve();
+ }
+ lastSize = fileInfo.size;
+ }, DELAY_CHECK_FILE_COMPLETELY_WRITTEN);
+ });
+
+ lazy.logger.debug(`PDF output written to ${filePath}`);
+ return filePath;
+};