Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
const PROMO_DISMISSED_PREF = "browser.aboutpdf.promo.dismissed";
const { PdfJsFeaturesNotification } = ChromeUtils.importESModule(
"resource://pdf.js/PdfJsFeaturesNotification.sys.mjs"
);
registerCleanupFunction(() => {
Services.prefs.clearUserPref(PROMO_DISMISSED_PREF);
resetPdfNotificationPrefs();
});
add_task(async function test_features_view_shown_at_hash() {
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const mainView = content.document.getElementById("main-view");
const featuresView = content.document.getElementById("features-view");
const back = content.document.getElementById("features-back");
ok(featuresView.checkVisibility(), "features view is shown at #features");
ok(!mainView.checkVisibility(), "main view is hidden at #features");
ok(back.checkVisibility(), "Back button is shown at #features");
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_main_view_by_default() {
const tab = await openAboutPDF();
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const mainView = content.document.getElementById("main-view");
const featuresView = content.document.getElementById("features-view");
const back = content.document.getElementById("features-back");
ok(mainView.checkVisibility(), "main view is shown by default");
ok(!featuresView.checkVisibility(), "features view is hidden by default");
ok(!back.checkVisibility(), "Back button is hidden by default");
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_features_cta_opens_features_view() {
const tab = await openAboutPDF();
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const cta = content.document.getElementById("features-cta");
ok(cta.checkVisibility(), "the features call to action is shown");
await ContentTaskUtils.waitForCondition(
() => cta.getAttribute("label"),
"the call to action is localized"
);
});
await clickInAboutPDF(tab, "#features-cta");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const mainView = content.document.getElementById("main-view");
const featuresView = content.document.getElementById("features-view");
await ContentTaskUtils.waitForCondition(
() => featuresView.checkVisibility() && !mainView.checkVisibility(),
"the call to action swaps to the features view"
);
is(content.location.hash, "#features", "the call to action sets the hash");
});
BrowserTestUtils.removeTab(tab);
});
// With no previous history entry, Back switches views in place.
add_task(async function test_back_returns_to_main_view() {
const tab = await openAboutPDF("#features");
ok(!tab.linkedBrowser.canGoBack, "nothing to go back to in a new tab");
await clickInAboutPDF(tab, "#features-back");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const mainView = content.document.getElementById("main-view");
const featuresView = content.document.getElementById("features-view");
await ContentTaskUtils.waitForCondition(
() => mainView.checkVisibility() && !featuresView.checkVisibility(),
"Back returns to the main view"
);
is(content.location.hash, "", "Back clears the #features fragment");
});
ok(!tab.linkedBrowser.canGoBack, "the in-place fallback adds no history");
BrowserTestUtils.removeTab(tab);
});
// Back returns to the previous history entry without adding one.
add_task(async function test_back_walks_session_history() {
const tab = await openAboutPDF();
const browser = tab.linkedBrowser;
await SpecialPowers.spawn(browser, [], async () => {
content.location.hash = "features";
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("features-view").checkVisibility(),
"features view is shown"
);
});
ok(browser.canGoBack, "entering the features view is a navigation");
const backNavigated = BrowserTestUtils.waitForLocationChange(
gBrowser,
"about:pdf"
);
await clickInAboutPDF(tab, "#features-back");
await backNavigated;
is(browser.currentURI.spec, "about:pdf", "Back drops the #features fragment");
ok(browser.canGoForward, "Back went back in session history");
await SpecialPowers.spawn(browser, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("main-view").checkVisibility(),
"main view is shown again"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_all_features_localized() {
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const intro = content.document.getElementById("features-intro");
ok(intro.checkVisibility(), "the intro is shown below the heading");
const grid = content.document.getElementById("feature-grid");
is(grid.localName, "ul", "the feature grid is a list");
const features = grid.querySelectorAll(":scope > .feature");
ok(features.length, "the grid has features");
ok(
[...features].every(feature => feature.localName === "li"),
"each feature is a list item"
);
const headings = content.document.querySelectorAll(
"#feature-grid .feature-text h2"
);
const descriptions = content.document.querySelectorAll(
"#feature-grid .feature-text p"
);
is(headings.length, features.length, "every feature has a heading");
is(descriptions.length, features.length, "every feature has a description");
await ContentTaskUtils.waitForCondition(
() =>
intro.textContent.trim() &&
[...headings].every(h => h.textContent.trim()),
"the intro and the feature headings are localized"
);
for (const p of descriptions) {
ok(p.textContent.trim(), "feature description is localized");
}
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_features_contrast_styles() {
await SpecialPowers.pushPrefEnv({
set: [["ui.useAccessibilityTheme", 1]],
});
const tab = await openAboutPDF("#features");
try {
await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
ok(
content.matchMedia("(prefers-contrast)").matches,
"the accessibility theme enables increased contrast"
);
const cardStyle = content.getComputedStyle(
content.document.getElementById("feature-card")
);
const iconStyle = content.getComputedStyle(
content.document.querySelector(".feature-icon")
);
is(cardStyle.borderTopStyle, "solid", "the card gains a border");
Assert.greater(
parseFloat(cardStyle.borderTopWidth),
0,
"the contrast border is visible"
);
isnot(
cardStyle.borderTopColor,
"rgba(0, 0, 0, 0)",
"the contrast border is not transparent"
);
// Check icon borders when forced colors are active.
if (content.matchMedia("(forced-colors: active)").matches) {
is(iconStyle.borderTopStyle, "solid", "HCM outlines each icon");
isnot(
iconStyle.borderTopColor,
"rgba(0, 0, 0, 0)",
"the HCM icon border is not transparent"
);
}
});
} finally {
BrowserTestUtils.removeTab(tab);
await SpecialPowers.popPrefEnv();
}
});
add_task(async function test_arrow_icons_flip_in_rtl() {
const tab = await openAboutPDF();
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const doc = content.document;
const icon = id => doc.getElementById(id).shadowRoot?.querySelector("img");
// The RTL override replaces the image through the content property.
const iconUrl = id => content.getComputedStyle(icon(id)).content;
await ContentTaskUtils.waitForCondition(
() => icon("features-cta") && icon("features-back"),
"the arrow icons are rendered"
);
Assert.stringContains(
icon("features-cta").src,
"shaft-arrow-right.svg",
"the call to action points forward in LTR"
);
Assert.stringContains(
icon("features-back").src,
"arrow-left.svg",
"Back points backward in LTR"
);
is(iconUrl("features-cta"), "normal", "no override applies in LTR");
doc.documentElement.dir = "rtl";
await ContentTaskUtils.waitForCondition(
() => iconUrl("features-cta").includes("shaft-arrow-left.svg"),
"the call to action points forward in RTL"
);
Assert.stringContains(
iconUrl("features-back"),
"arrow-right.svg",
"Back points backward in RTL"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_promo_shown_and_dismissable_in_features_view() {
Services.prefs.clearUserPref(PROMO_DISMISSED_PREF);
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const promo = content.document.getElementById("promo");
const featuresView = content.document.getElementById("features-view");
ok(featuresView.checkVisibility(), "features view is shown");
// Decouple this test from the default-handler state.
promo.hidden = false;
ok(promo.checkVisibility(), "promo is visible in the features view");
});
await clickInAboutPDF(tab, "#dismiss-promo");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("promo").hidden,
"promo is hidden after dismiss in the features view"
);
});
await TestUtils.waitForCondition(
() => Services.prefs.getBoolPref(PROMO_DISMISSED_PREF, false) === true,
"dismissed pref persisted from the features view"
);
BrowserTestUtils.removeTab(tab);
Services.prefs.clearUserPref(PROMO_DISMISSED_PREF);
});
add_task(async function test_back_button_keyboard_activation() {
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const back = content.document.getElementById("features-back");
back.focus();
is(content.document.activeElement, back, "Back button is focused");
});
await BrowserTestUtils.synthesizeKey("KEY_Enter", {}, tab.linkedBrowser);
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const mainView = content.document.getElementById("main-view");
await ContentTaskUtils.waitForCondition(
() => mainView.checkVisibility(),
"Enter on the Back button returns to the main view"
);
is(content.location.hash, "", "keyboard activation clears the fragment");
is(
content.document.activeElement.id,
"main-heading",
"focus returns to the main heading after Back"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_hashchange_moves_focus_to_features_heading() {
const tab = await openAboutPDF();
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
content.location.hash = "features";
await ContentTaskUtils.waitForCondition(
() => content.document.activeElement.id === "features-heading",
"hashchange moves focus to the features heading"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_notification_consumed_by_features_view() {
resetPdfNotificationPrefs();
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
ok(
!content.document.getElementById("pdf-notification"),
"notification is not in the page at #features"
);
});
// Leaving the promoted view must not restore the notification.
await clickInAboutPDF(tab, "#features-back");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("main-view").checkVisibility(),
"Back returns to the main view"
);
ok(
!content.document.getElementById("pdf-notification"),
"the notification does not come back once consumed"
);
});
await TestUtils.waitForCondition(
() => PdfJsFeaturesNotification.isConsumed(),
"reaching the features view spends the notification budget"
);
BrowserTestUtils.removeTab(tab);
resetPdfNotificationPrefs();
});
add_task(async function test_notification_dropped_on_hash_change() {
resetPdfNotificationPrefs();
const tab = await openAboutPDF();
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
const notification = content.document.getElementById("pdf-notification");
await ContentTaskUtils.waitForCondition(
() => !notification.hidden,
"notification starts visible on the main view"
);
content.location.hash = "features";
await ContentTaskUtils.waitForCondition(
() => !notification.isConnected,
"notification is dropped when the features view opens"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_dismissed_notification_stays_gone_after_back() {
PdfJsFeaturesNotification.consume();
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
});
await clickInAboutPDF(tab, "#features-back");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("main-view").checkVisibility(),
"Back returns to the main view"
);
ok(
!content.document.getElementById("pdf-notification"),
"a dismissed notification stays out of the page"
);
});
BrowserTestUtils.removeTab(tab);
resetPdfNotificationPrefs();
});
add_task(async function test_deep_link_does_not_steal_focus() {
const tab = await openAboutPDF("#features");
await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
await content.document.l10n.ready;
const featuresHeading = content.document.getElementById("features-heading");
isnot(
content.document.activeElement,
featuresHeading,
"a direct #features load does not steal focus to the heading"
);
});
BrowserTestUtils.removeTab(tab);
});