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/.eslintrc.js5
-rw-r--r--toolkit/components/reader/test/browser.ini43
-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_localfile_readerMode.js54
-rw-r--r--toolkit/components/reader/test/browser_readerMode.js381
-rw-r--r--toolkit/components/reader/test/browser_readerMode_cached.js32
-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.js90
-rw-r--r--toolkit/components/reader/test/browser_readerMode_readingTime.js104
-rw-r--r--toolkit/components/reader/test/browser_readerMode_with_anchor.js74
-rw-r--r--toolkit/components/reader/test/getCookies.sjs14
-rw-r--r--toolkit/components/reader/test/head.js65
-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/readerModeNonArticle.html9
-rw-r--r--toolkit/components/reader/test/readerModeRandom.sjs22
-rw-r--r--toolkit/components/reader/test/setSameSiteCookie.html9
-rw-r--r--toolkit/components/reader/test/setSameSiteCookie.html^headers^1
23 files changed, 1306 insertions, 0 deletions
diff --git a/toolkit/components/reader/test/.eslintrc.js b/toolkit/components/reader/test/.eslintrc.js
new file mode 100644
index 0000000000..1779fd7f1c
--- /dev/null
+++ b/toolkit/components/reader/test/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test"],
+};
diff --git a/toolkit/components/reader/test/browser.ini b/toolkit/components/reader/test/browser.ini
new file mode 100644
index 0000000000..9dede3eab2
--- /dev/null
+++ b/toolkit/components/reader/test/browser.ini
@@ -0,0 +1,43 @@
+[DEFAULT]
+support-files = head.js
+[browser_localfile_readerMode.js]
+support-files =
+ readerModeArticle.html
+[browser_readerMode.js]
+support-files =
+ readerModeNonArticle.html
+ readerModeArticle.html
+ readerModeArticleHiddenNodes.html
+[browser_readerMode_cached.js]
+support-files =
+ readerModeRandom.sjs
+[browser_readerMode_hidden_nodes.js]
+skip-if = debug && os == "linux" && os_version == "18.04" #bug 1638027
+support-files =
+ readerModeArticleHiddenNodes.html
+[browser_readerMode_menu.js]
+support-files =
+ readerModeArticleShort.html
+[browser_readerMode_pocket.js]
+skip-if = debug && os == "linux" && os_version == "18.04" #bug 1638027
+support-files =
+ readerModeArticleShort.html
+ readerModeArticleMedium.html
+[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]
+skip-if = debug && os == "linux" && os_version == "18.04" #bug 1638027
+support-files =
+ readerModeArticle.html
+ readerModeArticleShort.html
+ readerModeArticleMedium.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..86c6c200de
--- /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",
+ "http://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..f757a18edd
--- /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_localfile_readerMode.js b/toolkit/components/reader/test/browser_localfile_readerMode.js
new file mode 100644
index 0000000000..09513dc3b0
--- /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..9a2ef4ab37
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode.js
@@ -0,0 +1,381 @@
+/* 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");
+
+ChromeUtils.defineModuleGetter(
+ this,
+ "PlacesTestUtils",
+ "resource://testing-common/PlacesTestUtils.jsm"
+);
+
+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.substring("http://".length),
+ "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.import(
+ "resource://gre/modules/ReaderMode.jsm"
+ );
+ let url = "http://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 = "http://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.loadURI(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.loadURI(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.loadURI(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.loadURI(tab.linkedBrowser, url);
+ },
+ () => menuitem.hidden
+ );
+ is(
+ menuitem.hidden,
+ true,
+ "menuitem's hidden attribute should be true on a non-reader-able page"
+ );
+ await waitForPageshow;
+});
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..0d4c08350b
--- /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_hidden_nodes.js b/toolkit/components/reader/test/browser_readerMode_hidden_nodes.js
new file mode 100644
index 0000000000..0964b2f71b
--- /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.loadURI(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..504de2e527
--- /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..9f0f051488
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_pocket.js
@@ -0,0 +1,90 @@
+/* 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);
+});
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..a7480682fb
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_readingTime.js
@@ -0,0 +1,104 @@
+/* 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");
+ is(
+ readingTimeElement.textContent,
+ "9-12 minutes",
+ "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");
+ is(
+ readingTimeElement.textContent,
+ "1 minute",
+ "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");
+ is(
+ readingTimeElement.textContent,
+ "3 minutes",
+ "Reading time should be '3 minutes'"
+ );
+ });
+ }
+ );
+});
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..6aa9532497
--- /dev/null
+++ b/toolkit/components/reader/test/browser_readerMode_with_anchor.js
@@ -0,0 +1,74 @@
+/* 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;
+ 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 readerButton = document.getElementById("reader-mode-button");
+ readerButton.click();
+ await pageShownPromise;
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async () => {
+ Assert.equal(
+ content.document.documentElement.scrollTop,
+ 0,
+ "scrollTop should be 0"
+ );
+ });
+ await BrowserTestUtils.synthesizeMouseAtCenter(
+ "#foo-anchor",
+ {},
+ browser
+ );
+ 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..df0dad1314
--- /dev/null
+++ b/toolkit/components/reader/test/getCookies.sjs
@@ -0,0 +1,14 @@
+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..11b9c5e68d
--- /dev/null
+++ b/toolkit/components/reader/test/head.js
@@ -0,0 +1,65 @@
+ChromeUtils.defineModuleGetter(
+ this,
+ "Promise",
+ "resource://gre/modules/Promise.jsm"
+);
+
+/* 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 = Promise.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.loadURI(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..376ffc8685
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeArticle.html
@@ -0,0 +1,28 @@
+<!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>
+<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/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..963ea3cd5a
--- /dev/null
+++ b/toolkit/components/reader/test/readerModeRandom.sjs
@@ -0,0 +1,22 @@
+// 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