123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936 |
- /* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
- "use strict";
- var Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
- this.EXPORTED_SYMBOLS = [ "AboutReader" ];
- Cu.import("resource://gre/modules/ReaderMode.jsm");
- Cu.import("resource://gre/modules/Services.jsm");
- 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, "PluralForm", "resource://gre/modules/PluralForm.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
- var gStrings = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
- var AboutReader = function(win, articlePromise) {
- let url = this._getOriginalUrl(win);
- if (!(url.startsWith("http://") || url.startsWith("https://"))) {
- let errorMsg = "Only http:// and https:// URLs can be loaded in about:reader.";
- if (Services.prefs.getBoolPref("reader.errors.includeURLs"))
- errorMsg += " Tried to load: " + url + ".";
- Cu.reportError(errorMsg);
- win.location.href = "about:blank";
- return;
- }
- let doc = win.document;
- this._docRef = Cu.getWeakReference(doc);
- this._winRef = Cu.getWeakReference(win);
- this._innerWindowId = win.QueryInterface(Ci.nsIInterfaceRequestor)
- .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.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("mousedown", this);
- doc.addEventListener("click", this);
- win.addEventListener("pagehide", this);
- win.addEventListener("scroll", this);
- win.addEventListener("resize", this);
- win.addEventListener("AboutReaderAddButton", this, false, true);
- win.addEventListener("AboutReaderRemoveButton", this, false, true);
- Services.obs.addObserver(this, "inner-window-destroyed", false);
- this._setupStyleDropdown();
- this._setupButton("close-button", this._onReaderClose.bind(this), "aboutReader.toolbar.close");
- // we're ready for any external setup, send a signal for that.
- doc.dispatchEvent(
- new win.CustomEvent("AboutReaderOnSetup", { bubbles: true, cancelable: false }));
- let colorSchemeValues = JSON.parse(Services.prefs.getCharPref("reader.color_scheme.values"));
- let colorSchemeOptions = colorSchemeValues.map((value) => {
- return {
- name: gStrings.GetStringFromName("aboutReader.colorScheme." + value),
- value,
- itemClass: value + "-button"
- };
- });
- 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.fontType.sans-serif"),
- value: "sans-serif",
- itemClass: "sans-serif-button"
- },
- { name: fontTypeSample,
- description: gStrings.GetStringFromName("aboutReader.fontType.serif"),
- value: "serif",
- itemClass: "serif-button" },
- ];
- let fontType = Services.prefs.getCharPref("reader.font_type");
- this._setupSegmentedButton("font-type-buttons", fontTypeOptions, fontType, this._setFontType.bind(this));
- this._setFontType(fontType);
- this._setupFontSizeButtons();
- this._setupContentWidthButtons();
- this._setupLineHeightButtons();
- if (win.speechSynthesis && Services.prefs.getBoolPref("narrate.enabled")) {
- new NarrateControls(win, this._languagePromise);
- }
- this._loadArticle();
- let dropdown = this._toolbarElement;
- let elemL10nMap = {
- ".minus-button": "minus",
- ".plus-button": "plus",
- ".content-width-minus-button": "contentwidthminus",
- ".content-width-plus-button": "contentwidthplus",
- ".line-height-minus-button": "lineheightminus",
- ".line-height-plus-button": "lineheightplus",
- ".light-button": "colorschemelight",
- ".dark-button": "colorschemedark",
- ".sepia-button": "colorschemesepia",
- };
- for (let [selector, stringID] of Object.entries(elemL10nMap)) {
- dropdown.querySelector(selector).setAttribute("title",
- gStrings.GetStringFromName("aboutReader.toolbar." + stringID));
- }
- };
- 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 _readTimeElement() {
- return this._readTimeElementRef.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();
- },
- get _containerElement() {
- return this._containerElementRef.get();
- },
- get _isToolbarVertical() {
- if (this._toolbarVertical !== undefined) {
- return this._toolbarVertical;
- }
- return this._toolbarVertical = Services.prefs.getBoolPref("reader.toolbar.vertical");
- },
- // Provides unique view Id.
- get viewId() {
- let _viewId = Cc["@mozilla.org/uuid-generator;1"].
- getService(Ci.nsIUUIDGenerator).generateUUID().toString();
- Object.defineProperty(this, "viewId", { value: _viewId });
- return _viewId;
- },
- handleEvent(aEvent) {
- if (!aEvent.isTrusted)
- return;
- let target = aEvent.target;
- switch (aEvent.type) {
- case "mousedown":
- if (!target.closest(".dropdown-popup")) {
- this._closeDropdowns();
- }
- break;
- case "click":
- if (target.classList.contains("dropdown-toggle")) {
- this._toggleDropdownClicked(aEvent);
- }
- break;
- case "scroll":
- this._closeDropdowns(true);
- this._scrollOffset = aEvent.pageY;
- break;
- case "resize":
- this._updateImageMargins();
- if (this._isToolbarVertical) {
- this._win.setTimeout(() => {
- for (let dropdown of this._doc.querySelectorAll(".dropdown.open")) {
- this._updatePopupPosition(dropdown);
- }
- }, 0);
- }
- break;
- case "pagehide":
- // Close the Banners Font-dropdown, cleanup Android BackPressListener.
- this._closeDropdowns();
- this._windowUnloaded = true;
- break;
- case "AboutReaderAddButton": {
- if (aEvent.detail.id && aEvent.detail.image &&
- !this._doc.getElementById(aEvent.detail.id)) {
- let btn = this._doc.createElement("button");
- btn.setAttribute("class", "button " + aEvent.detail.id);
- btn.setAttribute("style", "background-image: url('" + aEvent.detail.image + "')");
- btn.setAttribute("id", aEvent.detail.id);
- if (aEvent.detail.title)
- btn.setAttribute("title", aEvent.detail.title);
- if (aEvent.detail.text)
- btn.textContent = aEvent.detail.text;
- let tb = this._toolbarElement;
- tb.appendChild(btn);
- this._setupButton(aEvent.detail.id, button => {
- var data = { article: this._article };
- this._doc.dispatchEvent(
- new this._win.CustomEvent("AboutReaderButtonClicked-" + button.getAttribute("id"), {detail: data, bubbles: true, cancelable: false}));
- });
- }
- break;
- }
- case "AboutReaderRemoveButton": {
- if (aEvent.detail.id) {
- let btn = this._doc.getElementById(aEvent.detail.id);
- if (btn)
- btn.remove();
- }
- break;
- }
- }
- },
- observe(subject, topic, data) {
- if (subject.QueryInterface(Ci.nsISupportsPRUint64).data != this._innerWindowId) {
- return;
- }
- Services.obs.removeObserver(this, "inner-window-destroyed");
- this._windowUnloaded = true;
- },
- _onReaderClose() {
- ReaderMode.leaveReaderMode(this._win.document.docShell, this._win);
- },
- _setFontSize(newFontSize) {
- this._fontSize = newFontSize;
- let size = (10 + 2 * this._fontSize) + "px";
- this._containerElement.style.setProperty("--font-size", size);
- return AsyncPrefs.set("reader.font_size", this._fontSize);
- },
- _setupFontSizeButtons() {
- const FONT_SIZE_MIN = 1;
- const FONT_SIZE_MAX = 9;
- let currentSize = Services.prefs.getIntPref("reader.font_size");
- currentSize = Math.max(FONT_SIZE_MIN, Math.min(FONT_SIZE_MAX, currentSize));
- let plusButton = this._doc.querySelector(".plus-button");
- let minusButton = this._doc.querySelector(".minus-button");
- function updateControls() {
- if (currentSize === FONT_SIZE_MIN) {
- minusButton.setAttribute("disabled", true);
- } else {
- minusButton.removeAttribute("disabled");
- }
- if (currentSize === FONT_SIZE_MAX) {
- plusButton.setAttribute("disabled", true);
- } else {
- plusButton.removeAttribute("disabled");
- }
- }
- updateControls();
- this._setFontSize(currentSize);
- plusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentSize >= FONT_SIZE_MAX) {
- return;
- }
- currentSize++;
- updateControls();
- this._setFontSize(currentSize);
- }, true);
- minusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentSize <= FONT_SIZE_MIN) {
- return;
- }
- currentSize--;
- updateControls();
- this._setFontSize(currentSize);
- }, true);
- },
- _setContentWidth(newContentWidth) {
- let containerClasses = this._containerElement.classList;
- if (this._contentWidth > 0)
- containerClasses.remove("content-width" + this._contentWidth);
- this._contentWidth = newContentWidth;
- containerClasses.add("content-width" + this._contentWidth);
- return AsyncPrefs.set("reader.content_width", this._contentWidth);
- },
- _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.querySelector(".content-width-plus-button");
- let minusButton = this._doc.querySelector(".content-width-minus-button");
- function updateControls() {
- if (currentContentWidth === CONTENT_WIDTH_MIN) {
- minusButton.setAttribute("disabled", true);
- } else {
- minusButton.removeAttribute("disabled");
- }
- if (currentContentWidth === CONTENT_WIDTH_MAX) {
- plusButton.setAttribute("disabled", true);
- } else {
- plusButton.removeAttribute("disabled");
- }
- }
- updateControls();
- this._setContentWidth(currentContentWidth);
- plusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentContentWidth >= CONTENT_WIDTH_MAX) {
- return;
- }
- currentContentWidth++;
- updateControls();
- this._setContentWidth(currentContentWidth);
- }, true);
- minusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentContentWidth <= CONTENT_WIDTH_MIN) {
- return;
- }
- currentContentWidth--;
- updateControls();
- this._setContentWidth(currentContentWidth);
- }, true);
- },
- _setLineHeight(newLineHeight) {
- let contentClasses = this._contentElement.classList;
- if (this._lineHeight > 0)
- contentClasses.remove("line-height" + this._lineHeight);
- this._lineHeight = newLineHeight;
- contentClasses.add("line-height" + this._lineHeight);
- return AsyncPrefs.set("reader.line_height", this._lineHeight);
- },
- _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.querySelector(".line-height-plus-button");
- let minusButton = this._doc.querySelector(".line-height-minus-button");
- function updateControls() {
- if (currentLineHeight === LINE_HEIGHT_MIN) {
- minusButton.setAttribute("disabled", true);
- } else {
- minusButton.removeAttribute("disabled");
- }
- if (currentLineHeight === LINE_HEIGHT_MAX) {
- plusButton.setAttribute("disabled", true);
- } else {
- plusButton.removeAttribute("disabled");
- }
- }
- updateControls();
- this._setLineHeight(currentLineHeight);
- plusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentLineHeight >= LINE_HEIGHT_MAX) {
- return;
- }
- currentLineHeight++;
- updateControls();
- this._setLineHeight(currentLineHeight);
- }, true);
- minusButton.addEventListener("click", (event) => {
- if (!event.isTrusted) {
- return;
- }
- event.stopPropagation();
- if (currentLineHeight <= LINE_HEIGHT_MIN) {
- return;
- }
- currentLineHeight--;
- updateControls();
- this._setLineHeight(currentLineHeight);
- }, true);
- },
- _setColorScheme(newColorScheme) {
- if (this._colorScheme === newColorScheme)
- 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 "sepia".
- _setColorSchemePref(colorSchemePref) {
- this._setColorScheme(colorSchemePref);
- AsyncPrefs.set("reader.color_scheme", colorSchemePref);
- },
- _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);
- AsyncPrefs.set("reader.font_type", this._fontType);
- },
- _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 = await this._articlePromise;
- } else {
- try {
- article = await this._getArticle(url);
- } catch (e) {
- if (e && e.newURL) {
- let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
- this._win.location.replace(readerURL);
- return;
- }
- }
- }
- if (this._windowUnloaded) {
- return;
- }
- // Replace the loading message with an error message if there's a failure.
- // Users are supposed to navigate away by themselves (because we cannot
- // remove ourselves from session history.)
- if (!article) {
- this._showError();
- return;
- }
- this._showContent(article);
- },
- _getArticle(url) {
- return ReaderMode.downloadAndParseDocument(url);
- },
- _requestFavicon() {
- let faviconUrl = PlacesUtils.promiseFaviconLinkUrl(this._article.url);
- var self = this;
- faviconUrl.then(function onResolution(favicon) {
- self._loadFavicon(self._article.url, favicon.path.replace(/^favicon:/, ""));
- },
- function onRejection(reason) {
- Cu.reportError("Error requesting favicon URL for about:reader content: " + reason);
- }).catch(Cu.reportError);
- },
- _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() {
- let windowWidth = this._win.innerWidth;
- let bodyWidth = this._doc.body.clientWidth;
- let setImageMargins = function(img) {
- // If the image is at least as wide as the window, make it fill edge-to-edge on mobile.
- if (img.naturalWidth >= windowWidth) {
- img.setAttribute("moz-reader-full-width", true);
- } else {
- img.removeAttribute("moz-reader-full-width");
- }
- // If the image is at least half as wide as the body, center it on desktop.
- if (img.naturalWidth >= bodyWidth / 2) {
- img.setAttribute("moz-reader-center", true);
- } else {
- img.removeAttribute("moz-reader-center");
- }
- };
- let imgs = this._doc.querySelectorAll(this._BLOCK_IMAGES_SELECTOR);
- for (let i = imgs.length; --i >= 0;) {
- let img = imgs[i];
- if (img.naturalWidth > 0) {
- setImageMargins(img);
- } else {
- img.onload = function() {
- setImageMargins(img);
- };
- }
- }
- },
- _maybeSetTextDirection: function Read_maybeSetTextDirection(article) {
- if (article.dir) {
- // Set "dir" attribute on content
- this._contentElement.setAttribute("dir", article.dir);
- this._headerElement.setAttribute("dir", article.dir);
- // 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";
- }
- },
- _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");
- }
- },
- _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";
- let errorMessage = gStrings.GetStringFromName("aboutReader.loadError");
- this._messageElement.textContent = errorMessage;
- this._messageElement.style.display = "block";
- 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(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(article) {
- this._messageElement.style.display = "none";
- this._article = article;
- this._domainElement.href = article.url;
- 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";
- let parserUtils = Cc["@mozilla.org/parserutils;1"].getService(Ci.nsIParserUtils);
- let contentFragment = parserUtils.parseFragment(article.content,
- Ci.nsIParserUtils.SanitizerDropForms | Ci.nsIParserUtils.SanitizerAllowStyle,
- 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();
- this._requestFavicon();
- this._doc.body.classList.add("loaded");
- this._goToReference(articleUri.ref);
- Services.obs.notifyObservers(this._win, "AboutReader:Ready", "");
- this._doc.dispatchEvent(
- new this._win.CustomEvent("AboutReaderContentReady", { bubbles: true, cancelable: false }));
- },
- _hideContent() {
- this._headerElement.style.display = "none";
- this._contentElement.style.display = "none";
- },
- _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.
- if (this._article || this._windowUnloaded || this._error) {
- return;
- }
- this._headerElement.style.display = "none";
- this._contentElement.style.display = "none";
- this._messageElement.textContent = gStrings.GetStringFromName("aboutReader.loading2");
- this._messageElement.style.display = "block";
- }, 300);
- },
- /**
- * Returns the original article URL for this about:reader view.
- */
- _getOriginalUrl(win) {
- let url = win ? win.location.href : this._win.location.href;
- return ReaderMode.getOriginalUrl(url) || url;
- },
- _setupSegmentedButton(id, options, initialValue, callback) {
- let doc = this._doc;
- let segmentedButton = doc.getElementsByClassName(id)[0];
- for (let i = 0; i < options.length; i++) {
- let option = options[i];
- let item = doc.createElement("button");
- // Put the name in a div so that Android can hide it.
- let div = doc.createElement("div");
- div.textContent = option.name;
- div.classList.add("name");
- item.appendChild(div);
- if (option.itemClass !== undefined)
- item.classList.add(option.itemClass);
- if (option.description !== undefined) {
- let description = doc.createElement("div");
- description.textContent = option.description;
- description.classList.add("description");
- item.appendChild(description);
- }
- segmentedButton.appendChild(item);
- item.addEventListener("click", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
- aEvent.stopPropagation();
- 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);
- }, true);
- if (option.value === initialValue)
- item.classList.add("selected");
- }
- },
- _setupButton(id, callback, titleEntity, textEntity) {
- if (titleEntity) {
- this._setButtonTip(id, titleEntity);
- }
- let button = this._doc.getElementsByClassName(id)[0];
- if (textEntity) {
- button.textContent = gStrings.GetStringFromName(textEntity);
- }
- button.removeAttribute("hidden");
- button.addEventListener("click", function(aEvent) {
- if (!aEvent.isTrusted)
- return;
- aEvent.stopPropagation();
- let btn = aEvent.target;
- callback(btn);
- }, true);
- },
- /**
- * Sets a toolTip for a button. Performed at initial button setup
- * and dynamically as button state changes.
- * @param Localizable string providing UI element usage tip.
- */
- _setButtonTip(id, titleEntity) {
- let button = this._doc.getElementsByClassName(id)[0];
- button.setAttribute("title", gStrings.GetStringFromName(titleEntity));
- },
- _setupStyleDropdown() {
- let dropdownToggle = this._doc.querySelector(".style-dropdown .dropdown-toggle");
- dropdownToggle.setAttribute("title", gStrings.GetStringFromName("aboutReader.toolbar.typeControls"));
- },
- _updatePopupPosition(dropdown) {
- let dropdownToggle = dropdown.querySelector(".dropdown-toggle");
- let dropdownPopup = dropdown.querySelector(".dropdown-popup");
- let toggleHeight = dropdownToggle.offsetHeight;
- let toggleTop = dropdownToggle.offsetTop;
- let popupTop = toggleTop - toggleHeight / 2;
- dropdownPopup.style.top = popupTop + "px";
- },
- _toggleDropdownClicked(event) {
- let dropdown = event.target.closest(".dropdown");
- if (!dropdown)
- return;
- event.stopPropagation();
- if (dropdown.classList.contains("open")) {
- this._closeDropdowns();
- } else {
- this._openDropdown(dropdown);
- if (this._isToolbarVertical) {
- this._updatePopupPosition(dropdown);
- }
- }
- },
- /*
- * If the ReaderView banner font-dropdown is closed, open it.
- */
- _openDropdown(dropdown) {
- if (dropdown.classList.contains("open")) {
- return;
- }
- this._closeDropdowns();
- dropdown.classList.add("open");
- },
- /*
- * If the ReaderView has open dropdowns, close them. If we are closing the
- * dropdowns because the page is scrolling, allow popups to stay open with
- * the keep-open class.
- */
- _closeDropdowns(scrolling) {
- let selector = ".dropdown.open";
- if (scrolling) {
- selector += ":not(.keep-open)";
- }
- let openDropdowns = this._doc.querySelectorAll(selector);
- for (let dropdown of openDropdowns) {
- dropdown.classList.remove("open");
- }
- },
- /*
- * Scroll reader view to a reference
- */
- _goToReference(ref) {
- if (ref) {
- this._win.location.hash = ref;
- }
- }
- };
|