Back out 3 changesets (bug 1111142) for having a startling perf impact
authorPhil Ringnalda <philringnalda@gmail.com>
Fri, 02 Jan 2015 22:34:15 -0800
changeset 247685 156a06b49da823e2d71cf6b34446ee1f07f8bd37
parent 247684 918ab351ea214253a5b8c2289e275afbfd9f776c
child 247686 b478b0c48bccb6cd5478be8b17ec4c23df7bfc98
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1111142
milestone37.0a1
backs out9b62d0e8b412dd289edefdb45d2c8342dd0d8e5a
e66b9aa4b22c50b09f6c5cccdc22c056ea49d4f4
6b480b80299ac822da975ad74e2c1fe83d64d71f
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Back out 3 changesets (bug 1111142) for having a startling perf impact CLOSED TREE Backed out changeset 9b62d0e8b412 (bug 1111142) Backed out changeset e66b9aa4b22c (bug 1111142) Backed out changeset 6b480b80299a (bug 1111142)
mobile/android/chrome/content/Reader.js
mobile/android/chrome/content/browser.js
mobile/android/chrome/content/content.js
mobile/android/chrome/jar.mn
toolkit/components/reader/AboutReader.jsm
toolkit/components/reader/ReaderMode.jsm
toolkit/components/reader/content/aboutReader.html
toolkit/components/reader/content/aboutReader.js
toolkit/components/reader/moz.build
--- a/mobile/android/chrome/content/Reader.js
+++ b/mobile/android/chrome/content/Reader.js
@@ -1,155 +1,34 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 /* 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";
 
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
+const { utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/ReaderMode.jsm");
 
 let Reader = {
   // These values should match those defined in BrowserContract.java.
   STATUS_UNFETCHED: 0,
   STATUS_FETCH_FAILED_TEMPORARY: 1,
   STATUS_FETCH_FAILED_PERMANENT: 2,
   STATUS_FETCH_FAILED_UNSUPPORTED_FORMAT: 3,
   STATUS_FETCHED_ARTICLE: 4,
 
-  MESSAGES: [
-    "Reader:AddToList",
-    "Reader:ArticleGet",
-    "Reader:FaviconRequest",
-    "Reader:ListStatusRequest",
-    "Reader:RemoveFromList",
-    "Reader:Share",
-    "Reader:ShowToast",
-    "Reader:ToolbarVisibility",
-    "Reader:SystemUIVisibility",
-    "Reader:UpdateIsArticle",
-  ],
-
-  init: function() {
-    for (let msg of this.MESSAGES) {
-      window.messageManager.addMessageListener(msg, this);
-    }
-
-    Services.obs.addObserver(this, "Reader:Added", false);
-    Services.obs.addObserver(this, "Reader:Removed", false);
-    Services.obs.addObserver(this, "Gesture:DoubleTap", false);
-  },
-
-  observe: function Reader_observe(aMessage, aTopic, aData) {
-    switch (aTopic) {
-      case "Reader:Added": {
-        window.messageManager.broadcastAsyncMessage("Reader:Added", { url: aData });
-        break;
-      }
-      case "Reader:Removed": {
-        let uri = Services.io.newURI(aData, null, null);
-        ReaderMode.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e));
-
-        window.messageManager.broadcastAsyncMessage("Reader:Removed", { url: aData });
-        break;
-      }
-      case "Gesture:DoubleTap": {
-        // XXX: Ideally, we would just do this all with web APIs in AboutReader.jsm.
-        let win = BrowserApp.selectedBrowser.contentWindow;
-        let scrollBy;
-        // Arbitrary choice of innerHeight (50) to give some context after scroll.
-        if (JSON.parse(aData).y < (win.innerHeight / 2)) {
-          scrollBy = - win.innerHeight + 50;
-        } else {
-          scrollBy = win.innerHeight - 50;
-        }
-
-        let viewport = BrowserApp.selectedTab.getViewport();
-        let newY = Math.min(Math.max(viewport.cssY + scrollBy, viewport.cssPageTop), viewport.cssPageBottom);
-        let newRect = new Rect(viewport.cssX, newY, viewport.cssWidth, viewport.cssHeight);
-        ZoomHelper.zoomToRect(newRect, -1);
-        break;
-      }
-    }
-  },
-
-  receiveMessage: function(message) {
-    switch (message.name) {
-      case "Reader:AddToList":
-        this.addArticleToReadingList(message.data.article);
-        break;
+  get isEnabledForParseOnLoad() {
+    delete this.isEnabledForParseOnLoad;
 
-      case "Reader:ArticleGet":
-        this._getArticle(message.data.url, message.target).then((article) => {
-          message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
-        });
-        break;
-
-      case "Reader:FaviconRequest": {
-        let observer = (s, t, d) => {
-          Services.obs.removeObserver(observer, "Reader:FaviconReturn", false);
-          message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", JSON.parse(d));
-        };
-        Services.obs.addObserver(observer, "Reader:FaviconReturn", false);
-        Messaging.sendRequest({
-          type: "Reader:FaviconRequest",
-          url: message.data.url
-        });
-        break;
-      }
-
-      case "Reader:ListStatusRequest":
-        Messaging.sendRequestForResult({
-          type: "Reader:ListStatusRequest",
-          url: message.data.url
-        }).then((data) => {
-          message.target.messageManager.sendAsyncMessage("Reader:ListStatusData", JSON.parse(data));
-        });
-        break;
+    // Listen for future pref changes.
+    Services.prefs.addObserver("reader.parse-on-load.", this, false);
 
-      case "Reader:RemoveFromList":
-        Messaging.sendRequest({
-          type: "Reader:RemoveFromList",
-          url: message.data.url
-        });
-        break;
-
-      case "Reader:Share":
-        Messaging.sendRequest({
-          type: "Reader:Share",
-          url: message.data.url,
-          title: message.data.title
-        });
-        break;
-
-      case "Reader:ShowToast":
-        NativeWindow.toast.show(message.data.toast, "short");
-        break;
-
-      case "Reader:SystemUIVisibility":
-        Messaging.sendRequest({
-          type: "SystemUI:Visibility",
-          visible: message.data.visible
-        });
-        break;
-
-      case "Reader:ToolbarVisibility":
-        Messaging.sendRequest({
-          type: "BrowserToolbar:Visibility",
-          visible: message.data.visible
-        });
-        break;
-
-      case "Reader:UpdateIsArticle": {
-        let tab = BrowserApp.getTabForBrowser(message.target);
-        tab.isArticle = message.data.isArticle;
-        this.updatePageAction(tab);
-        break;
-      }
-    }
+    return this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
   },
 
   pageAction: {
     readerModeCallback: function(tabID) {
       Messaging.sendRequest({
         type: "Reader:Toggle",
         tabID: tabID
       });
@@ -183,35 +62,53 @@ let Reader = {
       // not track background reader viewers.
       UITelemetry.startSession("reader.1", null);
       return;
     }
 
     // Only stop a reader session if the foreground viewer is not visible.
     UITelemetry.stopSession("reader.1", "", null);
 
-    if (tab.isArticle) {
+    if (tab.savedArticle) {
       this.pageAction.id = PageActions.add({
         title: Strings.browser.GetStringFromName("readerMode.enter"),
         icon: "drawable://reader",
         clickCallback: () => this.pageAction.readerModeCallback(tab.id),
         longClickCallback: () => this.pageAction.readerModeActiveCallback(tab.id),
         important: true
       });
     }
   },
 
+  observe: function(aMessage, aTopic, aData) {
+    switch(aTopic) {
+      case "Reader:Removed": {
+        let uri = Services.io.newURI(aData, null, null);
+        ReaderMode.removeArticleFromCache(uri).catch(e => Cu.reportError("Error removing article from cache: " + e));
+        break;
+      }
+
+      case "nsPref:changed":
+        if (aData.startsWith("reader.parse-on-load.")) {
+          this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
+        }
+        break;
+    }
+  },
+
   _addTabToReadingList: Task.async(function* (tabID) {
     let tab = BrowserApp.getTabForId(tabID);
     if (!tab) {
       throw new Error("Can't add tab to reading list because no tab found for ID: " + tabID);
     }
 
-    let urlWithoutRef = tab.browser.currentURI.specIgnoringRef;
-    let article = yield this._getArticle(urlWithoutRef, tab.browser).catch(e => {
+    let uri = tab.browser.currentURI;
+    let urlWithoutRef = uri.specIgnoringRef;
+
+    let article = yield this.getArticle(urlWithoutRef, tabID).catch(e => {
       Cu.reportError("Error getting article for tab: " + e);
       return null;
     });
     if (!article) {
       // If there was a problem getting the article, just store the
       // URL and title from the tab.
       article = {
         url: urlWithoutRef,
@@ -238,56 +135,55 @@ let Reader = {
       length: article.length || 0,
       excerpt: article.excerpt || "",
       status: article.status,
     });
 
     ReaderMode.storeArticleInCache(article).catch(e => Cu.reportError("Error storing article in cache: " + e));
   },
 
+  _getStateForParseOnLoad: function () {
+    let isEnabled = Services.prefs.getBoolPref("reader.parse-on-load.enabled");
+    let isForceEnabled = Services.prefs.getBoolPref("reader.parse-on-load.force-enabled");
+    // For low-memory devices, don't allow reader mode since it takes up a lot of memory.
+    // See https://bugzilla.mozilla.org/show_bug.cgi?id=792603 for details.
+    return isForceEnabled || (isEnabled && !BrowserApp.isOnLowMemoryPlatform);
+  },
+
   /**
    * Gets an article for a given URL. This method will download and parse a document
    * if it does not find the article in the tab data or the cache.
    *
    * @param url The article URL.
-   * @param browser The browser where the article is currently loaded.
+   * @param tabId (optional) The id of the tab where we can look for a saved article.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
-  _getArticle: Task.async(function* (url, browser) {
-    // First, look for a saved article.
-    let article = yield this._getSavedArticle(browser);
-    if (article && article.url == url) {
-      return article;
+  getArticle: Task.async(function* (url, tabId) {
+    // First, look for an article object stored on the tab.
+    let tab = BrowserApp.getTabForId(tabId);
+    if (tab) {
+      let article = tab.savedArticle;
+      if (article && article.url == url) {
+        return article;
+      }
     }
 
     // Next, try to find a parsed article in the cache.
     let uri = Services.io.newURI(url, null, null);
-    article = yield ReaderMode.getArticleFromCache(uri);
+    let article = yield ReaderMode.getArticleFromCache(uri);
     if (article) {
       return article;
     }
 
     // Article hasn't been found in the cache, we need to
     // download the page and parse the article out of it.
     return yield ReaderMode.downloadAndParseDocument(url);
   }),
 
-  _getSavedArticle: function(browser) {
-    return new Promise((resolve, reject) => {
-      let mm = browser.messageManager;
-      let listener = (message) => {
-        mm.removeMessageListener("Reader:SavedArticleData", listener);
-        resolve(message.data.article);
-      };
-      mm.addMessageListener("Reader:SavedArticleData", listener);
-      mm.sendAsyncMessage("Reader:SavedArticleGet");
-    });
-  },
-
   /**
    * Migrates old indexedDB reader mode cache to new JSON cache.
    */
   migrateCache: Task.async(function* () {
     let cacheDB = yield new Promise((resolve, reject) => {
       let request = window.indexedDB.open("about:reader", 1);
       request.onsuccess = event => resolve(event.target.result);
       request.onerror = event => reject(request.error);
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -104,23 +104,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/PermissionsUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
                                   "resource://gre/modules/SharedPreferences.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Notifications",
                                   "resource://gre/modules/Notifications.jsm");
 
-// XXX: Make this into a module?
-Services.scriptloader.loadSubScript("chrome://browser/content/Reader.js", this);
+XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
+                                  "resource://gre/modules/ReaderMode.jsm");
 
 // Lazily-loaded browser scripts:
 [
   ["SelectHelper", "chrome://browser/content/SelectHelper.js"],
   ["InputWidgetHelper", "chrome://browser/content/InputWidgetHelper.js"],
+  ["AboutReader", "chrome://global/content/reader/aboutReader.js"],
   ["MasterPassword", "chrome://browser/content/MasterPassword.js"],
   ["PluginHelper", "chrome://browser/content/PluginHelper.js"],
   ["OfflineApps", "chrome://browser/content/OfflineApps.js"],
   ["Linkifier", "chrome://browser/content/Linkify.js"],
   ["ZoomHelper", "chrome://browser/content/ZoomHelper.js"],
   ["CastingApps", "chrome://browser/content/CastingApps.js"],
 #ifdef NIGHTLY_BUILD
   ["WebcompatReporter", "chrome://browser/content/WebcompatReporter.js"],
@@ -141,16 +142,17 @@ Services.scriptloader.loadSubScript("chr
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
+  ["Reader", ["Reader:Removed"], "chrome://browser/content/Reader.js"],
 ].forEach(function (aScript) {
   let [name, notifications, script] = aScript;
   XPCOMUtils.defineLazyGetter(window, name, function() {
     let sandbox = {};
     Services.scriptloader.loadSubScript(script, sandbox);
     return sandbox[name];
   });
   let observer = (s, t, d) => {
@@ -442,17 +444,16 @@ var BrowserApp = {
     Distribution.init();
     Tabs.init();
 #ifdef ACCESSIBILITY
     AccessFu.attach(window);
 #endif
 #ifdef NIGHTLY_BUILD
     ShumwayUtils.init();
 #endif
-    Reader.init();
 
     let url = null;
     let pinned = false;
     if ("arguments" in window) {
       if (window.arguments[0])
         url = window.arguments[0];
       if (window.arguments[1])
         gScreenWidth = window.arguments[1];
@@ -494,18 +495,16 @@ var BrowserApp = {
       // Set the tiles click observer only if tiles reporting is enabled (that
       // is, a report URL is set in prefs).
       gTilesReportURL = Services.prefs.getCharPref("browser.tiles.reportURL");
       Services.obs.addObserver(this, "Tiles:Click", false);
     } catch (e) {
       // Tiles reporting is disabled.
     }
 
-    window.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
-
     // Notify Java that Gecko has loaded.
     Messaging.sendRequest({ type: "Gecko:Ready" });
   },
 
   get _startupStatus() {
     delete this._startupStatus;
 
     let savedMilestone = null;
@@ -3220,17 +3219,17 @@ function Tab(aURL, aParams) {
   this.viewportMeasureCallback = null;
   this.lastPageSizeAfterViewportRemeasure = { width: 0, height: 0 };
   this.contentDocumentIsDisplayed = true;
   this.pluginDoorhangerTimeout = null;
   this.shouldShowPluginDoorhanger = true;
   this.clickToPlayPluginsActivated = false;
   this.desktopMode = false;
   this.originalURI = null;
-  this.isArticle = false;
+  this.savedArticle = null;
   this.hasTouchListener = false;
   this.browserWidth = 0;
   this.browserHeight = 0;
   this.tilesData = null;
 
   this.create(aURL, aParams);
 }
 
@@ -3561,16 +3560,17 @@ Tab.prototype = {
 
     // Make sure the previously selected panel remains selected. The selected panel of a deck is
     // not stable when panels are removed.
     let selectedPanel = BrowserApp.deck.selectedPanel;
     BrowserApp.deck.removeChild(this.browser);
     BrowserApp.deck.selectedPanel = selectedPanel;
 
     this.browser = null;
+    this.savedArticle = null;
   },
 
   // This should be called to update the browser when the tab gets selected/unselected
   setActive: function setActive(aActive) {
     if (!this.browser || !this.browser.docShell)
       return;
 
     this.lastTouchedAt = Date.now();
@@ -3956,16 +3956,24 @@ Tab.prototype = {
             this.browser.removeEventListener("click", ErrorPageEventHandler, true);
             this.browser.removeEventListener("pagehide", listener, true);
           }.bind(this);
 
           this.browser.addEventListener("pagehide", listener, true);
         }
 
         if (docURI.startsWith("about:reader")) {
+          // During browser restart / recovery, duplicate "DOMContentLoaded" messages are received here
+          // For the visible tab ... where more than one tab is being reloaded, the inital "DOMContentLoaded"
+          // Message can be received before the document body is available ... so we avoid instantiating an
+          // AboutReader object, expecting that an eventual valid message will follow.
+          let contentDocument = this.browser.contentDocument;
+          if (contentDocument.body) {
+            new AboutReader(contentDocument, this.browser.contentWindow);
+          }
           // Update the page action to show the "reader active" icon.
           Reader.updatePageAction(this);
         }
 
         break;
       }
 
       case "DOMFormHasPassword": {
@@ -4260,16 +4268,41 @@ Tab.prototype = {
                 type: "Robocop:TilesResponse",
                 response: this.response
               });
             }
           };
           xhr.send(this.tilesData);
           this.tilesData = null;
         }
+
+        // Don't try to parse the document if reader mode is disabled,
+        // or if the page is already in reader mode.
+        if (!Reader.isEnabledForParseOnLoad || this.readerActive) {
+          return;
+        }
+
+        // Reader mode is disabled until proven enabled.
+        this.savedArticle = null;
+        Reader.updatePageAction(this);
+
+        // Once document is fully loaded, parse it
+        ReaderMode.parseDocumentFromBrowser(this.browser).then(article => {
+          // The loaded page may have changed while we were parsing the document. 
+          // Make sure we've got the current one.
+          let currentURL = this.browser.currentURI.specIgnoringRef;
+
+          // Do nothing if there's no article or the page in this tab has changed.
+          if (article == null || (article.url != currentURL)) {
+            return;
+          }
+
+          this.savedArticle = article;
+          Reader.updatePageAction(this);
+        }).catch(e => Cu.reportError("Error parsing document from tab: " + e));
       }
     }
   },
 
   onStateChange: function(aWebProgress, aRequest, aStateFlags, aStatus) {
     let contentWin = aWebProgress.DOMWindow;
     if (contentWin != contentWin.top)
         return;
deleted file mode 100644
--- a/mobile/android/chrome/content/content.js
+++ /dev/null
@@ -1,86 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* 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/. */
-
-let { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AboutReader", "resource://gre/modules/AboutReader.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
-
-let dump = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.d.bind(null, "Content");
-
-let global = this;
-
-let AboutReaderListener = {
-  _savedArticle: null,
-
-  init: function() {
-    addEventListener("AboutReaderContentLoaded", this, false, true);
-    addEventListener("pageshow", this, false);
-    addMessageListener("Reader:SavedArticleGet", this);
-  },
-
-  receiveMessage: function(message) {
-    switch (message.name) {
-      case "Reader:SavedArticleGet":
-        sendAsyncMessage("Reader:SavedArticleData", { article: this._savedArticle });
-        break;
-    }
-  },
-
-  get isAboutReader() {
-    return content.document.documentURI.startsWith("about:reader");
-  },
-
-  handleEvent: function(event) {
-    if (event.originalTarget.defaultView != content) {
-      return;
-    }
-
-    switch (event.type) {
-      case "AboutReaderContentLoaded":
-        if (!this.isAboutReader) {
-          return;
-        }
-
-        // If we are restoring multiple reader mode tabs during session restore, duplicate "DOMContentLoaded"
-        // events may be fired for the visible tab. The inital "DOMContentLoaded" may be received before the
-        // document body is available, so we avoid instantiating an AboutReader object, expecting that a
-        // valid message will follow. See bug 925983.
-        if (content.document.body) {
-          new AboutReader(global, content);
-        }
-        break;
-
-      case "pageshow":
-        if (!ReaderMode.isEnabledForParseOnLoad || this.isAboutReader) {
-          return;
-        }
-
-        // Reader mode is disabled until proven enabled.
-        this._savedArticle = null;
-        sendAsyncMessage("Reader:UpdateIsArticle", { isArticle: false });
-
-        ReaderMode.parseDocument(content.document).then(article => {
-          // The loaded page may have changed while we were parsing the document.
-          // Make sure we've got the current one.
-          let currentURL = Services.io.newURI(content.document.documentURI, null, null).specIgnoringRef;
-
-          // Do nothing if there's no article or the page in this tab has changed.
-          if (article == null || (article.url != currentURL)) {
-            return;
-          }
-
-          this._savedArticle = article;
-          sendAsyncMessage("Reader:UpdateIsArticle", { isArticle: true });
-
-        }).catch(e => Cu.reportError("Error parsing document: " + e));
-        break;
-    }
-  }
-};
-AboutReaderListener.init();
--- a/mobile/android/chrome/jar.mn
+++ b/mobile/android/chrome/jar.mn
@@ -5,17 +5,16 @@
 
 
 chrome.jar:
 % content browser %content/ contentaccessible=yes
 
 * content/about.xhtml                  (content/about.xhtml)
   content/config.xhtml                 (content/config.xhtml)
   content/config.js                    (content/config.js)
-  content/content.js                   (content/content.js)
   content/aboutAddons.xhtml            (content/aboutAddons.xhtml)
   content/aboutAddons.js               (content/aboutAddons.js)
   content/aboutCertError.xhtml         (content/aboutCertError.xhtml)
   content/aboutDownloads.xhtml         (content/aboutDownloads.xhtml)
   content/aboutDownloads.js            (content/aboutDownloads.js)
   content/aboutFeedback.xhtml          (content/aboutFeedback.xhtml)
   content/aboutFeedback.js             (content/aboutFeedback.js)
   content/aboutPrivateBrowsing.xhtml   (content/aboutPrivateBrowsing.xhtml)
deleted file mode 100644
--- a/toolkit/components/reader/AboutReader.jsm
+++ /dev/null
@@ -1,792 +0,0 @@
-/* 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";
-
-let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
-
-this.EXPORTED_SYMBOLS = [ "AboutReader" ];
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "Rect", "resource://gre/modules/Geometry.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry", "resource://gre/modules/UITelemetry.jsm");
-
-function dump(s) {
-  Services.console.logStringMessage("AboutReader: " + s);
-}
-
-let gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
-
-let AboutReader = function(mm, win) {
-  let doc = win.document;
-
-  this._mm = mm;
-  this._mm.addMessageListener("Reader:Added", this);
-  this._mm.addMessageListener("Reader:Removed", this);
-
-  this._docRef = Cu.getWeakReference(doc);
-  this._winRef = Cu.getWeakReference(win);
-
-  this._article = null;
-
-  this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
-  this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
-  this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
-  this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
-  this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
-  this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
-  this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
-
-  this._toolbarEnabled = false;
-
-  this._scrollOffset = win.pageYOffset;
-
-  let body = doc.body;
-  body.addEventListener("touchstart", this, false);
-  body.addEventListener("click", this, false);
-
-  win.addEventListener("unload", this, false);
-  win.addEventListener("scroll", this, false);
-  win.addEventListener("popstate", this, false);
-  win.addEventListener("resize", this, false);
-
-  doc.addEventListener("visibilitychange", this, false);
-
-  this._setupAllDropdowns();
-  this._setupButton("toggle-button", this._onReaderToggle.bind(this));
-  this._setupButton("share-button", this._onShare.bind(this));
-
-  let colorSchemeOptions = [
-    { name: gStrings.GetStringFromName("aboutReader.colorSchemeDark"),
-      value: "dark"},
-    { name: gStrings.GetStringFromName("aboutReader.colorSchemeLight"),
-      value: "light"},
-    { name: gStrings.GetStringFromName("aboutReader.colorSchemeAuto"),
-      value: "auto"}
-  ];
-
-  let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
-  this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
-  this._setColorSchemePref(colorScheme);
-
-  let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
-  let fontTypeOptions = [
-    { name: fontTypeSample,
-      description: gStrings.GetStringFromName("aboutReader.fontTypeSerif"),
-      value: "serif",
-      linkClass: "serif" },
-    { name: fontTypeSample,
-      description: gStrings.GetStringFromName("aboutReader.fontTypeSansSerif"),
-      value: "sans-serif",
-      linkClass: "sans-serif"
-    },
-  ];
-
-  let fontType = Services.prefs.getCharPref("reader.font_type");
-  this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
-  this._setFontType(fontType);
-
-  let fontSizeSample = gStrings.GetStringFromName("aboutReader.fontSizeSample");
-  let fontSizeOptions = [
-    { name: fontSizeSample,
-      value: 1,
-      linkClass: "font-size1-sample" },
-    { name: fontSizeSample,
-      value: 2,
-      linkClass: "font-size2-sample" },
-    { name: fontSizeSample,
-      value: 3,
-      linkClass: "font-size3-sample" },
-    { name: fontSizeSample,
-      value: 4,
-      linkClass: "font-size4-sample" },
-    { name: fontSizeSample,
-      value: 5,
-      linkClass: "font-size5-sample" }
-  ];
-
-  let fontSize = Services.prefs.getIntPref("reader.font_size");
-  this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
-  this._setFontSize(fontSize);
-
-  let queryArgs = this._decodeQueryString(win.location.href);
-
-  // Track status of reader toolbar add/remove toggle button
-  this._isReadingListItem = -1;
-  this._updateToggleButton();
-
-  this._loadArticle(queryArgs.url);
-}
-
-AboutReader.prototype = {
-  _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
-                          ".content p > a:only-child > img:only-child, " +
-                          ".content .wp-caption img, " +
-                          ".content figure img",
-
-  get _doc() {
-    return this._docRef.get();
-  },
-
-  get _win() {
-    return this._winRef.get();
-  },
-
-  get _headerElement() {
-    return this._headerElementRef.get();
-  },
-
-  get _domainElement() {
-    return this._domainElementRef.get();
-  },
-
-  get _titleElement() {
-    return this._titleElementRef.get();
-  },
-
-  get _creditsElement() {
-    return this._creditsElementRef.get();
-  },
-
-  get _contentElement() {
-    return this._contentElementRef.get();
-  },
-
-  get _toolbarElement() {
-    return this._toolbarElementRef.get();
-  },
-
-  get _messageElement() {
-    return this._messageElementRef.get();
-  },
-
-  receiveMessage: function (message) {
-    switch (message.name) {
-      case "Reader:Added": {
-        // Page can be added by long-press pageAction, or by tap on banner icon.
-        if (message.data.url == this._article.url) {
-          if (this._isReadingListItem != 1) {
-            this._isReadingListItem = 1;
-            this._updateToggleButton();
-          }
-        }
-        break;
-      }
-      case "Reader:Removed": {
-        if (message.data.url == this._article.url) {
-          if (this._isReadingListItem != 0) {
-            this._isReadingListItem = 0;
-            this._updateToggleButton();
-          }
-        }
-        break;
-      }
-    }
-  },
-
-  handleEvent: function Reader_handleEvent(aEvent) {
-    if (!aEvent.isTrusted)
-      return;
-
-    switch (aEvent.type) {
-      case "click":
-        // XXX: Don't toggle the toolbar on double click. (See the "Gesture:DoubleTap" handler in Reader.js)
-        this._toggleToolbarVisibility();
-        break;
-      case "scroll":
-        let isScrollingUp = this._scrollOffset > aEvent.pageY;
-        this._setToolbarVisibility(isScrollingUp);
-        this._scrollOffset = aEvent.pageY;
-        break;
-      case "popstate":
-        if (!aEvent.state)
-          this._closeAllDropdowns();
-        break;
-      case "resize":
-        this._updateImageMargins();
-        break;
-
-      case "devicelight":
-        this._handleDeviceLight(aEvent.value);
-        break;
-
-      case "visibilitychange":
-        this._handleVisibilityChange();
-        break;
-
-      case "unload":
-        this._mm.removeMessageListener("Reader:Added", this);
-        this._mm.removeMessageListener("Reader:Removed", this);
-        break;
-    }
-  },
-
-  _updateToggleButton: function Reader_updateToggleButton() {
-    let classes = this._doc.getElementById("toggle-button").classList;
-
-    if (this._isReadingListItem == 1) {
-      classes.add("on");
-    } else {
-      classes.remove("on");
-    }
-  },
-
-  _requestReadingListStatus: function Reader_requestReadingListStatus() {
-    let handleListStatusData = (message) => {
-      this._mm.removeMessageListener("Reader:ListStatusData", handleListStatusData);
-
-      let args = message.data;
-      if (args.url == this._article.url) {
-        if (this._isReadingListItem != args.inReadingList) {
-          let isInitialStateChange = (this._isReadingListItem == -1);
-          this._isReadingListItem = args.inReadingList;
-          this._updateToggleButton();
-
-          // Display the toolbar when all its initial component states are known
-          if (isInitialStateChange) {
-            this._setToolbarVisibility(true);
-          }
-        }
-      }
-    };
-
-    this._mm.addMessageListener("Reader:ListStatusData", handleListStatusData);
-    this._mm.sendAsyncMessage("Reader:ListStatusRequest", { url: this._article.url });
-  },
-
-  _onReaderToggle: function Reader_onToggle() {
-    if (!this._article)
-      return;
-
-    if (this._isReadingListItem == 0) {
-      this._mm.sendAsyncMessage("Reader:AddToList", { article: this._article });
-      UITelemetry.addEvent("save.1", "button", null, "reader");
-    } else {
-      this._mm.sendAsyncMessage("Reader:RemoveFromList", { url: this._article.url });
-      UITelemetry.addEvent("unsave.1", "button", null, "reader");
-    }
-  },
-
-  _onShare: function Reader_onShare() {
-    if (!this._article)
-      return;
-
-    this._mm.sendAsyncMessage("Reader:Share", {
-      url: this._article.url,
-      title: this._article.title
-    });
-    UITelemetry.addEvent("share.1", "list", null);
-  },
-
-  _setFontSize: function Reader_setFontSize(newFontSize) {
-    let bodyClasses = this._doc.body.classList;
-
-    if (this._fontSize > 0)
-      bodyClasses.remove("font-size" + this._fontSize);
-
-    this._fontSize = newFontSize;
-    bodyClasses.add("font-size" + this._fontSize);
-
-    Services.prefs.setIntPref("reader.font_size", this._fontSize);
-  },
-
-  _handleDeviceLight: function Reader_handleDeviceLight(newLux) {
-    // Desired size of the this._luxValues array.
-    let luxValuesSize = 10;
-    // Add new lux value at the front of the array.
-    this._luxValues.unshift(newLux);
-    // Add new lux value to this._totalLux for averaging later.
-    this._totalLux += newLux;
-
-    // Don't update when length of array is less than luxValuesSize except when it is 1.
-    if (this._luxValues.length < luxValuesSize) {
-      // Use the first lux value to set the color scheme until our array equals luxValuesSize.
-      if (this._luxValues.length == 1) {
-        this._updateColorScheme(newLux);
-      }
-      return;
-    }
-    // Holds the average of the lux values collected in this._luxValues.
-    let averageLuxValue = this._totalLux/luxValuesSize;
-
-    this._updateColorScheme(averageLuxValue);
-    // Pop the oldest value off the array.
-    let oldLux = this._luxValues.pop();
-    // Subtract oldLux since it has been discarded from the array.
-    this._totalLux -= oldLux;
-  },
-
-  _handleVisibilityChange: function Reader_handleVisibilityChange() {
-    let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
-    if (colorScheme != "auto") {
-      return;
-    }
-
-    // Turn off the ambient light sensor if the page is hidden
-    this._enableAmbientLighting(!this._doc.hidden);
-  },
-
-  // Setup or teardown the ambient light tracking system.
-  _enableAmbientLighting: function Reader_enableAmbientLighting(enable) {
-    if (enable) {
-      this._win.addEventListener("devicelight", this, false);
-      this._luxValues = [];
-      this._totalLux = 0;
-    } else {
-      this._win.removeEventListener("devicelight", this, false);
-      delete this._luxValues;
-      delete this._totalLux;
-    }
-  },
-
-  _updateColorScheme: function Reader_updateColorScheme(luxValue) {
-    // Upper bound value for "dark" color scheme beyond which it changes to "light".
-    let upperBoundDark = 50;
-    // Lower bound value for "light" color scheme beyond which it changes to "dark".
-    let lowerBoundLight = 10;
-    // Threshold for color scheme change.
-    let colorChangeThreshold = 20;
-
-    // Ignore changes that are within a certain threshold of previous lux values.
-    if ((this._colorScheme === "dark" && luxValue < upperBoundDark) ||
-        (this._colorScheme === "light" && luxValue > lowerBoundLight))
-      return;
-
-    if (luxValue < colorChangeThreshold)
-      this._setColorScheme("dark");
-    else
-      this._setColorScheme("light");
-  },
-
-  _setColorScheme: function Reader_setColorScheme(newColorScheme) {
-    // "auto" is not a real color scheme
-    if (this._colorScheme === newColorScheme || newColorScheme === "auto")
-      return;
-
-    let bodyClasses = this._doc.body.classList;
-
-    if (this._colorScheme)
-      bodyClasses.remove(this._colorScheme);
-
-    this._colorScheme = newColorScheme;
-    bodyClasses.add(this._colorScheme);
-  },
-
-  // Pref values include "dark", "light", and "auto", which automatically switches
-  // between light and dark color schemes based on the ambient light level.
-  _setColorSchemePref: function Reader_setColorSchemePref(colorSchemePref) {
-    this._enableAmbientLighting(colorSchemePref === "auto");
-    this._setColorScheme(colorSchemePref);
-
-    Services.prefs.setCharPref("reader.color_scheme", colorSchemePref);
-  },
-
-  _setFontType: function Reader_setFontType(newFontType) {
-    if (this._fontType === newFontType)
-      return;
-
-    let bodyClasses = this._doc.body.classList;
-
-    if (this._fontType)
-      bodyClasses.remove(this._fontType);
-
-    this._fontType = newFontType;
-    bodyClasses.add(this._fontType);
-
-    Services.prefs.setCharPref("reader.font_type", this._fontType);
-  },
-
-  _getToolbarVisibility: function Reader_getToolbarVisibility() {
-    return !this._toolbarElement.classList.contains("toolbar-hidden");
-  },
-
-  _setToolbarVisibility: function Reader_setToolbarVisibility(visible) {
-    let win = this._win;
-    if (win.history.state)
-      win.history.back();
-
-    if (!this._toolbarEnabled)
-      return;
-
-    // Don't allow visible toolbar until banner state is known
-    if (this._isReadingListItem == -1)
-      return;
-
-    if (this._getToolbarVisibility() === visible)
-      return;
-
-    this._toolbarElement.classList.toggle("toolbar-hidden");
-    this._setSystemUIVisibility(visible);
-
-    if (!visible && !this._hasUsedToolbar) {
-      this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
-      if (!this._hasUsedToolbar) {
-        this._mm.sendAsyncMessage("Reader:ShowToast", { toast: gStrings.GetStringFromName("aboutReader.toolbarTip") });
-        Services.prefs.setBoolPref("reader.has_used_toolbar", true);
-        this._hasUsedToolbar = true;
-      }
-    }
-  },
-
-  _toggleToolbarVisibility: function Reader_toggleToolbarVisibility() {
-    this._setToolbarVisibility(!this._getToolbarVisibility());
-  },
-
-  _setBrowserToolbarVisiblity: function Reader_setBrowserToolbarVisiblity(visible) {
-    this._mm.sendAsyncMessage("Reader:ToolbarVisibility", { visible: visible });
-  },
-
-  _setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
-    this._mm.sendAsyncMessage("Reader:SystemUIVisibility", { visible: visible });
-  },
-
-  _loadArticle: Task.async(function* (url) {
-    this._showProgressDelayed();
-
-    let article = yield this._getArticle(url);
-    if (article && article.url == url) {
-      this._showContent(article);
-    } else {
-      this._win.location.href = url;
-    }
-  }),
-
-  _getArticle: function(url) {
-    return new Promise((resolve, reject) => {
-      let listener = (message) => {
-        this._mm.removeMessageListener("Reader:ArticleData", listener);
-        resolve(message.data.article);
-      };
-      this._mm.addMessageListener("Reader:ArticleData", listener);
-      this._mm.sendAsyncMessage("Reader:ArticleGet", { url: url });
-    });
-  },
-
-  _requestFavicon: function Reader_requestFavicon() {
-    let handleFaviconReturn = (message) => {
-      this._mm.removeMessageListener("Reader:FaviconReturn", handleFaviconReturn);
-      this._loadFavicon(message.data.url, message.data.faviconUrl);
-    };
-
-    this._mm.addMessageListener("Reader:FaviconReturn", handleFaviconReturn);
-    this._mm.sendAsyncMessage("Reader:FaviconRequest", { url: this._article.url });
-  },
-
-  _loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
-    if (this._article.url !== url)
-      return;
-
-    let doc = this._doc;
-
-    let link = doc.createElement('link');
-    link.rel = 'shortcut icon';
-    link.href = faviconUrl;
-
-    doc.getElementsByTagName('head')[0].appendChild(link);
-  },
-
-  _updateImageMargins: function Reader_updateImageMargins() {
-    let windowWidth = this._win.innerWidth;
-    let contentWidth = this._contentElement.offsetWidth;
-    let maxWidthStyle = windowWidth + "px !important";
-
-    let setImageMargins = function(img) {
-      if (!img._originalWidth)
-        img._originalWidth = img.offsetWidth;
-
-      let imgWidth = img._originalWidth;
-
-      // If the image is taking more than half of the screen, just make
-      // it fill edge-to-edge.
-      if (imgWidth < contentWidth && imgWidth > windowWidth * 0.55)
-        imgWidth = windowWidth;
-
-      let sideMargin = Math.max((contentWidth - windowWidth) / 2,
-                                (contentWidth - imgWidth) / 2);
-
-      let imageStyle = sideMargin + "px !important";
-      let widthStyle = imgWidth + "px !important";
-
-      let cssText = "max-width: " + maxWidthStyle + ";" +
-                    "width: " + widthStyle + ";" +
-                    "margin-left: " + imageStyle + ";" +
-                    "margin-right: " + imageStyle + ";";
-
-      img.style.cssText = cssText;
-    }
-
-    let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
-    for (let i = imgs.length; --i >= 0;) {
-      let img = imgs[i];
-
-      if (img.width > 0) {
-        setImageMargins(img);
-      } else {
-        img.onload = function() {
-          setImageMargins(img);
-        }
-      }
-    }
-  },
-
-  _maybeSetTextDirection: function Read_maybeSetTextDirection(article){
-    if(!article.dir)
-      return;
-
-    //Set "dir" attribute on content
-    this._contentElement.setAttribute("dir", article.dir);
-    this._headerElement.setAttribute("dir", article.dir);
-  },
-
-  _showError: function Reader_showError(error) {
-    this._headerElement.style.display = "none";
-    this._contentElement.style.display = "none";
-
-    this._messageElement.innerHTML = error;
-    this._messageElement.style.display = "block";
-
-    this._doc.title = error;
-  },
-
-  // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
-  _stripHost: function Reader_stripHost(host) {
-    if (!host)
-      return host;
-
-    let start = 0;
-
-    if (host.startsWith("www."))
-      start = 4;
-    else if (host.startsWith("m."))
-      start = 2;
-    else if (host.startsWith("mobile."))
-      start = 7;
-
-    return host.substring(start);
-  },
-
-  _showContent: function Reader_showContent(article) {
-    this._messageElement.style.display = "none";
-
-    this._article = article;
-
-    this._domainElement.href = article.url;
-    let articleUri = Services.io.newURI(article.url, null, null);
-    this._domainElement.innerHTML = this._stripHost(articleUri.host);
-
-    this._creditsElement.innerHTML = article.byline;
-
-    this._titleElement.textContent = article.title;
-    this._doc.title = article.title;
-
-    this._headerElement.style.display = "block";
-
-    let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
-    let contentFragment = parserUtils.parseFragment(article.content, Ci.nsIParserUtils.SanitizerDropForms,
-                                                    false, articleUri, this._contentElement);
-    this._contentElement.innerHTML = "";
-    this._contentElement.appendChild(contentFragment);
-    this._updateImageMargins();
-    this._maybeSetTextDirection(article);
-
-    this._contentElement.style.display = "block";
-    this._requestReadingListStatus();
-
-    this._toolbarEnabled = true;
-    this._setToolbarVisibility(true);
-
-    this._requestFavicon();
-  },
-
-  _hideContent: function Reader_hideContent() {
-    this._headerElement.style.display = "none";
-    this._contentElement.style.display = "none";
-  },
-
-  _showProgressDelayed: function Reader_showProgressDelayed() {
-    this._win.setTimeout(function() {
-      // Article has already been loaded, no need to show
-      // progress anymore.
-      if (this._article)
-        return;
-
-      this._headerElement.style.display = "none";
-      this._contentElement.style.display = "none";
-
-      this._messageElement.innerHTML = gStrings.GetStringFromName("aboutReader.loading");
-      this._messageElement.style.display = "block";
-    }.bind(this), 300);
-  },
-
-  _decodeQueryString: function Reader_decodeQueryString(url) {
-    let result = {};
-    let query = url.split("?")[1];
-    if (query) {
-      let pairs = query.split("&");
-      for (let i = 0; i < pairs.length; i++) {
-        let [name, value] = pairs[i].split("=");
-        result[name] = decodeURIComponent(value);
-      }
-    }
-
-    return result;
-  },
-
-  _setupSegmentedButton: function Reader_setupSegmentedButton(id, options, initialValue, callback) {
-    let doc = this._doc;
-    let segmentedButton = doc.getElementById(id);
-
-    for (let i = 0; i < options.length; i++) {
-      let option = options[i];
-
-      let item = doc.createElement("li");
-      let link = doc.createElement("a");
-      link.textContent = option.name;
-      item.appendChild(link);
-
-      if (option.linkClass !== undefined)
-        link.classList.add(option.linkClass);
-
-      if (option.description !== undefined) {
-        let description = doc.createElement("div");
-        description.textContent = option.description;
-        item.appendChild(description);
-      }
-
-      link.style.MozUserSelect = 'none';
-      segmentedButton.appendChild(item);
-
-      link.addEventListener("click", function(aEvent) {
-        if (!aEvent.isTrusted)
-          return;
-
-        aEvent.stopPropagation();
-
-        // Just pass the ID of the button as an extra and hope the ID doesn't change
-        // unless the context changes
-        UITelemetry.addEvent("action.1", "button", null, id);
-
-        let items = segmentedButton.children;
-        for (let j = items.length - 1; j >= 0; j--) {
-          items[j].classList.remove("selected");
-        }
-
-        item.classList.add("selected");
-        callback(option.value);
-      }.bind(this), true);
-
-      if (option.value === initialValue)
-        item.classList.add("selected");
-    }
-  },
-
-  _setupButton: function Reader_setupButton(id, callback) {
-    let button = this._doc.getElementById(id);
-
-    button.addEventListener("click", function(aEvent) {
-      if (!aEvent.isTrusted)
-        return;
-
-      aEvent.stopPropagation();
-      callback();
-    }, true);
-  },
-
-  _setupAllDropdowns: function Reader_setupAllDropdowns() {
-    let doc = this._doc;
-    let win = this._win;
-
-    let dropdowns = doc.getElementsByClassName("dropdown");
-
-    for (let i = dropdowns.length - 1; i >= 0; i--) {
-      let dropdown = dropdowns[i];
-
-      let dropdownToggle = dropdown.getElementsByClassName("dropdown-toggle")[0];
-      let dropdownPopup = dropdown.getElementsByClassName("dropdown-popup")[0];
-
-      if (!dropdownToggle || !dropdownPopup)
-        continue;
-
-      let dropdownArrow = doc.createElement("div");
-      dropdownArrow.className = "dropdown-arrow";
-      dropdownPopup.appendChild(dropdownArrow);
-
-      let updatePopupPosition = function() {
-        let popupWidth = dropdownPopup.offsetWidth + 30;
-        let arrowWidth = dropdownArrow.offsetWidth;
-        let toggleWidth = dropdownToggle.offsetWidth;
-        let toggleLeft = dropdownToggle.offsetLeft;
-
-        let popupShift = (toggleWidth - popupWidth) / 2;
-        let popupLeft = Math.max(0, Math.min(win.innerWidth - popupWidth, toggleLeft + popupShift));
-        dropdownPopup.style.left = popupLeft + "px";
-
-        let arrowShift = (toggleWidth - arrowWidth) / 2;
-        let arrowLeft = toggleLeft - popupLeft + arrowShift;
-        dropdownArrow.style.left = arrowLeft + "px";
-      };
-
-      win.addEventListener("resize", function(aEvent) {
-        if (!aEvent.isTrusted)
-          return;
-
-        // Wait for reflow before calculating the new position of the popup.
-        win.setTimeout(updatePopupPosition, 0);
-      }, true);
-
-      dropdownToggle.addEventListener("click", function(aEvent) {
-        if (!aEvent.isTrusted)
-          return;
-
-        aEvent.stopPropagation();
-
-        if (!this._getToolbarVisibility())
-          return;
-
-        let dropdownClasses = dropdown.classList;
-
-        if (dropdownClasses.contains("open")) {
-          win.history.back();
-        } else {
-          updatePopupPosition();
-          if (!this._closeAllDropdowns())
-            this._pushDropdownState();
-
-          dropdownClasses.add("open");
-        }
-      }.bind(this), true);
-    }
-  },
-
-  _pushDropdownState: function Reader_pushDropdownState() {
-    // FIXME: We're getting a NS_ERROR_UNEXPECTED error when we try
-    // to do win.history.pushState() here (see bug 682296). This is
-    // a workaround that allows us to push history state on the target
-    // content document.
-
-    let doc = this._doc;
-    let body = doc.body;
-
-    if (this._pushStateScript)
-      body.removeChild(this._pushStateScript);
-
-    this._pushStateScript = doc.createElement('script');
-    this._pushStateScript.type = "text/javascript";
-    this._pushStateScript.innerHTML = 'history.pushState({ dropdown: 1 }, document.title);';
-
-    body.appendChild(this._pushStateScript);
-  },
-
-  _closeAllDropdowns : function Reader_closeAllDropdowns() {
-    let dropdowns = this._doc.querySelectorAll(".dropdown.open");
-    for (let i = dropdowns.length - 1; i >= 0; i--) {
-      dropdowns[i].classList.remove("open");
-    }
-
-    return (dropdowns.length > 0)
-  }
-};
--- a/toolkit/components/reader/ReaderMode.jsm
+++ b/toolkit/components/reader/ReaderMode.jsm
@@ -22,71 +22,39 @@ let ReaderMode = {
   CACHE_VERSION: 1,
 
   DEBUG: 0,
 
   // Don't try to parse the page if it has too many elements (for memory and
   // performance reasons)
   MAX_ELEMS_TO_PARSE: 3000,
 
-  get isEnabledForParseOnLoad() {
-    delete this.isEnabledForParseOnLoad;
-
-    // Listen for future pref changes.
-    Services.prefs.addObserver("reader.parse-on-load.", this, false);
-
-    return this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
-  },
-
-  get isOnLowMemoryPlatform() {
-    let memory = Cc["@mozilla.org/xpcom/memory-service;1"].getService(Ci.nsIMemory);
-    delete this.isOnLowMemoryPlatform;
-    return this.isOnLowMemoryPlatform = memory.isLowMemoryPlatform();
-  },
-
-  _getStateForParseOnLoad: function () {
-    let isEnabled = Services.prefs.getBoolPref("reader.parse-on-load.enabled");
-    let isForceEnabled = Services.prefs.getBoolPref("reader.parse-on-load.force-enabled");
-    // For low-memory devices, don't allow reader mode since it takes up a lot of memory.
-    // See https://bugzilla.mozilla.org/show_bug.cgi?id=792603 for details.
-    return isForceEnabled || (isEnabled && !this.isOnLowMemoryPlatform);
-  },
-
-  observe: function(aMessage, aTopic, aData) {
-    switch(aTopic) {
-      case "nsPref:changed":
-        if (aData.startsWith("reader.parse-on-load.")) {
-          this.isEnabledForParseOnLoad = this._getStateForParseOnLoad();
-        }
-        break;
-    }
-  },
-
   /**
    * Gets an article from a loaded browser's document. This method will parse the document
    * if it does not find the article in the cache.
    *
-   * @param doc A document to parse.
+   * @param browser A browser with a loaded page.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
-  parseDocument: Task.async(function* (doc) {
-    let uri = Services.io.newURI(doc.documentURI, null, null);
+  parseDocumentFromBrowser: Task.async(function* (browser) {
+    let uri = browser.currentURI;
     if (!this._shouldCheckUri(uri)) {
       this.log("Reader mode disabled for URI");
       return null;
     }
 
     // First, try to find a parsed article in the cache.
     let article = yield this.getArticleFromCache(uri);
     if (article) {
       this.log("Page found in cache, return article immediately");
       return article;
     }
 
+    let doc = browser.contentWindow.document;
     return yield this._readerParse(uri, doc);
   }),
 
   /**
    * Downloads and parses a document from a URL.
    *
    * @param url URL to download and parse.
    * @return {Promise}
--- a/toolkit/components/reader/content/aboutReader.html
+++ b/toolkit/components/reader/content/aboutReader.html
@@ -1,18 +1,16 @@
 <!DOCTYPE html>
 <html>
 
 <head>
   <meta content="text/html; charset=UTF-8" http-equiv="content-type">
   <meta name="viewport" content="width=device-width; user-scalable=0" />
 
   <link rel="stylesheet" href="chrome://global/skin/aboutReader.css" type="text/css"/>
-
-  <script type="text/javascript;version=1.8" src="chrome://global/content/reader/aboutReader.js"></script>
 </head>
 
 <body>
   <div id="reader-header" class="header">
     <a id="reader-domain" class="domain"></a>
     <div class="domain-border"></div>
     <h1 id="reader-title"></h1>
     <div id="reader-credits" class="credits"></div>
--- a/toolkit/components/reader/content/aboutReader.js
+++ b/toolkit/components/reader/content/aboutReader.js
@@ -1,9 +1,843 @@
 /* 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/. */
+ * 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/. */
+
+let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Messaging.jsm");
+Cu.import("resource://gre/modules/Services.jsm")
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
+                                  "resource://gre/modules/UITelemetry.jsm");
+
+XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
+  window.QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIWebNavigation)
+    .QueryInterface(Ci.nsIDocShellTreeItem)
+    .rootTreeItem
+    .QueryInterface(Ci.nsIInterfaceRequestor)
+    .getInterface(Ci.nsIDOMWindow)
+    .QueryInterface(Ci.nsIDOMChromeWindow));
+
+function dump(s) {
+  Services.console.logStringMessage("AboutReader: " + s);
+}
+
+let gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
+
+let AboutReader = function(doc, win) {
+  dump("Init()");
+
+  this._docRef = Cu.getWeakReference(doc);
+  this._winRef = Cu.getWeakReference(win);
+
+  Services.obs.addObserver(this, "Reader:FaviconReturn", false);
+  Services.obs.addObserver(this, "Reader:Added", false);
+  Services.obs.addObserver(this, "Reader:Removed", false);
+  Services.obs.addObserver(this, "Gesture:DoubleTap", false);
+
+  this._article = null;
+
+  dump("Feching toolbar, header and content notes from about:reader");
+  this._headerElementRef = Cu.getWeakReference(doc.getElementById("reader-header"));
+  this._domainElementRef = Cu.getWeakReference(doc.getElementById("reader-domain"));
+  this._titleElementRef = Cu.getWeakReference(doc.getElementById("reader-title"));
+  this._creditsElementRef = Cu.getWeakReference(doc.getElementById("reader-credits"));
+  this._contentElementRef = Cu.getWeakReference(doc.getElementById("reader-content"));
+  this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
+  this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
+
+  this._toolbarEnabled = false;
+
+  this._scrollOffset = win.pageYOffset;
+
+  let body = doc.body;
+  body.addEventListener("touchstart", this, false);
+  body.addEventListener("click", this, false);
+
+  win.addEventListener("unload", this, false);
+  win.addEventListener("scroll", this, false);
+  win.addEventListener("popstate", this, false);
+  win.addEventListener("resize", this, false);
+
+  doc.addEventListener("visibilitychange", this, false);
+
+  this._setupAllDropdowns();
+  this._setupButton("toggle-button", this._onReaderToggle.bind(this));
+  this._setupButton("share-button", this._onShare.bind(this));
+
+  let colorSchemeOptions = [
+    { name: gStrings.GetStringFromName("aboutReader.colorSchemeDark"),
+      value: "dark"},
+    { name: gStrings.GetStringFromName("aboutReader.colorSchemeLight"),
+      value: "light"},
+    { name: gStrings.GetStringFromName("aboutReader.colorSchemeAuto"),
+      value: "auto"}
+  ];
+
+  let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
+  this._setupSegmentedButton("color-scheme-buttons", colorSchemeOptions, colorScheme, this._setColorSchemePref.bind(this));
+  this._setColorSchemePref(colorScheme);
+
+  let fontTypeSample = gStrings.GetStringFromName("aboutReader.fontTypeSample");
+  let fontTypeOptions = [
+    { name: fontTypeSample,
+      description: gStrings.GetStringFromName("aboutReader.fontTypeSerif"),
+      value: "serif",
+      linkClass: "serif" },
+    { name: fontTypeSample,
+      description: gStrings.GetStringFromName("aboutReader.fontTypeSansSerif"),
+      value: "sans-serif",
+      linkClass: "sans-serif"
+    },
+  ];
+
+  let fontType = Services.prefs.getCharPref("reader.font_type");
+  this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
+  this._setFontType(fontType);
+
+  let fontSizeSample = gStrings.GetStringFromName("aboutReader.fontSizeSample");
+  let fontSizeOptions = [
+    { name: fontSizeSample,
+      value: 1,
+      linkClass: "font-size1-sample" },
+    { name: fontSizeSample,
+      value: 2,
+      linkClass: "font-size2-sample" },
+    { name: fontSizeSample,
+      value: 3,
+      linkClass: "font-size3-sample" },
+    { name: fontSizeSample,
+      value: 4,
+      linkClass: "font-size4-sample" },
+    { name: fontSizeSample,
+      value: 5,
+      linkClass: "font-size5-sample" }
+  ];
+
+  let fontSize = Services.prefs.getIntPref("reader.font_size");
+  this._setupSegmentedButton("font-size-buttons", fontSizeOptions, fontSize, this._setFontSize.bind(this));
+  this._setFontSize(fontSize);
+
+  dump("Decoding query arguments");
+  let queryArgs = this._decodeQueryString(win.location.href);
+
+  // Track status of reader toolbar add/remove toggle button
+  this._isReadingListItem = -1;
+  this._updateToggleButton();
+
+  let url = queryArgs.url;
+  let tabId = queryArgs.tabId;
+  this._loadArticle(url, tabId);
+}
+
+AboutReader.prototype = {
+  _BLOCK_IMAGES_SELECTOR: ".content p > img:only-child, " +
+                          ".content p > a:only-child > img:only-child, " +
+                          ".content .wp-caption img, " +
+                          ".content figure img",
+
+  get _doc() {
+    return this._docRef.get();
+  },
+
+  get _win() {
+    return this._winRef.get();
+  },
+
+  get _headerElement() {
+    return this._headerElementRef.get();
+  },
+
+  get _domainElement() {
+    return this._domainElementRef.get();
+  },
+
+  get _titleElement() {
+    return this._titleElementRef.get();
+  },
+
+  get _creditsElement() {
+    return this._creditsElementRef.get();
+  },
+
+  get _contentElement() {
+    return this._contentElementRef.get();
+  },
+
+  get _toolbarElement() {
+    return this._toolbarElementRef.get();
+  },
+
+  get _messageElement() {
+    return this._messageElementRef.get();
+  },
+
+  observe: function Reader_observe(aMessage, aTopic, aData) {
+    switch(aTopic) {
+      case "Reader:FaviconReturn": {
+        let args = JSON.parse(aData);
+        this._loadFavicon(args.url, args.faviconUrl);
+        Services.obs.removeObserver(this, "Reader:FaviconReturn");
+        break;
+      }
+
+      case "Reader:Added": {
+        // Page can be added by long-press pageAction, or by tap on banner icon.
+        if (aData == this._article.url) {
+          if (this._isReadingListItem != 1) {
+            this._isReadingListItem = 1;
+            this._updateToggleButton();
+          }
+        }
+        break;
+      }
+
+      case "Reader:Removed": {
+        if (aData == this._article.url) {
+          if (this._isReadingListItem != 0) {
+            this._isReadingListItem = 0;
+            this._updateToggleButton();
+          }
+        }
+        break;
+      }
+
+      case "Gesture:DoubleTap": {
+        let args = JSON.parse(aData);
+        let scrollBy;
+        // Arbitary choice of innerHeight - 50 to give some context after scroll
+        if (args.y < (this._win.innerHeight / 2)) {
+          scrollBy = -this._win.innerHeight + 50;
+        } else {
+          scrollBy = this._win.innerHeight - 50;
+        }
+        this._scrollPage(scrollBy);
+        break;
+      }
+    }
+  },
+
+  handleEvent: function Reader_handleEvent(aEvent) {
+    if (!aEvent.isTrusted)
+      return;
+
+    switch (aEvent.type) {
+      case "touchstart":
+        this._scrolled = false;
+        break;
+      case "click":
+        if (!this._scrolled)
+          this._toggleToolbarVisibility();
+        break;
+      case "scroll":
+        if (!this._scrolled) {
+          let isScrollingUp = this._scrollOffset > aEvent.pageY;
+          this._setToolbarVisibility(isScrollingUp);
+          this._scrollOffset = aEvent.pageY;
+        }
+        break;
+      case "popstate":
+        if (!aEvent.state)
+          this._closeAllDropdowns();
+        break;
+      case "resize":
+        this._updateImageMargins();
+        break;
+
+      case "devicelight":
+        this._handleDeviceLight(aEvent.value);
+        break;
+
+      case "visibilitychange":
+        this._handleVisibilityChange();
+        break;
+
+      case "unload":
+        Services.obs.removeObserver(this, "Reader:Added");
+        Services.obs.removeObserver(this, "Reader:Removed");
+        Services.obs.removeObserver(this, "Gesture:DoubleTap");
+        break;
+    }
+  },
+
+  _scrollPage: function Reader_scrollPage(scrollByPixels) {
+    let viewport = BrowserApp.selectedTab.getViewport();
+    let newY = Math.min(Math.max(viewport.cssY + scrollByPixels, viewport.cssPageTop), viewport.cssPageBottom);
+    let newRect = new Rect(viewport.cssX, newY, viewport.cssWidth, viewport.cssHeight);
+
+    this._setToolbarVisibility(false);
+    this._setBrowserToolbarVisiblity(false);
+    this._scrolled  = true;
+    ZoomHelper.zoomToRect(newRect, -1);
+  },
+
+  _updateToggleButton: function Reader_updateToggleButton() {
+    let classes = this._doc.getElementById("toggle-button").classList;
+
+    if (this._isReadingListItem == 1) {
+      classes.add("on");
+    } else {
+      classes.remove("on");
+    }
+  },
+
+  _requestReadingListStatus: function Reader_requestReadingListStatus() {
+    Messaging.sendRequestForResult({
+      type: "Reader:ListStatusRequest",
+      url: this._article.url
+    }).then((data) => {
+      let args = JSON.parse(data);
+      if (args.url == this._article.url) {
+        if (this._isReadingListItem != args.inReadingList) {
+          let isInitialStateChange = (this._isReadingListItem == -1);
+          this._isReadingListItem = args.inReadingList;
+          this._updateToggleButton();
+
+          // Display the toolbar when all its initial component states are known
+          if (isInitialStateChange) {
+            this._setToolbarVisibility(true);
+          }
+        }
+      }
+    });
+  },
+
+  _onReaderToggle: function Reader_onToggle() {
+    if (!this._article)
+      return;
+
+    if (this._isReadingListItem == 0) {
+      // If we're in reader mode, we must have fetched the article.
+      this._article.status = gChromeWin.Reader.STATUS_FETCHED_ARTICLE;
+      gChromeWin.Reader.addArticleToReadingList(this._article);
+
+      UITelemetry.addEvent("save.1", "button", null, "reader");
+    } else {
+      Messaging.sendRequest({
+        type: "Reader:RemoveFromList",
+        url: this._article.url
+      });
+
+      UITelemetry.addEvent("unsave.1", "button", null, "reader");
+    }
+  },
+
+  _onShare: function Reader_onShare() {
+    if (!this._article)
+      return;
+
+    Messaging.sendRequest({
+      type: "Reader:Share",
+      url: this._article.url,
+      title: this._article.title
+    });
+
+    UITelemetry.addEvent("share.1", "list", null);
+  },
+
+  _setFontSize: function Reader_setFontSize(newFontSize) {
+    let bodyClasses = this._doc.body.classList;
+
+    if (this._fontSize > 0)
+      bodyClasses.remove("font-size" + this._fontSize);
+
+    this._fontSize = newFontSize;
+    bodyClasses.add("font-size" + this._fontSize);
+
+    Services.prefs.setIntPref("reader.font_size", this._fontSize);
+  },
+
+  _handleDeviceLight: function Reader_handleDeviceLight(newLux) {
+    // Desired size of the this._luxValues array.
+    let luxValuesSize = 10;
+    // Add new lux value at the front of the array.
+    this._luxValues.unshift(newLux);
+    // Add new lux value to this._totalLux for averaging later.
+    this._totalLux += newLux;
+
+    // Don't update when length of array is less than luxValuesSize except when it is 1.
+    if (this._luxValues.length < luxValuesSize) {
+      // Use the first lux value to set the color scheme until our array equals luxValuesSize.
+      if (this._luxValues.length == 1) {
+        this._updateColorScheme(newLux);
+      }
+      return;
+    }
+    // Holds the average of the lux values collected in this._luxValues.
+    let averageLuxValue = this._totalLux/luxValuesSize;
+
+    this._updateColorScheme(averageLuxValue);
+    // Pop the oldest value off the array.
+    let oldLux = this._luxValues.pop();
+    // Subtract oldLux since it has been discarded from the array.
+    this._totalLux -= oldLux;
+  },
+
+  _handleVisibilityChange: function Reader_handleVisibilityChange() {
+    let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
+    if (colorScheme != "auto") {
+      return;
+    }
+
+    // Turn off the ambient light sensor if the page is hidden
+    this._enableAmbientLighting(!this._doc.hidden);
+  },
+
+  // Setup or teardown the ambient light tracking system.
+  _enableAmbientLighting: function Reader_enableAmbientLighting(enable) {
+    if (enable) {
+      this._win.addEventListener("devicelight", this, false);
+      this._luxValues = [];
+      this._totalLux = 0;
+    } else {
+      this._win.removeEventListener("devicelight", this, false);
+      delete this._luxValues;
+      delete this._totalLux;
+    }
+  },
+
+  _updateColorScheme: function Reader_updateColorScheme(luxValue) {
+    // Upper bound value for "dark" color scheme beyond which it changes to "light".
+    let upperBoundDark = 50;
+    // Lower bound value for "light" color scheme beyond which it changes to "dark".
+    let lowerBoundLight = 10;
+    // Threshold for color scheme change.
+    let colorChangeThreshold = 20;
+
+    // Ignore changes that are within a certain threshold of previous lux values.
+    if ((this._colorScheme === "dark" && luxValue < upperBoundDark) ||
+        (this._colorScheme === "light" && luxValue > lowerBoundLight))
+      return;
+
+    if (luxValue < colorChangeThreshold)
+      this._setColorScheme("dark");
+    else
+      this._setColorScheme("light");
+  },
 
-"use strict";
+  _setColorScheme: function Reader_setColorScheme(newColorScheme) {
+    // "auto" is not a real color scheme
+    if (this._colorScheme === newColorScheme || newColorScheme === "auto")
+      return;
+
+    let bodyClasses = this._doc.body.classList;
+
+    if (this._colorScheme)
+      bodyClasses.remove(this._colorScheme);
+
+    this._colorScheme = newColorScheme;
+    bodyClasses.add(this._colorScheme);
+  },
+
+  // Pref values include "dark", "light", and "auto", which automatically switches
+  // between light and dark color schemes based on the ambient light level.
+  _setColorSchemePref: function Reader_setColorSchemePref(colorSchemePref) {
+    this._enableAmbientLighting(colorSchemePref === "auto");
+    this._setColorScheme(colorSchemePref);
+
+    Services.prefs.setCharPref("reader.color_scheme", colorSchemePref);
+  },
+
+  _setFontType: function Reader_setFontType(newFontType) {
+    if (this._fontType === newFontType)
+      return;
+
+    let bodyClasses = this._doc.body.classList;
+
+    if (this._fontType)
+      bodyClasses.remove(this._fontType);
+
+    this._fontType = newFontType;
+    bodyClasses.add(this._fontType);
+
+    Services.prefs.setCharPref("reader.font_type", this._fontType);
+  },
+
+  _getToolbarVisibility: function Reader_getToolbarVisibility() {
+    return !this._toolbarElement.classList.contains("toolbar-hidden");
+  },
+
+  _setToolbarVisibility: function Reader_setToolbarVisibility(visible) {
+    let win = this._win;
+    if (win.history.state)
+      win.history.back();
+
+    if (!this._toolbarEnabled)
+      return;
+
+    // Don't allow visible toolbar until banner state is known
+    if (this._isReadingListItem == -1)
+      return;
+
+    if (this._getToolbarVisibility() === visible)
+      return;
+
+    this._toolbarElement.classList.toggle("toolbar-hidden");
+    this._setSystemUIVisibility(visible);
+
+    if (!visible && !this._hasUsedToolbar) {
+      this._hasUsedToolbar = Services.prefs.getBoolPref("reader.has_used_toolbar");
+      if (!this._hasUsedToolbar) {
+        gChromeWin.NativeWindow.toast.show(gStrings.GetStringFromName("aboutReader.toolbarTip"), "short");
+
+        Services.prefs.setBoolPref("reader.has_used_toolbar", true);
+        this._hasUsedToolbar = true;
+      }
+    }
+  },
+
+  _toggleToolbarVisibility: function Reader_toggleToolbarVisibility() {
+    this._setToolbarVisibility(!this._getToolbarVisibility());
+  },
+
+  _setBrowserToolbarVisiblity: function Reader_setBrowserToolbarVisiblity(visible) {
+    Messaging.sendRequest({
+      type: "BrowserToolbar:Visibility",
+      visible: visible
+    });
+  },
+
+  _setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
+    Messaging.sendRequest({
+      type: "SystemUI:Visibility",
+      visible: visible
+    });
+  },
+
+  _loadArticle: Task.async(function* (url, tabId) {
+    this._showProgressDelayed();
+
+    let article = yield gChromeWin.Reader.getArticle(url, tabId).catch(e => {
+      Cu.reportError("Error loading article: " + e);
+      return null;
+    });
+    if (article) {
+      this._showContent(article);
+    } else {
+      this._win.location.href = url;
+    }
+  }),
+
+  _requestFavicon: function Reader_requestFavicon() {
+    Messaging.sendRequest({
+      type: "Reader:FaviconRequest",
+      url: this._article.url
+    });
+  },
+
+  _loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
+    if (this._article.url !== url)
+      return;
+
+    let doc = this._doc;
+
+    let link = doc.createElement('link');
+    link.rel = 'shortcut icon';
+    link.href = faviconUrl;
+
+    doc.getElementsByTagName('head')[0].appendChild(link);
+  },
+
+  _updateImageMargins: function Reader_updateImageMargins() {
+    let windowWidth = this._win.innerWidth;
+    let contentWidth = this._contentElement.offsetWidth;
+    let maxWidthStyle = windowWidth + "px !important";
+
+    let setImageMargins = function(img) {
+      if (!img._originalWidth)
+        img._originalWidth = img.offsetWidth;
+
+      let imgWidth = img._originalWidth;
+
+      // If the image is taking more than half of the screen, just make
+      // it fill edge-to-edge.
+      if (imgWidth < contentWidth && imgWidth > windowWidth * 0.55)
+        imgWidth = windowWidth;
+
+      let sideMargin = Math.max((contentWidth - windowWidth) / 2,
+                                (contentWidth - imgWidth) / 2);
+
+      let imageStyle = sideMargin + "px !important";
+      let widthStyle = imgWidth + "px !important";
+
+      let cssText = "max-width: " + maxWidthStyle + ";" +
+                    "width: " + widthStyle + ";" +
+                    "margin-left: " + imageStyle + ";" +
+                    "margin-right: " + imageStyle + ";";
+
+      img.style.cssText = cssText;
+    }
+
+    let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
+    for (let i = imgs.length; --i >= 0;) {
+      let img = imgs[i];
+
+      if (img.width > 0) {
+        setImageMargins(img);
+      } else {
+        img.onload = function() {
+          setImageMargins(img);
+        }
+      }
+    }
+  },
+
+  _maybeSetTextDirection: function Read_maybeSetTextDirection(article){
+    if(!article.dir)
+      return;
+
+    //Set "dir" attribute on content
+    this._contentElement.setAttribute("dir", article.dir);
+    this._headerElement.setAttribute("dir", article.dir);
+  },
+
+  _showError: function Reader_showError(error) {
+    this._headerElement.style.display = "none";
+    this._contentElement.style.display = "none";
+
+    this._messageElement.innerHTML = error;
+    this._messageElement.style.display = "block";
+
+    this._doc.title = error;
+  },
+
+  // This function is the JS version of Java's StringUtils.stripCommonSubdomains.
+  _stripHost: function Reader_stripHost(host) {
+    if (!host)
+      return host;
+
+    let start = 0;
+
+    if (host.startsWith("www."))
+      start = 4;
+    else if (host.startsWith("m."))
+      start = 2;
+    else if (host.startsWith("mobile."))
+      start = 7;
+
+    return host.substring(start);
+  },
+
+  _showContent: function Reader_showContent(article) {
+    this._messageElement.style.display = "none";
+
+    this._article = article;
+
+    this._domainElement.href = article.url;
+    let articleUri = Services.io.newURI(article.url, null, null);
+    this._domainElement.innerHTML = this._stripHost(articleUri.host);
 
-window.addEventListener("DOMContentLoaded", function () {
-  document.dispatchEvent(new CustomEvent("AboutReaderContentLoaded", { bubbles: true }));
-});
+    this._creditsElement.innerHTML = article.byline;
+
+    this._titleElement.textContent = article.title;
+    this._doc.title = article.title;
+
+    this._headerElement.style.display = "block";
+
+    let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
+    let contentFragment = parserUtils.parseFragment(article.content, Ci.nsIParserUtils.SanitizerDropForms,
+                                                    false, articleUri, this._contentElement);
+    this._contentElement.innerHTML = "";
+    this._contentElement.appendChild(contentFragment);
+    this._updateImageMargins();
+    this._maybeSetTextDirection(article);
+
+    this._contentElement.style.display = "block";
+    this._requestReadingListStatus();
+
+    this._toolbarEnabled = true;
+    this._setToolbarVisibility(true);
+
+    this._requestFavicon();
+  },
+
+  _hideContent: function Reader_hideContent() {
+    this._headerElement.style.display = "none";
+    this._contentElement.style.display = "none";
+  },
+
+  _showProgressDelayed: function Reader_showProgressDelayed() {
+    this._win.setTimeout(function() {
+      // Article has already been loaded, no need to show
+      // progress anymore.
+      if (this._article)
+        return;
+
+      this._headerElement.style.display = "none";
+      this._contentElement.style.display = "none";
+
+      this._messageElement.innerHTML = gStrings.GetStringFromName("aboutReader.loading");
+      this._messageElement.style.display = "block";
+    }.bind(this), 300);
+  },
+
+  _decodeQueryString: function Reader_decodeQueryString(url) {
+    let result = {};
+    let query = url.split("?")[1];
+    if (query) {
+      let pairs = query.split("&");
+      for (let i = 0; i < pairs.length; i++) {
+        let [name, value] = pairs[i].split("=");
+        result[name] = decodeURIComponent(value);
+      }
+    }
+
+    return result;
+  },
+
+  _setupSegmentedButton: function Reader_setupSegmentedButton(id, options, initialValue, callback) {
+    let doc = this._doc;
+    let segmentedButton = doc.getElementById(id);
+
+    for (let i = 0; i < options.length; i++) {
+      let option = options[i];
+
+      let item = doc.createElement("li");
+      let link = doc.createElement("a");
+      link.textContent = option.name;
+      item.appendChild(link);
+
+      if (option.linkClass !== undefined)
+        link.classList.add(option.linkClass);
+
+      if (option.description !== undefined) {
+        let description = doc.createElement("div");
+        description.textContent = option.description;
+        item.appendChild(description);
+      }
+
+      link.style.MozUserSelect = 'none';
+      segmentedButton.appendChild(item);
+
+      link.addEventListener("click", function(aEvent) {
+        if (!aEvent.isTrusted)
+          return;
+
+        aEvent.stopPropagation();
+
+        // Just pass the ID of the button as an extra and hope the ID doesn't change
+        // unless the context changes
+        UITelemetry.addEvent("action.1", "button", null, id);
+
+        let items = segmentedButton.children;
+        for (let j = items.length - 1; j >= 0; j--) {
+          items[j].classList.remove("selected");
+        }
+
+        item.classList.add("selected");
+        callback(option.value);
+      }.bind(this), true);
+
+      if (option.value === initialValue)
+        item.classList.add("selected");
+    }
+  },
+
+  _setupButton: function Reader_setupButton(id, callback) {
+    let button = this._doc.getElementById(id);
+
+    button.addEventListener("click", function(aEvent) {
+      if (!aEvent.isTrusted)
+        return;
+
+      aEvent.stopPropagation();
+      callback();
+    }, true);
+  },
+
+  _setupAllDropdowns: function Reader_setupAllDropdowns() {
+    let doc = this._doc;
+    let win = this._win;
+
+    let dropdowns = doc.getElementsByClassName("dropdown");
+
+    for (let i = dropdowns.length - 1; i >= 0; i--) {
+      let dropdown = dropdowns[i];
+
+      let dropdownToggle = dropdown.getElementsByClassName("dropdown-toggle")[0];
+      let dropdownPopup = dropdown.getElementsByClassName("dropdown-popup")[0];
+
+      if (!dropdownToggle || !dropdownPopup)
+        continue;
+
+      let dropdownArrow = doc.createElement("div");
+      dropdownArrow.className = "dropdown-arrow";
+      dropdownPopup.appendChild(dropdownArrow);
+
+      let updatePopupPosition = function() {
+        let popupWidth = dropdownPopup.offsetWidth + 30;
+        let arrowWidth = dropdownArrow.offsetWidth;
+        let toggleWidth = dropdownToggle.offsetWidth;
+        let toggleLeft = dropdownToggle.offsetLeft;
+
+        let popupShift = (toggleWidth - popupWidth) / 2;
+        let popupLeft = Math.max(0, Math.min(win.innerWidth - popupWidth, toggleLeft + popupShift));
+        dropdownPopup.style.left = popupLeft + "px";
+
+        let arrowShift = (toggleWidth - arrowWidth) / 2;
+        let arrowLeft = toggleLeft - popupLeft + arrowShift;
+        dropdownArrow.style.left = arrowLeft + "px";
+      };
+
+      win.addEventListener("resize", function(aEvent) {
+        if (!aEvent.isTrusted)
+          return;
+
+        // Wait for reflow before calculating the new position of the popup.
+        setTimeout(updatePopupPosition, 0);
+      }, true);
+
+      dropdownToggle.addEventListener("click", function(aEvent) {
+        if (!aEvent.isTrusted)
+          return;
+
+        aEvent.stopPropagation();
+
+        if (!this._getToolbarVisibility())
+          return;
+
+        let dropdownClasses = dropdown.classList;
+
+        if (dropdownClasses.contains("open")) {
+          win.history.back();
+        } else {
+          updatePopupPosition();
+          if (!this._closeAllDropdowns())
+            this._pushDropdownState();
+
+          dropdownClasses.add("open");
+        }
+      }.bind(this), true);
+    }
+  },
+
+  _pushDropdownState: function Reader_pushDropdownState() {
+    // FIXME: We're getting a NS_ERROR_UNEXPECTED error when we try
+    // to do win.history.pushState() here (see bug 682296). This is
+    // a workaround that allows us to push history state on the target
+    // content document.
+
+    let doc = this._doc;
+    let body = doc.body;
+
+    if (this._pushStateScript)
+      body.removeChild(this._pushStateScript);
+
+    this._pushStateScript = doc.createElement('script');
+    this._pushStateScript.type = "text/javascript";
+    this._pushStateScript.innerHTML = 'history.pushState({ dropdown: 1 }, document.title);';
+
+    body.appendChild(this._pushStateScript);
+  },
+
+  _closeAllDropdowns : function Reader_closeAllDropdowns() {
+    let dropdowns = this._doc.querySelectorAll(".dropdown.open");
+    for (let i = dropdowns.length - 1; i >= 0; i--) {
+      dropdowns[i].classList.remove("open");
+    }
+
+    return (dropdowns.length > 0)
+  }
+};
--- a/toolkit/components/reader/moz.build
+++ b/toolkit/components/reader/moz.build
@@ -2,11 +2,10 @@
 # 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/.
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXTRA_JS_MODULES += [
-  'AboutReader.jsm',
   'ReaderMode.jsm'
 ]