Bug 1791785 - When dumping symbols never emit INLINE_ORIGIN directives with an empty...
[gecko.git] / browser / actors / AboutReaderChild.jsm
blob993cee751dba62981a9b10768d4c3da6ed1faeac
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 = ["AboutReaderChild"];
9 const lazy = {};
11 ChromeUtils.defineModuleGetter(
12   lazy,
13   "AboutReader",
14   "resource://gre/modules/AboutReader.jsm"
16 ChromeUtils.defineModuleGetter(
17   lazy,
18   "ReaderMode",
19   "resource://gre/modules/ReaderMode.jsm"
21 ChromeUtils.defineModuleGetter(
22   lazy,
23   "Readerable",
24   "resource://gre/modules/Readerable.jsm"
27 var gUrlsToDocContentType = new Map();
28 var gUrlsToDocTitle = new Map();
30 class AboutReaderChild extends JSWindowActorChild {
31   constructor() {
32     super();
34     this._reader = null;
35     this._articlePromise = null;
36     this._isLeavingReaderableReaderMode = false;
37   }
39   didDestroy() {
40     this.cancelPotentialPendingReadabilityCheck();
41     this.readerModeHidden();
42   }
44   readerModeHidden() {
45     if (this._reader) {
46       this._reader.clearActor();
47     }
48     this._reader = null;
49   }
51   async receiveMessage(message) {
52     switch (message.name) {
53       case "Reader:ToggleReaderMode":
54         if (!this.isAboutReader) {
55           gUrlsToDocContentType.set(
56             this.document.URL,
57             this.document.contentType
58           );
59           gUrlsToDocTitle.set(this.document.URL, this.document.title);
60           this._articlePromise = lazy.ReaderMode.parseDocument(
61             this.document
62           ).catch(Cu.reportError);
64           // Get the article data and cache it in the parent process. The reader mode
65           // page will retrieve it when it has loaded.
66           let article = await this._articlePromise;
67           this.sendAsyncMessage("Reader:EnterReaderMode", article);
68         } else {
69           this.closeReaderMode();
70         }
71         break;
73       case "Reader:PushState":
74         this.updateReaderButton(!!(message.data && message.data.isArticle));
75         break;
76       case "Reader:EnterReaderMode": {
77         lazy.ReaderMode.enterReaderMode(this.docShell, this.contentWindow);
78         break;
79       }
80       case "Reader:LeaveReaderMode": {
81         lazy.ReaderMode.leaveReaderMode(this.docShell, this.contentWindow);
82         break;
83       }
84     }
86     // Forward the message to the reader if it has been created.
87     if (this._reader) {
88       this._reader.receiveMessage(message);
89     }
90   }
92   get isAboutReader() {
93     if (!this.document) {
94       return false;
95     }
96     return this.document.documentURI.startsWith("about:reader");
97   }
99   get isReaderableAboutReader() {
100     return this.isAboutReader && !this.document.documentElement.dataset.isError;
101   }
103   handleEvent(aEvent) {
104     if (aEvent.originalTarget.defaultView != this.contentWindow) {
105       return;
106     }
108     switch (aEvent.type) {
109       case "DOMContentLoaded":
110         if (!this.isAboutReader) {
111           this.updateReaderButton();
112           return;
113         }
115         if (this.document.body) {
116           let url = this.document.documentURI;
117           if (!this._articlePromise) {
118             url = decodeURIComponent(url.substr("about:reader?url=".length));
119             this._articlePromise = this.sendQuery("Reader:GetCachedArticle", {
120               url,
121             });
122           }
123           // Update the toolbar icon to show the "reader active" icon.
124           this.sendAsyncMessage("Reader:UpdateReaderButton");
125           let docContentType =
126             gUrlsToDocContentType.get(url) === "text/plain"
127               ? "text/plain"
128               : "document";
130           let docTitle = gUrlsToDocTitle.get(url);
131           this._reader = new lazy.AboutReader(
132             this,
133             this._articlePromise,
134             docContentType,
135             docTitle
136           );
137           this._articlePromise = null;
138         }
139         break;
141       case "pagehide":
142         this.cancelPotentialPendingReadabilityCheck();
143         // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
144         // visible in the location bar when transitioning from reader-mode page
145         // back to the readable source page.
146         this.sendAsyncMessage("Reader:UpdateReaderButton", {
147           isArticle: this._isLeavingReaderableReaderMode,
148         });
149         this._isLeavingReaderableReaderMode = false;
150         break;
152       case "pageshow":
153         // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
154         // event, so we need to rely on "pageshow" in this case.
155         if (aEvent.persisted && this.canDoReadabilityCheck()) {
156           this.performReadabilityCheckNow();
157         }
158         break;
159     }
160   }
162   /**
163    * NB: this function will update the state of the reader button asynchronously
164    * after the next mozAfterPaint call (assuming reader mode is enabled and
165    * this is a suitable document). Calling it on things which won't be
166    * painted is not going to work.
167    */
168   updateReaderButton(forceNonArticle) {
169     if (!this.canDoReadabilityCheck()) {
170       return;
171     }
173     this.scheduleReadabilityCheckPostPaint(forceNonArticle);
174   }
176   canDoReadabilityCheck() {
177     return (
178       lazy.Readerable.isEnabledForParseOnLoad &&
179       !this.isAboutReader &&
180       this.contentWindow &&
181       this.contentWindow.windowRoot &&
182       this.contentWindow.HTMLDocument.isInstance(this.document) &&
183       !this.document.mozSyntheticDocument
184     );
185   }
187   cancelPotentialPendingReadabilityCheck() {
188     if (this._pendingReadabilityCheck) {
189       if (this._listenerWindow) {
190         this._listenerWindow.removeEventListener(
191           "MozAfterPaint",
192           this._pendingReadabilityCheck
193         );
194       }
195       delete this._pendingReadabilityCheck;
196       delete this._listenerWindow;
197     }
198   }
200   scheduleReadabilityCheckPostPaint(forceNonArticle) {
201     if (this._pendingReadabilityCheck) {
202       // We need to stop this check before we re-add one because we don't know
203       // if forceNonArticle was true or false last time.
204       this.cancelPotentialPendingReadabilityCheck();
205     }
206     this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
207       this,
208       forceNonArticle
209     );
211     this._listenerWindow = this.contentWindow.windowRoot;
212     this.contentWindow.windowRoot.addEventListener(
213       "MozAfterPaint",
214       this._pendingReadabilityCheck
215     );
216   }
218   onPaintWhenWaitedFor(forceNonArticle, event) {
219     // In non-e10s, we'll get called for paints other than ours, and so it's
220     // possible that this page hasn't been laid out yet, in which case we
221     // should wait until we get an event that does relate to our layout. We
222     // determine whether any of our this.contentWindow got painted by checking
223     // if there are any painted rects.
224     if (!event.clientRects.length) {
225       return;
226     }
228     this.performReadabilityCheckNow(forceNonArticle);
229   }
231   performReadabilityCheckNow(forceNonArticle) {
232     this.cancelPotentialPendingReadabilityCheck();
234     // Ignore errors from actors that have been unloaded before the
235     // paint event timer fires.
236     let document;
237     try {
238       document = this.document;
239     } catch (ex) {
240       return;
241     }
243     // Only send updates when there are articles; there's no point updating with
244     // |false| all the time.
245     if (
246       lazy.Readerable.shouldCheckUri(document.baseURIObject, true) &&
247       lazy.Readerable.isProbablyReaderable(document)
248     ) {
249       this.sendAsyncMessage("Reader:UpdateReaderButton", {
250         isArticle: true,
251       });
252     } else if (forceNonArticle) {
253       this.sendAsyncMessage("Reader:UpdateReaderButton", {
254         isArticle: false,
255       });
256     }
257   }
259   closeReaderMode() {
260     if (this.isAboutReader) {
261       this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
262       this.sendAsyncMessage("Reader:LeaveReaderMode", {});
263     }
264   }