Implement cmd_toggleAttachmentPane (Alt+M), cmd_removeAllAttachments, polish attachment bucket behaviour and keyboard access, code cleanup. r=aceman, ui-r=Paenglab
☠☠ backed out by 52e0316a7f8e ☠ ☠
authorThomas Duellmann <bugzilla2007@duellmann24.net>
Mon, 12 Feb 2018 21:04:17 +0200
changeset 31282 13aff03dc4c5f1564ecbca7cf30083bb4f4252a7
parent 31281 6654b9108d7e46f720e1581b121bd2baa1a3f519
child 31283 9872aea0db403365a32f69ec096e5be5d8cbb8c0
push id383
push userclokep@gmail.com
push dateMon, 07 May 2018 21:52:48 +0000
reviewersaceman, Paenglab
Implement cmd_toggleAttachmentPane (Alt+M), cmd_removeAllAttachments, polish attachment bucket behaviour and keyboard access, code cleanup. r=aceman, ui-r=Paenglab
mail/components/compose/content/MsgComposeCommands.js
mail/components/compose/content/messengercompose.xul
mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
mail/themes/linux/mail/compose/messengercompose.css
mail/themes/osx/mail/compose/messengercompose.css
mail/themes/windows/mail/compose/messengercompose.css
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -79,17 +79,16 @@ var gMsgAttachmentElement;
 var gMsgHeadersToolbarElement;
 // TODO: Maybe the following two variables can be combined.
 var gManualAttachmentReminder;
 var gDisableAttachmentReminder;
 var gComposeType;
 var gLanguageObserver;
 var gBodyFromArgs;
 
-
 // i18n globals
 var gCharsetConvertManager;
 var _gComposeBundle;
 function getComposeBundle() {
   // That one has to be lazy. Getting a reference to an element with a XBL
   // binding attached will cause the XBL constructors to fire if they haven't
   // already. If we get a reference to the compose bundle at script load-time,
   // this will cause the XBL constructor that's responsible for the personas to
@@ -671,16 +670,38 @@ var defaultController = {
       isEnabled: function() {
         return !gWindowLocked;
       },
       doCommand: function() {
         AttachPage();
       }
     },
 
+    cmd_toggleAttachmentPane: {
+      isEnabled: function() {
+        let cmdToggleAttachmentPane =
+          document.getElementById("cmd_toggleAttachmentPane");
+        let bucket = GetMsgAttachmentElement();
+        let paneShown = !document.getElementById("attachments-box").collapsed;
+        if (!paneShown) {
+          cmdToggleAttachmentPane.setAttribute("checked", "false");
+        } else {
+          cmdToggleAttachmentPane.setAttribute("checked", "true");
+        }
+
+        // Enable this command when the compose window isn't locked;
+        // disable for full, visible bucket (effective for menu only; command's
+        // shortcut key will still work via bucket's identical access key).
+        return (!gWindowLocked && !(bucket.itemCount > 0 && paneShown))
+      },
+      doCommand: function() {
+        toggleAttachmentPane();
+      }
+    },
+
     cmd_reorderAttachments: {
       isEnabled: function() {
         if (attachmentsCount() == 0) {
           let reorderAttachmentsPanel =
             document.getElementById("reorderAttachmentsPanel");
           if (reorderAttachmentsPanel.state == "open") {
             // When the panel is open and all attachments get deleted,
             // we get notified here and want to close the panel.
@@ -689,16 +710,25 @@ var defaultController = {
         }
         return (attachmentsCount() > 1);
       },
       doCommand: function() {
         showReorderAttachmentsPanel();
       }
     },
 
+    cmd_removeAllAttachments: {
+      isEnabled: function() {
+        return !gWindowLocked && attachmentsCount() > 0;
+      },
+      doCommand: function() {
+        RemoveAllAttachments();
+      }
+    },
+
     cmd_close: {
       isEnabled: function() {
         return !gWindowLocked;
       },
       doCommand: function() {
         DoCommandClose();
       }
     },
@@ -1028,27 +1058,30 @@ var attachmentBucketController = {
       },
       doCommand: function() {
         moveSelectedAttachments("bottom");
       }
     },
 
     cmd_sortAttachmentsToggle: {
       isEnabled: function() {
+        let attachmentsSelCount = attachmentsSelectedCount();
         let sortSelection;
         let currSortOrder;
         let isBlock;
         let btnAscending;
         let toggleCmd = document.getElementById("cmd_sortAttachmentsToggle");
         let toggleBtn = document.getElementById("btn_sortAttachmentsToggle");
         let sortDirection;
         let btnLabelAttr;
 
-        if (attachmentsSelectedCount() > 1) {
-          // Sort selected attachments only.
+        if (attachmentsSelCount > 1 &&
+            attachmentsSelCount < attachmentsCount()) {
+          // Sort selected attachments only, which needs at least 2 of them,
+          // but not all.
           sortSelection = true;
           currSortOrder = attachmentsSelectionGetSortOrder();
           isBlock = attachmentsSelectionIsBlock();
           // If current sorting is ascending AND it's a block; OR
           // if current sorting is descending AND it's NOT a block yet:
           // Offer toggle button face to sort descending.
           // In all other cases, offer toggle button face to sort ascending.
           btnAscending = !((currSortOrder == "ascending") && isBlock ||
@@ -1056,17 +1089,17 @@ var attachmentBucketController = {
           // Set sortDirection for toggleCmd, and respective button face.
           if (btnAscending) {
             sortDirection = "ascending";
             btnLabelAttr = "label-selection-AZ";
           } else {
             sortDirection = "descending";
             btnLabelAttr = "label-selection-ZA";
           }
-        } else { // attachmentsSelectedCount() <= 1
+        } else { // attachmentsSelectedCount() <= 1 or all attachments selected
           // Sort all attachments.
           sortSelection = false;
           currSortOrder = attachmentsGetSortOrder();
           btnAscending = !(currSortOrder == "ascending")
           // Set sortDirection for toggleCmd, and respective button face.
           if (btnAscending) {
             sortDirection = "ascending";
             btnLabelAttr = "label-AZ";
@@ -1406,23 +1439,30 @@ function updateEditItems()
   goUpdateCommand("cmd_selectAll");
   goUpdateCommand("cmd_openAttachment");
   goUpdateCommand("cmd_findReplace");
   goUpdateCommand("cmd_find");
   goUpdateCommand("cmd_findNext");
   goUpdateCommand("cmd_findPrev");
 }
 
+function updateViewItems()
+{
+  goUpdateCommand("cmd_toggleAttachmentPane");
+}
+
 function updateAttachmentItems()
 {
+  goUpdateCommand("cmd_toggleAttachmentPane");
   goUpdateCommand("cmd_attachCloud");
   goUpdateCommand("cmd_convertCloud");
   goUpdateCommand("cmd_convertAttachment");
   goUpdateCommand("cmd_cancelUpload");
   goUpdateCommand("cmd_delete");
+  goUpdateCommand("cmd_removeAllAttachments");
   goUpdateCommand("cmd_renameAttachment");
   updateReorderAttachmentsItems();
   goUpdateCommand("cmd_selectAll");
   goUpdateCommand("cmd_openAttachment");
 }
 
 function updateReorderAttachmentsItems() {
   goUpdateCommand("cmd_reorderAttachments");
@@ -2338,16 +2378,17 @@ attachmentWorker.onmessage = function(ev
   if (aManage)
     manageAttachmentNotification(true);
 };
 
 /**
  * Called when number of attachments changes.
  */
 function AttachmentsChanged() {
+  attachmentBucketMarkEmptyBucket();
   manageAttachmentNotification(true);
   updateAttachmentItems();
 }
 
 /**
  * This functions returns a valid spellcheck language. It checks that a
  * dictionary exists for the language passed in, if any. It also retrieves the
  * corresponding preference and ensures that a dictionary exists. If not, it
@@ -3068,16 +3109,18 @@ function ComposeLoad()
   // initialize the customizeDone method on the customizeable toolbar
   var toolbox = document.getElementById("compose-toolbox");
   toolbox.customizeDone = function(aEvent) { MailToolboxCustomizeDone(aEvent, "CustomizeComposeToolbar"); };
 
   var toolbarset = document.getElementById('customToolbars');
   toolbox.toolbarset = toolbarset;
 
   awInitializeNumberOfRowsShown();
+  updateAttachmentPane();
+  attachmentBucketMarkEmptyBucket();
 }
 
 function ComposeUnload()
 {
   // Send notification that the window is going away completely.
   document.getElementById("msgcomposeWindow").dispatchEvent(
     new Event("compose-window-unload", { bubbles: false, cancelable: false }));
 
@@ -4504,21 +4547,36 @@ function AddAttachments(aAttachments, aC
 
     items.push(item);
 
     if (aCallback)
       aCallback(item);
   }
 
   if (addedAttachments.length > 0) {
+    // If no attachment item has had focus yet (currentIndex == -1, or undefined
+    // on some platforms according to spec), make sure there's at least one item
+    // set as currentItem which will be focused when listbox gets focus, because
+    // currently we don't indicate focus on the listbox itself when there are
+    // attachments, assuming that one of them has focus.
+    if (!(bucket.currentIndex >= 0)) {
+      bucket.currentIndex = bucket.getIndexOfItem(items[0]);
+    }
+
     gContentChanged = true;
 
-    UpdateAttachmentBucket(true);
+    updateAttachmentPane("show");
     dispatchAttachmentBucketEvent("attachments-added", addedAttachments);
     AttachmentsChanged();
+  } else if (attachmentsCount() > 0) {
+    // We didn't succeed to add attachments (e.g. duplicate files),
+    // but user was trying to; so we must at least react by ensuring the panel
+    // is shown, which might be hidden by user with existing attachments.
+    // XXX To do: don't allow traceless hiding of pane with attachments.
+    toggleAttachmentPane("show");
   }
 
   return items;
 }
 
 /**
  * Get the number of all attachments of the message.
  *
@@ -4703,114 +4761,144 @@ function Attachments2CompFields(compFiel
     if (attachment)
       compFields.addAttachment(attachment);
   }
 }
 
 function RemoveAllAttachments()
 {
   let bucket = document.getElementById("attachmentBucket");
+  if (bucket.itemCount == 0)
+    return;
+
+  let fileHandler = Services.io.getProtocolHandler("file")
+                            .QueryInterface(Ci.nsIFileProtocolHandler);
   let removedAttachments = Cc["@mozilla.org/array;1"]
                              .createInstance(Ci.nsIMutableArray);
 
-  while (bucket.getRowCount())
-  {
-    let child = bucket.removeItemAt(bucket.getRowCount() - 1);
-
-    removedAttachments.appendElement(child.attachment);
+  while (bucket.itemCount > 0) {
+    let item = bucket.removeItemAt(bucket.itemCount - 1);
+    if (item.attachment.size != -1) {
+      gAttachmentsSize -= item.attachment.size;
+    }
+
+    if (item.attachment.sendViaCloud && item.cloudProvider) {
+      let originalUrl = item.originalUrl;
+      if (!originalUrl)
+        originalUrl = item.attachment.url;
+      let file = fileHandler.getFileFromURLSpec(originalUrl);
+      if (item.uploading)
+        item.cloudProvider.cancelFileUpload(file);
+      else
+        item.cloudProvider.deleteFile(file,
+          new deletionListener(item.attachment, item.cloudProvider));
+    }
+
+    removedAttachments.appendElement(item.attachment);
     // Let's release the attachment object hold by the node else it won't go
-    // away until the window is destroyed
-    child.attachment = null;
+    // away until the window is destroyed.
+    item.attachment = null;
   }
 
   if (removedAttachments.length > 0) {
     // Bug workaround: Force update of selectedCount and selectedItem.
     bucket.clearSelection();
-
+    updateAttachmentPane("show");
     gContentChanged = true;
-
     dispatchAttachmentBucketEvent("attachments-removed", removedAttachments);
-    UpdateAttachmentBucket(false);
     AttachmentsChanged();
   }
 }
 
 /**
- * Show or hide the attachment pane after updating its header bar information
- * (number and total file size of attachments).
+ * Update the header bar information (number and total file size of attachments)
+ * and tooltip of attachment pane, then (optionally) show or hide the pane.
  *
- * @param aShowPane {boolean} true:  show the attachment pane
- *                            false: hide the attachment pane
+ * @param aShowPane {string} "show":  show the attachment pane
+ *                           "hide":  hide the attachment pane
+ *                           omitted: just update without changing pane visibility
  */
-function UpdateAttachmentBucket(aShowPane)
-{
-  let count = document.getElementById("attachmentBucket").getRowCount();
+function updateAttachmentPane(aShowPane)
+{
+  let bucket = GetMsgAttachmentElement();
+  let bucketCountLabel = document.getElementById("attachmentBucketCount");
   let words = getComposeBundle().getString("attachmentCount");
+  let count = bucket.itemCount;
   let countStr = PluralForm.get(count, words).replace("#1", count);
 
-  document.getElementById("attachmentBucketCount").value = countStr;
+  bucketCountLabel.value = countStr;
   document.getElementById("attachmentBucketSize").value =
-    gMessenger.formatFileSize(gAttachmentsSize);
-
+    (count > 0) ? gMessenger.formatFileSize(gAttachmentsSize)
+                : "";
+  document.getElementById("attachmentBucketCloseButton").collapsed = count > 0;
+
+  attachmentBucketUpdateTooltips();
+
+  // If aShowPane argument is omitted, it's just updating, so we're done.
+  if (aShowPane === undefined)
+    return;
+
+  // Otherwise, show or hide the panel per aShowPane argument.
   toggleAttachmentPane(aShowPane);
 }
 
 function RemoveSelectedAttachment()
 {
-  let bucket = document.getElementById("attachmentBucket");
-  if (bucket.selectedItems.length > 0) {
-    // Remember the current focus index so we can try to restore it when done.
-    let focusIndex = bucket.currentIndex;
-
-    let fileHandler = Services.io.getProtocolHandler("file")
-                              .QueryInterface(Ci.nsIFileProtocolHandler);
-    let removedAttachments = Cc["@mozilla.org/array;1"]
-                               .createInstance(Ci.nsIMutableArray);
-
-    for (let i = bucket.selectedCount - 1; i >= 0; i--) {
-      let item = bucket.removeItemAt(bucket.getIndexOfItem(bucket.getSelectedItem(i)));
-      if (item.attachment.size != -1) {
-        gAttachmentsSize -= item.attachment.size;
-        UpdateAttachmentBucket(true);
-      }
-
-      if (item.attachment.sendViaCloud && item.cloudProvider) {
-        let originalUrl = item.originalUrl;
-        if (!originalUrl)
-          originalUrl = item.attachment.url;
-        let file = fileHandler.getFileFromURLSpec(originalUrl);
-        if (item.uploading)
-          item.cloudProvider.cancelFileUpload(file);
-        else
-          item.cloudProvider.deleteFile(file,
-            new deletionListener(item.attachment, item.cloudProvider));
-      }
-
-      removedAttachments.appendElement(item.attachment);
-      // Let's release the attachment object held by the node else it won't go
-      // away until the window is destroyed
-      item.attachment = null;
-    }
-
-    // Bug workaround: Force update of selectedCount and selectedItem, both wrong
-    // after item removal, to avoid confusion for listening command controllers.
-    bucket.clearSelection();
-
-    // Try to restore original focus or somewhere close by.
-    bucket.currentIndex = (focusIndex < bucket.itemCount) ?   // If possible,
-                          focusIndex   // restore focus at original position;
-                        : ( (bucket.itemCount > 0) ? // else: if attachments exist,
-                            (bucket.itemCount - 1)   // focus last item;
-                          : -1)                      // else: nothing to focus.
-
-    gContentChanged = true;
-
-    dispatchAttachmentBucketEvent("attachments-removed", removedAttachments);
-    AttachmentsChanged();
-  }
+  let bucket = GetMsgAttachmentElement();
+  if (bucket.selectedCount == 0)
+    return;
+
+  // Remember the current focus index so we can try to restore it when done.
+  let focusIndex = bucket.currentIndex;
+
+  let fileHandler = Services.io.getProtocolHandler("file")
+                            .QueryInterface(Ci.nsIFileProtocolHandler);
+  let removedAttachments = Cc["@mozilla.org/array;1"]
+                             .createInstance(Ci.nsIMutableArray);
+
+  for (let i = bucket.selectedCount - 1; i >= 0; i--) {
+    let item = bucket.removeItemAt(bucket.getIndexOfItem(bucket.getSelectedItem(i)));
+    if (item.attachment.size != -1) {
+      gAttachmentsSize -= item.attachment.size;
+    }
+
+    if (item.attachment.sendViaCloud && item.cloudProvider) {
+      let originalUrl = item.originalUrl;
+      if (!originalUrl)
+        originalUrl = item.attachment.url;
+      let file = fileHandler.getFileFromURLSpec(originalUrl);
+      if (item.uploading)
+        item.cloudProvider.cancelFileUpload(file);
+      else
+        item.cloudProvider.deleteFile(file,
+          new deletionListener(item.attachment, item.cloudProvider));
+    }
+
+    removedAttachments.appendElement(item.attachment);
+    // Let's release the attachment object held by the node else it won't go
+    // away until the window is destroyed
+    item.attachment = null;
+  }
+
+  updateAttachmentPane();
+
+  // Bug workaround: Force update of selectedCount and selectedItem, both wrong
+  // after item removal, to avoid confusion for listening command controllers.
+  bucket.clearSelection();
+
+  // Try to restore original focus or somewhere close by.
+  bucket.currentIndex = (focusIndex < bucket.itemCount) ?   // If possible,
+                        focusIndex   // restore focus at original position;
+                      : ( (bucket.itemCount > 0) ? // else: if attachments exist,
+                          (bucket.itemCount - 1)   // focus last item;
+                        : -1)                      // else: nothing to focus.
+
+  gContentChanged = true;
+  dispatchAttachmentBucketEvent("attachments-removed", removedAttachments);
+  AttachmentsChanged();
 }
 
 function RenameSelectedAttachment()
 {
   let bucket = document.getElementById("attachmentBucket");
   if (bucket.selectedItems.length != 1)
     return; // not one attachment selected
 
@@ -5077,29 +5165,70 @@ function moveSelectedAttachments(aDirect
   bucket.ensureIndexIsVisible(visibleIndex);
 
   // Moving selected items around does not trigger auto-updating of our command
   // handlers, so we must do it now as the position of selected items has changed.
   updateReorderAttachmentsItems();
 }
 
 /**
- * Show or hide the attachment pane.
+ * Toggle attachment pane view state: show or hide it (only if bucket is empty).
+ * If aAction parameter is omitted, auto-cycling of view states, bias for "show".
+ * Note: In the current UI layout, forcing "hide" is not recommended for full
+ *       bucket as it may violate ux-error-prevention.
  *
- * @param aShow {boolean} true:  show the attachment pane
- *                        false: hide the attachment pane
+ * @param aAction {string} "show":  show attachment pane
+ *                         "hide":  hide attachment pane
+ *                         "focus": focus the attachment pane
+ *                         "toggle": toggle attachment pane visibility
  */
-function toggleAttachmentPane(aShow) {
-  document.getElementById("attachments-box").collapsed = !aShow;
-  document.getElementById("attachmentbucket-sizer").collapsed = !aShow;
+function toggleAttachmentPane(aAction = "toggle") {
+  let bucket = GetMsgAttachmentElement();
+  let attachmentsBox = document.getElementById("attachments-box");
+  let emptyBucket = (bucket.itemCount == 0);
+  let bucketHasFocus = (document.activeElement == bucket);
+  let attachmentBucketSizer = document.getElementById("attachmentbucket-sizer");
+
+  if (aAction == "toggle") {
+    if (!attachmentsBox.collapsed && bucket.itemCount == 0) {
+      // Menu click (View > Attachment Pane) with empty, shown bucket: Hide it.
+      aAction = "hide"
+    } else {
+      // Menu click with hidden bucket (empty or full)
+      // or cmd_toggleAttachmentPane via shortcut key;
+      // we disable the menu to prevent hiding full and shown bucket.
+      aAction = "show";
+    }
+  }
+
+  switch (aAction) {
+    case "show":
+      attachmentsBox.collapsed = false;
+      attachmentBucketSizer.collapsed = false;
+      if (!bucketHasFocus)
+        bucket.focus();
+      break;
+
+    case "hide":
+      if (bucketHasFocus)
+        SetMsgBodyFrameFocus();
+      attachmentsBox.collapsed = true;
+      attachmentBucketSizer.collapsed = true;
+      break;
+
+    case "focus":
+      bucket.focus();
+  }
+
+  goUpdateCommand("cmd_toggleAttachmentPane");
 }
 
 function showReorderAttachmentsPanel() {
   // Ensure attachment pane visibility as it might be collapsed.
-  toggleAttachmentPane(true);
+  toggleAttachmentPane("show");
   showPopupById("reorderAttachmentsPanel", "attachmentBucket",
                 "after_start", 15, 0);
   // After the panel is shown, focus attachmentBucket so that keyboard
   // operation for selecting and moving attachment items works; the panel
   // helpfully presents the keyboard shortcuts for moving things around.
   // Bucket focus is also required because the panel will only close with ESC
   // or attachmentBucketOnBlur(), and that's because we're using noautohide as
   // event.preventDefault() of onpopuphiding event fails when the panel
@@ -5208,32 +5337,110 @@ function attachmentBucketOnBlur() {
     return;
   }
   let reorderAttachmentsPanel = document.getElementById("reorderAttachmentsPanel");
   if (document.activeElement.id != "attachmentBucket" ||
       document.activeElement.id != "reorderAttachmentsPanel")
     reorderAttachmentsPanel.hidePopup();
 }
 
-function attachmentBucketOnKeyUp(aEvent) {
-  // When ESC is pressed, close reorderAttachmentsPanel.
+function attachmentBucketOnKeyPress(aEvent) {
+  let bucket = GetMsgAttachmentElement();
+
+  // When ESC is pressed ...
   if (aEvent.key == "Escape") {
     let reorderAttachmentsPanel = document.getElementById("reorderAttachmentsPanel");
     if (reorderAttachmentsPanel.state == "open") {
+      // First close reorderAttachmentsPanel if open.
       reorderAttachmentsPanel.hidePopup();
-    }
+    } else if (bucket.itemCount > 0) {
+      if (bucket.selectedCount > 0) {
+        // Then deselect selected items in full bucket if any.
+        bucket.clearSelection();
+      } else {
+        // Then unfocus full bucket to continue with msg body.
+        SetMsgBodyFrameFocus();
+      }
+    } else {  // (bucket.itemCount == 0)
+      // Otherwise close empty bucket.
+      toggleAttachmentPane("hide");
+    }
+  }
+
+  if (aEvent.key == "Enter" && bucket.itemCount == 0) {
+    // Enter on empty bucket to add file attachments, convenience
+    // keyboard equivalent of single-click on bucket whitespace.
+    goDoCommand("cmd_attachFile");
+  }
+
+  if (aEvent.key == "m" && aEvent.altKey) {
+    toggleAttachmentPane();
+  }
+}
+
+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")
+      goDoCommand("cmd_attachFile");
+  }
+}
+
+function attachmentBucketOnSelect() {
+  attachmentBucketUpdateTooltips();
+  updateAttachmentItems();
+}
+
+
+function attachmentBucketUpdateTooltips() {
+  let bucket = GetMsgAttachmentElement();
+  let bucketHeader = document.getElementById("attachments-header-box");
+
+  // Attachment pane whitespace tooltip
+  if (attachmentsSelectedCount() > 0) {
+    bucket.tooltipText=
+      getComposeBundle().getString("attachmentBucketClearSelectionTooltip");
+  } else {
+    bucket.tooltipText=
+      getComposeBundle().getString("attachmentBucketAttachFilesTooltip");
+  }
+}
+
+function attachmentBucketHeaderOnClick() {
+  toggleAttachmentPane("focus");
+}
+
+function attachmentBucketSizerOnMouseUp() {
+  updateViewItems();
+  if (document.getElementById("attachments-box").collapsed) {
+    // If user collapsed the attachment pane, move focus to message body.
+    SetMsgBodyFrameFocus();
   }
 }
 
 function AttachmentElementHasItems()
 {
   var element = document.getElementById("attachmentBucket");
   return element ? (element.getRowCount() > 0) : false;
 }
 
+function attachmentBucketMarkEmptyBucket() {
+  let attachmentBucket = GetMsgAttachmentElement();
+  let bucketSizer = document.getElementById("attachmentbucket-sizer");
+  if (attachmentBucket.itemCount > 0) {
+    attachmentBucket.removeAttribute("empty");
+  } else {
+    attachmentBucket.setAttribute("empty", "true");
+  }
+}
+
 function OpenSelectedAttachment()
 {
   let bucket = document.getElementById("attachmentBucket");
   if (bucket.selectedItems.length == 1)
   {
     let attachmentUrl = bucket.getSelectedItem(0).attachment.url;
 
     let messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
@@ -5596,23 +5803,16 @@ function fromKeyPress(event)
 
 function subjectKeyPress(event)
 {
   gSubjectChanged = true;
   if (event.keyCode == KeyEvent.DOM_VK_RETURN)
     SetMsgBodyFrameFocus();
 }
 
-function AttachmentBucketDoubleClicked(event)
-{
-  let boundTarget = document.getBindingParent(event.originalTarget);
-  if (event.button == 0 && boundTarget && boundTarget.localName == "scrollbox")
-    goDoCommand('cmd_attachFile');
-}
-
 // we can drag and drop addresses, files, messages and urls into the compose envelope
 var envelopeDragObserver = {
 
   canHandleMultipleItems: true,
 
   /**
    * Adjust the drop target when dragging from the attachment bucket onto itself
    * by picking the nearest possible insertion point (generally, between two
@@ -5906,22 +6106,20 @@ var envelopeDragObserver = {
         // Indicate that we can't drop here.
         this._hideDropMarker();
         aEvent.dataTransfer.dropEffect = "none";
       }
       return;
     }
 
     if (aFlavour.contentType != "text/x-moz-address") {
-      // make sure the attachment box is visible during drag over
-      let attachmentBox = document.getElementById("attachments-box");
-      UpdateAttachmentBucket(true);
-    }
-    else {
-        DragAddressOverTargetControl(aEvent);
+      // Make sure the attachment pane is visible during drag over.
+      updateAttachmentPane("show");
+    } else {
+      DragAddressOverTargetControl(aEvent);
     }
   },
 
   onDragExit: function (aEvent, aDragSession)
   {
     this._hideDropMarker();
   },
 
--- a/mail/components/compose/content/messengercompose.xul
+++ b/mail/components/compose/content/messengercompose.xul
@@ -104,20 +104,24 @@
   <commandset id="composerEditMenuItems"/>
   <commandset id="composerStyleMenuItems"/>
   <commandset id="composerTableMenuItems"/>
   <commandset id="composerListMenuItems"/>
 
   <!-- File Menu -->
   <command id="cmd_new" oncommand="goDoCommand('cmd_newMessage')"/>
   <command id="cmd_attachFile" oncommand="goDoCommand('cmd_attachFile')"/>
-  <command id="cmd_attachCloud" oncommand="attachToCloud(event.target.cloudProvider); event.stopPropagation();"/>
+  <command id="cmd_attachCloud"
+           oncommand="attachToCloud(event.target.cloudProvider);
+                      event.stopPropagation();"/>
   <command id="cmd_attachPage" oncommand="goDoCommand('cmd_attachPage')"/>
-  <command id="cmd_attachVCard" checked="false" oncommand="ToggleAttachVCard(event.target)"/>
-  <command id="cmd_remindLater" checked="false" oncommand="toggleAttachmentReminder()"/>
+  <command id="cmd_attachVCard" checked="false"
+           oncommand="ToggleAttachVCard(event.target)"/>
+  <command id="cmd_remindLater" checked="false"
+           oncommand="toggleAttachmentReminder()"/>
   <command id="cmd_close" oncommand="goDoCommand('cmd_close')"/>
   <command id="cmd_saveDefault" oncommand="goDoCommand('cmd_saveDefault')"/>
   <command id="cmd_saveAsFile" oncommand="goDoCommand('cmd_saveAsFile')"/>
   <command id="cmd_saveAsDraft" oncommand="goDoCommand('cmd_saveAsDraft')"/>
   <command id="cmd_saveAsTemplate" oncommand="goDoCommand('cmd_saveAsTemplate')"/>
   <command id="cmd_sendButton" oncommand="goDoCommand('cmd_sendButton')"/>
   <command id="cmd_sendNow" oncommand="goDoCommand('cmd_sendNow')"/>
   <command id="cmd_sendWithCheck" oncommand="goDoCommand('cmd_sendWithCheck')"/>
@@ -139,22 +143,27 @@
   <command id="cmd_delete"
            oncommand="goDoCommand('cmd_delete')"
            valueDefault="&deleteCmd.label;"
            valueDefaultAccessKey="&deleteCmd.accesskey;"
            valueRemoveAttachmentAccessKey="&removeAttachment.accesskey;"
            disabled="true"/>
   <command id="cmd_selectAll"
            oncommand="goDoCommand('cmd_selectAll')" disabled="true"/>
+  <command id="cmd_removeAllAttachments"
+           oncommand="goDoCommand('cmd_removeAllAttachments')"/>
   <command id="cmd_openAttachment"
            oncommand="goDoCommand('cmd_openAttachment')" disabled="true"/>
   <command id="cmd_renameAttachment"
            oncommand="goDoCommand('cmd_renameAttachment')" disabled="true"/>
   <command id="cmd_reorderAttachments"
            oncommand="goDoCommand('cmd_reorderAttachments')" disabled="true"/>
+  <command id="cmd_toggleAttachmentPane"
+           checked="false"
+           oncommand="goDoCommand('cmd_toggleAttachmentPane')"/>
   <command id="cmd_account"
            oncommand="goDoCommand('cmd_account')"/>
 
   <!-- Reorder Attachments Panel -->
   <command id="cmd_moveAttachmentUp"
            oncommand="goDoCommand('cmd_moveAttachmentUp')" disabled="true"/>
   <command id="cmd_moveAttachmentDown"
            oncommand="goDoCommand('cmd_moveAttachmentDown')" disabled="true"/>
@@ -264,23 +273,29 @@
        modifiers="accel, shift" observes="cmd_pasteNoFormatting"/>
   <key id="key_rewrap" key="&editRewrapCmd.key;" command="cmd_rewrap" modifiers="accel"/>
 #ifdef XP_MACOSX
   <key id="key_delete" keycode="VK_BACK" command="cmd_delete"/>
   <key id="key_delete2" keycode="VK_DELETE" command="cmd_delete"/>
   <key id="key_reorderAttachments"
        key="&reorderAttachmentsCmd.key;" modifiers="control"
        command="cmd_reorderAttachments"/>
+  <key id="key_toggleAttachmentPane"
+       key="&toggleAttachmentPaneCmd.key;" modifiers="control"
+       command="cmd_toggleAttachmentPane"/>
 #else
   <key id="key_delete" keycode="VK_DELETE" command="cmd_delete"/>
   <key id="key_renameAttachment" keycode="VK_F2"
        command="cmd_renameAttachment"/>
   <key id="key_reorderAttachments"
        key="&reorderAttachmentsCmd.key;" modifiers="alt"
        command="cmd_reorderAttachments"/>
+  <key id="key_toggleAttachmentPane"
+       key="&toggleAttachmentPaneCmd.key;" modifiers="alt"
+       command="cmd_toggleAttachmentPane"/>
 #endif
   <key id="key_selectAll" key="&selectAllCmd.key;" modifiers="accel"/>
   <key id="key_find" key="&findBarCmd.key;" command="cmd_find" modifiers="accel"/>
 #ifndef XP_MACOSX
   <key id="key_findReplace" key="&findReplaceCmd.key;" command="cmd_findReplace" modifiers="accel"/>
 #endif
   <key id="key_findNext" key="&findAgainCmd.key;" command="cmd_findNext" modifiers="accel"/>
   <key id="key_findPrev" key="&findPrevCmd.key;" command="cmd_findPrev" modifiers="accel, shift"/>
@@ -531,16 +546,21 @@
             type="checkbox"
             label="&remindLater.label;"
             accesskey="&remindLater.accesskey;"
             command="cmd_remindLater"/>
   <menuitem id="attachmentListContext_reorderItem"
             label="&reorderAttachments.label;"
             accesskey="&reorderAttachments.accesskey;"
             command="cmd_reorderAttachments"/>
+  <menuseparator id="attachmentListContext_removeAllSeparator"/>
+  <menuitem id="attachmentListContext_removeAllItem"
+            label="&removeAllAttachments.label;"
+            accesskey="&removeAllAttachments.accesskey;"
+            command="cmd_removeAllAttachments"/>
 </menupopup>
 
 <menupopup id="toolbar-context-menu"
            onpopupshowing="onViewToolbarsPopupShowing(event, 'compose-toolbox');">
   <menuseparator/>
   <menuitem id="CustomizeComposeToolbar"
             command="cmd_CustomizeComposeToolbar"
             label="&customizeToolbar.label;"
@@ -656,16 +676,17 @@
           <menuitem id="menu_close"
                     label="&closeCmd.label;"
                     key="key_close"
                     accesskey="&closeCmd.accesskey;"
                     command="cmd_close"/>
         </menupopup>
       </menu>
 
+      <!-- Edit Menu -->
       <menu id="menu_Edit" label="&editMenu.label;" accesskey="&editMenu.accesskey;">
         <menupopup id="menu_EditPopup" onpopupshowing="updateEditItems();">
           <menuitem id="menu_undo" label="&undoCmd.label;" key="key_undo" accesskey="&undoCmd.accesskey;" command="cmd_undo"/>
           <menuitem id="menu_redo" label="&redoCmd.label;" key="key_redo" accesskey="&redoCmd.accesskey;" command="cmd_redo"/>
           <menuseparator/>
           <menuitem id="menu_cut" label="&cutCmd.label;" key="key_cut" accesskey="&cutCmd.accesskey;" command="cmd_cut"/>
           <menuitem id="menu_copy" label="&copyCmd.label;" key="key_copy" accesskey="&copyCmd.accesskey;" command="cmd_copy"/>
           <menuitem id="menu_paste" label="&pasteCmd.label;" key="key_paste" accesskey="&pasteCmd.accesskey;" command="cmd_paste"/>
@@ -736,18 +757,19 @@
                     command="cmd_account"/>
           <menuitem id="menu_preferences" label="&preferencesCmdUnix.label;" accesskey="&preferencesCmdUnix.accesskey;"
                     oncommand="openOptionsDialog('paneCompose');"/>
 #endif
 #endif
         </menupopup>
       </menu>
 
+      <!-- View Menu -->
       <menu id="menu_View" label="&viewMenu.label;" accesskey="&viewMenu.accesskey;">
-        <menupopup id="menu_View_Popup">
+        <menupopup id="menu_View_Popup" onpopupshowing="updateViewItems();">
           <menu id="menu_ToolbarsNew"
                 label="&viewToolbarsMenuNew.label;"
                 accesskey="&viewToolbarsMenuNew.accesskey;"
                 onpopupshowing="onViewToolbarsPopupShowing(event, 'compose-toolbox');">
             <menupopup id="view_toolbars_popup">
               <menuitem id="menu_showFormatToolbar"
                         type="checkbox"
                         label="&showFormattingBarCmd.label;"
@@ -787,16 +809,22 @@
               <menuitem id="menu_fullZoomToggle" label="&fullZoomToggleCmd.label;"
                         accesskey="&fullZoomToggleCmd.accesskey;"
                         type="checkbox" command="cmd_fullZoomToggle" checked="false"/>
             </menupopup>
           </menu>
           <menuitem id="menu_AddressSidebar"
                     label="&addressSidebar.label;" accesskey="&addressSidebar.accesskey;"
                     key="key_addressSidebar" observes="viewAddressPicker"/>
+          <menuitem id="menu_toggleAttachmentPane"
+                    label="&toggleAttachmentPaneCmd.label;"
+                    accesskey="&toggleAttachmentPaneCmd.accesskey;"
+                    type="checkbox"
+                    key="key_toggleAttachmentPane"
+                    command="cmd_toggleAttachmentPane"/>
           <menuseparator id="viewMenuBeforeSecurityStatusSeparator"/>
           <menuitem id="menu_viewSecurityStatus"
                     label="&menu_viewSecurityStatus.label;"
                     accesskey="&menu_viewSecurityStatus.accesskey;"
                     command="cmd_viewSecurityStatus"/>
         </menupopup>
       </menu>
 
@@ -920,17 +948,17 @@
 
     <toolbarbutton class="toolbarbutton-1"
                id="button-contacts" label="&addressButton.label;"
                tooltiptext="&addressButton.tooltip;"
                observes="viewAddressPicker"/>
 
     <toolbarbutton class="toolbarbutton-1" type="menu-button"
                id="button-attach" label="&attachButton.label;"
-               tooltiptext="&attachButton.tooltip;"
+               tooltiptext="&attachButton.tooltip2;"
                command="cmd_attachFile"
                ondragover="nsDragAndDrop.dragOver(event, envelopeDragObserver);"
                ondrop="nsDragAndDrop.drop(event, envelopeDragObserver);"
                ondragexit="nsDragAndDrop.dragExit(event, envelopeDragObserver);">
       <menupopup id="button-attachPopup" onpopupshowing="updateAttachmentItems();">
         <menuitem id="button-attachPopup_attachFileItem"
                   label="&attachFileCmd.label;"
                   accesskey="&attachFileCmd.accesskey;"
@@ -1085,17 +1113,17 @@
            defaultset="button-send,separator,button-address,spellingButton,button-attach,button-security,button-save,spring"
 #else
            defaultset="button-send,separator,button-address,spellingButton,button-attach,button-security,button-save"
 #endif
            customizable="true" context="toolbar-context-menu">
   </toolbar>
 
   <toolbarset id="customToolbars" context="toolbar-context-menu"/>
-  </toolbox>
+</toolbox>
 
   <hbox flex="1" id="composeContentBox">
     <vbox id="sidebar-box" persist="sidebarVisible width" hidden="true">
       <sidebarheader id="sidebarHeader" align="center">
         <label id="sidebar-title" value="&addressesSidebarTitle.label;"/>
         <spacer flex="1"/>
         <toolbarbutton class="ab-closebutton close-icon" oncommand="toggleAddressPicker();"/>
       </sidebarheader>
@@ -1173,31 +1201,48 @@
             <hbox align="center" pack="end" style="&headersSpace.style;">
               <label id="subjectLabel" value="&subject.label;" accesskey="&subject.accesskey;" control="msgSubject"/>
             </hbox>
             <textbox id="msgSubject" flex="1" class="toolbar" disableonsend="true" spellcheck="true"
                      oninput="gContentChanged=true;SetComposeWindowTitle();"
                      onkeypress="subjectKeyPress(event);" />
           </hbox>
         </vbox>
-        <splitter id="attachmentbucket-sizer" collapsed="true" collapse="after"/>
+        <splitter id="attachmentbucket-sizer"
+                  collapsed="true"
+                  collapse="after"
+                  onmouseup="attachmentBucketSizerOnMouseUp();"/>
         <vbox id="attachments-box" collapsed="true" persist="width">
-          <hbox id="attachments-header-box" align="center">
-            <label id="attachmentBucketCount" control="attachmentBucket"
-                   crop="right" accesskey="&attachments.accesskey;" flex="1"/>
+          <hbox id="attachments-header-box"
+                align="center"
+                tooltiptext="&attachmentBucketHeader.tooltip;"
+                onclick="attachmentBucketHeaderOnClick();">
+            <label id="attachmentBucketCount"
+                   accesskey="&attachments.accesskey;"
+                   flex="1" crop="end"
+                   control="attachmentBucket"/>
+            <toolbarspring/>
             <label id="attachmentBucketSize"/>
+            <toolbarbutton id="attachmentBucketCloseButton"
+                           class="ab-closebutton close-icon"
+                           tooltiptext="&attachmentBucketCloseButton.tooltip;"
+                           oncommand="toggleAttachmentPane('hide');"/>
           </hbox>
-          <attachmentlist orient="vertical" id="attachmentBucket"
+          <attachmentlist id="attachmentBucket"
+                          tooltiptext=""
                           disableonsend="true"
-                          seltype="multiple" flex="1" height="0"
+                          seltype="multiple"
+                          orient="vertical"
+                          flex="1"
+                          height="0"
                           context="msgComposeAttachmentListContext"
                           itemcontext="msgComposeAttachmentItemContext"
-                          ondblclick="AttachmentBucketDoubleClicked(event);"
-                          onkeyup="attachmentBucketOnKeyUp(event);"
-                          onselect="updateAttachmentItems();"
+                          onclick="attachmentBucketOnClick(event);"
+                          onkeypress="attachmentBucketOnKeyPress(event);"
+                          onselect="attachmentBucketOnSelect();"
                           ondragstart="nsDragAndDrop.startDrag(event, attachmentBucketDNDObserver);"
                           onblur="attachmentBucketOnBlur();"/>
         </vbox>
       </hbox>
     </toolbar>
 
     <!-- These toolbar items get filled out from the editorOverlay -->
     <toolbox id="FormatToolbox" mode="icons">
--- a/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
@@ -175,16 +175,22 @@ messageAttachmentSafeName=Attached Messa
 ## String used for attachment pretty name, when the attachment is a message part
 partAttachmentSafeName=Attached Message Part
 
 # LOCALIZATION NOTE (attachmentCount): Semi-colon list of plural forms.
 # See: http://developer.mozilla.org/en/Localization_and_Plurals
 # #1 number of attachments
 attachmentCount=#1 attachment;#1 attachments
 
+# LOCALIZATION NOTE (attachmentBucketAttachFilesTooltip):
+# This tooltip should be same as attachFile.label in messengercompose.dtd,
+# but without ellipsis (…).
+attachmentBucketAttachFilesTooltip=Attach File(s)
+attachmentBucketClearSelectionTooltip=Clear Selection
+
 ## String used by the Initialization Error dialog
 initErrorDlogTitle=Message Compose
 initErrorDlgMessage=An error occurred while creating a message compose window. Please try again.
 
 ## String used if a file to attach does not exist when passed as
 ## a command line argument
 errorFileAttachTitle=File Attach
 
--- a/mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
@@ -83,16 +83,27 @@
 <!ENTITY deleteCmd.accesskey "d">
 <!ENTITY editRewrapCmd.label "Rewrap">
 <!ENTITY editRewrapCmd.key "R">
 <!ENTITY renameAttachmentCmd.label "Rename Attachment…">
 <!ENTITY renameAttachmentCmd.accesskey "e">
 <!ENTITY reorderAttachmentsCmd.label "Reorder Attachments…">
 <!ENTITY reorderAttachmentsCmd.accesskey "s">
 <!ENTITY reorderAttachmentsCmd.key "x">
+<!ENTITY toggleAttachmentPaneCmd.label "Attachment Pane">
+<!-- LOCALIZATION NOTE (toggleAttachmentPaneCmd.accesskey):
+     For better mnemonics, toggleAttachmentPaneCmd.accesskey should be the same
+     as attachments.accesskey. -->
+<!ENTITY toggleAttachmentPaneCmd.accesskey "m">
+<!-- LOCALIZATION NOTE (toggleAttachmentPaneCmd.key):
+     With OS-specific access modifier, this defines the shortcut key for showing/
+     hiding/minimizing the attachment pane, e.g. Alt+M on Windows.
+     toggleAttachmentPaneCmd.key and attachments.accesskey must be the same
+     for the bucket view state machinery to work. -->
+<!ENTITY toggleAttachmentPaneCmd.key "m">
 <!ENTITY selectAllCmd.label "Select All">
 <!ENTITY selectAllCmd.key "A">
 <!ENTITY selectAllCmd.accesskey "a">
 <!ENTITY findBarCmd.label "Find…">
 <!ENTITY findBarCmd.accesskey "F">
 <!ENTITY findBarCmd.key "F">
 <!ENTITY findReplaceCmd.label "Find and Replace…">
 <!ENTITY findReplaceCmd.accesskey "l">
@@ -231,17 +242,17 @@
 <!ENTITY saveButton.label "Save">
 <!ENTITY printButton.label "Print">
 
 <!-- Mail Toolbar Tooltips -->
 <!ENTITY sendButton.tooltip "Send this message now">
 <!ENTITY sendlaterButton.tooltip "Send this message later">
 <!ENTITY quoteButton.tooltip "Quote the previous message">
 <!ENTITY addressButton.tooltip "Select a recipient from an Address Book">
-<!ENTITY attachButton.tooltip "Include an attachment">
+<!ENTITY attachButton.tooltip2 "Add an attachment">
 <!ENTITY spellingButton.tooltip "Check spelling of selection or entire message">
 <!ENTITY saveButton.tooltip "Save this message">
 <!ENTITY cutButton.tooltip              "Cut">
 <!ENTITY copyButton.tooltip             "Copy">
 <!ENTITY pasteButton.tooltip            "Paste">
 <!ENTITY printButton.tooltip "Print this message">
 
 <!-- Headers -->
@@ -253,19 +264,23 @@
 <!ENTITY toAddr.label "To:">
 <!ENTITY ccAddr.label "Cc:">
 <!ENTITY bccAddr.label "Bcc:">
 <!ENTITY replyAddr.label "Reply-To:">
 <!ENTITY newsgroupsAddr.label "Newsgroup:">
 <!ENTITY followupAddr.label "Followup-To:">
 <!ENTITY subject.label "Subject:">
 <!ENTITY subject.accesskey "S">
-<!-- LOCALIZATION NOTE attachments.accesskey This access key should correspond
-     to the strings in attachmentCount in composeMsgs.properties -->
+<!-- LOCALIZATION NOTE (attachments.accesskey) This access key character should
+     be taken from the strings in attachmentCount in composeMsgs.properties,
+     and it must be the same as toggleAttachmentPaneCmd.key, otherwise it won't
+     work, as the shortcut key functionality supersedes the access key. -->
 <!ENTITY attachments.accesskey "m">
+<!ENTITY attachmentBucketHeader.tooltip "Attachment Pane">
+<!ENTITY attachmentBucketCloseButton.tooltip "Hide the attachment pane">
 
 <!-- Format Toolbar, imported from editorAppShell.xul -->
 <!ENTITY SmileButton.tooltip "Insert a smiley face">
 <!ENTITY smiley1Cmd.label "Smile">
 <!ENTITY smiley2Cmd.label "Frown">
 <!ENTITY smiley3Cmd.label "Wink">
 <!ENTITY smiley4Cmd.label "Tongue-out">
 <!ENTITY smiley5Cmd.label "Laughing">
@@ -293,26 +308,29 @@
 <!ENTITY cut.accesskey "t">
 <!ENTITY copy.label "Copy">
 <!ENTITY copy.accesskey "C">
 <!ENTITY paste.label "Paste">
 <!ENTITY paste.accesskey "P">
 <!ENTITY pasteQuote.label "Paste As Quotation">
 <!ENTITY pasteQuote.accesskey "Q">
 
+<!-- Attachment Item and List Context Menus -->
 <!ENTITY openAttachment.label "Open">
 <!ENTITY openAttachment.accesskey "O">
 <!ENTITY delete.label "Delete">
 <!ENTITY delete.accesskey "D">
 <!ENTITY removeAttachment.label "Remove Attachment">
 <!ENTITY removeAttachment.accesskey "M">
 <!ENTITY renameAttachment.label "Rename…">
 <!ENTITY renameAttachment.accesskey "R">
 <!ENTITY reorderAttachments.label "Reorder Attachments…">
 <!ENTITY reorderAttachments.accesskey "s">
+<!ENTITY removeAllAttachments.label "Remove All Attachments">
+<!ENTITY removeAllAttachments.accesskey "v">
 <!ENTITY selectAll.label "Select All">
 <!ENTITY selectAll.accesskey "A">
 <!ENTITY attachFile.label "Attach File(s)…">
 <!ENTITY attachFile.accesskey "F">
 <!ENTITY attachCloud.label "Filelink…">
 <!ENTITY attachCloud.accesskey "i">
 <!ENTITY convertCloud.label "Convert to…">
 <!ENTITY convertCloud.accesskey "C">
--- a/mail/themes/linux/mail/compose/messengercompose.css
+++ b/mail/themes/linux/mail/compose/messengercompose.css
@@ -11,17 +11,17 @@
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 
 #attachmentBucket {
   width: 15em;
   min-width: 15em;
 }
 
-#attachmentBucket:focus {
+#attachmentBucket[empty]:focus {
   box-shadow: 0 0 2px Highlight inset, 0 0 2px Highlight inset;
 }
 
 #attachmentBucket > scrollbox > .scrollbox-innerbox {
   padding: 1px;
 }
 
 #attachmentBucket attachmentitem {
@@ -189,50 +189,46 @@ menulist:-moz-locale-dir(rtl) > .menulis
 
 /* ::::: special toolbar colors ::::: */
 
 #attachmentbucket-sizer {
   border-top: none;
 }
 
 #attachments-header-box {
-  height: 24px;
-  margin-top: 1px;
-}
-
-#attachmentBucketCount {
-  margin-top: 0;
-  margin-bottom: 0;
+  min-height: 28px;
 }
 
 #attachmentBucketSize {
   color: GrayText;
-  margin-top: 0;
-  margin-bottom: 0;
-  padding-inline-end: 1px;
+  padding-inline-end: 2px;
+}
+
+#attachmentBucketCloseButton {
+  padding: 0 1px;
 }
 
 /* ::::: attachment reminder ::::: */
 
 #attachmentNotificationBox b {
   font-weight: bold;
 }
 
 #attachmentNotificationBox > notification[image="null"] .messageImage {
   background-image: url("chrome://messenger/skin/icons/attach.svg");
 }
 
 #attachmentReminderText {
-  margin-inline-start: 0px;
+  margin-inline-start: 0;
   cursor: pointer;
 }
 
 #attachmentKeywords {
   font-weight: bold;
-  margin-inline-start: 0px;
+  margin-inline-start: 0;
   text-decoration: underline;
   cursor: pointer;
 }
 
 /* XXX I should really have a selector rule here to select just .listcell-icon objects underneath the attachmentList listbox */
 
 .listcell-icon {
   margin-inline-start: 2px;
@@ -434,16 +430,20 @@ html|span.ac-emphasize-text {
 #MsgHeadersToolbar {
   -moz-appearance: none !important;
   color: WindowText;
   text-shadow: none;
   background-image: url("chrome://messenger/skin/messengercompose/linux-noise.png");
   padding-top: 3px;
 }
 
+#addresses-box {
+  padding-top: 4px;
+}
+
 #identityLabel-box {
   margin-top: 1px;
 }
 
 #identityLabel {
   margin-bottom: 1px;
   margin-inline-end: 8px
 }
--- a/mail/themes/osx/mail/compose/messengercompose.css
+++ b/mail/themes/osx/mail/compose/messengercompose.css
@@ -17,17 +17,17 @@
   color: var(--toolbar-color, inherit);
 }
 
 #attachmentBucket {
   width: 15em;
   min-width: 15em;
 }
 
-#attachmentBucket:focus {
+#attachmentBucket[empty]:focus {
   box-shadow: 0 0 1.5px 1px -moz-mac-focusring inset;
 }
 
 #attachmentBucket > scrollbox > .scrollbox-innerbox {
   padding: 1px;
 }
 
 #attachmentBucket attachmentitem {
@@ -145,27 +145,27 @@ toolbar[nowindowdrag="true"] {
   padding-right: 3px;
   padding-left: 3px;
 }
 
 #composeContentBox:-moz-window-inactive {
   box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2) inset;
 }
 
-#headers-box {
-  padding-top: 5px;
-}
-
 #MsgHeadersToolbar {
   color: -moz-DialogText;
   text-shadow: none;
   background-image: url("chrome://messenger/skin/noise.png");
   border-bottom: 0px solid;
 }
 
+#addresses-box {
+  padding-top: 5px;
+}
+
 #msgIdentity {
   margin: 0;
   margin-inline-end: 1px;
   padding-inline-start: 3px;
   padding-inline-end: 12px;
   line-height: 1;
   border: 1px solid transparent;
   border-bottom-color: #C6C6C6;
@@ -225,30 +225,41 @@ toolbar[nowindowdrag="true"] {
   color: GrayText;
   margin-inline-start: 1ex !important;
 }
 
 #addresses-box {
   margin: 4px 6px;
 }
 
-#attachments-box {
-  padding-top: 5px;
-}
-
 #attachmentbucket-sizer {
   border-top: none;
   border-bottom: none;
   height: 7px;
 }
 
+#attachments-header-box {
+  min-height: 28px;
+}
+
+#attachmentBucketCount {
+  padding-top: 2px;
+}
+
 #attachmentBucketSize {
+  padding-top: 2px;
+  padding-inline-end: 2px;
   color: GrayText;
 }
 
+#attachmentBucketCloseButton {
+  padding: 0 1px;
+  margin-top: 1px;
+}
+
 /* ::::: attachment reminder ::::: */
 
 #attachmentNotificationBox b {
   font-weight: bold;
 }
 
 #attachmentNotificationBox > notification[image="null"] .messageImage {
   background-image: url("chrome://messenger/skin/icons/attach.svg");
--- a/mail/themes/windows/mail/compose/messengercompose.css
+++ b/mail/themes/windows/mail/compose/messengercompose.css
@@ -16,17 +16,17 @@
   width: 15em;
   min-width: 15em;
   border-top: 1px solid #A9B7C9; /* The same color as the splitters */
   border-bottom: 1px solid #A9B7C9;
   background-color: -moz-Field;
   color: -moz-FieldText;
 }
 
-#attachmentBucket:focus {
+#attachmentBucket[empty]:focus {
   box-shadow: 0 0 1px Highlight inset, 0 0 1px Highlight inset;
 }
 
 #attachmentBucket > scrollbox > .scrollbox-innerbox {
   padding: 1px;
 }
 
 @media (-moz-windows-default-theme: 0) {
@@ -68,30 +68,33 @@
 }
 
 #composeContentBox:-moz-window-inactive {
   box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05) inset;
 }
 
 #headers-box {
   -moz-appearance: none;
-  padding-top: 4px;
   padding-bottom: 2px;
   border-bottom: 1px solid ThreeDShadow;
 }
 
 #MsgHeadersToolbar {
   color: -moz-DialogText;
   text-shadow: none;
 }
 
 #msgheaderstoolbar-box {
   padding-bottom: 2px;
 }
 
+#addresses-box {
+  padding-top: 4px;
+}
+
 #msgIdentity {
   -moz-appearance: none;
   background-color: transparent;
   height: 22px;
   line-height: 1;
   border: 1px solid transparent;
   box-shadow: none;
   margin-inline-end: 5px;
@@ -737,28 +740,24 @@ toolbar:not(:-moz-lwtheme) {
 
 /* ::::: special toolbar colors ::::: */
 
 #attachmentbucket-sizer {
   position: relative;
   z-index: 10;
   min-width: 0;
   width: 5px;
-  margin-top: -4px; /* revert the #headers-box padding-top */
   margin-inline-end: -4px;
   border-inline-end-width: 0;
   border-inline-start: 1px solid #a9b7c9;
   background-color: transparent;
 }
 
-#attachments-box {
-  padding-top: 4px;
-}
-
 #attachmentBucketSize {
+  padding-inline-end: 2px;
   color: GrayText;
 }
 
 #attachmentNotificationBox b {
   font-weight: bold;
 }
 
 #attachmentNotificationBox > notification[image="null"] .messageImage {