Bug 1472491: Part 5b - Add AboutReaderChild actor. r=jaws f=felipe draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 29 Jul 2018 19:51:00 -0700
changeset 828437 2957bdbba3cd2943e5438debe6fd6a34062a89ed
parent 828436 908934a90c5341bdfd16ad97f07477ab41f5dcd8
child 828438 b48ee61b778d9556c66495f454483ebd21c2ef76
push id118680
push usermaglione.k@gmail.com
push dateFri, 10 Aug 2018 23:04:22 +0000
reviewersjaws
bugs1472491
milestone63.0a1
Bug 1472491: Part 5b - Add AboutReaderChild actor. r=jaws f=felipe MozReview-Commit-ID: H2vZT2lim3L
browser/actors/AboutReaderChild.jsm
browser/actors/moz.build
browser/base/content/tab-content.js
browser/base/content/test/performance/browser_startup_content.js
browser/components/nsBrowserGlue.js
new file mode 100644
--- /dev/null
+++ b/browser/actors/AboutReaderChild.jsm
@@ -0,0 +1,150 @@
+/* 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/. */
+"use strict";
+
+var EXPORTED_SYMBOLS = ["AboutReaderChild"];
+
+ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
+
+ChromeUtils.defineModuleGetter(this, "AboutReader",
+                               "resource://gre/modules/AboutReader.jsm");
+ChromeUtils.defineModuleGetter(this, "ReaderMode",
+                               "resource://gre/modules/ReaderMode.jsm");
+
+class AboutReaderChild extends ActorChild {
+  constructor(mm) {
+    super(mm);
+
+    this._articlePromise = null;
+    this._isLeavingReaderableReaderMode = false;
+  }
+
+  receiveMessage(message) {
+    switch (message.name) {
+      case "Reader:ToggleReaderMode":
+        if (!this.isAboutReader) {
+          this._articlePromise = ReaderMode.parseDocument(this.content.document).catch(Cu.reportError);
+          ReaderMode.enterReaderMode(this.mm.docShell, this.content);
+        } else {
+          this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
+          ReaderMode.leaveReaderMode(this.mm.docShell, this.content);
+        }
+        break;
+
+      case "Reader:PushState":
+        this.updateReaderButton(!!(message.data && message.data.isArticle));
+        break;
+    }
+  }
+
+  get isAboutReader() {
+    if (!this.content) {
+      return false;
+    }
+    return this.content.document.documentURI.startsWith("about:reader");
+  }
+
+  get isReaderableAboutReader() {
+    return this.isAboutReader &&
+      !this.content.document.documentElement.dataset.isError;
+  }
+
+  handleEvent(aEvent) {
+    if (aEvent.originalTarget.defaultView != this.content) {
+      return;
+    }
+
+    switch (aEvent.type) {
+      case "AboutReaderContentLoaded":
+        if (!this.isAboutReader) {
+          return;
+        }
+
+        if (this.content.document.body) {
+          // Update the toolbar icon to show the "reader active" icon.
+          this.mm.sendAsyncMessage("Reader:UpdateReaderButton");
+          new AboutReader(this.mm, this.content, this._articlePromise);
+          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.mm.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode });
+        if (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.updateReaderButton();
+        }
+        break;
+      case "DOMContentLoaded":
+        this.updateReaderButton();
+        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 (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
+        !this.content || !(this.content.document instanceof this.content.HTMLDocument) ||
+        this.content.document.mozSyntheticDocument) {
+      return;
+    }
+
+    this.scheduleReadabilityCheckPostPaint(forceNonArticle);
+  }
+
+  cancelPotentialPendingReadabilityCheck() {
+    if (this._pendingReadabilityCheck) {
+      this.mm.removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
+      delete this._pendingReadabilityCheck;
+    }
+  }
+
+  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.mm.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.content got painted by checking if there
+    // are any painted rects.
+    if (!event.clientRects.length) {
+      return;
+    }
+
+    this.cancelPotentialPendingReadabilityCheck();
+    // Only send updates when there are articles; there's no point updating with
+    // |false| all the time.
+    if (ReaderMode.isProbablyReaderable(this.content.document)) {
+      this.mm.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
+    } else if (forceNonArticle) {
+      this.mm.sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
+    }
+  }
+}
--- a/browser/actors/moz.build
+++ b/browser/actors/moz.build
@@ -1,9 +1,10 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 FINAL_TARGET_FILES.actors += [
+    'AboutReaderChild.jsm',
     'BrowserTabChild.jsm',
 ]
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -9,20 +9,16 @@
 
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 ChromeUtils.defineModuleGetter(this, "E10SUtils",
   "resource://gre/modules/E10SUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "AboutReader",
-  "resource://gre/modules/AboutReader.jsm");
-ChromeUtils.defineModuleGetter(this, "ReaderMode",
-  "resource://gre/modules/ReaderMode.jsm");
 ChromeUtils.defineModuleGetter(this, "PageStyleHandler",
   "resource:///modules/PageStyleHandler.jsm");
 
 ChromeUtils.import("resource://gre/modules/ActorManagerChild.jsm");
 
 ActorManagerChild.attach(this, "browsers");
 
 // TabChildGlobal
@@ -55,161 +51,16 @@ let themeablePagesWhitelist = new Set([
 
 addEventListener("pageshow", function({ originalTarget }) {
   if (originalTarget.defaultView == content && themeablePagesWhitelist.has(content.document.documentURI)) {
     LightweightThemeChildHelper.listen(themeablePagesWhitelist);
     LightweightThemeChildHelper.update(chromeOuterWindowID, content);
   }
 }, false, true);
 
-var AboutReaderListener = {
-
-  _articlePromise: null,
-
-  _isLeavingReaderableReaderMode: false,
-
-  init() {
-    addEventListener("AboutReaderContentLoaded", this, false, true);
-    addEventListener("DOMContentLoaded", this, false);
-    addEventListener("pageshow", this, false);
-    addEventListener("pagehide", this, false);
-    addMessageListener("Reader:ToggleReaderMode", this);
-    addMessageListener("Reader:PushState", this);
-    this.init = null;
-  },
-
-  receiveMessage(message) {
-    switch (message.name) {
-      case "Reader:ToggleReaderMode":
-        if (!this.isAboutReader) {
-          this._articlePromise = ReaderMode.parseDocument(content.document).catch(Cu.reportError);
-          ReaderMode.enterReaderMode(docShell, content);
-        } else {
-          this._isLeavingReaderableReaderMode = this.isReaderableAboutReader;
-          ReaderMode.leaveReaderMode(docShell, content);
-        }
-        break;
-
-      case "Reader:PushState":
-        this.updateReaderButton(!!(message.data && message.data.isArticle));
-        break;
-    }
-  },
-
-  get isAboutReader() {
-    if (!content) {
-      return false;
-    }
-    return content.document.documentURI.startsWith("about:reader");
-  },
-
-  get isReaderableAboutReader() {
-    return this.isAboutReader &&
-      !content.document.documentElement.dataset.isError;
-  },
-
-  handleEvent(aEvent) {
-    if (aEvent.originalTarget.defaultView != content) {
-      return;
-    }
-
-    switch (aEvent.type) {
-      case "AboutReaderContentLoaded":
-        if (!this.isAboutReader) {
-          return;
-        }
-
-        if (content.document.body) {
-          // Update the toolbar icon to show the "reader active" icon.
-          sendAsyncMessage("Reader:UpdateReaderButton");
-          new AboutReader(global, content, this._articlePromise);
-          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.
-        sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: this._isLeavingReaderableReaderMode });
-        if (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.updateReaderButton();
-        }
-        break;
-      case "DOMContentLoaded":
-        this.updateReaderButton();
-        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 (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader ||
-        !content || !(content.document instanceof content.HTMLDocument) ||
-        content.document.mozSyntheticDocument) {
-      return;
-    }
-
-    this.scheduleReadabilityCheckPostPaint(forceNonArticle);
-  },
-
-  cancelPotentialPendingReadabilityCheck() {
-    if (this._pendingReadabilityCheck) {
-      removeEventListener("MozAfterPaint", this._pendingReadabilityCheck);
-      delete this._pendingReadabilityCheck;
-    }
-  },
-
-  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);
-    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 content got painted by checking if there
-    // are any painted rects.
-    if (!event.clientRects.length) {
-      return;
-    }
-
-    this.cancelPotentialPendingReadabilityCheck();
-    // Only send updates when there are articles; there's no point updating with
-    // |false| all the time.
-    if (ReaderMode.isProbablyReaderable(content.document)) {
-      sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: true });
-    } else if (forceNonArticle) {
-      sendAsyncMessage("Reader:UpdateReaderButton", { isArticle: false });
-    }
-  },
-};
-AboutReaderListener.init();
-
 
 var ContentSearchMediator = {
 
   whitelist: new Set([
     "about:home",
     "about:newtab",
     "about:welcome",
   ]),
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -43,16 +43,17 @@ const whitelist = {
     "resource:///modules/sessionstore/ContentSessionStore.jsm",
     "resource://gre/modules/sessionstore/SessionHistory.jsm",
 
     // Forms and passwords
     "resource://formautofill/FormAutofill.jsm",
     "resource://formautofill/FormAutofillContent.jsm",
 
     // Browser front-end
+    "resource:///modules/AboutReaderChild.jsm",
     "resource:///modules/BrowserTabChild.jsm",
     "resource:///modules/ContentLinkHandler.jsm",
     "resource:///modules/ContentMetaHandler.jsm",
     "resource:///modules/PageStyleHandler.jsm",
     "resource://gre/modules/ActorChild.jsm",
     "resource://gre/modules/ActorManagerChild.jsm",
     "resource://gre/modules/BrowserUtils.jsm",
     "resource://gre/modules/E10SUtils.jsm",
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -7,16 +7,33 @@ const XULNS = "http://www.mozilla.org/ke
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 
 ChromeUtils.defineModuleGetter(this, "ActorManagerParent",
                                "resource://gre/modules/ActorManagerParent.jsm");
 
 let ACTORS = {
+  AboutReader: {
+    child: {
+      module: "resource:///actors/AboutReaderChild.jsm",
+      group: "browsers",
+      events: {
+        "AboutReaderContentLoaded": {wantUntrusted: true},
+        "DOMContentLoaded": {},
+        "pageshow": {},
+        "pagehide": {},
+      },
+      messages: [
+        "Reader:ToggleReaderMode",
+        "Reader:PushState",
+      ],
+    },
+  },
+
   BrowserTab: {
     child: {
       module: "resource:///actors/BrowserTabChild.jsm",
       group: "browsers",
 
       events: {
         "DOMWindowCreated": {once: true},
         "MozAfterPaint": {once: true},