tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

AboutReaderChild.sys.mjs (7749B)


      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 
      6 const lazy = {};
      7 
      8 ChromeUtils.defineESModuleGetters(lazy, {
      9  AboutReader: "moz-src:///toolkit/components/reader/AboutReader.sys.mjs",
     10  ReaderMode: "moz-src:///toolkit/components/reader/ReaderMode.sys.mjs",
     11  Readerable: "resource://gre/modules/Readerable.sys.mjs",
     12 });
     13 
     14 var gUrlsToDocContentType = new Map();
     15 var gUrlsToDocTitle = new Map();
     16 
     17 export class AboutReaderChild extends JSWindowActorChild {
     18  constructor() {
     19    super();
     20 
     21    this._reader = null;
     22    this._articlePromise = null;
     23    this._isLeavingReaderableReaderMode = false;
     24  }
     25 
     26  didDestroy() {
     27    this.cancelPotentialPendingReadabilityCheck();
     28    this.readerModeHidden();
     29  }
     30 
     31  readerModeHidden() {
     32    if (this._reader) {
     33      this._reader.clearActor();
     34    }
     35    this._reader = null;
     36  }
     37 
     38  async receiveMessage(message) {
     39    switch (message.name) {
     40      case "Reader:ToggleReaderMode":
     41        if (!this.isAboutReader) {
     42          gUrlsToDocContentType.set(
     43            this.document.URL,
     44            this.document.contentType
     45          );
     46          gUrlsToDocTitle.set(this.document.URL, this.document.title);
     47          this._articlePromise = lazy.ReaderMode.parseDocument(
     48            this.document
     49          ).catch(console.error);
     50 
     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);
     55        } else {
     56          this.closeReaderMode();
     57        }
     58        break;
     59 
     60      case "Reader:PushState":
     61        this.updateReaderButton(!!(message.data && message.data.isArticle));
     62        break;
     63      case "Reader:EnterReaderMode": {
     64        lazy.ReaderMode.enterReaderMode(this.docShell, this.contentWindow);
     65        break;
     66      }
     67      case "Reader:LeaveReaderMode": {
     68        lazy.ReaderMode.leaveReaderMode(this.docShell, this.contentWindow);
     69        break;
     70      }
     71    }
     72 
     73    // Forward the message to the reader if it has been created.
     74    if (this._reader) {
     75      this._reader.receiveMessage(message);
     76    }
     77  }
     78 
     79  get isAboutReader() {
     80    if (!this.document) {
     81      return false;
     82    }
     83    return this.document.documentURI.startsWith("about:reader");
     84  }
     85 
     86  get isReaderableAboutReader() {
     87    return this.isAboutReader && !this.document.documentElement.dataset.isError;
     88  }
     89 
     90  handleEvent(aEvent) {
     91    if (aEvent.originalTarget.defaultView != this.contentWindow) {
     92      return;
     93    }
     94 
     95    switch (aEvent.type) {
     96      case "DOMContentLoaded":
     97        if (!this.isAboutReader) {
     98          this.updateReaderButton();
     99          return;
    100        }
    101 
    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", {
    107              url,
    108            });
    109          }
    110          // Update the toolbar icon to show the "reader active" icon.
    111          this.sendAsyncMessage("Reader:UpdateReaderButton");
    112          let docContentType =
    113            gUrlsToDocContentType.get(url) === "text/plain"
    114              ? "text/plain"
    115              : "document";
    116 
    117          let docTitle = gUrlsToDocTitle.get(url);
    118          this._reader = new lazy.AboutReader(
    119            this,
    120            this._articlePromise,
    121            docContentType,
    122            docTitle
    123          );
    124          this._articlePromise = null;
    125        }
    126        break;
    127 
    128      case "pagehide":
    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,
    135        });
    136        this._isLeavingReaderableReaderMode = false;
    137        break;
    138 
    139      case "pageshow":
    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();
    144        }
    145        break;
    146    }
    147  }
    148 
    149  /**
    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.
    154   */
    155  updateReaderButton(forceNonArticle) {
    156    if (!this.canDoReadabilityCheck()) {
    157      return;
    158    }
    159 
    160    this.scheduleReadabilityCheckPostPaint(forceNonArticle);
    161  }
    162 
    163  canDoReadabilityCheck() {
    164    return (
    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
    171    );
    172  }
    173 
    174  cancelPotentialPendingReadabilityCheck() {
    175    if (this._pendingReadabilityCheck) {
    176      if (this._listenerWindow) {
    177        this._listenerWindow.removeEventListener(
    178          "MozAfterPaint",
    179          this._pendingReadabilityCheck
    180        );
    181      }
    182      delete this._pendingReadabilityCheck;
    183      delete this._listenerWindow;
    184    }
    185  }
    186 
    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();
    192    }
    193    this._pendingReadabilityCheck = this.onPaintWhenWaitedFor.bind(
    194      this,
    195      forceNonArticle
    196    );
    197 
    198    this._listenerWindow = this.contentWindow.windowRoot;
    199    this.contentWindow.windowRoot.addEventListener(
    200      "MozAfterPaint",
    201      this._pendingReadabilityCheck
    202    );
    203  }
    204 
    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) {
    212      return;
    213    }
    214 
    215    this.performReadabilityCheckNow(forceNonArticle);
    216  }
    217 
    218  performReadabilityCheckNow(forceNonArticle) {
    219    this.cancelPotentialPendingReadabilityCheck();
    220 
    221    // Ignore errors from actors that have been unloaded before the
    222    // paint event timer fires.
    223    let document;
    224    try {
    225      document = this.document;
    226    } catch (ex) {
    227      return;
    228    }
    229 
    230    // Only send updates when there are articles; there's no point updating with
    231    // |false| all the time.
    232    if (
    233      lazy.Readerable.shouldCheckUri(document.baseURIObject, true) &&
    234      lazy.Readerable.isProbablyReaderable(document)
    235    ) {
    236      this.sendAsyncMessage("Reader:UpdateReaderButton", {
    237        isArticle: true,
    238      });
    239    } else if (forceNonArticle) {
    240      this.sendAsyncMessage("Reader:UpdateReaderButton", {
    241        isArticle: false,
    242      });
    243    }
    244  }
    245 
    246  closeReaderMode() {
    247    if (this.isAboutReader) {
    248      this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
    249      this.sendAsyncMessage("Reader:LeaveReaderMode", {});
    250    }
    251  }
    252 }