343 lines
11 KiB
JavaScript
343 lines
11 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/*
|
|
* TODO: This is based on what PdfJs was already doing, it would be
|
|
* best to use this over there as well to reduce duplication and
|
|
* inconsistency.
|
|
*/
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"HandlerService",
|
|
"@mozilla.org/uriloader/handler-service;1",
|
|
"nsIHandlerService"
|
|
);
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
lazy,
|
|
"MIMEService",
|
|
"@mozilla.org/mime;1",
|
|
"nsIMIMEService"
|
|
);
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
Integration: "resource://gre/modules/Integration.sys.mjs",
|
|
});
|
|
|
|
const PREF_BRANCH = "browser.download.viewableInternally.";
|
|
export const PREF_ENABLED_TYPES = PREF_BRANCH + "enabledTypes";
|
|
export const PREF_BRANCH_WAS_REGISTERED = PREF_BRANCH + "typeWasRegistered.";
|
|
|
|
export const PREF_BRANCH_PREVIOUS_ACTION =
|
|
PREF_BRANCH + "previousHandler.preferredAction.";
|
|
|
|
export const PREF_BRANCH_PREVIOUS_ASK =
|
|
PREF_BRANCH + "previousHandler.alwaysAskBeforeHandling.";
|
|
|
|
export let DownloadsViewableInternally = {
|
|
/**
|
|
* Initially add/remove handlers, watch pref, register with Integration.downloads.
|
|
*/
|
|
register() {
|
|
// Watch the pref
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"_enabledTypes",
|
|
PREF_ENABLED_TYPES,
|
|
"",
|
|
() => this._updateAllHandlers(),
|
|
pref => {
|
|
let itemStr = pref.trim();
|
|
return itemStr ? itemStr.split(",").map(s => s.trim()) : [];
|
|
}
|
|
);
|
|
|
|
for (let handlerType of this._downloadTypesViewableInternally) {
|
|
if (handlerType.initAvailable) {
|
|
handlerType.initAvailable();
|
|
}
|
|
}
|
|
|
|
// Initially update handlers
|
|
this._updateAllHandlers();
|
|
|
|
// Register the check for use in DownloadIntegration
|
|
lazy.Integration.downloads.register(() => ({
|
|
shouldViewDownloadInternally:
|
|
this._shouldViewDownloadInternally.bind(this),
|
|
}));
|
|
},
|
|
|
|
/**
|
|
* MIME types to handle with an internal viewer, for downloaded files.
|
|
*
|
|
* |extension| is an extenson that will be viewable, as an alternative for
|
|
* the MIME type itself. It is also used more generally to identify this
|
|
* type: It is part of a pref name to indicate the handler was set up once,
|
|
* and it is the string present in |PREF_ENABLED_TYPES| to enable the type.
|
|
*
|
|
* |mimeTypes| are the types that will be viewable. A handler is set up for
|
|
* the first element in the array.
|
|
*
|
|
* If |managedElsewhere| is falsy, |_updateAllHandlers()| will set
|
|
* up or remove handlers for the type, and |_shouldViewDownloadInternally()|
|
|
* will check for it in |PREF_ENABLED_TYPES|.
|
|
*
|
|
* |available| is used to check whether this type should have
|
|
* handleInternally handlers set up, and if false then
|
|
* |_shouldViewDownloadInternally()| will also return false for this
|
|
* type. If |available| would change, |DownloadsViewableInternally._updateHandler()|
|
|
* should be called for the type.
|
|
*
|
|
* |initAvailable()| is an opportunity to initially set |available|, set up
|
|
* observers to change it when prefs change, etc.
|
|
*
|
|
*/
|
|
_downloadTypesViewableInternally: [
|
|
{
|
|
extension: "xml",
|
|
mimeTypes: ["text/xml", "application/xml"],
|
|
available: true,
|
|
managedElsewhere: true,
|
|
},
|
|
{
|
|
extension: "svg",
|
|
mimeTypes: ["image/svg+xml"],
|
|
|
|
initAvailable() {
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"available",
|
|
"svg.disabled",
|
|
true,
|
|
() => DownloadsViewableInternally._updateHandler(this),
|
|
// transform disabled to enabled/available
|
|
disabledPref => !disabledPref
|
|
);
|
|
},
|
|
// available getter is set by initAvailable()
|
|
managedElsewhere: true,
|
|
},
|
|
{
|
|
extension: "webp",
|
|
mimeTypes: ["image/webp"],
|
|
available: true,
|
|
managedElsewhere: false,
|
|
},
|
|
{
|
|
extension: "avif",
|
|
mimeTypes: ["image/avif"],
|
|
initAvailable() {
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"available",
|
|
"image.avif.enabled",
|
|
false,
|
|
() => DownloadsViewableInternally._updateHandler(this)
|
|
);
|
|
},
|
|
// available getter is set by initAvailable()
|
|
},
|
|
{
|
|
extension: "jxl",
|
|
mimeTypes: ["image/jxl"],
|
|
initAvailable() {
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"available",
|
|
"image.jxl.enabled",
|
|
false,
|
|
() => DownloadsViewableInternally._updateHandler(this)
|
|
);
|
|
},
|
|
// available getter is set by initAvailable()
|
|
},
|
|
{
|
|
extension: "pdf",
|
|
mimeTypes: ["application/pdf"],
|
|
// PDF uses pdfjs.disabled rather than PREF_ENABLED_TYPES.
|
|
// pdfjs.disabled isn't checked here because PdfJs's own _becomeHandler
|
|
// and _unbecomeHandler manage the handler if the pref is set, and there
|
|
// is an explicit check in nsUnknownContentTypeDialog.shouldShowInternalHandlerOption
|
|
available: true,
|
|
managedElsewhere: true,
|
|
},
|
|
],
|
|
|
|
/*
|
|
* Implementation for DownloadIntegration.shouldViewDownloadInternally
|
|
*/
|
|
_shouldViewDownloadInternally(aMimeType, aExtension) {
|
|
if (!aMimeType) {
|
|
return false;
|
|
}
|
|
|
|
return this._downloadTypesViewableInternally.some(handlerType => {
|
|
if (
|
|
!handlerType.managedElsewhere &&
|
|
!this._enabledTypes.includes(handlerType.extension)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
(handlerType.mimeTypes.includes(aMimeType) ||
|
|
handlerType.extension == aExtension?.toLowerCase()) &&
|
|
handlerType.available
|
|
);
|
|
});
|
|
},
|
|
|
|
_makeFakeHandler(aMimeType, aExtension) {
|
|
// Based on PdfJs gPdfFakeHandlerInfo.
|
|
return {
|
|
QueryInterface: ChromeUtils.generateQI(["nsIMIMEInfo"]),
|
|
getFileExtensions() {
|
|
return [aExtension];
|
|
},
|
|
possibleApplicationHandlers: Cc["@mozilla.org/array;1"].createInstance(
|
|
Ci.nsIMutableArray
|
|
),
|
|
extensionExists(ext) {
|
|
return ext == aExtension;
|
|
},
|
|
alwaysAskBeforeHandling: false,
|
|
preferredAction: Ci.nsIHandlerInfo.handleInternally,
|
|
type: aMimeType,
|
|
};
|
|
},
|
|
|
|
_saveSettings(handlerInfo, handlerType) {
|
|
Services.prefs.setIntPref(
|
|
PREF_BRANCH_PREVIOUS_ACTION + handlerType.extension,
|
|
handlerInfo.preferredAction
|
|
);
|
|
Services.prefs.setBoolPref(
|
|
PREF_BRANCH_PREVIOUS_ASK + handlerType.extension,
|
|
handlerInfo.alwaysAskBeforeHandling
|
|
);
|
|
},
|
|
|
|
_restoreSettings(handlerInfo, handlerType) {
|
|
const prevActionPref = PREF_BRANCH_PREVIOUS_ACTION + handlerType.extension;
|
|
if (Services.prefs.prefHasUserValue(prevActionPref)) {
|
|
handlerInfo.alwaysAskBeforeHandling = Services.prefs.getBoolPref(
|
|
PREF_BRANCH_PREVIOUS_ASK + handlerType.extension
|
|
);
|
|
handlerInfo.preferredAction = Services.prefs.getIntPref(prevActionPref);
|
|
lazy.HandlerService.store(handlerInfo);
|
|
} else {
|
|
// Nothing to restore, just remove the handler.
|
|
lazy.HandlerService.remove(handlerInfo);
|
|
}
|
|
},
|
|
|
|
_clearSavedSettings(extension) {
|
|
Services.prefs.clearUserPref(PREF_BRANCH_PREVIOUS_ACTION + extension);
|
|
Services.prefs.clearUserPref(PREF_BRANCH_PREVIOUS_ASK + extension);
|
|
},
|
|
|
|
_updateAllHandlers() {
|
|
// Set up or remove handlers for each type, if not done already
|
|
for (const handlerType of this._downloadTypesViewableInternally) {
|
|
if (!handlerType.managedElsewhere) {
|
|
this._updateHandler(handlerType);
|
|
}
|
|
}
|
|
},
|
|
|
|
_updateHandler(handlerType) {
|
|
const wasRegistered = Services.prefs.getBoolPref(
|
|
PREF_BRANCH_WAS_REGISTERED + handlerType.extension,
|
|
false
|
|
);
|
|
|
|
const toBeRegistered =
|
|
this._enabledTypes.includes(handlerType.extension) &&
|
|
handlerType.available;
|
|
|
|
if (toBeRegistered && !wasRegistered) {
|
|
this._becomeHandler(handlerType);
|
|
} else if (!toBeRegistered && wasRegistered) {
|
|
this._unbecomeHandler(handlerType);
|
|
}
|
|
},
|
|
|
|
_becomeHandler(handlerType) {
|
|
// Set up an empty handler with only a preferred action, to avoid
|
|
// having to ask the OS about handlers on startup.
|
|
let fakeHandlerInfo = this._makeFakeHandler(
|
|
handlerType.mimeTypes[0],
|
|
handlerType.extension
|
|
);
|
|
if (!lazy.HandlerService.exists(fakeHandlerInfo)) {
|
|
lazy.HandlerService.store(fakeHandlerInfo);
|
|
} else {
|
|
const handlerInfo = lazy.MIMEService.getFromTypeAndExtension(
|
|
handlerType.mimeTypes[0],
|
|
handlerType.extension
|
|
);
|
|
|
|
if (handlerInfo.preferredAction != Ci.nsIHandlerInfo.handleInternally) {
|
|
// Save the previous settings of preferredAction and
|
|
// alwaysAskBeforeHandling in case we need to revert them.
|
|
// Even if we don't force preferredAction here, the user could
|
|
// set handleInternally manually.
|
|
this._saveSettings(handlerInfo, handlerType);
|
|
} else {
|
|
// handleInternally shouldn't already have been set, the best we
|
|
// can do to restore is to remove the handler, so make sure
|
|
// the settings are clear.
|
|
this._clearSavedSettings(handlerType.extension);
|
|
}
|
|
|
|
// Replace the preferred action if it didn't indicate an external viewer.
|
|
// Note: This is a point of departure from PdfJs, which always replaces
|
|
// the preferred action.
|
|
if (
|
|
handlerInfo.preferredAction != Ci.nsIHandlerInfo.useHelperApp &&
|
|
handlerInfo.preferredAction != Ci.nsIHandlerInfo.useSystemDefault
|
|
) {
|
|
handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
|
|
handlerInfo.alwaysAskBeforeHandling = false;
|
|
|
|
lazy.HandlerService.store(handlerInfo);
|
|
}
|
|
}
|
|
|
|
// Note that we set up for this type so a) we don't keep replacing the
|
|
// handler and b) so it can be cleared later.
|
|
Services.prefs.setBoolPref(
|
|
PREF_BRANCH_WAS_REGISTERED + handlerType.extension,
|
|
true
|
|
);
|
|
},
|
|
|
|
_unbecomeHandler(handlerType) {
|
|
let handlerInfo;
|
|
try {
|
|
handlerInfo = lazy.MIMEService.getFromTypeAndExtension(
|
|
handlerType.mimeTypes[0],
|
|
handlerType.extension
|
|
);
|
|
} catch (ex) {
|
|
// Allow the handler lookup to fail.
|
|
}
|
|
// Restore preferred action if it is still handleInternally
|
|
// (possibly just removing the handler if nothing was saved for it).
|
|
if (handlerInfo?.preferredAction == Ci.nsIHandlerInfo.handleInternally) {
|
|
this._restoreSettings(handlerInfo, handlerType);
|
|
}
|
|
|
|
// In any case we do not control this handler now.
|
|
this._clearSavedSettings(handlerType.extension);
|
|
Services.prefs.clearUserPref(
|
|
PREF_BRANCH_WAS_REGISTERED + handlerType.extension
|
|
);
|
|
},
|
|
};
|