tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

DownloadsViewableInternally.sys.mjs (10721B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 /*
      6 * TODO: This is based on what PdfJs was already doing, it would be
      7 * best to use this over there as well to reduce duplication and
      8 * inconsistency.
      9 */
     10 
     11 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
     12 
     13 const lazy = {};
     14 
     15 XPCOMUtils.defineLazyServiceGetter(
     16  lazy,
     17  "HandlerService",
     18  "@mozilla.org/uriloader/handler-service;1",
     19  Ci.nsIHandlerService
     20 );
     21 XPCOMUtils.defineLazyServiceGetter(
     22  lazy,
     23  "MIMEService",
     24  "@mozilla.org/mime;1",
     25  Ci.nsIMIMEService
     26 );
     27 
     28 ChromeUtils.defineESModuleGetters(lazy, {
     29  Integration: "resource://gre/modules/Integration.sys.mjs",
     30 });
     31 
     32 const PREF_BRANCH = "browser.download.viewableInternally.";
     33 export const PREF_ENABLED_TYPES = PREF_BRANCH + "enabledTypes";
     34 export const PREF_BRANCH_WAS_REGISTERED = PREF_BRANCH + "typeWasRegistered.";
     35 
     36 export const PREF_BRANCH_PREVIOUS_ACTION =
     37  PREF_BRANCH + "previousHandler.preferredAction.";
     38 
     39 export const PREF_BRANCH_PREVIOUS_ASK =
     40  PREF_BRANCH + "previousHandler.alwaysAskBeforeHandling.";
     41 
     42 export let DownloadsViewableInternally = {
     43  /**
     44   * Initially add/remove handlers, watch pref, register with Integration.downloads.
     45   */
     46  register() {
     47    // Watch the pref
     48    XPCOMUtils.defineLazyPreferenceGetter(
     49      this,
     50      "_enabledTypes",
     51      PREF_ENABLED_TYPES,
     52      "",
     53      () => this._updateAllHandlers(),
     54      pref => {
     55        let itemStr = pref.trim();
     56        return itemStr ? itemStr.split(",").map(s => s.trim()) : [];
     57      }
     58    );
     59 
     60    for (let handlerType of this._downloadTypesViewableInternally) {
     61      if (handlerType.initAvailable) {
     62        handlerType.initAvailable();
     63      }
     64    }
     65 
     66    // Initially update handlers
     67    this._updateAllHandlers();
     68 
     69    // Register the check for use in DownloadIntegration
     70    lazy.Integration.downloads.register(() => ({
     71      shouldViewDownloadInternally:
     72        this._shouldViewDownloadInternally.bind(this),
     73    }));
     74  },
     75 
     76  /**
     77   * MIME types to handle with an internal viewer, for downloaded files.
     78   *
     79   * |extension| is an extenson that will be viewable, as an alternative for
     80   *   the MIME type itself. It is also used more generally to identify this
     81   *   type: It is part of a pref name to indicate the handler was set up once,
     82   *   and it is the string present in |PREF_ENABLED_TYPES| to enable the type.
     83   *
     84   * |mimeTypes| are the types that will be viewable. A handler is set up for
     85   *   the first element in the array.
     86   *
     87   * If |managedElsewhere| is falsy, |_updateAllHandlers()| will set
     88   *   up or remove handlers for the type, and |_shouldViewDownloadInternally()|
     89   *   will check for it in |PREF_ENABLED_TYPES|.
     90   *
     91   * |available| is used to check whether this type should have
     92   *   handleInternally handlers set up, and if false then
     93   *   |_shouldViewDownloadInternally()| will also return false for this
     94   *   type. If |available| would change, |DownloadsViewableInternally._updateHandler()|
     95   *   should be called for the type.
     96   *
     97   * |initAvailable()| is an opportunity to initially set |available|, set up
     98   *   observers to change it when prefs change, etc.
     99   *
    100   */
    101  _downloadTypesViewableInternally: [
    102    {
    103      extension: "xml",
    104      mimeTypes: ["text/xml", "application/xml"],
    105      available: true,
    106      managedElsewhere: true,
    107    },
    108    {
    109      extension: "svg",
    110      mimeTypes: ["image/svg+xml"],
    111 
    112      initAvailable() {
    113        XPCOMUtils.defineLazyPreferenceGetter(
    114          this,
    115          "available",
    116          "svg.disabled",
    117          true,
    118          () => DownloadsViewableInternally._updateHandler(this),
    119          // transform disabled to enabled/available
    120          disabledPref => !disabledPref
    121        );
    122      },
    123      // available getter is set by initAvailable()
    124      managedElsewhere: true,
    125    },
    126    {
    127      extension: "webp",
    128      mimeTypes: ["image/webp"],
    129      available: true,
    130      managedElsewhere: false,
    131    },
    132    {
    133      extension: "avif",
    134      mimeTypes: ["image/avif"],
    135      available: true,
    136      managedElsewhere: false,
    137    },
    138    {
    139      extension: "jxl",
    140      mimeTypes: ["image/jxl"],
    141      initAvailable() {
    142        XPCOMUtils.defineLazyPreferenceGetter(
    143          this,
    144          "available",
    145          "image.jxl.enabled",
    146          false,
    147          () => DownloadsViewableInternally._updateHandler(this)
    148        );
    149      },
    150      // available getter is set by initAvailable()
    151    },
    152    {
    153      extension: "pdf",
    154      mimeTypes: ["application/pdf"],
    155      // PDF uses pdfjs.disabled rather than PREF_ENABLED_TYPES.
    156      // pdfjs.disabled isn't checked here because PdfJs's own _becomeHandler
    157      // and _unbecomeHandler manage the handler if the pref is set, and there
    158      // is an explicit check in nsUnknownContentTypeDialog.shouldShowInternalHandlerOption
    159      available: true,
    160      managedElsewhere: true,
    161    },
    162  ],
    163 
    164  /*
    165   * Implementation for DownloadIntegration.shouldViewDownloadInternally
    166   */
    167  _shouldViewDownloadInternally(aMimeType, aExtension) {
    168    if (!aMimeType) {
    169      return false;
    170    }
    171 
    172    return this._downloadTypesViewableInternally.some(handlerType => {
    173      if (
    174        !handlerType.managedElsewhere &&
    175        !this._enabledTypes.includes(handlerType.extension)
    176      ) {
    177        return false;
    178      }
    179 
    180      return (
    181        (handlerType.mimeTypes.includes(aMimeType) ||
    182          handlerType.extension == aExtension?.toLowerCase()) &&
    183        handlerType.available
    184      );
    185    });
    186  },
    187 
    188  _makeFakeHandler(aMimeType, aExtension) {
    189    // Based on PdfJs gPdfFakeHandlerInfo.
    190    return {
    191      QueryInterface: ChromeUtils.generateQI(["nsIMIMEInfo"]),
    192      getFileExtensions() {
    193        return [aExtension];
    194      },
    195      possibleApplicationHandlers: Cc["@mozilla.org/array;1"].createInstance(
    196        Ci.nsIMutableArray
    197      ),
    198      extensionExists(ext) {
    199        return ext == aExtension;
    200      },
    201      alwaysAskBeforeHandling: false,
    202      preferredAction: Ci.nsIHandlerInfo.handleInternally,
    203      type: aMimeType,
    204    };
    205  },
    206 
    207  _saveSettings(handlerInfo, handlerType) {
    208    Services.prefs.setIntPref(
    209      PREF_BRANCH_PREVIOUS_ACTION + handlerType.extension,
    210      handlerInfo.preferredAction
    211    );
    212    Services.prefs.setBoolPref(
    213      PREF_BRANCH_PREVIOUS_ASK + handlerType.extension,
    214      handlerInfo.alwaysAskBeforeHandling
    215    );
    216  },
    217 
    218  _restoreSettings(handlerInfo, handlerType) {
    219    const prevActionPref = PREF_BRANCH_PREVIOUS_ACTION + handlerType.extension;
    220    if (Services.prefs.prefHasUserValue(prevActionPref)) {
    221      handlerInfo.alwaysAskBeforeHandling = Services.prefs.getBoolPref(
    222        PREF_BRANCH_PREVIOUS_ASK + handlerType.extension
    223      );
    224      handlerInfo.preferredAction = Services.prefs.getIntPref(prevActionPref);
    225      lazy.HandlerService.store(handlerInfo);
    226    } else {
    227      // Nothing to restore, just remove the handler.
    228      lazy.HandlerService.remove(handlerInfo);
    229    }
    230  },
    231 
    232  _clearSavedSettings(extension) {
    233    Services.prefs.clearUserPref(PREF_BRANCH_PREVIOUS_ACTION + extension);
    234    Services.prefs.clearUserPref(PREF_BRANCH_PREVIOUS_ASK + extension);
    235  },
    236 
    237  _updateAllHandlers() {
    238    // Set up or remove handlers for each type, if not done already
    239    for (const handlerType of this._downloadTypesViewableInternally) {
    240      if (!handlerType.managedElsewhere) {
    241        this._updateHandler(handlerType);
    242      }
    243    }
    244  },
    245 
    246  _updateHandler(handlerType) {
    247    const wasRegistered = Services.prefs.getBoolPref(
    248      PREF_BRANCH_WAS_REGISTERED + handlerType.extension,
    249      false
    250    );
    251 
    252    const toBeRegistered =
    253      this._enabledTypes.includes(handlerType.extension) &&
    254      handlerType.available;
    255 
    256    if (toBeRegistered && !wasRegistered) {
    257      this._becomeHandler(handlerType);
    258    } else if (!toBeRegistered && wasRegistered) {
    259      this._unbecomeHandler(handlerType);
    260    }
    261  },
    262 
    263  _becomeHandler(handlerType) {
    264    // Set up an empty handler with only a preferred action, to avoid
    265    // having to ask the OS about handlers on startup.
    266    let fakeHandlerInfo = this._makeFakeHandler(
    267      handlerType.mimeTypes[0],
    268      handlerType.extension
    269    );
    270    if (!lazy.HandlerService.exists(fakeHandlerInfo)) {
    271      lazy.HandlerService.store(fakeHandlerInfo);
    272    } else {
    273      const handlerInfo = lazy.MIMEService.getFromTypeAndExtension(
    274        handlerType.mimeTypes[0],
    275        handlerType.extension
    276      );
    277 
    278      if (handlerInfo.preferredAction != Ci.nsIHandlerInfo.handleInternally) {
    279        // Save the previous settings of preferredAction and
    280        // alwaysAskBeforeHandling in case we need to revert them.
    281        // Even if we don't force preferredAction here, the user could
    282        // set handleInternally manually.
    283        this._saveSettings(handlerInfo, handlerType);
    284      } else {
    285        // handleInternally shouldn't already have been set, the best we
    286        // can do to restore is to remove the handler, so make sure
    287        // the settings are clear.
    288        this._clearSavedSettings(handlerType.extension);
    289      }
    290 
    291      // Replace the preferred action if it didn't indicate an external viewer.
    292      // Note: This is a point of departure from PdfJs, which always replaces
    293      // the preferred action.
    294      if (
    295        handlerInfo.preferredAction != Ci.nsIHandlerInfo.useHelperApp &&
    296        handlerInfo.preferredAction != Ci.nsIHandlerInfo.useSystemDefault
    297      ) {
    298        handlerInfo.preferredAction = Ci.nsIHandlerInfo.handleInternally;
    299        handlerInfo.alwaysAskBeforeHandling = false;
    300 
    301        lazy.HandlerService.store(handlerInfo);
    302      }
    303    }
    304 
    305    // Note that we set up for this type so a) we don't keep replacing the
    306    // handler and b) so it can be cleared later.
    307    Services.prefs.setBoolPref(
    308      PREF_BRANCH_WAS_REGISTERED + handlerType.extension,
    309      true
    310    );
    311  },
    312 
    313  _unbecomeHandler(handlerType) {
    314    let handlerInfo;
    315    try {
    316      handlerInfo = lazy.MIMEService.getFromTypeAndExtension(
    317        handlerType.mimeTypes[0],
    318        handlerType.extension
    319      );
    320    } catch (ex) {
    321      // Allow the handler lookup to fail.
    322    }
    323    // Restore preferred action if it is still handleInternally
    324    // (possibly just removing the handler if nothing was saved for it).
    325    if (handlerInfo?.preferredAction == Ci.nsIHandlerInfo.handleInternally) {
    326      this._restoreSettings(handlerInfo, handlerType);
    327    }
    328 
    329    // In any case we do not control this handler now.
    330    this._clearSavedSettings(handlerType.extension);
    331    Services.prefs.clearUserPref(
    332      PREF_BRANCH_WAS_REGISTERED + handlerType.extension
    333    );
    334  },
    335 };