Bug 1460392 - Port bug 1457027 to TB: Part 5 - Move _describeType to HandlerInfoWrapper. r=aceman
authorRichard Marti <richard.marti@gmail.com>
Sat, 22 Sep 2018 09:53:24 +0200
changeset 33264 f686a4e62dc8801634ed319ae2c829239c5822d8
parent 33263 be4843f65ece5191f3c54d25adf03352cec1c729
child 33265 cc895db45e990bb2d1dada0f87b708a4310045aa
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 5 - Move _describeType to HandlerInfoWrapper. r=aceman
mail/components/preferences/applicationManager.js
mail/components/preferences/applications.js
--- a/mail/components/preferences/applicationManager.js
+++ b/mail/components/preferences/applicationManager.js
@@ -5,21 +5,19 @@
 // applications.js
 /* globals gApplicationsPane */
 
 var gAppManagerDialog = {
   _removed: [],
 
   init: function appManager_init() {
     this.handlerInfo = window.arguments[0];
-
     var bundle = document.getElementById("appManagerBundle");
     gApplicationsPane._prefsBundle = document.getElementById("bundlePreferences");
-
-    var description = gApplicationsPane._describeType(this.handlerInfo);
+    var description = this.handlerInfo.typeDescription;
     var key = (this.handlerInfo.wrappedHandlerInfo instanceof Ci.nsIMIMEInfo) ?
                 "handleFile" : "handleProtocol";
     var contentText = bundle.getFormattedString(key, [description]);
     contentText = bundle.getFormattedString("descriptionApplications", [contentText]);
     document.getElementById("appDescription").textContent = contentText;
 
     var list = document.getElementById("appList");
     var apps = this.handlerInfo.possibleApplicationHandlers.enumerate();
--- a/mail/components/preferences/applications.js
+++ b/mail/components/preferences/applications.js
@@ -91,16 +91,17 @@ function getLocalHandlerApp(aFile) {
  *
  * We don't implement all the original nsIHandlerInfo functionality,
  * just the stuff that the prefpane needs.
  */
 class HandlerInfoWrapper {
   constructor(type, handlerInfo) {
     this.type = type;
     this.wrappedHandlerInfo = handlerInfo;
+    this.disambiguateDescription = false;
 
     // 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 = "";
 
@@ -126,16 +127,32 @@ class HandlerInfoWrapper {
     if (this.primaryExtension) {
       var extension = this.primaryExtension.toUpperCase();
       return document.getElementById("bundlePreferences")
                      .getFormattedString("fileEnding", [extension]);
     }
     return this.type;
   }
 
+  /**
+   * Describe, in a human-readable fashion, the type represented by the given
+   * handler info object.  Normally this is just the description, but if more
+   * than one object presents the same description, "disambiguateDescription"
+   * is set and we annotate the duplicate descriptions with the type itself
+   * to help users distinguish between those types.
+   */
+  get typeDescription() {
+    if (this.disambiguateDescription) {
+      return this._prefsBundle.getFormattedString(
+        "typeDetailsWithTypeAndExt", [this.description, this.type]);
+    }
+
+    return this.description;
+  }
+
   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.
@@ -777,21 +794,21 @@ var gApplicationsPane = {
   // An array of HandlerInfoWrapper objects.  We build this list when we first
   // load the data and then rebuild it when users change a pref that affects
   // what types we can show or change the sort column/direction.
   // Note: this isn't necessarily the list of types we *will* show; if the user
   // provides a filter string, we'll only show the subset of types in this list
   // that match that string.
   _visibleTypes: [],
 
-  // A count of the number of times each visible type description appears.
-  // We use these counts to determine whether or not to annotate descriptions
-  // with their types to distinguish duplicate descriptions from each other.
-  // A hash of integer counts, indexed by string description.
-  _visibleTypeDescriptionCount: new Map(),
+  // Map whose keys are string descriptions and values are references to the
+  // first visible HandlerInfoWrapper that has this description. We use this
+  // to determine whether or not to annotate descriptions with their types to
+  // distinguish duplicate descriptions from each other.
+  _visibleDescriptions: new Map(),
 
 
   // -----------------------------------
   // Convenience & Performance Shortcuts
 
   // These get defined by init().
   _brandShortName: null,
   _prefsBundle: null,
@@ -954,19 +971,19 @@ var gApplicationsPane = {
       handlerInfoWrapper.handledOnlyByPlugin = false;
     }
   },
 
   // -----------------
   // View Construction
 
   _rebuildVisibleTypes() {
-    // Reset the list of visible types and the visible type description counts.
+    // Reset the list of visible types and the visible type description.
     this._visibleTypes.length = 0;
-    this._visibleTypeDescriptionCount.clear();
+    this._visibleDescriptions.clear();
 
     // Get the preferences that help determine what types to show.
     var showPlugins = Services.prefs.getBoolPref(PREF_SHOW_PLUGINS_IN_LIST);
     var hidePluginsWithoutExtensions =
       Services.prefs.getBoolPref(PREF_HIDE_PLUGINS_WITHOUT_EXTENSIONS);
 
     for (let type in this._handledTypes) {
       let handlerInfo = this._handledTypes[type];
@@ -985,19 +1002,30 @@ var gApplicationsPane = {
 
       // Hide types handled only by plugins if so prefed.
       if (handlerInfo.handledOnlyByPlugin && !showPlugins)
         continue;
 
       // We couldn't find any reason to exclude the type, so include it.
       this._visibleTypes.push(handlerInfo);
 
-      let descCount = this._visibleTypeDescriptionCount.has(handlerInfo.description) ?
-        (this._visibleTypeDescriptionCount.get(handlerInfo.description) + 1) : 1;
-      this._visibleTypeDescriptionCount.set(handlerInfo.description, descCount);
+      let otherHandlerInfo = this._visibleDescriptions
+                                 .get(handlerInfo.description);
+      if (!otherHandlerInfo) {
+        // This is the first type with this description that we encountered
+        // while rebuilding the _visibleTypes array this time. Make sure the
+        // flag is reset so we won't add the type to the description.
+        handlerInfo.disambiguateDescription = false;
+        this._visibleDescriptions.set(handlerInfo.description, handlerInfo);
+      } else {
+        // There is at least another type with this description. Make sure we
+        // add the type to the description on both HandlerInfoWrapper objects.
+        handlerInfo.disambiguateDescription = true;
+        otherHandlerInfo.disambiguateDescription = true;
+      }
     }
   },
 
   rebuildView() {
     let lastSelectedType = this._list.selectedItem &&
                            this._list.selectedItem.getAttribute("type");
 
     // Clear the list of entries.
@@ -1007,17 +1035,17 @@ var gApplicationsPane = {
 
     // If the user is filtering the list, then only show matching types.
     if (this._filter.value)
       visibleTypes = visibleTypes.filter(this._matchesFilter, this);
 
     for (let visibleType of visibleTypes) {
       let item = document.createElement("richlistitem");
       item.setAttribute("type", visibleType.type);
-      item.setAttribute("typeDescription", this._describeType(visibleType));
+      item.setAttribute("typeDescription", visibleType.typeDescription);
       item.setAttribute("shortTypeDescription", visibleType.description);
       item.setAttribute("shortTypeDetails", this._typeDetails(visibleType));
       if (visibleType.smallIcon)
         item.setAttribute("typeIcon", visibleType.smallIcon);
       item.setAttribute("actionDescription",
                         this._describePreferredAction(visibleType));
 
       if (!this._setIconClassForPreferredAction(visibleType, item)) {
@@ -1030,41 +1058,21 @@ var gApplicationsPane = {
       if (visibleType.type === lastSelectedType) {
         this._list.selectedItem = item;
       }
     }
   },
 
   _matchesFilter(aType) {
     var filterValue = this._filter.value.toLowerCase();
-    return this._describeType(aType).toLowerCase().includes(filterValue) ||
+    return aType.typeDescription.toLowerCase().includes(filterValue) ||
            this._describePreferredAction(aType).toLowerCase().includes(filterValue);
   },
 
   /**
-   * Describe, in a human-readable fashion, the type represented by the given
-   * handler info object.  Normally this is just the description provided by
-   * the info object, but if more than one object presents the same description,
-   * then we annotate the duplicate descriptions with the type itself to help
-   * users distinguish between those types.
-   *
-   * @param aHandlerInfo {nsIHandlerInfo} the type being described
-   * @return {string} a description of the type
-   */
-  _describeType(aHandlerInfo) {
-    let details = this._typeDetails(aHandlerInfo);
-
-    if (details)
-      return this._prefsBundle.getFormattedString("typeDescriptionWithDetails",
-                                                  [aHandlerInfo.description,
-                                                   details]);
-    return aHandlerInfo.description;
-  },
-
-  /**
    * Get the details for the type represented by the given handler info
    * object.
    *
    * @param aHandlerInfo {nsIHandlerInfo} the type to get the extensions for.
    * @return {string} the extensions for the type
    */
   _typeDetails(aHandlerInfo) {
     let exts = [];
@@ -1073,17 +1081,17 @@ var gApplicationsPane = {
       while (extIter.hasMore()) {
         let ext = "." + extIter.getNext();
         if (!exts.includes(ext))
           exts.push(ext);
       }
     }
     exts.sort();
     exts = exts.join(", ");
-    if (this._visibleTypeDescriptionCount.has(aHandlerInfo.description)) {
+    if (this._visibleDescriptions.has(aHandlerInfo.description)) {
       if (exts)
         return this._prefsBundle.getFormattedString("typeDetailsWithTypeAndExt",
                                                     [aHandlerInfo.type,
                                                      exts]);
       return this._prefsBundle.getFormattedString("typeDetailsWithTypeOrExt",
                                                   [ aHandlerInfo.type]);
     }
     if (exts)
@@ -1390,18 +1398,18 @@ var gApplicationsPane = {
    */
   _sortVisibleTypes() {
     if (!this._sortColumn)
       return;
 
     var t = this;
 
     function sortByType(a, b) {
-      return t._describeType(a).toLowerCase()
-              .localeCompare(t._describeType(b).toLowerCase());
+      return a.typeDescription.toLowerCase()
+              .localeCompare(b.typeDescription.toLowerCase());
     }
 
     function sortByAction(a, b) {
       return t._describePreferredAction(a).toLowerCase()
               .localeCompare(t._describePreferredAction(b).toLowerCase());
     }
 
     switch (this._sortColumn.getAttribute("value")) {