mail/components/compose/content/MsgComposeCommands.js
changeset 31499 b84ef4aee6c977f95fdf04d37f74791d3fecfbf4
parent 31461 2aadbc258f50daa18b3d768685fac89a2f58c496
child 31501 db4c36ca93869457c0086c965fc8d5f56b21b3da
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -97,17 +97,17 @@ var gCurrentIdentity;
 var defaultSaveOperation;
 var gSendOperationInProgress;
 var gSaveOperationInProgress;
 var gCloseWindowAfterSave;
 var gSavedSendNowKey;
 var gSendFormat;
 var gContextMenu;
 
-var gMsgAttachmentElement;
+var gAttachmentBucket;
 var gMsgHeadersToolbarElement;
 // TODO: Maybe the following two variables can be combined.
 var gManualAttachmentReminder;
 var gDisableAttachmentReminder;
 var gComposeType;
 var gLanguageObserver;
 var gBodyFromArgs;
 
@@ -890,36 +890,36 @@ var defaultController = {
         // Note: We cannot pass event along the call chain: Bug 461578 / 959494.
         // eslint-disable-next-line no-restricted-globals
         toggleAttachmentPane("toggle", event);
       },
     },
 
     cmd_reorderAttachments: {
       isEnabled() {
-        if (attachmentsCount() == 0) {
+        if (!gAttachmentBucket.itemCount) {
           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.
             reorderAttachmentsPanel.hidePopup();
           }
         }
-        return attachmentsCount() > 1;
+        return gAttachmentBucket.itemCount > 1;
       },
       doCommand() {
         showReorderAttachmentsPanel();
       },
     },
 
     cmd_removeAllAttachments: {
       isEnabled() {
-        return !gWindowLocked && attachmentsCount() > 0;
+        return !gWindowLocked && gAttachmentBucket.itemCount;
       },
       doCommand() {
         RemoveAllAttachments();
       },
     },
 
     cmd_close: {
       isEnabled() {
@@ -1178,136 +1178,138 @@ var defaultController = {
 
 var attachmentBucketController = {
   commands: {
     cmd_selectAll: {
       isEnabled() {
         return true;
       },
       doCommand() {
-        document.getElementById("attachmentBucket").selectAll();
+        gAttachmentBucket.selectAll();
       },
     },
 
     cmd_delete: {
       isEnabled() {
-        let selectedCount = attachmentsSelectedCount();
         let cmdDelete = document.getElementById("cmd_delete");
         let textValue = getComposeBundle().getString("removeAttachmentMsgs");
-        textValue = PluralForm.get(selectedCount, textValue);
+        textValue = PluralForm.get(gAttachmentBucket.selectedCount, textValue);
         let accesskeyValue = cmdDelete.getAttribute(
           "valueRemoveAttachmentAccessKey"
         );
         cmdDelete.setAttribute("label", textValue);
         cmdDelete.setAttribute("accesskey", accesskeyValue);
 
-        return selectedCount > 0;
+        return gAttachmentBucket.selectedCount;
       },
       doCommand() {
         RemoveSelectedAttachment();
       },
     },
 
     cmd_openAttachment: {
       isEnabled() {
-        return attachmentsSelectedCount() == 1;
+        return gAttachmentBucket.selectedCount == 1;
       },
       doCommand() {
         OpenSelectedAttachment();
       },
     },
 
     cmd_renameAttachment: {
       isEnabled() {
-        return attachmentsSelectedCount() == 1;
+        return gAttachmentBucket.selectedCount == 1;
       },
       doCommand() {
         RenameSelectedAttachment();
       },
     },
 
     cmd_moveAttachmentUp: {
       isEnabled() {
         return (
-          attachmentsSelectedCount() > 0 && !attachmentsSelectionIsBlock("top")
+          gAttachmentBucket.selectedCount && !attachmentsSelectionIsBlock("top")
         );
       },
       doCommand() {
         moveSelectedAttachments("up");
       },
     },
 
     cmd_moveAttachmentDown: {
       isEnabled() {
         return (
-          attachmentsSelectedCount() > 0 &&
+          gAttachmentBucket.selectedCount &&
           !attachmentsSelectionIsBlock("bottom")
         );
       },
       doCommand() {
         moveSelectedAttachments("down");
       },
     },
 
     cmd_moveAttachmentBundleUp: {
       isEnabled() {
-        return attachmentsSelectedCount() > 1 && !attachmentsSelectionIsBlock();
+        return (
+          gAttachmentBucket.selectedCount > 1 && !attachmentsSelectionIsBlock()
+        );
       },
       doCommand() {
         moveSelectedAttachments("bundleUp");
       },
     },
 
     cmd_moveAttachmentBundleDown: {
       isEnabled() {
-        return attachmentsSelectedCount() > 1 && !attachmentsSelectionIsBlock();
+        return (
+          gAttachmentBucket.selectedCount > 1 && !attachmentsSelectionIsBlock()
+        );
       },
       doCommand() {
         moveSelectedAttachments("bundleDown");
       },
     },
 
     cmd_moveAttachmentTop: {
       isEnabled() {
         return (
-          attachmentsSelectedCount() > 0 && !attachmentsSelectionIsBlock("top")
+          gAttachmentBucket.selectedCount && !attachmentsSelectionIsBlock("top")
         );
       },
       doCommand() {
         moveSelectedAttachments("top");
       },
     },
 
     cmd_moveAttachmentBottom: {
       isEnabled() {
         return (
-          attachmentsSelectedCount() > 0 &&
+          gAttachmentBucket.selectedCount &&
           !attachmentsSelectionIsBlock("bottom")
         );
       },
       doCommand() {
         moveSelectedAttachments("bottom");
       },
     },
 
     cmd_sortAttachmentsToggle: {
       isEnabled() {
-        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 (
-          attachmentsSelCount > 1 &&
-          attachmentsSelCount < attachmentsCount()
+          gAttachmentBucket.selectedCount > 1 &&
+          gAttachmentBucket.selectedCount < gAttachmentBucket.itemCount
         ) {
           // 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:
@@ -1321,17 +1323,17 @@ var attachmentBucketController = {
           if (btnAscending) {
             sortDirection = "ascending";
             btnLabelAttr = "label-selection-AZ";
           } else {
             sortDirection = "descending";
             btnLabelAttr = "label-selection-ZA";
           }
         } else {
-          // attachmentsSelectedCount() <= 1 or all attachments selected
+          // gAttachmentBucket.selectedCount <= 1 or all attachments are 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";
@@ -1365,18 +1367,17 @@ var attachmentBucketController = {
         cmd.hidden =
           !Services.prefs.getBoolPref("mail.cloud_files.enabled") ||
           cloudFileAccounts.configuredAccounts.length == 0 ||
           Services.io.offline;
         if (cmd.hidden) {
           return false;
         }
 
-        let bucket = document.getElementById("attachmentBucket");
-        for (let item of bucket.selectedItems) {
+        for (let item of gAttachmentBucket.selectedItems) {
           if (item.uploading) {
             return false;
           }
           if (item.cloudFileUpload && item.cloudFileUpload.repeat) {
             return false;
           }
         }
         return true;
@@ -1388,18 +1389,17 @@ var attachmentBucketController = {
     },
 
     cmd_convertAttachment: {
       isEnabled() {
         if (!Services.prefs.getBoolPref("mail.cloud_files.enabled")) {
           return false;
         }
 
-        let bucket = document.getElementById("attachmentBucket");
-        for (let item of bucket.selectedItems) {
+        for (let item of gAttachmentBucket.selectedItems) {
           if (item.uploading) {
             return false;
           }
           if (item.cloudFileUpload && item.cloudFileUpload.repeat) {
             return false;
           }
         }
         return true;
@@ -1416,18 +1416,17 @@ var attachmentBucketController = {
         );
 
         // If Filelink is disabled, hide this menuitem and bailout.
         if (!Services.prefs.getBoolPref("mail.cloud_files.enabled")) {
           cmd.hidden = true;
           return false;
         }
 
-        let bucket = document.getElementById("attachmentBucket");
-        for (let item of bucket.selectedItems) {
+        for (let item of gAttachmentBucket.selectedItems) {
           if (item && item.uploading) {
             cmd.hidden = false;
             return true;
           }
         }
 
         // Hide the command entirely if the selected attachments aren't cloud
         // files.
@@ -1436,18 +1435,17 @@ var attachmentBucketController = {
         cmd.hidden = true;
         return false;
       },
       doCommand() {
         let fileHandler = Services.io
           .getProtocolHandler("file")
           .QueryInterface(Ci.nsIFileProtocolHandler);
 
-        let bucket = document.getElementById("attachmentBucket");
-        for (let item of bucket.selectedItems) {
+        for (let item of gAttachmentBucket.selectedItems) {
           if (item && item.uploading) {
             let file = fileHandler.getFileFromURLSpec(item.attachment.url);
             item.cloudFileAccount.cancelFileUpload(file);
           }
         }
       },
     },
   },
@@ -1510,34 +1508,30 @@ function QuoteSelectedMessage() {
 }
 
 function GetSelectedMessages() {
   let mailWindow = Services.wm.getMostRecentWindow("mail:3pane");
   return mailWindow ? mailWindow.gFolderDisplay.selectedMessageUris : null;
 }
 
 function SetupCommandUpdateHandlers() {
-  let attachmentBucket = document.getElementById("attachmentBucket");
-
   top.controllers.appendController(defaultController);
-  attachmentBucket.controllers.appendController(attachmentBucketController);
+  gAttachmentBucket.controllers.appendController(attachmentBucketController);
 
   document
     .getElementById("optionsMenuPopup")
     .addEventListener("popupshowing", updateOptionItems, true);
 }
 
 function UnloadCommandUpdateHandlers() {
-  let attachmentBucket = document.getElementById("attachmentBucket");
-
   document
     .getElementById("optionsMenuPopup")
     .removeEventListener("popupshowing", updateOptionItems, true);
 
-  attachmentBucket.controllers.removeController(attachmentBucketController);
+  gAttachmentBucket.controllers.removeController(attachmentBucketController);
   top.controllers.removeController(defaultController);
 }
 
 function CommandUpdate_MsgCompose() {
   var focusedWindow = top.document.commandDispatcher.focusedWindow;
 
   // we're just setting focus to where it was before
   if (focusedWindow == gLastWindowToHaveFocus) {
@@ -2074,40 +2068,40 @@ function addAttachCloudMenuItems(aParent
       fileItem.setAttribute("class", "menuitem-iconic");
       fileItem.setAttribute("image", "moz-icon://" + file.leafName);
       aParentMenu.appendChild(fileItem);
     }
   }
 }
 
 function addConvertCloudMenuItems(aParentMenu, aAfterNodeId, aRadioGroup) {
-  let attachment = document.getElementById("attachmentBucket").selectedItem;
   let afterNode = document.getElementById(aAfterNodeId);
   while (afterNode.nextElementSibling) {
     afterNode.nextElementSibling.remove();
   }
 
-  if (!attachment.sendViaCloud) {
+  if (!gAttachmentBucket.selectedItem.sendViaCloud) {
     let item = document.getElementById(
       "convertCloudMenuItems_popup_convertAttachment"
     );
     item.setAttribute("checked", "true");
   }
 
   for (let account of cloudFileAccounts.configuredAccounts) {
     let item = document.createXULElement("menuitem");
     let iconURL = account.iconURL;
     item.cloudFileAccount = account;
     item.setAttribute("label", cloudFileAccounts.getDisplayName(account));
     item.setAttribute("type", "radio");
     item.setAttribute("name", aRadioGroup);
 
     if (
-      attachment.cloudFileAccount &&
-      attachment.cloudFileAccount.accountKey == account.accountKey
+      gAttachmentBucket.selectedItem.cloudFileAccount &&
+      gAttachmentBucket.selectedItem.cloudFileAccount.accountKey ==
+        account.accountKey
     ) {
       item.setAttribute("checked", "true");
     } else if (iconURL) {
       item.setAttribute("class", "menu-iconic");
       item.setAttribute("image", iconURL);
     }
 
     aParentMenu.appendChild(item);
@@ -2117,18 +2111,17 @@ function addConvertCloudMenuItems(aParen
 async function uploadCloudAttachment(attachment, file, cloudFileAccount) {
   // Notify the UI that we're starting the upload process: disable send commands
   // and show a "connecting" icon for the attachment.
   attachment.sendViaCloud = true;
   gNumUploadingAttachments++;
   updateSendCommands(true);
 
   let displayName = cloudFileAccounts.getDisplayName(cloudFileAccount);
-  let bucket = document.getElementById("attachmentBucket");
-  let attachmentItem = bucket.findItemForAttachment(attachment);
+  let attachmentItem = gAttachmentBucket.findItemForAttachment(attachment);
   if (attachmentItem) {
     let itemIcon = attachmentItem.querySelector(".attachmentcell-icon");
     itemIcon.setAttribute("src", "chrome://global/skin/icons/loading.png");
     attachmentItem.setAttribute(
       "tooltiptext",
       getComposeBundle().getFormattedString("cloudFileUploadingTooltip", [
         displayName,
       ])
@@ -2444,31 +2437,32 @@ function convertListItemsToCloudAttachme
 }
 
 /**
  * Convert the selected attachments to cloud attachments.
  *
  * @param aAccount the cloud account to upload the files to
  */
 function convertSelectedToCloudAttachment(aAccount) {
-  let bucket = document.getElementById("attachmentBucket");
-  convertListItemsToCloudAttachment([...bucket.selectedItems], aAccount);
+  convertListItemsToCloudAttachment(
+    [...gAttachmentBucket.selectedItems],
+    aAccount
+  );
 }
 
 /**
  * Convert an array of nsIMsgAttachments to cloud attachments.
  *
  * @param aAttachments an array of nsIMsgAttachments
  * @param aAccount the cloud account to upload the files to
  */
 function convertToCloudAttachment(aAttachments, aAccount) {
-  let bucket = document.getElementById("attachmentBucket");
   let items = [];
   for (let attachment of aAttachments) {
-    let item = bucket.findItemForAttachment(attachment);
+    let item = gAttachmentBucket.findItemForAttachment(attachment);
     if (item) {
       items.push(item);
     }
   }
 
   convertListItemsToCloudAttachment(items, aAccount);
 }
 
@@ -2518,30 +2512,28 @@ function convertListItemsToRegularAttach
     delete item.attachment.contentLocation;
   }
 }
 
 /**
  * Convert the selected attachments to regular (non-cloud) attachments.
  */
 function convertSelectedToRegularAttachment() {
-  let bucket = document.getElementById("attachmentBucket");
-  convertListItemsToRegularAttachment([...bucket.selectedItems]);
+  convertListItemsToRegularAttachment([...gAttachmentBucket.selectedItems]);
 }
 
 /**
  * Convert an array of nsIMsgAttachments to regular (non-cloud) attachments.
  *
  * @param aAttachments an array of nsIMsgAttachments
  */
 function convertToRegularAttachment(aAttachments) {
-  let bucket = document.getElementById("attachmentBucket");
   let items = [];
   for (let attachment of aAttachments) {
-    let item = bucket.findItemForAttachment(attachment);
+    let item = gAttachmentBucket.findItemForAttachment(attachment);
     if (item) {
       items.push(item);
     }
   }
 
   convertListItemsToRegularAttachment(items);
 }
 
@@ -2941,24 +2933,24 @@ function manageAttachmentNotification(aF
 
   notification.messageDetails.querySelector("button").before(msg);
   notification.messageDetails
     .querySelector("button:last-child")
     .appendChild(remindLaterMenuPopup);
 }
 
 /**
- * Returns whether the attachment notification should be suppressed regardless of
- * the state of keywords.
+ * Returns whether the attachment notification should be suppressed regardless
+ * of the state of keywords.
  */
 function attachmentNotificationSupressed() {
   return (
     gDisableAttachmentReminder ||
     gManualAttachmentReminder ||
-    AttachmentElementHasItems()
+    gAttachmentBucket.getRowCount()
   );
 }
 
 var attachmentWorker = new Worker("resource:///modules/AttachmentChecker.jsm");
 
 // The array of currently found keywords. Or null if keyword detection wasn't
 // run yet so we don't know.
 attachmentWorker.lastMessage = null;
@@ -3003,19 +2995,18 @@ attachmentWorker.onmessage = function(ev
  * @param aShowPane {string} "show":  show the attachment pane
  *                           "hide":  hide the attachment pane
  *                           omitted: just update without changing pane visibility
  * @param aContentChanged {Boolean} optional value to assign to gContentChanged;
  *                                  defaults to true.
  */
 function AttachmentsChanged(aShowPane, aContentChanged = true) {
   gAttachmentsSize = 0;
-  let bucket = document.getElementById("attachmentBucket");
-  for (let item of bucket.itemChildren) {
-    bucket.invalidateItem(item);
+  for (let item of gAttachmentBucket.itemChildren) {
+    gAttachmentBucket.invalidateItem(item);
     gAttachmentsSize += item.attachment.size;
   }
 
   gContentChanged = aContentChanged;
   updateAttachmentPane(aShowPane);
   attachmentBucketMarkEmptyBucket();
   manageAttachmentNotification(true);
   updateAttachmentItems();
@@ -3961,16 +3952,22 @@ function ComposeLoad() {
     "mail.compose.other.header",
     ""
   );
 
   AddMessageComposeOfflineQuitObserver();
 
   BondOpenPGP.init();
 
+  // Setup the attachment bucket.
+  gAttachmentBucket = document.getElementById("attachmentBucket");
+  let viewMode = Services.prefs.getIntPref("mailnews.attachments.display.view");
+  let views = ["small", "large", "tile"];
+  gAttachmentBucket.view = views[viewMode];
+
   try {
     SetupCommandUpdateHandlers();
     // This will do migration, or create a new account if we need to.
     // We also want to open the account wizard if no identities are found
     let state = verifyAccounts(WizCallback, true);
 
     if (otherHeaders) {
       let extraRecipientsPanel = document.getElementById(
@@ -4698,17 +4695,17 @@ async function CompleteGenericSendMessag
     msgType == Ci.nsIMsgCompDeliverMode.Later ||
     msgType == Ci.nsIMsgCompDeliverMode.Background
   ) {
     window.dispatchEvent(new CustomEvent("aftersend"));
 
     let maxSize =
       Services.prefs.getIntPref("mail.compose.big_attachments.threshold_kb") *
       1024;
-    let items = [...document.getElementById("attachmentBucket").itemChildren];
+    let items = [...gAttachmentBucket.itemChildren];
 
     // When any big attachment is not sent via filelink, increment
     // `tb.filelink.ignored`.
     if (
       items.some(
         item => item.attachment.size >= maxSize && !item.attachment.sendViaCloud
       )
     ) {
@@ -5795,17 +5792,17 @@ function SetLastAttachDirectory(attached
       parent
     );
   } catch (ex) {
     dump("error: SetLastAttachDirectory failed: " + ex + "\n");
   }
 }
 
 function AttachFile() {
-  if (attachmentsCount() > 0) {
+  if (gAttachmentBucket.itemCount) {
     // If there are existing attachments already, restore attachment pane before
     // showing the file picker so that user can see them while adding more.
     toggleAttachmentPane("show");
   }
 
   // Get file using nsIFilePicker and convert to URL
   let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
   fp.init(
@@ -5863,17 +5860,16 @@ function FileToAttachment(file) {
  * @param {nsIMsgAttachment[]} aAttachments - Objects to add as attachments.
  * @param {function} [aCallback] - An optional callback function called after
  *   adding each attachment. Takes one argument: the newly-added
  *   <attachmentitem> node.
  * @param {Boolean} [aContentChanged=true] - Optional value to assign gContentChanged
  *   after adding attachments.
  */
 function AddAttachments(aAttachments, aCallback, aContentChanged = true) {
-  let bucket = document.getElementById("attachmentBucket");
   let addedAttachments = Cc["@mozilla.org/array;1"].createInstance(
     Ci.nsIMutableArray
   );
   let items = [];
 
   for (let attachment of fixIterator(aAttachments, Ci.nsIMsgAttachment)) {
     if (
       !(attachment && attachment.url) ||
@@ -5895,17 +5891,17 @@ function AddAttachments(aAttachments, aC
     ) {
       attachment.name = getComposeBundle().getString(
         "messageAttachmentSafeName"
       );
     } else if (/^file:|^mailbox:|^imap:|^s?news:/i.test(attachment.name)) {
       attachment.name = getComposeBundle().getString("partAttachmentSafeName");
     }
 
-    let item = bucket.appendItem(attachment);
+    let item = gAttachmentBucket.appendItem(attachment);
     addedAttachments.appendElement(attachment);
 
     if (attachment.size != -1) {
       gAttachmentsSize += attachment.size;
     }
 
     try {
       item.setAttribute("tooltiptext", decodeURI(attachment.url));
@@ -5934,99 +5930,81 @@ function AddAttachments(aAttachments, aC
   }
 
   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]);
+    if (!(gAttachmentBucket.currentIndex >= 0)) {
+      gAttachmentBucket.currentIndex = gAttachmentBucket.getIndexOfItem(
+        items[0]
+      );
     }
 
     AttachmentsChanged("show", aContentChanged);
     dispatchAttachmentBucketEvent("attachments-added", addedAttachments);
-  } else if (attachmentsCount() > 0) {
+  } else if (gAttachmentBucket.itemCount) {
     // 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 pane
     // is shown, which might be hidden by user with existing attachments.
     toggleAttachmentPane("show");
   }
 
   return items;
 }
 
 /**
- * Get the number of all attachments of the message.
- *
- * @return the number of all attachment items in attachmentBucket;
- *         0 if attachmentBucket not found or no attachments in the list.
- */
-function attachmentsCount() {
-  let bucketList = GetMsgAttachmentElement();
-  return bucketList ? bucketList.itemCount : 0;
-}
-
-/**
- * Get the number of selected attachments.
- *
- * @return {number}  the number of selected attachments, or 0 if there are
- *                   no attachments selected, no attachments, or no attachmentBucket
- */
-function attachmentsSelectedCount() {
-  let bucketList = GetMsgAttachmentElement();
-  return bucketList ? bucketList.selectedCount : 0;
-}
-
-/**
  * Returns a sorted-by-index, "non-live" array of attachment list items.
  *
  * @param aAscending {boolean}: true (default): sort return array ascending
  *                              false         : sort return array descending
  * @param aSelectedOnly {boolean}: true: return array of selected items only.
  *                                 false (default): return array of all items.
  *
  * @return {array} an array of (all | selected) listItem elements in
  *                 attachmentBucket listbox, "non-live" and sorted by their index
  *                 in the list; [] if there are (no | no selected) attachments.
  */
 function attachmentsGetSortedArray(aAscending = true, aSelectedOnly = false) {
-  let bucketList;
   let listItems;
 
   if (aSelectedOnly) {
     // Selected attachments only.
-    if (attachmentsSelectedCount() < 1) {
+    if (!gAttachmentBucket.selectedCount) {
       return [];
     }
 
-    bucketList = document.getElementById("attachmentBucket");
-    // bucketList.selectedItems is a "live" and "unordered" node list (items get
-    // added in the order they were added to the selection). But we want a stable
-    // ("non-live") array of selected items, sorted by their index in the list.
-    listItems = [...bucketList.selectedItems];
+    // gAttachmentBucket.selectedItems is a "live" and "unordered" node list
+    // (items get added in the order they were added to the selection). But we
+    // want a stable ("non-live") array of selected items, sorted by their index
+    // in the list.
+    listItems = [...gAttachmentBucket.selectedItems];
   } else {
     // All attachments.
-    if (attachmentsCount() < 1) {
+    if (!gAttachmentBucket.itemCount) {
       return [];
     }
 
-    bucketList = document.getElementById("attachmentBucket");
-    listItems = [...bucketList.itemChildren];
+    listItems = [...gAttachmentBucket.itemChildren];
   }
 
   if (aAscending) {
     listItems.sort(
-      (a, b) => bucketList.getIndexOfItem(a) - bucketList.getIndexOfItem(b)
+      (a, b) =>
+        gAttachmentBucket.getIndexOfItem(a) -
+        gAttachmentBucket.getIndexOfItem(b)
     );
   } else {
     // descending
     listItems.sort(
-      (a, b) => bucketList.getIndexOfItem(b) - bucketList.getIndexOfItem(a)
+      (a, b) =>
+        gAttachmentBucket.getIndexOfItem(b) -
+        gAttachmentBucket.getIndexOfItem(a)
     );
   }
   return listItems;
 }
 
 /**
  * Returns a sorted-by-index, "non-live" array of selected attachment list items.
  *
@@ -6052,38 +6030,39 @@ function attachmentsSelectionGetSortedAr
  *                          (at the list edge if/as specified by 'aListPosition'),
  *                          or only 1 item selected.
  *                   false: The selected attachment items are NOT a coherent block
  *                          (at the list edge if/as specified by 'aListPosition'),
  *                          or no attachments selected, or no attachments,
  *                          or no attachmentBucket.
  */
 function attachmentsSelectionIsBlock(aListPosition) {
-  let selectedCount = attachmentsSelectedCount();
-  if (selectedCount < 1) {
+  if (!gAttachmentBucket.selectedCount) {
     // No attachments selected, no attachments, or no attachmentBucket.
     return false;
   }
 
-  let bucketList = document.getElementById("attachmentBucket");
   let selItems = attachmentsSelectionGetSortedArray();
-  let indexFirstSelAttachment = bucketList.getIndexOfItem(selItems[0]);
-  let indexLastSelAttachment = bucketList.getIndexOfItem(
-    selItems[selectedCount - 1]
+  let indexFirstSelAttachment = gAttachmentBucket.getIndexOfItem(selItems[0]);
+  let indexLastSelAttachment = gAttachmentBucket.getIndexOfItem(
+    selItems[gAttachmentBucket.selectedCount - 1]
   );
   let isBlock =
-    indexFirstSelAttachment == indexLastSelAttachment + 1 - selectedCount;
+    indexFirstSelAttachment ==
+    indexLastSelAttachment + 1 - gAttachmentBucket.selectedCount;
 
   switch (aListPosition) {
     case "top":
       // True if selection is a coherent block at the top of the list.
       return indexFirstSelAttachment == 0 && isBlock;
     case "bottom":
       // True if selection is a coherent block at the bottom of the list.
-      return indexLastSelAttachment == attachmentsCount() - 1 && isBlock;
+      return (
+        indexLastSelAttachment == gAttachmentBucket.itemCount - 1 && isBlock
+      );
     default:
       // True if selection is a coherent block.
       return isBlock;
   }
 }
 
 function AttachPage() {
   let result = { value: "http://" };
@@ -6112,52 +6091,45 @@ function AttachPage() {
 }
 
 /**
  * Check if the given fileURL already exists in the attachment bucket.
  * @param fileURL the URL (as a String) of the file to check
  * @return true if the fileURL is already attached
  */
 function DuplicateFileAlreadyAttached(fileURL) {
-  var bucket = document.getElementById("attachmentBucket");
-  let rowCount = bucket.getRowCount();
-  for (let i = 0; i < rowCount; i++) {
-    let attachment = bucket.getItemAtIndex(i).attachment;
-    if (attachment && attachment.url == fileURL) {
+  for (let item of gAttachmentBucket.itemChildren) {
+    if (item.attachment && item.attachment.url == fileURL) {
       return true;
     }
   }
+
   return false;
 }
 
 function Attachments2CompFields(compFields) {
-  var bucket = document.getElementById("attachmentBucket");
-
-  // First, we need to clear all attachment in the compose fields
+  // First, we need to clear all attachment in the compose fields.
   compFields.removeAttachments();
 
-  let rowCount = bucket.getRowCount();
-  for (let i = 0; i < rowCount; i++) {
-    let attachment = bucket.getItemAtIndex(i).attachment;
-    if (attachment) {
-      compFields.addAttachment(attachment);
+  for (let item of gAttachmentBucket.itemChildren) {
+    if (item.attachment) {
+      compFields.addAttachment(item.attachment);
     }
   }
 }
 
 function RemoveAllAttachments() {
   // Ensure that attachment pane is shown before removing all attachments.
   toggleAttachmentPane("show");
 
-  let bucket = document.getElementById("attachmentBucket");
-  if (bucket.itemCount == 0) {
+  if (!gAttachmentBucket.itemCount) {
     return;
   }
 
-  RemoveAttachments(bucket.itemChildren);
+  RemoveAttachments(gAttachmentBucket.itemChildren);
 }
 
 /**
  * Show or hide the attachment pane after updating its header bar information
  * (number and total file size of attachments) and tooltip.
  *
  * @param aShowBucket {Boolean} true: show the attachment pane
  *                              false (or omitted): hide the attachment pane
@@ -6170,18 +6142,17 @@ function UpdateAttachmentBucket(aShowBuc
  * 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 {string} "show":  show the attachment pane
  *                           "hide":  hide the attachment pane
  *                           omitted: just update without changing pane visibility
  */
 function updateAttachmentPane(aShowPane) {
-  let bucket = GetMsgAttachmentElement();
-  let count = bucket.itemCount;
+  let count = gAttachmentBucket.itemCount;
 
   document.l10n.setAttributes(
     document.getElementById("attachmentBucketCount"),
     "attachment-bucket-count",
     { count }
   );
 
   document.getElementById("attachmentBucketSize").value =
@@ -6201,28 +6172,26 @@ function updateAttachmentPane(aShowPane)
     return;
   }
 
   // Otherwise, show or hide the panel per aShowPane argument.
   toggleAttachmentPane(aShowPane);
 }
 
 function RemoveSelectedAttachment() {
-  let bucket = GetMsgAttachmentElement();
-  if (bucket.selectedCount == 0) {
+  if (!gAttachmentBucket.selectedCount) {
     return;
   }
 
-  RemoveAttachments(bucket.selectedItems);
+  RemoveAttachments(gAttachmentBucket.selectedItems);
 }
 
 function RemoveAttachments(items) {
-  let bucket = document.getElementById("attachmentBucket");
   // Remember the current focus index so we can try to restore it when done.
-  let focusIndex = bucket.currentIndex;
+  let focusIndex = gAttachmentBucket.currentIndex;
 
   let fileHandler = Services.io
     .getProtocolHandler("file")
     .QueryInterface(Ci.nsIFileProtocolHandler);
   let removedAttachments = Cc["@mozilla.org/array;1"].createInstance(
     Ci.nsIMutableArray
   );
 
@@ -6256,42 +6225,42 @@ function RemoveAttachments(items) {
     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;
     item.remove();
   }
 
   // Try to restore original focus or somewhere close by.
-  if (bucket.itemCount == 0) {
-    bucket.currentIndex = -1;
-  } else if (focusIndex < bucket.itemCount) {
-    bucket.currentIndex = focusIndex;
+  if (gAttachmentBucket.itemCount == 0) {
+    gAttachmentBucket.currentIndex = -1;
+  } else if (focusIndex < gAttachmentBucket.itemCount) {
+    gAttachmentBucket.currentIndex = focusIndex;
   } else {
-    bucket.currentIndex = bucket.itemCount - 1;
+    gAttachmentBucket.currentIndex = gAttachmentBucket.itemCount - 1;
   }
 
   if (removedAttachments.length > 0) {
-    // Bug workaround: Force update of selectedCount and selectedItem, both wrong
-    // after item removal, to avoid confusion for listening command controllers.
-    bucket.clearSelection();
+    // Bug 1661507 workaround: Force update of selectedCount and selectedItem,
+    // both wrong after item removal, to avoid confusion for listening command
+    // controllers.
+    gAttachmentBucket.clearSelection();
 
     AttachmentsChanged();
     dispatchAttachmentBucketEvent("attachments-removed", removedAttachments);
   }
 }
 
 function RenameSelectedAttachment() {
-  let bucket = document.getElementById("attachmentBucket");
-  if (bucket.selectedItems.length != 1) {
+  if (gAttachmentBucket.selectedItems.length != 1) {
     // Not one attachment selected.
     return;
   }
 
-  let item = bucket.getSelectedItem(0);
+  let item = gAttachmentBucket.getSelectedItem(0);
   let attachmentName = { value: item.attachment.name };
   if (
     Services.prompt.prompt(
       window,
       getComposeBundle().getString("renameAttachmentTitle"),
       getComposeBundle().getString("renameAttachmentMessage"),
       attachmentName,
       null,
@@ -6332,34 +6301,33 @@ function RenameSelectedAttachment() {
  *                    "bundleUp"  : Move attachments together (upwards).
  *                    "bundleDown": Move attachments together (downwards).
  *                    "toggleSort": Sort attachments alphabetically (toggle).
  */
 function moveSelectedAttachments(aDirection) {
   // Command controllers will bail out if no or all attachments are selected,
   // or if block selections can't be moved, or if other direction-specific
   // adverse circumstances prevent the intended movement.
-
   if (!aDirection) {
     return;
   }
 
-  let bucket = document.getElementById("attachmentBucket");
-
-  // Ensure focus on bucket when we're coming from 'Reorder Attachments' panel.
-  bucket.focus();
-
-  // Get a sorted and "non-live" array of bucket.selectedItems.
+  // Ensure focus on gAttachmentBucket when we're coming from
+  // 'Reorder Attachments' panel.
+  gAttachmentBucket.focus();
+
+  // Get a sorted and "non-live" array of gAttachmentBucket.selectedItems.
   let selItems = attachmentsSelectionGetSortedArray();
 
-  let visibleIndex = bucket.currentIndex; // In case of misspelled aDirection.
+  // In case of misspelled aDirection.
+  let visibleIndex = gAttachmentBucket.currentIndex;
   // Keep track of the item we had focused originally. Deselect it though,
   // since listbox gets confused if you move its focused item around.
-  let focusItem = bucket.currentItem;
-  bucket.currentItem = null;
+  let focusItem = gAttachmentBucket.currentItem;
+  gAttachmentBucket.currentItem = null;
   let upwards;
   let targetItem;
 
   switch (aDirection) {
     case "up":
     case "down":
       // Move selected attachments upwards/downwards.
       upwards = aDirection == "up";
@@ -6385,45 +6353,45 @@ function moveSelectedAttachments(aDirect
                 // i.e. before previousElementSibling of block.
                 checkItem
               : // Downwards: Insert block items *after* checkItem,
                 // i.e. *before* nextElementSibling.nextElementSibling of block,
                 // which works according to spec even if that's null.
                 checkItem.nextElementSibling;
             // Move current blockItems.
             for (let blockItem of blockItems) {
-              bucket.insertBefore(blockItem, targetItem);
+              gAttachmentBucket.insertBefore(blockItem, targetItem);
             }
           }
           // Else if checkItem doesn't exist, the block is already at the edge
           // of the list, so we can't move it in the intended direction.
           blockItems.length = 0; // Either way, we're done with the current block.
         }
         // Else if current selItem is NOT the end of the current block, proceed:
         // Add next selItem to the block and see if that's the end of the block.
       } // Next selItem.
 
       // Ensure helpful visibility of moved items (scroll into view if needed):
       // If first item of selection is now at the top, first list item.
       // Else if last item of selection is now at the bottom, last list item.
       // Otherwise, let's see where we are going by ensuring visibility of the
       // nearest unselected sibling of selection according to direction of move.
-      if (bucket.getIndexOfItem(selItems[0]) == 0) {
+      if (gAttachmentBucket.getIndexOfItem(selItems[0]) == 0) {
         visibleIndex = 0;
       } else if (
-        bucket.getIndexOfItem(selItems[selItems.length - 1]) ==
-        bucket.itemCount - 1
+        gAttachmentBucket.getIndexOfItem(selItems[selItems.length - 1]) ==
+        gAttachmentBucket.itemCount - 1
       ) {
-        visibleIndex = bucket.itemCount - 1;
+        visibleIndex = gAttachmentBucket.itemCount - 1;
       } else if (upwards) {
-        visibleIndex = bucket.getIndexOfItem(
+        visibleIndex = gAttachmentBucket.getIndexOfItem(
           selItems[0].previousElementSibling
         );
       } else {
-        visibleIndex = bucket.getIndexOfItem(
+        visibleIndex = gAttachmentBucket.getIndexOfItem(
           selItems[selItems.length - 1].nextElementSibling
         );
       }
       break;
 
     case "top":
     case "bottom":
     case "bundleUp":
@@ -6432,43 +6400,43 @@ function moveSelectedAttachments(aDirect
 
       upwards = ["top", "bundleUp"].includes(aDirection);
       // Downwards: Reverse order of selItems so we can use the same algorithm.
       if (!upwards) {
         selItems.reverse();
       }
 
       if (["top", "bottom"].includes(aDirection)) {
-        let listEdgeItem = bucket.getItemAtIndex(
-          upwards ? 0 : bucket.itemCount - 1
+        let listEdgeItem = gAttachmentBucket.getItemAtIndex(
+          upwards ? 0 : gAttachmentBucket.itemCount - 1
         );
         let selEdgeItem = selItems[0];
         if (selEdgeItem != listEdgeItem) {
           // Top/Bottom: Move the first/last selected item to the edge of the list
           // so that we always have an initial anchor target block in the right
           // place, so we can use the same algorithm for top/bottom and
           // inner bundling.
           targetItem = upwards
             ? // Upwards: Insert before first list item.
               listEdgeItem
             : // Downwards: Insert after last list item, i.e.
               // *before* non-existing listEdgeItem.nextElementSibling,
               // which is null. It works because it's a feature.
               null;
-          bucket.insertBefore(selEdgeItem, targetItem);
+          gAttachmentBucket.insertBefore(selEdgeItem, targetItem);
         }
       }
       // We now have a selected block (at least one item) at the target position.
       // Let's find the end (inner edge) of that block and move only the
       // remaining selected items to avoid unnecessary moves.
       targetItem = null;
       for (let item of selItems) {
         if (targetItem) {
           // We know where to move it, so move it!
-          bucket.insertBefore(item, targetItem);
+          gAttachmentBucket.insertBefore(item, targetItem);
           if (!upwards) {
             // Downwards: As selItems are reversed, and there's no insertAfter()
             // method to insert *after* a stable target, we need to insert
             // *before* the first item of the target block at target position,
             // which is the current selItem which we've just moved onto the block.
             targetItem = item;
           }
         } else {
@@ -6486,31 +6454,31 @@ function moveSelectedAttachments(aDirect
                 item;
           }
           // Else if nextItem is selected, it is still part of initial anchor
           // target block, so just proceed to look for the edge of that block.
         }
       } // next selItem
 
       // Ensure visibility of first/last selected item after the move.
-      visibleIndex = bucket.getIndexOfItem(selItems[0]);
+      visibleIndex = gAttachmentBucket.getIndexOfItem(selItems[0]);
       break;
 
     case "toggleSort":
       // Sort the selected attachments alphabetically after moving them together.
       // The command updater of cmd_sortAttachmentsToggle toggles the sorting
       // direction based on the current sorting and block status of the selection.
 
       let toggleCmd = document.getElementById("cmd_sortAttachmentsToggle");
       let sortDirection =
         toggleCmd.getAttribute("sortdirection") || "ascending";
       let sortItems;
       let sortSelection;
 
-      if (attachmentsSelectedCount() > 1) {
+      if (gAttachmentBucket.selectedCount > 1) {
         // Sort selected attachments only.
         sortSelection = true;
         sortItems = selItems;
         // Move selected attachments together before sorting as a block.
         goDoCommand("cmd_moveAttachmentBundleUp");
 
         // Find the end of the selected block to find our targetItem.
         for (let item of selItems) {
@@ -6541,34 +6509,35 @@ function moveSelectedAttachments(aDirect
         // "descending"
         sortItems.sort((a, b) =>
           b.attachment.name.localeCompare(a.attachment.name)
         );
       }
 
       // Insert sortItems in new order before the nextElementSibling of the block.
       for (let item of sortItems) {
-        bucket.insertBefore(item, targetItem);
+        gAttachmentBucket.insertBefore(item, targetItem);
       }
 
       if (sortSelection) {
         // After sorting selection: Ensure visibility of first selected item.
-        visibleIndex = bucket.getIndexOfItem(selItems[0]);
+        visibleIndex = gAttachmentBucket.getIndexOfItem(selItems[0]);
       } else {
         // After sorting all items: Ensure visibility of selected item,
         // otherwise first list item.
-        visibleIndex = selItems.length == 1 ? bucket.selectedIndex : 0;
+        visibleIndex =
+          selItems.length == 1 ? gAttachmentBucket.selectedIndex : 0;
       }
       break;
   } // end switch (aDirection)
 
   // Restore original focus.
-  bucket.currentItem = focusItem;
+  gAttachmentBucket.currentItem = focusItem;
   // Ensure smart visibility of a relevant item according to direction.
-  bucket.ensureIndexIsVisible(visibleIndex);
+  gAttachmentBucket.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();
 }
 /* eslint-enable complexity */
 
 /**
@@ -6576,48 +6545,47 @@ function moveSelectedAttachments(aDirect
  * If aAction parameter is omitted, toggle current view state.
  *
  * @param {string} [aAction = "toggle"] - "show":   show attachment pane
  *                                        "hide":   hide attachment pane
  *                                        "toggle": toggle attachment pane
  * @param {Event} [event] - The command event (cmd_toggleAttachmentPane)
  */
 function toggleAttachmentPane(aAction = "toggle", event) {
-  let bucket = GetMsgAttachmentElement();
   let attachmentsBox = document.getElementById("attachments-box");
   let attachmentBucketSizer = document.getElementById("attachmentbucket-sizer");
-  let bucketHasFocus = document.activeElement == bucket;
+  let bucketHasFocus = document.activeElement == gAttachmentBucket;
 
   if (aAction == "toggle") {
     let shown = !attachmentsBox.collapsed;
 
     if (shown && !bucketHasFocus && event && (event.altKey || event.ctrlKey)) {
       // If attachment pane is shown but not focused, and we're here via
       // key_toggleAttachmentPane, handle access key here: Focus bucket.
-      bucket.focus();
-      if (bucket.currentItem) {
-        bucket.ensureElementIsVisible(bucket.currentItem);
+      gAttachmentBucket.focus();
+      if (gAttachmentBucket.currentItem) {
+        gAttachmentBucket.ensureElementIsVisible(gAttachmentBucket.currentItem);
       }
       return;
     }
 
     // Toggle attachment pane.
     aAction = shown ? "hide" : "show";
   }
 
   switch (aAction) {
     case "show": {
       attachmentsBox.collapsed = false;
       attachmentBucketSizer.collapsed = false;
       attachmentBucketSizer.setAttribute("state", "");
       if (!bucketHasFocus) {
-        bucket.focus();
-      }
-      if (bucket.currentItem) {
-        bucket.ensureElementIsVisible(bucket.currentItem);
+        gAttachmentBucket.focus();
+      }
+      if (gAttachmentBucket.currentItem) {
+        gAttachmentBucket.ensureElementIsVisible(gAttachmentBucket.currentItem);
       }
       break;
     }
 
     case "hide": {
       if (bucketHasFocus) {
         SetMsgBodyFrameFocus();
       }
@@ -6652,17 +6620,17 @@ function showReorderAttachmentsPanel() {
   );
   // 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
   // is auto-hiding, but we don't want panel to hide when focus goes to bucket.
-  document.getElementById("attachmentBucket").focus();
+  gAttachmentBucket.focus();
 }
 
 /**
  * Returns a string representing the current sort order of selected attachment
  * items by their names. We don't check if selected items form a coherent block
  * or not; use attachmentsSelectionIsBlock() to check on that.
  *
  * @return {string} "ascending" : Sort order is ascending.
@@ -6688,24 +6656,24 @@ function attachmentsSelectionGetSortOrde
  *                  "equivalent": The names of the items are equivalent.
  *                  ""          : There's no sort order, or no attachments,
  *                                or no attachmentBucket; or (with aSelectedOnly),
  *                                only 1 item selected, or no items selected.
  */
 function attachmentsGetSortOrder(aSelectedOnly = false) {
   let listItems;
   if (aSelectedOnly) {
-    if (attachmentsSelectedCount() <= 1) {
+    if (gAttachmentBucket.selectedCount <= 1) {
       return "";
     }
 
     listItems = attachmentsSelectionGetSortedArray();
   } else {
     // aSelectedOnly == false
-    if (attachmentsCount() < 1) {
+    if (!gAttachmentBucket.itemCount) {
       return "";
     }
 
     listItems = attachmentsGetSortedArray();
   }
 
   // We're comparing each item to the next item, so exclude the last item.
   let listItems1 = listItems.slice(0, -1);
@@ -6751,17 +6719,17 @@ function reorderAttachmentsPanelOnPopupS
   // Let's add some pretty keyboard shortcuts to the buttons.
   buttons.forEach(btn => {
     if (btn.hasAttribute("key")) {
       btn.setAttribute("prettykey", getPrettyKey(btn.getAttribute("key")));
     }
   });
   // Focus attachment bucket to activate attachmentBucketController, which is
   // required for updating the reorder commands.
-  document.getElementById("attachmentBucket").focus();
+  gAttachmentBucket.focus();
   // We're updating commands before showing the panel so that button states
   // don't change after the panel is shown, and also because focus is still
   // in attachment bucket right now, which is required for updating them.
   updateReorderAttachmentsItems();
 }
 
 function attachmentHeaderContextOnPopupShowing() {
   let initiallyShowItem = document.getElementById(
@@ -6797,42 +6765,40 @@ function attachmentBucketOnBlur() {
     document.activeElement.id != "attachmentBucket" &&
     reorderAttachmentsPanel.state != "showing"
   ) {
     reorderAttachmentsPanel.hidePopup();
   }
 }
 
 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 if (gAttachmentBucket.itemCount) {
+      if (gAttachmentBucket.selectedCount) {
+        // Then deselect selected items in full gAttachmentBucket if any.
+        gAttachmentBucket.clearSelection();
       } else {
-        // Then unfocus full bucket to continue with msg body.
+        // Then unfocus full gAttachmentBucket to continue with msg body.
         SetMsgBodyFrameFocus();
       }
     } else {
-      // (bucket.itemCount == 0)
-      // Otherwise close empty bucket.
+      // (gAttachmentBucket.itemCount == 0)
+      // Otherwise close empty gAttachmentBucket.
       toggleAttachmentPane("hide");
     }
   }
 
-  if (aEvent.key == "Enter" && bucket.itemCount == 0) {
+  if (aEvent.key == "Enter" && !gAttachmentBucket.itemCount) {
     // Enter on empty bucket to add file attachments, convenience
     // keyboard equivalent of single-click on bucket whitespace.
     goDoCommand("cmd_attachFile");
   }
 }
 
 function attachmentBucketOnClick(aEvent) {
   // Handle click on attachment pane whitespace normally clear selection.
@@ -6847,25 +6813,23 @@ function attachmentBucketOnClick(aEvent)
 }
 
 function attachmentBucketOnSelect() {
   attachmentBucketUpdateTooltips();
   updateAttachmentItems();
 }
 
 function attachmentBucketUpdateTooltips() {
-  let bucket = GetMsgAttachmentElement();
-
   // Attachment pane whitespace tooltip
-  if (attachmentsSelectedCount() > 0) {
-    bucket.tooltipText = getComposeBundle().getString(
+  if (gAttachmentBucket.selectedCount) {
+    gAttachmentBucket.tooltipText = getComposeBundle().getString(
       "attachmentBucketClearSelectionTooltip"
     );
   } else {
-    bucket.tooltipText = getComposeBundle().getString(
+    gAttachmentBucket.tooltipText = getComposeBundle().getString(
       "attachmentBucketAttachFilesTooltip"
     );
   }
 }
 
 function attachmentBucketHeaderOnClick(aEvent) {
   if (aEvent.button == 0) {
     // Left click
@@ -6880,35 +6844,28 @@ function attachmentBucketCloseButtonOnCo
 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 attachmentsBox = document.getElementById("attachments-box");
-  if (attachmentBucket.itemCount > 0) {
+  if (gAttachmentBucket.itemCount) {
     attachmentsBox.removeAttribute("empty");
   } else {
     attachmentsBox.setAttribute("empty", "true");
   }
 }
 
 function OpenSelectedAttachment() {
-  let bucket = document.getElementById("attachmentBucket");
-  if (bucket.selectedItems.length == 1) {
-    let attachmentUrl = bucket.getSelectedItem(0).attachment.url;
+  if (gAttachmentBucket.selectedItems.length == 1) {
+    let attachmentUrl = gAttachmentBucket.getSelectedItem(0).attachment.url;
 
     let messagePrefix = /^mailbox-message:|^imap-message:|^news-message:/i;
     if (messagePrefix.test(attachmentUrl)) {
       // we must be dealing with a forwarded attachment, treat this special
       let msgHdr = gMessenger
         .messageServiceFromURI(attachmentUrl)
         .messageURIToMsgHdr(attachmentUrl);
       if (msgHdr) {
@@ -7392,40 +7349,38 @@ var envelopeDragObserver = {
    * @param {Event} event - The drag-and-drop event being performed.
    * @return {attachmentitem|string} - the adjusted drop target:
    *   - an attachmentitem node for inserting *before*
    *   - "none" if this isn't a valid insertion point
    *   - "afterLastItem" for appending at the bottom of the list.
    */
   _adjustDropTarget(event) {
     let target = event.target;
-    let bucket = document.getElementById("attachmentBucket");
-
-    if (target == bucket) {
+    if (target == gAttachmentBucket) {
       // Dragging or dropping at top/bottom border of the listbox
       if (
         (event.screenY - target.screenY) /
           target.getBoundingClientRect().height <
         0.5
       ) {
-        target = bucket.firstElementChild;
+        target = gAttachmentBucket.firstElementChild;
       } else {
-        target = bucket.lastElementChild;
+        target = gAttachmentBucket.lastElementChild;
       }
       // We'll check below if this is a valid target.
     } else if (target.id == "attachmentBucketCount") {
       // Dragging or dropping at top border of the listbox.
       // Allow bottom half of attachment list header as extended drop target
       // for top of list, because otherwise it would be too small.
       if (
         (event.screenY - target.screenY) /
           target.getBoundingClientRect().height >=
         0.5
       ) {
-        target = bucket.firstElementChild;
+        target = gAttachmentBucket.firstElementChild;
         // We'll check below if this is a valid target.
       } else {
         // Top half of attachment list header: sorry, can't drop here.
         return "none";
       }
     }
 
     // Target is an attachmentitem.
@@ -7468,27 +7423,25 @@ var envelopeDragObserver = {
       }
       return target;
     }
 
     return "none";
   },
 
   _showDropMarker(targetItem) {
-    let bucket = document.getElementById("attachmentBucket");
-
-    let oldDropMarkerItem = bucket.querySelector(
+    let oldDropMarkerItem = gAttachmentBucket.querySelector(
       "richlistitem.attachmentItem[dropOn]"
     );
     if (oldDropMarkerItem) {
       oldDropMarkerItem.removeAttribute("dropOn");
     }
 
     if (targetItem == "afterLastItem") {
-      targetItem = bucket.lastElementChild;
+      targetItem = gAttachmentBucket.lastElementChild;
       targetItem.setAttribute("dropOn", "bottom");
     } else {
       targetItem.setAttribute("dropOn", "top");
     }
   },
 
   _hideDropMarker() {
     let oldDropMarkerItem = document
@@ -7496,94 +7449,96 @@ var envelopeDragObserver = {
       .querySelector("richlistitem.attachmentItem[dropOn]");
     if (oldDropMarkerItem) {
       oldDropMarkerItem.removeAttribute("dropOn");
     }
   },
 
   // eslint-disable-next-line complexity
   onDrop(event) {
-    let bucket = document.getElementById("attachmentBucket");
     let dragSession = Cc["@mozilla.org/widget/dragservice;1"]
       .getService(Ci.nsIDragService)
       .getCurrentSession();
     let dragSourceNode = dragSession.sourceNode;
-    if (dragSourceNode && dragSourceNode.parentNode == bucket) {
+    if (dragSourceNode && dragSourceNode.parentNode == gAttachmentBucket) {
       // We dragged from the attachment pane onto itself, so instead of
       // attaching a new object, we're just reordering them.
 
       // Adjust the drop target according to mouse position on list (items).
       let target = this._adjustDropTarget(event);
 
       // Get a non-live, sorted list of selected attachment list items.
       let selItems = attachmentsSelectionGetSortedArray();
       // Keep track of the item we had focused originally. Deselect it though,
       // since listbox gets confused if you move its focused item around.
-      let focus = bucket.currentItem;
-      bucket.currentItem = null;
+      let focus = gAttachmentBucket.currentItem;
+      gAttachmentBucket.currentItem = null;
 
       // Moving possibly non-coherent multiple selections around correctly
       // is much more complex than one might think...
       if (
         (target.matches && target.matches("richlistitem.attachmentItem")) ||
         target == "afterLastItem"
       ) {
         // Drop before targetItem in the list, or after last item.
         let blockItems = [];
         let targetItem;
         for (let item of selItems) {
           blockItems.push(item);
           if (target == "afterLastItem") {
             // Original target is the end of the list; append all items there.
-            bucket.appendChild(item);
+            gAttachmentBucket.appendChild(item);
           } else if (target == selItems[0]) {
             // Original target is first item of first selected block.
             if (blockItems.includes(target)) {
               // Item is in first block: do nothing, find the end of the block.
               let nextItem = item.nextElementSibling;
               if (!nextItem || !nextItem.selected) {
                 // We've reached the end of the first block.
                 blockItems.length = 0;
                 targetItem = nextItem;
               }
             } else {
               // Item is NOT in first block: insert before targetItem,
               // i.e. after end of first block.
-              bucket.insertBefore(item, targetItem);
+              gAttachmentBucket.insertBefore(item, targetItem);
             }
           } else if (target.selected) {
             // Original target is not first item of first block,
             // but first item of another block.
-            if (bucket.getIndexOfItem(item) < bucket.getIndexOfItem(target)) {
+            if (
+              gAttachmentBucket.getIndexOfItem(item) <
+              gAttachmentBucket.getIndexOfItem(target)
+            ) {
               // Insert all items from preceding blocks before original target.
-              bucket.insertBefore(item, target);
+              gAttachmentBucket.insertBefore(item, target);
             } else if (blockItems.includes(target)) {
               // target is included in any selected block except first:
               // do nothing for that block, find its end.
               let nextItem = item.nextElementSibling;
               if (!nextItem || !nextItem.selected) {
                 // end of block containing target
                 blockItems.length = 0;
                 targetItem = nextItem;
               }
             } else {
               // Item from block after block containing target: insert before
               // targetItem, i.e. after end of block containing target.
-              bucket.insertBefore(item, targetItem);
+              gAttachmentBucket.insertBefore(item, targetItem);
             }
           } else {
             // target != selItems [0]
             // Original target is NOT first item of any block, and NOT selected:
             // Insert all items before the original target.
-            bucket.insertBefore(item, target);
+            gAttachmentBucket.insertBefore(item, target);
           }
         }
       }
 
-      bucket.currentItem = focus;
+      gAttachmentBucket.currentItem = focus;
       this._hideDropMarker();
       return;
     }
 
     let attachments = [];
     let dt = event.dataTransfer;
     let dataList = [];
     for (let i = 0; i < dt.mozItemCount; i++) {
@@ -7686,29 +7641,28 @@ var envelopeDragObserver = {
       }
     }
 
     // Add attachments if any.
     if (attachments.length > 0) {
       AddAttachments(attachments);
     }
 
-    bucket.focus();
+    gAttachmentBucket.focus();
     event.stopPropagation();
   },
 
   onDragOver(event) {
     let dragSession = Cc["@mozilla.org/widget/dragservice;1"]
       .getService(Ci.nsIDragService)
       .getCurrentSession();
-    let bucket = document.getElementById("attachmentBucket");
     let dragSourceNode = dragSession.sourceNode;
-    if (dragSourceNode && dragSourceNode.parentNode == bucket) {
-      // If we're dragging from the attachment bucket onto itself, we need to
-      // show a drop marker.
+    if (dragSourceNode && dragSourceNode.parentNode == gAttachmentBucket) {
+      // If we're dragging from the attachment gAttachmentBucket onto itself,
+      // we need to show a drop marker.
 
       let target = this._adjustDropTarget(event);
 
       if (
         (target.matches && target.matches("richlistitem.attachmentItem")) ||
         target == "afterLastItem"
       ) {
         // Adjusted target is an attachment list item; show dropmarker.
@@ -7799,21 +7753,16 @@ function SetMsgToRecipientElementFocus()
 function SetMsgIdentityElementFocus() {
   document.getElementById("msgIdentity").focus();
 }
 
 function SetMsgSubjectElementFocus() {
   document.getElementById("msgSubject").focus();
 }
 
-function SetMsgAttachmentElementFocus() {
-  // Caveat: Callers must ensure that attachment pane is visible.
-  GetMsgAttachmentElement().focus();
-}
-
 /**
  * Focus the people search input in contacts side bar.
  *
  * @return {Boolean} true if peopleSearchInput was found, false otherwise.
  */
 function focusContactsSidebarSearchInput() {
   // Caveat: Callers must ensure that contacts side bar is visible.
   let peopleSearchInput = sidebarDocumentGetElementById(
@@ -7829,24 +7778,16 @@ function focusContactsSidebarSearchInput
 
 function SetMsgBodyFrameFocus() {
   // window.content.focus() fails to blur the currently focused element
   document.commandDispatcher.advanceFocusIntoSubtree(
     document.getElementById("appcontent")
   );
 }
 
-function GetMsgAttachmentElement() {
-  if (!gMsgAttachmentElement) {
-    gMsgAttachmentElement = document.getElementById("attachmentBucket");
-  }
-
-  return gMsgAttachmentElement;
-}
-
 /**
  * Get an element by ID in the current sidebar browser document.
  *
  * @param aId {string}       the ID of the element to get
  * @param aWindowId {string} the ID of a <window> in the sidebar <browser>;
  *                           only return the element if the window exists.
  *                           Assuming unique window ids and that there there can
  *                           only ever be one <window> in a <browser>'s src.xhtml
@@ -7971,22 +7912,22 @@ function SwitchElementFocus(event) {
         }
         break;
       case sidebarDocumentGetElementById("abContactsPanel"):
         SetMsgBodyFrameFocus();
         break;
       case document.getElementById("content-frame"): // message body
         // Focus attachment bucket if shown, otherwise message subject.
         if (!document.getElementById("attachments-box").collapsed) {
-          SetMsgAttachmentElementFocus();
+          gAttachmentBucket.focus();
         } else {
           SetMsgSubjectElementFocus();
         }
         break;
-      case gMsgAttachmentElement:
+      case gAttachmentBucket:
         SetMsgSubjectElementFocus();
         break;
       case document.getElementById("msgSubject"):
         SetFocusOnPreviousAvailableElement(focusedElement);
         break;
       default:
         SetMsgToRecipientElementFocus();
         break;
@@ -8012,22 +7953,22 @@ function SwitchElementFocus(event) {
     case document.getElementById("replyAddrInput"):
     case document.getElementById("followupAddrInput"):
     case document.getElementById("newsgroupsAddrInput"):
       SetFocusOnNextAvailableElement(focusedElement);
       break;
     case document.getElementById("msgSubject"):
       // Focus attachment bucket if shown, otherwise message body.
       if (!document.getElementById("attachments-box").collapsed) {
-        SetMsgAttachmentElementFocus();
+        gAttachmentBucket.focus();
       } else {
         SetMsgBodyFrameFocus();
       }
       break;
-    case gMsgAttachmentElement:
+    case gAttachmentBucket:
       SetMsgBodyFrameFocus();
       break;
     case document.getElementById("content-frame"): // message body
       // Focus the search input of contacts side bar if that's available,
       // otherwise focus "From" selector.
       if (sidebar_is_hidden() || !focusContactsSidebarSearchInput()) {
         SetMsgIdentityElementFocus();
       }
@@ -8752,18 +8693,17 @@ function getMailToolbox() {
 
 /**
  * Helper function to dispatch a CustomEvent to the attachmentbucket.
  *
  * @param aEventType the name of the event to fire.
  * @param aData any detail data to pass to the CustomEvent.
  */
 function dispatchAttachmentBucketEvent(aEventType, aData) {
-  let bucket = document.getElementById("attachmentBucket");
-  bucket.dispatchEvent(
+  gAttachmentBucket.dispatchEvent(
     new CustomEvent(aEventType, {
       bubbles: true,
       cancelable: true,
       detail: aData,
     })
   );
 }