1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 ChromeUtils.defineESModuleGetters(lazy, {
8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs",
9 Log: "chrome://remote/content/shared/Log.sys.mjs",
12 ChromeUtils.defineLazyGetter(lazy, "logger", () => lazy.Log.get());
14 export const print = {
20 // The size of the page in centimeters.
31 orientationValue: ["landscape", "portrait"],
34 print.addDefaultSettings = function (settings) {
38 orientation = "portrait",
45 lazy.assert.object(page, `Expected "page" to be a object, got ${page}`);
46 lazy.assert.object(margin, `Expected "margin" to be a object, got ${margin}`);
48 if (!("width" in page)) {
49 page.width = print.defaults.page.width;
52 if (!("height" in page)) {
53 page.height = print.defaults.page.height;
56 if (!("top" in margin)) {
57 margin.top = print.defaults.margin.top;
60 if (!("bottom" in margin)) {
61 margin.bottom = print.defaults.margin.bottom;
64 if (!("right" in margin)) {
65 margin.right = print.defaults.margin.right;
68 if (!("left" in margin)) {
69 margin.left = print.defaults.margin.left;
83 print.getPrintSettings = function (settings) {
84 const psService = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
85 Ci.nsIPrintSettingsService
88 let cmToInches = cm => cm / 2.54;
89 const printSettings = psService.createNewPrintSettings();
90 printSettings.isInitializedFromPrinter = true;
91 printSettings.isInitializedFromPrefs = true;
92 printSettings.outputFormat = Ci.nsIPrintSettings.kOutputFormatPDF;
93 printSettings.printerName = "marionette";
94 printSettings.printSilent = true;
96 // Setting the paperSizeUnit to kPaperSizeMillimeters doesn't work on mac
97 printSettings.paperSizeUnit = Ci.nsIPrintSettings.kPaperSizeInches;
98 printSettings.paperWidth = cmToInches(settings.page.width);
99 printSettings.paperHeight = cmToInches(settings.page.height);
100 printSettings.usePageRuleSizeAsPaperSize = true;
102 printSettings.marginBottom = cmToInches(settings.margin.bottom);
103 printSettings.marginLeft = cmToInches(settings.margin.left);
104 printSettings.marginRight = cmToInches(settings.margin.right);
105 printSettings.marginTop = cmToInches(settings.margin.top);
107 printSettings.printBGColors = settings.background;
108 printSettings.printBGImages = settings.background;
109 printSettings.scaling = settings.scale;
110 printSettings.shrinkToFit = settings.shrinkToFit;
112 printSettings.headerStrCenter = "";
113 printSettings.headerStrLeft = "";
114 printSettings.headerStrRight = "";
115 printSettings.footerStrCenter = "";
116 printSettings.footerStrLeft = "";
117 printSettings.footerStrRight = "";
119 // Override any os-specific unwriteable margins
120 printSettings.unwriteableMarginTop = 0;
121 printSettings.unwriteableMarginLeft = 0;
122 printSettings.unwriteableMarginBottom = 0;
123 printSettings.unwriteableMarginRight = 0;
125 if (settings.orientation === "landscape") {
126 printSettings.orientation = Ci.nsIPrintSettings.kLandscapeOrientation;
129 if (settings.pageRanges?.length) {
130 printSettings.pageRanges = parseRanges(settings.pageRanges);
133 return printSettings;
137 * Convert array of strings of the form ["1-3", "2-4", "7", "9-"] to an flat array of
138 * limits, like [1, 4, 7, 7, 9, 2**31 - 1] (meaning 1-4, 7, 9-end)
140 * @param {Array.<string|number>} ranges
141 * Page ranges to print, e.g., ['1-5', '8', '11-13'].
142 * Defaults to the empty string, which means print all pages.
144 * @returns {Array.<number>}
145 * Even-length array containing page range limits
147 function parseRanges(ranges) {
148 const MAX_PAGES = 0x7fffffff;
150 if (ranges.length === 0) {
156 for (let range of ranges) {
158 if (typeof range !== "string") {
159 // We got a single integer so the limits are just that page
160 lazy.assert.positiveInteger(range);
161 limits = [range, range];
163 // We got a string presumably of the form <int> | <int>? "-" <int>?
164 const msg = `Expected a range of the form <int> or <int>-<int>, got ${range}`;
166 limits = range.split("-").map(x => x.trim());
167 lazy.assert.that(o => [1, 2].includes(o.length), msg)(limits);
169 // Single numbers map to a range with that page at the start and the end
170 if (limits.length == 1) {
171 limits.push(limits[0]);
174 // Need to check that both limits are strings conisting only of
175 // decimal digits (or empty strings)
176 const assertNumeric = lazy.assert.that(o => /^\d*$/.test(o), msg);
177 limits.every(x => assertNumeric(x));
179 // Convert from strings representing numbers to actual numbers
180 // If we don't have an upper bound, choose something very large;
181 // the print code will later truncate this to the number of pages
182 limits = limits.map((limitStr, i) => {
183 if (limitStr == "") {
184 return i == 0 ? 1 : MAX_PAGES;
186 return parseInt(limitStr);
191 "Lower limit ${parts[0]} is higher than upper limit ${parts[1]}"
194 allLimits.push(limits);
196 // Order by lower limit
197 allLimits.sort((a, b) => a[0] - b[0]);
198 let parsedRanges = [allLimits.shift()];
199 for (let limits of allLimits) {
200 let prev = parsedRanges[parsedRanges.length - 1];
201 let prevMax = prev[1];
202 let [min, max] = limits;
203 if (min <= prevMax) {
204 // min is inside previous range, so extend the max if needed
209 // Otherwise we have a new range
210 parsedRanges.push(limits);
214 let rv = parsedRanges.flat();
215 lazy.logger.debug(`Got page ranges [${rv.join(", ")}]`);
219 print.printToBinaryString = async function (browsingContext, printSettings) {
220 // Create a stream to write to.
221 const stream = Cc["@mozilla.org/storagestream;1"].createInstance(
224 stream.init(4096, 0xffffffff);
226 printSettings.outputDestination =
227 Ci.nsIPrintSettings.kOutputDestinationStream;
228 printSettings.outputStream = stream.getOutputStream(0);
230 await browsingContext.print(printSettings);
232 const inputStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
233 Ci.nsIBinaryInputStream
236 inputStream.setInputStream(stream.newInputStream(0));
238 const available = inputStream.available();
239 const bytes = inputStream.readBytes(available);