tor-browser

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

AboutReaderParent.sys.mjs (8246B)


      1 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
      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  PageActions: "resource:///modules/PageActions.sys.mjs",
     10  PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
     11  ReaderMode: "moz-src:///toolkit/components/reader/ReaderMode.sys.mjs",
     12 });
     13 
     14 // A set of all of the AboutReaderParent actors that exist.
     15 // See bug 1631146 for a request for a less manual way of doing this.
     16 let gAllActors = new Set();
     17 
     18 // A map of message names to listeners that listen to messages
     19 // received by the AboutReaderParent actors.
     20 let gListeners = new Map();
     21 
     22 // As a reader mode document could be loaded in a different process than
     23 // the source article, temporarily cache the article data here in the
     24 // parent while switching to it.
     25 let gCachedArticles = new Map();
     26 
     27 export class AboutReaderParent extends JSWindowActorParent {
     28  didDestroy() {
     29    gAllActors.delete(this);
     30 
     31    if (this.isReaderMode()) {
     32      let url = this.manager.documentURI.spec;
     33      url = decodeURIComponent(url.substr("about:reader?url=".length));
     34      gCachedArticles.delete(url);
     35    }
     36  }
     37 
     38  isReaderMode() {
     39    return this.manager.documentURI.spec.startsWith("about:reader");
     40  }
     41 
     42  static addMessageListener(name, listener) {
     43    if (!gListeners.has(name)) {
     44      gListeners.set(name, new Set([listener]));
     45    } else {
     46      gListeners.get(name).add(listener);
     47    }
     48  }
     49 
     50  static removeMessageListener(name, listener) {
     51    if (!gListeners.has(name)) {
     52      return;
     53    }
     54 
     55    gListeners.get(name).delete(listener);
     56  }
     57 
     58  static broadcastAsyncMessage(name, data) {
     59    for (let actor of gAllActors) {
     60      // Ignore errors for actors that might not be valid yet or anymore.
     61      try {
     62        actor.sendAsyncMessage(name, data);
     63      } catch (ex) {}
     64    }
     65  }
     66 
     67  callListeners(message) {
     68    let listeners = gListeners.get(message.name);
     69    if (!listeners) {
     70      return;
     71    }
     72 
     73    message.target = this.browsingContext.embedderElement;
     74    for (let listener of listeners.values()) {
     75      try {
     76        listener.receiveMessage(message);
     77      } catch (e) {
     78        console.error(e);
     79      }
     80    }
     81  }
     82 
     83  async receiveMessage(message) {
     84    switch (message.name) {
     85      case "Reader:EnterReaderMode": {
     86        gCachedArticles.set(message.data.url, message.data);
     87        this.enterReaderMode(message.data.url);
     88        break;
     89      }
     90      case "Reader:LeaveReaderMode": {
     91        this.leaveReaderMode();
     92        break;
     93      }
     94      case "Reader:GetCachedArticle": {
     95        let cachedArticle = gCachedArticles.get(message.data.url);
     96        gCachedArticles.delete(message.data.url);
     97        return cachedArticle;
     98      }
     99      case "Reader:FaviconRequest": {
    100        try {
    101          let preferredWidth = message.data.preferredWidth || 0;
    102          let uri = Services.io.newURI(message.data.url);
    103 
    104          let result = await lazy.PlacesUtils.favicons.getFaviconForPage(
    105            uri,
    106            preferredWidth
    107          );
    108 
    109          this.callListeners(message);
    110          return result && { url: uri.spec, faviconUrl: result.uri.spec };
    111        } catch (ex) {
    112          console.error(
    113            "Error requesting favicon URL for about:reader content: ",
    114            ex
    115          );
    116        }
    117 
    118        break;
    119      }
    120 
    121      case "Reader:UpdateReaderButton": {
    122        let browser = this.browsingContext.embedderElement;
    123        if (!browser) {
    124          return undefined;
    125        }
    126 
    127        if (message.data && message.data.isArticle !== undefined) {
    128          browser.isArticle = message.data.isArticle;
    129        }
    130        this.updateReaderButton(browser);
    131        this.callListeners(message);
    132        break;
    133      }
    134 
    135      case "RedirectTo": {
    136        gCachedArticles.set(message.data.newURL, message.data.article);
    137        // This is setup as a query so we can navigate the page after we've
    138        // cached the relevant info in the parent.
    139        return true;
    140      }
    141 
    142      default:
    143        this.callListeners(message);
    144        break;
    145    }
    146 
    147    return undefined;
    148  }
    149 
    150  static updateReaderButton(browser) {
    151    let windowGlobal = browser.browsingContext.currentWindowGlobal;
    152    let actor = windowGlobal.getActor("AboutReader");
    153    actor.updateReaderButton(browser);
    154  }
    155 
    156  updateReaderButton(browser) {
    157    let tabBrowser = browser.getTabBrowser();
    158    if (!tabBrowser || browser != tabBrowser.selectedBrowser) {
    159      return;
    160    }
    161 
    162    let doc = browser.ownerGlobal.document;
    163    let button = doc.getElementById("reader-mode-button");
    164    let menuitem = doc.getElementById("menu_readerModeItem");
    165    let key = doc.getElementById("key_toggleReaderMode");
    166    if (this.isReaderMode()) {
    167      gAllActors.add(this);
    168 
    169      button.setAttribute("readeractive", true);
    170      button.hidden = false;
    171      doc.l10n.setAttributes(button, "reader-view-close-button");
    172 
    173      menuitem.hidden = false;
    174      doc.l10n.setAttributes(menuitem, "menu-view-close-readerview");
    175 
    176      key.removeAttribute("disabled");
    177 
    178      Services.obs.notifyObservers(null, "reader-mode-available");
    179    } else {
    180      button.removeAttribute("readeractive");
    181      button.hidden = !browser.isArticle;
    182      doc.l10n.setAttributes(button, "reader-view-enter-button");
    183 
    184      menuitem.hidden = !browser.isArticle;
    185      doc.l10n.setAttributes(menuitem, "menu-view-enter-readerview");
    186 
    187      key.toggleAttribute("disabled", !browser.isArticle);
    188 
    189      if (browser.isArticle) {
    190        Services.obs.notifyObservers(null, "reader-mode-available");
    191      }
    192    }
    193 
    194    if (!button.hidden) {
    195      lazy.PageActions.sendPlacedInUrlbarTrigger(button);
    196    }
    197  }
    198 
    199  static forceShowReaderIcon(browser) {
    200    browser.isArticle = true;
    201    AboutReaderParent.updateReaderButton(browser);
    202  }
    203 
    204  static toggleReaderMode(event) {
    205    let win = event.target.ownerGlobal;
    206    if (win.gBrowser) {
    207      let browser = win.gBrowser.selectedBrowser;
    208 
    209      let windowGlobal = browser.browsingContext.currentWindowGlobal;
    210      let actor = windowGlobal.getActor("AboutReader");
    211      if (actor) {
    212        if (actor.isReaderMode()) {
    213          gAllActors.delete(this);
    214        }
    215        actor.sendAsyncMessage("Reader:ToggleReaderMode", {});
    216      }
    217    }
    218  }
    219 
    220  hasReaderModeEntryAtOffset(url, offset) {
    221    if (Services.appinfo.sessionHistoryInParent) {
    222      let browsingContext = this.browsingContext;
    223      if (browsingContext.childSessionHistory.canGo(offset)) {
    224        let shistory = browsingContext.sessionHistory;
    225        let nextEntry = shistory.getEntryAtIndex(shistory.index + offset);
    226        let nextURL = nextEntry.URI.spec;
    227        return nextURL && (nextURL == url || !url);
    228      }
    229    }
    230 
    231    return false;
    232  }
    233 
    234  enterReaderMode(url) {
    235    let readerURL = "about:reader?url=" + encodeURIComponent(url);
    236    if (this.hasReaderModeEntryAtOffset(readerURL, +1)) {
    237      let browsingContext = this.browsingContext;
    238      browsingContext.childSessionHistory.go(+1);
    239      return;
    240    }
    241 
    242    this.sendAsyncMessage("Reader:EnterReaderMode", {});
    243  }
    244 
    245  leaveReaderMode() {
    246    let browsingContext = this.browsingContext;
    247    let url = browsingContext.currentWindowGlobal.documentURI.spec;
    248    let originalURL = lazy.ReaderMode.getOriginalUrl(url);
    249    if (this.hasReaderModeEntryAtOffset(originalURL, -1)) {
    250      browsingContext.childSessionHistory.go(-1);
    251      return;
    252    }
    253 
    254    this.sendAsyncMessage("Reader:LeaveReaderMode", {});
    255  }
    256 
    257  /**
    258   * Gets an article for a given URL. This method will download and parse a document.
    259   *
    260   * @param url The article URL.
    261   * @param browser The browser where the article is currently loaded.
    262   * @return {Promise<?object>}
    263   *   Resolves to the JS object representing the article, or null if no article
    264   *   is found.
    265   */
    266  async _getArticle(url) {
    267    return lazy.ReaderMode.downloadAndParseDocument(url).catch(e => {
    268      if (e && e.newURL) {
    269        // Pass up the error so we can navigate the browser in question to the new URL:
    270        throw e;
    271      }
    272      console.error("Error downloading and parsing document: ", e);
    273      return null;
    274    });
    275  }
    276 }