summaryrefslogtreecommitdiffstats
path: root/browser/components/downloads/DownloadsViewableInternally.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/downloads/DownloadsViewableInternally.sys.mjs')
-rw-r--r--browser/components/downloads/DownloadsViewableInternally.sys.mjs343
1 files changed, 343 insertions, 0 deletions
diff --git a/browser/components/downloads/DownloadsViewableInternally.sys.mjs b/browser/components/downloads/DownloadsViewableInternally.sys.mjs
new file mode 100644
index 0000000000..f9c77168b1
--- /dev/null
+++ b/browser/components/downloads/DownloadsViewableInternally.sys.mjs
@@ -0,0 +1,343 @@
+/* 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(base => ({
+ 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
+ );
+ },
+};