forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			252 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			252 lines
		
	
	
	
		
			7.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   AboutReader: "resource://gre/modules/AboutReader.sys.mjs",
 | |
|   ReaderMode: "resource://gre/modules/ReaderMode.sys.mjs",
 | |
|   Readerable: "resource://gre/modules/Readerable.sys.mjs",
 | |
| });
 | |
| 
 | |
| var gUrlsToDocContentType = new Map();
 | |
| var gUrlsToDocTitle = new Map();
 | |
| 
 | |
| export class AboutReaderChild extends JSWindowActorChild {
 | |
|   constructor() {
 | |
|     super();
 | |
| 
 | |
|     this._reader = null;
 | |
|     this._articlePromise = null;
 | |
|     this._isLeavingReaderableReaderMode = false;
 | |
|   }
 | |
| 
 | |
|   didDestroy() {
 | |
|     this.cancelPotentialPendingReadabilityCheck();
 | |
|     this.readerModeHidden();
 | |
|   }
 | |
| 
 | |
|   readerModeHidden() {
 | |
|     if (this._reader) {
 | |
|       this._reader.clearActor();
 | |
|     }
 | |
|     this._reader = null;
 | |
|   }
 | |
| 
 | |
|   async receiveMessage(message) {
 | |
|     switch (message.name) {
 | |
|       case "Reader:ToggleReaderMode":
 | |
|         if (!this.isAboutReader) {
 | |
|           gUrlsToDocContentType.set(
 | |
|             this.document.URL,
 | |
|             this.document.contentType
 | |
|           );
 | |
|           gUrlsToDocTitle.set(this.document.URL, this.document.title);
 | |
|           this._articlePromise = lazy.ReaderMode.parseDocument(
 | |
|             this.document
 | |
|           ).catch(console.error);
 | |
| 
 | |
|           // Get the article data and cache it in the parent process. The reader mode
 | |
|           // page will retrieve it when it has loaded.
 | |
|           let article = await this._articlePromise;
 | |
|           this.sendAsyncMessage("Reader:EnterReaderMode", article);
 | |
|         } else {
 | |
|           this.closeReaderMode();
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case "Reader:PushState":
 | |
|         this.updateReaderButton(!!(message.data && message.data.isArticle));
 | |
|         break;
 | |
|       case "Reader:EnterReaderMode": {
 | |
|         lazy.ReaderMode.enterReaderMode(this.docShell, this.contentWindow);
 | |
|         break;
 | |
|       }
 | |
|       case "Reader:LeaveReaderMode": {
 | |
|         lazy.ReaderMode.leaveReaderMode(this.docShell, this.contentWindow);
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     // Forward the message to the reader if it has been created.
 | |
|     if (this._reader) {
 | |
|       this._reader.receiveMessage(message);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   get isAboutReader() {
 | |
|     if (!this.document) {
 | |
|       return false;
 | |
|     }
 | |
|     return this.document.documentURI.startsWith("about:reader");
 | |
|   }
 | |
| 
 | |
|   get isReaderableAboutReader() {
 | |
|     return this.isAboutReader && !this.document.documentElement.dataset.isError;
 | |
|   }
 | |
| 
 | |
|   handleEvent(aEvent) {
 | |
|     if (aEvent.originalTarget.defaultView != this.contentWindow) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     switch (aEvent.type) {
 | |
|       case "DOMContentLoaded":
 | |
|         if (!this.isAboutReader) {
 | |
|           this.updateReaderButton();
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         if (this.document.body) {
 | |
|           let url = this.document.documentURI;
 | |
|           if (!this._articlePromise) {
 | |
|             url = decodeURIComponent(url.substr("about:reader?url=".length));
 | |
|             this._articlePromise = this.sendQuery("Reader:GetCachedArticle", {
 | |
|               url,
 | |
|             });
 | |
|           }
 | |
|           // Update the toolbar icon to show the "reader active" icon.
 | |
|           this.sendAsyncMessage("Reader:UpdateReaderButton");
 | |
|           let docContentType =
 | |
|             gUrlsToDocContentType.get(url) === "text/plain"
 | |
|               ? "text/plain"
 | |
|               : "document";
 | |
| 
 | |
|           let docTitle = gUrlsToDocTitle.get(url);
 | |
|           this._reader = new lazy.AboutReader(
 | |
|             this,
 | |
|             this._articlePromise,
 | |
|             docContentType,
 | |
|             docTitle
 | |
|           );
 | |
|           this._articlePromise = null;
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case "pagehide":
 | |
|         this.cancelPotentialPendingReadabilityCheck();
 | |
|         // this._isLeavingReaderableReaderMode is used here to keep the Reader Mode icon
 | |
|         // visible in the location bar when transitioning from reader-mode page
 | |
|         // back to the readable source page.
 | |
|         this.sendAsyncMessage("Reader:UpdateReaderButton", {
 | |
|           isArticle: this._isLeavingReaderableReaderMode,
 | |
|         });
 | |
|         this._isLeavingReaderableReaderMode = false;
 | |
|         break;
 | |
| 
 | |
|       case "pageshow":
 | |
|         // If a page is loaded from the bfcache, we won't get a "DOMContentLoaded"
 | |
|         // event, so we need to rely on "pageshow" in this case.
 | |
|         if (aEvent.persisted && this.canDoReadabilityCheck()) {
 | |
|           this.performReadabilityCheckNow();
 | |
|         }
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * NB: this function will update the state of the reader button asynchronously
 | |
|    * after the next mozAfterPaint call (assuming reader mode is enabled and
 | |
|    * this is a suitable document). Calling it on things which won't be
 | |
|    * painted is not going to work.
 | |
|    */
 | |
|   updateReaderButton(forceNonArticle) {
 | |
|     if (!this.canDoReadabilityCheck()) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.scheduleReadabilityCheckPostPaint(forceNonArticle);
 | |
|   }
 | |
| 
 | |
|   canDoReadabilityCheck() {
 | |
|     return (
 | |
|       lazy.Readerable.isEnabledForParseOnLoad &&
 | |
|       !this.isAboutReader &&
 | |
|       this.contentWindow &&
 | |
|       this.contentWindow.windowRoot &&
 | |
|       this.contentWindow.HTMLDocument.isInstance(this.document) &&
 | |
|       !this.document.mozSyntheticDocument
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   cancelPotentialPendingReadabilityCheck() {
 | |
|     if (this._pendingReadabilityCheck) {
 | |
|       if (this._listenerWindow) {
 | |
|         this._listenerWindow.removeEventListener(
 | |
|           "MozAfterPaint",
 | |
|           this._pendingReadabilityCheck
 | |
|         );
 | |
|       }
 | |
|       delete this._pendingReadabilityCheck;
 | |
|       delete this._listenerWindow;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   scheduleReadabilityCheckPostPaint(forceNonArticle) {
 | |
|     if (this._pendingReadabilityCheck) {
 | |
|       // We need to stop this check before we re-add one because we don't know
 | |
|       // if forceNonArticle was true or false last time.
 | |
|       this.cancelPotentialPendingReadabilityCheck();
 | |
|     }
 | |
|     this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
 | |
|       this,
 | |
|       forceNonArticle
 | |
|     );
 | |
| 
 | |
|     this._listenerWindow = this.contentWindow.windowRoot;
 | |
|     this.contentWindow.windowRoot.addEventListener(
 | |
|       "MozAfterPaint",
 | |
|       this._pendingReadabilityCheck
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   onPaintWhenWaitedFor(forceNonArticle, event) {
 | |
|     // In non-e10s, we'll get called for paints other than ours, and so it's
 | |
|     // possible that this page hasn't been laid out yet, in which case we
 | |
|     // should wait until we get an event that does relate to our layout. We
 | |
|     // determine whether any of our this.contentWindow got painted by checking
 | |
|     // if there are any painted rects.
 | |
|     if (!event.clientRects.length) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     this.performReadabilityCheckNow(forceNonArticle);
 | |
|   }
 | |
| 
 | |
|   performReadabilityCheckNow(forceNonArticle) {
 | |
|     this.cancelPotentialPendingReadabilityCheck();
 | |
| 
 | |
|     // Ignore errors from actors that have been unloaded before the
 | |
|     // paint event timer fires.
 | |
|     let document;
 | |
|     try {
 | |
|       document = this.document;
 | |
|     } catch (ex) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Only send updates when there are articles; there's no point updating with
 | |
|     // |false| all the time.
 | |
|     if (
 | |
|       lazy.Readerable.shouldCheckUri(document.baseURIObject, true) &&
 | |
|       lazy.Readerable.isProbablyReaderable(document)
 | |
|     ) {
 | |
|       this.sendAsyncMessage("Reader:UpdateReaderButton", {
 | |
|         isArticle: true,
 | |
|       });
 | |
|     } else if (forceNonArticle) {
 | |
|       this.sendAsyncMessage("Reader:UpdateReaderButton", {
 | |
|         isArticle: false,
 | |
|       });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   closeReaderMode() {
 | |
|     if (this.isAboutReader) {
 | |
|       this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
 | |
|       this.sendAsyncMessage("Reader:LeaveReaderMode", {});
 | |
|     }
 | |
|   }
 | |
| }
 | 
