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/. */
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
23 get shouldSavePrintSettings() {
24 return Services.prefs.getBoolPref("print.use_global_printsettings") &&
25 Services.prefs.getBoolPref("print.save_print_settings");
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,
41 case "printPreviewUpdate": {
42 let info = this.printPreviewInitializingInfo;
44 // If there is no printPreviewInitializingInfo then we did not
45 // initiate the preview so ignore this event.
49 // Only send Printing:Preview:Entered message on first update, indicated
50 // by printPreviewInitializingInfo.entered not being set.
53 this.mm.sendAsyncMessage("Printing:Preview:Entered", {
55 changingBrowsers: info.changingBrowsers,
58 // If we have another request waiting, dispatch it now.
59 if (info.nextRequest) {
60 Services.tm.dispatchToMainThread(info.nextRequest);
64 // Always send page count update.
65 this.updatePageCount(this.mm);
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),
77 data.changingBrowsers,
78 data.defaultPrinterName);
82 case "Printing:Preview:Exit": {
83 this.exitPrintPreview();
87 case "Printing:Preview:Navigate": {
88 this.navigate(data.navType, data.pageNum);
92 case "Printing:Preview:ParseDocument": {
93 this.parseDocument(data.URL, Services.wm.getOuterWindowWithId(data.windowID));
97 case "Printing:Print": {
98 this.print(Services.wm.getOuterWindowWithId(data.windowID),
100 data.defaultPrinterName);
106 getPrintSettings(defaultPrinterName) {
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;
115 // First get any defaults from the printer
116 PSSVC.initPrintSettingsFromPrinter(printSettings.printerName,
118 // now augment them with any values from last time
119 PSSVC.initPrintSettingsFromPrefs(printSettings, true,
120 printSettings.kInitSaveAll);
122 return printSettings;
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.
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");
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");
158 mm.sendAsyncMessage("Printing:Preview:ReaderModeReady");
163 QueryInterface: ChromeUtils.generateQI([
164 Ci.nsIWebProgressListener,
165 Ci.nsISupportsWeakReference,
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.
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";
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";
272 enterPrintPreview(contentWindow, simplifiedMode, changingBrowsers, defaultPrinterName) {
273 const {docShell} = this;
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 = () => {
288 let listener = new PrintingListener(this.mm);
290 this.printPreviewInitializingInfo = { changingBrowsers };
291 docShell.printPreview.printPreview(printSettings, contentWindow, listener);
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 });
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;
309 Services.tm.dispatchToMainThread(printPreviewInitialize);
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 });
319 exitPrintPreview(glo) {
320 this.printPreviewInitializingInfo = null;
321 this.docShell.printPreview.exitPrintPreview();
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;
335 let print = contentWindow.getInterface(Ci.nsIWebBrowserPrint);
337 if (print.doingPrintPreview) {
338 this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PREVIEW");
340 this.logKeyedTelemetry("PRINT_DIALOG_OPENED_COUNT", "FROM_PAGE");
343 print.print(printSettings, null);
345 if (print.doingPrintPreview) {
346 if (simplifiedMode) {
347 this.logKeyedTelemetry("PRINT_COUNT", "SIMPLIFIED");
349 this.logKeyedTelemetry("PRINT_COUNT", "WITH_PREVIEW");
352 this.logKeyedTelemetry("PRINT_COUNT", "WITHOUT_PREVIEW");
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
360 this.mm.sendAsyncMessage("Printing:Error", {
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);
378 logKeyedTelemetry(id, key) {
379 let histogram = Services.telemetry.getKeyedHistogramById(id);
384 let numPages = this.docShell.printPreview.printPreviewNumPages;
385 this.mm.sendAsyncMessage("Printing:Preview:UpdatePageCount", {
390 navigate(navType, pageNum) {
391 this.docShell.printPreview.printPreviewNavigate(navType, pageNum);
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,
411 onProgressChange(aWebProgress, aRequest, aCurSelfProgress,
412 aMaxSelfProgress, aCurTotalProgress,
414 this.global.sendAsyncMessage("Printing:Preview:ProgressChange", {
415 curSelfProgress: aCurSelfProgress,
416 maxSelfProgress: aMaxSelfProgress,
417 curTotalProgress: aCurTotalProgress,
418 maxTotalProgress: aMaxTotalProgress,
422 onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {},
423 onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {},
424 onSecurityChange(aWebProgress, aRequest, aState) {},
425 onContentBlockingEvent(aWebProgress, aRequest, aEvent) {},