forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			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(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
 | 
						|
    );
 | 
						|
  },
 | 
						|
};
 |