mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
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) --HG-- rename : toolkit/components/reader/AboutReader.jsm => toolkit/components/reader/content/aboutReader.js
This commit is contained in:
parent
f17c7b51bb
commit
63631dcf81
@ -5,7 +5,9 @@
|
||||
|
||||
"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.
|
||||
@ -15,136 +17,13 @@ let Reader = {
|
||||
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",
|
||||
],
|
||||
get isEnabledForParseOnLoad() {
|
||||
delete this.isEnabledForParseOnLoad;
|
||||
|
||||
init: function() {
|
||||
for (let msg of this.MESSAGES) {
|
||||
window.messageManager.addMessageListener(msg, this);
|
||||
}
|
||||
// Listen for future pref changes.
|
||||
Services.prefs.addObserver("reader.parse-on-load.", this, false);
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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: {
|
||||
@ -188,7 +67,7 @@ let Reader = {
|
||||
// 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",
|
||||
@ -199,14 +78,32 @@ let Reader = {
|
||||
}
|
||||
},
|
||||
|
||||
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;
|
||||
});
|
||||
@ -243,25 +140,36 @@ let Reader = {
|
||||
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;
|
||||
}
|
||||
@ -271,18 +179,6 @@ let Reader = {
|
||||
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.
|
||||
*/
|
||||
|
@ -109,13 +109,14 @@ XPCOMUtils.defineLazyModuleGetter(this, "SharedPreferences",
|
||||
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"],
|
||||
@ -146,6 +147,7 @@ Services.scriptloader.loadSubScript("chrome://browser/content/Reader.js", this);
|
||||
["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() {
|
||||
@ -447,7 +449,6 @@ var BrowserApp = {
|
||||
#ifdef NIGHTLY_BUILD
|
||||
ShumwayUtils.init();
|
||||
#endif
|
||||
Reader.init();
|
||||
|
||||
let url = null;
|
||||
let pinned = false;
|
||||
@ -499,8 +500,6 @@ var BrowserApp = {
|
||||
// Tiles reporting is disabled.
|
||||
}
|
||||
|
||||
window.messageManager.loadFrameScript("chrome://browser/content/content.js", true);
|
||||
|
||||
// Notify Java that Gecko has loaded.
|
||||
Messaging.sendRequest({ type: "Gecko:Ready" });
|
||||
},
|
||||
@ -3225,7 +3224,7 @@ function Tab(aURL, aParams) {
|
||||
this.clickToPlayPluginsActivated = false;
|
||||
this.desktopMode = false;
|
||||
this.originalURI = null;
|
||||
this.isArticle = false;
|
||||
this.savedArticle = null;
|
||||
this.hasTouchListener = false;
|
||||
this.browserWidth = 0;
|
||||
this.browserHeight = 0;
|
||||
@ -3566,6 +3565,7 @@ Tab.prototype = {
|
||||
BrowserApp.deck.selectedPanel = selectedPanel;
|
||||
|
||||
this.browser = null;
|
||||
this.savedArticle = null;
|
||||
},
|
||||
|
||||
// This should be called to update the browser when the tab gets selected/unselected
|
||||
@ -3961,6 +3961,14 @@ Tab.prototype = {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
@ -4265,6 +4273,31 @@ Tab.prototype = {
|
||||
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));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -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();
|
@ -10,7 +10,6 @@ chrome.jar:
|
||||
* 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)
|
||||
|
@ -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)
|
||||
}
|
||||
};
|
@ -27,49 +27,16 @@ let ReaderMode = {
|
||||
// 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;
|
||||
@ -82,6 +49,7 @@ let ReaderMode = {
|
||||
return article;
|
||||
}
|
||||
|
||||
let doc = browser.contentWindow.document;
|
||||
return yield this._readerParse(uri, doc);
|
||||
}),
|
||||
|
||||
|
@ -6,8 +6,6 @@
|
||||
<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>
|
||||
|
@ -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/. */
|
||||
|
||||
"use strict";
|
||||
let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
|
||||
|
||||
window.addEventListener("DOMContentLoaded", function () {
|
||||
document.dispatchEvent(new CustomEvent("AboutReaderContentLoaded", { bubbles: true }));
|
||||
});
|
||||
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");
|
||||
},
|
||||
|
||||
_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);
|
||||
|
||||
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)
|
||||
}
|
||||
};
|
||||
|
@ -7,6 +7,5 @@
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AboutReader.jsm',
|
||||
'ReaderMode.jsm'
|
||||
]
|
||||
|
Loading…
Reference in New Issue
Block a user