mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-15 11:47:31 +09:00
Initial updates for Reader View.
This commit is contained in:
parent
faaff24342
commit
1c494e80a6
27 changed files with 1757 additions and 1786 deletions
|
|
@ -15,8 +15,7 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|||
XPCOMUtils.defineLazyModuleGetter(this, "AsyncPrefs", "resource://gre/modules/AsyncPrefs.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NarrateControls", "resource://gre/modules/narrate/NarrateControls.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");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
|
||||
|
||||
var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
|
||||
|
||||
|
|
@ -45,45 +44,49 @@ var AboutReader = function(mm, win, articlePromise) {
|
|||
.getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
||||
|
||||
this._article = null;
|
||||
this._languagePromise = new Promise(resolve => {
|
||||
this._foundLanguage = resolve;
|
||||
});
|
||||
|
||||
if (articlePromise) {
|
||||
this._articlePromise = articlePromise;
|
||||
}
|
||||
|
||||
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("moz-reader-content"));
|
||||
this._toolbarElementRef = Cu.getWeakReference(doc.getElementById("reader-toolbar"));
|
||||
this._messageElementRef = Cu.getWeakReference(doc.getElementById("reader-message"));
|
||||
this._headerElementRef = Cu.getWeakReference(doc.querySelector(".reader-header"));
|
||||
this._domainElementRef = Cu.getWeakReference(doc.querySelector(".reader-domain"));
|
||||
this._titleElementRef = Cu.getWeakReference(doc.querySelector(".reader-title"));
|
||||
this._readTimeElementRef = Cu.getWeakReference(doc.querySelector(".reader-estimated-time"));
|
||||
this._creditsElementRef = Cu.getWeakReference(doc.querySelector(".reader-credits"));
|
||||
this._contentElementRef = Cu.getWeakReference(doc.querySelector(".moz-reader-content"));
|
||||
this._toolbarElementRef = Cu.getWeakReference(doc.querySelector(".reader-toolbar"));
|
||||
this._messageElementRef = Cu.getWeakReference(doc.querySelector(".reader-message"));
|
||||
this._containerElementRef = Cu.getWeakReference(doc.querySelector(".container"));
|
||||
|
||||
this._scrollOffset = win.pageYOffset;
|
||||
|
||||
doc.addEventListener("click", this, false);
|
||||
doc.addEventListener("click", this);
|
||||
|
||||
win.addEventListener("pagehide", this, false);
|
||||
win.addEventListener("scroll", this, false);
|
||||
win.addEventListener("resize", this, false);
|
||||
win.addEventListener("pagehide", this);
|
||||
win.addEventListener("scroll", this);
|
||||
win.addEventListener("resize", this);
|
||||
|
||||
Services.obs.addObserver(this, "inner-window-destroyed", false);
|
||||
|
||||
doc.addEventListener("visibilitychange", this, false);
|
||||
doc.addEventListener("visibilitychange", this);
|
||||
|
||||
this._setupStyleDropdown();
|
||||
this._setupButton("close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close");
|
||||
|
||||
const gIsFirefoxDesktop = Services.appinfo.ID == "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}";
|
||||
if (gIsFirefoxDesktop) {
|
||||
// we're ready for any external setup, send a signal for that.
|
||||
this._mm.sendAsyncMessage("Reader:OnSetup");
|
||||
}
|
||||
// we're ready for any external setup, send a signal for that.
|
||||
this._mm.sendAsyncMessage("Reader:OnSetup");
|
||||
|
||||
let colorSchemeValues = JSON.parse(Services.prefs.getCharPref("reader.color_scheme.values"));
|
||||
let colorSchemeOptions = colorSchemeValues.map((value) => {
|
||||
return { name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
|
||||
value: value,
|
||||
itemClass: value + "-button" };
|
||||
return {
|
||||
name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
|
||||
value,
|
||||
itemClass: value + "-button"
|
||||
};
|
||||
});
|
||||
|
||||
let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
|
||||
|
|
@ -114,7 +117,7 @@ var AboutReader = function(mm, win, articlePromise) {
|
|||
this._setupLineHeightButtons();
|
||||
|
||||
if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
|
||||
new NarrateControls(mm, win);
|
||||
new NarrateControls(mm, win, this._languagePromise);
|
||||
}
|
||||
|
||||
this._loadArticle();
|
||||
|
|
@ -146,6 +149,10 @@ AboutReader.prototype = {
|
|||
return this._titleElementRef.get();
|
||||
},
|
||||
|
||||
get _readTimeElement() {
|
||||
return this._readTimeElementRef.get();
|
||||
},
|
||||
|
||||
get _creditsElement() {
|
||||
return this._creditsElementRef.get();
|
||||
},
|
||||
|
|
@ -162,6 +169,10 @@ AboutReader.prototype = {
|
|||
return this._messageElementRef.get();
|
||||
},
|
||||
|
||||
get _containerElement() {
|
||||
return this._containerElementRef.get();
|
||||
},
|
||||
|
||||
get _isToolbarVertical() {
|
||||
if (this._toolbarVertical !== undefined) {
|
||||
return this._toolbarVertical;
|
||||
|
|
@ -178,7 +189,7 @@ AboutReader.prototype = {
|
|||
return _viewId;
|
||||
},
|
||||
|
||||
receiveMessage: function (message) {
|
||||
receiveMessage (message) {
|
||||
switch (message.name) {
|
||||
// Triggered by Android user pressing BACK while the banner font-dropdown is open.
|
||||
case "Reader:CloseDropdown": {
|
||||
|
|
@ -191,14 +202,14 @@ AboutReader.prototype = {
|
|||
if (message.data.id && message.data.image &&
|
||||
!this._doc.getElementById(message.data.id)) {
|
||||
let btn = this._doc.createElement("button");
|
||||
btn.setAttribute("class", "button");
|
||||
btn.setAttribute("class", "button " + message.data.id);
|
||||
btn.setAttribute("style", "background-image: url('" + message.data.image + "')");
|
||||
btn.setAttribute("id", message.data.id);
|
||||
if (message.data.title)
|
||||
btn.setAttribute("title", message.data.title);
|
||||
if (message.data.text)
|
||||
btn.textContent = message.data.text;
|
||||
let tb = this._doc.getElementById("reader-toolbar");
|
||||
let tb = this._toolbarElement;
|
||||
tb.appendChild(btn);
|
||||
this._setupButton(message.data.id, button => {
|
||||
this._mm.sendAsyncMessage("Reader:Clicked-" + button.getAttribute("id"), { article: this._article });
|
||||
|
|
@ -220,18 +231,21 @@ AboutReader.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
handleEvent: function(aEvent) {
|
||||
handleEvent(aEvent) {
|
||||
if (!aEvent.isTrusted)
|
||||
return;
|
||||
|
||||
switch (aEvent.type) {
|
||||
case "click":
|
||||
let target = aEvent.target;
|
||||
if (target.classList.contains('dropdown-toggle')) {
|
||||
if (target.classList.contains("dropdown-toggle")) {
|
||||
this._toggleDropdownClicked(aEvent);
|
||||
} else if (!target.closest('.dropdown-popup')) {
|
||||
} else if (!target.closest(".dropdown-popup")) {
|
||||
this._closeDropdowns();
|
||||
}
|
||||
if (target.tagName == "A" && !target.classList.contains("reader-domain")) {
|
||||
this._linkClicked(aEvent);
|
||||
}
|
||||
break;
|
||||
case "scroll":
|
||||
this._closeDropdowns(true);
|
||||
|
|
@ -243,7 +257,7 @@ AboutReader.prototype = {
|
|||
this._updateImageMargins();
|
||||
if (this._isToolbarVertical) {
|
||||
this._win.setTimeout(() => {
|
||||
for (let dropdown of this._doc.querySelectorAll('.dropdown.open')) {
|
||||
for (let dropdown of this._doc.querySelectorAll(".dropdown.open")) {
|
||||
this._updatePopupPosition(dropdown);
|
||||
}
|
||||
}, 0);
|
||||
|
|
@ -271,12 +285,12 @@ AboutReader.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
observe: function(subject, topic, data) {
|
||||
observe(subject, topic, data) {
|
||||
if (subject.QueryInterface(Ci.nsISupportsPRUint64).data != this._innerWindowId) {
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed", false);
|
||||
Services.obs.removeObserver(this, "inner-window-destroyed");
|
||||
|
||||
this._mm.removeMessageListener("Reader:CloseDropdown", this);
|
||||
this._mm.removeMessageListener("Reader:AddButton", this);
|
||||
|
|
@ -284,12 +298,12 @@ AboutReader.prototype = {
|
|||
this._windowUnloaded = true;
|
||||
},
|
||||
|
||||
_onReaderClose: function() {
|
||||
_onReaderClose() {
|
||||
ReaderMode.leaveReaderMode(this._mm.docShell, this._win);
|
||||
},
|
||||
|
||||
_setFontSize: function(newFontSize) {
|
||||
let containerClasses = this._doc.getElementById("container").classList;
|
||||
_setFontSize(newFontSize) {
|
||||
let containerClasses = this._containerElement.classList;
|
||||
|
||||
if (this._fontSize > 0)
|
||||
containerClasses.remove("font-size" + this._fontSize);
|
||||
|
|
@ -299,19 +313,19 @@ AboutReader.prototype = {
|
|||
return AsyncPrefs.set("reader.font_size", this._fontSize);
|
||||
},
|
||||
|
||||
_setupFontSizeButtons: function() {
|
||||
_setupFontSizeButtons() {
|
||||
const FONT_SIZE_MIN = 1;
|
||||
const FONT_SIZE_MAX = 9;
|
||||
|
||||
// Sample text shown in Android UI.
|
||||
let sampleText = this._doc.getElementById("font-size-sample");
|
||||
let sampleText = this._doc.querySelector(".font-size-sample");
|
||||
sampleText.textContent = gStrings.GetStringFromName("aboutReader.fontTypeSample");
|
||||
|
||||
let currentSize = Services.prefs.getIntPref("reader.font_size");
|
||||
currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize));
|
||||
|
||||
let plusButton = this._doc.getElementById("font-size-plus");
|
||||
let minusButton = this._doc.getElementById("font-size-minus");
|
||||
let plusButton = this._doc.querySelector(".plus-button");
|
||||
let minusButton = this._doc.querySelector(".minus-button");
|
||||
|
||||
function updateControls() {
|
||||
if (currentSize === FONT_SIZE_MIN) {
|
||||
|
|
@ -360,8 +374,8 @@ AboutReader.prototype = {
|
|||
}, true);
|
||||
},
|
||||
|
||||
_setContentWidth: function(newContentWidth) {
|
||||
let containerClasses = this._doc.getElementById("container").classList;
|
||||
_setContentWidth(newContentWidth) {
|
||||
let containerClasses = this._containerElement.classList;
|
||||
|
||||
if (this._contentWidth > 0)
|
||||
containerClasses.remove("content-width" + this._contentWidth);
|
||||
|
|
@ -371,15 +385,15 @@ AboutReader.prototype = {
|
|||
return AsyncPrefs.set("reader.content_width", this._contentWidth);
|
||||
},
|
||||
|
||||
_setupContentWidthButtons: function() {
|
||||
_setupContentWidthButtons() {
|
||||
const CONTENT_WIDTH_MIN = 1;
|
||||
const CONTENT_WIDTH_MAX = 9;
|
||||
|
||||
let currentContentWidth = Services.prefs.getIntPref("reader.content_width");
|
||||
currentContentWidth = Math.max(CONTENT_WIDTH_MIN, Math.min(CONTENT_WIDTH_MAX, currentContentWidth));
|
||||
|
||||
let plusButton = this._doc.getElementById("content-width-plus");
|
||||
let minusButton = this._doc.getElementById("content-width-minus");
|
||||
let plusButton = this._doc.querySelector(".content-width-plus-button");
|
||||
let minusButton = this._doc.querySelector(".content-width-minus-button");
|
||||
|
||||
function updateControls() {
|
||||
if (currentContentWidth === CONTENT_WIDTH_MIN) {
|
||||
|
|
@ -428,8 +442,8 @@ AboutReader.prototype = {
|
|||
}, true);
|
||||
},
|
||||
|
||||
_setLineHeight: function(newLineHeight) {
|
||||
let contentClasses = this._doc.getElementById("moz-reader-content").classList;
|
||||
_setLineHeight(newLineHeight) {
|
||||
let contentClasses = this._contentElement.classList;
|
||||
|
||||
if (this._lineHeight > 0)
|
||||
contentClasses.remove("line-height" + this._lineHeight);
|
||||
|
|
@ -439,15 +453,15 @@ AboutReader.prototype = {
|
|||
return AsyncPrefs.set("reader.line_height", this._lineHeight);
|
||||
},
|
||||
|
||||
_setupLineHeightButtons: function() {
|
||||
_setupLineHeightButtons() {
|
||||
const LINE_HEIGHT_MIN = 1;
|
||||
const LINE_HEIGHT_MAX = 9;
|
||||
|
||||
let currentLineHeight = Services.prefs.getIntPref("reader.line_height");
|
||||
currentLineHeight = Math.max(LINE_HEIGHT_MIN, Math.min(LINE_HEIGHT_MAX, currentLineHeight));
|
||||
|
||||
let plusButton = this._doc.getElementById("line-height-plus");
|
||||
let minusButton = this._doc.getElementById("line-height-minus");
|
||||
let plusButton = this._doc.querySelector(".line-height-plus-button");
|
||||
let minusButton = this._doc.querySelector(".line-height-minus-button");
|
||||
|
||||
function updateControls() {
|
||||
if (currentLineHeight === LINE_HEIGHT_MIN) {
|
||||
|
|
@ -496,7 +510,7 @@ AboutReader.prototype = {
|
|||
}, true);
|
||||
},
|
||||
|
||||
_handleDeviceLight: function(newLux) {
|
||||
_handleDeviceLight(newLux) {
|
||||
// Desired size of the this._luxValues array.
|
||||
let luxValuesSize = 10;
|
||||
// Add new lux value at the front of the array.
|
||||
|
|
@ -513,7 +527,7 @@ AboutReader.prototype = {
|
|||
return;
|
||||
}
|
||||
// Holds the average of the lux values collected in this._luxValues.
|
||||
let averageLuxValue = this._totalLux/luxValuesSize;
|
||||
let averageLuxValue = this._totalLux / luxValuesSize;
|
||||
|
||||
this._updateColorScheme(averageLuxValue);
|
||||
// Pop the oldest value off the array.
|
||||
|
|
@ -522,7 +536,7 @@ AboutReader.prototype = {
|
|||
this._totalLux -= oldLux;
|
||||
},
|
||||
|
||||
_handleVisibilityChange: function() {
|
||||
_handleVisibilityChange() {
|
||||
let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
|
||||
if (colorScheme != "auto") {
|
||||
return;
|
||||
|
|
@ -533,19 +547,19 @@ AboutReader.prototype = {
|
|||
},
|
||||
|
||||
// Setup or teardown the ambient light tracking system.
|
||||
_enableAmbientLighting: function(enable) {
|
||||
_enableAmbientLighting(enable) {
|
||||
if (enable) {
|
||||
this._win.addEventListener("devicelight", this, false);
|
||||
this._win.addEventListener("devicelight", this);
|
||||
this._luxValues = [];
|
||||
this._totalLux = 0;
|
||||
} else {
|
||||
this._win.removeEventListener("devicelight", this, false);
|
||||
this._win.removeEventListener("devicelight", this);
|
||||
delete this._luxValues;
|
||||
delete this._totalLux;
|
||||
}
|
||||
},
|
||||
|
||||
_updateColorScheme: function(luxValue) {
|
||||
_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".
|
||||
|
|
@ -564,7 +578,7 @@ AboutReader.prototype = {
|
|||
this._setColorScheme("light");
|
||||
},
|
||||
|
||||
_setColorScheme: function(newColorScheme) {
|
||||
_setColorScheme(newColorScheme) {
|
||||
// "auto" is not a real color scheme
|
||||
if (this._colorScheme === newColorScheme || newColorScheme === "auto")
|
||||
return;
|
||||
|
|
@ -580,14 +594,14 @@ AboutReader.prototype = {
|
|||
|
||||
// Pref values include "dark", "light", and "auto", which automatically switches
|
||||
// between light and dark color schemes based on the ambient light level.
|
||||
_setColorSchemePref: function(colorSchemePref) {
|
||||
_setColorSchemePref(colorSchemePref) {
|
||||
this._enableAmbientLighting(colorSchemePref === "auto");
|
||||
this._setColorScheme(colorSchemePref);
|
||||
|
||||
AsyncPrefs.set("reader.color_scheme", colorSchemePref);
|
||||
},
|
||||
|
||||
_setFontType: function(newFontType) {
|
||||
_setFontType(newFontType) {
|
||||
if (this._fontType === newFontType)
|
||||
return;
|
||||
|
||||
|
|
@ -602,20 +616,38 @@ AboutReader.prototype = {
|
|||
AsyncPrefs.set("reader.font_type", this._fontType);
|
||||
},
|
||||
|
||||
_setSystemUIVisibility: function(visible) {
|
||||
_setSystemUIVisibility(visible) {
|
||||
this._mm.sendAsyncMessage("Reader:SystemUIVisibility", { visible: visible });
|
||||
},
|
||||
|
||||
_loadArticle: Task.async(function* () {
|
||||
_setToolbarVisibility(visible) {
|
||||
let tb = this._toolbarElement;
|
||||
|
||||
if (visible) {
|
||||
if (tb.style.opacity != "1") {
|
||||
tb.removeAttribute("hidden");
|
||||
tb.style.opacity = "1";
|
||||
}
|
||||
} else if (tb.style.opacity != "0") {
|
||||
tb.addEventListener("transitionend", evt => {
|
||||
if (tb.style.opacity == "0") {
|
||||
tb.setAttribute("hidden", "");
|
||||
}
|
||||
}, { once: true });
|
||||
tb.style.opacity = "0";
|
||||
}
|
||||
},
|
||||
|
||||
async _loadArticle() {
|
||||
let url = this._getOriginalUrl();
|
||||
this._showProgressDelayed();
|
||||
|
||||
let article;
|
||||
if (this._articlePromise) {
|
||||
article = yield this._articlePromise;
|
||||
article = await this._articlePromise;
|
||||
} else {
|
||||
try {
|
||||
article = yield this._getArticle(url);
|
||||
article = await this._getArticle(url);
|
||||
} catch (e) {
|
||||
if (e && e.newURL) {
|
||||
let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
|
||||
|
|
@ -638,24 +670,26 @@ AboutReader.prototype = {
|
|||
}
|
||||
|
||||
this._showContent(article);
|
||||
}),
|
||||
|
||||
_getArticle: function(url) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let listener = (message) => {
|
||||
this._mm.removeMessageListener("Reader:ArticleData", listener);
|
||||
if (message.data.newURL) {
|
||||
reject({ newURL: message.data.newURL });
|
||||
return;
|
||||
}
|
||||
resolve(message.data.article);
|
||||
};
|
||||
this._mm.addMessageListener("Reader:ArticleData", listener);
|
||||
this._mm.sendAsyncMessage("Reader:ArticleGet", { url: url });
|
||||
});
|
||||
},
|
||||
|
||||
_requestFavicon: function() {
|
||||
_getArticle(url) {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// let listener = (message) => {
|
||||
// this._mm.removeMessageListener("Reader:ArticleData", listener);
|
||||
// if (message.data.newURL) {
|
||||
// reject({ newURL: message.data.newURL });
|
||||
// return;
|
||||
// }
|
||||
// resolve(message.data.article);
|
||||
// };
|
||||
// this._mm.addMessageListener("Reader:ArticleData", listener);
|
||||
// this._mm.sendAsyncMessage("Reader:ArticleGet", { url });
|
||||
// });
|
||||
//}
|
||||
return ReaderMode.downloadAndParseDocument(url);
|
||||
},
|
||||
|
||||
_requestFavicon() {
|
||||
let handleFaviconReturn = (message) => {
|
||||
this._mm.removeMessageListener("Reader:FaviconReturn", handleFaviconReturn);
|
||||
this._loadFavicon(message.data.url, message.data.faviconUrl);
|
||||
|
|
@ -665,20 +699,20 @@ AboutReader.prototype = {
|
|||
this._mm.sendAsyncMessage("Reader:FaviconRequest", { url: this._article.url });
|
||||
},
|
||||
|
||||
_loadFavicon: function(url, faviconUrl) {
|
||||
_loadFavicon(url, faviconUrl) {
|
||||
if (this._article.url !== url)
|
||||
return;
|
||||
|
||||
let doc = this._doc;
|
||||
|
||||
let link = doc.createElement('link');
|
||||
link.rel = 'shortcut icon';
|
||||
let link = doc.createElement("link");
|
||||
link.rel = "shortcut icon";
|
||||
link.href = faviconUrl;
|
||||
|
||||
doc.getElementsByTagName('head')[0].appendChild(link);
|
||||
doc.getElementsByTagName("head")[0].appendChild(link);
|
||||
},
|
||||
|
||||
_updateImageMargins: function() {
|
||||
_updateImageMargins() {
|
||||
let windowWidth = this._win.innerWidth;
|
||||
let bodyWidth = this._doc.body.clientWidth;
|
||||
|
||||
|
|
@ -691,7 +725,7 @@ AboutReader.prototype = {
|
|||
}
|
||||
|
||||
// If the image is at least half as wide as the body, center it on desktop.
|
||||
if (img.naturalWidth >= bodyWidth/2) {
|
||||
if (img.naturalWidth >= bodyWidth / 2) {
|
||||
img.setAttribute("moz-reader-center", true);
|
||||
} else {
|
||||
img.removeAttribute("moz-reader-center");
|
||||
|
|
@ -713,30 +747,32 @@ AboutReader.prototype = {
|
|||
},
|
||||
|
||||
_maybeSetTextDirection: function Read_maybeSetTextDirection(article) {
|
||||
if (!article.dir)
|
||||
return;
|
||||
if (article.dir) {
|
||||
// Set "dir" attribute on content
|
||||
this._contentElement.setAttribute("dir", article.dir);
|
||||
this._headerElement.setAttribute("dir", article.dir);
|
||||
|
||||
// Set "dir" attribute on content
|
||||
this._contentElement.setAttribute("dir", article.dir);
|
||||
this._headerElement.setAttribute("dir", article.dir);
|
||||
},
|
||||
|
||||
_fixLocalLinks() {
|
||||
// We need to do this because preprocessing the content through nsIParserUtils
|
||||
// gives back a DOM with a <base> element. That influences how these URLs get
|
||||
// resolved, making them no longer match the document URI (which is
|
||||
// about:reader?url=...). To fix this, make all the hash URIs absolute. This
|
||||
// is hacky, but the alternative of removing the base element has potential
|
||||
// security implications if Readability has not successfully made all the URLs
|
||||
// absolute, so we pick just fixing these in-document links explicitly.
|
||||
let localLinks = this._contentElement.querySelectorAll("a[href^='#']");
|
||||
for (let localLink of localLinks) {
|
||||
// Have to get the attribute because .href provides an absolute URI.
|
||||
localLink.href = this._doc.documentURI + localLink.getAttribute("href");
|
||||
// The native locale could be set differently than the article's text direction.
|
||||
var localeDirection = Services.locale.isAppLocaleRTL ? "rtl" : "ltr";
|
||||
this._readTimeElement.setAttribute("dir", localeDirection);
|
||||
this._readTimeElement.style.textAlign = article.dir == "rtl" ? "right" : "left";
|
||||
}
|
||||
},
|
||||
|
||||
_showError: function() {
|
||||
_formatReadTime(slowEstimate, fastEstimate) {
|
||||
let displayStringKey = "aboutReader.estimatedReadTimeRange1";
|
||||
|
||||
// only show one reading estimate when they are the same value
|
||||
if (slowEstimate == fastEstimate) {
|
||||
displayStringKey = "aboutReader.estimatedReadTimeValue1";
|
||||
}
|
||||
|
||||
return PluralForm.get(slowEstimate, gStrings.GetStringFromName(displayStringKey))
|
||||
.replace("#1", fastEstimate)
|
||||
.replace("#2", slowEstimate);
|
||||
},
|
||||
|
||||
_showError() {
|
||||
this._headerElement.style.display = "none";
|
||||
this._contentElement.style.display = "none";
|
||||
|
||||
|
|
@ -746,11 +782,16 @@ AboutReader.prototype = {
|
|||
|
||||
this._doc.title = errorMessage;
|
||||
|
||||
this._doc.documentElement.dataset.isError = true;
|
||||
|
||||
this._error = true;
|
||||
|
||||
this._doc.dispatchEvent(
|
||||
new this._win.CustomEvent("AboutReaderContentError", { bubbles: true, cancelable: false }));
|
||||
},
|
||||
|
||||
// This function is the JS version of Java's StringUtils.stripCommonSubdomains.
|
||||
_stripHost: function(host) {
|
||||
_stripHost(host) {
|
||||
if (!host)
|
||||
return host;
|
||||
|
||||
|
|
@ -766,17 +807,18 @@ AboutReader.prototype = {
|
|||
return host.substring(start);
|
||||
},
|
||||
|
||||
_showContent: function(article) {
|
||||
_showContent(article) {
|
||||
this._messageElement.style.display = "none";
|
||||
|
||||
this._article = article;
|
||||
|
||||
this._domainElement.href = article.url;
|
||||
let articleUri = Services.io.newURI(article.url, null, null);
|
||||
let articleUri = Services.io.newURI(article.url);
|
||||
this._domainElement.textContent = this._stripHost(articleUri.host);
|
||||
this._creditsElement.textContent = article.byline;
|
||||
|
||||
this._titleElement.textContent = article.title;
|
||||
this._readTimeElement.textContent = this._formatReadTime(article.readingTimeMinsSlow, article.readingTimeMinsFast);
|
||||
this._doc.title = article.title;
|
||||
|
||||
this._headerElement.style.display = "block";
|
||||
|
|
@ -787,8 +829,8 @@ AboutReader.prototype = {
|
|||
false, articleUri, this._contentElement);
|
||||
this._contentElement.innerHTML = "";
|
||||
this._contentElement.appendChild(contentFragment);
|
||||
this._fixLocalLinks();
|
||||
this._maybeSetTextDirection(article);
|
||||
this._foundLanguage(article.language);
|
||||
|
||||
this._contentElement.style.display = "block";
|
||||
this._updateImageMargins();
|
||||
|
|
@ -804,13 +846,13 @@ AboutReader.prototype = {
|
|||
new this._win.CustomEvent("AboutReaderContentReady", { bubbles: true, cancelable: false }));
|
||||
},
|
||||
|
||||
_hideContent: function() {
|
||||
_hideContent() {
|
||||
this._headerElement.style.display = "none";
|
||||
this._contentElement.style.display = "none";
|
||||
},
|
||||
|
||||
_showProgressDelayed: function() {
|
||||
this._win.setTimeout(function() {
|
||||
_showProgressDelayed() {
|
||||
this._win.setTimeout(() => {
|
||||
// No need to show progress if the article has been loaded,
|
||||
// if the window has been unloaded, or if there was an error
|
||||
// trying to load the article.
|
||||
|
|
@ -823,20 +865,20 @@ AboutReader.prototype = {
|
|||
|
||||
this._messageElement.textContent = gStrings.GetStringFromName("aboutReader.loading2");
|
||||
this._messageElement.style.display = "block";
|
||||
}.bind(this), 300);
|
||||
}, 300);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the original article URL for this about:reader view.
|
||||
*/
|
||||
_getOriginalUrl: function(win) {
|
||||
_getOriginalUrl(win) {
|
||||
let url = win ? win.location.href : this._win.location.href;
|
||||
return ReaderMode.getOriginalUrl(url) || url;
|
||||
},
|
||||
|
||||
_setupSegmentedButton: function(id, options, initialValue, callback) {
|
||||
_setupSegmentedButton(id, options, initialValue, callback) {
|
||||
let doc = this._doc;
|
||||
let segmentedButton = doc.getElementById(id);
|
||||
let segmentedButton = doc.getElementsByClassName(id)[0];
|
||||
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
let option = options[i];
|
||||
|
|
@ -867,10 +909,6 @@ AboutReader.prototype = {
|
|||
|
||||
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");
|
||||
|
|
@ -878,19 +916,19 @@ AboutReader.prototype = {
|
|||
|
||||
item.classList.add("selected");
|
||||
callback(option.value);
|
||||
}.bind(this), true);
|
||||
}, true);
|
||||
|
||||
if (option.value === initialValue)
|
||||
item.classList.add("selected");
|
||||
}
|
||||
},
|
||||
|
||||
_setupButton: function(id, callback, titleEntity, textEntity) {
|
||||
_setupButton(id, callback, titleEntity, textEntity) {
|
||||
if (titleEntity) {
|
||||
this._setButtonTip(id, titleEntity);
|
||||
}
|
||||
|
||||
let button = this._doc.getElementById(id);
|
||||
let button = this._doc.getElementsByClassName(id)[0];
|
||||
if (textEntity) {
|
||||
button.textContent = gStrings.GetStringFromName(textEntity);
|
||||
}
|
||||
|
|
@ -910,17 +948,17 @@ AboutReader.prototype = {
|
|||
* and dynamically as button state changes.
|
||||
* @param Localizable string providing UI element usage tip.
|
||||
*/
|
||||
_setButtonTip: function(id, titleEntity) {
|
||||
let button = this._doc.getElementById(id);
|
||||
_setButtonTip(id, titleEntity) {
|
||||
let button = this._doc.getElementsByClassName(id)[0];
|
||||
button.setAttribute("title", gStrings.GetStringFromName(titleEntity));
|
||||
},
|
||||
|
||||
_setupStyleDropdown: function() {
|
||||
let dropdownToggle = this._doc.querySelector("#style-dropdown .dropdown-toggle");
|
||||
_setupStyleDropdown() {
|
||||
let dropdownToggle = this._doc.querySelector(".style-dropdown .dropdown-toggle");
|
||||
dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
|
||||
},
|
||||
|
||||
_updatePopupPosition: function(dropdown) {
|
||||
_updatePopupPosition(dropdown) {
|
||||
let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
|
||||
let dropdownPopup = dropdown.querySelector(".dropdown-popup");
|
||||
|
||||
|
|
@ -931,8 +969,8 @@ AboutReader.prototype = {
|
|||
dropdownPopup.style.top = popupTop + "px";
|
||||
},
|
||||
|
||||
_toggleDropdownClicked: function(event) {
|
||||
let dropdown = event.target.closest('.dropdown');
|
||||
_toggleDropdownClicked(event) {
|
||||
let dropdown = event.target.closest(".dropdown");
|
||||
|
||||
if (!dropdown)
|
||||
return;
|
||||
|
|
@ -952,7 +990,7 @@ AboutReader.prototype = {
|
|||
/*
|
||||
* If the ReaderView banner font-dropdown is closed, open it.
|
||||
*/
|
||||
_openDropdown: function(dropdown) {
|
||||
_openDropdown(dropdown) {
|
||||
if (dropdown.classList.contains("open")) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -969,7 +1007,7 @@ AboutReader.prototype = {
|
|||
* dropdowns because the page is scrolling, allow popups to stay open with
|
||||
* the keep-open class.
|
||||
*/
|
||||
_closeDropdowns: function(scrolling) {
|
||||
_closeDropdowns(scrolling) {
|
||||
let selector = ".dropdown.open";
|
||||
if (scrolling) {
|
||||
selector += ":not(.keep-open)";
|
||||
|
|
@ -986,6 +1024,18 @@ AboutReader.prototype = {
|
|||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Override link handling for same-page references so we don't exit Reader View.
|
||||
*/
|
||||
_linkClicked(event) {
|
||||
var originalUrl = Services.io.newURI(this._getOriginalUrl(), null, null);
|
||||
var targetUrl = Services.io.newURI(event.target.href, null, null);
|
||||
if (originalUrl.specIgnoringRef == targetUrl.specIgnoringRef) {
|
||||
event.preventDefault();
|
||||
this._goToReference(targetUrl.ref);
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* Scroll reader view to a reference
|
||||
*/
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue