dArticle = gCachedArticles.get(message.data.url);
        gCachedArticles.delete(message.data.url);
        return cachedArticle;
      }
      case "Reader:FaviconRequest": {
        try {
          let preferredWidth = message.data.preferredWidth || 0;
          let uri = Services.io.newURI(message.data.url);

          let result = await lazy.PlacesUtils.favicons.getFaviconForPage(
            uri,
            preferredWidth
          );

          this.callListeners(message);
          return result && { url: uri.spec, faviconUrl: result.uri.spec };
        } catch (ex) {
          console.error(
            "Error requesting favicon URL for about:reader content: ",
            ex
          );
        }

        break;
      }

      case "Reader:UpdateReaderButton": {
        let browser = this.browsingContext.embedderElement;
        if (!browser) {
          return undefined;
        }

        if (message.data && message.data.isArticle !== undefined) {
          browser.isArticle = message.data.isArticle;
        }
        this.updateReaderButton(browser);
        this.callListeners(message);
        break;
      }

      case "RedirectTo": {
        gCachedArticles.set(message.data.newURL, message.data.article);
        // This is setup as a query so we can navigate the page after we've
        // cached the relevant info in the parent.
        return true;
      }

      default:
        this.callListeners(message);
        break;
    }

    return undefined;
  }

  static updateReaderButton(browser) {
    let windowGlobal = browser.browsingContext.currentWindowGlobal;
    let actor = windowGlobal.getActor("AboutReader");
    actor.updateReaderButton(browser);
  }

  updateReaderButton(browser) {
    let tabBrowser = browser.getTabBrowser();
    if (!tabBrowser || browser != tabBrowser.selectedBrowser) {
      return;
    }

    let doc = browser.ownerGlobal.document;
    let button = doc.getElementById("reader-mode-button");
    let menuitem = doc.getElementById("menu_readerModeItem");
    let key = doc.getElementById("key_toggleReaderMode");
    if (this.isReaderMode()) {
      gAllActors.add(this);

      button.setAttribute("readeractive", true);
      button.hidden = false;
      doc.l10n.setAttributes(button, "reader-view-close-button");

      menuitem.hidden = false;
      doc.l10n.setAttributes(menuitem, "menu-view-close-readerview");

      key.setAttribute("disabled", false);

      Services.obs.notifyObservers(null, "reader-mode-available");
    } else {
      button.removeAttribute("readeractive");
      button.hidden = !browser.isArticle;
      doc.l10n.setAttributes(button, "reader-view-enter-button");

      menuitem.hidden = !browser.isArticle;
      doc.l10n.setAttributes(menuitem, "menu-view-enter-readerview");

      key.setAttribute("disabled", !browser.isArticle);

      if (browser.isArticle) {
        Services.obs.notifyObservers(null, "reader-mode-available");
      }
    }

    if (!button.hidden) {
      lazy.PageActions.sendPlacedInUrlbarTrigger(button);
    }
  }

  static forceShowReaderIcon(browser) {
    browser.isArticle = true;
    AboutReaderParent.updateReaderButton(browser);
  }

  static toggleReaderMode(event) {
    let win = event.target.ownerGlobal;
    if (win.gBrowser) {
      let browser = win.gBrowser.selectedBrowser;

      let windowGlobal = browser.browsingContext.currentWindowGlobal;
      let actor = windowGlobal.getActor("AboutReader");
      if (actor) {
        if (actor.isReaderMode()) {
          gAllActors.delete(this);
        }
        actor.sendAsyncMessage("Reader:ToggleReaderMode", {});
      }
    }
  }

  hasReaderModeEntryAtOffset(url, offset) {
    if (Services.appinfo.sessionHistoryInParent) {
      let browsingContext = this.browsingContext;
      if (browsingContext.childSessionHistory.canGo(offset)) {
        let shistory = browsingContext.sessionHistory;
        let nextEntry = shistory.getEntryAtIndex(shistory.index + offset);
        let nextURL = nextEntry.URI.spec;
        return nextURL && (nextURL == url || !url);
      }
    }

    return false;
  }

  enterReaderMode(url) {
    let readerURL = "about:reader?url=" + encodeURIComponent(url);
    if (this.hasReaderModeEntryAtOffset(readerURL, +1)) {
      let browsingContext = this.browsingContext;
      browsingContext.childSessionHistory.go(+1);
      return;
    }

    this.sendAsyncMessage("Reader:EnterReaderMode", {});
  }

  leaveReaderMode() {
    let browsingContext = this.browsingContext;
    let url = browsingContext.currentWindowGlobal.documentURI.spec;
    let originalURL = lazy.ReaderMode.getOriginalUrl(url);
    if (this.hasReaderModeEntryAtOffset(originalURL, -1)) {
      browsingContext.childSessionHistory.go(-1);
      return;
    }

    this.sendAsyncMessage("Reader:LeaveReaderMode", {});
  }

  /**
   * Gets an article for a given URL. This method will download and parse a document.
   *
   * @param url The article URL.
   * @param browser The browser where the article is currently loaded.
   * @return {Promise<?object>}
   *   Resolves to the JS object representing the article, or null if no article
   *   is found.
   */
  async _getArticle(url) {
    return lazy.ReaderMode.downloadAndParseDocument(url).catch(e => {
      if (e && e.newURL) {
        // Pass up the error so we can navigate the browser in question to the new URL:
        throw e;
      }
      console.error("Error downloading and parsing document: ", e);
      return null;
    });
  }
}
PK