summaryrefslogtreecommitdiffstats
path: root/toolkit/components/reader/test
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/reader/test')
-rw-r--r--toolkit/components/reader/test/browser.ini66
-rw-r--r--toolkit/components/reader/test/browser_bug1124271_readerModePinnedTab.js53
-rw-r--r--toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js132
-rw-r--r--toolkit/components/reader/test/browser_drag_url_readerMode.js54
-rw-r--r--toolkit/components/reader/test/browser_localfile_readerMode.js54
-rw-r--r--toolkit/components/reader/test/browser_readerMode.js394
-rw-r--r--toolkit/components/reader/test/browser_readerMode_bc_reuse.js44
-rw-r--r--toolkit/components/reader/test/browser_readerMode_cached.js32
-rw-r--r--toolkit/components/reader/test/browser_readerMode_colorSchemePref.js67
-rw-r--r--toolkit/components/reader/test/browser_readerMode_hidden_nodes.js56
-rw-r--r--toolkit/components/reader/test/browser_readerMode_menu.js69
-rw-r--r--toolkit/components/reader/test/browser_readerMode_pocket.js136
-rw-r--r--toolkit/components/reader/test/browser_readerMode_readingTime.js101
-rw-r--r--toolkit/components/reader/test/browser_readerMode_refresh.js49
-rw-r--r--toolkit/components/reader/test/browser_readerMode_remoteType.js87
-rw-r--r--toolkit/components/reader/test/browser_readerMode_samesite_cookie_redirect.js50
-rw-r--r--toolkit/components/reader/test/browser_readerMode_with_anchor.js89
-rw-r--r--toolkit/components/reader/test/getCookies.sjs16
-rw-r--r--toolkit/components/reader/test/head.js63
-rw-r--r--toolkit/components/reader/test/linkToGetCookies.html13
-rw-r--r--toolkit/components/reader/test/readerModeArticle.html28
-rw-r--r--toolkit/components/reader/test/readerModeArticleHiddenNodes.html22
-rw-r--r--toolkit/components/reader/test/readerModeArticleMedium.html16
-rw-r--r--toolkit/components/reader/test/readerModeArticleShort.html14
-rw-r--r--toolkit/components/reader/test/readerModeArticleTextPlain.txt10
-rw-r--r--toolkit/components/reader/test/readerModeNonArticle.html9
-rw-r--r--toolkit/components/reader/test/readerModeRandom.sjs23
-rw-r--r--toolkit/components/reader/test/setSameSiteCookie.html9
-rw-r--r--toolkit/components/reader/test/setSameSiteCookie.html^headers^1
29 files changed, 1757 insertions, 0 deletions
diff --git a/toolkit/components/reader/test/browser.ini b/toolkit/components/reader/test/browser.ini
new file mode 100644
index 0000000000..ee8c6d2ad8
--- /dev/null
+++ b/toolkit/components/reader/test/browser.ini
@@ -0,0 +1,66 @@
+[DEFAULT]
+support-files = head.js
+[browser_localfile_readerMode.js]
+support-files =
+ readerModeArticle.html
+[browser_readerMode.js]
+support-files =
+ readerModeNonArticle.html
+ readerModeArticle.html
+ readerModeArticleHiddenNodes.html
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_readerMode_bc_reuse.js]
+support-files =
+ readerModeArticle.html
+[browser_readerMode_cached.js]
+support-files =
+ readerModeRandom.sjs
+[browser_readerMode_colorSchemePref.js]
+support-files =
+ readerModeArticle.html
+[browser_readerMode_hidden_nodes.js]
+support-files =
+ readerModeArticleHiddenNodes.html
+[browser_readerMode_menu.js]
+support-files =
+ readerModeArticleShort.html
+[browser_readerMode_pocket.js]
+support-files =
+ readerModeArticleShort.html
+ readerModeArticleMedium.html
+[browser_readerMode_refresh.js]
+support-files =
+ readerModeArticleShort.html
+ readerModeArticleTextPlain.txt
+skip-if =
+ os == "win" && os_version == "6.1" # Skip on Azure - frequent failure
+[browser_readerMode_samesite_cookie_redirect.js]
+support-files =
+ getCookies.sjs
+ setSameSiteCookie.html
+ setSameSiteCookie.html^headers^
+[browser_readerMode_with_anchor.js]
+support-files =
+ readerModeArticle.html
+[browser_bug1124271_readerModePinnedTab.js]
+support-files =
+ readerModeArticle.html
+[browser_bug1453818_samesite_cookie.js]
+support-files =
+ getCookies.sjs
+ linkToGetCookies.html
+ setSameSiteCookie.html
+ setSameSiteCookie.html^headers^
+[browser_readerMode_readingTime.js]
+support-files =
+ readerModeArticle.html
+ readerModeArticleShort.html
+ readerModeArticleMedium.html
+[browser_readerMode_remoteType.js]
+support-files =
+ readerModeArticleShort.html
+[browser_drag_url_readerMode.js]
+support-files =
+ readerModeArticle.html
+
diff --git a/toolkit/components/reader/test/browser_bug1124271_readerModePinnedTab.js b/toolkit/components/reader/test/browser_bug1124271_readerModePinnedTab.js
new file mode 100644
index 0000000000..346d503675
--- /dev/null
+++ b/toolkit/components/reader/test/browser_bug1124271_readerModePinnedTab.js
@@ -0,0 +1,53 @@
+/* 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/. */
+
+// Test that the reader mode button won't open in a new tab when clicked from a pinned tab
+
+const PREF = "reader.parse-on-load.enabled";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+var readerButton = document.getElementById("reader-mode-button");
+
+add_task(async function () {
+ registerCleanupFunction(function () {
+ Services.prefs.clearUserPref(PREF);
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+ });
+
+ // Enable the reader mode button.
+ Services.prefs.setBoolPref(PREF, true);
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ gBrowser.pinTab(tab);
+
+ let initialTabsCount = gBrowser.tabs.length;
+
+ // Point tab to a test page that is reader-able.
+ let url = TEST_PATH + "readerModeArticle.html";
+ await promiseTabLoadEvent(tab, url);
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+
+ readerButton.click();
+ await promiseTabLoadEvent(tab);
+
+ // Ensure no new tabs are opened when exiting reader mode in a pinned tab
+ is(gBrowser.tabs.length, initialTabsCount, "No additional tabs were opened.");
+
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow"
+ );
+ readerButton.click();
+ await pageShownPromise;
+ // Ensure no new tabs are opened when exiting reader mode in a pinned tab
+ is(gBrowser.tabs.length, initialTabsCount, "No additional tabs were opened.");
+
+ gBrowser.removeCurrentTab();
+});
diff --git a/toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js b/toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js
new file mode 100644
index 0000000000..1fbfdeabfb
--- /dev/null
+++ b/toolkit/components/reader/test/browser_bug1453818_samesite_cookie.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ORIGIN1 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+const TEST_ORIGIN2 = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+);
+
+async function clickLink(browser) {
+ info("Waiting for the page to load after clicking the link...");
+ let pageLoaded = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "DOMContentLoaded"
+ );
+ await SpecialPowers.spawn(browser, [], async function () {
+ let link = content.document.getElementById("link");
+ ok(link, "The link element was found.");
+ link.click();
+ });
+ await pageLoaded;
+}
+
+async function checkCookiePresent(browser) {
+ await SpecialPowers.spawn(browser, [], async function () {
+ let cookieSpan = content.document.getElementById("cookieSpan");
+ ok(cookieSpan, "cookieSpan element should be in document");
+ is(
+ cookieSpan.textContent,
+ "foo=bar",
+ "The SameSite cookie was sent correctly."
+ );
+ });
+}
+
+async function checkCookie(browser) {
+ info("Check that the SameSite cookie was not sent.");
+ await SpecialPowers.spawn(browser, [], async function () {
+ let cookieSpan = content.document.getElementById("cookieSpan");
+ ok(cookieSpan, "cookieSpan element should be in document");
+ is(
+ cookieSpan.textContent,
+ "",
+ "The SameSite cookie was blocked correctly."
+ );
+ });
+}
+
+async function runTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["reader.parse-on-load.enabled", true]],
+ });
+
+ info("Set a SameSite=strict cookie.");
+ await BrowserTestUtils.withNewTab(
+ TEST_ORIGIN1 + "setSameSiteCookie.html",
+ () => {}
+ );
+
+ info("Check that the cookie has been correctly set.");
+ await BrowserTestUtils.withNewTab(
+ TEST_ORIGIN1 + "getCookies.sjs",
+ async function (browser) {
+ await checkCookiePresent(browser);
+ }
+ );
+
+ info(
+ "Open a cross-origin page with a link to the domain that set the cookie."
+ );
+ {
+ let browser;
+ let pageLoaded;
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ () => {
+ let t = BrowserTestUtils.addTab(
+ gBrowser,
+ TEST_ORIGIN2 + "linkToGetCookies.html"
+ );
+ gBrowser.selectedTab = t;
+ browser = gBrowser.selectedBrowser;
+ pageLoaded = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "DOMContentLoaded"
+ );
+ return t;
+ },
+ false
+ );
+
+ info("Waiting for the page to load in normal mode...");
+ await pageLoaded;
+
+ await clickLink(browser);
+ await checkCookie(browser);
+ await BrowserTestUtils.removeTab(tab);
+ }
+
+ info("Open the cross-origin page again.");
+ await BrowserTestUtils.withNewTab(
+ TEST_ORIGIN2 + "linkToGetCookies.html",
+ async function (browser) {
+ let pageShown = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ ok(readerButton, "readerButton should be available");
+ readerButton.click();
+
+ info("Waiting for the page to be displayed in reader mode...");
+ await pageShown;
+
+ await clickLink(browser);
+ await checkCookie(browser);
+ }
+ );
+}
+
+add_task(async function () {
+ await runTest(true);
+});
+
+add_task(async function () {
+ await runTest(false);
+});
diff --git a/toolkit/components/reader/test/browser_drag_url_readerMode.js b/toolkit/components/reader/test/browser_drag_url_readerMode.js
new file mode 100644
index 0000000000..e8a157c527
--- /dev/null
+++ b/toolkit/components/reader/test/browser_drag_url_readerMode.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+add_task(async function test_readerModeURLDrag() {
+ await BrowserTestUtils.withNewTab(
+ {
+ gBrowser,
+ url: TEST_PATH + "readerModeArticle.html",
+ },
+
+ async browser => {
+ let readerButton = document.getElementById("reader-mode-button");
+ await TestUtils.waitForCondition(
+ () => !readerButton.hidden,
+ "Reader mode button should become visible"
+ );
+
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on a reader-able page"
+ );
+
+ // Switch page into reader mode.
+ let promiseTabLoad = BrowserTestUtils.browserLoaded(browser);
+ readerButton.click();
+ await promiseTabLoad;
+ let urlbar = document.getElementById("urlbar-input");
+ let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
+ ok(
+ readerUrl.startsWith("about:reader"),
+ "about:reader loaded after clicking reader mode button"
+ );
+
+ let dataTran = new DataTransfer();
+ let urlEvent = new DragEvent("dragstart", { dataTransfer: dataTran });
+ let oldUrl = TEST_PATH + "readerModeArticle.html";
+ let urlBarContainer = document.getElementById("urlbar-input-container");
+ urlBarContainer.click();
+ urlbar.dispatchEvent(urlEvent);
+
+ let newUrl = urlEvent.dataTransfer.getData("text/plain");
+ ok(!newUrl.includes("about:reader"), "URL does not contain about:reader");
+
+ Assert.equal(newUrl, oldUrl, "URL is the same");
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_localfile_readerMode.js b/toolkit/components/reader/test/browser_localfile_readerMode.js
new file mode 100644
index 0000000000..118e4bb23f
--- /dev/null
+++ b/toolkit/components/reader/test/browser_localfile_readerMode.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_BASE_URI = getResolvedURI(getRootDirectory(gTestPath)).spec;
+
+let readerButton = document.getElementById("reader-mode-button");
+
+/**
+ * Reader mode should work on local files.
+ */
+add_task(async function test_readermode_available_for_local_files() {
+ await BrowserTestUtils.withNewTab(
+ TEST_BASE_URI + "readerModeArticle.html",
+ async function (browser) {
+ await TestUtils.waitForCondition(
+ () => !readerButton.hidden,
+ "Reader mode button should become visible"
+ );
+
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on a reader-able page"
+ );
+
+ // Switch page into reader mode.
+ let promiseTabLoad = BrowserTestUtils.browserLoaded(browser);
+ readerButton.click();
+ await promiseTabLoad;
+
+ let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
+ ok(
+ readerUrl.startsWith("about:reader"),
+ "about:reader loaded after clicking reader mode button"
+ );
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on about:reader"
+ );
+
+ is(
+ gURLBar.untrimmedValue,
+ TEST_BASE_URI + "readerModeArticle.html",
+ "gURLBar value is about:reader URL"
+ );
+ is(
+ gURLBar.value,
+ gURLBar.untrimmedValue,
+ "gURLBar is displaying original article URL"
+ );
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode.js b/toolkit/components/reader/test/browser_readerMode.js
new file mode 100644
index 0000000000..9a1c11329b
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode.js
@@ -0,0 +1,394 @@
+/* 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/. */
+
+/**
+ * Test that the reader mode button appears and works properly on
+ * reader-able content.
+ */
+const TEST_PREFS = [["reader.parse-on-load.enabled", true]];
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "https://example.com"
+);
+
+var readerButton = document.getElementById("reader-mode-button");
+
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+add_task(async function test_reader_button() {
+ registerCleanupFunction(function () {
+ // Reset test prefs.
+ TEST_PREFS.forEach(([name, value]) => {
+ Services.prefs.clearUserPref(name);
+ });
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+ });
+
+ // Set required test prefs.
+ TEST_PREFS.forEach(([name, value]) => {
+ Services.prefs.setBoolPref(name, value);
+ });
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ is_element_hidden(
+ readerButton,
+ "Reader mode button is not present on a new tab"
+ );
+ ok(
+ !UITour.isInfoOnTarget(window, "readerMode-urlBar"),
+ "Info panel shouldn't appear without the reader mode button"
+ );
+
+ // Point tab to a test page that is reader-able.
+ let url = TEST_PATH + "readerModeArticle.html";
+ // Set up favicon for testing.
+ let favicon =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAA" +
+ "AAAA6fptVAAAACklEQVQI12NgAAAAAgAB4iG8MwAAAABJRU5ErkJggg==";
+ info("Adding visit so we can add favicon");
+ await PlacesTestUtils.addVisits(new URL(url));
+ info("Adding favicon");
+ await PlacesTestUtils.addFavicons(new Map([[url, favicon]]));
+ info("Opening tab and waiting for reader mode button to show up");
+
+ await promiseTabLoadEvent(tab, url);
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on a reader-able page"
+ );
+
+ // Switch page into reader mode.
+ let promiseTabLoad = promiseTabLoadEvent(tab);
+ readerButton.click();
+ await promiseTabLoad;
+
+ let readerUrl = gBrowser.selectedBrowser.currentURI.spec;
+ ok(
+ readerUrl.startsWith("about:reader"),
+ "about:reader loaded after clicking reader mode button"
+ );
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on about:reader"
+ );
+ let iconEl = tab.iconImage;
+ await TestUtils.waitForCondition(
+ () => iconEl.getBoundingClientRect().width != 0
+ );
+ is_element_visible(iconEl, "Favicon should be visible");
+ is(iconEl.src, favicon, "Correct favicon should be loaded");
+
+ is(gURLBar.untrimmedValue, url, "gURLBar value is about:reader URL");
+ is(gURLBar.value, url, "gURLBar is displaying original article URL");
+
+ // Check selected value for URL bar
+ await new Promise((resolve, reject) => {
+ waitForClipboard(
+ url,
+ function () {
+ gURLBar.focus();
+ gURLBar.select();
+ goDoCommand("cmd_copy");
+ },
+ resolve,
+ reject
+ );
+ });
+
+ info("Got correct URL when copying");
+
+ // Switch page back out of reader mode.
+ let promisePageShow = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow",
+ false,
+ e => e.target.location.href != "about:blank"
+ );
+ readerButton.click();
+ await promisePageShow;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ url,
+ "Back to the original page after clicking active reader mode button"
+ );
+ ok(
+ gBrowser.selectedBrowser.canGoForward,
+ "Moved one step back in the session history."
+ );
+
+ let nonReadableUrl = TEST_PATH + "readerModeNonArticle.html";
+
+ // Load a new tab that is NOT reader-able.
+ let newTab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ await promiseTabLoadEvent(newTab, nonReadableUrl);
+ await TestUtils.waitForCondition(() => readerButton.hidden);
+ is_element_hidden(
+ readerButton,
+ "Reader mode button is not present on a non-reader-able page"
+ );
+
+ // Switch back to the original tab to make sure reader mode button is still visible.
+ gBrowser.removeCurrentTab();
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on a reader-able page"
+ );
+
+ // Load a new tab in reader mode that is NOT reader-able in the reader mode.
+ newTab = gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser);
+ let promiseAboutReaderError = BrowserTestUtils.waitForContentEvent(
+ newTab.linkedBrowser,
+ "AboutReaderContentError"
+ );
+ await promiseTabLoadEvent(newTab, "about:reader?url=" + nonReadableUrl);
+ await promiseAboutReaderError;
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+ is_element_visible(
+ readerButton,
+ "Reader mode button is present on about:reader even in error state"
+ );
+
+ // Switch page back out of reader mode.
+ promisePageShow = BrowserTestUtils.waitForContentEvent(
+ newTab.linkedBrowser,
+ "pageshow",
+ false,
+ e => e.target.location.href != "about:blank"
+ );
+ readerButton.click();
+ await promisePageShow;
+ is(
+ gBrowser.selectedBrowser.currentURI.spec,
+ nonReadableUrl,
+ "Back to the original non-reader-able page after clicking active reader mode button"
+ );
+ await TestUtils.waitForCondition(() => readerButton.hidden);
+ is_element_hidden(
+ readerButton,
+ "Reader mode button is not present on a non-reader-able page"
+ );
+});
+
+add_task(async function test_getOriginalUrl() {
+ let { ReaderMode } = ChromeUtils.importESModule(
+ "resource://gre/modules/ReaderMode.sys.mjs"
+ );
+ let url = "https://foo.com/article.html";
+
+ is(
+ ReaderMode.getOriginalUrl("about:reader?url=" + encodeURIComponent(url)),
+ url,
+ "Found original URL from encoded URL"
+ );
+ is(
+ ReaderMode.getOriginalUrl("about:reader?foobar"),
+ null,
+ "Did not find original URL from malformed reader URL"
+ );
+ is(
+ ReaderMode.getOriginalUrl(url),
+ null,
+ "Did not find original URL from non-reader URL"
+ );
+
+ let badUrl = "https://foo.com/?;$%^^";
+ is(
+ ReaderMode.getOriginalUrl("about:reader?url=" + encodeURIComponent(badUrl)),
+ badUrl,
+ "Found original URL from encoded malformed URL"
+ );
+ is(
+ ReaderMode.getOriginalUrl("about:reader?url=" + badUrl),
+ badUrl,
+ "Found original URL from non-encoded malformed URL"
+ );
+});
+
+add_task(async function test_reader_view_element_attribute_transform() {
+ registerCleanupFunction(function () {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+ });
+
+ function observeAttribute(element, attributes, triggerFn, checkFn) {
+ return new Promise(resolve => {
+ let observer = new MutationObserver(mutations => {
+ for (let mu of mutations) {
+ if (element.getAttribute(mu.attributeName) !== mu.oldValue) {
+ if (checkFn()) {
+ resolve();
+ observer.disconnect();
+ return;
+ }
+ }
+ }
+ });
+
+ observer.observe(element, {
+ attributes: true,
+ attributeOldValue: true,
+ attributeFilter: Array.isArray(attributes) ? attributes : [attributes],
+ });
+
+ triggerFn();
+ });
+ }
+
+ let menuitem = document.getElementById("menu_readerModeItem");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ is(
+ menuitem.hidden,
+ true,
+ "menuitem element should have the hidden attribute"
+ );
+
+ info("Navigate a reader-able page");
+ function waitForNonBlankPage() {
+ return BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "pageshow",
+ false,
+ e => e.target.location.href != "about:blank"
+ );
+ }
+ let waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ menuitem,
+ "hidden",
+ () => {
+ let url = TEST_PATH + "readerModeArticle.html";
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ },
+ () => !menuitem.hidden
+ );
+ is(
+ menuitem.hidden,
+ false,
+ "menuitem's hidden attribute should be false on a reader-able page"
+ );
+ await waitForPageshow;
+
+ info("Navigate a non-reader-able page");
+ waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ menuitem,
+ "hidden",
+ () => {
+ let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ },
+ () => menuitem.hidden
+ );
+ is(
+ menuitem.hidden,
+ true,
+ "menuitem's hidden attribute should be true on a non-reader-able page"
+ );
+ await waitForPageshow;
+
+ info("Navigate a reader-able page");
+ waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ menuitem,
+ "hidden",
+ () => {
+ let url = TEST_PATH + "readerModeArticle.html";
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ },
+ () => !menuitem.hidden
+ );
+ is(
+ menuitem.hidden,
+ false,
+ "menuitem's hidden attribute should be false on a reader-able page"
+ );
+ await waitForPageshow;
+
+ info("Enter Reader Mode");
+ waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ readerButton,
+ "readeractive",
+ () => {
+ readerButton.click();
+ },
+ () => readerButton.getAttribute("readeractive") == "true"
+ );
+ is(
+ readerButton.getAttribute("readeractive"),
+ "true",
+ "readerButton's readeractive attribute should be true when entering reader mode"
+ );
+ await waitForPageshow;
+
+ info("Exit Reader Mode");
+ waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ readerButton,
+ ["readeractive", "hidden"],
+ () => {
+ readerButton.click();
+ },
+ () => {
+ info(
+ `active: ${readerButton.getAttribute("readeractive")}; hidden: ${
+ menuitem.hidden
+ }`
+ );
+ return !readerButton.getAttribute("readeractive") && !menuitem.hidden;
+ }
+ );
+ ok(
+ !readerButton.getAttribute("readeractive"),
+ "readerButton's readeractive attribute should be empty when reader mode is exited"
+ );
+ ok(!menuitem.hidden, "menuitem should not be hidden.");
+ await waitForPageshow;
+
+ info("Navigate a non-reader-able page");
+ waitForPageshow = waitForNonBlankPage();
+ await observeAttribute(
+ menuitem,
+ "hidden",
+ () => {
+ let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ },
+ () => menuitem.hidden
+ );
+ is(
+ menuitem.hidden,
+ true,
+ "menuitem's hidden attribute should be true on a non-reader-able page"
+ );
+ await waitForPageshow;
+});
+
+add_task(async function test_reader_mode_lang() {
+ let url = TEST_PATH + "readerModeArticle.html";
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser);
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+
+ await promiseTabLoadEvent(tab, url);
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+
+ // Switch page into reader mode.
+ let promiseTabLoad = promiseTabLoadEvent(tab);
+ readerButton.click();
+ await promiseTabLoad;
+
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => {
+ let container = content.document.querySelector(".container");
+ is(container.lang, "en");
+ });
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_bc_reuse.js b/toolkit/components/reader/test/browser_readerMode_bc_reuse.js
new file mode 100644
index 0000000000..9ac0e367ca
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_bc_reuse.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const TEST_URL = TEST_PATH + "readerModeArticle.html";
+
+add_task(async function test_TODO() {
+ await BrowserTestUtils.withNewTab(
+ "data:text/html,<p>Opener",
+ async browser => {
+ let newTabPromise = BrowserTestUtils.waitForNewTab(
+ gBrowser,
+ TEST_URL,
+ true
+ );
+ await SpecialPowers.spawn(browser, [TEST_URL], url => {
+ content.eval(`window.x = open("${url}", "_blank");`);
+ });
+ let newTab = await newTabPromise;
+
+ let readerButton = document.getElementById("reader-mode-button");
+ await BrowserTestUtils.waitForMutationCondition(
+ readerButton,
+ { attributes: true },
+ () => !readerButton.hidden
+ );
+ let tabLoaded = promiseTabLoadEvent(newTab);
+ readerButton.click();
+ await tabLoaded;
+ isnot(
+ newTab.linkedBrowser.browsingContext.group.id,
+ browser.browsingContext.group.id,
+ "BC should be in a different group now."
+ );
+ BrowserTestUtils.removeTab(newTab);
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_cached.js b/toolkit/components/reader/test/browser_readerMode_cached.js
new file mode 100644
index 0000000000..7f36a15dbb
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_cached.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// This test verifies that the article is properly using the cached data
+// when switching into reader mode. The article produces a random number
+// contained within it, so if the article gets reloaded instead of using
+// the cached version, it would have a different value in it.
+const URL =
+ "http://mochi.test:8888/browser/toolkit/components/reader/test/readerModeRandom.sjs";
+
+add_task(async function () {
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, URL);
+
+ let randomNumber = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ return content.document.getElementById("rnd").textContent;
+ });
+
+ let promiseTabLoad = promiseTabLoadEvent(tab);
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await promiseTabLoad;
+ await TestUtils.waitForCondition(() => !readerButton.hidden);
+
+ let newRandomNumber = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ return content.document.getElementById("rnd").textContent;
+ });
+
+ is(randomNumber, newRandomNumber, "used the same value");
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_colorSchemePref.js b/toolkit/components/reader/test/browser_readerMode_colorSchemePref.js
new file mode 100644
index 0000000000..0bee6eaf05
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_colorSchemePref.js
@@ -0,0 +1,67 @@
+/* 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 testColorScheme(aPref, aExpectation) {
+ // Set the browser content theme to light or dark.
+ Services.prefs.setIntPref("browser.theme.content-theme", aPref);
+
+ // Reader Mode Color Scheme Preference must be manually set by the user, will
+ // default to "auto" initially.
+ Services.prefs.setCharPref("reader.color_scheme", aExpectation);
+
+ let aBodyExpectation = aExpectation;
+ if (aBodyExpectation === "auto") {
+ aBodyExpectation = aPref === 1 ? "light" : "dark";
+ }
+
+ // Open a browser tab, enter reader mode, and test if we have the valid
+ // reader mode color scheme preference pre-selected.
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticle.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+
+ let colorScheme = Services.prefs.getCharPref("reader.color_scheme");
+
+ Assert.equal(colorScheme, aExpectation);
+
+ await SpecialPowers.spawn(browser, [aBodyExpectation], expectation => {
+ let bodyClass = content.document.body.className;
+ ok(
+ bodyClass.includes(expectation),
+ "The body of the test document has the correct color scheme."
+ );
+ });
+ }
+ );
+}
+
+/**
+ * Test that opening reader mode maintains the correct color scheme preference
+ * until the user manually sets a different color scheme.
+ */
+add_task(async function () {
+ await testColorScheme(0, "auto");
+ await testColorScheme(1, "auto");
+ await testColorScheme(0, "light");
+ await testColorScheme(1, "light");
+ await testColorScheme(0, "dark");
+ await testColorScheme(1, "dark");
+ await testColorScheme(0, "sepia");
+ await testColorScheme(1, "sepia");
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_hidden_nodes.js b/toolkit/components/reader/test/browser_readerMode_hidden_nodes.js
new file mode 100644
index 0000000000..38a5955f61
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_hidden_nodes.js
@@ -0,0 +1,56 @@
+/* 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/. */
+
+/**
+ * Test that the reader mode button appears and works properly on
+ * reader-able content.
+ */
+const TEST_PREFS = [["reader.parse-on-load.enabled", true]];
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+var readerButton = document.getElementById("reader-mode-button");
+
+add_task(async function test_reader_button() {
+ registerCleanupFunction(function () {
+ // Reset test prefs.
+ TEST_PREFS.forEach(([name, value]) => {
+ Services.prefs.clearUserPref(name);
+ });
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+ });
+
+ // Set required test prefs.
+ TEST_PREFS.forEach(([name, value]) => {
+ Services.prefs.setBoolPref(name, value);
+ });
+
+ let tab = (gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser));
+ is_element_hidden(
+ readerButton,
+ "Reader mode button is not present on a new tab"
+ );
+ // Point tab to a test page that is not reader-able due to hidden nodes.
+ let url = TEST_PATH + "readerModeArticleHiddenNodes.html";
+ let paintPromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "MozAfterPaint",
+ false,
+ e =>
+ e.originalTarget.location.href.endsWith("HiddenNodes.html") &&
+ e.originalTarget.document.readyState == "complete"
+ );
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ await paintPromise;
+
+ is_element_hidden(
+ readerButton,
+ "Reader mode button is still not present on tab with unreadable content."
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_menu.js b/toolkit/components/reader/test/browser_readerMode_menu.js
new file mode 100644
index 0000000000..304fefa5d0
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_menu.js
@@ -0,0 +1,69 @@
+/* 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"
+);
+
+/**
+ * Test that the reader mode correctly calculates and displays the
+ * estimated reading time for a short article
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticleShort.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ function dispatchMouseEvent(win, target, eventName) {
+ let mouseEvent = new win.MouseEvent(eventName, {
+ view: win,
+ bubbles: true,
+ cancelable: true,
+ composed: true,
+ });
+ target.dispatchEvent(mouseEvent);
+ }
+
+ function simulateClick(target) {
+ dispatchMouseEvent(win, target, "mousedown");
+ dispatchMouseEvent(win, target, "mouseup");
+ dispatchMouseEvent(win, target, "click");
+ }
+
+ let doc = content.document;
+ let win = content.window;
+ let styleButton = doc.querySelector(".style-button");
+
+ let styleDropdown = doc.querySelector(".style-dropdown");
+ ok(!styleDropdown.classList.contains("open"), "dropdown is closed");
+
+ simulateClick(styleButton);
+ ok(styleDropdown.classList.contains("open"), "dropdown is open");
+
+ // simulate clicking on the article title to close the dropdown
+ let title = doc.querySelector("h1");
+ simulateClick(title);
+ ok(!styleDropdown.classList.contains("open"), "dropdown is closed");
+
+ // reopen the dropdown
+ simulateClick(styleButton);
+ ok(styleDropdown.classList.contains("open"), "dropdown is open");
+
+ // now click on the button again to close it
+ simulateClick(styleButton);
+ ok(!styleDropdown.classList.contains("open"), "dropdown is closed");
+ });
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_pocket.js b/toolkit/components/reader/test/browser_readerMode_pocket.js
new file mode 100644
index 0000000000..e68aa7f9c0
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_pocket.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// This test verifies that the Save To Pocket button appears in reader mode,
+// and is toggled hidden and visible when pocket is disabled and enabled.
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+async function getPocketButtonsCount(browser) {
+ return SpecialPowers.spawn(browser, [], () => {
+ return content.document.getElementsByClassName("pocket-button").length;
+ });
+}
+
+add_task(async function () {
+ // set the pocket preference before beginning.
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.pocket.enabled", true]],
+ });
+
+ var readerButton = document.getElementById("reader-mode-button");
+
+ let tab1 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "readerModeArticleShort.html"
+ );
+
+ let promiseTabLoad = promiseTabLoadEvent(tab1);
+ readerButton.click();
+ await promiseTabLoad;
+
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_PATH + "readerModeArticleMedium.html"
+ );
+
+ promiseTabLoad = promiseTabLoadEvent(tab2);
+ readerButton.click();
+ await promiseTabLoad;
+
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 1,
+ "tab 1 has a pocket button"
+ );
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 1,
+ "tab 2 has a pocket button"
+ );
+
+ // Turn off the pocket preference. The Save To Pocket buttons should disappear.
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.pocket.enabled", false]],
+ });
+
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 0,
+ "tab 1 has no pocket button"
+ );
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 0,
+ "tab 2 has no pocket button"
+ );
+
+ // Turn on the pocket preference. The Save To Pocket buttons should reappear again.
+ await SpecialPowers.pushPrefEnv({
+ set: [["extensions.pocket.enabled", true]],
+ });
+
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 1,
+ "tab 1 has a pocket button again"
+ );
+ is(
+ await getPocketButtonsCount(tab1.linkedBrowser),
+ 1,
+ "tab 2 has a pocket button again"
+ );
+
+ BrowserTestUtils.removeTab(tab1);
+ BrowserTestUtils.removeTab(tab2);
+});
+
+/**
+ * Test that the pocket button toggles the pocket popup successfully
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticleShort.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.querySelector(".pocket-button").click();
+ });
+ let panel = gBrowser.selectedBrowser.ownerDocument.querySelector(
+ "#customizationui-widget-panel"
+ );
+ await BrowserTestUtils.waitForMutationCondition(
+ panel,
+ { attributes: true },
+ () => {
+ return BrowserTestUtils.is_visible(panel);
+ }
+ );
+ ok(BrowserTestUtils.is_visible(panel), "Panel buttons are visible");
+
+ await SpecialPowers.spawn(browser, [], async function () {
+ content.document.querySelector(".pocket-button").click();
+ });
+ await BrowserTestUtils.waitForMutationCondition(
+ panel,
+ { attributes: true },
+ () => {
+ return BrowserTestUtils.is_hidden(panel);
+ }
+ );
+
+ ok(BrowserTestUtils.is_hidden(panel), "Panel buttons are hidden");
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_readingTime.js b/toolkit/components/reader/test/browser_readerMode_readingTime.js
new file mode 100644
index 0000000000..91631b6234
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_readingTime.js
@@ -0,0 +1,101 @@
+/* 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"
+);
+
+/**
+ * Test that the reader mode correctly calculates and displays the
+ * estimated reading time for a normal length article
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticle.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ // make sure there is a reading time on the page and that it displays the correct information
+ let readingTimeElement = content.document.querySelector(
+ ".reader-estimated-time"
+ );
+ ok(readingTimeElement, "Reading time element should be in document");
+ const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
+ is(args.rangePlural, "other", "Reading time should be '9-12 minutes'");
+ ok(
+ /\b9\b.*\b12\b/.test(args.range),
+ "Reading time should be '9-12 minutes'"
+ );
+ });
+ }
+ );
+});
+
+/**
+ * Test that the reader mode correctly calculates and displays the
+ * estimated reading time for a short article
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticleShort.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ // make sure there is a reading time on the page and that it displays the correct information
+ let readingTimeElement = content.document.querySelector(
+ ".reader-estimated-time"
+ );
+ ok(readingTimeElement, "Reading time element should be in document");
+ const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
+ is(args.rangePlural, "one", "Reading time should be '~1 minute'");
+ ok(/\b1\b/.test(args.range), "Reading time should be '~1 minute'");
+ });
+ }
+ );
+});
+
+/**
+ * Test that the reader mode correctly calculates and displays the
+ * estimated reading time for a medium article where a single number
+ * is displayed.
+ */
+add_task(async function () {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticleMedium.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ // make sure there is a reading time on the page and that it displays the correct information
+ let readingTimeElement = content.document.querySelector(
+ ".reader-estimated-time"
+ );
+ ok(readingTimeElement, "Reading time element should be in document");
+ const args = JSON.parse(readingTimeElement.dataset.l10nArgs);
+ is(args.rangePlural, "other", "Reading time should be '~3 minutes'");
+ ok(/\b3\b/.test(args.range), "Reading time should be '~3 minutes'");
+ });
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_refresh.js b/toolkit/components/reader/test/browser_readerMode_refresh.js
new file mode 100644
index 0000000000..00b4557f70
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_refresh.js
@@ -0,0 +1,49 @@
+/* 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
+ pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ 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");
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_remoteType.js b/toolkit/components/reader/test/browser_readerMode_remoteType.js
new file mode 100644
index 0000000000..690cb74338
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_remoteType.js
@@ -0,0 +1,87 @@
+/* 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"
+);
+
+const CROSS_SITE_TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.org"
+);
+
+/**
+ * Test that switching an article into readermode doesn't change its' remoteType.
+ * Test that the reader mode correctly calculates and displays the
+ * estimated reading time for a short article
+ */
+add_task(async function () {
+ info("opening readermode normally to ensure process doesn't change");
+ let articleRemoteType;
+ let aboutReaderURL;
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticleShort.html",
+ async function (browser) {
+ articleRemoteType = browser.remoteType;
+
+ // Click on the readermode button to switch into reader mode, and get the
+ // URL for that reader mode.
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+
+ aboutReaderURL = browser.documentURI.spec;
+ ok(
+ aboutReaderURL.startsWith("about:reader"),
+ "about:reader should have been opened"
+ );
+ is(
+ browser.remoteType,
+ articleRemoteType,
+ "remote type should not have changed"
+ );
+ }
+ );
+
+ info(
+ "opening new tab directly with about reader URL into correct remote type"
+ );
+ await BrowserTestUtils.withNewTab(aboutReaderURL, async function (browser) {
+ is(
+ browser.remoteType,
+ articleRemoteType,
+ "Should have performed about:reader load in the correct remote type"
+ );
+ });
+
+ info("navigating process into correct remote type");
+ await BrowserTestUtils.withNewTab(
+ CROSS_SITE_TEST_PATH + "readerModeArticleShort.html",
+ async function (browser) {
+ if (SpecialPowers.useRemoteSubframes) {
+ isnot(
+ browser.remoteType,
+ articleRemoteType,
+ "Cross-site article should have different remote type with fission"
+ );
+ }
+
+ BrowserTestUtils.loadURIString(browser, aboutReaderURL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ is(
+ browser.remoteType,
+ articleRemoteType,
+ "Should have switched into the correct remote type"
+ );
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_samesite_cookie_redirect.js b/toolkit/components/reader/test/browser_readerMode_samesite_cookie_redirect.js
new file mode 100644
index 0000000000..caa152360c
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_samesite_cookie_redirect.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_ORIGIN = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+const { HttpServer } = ChromeUtils.import("resource://testing-common/httpd.js");
+
+add_task(async function test_ss_cookie_redirect() {
+ // Set the samesite cookie
+ let tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ TEST_ORIGIN + "setSameSiteCookie.html"
+ );
+ BrowserTestUtils.removeTab(tab);
+
+ let server = new HttpServer();
+ server.start(-1);
+ server.registerPathHandler("/foo", (request, response) => {
+ response.setStatusLine(request.httpVersion, 302, "Found");
+ response.setHeader("Location", TEST_ORIGIN + "getCookies.sjs");
+ });
+ registerCleanupFunction(() => server.stop());
+ const { primaryPort, primaryHost } = server.identity;
+ const serverURL = `http://${primaryHost}:${primaryPort}/foo`;
+
+ // Now open `getCookies.sjs` but via a redirect:
+ await BrowserTestUtils.withNewTab("about:blank", async browser => {
+ let loaded = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ await BrowserTestUtils.loadURIString(
+ browser,
+ "about:reader?url=" + encodeURIComponent(serverURL)
+ );
+ await loaded;
+ await SpecialPowers.spawn(browser, [], () => {
+ is(
+ content.document.getElementById("cookieSpan").textContent,
+ "",
+ "Shouldn't get cookies."
+ );
+ });
+ });
+});
diff --git a/toolkit/components/reader/test/browser_readerMode_with_anchor.js b/toolkit/components/reader/test/browser_readerMode_with_anchor.js
new file mode 100644
index 0000000000..229daaed9d
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_with_anchor.js
@@ -0,0 +1,89 @@
+/* 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"
+);
+
+add_task(async function test_loading_withHash() {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticle.html#foo",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ let foo = content.document.getElementById("foo");
+ ok(foo, "foo element should be in document");
+ let { scrollTop } = content.document.documentElement;
+ if (scrollTop == 0) {
+ await ContentTaskUtils.waitForEvent(content.document, "scroll");
+ ({ scrollTop } = content.document.documentElement);
+ }
+ let { offsetTop } = foo;
+ Assert.lessOrEqual(
+ Math.abs(scrollTop - offsetTop),
+ 1,
+ `scrollTop (${scrollTop}) should be within 1 CSS pixel of offsetTop (${offsetTop})`
+ );
+ });
+ }
+ );
+});
+
+add_task(async function test_loading_withoutHash() {
+ await BrowserTestUtils.withNewTab(
+ TEST_PATH + "readerModeArticle.html",
+ async function (browser) {
+ let pageShownPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "AboutReaderContentReady"
+ );
+ let pageLoadedPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "load",
+ true
+ );
+ let readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await Promise.all([pageShownPromise, pageLoadedPromise]);
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
+ Assert.equal(
+ content.document.documentElement.scrollTop,
+ 0,
+ "scrollTop should be 0"
+ );
+ });
+ let scrollEventPromise = BrowserTestUtils.waitForContentEvent(
+ browser,
+ "scroll",
+ true
+ );
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#foo-anchor",
+ {},
+ browser
+ );
+ await scrollEventPromise;
+ await SpecialPowers.spawn(browser, [], async function () {
+ let foo = content.document.getElementById("foo");
+ ok(foo, "foo element should be in document");
+ let { scrollTop } = content.document.documentElement;
+ let { offsetTop } = foo;
+ Assert.lessOrEqual(
+ Math.abs(scrollTop - offsetTop),
+ 1,
+ `scrollTop (${scrollTop}) should be within 1 CSS pixel of offsetTop (${offsetTop})`
+ );
+ });
+ }
+ );
+});
diff --git a/toolkit/components/reader/test/getCookies.sjs b/toolkit/components/reader/test/getCookies.sjs
new file mode 100644
index 0000000000..02e29fd877
--- /dev/null
+++ b/toolkit/components/reader/test/getCookies.sjs
@@ -0,0 +1,16 @@
+function handleRequest(request, response) {
+ const cookies = request.hasHeader("Cookie")
+ ? request.getHeader("Cookie")
+ : "";
+ response.write(`
+ <!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8"/>
+ </head>
+ <body>
+ <p>Cookie: <span id="cookieSpan">${cookies}</span></p>
+ </body>
+ </html>
+ `);
+}
diff --git a/toolkit/components/reader/test/head.js b/toolkit/components/reader/test/head.js
new file mode 100644
index 0000000000..6a8e09ecb2
--- /dev/null
+++ b/toolkit/components/reader/test/head.js
@@ -0,0 +1,63 @@
+ChromeUtils.defineESModuleGetters(this, {
+ PromiseUtils: "resource://gre/modules/PromiseUtils.sys.mjs",
+});
+
+/* exported promiseTabLoadEvent, is_element_visible, is_element_hidden */
+
+/**
+ * Waits for a load (or custom) event to finish in a given tab. If provided
+ * load an uri into the tab.
+ *
+ * @param tab
+ * The tab to load into.
+ * @param [optional] url
+ * The url to load, or the current url.
+ * @return {Promise} resolved when the event is handled.
+ * @resolves to the received event
+ * @rejects if a valid load event is not received within a meaningful interval
+ */
+function promiseTabLoadEvent(tab, url) {
+ let deferred = PromiseUtils.defer();
+ info("Wait tab event: load");
+
+ function handle(loadedUrl) {
+ if (loadedUrl === "about:blank" || (url && loadedUrl !== url)) {
+ info(`Skipping spurious load event for ${loadedUrl}`);
+ return false;
+ }
+
+ info("Tab event received: load");
+ return true;
+ }
+
+ // Create two promises: one resolved from the content process when the page
+ // loads and one that is rejected if we take too long to load the url.
+ let loaded = BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, handle);
+
+ let timeout = setTimeout(() => {
+ deferred.reject(new Error("Timed out while waiting for a 'load' event"));
+ }, 30000);
+
+ loaded.then(() => {
+ clearTimeout(timeout);
+ deferred.resolve();
+ });
+
+ if (url) {
+ BrowserTestUtils.loadURIString(tab.linkedBrowser, url);
+ }
+
+ // Promise.all rejects if either promise rejects (i.e. if we time out) and
+ // if our loaded promise resolves before the timeout, then we resolve the
+ // timeout promise as well, causing the all promise to resolve.
+ return Promise.all([deferred.promise, loaded]);
+}
+
+function is_element_visible(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(BrowserTestUtils.is_visible(element), msg || "Element should be visible");
+}
+function is_element_hidden(element, msg) {
+ isnot(element, null, "Element should not be null, when checking visibility");
+ ok(BrowserTestUtils.is_hidden(element), msg || "Element should be hidden");
+}
diff --git a/toolkit/components/reader/test/linkToGetCookies.html b/toolkit/components/reader/test/linkToGetCookies.html
new file mode 100644
index 0000000000..341046a21d
--- /dev/null
+++ b/toolkit/components/reader/test/linkToGetCookies.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <article>
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+
+ <p><a href="http://example.com/browser/toolkit/components/reader/test/getCookies.sjs" id="link">Cross-origin link to getCookies.html</a></p>
+ </article>
+ </body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeArticle.html b/toolkit/components/reader/test/readerModeArticle.html
new file mode 100644
index 0000000000..a0f1c64da0
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticle.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<ul>
+ <li><a id="foo-anchor" href="#foo">by John Doe</a></li>
+</ul>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p id="foo">by John Doe</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+</div>
+</body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeArticleHiddenNodes.html b/toolkit/components/reader/test/readerModeArticleHiddenNodes.html
new file mode 100644
index 0000000000..92441b7978
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticleHiddenNodes.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<style>
+p { display: none }
+</style>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In rutrum accumsan ultricies. Mauris vitae nisi at sem facilisis semper ac in est.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+<p>Vivamus fermentum semper porta. Nunc diam velit, adipiscing ut tristique vitae, sagittis vel odio. Maecenas convallis ullamcorper ultricies. Curabitur ornare, ligula semper consectetur sagittis, nisi diam iaculis velit, id fringilla sem nunc vel mi. Nam dictum, odio nec pretium volutpat, arcu ante placerat erat, non tristique elit urna et turpis. Quisque mi metus, ornare sit amet fermentum et, tincidunt et orci. Fusce eget orci a orci congue vestibulum. Ut dolor diam, elementum et vestibulum eu, porttitor vel elit. Curabitur venenatis pulvinar tellus gravida ornare. Sed et erat faucibus nunc euismod ultricies ut id justo. Nullam cursus suscipit nisi, et ultrices justo sodales nec. Fusce venenatis facilisis lectus ac semper. Aliquam at massa ipsum. Quisque bibendum purus convallis nulla ultrices ultricies. Nullam aliquam, mi eu aliquam tincidunt, purus velit laoreet tortor, viverra pretium nisi quam vitae mi. Fusce vel volutpat elit. Nam sagittis nisi dui.</p>
+</div>
+</body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeArticleMedium.html b/toolkit/components/reader/test/readerModeArticleMedium.html
new file mode 100644
index 0000000000..70b172cf63
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticleMedium.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+</body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeArticleShort.html b/toolkit/components/reader/test/readerModeArticleShort.html
new file mode 100644
index 0000000000..692471f27f
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticleShort.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+</body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeArticleTextPlain.txt b/toolkit/components/reader/test/readerModeArticleTextPlain.txt
new file mode 100644
index 0000000000..c5b7861b73
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticleTextPlain.txt
@@ -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.
diff --git a/toolkit/components/reader/test/readerModeNonArticle.html b/toolkit/components/reader/test/readerModeNonArticle.html
new file mode 100644
index 0000000000..e216af3c1f
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeNonArticle.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Non article title</title>
+<meta name="description" content="This is the non-article description." />
+</head>
+<body>
+</body>
+</html>
diff --git a/toolkit/components/reader/test/readerModeRandom.sjs b/toolkit/components/reader/test/readerModeRandom.sjs
new file mode 100644
index 0000000000..f6bb15c06a
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeRandom.sjs
@@ -0,0 +1,23 @@
+// Generate a article ending in a piece of text with some random values in it.
+
+let readerModeText = `<!DOCTYPE html>
+<html>
+<head>
+<title>Article title</title>
+<meta name="description" content="This is the article description." />
+</head>
+<body>
+<header>Site header</header>
+<div>
+<h1>Article title</h1>
+<h2 class="author">by Jane Doe</h2>
+<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec a diam lectus. Sed sit amet ipsum mauris. Maecenas congue ligula ac quam viverra nec consectetur ante hendrerit. Donec et mollis dolor. Praesent et diam eget libero egestas mattis sit amet vitae augue. Nam tincidunt congue enim, ut porta lorem lacinia consectetur. Donec ut libero sed arcu vehicula ultricies a non tortor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ut gravida lorem. Ut turpis felis, pulvinar a semper sed, adipiscing id dolor. Pellentesque auctor nisi id magna consequat sagittis. Curabitur dapibus enim sit amet elit pharetra tincidunt feugiat nisl imperdiet. Ut convallis libero in urna ultrices accumsan. Donec sed odio eros. Donec viverra mi quis quam pulvinar at malesuada arcu rhoncus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetu</p>
+`;
+
+function handleRequest(aRequest, aResponse) {
+ aResponse.setStatusLine(aRequest.httpVersion, 200);
+ aResponse.setHeader("Content-Type", "text/html", false);
+ aResponse.write(
+ readerModeText + "<p id='rnd'>" + Date.now() + "," + Math.random() + "</p>"
+ );
+}
diff --git a/toolkit/components/reader/test/setSameSiteCookie.html b/toolkit/components/reader/test/setSameSiteCookie.html
new file mode 100644
index 0000000000..67bb714922
--- /dev/null
+++ b/toolkit/components/reader/test/setSameSiteCookie.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <p>This page just set a cookie with the <code>SameSite</code> attribute.</p>
+ </body>
+</html>
diff --git a/toolkit/components/reader/test/setSameSiteCookie.html^headers^ b/toolkit/components/reader/test/setSameSiteCookie.html^headers^
new file mode 100644
index 0000000000..c0229c93b6
--- /dev/null
+++ b/toolkit/components/reader/test/setSameSiteCookie.html^headers^
@@ -0,0 +1 @@
+Set-Cookie: foo=bar; Path='/' ; SameSite=strict