Bug 1331981 - Fixed Refresh in Reader Mode for text/plain documents. r=mtigley

Differential Revision: https://phabricator.services.mozilla.com/D137253
This commit is contained in:
Tyler Kabaker 2022-02-14 17:36:03 +00:00
parent a0b9f66470
commit 361867b5bb
8 changed files with 183 additions and 74 deletions

View File

@ -22,6 +22,8 @@ ChromeUtils.defineModuleGetter(
"resource://gre/modules/Readerable.jsm"
);
var gUrlsToDocContentType = new Map();
class AboutReaderChild extends JSWindowActorChild {
constructor() {
super();
@ -47,6 +49,10 @@ class AboutReaderChild extends JSWindowActorChild {
switch (message.name) {
case "Reader:ToggleReaderMode":
if (!this.isAboutReader) {
gUrlsToDocContentType.set(
this.document.URL,
this.document.contentType
);
this._articlePromise = ReaderMode.parseDocument(this.document).catch(
Cu.reportError
);
@ -103,17 +109,25 @@ class AboutReaderChild extends JSWindowActorChild {
}
if (this.document.body) {
let url = this.document.documentURI;
if (!this._articlePromise) {
let url = this.document.documentURI;
url = decodeURIComponent(url.substr("about:reader?url=".length));
this._articlePromise = this.sendQuery("Reader:GetCachedArticle", {
url,
});
}
// Update the toolbar icon to show the "reader active" icon.
this.sendAsyncMessage("Reader:UpdateReaderButton");
this._reader = new AboutReader(this, this._articlePromise);
let docContentType =
gUrlsToDocContentType.get(url) === "text/plain"
? "text/plain"
: "document";
this._reader = new AboutReader(
this,
this._articlePromise,
docContentType
);
this._articlePromise = null;
}
break;

View File

@ -47,7 +47,7 @@ const zoomOnMeta =
Services.prefs.getIntPref("mousewheel.with_meta.action", 1) == 3;
const isAppLocaleRTL = Services.locale.isAppLocaleRTL;
var AboutReader = function(actor, articlePromise) {
var AboutReader = function(actor, articlePromise, docContentType = "document") {
let win = actor.contentWindow;
let url = this._getOriginalUrl(win);
if (
@ -218,7 +218,7 @@ var AboutReader = function(actor, articlePromise) {
new NarrateControls(win, this._languagePromise);
}
this._loadArticle();
this._loadArticle(docContentType);
let dropdown = this._toolbarElement;
@ -782,7 +782,7 @@ AboutReader.prototype = {
AsyncPrefs.set("reader.font_type", this._fontType);
},
async _loadArticle() {
async _loadArticle(docContentType = "document") {
let url = this._getOriginalUrl();
this._showProgressDelayed();
@ -793,7 +793,10 @@ AboutReader.prototype = {
if (!article) {
try {
article = await ReaderMode.downloadAndParseDocument(url);
article = await ReaderMode.downloadAndParseDocument(
url,
docContentType
);
} catch (e) {
if (e && e.newURL) {
let readerURL = "about:reader?url=" + encodeURIComponent(e.newURL);
@ -1009,7 +1012,17 @@ AboutReader.prototype = {
this._domainElement.href = article.url;
let articleUri = Services.io.newURI(article.url);
this._domainElement.textContent = this._stripHost(articleUri.host);
try {
this._domainElement.textContent = this._stripHost(articleUri.host);
} catch (ex) {
let url = this._actor.document.URL;
url = url.substring(url.indexOf("%2F") + 6);
url = url.substring(0, url.indexOf("%2F"));
this._domainElement.textContent = url;
}
this._creditsElement.textContent = article.byline;
this._titleElement.textContent = article.title;

View File

@ -263,12 +263,11 @@ var ReaderMode = {
* @return {Promise}
* @resolves JS object representing the article, or null if no article is found.
*/
async downloadAndParseDocument(url) {
let doc = await this._downloadDocument(url);
async downloadAndParseDocument(url, docContentType = "document") {
let doc = await this._downloadDocument(url, docContentType);
if (!doc) {
return null;
}
if (
} else if (
!Readerable.shouldCheckUri(doc.documentURIObject) ||
!Readerable.shouldCheckUri(doc.baseURIObject, true)
) {
@ -279,7 +278,7 @@ var ReaderMode = {
return this._readerParse(doc);
},
_downloadDocument(url) {
_downloadDocument(url, docContentType = "document") {
try {
if (!Readerable.shouldCheckUri(Services.io.newURI(url))) {
return null;
@ -297,7 +296,7 @@ var ReaderMode = {
let xhr = new XMLHttpRequest();
xhr.open("GET", url, true);
xhr.onerror = evt => reject(evt.error);
xhr.responseType = "document";
xhr.responseType = docContentType === "text/plain" ? "text" : "document";
xhr.onload = evt => {
if (xhr.status !== 200) {
reject("Reader mode XHR failed with status: " + xhr.status);
@ -305,76 +304,84 @@ var ReaderMode = {
return;
}
let doc = xhr.responseXML;
let doc =
xhr.responseType === "text" ? xhr.responseText : xhr.responseXML;
if (!doc) {
reject("Reader mode XHR didn't return a document");
histogram.add(DOWNLOAD_ERROR_NO_DOC);
return;
}
// Manually follow a meta refresh tag if one exists.
let meta = doc.querySelector("meta[http-equiv=refresh]");
if (meta) {
let content = meta.getAttribute("content");
if (content) {
let urlIndex = content.toUpperCase().indexOf("URL=");
if (urlIndex > -1) {
let baseURI = Services.io.newURI(url);
let newURI = Services.io.newURI(
content.substring(urlIndex + 4),
null,
baseURI
);
let newURL = newURI.spec;
let ssm = Services.scriptSecurityManager;
let flags =
ssm.LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
ssm.DISALLOW_INHERIT_PRINCIPAL;
try {
ssm.checkLoadURIStrWithPrincipal(
doc.nodePrincipal,
newURL,
flags
if (xhr.responseType === "document") {
// Manually follow a meta refresh tag if one exists.
let meta = doc.querySelector("meta[http-equiv=refresh]");
if (meta) {
let content = meta.getAttribute("content");
if (content) {
let urlIndex = content.toUpperCase().indexOf("URL=");
if (urlIndex > -1) {
let baseURI = Services.io.newURI(url);
let newURI = Services.io.newURI(
content.substring(urlIndex + 4),
null,
baseURI
);
} catch (ex) {
let errorMsg =
"Reader mode disallowed meta refresh (reason: " + ex + ").";
let newURL = newURI.spec;
let ssm = Services.scriptSecurityManager;
let flags =
ssm.LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT |
ssm.DISALLOW_INHERIT_PRINCIPAL;
try {
ssm.checkLoadURIStrWithPrincipal(
doc.nodePrincipal,
newURL,
flags
);
} catch (ex) {
let errorMsg =
"Reader mode disallowed meta refresh (reason: " + ex + ").";
if (Services.prefs.getBoolPref("reader.errors.includeURLs")) {
errorMsg += " Refresh target URI: '" + newURL + "'.";
if (Services.prefs.getBoolPref("reader.errors.includeURLs")) {
errorMsg += " Refresh target URI: '" + newURL + "'.";
}
reject(errorMsg);
return;
}
// Otherwise, pass an object indicating our new URL:
if (!baseURI.equalsExceptRef(newURI)) {
reject({ newURL });
return;
}
reject(errorMsg);
return;
}
// Otherwise, pass an object indicating our new URL:
if (!baseURI.equalsExceptRef(newURI)) {
reject({ newURL });
return;
}
}
}
}
let responseURL = xhr.responseURL;
let givenURL = url;
// Convert these to real URIs to make sure the escaping (or lack
// thereof) is identical:
try {
responseURL = Services.io.newURI(responseURL).specIgnoringRef;
} catch (ex) {
/* Ignore errors - we'll use what we had before */
}
try {
givenURL = Services.io.newURI(givenURL).specIgnoringRef;
} catch (ex) {
/* Ignore errors - we'll use what we had before */
let responseURL = xhr.responseURL;
let givenURL = url;
// Convert these to real URIs to make sure the escaping (or lack
// thereof) is identical:
try {
responseURL = Services.io.newURI(responseURL).specIgnoringRef;
} catch (ex) {
/* Ignore errors - we'll use what we had before */
}
try {
givenURL = Services.io.newURI(givenURL).specIgnoringRef;
} catch (ex) {
/* Ignore errors - we'll use what we had before */
}
if (responseURL != givenURL) {
// We were redirected without a meta refresh tag.
// Force redirect to the correct place:
reject({ newURL: xhr.responseURL });
return;
}
} else {
let parser = new DOMParser();
let htmlString = `<pre>${doc}</pre>`;
doc = parser.parseFromString(htmlString, "text/html");
}
if (responseURL != givenURL) {
// We were redirected without a meta refresh tag.
// Force redirect to the correct place:
reject({ newURL: xhr.responseURL });
return;
}
resolve(doc);
histogram.add(DOWNLOAD_SUCCESS);
};
@ -479,14 +486,26 @@ var ReaderMode = {
// document might be nuked but we will still want the URI.
let { documentURI } = doc;
let uriParam = {
let uriParam;
uriParam = {
spec: doc.baseURIObject.spec,
host: doc.baseURIObject.host,
prePath: doc.baseURIObject.prePath,
scheme: doc.baseURIObject.scheme,
pathBase: Services.io.newURI(".", null, doc.baseURIObject).spec,
// Fallback
host: documentURI,
pathBase: documentURI,
};
// nsIURI.host throws an exception if a host doesn't exist.
try {
uriParam.host = doc.baseURIObject.host;
uriParam.pathBase = Services.io.newURI(".", null, doc.baseURIObject).spec;
} catch (ex) {
// Fall back to the initial values we assigned.
console.warn("Error accessing host name: ", ex);
}
// convert text/plain document, if any, to XHTML format
if (this._isDocumentPlainText(doc)) {
doc = this._convertPlainTextDocument(doc);

View File

@ -58,7 +58,7 @@ var Readerable = {
],
shouldCheckUri(uri, isBaseUri = false) {
if (!["http", "https", "file"].includes(uri.scheme)) {
if (!["http", "https", "file", "moz-nullprincipal"].includes(uri.scheme)) {
return false;
}

View File

@ -23,6 +23,10 @@ skip-if = debug && os == "linux" && os_version == "18.04" #bug 1638027
support-files =
readerModeArticleShort.html
readerModeArticleMedium.html
[browser_readerMode_refresh.js]
support-files =
readerModeArticleShort.html
readerModeArticleTextPlain.txt
[browser_readerMode_with_anchor.js]
support-files =
readerModeArticle.html

View File

@ -0,0 +1,45 @@
/* 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";
const TEST_PATH = getRootDirectory(gTestPath).replace(
"chrome://mochitests/content",
"http://example.com"
);
async function testRefresh(url) {
// Open an article in a browser tab
await BrowserTestUtils.withNewTab(url, async function(browser) {
let pageShownPromise = BrowserTestUtils.waitForContentEvent(
browser,
"AboutReaderContentReady"
);
let readerButton = document.getElementById("reader-mode-button");
let refreshButton = document.getElementById("reload-button");
// Enter Reader Mode
readerButton.click();
await pageShownPromise;
// Refresh the page
refreshButton.click();
await pageShownPromise;
await SpecialPowers.spawn(browser, [], () => {
ok(
!content.document.documentElement.hasAttribute("data-is-error"),
"The data-is-error attribute is present when Reader Mode failed to load an article."
);
});
});
}
add_task(async function() {
// Testing a non-text/plain document
await testRefresh(TEST_PATH + "readerModeArticle.html");
// Testing a test/plain document
await testRefresh(TEST_PATH + "readerModeArticleTextPlain.txt");
});

View File

@ -0,0 +1,10 @@
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tortor id aliquet lectus proin nibh nisl condimentum. Eget magna fermentum iaculis eu non diam phasellus. Sed viverra tellus in hac habitasse platea dictumst. Quis commodo odio aenean sed. Diam vulputate ut pharetra sit amet aliquam id diam. Felis imperdiet proin fermentum leo vel orci. Diam vel quam elementum pulvinar. Vestibulum lectus mauris ultrices eros in cursus turpis massa. Sagittis vitae et leo duis ut diam. Quam elementum pulvinar etiam non quam lacus suspendisse faucibus interdum. At augue eget arcu dictum varius duis at consectetur. Bibendum enim facilisis gravida neque convallis a cras semper auctor. Suspendisse interdum consectetur libero id faucibus. Neque ornare aenean euismod elementum nisi.
Lacus sed turpis tincidunt id aliquet. Euismod nisi porta lorem mollis. Sollicitudin aliquam ultrices sagittis orci. A diam sollicitudin tempor id eu nisl nunc. Molestie a iaculis at erat pellentesque adipiscing commodo elit. Tellus mauris a diam maecenas. Dolor morbi non arcu risus quis. Dictum non consectetur a erat nam at lectus. Convallis posuere morbi leo urna molestie. Blandit turpis cursus in hac habitasse platea dictumst quisque sagittis. Sed ullamcorper morbi tincidunt ornare massa eget egestas. Sit amet risus nullam eget felis eget nunc. Turpis in eu mi bibendum neque egestas congue. Accumsan in nisl nisi scelerisque eu ultrices vitae. Vel quam elementum pulvinar etiam non quam lacus.
Erat velit scelerisque in dictum non consectetur a. Vulputate sapien nec sagittis aliquam malesuada bibendum. Odio facilisis mauris sit amet massa vitae tortor condimentum lacinia. Tempor nec feugiat nisl pretium. At urna condimentum mattis pellentesque id nibh tortor. Viverra tellus in hac habitasse platea dictumst. Turpis massa tincidunt dui ut ornare. Nunc id cursus metus aliquam eleifend mi. Etiam dignissim diam quis enim lobortis scelerisque fermentum. Aenean sed adipiscing diam donec adipiscing tristique risus nec feugiat. Vitae aliquet nec ullamcorper sit amet risus nullam eget felis. Quis hendrerit dolor magna eget est lorem ipsum dolor. Ultrices vitae auctor eu augue ut lectus. Curabitur gravida arcu ac tortor dignissim convallis. Justo laoreet sit amet cursus sit. Lorem ipsum dolor sit amet. Sed sed risus pretium quam vulputate dignissim suspendisse in.
Egestas erat imperdiet sed euismod nisi porta lorem mollis. Pharetra magna ac placerat vestibulum lectus mauris ultrices eros in. Est ante in nibh mauris cursus mattis. Habitasse platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim. Nunc aliquet bibendum enim facilisis gravida neque. Massa sapien faucibus et molestie. Sapien eget mi proin sed libero enim sed faucibus. Mauris a diam maecenas sed enim ut sem. Consectetur adipiscing elit duis tristique sollicitudin nibh sit. Sed arcu non odio euismod lacinia at.
Ultricies mi quis hendrerit dolor. A erat nam at lectus urna duis convallis convallis tellus. Est sit amet facilisis magna etiam tempor orci. Porttitor massa id neque aliquam vestibulum. Lobortis feugiat vivamus at augue eget arcu dictum varius duis. Diam sit amet nisl suscipit adipiscing. Leo in vitae turpis massa. Netus et malesuada fames ac. Ac turpis egestas sed tempus urna et pharetra. Ut eu sem integer vitae justo. At erat pellentesque adipiscing commodo elit at. Consectetur purus ut faucibus pulvinar elementum integer enim. Cursus eget nunc scelerisque viverra mauris in aliquam sem. Aenean et tortor at risus viverra adipiscing at in. Platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras. Tincidunt id aliquet risus feugiat in ante. Amet consectetur adipiscing elit pellentesque. Dignissim enim sit amet venenatis urna cursus eget nunc. Sit amet porttitor eget dolor morbi non.

View File

@ -49,6 +49,10 @@ body {
/* light colours */
}
pre {
font-family: inherit;
}
body.sepia {
--main-background: #f4ecd8;
--main-foreground: #5b4636;