Bug 1519650 - Port bug 1459556, part 1: Remove the implementation from the "handler" binding. r=jorgk
authorRichard Marti <richard.marti@gmail.com>
Sat, 12 Jan 2019 20:17:00 +0100
changeset 33322 34939e75acc1
parent 33321 041318b618cf
child 33323 253562a57fb5
push id2368
push userclokep@gmail.com
push dateMon, 28 Jan 2019 21:12:50 +0000
treeherdercomm-beta@56d23c07d815 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorgk
bugs1519650, 1459556
Bug 1519650 - Port bug 1459556, part 1: Remove the implementation from the "handler" binding. r=jorgk
mail/components/preferences/applications.js
mail/components/preferences/handlers.xml
--- a/mail/components/preferences/applications.js
+++ b/mail/components/preferences/applications.js
@@ -97,16 +97,23 @@ class HandlerListItem {
       this.node.setAttribute(APP_ICON_ATTR_NAME,
                              this.handlerInfoWrapper.actionIconClass);
       this.node.removeAttribute("actionIcon");
     } else {
       this.node.removeAttribute(APP_ICON_ATTR_NAME);
       this.node.setAttribute("actionIcon", this.handlerInfoWrapper.actionIcon);
     }
   }
+
+  set showActionsMenu(value) {
+    document.getAnonymousElementByAttribute(this.node, "anonid", "selected")
+            .setAttribute("hidden", !value);
+    document.getAnonymousElementByAttribute(this.node, "anonid", "not-selected")
+            .setAttribute("hidden", !!value);
+  }
 }
 
 /**
  * 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
@@ -751,17 +758,17 @@ var gCloudFileTab = {
     this.requestUserInfoForItem(item, true);
   },
 
   addCloudFileAccount() {
     let accountKey = cloudFileAccounts.addAccountDialog();
     if (!accountKey)
       return;
 
-    this.rebuildView();
+    this._rebuildView();
     let newItem = this._list.querySelector("richlistitem[value='" + accountKey + "']");
     this._list.selectItem(newItem);
     this._removeAccountButton.disabled = false;
   },
 
   removeCloudFileAccount() {
     // Get the selected account key
     let selection = this._list.selectedItem;
@@ -773,17 +780,17 @@ var gCloudFileTab = {
     // Does the user really want to remove this account?
     let confirmMessage = this._strings
                              .formatStringFromName("dialog_removeAccount",
                                                    [accountName], 1);
 
     if (Services.prompt.confirm(null, "", confirmMessage)) {
       this._list.clearSelection();
       cloudFileAccounts.removeAccount(accountKey);
-      this.rebuildView();
+      this._rebuildView();
       this._settingsDeck.selectedPanel = this._defaultPanel;
       delete this._accountCache[accountKey];
 
       this._removeAccountButton.disabled = (this._list.selectedCount == 0);
     }
   },
 
   handleEvent(aEvent) {
@@ -869,20 +876,21 @@ var gApplicationsPane = {
 
     // By doing this in a timeout, we let the preferences dialog resize itself
     // to an appropriate size before we add a bunch of items to the list.
     // Otherwise, if there are many items, and the Applications prefpane
     // is the one that gets displayed when the user first opens the dialog,
     // the dialog might stretch too much in an attempt to fit them all in.
     // XXX Shouldn't we perhaps just set a max-height on the richlistbox?
     var _delayedPaneLoad = function(self) {
+      self._initListEventHandlers();
       self._loadData();
       self._rebuildVisibleTypes();
       self._sortVisibleTypes();
-      self.rebuildView();
+      self._rebuildView();
 
       // Notify observers that the UI is now ready
       Services.obs.notifyObservers(window, "app-handler-pane-loaded");
     };
     setTimeout(_delayedPaneLoad, 0, this);
   },
 
   // ---------------------------
@@ -910,16 +918,41 @@ var gApplicationsPane = {
         this._handledTypes[type] = handlerInfoWrapper;
       }
     }
   },
 
   // -----------------
   // View Construction
 
+  selectedHandlerListItem: null,
+
+  _initListEventHandlers() {
+    this._list.addEventListener("select", event => {
+      if (event.target != this._list) {
+        return;
+      }
+
+      let handlerListItem = this._list.selectedItem &&
+                            HandlerListItem.forNode(this._list.selectedItem);
+      if (this.selectedHandlerListItem == handlerListItem) {
+        return;
+      }
+
+      if (this.selectedHandlerListItem) {
+        this.selectedHandlerListItem.showActionsMenu = false;
+      }
+      this.selectedHandlerListItem = handlerListItem;
+      if (handlerListItem) {
+        this.rebuildActionsMenu();
+        handlerListItem.showActionsMenu = true;
+      }
+    });
+  },
+
   _rebuildVisibleTypes() {
     // Reset the list of visible types and the visible type description.
     this._visibleTypes.length = 0;
     this._visibleDescriptions.clear();
 
     for (let type in this._handledTypes) {
       let handlerInfo = this._handledTypes[type];
 
@@ -938,20 +971,20 @@ var gApplicationsPane = {
         // 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 &&
-                           HandlerListItem.forNode(this._list.selectedItem)
-                           .handlerInfoWrapper.type;
+  _rebuildView() {
+    let lastSelectedType = this.selectedHandlerListItem &&
+                           this.selectedHandlerListItem.handlerInfoWrapper.type;
+    this.selectedHandlerListItem = null;
 
     // Clear the list of entries.
     while (this._list.childNodes.length > 1)
       this._list.lastChild.remove();
     var visibleTypes = this._visibleTypes;
 
     // If the user is filtering the list, then only show matching types.
     if (this._filter.value)
@@ -1048,17 +1081,17 @@ var gApplicationsPane = {
    * the richlistitem constructor when an entry in the list gets selected.
    */
   rebuildActionsMenu() {
     var typeItem = this._list.selectedItem;
 
     if (!typeItem)
       return;
 
-    var handlerInfo = this._handledTypes[typeItem.type];
+    var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
     var menu =
       document.getAnonymousElementByAttribute(typeItem, "class", "actionsMenu");
     var menuPopup = menu.menupopup;
 
     // Clear out existing items.
     while (menuPopup.hasChildNodes())
       menuPopup.lastChild.remove();
 
@@ -1218,17 +1251,17 @@ var gApplicationsPane = {
 
     // Set (or switch) the sort direction indicator.
     if (column.getAttribute("sortDirection") == "ascending")
       column.setAttribute("sortDirection", "descending");
     else
       column.setAttribute("sortDirection", "ascending");
 
     this._sortVisibleTypes();
-    this.rebuildView();
+    this._rebuildView();
   },
 
   /**
    * Sort the list of visible types by the current sort column/direction.
    */
   _sortVisibleTypes() {
     if (!this._sortColumn)
       return;
@@ -1282,18 +1315,17 @@ var gApplicationsPane = {
     try {
       this._storeAction(aActionItem);
     } finally {
       this._storingAction = false;
     }
   },
 
   _storeAction(aActionItem) {
-    var typeItem = this._list.selectedItem;
-    var handlerInfo = this._handledTypes[typeItem.type];
+    var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
 
     if (aActionItem.hasAttribute("alwaysAsk")) {
       handlerInfo.alwaysAskBeforeHandling = true;
     } else if (aActionItem.hasAttribute("action")) {
       let action = parseInt(aActionItem.getAttribute("action"));
 
       // Set the preferred application handler.
       // We leave the existing preferred app in the list when we set
@@ -1309,34 +1341,33 @@ var gApplicationsPane = {
 
       // Set the preferred action.
       handlerInfo.preferredAction = action;
     }
 
     handlerInfo.store();
 
     // Update the action label and image to reflect the new preferred action.
-    HandlerListItem.forNode(typeItem).refreshAction();
+    this.selectedHandlerListItem.refreshAction();
   },
 
   manageApp(aEvent) {
     // Don't let the normal "on select action" handler get this event,
     // as we handle it specially ourselves.
     aEvent.stopPropagation();
 
-    var typeItem = this._list.selectedItem;
-    var handlerInfo = this._handledTypes[typeItem.type];
+    var handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
 
     let closingCallback = () => {
       // Rebuild the actions menu so that we revert to the previous selection,
       // or "Always ask" if the previous default application has been removed.
       this.rebuildActionsMenu();
 
       // Update the richlistitem too. Will be visible when selecting another row.
-      HandlerListItem.forNode(typeItem).refreshAction();
+      this.selectedHandlerListItem.refreshAction();
     };
 
     gSubDialog.open(
       "chrome://messenger/content/preferences/applicationManager.xul",
       "resizable=no", handlerInfo, closingCallback);
   },
 
   chooseApp(aEvent) {
@@ -1365,17 +1396,17 @@ var gApplicationsPane = {
             break;
           }
         }
       }
     }.bind(this);
 
     if (AppConstants.platform == "win") {
       let params = {};
-      let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+      let handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
 
       params.mimeInfo = handlerInfo.wrappedHandlerInfo;
 
       params.title         = this._prefsBundle.getString("fpTitleChooseApp");
       params.description   = handlerInfo.description;
       params.filename      = null;
       params.handlerApp    = null;
 
@@ -1407,17 +1438,17 @@ var gApplicationsPane = {
         if (rv == nsIFilePicker.returnOK && fp.file &&
             this._isValidHandlerExecutable(fp.file)) {
           handlerApp = Cc["@mozilla.org/uriloader/local-handler-app;1"]
                          .createInstance(Ci.nsILocalHandlerApp);
           handlerApp.name = getDisplayNameForFile(fp.file);
           handlerApp.executable = fp.file;
 
           // Add the app to the type's list of possible handlers.
-          let handlerInfo = this._handledTypes[this._list.selectedItem.type];
+          let handlerInfo = this.selectedHandlerListItem.handlerInfoWrapper;
           handlerInfo.addPossibleApplicationHandler(handlerApp);
         }
         onSelectionDone();
       });
     }
   },
 
   confirmDelete(aEvent) {
--- a/mail/components/preferences/handlers.xml
+++ b/mail/components/preferences/handlers.xml
@@ -4,41 +4,16 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <bindings id="handlerBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="handler" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <implementation>
-      <property name="type" readonly="true">
-        <getter>
-          return this.getAttribute("type");
-        </getter>
-      </property>
-      <!-- Overriding listitem -->
-      <property name="selected" onget="return this.getAttribute('selected') == 'true';">
-        <setter><![CDATA[
-          // applications.js
-          /* globals gApplicationsPane */
-          if (val) {
-            this.setAttribute("selected", "true");
-            gApplicationsPane.rebuildActionsMenu();
-          } else {
-            this.removeAttribute("selected");
-          }
-
-          document.getAnonymousElementByAttribute(this, "anonid", "selected").setAttribute("hidden", !val);
-          document.getAnonymousElementByAttribute(this, "anonid", "not-selected").setAttribute("hidden", !!val);
-
-          return val;
-        ]]></setter>
-      </property>
-    </implementation>
     <content>
       <xul:hbox flex="1" equalsize="always">
         <xul:hbox flex="1" align="center" xbl:inherits="tooltiptext=typeDescription">
           <xul:image src="moz-icon://goat?size=16" class="typeIcon"
                      xbl:inherits="src=typeIcon" height="16" width="16"/>
           <xul:label flex="1" class="shortDescription"
                      xbl:inherits="value=shortTypeDescription"/>
           <xul:label flex="1" crop="end" class="shortDetails"