Bug 1547699 - Improvements/Simplifications for MozAttachmentlist. r=mkmelin
authorArshad Khan <arshdkhn1@gmail.com>
Sat, 01 Jun 2019 22:00:50 +0200
changeset 35741 5d14d6540a92fba46b3aa40959baae535a5eedac
parent 35740 199071a3bf1634a4b79a0471eaf64ea27d0b1717
child 35742 44e0b7bccc36e2fbea92430028eb2e57faca7099
push id392
push userclokep@gmail.com
push dateMon, 02 Sep 2019 20:17:19 +0000
reviewersmkmelin
bugs1547699, 1554943, 1552965
Bug 1547699 - Improvements/Simplifications for MozAttachmentlist. r=mkmelin Removes xbl-richlistbox. Fixes for bug 1554943 (height resize) and bug 1552965 (click in empty space in attachment bucket).
common/bindings/richlistbox.xml
mail/base/content/attachmentList.css
mail/base/content/mailWidgets.js
mail/base/content/msgHdrView.js
mail/base/jar.mn
mail/components/compose/content/MsgComposeCommands.js
mail/themes/windows/mail/attachmentList.css
deleted file mode 100644
--- a/common/bindings/richlistbox.xml
+++ /dev/null
@@ -1,931 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- 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/. -->
-
-<!-- This file relies on these specific Chrome/XBL globals -->
-<!-- globals ChromeNodeList -->
-
-<bindings id="richlistboxBindings"
-          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="xbl-richlistbox"
-           extends="chrome://global/content/bindings/general.xml#basecontrol">
-    <content allowevents="true" orient="vertical"/>
-
-    <implementation implements="nsIDOMXULMultiSelectControlElement">
-      <constructor>
-        <![CDATA[
-          this._refreshSelection();
-        ]]>
-      </constructor>
-
-      <method name="_fireOnSelect">
-        <body>
-          <![CDATA[
-            // make sure not to modify last-selected when suppressing select events
-            // (otherwise we'll lose the selection when a template gets rebuilt)
-            if (this._suppressOnSelect || this.suppressOnSelect)
-              return;
-
-            // remember the current item and all selected items with IDs
-            var state = this.currentItem ? this.currentItem.id : "";
-            if (this.selType == "multiple" && this.selectedCount) {
-              let getId = function(aItem) { return aItem.id; };
-              state += " " + [...this.selectedItems].filter(getId).map(getId).join(" ");
-            }
-            if (state)
-              this.setAttribute("last-selected", state);
-            else
-              this.removeAttribute("last-selected");
-
-            // preserve the index just in case no IDs are available
-            if (this.currentIndex > -1)
-              this._currentIndex = this.currentIndex + 1;
-
-            var event = document.createEvent("Events");
-            event.initEvent("select", true, true);
-            this.dispatchEvent(event);
-
-            // always call this (allows a commandupdater without controller)
-            document.commandDispatcher.updateCommands("richlistbox-select");
-          ]]>
-        </body>
-      </method>
-
-      <method name="getNextItem">
-        <parameter name="aStartItem"/>
-        <parameter name="aDelta"/>
-        <body>
-        <![CDATA[
-          while (aStartItem) {
-            aStartItem = aStartItem.nextSibling;
-            if (aStartItem && aStartItem.localName == "richlistitem" &&
-                (!this._userSelecting || this._canUserSelect(aStartItem))) {
-              --aDelta;
-              if (aDelta == 0)
-                return aStartItem;
-            }
-          }
-          return null;
-        ]]>
-        </body>
-      </method>
-
-      <method name="getPreviousItem">
-        <parameter name="aStartItem"/>
-        <parameter name="aDelta"/>
-        <body>
-        <![CDATA[
-          while (aStartItem) {
-            aStartItem = aStartItem.previousSibling;
-            if (aStartItem && aStartItem.localName == "richlistitem" &&
-                (!this._userSelecting || this._canUserSelect(aStartItem))) {
-              --aDelta;
-              if (aDelta == 0)
-                return aStartItem;
-            }
-          }
-          return null;
-        ]]>
-        </body>
-      </method>
-
-      <method name="appendItem">
-        <parameter name="aLabel"/>
-        <parameter name="aValue"/>
-        <body>
-          var item =
-            this.ownerDocument.createXULElement("richlistitem");
-          item.setAttribute("value", aValue);
-
-          var label = this.ownerDocument.createXULElement("label");
-          label.setAttribute("value", aLabel);
-          label.setAttribute("flex", "1");
-          label.setAttribute("crop", "end");
-          item.appendChild(label);
-
-          this.appendChild(item);
-
-          return item;
-        </body>
-      </method>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <property name="selectedItem"
-                onset="this.selectItem(val);">
-        <getter>
-        <![CDATA[
-          return this.selectedItems.length > 0 ? this.selectedItems[0] : null;
-        ]]>
-        </getter>
-      </property>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <property name="selectedIndex">
-        <getter>
-        <![CDATA[
-          if (this.selectedItems.length > 0)
-            return this.getIndexOfItem(this.selectedItems[0]);
-          return -1;
-        ]]>
-        </getter>
-        <setter>
-        <![CDATA[
-          if (val >= 0) {
-            // This is a micro-optimization so that a call to getIndexOfItem or
-            // getItemAtIndex caused by _fireOnSelect (especially for derived
-            // widgets) won't loop the children.
-            this._selecting = {
-              item: this.getItemAtIndex(val),
-              index: val,
-            };
-            this.selectItem(this._selecting.item);
-            delete this._selecting;
-          } else {
-            this.clearSelection();
-            this.currentItem = null;
-          }
-        ]]>
-        </setter>
-      </property>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <property name="value">
-        <getter>
-        <![CDATA[
-          if (this.selectedItems.length > 0)
-            return this.selectedItem.value;
-          return null;
-        ]]>
-        </getter>
-        <setter>
-        <![CDATA[
-          var kids = this.getElementsByAttribute("value", val);
-          if (kids && kids.item(0))
-            this.selectItem(kids[0]);
-          return val;
-        ]]>
-        </setter>
-      </property>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <property name="itemCount" readonly="true"
-                onget="return this.itemChildren.length"/>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <method name="getIndexOfItem">
-        <parameter name="aItem"/>
-        <body>
-          <![CDATA[
-            // don't search the children, if we're looking for none of them
-            if (aItem == null)
-              return -1;
-            if (this._selecting && this._selecting.item == aItem)
-              return this._selecting.index;
-            return this.itemChildren.indexOf(aItem);
-          ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULSelectControlElement -->
-      <method name="getItemAtIndex">
-        <parameter name="aIndex"/>
-        <body>
-          <![CDATA[
-            if (this._selecting && this._selecting.index == aIndex)
-              return this._selecting.item;
-            return this.itemChildren[aIndex] || null;
-          ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <property name="selType"
-                onget="return this.getAttribute('seltype');"
-                onset="this.setAttribute('seltype', val); return val;"/>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <property name="currentItem" onget="return this._currentItem;">
-        <setter>
-          if (this._currentItem == val)
-            return val;
-
-          if (this._currentItem)
-            this._currentItem.current = false;
-          this._currentItem = val;
-
-          if (val)
-            val.current = true;
-
-          return val;
-        </setter>
-      </property>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <property name="currentIndex">
-        <getter>
-          return this.currentItem ? this.getIndexOfItem(this.currentItem) : -1;
-        </getter>
-        <setter>
-        <![CDATA[
-          if (val >= 0)
-            this.currentItem = this.getItemAtIndex(val);
-          else
-            this.currentItem = null;
-        ]]>
-        </setter>
-      </property>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <field name="selectedItems">new ChromeNodeList()</field>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="addItemToSelection">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          if (this.selType != "multiple" && this.selectedCount)
-            return;
-
-          if (aItem.selected)
-            return;
-
-          this.selectedItems.append(aItem);
-          aItem.selected = true;
-
-          this._fireOnSelect();
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="removeItemFromSelection">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          if (!aItem.selected)
-            return;
-
-          this.selectedItems.remove(aItem);
-          aItem.selected = false;
-          this._fireOnSelect();
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="toggleItemSelection">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          if (aItem.selected)
-            this.removeItemFromSelection(aItem);
-          else
-            this.addItemToSelection(aItem);
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="selectItem">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          if (!aItem)
-            return;
-
-          if (this.selectedItems.length == 1 && this.selectedItems[0] == aItem)
-            return;
-
-          this._selectionStart = null;
-
-          var suppress = this._suppressOnSelect;
-          this._suppressOnSelect = true;
-
-          this.clearSelection();
-          this.addItemToSelection(aItem);
-          this.currentItem = aItem;
-
-          this._suppressOnSelect = suppress;
-          this._fireOnSelect();
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="selectItemRange">
-        <parameter name="aStartItem"/>
-        <parameter name="aEndItem"/>
-        <body>
-        <![CDATA[
-          if (this.selType != "multiple")
-            return;
-
-          if (!aStartItem)
-            aStartItem = this._selectionStart ?
-              this._selectionStart : this.currentItem;
-
-          if (!aStartItem)
-            aStartItem = aEndItem;
-
-          var suppressSelect = this._suppressOnSelect;
-          this._suppressOnSelect = true;
-
-          this._selectionStart = aStartItem;
-
-          var currentItem;
-          var startIndex = this.getIndexOfItem(aStartItem);
-          var endIndex = this.getIndexOfItem(aEndItem);
-          if (endIndex < startIndex) {
-            currentItem = aEndItem;
-            aEndItem = aStartItem;
-            aStartItem = currentItem;
-          } else {
-            currentItem = aStartItem;
-          }
-
-          while (currentItem) {
-            this.addItemToSelection(currentItem);
-            if (currentItem == aEndItem) {
-              currentItem = this.getNextItem(currentItem, 1);
-              break;
-            }
-            currentItem = this.getNextItem(currentItem, 1);
-          }
-
-          // Clear around new selection
-          // Don't use clearSelection() because it causes a lot of noise
-          // with respect to selection removed notifications used by the
-          // accessibility API support.
-          var userSelecting = this._userSelecting;
-          this._userSelecting = false; // that's US automatically unselecting
-          for (; currentItem; currentItem = this.getNextItem(currentItem, 1))
-            this.removeItemFromSelection(currentItem);
-
-          for (currentItem = this.getItemAtIndex(0); currentItem != aStartItem;
-               currentItem = this.getNextItem(currentItem, 1))
-            this.removeItemFromSelection(currentItem);
-          this._userSelecting = userSelecting;
-
-          this._suppressOnSelect = suppressSelect;
-
-          this._fireOnSelect();
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="selectAll">
-        <body>
-          this._selectionStart = null;
-
-          var suppress = this._suppressOnSelect;
-          this._suppressOnSelect = true;
-
-          var item = this.getItemAtIndex(0);
-          while (item) {
-            this.addItemToSelection(item);
-            item = this.getNextItem(item, 1);
-          }
-
-          this._suppressOnSelect = suppress;
-          this._fireOnSelect();
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="invertSelection">
-        <body>
-          this._selectionStart = null;
-
-          var suppress = this._suppressOnSelect;
-          this._suppressOnSelect = true;
-
-          var item = this.getItemAtIndex(0);
-          while (item) {
-            if (item.selected)
-              this.removeItemFromSelection(item);
-            else
-              this.addItemToSelection(item);
-            item = this.getNextItem(item, 1);
-          }
-
-          this._suppressOnSelect = suppress;
-          this._fireOnSelect();
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="clearSelection">
-        <body>
-        <![CDATA[
-          if (this.selectedItems) {
-            while (this.selectedItems.length > 0) {
-              let item = this.selectedItems[0];
-              item.selected = false;
-              this.selectedItems.remove(item);
-            }
-          }
-
-          this._selectionStart = null;
-          this._fireOnSelect();
-        ]]>
-        </body>
-      </method>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <property name="selectedCount" readonly="true"
-                onget="return this.selectedItems.length;"/>
-
-      <!-- nsIDOMXULMultiSelectControlElement -->
-      <method name="getSelectedItem">
-        <parameter name="aIndex"/>
-        <body>
-        <![CDATA[
-          return aIndex < this.selectedItems.length ?
-            this.selectedItems[aIndex] : null;
-        ]]>
-        </body>
-      </method>
-
-      <method name="ensureIndexIsVisible">
-        <parameter name="aIndex"/>
-        <body>
-          <![CDATA[
-            return this.ensureElementIsVisible(this.getItemAtIndex(aIndex));
-          ]]>
-        </body>
-      </method>
-
-      <method name="ensureElementIsVisible">
-        <parameter name="aElement"/>
-        <parameter name="aAlignToTop"/>
-        <body>
-          <![CDATA[
-            if (!aElement) {
-              return;
-            }
-
-            // These calculations assume that there is no padding on the
-            // "richlistbox" element, although there might be a margin.
-            var targetRect = aElement.getBoundingClientRect();
-            var scrollRect = this.getBoundingClientRect();
-            var offset = targetRect.top - scrollRect.top;
-            if (!aAlignToTop && offset >= 0) {
-              // scrollRect.bottom wouldn't take a horizontal scroll bar into account
-              let scrollRectBottom = scrollRect.top + this.clientHeight;
-              offset = targetRect.bottom - scrollRectBottom;
-              if (offset <= 0)
-                return;
-            }
-            this.scrollTop += offset;
-          ]]>
-        </body>
-      </method>
-
-      <method name="scrollToIndex">
-        <parameter name="aIndex"/>
-        <body>
-          <![CDATA[
-            var item = this.getItemAtIndex(aIndex);
-            if (item) {
-              this.ensureElementIsVisible(item, true);
-            }
-          ]]>
-        </body>
-      </method>
-
-      <method name="getIndexOfFirstVisibleRow">
-        <body>
-          <![CDATA[
-            var children = this.itemChildren;
-
-            for (var ix = 0; ix < children.length; ix++)
-              if (this._isItemVisible(children[ix]))
-                return ix;
-
-            return -1;
-          ]]>
-        </body>
-      </method>
-
-      <method name="getRowCount">
-        <body>
-          <![CDATA[
-            return this.itemChildren.length;
-          ]]>
-        </body>
-      </method>
-
-      <method name="scrollOnePage">
-        <parameter name="aDirection"/> <!-- Must be -1 or 1 -->
-        <body>
-          <![CDATA[
-            var children = this.itemChildren;
-
-            if (children.length == 0)
-              return 0;
-
-            // If nothing is selected, we just select the first element
-            // at the extreme we're moving away from
-            if (!this.currentItem)
-              return aDirection == -1 ? children.length : 0;
-
-            // If the current item is visible, scroll by one page so that
-            // the new current item is at approximately the same position as
-            // the existing current item.
-            if (this._isItemVisible(this.currentItem))
-              this.scrollBy(0, this.clientHeight * aDirection);
-
-            // Figure out, how many items fully fit into the view port
-            // (including the currently selected one), and determine
-            // the index of the first one lying (partially) outside
-            var height = this.clientHeight;
-            var startBorder = this.currentItem.getBoundingClientRect().y;
-            if (aDirection == -1)
-              startBorder += this.currentItem.clientHeight;
-
-            var index = this.currentIndex;
-            for (var ix = index; 0 <= ix && ix < children.length; ix += aDirection) {
-              var boundingRect = children[ix].getBoundingClientRect();
-              if (boundingRect.height == 0)
-                continue; // hidden children have a y of 0
-              var endBorder = boundingRect.y + (aDirection == -1 ? boundingRect.height : 0);
-              if ((endBorder - startBorder) * aDirection > height)
-                break; // we've reached the desired distance
-              index = ix;
-            }
-
-            return index != this.currentIndex ? index - this.currentIndex : aDirection;
-          ]]>
-        </body>
-      </method>
-
-      <property name="itemChildren" readonly="true">
-        <getter>
-          <![CDATA[
-            let children = Array.from(this.children)
-                                .filter(node => node.localName == "richlistitem");
-            return children;
-          ]]>
-        </getter>
-      </property>
-
-      <method name="_refreshSelection">
-        <body>
-          <![CDATA[
-            // when this method is called, we know that either the currentItem
-            // and selectedItems we have are null (ctor) or a reference to an
-            // element no longer in the DOM (template).
-
-            // first look for the last-selected attribute
-            var state = this.getAttribute("last-selected");
-            if (state) {
-              var ids = state.split(" ");
-
-              var suppressSelect = this._suppressOnSelect;
-              this._suppressOnSelect = true;
-              this.clearSelection();
-              for (let i = 1; i < ids.length; i++) {
-                var selectedItem = document.getElementById(ids[i]);
-                if (selectedItem)
-                  this.addItemToSelection(selectedItem);
-              }
-
-              var currentItem = document.getElementById(ids[0]);
-              if (!currentItem && this._currentIndex)
-                currentItem = this.getItemAtIndex(Math.min(
-                  this._currentIndex - 1, this.getRowCount()));
-              if (currentItem) {
-                this.currentItem = currentItem;
-                if (this.selType != "multiple" && this.selectedCount == 0)
-                  this.selectedItem = currentItem;
-
-                if (this.clientHeight) {
-                  this.ensureElementIsVisible(currentItem);
-                } else {
-                  // XXX hack around a bug in ensureElementIsVisible as it will
-                  // scroll beyond the last element, bug 493645.
-                  this.ensureElementIsVisible(currentItem.previousElementSibling);
-                }
-              }
-              this._suppressOnSelect = suppressSelect;
-              // XXX actually it's just a refresh, but at least
-              // the Extensions manager expects this:
-              this._fireOnSelect();
-              return;
-            }
-
-            // try to restore the selected items according to their IDs
-            // (applies after a template rebuild, if last-selected was not set)
-            if (this.selectedItems) {
-              let itemIds = [];
-              for (let i = this.selectedCount - 1; i >= 0; i--) {
-                let selectedItem = this.selectedItems[i];
-                itemIds.push(selectedItem.id);
-                this.selectedItems.remove(selectedItem);
-              }
-              for (let i = 0; i < itemIds.length; i++) {
-                let selectedItem = document.getElementById(itemIds[i]);
-                if (selectedItem) {
-                  this.selectedItems.append(selectedItem);
-                }
-              }
-            }
-            if (this.currentItem && this.currentItem.id)
-              this.currentItem = document.getElementById(this.currentItem.id);
-            else
-              this.currentItem = null;
-
-            // if we have no previously current item or if the above check fails to
-            // find the previous nodes (which causes it to clear selection)
-            if (!this.currentItem && this.selectedCount == 0) {
-              this.currentIndex = this._currentIndex ? this._currentIndex - 1 : 0;
-
-              // cf. listbox constructor:
-              // select items according to their attributes
-              var children = this.itemChildren;
-              for (let i = 0; i < children.length; ++i) {
-                if (children[i].getAttribute("selected") == "true")
-                  this.selectedItems.append(children[i]);
-              }
-            }
-
-            if (this.selType != "multiple" && this.selectedCount == 0)
-              this.selectedItem = this.currentItem;
-          ]]>
-        </body>
-      </method>
-
-      <method name="_isItemVisible">
-        <parameter name="aItem"/>
-        <body>
-          <![CDATA[
-            if (!aItem)
-              return false;
-
-            var y = this.scrollTop + this.getBoundingClientRect().y;
-
-            // Partially visible items are also considered visible
-            let boundingRect = aItem.getBoundingClientRect();
-            return (boundingRect.y + aItem.clientHeight > y) &&
-                   (boundingRect.y < y + this.clientHeight);
-          ]]>
-        </body>
-      </method>
-
-      <property name="suppressOnSelect"
-                onget="return this.getAttribute('suppressonselect') == 'true';"
-                onset="this.setAttribute('suppressonselect', val);"/>
-
-      <property name="_selectDelay"
-                onset="this.setAttribute('_selectDelay', val);"
-                onget="return this.getAttribute('_selectDelay') || 50;"/>
-
-      <method name="moveByOffset">
-        <parameter name="aOffset"/>
-        <parameter name="aIsSelecting"/>
-        <parameter name="aIsSelectingRange"/>
-        <body>
-        <![CDATA[
-          if ((aIsSelectingRange || !aIsSelecting) &&
-              this.selType != "multiple")
-            return;
-
-          var newIndex = this.currentIndex + aOffset;
-          if (newIndex < 0)
-            newIndex = 0;
-
-          var numItems = this.getRowCount();
-          if (newIndex > numItems - 1)
-            newIndex = numItems - 1;
-
-          var newItem = this.getItemAtIndex(newIndex);
-          // make sure that the item is actually visible/selectable
-          if (this._userSelecting && newItem && !this._canUserSelect(newItem))
-            newItem =
-              aOffset > 0 ? this.getNextItem(newItem, 1) || this.getPreviousItem(newItem, 1) :
-                            this.getPreviousItem(newItem, 1) || this.getNextItem(newItem, 1);
-          if (newItem) {
-            this.ensureIndexIsVisible(this.getIndexOfItem(newItem));
-            if (aIsSelectingRange)
-              this.selectItemRange(null, newItem);
-            else if (aIsSelecting)
-              this.selectItem(newItem);
-
-            this.currentItem = newItem;
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="_moveByOffsetFromUserEvent">
-        <parameter name="aOffset"/>
-        <parameter name="aEvent"/>
-        <body>
-        <![CDATA[
-          if (!aEvent.defaultPrevented) {
-            this._userSelecting = true;
-            this.moveByOffset(aOffset, !aEvent.ctrlKey, aEvent.shiftKey);
-            this._userSelecting = false;
-            aEvent.preventDefault();
-          }
-        ]]>
-        </body>
-      </method>
-
-      <method name="_canUserSelect">
-        <parameter name="aItem"/>
-        <body>
-        <![CDATA[
-          var style = document.defaultView.getComputedStyle(aItem);
-          return style.display != "none" && style.visibility == "visible" &&
-                 style.MozUserInput != "none";
-        ]]>
-        </body>
-      </method>
-
-      <method name="_selectTimeoutHandler">
-        <parameter name="aMe"/>
-        <body>
-          aMe._fireOnSelect();
-          aMe._selectTimeout = null;
-        </body>
-      </method>
-
-      <method name="timedSelect">
-        <parameter name="aItem"/>
-        <parameter name="aTimeout"/>
-        <body>
-        <![CDATA[
-          var suppress = this._suppressOnSelect;
-          if (aTimeout != -1)
-            this._suppressOnSelect = true;
-
-          this.selectItem(aItem);
-
-          this._suppressOnSelect = suppress;
-
-          if (aTimeout != -1) {
-            if (this._selectTimeout)
-              window.clearTimeout(this._selectTimeout);
-            this._selectTimeout =
-              window.setTimeout(this._selectTimeoutHandler, aTimeout, this);
-          }
-        ]]>
-        </body>
-      </method>
-
-      <field name="_currentIndex">null</field>
-      <field name="_lastKeyTime">0</field>
-      <field name="_incrementalString">""</field>
-      <field name="_suppressOnSelect">false</field>
-      <field name="_userSelecting">false</field>
-      <field name="_selectTimeout">null</field>
-      <field name="_currentItem">null</field>
-      <field name="_selectionStart">null</field>
-
-      <!-- For backwards-compatibility and for convenience.
-        Use ensureElementIsVisible instead -->
-      <method name="ensureSelectedElementIsVisible">
-        <body>
-          <![CDATA[
-            return this.ensureElementIsVisible(this.selectedItem);
-          ]]>
-        </body>
-      </method>
-    </implementation>
-
-    <handlers>
-      <handler event="keypress" keycode="VK_UP" modifiers="control shift any"
-               action="this._moveByOffsetFromUserEvent(-1, event);"
-               group="system"/>
-
-      <handler event="keypress" keycode="VK_DOWN" modifiers="control shift any"
-               action="this._moveByOffsetFromUserEvent(1, event);"
-               group="system"/>
-
-      <handler event="keypress" keycode="VK_HOME" modifiers="control shift any"
-               group="system">
-        <![CDATA[
-          this._moveByOffsetFromUserEvent(-this.currentIndex, event);
-        ]]>
-      </handler>
-
-      <handler event="keypress" keycode="VK_END" modifiers="control shift any"
-               group="system">
-        <![CDATA[
-          this._moveByOffsetFromUserEvent(this.getRowCount() - this.currentIndex - 1, event);
-        ]]>
-      </handler>
-
-      <handler event="keypress" keycode="VK_PAGE_UP" modifiers="control shift any"
-               group="system">
-        <![CDATA[
-          this._moveByOffsetFromUserEvent(this.scrollOnePage(-1), event);
-        ]]>
-      </handler>
-
-      <handler event="keypress" keycode="VK_PAGE_DOWN" modifiers="control shift any"
-               group="system">
-        <![CDATA[
-          this._moveByOffsetFromUserEvent(this.scrollOnePage(1), event);
-        ]]>
-      </handler>
-
-      <handler event="keypress" key=" " modifiers="control" phase="target">
-        <![CDATA[
-          if (this.currentItem && this.selType == "multiple")
-            this.toggleItemSelection(this.currentItem);
-        ]]>
-      </handler>
-
-      <handler event="focus">
-        <![CDATA[
-          if (this.getRowCount() > 0) {
-            if (this.currentIndex == -1) {
-              this.currentIndex = this.getIndexOfFirstVisibleRow();
-              let currentItem = this.getItemAtIndex(this.currentIndex);
-              if (currentItem) {
-                this.selectItem(currentItem);
-              }
-            }
-          }
-          this._lastKeyTime = 0;
-        ]]>
-      </handler>
-
-      <handler event="keypress" phase="target">
-        <![CDATA[
-          if (!event.charCode || event.altKey || event.ctrlKey || event.metaKey)
-            return;
-
-          if (event.timeStamp - this._lastKeyTime > 1000)
-            this._incrementalString = "";
-
-          var key = String.fromCharCode(event.charCode).toLowerCase();
-          this._incrementalString += key;
-          this._lastKeyTime = event.timeStamp;
-
-          // If all letters in the incremental string are the same, just
-          // try to match the first one
-          var incrementalString = /^(.)\1+$/.test(this._incrementalString) ?
-                                  RegExp.$1 : this._incrementalString;
-          var length = incrementalString.length;
-
-          var rowCount = this.getRowCount();
-          var l = this.selectedItems.length;
-          var start = l > 0 ? this.getIndexOfItem(this.selectedItems[l - 1]) : -1;
-          // start from the first element if none was selected or from the one
-          // following the selected one if it's a new or a repeated-letter search
-          if (start == -1 || length == 1)
-            start++;
-
-          for (var i = 0; i < rowCount; i++) {
-            var k = (start + i) % rowCount;
-            var listitem = this.getItemAtIndex(k);
-            if (!this._canUserSelect(listitem))
-              continue;
-            // allow richlistitems to specify the string being searched for
-            var searchText = "searchLabel" in listitem ? listitem.searchLabel :
-                             listitem.getAttribute("label"); // (see also bug 250123)
-            searchText = searchText.substring(0, length).toLowerCase();
-            if (searchText == incrementalString) {
-              this.ensureIndexIsVisible(k);
-              this.timedSelect(listitem, this._selectDelay);
-              break;
-            }
-          }
-        ]]>
-      </handler>
-
-      <handler event="click">
-        <![CDATA[
-          // clicking into nothing should unselect
-          if (event.originalTarget == this) {
-            this.clearSelection();
-            this.currentItem = null;
-          }
-        ]]>
-      </handler>
-
-      <handler event="MozSwipeGesture">
-        <![CDATA[
-          // Only handle swipe gestures up and down
-          switch (event.direction) {
-            case event.DIRECTION_DOWN:
-              this.scrollTop = this.scrollHeight;
-              break;
-            case event.DIRECTION_UP:
-              this.scrollTop = 0;
-              break;
-          }
-        ]]>
-      </handler>
-    </handlers>
-  </binding>
-</bindings>
--- a/mail/base/content/attachmentList.css
+++ b/mail/base/content/attachmentList.css
@@ -1,23 +1,23 @@
 /* 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/. */
 
 .attachmentList {
   -moz-appearance: listbox;
   -moz-user-focus: normal;
-  margin: 0;
 }
 
-.attachmentlist-wrapper {
+.attachmentList[orient="horizontal"] {
   display: block;
-  margin: 0;
-  padding: 0;
-  line-height: 0;
+}
+
+.attachmentList[collapsed] {
+  height: 0;
 }
 
 .attachmentItem {
   -moz-binding: url("chrome://messenger/content/mailWidgets.xml#attachmentitem");
 }
 
 .attachmentcell-content {
   -moz-box-orient: horizontal;
--- a/mail/base/content/mailWidgets.js
+++ b/mail/base/content/mailWidgets.js
@@ -1130,20 +1130,17 @@ customElements.define("mail-multi-emailh
  *
  * @extends {MozElements.RichListBox}
  */
 class MozAttachmentlist extends MozElements.RichListBox {
   constructor() {
     super();
 
     this.messenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
-    this._setupEventListeners();
-  }
 
-  _setupEventListeners() {
     this.addEventListener("keypress", (event) => {
       // The spacebar should work just like the arrow keys, except that the
       // focused element doesn't change, so use moveByOffset here.
       if (event.keyCode == KeyEvent.DOM_VK_SPACE) {
         this.moveByOffset(0, !event.ctrlKey, event.shiftKey);
         event.preventDefault();
       } else if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
         if (this.currentItem) {
--- a/mail/base/content/msgHdrView.js
+++ b/mail/base/content/msgHdrView.js
@@ -2554,18 +2554,16 @@ function toggleAttachmentList(expanded, 
                                       .getBoundingClientRect().height / 4;
 
     attachmentView.setAttribute("height", Math.min(attachmentHeight,
                                                    maxAttachmentHeight));
     attachmentView.setAttribute("maxheight", attachmentHeight);
 
     if (updateFocus)
       attachmentList.focus();
-
-    attachmentList.selectItem(attachmentList.firstChild);
   } else {
     attachmentList.collapsed = true;
     attachmentSplitter.collapsed = true;
     attachmentBar.setAttribute("tooltiptext", bundle.getString(
       "expandAttachmentPaneTooltip"));
     attachmentView.removeAttribute("height");
     attachmentView.removeAttribute("maxheight");
 
--- a/mail/base/jar.mn
+++ b/mail/base/jar.mn
@@ -34,17 +34,16 @@ messenger.jar:
     content/messenger/customElements.js             (content/customElements.js)
     content/messenger/customizeToolbar.css          (../../common/src/customizeToolbar.css)
     content/messenger/customizeToolbar.js           (../../common/src/customizeToolbar.js)
 *   content/messenger/customizeToolbar.xul          (../../common/src/customizeToolbar.xul)
     content/messenger/viewSource.js                 (../../common/src/viewSource.js)
 *   content/messenger/viewSource.xul                (../../common/src/viewSource.xul)
     content/messenger/viewZoomOverlay.js            (../../common/src/viewZoomOverlay.js)
     content/messenger/generalBindings.js            (../../common/bindings/generalBindings.js)
-    content/messenger/richlistbox.xml               (../../common/bindings/richlistbox.xml)
 *   content/messenger/toolbar.xml                   (../../common/bindings/toolbar.xml)
     content/messenger/attachmentList.css            (content/attachmentList.css)
     content/messenger/menulist.css                  (content/menulist.css)
 *   content/messenger/bindings.css                  (content/bindings.css)
     content/messenger/nsDragAndDrop.js              (content/nsDragAndDrop.js)
     content/messenger/editContactPanel.js           (content/editContactPanel.js)
     content/messenger/msgMail3PaneWindow.js         (content/msgMail3PaneWindow.js)
     content/messenger/mail3PaneWindowCommands.js    (content/mail3PaneWindowCommands.js)
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -5299,18 +5299,18 @@ function attachmentBucketOnDragStart(aEv
   nsDragAndDrop.startDrag(aEvent, attachmentBucketDNDObserver);
 }
 
 function attachmentBucketOnClick(aEvent) {
   // Handle click on attachment pane whitespace:
   // - With selected attachments, clear selection first.
   // - Otherwise, e.g. on a plain empty bucket, show 'Attach File(s)' dialog.
   if (attachmentsSelectedCount() == 0) {
-    let boundTarget = document.getBindingParent(aEvent.originalTarget);
-    if (aEvent.button == 0 && boundTarget && boundTarget.localName == "scrollbox")
+    let boundTarget = aEvent.originalTarget;
+    if (aEvent.button == 0 && boundTarget && boundTarget.getAttribute("is") == "attachment-list")
       goDoCommand("cmd_attachFile");
   }
 }
 
 function attachmentBucketOnSelect() {
   attachmentBucketUpdateTooltips();
   updateAttachmentItems();
 }
--- a/mail/themes/windows/mail/attachmentList.css
+++ b/mail/themes/windows/mail/attachmentList.css
@@ -26,17 +26,17 @@
     border: 1px solid transparent;
     color: -moz-FieldText !important;
     background-color: transparent;
     background-repeat: no-repeat;
     background-size: 100% 100%;
     padding: 1px;
   }
 
-  .attachmentlist-wrapper {
+  .attachmentlist[orient="horizontal"] {
     margin-inline-end: 1px;
     margin-bottom: 1px;
   }
 
   .attachmentList[orient="horizontal"] .attachmentItem {
     margin-top: 1px;
     margin-inline-start: 1px;
   }