Bug 1460392 - Port bug 1457027 to TB: Part 4 - Use class syntax for the HandlerInfoWrapper hierarchy. r=aceman
authorRichard Marti <richard.marti@gmail.com>
Sat, 22 Sep 2018 09:48:48 +0200
changeset 33263 be4843f65ece5191f3c54d25adf03352cec1c729
parent 33262 e490dff436ddb355e7c6532e01adee7725d5aaf8
child 33264 f686a4e62dc8801634ed319ae2c829239c5822d8
push id387
push userclokep@gmail.com
push dateMon, 10 Dec 2018 21:30:47 +0000
reviewersaceman
bugs1460392, 1457027
Bug 1460392 - Port bug 1457027 to TB: Part 4 - Use class syntax for the HandlerInfoWrapper hierarchy. r=aceman
mail/components/preferences/applications.js
--- a/mail/components/preferences/applications.js
+++ b/mail/components/preferences/applications.js
@@ -72,138 +72,121 @@ function getLocalHandlerApp(aFile) {
   var localHandlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
                           .createInstance(Ci.nsILocalHandlerApp);
   localHandlerApp.name = getDisplayNameForFile(aFile);
   localHandlerApp.executable = aFile;
 
   return localHandlerApp;
 }
 
-/**
- * An enumeration of items in a JS array.
- *
- * FIXME: use ArrayConverter once it lands (bug 380839).
- *
- * @constructor
- */
-function ArrayEnumerator(aItems) {
-  this._index = 0;
-  this._contents = aItems;
-}
-
-ArrayEnumerator.prototype = {
-  _index: 0,
-
-  hasMoreElements() {
-    return this._index < this._contents.length;
-  },
-
-  getNext() {
-    return this._contents[this._index++];
-  },
-};
-
 // ------------------
 // HandlerInfoWrapper
 
 /**
  * This object wraps nsIHandlerInfo with some additional functionality
  * the Applications prefpane needs to display and allow modification of
  * the list of handled types.
  *
  * We create an instance of this wrapper for each entry we might display
  * in the prefpane, and we compose the instances from various sources,
  * including navigator.plugins and the handler service.
  *
  * We don't implement all the original nsIHandlerInfo functionality,
  * just the stuff that the prefpane needs.
- *
- * In theory, all of the custom functionality in this wrapper should get
- * pushed down into nsIHandlerInfo eventually.
  */
-function HandlerInfoWrapper(aType, aHandlerInfo) {
-  this._type = aType;
-  this.wrappedHandlerInfo = aHandlerInfo;
-}
+class HandlerInfoWrapper {
+  constructor(type, handlerInfo) {
+    this.type = type;
+    this.wrappedHandlerInfo = handlerInfo;
+
+    // A plugin that can handle this type, if any.
+    //
+    // Note: just because we have one doesn't mean it *will* handle the type.
+    // That depends on whether or not the type is in the list of types for which
+    // plugin handling is disabled.
+    this.pluginName = "";
 
-HandlerInfoWrapper.prototype = {
-  // The wrapped nsIHandlerInfo object.  In general, this object is private,
-  // but there are a couple cases where callers access it directly for things
-  // we haven't (yet?) implemented, so we make it a public property.
-  wrappedHandlerInfo: null,
-
-  // --------------
-  // nsIHandlerInfo
-
-  // The MIME type or protocol scheme.
-  _type: null,
-  get type() {
-    return this._type;
-  },
+    // Whether or not this type is only handled by a plugin or is also handled
+    // by some user-configured action as specified in the handler info object.
+    //
+    // Note: we can't just check if there's a handler info object for this type,
+    // because OS and user configuration is mixed up in the handler info object,
+    // so we always need to retrieve it for the OS info and can't tell whether
+    // it represents only OS-default information or user-configured information.
+    //
+    // FIXME: once handler info records are broken up into OS-provided records
+    // and user-configured records, stop using this boolean flag and simply
+    // check for the presence of a user-configured record to determine whether
+    // or not this type is only handled by a plugin.  Filed as bug 395142.
+    this.handledOnlyByPlugin = false;
+  }
 
   get description() {
     if (this.wrappedHandlerInfo.description)
       return this.wrappedHandlerInfo.description;
 
     if (this.primaryExtension) {
       var extension = this.primaryExtension.toUpperCase();
       return document.getElementById("bundlePreferences")
                      .getFormattedString("fileEnding", [extension]);
     }
     return this.type;
-  },
+  }
 
   get preferredApplicationHandler() {
     return this.wrappedHandlerInfo.preferredApplicationHandler;
-  },
+  }
 
   set preferredApplicationHandler(aNewValue) {
     this.wrappedHandlerInfo.preferredApplicationHandler = aNewValue;
 
     // Make sure the preferred handler is in the set of possible handlers.
     if (aNewValue)
       this.addPossibleApplicationHandler(aNewValue);
-  },
+  }
 
   get possibleApplicationHandlers() {
     return this.wrappedHandlerInfo.possibleApplicationHandlers;
-  },
+  }
 
   addPossibleApplicationHandler(aNewHandler) {
-    try {
-      /* eslint-disable mozilla/use-includes-instead-of-indexOf */
-      if (this.possibleApplicationHandlers.indexOf(0, aNewHandler) != -1)
+    var possibleApps = this.possibleApplicationHandlers.enumerate();
+    while (possibleApps.hasMoreElements()) {
+      if (possibleApps.getNext().equals(aNewHandler))
         return;
-      /* eslint-enable mozilla/use-includes-instead-of-indexOf */
-    } catch (e) { }
+    }
     this.possibleApplicationHandlers.appendElement(aNewHandler);
-  },
+  }
 
   removePossibleApplicationHandler(aHandler) {
     var defaultApp = this.preferredApplicationHandler;
     if (defaultApp && aHandler.equals(defaultApp)) {
       // If the app we remove was the default app, we must make sure
       // it won't be used anymore
       this.alwaysAskBeforeHandling = true;
       this.preferredApplicationHandler = null;
     }
 
-    try {
-      var handlerIdx = this.possibleApplicationHandlers.indexOf(0, aHandler);
-      this.possibleApplicationHandlers.removeElementAt(handlerIdx);
-    } catch (e) { }
-  },
+    var handlers = this.possibleApplicationHandlers;
+    for (var i = 0; i < handlers.length; ++i) {
+      var handler = handlers.queryElementAt(i, Ci.nsIHandlerApp);
+      if (handler.equals(aHandler)) {
+        handlers.removeElementAt(i);
+        break;
+      }
+    }
+  }
 
   get hasDefaultHandler() {
     return this.wrappedHandlerInfo.hasDefaultHandler;
-  },
+  }
 
   get defaultDescription() {
     return this.wrappedHandlerInfo.defaultDescription;
-  },
+  }
 
   // What to do with content of this type.
   get preferredAction() {
     // If we have an enabled plugin, then the action is to use that plugin.
     if (this.plugin && !this.isDisabledPluginType)
       return kActionUsePlugin;
 
     // If the action is to use a helper app, but we don't have a preferred
@@ -216,27 +199,27 @@ HandlerInfoWrapper.prototype = {
           Ci.nsIHandlerInfo.useHelperApp &&
         !gApplicationsPane.isValidHandlerApp(this.preferredApplicationHandler)) {
       if (this.wrappedHandlerInfo.hasDefaultHandler)
         return Ci.nsIHandlerInfo.useSystemDefault;
       return Ci.nsIHandlerInfo.saveToDisk;
     }
 
     return this.wrappedHandlerInfo.preferredAction;
-  },
+  }
 
   set preferredAction(aNewValue) {
     // We don't modify the preferred action if the new action is to use a plugin
     // because handler info objects don't understand our custom "use plugin"
     // value.  Also, leaving it untouched means that we can automatically revert
     // to the old setting if the user ever removes the plugin.
 
     if (aNewValue != kActionUsePlugin)
       this.wrappedHandlerInfo.preferredAction = aNewValue;
-  },
+  }
 
   get alwaysAskBeforeHandling() {
     // If this type is handled only by a plugin, we can't trust the value
     // in the handler info object, since it'll be a default based on the absence
     // of any user configuration, and the default in that case is to always ask,
     // even though we never ask for content handled by a plugin, so special case
     // plugin-handled types by returning false here.
     if (this.plugin && this.handledOnlyByPlugin)
@@ -248,143 +231,119 @@ HandlerInfoWrapper.prototype = {
     // app, but the preferredApplicationHandler is invalid, and there isn't
     // a default handler, so the preferredAction getter returns save to disk
     // instead.
     if (!(this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) &&
         this.preferredAction == Ci.nsIHandlerInfo.saveToDisk)
       return true;
 
     return this.wrappedHandlerInfo.alwaysAskBeforeHandling;
-  },
+  }
 
   set alwaysAskBeforeHandling(aNewValue) {
     this.wrappedHandlerInfo.alwaysAskBeforeHandling = aNewValue;
-  },
-
-  // -----------
-  // nsIMIMEInfo
+  }
 
   // The primary file extension associated with this type, if any.
   //
   // XXX Plugin objects contain an array of MimeType objects with "suffixes"
   // properties; if this object has an associated plugin, shouldn't we check
   // those properties for an extension?
   get primaryExtension() {
     try {
       if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo &&
           this.wrappedHandlerInfo.primaryExtension)
         return this.wrappedHandlerInfo.primaryExtension;
     } catch (ex) {}
 
     return null;
-  },
-
-  // ---------------
-  // Plugin Handling
-
-  // A plugin that can handle this type, if any.
-  //
-  // Note: just because we have one doesn't mean it *will* handle the type.
-  // That depends on whether or not the type is in the list of types for which
-  // plugin handling is disabled.
-  plugin: null,
-
-  // Whether or not this type is only handled by a plugin or is also handled
-  // by some user-configured action as specified in the handler info object.
-  //
-  // Note: we can't just check if there's a handler info object for this type,
-  // because OS and user configuration is mixed up in the handler info object,
-  // so we always need to retrieve it for the OS info and can't tell whether
-  // it represents only OS-default information or user-configured information.
-  //
-  // FIXME: once handler info records are broken up into OS-provided records
-  // and user-configured records, stop using this boolean flag and simply
-  // check for the presence of a user-configured record to determine whether
-  // or not this type is only handled by a plugin.  Filed as bug 395142.
-  handledOnlyByPlugin: undefined,
+  }
 
   get isDisabledPluginType() {
     return this._getDisabledPluginTypes().includes(this.type);
-  },
+  }
 
   _getDisabledPluginTypes() {
     var types = "";
 
     if (Services.prefs.prefHasUserValue(PREF_DISABLED_PLUGIN_TYPES))
       types = Services.prefs.getCharPref(PREF_DISABLED_PLUGIN_TYPES);
 
     // Only split if the string isn't empty so we don't end up with an array
     // containing a single empty string.
-    return types ? types.split(",") : [];
-  },
+    if (types != "")
+      return types.split(",");
+
+    return [];
+  }
 
   disablePluginType() {
     var disabledPluginTypes = this._getDisabledPluginTypes();
 
     if (!disabledPluginTypes.includes(this.type))
       disabledPluginTypes.push(this.type);
 
     Services.prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
                                disabledPluginTypes.join(","));
 
     // Update the category manager so existing browser windows update.
     Services.catMan.deleteCategoryEntry("Gecko-Content-Viewers", this.type, false);
-  },
+  }
 
   enablePluginType() {
     var disabledPluginTypes = this._getDisabledPluginTypes();
 
     var type = this.type;
     disabledPluginTypes = disabledPluginTypes.filter(v => v != type);
 
     Services.prefs.setCharPref(PREF_DISABLED_PLUGIN_TYPES,
                                disabledPluginTypes.join(","));
 
     // Update the category manager so existing browser windows update.
     Services.catMan.addCategoryEntry(
       "Gecko-Content-Viewers", this.type,
       "@mozilla.org/content/plugin/document-loader-factory;1",
       false, true
     );
-  },
+  }
 
   // -------
   // Storage
 
   store() {
     gHandlerService.store(this.wrappedHandlerInfo);
-  },
+  }
 
   remove() {
     gHandlerService.remove(this.wrappedHandlerInfo);
-  },
+  }
 
   // -----
   // Icons
 
   get smallIcon() {
     return this._getIcon(16);
-  },
+  }
 
   get largeIcon() {
     return this._getIcon(32);
-  },
+  }
 
   _getIcon(aSize) {
     if (this.primaryExtension)
       return "moz-icon://goat." + this.primaryExtension + "?size=" + aSize;
 
     if (this.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo)
       return "moz-icon://goat?size=" + aSize + "&contentType=" + this.type;
 
     // FIXME: consider returning some generic icon when we can't get a URL for
     // one (for example in the case of protocol schemes).  Filed as bug 395141.
     return null;
-  },
-};
+  }
+}
 
 var gApplicationsTabController = {
   mInitialized: false,
   // We default to displaying the Outgoing tab, which is the tab at index 1
   // of the attachmentPrefs tabs.
   mDefaultIndex: 1,
 
   init() {
@@ -944,42 +903,38 @@ var gApplicationsPane = {
    * correct, but it's how we've been doing it for years.
    *
    * Perhaps we should instead query navigator.mimeTypes for the set of types
    * supported by the application and then get the plugin from each MIME type's
    * enabledPlugin property.  But if there's a plugin for a type, we need
    * to know about it even if it isn't enabled, since we're going to give
    * the user an option to enable it.
    *
-   * I'll also note that my reading of nsPluginTag::RegisterWithCategoryManager
-   * suggests that enabledPlugin is only determined during registration
-   * and does not get updated when plugin.disable_full_page_plugin_for_types
-   * changes (unless modification of that preference spawns reregistration).
-   * So even if we could use enabledPlugin to get the plugin that would be used,
-   * we'd still need to check the pref ourselves to find out if it's enabled.
+   * Also note that enabledPlugin does not get updated when
+   * plugin.disable_full_page_plugin_for_types changes, so even if we could use
+   * enabledPlugin to get the plugin that would be used, we'd still need to
+   * check the pref ourselves to find out if it's enabled.
    */
   _loadPluginHandlers() {
-    for (let i = 0; i < navigator.plugins.length; ++i) {
-      let plugin = navigator.plugins[i];
-      for (let j = 0; j < plugin.length; ++j) {
-        let type = plugin[j].type;
+    "use strict";
+
+    let mimeTypes = navigator.mimeTypes;
 
-        let handlerInfoWrapper;
-        if (type in this._handledTypes)
-          handlerInfoWrapper = this._handledTypes[type];
-        else {
-          let wrappedHandlerInfo =
-            gMIMEService.getFromTypeAndExtension(type, null);
-          handlerInfoWrapper = new HandlerInfoWrapper(type, wrappedHandlerInfo);
-          handlerInfoWrapper.handledOnlyByPlugin = true;
-          this._handledTypes[type] = handlerInfoWrapper;
-        }
-
-        handlerInfoWrapper.plugin = plugin;
+    for (let mimeType of mimeTypes) {
+      let handlerInfoWrapper;
+      if (mimeType.type in this._handledTypes) {
+        handlerInfoWrapper = this._handledTypes[mimeType.type];
+      } else {
+        let wrappedHandlerInfo =
+          gMIMEService.getFromTypeAndExtension(mimeType.type, null);
+        handlerInfoWrapper = new HandlerInfoWrapper(mimeType.type, wrappedHandlerInfo);
+        handlerInfoWrapper.handledOnlyByPlugin = true;
+        this._handledTypes[mimeType.type] = handlerInfoWrapper;
       }
+      handlerInfoWrapper.pluginName = mimeType.enabledPlugin.name;
     }
   },
 
   /**
    * Load the set of handlers defined by the application datastore.
    */
   _loadApplicationHandlers() {
     var wrappedHandlerInfos = gHandlerService.enumerate();
@@ -1465,16 +1420,24 @@ var gApplicationsPane = {
   focusFilterBox() {
     this._filter.focus();
     this._filter.select();
   },
 
   // -------
   // Changes
 
+  // Whether or not we are currently storing the action selected by the user.
+  // We use this to suppress notification-triggered updates to the list when
+  // we make changes that may spawn such updates, specifically when we change
+  // the action for the feed type, which results in feed preference updates,
+  // which spawn "pref changed" notifications that would otherwise cause us
+  // to rebuild the view unnecessarily.
+  _storingAction: false,
+
   onSelectAction(aActionItem) {
     this._storingAction = true;
 
     let typeItem = this._list.selectedItem;
     let menu = document.getAnonymousElementByAttribute(typeItem, "class",
                                                        "actionsMenu");
     menu.previousSelectedItem = aActionItem;
     try {