Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test failed 7 times in the preceding 30 days. quicksearch this test
- Manifest: toolkit/components/pdfjs/test/browser.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
requestLongerTimeout(2);
const { sinon } = ChromeUtils.importESModule(
);
const RELATIVE_DIR = "toolkit/components/pdfjs/test/";
const PDF_URL = TESTROOT + "file_pdfjs_test.pdf";
const CROSS_SITE_PDF_URL = CROSS_SITE_TESTROOT + "file_pdfjs_test.pdf";
const SHORT_EMBED_URL = TESTROOT + "file_pdfjs_embed_short.html";
const REFERRER_PAGE_URL = TESTROOT + "file_pdfjs_embed_referrer.html";
const REFERRER_PDF_URL = TESTROOT + "file_pdfjs_referrer.sjs?pdf";
const REFERRER_RESULT_URL = TESTROOT + "file_pdfjs_referrer.sjs?result";
const SLOW_PDF_URL = TESTROOT + "file_pdfjs_slow.sjs";
const FALLBACK_CONVERTER_URI =
"resource://pdf.js/PdfEmbedFallbackStreamConverter.sys.mjs";
const SITES = [
{ name: "same-site", suffix: "", pdfURL: PDF_URL },
{ name: "cross-site", suffix: "_cross_site", pdfURL: CROSS_SITE_PDF_URL },
];
function pageURL(name, { suffix }) {
return `${TESTROOT}file_pdfjs_${name}${suffix}.html`;
}
function promiseDownloadFinished(list) {
return new Promise(resolve => {
list.addView({
onDownloadChanged(download) {
download.launchWhenSucceeded = false;
if (download.succeeded || download.error) {
list.removeView(this);
resolve(download);
}
},
});
});
}
async function clickOpenButton(embedContext) {
await SpecialPowers.spawn(embedContext, [], async () => {
const button = content.document.getElementById("fallbackOpenButton");
ok(button, "The fallback page must be displayed in the embed element");
await content.document.l10n.ready;
isnot(button.textContent, "", "The button must be localized");
await ContentTaskUtils.waitForCondition(
() => button.getBoundingClientRect().height,
"Waiting for the button of the fallback page to be laid out"
);
});
await BrowserTestUtils.synthesizeMouseAtCenter(
"#fallbackOpenButton",
{},
embedContext
);
}
add_setup(async function () {
const saveDir = createTemporarySaveDirectory();
const oldAction = changeMimeHandler(Ci.nsIHandlerInfo.saveToDisk, false);
await SpecialPowers.pushPrefEnv({
set: [
["pdfjs.disabled", true],
["pdfjs.embedFallback", true],
["browser.download.always_ask_before_handling_new_types", false],
["browser.download.folderList", 2],
["browser.download.dir", saveDir.path],
],
});
registerCleanupFunction(async function () {
changeMimeHandler(oldAction[0], oldAction[1]);
await cleanupDownloads();
saveDir.remove(true);
});
});
add_task(async function test_stream_converter_factory_dispatch() {
const { StreamConverterFactory } = ChromeUtils.importESModule(
"resource://gre/modules/pdfjs.sys.mjs"
);
const { PdfEmbedFallbackStreamConverter } = ChromeUtils.importESModule(
FALLBACK_CONVERTER_URI
);
ok(
StreamConverterFactory() instanceof PdfEmbedFallbackStreamConverter,
"The lightweight converter must handle PDFs when PDF.js is disabled"
);
await SpecialPowers.pushPrefEnv({ set: [["pdfjs.disabled", false]] });
try {
const { PdfStreamConverter } = ChromeUtils.importESModule(
"resource://pdf.js/PdfStreamConverter.sys.mjs"
);
ok(
StreamConverterFactory() instanceof PdfStreamConverter,
"The PDF.js converter must handle PDFs when the viewer is enabled"
);
} finally {
await SpecialPowers.popPrefEnv();
}
});
add_task(async function test_get_converted_type_without_channel() {
// nsIStreamConverter documents the channel as nullable, but neither converter
// can decide anything without one.
const checkConverter = name => {
const converter = Cc[
"@mozilla.org/streamconv;1?from=application/pdf&to=*/*"
].createInstance(Ci.nsIStreamConverter);
Assert.throws(
() => converter.getConvertedType("application/pdf", null),
error => error.result === Cr.NS_ERROR_INVALID_ARG,
`${name} must reject a missing channel with NS_ERROR_INVALID_ARG`
);
};
checkConverter("The fallback converter");
await SpecialPowers.pushPrefEnv({ set: [["pdfjs.disabled", false]] });
try {
checkConverter("The PDF.js converter");
} finally {
await SpecialPowers.popPrefEnv();
}
});
add_task(async function test_fallback_cancels_pdf_request() {
const requestStopped = TestUtils.topicObserved(
"http-on-stop-request",
subject =>
subject.QueryInterface(Ci.nsIHttpChannel).URI.spec === SLOW_PDF_URL
);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
SLOW_PDF_URL
);
BrowserTestUtils.startLoadingURIString(
browser,
TESTROOT + "file_pdfjs_embed_slow.html"
);
await fallbackLoaded;
const [channel] = await requestStopped;
is(
channel.status,
Cr.NS_BINDING_ABORTED,
"The original PDF request must be cancelled"
);
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_fallback_cancels_pdf_request_on_pagehide() {
const requestStopped = TestUtils.topicObserved(
"http-on-stop-request",
subject =>
subject.QueryInterface(Ci.nsIHttpChannel).URI.spec === SLOW_PDF_URL
);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackHidden = BrowserTestUtils.waitForContentEvent(
browser,
"pagehide",
true,
event => !!event.target.getElementById("fallbackOpenButton")
);
const fallbackNavigated = BrowserTestUtils.waitForContentEvent(
browser,
"DOMContentLoaded",
true,
event => {
const { target: document } = event;
if (!document.getElementById("fallbackOpenButton")) {
return false;
}
document.defaultView.location.replace("about:blank");
return true;
}
);
BrowserTestUtils.startLoadingURIString(
browser,
TESTROOT + "file_pdfjs_embed_slow.html"
);
await fallbackNavigated;
await fallbackHidden;
const [channel] = await requestStopped;
is(
channel.status,
Cr.NS_BINDING_ABORTED,
"The original PDF request must be cancelled when the fallback is hidden"
);
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_open_pdf_from_the_fallback_page() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
PDF_URL
);
BrowserTestUtils.startLoadingURIString(
browser,
TESTROOT + "file_pdfjs_embed.html"
);
await fallbackLoaded;
const embedContext = browser.browsingContext.children[0];
ok(embedContext, "The embed element must have a browsing context");
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
const embedderPrincipal = browser.contentPrincipal;
const retryStarted = TestUtils.topicObserved(
"http-on-modify-request",
subject => subject.QueryInterface(Ci.nsIHttpChannel).URI.spec === PDF_URL
);
const converterErrors = [];
const consoleListener = {
observe(message) {
if (
message instanceof Ci.nsIScriptError &&
message.sourceName === FALLBACK_CONVERTER_URI
) {
converterErrors.push(message);
}
},
};
Services.console.registerListener(consoleListener);
let download;
try {
info("Clicking on the button to open the pdf...");
await clickOpenButton(embedContext);
const [retryChannel] = await retryStarted;
is(
retryChannel.loadInfo.externalContentPolicyType,
Ci.nsIContentPolicy.TYPE_SUBDOCUMENT,
"The retried PDF request must be a subdocument load"
);
ok(
retryChannel.loadInfo.loadingPrincipal.equals(embedderPrincipal),
"The retried PDF request must use the embedder's loading principal"
);
ok(
retryChannel.loadInfo.triggeringPrincipal.equals(embedderPrincipal),
"The retried PDF request must preserve the embedder's triggering principal"
);
download = await downloadFinished;
} finally {
Services.console.unregisterListener(consoleListener);
}
is(
converterErrors.length,
0,
"Declining the subdocument retry must not produce a console error"
);
ok(download.succeeded, "The PDF must have been downloaded successfully");
is(download.source.url, PDF_URL, "The PDF must have the expected URL");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_open_pdf_from_the_fallback_page_in_a_frame() {
for (const site of SITES) {
for (const page of ["iframe", "frameset"]) {
info(`Testing the ${site.name} case with the ${page} page`);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
site.pdfURL
);
BrowserTestUtils.startLoadingURIString(browser, pageURL(page, site));
await fallbackLoaded;
const frameContext = browser.browsingContext.children[0];
ok(frameContext, "The frame must have a browsing context");
if (site.suffix && gFissionBrowser) {
isnot(
frameContext.currentWindowGlobal.osPid,
browser.browsingContext.currentWindowGlobal.osPid,
"The fallback must be displayed in the process of the PDF's site"
);
}
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
const embedderPrincipal = browser.contentPrincipal;
const retryStarted = TestUtils.topicObserved(
"http-on-modify-request",
subject =>
subject.QueryInterface(Ci.nsIHttpChannel).URI.spec === site.pdfURL
);
info("Clicking on the button to open the pdf of the frame...");
await clickOpenButton(frameContext);
const [retryChannel] = await retryStarted;
ok(
retryChannel.loadInfo.loadingPrincipal.equals(embedderPrincipal),
"The retried PDF request must use the embedder's loading principal"
);
ok(
retryChannel.loadInfo.triggeringPrincipal.equals(embedderPrincipal),
"The retried request must preserve the embedder's triggering principal"
);
const download = await downloadFinished;
ok(download.succeeded, "The PDF must have been downloaded successfully");
is(
download.source.url,
site.pdfURL,
"The PDF must have the expected URL"
);
// Downloading leaves the fallback in place.
await SpecialPowers.spawn(frameContext, [], () => {
ok(
content.document.getElementById("fallbackOpenButton"),
"The frame must still display the fallback page"
);
});
BrowserTestUtils.removeTab(tab);
await cleanupDownloads();
}
}
});
add_task(async function test_frames_are_not_downloaded() {
for (const site of SITES) {
info(`Testing the ${site.name} case`);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
pageURL("frames", site)
);
const frames = tab.linkedBrowser.browsingContext;
await TestUtils.waitForCondition(
() => frames.children.length === 3,
"Waiting for the browsing contexts of the frames"
);
for (const frame of frames.children) {
await SpecialPowers.spawn(frame, [], async () => {
const { ContentTaskUtils } = ChromeUtils.importESModule(
);
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("fallbackOpenButton"),
"Each frame must display the fallback page"
);
});
}
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloads = (await downloadList.getAll()).filter(download =>
download.source.url.startsWith(`${site.pdfURL}?`)
);
is(downloads.length, 0, "The PDFs must not have been downloaded");
BrowserTestUtils.removeTab(tab);
}
});
add_task(async function test_pref_disables_the_frame_fallback() {
await SpecialPowers.pushPrefEnv({
set: [["pdfjs.handleFrameAttributeLoads", false]],
});
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
pageURL("iframe", SITES[0])
);
const download = await downloadFinished;
ok(download.succeeded, "The PDF must have been downloaded successfully");
is(download.source.url, PDF_URL, "The PDF must have the expected URL");
BrowserTestUtils.removeTab(tab);
await cleanupDownloads();
// Object and embed elements are not affected by the pref.
const embedTab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
TESTROOT + "file_pdfjs_embed.html"
);
const embedder = embedTab.linkedBrowser.browsingContext;
await TestUtils.waitForCondition(
() => embedder.children.length === 1,
"Waiting for the browsing context of the embed element"
);
await SpecialPowers.spawn(embedder.children[0], [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("fallbackOpenButton"),
"The embed element must still display the fallback page"
);
});
BrowserTestUtils.removeTab(embedTab);
await SpecialPowers.popPrefEnv();
});
add_task(async function test_open_cross_site_pdf_from_the_fallback_page() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
CROSS_SITE_PDF_URL
);
BrowserTestUtils.startLoadingURIString(
browser,
TESTROOT + "file_pdfjs_embed_cross_site.html"
);
await fallbackLoaded;
const embedContext = browser.browsingContext.children[0];
ok(embedContext, "The embed element must have a browsing context");
if (gFissionBrowser) {
// A cross-site fallback runs in the PDF site's process, which cannot
// initiate the retry with the embedder's principal.
isnot(
embedContext.currentWindowGlobal.osPid,
browser.browsingContext.currentWindowGlobal.osPid,
"The fallback must be displayed in the process of the PDF's site"
);
}
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
const embedderPrincipal = browser.contentPrincipal;
const retryStarted = TestUtils.topicObserved(
"http-on-modify-request",
subject =>
subject.QueryInterface(Ci.nsIHttpChannel).URI.spec === CROSS_SITE_PDF_URL
);
info("Clicking on the button to open the cross-site pdf...");
await clickOpenButton(embedContext);
const [retryChannel] = await retryStarted;
ok(
retryChannel.loadInfo.loadingPrincipal.equals(embedderPrincipal),
"The retried PDF request must use the embedder's loading principal"
);
ok(
retryChannel.loadInfo.triggeringPrincipal.equals(embedderPrincipal),
"The retried PDF request must preserve the embedder's triggering principal"
);
const download = await downloadFinished;
ok(download.succeeded, "The PDF must have been downloaded successfully");
is(
download.source.url,
CROSS_SITE_PDF_URL,
"The PDF must have the expected URL"
);
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_only_frames_embedded_in_pages_are_opened() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
TESTROOT + "file_pdfjs_embed.html"
);
const { linkedBrowser: browser } = tab;
const topContext = browser.browsingContext;
await SpecialPowers.spawn(browser, [], () => {
const append = (name, properties) => {
content.document.body.append(
Object.assign(content.document.createElement(name), properties)
);
};
append("iframe", { src: "file_pdfjs_test.pdf" });
append("object", { type: "application/pdf", data: "file_pdfjs_test.pdf" });
});
await TestUtils.waitForCondition(
() => topContext.children.length === 3,
"Waiting for the browsing contexts of the added frames"
);
const [embedContext, iframeContext, objectContext] = topContext.children;
for (const context of topContext.children) {
await SpecialPowers.spawn(context, [], async () => {
await ContentTaskUtils.waitForCondition(
() => content.document.getElementById("fallbackOpenButton"),
"Waiting for the fallback page"
);
});
}
const openPdf = context => {
const sandbox = sinon.createSandbox();
const loadURI = sandbox.stub(context, "loadURI");
try {
context.currentWindowGlobal
.getActor("PdfEmbedFallback")
.receiveMessage({ name: "PdfEmbedFallback:OpenPdf", data: {} });
return loadURI.called;
} finally {
sandbox.restore();
}
};
is(
topContext.embedderElementType,
"browser",
"A tab is embedded in a browser element"
);
ok(
topContext.embedderWindowGlobal.documentPrincipal.isSystemPrincipal,
"A tab is embedded in the chrome window, which has the system principal"
);
ok(!openPdf(topContext), "A top-level document must not be loaded again");
is(iframeContext.embedderElementType, "iframe", "The PDF is in an iframe");
ok(openPdf(iframeContext), "The PDF in an iframe must be loaded");
is(embedContext.embedderElementType, "embed", "The PDF is in an embed");
ok(openPdf(embedContext), "The PDF embedded with an embed must be loaded");
is(objectContext.embedderElementType, "object", "The PDF is in an object");
ok(openPdf(objectContext), "The PDF embedded with an object must be loaded");
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_fallback_hidden_when_embed_is_too_short() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
PDF_URL
);
BrowserTestUtils.startLoadingURIString(browser, SHORT_EMBED_URL);
await fallbackLoaded;
const embedContext = browser.browsingContext.children[0];
ok(embedContext, "The embed element must have a browsing context");
await SpecialPowers.spawn(embedContext, [], async () => {
const container = content.document.getElementById("fallbackContainer");
// The subdocument's viewport may update after its load event.
await ContentTaskUtils.waitForCondition(
() => content.getComputedStyle(container).display === "none",
"The fallback must be hidden when its complete UI cannot be shown"
);
});
BrowserTestUtils.removeTab(tab);
});
add_task(async function test_open_pdf_preserves_referrer() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: "about:blank" },
async browser => {
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
REFERRER_PDF_URL
);
BrowserTestUtils.startLoadingURIString(browser, REFERRER_PAGE_URL);
await fallbackLoaded;
const embedContext = browser.browsingContext.children[0];
ok(embedContext, "The embed element must have a browsing context");
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
await clickOpenButton(embedContext);
const download = await downloadFinished;
ok(download.succeeded, "The PDF must have been downloaded successfully");
// The retry overwrites the referrer recorded by the embed's own load: a
// retry without referrer would leave an empty one behind.
const response = await fetch(REFERRER_RESULT_URL);
is(
await response.text(),
REFERRER_PAGE_URL,
"The retried PDF request must preserve the original referrer"
);
}
);
});
add_task(async function test_open_local_pdf_from_the_fallback_page() {
// retries the load with the original request's triggering principal.
const dir = createTemporarySaveDirectory("localpdf");
const pdfPath = PathUtils.join(dir.path, "test.pdf");
const pagePath = PathUtils.join(dir.path, "embed.html");
await IOUtils.writeUTF8(pdfPath, "%PDF-1.7\n");
await IOUtils.writeUTF8(
pagePath,
`<!DOCTYPE html><embed type="application/pdf" src="test.pdf"
width="600" height="400">`
);
const pdfURL = PathUtils.toFileURI(pdfPath);
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const fallbackLoaded = BrowserTestUtils.browserLoaded(
browser,
/* includeSubFrames = */ true,
pdfURL
);
BrowserTestUtils.startLoadingURIString(
browser,
PathUtils.toFileURI(pagePath)
);
await fallbackLoaded;
const embedContext = browser.browsingContext.children[0];
// The setup configures PDFs to save without asking, but Firefox still prompts
// for a local file to avoid creating a second copy.
const dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded();
info("Clicking on the button to open the local pdf...");
await clickOpenButton(embedContext);
const dialogWindow = await dialogPromise;
is(
dialogWindow.location.href,
"chrome://mozapps/content/downloads/unknownContentType.xhtml",
"The dialog to open the local pdf must be displayed"
);
const dialogClosed = BrowserTestUtils.domWindowClosed(dialogWindow);
dialogWindow.document.querySelector("#unknownContentType").cancelDialog();
await dialogClosed;
BrowserTestUtils.removeTab(tab);
dir.remove(true);
});
add_task(async function test_toplevel_pdf_is_still_downloaded() {
const tab = await BrowserTestUtils.openNewForegroundTab(
gBrowser,
"about:blank"
);
const { linkedBrowser: browser } = tab;
const downloadList = await Downloads.getList(Downloads.PUBLIC);
const downloadFinished = promiseDownloadFinished(downloadList);
BrowserTestUtils.startLoadingURIString(browser, PDF_URL);
const download = await downloadFinished;
ok(download.succeeded, "The PDF must have been downloaded successfully");
is(download.source.url, PDF_URL, "The PDF must have the expected URL");
is(
browser.currentURI.spec,
"about:blank",
"The pdf must not have been displayed"
);
BrowserTestUtils.removeTab(tab);
});