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 AboutReader: "resource://gre/modules/AboutReader.sys.mjs",
10 ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
11 Readerable: "resource://gre/modules/Readerable.sys.mjs",
14 var gUrlsToDocContentType = new Map();
15 var gUrlsToDocTitle = new Map();
17 export class AboutReaderChild extends JSWindowActorChild {
22 this._articlePromise = null;
23 this._isLeavingReaderableReaderMode = false;
27 this.cancelPotentialPendingReadabilityCheck();
28 this.readerModeHidden();
33 this._reader.clearActor();
38 async receiveMessage(message) {
39 switch (message.name) {
40 case "Reader:ToggleReaderMode":
41 if (!this.isAboutReader) {
42 gUrlsToDocContentType.set(
44 this.document.contentType
46 gUrlsToDocTitle.set(this.document.URL, this.document.title);
47 this._articlePromise = lazy.ReaderMode.parseDocument(
49 ).catch(console.error);
51 // Get the article data and cache it in the parent process. The reader mode
52 // page will retrieve it when it has loaded.
53 let article = await this._articlePromise;
54 this.sendAsyncMessage("Reader:EnterReaderMode", article);
56 this.closeReaderMode();
60 case "Reader:PushState":
61 this.updateReaderButton(!!(message.data && message.data.isArticle));
63 case "Reader:EnterReaderMode": {
64 lazy.ReaderMode.enterReaderMode(this.docShell, this.contentWindow);
67 case "Reader:LeaveReaderMode": {
68 lazy.ReaderMode.leaveReaderMode(this.docShell, this.contentWindow);
73 // Forward the message to the reader if it has been created.
75 this._reader.receiveMessage(message);
83 return this.document.documentURI.startsWith("about:reader");
86 get isReaderableAboutReader() {
87 return this.isAboutReader && !this.document.documentElement.dataset.isError;
91 if (aEvent.originalTarget.defaultView != this.contentWindow) {
95 switch (aEvent.type) {
96 case "DOMContentLoaded":
97 if (!this.isAboutReader) {
98 this.updateReaderButton();
102 if (this.document.body) {
103 let url = this.document.documentURI;
104 if (!this._articlePromise) {
105 url = decodeURIComponent(url.substr("about:reader?url=".length));
106 this._articlePromise = this.sendQuery("Reader:GetCachedArticle", {
110 // Update the toolbar icon to show the "reader active" icon.
111 this.sendAsyncMessage("Reader:UpdateReaderButton");
113 gUrlsToDocContentType.get(url) === "text/plain"
117 let docTitle = gUrlsToDocTitle.get(url);
118 this._reader = new lazy.AboutReader(
120 this._articlePromise,
124 this._articlePromise = null;
129 this.cancelPotentialPendingReadabilityCheck();
130 // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
131 // visible in the location bar when transitioning from reader-mode page
132 // back to the readable source page.
133 this.sendAsyncMessage("Reader:UpdateReaderButton", {
134 isArticle: this._isLeavingReaderableReaderMode,
136 this._isLeavingReaderableReaderMode = false;
140 // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
141 // event, so we need to rely on "pageshow" in this case.
142 if (aEvent.persisted && this.canDoReadabilityCheck()) {
143 this.performReadabilityCheckNow();
150 * NB: this function will update the state of the reader button asynchronously
151 * after the next mozAfterPaint call (assuming reader mode is enabled and
152 * this is a suitable document). Calling it on things which won't be
153 * painted is not going to work.
155 updateReaderButton(forceNonArticle) {
156 if (!this.canDoReadabilityCheck()) {
160 this.scheduleReadabilityCheckPostPaint(forceNonArticle);
163 canDoReadabilityCheck() {
165 lazy.Readerable.isEnabledForParseOnLoad &&
166 !this.isAboutReader &&
167 this.contentWindow &&
168 this.contentWindow.windowRoot &&
169 this.contentWindow.HTMLDocument.isInstance(this.document) &&
170 !this.document.mozSyntheticDocument
174 cancelPotentialPendingReadabilityCheck() {
175 if (this._pendingReadabilityCheck) {
176 if (this._listenerWindow) {
177 this._listenerWindow.removeEventListener(
179 this._pendingReadabilityCheck
182 delete this._pendingReadabilityCheck;
183 delete this._listenerWindow;
187 scheduleReadabilityCheckPostPaint(forceNonArticle) {
188 if (this._pendingReadabilityCheck) {
189 // We need to stop this check before we re-add one because we don't know
190 // if forceNonArticle was true or false last time.
191 this.cancelPotentialPendingReadabilityCheck();
193 this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
198 this._listenerWindow = this.contentWindow.windowRoot;
199 this.contentWindow.windowRoot.addEventListener(
201 this._pendingReadabilityCheck
205 onPaintWhenWaitedFor(forceNonArticle, event) {
206 // In non-e10s, we'll get called for paints other than ours, and so it's
207 // possible that this page hasn't been laid out yet, in which case we
208 // should wait until we get an event that does relate to our layout. We
209 // determine whether any of our this.contentWindow got painted by checking
210 // if there are any painted rects.
211 if (!event.clientRects.length) {
215 this.performReadabilityCheckNow(forceNonArticle);
218 performReadabilityCheckNow(forceNonArticle) {
219 this.cancelPotentialPendingReadabilityCheck();
221 // Ignore errors from actors that have been unloaded before the
222 // paint event timer fires.
225 document = this.document;
230 // Only send updates when there are articles; there's no point updating with
231 // |false| all the time.
233 lazy.Readerable.shouldCheckUri(document.baseURIObject, true) &&
234 lazy.Readerable.isProbablyReaderable(document)
236 this.sendAsyncMessage("Reader:UpdateReaderButton", {
239 } else if (forceNonArticle) {
240 this.sendAsyncMessage("Reader:UpdateReaderButton", {
247 if (this.isAboutReader) {
248 this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
249 this.sendAsyncMessage("Reader:LeaveReaderMode", {});