Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
const PDF_URL = TESTROOT + "file_pdfjs_test.pdf";
const { PdfJsFeaturesNotification } = ChromeUtils.importESModule(
"resource://pdf.js/PdfJsFeaturesNotification.sys.mjs"
);
const IMPRESSION_COUNT_PREF = PdfJsFeaturesNotification.IMPRESSION_COUNT_PREF;
function waitForBarShown(browser) {
return SpecialPowers.spawn(browser, [], async () => {
await ContentTaskUtils.waitForCondition(() => {
const el = content.document.querySelector("moz-message-bar");
return el && !el.hidden;
}, "features-notification bar is visible");
});
}
function waitForBarHidden(browser) {
return SpecialPowers.spawn(browser, [], async () => {
await ContentTaskUtils.waitForCondition(() => {
const el = content.document.querySelector("moz-message-bar");
// Hosts may either hide or remove a consumed notification.
return !el || el.hidden;
}, "features-notification bar is no longer shown");
});
}
function dismissBar(browser) {
return SpecialPowers.spawn(browser, [], async () => {
const bar = content.document.querySelector("moz-message-bar");
// Query the shadow DOM without waiving Xrays.
const closeButton = () => bar.shadowRoot?.querySelector("moz-button.close");
await ContentTaskUtils.waitForCondition(
() => closeButton(),
"close button is rendered"
);
closeButton().click();
});
}
// Computed styles to compare across both hosts.
const SHARED_STYLES = {
bar: [
"colorScheme",
"backgroundColor",
"color",
"borderBottomColor",
"fontFamily",
"fontSize",
"lineHeight",
],
heading: ["fontWeight", "fontSize"],
link: ["color", "textDecorationLine"],
};
// Set the root color scheme and font size for the comparison.
function setDocumentStyles(browser, scheme) {
return SpecialPowers.spawn(browser, [scheme], value => {
content.document.documentElement.style.colorScheme = value;
content.document.documentElement.style.fontSize = content.getComputedStyle(
content.document.querySelector("moz-message-bar")
).fontSize;
});
}
function getBarStyles(browser) {
return SpecialPowers.spawn(browser, [SHARED_STYLES], async parts => {
const bar = content.document.querySelector("moz-message-bar");
const barStyle = content.getComputedStyle(bar);
await ContentTaskUtils.waitForCondition(
() =>
barStyle.backgroundColor !== "rgba(0, 0, 0, 0)" &&
bar.shadowRoot?.querySelector(".heading"),
"the widget stylesheet is applied and the heading is rendered"
);
const elements = {
bar,
heading: bar.shadowRoot.querySelector(".heading"),
link: bar.querySelector("a"),
};
const styles = {};
for (const [part, properties] of Object.entries(parts)) {
const style = content.getComputedStyle(elements[part]);
for (const property of properties) {
styles[`${part} ${property}`] = style[property];
}
}
styles.height = bar.getBoundingClientRect().height;
return styles;
});
}
async function openViewer() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
await waitForPdfJS(tab.linkedBrowser, PDF_URL);
return tab;
}
function resetNotificationBudget() {
Services.prefs.clearUserPref(IMPRESSION_COUNT_PREF);
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [[IMPRESSION_COUNT_PREF, 0]],
});
registerCleanupFunction(resetNotificationBudget);
});
add_task(async function dismiss_in_viewer_hides_about_pdf_live() {
resetNotificationBudget();
const viewerTab = await openViewer();
await waitForBarShown(viewerTab.linkedBrowser);
const aboutTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:pdf"
);
await waitForBarShown(aboutTab.linkedBrowser);
await dismissBar(viewerTab.linkedBrowser);
await waitForBarHidden(aboutTab.linkedBrowser);
await waitForBarHidden(viewerTab.linkedBrowser);
Assert.ok(
PdfJsFeaturesNotification.isConsumed(),
"dismissing in the viewer spent the shared budget"
);
BrowserTestUtils.removeTab(aboutTab);
await waitForPdfJSClose(viewerTab.linkedBrowser, true);
});
add_task(async function dismiss_in_about_pdf_hides_viewer_live() {
resetNotificationBudget();
const viewerTab = await openViewer();
await waitForBarShown(viewerTab.linkedBrowser);
const aboutTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:pdf"
);
await waitForBarShown(aboutTab.linkedBrowser);
await dismissBar(aboutTab.linkedBrowser);
await waitForBarHidden(viewerTab.linkedBrowser);
await waitForBarHidden(aboutTab.linkedBrowser);
Assert.ok(
PdfJsFeaturesNotification.isConsumed(),
"dismissing on about:pdf spent the shared budget"
);
BrowserTestUtils.removeTab(aboutTab);
await waitForPdfJSClose(viewerTab.linkedBrowser, true);
});
add_task(async function bar_styles_match_across_hosts() {
resetNotificationBudget();
await SpecialPowers.pushPrefEnv({ set: [[IMPRESSION_COUNT_PREF, 0]] });
const viewerTab = await openViewer();
await waitForBarShown(viewerTab.linkedBrowser);
const aboutTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:pdf"
);
await waitForBarShown(aboutTab.linkedBrowser);
for (const scheme of ["light", "dark"]) {
await setDocumentStyles(viewerTab.linkedBrowser, scheme);
await setDocumentStyles(aboutTab.linkedBrowser, scheme);
const viewerStyles = await getBarStyles(viewerTab.linkedBrowser);
const aboutStyles = await getBarStyles(aboutTab.linkedBrowser);
Assert.equal(
aboutStyles["bar colorScheme"],
scheme,
`about:pdf renders the bar in ${scheme} mode`
);
for (const [key, value] of Object.entries(aboutStyles)) {
Assert.equal(
viewerStyles[key],
value,
`${scheme}: viewer ${key} matches about:pdf`
);
}
}
BrowserTestUtils.removeTab(aboutTab);
await waitForPdfJSClose(viewerTab.linkedBrowser, true);
await SpecialPowers.popPrefEnv();
});
add_task(async function dismiss_in_one_viewer_hides_the_other_live() {
resetNotificationBudget();
const tabA = await openViewer();
const tabB = await openViewer();
await waitForBarShown(tabA.linkedBrowser);
await waitForBarShown(tabB.linkedBrowser);
await dismissBar(tabA.linkedBrowser);
await waitForBarHidden(tabB.linkedBrowser);
await waitForBarHidden(tabA.linkedBrowser);
await waitForPdfJSClose(tabB.linkedBrowser, true);
await waitForPdfJSClose(tabA.linkedBrowser, true);
});
add_task(async function concurrent_viewers_claim_one_impression() {
resetNotificationBudget();
await SpecialPowers.pushPrefEnv({
set: [[PdfJsFeaturesNotification.IMPRESSION_LIMIT_PREF, 1]],
});
const tabA = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const tabB = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
await Promise.all(
[tabA, tabB].map(tab => {
const browser = tab.linkedBrowser;
const loaded = BrowserTestUtils.browserLoaded(browser, false, PDF_URL);
BrowserTestUtils.startLoadingURIString(browser, PDF_URL);
return loaded;
})
);
// A background winner may not render its bar, so compare claim results.
const dismissed = await Promise.all(
[tabA, tabB].map(tab =>
SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
// Wait for document loading without requiring a rendered page.
await ContentTaskUtils.waitForCondition(
() => content.wrappedJSObject.PDFViewerApplication?.pdfDocument,
"the PDF document is loaded"
);
await content.wrappedJSObject.PDFViewerApplication.preferences
.initializedPromise;
return content.wrappedJSObject.PDFViewerApplicationOptions.get(
"featuresNotificationDismissed"
);
})
)
);
Assert.equal(
dismissed.filter(value => !value).length,
1,
"only one concurrent viewer claims the last impression"
);
Assert.equal(
Services.prefs.getIntPref(IMPRESSION_COUNT_PREF, 0),
1,
"the concurrent loads spend exactly one impression"
);
const winner = dismissed[0] ? tabB : tabA,
loser = dismissed[0] ? tabA : tabB;
gBrowser.selectedTab = winner;
await waitForBarShown(winner.linkedBrowser);
await SpecialPowers.spawn(loser.linkedBrowser, [], async () => {
Assert.ok(
content.document.querySelector("moz-message-bar").hidden,
"the other viewer keeps the notification hidden"
);
Assert.equal(
content.customElements.get("moz-message-bar"),
undefined,
"the other viewer doesn't load the moz-message-bar module"
);
});
await waitForPdfJSClose(tabB.linkedBrowser, true);
await waitForPdfJSClose(tabA.linkedBrowser, true);
await SpecialPowers.popPrefEnv();
});
// Reaching the limit must leave both existing bars open.
add_task(async function last_impression_keeps_open_bars() {
resetNotificationBudget();
await SpecialPowers.pushPrefEnv({
set: [[PdfJsFeaturesNotification.IMPRESSION_LIMIT_PREF, 2]],
});
const viewerTab = await openViewer();
await waitForBarShown(viewerTab.linkedBrowser);
const aboutTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:pdf"
);
await waitForBarShown(aboutTab.linkedBrowser);
Assert.equal(
Services.prefs.getIntPref(IMPRESSION_COUNT_PREF, 0),
2,
"the two showings spent the whole budget"
);
Assert.ok(
!PdfJsFeaturesNotification.isEligible(),
"no further showing is allowed"
);
Assert.ok(
!PdfJsFeaturesNotification.isConsumed(),
"an exhausted budget is not a dismissal"
);
await TestUtils.waitForTick();
for (const [host, browser] of [
["viewer", viewerTab.linkedBrowser],
["about:pdf", aboutTab.linkedBrowser],
]) {
const hidden = await SpecialPowers.spawn(browser, [], () => {
return content.document.querySelector("moz-message-bar").hidden;
});
Assert.ok(!hidden, `the ${host} bar stays open once the budget is spent`);
}
BrowserTestUtils.removeTab(aboutTab);
await waitForPdfJSClose(viewerTab.linkedBrowser, true);
await SpecialPowers.popPrefEnv();
});