Bug 1523562 [wpt PR 14799] - [Animation Worklet] Upstream animation worklet inside...
[gecko.git] / toolkit / actors / PrintingChild.jsm
blob24f84ef38c8d876a4716d83807edfc531a6d311e
1 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 "use strict";
7 var EXPORTED_SYMBOLS = ["PrintingChild"];
9 const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
10 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
12 ChromeUtils.defineModuleGetter(this, "ReaderMode",
13   "resource://gre/modules/ReaderMode.jsm");
15 class PrintingChild extends ActorChild {
16   // Bug 1088061: nsPrintJob's DoCommonPrint currently expects the
17   // progress listener passed to it to QI to an nsIPrintingPromptService
18   // in order to know that a printing progress dialog has been shown. That's
19   // really all the interface is used for, hence the fact that I don't actually
20   // implement the interface here. Bug 1088061 has been filed to remove
21   // this hackery.
23   get shouldSavePrintSettings() {
24     return Services.prefs.getBoolPref("print.use_global_printsettings") &&
25            Services.prefs.getBoolPref("print.save_print_settings");
26   }
28   handleEvent(event) {
29     switch (event.type) {
30       case "PrintingError": {
31         let win = event.target.defaultView;
32         let wbp = win.getInterface(Ci.nsIWebBrowserPrint);
33         let nsresult = event.detail;
34         this.mm.sendAsyncMessage("Printing:Error", {
35           isPrinting: wbp.doingPrint,
36           nsresult,
37         });
38         break;
39       }
41       case "printPreviewUpdate": {
42         let info = this.printPreviewInitializingInfo;
43         if (!info) {
44           // If there is no printPreviewInitializingInfo then we did not
45           // initiate the preview so ignore this event.
46           return;
47         }
49         // Only send Printing:Preview:Entered message on first update, indicated
50         // by printPreviewInitializingInfo.entered not being set.
51         if (!info.entered) {
52           info.entered = true;
53           this.mm.sendAsyncMessage("Printing:Preview:Entered", {
54             failed: false,
55             changingBrowsers: info.changingBrowsers,
56           });
58           // If we have another request waiting, dispatch it now.
59           if (info.nextRequest) {
60             Services.tm.dispatchToMainThread(info.nextRequest);
61           }
62         }
64         // Always send page count update.
65         this.updatePageCount(this.mm);
66         break;
67       }
68     }
69   }
71   receiveMessage(message) {
72     let data = message.data;
73     switch (message.name) {
74       case "Printing:Preview:Enter": {
75         this.enterPrintPreview(Services.wm.getOuterWindowWithId(data.windowID),
76                                data.simplifiedMode,
77                                data.changingBrowsers,
78                                data.defaultPrinterName);
79         break;
80       }
82       case "Printing:Preview:Exit": {
83         this.exitPrintPreview();
84         break;
85       }
87       case "Printing:Preview:Navigate": {
88         this.navigate(data.navType, data.pageNum);
89         break;
90       }
92       case "Printing:Preview:ParseDocument": {
93         this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID));
94         break;
95       }
97       case "Printing:Print": {
98         this.print(Services.wm.getOuterWindowWithId(data.windowID),
99                    data.simplifiedMode,
100                    data.defaultPrinterName);
101         break;
102       }
103     }
104   }
106   getPrintSettings(defaultPrinterName) {
107     try {
108       let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
109                     .getService(Ci.nsIPrintSettingsService);
111       let printSettings = PSSVC.globalPrintSettings;
112       if (!printSettings.printerName) {
113         printSettings.printerName = defaultPrinterName;
114       }
115       // First get any defaults from the printer
116       PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
117                                          printSettings);
118       // now augment them with any values from last time
119       PSSVC.initPrintSettingsFromPrefs(printSettings, true,
120                                        printSettings.kInitSaveAll);
122       return printSettings;
123     } catch (e) {
124       Cu.reportError(e);
125     }
127     return null;
128   }
130   parseDocument(URL, contentWindow) {
131     // By using ReaderMode primitives, we parse given document and place the
132     // resulting JS object into the DOM of current browser.
133     let articlePromise = ReaderMode.parseDocument(contentWindow.document).catch(Cu.reportError);
134     articlePromise.then((article) => {
135       // We make use of a web progress listener in order to know when the content we inject
136       // into the DOM has finished rendering. If our layout engine is still painting, we
137       // will wait for MozAfterPaint event to be fired.
138       let {mm} = this;
139       let webProgressListener = {
140         onStateChange(webProgress, req, flags, status) {
141           if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
142             webProgress.removeProgressListener(webProgressListener);
143             let domUtils = contentWindow.windowUtils;
144             // Here we tell the parent that we have parsed the document successfully
145             // using ReaderMode primitives and we are able to enter on preview mode.
146             if (domUtils.isMozAfterPaintPending) {
147               let onPaint = function() {
148                 mm.removeEventListener("MozAfterPaint", onPaint);
149                 mm.sendAsyncMessage("Printing:Preview:ReaderModeReady");
150               };
151               contentWindow.addEventListener("MozAfterPaint", onPaint);
152               // This timer need when display list invalidation doesn't invalidate.
153               mm.setTimeout(() => {
154                 mm.removeEventListener("MozAfterPaint", onPaint);
155                 mm.sendAsyncMessage("Printing:Preview:ReaderModeReady");
156               }, 100);
157             } else {
158               mm.sendAsyncMessage("Printing:Preview:ReaderModeReady");
159             }
160           }
161         },
163         QueryInterface: ChromeUtils.generateQI([
164           Ci.nsIWebProgressListener,
165           Ci.nsISupportsWeakReference,
166           Ci.nsIObserver,
167         ]),
168       };
170       const {content, docShell} = this.mm;
172       // Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
173       let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
174                                 .getInterface(Ci.nsIWebProgress);
175       webProgress.addProgressListener(webProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST);
177       content.document.head.innerHTML = "";
179       // Set base URI of document. Print preview code will read this value to
180       // populate the URL field in print settings so that it doesn't show
181       // "about:blank" as its URI.
182       let headBaseElement = content.document.createElement("base");
183       headBaseElement.setAttribute("href", URL);
184       content.document.head.appendChild(headBaseElement);
186       // Create link element referencing aboutReader.css and append it to head
187       let headStyleElement = content.document.createElement("link");
188       headStyleElement.setAttribute("rel", "stylesheet");
189       headStyleElement.setAttribute("href", "chrome://global/skin/aboutReader.css");
190       headStyleElement.setAttribute("type", "text/css");
191       content.document.head.appendChild(headStyleElement);
193       // Create link element referencing simplifyMode.css and append it to head
194       headStyleElement = content.document.createElement("link");
195       headStyleElement.setAttribute("rel", "stylesheet");
196       headStyleElement.setAttribute("href", "chrome://global/content/simplifyMode.css");
197       headStyleElement.setAttribute("type", "text/css");
198       content.document.head.appendChild(headStyleElement);
200       content.document.body.innerHTML = "";
202       // Create container div (main element) and append it to body
203       let containerElement = content.document.createElement("div");
204       containerElement.setAttribute("id", "container");
205       content.document.body.appendChild(containerElement);
207       // Reader Mode might return null if there's a failure when parsing the document.
208       // We'll render the error message for the Simplify Page document when that happens.
209       if (article) {
210         // Set title of document
211         content.document.title = article.title;
213         // Create header div and append it to container
214         let headerElement = content.document.createElement("div");
215         headerElement.setAttribute("id", "reader-header");
216         headerElement.setAttribute("class", "header");
217         containerElement.appendChild(headerElement);
219         // Jam the article's title and byline into header div
220         let titleElement = content.document.createElement("h1");
221         titleElement.setAttribute("id", "reader-title");
222         titleElement.textContent = article.title;
223         headerElement.appendChild(titleElement);
225         let bylineElement = content.document.createElement("div");
226         bylineElement.setAttribute("id", "reader-credits");
227         bylineElement.setAttribute("class", "credits");
228         bylineElement.textContent = article.byline;
229         headerElement.appendChild(bylineElement);
231         // Display header element
232         headerElement.style.display = "block";
234         // Create content div and append it to container
235         let contentElement = content.document.createElement("div");
236         contentElement.setAttribute("class", "content");
237         containerElement.appendChild(contentElement);
239         // Jam the article's content into content div
240         let readerContent = content.document.createElement("div");
241         readerContent.setAttribute("id", "moz-reader-content");
242         contentElement.appendChild(readerContent);
244         let articleUri = Services.io.newURI(article.url);
245         let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
246         let contentFragment = parserUtils.parseFragment(article.content,
247           Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
248           false, articleUri, readerContent);
250         readerContent.appendChild(contentFragment);
252         // Display reader content element
253         readerContent.style.display = "block";
254       } else {
255         let aboutReaderStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
256         let errorMessage = aboutReaderStrings.GetStringFromName("aboutReader.loadError");
258         content.document.title = errorMessage;
260         // Create reader message div and append it to body
261         let readerMessageElement = content.document.createElement("div");
262         readerMessageElement.setAttribute("class", "reader-message");
263         readerMessageElement.textContent = errorMessage;
264         containerElement.appendChild(readerMessageElement);
266         // Display reader message element
267         readerMessageElement.style.display = "block";
268       }
269     });
270   }
272   enterPrintPreview(contentWindow, simplifiedMode, changingBrowsers, defaultPrinterName) {
273     const {docShell} = this;
274     try {
275       let printSettings = this.getPrintSettings(defaultPrinterName);
277       // If we happen to be on simplified mode, we need to set docURL in order
278       // to generate header/footer content correctly, since simplified tab has
279       // "about:blank" as its URI.
280       if (printSettings && simplifiedMode)
281         printSettings.docURL = contentWindow.document.baseURI;
283       // The print preview docshell will be in a different TabGroup, so
284       // printPreviewInitialize must be run in a separate runnable to avoid
285       // touching a different TabGroup in our own runnable.
286       let printPreviewInitialize = () => {
287         try {
288           let listener = new PrintingListener(this.mm);
290           this.printPreviewInitializingInfo = { changingBrowsers };
291           docShell.printPreview.printPreview(printSettings, contentWindow, listener);
292         } catch (error) {
293           // This might fail if we, for example, attempt to print a XUL document.
294           // In that case, we inform the parent to bail out of print preview.
295           Cu.reportError(error);
296           this.printPreviewInitializingInfo = null;
297           this.mm.sendAsyncMessage("Printing:Preview:Entered", { failed: true });
298         }
299       };
301       // If printPreviewInitializingInfo.entered is not set we are still in the
302       // initial setup of a previous preview request. We delay this one until
303       // that has finished because running them at the same time will almost
304       // certainly cause failures.
305       if (this.printPreviewInitializingInfo &&
306           !this.printPreviewInitializingInfo.entered) {
307         this.printPreviewInitializingInfo.nextRequest = printPreviewInitialize;
308       } else {
309         Services.tm.dispatchToMainThread(printPreviewInitialize);
310       }
311     } catch (error) {
312       // This might fail if we, for example, attempt to print a XUL document.
313       // In that case, we inform the parent to bail out of print preview.
314       Cu.reportError(error);
315       this.mm.sendAsyncMessage("Printing:Preview:Entered", { failed: true });
316     }
317   }
319   exitPrintPreview(glo) {
320     this.printPreviewInitializingInfo = null;
321     this.docShell.printPreview.exitPrintPreview();
322   }
324   print(contentWindow, simplifiedMode, defaultPrinterName) {
325     let printSettings = this.getPrintSettings(defaultPrinterName);
327     // If we happen to be on simplified mode, we need to set docURL in order
328     // to generate header/footer content correctly, since simplified tab has
329     // "about:blank" as its URI.
330     if (printSettings && simplifiedMode) {
331       printSettings.docURL = contentWindow.document.baseURI;
332     }
334     try {
335       let print = contentWindow.getInterface(Ci.nsIWebBrowserPrint);
337       if (print.doingPrintPreview) {
338         this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW");
339       } else {
340         this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE");
341       }
343       print.print(printSettings, null);
345       if (print.doingPrintPreview) {
346         if (simplifiedMode) {
347           this.logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED");
348         } else {
349           this.logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW");
350         }
351       } else {
352         this.logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW");
353       }
354     } catch (e) {
355       // Pressing cancel is expressed as an NS_ERROR_ABORT return value,
356       // causing an exception to be thrown which we catch here.
357       if (e.result != Cr.NS_ERROR_ABORT) {
358         Cu.reportError(`In Printing:Print:Done handler, got unexpected rv
359                         ${e.result}.`);
360         this.mm.sendAsyncMessage("Printing:Error", {
361           isPrinting: true,
362           nsresult: e.result,
363         });
364       }
365     }
367     if (this.shouldSavePrintSettings) {
368       let PSSVC = Cc["@mozilla.org/gfx/printsettings-service;1"]
369                     .getService(Ci.nsIPrintSettingsService);
371       PSSVC.savePrintSettingsToPrefs(printSettings, true,
372                                      printSettings.kInitSaveAll);
373       PSSVC.savePrintSettingsToPrefs(printSettings, false,
374                                      printSettings.kInitSavePrinterName);
375     }
376   }
378   logKeyedTelemetry(id, key) {
379     let histogram = Services.telemetry.getKeyedHistogramById(id);
380     histogram.add(key);
381   }
383   updatePageCount() {
384     let numPages = this.docShell.printPreview.printPreviewNumPages;
385     this.mm.sendAsyncMessage("Printing:Preview:UpdatePageCount", {
386       numPages,
387     });
388   }
390   navigate(navType, pageNum) {
391     this.docShell.printPreview.printPreviewNavigate(navType, pageNum);
392   }
395 PrintingChild.prototype.QueryInterface =
396   ChromeUtils.generateQI([Ci.nsIPrintingPromptService]);
398 function PrintingListener(global) {
399   this.global = global;
401 PrintingListener.prototype = {
402   QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener]),
404   onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
405     this.global.sendAsyncMessage("Printing:Preview:StateChange", {
406       stateFlags: aStateFlags,
407       status: aStatus,
408     });
409   },
411   onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
412                    aMaxSelfProgress, aCurTotalProgress,
413                    aMaxTotalProgress) {
414     this.global.sendAsyncMessage("Printing:Preview:ProgressChange", {
415       curSelfProgress: aCurSelfProgress,
416       maxSelfProgress: aMaxSelfProgress,
417       curTotalProgress: aCurTotalProgress,
418       maxTotalProgress: aMaxTotalProgress,
419     });
420   },
422   onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
423   onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
424   onSecurityChange(aWebProgress, aRequest, aState) {},
425   onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},