summaryrefslogtreecommitdiffstats
path: root/toolkit/components/printing/content/printUtils.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/printing/content/printUtils.js')
-rw-r--r--toolkit/components/printing/content/printUtils.js1073
1 files changed, 1073 insertions, 0 deletions
diff --git a/toolkit/components/printing/content/printUtils.js b/toolkit/components/printing/content/printUtils.js
new file mode 100644
index 0000000000..2ecea1c8e0
--- /dev/null
+++ b/toolkit/components/printing/content/printUtils.js
@@ -0,0 +1,1073 @@
+// This file is loaded into the browser window scope.
+/* eslint-env mozilla/browser-window */
+
+// -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
+
+/* 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/. */
+
+/**
+ * PrintUtils is a utility for front-end code to trigger common print
+ * operations (printing, show print preview, show page settings).
+ *
+ * Unfortunately, likely due to inconsistencies in how different operating
+ * systems do printing natively, our XPCOM-level printing interfaces
+ * are a bit confusing and the method by which we do something basic
+ * like printing a page is quite circuitous.
+ *
+ * To compound that, we need to support remote browsers, and that means
+ * kicking off the print jobs in the content process. This means we send
+ * messages back and forth to that process via the Printing actor.
+ *
+ * This also means that <xul:browser>'s that hope to use PrintUtils must have
+ * their type attribute set to "content".
+ *
+ * Messages sent:
+ *
+ * Printing:Preview:Enter
+ * This message is sent to put content into print preview mode. We pass
+ * the content window of the browser we're showing the preview of, and
+ * the target of the message is the browser that we'll be showing the
+ * preview in.
+ *
+ * Printing:Preview:Exit
+ * This message is sent to take content out of print preview mode.
+ */
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "PRINT_TAB_MODAL",
+ "print.tab_modal.enabled",
+ false
+);
+
+XPCOMUtils.defineLazyPreferenceGetter(
+ this,
+ "PRINT_ALWAYS_SILENT",
+ "print.always_print_silent",
+ false
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PromptUtils",
+ "resource://gre/modules/SharedPromptUtils.jsm"
+);
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PrintingParent",
+ "resource://gre/actors/PrintingParent.jsm"
+);
+
+var gFocusedElement = null;
+
+var gPendingPrintPreviews = new Map();
+
+var PrintUtils = {
+ SAVE_TO_PDF_PRINTER: "Mozilla Save to PDF",
+
+ get _bundle() {
+ delete this._bundle;
+ return (this._bundle = Services.strings.createBundle(
+ "chrome://global/locale/printing.properties"
+ ));
+ },
+
+ /**
+ * Shows the page setup dialog, and saves any settings changed in
+ * that dialog if print.save_print_settings is set to true.
+ *
+ * @return true on success, false on failure
+ */
+ showPageSetup() {
+ let printSettings = this.getPrintSettings();
+ // If we come directly from the Page Setup menu, the hack in
+ // _enterPrintPreview will not have been invoked to set the last used
+ // printer name. For the reasons outlined at that hack, we want that set
+ // here too.
+ let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+ if (!PSSVC.lastUsedPrinterName) {
+ if (printSettings.printerName) {
+ PSSVC.savePrintSettingsToPrefs(
+ printSettings,
+ false,
+ Ci.nsIPrintSettings.kInitSavePrinterName
+ );
+ PSSVC.savePrintSettingsToPrefs(
+ printSettings,
+ true,
+ Ci.nsIPrintSettings.kInitSaveAll
+ );
+ }
+ }
+ try {
+ var PRINTPROMPTSVC = Cc[
+ "@mozilla.org/embedcomp/printingprompt-service;1"
+ ].getService(Ci.nsIPrintingPromptService);
+ PRINTPROMPTSVC.showPageSetupDialog(window, printSettings, null);
+ } catch (e) {
+ dump("showPageSetup " + e + "\n");
+ return false;
+ }
+ return true;
+ },
+
+ getPreviewBrowser(sourceBrowser) {
+ let dialogBox = gBrowser.getTabDialogBox(sourceBrowser);
+ for (let dialog of dialogBox.getTabDialogManager()._dialogs) {
+ let browser = dialog._box.querySelector(".printPreviewBrowser");
+ if (browser) {
+ return browser;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Updates the hidden state of the "Print preview" and "Page Setup"
+ * menu items in the file menu depending on the print tab modal pref.
+ * The print preview menu item is not available on mac.
+ */
+ updatePrintPreviewMenuHiddenState() {
+ let printPreviewMenuItem = document.getElementById("menu_printPreview");
+ if (printPreviewMenuItem) {
+ printPreviewMenuItem.hidden = PRINT_TAB_MODAL;
+ }
+ let pageSetupMenuItem = document.getElementById("menu_printSetup");
+ if (pageSetupMenuItem) {
+ pageSetupMenuItem.hidden = PRINT_TAB_MODAL;
+ }
+ },
+
+ createPreviewBrowsers(aBrowsingContext, aDialogBrowser) {
+ let _createPreviewBrowser = previewType => {
+ // When we're not previewing the selection we want to make
+ // sure that the top-level browser is being printed.
+ let browsingContext =
+ previewType == "selection"
+ ? aBrowsingContext
+ : aBrowsingContext.top.embedderElement.browsingContext;
+ let browser = gBrowser.createBrowser({
+ remoteType: browsingContext.currentRemoteType,
+ userContextId: browsingContext.originAttributes.userContextId,
+ initialBrowsingContextGroupId: browsingContext.group.id,
+ skipLoad: true,
+ initiallyActive: true,
+ });
+ browser.addEventListener("DOMWindowClose", function(e) {
+ // Ignore close events from printing, see the code creating browsers in
+ // printUtils.js and nsDocumentViewer::OnDonePrinting.
+ //
+ // When we print with the new print UI we don't bother creating a new
+ // <browser> element, so the close event gets dispatched to us.
+ //
+ // Ignoring it is harmless (and doesn't cause correctness issues, because
+ // the preview document can't run script anyways).
+ e.preventDefault();
+ e.stopPropagation();
+ });
+ browser.addEventListener("contextmenu", function(e) {
+ e.preventDefault();
+ });
+ browser.classList.add("printPreviewBrowser");
+ browser.setAttribute("flex", "1");
+ browser.setAttribute("printpreview", "true");
+ browser.setAttribute("previewtype", previewType);
+ document.l10n.setAttributes(browser, "printui-preview-label");
+ return browser;
+ };
+
+ let previewStack = document.importNode(
+ document.getElementById("printPreviewStackTemplate").content,
+ true
+ ).firstElementChild;
+
+ let previewBrowser = _createPreviewBrowser("primary");
+ previewStack.append(previewBrowser);
+ let selectionPreviewBrowser;
+ if (aBrowsingContext.currentRemoteType) {
+ selectionPreviewBrowser = _createPreviewBrowser("selection");
+ previewStack.append(selectionPreviewBrowser);
+ }
+
+ // show the toolbar after we go into print preview mode so
+ // that we can initialize the toolbar with total num pages
+ let previewPagination = document.createElement("printpreview-pagination");
+ previewPagination.classList.add("printPreviewNavigation");
+ previewStack.append(previewPagination);
+
+ aDialogBrowser.parentElement.prepend(previewStack);
+ return { previewBrowser, selectionPreviewBrowser };
+ },
+
+ /**
+ * Opens the tab modal version of the print UI for the current tab.
+ *
+ * @param aBrowsingContext
+ * The BrowsingContext of the window to print.
+ * @param aExistingPreviewBrowser
+ * An existing browser created for printing from window.print().
+ * @param aPrintInitiationTime
+ * The time the print was initiated (typically by the user) as obtained
+ * from `Date.now()`. That is, the initiation time as the number of
+ * milliseconds since January 1, 1970.
+ * @param aPrintSelectionOnly
+ * Whether to print only the active selection of the given browsing
+ * context.
+ * @param aPrintFrameOnly
+ * Whether to print the selected frame only
+ * @return promise resolving when the dialog is open, rejected if the preview
+ * fails.
+ */
+ async _openTabModalPrint(
+ aBrowsingContext,
+ aExistingPreviewBrowser,
+ aPrintInitiationTime,
+ aPrintSelectionOnly,
+ aPrintFrameOnly
+ ) {
+ let hasSelection = aPrintSelectionOnly;
+ if (!aPrintSelectionOnly) {
+ let sourceActor = aBrowsingContext.currentWindowGlobal.getActor(
+ "PrintingSelection"
+ );
+ hasSelection = await sourceActor.sendQuery(
+ "PrintingSelection:HasSelection"
+ );
+ }
+
+ let sourceBrowser = aBrowsingContext.top.embedderElement;
+ let previewBrowser = this.getPreviewBrowser(sourceBrowser);
+ if (previewBrowser) {
+ // Don't open another dialog if we're already printing.
+ //
+ // XXX This can be racy can't it? getPreviewBrowser looks at browser that
+ // we set up after opening the dialog. But I guess worst case we just
+ // open two dialogs so...
+ if (aExistingPreviewBrowser) {
+ aExistingPreviewBrowser.remove();
+ }
+ return Promise.reject();
+ }
+
+ // Create a preview browser.
+ let args = PromptUtils.objectToPropBag({
+ previewBrowser: aExistingPreviewBrowser,
+ printSelectionOnly: !!aPrintSelectionOnly,
+ hasSelection,
+ printFrameOnly: !!aPrintFrameOnly,
+ });
+ let dialogBox = gBrowser.getTabDialogBox(sourceBrowser);
+ return dialogBox.open(
+ `chrome://global/content/print.html?browsingContextId=${aBrowsingContext.id}&printInitiationTime=${aPrintInitiationTime}`,
+ { features: "resizable=no", sizeTo: "available" },
+ args
+ );
+ },
+
+ /**
+ * Initialize a print, this will open the tab modal UI if it is enabled or
+ * defer to the native dialog/silent print.
+ *
+ * @param aTrigger What triggered the print, in string format, for telemetry
+ * purposes.
+ * @param aBrowsingContext
+ * The BrowsingContext of the window to print.
+ * Note that the browsing context could belong to a subframe of the
+ * tab that called window.print, or similar shenanigans.
+ * @param aOptions
+ * {openWindowInfo} Non-null if this call comes from window.print().
+ * This is the nsIOpenWindowInfo object that has to
+ * be passed down to createBrowser in order for the
+ * child process to clone into it.
+ * {printSelectionOnly} Whether to print only the active selection of
+ * the given browsing context.
+ * {printFrameOnly} Whether to print the selected frame.
+ */
+ startPrintWindow(aTrigger, aBrowsingContext, aOptions) {
+ const printInitiationTime = Date.now();
+ let openWindowInfo, printSelectionOnly, printFrameOnly;
+ if (aOptions) {
+ ({ openWindowInfo, printSelectionOnly, printFrameOnly } = aOptions);
+ }
+
+ // When we have a non-null openWindowInfo, we only want to record
+ // telemetry if we're triggered by window.print() itself, otherwise it's an
+ // internal print (like the one we do when we actually print from the
+ // preview dialog, etc.), and that'd cause us to overcount.
+ if (!openWindowInfo || openWindowInfo.isForWindowDotPrint) {
+ Services.telemetry.keyedScalarAdd("printing.trigger", aTrigger, 1);
+ }
+
+ let browser = null;
+ if (openWindowInfo) {
+ browser = document.createXULElement("browser");
+ browser.openWindowInfo = openWindowInfo;
+ browser.setAttribute("type", "content");
+ let remoteType = aBrowsingContext.currentRemoteType;
+ if (remoteType) {
+ browser.setAttribute("remoteType", remoteType);
+ browser.setAttribute("remote", "true");
+ }
+ // When the print process finishes, we get closed by
+ // nsDocumentViewer::OnDonePrinting, or by the print preview code.
+ //
+ // When that happens, we should remove us from the DOM if connected.
+ browser.addEventListener("DOMWindowClose", function(e) {
+ if (browser.isConnected) {
+ browser.remove();
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ });
+ browser.style.visibility = "collapse";
+ document.documentElement.appendChild(browser);
+ }
+
+ if (
+ PRINT_TAB_MODAL &&
+ !PRINT_ALWAYS_SILENT &&
+ (!openWindowInfo || openWindowInfo.isForWindowDotPrint)
+ ) {
+ let browsingContext = aBrowsingContext;
+ let focusedBc = Services.focus.focusedContentBrowsingContext;
+ if (
+ focusedBc &&
+ focusedBc.top.embedderElement == browsingContext.top.embedderElement &&
+ (!openWindowInfo || !openWindowInfo.isForWindowDotPrint) &&
+ !printFrameOnly
+ ) {
+ browsingContext = focusedBc;
+ }
+ this._openTabModalPrint(
+ browsingContext,
+ browser,
+ printInitiationTime,
+ printSelectionOnly,
+ printFrameOnly
+ ).catch(() => {});
+ return browser;
+ }
+
+ if (browser) {
+ // Legacy print dialog or silent printing, the content process will print
+ // in this <browser>.
+ return browser;
+ }
+
+ let settings = this.getPrintSettings();
+ settings.printSelectionOnly = printSelectionOnly;
+ this.printWindow(aBrowsingContext, settings);
+ return null;
+ },
+
+ /**
+ * Starts the process of printing the contents of a window.
+ *
+ * @param aBrowsingContext
+ * The BrowsingContext of the window to print.
+ * @param {Object?} aPrintSettings
+ * Optional print settings for the print operation
+ */
+ printWindow(aBrowsingContext, aPrintSettings) {
+ let windowID = aBrowsingContext.currentWindowGlobal.outerWindowId;
+ let topBrowser = aBrowsingContext.top.embedderElement;
+
+ const printPreviewIsOpen = !!document.getElementById(
+ "print-preview-toolbar"
+ );
+
+ if (printPreviewIsOpen) {
+ this._logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW");
+ } else {
+ this._logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE");
+ }
+
+ // Use the passed in settings if provided, otherwise pull the saved ones.
+ let printSettings = aPrintSettings || this.getPrintSettings();
+
+ // Set the title so that the print dialog can pick it up and
+ // use it to generate the filename for save-to-PDF.
+ printSettings.title = this._originalTitle || topBrowser.contentTitle;
+
+ if (this._shouldSimplify) {
+ // The generated document for simplified print preview has "about:blank"
+ // as its URL. We need to set docURL here so that the print header/footer
+ // can be given the original document's URL.
+ printSettings.docURL = this._originalURL || topBrowser.currentURI.spec;
+ }
+
+ // At some point we should handle the Promise that this returns (report
+ // rejection to telemetry?)
+ let promise = topBrowser.print(windowID, printSettings);
+
+ if (printPreviewIsOpen) {
+ if (this._shouldSimplify) {
+ this._logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED");
+ } else {
+ this._logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW");
+ }
+ } else {
+ this._logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW");
+ }
+
+ return promise;
+ },
+
+ /**
+ * Initializes print preview.
+ *
+ * @param aTrigger Optionaly, if it's an external call, what triggered the
+ * print, in string format, for telemetry purposes.
+ * @param aListenerObj
+ * An object that defines the following functions:
+ *
+ * getPrintPreviewBrowser:
+ * Returns the <xul:browser> to display the print preview in. This
+ * <xul:browser> must have its type attribute set to "content".
+ *
+ * getSimplifiedPrintPreviewBrowser:
+ * Returns the <xul:browser> to display the simplified print preview
+ * in. This <xul:browser> must have its type attribute set to
+ * "content".
+ *
+ * getSourceBrowser:
+ * Returns the <xul:browser> that contains the document being
+ * printed. This <xul:browser> must have its type attribute set to
+ * "content".
+ *
+ * getSimplifiedSourceBrowser:
+ * Returns the <xul:browser> that contains the simplified version
+ * of the document being printed. This <xul:browser> must have its
+ * type attribute set to "content".
+ *
+ * getNavToolbox:
+ * Returns the primary toolbox for this window.
+ *
+ * onEnter:
+ * Called upon entering print preview.
+ *
+ * onExit:
+ * Called upon exiting print preview.
+ *
+ * These methods must be defined. printPreview can be called
+ * with aListenerObj as null iff this window is already displaying
+ * print preview (in which case, the previous aListenerObj passed
+ * to it will be used).
+ *
+ * Due to a timing issue resulting in a main-process crash, we have to
+ * manually open the progress dialog for print preview. The progress
+ * dialog is opened here in PrintUtils, and then we listen for update
+ * messages from the child. Bug 1558588 is about removing this.
+ */
+ printPreview(aTrigger, aListenerObj) {
+ if (aTrigger) {
+ Services.telemetry.keyedScalarAdd("printing.trigger", aTrigger, 1);
+ }
+
+ if (PRINT_TAB_MODAL) {
+ let browsingContext = gBrowser.selectedBrowser.browsingContext;
+ let focusedBc = Services.focus.focusedContentBrowsingContext;
+ if (
+ focusedBc &&
+ focusedBc.top.embedderElement == browsingContext.top.embedderElement
+ ) {
+ browsingContext = focusedBc;
+ }
+ return this._openTabModalPrint(
+ browsingContext,
+ /* aExistingPreviewBrowser = */ undefined,
+ Date.now()
+ );
+ }
+
+ // If we already have a toolbar someone is calling printPreview() to get us
+ // to refresh the display and aListenerObj won't be passed.
+ let printPreviewTB = document.getElementById("print-preview-toolbar");
+ if (!printPreviewTB) {
+ this._listener = aListenerObj;
+ this._sourceBrowser = aListenerObj.getSourceBrowser();
+ this._originalTitle = this._sourceBrowser.contentTitle;
+ this._originalURL = this._sourceBrowser.currentURI.spec;
+
+ // Here we log telemetry data for when the user enters print preview.
+ this.logTelemetry("PRINT_PREVIEW_OPENED_COUNT");
+ } else {
+ // Disable toolbar elements that can cause another update to be triggered
+ // during this update.
+ printPreviewTB.disableUpdateTriggers(true);
+
+ // collapse the browser here -- it will be shown in
+ // _enterPrintPreview; this forces a reflow which fixes display
+ // issues in bug 267422.
+ // We use the print preview browser as the source browser to avoid
+ // re-initializing print preview with a document that might now have changed.
+ this._sourceBrowser = this._shouldSimplify
+ ? this._listener.getSimplifiedPrintPreviewBrowser()
+ : this._listener.getPrintPreviewBrowser();
+ this._sourceBrowser.collapsed = true;
+
+ // If the user transits too quickly within preview and we have a pending
+ // progress dialog, we will close it before opening a new one.
+ this.ensureProgressDialogClosed();
+ }
+
+ this._webProgressPP = {};
+ let ppParams = {};
+ let notifyOnOpen = {};
+ let printSettings = this.getPrintSettings();
+ // Here we get the PrintingPromptService so we can display the PP Progress from script
+ // For the browser implemented via XUL with the PP toolbar we cannot let it be
+ // automatically opened from the print engine because the XUL scrollbars in the PP window
+ // will layout before the content window and a crash will occur.
+ // Doing it all from script, means it lays out before hand and we can let printing do its own thing
+ let PPROMPTSVC = Cc[
+ "@mozilla.org/embedcomp/printingprompt-service;1"
+ ].getService(Ci.nsIPrintingPromptService);
+
+ let promise = new Promise((resolve, reject) => {
+ this._onEntered.push({ resolve, reject });
+ });
+
+ // just in case we are already printing,
+ // an error code could be returned if the Progress Dialog is already displayed
+ try {
+ PPROMPTSVC.showPrintProgressDialog(
+ window,
+ printSettings,
+ this._obsPP,
+ false,
+ this._webProgressPP,
+ ppParams,
+ notifyOnOpen
+ );
+ if (ppParams.value) {
+ ppParams.value.docTitle = this._originalTitle;
+ ppParams.value.docURL = this._originalURL;
+ }
+
+ // this tells us whether we should continue on with PP or
+ // wait for the callback via the observer
+ if (!notifyOnOpen.value.valueOf() || this._webProgressPP.value == null) {
+ this._enterPrintPreview();
+ }
+ } catch (e) {
+ this._enterPrintPreview();
+ }
+ return promise;
+ },
+
+ // "private" methods and members. Don't use them.
+
+ _listener: null,
+ _closeHandlerPP: null,
+ _webProgressPP: null,
+ _sourceBrowser: null,
+ _originalTitle: "",
+ _originalURL: "",
+ _shouldSimplify: false,
+
+ _getErrorCodeForNSResult(nsresult) {
+ const MSG_CODES = [
+ "GFX_PRINTER_NO_PRINTER_AVAILABLE",
+ "GFX_PRINTER_NAME_NOT_FOUND",
+ "GFX_PRINTER_COULD_NOT_OPEN_FILE",
+ "GFX_PRINTER_STARTDOC",
+ "GFX_PRINTER_ENDDOC",
+ "GFX_PRINTER_STARTPAGE",
+ "GFX_PRINTER_DOC_IS_BUSY",
+ "ABORT",
+ "NOT_AVAILABLE",
+ "NOT_IMPLEMENTED",
+ "OUT_OF_MEMORY",
+ "UNEXPECTED",
+ ];
+
+ for (let code of MSG_CODES) {
+ let nsErrorResult = "NS_ERROR_" + code;
+ if (Cr[nsErrorResult] == nsresult) {
+ return code;
+ }
+ }
+
+ // PERR_FAILURE is the catch-all error message if we've gotten one that
+ // we don't recognize.
+ return "FAILURE";
+ },
+
+ _displayPrintingError(nsresult, isPrinting) {
+ // The nsresults from a printing error are mapped to strings that have
+ // similar names to the errors themselves. For example, for error
+ // NS_ERROR_GFX_PRINTER_NO_PRINTER_AVAILABLE, the name of the string
+ // for the error message is: PERR_GFX_PRINTER_NO_PRINTER_AVAILABLE. What's
+ // more, if we're in the process of doing a print preview, it's possible
+ // that there are strings specific for print preview for these errors -
+ // if so, the names of those strings have _PP as a suffix. It's possible
+ // that no print preview specific strings exist, in which case it is fine
+ // to fall back to the original string name.
+ let msgName = "PERR_" + this._getErrorCodeForNSResult(nsresult);
+ let msg, title;
+ if (!isPrinting) {
+ // Try first with _PP suffix.
+ let ppMsgName = msgName + "_PP";
+ try {
+ msg = this._bundle.GetStringFromName(ppMsgName);
+ } catch (e) {
+ // We allow localizers to not have the print preview error string,
+ // and just fall back to the printing error string.
+ }
+ }
+
+ if (!msg) {
+ msg = this._bundle.GetStringFromName(msgName);
+ }
+
+ title = this._bundle.GetStringFromName(
+ isPrinting
+ ? "print_error_dialog_title"
+ : "printpreview_error_dialog_title"
+ );
+
+ Services.prompt.alert(window, title, msg);
+
+ Services.telemetry.keyedScalarAdd(
+ "printing.error",
+ this._getErrorCodeForNSResult(nsresult),
+ 1
+ );
+ },
+
+ _setPrinterDefaultsForSelectedPrinter(
+ aPSSVC,
+ aPrintSettings,
+ defaultsOnly = false
+ ) {
+ if (!aPrintSettings.printerName) {
+ aPrintSettings.printerName = aPSSVC.lastUsedPrinterName;
+ if (!aPrintSettings.printerName) {
+ // It is important to try to avoid passing settings over to the
+ // content process in the old print UI by saving to unprefixed prefs.
+ // To avoid that we try to get the name of a printer we can use.
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].getService(
+ Ci.nsIPrinterList
+ );
+ aPrintSettings.printerName = printerList.systemDefaultPrinterName;
+ }
+ }
+
+ // First get any defaults from the printer. We want to skip this for Save to
+ // PDF since it isn't a real printer and will throw.
+ if (aPrintSettings.printerName != this.SAVE_TO_PDF_PRINTER) {
+ aPSSVC.initPrintSettingsFromPrinter(
+ aPrintSettings.printerName,
+ aPrintSettings
+ );
+ }
+
+ if (!defaultsOnly) {
+ // now augment them with any values from last time
+ aPSSVC.initPrintSettingsFromPrefs(
+ aPrintSettings,
+ true,
+ aPrintSettings.kInitSaveAll
+ );
+ }
+ },
+
+ getPrintSettings(aPrinterName, defaultsOnly) {
+ var printSettings;
+ try {
+ var PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+ printSettings = PSSVC.newPrintSettings;
+ if (aPrinterName) {
+ printSettings.printerName = aPrinterName;
+ }
+ this._setPrinterDefaultsForSelectedPrinter(
+ PSSVC,
+ printSettings,
+ defaultsOnly
+ );
+ } catch (e) {
+ dump("getPrintSettings: " + e + "\n");
+ }
+ return printSettings;
+ },
+
+ // This observer is called once the progress dialog has been "opened"
+ _obsPP: {
+ observe(aSubject, aTopic, aData) {
+ // Only process a null topic which means the progress dialog is open.
+ if (aTopic) {
+ return;
+ }
+
+ // delay the print preview to show the content of the progress dialog
+ setTimeout(function() {
+ PrintUtils._enterPrintPreview();
+ }, 0);
+ },
+
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIObserver",
+ "nsISupportsWeakReference",
+ ]),
+ },
+
+ get shouldSimplify() {
+ return this._shouldSimplify;
+ },
+
+ setSimplifiedMode(shouldSimplify) {
+ this._shouldSimplify = shouldSimplify;
+ },
+
+ _onEntered: [],
+
+ /**
+ * Currently, we create a new print preview browser to host the simplified
+ * cloned-document when Simplify Page option is used on preview. To accomplish
+ * this, we need to keep track of what browser should be presented, based on
+ * whether the 'Simplify page' checkbox is checked.
+ *
+ * _ppBrowsers
+ * Set of print preview browsers.
+ * _currentPPBrowser
+ * References the current print preview browser that is being presented.
+ */
+ _ppBrowsers: new Set(),
+ _currentPPBrowser: null,
+
+ _enterPrintPreview() {
+ // Send a message to the print preview browser to initialize
+ // print preview. If we happen to have gotten a print preview
+ // progress listener from nsIPrintingPromptService.showPrintProgressDialog
+ // in printPreview, we add listeners to feed that progress
+ // listener.
+ let ppBrowser = this._shouldSimplify
+ ? this._listener.getSimplifiedPrintPreviewBrowser()
+ : this._listener.getPrintPreviewBrowser();
+ this._ppBrowsers.add(ppBrowser);
+
+ // If we're switching from 'normal' print preview to 'simplified' print
+ // preview, we will want to run reader mode against the 'normal' print
+ // preview browser's content:
+ let oldPPBrowser = null;
+ let changingPrintPreviewBrowsers = false;
+ if (this._currentPPBrowser && ppBrowser != this._currentPPBrowser) {
+ changingPrintPreviewBrowsers = true;
+ oldPPBrowser = this._currentPPBrowser;
+ }
+ this._currentPPBrowser = ppBrowser;
+
+ let waitForPrintProgressToEnableToolbar = false;
+ if (this._webProgressPP.value) {
+ waitForPrintProgressToEnableToolbar = true;
+ }
+
+ gPendingPrintPreviews.set(ppBrowser, waitForPrintProgressToEnableToolbar);
+
+ // If we happen to have gotten simplify page checked, we will lazily
+ // instantiate a new tab that parses the original page using ReaderMode
+ // primitives. When it's ready, and in order to enter on preview, we send
+ // over a message to print preview browser passing up the simplified tab as
+ // reference. If not, we pass the original tab instead as content source.
+ if (this._shouldSimplify) {
+ let simplifiedBrowser = this._listener.getSimplifiedSourceBrowser();
+ if (!simplifiedBrowser) {
+ simplifiedBrowser = this._listener.createSimplifiedBrowser();
+
+ // Here, we send down a message to simplified browser in order to parse
+ // the original page. After we have parsed it, content will tell parent
+ // that the document is ready for print previewing.
+ simplifiedBrowser.sendMessageToActor(
+ "Printing:Preview:ParseDocument",
+ {
+ URL: this._originalURL,
+ windowID: oldPPBrowser.outerWindowID,
+ },
+ "Printing"
+ );
+
+ // Here we log telemetry data for when the user enters simplify mode.
+ this.logTelemetry("PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT");
+
+ return;
+ }
+ }
+
+ this.sendEnterPrintPreviewToChild(
+ ppBrowser,
+ this._sourceBrowser,
+ this._shouldSimplify,
+ changingPrintPreviewBrowsers
+ );
+ },
+
+ sendEnterPrintPreviewToChild(
+ ppBrowser,
+ sourceBrowser,
+ simplifiedMode,
+ changingBrowsers
+ ) {
+ ppBrowser.sendMessageToActor(
+ "Printing:Preview:Enter",
+ {
+ browsingContextId: sourceBrowser.browsingContext.id,
+ simplifiedMode,
+ changingBrowsers,
+ lastUsedPrinterName: this.getLastUsedPrinterName(),
+ },
+ "Printing"
+ );
+ },
+
+ printPreviewEntered(ppBrowser, previewResult) {
+ let waitForPrintProgressToEnableToolbar = gPendingPrintPreviews.get(
+ ppBrowser
+ );
+ gPendingPrintPreviews.delete(ppBrowser);
+
+ for (let { resolve, reject } of this._onEntered) {
+ if (previewResult.failed) {
+ reject();
+ } else {
+ resolve();
+ }
+ }
+
+ this._onEntered = [];
+ if (previewResult.failed) {
+ // Something went wrong while putting the document into print preview
+ // mode. Bail out.
+ this._ppBrowsers.clear();
+ this._listener.onEnter();
+ this._listener.onExit();
+ return;
+ }
+
+ // Stash the focused element so that we can return to it after exiting
+ // print preview.
+ gFocusedElement = document.commandDispatcher.focusedElement;
+
+ let printPreviewTB = document.getElementById("print-preview-toolbar");
+ if (printPreviewTB) {
+ if (previewResult.changingBrowsers) {
+ printPreviewTB.destroy();
+ printPreviewTB.initialize(ppBrowser);
+ } else {
+ // printPreviewTB.initialize above already calls updateToolbar.
+ printPreviewTB.updateToolbar();
+ }
+
+ // If we don't have a progress listener to enable the toolbar do it now.
+ if (!waitForPrintProgressToEnableToolbar) {
+ printPreviewTB.disableUpdateTriggers(false);
+ }
+
+ ppBrowser.collapsed = false;
+ ppBrowser.focus();
+ return;
+ }
+
+ // Set the original window as an active window so any mozPrintCallbacks can
+ // run without delayed setTimeouts.
+ if (this._listener.activateBrowser) {
+ this._listener.activateBrowser(this._sourceBrowser);
+ } else {
+ this._sourceBrowser.docShellIsActive = true;
+ }
+
+ // show the toolbar after we go into print preview mode so
+ // that we can initialize the toolbar with total num pages
+ printPreviewTB = document.createXULElement("toolbar", {
+ is: "printpreview-toolbar",
+ });
+ printPreviewTB.setAttribute("fullscreentoolbar", true);
+ printPreviewTB.setAttribute("flex", "1");
+ printPreviewTB.id = "print-preview-toolbar";
+
+ let navToolbox = this._listener.getNavToolbox();
+ navToolbox.parentNode.insertBefore(printPreviewTB, navToolbox);
+ printPreviewTB.initialize(ppBrowser);
+
+ // The print preview processing may not have fully completed, so if we
+ // have a progress listener, disable the toolbar elements that can trigger
+ // updates and it will enable them when completed.
+ if (waitForPrintProgressToEnableToolbar) {
+ printPreviewTB.disableUpdateTriggers(true);
+ }
+
+ // Enable simplify page checkbox when the page is an article
+ if (this._sourceBrowser.isArticle) {
+ printPreviewTB.enableSimplifyPage();
+ } else {
+ this.logTelemetry("PRINT_PREVIEW_SIMPLIFY_PAGE_UNAVAILABLE_COUNT");
+ printPreviewTB.disableSimplifyPage();
+ }
+
+ // copy the window close handler
+ if (window.onclose) {
+ this._closeHandlerPP = window.onclose;
+ } else {
+ this._closeHandlerPP = null;
+ }
+ window.onclose = function() {
+ PrintUtils.exitPrintPreview();
+ return false;
+ };
+
+ // disable chrome shortcuts...
+ window.addEventListener("keydown", this.onKeyDownPP, true);
+ window.addEventListener("keypress", this.onKeyPressPP, true);
+
+ ppBrowser.collapsed = false;
+ ppBrowser.focus();
+ // on Enter PP Call back
+ this._listener.onEnter();
+ },
+
+ readerModeReady(sourceBrowser) {
+ let ppBrowser = this._listener.getSimplifiedPrintPreviewBrowser();
+ this.sendEnterPrintPreviewToChild(ppBrowser, sourceBrowser, true, true);
+ },
+
+ getLastUsedPrinterName() {
+ let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+ let lastUsedPrinterName = PSSVC.lastUsedPrinterName;
+ if (!lastUsedPrinterName) {
+ // We "pass" print settings over to the content process by saving them to
+ // prefs (yuck!). It is important to try to avoid saving to prefs without
+ // prefixing them with a printer name though, so this hack tries to make
+ // sure that (in the common case) we have set the "last used" printer,
+ // which makes us save to prefs prefixed with its name, and makes sure
+ // the content process will pick settings up from those prefixed prefs
+ // too.
+ let settings = this.getPrintSettings();
+ if (settings.printerName) {
+ PSSVC.savePrintSettingsToPrefs(
+ settings,
+ false,
+ Ci.nsIPrintSettings.kInitSavePrinterName
+ );
+ PSSVC.savePrintSettingsToPrefs(
+ settings,
+ true,
+ Ci.nsIPrintSettings.kInitSaveAll
+ );
+ lastUsedPrinterName = settings.printerName;
+ }
+ }
+
+ return lastUsedPrinterName;
+ },
+
+ exitPrintPreview() {
+ for (let browser of this._ppBrowsers) {
+ browser.sendMessageToActor("Printing:Preview:Exit", {}, "Printing");
+ }
+ this._ppBrowsers.clear();
+ this._currentPPBrowser = null;
+ window.removeEventListener("keydown", this.onKeyDownPP, true);
+ window.removeEventListener("keypress", this.onKeyPressPP, true);
+
+ // restore the old close handler
+ if (this._closeHandlerPP) {
+ window.onclose = this._closeHandlerPP;
+ } else {
+ window.onclose = null;
+ }
+ this._closeHandlerPP = null;
+
+ // remove the print preview toolbar
+ let printPreviewTB = document.getElementById("print-preview-toolbar");
+ printPreviewTB.destroy();
+ printPreviewTB.remove();
+
+ if (gFocusedElement) {
+ Services.focus.setFocus(gFocusedElement, Services.focus.FLAG_NOSCROLL);
+ } else {
+ this._sourceBrowser.focus();
+ }
+ gFocusedElement = null;
+
+ this.setSimplifiedMode(false);
+
+ this.ensureProgressDialogClosed();
+
+ this._listener.onExit();
+
+ this._originalTitle = "";
+ this._originalURL = "";
+ },
+
+ logTelemetry(ID) {
+ let histogram = Services.telemetry.getHistogramById(ID);
+ histogram.add(true);
+ },
+
+ _logKeyedTelemetry(id, key) {
+ let histogram = Services.telemetry.getKeyedHistogramById(id);
+ histogram.add(key);
+ },
+
+ onKeyDownPP(aEvent) {
+ // Esc exits the PP
+ if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+ PrintUtils.exitPrintPreview();
+ }
+ },
+
+ onKeyPressPP(aEvent) {
+ var closeKey;
+ try {
+ closeKey = document.getElementById("key_close").getAttribute("key");
+ closeKey = aEvent["DOM_VK_" + closeKey];
+ } catch (e) {}
+ var isModif = aEvent.ctrlKey || aEvent.metaKey;
+ // Ctrl-W exits the PP
+ if (
+ isModif &&
+ (aEvent.charCode == closeKey || aEvent.charCode == closeKey + 32)
+ ) {
+ PrintUtils.exitPrintPreview();
+ } else if (isModif) {
+ var printPreviewTB = document.getElementById("print-preview-toolbar");
+ var printKey = document
+ .getElementById("printKb")
+ .getAttribute("key")
+ .toUpperCase();
+ var pressedKey = String.fromCharCode(aEvent.charCode).toUpperCase();
+ if (printKey == pressedKey) {
+ printPreviewTB.print();
+ }
+ }
+ // cancel shortkeys
+ if (isModif) {
+ aEvent.preventDefault();
+ aEvent.stopPropagation();
+ }
+ },
+
+ /**
+ * If there's a printing or print preview progress dialog displayed, force
+ * it to close now.
+ */
+ ensureProgressDialogClosed() {
+ if (this._webProgressPP && this._webProgressPP.value) {
+ this._webProgressPP.value.onStateChange(
+ null,
+ null,
+ Ci.nsIWebProgressListener.STATE_STOP,
+ 0
+ );
+ }
+ },
+};