Bug 1971140 - Improve Content-Disposition: attachment handling for object/embed, r=smaug a=dmeehan
Differential Revision: https://phabricator.services.mozilla.com/D253075
This commit is contained in:
committed by
dmeehan@mozilla.com
parent
2b7bee41af
commit
7bfb0e8915
@@ -10795,11 +10795,14 @@ static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure,
|
||||
openFlags |= nsIURILoader::DONT_RETARGET;
|
||||
}
|
||||
|
||||
// Unless the pref is set, object/embed loads always specify DONT_RETARGET.
|
||||
// See bug 1868001 for details.
|
||||
if (!aIsDocumentLoad &&
|
||||
!StaticPrefs::dom_navigation_object_embed_allow_retargeting()) {
|
||||
openFlags |= nsIURILoader::DONT_RETARGET;
|
||||
if (!aIsDocumentLoad) {
|
||||
openFlags |= nsIURILoader::IS_OBJECT_EMBED;
|
||||
|
||||
// Unless the pref is set, object/embed loads always specify DONT_RETARGET.
|
||||
// See bug 1868001 for details.
|
||||
if (!StaticPrefs::dom_navigation_object_embed_allow_retargeting()) {
|
||||
openFlags |= nsIURILoader::DONT_RETARGET;
|
||||
}
|
||||
}
|
||||
|
||||
return openFlags;
|
||||
@@ -10890,8 +10893,14 @@ nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) {
|
||||
// ClientInfo, so we just need to allocate a corresponding ClientSource.
|
||||
CreateReservedSourceIfNeeded(channel, GetMainThreadSerialEventTarget());
|
||||
|
||||
uint32_t documentOpenInfoFlags = nsIURILoader::DONT_RETARGET;
|
||||
if (loadInfo->GetExternalContentPolicyType() ==
|
||||
ExtContentPolicy::TYPE_OBJECT) {
|
||||
documentOpenInfoFlags |= nsIURILoader::IS_OBJECT_EMBED;
|
||||
}
|
||||
|
||||
RefPtr<nsDocumentOpenInfo> loader =
|
||||
new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr);
|
||||
new nsDocumentOpenInfo(this, documentOpenInfoFlags, nullptr);
|
||||
channel->SetLoadGroup(mLoadGroup);
|
||||
|
||||
MOZ_ALWAYS_SUCCEEDS(loader->Prepare());
|
||||
|
||||
@@ -123,6 +123,12 @@ support-files = [
|
||||
"file_pdf_object_attachment.html",
|
||||
"file_pdf_attachment.pdf",
|
||||
"file_pdf_attachment.pdf^headers^",
|
||||
"file_svg_object_attachment.html",
|
||||
"file_svg_attachment.svg",
|
||||
"file_svg_attachment.svg^headers^",
|
||||
"file_html_object_attachment.html",
|
||||
"file_html_attachment.html",
|
||||
"file_html_attachment.html^headers^",
|
||||
]
|
||||
|
||||
["browser_outline_refocus.js"]
|
||||
|
||||
@@ -7,29 +7,75 @@ const httpsTestRoot = getRootDirectory(gTestPath).replace(
|
||||
"https://example.com"
|
||||
);
|
||||
|
||||
add_task(async function test_pdf_object_attachment() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", false]],
|
||||
});
|
||||
async function loadAndCheck(file, displayInline, downloadFile = null) {
|
||||
// Get the downloads list and add a view to listen for a download to be added.
|
||||
// We do this even if we aren't going to download anything, so we notice if a
|
||||
// download is started.
|
||||
let download;
|
||||
let downloadList = await Downloads.getList(Downloads.ALL);
|
||||
let downloadView = {
|
||||
async onDownloadAdded(aDownload) {
|
||||
info("download added");
|
||||
ok(downloadFile, "Should be expecting a download");
|
||||
download = aDownload;
|
||||
|
||||
// Clean up the download from the list
|
||||
downloadList.remove(aDownload);
|
||||
await aDownload.finalize(true);
|
||||
},
|
||||
};
|
||||
await downloadList.addView(downloadView);
|
||||
|
||||
// Open the new URL and perform the load.
|
||||
await BrowserTestUtils.withNewTab(
|
||||
`${httpsTestRoot}/file_pdf_object_attachment.html`,
|
||||
`${httpsTestRoot}/${file}`,
|
||||
async browser => {
|
||||
is(
|
||||
browser.browsingContext.children.length,
|
||||
1,
|
||||
"Should have a child frame"
|
||||
displayInline ? 1 : 0,
|
||||
`Should ${displayInline ? "not " : ""}have a child frame`
|
||||
);
|
||||
|
||||
await SpecialPowers.spawn(
|
||||
browser,
|
||||
[displayInline],
|
||||
async displayInline => {
|
||||
let obj = content.document.querySelector("object");
|
||||
is(
|
||||
obj.displayedType,
|
||||
displayInline
|
||||
? Ci.nsIObjectLoadingContent.TYPE_DOCUMENT
|
||||
: Ci.nsIObjectLoadingContent.TYPE_FALLBACK,
|
||||
`should be displaying TYPE_${displayInline ? "DOCUMENT" : "FALLBACK"}`
|
||||
);
|
||||
}
|
||||
);
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let obj = content.document.querySelector("object");
|
||||
is(
|
||||
obj.displayedType,
|
||||
Ci.nsIObjectLoadingContent.TYPE_DOCUMENT,
|
||||
"should be displaying TYPE_DOCUMENT"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// Clean up our download observer.
|
||||
await downloadList.removeView(downloadView);
|
||||
if (downloadFile) {
|
||||
is(
|
||||
download.source.url,
|
||||
`${httpsTestRoot}/${downloadFile}`,
|
||||
"Download has the correct source"
|
||||
);
|
||||
} else {
|
||||
is(download, undefined, "Should not have seen a download");
|
||||
}
|
||||
}
|
||||
|
||||
add_task(async function test_pdf_object_attachment() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.navigation.object_embed.allow_retargeting", false],
|
||||
["browser.download.open_pdf_attachments_inline", false],
|
||||
],
|
||||
});
|
||||
|
||||
// PDF attachment should display inline.
|
||||
await loadAndCheck("file_pdf_object_attachment.html", true);
|
||||
});
|
||||
|
||||
add_task(async function test_img_object_attachment() {
|
||||
@@ -37,132 +83,71 @@ add_task(async function test_img_object_attachment() {
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", false]],
|
||||
});
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
`${httpsTestRoot}/file_img_object_attachment.html`,
|
||||
async browser => {
|
||||
is(
|
||||
browser.browsingContext.children.length,
|
||||
1,
|
||||
"Should have a child frame"
|
||||
);
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let obj = content.document.querySelector("object");
|
||||
is(
|
||||
obj.displayedType,
|
||||
Ci.nsIObjectLoadingContent.TYPE_DOCUMENT,
|
||||
"should be displaying TYPE_DOCUMENT"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
// Image attachment should display inline.
|
||||
await loadAndCheck("file_img_object_attachment.html", true);
|
||||
});
|
||||
|
||||
async function waitForDownload() {
|
||||
// Get the downloads list and add a view to listen for a download to be added.
|
||||
let downloadList = await Downloads.getList(Downloads.ALL);
|
||||
|
||||
// Wait for a single download
|
||||
let downloadView;
|
||||
let finishedAllDownloads = new Promise(resolve => {
|
||||
downloadView = {
|
||||
onDownloadAdded(aDownload) {
|
||||
info("download added");
|
||||
resolve(aDownload);
|
||||
},
|
||||
};
|
||||
add_task(async function test_svg_object_attachment() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", false]],
|
||||
});
|
||||
await downloadList.addView(downloadView);
|
||||
let download = await finishedAllDownloads;
|
||||
await downloadList.removeView(downloadView);
|
||||
|
||||
// Clean up the download from the list.
|
||||
await downloadList.remove(download);
|
||||
await download.finalize(true);
|
||||
// SVG attachment should fail to load.
|
||||
await loadAndCheck("file_svg_object_attachment.html", false);
|
||||
});
|
||||
|
||||
// Return the download
|
||||
return download;
|
||||
}
|
||||
add_task(async function test_html_object_attachment() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", false]],
|
||||
});
|
||||
|
||||
add_task(async function test_pdf_object_attachment_download() {
|
||||
// HTML attachment should fail to load.
|
||||
await loadAndCheck("file_html_object_attachment.html", false);
|
||||
});
|
||||
|
||||
add_task(async function test_pdf_object_attachment_allow_retargeting() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
["dom.navigation.object_embed.allow_retargeting", true],
|
||||
["browser.download.open_pdf_attachments_inline", false],
|
||||
],
|
||||
});
|
||||
|
||||
// Even if `allow_retargeting` is enabled, we always display PDFs inline.
|
||||
await loadAndCheck("file_pdf_object_attachment.html", true);
|
||||
});
|
||||
|
||||
add_task(async function test_img_object_attachment_allow_retargeting() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", true]],
|
||||
});
|
||||
|
||||
// Set the behaviour to save pdfs to disk and not handle internally, so we
|
||||
// don't end up with extra tabs after the test.
|
||||
var gMimeSvc = Cc["@mozilla.org/mime;1"].getService(Ci.nsIMIMEService);
|
||||
var gHandlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
|
||||
Ci.nsIHandlerService
|
||||
);
|
||||
const mimeInfo = gMimeSvc.getFromTypeAndExtension("application/pdf", "pdf");
|
||||
let previousAction = mimeInfo.preferredAction;
|
||||
mimeInfo.preferredAction = Ci.nsIHandlerInfo.saveToDisk;
|
||||
gHandlerSvc.store(mimeInfo);
|
||||
registerCleanupFunction(() => {
|
||||
mimeInfo.preferredAction = previousAction;
|
||||
gHandlerSvc.store(mimeInfo);
|
||||
});
|
||||
|
||||
// Start listening for the download before opening the new tab.
|
||||
let downloadPromise = waitForDownload();
|
||||
await BrowserTestUtils.withNewTab(
|
||||
`${httpsTestRoot}/file_pdf_object_attachment.html`,
|
||||
async browser => {
|
||||
let download = await downloadPromise;
|
||||
is(
|
||||
download.source.url,
|
||||
`${httpsTestRoot}/file_pdf_attachment.pdf`,
|
||||
"download should be the pdf"
|
||||
);
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let obj = content.document.querySelector("object");
|
||||
is(
|
||||
obj.displayedType,
|
||||
Ci.nsIObjectLoadingContent.TYPE_FALLBACK,
|
||||
"should be displaying TYPE_FALLBACK"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
// Even if `allow_retargeting` is enabled, we always display images inline.
|
||||
await loadAndCheck("file_img_object_attachment.html", true);
|
||||
});
|
||||
|
||||
add_task(async function test_img_object_attachment_download() {
|
||||
// NOTE: This is testing our current behaviour here as of bug 1868001 (which
|
||||
// is to download an image with `Content-Disposition: attachment` embedded
|
||||
// within an object or embed element).
|
||||
//
|
||||
// Other browsers ignore the `Content-Disposition: attachment` header when
|
||||
// loading images within object or embed element as-of december 2023, as
|
||||
// we did prior to the changes in bug 1595491.
|
||||
//
|
||||
// If this turns out to be a web-compat issue, we may want to introduce
|
||||
// special handling to ignore content-disposition when loading images within
|
||||
// an object or embed element.
|
||||
add_task(async function test_svg_object_attachment_allow_retargeting() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", true]],
|
||||
});
|
||||
|
||||
// Start listening for the download before opening the new tab.
|
||||
let downloadPromise = waitForDownload();
|
||||
await BrowserTestUtils.withNewTab(
|
||||
`${httpsTestRoot}/file_img_object_attachment.html`,
|
||||
async browser => {
|
||||
let download = await downloadPromise;
|
||||
is(
|
||||
download.source.url,
|
||||
`${httpsTestRoot}/file_img_attachment.jpg`,
|
||||
"download should be the jpg"
|
||||
);
|
||||
|
||||
await SpecialPowers.spawn(browser, [], async () => {
|
||||
let obj = content.document.querySelector("object");
|
||||
is(
|
||||
obj.displayedType,
|
||||
Ci.nsIObjectLoadingContent.TYPE_FALLBACK,
|
||||
"should be displaying TYPE_FALLBACK"
|
||||
);
|
||||
});
|
||||
}
|
||||
// SVG attachments are downloaded if allow_retargeting is set.
|
||||
await loadAndCheck(
|
||||
"file_svg_object_attachment.html",
|
||||
false,
|
||||
"file_svg_attachment.svg"
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_html_object_attachment_allow_retargeting() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.navigation.object_embed.allow_retargeting", true]],
|
||||
});
|
||||
|
||||
// HTML attachments are downloaded if allow_retargeting is set.
|
||||
await loadAndCheck(
|
||||
"file_html_object_attachment.html",
|
||||
false,
|
||||
"file_html_attachment.html"
|
||||
);
|
||||
});
|
||||
|
||||
6
dom/base/test/file_html_attachment.html
Normal file
6
dom/base/test/file_html_attachment.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<h1>Hello, World!</h1>
|
||||
</body>
|
||||
</html>
|
||||
2
dom/base/test/file_html_attachment.html^headers^
Normal file
2
dom/base/test/file_html_attachment.html^headers^
Normal file
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/html
|
||||
Content-Disposition: attachment
|
||||
6
dom/base/test/file_html_object_attachment.html
Normal file
6
dom/base/test/file_html_object_attachment.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<object data="file_html_attachment.html" width="100%" height="600">fallback</object>
|
||||
</body>
|
||||
</html>
|
||||
3
dom/base/test/file_svg_attachment.svg
Normal file
3
dom/base/test/file_svg_attachment.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="500px" height="500px">
|
||||
<rect x1="0" y1="0" width="100%" height="100%" fill="lime"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 140 B |
2
dom/base/test/file_svg_attachment.svg^headers^
Normal file
2
dom/base/test/file_svg_attachment.svg^headers^
Normal file
@@ -0,0 +1,2 @@
|
||||
Content-Type: text/svg+xml
|
||||
Content-Disposition: attachment
|
||||
6
dom/base/test/file_svg_object_attachment.html
Normal file
6
dom/base/test/file_svg_object_attachment.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<body>
|
||||
<object data="file_svg_attachment.svg" width="100%" height="600">fallback</object>
|
||||
</body>
|
||||
</html>
|
||||
@@ -53,6 +53,12 @@ interface nsIURILoader : nsISupports
|
||||
* be indicated.
|
||||
*/
|
||||
const unsigned long DONT_RETARGET = 1 << 1;
|
||||
/**
|
||||
* If this flag is set, the navigation is for an object or embed element,
|
||||
* which may handle some content types internally, even if an attachment
|
||||
* content disposition is specified.
|
||||
*/
|
||||
const unsigned long IS_OBJECT_EMBED = 1 << 2;
|
||||
/* @} */
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
#include "mozilla/StaticPrefs_dom.h"
|
||||
#include "mozilla/StaticPrefs_general.h"
|
||||
#include "nsContentUtils.h"
|
||||
#include "imgLoader.h"
|
||||
|
||||
mozilla::LazyLogModule nsURILoader::mLog("URILoader");
|
||||
|
||||
@@ -409,26 +410,26 @@ nsresult nsDocumentOpenInfo::DispatchContent(nsIRequest* request) {
|
||||
// could happen because the Content-Disposition header is set so, or, in the
|
||||
// future, because the user has specified external handling for the MIME
|
||||
// type.
|
||||
//
|
||||
// If we're not going to be able to retarget to an external handler, ignore
|
||||
// content-disposition, and unconditionally try to display the content.
|
||||
// This is used for object/embed tags, which expect to display subresources
|
||||
// marked with an attachment disposition.
|
||||
bool forceExternalHandling = false;
|
||||
if (!(mFlags & nsIURILoader::DONT_RETARGET)) {
|
||||
uint32_t disposition;
|
||||
rv = aChannel->GetContentDisposition(&disposition);
|
||||
uint32_t disposition;
|
||||
rv = aChannel->GetContentDisposition(&disposition);
|
||||
|
||||
if (NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT) {
|
||||
forceExternalHandling = true;
|
||||
}
|
||||
}
|
||||
bool forceExternalHandling =
|
||||
NS_SUCCEEDED(rv) && disposition == nsIChannel::DISPOSITION_ATTACHMENT;
|
||||
|
||||
LOG((" forceExternalHandling: %s", forceExternalHandling ? "yes" : "no"));
|
||||
LOG((" IsSandboxed: %s", IsSandboxed(aChannel) ? "yes" : "no"));
|
||||
LOG((" IsContentPDF: %s",
|
||||
IsContentPDF(aChannel, mContentType) ? "yes" : "no"));
|
||||
|
||||
// Ignore the Content-Disposition header if we're loading a PDF or Image
|
||||
// subresource within an object/embed element.
|
||||
if (forceExternalHandling && (mFlags & nsIURILoader::IS_OBJECT_EMBED) &&
|
||||
(imgLoader::SupportImageWithMimeType(mContentType) ||
|
||||
IsContentPDF(aChannel, mContentType))) {
|
||||
LOG(("Handling pdf/image MIME internally for object/embed element"));
|
||||
forceExternalHandling = false;
|
||||
}
|
||||
|
||||
bool maybeForceInternalHandling =
|
||||
forceExternalHandling &&
|
||||
mozilla::StaticPrefs::browser_download_open_pdf_attachments_inline();
|
||||
|
||||
Reference in New Issue
Block a user