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/. */
8 ChromeUtils.defineESModuleGetters(lazy, {
9 DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
10 ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
11 setTimeout: "resource://gre/modules/Timer.sys.mjs",
14 let gPendingPreviewsMap = new Map();
16 export class PrintingChild extends JSWindowActorChild {
18 // When the print preview page is loaded, the actor will change, so update
19 // the state/progress listener to the new actor.
20 let listener = gPendingPreviewsMap.get(this.browsingContext.id);
22 listener.actor = this;
24 this.contentWindow.addEventListener("scroll", this);
28 this._scrollTask?.disarm();
29 this.contentWindow?.removeEventListener("scroll", this);
34 case "PrintingError": {
35 let win = event.target.defaultView;
36 let wbp = win.getInterface(Ci.nsIWebBrowserPrint);
37 let nsresult = event.detail;
38 this.sendAsyncMessage("Printing:Error", {
39 isPrinting: wbp.doingPrint,
46 if (!this._scrollTask) {
47 this._scrollTask = new lazy.DeferredTask(
48 () => this.updateCurrentPage(),
53 this._scrollTask.arm();
58 receiveMessage(message) {
59 let data = message.data;
60 switch (message.name) {
61 case "Printing:Preview:Navigate": {
62 this.navigate(data.navType, data.pageNum);
66 case "Printing:Preview:ParseDocument": {
67 return this.parseDocument(
69 Services.wm.getOuterWindowWithId(data.windowID)
77 async parseDocument(URL, contentWindow) {
78 // The document in 'contentWindow' will be simplified and the resulting nodes
79 // will be inserted into this.contentWindow.
80 let thisWindow = this.contentWindow;
82 // By using ReaderMode primitives, we parse given document and place the
83 // resulting JS object into the DOM of current browser.
86 article = await lazy.ReaderMode.parseDocument(contentWindow.document);
91 await new Promise(resolve => {
92 // We make use of a web progress listener in order to know when the content we inject
93 // into the DOM has finished rendering. If our layout engine is still painting, we
94 // will wait for MozAfterPaint event to be fired.
95 let actor = thisWindow.windowGlobalChild.getActor("Printing");
96 let webProgressListener = {
97 onStateChange(webProgress, req, flags, status) {
98 if (flags & Ci.nsIWebProgressListener.STATE_STOP) {
99 webProgress.removeProgressListener(webProgressListener);
100 let domUtils = contentWindow.windowUtils;
101 // Here we tell the parent that we have parsed the document successfully
102 // using ReaderMode primitives and we are able to enter on preview mode.
103 if (domUtils.isMozAfterPaintPending) {
104 let onPaint = function () {
105 contentWindow.removeEventListener("MozAfterPaint", onPaint);
106 actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
109 contentWindow.addEventListener("MozAfterPaint", onPaint);
110 // This timer is needed for when display list invalidation doesn't invalidate.
111 lazy.setTimeout(() => {
112 contentWindow.removeEventListener("MozAfterPaint", onPaint);
113 actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
117 actor.sendAsyncMessage("Printing:Preview:ReaderModeReady");
123 QueryInterface: ChromeUtils.generateQI([
124 "nsIWebProgressListener",
125 "nsISupportsWeakReference",
130 // Here we QI the docShell into a nsIWebProgress passing our web progress listener in.
131 let webProgress = thisWindow.docShell
132 .QueryInterface(Ci.nsIInterfaceRequestor)
133 .getInterface(Ci.nsIWebProgress);
134 webProgress.addProgressListener(
136 Ci.nsIWebProgress.NOTIFY_STATE_REQUEST
139 let document = thisWindow.document;
140 document.head.innerHTML = "";
142 // Set base URI of document. Print preview code will read this value to
143 // populate the URL field in print settings so that it doesn't show
144 // "about:blank" as its URI.
145 let headBaseElement = document.createElement("base");
146 headBaseElement.setAttribute("href", URL);
147 document.head.appendChild(headBaseElement);
149 // Create link element referencing aboutReader.css and append it to head
150 let headStyleElement = document.createElement("link");
151 headStyleElement.setAttribute("rel", "stylesheet");
152 headStyleElement.setAttribute(
154 "chrome://global/skin/aboutReader.css"
156 headStyleElement.setAttribute("type", "text/css");
157 document.head.appendChild(headStyleElement);
159 // Create link element referencing simplifyMode.css and append it to head
160 headStyleElement = document.createElement("link");
161 headStyleElement.setAttribute("rel", "stylesheet");
162 headStyleElement.setAttribute(
164 "chrome://global/content/simplifyMode.css"
166 headStyleElement.setAttribute("type", "text/css");
167 document.head.appendChild(headStyleElement);
169 document.body.innerHTML = "";
171 // Create container div (main element) and append it to body
172 let containerElement = document.createElement("div");
173 containerElement.setAttribute("class", "container");
174 document.body.appendChild(containerElement);
176 // Reader Mode might return null if there's a failure when parsing the document.
177 // We'll render the error message for the Simplify Page document when that happens.
179 // Set title of document
180 document.title = article.title;
182 // Create header div and append it to container
183 let headerElement = document.createElement("div");
184 headerElement.setAttribute("class", "reader-header");
185 headerElement.setAttribute("class", "header");
186 containerElement.appendChild(headerElement);
188 // Jam the article's title and byline into header div
189 let titleElement = document.createElement("h1");
190 titleElement.setAttribute("class", "reader-title");
191 titleElement.textContent = article.title;
192 headerElement.appendChild(titleElement);
194 let bylineElement = document.createElement("div");
195 bylineElement.setAttribute("class", "reader-credits credits");
196 bylineElement.textContent = article.byline;
197 headerElement.appendChild(bylineElement);
199 // Display header element
200 headerElement.style.display = "block";
202 // Create content div and append it to container
203 let contentElement = document.createElement("div");
204 contentElement.setAttribute("class", "content");
205 containerElement.appendChild(contentElement);
207 // Jam the article's content into content div
208 let readerContent = document.createElement("div");
209 readerContent.setAttribute("class", "moz-reader-content");
210 contentElement.appendChild(readerContent);
212 let articleUri = Services.io.newURI(article.url);
213 let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(
216 let contentFragment = parserUtils.parseFragment(
218 Ci.nsIParserUtils.SanitizerDropForms |
219 Ci.nsIParserUtils.SanitizerAllowStyle,
225 readerContent.appendChild(contentFragment);
227 // Display reader content element
228 readerContent.style.display = "block";
230 const l10n = new Localization(["toolkit/about/aboutReader.ftl"], true);
231 const errorMessage = l10n.formatValueSync("about-reader-load-error");
233 document.title = errorMessage;
235 // Create reader message div and append it to body
236 let readerMessageElement = document.createElement("div");
237 readerMessageElement.setAttribute("class", "reader-message");
238 readerMessageElement.textContent = errorMessage;
239 containerElement.appendChild(readerMessageElement);
241 // Display reader message element
242 readerMessageElement.style.display = "block";
247 updateCurrentPage() {
248 let cv = this.docShell.docViewer;
249 cv.QueryInterface(Ci.nsIWebBrowserPrint);
250 this.sendAsyncMessage("Printing:Preview:CurrentPage", {
251 currentPage: cv.printPreviewCurrentPageNumber,
255 navigate(navType, pageNum) {
256 let cv = this.docShell.docViewer;
257 cv.QueryInterface(Ci.nsIWebBrowserPrint);
258 cv.printPreviewScrollToPage(navType, pageNum);