Bug 196078 - Part 2: Support displaying arbitrary text/* MIME types as plain text, r=smaug,necko-reviewers,valentin

This patch refactors how we check for text formats when deciding how to handle
resources, such that more text MIME types will be rendered in-browser, rather
than downloaded.

This change requires us to move more away from using the Gecko-Content-Viewers
category in the category manager for this decision, as we need to handle an
unlimited number of MIME types behind the scenes.

Support for Gecko-Content-Viewers was left in for both the in-tree use for
application/http-index-format and dynamically determining whether image/avif
and image/jxl are supported, as well as for the message/rfc822 type used by
Thunderbird.

Differential Revision: https://phabricator.services.mozilla.com/D212078
This commit is contained in:
Nika Layzell 2024-06-05 00:05:58 +00:00
parent c613d7241c
commit 855d5761c0
9 changed files with 111 additions and 89 deletions

View File

@ -17,10 +17,9 @@ webidl Document;
/**
* To get a component that implements nsIDocumentLoaderFactory
* for a given mimetype, use nsICategoryManager to find an entry
* with the mimetype as its name in the category "Gecko-Content-Viewers".
* The value of the entry is the contractid of the component.
* The component is a service, so use GetService, not CreateInstance to get it.
* for a given mimetype, use nsContentUtils::FindInternalDocumentViewer.
* This will look up the MIME type within the "Gecko-Content-Viewers" category,
* with additional handlers for other content types.
*/
[scriptable, uuid(e795239e-9d3c-47c4-b063-9e600fb3b287)]

View File

@ -1081,21 +1081,13 @@ nsresult ExternalResourceMap::PendingLoad::SetupViewer(
new LoadgroupCallbacks(callbacks);
newLoadGroup->SetNotificationCallbacks(newCallbacks);
// This is some serious hackery cribbed from docshell
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
nsCString contractId;
nsresult rv =
catMan->GetCategoryEntry("Gecko-Content-Viewers", type, contractId);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
do_GetService(contractId.get());
nsContentUtils::FindInternalDocumentViewer(type);
NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIDocumentViewer> viewer;
nsCOMPtr<nsIStreamListener> listener;
rv = docLoaderFactory->CreateInstance(
nsresult rv = docLoaderFactory->CreateInstance(
"external-resource", chan, newLoadGroup, type, nullptr, nullptr,
getter_AddRefs(listener), getter_AddRefs(viewer));
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -4631,28 +4631,66 @@ bool nsContentUtils::IsChildOfSameType(Document* aDoc) {
return false;
}
static bool IsJSONType(const nsACString& aContentType) {
return aContentType.EqualsLiteral(TEXT_JSON) ||
aContentType.EqualsLiteral(APPLICATION_JSON);
}
static bool IsNonPlainTextType(const nsACString& aContentType) {
// MIME type suffixes which should not be plain text.
static constexpr std::string_view kNonPlainTextTypes[] = {
"html",
"xml",
"xsl",
"calendar",
"x-calendar",
"x-vcalendar",
"vcalendar",
"vcard",
"x-vcard",
"directory",
"ldif",
"qif",
"x-qif",
"x-csv",
"x-vcf",
"rtf",
"comma-separated-values",
"csv",
"tab-separated-values",
"tsv",
"ofx",
"vnd.sun.j2me.app-descriptor",
"x-ms-iqy",
"x-ms-odc",
"x-ms-rqy",
"x-ms-contact"};
// Trim off the "text/" prefix for comparison.
MOZ_ASSERT(StringBeginsWith(aContentType, "text/"_ns));
std::string_view suffix = aContentType;
suffix.remove_prefix(5);
for (std::string_view type : kNonPlainTextTypes) {
if (type == suffix) {
return true;
}
}
return false;
}
bool nsContentUtils::IsPlainTextType(const nsACString& aContentType) {
// NOTE: if you add a type here, add it to the content_types array in
// layout/build/components.conf as well.
return aContentType.EqualsLiteral(TEXT_PLAIN) ||
aContentType.EqualsLiteral(TEXT_CSS) ||
aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
aContentType.EqualsLiteral(TEXT_VTT) ||
aContentType.EqualsLiteral(APPLICATION_JAVASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_XJAVASCRIPT) ||
aContentType.EqualsLiteral(TEXT_ECMASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_ECMASCRIPT) ||
aContentType.EqualsLiteral(TEXT_JAVASCRIPT) ||
aContentType.EqualsLiteral(APPLICATION_JSON) ||
aContentType.EqualsLiteral(TEXT_JSON) ||
aContentType.EqualsLiteral(TEXT_EVENT_STREAM);
// All `text/*`, any JSON type and any JavaScript type are considered "plain
// text" types for the purposes of how to render them as a document.
return (StringBeginsWith(aContentType, "text/"_ns) &&
!IsNonPlainTextType(aContentType)) ||
IsJSONType(aContentType) || IsJavascriptMIMEType(aContentType);
}
bool nsContentUtils::IsUtf8OnlyPlainTextType(const nsACString& aContentType) {
// NOTE: This must be a subset of the list in IsPlainTextType().
return aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
aContentType.EqualsLiteral(APPLICATION_JSON) ||
aContentType.EqualsLiteral(TEXT_JSON) ||
return IsJSONType(aContentType) ||
aContentType.EqualsLiteral(TEXT_CACHE_MANIFEST) ||
aContentType.EqualsLiteral(TEXT_VTT);
}
@ -7218,9 +7256,12 @@ nsContentUtils::FindInternalDocumentViewer(const nsACString& aType,
return docFactory.forget();
}
if (DecoderTraits::IsSupportedInVideoDocument(aType)) {
docFactory =
do_GetService("@mozilla.org/content/document-loader-factory;1");
// If the type wasn't registered in `Gecko-Content-Viewers`, check if it's
// another type which we may dynamically support, such as `text/*` types or
// video document types. These types are all backed by the nsContentDLF.
if (IsPlainTextType(aType) ||
DecoderTraits::IsSupportedInVideoDocument(aType)) {
docFactory = do_GetService(CONTENT_DLF_CONTRACTID);
if (docFactory && aLoaderType) {
*aLoaderType = TYPE_CONTENT;
}
@ -7930,32 +7971,40 @@ void nsContentUtils::DestroyMatchString(void* aData) {
}
}
bool nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) {
// Table ordered from most to least likely JS MIME types.
static const char* jsTypes[] = {"text/javascript",
"text/ecmascript",
"application/javascript",
"application/ecmascript",
"application/x-javascript",
"application/x-ecmascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript",
nullptr};
// Table ordered from most to least likely JS MIME types.
static constexpr std::string_view kJavascriptMIMETypes[] = {
"text/javascript",
"text/ecmascript",
"application/javascript",
"application/ecmascript",
"application/x-javascript",
"application/x-ecmascript",
"text/javascript1.0",
"text/javascript1.1",
"text/javascript1.2",
"text/javascript1.3",
"text/javascript1.4",
"text/javascript1.5",
"text/jscript",
"text/livescript",
"text/x-ecmascript",
"text/x-javascript"};
for (uint32_t i = 0; jsTypes[i]; ++i) {
if (aMIMEType.LowerCaseEqualsASCII(jsTypes[i])) {
bool nsContentUtils::IsJavascriptMIMEType(const nsAString& aMIMEType) {
for (std::string_view type : kJavascriptMIMETypes) {
if (aMIMEType.LowerCaseEqualsASCII(type.data(), type.length())) {
return true;
}
}
return false;
}
bool nsContentUtils::IsJavascriptMIMEType(const nsACString& aMIMEType) {
for (std::string_view type : kJavascriptMIMETypes) {
if (aMIMEType.LowerCaseEqualsASCII(type.data(), type.length())) {
return true;
}
}
return false;
}

View File

@ -2769,6 +2769,7 @@ class nsContentUtils {
static bool IsJavaScriptLanguage(const nsString& aName);
static bool IsJavascriptMIMEType(const nsAString& aMIMEType);
static bool IsJavascriptMIMEType(const nsACString& aMIMEType);
static void SplitMimeType(const nsAString& aValue, nsString& aType,
nsString& aParams);

View File

@ -131,20 +131,13 @@ gfxSVGGlyphsDocument* gfxSVGGlyphs::FindOrCreateGlyphsDocument(
}
nsresult gfxSVGGlyphsDocument::SetupPresentation() {
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
nsCString contractId;
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers",
"image/svg+xml", contractId);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
do_GetService(contractId.get());
nsContentUtils::FindInternalDocumentViewer("image/svg+xml"_ns);
NS_ASSERTION(docLoaderFactory, "Couldn't get DocumentLoaderFactory");
nsCOMPtr<nsIDocumentViewer> viewer;
rv = docLoaderFactory->CreateInstanceForDocument(nullptr, mDocument, nullptr,
getter_AddRefs(viewer));
nsresult rv = docLoaderFactory->CreateInstanceForDocument(
nullptr, mDocument, nullptr, getter_AddRefs(viewer));
NS_ENSURE_SUCCESS(rv, rv);
auto upem = mOwner->FontEntry()->UnitsPerEm();

View File

@ -283,20 +283,14 @@ nsresult SVGDocumentWrapper::SetupViewer(nsIRequest* aRequest,
NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
newLoadGroup->SetLoadGroup(loadGroup);
nsCOMPtr<nsICategoryManager> catMan =
do_GetService(NS_CATEGORYMANAGER_CONTRACTID);
NS_ENSURE_TRUE(catMan, NS_ERROR_NOT_AVAILABLE);
nsCString contractId;
nsresult rv = catMan->GetCategoryEntry("Gecko-Content-Viewers", IMAGE_SVG_XML,
contractId);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory =
do_GetService(contractId.get());
nsContentUtils::FindInternalDocumentViewer(
nsLiteralCString(IMAGE_SVG_XML));
NS_ENSURE_TRUE(docLoaderFactory, NS_ERROR_NOT_AVAILABLE);
nsCOMPtr<nsIDocumentViewer> viewer;
nsCOMPtr<nsIStreamListener> listener;
rv = docLoaderFactory->CreateInstance(
nsresult rv = docLoaderFactory->CreateInstance(
"external-resource", chan, newLoadGroup, nsLiteralCString(IMAGE_SVG_XML),
nullptr, nullptr, getter_AddRefs(listener), getter_AddRefs(viewer));
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -14,24 +14,14 @@ UnloadFunc = 'nsLayoutModuleDtor'
Priority = 100
content_types = [
'application/ecmascript',
'application/javascript',
'application/json',
'application/mathml+xml',
'application/rdf+xml',
'application/vnd.wap.xhtml+xml',
'application/x-javascript',
'application/x-view-source',
'application/xhtml+xml',
'application/xml',
'image/svg+xml',
'text/cache-manifest',
'text/css',
'text/ecmascript',
'text/event-stream',
'text/html',
'text/javascript',
'text/json',
'text/plain',
'text/rdf',
'text/vtt',

View File

@ -61,4 +61,7 @@ add_task(async function test_display_plaintext_type() {
await expectOutcome("application/json", "jsonviewer");
// NOTE: text/json does not load JSON viewer?
await expectOutcome("text/json", "text");
// Unknown text/ types should be loadable as plain text documents.
await expectOutcome("text/unknown-type", "text");
});

View File

@ -166,11 +166,12 @@ HttpIndexViewer.prototype = {
aChannel.contentType = contentType;
let contract = Services.catMan.getCategoryEntry(
"Gecko-Content-Viewers",
contentType
);
let factory = Cc[contract].getService(Ci.nsIDocumentLoaderFactory);
// NOTE: This assumes that both text/html and text/plain will continue to be
// handled by nsContentDLF. If this ever changes this logic will need to be
// updated.
let factory = Cc[
"@mozilla.org/content/document-loader-factory;1"
].getService(Ci.nsIDocumentLoaderFactory);
let listener = {};
let res = factory.createInstance(