Bug 440377 - Implement new recipients address fields. r=mkmelin, ui-review=Paenglab
authorAlessandro Castellani <alessandro@thunderbird.net>
Thu, 12 Dec 2019 11:58:10 +0200
changeset 37740 019eaf0f9cf72621a436ce06b2607bc5a3532fae
parent 37739 c10cbc3c87fb2004d7ca3ae2525cf4be48bf377c
child 37741 8157030f14a55019fa7bbeb4e0bd9f9228c73f2d
push id397
push userclokep@gmail.com
push dateMon, 10 Feb 2020 21:16:13 +0000
reviewersmkmelin
bugs440377
Bug 440377 - Implement new recipients address fields. r=mkmelin, ui-review=Paenglab
mail/base/content/mailCommands.js
mail/base/content/mailWidgets.js
mail/components/addrbook/content/abEditListDialog.xul
mail/components/addrbook/content/abMailListDialog.xul
mail/components/compose/content/MsgComposeCommands.js
mail/components/compose/content/addressingWidgetOverlay.js
mail/components/compose/content/messengercompose.xul
mail/extensions/smime/content/msgCompSMIMEOverlay.js
mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
mail/test/browser/composition/browser_addressWidgets.js
mail/test/browser/composition/browser_draftIdentity.js
mail/test/browser/composition/browser_focus.js
mail/test/browser/composition/browser_replyAddresses.js
mail/test/browser/composition/browser_sendButton.js
mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js
mail/test/mozmill/composition/test-address-widgets.js
mail/test/mozmill/composition/test-draft-identity.js
mail/test/mozmill/composition/test-focus.js
mail/test/mozmill/composition/test-reply-addresses.js
mail/test/mozmill/composition/test-send-button.js
mail/test/mozmill/folder-display/test-message-commands-on-msgstore.js
mail/test/mozmill/message-header/test-reply-identity.js
mail/test/mozmill/shared-modules/ComposeHelpers.jsm
mail/themes/linux/mail/compose/messengercompose.css
mail/themes/osx/mail/compose/messengercompose.css
mail/themes/shared/mail/compacttheme.css
mail/themes/shared/mail/input-fields.css
mail/themes/shared/mail/messengercompose.css
mail/themes/windows/mail/compose/messengercompose.css
mail/themes/windows/mail/messenger.css
mailnews/addrbook/content/abMailListDialog.js
--- a/mail/base/content/mailCommands.js
+++ b/mail/base/content/mailCommands.js
@@ -261,16 +261,17 @@ function ComposeMessage(type, format, fo
           let hdrIdentity = MailUtils.getIdentityForHeader(
             hdr,
             type,
             findDeliveredToIdentityEmail(hdr)
           );
           if (ignoreQuote) {
             type += msgComposeType.ReplyIgnoreQuote;
           }
+
           MailServices.compose.OpenComposeWindow(
             null,
             hdr,
             messageUri,
             type,
             format,
             hdrIdentity,
             msgWindow
--- a/mail/base/content/mailWidgets.js
+++ b/mail/base/content/mailWidgets.js
@@ -1,37 +1,44 @@
 /**
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* import-globals-from ../../components/compose/content/addressingWidgetOverlay.js */
+
 /* global MozElements */
 /* global MozXULElement */
 /* global openUILink */
 /* global MessageIdClick */
 /* global onClickEmailStar */
 /* global onClickEmailPresence */
 /* global gFolderDisplay */
 /* global UpdateEmailNodeDetails */
 /* global PluralForm */
 /* global UpdateExtraAddressProcessing */
+/* global getSiblingPills setFocusOnFirstPill getAllPills getAllSelectedPills onRecipientsChanged */
 
 // Wrap in a block to prevent leaking to window scope.
 {
   const { Services } = ChromeUtils.import(
     "resource://gre/modules/Services.jsm"
   );
   const { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
   const { MailServices } = ChromeUtils.import(
     "resource:///modules/MailServices.jsm"
   );
   const { DBViewWrapper } = ChromeUtils.import(
     "resource:///modules/DBViewWrapper.jsm"
   );
   const { TagUtils } = ChromeUtils.import("resource:///modules/TagUtils.jsm");
+  var { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+  var { DisplayNameUtils } = ChromeUtils.import(
+    "resource:///modules/DisplayNameUtils.jsm"
+  );
 
   class MozMailHeaderfield extends MozXULElement {
     connectedCallback() {
       this.setAttribute("context", "copyPopup");
       this.classList.add("headerValue");
     }
 
     set headerValue(val) {
@@ -1742,9 +1749,464 @@
         child.width = width;
       }
     }
   }
 
   customElements.define("attachment-list", MozAttachmentlist, {
     extends: "richlistbox",
   });
+
+  /**
+   * The MailAddressPill widget is used to display the email addresses in the
+   * messengercompose.xul window.
+   *
+   * @extends {MozXULElement}
+   */
+  class MailAddressPill extends MozXULElement {
+    static get inheritedAttributes() {
+      return {
+        ".pill-label": "crop,value=label",
+      };
+    }
+
+    connectedCallback() {
+      if (this.hasChildNodes() || this.delayConnectedCallback()) {
+        return;
+      }
+
+      // Used to store the html:input element from where the pill was generated.
+      this.originalInput;
+
+      this.classList.add("address-pill");
+      this.setAttribute("context", "emailAddressPillPopup");
+      this.setAttribute("allowevents", "true");
+
+      this.pillLabel = document.createXULElement("label");
+      this.pillLabel.classList.add("pill-label");
+
+      this.pillDeleteImage = document.createXULElement("image");
+      this.pillDeleteImage.classList.add("delete-pill-icon");
+
+      this.appendChild(this.pillLabel);
+      this._setupEmailInput();
+      this.appendChild(this.pillDeleteImage);
+
+      this._setupEventListeners();
+      this.initializeAttributeInheritance();
+
+      // @implements {nsIObserver}
+      this.inputObserver = {
+        observe: (subject, topic, data) => {
+          if (topic == "autocomplete-did-enter-text") {
+            this.updatePill();
+          }
+        },
+      };
+
+      Services.obs.addObserver(
+        this.inputObserver,
+        "autocomplete-did-enter-text"
+      );
+
+      // Remove the observer on window unload as the disconnectedCallback()
+      // will never be called when closing a window, so we might therefore
+      // leak if XPCOM isn't smart enough.
+      window.addEventListener(
+        "unload",
+        () => {
+          this.removeObserver();
+        },
+        { once: true }
+      );
+    }
+
+    get emailAddress() {
+      return this.getAttribute("emailAddress");
+    }
+
+    set emailAddress(val) {
+      this.setAttribute("emailAddress", val);
+    }
+
+    get label() {
+      return this.getAttribute("label");
+    }
+
+    set label(val) {
+      this.setAttribute("label", val);
+    }
+
+    get fullAddress() {
+      return this.getAttribute("fullAddress");
+    }
+
+    set fullAddress(val) {
+      this.setAttribute("fullAddress", val);
+    }
+
+    get displayName() {
+      return this.getAttribute("displayName");
+    }
+
+    set displayName(val) {
+      this.setAttribute("displayName", val);
+    }
+
+    /**
+     * Check if the pill is currently in "Edit Mode", meaning the label is
+     * hidden and the html:input field is visible.
+     *
+     * @returns {boolean} True if the pill is currently being edited.
+     */
+    get isEditing() {
+      return !this.emailInput.hasAttribute("hidden");
+    }
+
+    get fragment() {
+      if (!this.constructor.hasOwnProperty("_fragment")) {
+        this.constructor._fragment = MozXULElement.parseXULToFragment(`
+          <html:input is="autocomplete-input"
+                      type="text"
+                      class="${this.originalInput.getAttribute(
+                        "class"
+                      )} input-pill"
+                      disableonsend="true"
+                      aria-labelledby="${this.originalInput.getAttribute(
+                        "aria-labelledby"
+                      )}"
+                      autocompletesearch="mydomain addrbook ldap news"
+                      autocompletesearchparam="{}"
+                      timeout="300"
+                      maxrows="6"
+                      completedefaultindex="true"
+                      forcecomplete="true"
+                      completeselectedindex="true"
+                      minresultsforpopup="2"
+                      ignoreblurwhilesearching="true"
+                      hidden="hidden"/>
+        `);
+      }
+      return document.importNode(this.constructor._fragment, true);
+    }
+
+    _setupEmailInput() {
+      this.appendChild(this.fragment);
+
+      this.emailInput = this.querySelector(`input[is="autocomplete-input"]`);
+      this.emailInput.value = this.fullAddress;
+
+      let params = JSON.parse(
+        this.emailInput.getAttribute("autocompletesearchparam")
+      );
+      params.type = this.originalInput.getAttribute("recipienttype");
+      this.emailInput.setAttribute(
+        "autocompletesearchparam",
+        JSON.stringify(params)
+      );
+
+      this.appendChild(this.emailInput);
+    }
+
+    _setupEventListeners() {
+      this.addEventListener("click", this);
+      this.addEventListener("dblclick", this);
+      this.addEventListener("keypress", this);
+
+      this.emailInput.addEventListener("keypress", event => {
+        this.finishEditing(event);
+      });
+
+      this.emailInput.addEventListener("blur", () => {
+        this.updatePill();
+      });
+
+      this.pillDeleteImage.addEventListener("click", () => {
+        this.removePills();
+      });
+    }
+
+    handleEvent(event) {
+      switch (event.type) {
+        case "click":
+          this.checkSelected(event);
+          break;
+        case "dblclick":
+          this.startEditing(event);
+          break;
+        case "keypress":
+          this.handleKeyPress(event);
+          break;
+      }
+    }
+
+    handleKeyPress(event) {
+      if (this.isEditing) {
+        return;
+      }
+
+      switch (event.key) {
+        case " ":
+          this.checkSelected(event);
+          break;
+
+        case "Enter":
+        case "F2": // For Windows users
+          this.startEditing(event);
+          break;
+
+        case "Delete":
+        case "Backspace":
+          this.removePills();
+          break;
+
+        case "ArrowLeft":
+          if (this.previousElementSibling) {
+            this.previousElementSibling.focus();
+            this.checkKeyboardSelected(event, this.previousElementSibling);
+          }
+          break;
+
+        case "ArrowRight":
+          if (this.nextElementSibling.hasAttribute("hidden")) {
+            this.nextElementSibling.removeAttribute("hidden");
+            this.nextElementSibling.focus();
+            break;
+          }
+          this.nextElementSibling.focus();
+          this.checkKeyboardSelected(event, this.nextElementSibling);
+          break;
+
+        case "Home":
+          this.removeAttribute("selected");
+          setFocusOnFirstPill(this);
+          break;
+
+        case "End":
+          this.originalInput.focus();
+          break;
+
+        case "Tab":
+          for (let pill of getSiblingPills(this)) {
+            pill.removeAttribute("selected");
+          }
+          break;
+
+        case "a":
+          if (event.ctrlKey || event.metaKey) {
+            this.selectPills();
+          }
+          break;
+
+        case "c":
+          if (event.ctrlKey || event.metaKey) {
+            copyEmailNewsAddress(this);
+          }
+          break;
+
+        case "x":
+          if (event.ctrlKey || event.metaKey) {
+            copyEmailNewsAddress(this);
+            deleteAddressPill(this);
+          }
+          break;
+      }
+    }
+
+    selectPills() {
+      for (let pill of getSiblingPills(this)) {
+        pill.setAttribute("selected", "selected");
+      }
+    }
+
+    clearSelected() {
+      for (let pill of getAllPills()) {
+        pill.removeAttribute("selected");
+      }
+    }
+
+    checkSelected(event) {
+      if (
+        this.isEditing ||
+        (this.hasAttribute("selected") && event.which == 3)
+      ) {
+        return;
+      }
+
+      if (!event.ctrlKey && !event.metaKey && event.key != " ") {
+        this.clearSelected();
+      }
+
+      this.toggleAttribute("selected");
+      if (!this.hasAttribute("selected") && event.key != " ") {
+        this.blur();
+      } else {
+        this.focus();
+      }
+    }
+
+    checkKeyboardSelected(event, element) {
+      if (event.shiftKey) {
+        if (this.hasAttribute("selected") && element.hasAttribute("selected")) {
+          this.removeAttribute("selected");
+          return;
+        }
+
+        this.setAttribute("selected", "selected");
+        element.setAttribute("selected", "selected");
+      } else if (!event.ctrlKey) {
+        this.clearSelected();
+      }
+    }
+
+    /**
+     * When a "Delete" action is triggered, we need to check if other pills are
+     * currently selected and delete them all.
+     */
+    removePills() {
+      for (let pill of getAllSelectedPills()) {
+        pill.remove();
+      }
+
+      this.originalInput.focus();
+      this.remove();
+
+      onRecipientsChanged();
+    }
+
+    /**
+     * Simple email address validation.
+     *
+     * @param {String} address - An email address.
+     */
+    isValidAddress(address) {
+      return address.includes("@", 1) && !address.endsWith("@");
+    }
+
+    /**
+     * Convert the pill into "Edit Mode", meaning hiding the label and showing
+     * the html:input element.
+     *
+     * @param {Event} event - The DOM Event.
+     */
+    startEditing(event) {
+      if (this.isEditing) {
+        event.stopPropagation();
+        return;
+      }
+
+      for (let pill of getAllPills()) {
+        pill.finishEditing();
+      }
+
+      // We need to set the min and max width before hiding and showing the
+      // child nodes in order to prevent unwanted jumps in the resizing of the
+      // edited pill. Both properties are necessary to handle flexbox.
+      this.style.setProperty("max-width", `${this.clientWidth}px`);
+      this.style.setProperty("min-width", `${this.clientWidth}px`);
+
+      this.classList.add("editing");
+      this.pillLabel.setAttribute("hidden", "true");
+      this.pillDeleteImage.setAttribute("hidden", "true");
+      this.emailInput.removeAttribute("hidden");
+      this.emailInput.focus();
+
+      // In case the original address is shorter than the input field child node
+      // force resize the pill container to prevent overflows.
+      if (this.emailInput.clientWidth > this.clientWidth) {
+        this.style.setProperty("max-width", `${this.emailInput.clientWidth}px`);
+        this.style.setProperty("min-width", `${this.emailInput.clientWidth}px`);
+      } else {
+        this.style.setProperty("max-width", `${this.clientWidth}px`);
+        this.style.setProperty("min-width", `${this.clientWidth}px`);
+      }
+    }
+
+    /**
+     * Revert the pill UI to a regular selectable element, meaning the label is
+     * visible and the html:input field is hidden.
+     *
+     * @param {Event} event - The DOM Event.
+     */
+    finishEditing(event = null) {
+      let key = event ? event.key : "Escape";
+
+      switch (key) {
+        case "Escape":
+          this.emailInput.value = this.fullAddress;
+          this.resetPill();
+          break;
+        case "Delete":
+        case "Backspace":
+          if (!this.emailInput.value.trim() && !event.repeat) {
+            this.originalInput.focus();
+            this.remove();
+          }
+          break;
+      }
+    }
+
+    updatePill() {
+      let addresses = MailServices.headerParser.makeFromDisplayAddress(
+        this.emailInput.value
+      );
+
+      if (!addresses[0]) {
+        this.originalInput.focus();
+        this.remove();
+        return;
+      }
+
+      this.label = addresses[0].toString();
+      this.emailAddress = addresses[0].email || "";
+      this.fullAddress = addresses[0].toString();
+      this.displayName = addresses[0].name || "";
+      // We need to detach the autocomplete Controller to prevent the input
+      // to be filled with the previously selected address when the "blur"
+      // event gets triggered.
+      this.emailInput.detachController();
+      // Attach it again to enable autocomplete.
+      this.emailInput.attachController();
+
+      this.resetPill();
+    }
+
+    resetPill() {
+      let isValid = this.isValidAddress(this.emailAddress);
+      let listNames = MimeParser.parseHeaderField(
+        this.fullAddress,
+        MimeParser.HEADER_ADDRESS
+      );
+      let isMailingList =
+        listNames.length > 0 &&
+        MailServices.ab.mailListNameExists(listNames[0].name);
+      let isNewsgroup = this.originalInput.classList.contains("nntp-input");
+
+      this.classList.toggle(
+        "error",
+        !isValid && !isMailingList && !isNewsgroup
+      );
+
+      let emailCard = DisplayNameUtils.getCardForEmail(this.emailAddress);
+      this.classList.toggle(
+        "warning",
+        isValid && !emailCard.card && !isMailingList && !isNewsgroup
+      );
+
+      this.style.removeProperty("max-width");
+      this.style.removeProperty("min-width");
+      this.classList.remove("editing");
+      this.pillLabel.removeAttribute("hidden");
+      this.pillDeleteImage.removeAttribute("hidden");
+      this.emailInput.setAttribute("hidden", "hidden");
+      this.originalInput.focus();
+    }
+
+    removeObserver() {
+      Services.obs.removeObserver(
+        this.inputObserver,
+        "autocomplete-did-enter-text"
+      );
+    }
+  }
+
+  customElements.define("mail-address-pill", MailAddressPill);
 }
--- a/mail/components/addrbook/content/abEditListDialog.xul
+++ b/mail/components/addrbook/content/abEditListDialog.xul
@@ -16,17 +16,16 @@
         ondrop="DropOnAddressListTree(event);">
 <dialog id="ablistWindow">
 
   <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
 
   <script src="chrome://global/content/globalOverlay.js"/>
   <script src="chrome://global/content/editMenuOverlay.js"/>
   <!-- move needed functions into a single js file -->
-  <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
   <script src="chrome://messenger/content/addressbook/abCommon.js"/>
   <script src="chrome://messenger/content/addressbook/abMailListDialog.js"/>
 
   <vbox id="editlist">
     <hbox id="ListNameContainer" align="center">
       <spacer flex="1"/>
       <label control="ListName" value="&ListName.label;" accesskey="&ListName.accesskey;" class="CardEditLabel"/>
       <hbox class="CardEditWidth input-container">
--- a/mail/components/addrbook/content/abMailListDialog.xul
+++ b/mail/components/addrbook/content/abMailListDialog.xul
@@ -16,17 +16,16 @@
         ondrop="DropOnAddressListTree(event);">
 <dialog id="ablistWindow">
 
   <stringbundle id="bundle_addressBook" src="chrome://messenger/locale/addressbook/addressBook.properties"/>
 
   <script src="chrome://global/content/globalOverlay.js"/>
   <script src="chrome://global/content/editMenuOverlay.js"/>
   <!-- move needed functions into a single js file -->
-  <script src="chrome://messenger/content/messengercompose/addressingWidgetOverlay.js"/>
   <script src="chrome://messenger/content/addressbook/abCommon.js"/>
   <script src="chrome://messenger/content/addressbook/abMailListDialog.js"/>
   <script><![CDATA[
     document.addEventListener("dialogaccept", MailListOKButton);
   ]]></script>
 
   <hbox align="center">
     <label control="abPopup" value="&addToAddressBook.label;" accesskey="&addToAddressBook.accesskey;"/>
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-/* global MozElements */
+/* global MozElements getSiblingPills */
 
 /* import-globals-from ../../../../mailnews/addrbook/content/abDragDrop.js */
 /* import-globals-from ../../../base/content/mailCore.js */
 /* import-globals-from ../../../base/content/utilityOverlay.js */
 /* import-globals-from addressingWidgetOverlay.js */
 /* import-globals-from ComposerCommands.js */
 /* import-globals-from editor.js */
 /* import-globals-from editorUtilities.js */
@@ -81,19 +81,16 @@ var gAutoSaving;
 var gCurrentIdentity;
 var defaultSaveOperation;
 var gSendOperationInProgress;
 var gSaveOperationInProgress;
 var gCloseWindowAfterSave;
 var gSavedSendNowKey;
 var gSendFormat;
 
-var gMsgIdentityElement;
-var gMsgAddressingWidgetTreeElement;
-var gMsgSubjectElement;
 var gMsgAttachmentElement;
 var gMsgHeadersToolbarElement;
 // TODO: Maybe the following two variables can be combined.
 var gManualAttachmentReminder;
 var gDisableAttachmentReminder;
 var gComposeType;
 var gLanguageObserver;
 var gBodyFromArgs;
@@ -124,16 +121,37 @@ var gAutoSaveInterval;
 var gAutoSaveTimeout;
 var gAutoSaveKickedIn;
 var gEditingDraft;
 var gAttachmentsSize;
 var gNumUploadingAttachments;
 
 var kComposeAttachDirPrefName = "mail.compose.attach.dir";
 
+// Observer for the autocomplete input.
+const inputObserver = {
+  observe: (subject, topic, data) => {
+    if (topic == "autocomplete-did-enter-text") {
+      let input = subject.QueryInterface(Ci.nsIAutoCompleteInput)
+        .wrappedJSObject;
+
+      let element = document.getElementById(input.id);
+      // The observer is triggered also from within an already existing pill.
+      // Since the autocomplete-input inside a pill doesn't have an ID, we can
+      // interrupt this method if no element was selected, or the element has
+      // the .input-pill class.
+      if (!element || element.classList.contains("input-pill")) {
+        return;
+      }
+      // Trigger the pill creation.
+      recipientAddPill(element);
+    }
+  },
+};
+
 function InitializeGlobalVariables() {
   gMessenger = Cc["@mozilla.org/messenger;1"].createInstance(Ci.nsIMessenger);
 
   gMsgCompose = null;
   gOriginalMsgURI = null;
   gWindowLocked = false;
   gContentChanged = false;
   gSubjectChanged = false;
@@ -158,30 +176,36 @@ function InitializeGlobalVariables() {
   gAttachVCardOptionChanged = false;
   gAttachmentsSize = 0;
   gNumUploadingAttachments = 0;
   // eslint-disable-next-line no-global-assign
   msgWindow = Cc["@mozilla.org/messenger/msgwindow;1"].createInstance(
     Ci.nsIMsgWindow
   );
   MailServices.mailSession.AddMsgWindow(msgWindow);
+
+  // Add the observer.
+  Services.obs.addObserver(inputObserver, "autocomplete-did-enter-text");
 }
 InitializeGlobalVariables();
 
 function ReleaseGlobalVariables() {
   gCurrentIdentity = null;
   gCharsetConvertManager = null;
   gMsgCompose = null;
   gOriginalMsgURI = null;
   gMessenger = null;
   gDisableAttachmentReminder = false;
   _gComposeBundle = null;
   MailServices.mailSession.RemoveMsgWindow(msgWindow);
   // eslint-disable-next-line no-global-assign
   msgWindow = null;
+
+  // Remove the observer.
+  Services.obs.removeObserver(inputObserver, "autocomplete-did-enter-text");
 }
 
 // Notification box shown at the bottom of the window.
 var gNotification = {};
 XPCOMUtils.defineLazyGetter(gNotification, "notificationbox", () => {
   return new MozElements.NotificationBox(element => {
     element.setAttribute("flex", "1");
     element.setAttribute("notificationside", "bottom");
@@ -290,17 +314,17 @@ function SidebarGetState() {
  */
 function preparePrintPreviewTitleHeader() {
   // For title header of print (preview), use message content document title
   // if existing, otherwise message subject. To apply the message subject,
   // we temporarily change the title of message content document before going
   // into print preview (workaround for bug 1396455).
   let msgDocument = getBrowser().contentDocument;
   let msgSubject =
-    GetMsgSubjectElement().value.trim() ||
+    document.getElementById("msgSubject").value.trim() ||
     getComposeBundle().getString("defaultSubject");
   gChromeState.msgDocumentHadTitle = !!msgDocument.querySelector("title");
   gChromeState.msgDocumentTitle = msgDocument.title;
   msgDocument.title = msgDocument.title || msgSubject;
 }
 
 /**
  * When going in and out of Print Preview, hide or show respective UI elements.
@@ -423,17 +447,17 @@ var stateListener = {
       // and restore it later.
       let editor = GetCurrentEditor();
       let selection = editor.selection;
       let range = selection.getRangeAt(0);
       let start = range.startOffset;
       let startNode = range.startContainer;
 
       editor.enableUndo(false);
-      let identityList = GetMsgIdentityElement();
+      let identityList = document.getElementById("msgIdentity");
       identityList.selectedItem = identityList.getElementsByAttribute(
         "identitykey",
         gMsgCompose.identity.key
       )[0];
       LoadIdentity(false);
 
       editor.enableUndo(true);
       editor.resetModificationCount();
@@ -2316,16 +2340,17 @@ function ComposeFieldsReady() {
   if (!gMsgCompose.composeHTML) {
     try {
       gMsgCompose.editor.QueryInterface(Ci.nsIPlaintextEditor).wrapWidth =
         gMsgCompose.wrapLength;
     } catch (e) {
       dump("### textEditor.wrapWidth exception text: " + e + " - failed\n");
     }
   }
+
   CompFields2Recipients(gMsgCompose.compFields);
   SetComposeWindowTitle();
   updateEditableFields(false);
 }
 
 // checks if the passed in string is a mailto url, if it is, generates nsIMsgComposeParams
 // for the url and returns them.
 function handleMailtoArgs(mailtoUrl) {
@@ -2888,30 +2913,17 @@ function ComposeStartup(aParams) {
   gLanguageObserver.observe(document.documentElement, { attributes: true });
 
   // Observe dictionary removals.
   dictionaryRemovalObserver.addObserver();
 
   document.addEventListener("paste", onPasteOrDrop);
   document.addEventListener("drop", onPasteOrDrop);
 
-  // Workaround for missing inbuilt funcionality of <menulist> to restore
-  // visibility when focused and receiving key presses while scrolled out of view.
-  // Note: Unrelated key presses (e.g. access keys for other UI elements)
-  // typically do not fire keyup on the menulist as focus will have shifted.
-  // Some false positives like function or OS keys might occur; we accept that.
-  // Alt+CursorDown will still show the dropdown in the wrong place.
-  let addressingWidget = GetMsgAddressingWidgetTreeElement();
-  addressingWidget.addEventListener("keyup", event => {
-    if (event.target.classList.contains("aw-menulist")) {
-      addressingWidget.ensureElementIsVisible(event.target);
-    }
-  });
-
-  let identityList = GetMsgIdentityElement();
+  let identityList = document.getElementById("msgIdentity");
   if (identityList) {
     FillIdentityList(identityList);
   }
 
   if (!params) {
     // This code will go away soon as now arguments are passed to the window using a object of type nsMsgComposeParams instead of a string
 
     params = Cc["@mozilla.org/messengercompose/composeparams;1"].createInstance(
@@ -3257,17 +3269,17 @@ function ComposeStartup(aParams) {
       body = body.replace(/&/g, "&amp;");
       gMsgCompose.compFields.body =
         '<br /><a href="' + body + '">' + cleanBody + "</a><br />";
     } else {
       gMsgCompose.compFields.body = "\n<" + body + ">\n";
     }
   }
 
-  GetMsgSubjectElement().value = gMsgCompose.compFields.subject;
+  document.getElementById("msgSubject").value = gMsgCompose.compFields.subject;
 
   AddAttachments(gMsgCompose.compFields.attachments, null, false);
 
   if (Services.prefs.getBoolPref("mail.compose.show_attachment_pane")) {
     toggleAttachmentPane("show");
   }
 
   document
@@ -3375,36 +3387,40 @@ function WizCallback(state) {
     ComposeStartup(null);
   } else {
     // The account wizard is still closing so we can't close just yet
     setTimeout(MsgComposeCloseWindow, 0);
   }
 }
 
 function ComposeLoad() {
-  var other_headers = Services.prefs.getCharPref(
+  let otherHeaders = Services.prefs.getCharPref(
     "mail.compose.other.header",
     ""
   );
 
   AddMessageComposeOfflineQuitObserver();
 
   setupAutocomplete();
 
   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
-    var state = verifyAccounts(WizCallback, true);
-
-    if (other_headers) {
-      var selectNode = document.getElementById("addressCol1#1");
-      var other_headers_Array = other_headers.split(",");
-      for (let i = 0; i < other_headers_Array.length; i++) {
-        selectNode.appendItem(other_headers_Array[i] + ":", "addr_other");
+    let state = verifyAccounts(WizCallback, true);
+
+    if (otherHeaders) {
+      let addressingWidgetLabels = document.getElementById(
+        "addressingWidgetLabels"
+      );
+      let recipientsContainer = document.getElementById("recipientsContainer");
+
+      for (let header of otherHeaders.split(",")) {
+        addressingWidgetLabels.appendChild(createRecipientLabel(header));
+        recipientsContainer.appendChild(createRecipientRow(header));
       }
     }
     if (state) {
       ComposeStartup(null);
     }
   } catch (ex) {
     Cu.reportError(ex);
     Services.prompt.alert(
@@ -3420,17 +3436,16 @@ function ComposeLoad() {
   ToolbarIconColor.init();
 
   // initialize the customizeDone method on the customizeable toolbar
   var toolbox = document.getElementById("compose-toolbox");
   toolbox.customizeDone = function(aEvent) {
     MailToolboxCustomizeDone(aEvent, "CustomizeComposeToolbar");
   };
 
-  awInitializeNumberOfRowsShown();
   updateAttachmentPane();
   attachmentBucketMarkEmptyBucket();
 }
 
 function ComposeUnload() {
   // Send notification that the window is going away completely.
   document
     .getElementById("msgcomposeWindow")
@@ -3485,16 +3500,151 @@ function SetDocumentCharacterSet(aCharse
     gMsgCompose.SetDocumentCharset(aCharset);
     updateEncodingInStatusBar();
   } else {
     dump("Compose has not been created!\n");
   }
 }
 
 /**
+ * Create the recipient label to add in the messenger compose dialog.
+ *
+ * @param {string} labelID - The unique identifier of the email header.
+ * @returns {XULelement} The newly create XUL label.
+ */
+function createRecipientLabel(labelID) {
+  let label = document.createXULElement("label");
+  label.setAttribute("id", labelID);
+  label.textContent = labelID;
+
+  label.addEventListener("click", () => {
+    showAddressRow(label, `addressRow${labelID}`);
+  });
+  label.addEventListener("keypress", event => {
+    showAddressRowKeyPress(event, label, `addressRow${labelID}`);
+  });
+  label.setAttribute("control", `${labelID}AddrInput`);
+
+  // Necessary to allow focus via TAB key.
+  label.setAttribute("tabindex", 0);
+
+  let newImage = document.createXULElement("image");
+  newImage.classList.add("new-icon");
+  label.prepend(newImage);
+
+  return label;
+}
+
+/**
+ * Create a new recipient row container with the input autocomplete.
+ *
+ * @param {string} labelID - The unique identifier of the email header.
+ * @returns {XULElement} - The newly created recipient row.
+ */
+function createRecipientRow(labelID) {
+  let row = document.createXULElement("hbox");
+  row.setAttribute("id", `addressRow${labelID}`);
+  row.classList.add("addressingWidgetItem", "address-row", "hidden");
+
+  let firstCol = document.createXULElement("hbox");
+  firstCol.setAttribute("align", "start");
+  firstCol.classList.add("aw-firstColBox");
+
+  let firstLabel = document.createXULElement("label");
+  firstLabel.addEventListener("click", () => {
+    hideAddressRow(firstLabel, labelID);
+  });
+  firstLabel.addEventListener("keypress", event => {
+    if (event.key == "Enter") {
+      hideAddressRow(firstLabel, labelID);
+    }
+  });
+  // Necessary to allow focus via TAB key.
+  firstLabel.setAttribute("tabindex", 0);
+
+  let closeImage = document.createXULElement("image");
+  closeImage.classList.add("close-icon");
+
+  firstLabel.appendChild(closeImage);
+  firstCol.appendChild(firstLabel);
+
+  let secondCol = document.createXULElement("hbox");
+  secondCol.setAttribute("align", "start");
+  secondCol.setAttribute("pack", "end");
+  secondCol.setAttribute(
+    "style",
+    getComposeBundle().getString("headersSpaceStyle")
+  );
+  secondCol.classList.add("address-label-container");
+
+  let secondLabel = document.createXULElement("label");
+  secondLabel.setAttribute("id", `${labelID}AddrLabel`);
+  secondLabel.value = labelID;
+  secondLabel.setAttribute("control", `${labelID}AddrInput`);
+
+  secondCol.appendChild(secondLabel);
+
+  let container = document.createXULElement("hbox");
+  container.setAttribute("id", `${labelID}AddrContainer`);
+  container.setAttribute("flex", "1");
+  container.setAttribute("align", "center");
+  container.classList.add(
+    "input-container",
+    "wrap-container",
+    "address-container"
+  );
+  container.addEventListener("click", focusAddressInput);
+
+  let input = document.createElement("input", {
+    is: "autocomplete-input",
+  });
+  input.setAttribute("id", `${labelID}AddrInput`);
+
+  input.setAttribute("type", "text");
+  input.classList.add("plain", "address-input", "nntp-input");
+  input.setAttribute("disableonsend", true);
+  input.setAttribute("autocompletesearch", "mydomain addrbook ldap news");
+  input.setAttribute("autocompletesearchparam", "{}");
+  input.setAttribute("timeout", 300);
+  input.setAttribute("maxrows", 6);
+  input.setAttribute("completedefaultindex", true);
+  input.setAttribute("forcecomplete", true);
+  input.setAttribute("completeselectedindex", true);
+  input.setAttribute("minresultsforpopup", 2);
+  input.setAttribute("ignoreblurwhilesearching", true);
+
+  input.addEventListener("focus", () => {
+    highlightAddressContainer(input);
+  });
+  input.addEventListener("blur", () => {
+    resetAddressContainer(input);
+  });
+  input.addEventListener("keypress", event => {
+    recipientKeyPress(event, input);
+  });
+
+  input.setAttribute("recipienttype", "addr_other");
+  input.setAttribute("size", 1);
+
+  let highlightNonMatches = Services.prefs.getBoolPref(
+    "mail.autoComplete.highlightNonMatches"
+  );
+
+  setupAutocompleteInput(input, highlightNonMatches);
+
+  container.appendChild(input);
+
+  row.appendChild(firstCol);
+  row.appendChild(secondCol);
+  row.appendChild(container);
+
+  return row;
+}
+
+/**
  * Return the full display string for any non-default text encoding of the
  * current composition (friendly name plus official character set name).
  * For the default text encoding, return empty string (""), to reduce
  * ux-complexity, e.g. for the default Status Bar display.
  * Note: The default is retrieved from mailnews.send_default_charset.
  *
  * @return string representation of non-default charset, otherwise "".
  */
@@ -3530,20 +3680,20 @@ function DoSpellCheckBeforeSend() {
  * Handles message sending operations.
  * @param msgType nsIMsgCompDeliverMode of the operation.
  */
 function GenericSendMessage(msgType) {
   var msgCompFields = gMsgCompose.compFields;
 
   Recipients2CompFields(msgCompFields);
   let addresses = MailServices.headerParser.makeFromDisplayAddress(
-    GetMsgIdentityElement().value
+    document.getElementById("msgIdentity").value
   );
   msgCompFields.from = MailServices.headerParser.makeMimeHeader(addresses);
-  var subject = GetMsgSubjectElement().value;
+  var subject = document.getElementById("msgSubject").value;
   msgCompFields.subject = subject;
   Attachments2CompFields(msgCompFields);
   // Some other msgCompFields have already been updated instantly in their respective
   // toggle functions, e.g. ToggleReturnReceipt(), ToggleDSN(),  ToggleAttachVCard(),
   // and toggleAttachmentReminder().
 
   let sending =
     msgType == Ci.nsIMsgCompDeliverMode.Now ||
@@ -3579,17 +3729,17 @@ function GenericSendMessage(msgType) {
     }
 
     // Strip trailing spaces and long consecutive WSP sequences from the
     // subject line to prevent getting only WSP chars on a folded line.
     let fixedSubject = subject.replace(/\s{74,}/g, "    ").trimRight();
     if (fixedSubject != subject) {
       subject = fixedSubject;
       msgCompFields.subject = fixedSubject;
-      GetMsgSubjectElement().value = fixedSubject;
+      document.getElementById("msgSubject").value = fixedSubject;
     }
 
     // Remind the person if there isn't a subject
     if (subject == "") {
       if (
         Services.prompt.confirmEx(
           window,
           getComposeBundle().getString("subjectEmptyTitle"),
@@ -3600,17 +3750,17 @@ function GenericSendMessage(msgType) {
               Services.prompt.BUTTON_POS_1,
           getComposeBundle().getString("sendWithEmptySubjectButton"),
           getComposeBundle().getString("cancelSendingButton"),
           null,
           null,
           { value: 0 }
         ) == 1
       ) {
-        GetMsgSubjectElement().focus();
+        document.getElementById("msgSubject").focus();
         return;
       }
     }
 
     // Attachment Reminder: Alert the user if
     //  - the user requested "Remind me later" from either the notification bar or the menu
     //    (alert regardless of the number of files already attached: we can't guess for how many
     //    or which files users want the reminder, and guessing wrong will annoy them a lot), OR
@@ -3847,53 +3997,74 @@ function GenericSendMessage(msgType) {
  *
  * @param aAddress  The address string to check.
  */
 function isValidAddress(aAddress) {
   return aAddress.includes("@", 1) && !aAddress.endsWith("@");
 }
 
 /**
+ * Force the focus on the autocomplete input if the user clicks on an empty
+ * area of the address container.
+ *
+ * @param {Event} event - the event triggered by the click.
+ */
+function focusAddressInput(event) {
+  let container = event.originalTarget;
+  if (container.classList.contains("address-container")) {
+    container
+      .querySelector(`input[is="autocomplete-input"][recipienttype]`)
+      .focus();
+  }
+}
+
+/**
  * Keep the Send buttons disabled until any recipient is entered.
  */
 function updateSendLock() {
   gSendLocked = true;
   if (!gMsgCompose) {
     return;
   }
 
-  const mailTypes = ["addr_to", "addr_cc", "addr_bcc"];
-
-  // Enable the send buttons if anything usable was entered into at least one
-  // recipient field.
-  for (let row = 1; row <= top.MAX_RECIPIENTS; row++) {
-    let popupValue = awGetPopupElement(row).value;
-    let inputValue = awGetInputElement(row).value.trim();
-    if (mailTypes.includes(popupValue)) {
-      if (isValidAddress(inputValue)) {
-        // a properly looking email address
+  const addressRows = [
+    "toAddrContainer",
+    "ccAddrContainer",
+    "bccAddrContainer",
+    "newsgroupsAddrContainer",
+  ];
+
+  for (let parentID of addressRows) {
+    if (!gSendLocked) {
+      break;
+    }
+
+    let parent = document.getElementById(parentID);
+
+    if (!parent) {
+      continue;
+    }
+
+    for (let address of parent.querySelectorAll(".address-pill")) {
+      let listNames = MimeParser.parseHeaderField(
+        address.fullAddress,
+        MimeParser.HEADER_ADDRESS
+      );
+      let isMailingList =
+        listNames.length > 0 &&
+        MailServices.ab.mailListNameExists(listNames[0].name);
+
+      if (
+        isValidAddress(address.emailAddress) ||
+        isMailingList ||
+        address.originalInput.classList.contains("nntp-input")
+      ) {
         gSendLocked = false;
         break;
-      } else {
-        // a valid mailing list name in some or our addressbooks
-        let listNames = MimeParser.parseHeaderField(
-          inputValue,
-          MimeParser.HEADER_ADDRESS
-        );
-        if (
-          listNames.length > 0 &&
-          MailServices.ab.mailListNameExists(listNames[0].name)
-        ) {
-          gSendLocked = false;
-          break;
-        }
       }
-    } else if (popupValue == "addr_newsgroups" && inputValue != "") {
-      gSendLocked = false;
-      break;
     }
   }
 }
 
 /**
  * Check if the entered addresses are valid and alert the user if they are not.
  *
  * @param aMsgCompFields  A nsIMsgCompFields object containing the fields to check.
@@ -4029,18 +4200,19 @@ function Save() {
       break;
     default:
       SaveAsDraft(false);
       break;
   }
 }
 
 function SaveAsFile(saveAs) {
-  var subject = GetMsgSubjectElement().value;
-  GetCurrentEditorElement().contentDocument.title = subject;
+  GetCurrentEditorElement().contentDocument.title = document.getElementById(
+    "msgSubject"
+  ).value;
 
   if (gMsgCompose.bodyConvertible() == Ci.nsIMsgCompConvertible.Plain) {
     SaveDocument(saveAs, false, "text/plain");
   } else {
     SaveDocument(saveAs, false, "text/html");
   }
   defaultSaveOperation = "file";
 }
@@ -4288,23 +4460,16 @@ var spellCheckReadyObserver = {
     this._clearPendingWords();
   },
 
   _clearPendingWords() {
     this._ignoreWords.length = 0;
   },
 };
 
-function onAddressColCommand(aAddressWidgetId) {
-  gContentChanged = true;
-  let row = aAddressWidgetId.slice(aAddressWidgetId.lastIndexOf("#") + 1);
-  awSetAutoComplete(row);
-  updateSendCommands(true);
-}
-
 /**
  * Called if the list of recipients changed in any way.
  *
  * @param aAutomatic  Set to true if the change of recipients was invoked
  *                    programmatically and should not be considered a change
  *                    of message content.
  */
 function onRecipientsChanged(aAutomatic) {
@@ -4428,19 +4593,19 @@ function ComposeChangeLanguage(aLang) {
       spellChecker.SetCurrentDictionary(aLang);
 
       // now check the document over again with the new dictionary
       if (gSpellChecker.enabled) {
         gSpellChecker.mInlineSpellChecker.spellCheckRange(null);
 
         // Also force a recheck of the subject. If for some reason the spell
         // checker isn't ready yet, don't auto-create it, hence pass 'false'.
-        let inlineSpellChecker = GetMsgSubjectElement().editor.getInlineSpellChecker(
-          false
-        );
+        let inlineSpellChecker = document
+          .getElementById("msgSubject")
+          .editor.getInlineSpellChecker(false);
         if (inlineSpellChecker) {
           inlineSpellChecker.spellCheckRange(null);
         }
       }
     }
   }
 }
 
@@ -4601,17 +4766,17 @@ function FillIdentityList(menulist) {
   menulist.menupopup.appendChild(document.createXULElement("menuseparator"));
   menulist.menupopup
     .appendChild(document.createXULElement("menuitem"))
     .setAttribute("command", "cmd_customizeFromAddress");
 }
 
 function getCurrentAccountKey() {
   // Get the account's key.
-  let identityList = GetMsgIdentityElement();
+  let identityList = document.getElementById("msgIdentity");
   return identityList.getAttribute("accountkey");
 }
 
 function getCurrentIdentityKey() {
   // Get the identity key.
   return gCurrentIdentity.key;
 }
 
@@ -4619,22 +4784,22 @@ function getIdentityForKey(key) {
   return MailServices.accounts.getIdentity(key);
 }
 
 function getCurrentIdentity() {
   return getIdentityForKey(getCurrentIdentityKey());
 }
 
 function AdjustFocus() {
-  let element = awGetInputElement(awGetNumberOfRecipients());
+  let element = document.getElementById("toAddrInput");
   if (element.value == "") {
     // Focus last row of addressing widget.
-    awSetFocusTo(element);
+    element.focus();
   } else {
-    element = GetMsgSubjectElement();
+    element = document.getElementById("msgSubject");
     if (element.value == "") {
       // Focus subject.
       element.focus();
     } else {
       // Focus message body.
       SetMsgBodyFrameFocus();
     }
   }
@@ -4646,17 +4811,17 @@ function AdjustFocus() {
  * @param isPrintPreview (optional) true:  Set title for 'Print Preview' window.
  *                                  false: Set title for 'Write' window (default).
  */
 function SetComposeWindowTitle(isPrintPreview = false) {
   let aStringName = isPrintPreview
     ? "windowTitlePrintPreview"
     : "windowTitleWrite";
   let subject =
-    GetMsgSubjectElement().value.trim() ||
+    document.getElementById("msgSubject").value.trim() ||
     getComposeBundle().getString("defaultSubject");
   let brandBundle = document.getElementById("brandBundle");
   let brandShortName = brandBundle.getString("brandShortName");
   let newTitle = getComposeBundle().getFormattedString(aStringName, [
     subject,
     brandShortName,
   ]);
   document.title = newTitle;
@@ -6178,24 +6343,17 @@ function hideIrrelevantAddressingOptions
     Ci.nsIMsgAccount
   )) {
     if (account.incomingServer.type == "nntp") {
       hideNews = false;
     }
   }
   // If there is no News (NNTP) account existing then
   // hide the Newsgroup and Followup-To recipient type in all the menulists.
-  let addrWidget = document.getElementById("addressingWidget");
-  // Only really touch the News related items we know about.
-  let newsTypes = addrWidget.querySelectorAll(
-    'menuitem[value="addr_newsgroups"], menuitem[value="addr_followup"]'
-  );
-  // Collapsing the menuitem only prevents it getting chosen, it does not
-  // affect the menulist widget display when Newsgroup is already selected.
-  for (let item of newsTypes) {
+  for (let item of document.querySelectorAll(".nntp-label")) {
     item.collapsed = hideNews;
   }
 }
 
 function LoadIdentity(startup) {
   var identityElement = document.getElementById("msgIdentity");
   var prevIdentity = gCurrentIdentity;
 
@@ -6209,22 +6367,23 @@ function LoadIdentity(startup) {
       gCurrentIdentity = MailServices.accounts.getIdentity(idKey);
 
       // Set the account key value on the menu list.
       accountKey = identityElement.selectedItem.getAttribute("accountkey");
       identityElement.setAttribute("accountkey", accountKey);
       hideIrrelevantAddressingOptions(accountKey);
     }
 
-    let maxRecipients = awGetNumberOfRecipients();
-    for (let i = 1; i <= maxRecipients; i++) {
-      let params = JSON.parse(awGetInputElement(i).searchParam);
+    for (let input of document.querySelectorAll(
+      ".pop-imap-input,.nntp-input"
+    )) {
+      let params = JSON.parse(input.searchParam);
       params.idKey = idKey;
       params.accountKey = accountKey;
-      awGetInputElement(i).searchParam = JSON.stringify(params);
+      input.searchParam = JSON.stringify(params);
     }
 
     if (!startup && prevIdentity && idKey != prevIdentity.key) {
       var prevReplyTo = prevIdentity.replyTo;
       var prevCc = "";
       var prevBcc = "";
       var prevReceipt = prevIdentity.requestReturnReceipt;
       var prevDSN = prevIdentity.DSN;
@@ -6248,17 +6407,16 @@ function LoadIdentity(startup) {
       if (gCurrentIdentity.doCc) {
         newCc += gCurrentIdentity.doCcList;
       }
 
       if (gCurrentIdentity.doBcc) {
         newBcc += gCurrentIdentity.doBccList;
       }
 
-      var needToCleanUp = false;
       var msgCompFields = gMsgCompose.compFields;
 
       if (
         !gReceiptOptionChanged &&
         prevReceipt == msgCompFields.returnReceipt &&
         prevReceipt != newReceipt
       ) {
         msgCompFields.returnReceipt = newReceipt;
@@ -6285,93 +6443,85 @@ function LoadIdentity(startup) {
       ) {
         msgCompFields.attachVCard = newAttachVCard;
         document
           .getElementById("cmd_attachVCard")
           .setAttribute("checked", msgCompFields.attachVCard);
       }
 
       if (newReplyTo != prevReplyTo) {
-        needToCleanUp = true;
         if (prevReplyTo != "") {
           awRemoveRecipients(msgCompFields, "addr_reply", prevReplyTo);
         }
         if (newReplyTo != "") {
           awAddRecipients(msgCompFields, "addr_reply", newReplyTo);
         }
       }
 
       let toAddrs = new Set(
         msgCompFields.splitRecipients(msgCompFields.to, true)
       );
       let ccAddrs = new Set(
         msgCompFields.splitRecipients(msgCompFields.cc, true)
       );
 
       if (newCc != prevCc) {
-        needToCleanUp = true;
         if (prevCc) {
           awRemoveRecipients(msgCompFields, "addr_cc", prevCc);
         }
         if (newCc) {
           // Ensure none of the Ccs are already in To.
           let cc2 = msgCompFields.splitRecipients(newCc, true);
           newCc = cc2.filter(x => !toAddrs.has(x)).join(", ");
           awAddRecipients(msgCompFields, "addr_cc", newCc);
         }
       }
 
       if (newBcc != prevBcc) {
-        needToCleanUp = true;
         if (prevBcc) {
           awRemoveRecipients(msgCompFields, "addr_bcc", prevBcc);
         }
         if (newBcc) {
           // Ensure none of the Bccs are already in To or Cc.
           let bcc2 = msgCompFields.splitRecipients(newBcc, true);
           let toCcAddrs = new Set([...toAddrs, ...ccAddrs]);
           newBcc = bcc2.filter(x => !toCcAddrs.has(x)).join(", ");
           awAddRecipients(msgCompFields, "addr_bcc", newBcc);
         }
       }
 
-      if (needToCleanUp) {
-        awCleanupRows();
-      }
-
       try {
         gMsgCompose.identity = gCurrentIdentity;
       } catch (ex) {
         dump("### Cannot change the identity: " + ex + "\n");
       }
 
       var event = document.createEvent("Events");
       event.initEvent("compose-from-changed", false, true);
       document.getElementById("msgcomposeWindow").dispatchEvent(event);
 
       gComposeNotificationBar.clearIdentityWarning();
     }
 
     if (!startup) {
-      if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches")) {
-        document.getElementById("addressCol2#1").highlightNonMatches = true;
-      }
-
       // Only do this if we aren't starting up...
       // It gets done as part of startup already.
       addRecipientsToIgnoreList(gCurrentIdentity.fullAddress);
 
       // If the From field is editable, reset the address from the identity.
       if (identityElement.editable) {
         identityElement.value = identityElement.selectedItem.value;
         identityElement.placeholder = getComposeBundle().getFormattedString(
           "msgIdentityPlaceholder",
           [identityElement.selectedItem.value]
         );
       }
+
+      SetMsgToRecipientElementFocus();
+      onRecipientsChanged(true);
     }
   }
 }
 
 function MakeFromFieldEditable(ignoreWarning) {
   let bundle = getComposeBundle();
   if (
     !ignoreWarning &&
@@ -6411,30 +6561,65 @@ function MakeFromFieldEditable(ignoreWar
   identityElement.select();
   identityElement.placeholder = bundle.getFormattedString(
     "msgIdentityPlaceholder",
     [identityElement.selectedItem.value]
   );
 }
 
 function setupAutocomplete() {
-  var autoCompleteWidget = document.getElementById("addressCol2#1");
-  try {
-    // Request that input that isn't matched be highlighted.
-    // This element then gets cloned for subsequent rows, so they should
-    // honor it as well.
-    if (Services.prefs.getBoolPref("mail.autoComplete.highlightNonMatches")) {
-      autoCompleteWidget.highlightNonMatches = true;
-    }
-  } catch (ex) {}
+  let highlightNonMatches = Services.prefs.getBoolPref(
+    "mail.autoComplete.highlightNonMatches"
+  );
+
+  for (let input of document.querySelectorAll(".pop-imap-input,.nntp-input")) {
+    setupAutocompleteInput(input, highlightNonMatches);
+  }
+}
+
+function setupAutocompleteInput(input, highlightNonMatches) {
+  let params = JSON.parse(input.getAttribute("autocompletesearchparam"));
+  params.type = input.getAttribute("recipienttype");
+  input.setAttribute("autocompletesearchparam", JSON.stringify(params));
+
+  // This method overrides the autocomplete binding's openPopup (essentially
+  // duplicating the logic from the autocomplete popup binding's
+  // openAutocompletePopup method), modifying it so that the popup is aligned
+  // and sized based on the parentNode of the input field.
+  input.openPopup = () => {
+    if (input.focused) {
+      input.popup.openAutocompletePopup(
+        input.nsIAutocompleteInput,
+        input.closest(".address-container")
+      );
+    }
+  };
+
+  // Request that input that isn't matched be highlighted.
+  input.highlightNonMatches = highlightNonMatches;
+}
+
+/**
+ * Select all the pills in the same recipient container if they exist.
+ *
+ * @param {HTMLElement} input - The autocomplete input field.
+ */
+function selectRecipientPills(input) {
+  let previous = input.previousElementSibling;
+  if (previous && previous.tagName == "mail-address-pill") {
+    for (let pill of getSiblingPills(input)) {
+      pill.setAttribute("selected", "selected");
+    }
+    previous.focus();
+  }
 }
 
 function fromKeyPress(event) {
   if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
-    awSetFocusTo(awGetInputElement(1));
+    document.getElementById("toAddrInput").focus();
   }
 }
 
 function subjectKeyPress(event) {
   gSubjectChanged = true;
   if (event.keyCode == KeyEvent.DOM_VK_RETURN) {
     SetMsgBodyFrameFocus();
   }
@@ -6841,26 +7026,26 @@ function DisplaySaveFolderDlg(folderURI)
       checkbox
     );
     try {
       gCurrentIdentity.showSaveMsgDlg = !checkbox.value;
     } catch (e) {}
   }
 }
 
-function SetMsgAddressingWidgetTreeElementFocus() {
-  awSetFocusTo(awGetInputElement(awGetNumberOfRecipients()));
+function SetMsgToRecipientElementFocus() {
+  document.getElementById("toAddrInput").focus();
 }
 
 function SetMsgIdentityElementFocus() {
-  GetMsgIdentityElement().focus();
+  document.getElementById("msgIdentity").focus();
 }
 
 function SetMsgSubjectElementFocus() {
-  GetMsgSubjectElement().focus();
+  document.getElementById("msgSubject").focus();
 }
 
 function SetMsgAttachmentElementFocus() {
   // Caveat: Callers must ensure that attachment pane is visible.
   GetMsgAttachmentElement().focus();
 }
 
 /**
@@ -6883,42 +7068,16 @@ function focusContactsSidebarSearchInput
 
 function SetMsgBodyFrameFocus() {
   // window.content.focus() fails to blur the currently focused element
   document.commandDispatcher.advanceFocusIntoSubtree(
     document.getElementById("appcontent")
   );
 }
 
-function GetMsgAddressingWidgetTreeElement() {
-  if (!gMsgAddressingWidgetTreeElement) {
-    gMsgAddressingWidgetTreeElement = document.getElementById(
-      "addressingWidget"
-    );
-  }
-
-  return gMsgAddressingWidgetTreeElement;
-}
-
-function GetMsgIdentityElement() {
-  if (!gMsgIdentityElement) {
-    gMsgIdentityElement = document.getElementById("msgIdentity");
-  }
-
-  return gMsgIdentityElement;
-}
-
-function GetMsgSubjectElement() {
-  if (!gMsgSubjectElement) {
-    gMsgSubjectElement = document.getElementById("msgSubject");
-  }
-
-  return gMsgSubjectElement;
-}
-
 function GetMsgAttachmentElement() {
   if (!gMsgAttachmentElement) {
     gMsgAttachmentElement = document.getElementById("attachmentBucket");
   }
 
   return gMsgAttachmentElement;
 }
 
@@ -6959,72 +7118,69 @@ function GetMsgHeadersToolbarElement() {
 /**
  * Determine which element of the fast-track focus ring has focus.
  * Note that only elements of the fast-track focus ring will be returned.
  *
  * @return an element node of the fast-track focus ring if the node or one of
  *         its descendants has focus, otherwise null.
  */
 function WhichElementHasFocus() {
-  let msgIdentityElement = GetMsgIdentityElement();
-  let msgAddressingWidgetTreeElement = GetMsgAddressingWidgetTreeElement();
-  let msgSubjectElement = GetMsgSubjectElement();
   let msgAttachmentElement = GetMsgAttachmentElement();
   let abContactsPanelElement = sidebarDocumentGetElementById("abContactsPanel");
 
   if (top.document.commandDispatcher.focusedWindow == window.content) {
     return window.content;
   }
 
   let currentNode = top.document.commandDispatcher.focusedElement;
   while (currentNode) {
     if (
-      currentNode == msgIdentityElement ||
-      currentNode == msgAddressingWidgetTreeElement ||
-      currentNode == msgSubjectElement ||
+      currentNode == document.getElementById("msgIdentity") ||
+      currentNode == document.getElementById("toAddrInput") ||
+      currentNode == document.getElementById("msgSubject") ||
       currentNode == msgAttachmentElement ||
       currentNode == abContactsPanelElement
     ) {
       return currentNode;
     }
     currentNode = currentNode.parentNode;
   }
 
   return null;
 }
 
 /**
  * Fast-track focus ring: Switch focus between important (not all) elements
  * in the message compose window. Ctrl+[Shift+]Tab | [Shift+]F6 on Windows.
  *
  * The default element to switch to when going in either direction (with or
- * without shift key pressed) is the AddressingWidgetTreeElement.
+ * without shift key pressed) is the ToRecipientElement.
  *
  * The only exception is when the MsgHeadersToolbar is collapsed,
  * then the focus will always be on the body of the message.
  */
 function SwitchElementFocus(event) {
   let focusedElement = WhichElementHasFocus();
 
   if (!focusedElement) {
     // None of the pre-defined focus ring elements has focus: This should never
     // happen with the default installation, but might happen with add-ons.
     // In that case, default to focusing the address widget as the first element
     // of the focus ring.
-    SetMsgAddressingWidgetTreeElementFocus();
+    SetMsgToRecipientElementFocus();
     return;
   }
 
   if (event && event.shiftKey) {
     // Backwards focus ring: e.g. Ctrl+Shift+Tab | Shift+F6
     switch (focusedElement) {
-      case gMsgAddressingWidgetTreeElement:
+      case document.getElementById("toAddrInput"):
         SetMsgIdentityElementFocus();
         break;
-      case gMsgIdentityElement:
+      case document.getElementById("msgIdentity"):
         // Focus the search input of contacts side bar if that's available,
         // otherwise focus message body.
         if (sidebar_is_hidden() || !focusContactsSidebarSearchInput()) {
           SetMsgBodyFrameFocus();
         }
         break;
       case sidebarDocumentGetElementById("abContactsPanel"):
         SetMsgBodyFrameFocus();
@@ -7036,27 +7192,27 @@ function SwitchElementFocus(event) {
         } else {
           SetMsgSubjectElementFocus();
         }
         break;
       case gMsgAttachmentElement:
         SetMsgSubjectElementFocus();
         break;
       default:
-        // gMsgSubjectElement
-        SetMsgAddressingWidgetTreeElementFocus();
+        // document.getElementById("msgSubject")
+        SetMsgToRecipientElementFocus();
         break;
     }
   } else {
     // Forwards focus ring: e.g. Ctrl+Tab | F6
     switch (focusedElement) {
-      case gMsgAddressingWidgetTreeElement:
+      case document.getElementById("toAddrInput"):
         SetMsgSubjectElementFocus();
         break;
-      case gMsgSubjectElement:
+      case document.getElementById("msgSubject"):
         // Only set focus to the attachment element if it is shown.
         if (!document.getElementById("attachments-box").collapsed) {
           SetMsgAttachmentElementFocus();
         } else {
           SetMsgBodyFrameFocus();
         }
         break;
       case gMsgAttachmentElement:
@@ -7068,18 +7224,18 @@ function SwitchElementFocus(event) {
         if (sidebar_is_hidden() || !focusContactsSidebarSearchInput()) {
           SetMsgIdentityElementFocus();
         }
         break;
       case sidebarDocumentGetElementById("abContactsPanel"):
         SetMsgIdentityElementFocus();
         break;
       default:
-        // gMsgIdentityElement
-        SetMsgAddressingWidgetTreeElementFocus();
+        // document.getElementById("msgIdentity")
+        SetMsgToRecipientElementFocus();
         break;
     }
   }
 }
 
 function sidebarCloseButtonOnCommand() {
   toggleAddressPicker();
 }
@@ -7163,17 +7319,17 @@ function toggleAddressPicker(aFocus = tr
     // Contacts sidebar and then the main window/frame does not respond to accesskeys.
     // This may be fixed by bug 570835.
     let composerBox = document.getElementById("headers-parent");
     let focusedElement =
       composerBox.querySelector(":focus") ||
       composerBox.querySelector('[focused="true"]');
     if (focusedElement) {
       focusedElement.focus();
-    } else if (!GetMsgSubjectElement().value) {
+    } else if (!document.getElementById("msgSubject").value) {
       SetMsgSubjectElementFocus();
     } else {
       SetMsgBodyFrameFocus();
     }
   }
 }
 
 // Public method called by addons.
@@ -7271,21 +7427,19 @@ var gAttachmentNotifier = {
       attributes: true,
       childList: true,
       characterData: true,
       subtree: true,
     });
 
     // Add an input event listener for the subject field since there
     // are ways of changing its value without key presses.
-    GetMsgSubjectElement().addEventListener(
-      "input",
-      this.subjectObserver,
-      true
-    );
+    document
+      .getElementById("msgSubject")
+      .addEventListener("input", this.subjectObserver, true);
 
     // We could have been opened with a draft message already containing
     // some keywords, so run the checker once to pick them up.
     this.event.notify();
   },
 
   // Timer based function triggered by the inputEventListener
   // for the subject field.
@@ -7403,17 +7557,17 @@ var gAttachmentNotifier = {
     );
     let fwdIndex = mailData.indexOf(fwdText);
     if (fwdIndex > 0) {
       mailData = mailData.substring(0, fwdIndex);
     }
 
     // Prepend the subject to see if the subject contains any attachment
     // keywords too, after making sure that the subject has changed.
-    let subject = GetMsgSubjectElement().value;
+    let subject = document.getElementById("msgSubject").value;
     if (
       subject &&
       (gSubjectChanged ||
         (gEditingDraft &&
           (gComposeType == Ci.nsIMsgCompType.New ||
             gComposeType == Ci.nsIMsgCompType.NewsPost ||
             gComposeType == Ci.nsIMsgCompType.Draft ||
             gComposeType == Ci.nsIMsgCompType.Template ||
@@ -7487,17 +7641,17 @@ function removeQueryPart(aURL, aQuery) {
 function InitEditor() {
   var editor = GetCurrentEditor();
 
   // Set eEditorMailMask flag to avoid using content prefs for spell checker,
   // otherwise dictionary setting in preferences is ignored and dictionary is
   // inconsistent in subject and message body.
   let eEditorMailMask = Ci.nsIPlaintextEditor.eEditorMailMask;
   editor.flags |= eEditorMailMask;
-  GetMsgSubjectElement().editor.flags |= eEditorMailMask;
+  document.getElementById("msgSubject").editor.flags |= eEditorMailMask;
 
   // Control insertion of line breaks.
   editor.returnInParagraphCreatesNewParagraph = Services.prefs.getBoolPref(
     "editor.CR_creates_new_p"
   );
   editor.document.execCommand(
     "defaultparagraphseparator",
     false,
--- a/mail/components/compose/content/addressingWidgetOverlay.js
+++ b/mail/components/compose/content/addressingWidgetOverlay.js
@@ -1,1346 +1,781 @@
 /* -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* import-globals-from MsgComposeCommands.js */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-top.MAX_RECIPIENTS = 1; // For the initial listitem created in the XUL.
-
-var inputElementType = "";
-var selectElementType = "";
-var selectElementIndexTable = null;
-
-var gNumberOfCols = 0;
-
+var { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+var { DisplayNameUtils } = ChromeUtils.import(
+  "resource:///modules/DisplayNameUtils.jsm"
+);
 var gDragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
   Ci.nsIDragService
 );
 
-var test_addresses_sequence = false;
-
-if (
-  Services.prefs.getPrefType("mail.debug.test_addresses_sequence") ==
-  Ci.nsIPrefBranch.PREF_BOOL
-) {
-  test_addresses_sequence = Services.prefs.getBoolPref(
-    "mail.debug.test_addresses_sequence"
-  );
-}
-
-function awGetNumberOfCols() {
-  if (gNumberOfCols == 0) {
-    var listbox = document.getElementById("addressingWidget");
-    var listCols = listbox.getElementsByTagName("treecol");
-    gNumberOfCols = listCols.length;
-    if (!gNumberOfCols) {
-      // If no cols defined, that means we have only one!
-      gNumberOfCols = 1;
-    }
-  }
-
-  return gNumberOfCols;
-}
-
 /**
- * Adjust the default and minimum number of visible recipient rows for addressingWidget
+ * Convert all the written recipients into string and store them into the
+ * msgCompFields array to be printed in the message header.
+ *
+ * @param {Array} msgCompFields - The array containing all the recipients.
  */
-function awInitializeNumberOfRowsShown() {
-  let msgHeadersToolbar = document.getElementById("MsgHeadersToolbar");
-  let addressingWidget = document.getElementById("addressingWidget");
-  let awNumRowsShownDefault = Services.prefs.getIntPref(
-    "mail.compose.addresswidget.numRowsShownDefault"
-  );
-
-  // Work around bug 966655: extraHeight 2 pixels for msgHeadersToolbar ensures
-  // visibility of recipient rows per awNumRowsShownDefault and prevents scrollbar
-  // on empty Address Widget, depending on OS screen resolution dpi scaling
-  // (> 100%; thresholds differ).
-  let extraHeight = 2;
-
-  // Set minimum number of rows shown for address widget, per hardwired
-  // rows="1" attribute of addressingWidget, to prevent resizing the
-  // subject and format toolbar over the address widget.
-  // This lets users shrink the address widget to one row (with delicate UX)
-  // and thus maximize the space available for composition body,
-  // especially on small screens.
-  let toolbarRect = msgHeadersToolbar.getBoundingClientRect();
-  msgHeadersToolbar.minHeight = toolbarRect.height;
-
-  msgHeadersToolbar.height =
-    toolbarRect.height +
-    addressingWidget.getBoundingClientRect().height *
-      (awNumRowsShownDefault - 1) +
-    extraHeight;
-
-  // Update addressingWidget internals.
-  awCreateOrRemoveDummyRows();
-}
-
-// TODO: replace awGetSelectItemIndex with recipient type index constants
-
-function awGetSelectItemIndex(itemData) {
-  if (selectElementIndexTable == null) {
-    selectElementIndexTable = {};
-    var selectElem = document.getElementById("addressCol1#1");
-    for (var i = 0; i < selectElem.menupopup.children.length; i++) {
-      var aData = selectElem.menupopup.children[i].getAttribute("value");
-      selectElementIndexTable[aData] = i;
-    }
-  }
-
-  return selectElementIndexTable[itemData];
-}
-
 function Recipients2CompFields(msgCompFields) {
   if (!msgCompFields) {
     throw new Error(
       "Message Compose Error: msgCompFields is null (ExtractRecipients)"
     );
   }
 
-  var i = 1;
-  var addrTo = "";
-  var addrCc = "";
-  var addrBcc = "";
-  var addrReply = "";
-  var addrNg = "";
-  var addrFollow = "";
-  var to_Sep = "";
-  var cc_Sep = "";
-  var bcc_Sep = "";
-  var reply_Sep = "";
-  var ng_Sep = "";
-  var follow_Sep = "";
+  let addrTo = "";
+  let addrCc = "";
+  let addrBcc = "";
+  let addrReply = "";
+  let addrNg = "";
+  let addrFollow = "";
+  let to_Sep = "";
+  let cc_Sep = "";
+  let bcc_Sep = "";
+  let reply_Sep = "";
+  let ng_Sep = "";
+  let follow_Sep = "";
 
-  var recipientType;
-  var inputField;
-  var fieldValue;
-  var recipient;
-  while ((inputField = awGetInputElement(i))) {
-    fieldValue = inputField.value;
-    if (fieldValue != "") {
-      recipientType = awGetPopupElement(i).value;
-      recipient = null;
+  for (let pill of document.getElementsByTagName("mail-address-pill")) {
+    let fieldValue = pill.fullAddress;
+    let headerParser = MailServices.headerParser;
+    let recipient = headerParser
+      .makeFromDisplayAddress(fieldValue)
+      .map(fullValue =>
+        headerParser.makeMimeAddress(fullValue.name, fullValue.email)
+      )
+      .join(", ");
 
-      switch (recipientType) {
-        case "addr_to":
-        case "addr_cc":
-        case "addr_bcc":
-        case "addr_reply":
-          try {
-            let headerParser = MailServices.headerParser;
-            recipient = headerParser
-              .makeFromDisplayAddress(fieldValue)
-              .map(fullValue =>
-                headerParser.makeMimeAddress(fullValue.name, fullValue.email)
-              )
-              .join(", ");
-          } catch (ex) {
-            recipient = fieldValue;
-          }
-          break;
-      }
-
-      switch (recipientType) {
-        case "addr_to":
-          addrTo += to_Sep + recipient;
-          to_Sep = ",";
-          break;
-        case "addr_cc":
-          addrCc += cc_Sep + recipient;
-          cc_Sep = ",";
-          break;
-        case "addr_bcc":
-          addrBcc += bcc_Sep + recipient;
-          bcc_Sep = ",";
-          break;
-        case "addr_reply":
-          addrReply += reply_Sep + recipient;
-          reply_Sep = ",";
-          break;
-        case "addr_newsgroups":
-          addrNg += ng_Sep + fieldValue;
-          ng_Sep = ",";
-          break;
-        case "addr_followup":
-          addrFollow += follow_Sep + fieldValue;
-          follow_Sep = ",";
-          break;
-        case "addr_other":
-          let headerName = awGetPopupElement(i).label;
-          headerName = headerName.substring(0, headerName.indexOf(":"));
-          msgCompFields.setRawHeader(headerName, fieldValue, null);
-          break;
-      }
+    // Each pill knows from which recipient they were generated
+    // (addr_to, addrs_bcc, etc.).
+    let recipientType = pill.getAttribute("recipienttype");
+    switch (recipientType) {
+      case "addr_to":
+        addrTo += to_Sep + recipient;
+        to_Sep = ",";
+        break;
+      case "addr_cc":
+        addrCc += cc_Sep + recipient;
+        cc_Sep = ",";
+        break;
+      case "addr_bcc":
+        addrBcc += bcc_Sep + recipient;
+        bcc_Sep = ",";
+        break;
+      case "addr_reply":
+        addrReply += reply_Sep + recipient;
+        reply_Sep = ",";
+        break;
+      case "addr_newsgroups":
+        addrNg += ng_Sep + recipient;
+        ng_Sep = ",";
+        break;
+      case "addr_followup":
+        addrFollow += follow_Sep + recipient;
+        follow_Sep = ",";
+        break;
+      case "addr_other":
+        let label = pill.emailInput.getAttribute("aria-labelledby");
+        msgCompFields.setRawHeader(label, recipient, null);
+        break;
     }
-    i++;
   }
 
   msgCompFields.to = addrTo;
   msgCompFields.cc = addrCc;
   msgCompFields.bcc = addrBcc;
   msgCompFields.replyTo = addrReply;
   msgCompFields.newsgroups = addrNg;
   msgCompFields.followupTo = addrFollow;
 }
 
+/**
+ * Convert all the recipients coming from a message header into pills.
+ *
+ * @param {Array} msgCompFields - The array containing all the recipients.
+ */
 function CompFields2Recipients(msgCompFields) {
   if (msgCompFields) {
-    let listbox = document.getElementById("addressingWidget");
-    let templateNode = listbox.getItemAtIndex(0);
-    templateNode.remove();
+    let msgReplyTo = msgCompFields.replyTo
+      ? MailServices.headerParser.parseEncodedHeader(msgCompFields.replyTo)
+      : null;
+    let msgTo = msgCompFields.to
+      ? MailServices.headerParser.parseEncodedHeader(msgCompFields.to)
+      : null;
+    let msgCC = msgCompFields.cc
+      ? MailServices.headerParser.parseEncodedHeader(msgCompFields.cc)
+      : null;
+    let msgBCC = msgCompFields.bcc
+      ? MailServices.headerParser.parseEncodedHeader(msgCompFields.bcc)
+      : null;
+    let msgNewsgroups = msgCompFields.newsgroups;
+    let msgFollowupTo = msgCompFields.followupTo
+      ? MailServices.headerParser.parseEncodedHeader(msgCompFields.followupTo)
+      : null;
 
-    top.MAX_RECIPIENTS = 0;
-    let msgReplyTo = msgCompFields.replyTo;
-    let msgTo = msgCompFields.to;
-    let msgCC = msgCompFields.cc;
-    let msgBCC = msgCompFields.bcc;
-    let msgNewsgroups = msgCompFields.newsgroups;
-    let msgFollowupTo = msgCompFields.followupTo;
-    let havePrimaryRecipient = false;
+    // Populate all the recipients with the proper values.
+    // We need to force the focus() on each input to trigger the attachment
+    // of the autocomplete mController.
     if (msgReplyTo) {
-      awSetInputAndPopupFromArray(
-        msgCompFields.splitRecipients(msgReplyTo, false),
-        "addr_reply",
-        listbox,
-        templateNode
-      );
-    }
-    if (msgTo) {
-      let rcp = msgCompFields.splitRecipients(msgTo, false);
-      if (rcp.length) {
-        awSetInputAndPopupFromArray(rcp, "addr_to", listbox, templateNode);
-        havePrimaryRecipient = true;
-      }
+      showAddressRow(document.getElementById("addr_reply"), "addressRowReply");
+      let input = document.getElementById("replyAddrInput");
+      input.focus();
+      input.value = msgReplyTo.join(", ");
+      recipientAddPill(input, true);
     }
-    if (msgCC) {
-      awSetInputAndPopupFromArray(
-        msgCompFields.splitRecipients(msgCC, false),
-        "addr_cc",
-        listbox,
-        templateNode
-      );
-    }
-    if (msgBCC) {
-      awSetInputAndPopupFromArray(
-        msgCompFields.splitRecipients(msgBCC, false),
-        "addr_bcc",
-        listbox,
-        templateNode
-      );
-    }
-    if (msgNewsgroups) {
-      awSetInputAndPopup(
-        msgNewsgroups,
-        "addr_newsgroups",
-        listbox,
-        templateNode
-      );
-      havePrimaryRecipient = true;
-    }
-    if (msgFollowupTo) {
-      awSetInputAndPopup(msgFollowupTo, "addr_followup", listbox, templateNode);
+
+    if (msgTo) {
+      let input = document.getElementById("toAddrInput");
+      input.focus();
+      input.value = msgTo.join(", ");
+      recipientAddPill(input, true);
     }
 
-    // If it's a new message, we need to add an extra empty recipient.
-    if (!havePrimaryRecipient) {
-      _awSetInputAndPopup("", "addr_to", listbox, templateNode);
+    if (msgCC) {
+      showAddressRow(document.getElementById("addr_cc"), "addressRowCc");
+      let input = document.getElementById("ccAddrInput");
+      input.focus();
+      input.value = msgCC.join(", ");
+      recipientAddPill(input, true);
+    }
+
+    if (msgBCC) {
+      showAddressRow(document.getElementById("addr_bcc"), "addressRowBcc");
+      let input = document.getElementById("bccAddrInput");
+      input.focus();
+      input.value = msgBCC.join(", ");
+      recipientAddPill(input, true);
     }
-    awFitDummyRows(2);
+
+    if (msgNewsgroups) {
+      showAddressRow(
+        document.getElementById("addr_newsgroups"),
+        "addressRowNewsgroups"
+      );
+      let input = document.getElementById("newsgroupsAddrInput");
+      input.focus();
+      input.value = msgNewsgroups;
+      recipientAddPill(input, true);
+    }
+
+    if (msgFollowupTo) {
+      showAddressRow(
+        document.getElementById("addr_followup"),
+        "addressRowFollowup"
+      );
+      let input = document.getElementById("followupAddrInput");
+      input.focus();
+      input.value = msgFollowupTo.join(", ");
+      recipientAddPill(input, true);
+    }
 
     // CompFields2Recipients is called whenever a user replies or edits an existing message. We want to
     // add all of the non-empty recipients for this message to the ignore list for spell check
     let currentAddress = gCurrentIdentity ? gCurrentIdentity.fullAddress : "";
     addRecipientsToIgnoreList(
       [currentAddress, msgTo, msgCC, msgBCC].filter(adr => adr).join(", ")
     );
   }
 }
 
-function awSetInputAndPopupId(inputElem, popupElem, rowNumber) {
-  popupElem.id = "addressCol1#" + rowNumber;
-  inputElem.id = "addressCol2#" + rowNumber;
-  inputElem.setAttribute("aria-labelledby", popupElem.id);
-  inputElem.popup.addEventListener("click", () => {
-    awReturnHit(inputElem);
-  });
-}
-
 /**
- * Set value of the recipient input field at row rowNumber and set up
- * the recipient type menulist.
+ * Clear a specific recipient row if is visible and pills are present. This is
+ * commonly used when loading a new identity.
  *
- * @param inputElem                 recipient input element
- * @param inputValue                recipient value (address)
- * @param popupElem                 recipient type menulist element
- * @param popupValue
- * @param aNotifyRecipientsChanged  Notify that the recipients have changed.
- *                                  Generally we notify unless recipients are
- *                                  added in batch when the caller takes care
- *                                  of the notification.
+ * @param {Array} msgCompFields - The array containing all the recipient fields.
+ * @param {string} recipientType - Which recipient needs to be cleared.
+ * @param {Array} recipientsList - The array containing the old recipients.
  */
-function awSetInputAndPopupValue(
-  inputElem,
-  inputValue,
-  popupElem,
-  popupValue,
-  rowNumber,
-  aNotifyRecipientsChanged = true
-) {
-  inputElem.value = inputValue.trimLeft();
-
-  popupElem.selectedItem =
-    popupElem.menupopup.children[awGetSelectItemIndex(popupValue)];
-  // TODO: can there be a row without ID yet?
-  if (rowNumber >= 0) {
-    awSetInputAndPopupId(inputElem, popupElem, rowNumber);
-  }
-
-  _awSetAutoComplete(popupElem, inputElem);
-
-  if (aNotifyRecipientsChanged) {
-    onRecipientsChanged(true);
-  }
-}
-
-function _awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode) {
-  top.MAX_RECIPIENTS++;
-
-  var newNode = templateNode.cloneNode(true);
-  parentNode.appendChild(newNode); // we need to insert the new node before we set the value of the select element!
-
-  var input = newNode.querySelector(`input[is="autocomplete-input"]`);
-  var select = newNode.querySelector("menulist");
-
-  if (input && select) {
-    awSetInputAndPopupValue(
-      input,
-      inputValue,
-      select,
-      popupValue,
-      top.MAX_RECIPIENTS
-    );
-  }
-}
-
-function awSetInputAndPopup(inputValue, popupValue, parentNode, templateNode) {
-  if (inputValue && popupValue) {
-    var addressArray = inputValue.split(",");
-
-    for (var index = 0; index < addressArray.length; index++) {
-      _awSetInputAndPopup(
-        addressArray[index],
-        popupValue,
-        parentNode,
-        templateNode
-      );
-    }
-  }
-}
-
-function awSetInputAndPopupFromArray(
-  inputArray,
-  popupValue,
-  parentNode,
-  templateNode
-) {
-  if (popupValue) {
-    for (let recipient of inputArray) {
-      _awSetInputAndPopup(recipient, popupValue, parentNode, templateNode);
-    }
-  }
-}
-
 function awRemoveRecipients(msgCompFields, recipientType, recipientsList) {
   if (!msgCompFields || !recipientsList) {
     return;
   }
 
-  var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
+  let element;
+  switch (recipientType) {
+    case "addr_cc":
+      element = document.getElementById("ccAddrInput");
+      break;
+    case "addr_bcc":
+      element = document.getElementById("bccAddrInput");
+      break;
+    case "addr_reply":
+      element = document.getElementById("replyAddrInput");
+      break;
+    case "addr_to":
+    default:
+      element = document.getElementById("toAddrInput");
+      break;
+  }
 
-  for (var index = 0; index < recipientArray.length; index++) {
-    for (var row = 1; row <= top.MAX_RECIPIENTS; row++) {
-      var popup = awGetPopupElement(row);
-      if (popup.value == recipientType) {
-        var input = awGetInputElement(row);
-        if (input.value == recipientArray[index]) {
-          awSetInputAndPopupValue(input, "", popup, "addr_to", -1);
-          break;
-        }
-      }
-    }
+  let container = element.closest(".address-container");
+  for (let pill of container.querySelectorAll("mail-address-pill")) {
+    pill.remove();
+  }
+
+  // Reset the original input.
+  let input = container.querySelector(`input[is="autocomplete-input"]`);
+  input.value = "";
+
+  if (recipientType != "addr_to") {
+    container.classList.add("hidden");
+    document.getElementById(recipientType).removeAttribute("collapsed");
   }
 }
 
 /**
  * Adds a batch of new rows matching recipientType and drops in the list of addresses.
  *
  * @param msgCompFields  A nsIMsgCompFields object that is only used as a helper,
  *                       it will not get the addresses appended.
  * @param recipientType  Type of recipient, e.g. "addr_to".
  * @param recipientList  A string of addresses to add.
  */
 function awAddRecipients(msgCompFields, recipientType, recipientsList) {
   if (!msgCompFields || !recipientsList) {
     return;
   }
 
-  var recipientArray = msgCompFields.splitRecipients(recipientsList, false);
-  awAddRecipientsArray(recipientType, recipientArray);
+  awAddRecipientsArray(
+    recipientType,
+    msgCompFields.splitRecipients(recipientsList, false)
+  );
 }
 
 /**
- * Adds a batch of new rows matching recipientType and drops in the array of addresses.
+ * Adds a batch of new recipient pill matching recipientType
+ * and drops in the array of addresses.
  *
  * @param aRecipientType  Type of recipient, e.g. "addr_to".
  * @param aAddressArray   An array of recipient addresses (strings) to add.
  */
 function awAddRecipientsArray(aRecipientType, aAddressArray) {
-  // Find rows that are empty so that we can fill them.
-  let emptyRows = [];
-  for (let row = 1; row <= top.MAX_RECIPIENTS; row++) {
-    if (awGetInputElement(row).value == "") {
-      emptyRows.push(row);
-    }
+  let label = document.getElementById(aRecipientType);
+  let addresses = MailServices.headerParser.makeFromDisplayAddress(
+    aAddressArray
+  );
+  let element = document.getElementById(label.getAttribute("control"));
+
+  if (label && element.closest(".address-row").classList.contains("hidden")) {
+    label.click();
   }
 
-  // Push the new recipients into the found empty rows or append new rows when needed.
-  let row = 1;
-  for (let address of aAddressArray) {
-    if (emptyRows.length > 0) {
-      row = emptyRows.shift();
-    } else {
-      awAppendNewRow(false);
-      row = top.MAX_RECIPIENTS;
-    }
-
-    awSetInputAndPopupValue(
-      awGetInputElement(row),
-      address,
-      awGetPopupElement(row),
-      aRecipientType,
-      row,
-      false
-    );
+  for (let address of addresses) {
+    let pill = createRecipientPill(element, address);
+    element.closest(".address-container").insertBefore(pill, element);
   }
 
-  // Be sure we still have an empty row left.
-  if (
-    emptyRows.length == 0 &&
-    awGetInputElement(top.MAX_RECIPIENTS).value != ""
-  ) {
-    // Insert empty row at the end and focus.
-    awAppendNewRow(true);
-    awSetInputAndPopupValue(
-      awGetInputElement(top.MAX_RECIPIENTS),
-      "",
-      awGetPopupElement(top.MAX_RECIPIENTS),
-      aRecipientType,
-      top.MAX_RECIPIENTS,
-      false
-    );
-  } else {
-    // Focus the next empty row, if any, or the pre-existing empty last row.
-    row = emptyRows.length > 0 ? emptyRows.shift() : top.MAX_RECIPIENTS;
-    awSetFocusTo(awGetInputElement(row));
+  if (element.id != "replyAddrInput") {
+    onRecipientsChanged();
   }
 
-  onRecipientsChanged(true);
-
   // Add the recipients to our spell check ignore list.
   addRecipientsToIgnoreList(aAddressArray.join(", "));
-}
-
-/**
- * Adds a new row matching recipientType and drops in the single address.
- *
- * This is mostly used by addons, even though they should use AddRecipient().
- *
- * @param aRecipientType  Type of recipient, e.g. addr_to.
- * @param aAddress        A string with recipient address.
- */
-function awAddRecipient(aRecipientType, aAddress) {
-  awAddRecipientsArray(aRecipientType, [aAddress]);
-}
-
-function awTestRowSequence() {
-  /*
-    This function is for debug and testing purpose only, normal user should not run it!
-
-    Every time we insert or delete a row, we must be sure we didn't break the ID sequence of
-    the addressing widget rows. This function will run a quick test to see if the sequence still ok
-
-    You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
-  */
-
-  if (!test_addresses_sequence) {
-    return true;
-  }
-
-  // Debug code to verify the sequence is still good.
-
-  let listbox = document.getElementById("addressingWidget");
-  let listitems = listbox.itemChildren;
-  if (listitems.length >= top.MAX_RECIPIENTS) {
-    for (let i = 1; i <= listitems.length; i++) {
-      let item = listitems[i - 1];
-      let inputID = item
-        .querySelector(`input[is="autocomplete-input"]`)
-        .id.split("#")[1];
-      let menulist = item.querySelector("menulist");
-      // In some places like the mailing list dialog there is no menulist,
-      // and so no popupID that needs to be kept in sequence.
-      let popupID = menulist && menulist.id.split("#")[1];
-      if (inputID != i || (popupID && popupID != i)) {
-        dump(
-          `#ERROR: sequence broken at row ${i}, ` +
-            `inputID=${inputID}, popupID=${popupID}\n`
-        );
-        return false;
-      }
-      dump("---SEQUENCE OK---\n");
-      return true;
-    }
-  } else {
-    dump(
-      `#ERROR: listitems.length(${listitems.length}) < ` +
-        `top.MAX_RECIPIENTS(${top.MAX_RECIPIENTS})\n`
-    );
-  }
-
-  return false;
-}
-
-function awCleanupRows() {
-  var maxRecipients = top.MAX_RECIPIENTS;
-  var rowID = 1;
-
-  for (var row = 1; row <= maxRecipients; row++) {
-    var inputElem = awGetInputElement(row);
-    if (inputElem.value == "" && row < maxRecipients) {
-      awRemoveRow(awGetRowByInputElement(inputElem));
-    } else {
-      awSetInputAndPopupId(inputElem, awGetPopupElement(row), rowID);
-      rowID++;
-    }
-  }
-
-  awTestRowSequence();
-}
-
-function awDeleteRow(rowToDelete) {
-  // When we delete a row, we must reset the id of other rows in order to not break the sequence.
-  var maxRecipients = top.MAX_RECIPIENTS;
-  awRemoveRow(rowToDelete);
-
-  // assume 2 column update (input and popup)
-  for (var row = rowToDelete + 1; row <= maxRecipients; row++) {
-    awSetInputAndPopupId(
-      awGetInputElement(row),
-      awGetPopupElement(row),
-      row - 1
-    );
-  }
-
-  awTestRowSequence();
-}
-
-function awClickEmptySpace(target, setFocus) {
-  if (
-    document.getElementById("addressCol2#1").disabled ||
-    target == null ||
-    target.localName != "hbox"
-  ) {
-    return;
-  }
-
-  let lastInput = awGetInputElement(top.MAX_RECIPIENTS);
-
-  if (lastInput && lastInput.value) {
-    awAppendNewRow(setFocus);
-  } else if (setFocus) {
-    awSetFocusTo(lastInput);
-  }
-}
-
-function awReturnHit(inputElement) {
-  let row = awGetRowByInputElement(inputElement);
-  let nextInput = awGetInputElement(row + 1);
-
-  if (!nextInput) {
-    if (inputElement.value) {
-      awAppendNewRow(true);
-    } else {
-      // No address entered, switch to Subject field
-      let subjectField = document.getElementById("msgSubject");
-      subjectField.select();
-      subjectField.focus();
-    }
-  } else {
-    nextInput.select();
-    awSetFocusTo(nextInput);
-  }
-
-  // be sure to add the user add recipient to our ignore list
-  // when the user hits enter in an autocomplete widget...
-  addRecipientsToIgnoreList(inputElement.value);
-}
-
-function awDeleteAddressOnClick(deleteAddressElement) {
-  awDeleteHit(
-    deleteAddressElement.parentNode.parentNode.querySelector(
-      ".textbox-addressingWidget"
-    ),
-    true
-  );
-}
-
-/**
- * Delete recipient row (addressingWidgetItem) from UI.
- *
- * @param {<html:input>} inputElement  the recipient input element
- *                                     (textbox-addressingWidget) whose parent
- *                                     row (addressingWidgetItem) will be deleted.
- * @param {boolean} deleteForward  true: focus next row after deleting the row
- *                                 false: focus previous row after deleting the row
- */
-function awDeleteHit(inputElement, deleteForward = false) {
-  let row = awGetRowByInputElement(inputElement);
-
-  // Don't delete the row if it's the last one remaining; just reset it.
-  if (top.MAX_RECIPIENTS <= 1) {
-    inputElement.value = "";
-    return;
-  }
-
-  // Set the focus to the input field of the next/previous row according to
-  // the direction of deleting if possible.
-  // Note: awSetFocusTo() is asynchronous, i.e. we'll focus after row removal.
-  if (
-    (!deleteForward && row > 1) ||
-    (deleteForward && row == top.MAX_RECIPIENTS)
-  ) {
-    // We're deleting backwards, but not the first row,
-    // or forwards on the last row: Focus previous row.
-    awSetFocusTo(awGetInputElement(row - 1));
-  } else {
-    // We're deleting forwards, but not the last row,
-    // or backwards on the first row: Focus next row.
-    awSetFocusTo(awGetInputElement(row + 1));
-  }
-
-  // Delete the row.
-  awDeleteRow(row);
-}
-
-// If we add a menulist to the DOM, it has some child nodes added to it
-// by the menulist custom element. If we then clone the menulist and add
-// it to the DOM again, more child nodes are added and we end up with
-// bug 1525828. This function clones any menulist as it originally was.
-function _menulistFriendlyClone(element) {
-  let clone = element.cloneNode(false);
-  if (element.localName == "menulist") {
-    clone.appendChild(element.menupopup.cloneNode(true));
-    return clone;
-  }
-  for (let child of element.children) {
-    clone.appendChild(_menulistFriendlyClone(child));
-  }
-  return clone;
-}
-
-function awAppendNewRow(setFocus) {
-  var listbox = document.getElementById("addressingWidget");
-  var listitem1 = awGetListItem(1);
-
-  if (listbox && listitem1) {
-    var lastRecipientType = awGetPopupElement(top.MAX_RECIPIENTS).value;
-
-    var nextDummy = awGetNextDummyRow();
-    var newNode = _menulistFriendlyClone(listitem1);
-    if (nextDummy) {
-      listbox.replaceChild(newNode, nextDummy);
-    } else {
-      listbox.appendChild(newNode);
-    }
-
-    top.MAX_RECIPIENTS++;
-
-    var input = newNode.querySelector(`input[is="autocomplete-input"]`);
-    if (input) {
-      input.value = "";
-
-      // Reset autocomplete attribute "nomatch" so we don't cause red addresses
-      // on a cloned row.
-      input.removeAttribute("nomatch");
-    }
-    var select = newNode.querySelector("menulist");
-    if (select) {
-      // It only makes sense to clone some field types; others
-      // should not be cloned, since it just makes the user have
-      // to go to the trouble of selecting something else. In such
-      // cases let's default to 'To' (a reasonable default since
-      // we already default to 'To' on the first dummy field of
-      // a new message).
-      switch (lastRecipientType) {
-        case "addr_reply":
-        case "addr_other":
-          select.selectedIndex = awGetSelectItemIndex("addr_to");
-          break;
-        case "addr_followup":
-          select.selectedIndex = awGetSelectItemIndex("addr_newsgroups");
-          break;
-        default:
-          // e.g. "addr_to","addr_cc","addr_bcc","addr_newsgroups":
-          select.selectedIndex = awGetSelectItemIndex(lastRecipientType);
-      }
-
-      awSetInputAndPopupId(input, select, top.MAX_RECIPIENTS);
-
-      if (input) {
-        _awSetAutoComplete(select, input);
-      }
-    }
-
-    // Focus the new input widget
-    if (setFocus && input) {
-      awSetFocusTo(input);
-    }
-  }
-}
-
-// functions for accessing the elements in the addressing widget
-
-/**
- * Returns the recipient type popup for a row.
- *
- * @param row  Index of the recipient row to return. Starts at 1.
- * @return     This returns the menulist (not its child menupopup), despite the function name.
- */
-function awGetPopupElement(row) {
-  return document.getElementById("addressCol1#" + row);
-}
-
-/**
- * Returns the recipient inputbox for a row.
- *
- * @param row  Index of the recipient row to return. Starts at 1.
- * @return     This returns the input element.
- */
-function awGetInputElement(row) {
-  return document.getElementById("addressCol2#" + row);
-}
-
-function awGetElementByCol(row, col) {
-  var colID = "addressCol" + col + "#" + row;
-  return document.getElementById(colID);
-}
-
-function awGetListItem(row) {
-  var listbox = document.getElementById("addressingWidget");
-  if (listbox && row > 0) {
-    return listbox.getItemAtIndex(row - 1);
-  }
-
-  return null;
-}
-
-/**
- * @param inputElement  The recipient input element.
- * @return              The row index (starting from 1) where the input element
- *                      is found. 0 if the element is not found.
- */
-function awGetRowByInputElement(inputElement) {
-  if (!inputElement) {
-    return 0;
-  }
-
-  var listitem = inputElement.parentNode.parentNode;
-  return (
-    document.getElementById("addressingWidget").getIndexOfItem(listitem) + 1
-  );
-}
-
-// Copy Node - copy this node and insert ahead of the (before) node.  Append to end if before=0
-function awCopyNode(node, parentNode, beforeNode) {
-  var newNode = node.cloneNode(true);
-
-  if (beforeNode) {
-    parentNode.insertBefore(newNode, beforeNode);
-  } else {
-    parentNode.appendChild(newNode);
-  }
-
-  return newNode;
-}
-
-function awRemoveRow(row) {
-  awGetListItem(row).remove();
-  awFitDummyRows();
-
-  top.MAX_RECIPIENTS--;
-}
-
-/**
- * Set focus to the specified element, typically a recipient input element.
- * We do this asynchronously to allow other processes like adding or removing rows
- * to complete before shifting focus.
- *
- * @param element  the element to receive focus asynchronously
- */
-function awSetFocusTo(element) {
-  // Remember the (input) element to focus for asynchronous focusing, so that we
-  // play safe if this gets called again and the original element gets removed
-  // before we can focus it.
-  top.awInputToFocus = element;
-  setTimeout(_awSetFocusTo, 0);
-}
-
-function _awSetFocusTo() {
-  top.awInputToFocus.focus();
-}
-
-// Deprecated - use awSetFocusTo() instead.
-// ### TODO: This function should be removed if we're sure addons aren't using it.
-function awSetFocus(row, inputElement) {
-  awSetFocusTo(inputElement);
-}
-
-function awGetNumberOfRecipients() {
-  return top.MAX_RECIPIENTS;
+  calculateHeaderHeight();
 }
 
 function DragOverAddressingWidget(event) {
-  var validFlavor = false;
-  var dragSession = (dragSession = gDragService.getCurrentSession());
+  let dragSession = (dragSession = gDragService.getCurrentSession());
 
   if (dragSession.isDataFlavorSupported("text/x-moz-address")) {
-    validFlavor = true;
-  }
-
-  if (validFlavor) {
     dragSession.canDrop = true;
   }
 }
 
 function DropOnAddressingWidget(event) {
-  var dragSession = gDragService.getCurrentSession();
+  let dragSession = gDragService.getCurrentSession();
 
-  var trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
+  let trans = Cc["@mozilla.org/widget/transferable;1"].createInstance(
     Ci.nsITransferable
   );
   trans.init(getLoadContext());
   trans.addDataFlavor("text/x-moz-address");
 
-  for (var i = 0; i < dragSession.numDropItems; ++i) {
+  for (let i = 0; i < dragSession.numDropItems; ++i) {
     dragSession.getData(trans, i);
-    var dataObj = {};
-    var bestFlavor = {};
+    let dataObj = {};
+    let bestFlavor = {};
     trans.getAnyTransferData(bestFlavor, dataObj);
     if (dataObj) {
       dataObj = dataObj.value.QueryInterface(Ci.nsISupportsString);
     }
     if (!dataObj) {
       continue;
     }
 
     // pull the address out of the data object
-    var address = dataObj.data.substring(0, dataObj.length);
+    let address = dataObj.data.substring(0, dataObj.length);
     if (!address) {
       continue;
     }
 
     DropRecipient(event.target, address);
   }
 }
 
+/**
+ * Find the autocomplete input when an address is dropped in the compose header.
+ *
+ * @param {XULElement} target - The element where an address was dropped.
+ * @param {string} recipient - The email address dragged by the user.
+ */
 function DropRecipient(target, recipient) {
-  // break down and add each address
-  return parseAndAddAddresses(
-    recipient,
-    awGetPopupElement(top.MAX_RECIPIENTS).value
-  );
-}
-
-function _awSetAutoComplete(selectElem, inputElem) {
-  let params = JSON.parse(inputElem.getAttribute("autocompletesearchparam"));
-  params.type = selectElem.value;
-  inputElem.setAttribute("autocompletesearchparam", JSON.stringify(params));
-}
+  let input;
 
-function awSetAutoComplete(rowNumber) {
-  var inputElem = awGetInputElement(rowNumber);
-  var selectElem = awGetPopupElement(rowNumber);
-  _awSetAutoComplete(selectElem, inputElem);
-}
-
-function awRecipientOnFocus(inputElement) {
-  inputElement.select();
-}
+  if (target.tagName == "label" && target.hasAttribute("control")) {
+    input = document.getElementById(target.getAttribute("control"));
+  } else {
+    let container = target.classList.contains("address-row")
+      ? target
+      : target.closest("hbox.address-row");
 
-/**
- * Handles keypress events for the email address inputs (that auto-fill)
- * in the Address Book Mailing List dialogs. When a comma-separated list of
- * addresses is entered on one row, split them into one address per row. Only
- * add a new blank row on "Enter" key. On "Tab" key focus moves to the "Cancel"
- * button.
- *
- * @param {KeyboardEvent} event  The DOM keypress event.
- * @param {Element} element      The element that triggered the keypress event.
- */
-function awAbRecipientKeyPress(event, element) {
-  if (event.key != "Enter" && event.key != "Tab") {
+    if (!container) {
+      return;
+    }
+    input = container.querySelector(
+      `.address-container > input[is="autocomplete-input"]`
+    );
+  }
+
+  if (!input || !input.hasAttribute("is")) {
     return;
   }
 
-  if (!element.value) {
-    if (event.key == "Enter") {
-      awReturnHit(element);
-    }
-  } else {
-    let inputElement = element;
-    let originalRow = awGetRowByInputElement(element);
-    let row;
-    let addresses = MailServices.headerParser.makeFromDisplayAddress(
-      element.value
-    );
+  let recipientType =
+    input.getAttribute("recipienttype") != "addr_other"
+      ? input.getAttribute("recipienttype")
+      : input.getAttribute("aria-labelledby");
 
-    if (addresses.length > 1) {
-      // Collect any existing addresses from the following rows so we don't
-      // simply overwrite them.
-      row = originalRow + 1;
-      inputElement = awGetInputElement(row);
-
-      while (inputElement) {
-        if (inputElement.value) {
-          addresses.push(inputElement.value);
-          inputElement.value = "";
-        }
-        row += 1;
-        inputElement = awGetInputElement(row);
-      }
-    }
+  awAddRecipientsArray(recipientType, [recipient]);
+}
 
-    // Insert the addresses, adding new rows if needed.
-    row = originalRow;
-    let needNewRows = false;
-
-    for (let address of addresses) {
-      if (needNewRows) {
-        inputElement = awAppendNewRow(false);
-      } else {
-        inputElement = awGetInputElement(row);
-        if (!inputElement) {
-          needNewRows = true;
-          inputElement = awAppendNewRow(false);
-        }
-      }
+function awSizerListen() {
+  // when splitter is clicked, fill in necessary dummy rows each time
+  // the mouse is moved.
+  document.addEventListener("mousemove", awSizerMouseMove, true);
+  document.addEventListener("mouseup", awSizerMouseUp, {
+    capture: false,
+    once: true,
+  });
+}
+// Add the overflow scroll attribute to the recipients container.
+function awSizerMouseMove() {
+  document.getElementById("recipientsContainer").classList.add("overflow");
+}
 
-      if (inputElement) {
-        inputElement.value = address;
-      }
-      row += 1;
-    }
+function awSizerMouseUp() {
+  document.removeEventListener("mousemove", awSizerMouseMove, true);
+}
 
-    if (event.key == "Enter") {
-      // Prevent the dialog from closing. "Enter" inserted a new row instead.
-      event.preventDefault();
-      awReturnHit(inputElement);
-    } else if (event.key == "Tab") {
-      // Focus the last row to let "Tab" move focus to the "Cancel" button.
-      let lastRow = row - 1;
-      awGetInputElement(lastRow).focus();
-    }
-  }
+// Returns the load context for the current window
+function getLoadContext() {
+  return window.docShell.QueryInterface(Ci.nsILoadContext);
 }
 
 /**
  * Handles keypress events for the email address inputs (that auto-fill)
  * in the Message Compose window.
  *
- * @param event       The DOM keypress event
- * @param element     The element that triggered the keypress event
+ * @param {Event} event - The DOM keypress event.
+ * @param {HTMLElement} element - The element that triggered the keypress event.
  */
-function awRecipientKeyPress(event, element) {
+function recipientKeyPress(event, element) {
   switch (event.key) {
+    case "a":
+      // Select all the pills if the input is empty.
+      if ((event.ctrlKey || event.metaKey) && !element.value.trim()) {
+        selectRecipientPills(element);
+      }
+      break;
+    case ",":
+      event.preventDefault();
+      element.handleEnter(event);
+      break;
+    case "Home":
+    case "End":
+    case "ArrowLeft":
+    case "Backspace":
+      if (!element.value.trim() && !event.repeat) {
+        let pills = element
+          .closest(".address-container")
+          .querySelectorAll("mail-address-pill");
+        if (pills.length) {
+          let key = event.key == "Home" ? 0 : pills.length - 1;
+          pills[key].focus();
+          pills[key].checkKeyboardSelected(event, pills[key]);
+        }
+      }
+      break;
     case "Enter":
+      // No address entered, move focus to Subject field.
+      if (!element.value.trim()) {
+        document.getElementById("msgSubject").focus();
+        return;
+      }
+      break;
     case "Tab":
-      // If the recipient input text contains a comma (we also convert pasted
-      // line feeds into commas), check if multiple recipients and add them
-      // accordingly. Handle semicolons too.
-      if (element.value.includes(",") || element.value.includes(";")) {
-        let addresses = element.value;
-        element.value = ""; // Clear out the current line so we don't try to autocomplete it.
-        parseAndAddAddresses(
-          addresses,
-          awGetPopupElement(awGetRowByInputElement(element)).value
-        );
-      } else if (event.key == "Tab") {
-        // Single recipient added via Tab key:
-        // Add the recipient to our spellcheck ignore list.
-        // For Enter key, this is done in awReturnHit().
-        addRecipientsToIgnoreList(element.value);
-      } else if (event.key == "Enter") {
-        awReturnHit(element);
+      // Trigger the autocomplete controller only if we have a value
+      // to prevent interfering with the natural change of focus on Tab.
+      if (element.value.trim()) {
+        event.preventDefault();
+        element.handleEnter(event);
       }
+      break;
+  }
 
-      break;
+  // Don't alter the field size if any arrow key is triggered.
+  if ([37, 38, 39, 40].includes(event.keyCode)) {
+    return;
+  }
+
+  let size = parseInt(element.getAttribute("size"));
+  // Change the min size of the input field on typing.
+  if (event.key == "Backspace" && size > 1) {
+    element.setAttribute("size", size - 1);
+  } else {
+    element.setAttribute("size", size + 1);
+  }
+}
+
+/**
+ * Add a new "address-pill" to the parent recipient container.
+ *
+ * @param {HTMLElement} element - The element that triggered the keypress event.
+ * @param {boolean} [automatic=false] - Set to true if the change of recipients
+ *   was invoked programmatically and should not be considered a change of
+ *   message content.
+ */
+function recipientAddPill(element, automatic = false) {
+  if (!element.value.trim()) {
+    return;
+  }
+
+  let parent = document.getElementById(
+    element.closest(".address-container").id
+  );
+  let addresses = MailServices.headerParser.makeFromDisplayAddress(
+    element.value
+  );
+
+  for (let address of addresses) {
+    let pill = createRecipientPill(element, address);
+    parent.insertBefore(pill, element);
+
+    // Be sure to add the user add recipient to our ignore list
+    // when the user hits enter in an autocomplete widget...
+    addRecipientsToIgnoreList(element.value);
+  }
+
+  // Reset the input element.
+  element.removeAttribute("nomatch");
+  element.setAttribute("size", 1);
+  element.value = "";
+
+  // We need to detach the autocomplete Controller to prevent the input
+  // to be filled with the previously selected address when the "blur" event
+  // gets triggered.
+  element.detachController();
+  // Attach it again to enable autocomplete.
+  element.attachController();
+
+  onRecipientsChanged(automatic);
+  calculateHeaderHeight();
+}
+
+/**
+ * Create a new recipient pill.
+ *
+ * @param {HTMLElement} element - The original autocomplete input that generated
+ *   the pill.
+ * @param {Array} address - The array containing the recipient's info.
+ * @returns {XULElement} The newly created pill element.
+ */
+function createRecipientPill(element, address) {
+  let pill = document.createXULElement("mail-address-pill");
+
+  pill.originalInput = element;
+  pill.label = address.toString();
+  pill.emailAddress = address.email || "";
+  pill.fullAddress = address.toString();
+  pill.displayName = address.name || "";
+  pill.setAttribute("recipienttype", element.getAttribute("recipienttype"));
+
+  let listNames = MimeParser.parseHeaderField(
+    address.toString(),
+    MimeParser.HEADER_ADDRESS
+  );
+  let isMailingList =
+    listNames.length > 0 &&
+    MailServices.ab.mailListNameExists(listNames[0].name);
+  let isNewsgroup = element.classList.contains("nntp-input");
+
+  pill.classList.toggle(
+    "error",
+    !isValidAddress(address.email) && !isMailingList && !isNewsgroup
+  );
+
+  let emailCard = DisplayNameUtils.getCardForEmail(address.email);
+  pill.classList.toggle(
+    "warning",
+    isValidAddress(address.email) &&
+      !emailCard.card &&
+      !isMailingList &&
+      !isNewsgroup
+  );
+
+  return pill;
+}
+
+/**
+ * Force a focused styling on the recipient container of the currently
+ * selected input element.
+ *
+ * @param {HTMLElement} element - The element receving focus.
+ */
+function highlightAddressContainer(element) {
+  element.closest(".address-container").setAttribute("focused", "true");
+  deselectAllPills();
+}
+
+/**
+ * Deselect any previously selected pills.
+ */
+function deselectAllPills() {
+  for (let pill of document.querySelectorAll(`mail-address-pill[selected]`)) {
+    pill.removeAttribute("selected");
   }
 }
 
 /**
- * Handle keydown event on a recipient input.
- * Enables recipient row deletion with DEL or BACKSPACE and
- * recipient list navigation with cursor up/down.
+ * Remove the focused styling from the recipient container and create
+ * address pills if valid recipients were written.
  *
- * Note that the keydown event fires for ALL keys, so this may affect
- * autocomplete as user enters a recipient text.
+ * @param {HTMLElement} element - The element losing focus.
+ */
+function resetAddressContainer(element) {
+  let address = element.value.trim();
+  let listNames = MimeParser.parseHeaderField(
+    address,
+    MimeParser.HEADER_ADDRESS
+  );
+  let isMailingList =
+    listNames.length > 0 &&
+    MailServices.ab.mailListNameExists(listNames[0].name);
+
+  if (
+    address &&
+    (isValidAddress(address) ||
+      isMailingList ||
+      element.classList.contains("nntp-input"))
+  ) {
+    recipientAddPill(element);
+  }
+
+  // Reset the input size if no pill was created.
+  if (!address) {
+    element.setAttribute("size", 1);
+  }
+  element.closest(".address-container").removeAttribute("focused");
+}
+
+/**
+ * Trigger the startEditing() method of the mail-address-pill element.
  *
- * @param {keydown event} event  the keydown event fired on a recipient input
- * @param {<html:input>} inputElement  the recipient input element
- *                                     on which the event fired (textbox-addressingWidget)
+ * @param {XULlement} element - The element from which the context menu was
+ *   opened.
+ * @param {Event} event - The DOM event.
+ */
+function editAddressPill(element, event) {
+  element.closest("mail-address-pill").startEditing(event);
+}
+
+/**
+ * Copy the selected pills email address.
+ *
+ * @param {XULElement} element - The element from which the context menu was
+ *   opened.
  */
-function awRecipientKeyDown(event, inputElement) {
-  switch (event.key) {
-    // Enable deletion of empty recipient rows.
-    case "Delete":
-    case "Backspace":
-      if (inputElement.textLength == 1 && event.repeat) {
-        // User is holding down Delete or Backspace to delete recipient text
-        // inline and is now deleting the last character: Set flag to
-        // temporarily block row deletion.
-        top.awRecipientInlineDelete = true;
-      }
-      if (!inputElement.value && !event.altKey) {
-        // When user presses DEL or BACKSPACE on an empty row, and it's not an
-        // ongoing inline deletion, and not ALT+BACKSPACE for input undo,
-        // we delete the row.
-        if (top.awRecipientInlineDelete && !event.repeat) {
-          // User has released and re-pressed Delete or Backspace key
-          // after holding them down to delete recipient text inline:
-          // unblock row deletion.
-          top.awRecipientInlineDelete = false;
-        }
-        if (!top.awRecipientInlineDelete) {
-          let deleteForward = event.key == "Delete";
-          awDeleteHit(inputElement, deleteForward);
-        }
-      }
-      break;
+function copyEmailNewsAddress(element) {
+  let allAddresses = [];
+  for (let pill of getAllSelectedPills(element.closest("mail-address-pill"))) {
+    allAddresses.push(pill.fullAddress);
+  }
+
+  let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(
+    Ci.nsIClipboardHelper
+  );
+  clipboard.copyString(allAddresses.join(", "));
+}
+
+/**
+ * Cut the selected pills email address.
+ *
+ * @param {XULElement} element - The element from which the context menu was
+ *   opened.
+ */
+function cutEmailNewsAddress(element) {
+  copyEmailNewsAddress(element);
+  deleteAddressPill(element);
+}
 
-    // Enable browsing the list of recipients up and down with cursor keys.
-    case "ArrowDown":
-    case "ArrowUp":
-      // Only browse recipients if the autocomplete popup is not open.
-      if (!inputElement.popupOpen) {
-        let row = awGetRowByInputElement(inputElement);
-        let down = event.key == "ArrowDown";
-        let noEdgeRow = down ? row < top.MAX_RECIPIENTS : row > 1;
-        if (noEdgeRow) {
-          let targetRow = down ? row + 1 : row - 1;
-          awSetFocusTo(awGetInputElement(targetRow));
-        }
-      }
-      break;
+/**
+ * Delete the selected pill/pills.
+ *
+ * @param {XULElement} element - The element from which the context menu was
+ *   opened.
+ */
+function deleteAddressPill(element) {
+  let firstPill = element.closest("mail-address-pill");
+
+  // We need to store the input location before removing the pills.
+  let input = firstPill
+    .closest(".address-container")
+    .querySelector(`input[is="autocomplete-input"][recipienttype]`);
+
+  for (let pill of getAllSelectedPills(firstPill)) {
+    pill.remove();
+  }
+
+  input.focus();
+  onRecipientsChanged();
+}
+
+/**
+ * Handle the keypress event on the labels to show the container row
+ * of an hidden recipient (Cc, Bcc, etc.).
+ *
+ * @param {Event} event - The DOM keypress event.
+ * @param {XULelement} label - The clicked label to hide.
+ * @param {string} rowID - The ID of the container to reveal.
+ */
+function showAddressRowKeyPress(event, label, rowID) {
+  if (event.key == "Enter") {
+    showAddressRow(label, rowID);
   }
 }
 
-/* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
+/**
+ * Show the container row of an hidden recipient (Cc, Bcc, etc.).
+ *
+ * @param {XULelement} label - The clicked label to hide.
+ * @param {string} rowID - The ID of the container to reveal.
+ */
+function showAddressRow(label, rowID) {
+  let container = document.getElementById(rowID);
+  let input = container.querySelector(`input[is="autocomplete-input"]`);
 
-var gAWContentHeight = 0;
-var gAWRowHeight = 0;
-
-function awFitDummyRows() {
-  awCalcContentHeight();
-  awCreateOrRemoveDummyRows();
+  container.classList.remove("hidden");
+  label.setAttribute("collapsed", "true");
+  input.focus();
 }
 
-function awCreateOrRemoveDummyRows() {
-  let listbox = document.getElementById("addressingWidget");
-  let listboxHeight = listbox.getBoundingClientRect().height;
+/**
+ * Hide the container row of a recipient (Cc, Bcc, etc.).
+ * The container can't be hidden if previously typed addresses are listed.
+ *
+ * @param {XULelement} element - The clicked label.
+ * @param {string} labelID - The ID of the label to show.
+ */
+function hideAddressRow(element, labelID) {
+  let container = element.closest(".address-row");
+  let fieldName = container.querySelector(".address-label-container > label");
+  let confirmTitle = getComposeBundle().getFormattedString(
+    "confirmRemoveRecipientRowTitle",
+    [fieldName.value]
+  );
+  let confirmBody = getComposeBundle().getFormattedString(
+    "confirmRemoveRecipientRowBody",
+    [fieldName.value]
+  );
 
-  // remove rows to remove scrollbar
-  let kids = listbox.querySelectorAll("[_isDummyRow]");
-  for (
-    let i = kids.length - 1;
-    gAWContentHeight > listboxHeight && i >= 0;
-    --i
+  let pills = container.querySelectorAll("mail-address-pill");
+  // Ask the user to confirm the removal of all the typed addresses.
+  if (
+    pills.length &&
+    !Services.prompt.confirm(null, confirmTitle, confirmBody)
   ) {
-    gAWContentHeight -= gAWRowHeight;
-    kids[i].remove();
+    return;
   }
 
-  // add rows to fill space
-  if (gAWRowHeight) {
-    while (gAWContentHeight + gAWRowHeight < listboxHeight) {
-      awCreateDummyItem(listbox);
-      gAWContentHeight += gAWRowHeight;
+  for (let pill of pills) {
+    pill.remove();
+  }
+
+  // Reset the original input.
+  let input = container.querySelector(`input[is="autocomplete-input"]`);
+  input.value = "";
+
+  container.classList.add("hidden");
+  document.getElementById(labelID).removeAttribute("collapsed");
+
+  onRecipientsChanged();
+}
+
+/**
+ * Calculate the height of the composer header area every time a pill is created.
+ * If the height is bigger than 2/3 of the compose window heigh, enable overflow.
+ */
+function calculateHeaderHeight() {
+  let container = document.getElementById("msgheaderstoolbar-box");
+  if (container.classList.contains("overflow")) {
+    return;
+  }
+
+  if (container.clientHeight >= window.outerHeight * 0.7) {
+    document.getElementById("recipientsContainer").classList.add("overflow");
+
+    let header = document.getElementById("headers-box");
+    if (!header.hasAttribute("height")) {
+      header.setAttribute("height", 300);
     }
   }
 }
 
-function awCalcContentHeight() {
-  var listbox = document.getElementById("addressingWidget");
-  var items = listbox.itemChildren;
-
-  gAWContentHeight = 0;
-  if (items.length > 0) {
-    // all rows are forced to a uniform height in xul listboxes, so
-    // find the first listitem with a boxObject and use it as precedent
-    var i = 0;
-    do {
-      gAWRowHeight = items[i].getBoundingClientRect().height;
-      ++i;
-    } while (i < items.length && !gAWRowHeight);
-    gAWContentHeight = gAWRowHeight * items.length;
-  }
-}
-
-function awCreateDummyItem(aParent) {
-  var listbox = document.getElementById("addressingWidget");
-  var item = listbox.getItemAtIndex(0);
-
-  var titem = document.createXULElement("richlistitem");
-  titem.setAttribute("_isDummyRow", "true");
-  titem.setAttribute("class", "dummy-row");
-  titem.style.height = item.getBoundingClientRect().height + "px";
-
-  for (let i = 0; i < awGetNumberOfCols(); i++) {
-    let cell = awCreateDummyCell(titem);
-    if (item.children[i].hasAttribute("style")) {
-      cell.setAttribute("style", item.children[i].getAttribute("style"));
-    }
-    if (item.children[i].hasAttribute("flex")) {
-      cell.setAttribute("flex", item.children[i].getAttribute("flex"));
-    }
-  }
-
-  if (aParent) {
-    aParent.appendChild(titem);
-  }
-
-  return titem;
-}
-
-function awCreateDummyCell(aParent) {
-  var cell = document.createXULElement("hbox");
-  cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
-  if (aParent) {
-    aParent.appendChild(cell);
-  }
-
-  return cell;
-}
-
-function awGetNextDummyRow() {
-  // gets the next row from the top down
-  return document.querySelector("#addressingWidget > [_isDummyRow]");
-}
-
-function awSizerListen() {
-  // when splitter is clicked, fill in necessary dummy rows each time the mouse is moved
-  awCalcContentHeight(); // precalculate
-  document.addEventListener("mousemove", awSizerMouseMove, true);
-  document.addEventListener("mouseup", awSizerMouseUp, {
-    capture: false,
-    once: true,
-  });
-}
-
-function awSizerMouseMove() {
-  awCreateOrRemoveDummyRows(2);
-}
-
-function awSizerMouseUp() {
-  document.removeEventListener("mousemove", awSizerMouseMove, true);
-}
-
-// Given an arbitrary block of text like a comma delimited list of names or a names separated by spaces,
-// we will try to autocomplete each of the names and then take the FIRST match for each name, adding it the
-// addressing widget on the compose window.
-
-var gAutomatedAutoCompleteListener = null;
-
-function parseAndAddAddresses(addressText, recipientType) {
-  // strip any leading >> characters inserted by the autocomplete widget
-  var strippedAddresses = addressText.replace(/.* >> /, "");
-
-  let addresses = MailServices.headerParser.makeFromDisplayAddress(
-    strippedAddresses
-  );
-
-  if (addresses.length > 0) {
-    // we need to set up our own autocomplete session and search for results
-    if (!gAutomatedAutoCompleteListener) {
-      gAutomatedAutoCompleteListener = new AutomatedAutoCompleteHandler();
-    }
-
-    gAutomatedAutoCompleteListener.init(
-      addresses.map(addr => addr.toString()),
-      recipientType
-    );
-  }
+/**
+ * Move the focus on the first pill from the same .address-container.
+ *
+ * @param {XULElement} pill - The mail-address-pill element.
+ */
+function setFocusOnFirstPill(pill) {
+  pill.closest(".address-container").firstElementChild.focus();
 }
 
-function AutomatedAutoCompleteHandler() {}
-
-// state driven self contained object which will autocomplete a block of addresses without any UI.
-// force picks the first match and adds it to the addressing widget, then goes on to the next
-// name to complete.
-
-AutomatedAutoCompleteHandler.prototype = {
-  param: this,
-  sessionName: null,
-  namesToComplete: null,
-  numNamesToComplete: 0,
-  indexIntoNames: 0,
-  finalAddresses: null,
-
-  numSessionsToSearch: 0,
-  numSessionsSearched: 0,
-  recipientType: null,
-  searchResults: null,
-
-  init(namesToComplete, recipientType) {
-    this.indexIntoNames = 0;
-    this.numNamesToComplete = namesToComplete.length;
-    this.namesToComplete = namesToComplete;
-    this.finalAddresses = [];
-
-    this.recipientType = recipientType ? recipientType : "addr_to";
-
-    // set up the auto complete sessions to use
-    this.autoCompleteNextAddress();
-  },
-
-  autoCompleteNextAddress() {
-    this.numSessionsToSearch = 0;
-    this.numSessionsSearched = 0;
-    this.searchResults = [];
+// #TODO: The getSiblingPills(), getAllPills(), and getAllSelectedPills()
+// methods are not a good way to handle these scenarios, and they should be
+// moved into their own CE. See Bug 1601740
 
-    if (this.indexIntoNames < this.numNamesToComplete) {
-      if (this.namesToComplete[this.indexIntoNames]) {
-        /* XXX This is used to work, until switching to the new toolkit broke it
-         We should fix it see bug 456550.
-      if (!this.namesToComplete[this.indexIntoNames].includes('@')) // don't autocomplete if address has an @ sign in it
-      {
-        // make sure total session count is updated before we kick off ANY actual searches
-        if (gAutocompleteSession)
-          this.numSessionsToSearch++;
-
-        if (gLDAPSession && gCurrentAutocompleteDirectory)
-          this.numSessionsToSearch++;
-
-        if (gAutocompleteSession)
-        {
-           gAutocompleteSession.onAutoComplete(this.namesToComplete[this.indexIntoNames], null, this);
-           // AB searches are actually synchronous. So by the time we get here we have already looked up results.
-
-           // if we WERE going to also do an LDAP lookup, then check to see if we have a valid match in the AB, if we do
-           // don't bother with the LDAP search too just return
-
-           if (gLDAPSession && gCurrentAutocompleteDirectory && this.searchResults[0] && this.searchResults[0].defaultItemIndex != -1)
-           {
-             this.processAllResults();
-             return;
-           }
-        }
-
-        if (gLDAPSession && gCurrentAutocompleteDirectory)
-          gLDAPSession.onStartLookup(this.namesToComplete[this.indexIntoNames], null, this);
-      }
-      */
-
-        if (!this.numSessionsToSearch) {
-          // LDAP and ab are turned off, so leave text alone.
-          this.processAllResults();
-        }
-      }
-    } else {
-      this.finish();
-    }
-  },
+/**
+ * Return all the pills from the same .address-container.
+ *
+ * @param {XULElement} pill - The mail-address-pill element.
+ * @return {Array} Array of mail-address-pill elements.
+ */
+function getSiblingPills(pill) {
+  return pill
+    .closest(".address-container")
+    .querySelectorAll("mail-address-pill");
+}
 
-  onStatus(aStatus) {},
-
-  onAutoComplete(aResults, aStatus) {
-    // store the results until all sessions are done and have reported in
-    if (aResults) {
-      this.searchResults[this.numSessionsSearched] = aResults;
-    }
-
-    this.numSessionsSearched++; // bump our counter
-
-    if (this.numSessionsToSearch <= this.numSessionsSearched) {
-      // We are all done.
-      setTimeout(gAutomatedAutoCompleteListener.processAllResults, 0);
-    }
-  },
-
-  processAllResults() {
-    // Take the first result and add it to the compose window
-    var addressToAdd;
-
-    // loop through the results looking for the non default case (default case is the address book with only one match, the default domain)
-    var sessionIndex;
-
-    var searchResultsForSession;
-
-    for (sessionIndex in this.searchResults) {
-      searchResultsForSession = this.searchResults[sessionIndex];
-      if (
-        searchResultsForSession &&
-        searchResultsForSession.defaultItemIndex > -1
-      ) {
-        addressToAdd = searchResultsForSession.items.queryElementAt(
-          searchResultsForSession.defaultItemIndex,
-          Ci.nsIAutoCompleteItem
-        ).value;
-        break;
-      }
-    }
+/**
+ * Return all the pills currently available in the document.
+ *
+ * @return {Array} Array of mail-address-pill elements.
+ */
+function getAllPills() {
+  return document.querySelectorAll("mail-address-pill");
+}
 
-    // still no match? loop through looking for the -1 default index
-    if (!addressToAdd) {
-      for (sessionIndex in this.searchResults) {
-        searchResultsForSession = this.searchResults[sessionIndex];
-        if (
-          searchResultsForSession &&
-          searchResultsForSession.defaultItemIndex == -1
-        ) {
-          addressToAdd = searchResultsForSession.items.queryElementAt(
-            0,
-            Ci.nsIAutoCompleteItem
-          ).value;
-          break;
-        }
-      }
-    }
+/**
+ * Return all the selected pills currently available in the document.
+ *
+ * @return {Array} Array of selected mail-address-pill elements.
+ */
+function getAllSelectedPills() {
+  return document.querySelectorAll(`mail-address-pill[selected]`);
+}
 
-    // no matches anywhere...just use what we were given
-    if (!addressToAdd) {
-      addressToAdd = this.namesToComplete[this.indexIntoNames];
-    }
-
-    this.finalAddresses.push(addressToAdd);
-
-    this.indexIntoNames++;
-    this.autoCompleteNextAddress();
-  },
-
-  finish() {
-    // This will now append all the recipients, set the focus on a new
-    // available row, and make sure it is visible.
-    awAddRecipientsArray(this.recipientType, this.finalAddresses);
-  },
-
-  QueryInterface: ChromeUtils.generateQI(["nsIAutoCompleteListener"]),
-};
-
-// Returns the load context for the current window
-function getLoadContext() {
-  return window.docShell.QueryInterface(Ci.nsILoadContext);
-}
+// #END TODO
--- a/mail/components/compose/content/messengercompose.xul
+++ b/mail/components/compose/content/messengercompose.xul
@@ -738,16 +738,32 @@
            onpopupshowing="onBlockedContentOptionsShowing(event);">
 </menupopup>
 
 <menupopup id="languageMenuList"
            oncommand="ChangeLanguage(event);"
            onpopupshowing="OnShowDictionaryMenu(event.target);">
 </menupopup>
 
+
+<menupopup id="emailAddressPillPopup" class="emailAddressPopup">
+  <menuitem id="editAddressPill" label="&editMenu.label;"
+            accesskey="&editMenu.accesskey;"
+            oncommand="editAddressPill(document.popupNode, event)"/>
+  <menuitem id="menu_cut" label="&cutCmd.label;"
+            accesskey="&cutCmd.accesskey;"
+            oncommand="cutEmailNewsAddress(document.popupNode)"/>
+  <menuitem id="menu_copy" label="&copyCmd.label;"
+            accesskey="&copyCmd.accesskey;"
+            oncommand="copyEmailNewsAddress(document.popupNode)"/>
+  <menuitem id="menu_delete" label="&deleteCmd.label;"
+            accesskey="&deleteCmd.accesskey;"
+            oncommand="deleteAddressPill(document.popupNode)"/>
+</menupopup>
+
 #ifdef XP_MACOSX
   <vbox id="titlebar">
     <hbox id="titlebar-content">
       <spacer id="titlebar-spacer" flex="1"/>
       <hbox id="titlebar-buttonbox-container" align="start">
         <hbox id="titlebar-buttonbox">
           <toolbarbutton class="titlebar-button" id="titlebar-min" oncommand="window.minimize();"/>
           <toolbarbutton class="titlebar-button" id="titlebar-max" oncommand="onTitlebarMaxClick();"/>
@@ -1976,90 +1992,299 @@
              customizable="true" nowindowdrag="true"
              ondragover="nsDragAndDrop.dragOver(event, envelopeDragObserver);"
              ondrop="nsDragAndDrop.drop(event, envelopeDragObserver);"
              ondragexit="nsDragAndDrop.dragExit(event, envelopeDragObserver);">
       <hbox id="msgheaderstoolbar-box" flex="1">
         <vbox flex="1" id="addresses-box">
           <hbox id="top-gradient-box">
             <hbox class="aw-firstColBox"/>
-            <hbox id="identityLabel-box" align="center" pack="end" style="&headersSpace.style;">
-              <label id="identityLabel" value="&fromAddr.label;"
+            <hbox id="identityLabel-box" align="center"
+                  pack="end" style="&headersSpace2.style;">
+              <label id="identityLabel" value="&fromAddr2.label;"
                      accesskey="&fromAddr.accesskey;" control="msgIdentity"/>
             </hbox>
             <menulist is="menulist-editable" id="msgIdentity"
                       type="description" flex="1"
                       disableautoselect="true" onkeypress="fromKeyPress(event);"
                       oncommand="LoadIdentity(false);" disableonsend="true">
               <menupopup id="msgIdentityPopup"/>
             </menulist>
           </hbox>
-          <richlistbox id="addressingWidget" flex="1" seltype="multiple"
-                       onclick="awClickEmptySpace(event.originalTarget, true)"
-                       disableonsend="true">
-            <treecols hidden="true">
-              <treecol id="firstcol-addressingWidget"/>
-              <treecol id="typecol-addressingWidget" style="&headersSpace.style;"/>
-              <treecol id="textcol-addressingWidget" flex="1"/>
-            </treecols>
-            <richlistitem class="addressingWidgetItem" allowevents="true">
-              <hbox class="aw-firstColBox" align="center">
-                <image class="deleteAddress close-icon"
-                       onclick="awDeleteAddressOnClick(this);"/>
+
+          <vbox id="recipientsContainer" class="recipients-container" flex="1">
+            <hbox id="addressRowTo" class="addressingWidgetItem address-row">
+              <hbox class="aw-firstColBox"/>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="toAddrLabel" value="&toAddr2.label;"
+                       control="toAddrInput"/>
+              </hbox>
+              <hbox id="toAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="toAddrInput"
+                            type="text"
+                            class="plain address-input pop-imap-input"
+                            disableonsend="true"
+                            aria-labelledby="toAddrLabel"
+                            autocompletesearch="mydomain addrbook ldap news"
+                            autocompletesearchparam="{}"
+                            timeout="300"
+                            maxrows="6"
+                            completedefaultindex="true"
+                            forcecomplete="true"
+                            completeselectedindex="true"
+                            minresultsforpopup="2"
+                            ignoreblurwhilesearching="true"
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_to"
+                            size="1"/>
+              </hbox>
+            </hbox>
+
+            <hbox id="addressRowCc" class="addressingWidgetItem address-row hidden">
+              <hbox class="aw-firstColBox" align="top">
+                <label onclick="hideAddressRow(this, 'addr_cc');"
+                       onkeypress="if (event.key == 'Enter') { hideAddressRow(this, 'addr_cc'); }">
+                  <image class="close-icon"/>
+                </label>
+              </hbox>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="ccAddrLabel" value="&ccAddr2.label;"
+                       control="ccAddrInput"/>
               </hbox>
-              <hbox class="addressingWidgetCell" align="center" style="&headersSpace.style;">
-                <menulist id="addressCol1#1" disableonsend="true"
-                          class="aw-menulist" flex="1"
-                          oncommand="onAddressColCommand(this.id);">
-                  <menupopup>
-                    <menuitem value="addr_to" label="&toAddr.label;"/>
-                    <menuitem value="addr_cc" label="&ccAddr.label;"/>
-                    <menuitem value="addr_bcc" label="&bccAddr.label;"/>
-                    <menuitem value="addr_reply" label="&replyAddr.label;"/>
-                    <menuitem value="addr_newsgroups" label="&newsgroupsAddr.label;"/>
-                    <menuitem value="addr_followup" label="&followupAddr.label;"/>
-                  </menupopup>
-                </menulist>
+              <hbox id="ccAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="ccAddrInput"
+                            type="text"
+                            class="plain address-input pop-imap-input"
+                            disableonsend="true"
+                            aria-labelledby="ccAddrLabel"
+                            autocompletesearch="mydomain addrbook ldap news"
+                            autocompletesearchparam="{}"
+                            timeout="300"
+                            maxrows="6"
+                            completedefaultindex="true"
+                            forcecomplete="true"
+                            completeselectedindex="true"
+                            minresultsforpopup="2"
+                            ignoreblurwhilesearching="true"
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_cc"
+                            size="1"/>
               </hbox>
-              <hbox class="addressingWidgetCell input-container" flex="1" role="combobox">
-                <html:input is="autocomplete-input" id="addressCol2#1"
-                            class="plain textbox-addressingWidget uri-element"
-                            aria-labelledby="addressCol1#1"
+            </hbox>
+
+            <hbox id="addressRowBcc" class="addressingWidgetItem address-row hidden">
+              <hbox class="aw-firstColBox" align="top">
+                <label onclick="hideAddressRow(this, 'addr_bcc');"
+                       onkeypress="if (event.key == 'Enter') { hideAddressRow(this, 'addr_bcc'); }">
+                  <image class="close-icon"/>
+                </label>
+              </hbox>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="bccAddrLabel" value="&bccAddr2.label;"
+                       control="bccAddrInput"/>
+              </hbox>
+              <hbox id="bccAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="bccAddrInput"
+                            type="text"
+                            class="plain address-input pop-imap-input"
+                            disableonsend="true"
+                            aria-labelledby="bccAddrLabel"
                             autocompletesearch="mydomain addrbook ldap news"
                             autocompletesearchparam="{}"
                             timeout="300"
                             maxrows="6"
                             completedefaultindex="true"
                             forcecomplete="true"
                             completeselectedindex="true"
                             minresultsforpopup="2"
                             ignoreblurwhilesearching="true"
-                            onfocus="awRecipientOnFocus(this);"
-                            onchange="onRecipientsChanged();"
-                            oninput="onRecipientsChanged();"
-                            onkeypress="awRecipientKeyPress(event, this)"
-                            onkeydown="awRecipientKeyDown(event, this)"
-                            disableonsend="true"/>
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_bcc"
+                            size="1"/>
+              </hbox>
+            </hbox>
+
+            <hbox id="addressRowReply" class="addressingWidgetItem address-row hidden">
+              <hbox class="aw-firstColBox" align="top">
+                <label onclick="hideAddressRow(this, 'addr_reply');"
+                       onkeypress="if (event.key == 'Enter') { hideAddressRow(this, 'addr_reply'); }">
+                  <image class="close-icon"/>
+                </label>
+              </hbox>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="replyAddrLabel" value="&replyAddr2.label;"
+                       control="replyAddrInput"/>
+              </hbox>
+              <hbox id="replyAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="replyAddrInput"
+                            type="text"
+                            class="plain address-input pop-imap-input"
+                            disableonsend="true"
+                            aria-labelledby="replyAddrLabel"
+                            autocompletesearch="mydomain addrbook ldap news"
+                            autocompletesearchparam="{}"
+                            timeout="300"
+                            maxrows="6"
+                            completedefaultindex="true"
+                            forcecomplete="true"
+                            completeselectedindex="true"
+                            minresultsforpopup="2"
+                            ignoreblurwhilesearching="true"
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_reply"
+                            size="1"/>
+              </hbox>
+            </hbox>
+
+            <hbox id="addressRowNewsgroups" class="addressingWidgetItem address-row hidden">
+              <hbox class="aw-firstColBox" align="top">
+                <label onclick="hideAddressRow(this, 'addr_newsgroups');"
+                       onkeypress="if (event.key == 'Enter') { hideAddressRow(this, 'addr_newsgroups'); }">
+                  <image class="close-icon"/>
+                </label>
+              </hbox>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="newsgroupsAddrLabel" value="&newsgroupsAddr2.label;"
+                       control="newsgroupsAddrInput"/>
               </hbox>
-            </richlistitem>
-          </richlistbox>
+              <hbox id="newsgroupsAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="newsgroupsAddrInput"
+                            type="text"
+                            class="plain address-input nntp-input"
+                            disableonsend="true"
+                            aria-labelledby="newsgroupsAddrLabel"
+                            autocompletesearch="mydomain addrbook ldap news"
+                            autocompletesearchparam="{}"
+                            timeout="300"
+                            maxrows="6"
+                            completedefaultindex="true"
+                            forcecomplete="true"
+                            completeselectedindex="true"
+                            minresultsforpopup="2"
+                            ignoreblurwhilesearching="true"
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_newsgroups"
+                            size="1"/>
+              </hbox>
+            </hbox>
+
+            <hbox id="addressRowFollowup" class="addressingWidgetItem address-row hidden">
+              <hbox class="aw-firstColBox" align="top">
+                <label onclick="hideAddressRow(this, 'addr_followup');"
+                       onkeypress="if (event.key == 'Enter') { hideAddressRow(this, 'addr_followup'); }">
+                  <image class="close-icon"/>
+                </label>
+              </hbox>
+              <hbox class="address-label-container" align="top"
+                    pack="end" style="&headersSpace2.style;">
+                <label id="followupAddrLabel" value="&followupAddr2.label;"
+                       control="followupAddrInput"/>
+              </hbox>
+              <hbox id="followupAddrContainer" flex="1" align="center"
+                    class="input-container wrap-container address-container"
+                    onclick="focusAddressInput(event);">
+                <html:input is="autocomplete-input" id="followupAddrInput"
+                            type="text"
+                            class="plain address-input nntp-input"
+                            disableonsend="true"
+                            aria-labelledby="followupAddrLabel"
+                            autocompletesearch="mydomain addrbook ldap news"
+                            autocompletesearchparam="{}"
+                            timeout="300"
+                            maxrows="6"
+                            completedefaultindex="true"
+                            forcecomplete="true"
+                            completeselectedindex="true"
+                            minresultsforpopup="2"
+                            ignoreblurwhilesearching="true"
+                            onfocus="highlightAddressContainer(this);"
+                            onblur="resetAddressContainer(this);"
+                            onkeypress="recipientKeyPress(event, this);"
+                            recipienttype="addr_followup"
+                            size="1"/>
+              </hbox>
+            </hbox>
+          </vbox>
+
+          <hbox class="addressingWidgetItem">
+            <hbox class="aw-firstColBox"/>
+            <hbox class="address-label-container" style="&headersSpace2.style;"/>
+            <hbox id="addressingWidgetLabels" class="address-extra-recipients"
+                  flex="1" align="center">
+              <label id="addr_to" control="toAddrInput" hidden="true"/>
+              <label id="addr_cc" onclick="showAddressRow(this, 'addressRowCc')"
+                     onkeypress="showAddressRowKeyPress(event, this, 'addressRowCc')"
+                     control="ccAddrInput">
+                <image class="new-icon"/>&ccAddr2.label;
+              </label>
+              <label id="addr_bcc" onclick="showAddressRow(this, 'addressRowBcc')"
+                     onkeypress="showAddressRowKeyPress(event, this, 'addressRowBcc')"
+                     control="bccAddrInput">
+                <image class="new-icon"/>&bccAddr2.label;
+              </label>
+              <label id="addr_reply" onclick="showAddressRow(this, 'addressRowReply')"
+                     onkeypress="showAddressRowKeyPress(event, this, 'addressRowReply')"
+                     control="replyAddrInput">
+                <image class="new-icon"/>&replyAddr2.label;
+              </label>
+              <label id="addr_newsgroups" onclick="showAddressRow(this, 'addressRowNewsgroups')"
+                     onkeypress="showAddressRowKeyPress(event, this, 'addressRowNewsgroups')"
+                     control="newsgroupsAddrInput" class="nntp-label">
+                <image class="new-icon"/>&newsgroupsAddr2.label;
+              </label>
+              <label id="addr_followup" onclick="showAddressRow(this, 'addressRowFollowup')"
+                     onkeypress="showAddressRowKeyPress(event, this, 'addressRowFollowup')"
+                     control="followupAddrInput" class="nntp-label">
+                <image class="new-icon"/>&followupAddr2.label;
+              </label>
+            </hbox>
+          </hbox>
+
           <hbox id="subject-box">
             <hbox class="aw-firstColBox"/>
-            <hbox align="center" pack="end" style="&headersSpace.style;">
-              <label id="subjectLabel" value="&subject.label;" accesskey="&subject.accesskey;" control="msgSubject"/>
+            <hbox id="subjectLabel-box" align="center"
+                  pack="end" style="&headersSpace2.style;">
+              <label id="subjectLabel" value="&subject2.label;"
+                     accesskey="&subject.accesskey;" control="msgSubject"/>
             </hbox>
             <hbox flex="1" align="center" class="input-container">
               <moz-input-box spellcheck="true" style="flex: 1;">
                 <html:input id="msgSubject"
                             type="text"
                             class="input-inline textbox-input"
                             disableonsend="true"
                             oninput="gContentChanged = true; SetComposeWindowTitle();"
                             onkeypress="subjectKeyPress(event);"
                             aria-labelledby="subjectLabel"
+                            onfocus="deselectAllPills();"
                             style="-moz-box-flex: 1;"/>
                 </moz-input-box>
             </hbox>
           </hbox>
         </vbox>
         <splitter id="attachmentbucket-sizer"
                   collapsed="true"
                   collapse="after"
@@ -2133,17 +2358,18 @@
             type="content"
             primary="true"
             src="about:blank"
             name="browser.message.body"
             aria-label="&aria.message.bodyName;"
             minheight="100"
             flex="1"
             ondblclick="EditorDblClick(event);"
-            context="msgComposeContext"/>
+            context="msgComposeContext"
+            onclick="deselectAllPills();"/>
     <findbar id="FindToolbar" browserid="content-frame"/>
   </vbox>
 
   </vbox>
   </hbox>
   <panel id="customizeToolbarSheetPopup" noautohide="true">
     <iframe id="customizeToolbarSheetIFrame"
             style="&dialog.dimensions;"
--- a/mail/extensions/smime/content/msgCompSMIMEOverlay.js
+++ b/mail/extensions/smime/content/msgCompSMIMEOverlay.js
@@ -229,17 +229,17 @@ function showMessageComposeSecurityStatu
   Recipients2CompFields(gMsgCompose.compFields);
 
   window.openDialog(
     "chrome://messenger-smime/content/msgCompSecurityInfo.xul",
     "",
     "chrome,modal,resizable,centerscreen",
     {
       compFields: gMsgCompose.compFields,
-      subject: GetMsgSubjectElement().value,
+      subject: document.getElementById("msgSubject").value,
       smFields: gSMFields,
       isSigningCertAvailable:
         gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
       isEncryptionCertAvailable:
         gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
       currentIdentity: gCurrentIdentity,
     }
   );
--- a/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/composeMsgs.properties
@@ -475,8 +475,18 @@ blockedContentPrefLabel=Options
 blockedContentPrefAccesskey=O
 
 blockedContentPrefLabelUnix=Preferences
 blockedContentPrefAccesskeyUnix=P
 
 ## Identity matching warning notification bar.
 ## LOCALIZATION NOTE(identityWarning): %S will be replaced with the identity name.
 identityWarning=A unique identity matching the From address was not found. The message will be sent using the current From field and settings from identity %S.
+
+## Recipient pills fileds.
+## LOCALIZATION NOTE(confirmRemoveRecipientRowTitle): %S will be replaced with the field name.
+confirmRemoveRecipientRowTitle=Remove %S
+## LOCALIZATION NOTE(confirmRemoveRecipientRowTitle): %S will be replaced with the field name.
+confirmRemoveRecipientRowBody=All the addresses in the %S field will be removed. Do you want to proceed?
+
+## LOCALIZATION NOTE headersSpaceStyle is for aligning label of a newly create recipient row.
+## It should be larger than the largest Header label and identical to &headersSpace2.style;
+headersSpaceStyle=width: 8em
--- a/mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
+++ b/mail/locales/en-US/chrome/messenger/messengercompose/messengercompose.dtd
@@ -250,28 +250,28 @@
 <!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 -->
-<!--LOCALIZATION NOTE headersSpace.style is for aligning  the From:, To: and
+<!--LOCALIZATION NOTE headersSpaces.style is for aligning  the From:, To: and
     Subject: rows. It should be larger than the largest Header label  -->
-<!ENTITY headersSpace.style "width: 9em;">
-<!ENTITY fromAddr.label "From:">
+<!ENTITY headersSpace2.style "width: 8em;">
+<!ENTITY fromAddr2.label "From">
 <!ENTITY fromAddr.accesskey "r">
-<!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 toAddr2.label "To">
+<!ENTITY ccAddr2.label "Cc">
+<!ENTITY bccAddr2.label "Bcc">
+<!ENTITY replyAddr2.label "Reply-To">
+<!ENTITY newsgroupsAddr2.label "Newsgroup">
+<!ENTITY followupAddr2.label "Followup-To">
+<!ENTITY subject2.label "Subject">
 <!ENTITY subject.accesskey "S">
 <!-- LOCALIZATION NOTE (attachments.accesskey) This access key character should
      be taken from the strings of attachmentCount in composeMsgs.properties.
      Please ensure that this access key is unique: Do not duplicate any other
      first-level access keys of the compose window, e.g. those of main menu,
      buttons, or labels (message headers, contacts side bar, attachment reminder
      bar). -->
 <!ENTITY attachments.accesskey "m">
--- a/mail/test/browser/composition/browser_addressWidgets.js
+++ b/mail/test/browser/composition/browser_addressWidgets.js
@@ -46,31 +46,19 @@ add_task(function setupModule(module) {
 });
 
 /**
  * Check if the address type items are in the wished state.
  *
  * @param aItemsEnabled  List of item values that should be enabled (uncollapsed).
  */
 function check_address_types_state(aItemsEnabled) {
-  let addr_types = cwc
-    .e("addressingWidget")
-    .querySelectorAll("menuitem[value]");
+  let addr_types = cwc.e("addressingWidgetLabels").querySelectorAll("label");
   for (let item of addr_types) {
-    Assert.ok(
-      item.collapsed != aItemsEnabled.includes(item.getAttribute("value"))
-    );
-  }
-
-  // Even if the currently selected type is collaped,
-  // the containing menulist should never be collapsed.
-  let addr_lists = cwc.e("addressingWidget").querySelectorAll("menulist");
-  for (let list in addr_lists) {
-    Assert.ok(!list.collapsed);
-    Assert.ok(!list.disabled);
+    Assert.ok(item.collapsed != aItemsEnabled.includes(item.id));
   }
 }
 
 /**
  * With only a POP3 account, no News related address types should be enabled.
  */
 function check_mail_address_types() {
   check_address_types_state(["addr_to", "addr_cc", "addr_reply", "addr_bcc"]);
@@ -157,24 +145,17 @@ add_task(function test_address_types() {
 
   let NNTPidentity = accountNNTP.defaultIdentity.key;
   cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [
     { identitykey: NNTPidentity },
   ]);
   check_nntp_address_types();
 
-  // In a News account, choose "Newsgroup:" as the address type.
-  cwc.click(cwc.eid("addressCol1#1"));
-  cwc.click_menus_in_sequence(cwc.e("addressCol1#1").menupopup, [
-    { value: "addr_newsgroups" },
-  ]);
-  check_nntp_address_types();
-
-  // And switch back to the POP3 account.
+  // Switch back to the POP3 account.
   let POP3identity = accountPOP3.defaultIdentity.key;
   cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [
     { identitykey: POP3identity },
   ]);
   check_nntp_address_types();
 
   close_compose_window(cwc);
--- a/mail/test/browser/composition/browser_draftIdentity.js
+++ b/mail/test/browser/composition/browser_draftIdentity.js
@@ -82,17 +82,17 @@ add_task(function setupModule(module) {
  * @return {integer}  The index (position) of the created message in the drafts folder.
  */
 function create_draft(aFrom, aIdKey) {
   let msgCount = gDrafts.getTotalMessages(false);
   let source =
     "From - Wed Mar 01 01:02:03 2017\n" +
     "X-Mozilla-Status: 0000\n" +
     "X-Mozilla-Status2: 00000000\n" +
-    "X-Mozilla-Keys:                                                                                 \n" +
+    "X-Mozilla-Keys:\n" +
     "FCC: mailbox://nobody@Local%20Folders/Sent\n" +
     (aIdKey
       ? // prettier-ignore
         `X-Identity-Key: ${aIdKey}\n` +
         `X-Account-Key: ${gAccount.key}\n`
       : "") +
     `From: ${aFrom}\n` +
     "To: nobody@example.invalid\n" +
@@ -129,17 +129,17 @@ function checkCompIdentity(cwc, aIdentit
     "The From account is not correctly selected"
   );
   Assert.equal(
     cwc.window.getCurrentIdentityKey(),
     aIdentityKey,
     "The From identity is not correctly selected"
   );
   Assert.equal(
-    cwc.window.GetMsgIdentityElement().value,
+    cwc.window.document.getElementById("msgIdentity").value,
     aFrom,
     "The From value was initialized to an unexpected value"
   );
 }
 
 /**
  * Bug 394216
  * Test that starting a new message from a draft with various combinations
--- a/mail/test/browser/composition/browser_focus.js
+++ b/mail/test/browser/composition/browser_focus.js
@@ -22,17 +22,17 @@ var { mc } = ChromeUtils.import(
  * elements forward and backward.
  *
  * @param controller the compose window controller
  * @param attachmentsExpanded true if the attachment pane is expanded
  * @param ctrlTab true if we should use Ctrl+Tab to cycle, false if we should
  *                use F6
  */
 function check_element_cycling(controller, attachmentsExpanded, ctrlTab) {
-  let addressingElement = controller.e("addressingWidget");
+  let addressingElement = controller.e("toAddrInput");
   let subjectElement = controller.e("msgSubject");
   let attachmentElement = controller.e("attachmentBucket");
   let contentElement = controller.window.content;
   let identityElement = controller.e("msgIdentity");
 
   let key = ctrlTab ? "VK_TAB" : "VK_F6";
 
   // We start on the addressing widget and go from there.
--- a/mail/test/browser/composition/browser_replyAddresses.js
+++ b/mail/test/browser/composition/browser_replyAddresses.js
@@ -79,44 +79,32 @@ function checkReply(aReplyFunction, aExp
   checkToAddresses(rwc, aExpectedFields);
   close_compose_window(rwc);
 }
 
 /**
  * Helper to check that the reply window has the expected address fields.
  */
 function checkToAddresses(replyWinController, expectedFields) {
-  let addressingWidgetItems = replyWinController.window.document.querySelectorAll(
-    "#addressingWidget .addressingWidgetItem"
+  let rows = replyWinController.window.document.querySelectorAll(
+    "#recipientsContainer .address-row:not(.hidden)"
   );
 
   let obtainedFields = [];
-  for (let i = 0; i < addressingWidgetItems.length; i++) {
-    let addrTypePopup = addressingWidgetItems[i].querySelector("menupopup");
-    let addrTextbox = addressingWidgetItems[i].querySelector(
-      `input[is="autocomplete-input"]`
+  for (let row of rows) {
+    let addrTextbox = row.querySelector(
+      `input[is="autocomplete-input"][recipienttype]`
     );
 
-    let selectedIndex = addrTypePopup.parentNode.selectedIndex;
-    let typeMenuitems = addrTypePopup.children;
-    let addrType =
-      selectedIndex != -1
-        ? typeMenuitems[selectedIndex].value
-        : typeMenuitems[0].value;
+    let addresses = [];
+    for (let pill of row.querySelectorAll("mail-address-pill")) {
+      addresses.push(pill.fullAddress);
+    }
 
-    if (!addrTextbox.value) {
-      continue;
-    }
-    let addresses = obtainedFields[addrType];
-    if (addresses) {
-      addresses.push(addrTextbox.value);
-    } else {
-      addresses = [addrTextbox.value];
-    }
-    obtainedFields[addrType] = addresses;
+    obtainedFields[addrTextbox.getAttribute("recipienttype")] = addresses;
   }
 
   // Check what we expect is there.
   for (let type in expectedFields) {
     let expected = expectedFields[type];
     let obtained = obtainedFields[type];
 
     for (let i = 0; i < expected.length; i++) {
--- a/mail/test/browser/composition/browser_sendButton.js
+++ b/mail/test/browser/composition/browser_sendButton.js
@@ -25,16 +25,17 @@ var {
   click_tree_row,
   FAKE_SERVER_HOSTNAME,
   get_special_folder,
 } = ChromeUtils.import(
   "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
 );
 var {
   clear_recipient,
+  get_first_pill,
   close_compose_window,
   open_compose_new_mail,
   setup_msg_contents,
   toggle_recipient_type,
 } = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
 var { wait_for_frame_load } = ChromeUtils.import(
   "resource://testing-common/mozmill/WindowHelpers.jsm"
 );
@@ -89,55 +90,52 @@ function check_send_commands_state(aCwc,
  * by the user.
  */
 add_task(function test_send_enabled_manual_address() {
   let cwc = open_compose_new_mail(); // compose controller
   // On an empty window, Send must be disabled.
   check_send_commands_state(cwc, false);
 
   // On valid "To:" addressee input, Send must be enabled.
-  toggle_recipient_type(cwc, "addr_to");
   setup_msg_contents(cwc, " recipient@fake.invalid ", "", "");
   check_send_commands_state(cwc, true);
 
   // When the addressee is not in To, Cc, Bcc or Newsgroup, disable Send again.
-  toggle_recipient_type(cwc, "addr_reply");
+  clear_recipient(cwc);
+  cwc.click(cwc.eid("addr_reply"));
+  setup_msg_contents(cwc, " recipient@fake.invalid ", "", "", "replyAddrInput");
   check_send_commands_state(cwc, false);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // Bug 1296535
   // Try some other invalid and valid recipient strings:
   // - random string that is no email.
   setup_msg_contents(cwc, " recipient@", "", "");
   check_send_commands_state(cwc, false);
 
-  toggle_recipient_type(cwc, "addr_cc");
+  cwc.click(cwc.eid("addr_cc"));
   check_send_commands_state(cwc, false);
 
-  // We change focus from recipient type box to recipient input box.
-  // One click on the recipient input box selects the whole typed string (bug 1527547).
-  cwc.click(cwc.eid("addressCol2#1"), 200, 5);
-  // On macOS the focus is automatically returned to the input box after operating
-  // the type box and no focus event is fired. So the recipient is not selected
-  // and the caret is after the last typed character (where we left off).
-  if (AppConstants.platform == "macosx") {
-    // Click subject and then back to recipient input to see that it gets selected.
-    cwc.click(cwc.eid("msgSubject"));
-    cwc.click(cwc.eid("addressCol2#1"));
-  }
-  Assert.equal(cwc.e("addressCol2#1").selectionStart, 0);
-  // End of selection counts the length of the " recipient@" string from above.
-  Assert.equal(cwc.e("addressCol2#1").selectionEnd, 11);
-  // Another click after the recipient deselects it to allow typing.
-  cwc.click(cwc.eid("addressCol2#1"), 200, 5);
-  Assert.equal(cwc.e("addressCol2#1").selectionStart, 11);
-  // This types additional characters into the recipient.
-  setup_msg_contents(cwc, "domain.invalid", "", "");
+  // Select the newly generated pill.
+  cwc.click(get_first_pill(cwc));
+  // Delete the selected pill.
+  cwc.keypress(null, "VK_DELETE", {});
+  // Confirm the address row is now empty.
+  Assert.ok(!get_first_pill(cwc).length);
+  // Confirm the send button is disabled.
+  check_send_commands_state(cwc, false);
+  // Add multiple recipients.
+  setup_msg_contents(
+    cwc,
+    "recipient@domain.invalid, info@somedomain.extension, name@incomplete",
+    "",
+    ""
+  );
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // - a mailinglist in addressbook
   // Button is enabled without checking whether it contains valid addresses.
   let defaultAB = MailServices.ab.getDirectory("jsaddrbook://abook.sqlite");
@@ -152,18 +150,19 @@ add_task(function test_send_enabled_manu
 
   setup_msg_contents(cwc, "emptyList <list> ", "", "");
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // - some string as a newsgroup
-  toggle_recipient_type(cwc, "addr_newsgroups");
-  setup_msg_contents(cwc, "newsgroup ", "", "");
+  cwc.e("addr_newsgroups").removeAttribute("collapsed");
+  cwc.click(cwc.eid("addr_newsgroups"));
+  setup_msg_contents(cwc, "newsgroup ", "", "", "newsgroupsAddrInput");
   check_send_commands_state(cwc, true);
 
   close_compose_window(cwc);
 });
 
 /**
  * Bug 431217
  * Test that the Send buttons are properly enabled if an addressee is prefilled
@@ -174,20 +173,19 @@ add_task(function test_send_enabled_pref
   let identity = account.defaultIdentity;
   identity.doCc = true;
   identity.doCcList = "Auto@recipient.invalid";
 
   // In that case the recipient is input, enabled Send.
   let cwc = open_compose_new_mail(); // compose controller
   check_send_commands_state(cwc, true);
 
-  // Press backspace to remove the recipient. No other valid one is there,
-  // Send should become disabled.
-  cwc.e("addressCol2#1").select();
-  cwc.keypress(null, "VK_BACK_SPACE", {});
+  // Clear the CC list.
+  clear_recipient(cwc);
+  // No other pill is there. Send should become disabled.
   check_send_commands_state(cwc, false);
 
   close_compose_window(cwc);
   identity.doCcList = "";
   identity.doCc = false;
 });
 
 /**
--- a/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js
+++ b/mail/test/browser/folder-display/browser_messageCommandsOnMsgstore.js
@@ -6,19 +6,21 @@
  * This tests some commands on messages via the UI. But we specifically check,
  * whether the commands have an effect in the message store on disk, i.e. the
  * markings on the messages are stored in the msgStore, not only in the database.
  * For now, it checks for bug 840418.
  */
 
 "use strict";
 
-var { open_compose_with_forward, open_compose_with_reply } = ChromeUtils.import(
-  "resource://testing-common/mozmill/ComposeHelpers.jsm"
-);
+var {
+  open_compose_with_forward,
+  open_compose_with_reply,
+  setup_msg_contents,
+} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
 var {
   be_in_folder,
   create_folder,
   empty_folder,
   get_special_folder,
   make_new_sets_in_folder,
   mc,
   press_delete,
@@ -218,17 +220,17 @@ function reply_forward_message(aMsgRow, 
   let cwc;
   if (aReply) {
     // Reply to the message.
     cwc = open_compose_with_reply();
   } else {
     // Forward the message.
     cwc = open_compose_with_forward();
     // Type in some recipient.
-    cwc.type(cwc.eid("addressCol2#1"), "somewhere@host.invalid");
+    setup_msg_contents(cwc, "somewhere@host.invalid", "", "");
   }
 
   // Send it later.
   plan_for_window_close(cwc);
   // Ctrl+Shift+Return = Send Later
   cwc.keypress(cwc.eid("content-frame"), "VK_RETURN", {
     shiftKey: true,
     accelKey: true,
--- a/mail/test/mozmill/composition/test-address-widgets.js
+++ b/mail/test/mozmill/composition/test-address-widgets.js
@@ -51,31 +51,19 @@ function setupModule(module) {
 function teardownModule(module) {}
 
 /**
  * Check if the address type items are in the wished state.
  *
  * @param aItemsEnabled  List of item values that should be enabled (uncollapsed).
  */
 function check_address_types_state(aItemsEnabled) {
-  let addr_types = cwc
-    .e("addressingWidget")
-    .querySelectorAll("menuitem[value]");
+  let addr_types = cwc.e("addressingWidgetLabels").querySelectorAll("label");
   for (let item of addr_types) {
-    assert_true(
-      item.collapsed != aItemsEnabled.includes(item.getAttribute("value"))
-    );
-  }
-
-  // Even if the currently selected type is collaped,
-  // the containing menulist should never be collapsed.
-  let addr_lists = cwc.e("addressingWidget").querySelectorAll("menulist");
-  for (let list in addr_lists) {
-    assert_false(list.collapsed);
-    assert_false(list.disabled);
+    assert_true(item.collapsed != aItemsEnabled.includes(item.id));
   }
 }
 
 /**
  * With only a POP3 account, no News related address types should be enabled.
  */
 function check_mail_address_types() {
   check_address_types_state(["addr_to", "addr_cc", "addr_reply", "addr_bcc"]);
@@ -162,24 +150,17 @@ function test_address_types() {
 
   let NNTPidentity = accountNNTP.defaultIdentity.key;
   cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [
     { identitykey: NNTPidentity },
   ]);
   check_nntp_address_types();
 
-  // In a News account, choose "Newsgroup:" as the address type.
-  cwc.click(cwc.eid("addressCol1#1"));
-  cwc.click_menus_in_sequence(cwc.e("addressCol1#1").menupopup, [
-    { value: "addr_newsgroups" },
-  ]);
-  check_nntp_address_types();
-
-  // And switch back to the POP3 account.
+  // Switch back to the POP3 account.
   let POP3identity = accountPOP3.defaultIdentity.key;
   cwc.click(cwc.eid("msgIdentity"));
   cwc.click_menus_in_sequence(cwc.e("msgIdentityPopup"), [
     { identitykey: POP3identity },
   ]);
   check_nntp_address_types();
 
   close_compose_window(cwc);
--- a/mail/test/mozmill/composition/test-draft-identity.js
+++ b/mail/test/mozmill/composition/test-draft-identity.js
@@ -130,17 +130,17 @@ function checkCompIdentity(cwc, aIdentit
     "The From account is not correctly selected"
   );
   assert_equals(
     cwc.window.getCurrentIdentityKey(),
     aIdentityKey,
     "The From identity is not correctly selected"
   );
   assert_equals(
-    cwc.window.GetMsgIdentityElement().value,
+    cwc.window.document.getElementById("msgIdentity").value,
     aFrom,
     "The From value was initialized to an unexpected value"
   );
 }
 
 /**
  * Bug 394216
  * Test that starting a new message from a draft with various combinations
--- a/mail/test/mozmill/composition/test-focus.js
+++ b/mail/test/mozmill/composition/test-focus.js
@@ -22,17 +22,17 @@ var { assert_equals, mc } = ChromeUtils.
  * elements forward and backward.
  *
  * @param controller the compose window controller
  * @param attachmentsExpanded true if the attachment pane is expanded
  * @param ctrlTab true if we should use Ctrl+Tab to cycle, false if we should
  *                use F6
  */
 function check_element_cycling(controller, attachmentsExpanded, ctrlTab) {
-  let addressingElement = controller.e("addressingWidget");
+  let addressingElement = controller.e("toAddrInput");
   let subjectElement = controller.e("msgSubject");
   let attachmentElement = controller.e("attachmentBucket");
   let contentElement = controller.window.content;
   let identityElement = controller.e("msgIdentity");
 
   let key = ctrlTab ? "VK_TAB" : "VK_F6";
 
   // We start on the addressing widget and go from there.
--- a/mail/test/mozmill/composition/test-reply-addresses.js
+++ b/mail/test/mozmill/composition/test-reply-addresses.js
@@ -78,44 +78,32 @@ function checkReply(aReplyFunction, aExp
   checkToAddresses(rwc, aExpectedFields);
   close_compose_window(rwc);
 }
 
 /**
  * Helper to check that the reply window has the expected address fields.
  */
 function checkToAddresses(replyWinController, expectedFields) {
-  let addressingWidgetItems = replyWinController.window.document.querySelectorAll(
-    "#addressingWidget .addressingWidgetItem"
+  let rows = replyWinController.window.document.querySelectorAll(
+    "#recipientsContainer .address-row:not(.hidden)"
   );
 
   let obtainedFields = [];
-  for (let i = 0; i < addressingWidgetItems.length; i++) {
-    let addrTypePopup = addressingWidgetItems[i].querySelector("menupopup");
-    let addrTextbox = addressingWidgetItems[i].querySelector(
-      `input[is="autocomplete-input"]`
+  for (let row of rows) {
+    let addrTextbox = row.querySelector(
+      `input[is="autocomplete-input"][recipienttype]`
     );
 
-    let selectedIndex = addrTypePopup.parentNode.selectedIndex;
-    let typeMenuitems = addrTypePopup.children;
-    let addrType =
-      selectedIndex != -1
-        ? typeMenuitems[selectedIndex].value
-        : typeMenuitems[0].value;
+    let addresses = [];
+    for (let pill of row.querySelectorAll("mail-address-pill")) {
+      addresses.push(pill.fullAddress);
+    }
 
-    if (!addrTextbox.value) {
-      continue;
-    }
-    let addresses = obtainedFields[addrType];
-    if (addresses) {
-      addresses.push(addrTextbox.value);
-    } else {
-      addresses = [addrTextbox.value];
-    }
-    obtainedFields[addrType] = addresses;
+    obtainedFields[addrTextbox.getAttribute("recipienttype")] = addresses;
   }
 
   // Check what we expect is there.
   for (let type in expectedFields) {
     let expected = expectedFields[type];
     let obtained = obtainedFields[type];
 
     for (let i = 0; i < expected.length; i++) {
--- a/mail/test/mozmill/composition/test-send-button.js
+++ b/mail/test/mozmill/composition/test-send-button.js
@@ -28,16 +28,17 @@ var {
   click_tree_row,
   FAKE_SERVER_HOSTNAME,
   get_special_folder,
 } = ChromeUtils.import(
   "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
 );
 var {
   clear_recipient,
+  get_first_pill,
   close_compose_window,
   open_compose_new_mail,
   setup_msg_contents,
   toggle_recipient_type,
 } = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
 var { wait_for_frame_load } = ChromeUtils.import(
   "resource://testing-common/mozmill/WindowHelpers.jsm"
 );
@@ -98,55 +99,53 @@ function check_send_commands_state(aCwc,
  * by the user.
  */
 function test_send_enabled_manual_address() {
   let cwc = open_compose_new_mail(); // compose controller
   // On an empty window, Send must be disabled.
   check_send_commands_state(cwc, false);
 
   // On valid "To:" addressee input, Send must be enabled.
-  toggle_recipient_type(cwc, "addr_to");
   setup_msg_contents(cwc, " recipient@fake.invalid ", "", "");
   check_send_commands_state(cwc, true);
 
   // When the addressee is not in To, Cc, Bcc or Newsgroup, disable Send again.
-  toggle_recipient_type(cwc, "addr_reply");
+  clear_recipient(cwc);
+  cwc.click(cwc.eid("addr_reply"));
+  setup_msg_contents(cwc, " recipient@fake.invalid ", "", "", "replyAddrInput");
   check_send_commands_state(cwc, false);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // Bug 1296535
   // Try some other invalid and valid recipient strings:
   // - random string that is no email.
   setup_msg_contents(cwc, " recipient@", "", "");
   check_send_commands_state(cwc, false);
 
-  toggle_recipient_type(cwc, "addr_cc");
+  cwc.click(cwc.eid("addr_cc"));
   check_send_commands_state(cwc, false);
 
-  // We change focus from recipient type box to recipient input box.
-  // One click on the recipient input box selects the whole typed string (bug 1527547).
-  cwc.click(cwc.eid("addressCol2#1"), 200, 5);
-  // On macOS the focus is automatically returned to the input box after operating
-  // the type box and no focus event is fired. So the recipient is not selected
-  // and the caret is after the last typed character (where we left off).
-  if (AppConstants.platform == "macosx") {
-    // Click subject and then back to recipient input to see that it gets selected.
-    cwc.click(cwc.eid("msgSubject"));
-    cwc.click(cwc.eid("addressCol2#1"));
-  }
-  assert_equals(cwc.e("addressCol2#1").selectionStart, 0);
-  // End of selection counts the length of the " recipient@" string from above.
-  assert_equals(cwc.e("addressCol2#1").selectionEnd, 11);
-  // Another click after the recipient deselects it to allow typing.
-  cwc.click(cwc.eid("addressCol2#1"), 200, 5);
-  assert_equals(cwc.e("addressCol2#1").selectionStart, 11);
-  // This types additional characters into the recipient.
-  setup_msg_contents(cwc, "domain.invalid", "", "");
+  // Select the newly generated pill.
+  cwc.click(get_first_pill(cwc));
+  // Delete the selected pill.
+  cwc.keypress(null, "VK_DELETE", {});
+  // Confirm the address row is now empty.
+  assert_true(!get_first_pill(cwc).length);
+  // Confirm the send button is disabled.
+  check_send_commands_state(cwc, false);
+
+  // Add multiple recipients.
+  setup_msg_contents(
+    cwc,
+    "recipient@domain.invalid, info@somedomain.extension, name@incomplete",
+    "",
+    ""
+  );
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // - a mailinglist in addressbook
   // Button is enabled without checking whether it contains valid addresses.
   let defaultAB = MailServices.ab.getDirectory("jsaddrbook://abook.sqlite");
@@ -161,18 +160,19 @@ function test_send_enabled_manual_addres
 
   setup_msg_contents(cwc, "emptyList <list> ", "", "");
   check_send_commands_state(cwc, true);
 
   clear_recipient(cwc);
   check_send_commands_state(cwc, false);
 
   // - some string as a newsgroup
-  toggle_recipient_type(cwc, "addr_newsgroups");
-  setup_msg_contents(cwc, "newsgroup ", "", "");
+  cwc.e("addr_newsgroups").removeAttribute("collapsed");
+  cwc.click(cwc.eid("addr_newsgroups"));
+  setup_msg_contents(cwc, "newsgroup ", "", "", "newsgroupsAddrInput");
   check_send_commands_state(cwc, true);
 
   close_compose_window(cwc);
 }
 
 /**
  * Bug 431217
  * Test that the Send buttons are properly enabled if an addressee is prefilled
@@ -183,20 +183,19 @@ function test_send_enabled_prefilled_add
   let identity = account.defaultIdentity;
   identity.doCc = true;
   identity.doCcList = "Auto@recipient.invalid";
 
   // In that case the recipient is input, enabled Send.
   let cwc = open_compose_new_mail(); // compose controller
   check_send_commands_state(cwc, true);
 
-  // Press backspace to remove the recipient. No other valid one is there,
-  // Send should become disabled.
-  cwc.e("addressCol2#1").select();
-  cwc.keypress(null, "VK_BACK_SPACE", {});
+  // Clear the CC list.
+  clear_recipient(cwc);
+  // No other pill is there. Send should become disabled.
   check_send_commands_state(cwc, false);
 
   close_compose_window(cwc);
   identity.doCcList = "";
   identity.doCc = false;
 }
 
 /**
--- a/mail/test/mozmill/folder-display/test-message-commands-on-msgstore.js
+++ b/mail/test/mozmill/folder-display/test-message-commands-on-msgstore.js
@@ -6,19 +6,21 @@
  * This tests some commands on messages via the UI. But we specifically check,
  * whether the commands have an effect in the message store on disk, i.e. the
  * markings on the messages are stored in the msgStore, not only in the database.
  * For now, it checks for bug 840418.
  */
 
 "use strict";
 
-var { open_compose_with_forward, open_compose_with_reply } = ChromeUtils.import(
-  "resource://testing-common/mozmill/ComposeHelpers.jsm"
-);
+var {
+  open_compose_with_forward,
+  open_compose_with_reply,
+  setup_msg_contents,
+} = ChromeUtils.import("resource://testing-common/mozmill/ComposeHelpers.jsm");
 var {
   assert_equals,
   assert_false,
   assert_not_equals,
   assert_true,
   be_in_folder,
   create_folder,
   empty_folder,
@@ -222,17 +224,17 @@ function reply_forward_message(aMsgRow, 
   let cwc;
   if (aReply) {
     // Reply to the message.
     cwc = open_compose_with_reply();
   } else {
     // Forward the message.
     cwc = open_compose_with_forward();
     // Type in some recipient.
-    cwc.type(cwc.eid("addressCol2#1"), "somewhere@host.invalid");
+    setup_msg_contents(cwc, "somewhere@host.invalid", "", "");
   }
 
   // Send it later.
   plan_for_window_close(cwc);
   // Ctrl+Shift+Return = Send Later
   cwc.keypress(cwc.eid("content-frame"), "VK_RETURN", {
     shiftKey: true,
     accelKey: true,
--- a/mail/test/mozmill/message-header/test-reply-identity.js
+++ b/mail/test/mozmill/message-header/test-reply-identity.js
@@ -163,17 +163,17 @@ function test_reply_matching_only_delive
   be_in_folder(testFolder);
 
   let msg = select_click_row(1);
   assert_selected_and_displayed(mc, msg);
 
   let replyWin = open_compose_with_reply();
   // Should have selected the second id, which is listed in Delivered-To:.
   checkReply(replyWin, identity2Email);
-  close_compose_window(replyWin);
+  close_compose_window(replyWin, false /* no prompt*/);
 }
 
 function test_reply_matching_subaddress() {
   be_in_folder(testFolder);
 
   let msg = select_click_row(2);
   assert_selected_and_displayed(mc, msg);
 
--- a/mail/test/mozmill/shared-modules/ComposeHelpers.jsm
+++ b/mail/test/mozmill/shared-modules/ComposeHelpers.jsm
@@ -13,16 +13,17 @@ this.EXPORTED_SYMBOLS = [
   "open_compose_with_forward_as_attachments",
   "open_compose_with_edit_as_new",
   "open_compose_with_element_click",
   "open_compose_from_draft",
   "close_compose_window",
   "wait_for_compose_window",
   "setup_msg_contents",
   "clear_recipient",
+  "get_first_pill",
   "toggle_recipient_type",
   "create_msg_attachment",
   "add_attachments",
   "add_cloud_attachments",
   "delete_attachment",
   "get_compose_body",
   "type_in_composer",
   "assert_previous_text",
@@ -312,37 +313,59 @@ function wait_for_compose_window(aContro
 
 /**
  * Fills in the given message recipient/subject/body into the right widgets.
  *
  * @param aCwc   Compose window controller.
  * @param aAddr  Recipient to fill in.
  * @param aSubj  Subject to fill in.
  * @param aBody  Message body to fill in.
+ * @param inputID  The input field to fill in.
  */
-function setup_msg_contents(aCwc, aAddr, aSubj, aBody) {
-  aCwc.type(aCwc.eid("addressCol2#1"), aAddr);
+function setup_msg_contents(
+  aCwc,
+  aAddr,
+  aSubj,
+  aBody,
+  inputID = "toAddrInput"
+) {
+  aCwc.type(aCwc.eid(inputID), aAddr);
+  aCwc.keypress(aCwc.eid(inputID), "VK_RETURN", {});
   aCwc.type(aCwc.eid("msgSubject"), aSubj);
   aCwc.type(aCwc.eid("content-frame"), aBody);
+
+  // Wait 1 second for the pill to be created.
+  aCwc.sleep(1000);
 }
 
 /**
  * Remove the recipient by typing backspaces.
  *
  * @param aController    Compose window controller.
- * @param aRecipientRow  The compose widget row containing recipient to remove.
  */
-function clear_recipient(aController, aRecipientRow = 1) {
-  let recipientElem = aController.window.awGetInputElement(aRecipientRow);
-  while (recipientElem.value != "") {
-    aController.keypress(new elib.Elem(recipientElem), "VK_BACK_SPACE", {});
+function clear_recipient(aController) {
+  for (let pill of aController.window.document.querySelectorAll(
+    "mail-address-pill"
+  )) {
+    aController.keypress(new elib.Elem(pill), "VK_BACK_SPACE", {});
   }
 }
 
 /**
+ * Return the first available recipient pill.
+ *
+ * @param aController - Compose window controller.
+ */
+function get_first_pill(aController) {
+  return new elib.Elem(
+    aController.window.document.querySelector("mail-address-pill")
+  );
+}
+
+/**
  * Change recipient type in compose widget.
  *
  * @param aController    Compose window controller.
  * @param aType          The recipient type, e.g. "addr_to".
  * @param aRecipientRow  The compose widget row containing recipient to remove.
  */
 function toggle_recipient_type(aController, aType, aRecipientRow = 1) {
   let addrType = aController.window.awGetPopupElement(aRecipientRow);
--- a/mail/themes/linux/mail/compose/messengercompose.css
+++ b/mail/themes/linux/mail/compose/messengercompose.css
@@ -131,144 +131,32 @@ menulist:-moz-locale-dir(rtl) > .menulis
   padding-inline-end: 2px;
 }
 
 #attachmentBucketCloseButton {
   padding: 0 1px;
 }
 
 #subjectLabel {
+  margin-bottom: 0;
   margin-inline-end: 6px;
-  padding-bottom: 1px;
-}
-
-#subject-box {
-  margin-inline-start: -2px;
-}
-
-#msgSubject {
-  -moz-appearance: none;
-  margin-top: 0;
-  margin-inline-start: 6px;
-  margin-inline-end: 5px;
-  background-color: inherit;
-  border: 1px solid transparent;
-  border-bottom-color: var(--toolbarbutton-hover-bordercolor);
-  padding: 3px 2px;
-  padding-inline-start: 5px !important;
-  transition: border .2s, background-color .2s;
-}
-
-#msgSubject:hover,
-#msgSubject:focus {
-  background-color: -moz-field;
-  border-color: var(--toolbarbutton-hover-bordercolor);
 }
 
 /* ::::: autocomplete icons ::::: */
 
 .ac-site-icon {
   display: -moz-box;
   margin: 1px 5px;
 }
 
 .autocomplete-richlistitem[type="subscribed-news"] > .ac-site-icon {
   list-style-image: url("chrome://messenger/skin/icons/folder-pane.png");
   -moz-image-region: rect(208px 16px 224px 0);
 }
 
-/* ::::: addressing widget ::::: */
-
-#addressingWidget {
-  -moz-user-focus: none;
-  -moz-appearance: none;
-  width: 0;
-  margin-top: 0;
-  margin-bottom: 0;
-  padding-inline-end: 1px;
-  border: none;
-  background-color: transparent;
-}
-
-.addressingWidgetItem,
-.dummy-row {
-  background-color: transparent !important;
-  color: inherit !important;
-  margin-top: 1px;
-}
-
-.textbox-addressingWidget,
-.dummy-row-cell:last-child {
-  padding: 3px 2px !important;
-  padding-inline-start: 5px !important;
-  border: solid 1px transparent !important;
-  border-bottom-color: var(--toolbarbutton-hover-bordercolor) !important;
-  transition: border .2s, background-color .2s;
- }
-
-.addressingWidgetCell:nth-child(2),
-.dummy-row-cell:nth-child(2) {
-  border-bottom-color: transparent;
-}
-
-.textbox-addressingWidget:focus,
-.textbox-addressingWidget:hover {
-  background-color: -moz-field;
-  border-color: var(--toolbarbutton-hover-bordercolor) !important;
-}
-
-.deleteAddress {
-  margin-inline-start: 0;
-  margin-bottom: initial;
-}
-
-.addressingWidgetCell:hover > .aw-menulist:not([open="true"]) + .deleteAddress {
-  margin-inline-start: 5px;
-  width: 18px;
-}
-
-.aw-menulist {
-  font: inherit;
-  margin: 0;
-  margin-inline-start: 1px;
-  margin-inline-end: 7px;
-  padding-inline-end: 0;
-  outline: 1px solid transparent;
-  outline-offset: -3px;
-}
-
-.aw-menulist > .menulist-label-box {
-  margin: -3px 1px;
-  padding-inline-start: 12px;
-  padding-inline-end: 0;
-  background-position: left;
-}
-
-.aw-menulist:-moz-locale-dir(rtl) > .menulist-label-box {
-  background-position: right;
-}
-
-.aw-menulist:focus {
-  outline-color: -moz-DialogText;
-  outline-style: dotted;
-}
-
-.aw-menulist > .menulist-label-box > .menulist-label {
-  margin: 0 3px !important;
-  text-align: end;
-}
-
-.aw-menulist > .menulist-label-box > .menulist-icon {
-  margin-inline-start: 2px;
-}
-
-.aw-menulist > .menulist-dropmarker {
-  height: 11px;
-}
-
 #composeContentBox {
   background-color: -moz-dialog;
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2) inset;
 }
 
 #composeContentBox:-moz-window-inactive {
   box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1) inset;
 }
@@ -291,59 +179,46 @@ menulist:-moz-locale-dir(rtl) > .menulis
   text-shadow: none;
   padding-top: 3px;
 }
 
 #addresses-box {
   padding-top: 4px;
 }
 
+.address-label-container {
+  padding-top: 6px;
+}
+
+.address-pill {
+  padding-block: 2px;
+}
+
+.address-pill label {
+  margin-block: 0;
+}
+
 #identityLabel-box {
   margin-top: 1px;
 }
 
-#identityLabel {
-  margin-bottom: 1px;
-  margin-inline-end: 8px
-}
-
 #msgIdentity {
-  -moz-appearance: none;
-  -moz-box-align: center;
-  margin-right: 5px;
-  margin-bottom: 0;
-  padding-top: 2px;
-  padding-bottom: 2px;
-  padding-inline-start: 2px;
-  padding-inline-end: 20px;
-  font: inherit;
-  border: 1px solid transparent;
-  border-bottom-color: var(--toolbarbutton-hover-bordercolor);
-  background-color: transparent;
+  margin-block: 2px 0;
+  padding-block: 4px;
+  padding-inline: 2px 20px;
   background-repeat: no-repeat;
-  background-position: calc( 100% - 5px);
+  background-position: calc(100% - 5px);
   background-size: 9px 7px;
   background-image: url("chrome://messenger/skin/icons/toolbarbutton-arrow.svg");
-  -moz-context-properties: fill;
-  fill: currentColor;
-  transition: border .2s, background-color .2s;
 }
 
 #msgIdentity:-moz-locale-dir(rtl) {
   background-position: 5px;
 }
 
-#msgIdentity:hover,
-#msgIdentity:focus,
-#msgIdentity:focus-within,
-#msgIdentity[focused="true"] {
-  background-color: -moz-field;
-  border-color: var(--toolbarbutton-hover-bordercolor);
-}
-
 #msgIdentity > .menulist-label-box {
   background: none;
   padding-inline-end: initial;
 }
 
 #msgIdentity > html|input.menulist-input {
   -moz-appearance: none;
   padding-top: 1px;
@@ -360,16 +235,17 @@ menulist:-moz-locale-dir(rtl) > .menulis
 
 /* ::::: format toolbar ::::: */
 
 #FormatToolbar {
   -moz-appearance: none;
   color: WindowText;
   margin-left: 3px;
   margin-right: 3px;
+  padding-block: 4px;
 }
 
 .formatting-button {
   margin: 1px;
 }
 
 toolbarbutton.formatting-button {
   -moz-appearance: none;
--- a/mail/themes/osx/mail/compose/messengercompose.css
+++ b/mail/themes/osx/mail/compose/messengercompose.css
@@ -91,48 +91,35 @@ toolbar[nowindowdrag="true"] {
   text-shadow: none;
   border-bottom: 0 solid;
 }
 
 #addresses-box {
   padding-top: 5px;
 }
 
+#identityLabel-box {
+  margin-top: 3px;
+}
+
 #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;
-  border-radius: 2px;
-  -moz-appearance: none;
-  background-color: transparent;
+  margin-block: 2px 0;
+  margin-inline-start: 4px;
+  padding-block: 3px;
+  padding-inline-end: 20px;
   background-repeat: no-repeat;
-  background-position: calc(100% - 3px) center;
+  background-position: calc(100% - 5px);
   background-size: 9px 7px;
   background-image: url("chrome://messenger/skin/icons/toolbarbutton-arrow.svg");
-  -moz-context-properties: fill;
-  fill: currentColor;
-  transition: border .2s, background-color .2s;
 }
 
 #msgIdentity:-moz-locale-dir(rtl) {
   background-position: 3px center;
 }
 
-#msgIdentity:hover,
-#msgIdentity:focus,
-#msgIdentity:focus-within,
-#msgIdentity[focused="true"] {
-  background-color: white;
-  border-color: #C6C6C6;
-}
-
 #msgIdentity > html|input.menulist-input {
   color: inherit;
   padding-inline-start: 3px;
 }
 
 #msgIdentity[is="menulist-editable"][editable="true"] > menupopup {
   -moz-appearance: menupopup;
   margin-inline-start: 0;
@@ -141,20 +128,16 @@ toolbar[nowindowdrag="true"] {
 #msgIdentity[is="menulist-editable"][editable="true"] > menupopup > menuitem {
   -moz-appearance: menuitem;
 }
 
 #msgIdentityPopup {
   margin-inline-start: initial;
 }
 
-#addresses-box {
-  margin: 4px 6px;
-}
-
 #attachments-header-box {
   min-height: 28px;
 }
 
 #attachmentBucketCount {
   padding-top: 2px;
 }
 
@@ -180,41 +163,43 @@ toolbar[nowindowdrag="true"] {
   margin-bottom: -1px;
   margin-inline-start: -3px;
   margin-inline-end: 3px;
   padding-inline-end: 5px;
   border-inline-end: 1px solid #9b9b9b;
 }
 
 #subjectLabel {
-  margin-top: 0;
-  margin-bottom: 4px;
+  margin-top: 3px;
   margin-inline-end: 6px;
 }
 
-#msgSubject {
-  margin-top: 0;
-  margin-inline-start: 0;
-  margin-inline-end: 1px;
-  min-height: 20px;
-  background-color: inherit;
-  -moz-appearance: none;
-  border: 1px solid transparent;
-  border-bottom-color: #c6c6c6;
-  border-radius: 2px;
-  padding: 1px 2px;
-  padding-inline-start: 5px;
-  transition: border .2s, background-color .2s;
+#msgIdentity:focus,
+#msgIdentity:focus-within,
+#msgSubject:focus,
+.address-container[focused="true"] {
+  border-color: -moz-mac-focusring;
+  box-shadow: var(--focus-ring-box-shadow);
+}
+
+.address-label-container {
+  padding-top: 7px;
 }
 
-#msgSubject:hover,
-#msgSubject:focus {
-  background-color: white;
-  background-image: none;
-  border-color: #c6c6c6;
+.address-pill {
+  padding-block: 2px;
+}
+
+.address-pill label {
+  margin-block: 0;
+}
+
+.address-extra-recipients label:focus:not(:hover),
+.aw-firstColBox label:focus:not(:hover) image {
+  outline: 2px dashed -moz-mac-focusring;
 }
 
 /* ::::: autocomplete icons ::::: */
 
 .ac-site-icon {
   display: -moz-box;
   margin: 2px 5px;
 }
@@ -235,111 +220,16 @@ toolbar[nowindowdrag="true"] {
   }
 }
 
 .autocomplete-richlistitem[type="subscribed-news"] > .ac-site-icon {
   list-style-image: url("chrome://messenger/skin/icons/folder-pane.png");
   -moz-image-region: rect(0 160px 16px 144px);
 }
 
-/* ::::: addressing widget ::::: */
-
-#addressingWidget {
-  -moz-user-focus: none;
-  -moz-appearance: none;
-  width: 0;
-  margin: 0;
-  padding-inline-end: 1px;
-  border: none;
-  background-color: transparent;
-}
-
-.addressingWidgetItem,
-.dummy-row {
-  background-color: inherit !important;
-  color: inherit !important;
-}
-
-.textbox-addressingWidget,
-.dummy-row-cell:last-child {
-  border: 1px solid transparent !important;
-  border-bottom-color: #C6C6C6 !important;
-  border-top-left-radius: 2px;
-  border-bottom-right-radius: 2px;
-  min-height: 20px;
-  margin-top: 1px !important;
-  margin-bottom: 1px !important;
-  padding: 1px 3px !important;
-  padding-inline-start: 6px !important;
-  transition: border .2s, background-color .2s;
-}
-
-.textbox-addressingWidget:focus,
-.textbox-addressingWidget:hover {
-  background-color: white;
-  background-image: none;
-  border-color: #C6C6C6 !important;
-}
-
-.deleteAddress {
-  margin-top: 1px;
-  margin-inline-end: 0;
-}
-
-.addressingWidgetCell:hover > .aw-menulist:not([open="true"]) + .deleteAddress {
-  margin-inline-end: 3px;
-  width: 18px;
-}
-
-.aw-menulist {
-  -moz-appearance: none;
-  -moz-box-align: center;
-  -moz-box-pack: center;
-  margin: 1px;
-  margin-inline-end: 4px;
-  border: 1px solid transparent;
-  border-radius: 2px;
-  background: var(--toolbarbutton-active-background);
-  border-color: var(--toolbarbutton-active-bordercolor);
-  list-style-image: url("chrome://messenger/skin/icons/toolbarbutton-arrow.svg");
-  -moz-context-properties: fill;
-  fill: currentColor;
-  transition: background-color .25s ease-in;
-}
-
-.aw-menulist:-moz-locale-dir(rtl) {
-  background-position: calc(100% - 3px) center;
-}
-
-.aw-menulist[open="true"] {
-  background: var(--toolbarbutton-active-background);
-  border-color: var(--toolbarbutton-active-bordercolor);
-  box-shadow: var(--toolbarbutton-active-boxshadow);
-}
-
-.aw-menulist:focus {
-  border-color: -moz-mac-focusring;
-  box-shadow: 0 0 1.5px 1px -moz-mac-focusring;
-}
-
-.aw-menulist > .menulist-label-box > .menulist-icon {
-  width: 9px;
-  height: 7px;
-  margin-inline-start: 2px;
-}
-
-.aw-menulist  > .menulist-label-box > .menulist-label {
-  margin: 1px 1px 0 !important;
-  text-align: end;
-}
-
-.aw-menulist > .menulist-dropmarker {
-  display: none;
-}
-
 .menulist-description {
   color: #999;
 }
 
 /* ::::: compose toolbar sizer ::::: */
 
 #compose-toolbar-sizer {
   position: relative;
@@ -352,17 +242,17 @@ toolbar[nowindowdrag="true"] {
   background-color: transparent;
 }
 
 /* ::::: format toolbar ::::: */
 
 #FormatToolbar {
   border-bottom: none;
   background-color: rgb(242, 242, 242) !important;
-  padding-top: 2px;
+  padding-block: 4px;
   margin-left: 3px;
   margin-right: 3px;
 }
 
 #FormatToolbar toolbarseparator {
   background-image: none;
 }
 
--- a/mail/themes/shared/mail/compacttheme.css
+++ b/mail/themes/shared/mail/compacttheme.css
@@ -101,42 +101,49 @@
 
   --toolbarbutton-active-background: var(--lwt-toolbarbutton-active-background);
   --toolbarbutton-active-bordercolor: var(--lwt-toolbarbutton-active-background);
   --toolbarbutton-active-boxshadow: 0 0 0 1px var(--lwt-toolbarbutton-active-background) inset;
 
   scrollbar-color: rgba(249,249,250,.4) rgba(20,20,25,.3);
 }
 
-#MsgHeadersToolbar,
-#addressingWidget,
-.aw-menulist {
+#MsgHeadersToolbar {
   color: inherit;
 }
 
 #msgIdentity,
-#msgSubject,
-.textbox-addressingWidget,
-.dummy-row-cell:last-child {
+#msgSubject {
   border-bottom-color: var(--composer-header-border-color) !important;
   color: var(--lwt-text-color);
 }
 
 #msgIdentity:hover,
 #msgIdentity:focus,
 #msgIdentity:focus-within,
 #msgSubject:hover,
-#msgSubject:focus,
-.textbox-addressingWidget:hover,
-.textbox-addressingWidget:focus {
+#msgSubject:focus {
   background-color: var(--lwt-toolbar-field-background-color);
   color: var(--lwt-toolbar-field-color);
   border-color: var(--lwt-toolbar-field-border-color) !important;
 }
 
+#msgIdentity,
+#msgSubject,
+.address-container {
+  background-color: var(--lwt-toolbar-field-background-color);
+  color: var(--lwt-toolbar-field-color);
+}
+
+#msgIdentity:focus,
+#msgSubject:focus,
+.address-container[focused="true"] {
+  border-color: Highlight !important;
+}
+
 :root[lwt-popup-brighttext] panel[type="autocomplete-richlistbox"]:-moz-lwtheme {
   margin-top: -1px;
   padding: 2px 0;
   background: var(--autocomplete-popup-background);
   color: var(--autocomplete-popup-color);
   border-color: var(--autocomplete-popup-border-color);
   scrollbar-color: rgba(249,249,250,.4) rgba(20,20,25,.3);
 }
--- a/mail/themes/shared/mail/input-fields.css
+++ b/mail/themes/shared/mail/input-fields.css
@@ -36,16 +36,20 @@ html|input.input-inline-color {
   align-items: center;
   flex-wrap: nowrap;
 }
 
 .input-container.items-stretch {
   align-items: stretch;
 }
 
+.input-container.wrap-container {
+  flex-wrap: wrap;
+}
+
 .input-container html|input:not([type="number"]):not([type="color"]),
 .input-container .label-inline,
 .input-container .spacer-inline {
   flex: 1;
 }
 
 html|input[type="number"].input-number-inline {
   flex: 1 !important;
--- a/mail/themes/shared/mail/messengercompose.css
+++ b/mail/themes/shared/mail/messengercompose.css
@@ -29,65 +29,60 @@
 
   --toolbarbutton-active-background: var(--lwt-toolbarbutton-active-background);
   --toolbarbutton-active-bordercolor: var(--lwt-toolbarbutton-active-background);
   --toolbarbutton-active-boxshadow: 0 0 0 1px var(--lwt-toolbarbutton-active-background) inset;
 
   scrollbar-color: rgba(249,249,250,.4) rgba(20,20,25,.3);
 }
 
-:root[lwt-default-theme-in-dark-mode] #MsgHeadersToolbar,
-:root[lwt-default-theme-in-dark-mode] #addressingWidget,
-:root[lwt-default-theme-in-dark-mode] .aw-menulist {
+:root[lwt-default-theme-in-dark-mode] #MsgHeadersToolbar {
   color: inherit;
 }
 
 :root[lwt-default-theme-in-dark-mode] #msgIdentity,
 :root[lwt-default-theme-in-dark-mode] #msgSubject,
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget,
-:root[lwt-default-theme-in-dark-mode] .dummy-row-cell:last-child {
+:root[lwt-default-theme-in-dark-mode] .address-container {
   border-bottom-color: var(--composer-header-border-color) !important;
   color: var(--lwt-text-color);
 }
 
 :root[lwt-default-theme-in-dark-mode] #msgIdentity:hover,
 :root[lwt-default-theme-in-dark-mode] #msgIdentity:focus,
 :root[lwt-default-theme-in-dark-mode] #msgIdentity:focus-within,
 :root[lwt-default-theme-in-dark-mode] #msgSubject:hover,
-:root[lwt-default-theme-in-dark-mode] #msgSubject:focus,
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget:hover,
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget:focus {
+:root[lwt-default-theme-in-dark-mode] #msgSubject:focus {
   background-color: var(--lwt-toolbar-field-background-color);
   color: var(--lwt-toolbar-field-color);
   border-color: var(--lwt-toolbar-field-border-color) !important;
 }
 
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget >
+:root[lwt-default-theme-in-dark-mode] .address-container >
   .autocomplete-result-popupset {
   -moz-appearance: none;
   margin-top: -1px;
   background: var(--autocomplete-popup-background);
   color: var(--autocomplete-popup-color);
 }
 
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget
+:root[lwt-default-theme-in-dark-mode] .address-container
   panel[type="autocomplete-richlistbox"] {
   padding: 2px 0;
   color: inherit;
   background-color: inherit;
   border-color: var(--autocomplete-popup-border-color);
 }
 
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget
+:root[lwt-default-theme-in-dark-mode] .address-container
   .autocomplete-richlistbox {
   color: inherit;
   background-color: inherit;
 }
 
-:root[lwt-default-theme-in-dark-mode] .textbox-addressingWidget
+:root[lwt-default-theme-in-dark-mode] .address-container
   .autocomplete-richlistitem[selected] {
   background: #0a84ff;
   color: #fff;
 }
 
 /* Rules to help integrate WebExtension buttons */
 
 .webextension-browser-action > .toolbarbutton-badge-stack > .toolbarbutton-icon {
@@ -335,48 +330,20 @@
 
 .menulist-description {
   font-style: italic;
   color: GrayText;
   margin-inline-start: 1ex !important;
 }
 
 .aw-firstColBox,
-#firstcol-addressingWidget,
-.dummy-row-cell:first-child {
+#firstcol-addressingWidget {
   width: 21px;
 }
 
-.deleteAddress {
-  cursor: default;
-  margin-inline-end: 3px;
-  margin-bottom: 1px;
-  width: 18px;
-  height: 18px;
-  transition-property: width, margin-inline-end;
-  transition-duration: 50ms, 50ms;
-  transition-timing-function: ease-in-out, ease-in-out;
-}
-
-.addressingWidgetItem:not(:hover):not(:focus-within) .aw-menulist {
-  border-color: transparent;
-  background: transparent;
-}
-
-.addressingWidgetItem:not(:hover):not(:focus-within) .deleteAddress,
-.addressingWidgetItem:not(:hover):not(:focus-within) .aw-menulist > .menulist-label-box {
-  fill: transparent;
-}
-
-.aw-menulist:focus,
-.aw-menulist[focused="true"] {
-  background: var(--toolbarbutton-active-background);
-  border-color: var(--toolbarbutton-active-bordercolor);
-}
-
 /* :::: Format toolbar :::: */
 
 /*
  * Removed from global.css in bug 1484949. It's needed so the formatting
  * toolbar is not disabled while a dropdown (paragraph format or font) is active.
  */
 .toolbar-focustarget {
   -moz-user-focus: ignore !important;
@@ -478,8 +445,279 @@
   margin-inline-start: 0;
   text-decoration: underline;
   cursor: pointer;
 }
 
 #attachmentNotificationBox > hbox > .messageImage {
   background-image: url("chrome://messenger/skin/icons/attach.svg");
 }
+
+#identityLabel,
+.address-label-container label {
+  margin-inline-end: 6px
+}
+
+#msgIdentity {
+  -moz-appearance: none;
+  -moz-box-align: center;
+  font: inherit;
+  margin-inline-end: 8px;
+  border: 1px solid var(--toolbarbutton-hover-bordercolor);
+  border-radius: 2px;
+  background-color: -moz-field;
+  -moz-context-properties: fill;
+  fill: currentColor;
+  transition: border .2s, box-shadow .2s;
+}
+
+#msgSubject {
+  -moz-appearance: none;
+  margin-top: 0;
+  margin-inline: 4px 8px;
+  background-color: -moz-field;
+  border: 1px solid var(--toolbarbutton-hover-bordercolor);
+  border-radius: 2px;
+  padding-block: 5px;
+  padding-inline: 4px 2px;
+  transition: border .2s, box-shadow .2s;
+}
+
+.recipients-container {
+  display: block;
+  /* Necessary for unwanted overflow while resizing the message header */
+  height: 0;
+}
+
+.recipients-container.overflow {
+  overflow-y: auto;
+}
+
+.address-row {
+  display: flex;
+  flex: 1;
+  margin-block: 5px;
+  margin-inline-end: 8px;
+  align-items: self-start;
+}
+
+.address-row > .aw-firstColBox {
+  transition: opacity .2s ease;
+  opacity: 0;
+}
+
+.address-row:hover > .aw-firstColBox,
+.address-row:focus > .aw-firstColBox,
+.address-row:focus-within > .aw-firstColBox {
+  opacity: 1;
+}
+
+.address-row.hidden {
+  display: none;
+}
+
+.address-container {
+  flex: 1;
+  margin-inline-start: 4px;
+  margin-inline-end: 0;
+  padding: 1px 4px;
+  border: solid 1px var(--toolbarbutton-hover-bordercolor);
+  border-radius: 2px;
+  background-color: -moz-field;
+  transition: border .2s, box-shadow .2s;
+  cursor: text;
+}
+
+#msgSubject,
+#msgIdentity,
+.address-container {
+  min-height: 30px;
+}
+
+.address-input {
+  color: inherit;
+}
+
+.address-pill {
+  display: flex;
+  align-items: center;
+  border-radius: 9999px;
+  margin-inline-end: 2px;
+  margin-block: 2px;
+  background-color: rgba(0,0,0,0.1);
+  transition: color .2s ease, background-color .2s ease, box-shadow .2s ease;
+  -moz-user-focus: normal;
+  cursor: default;
+  box-shadow: inset 0 0 0 2px transparent;
+}
+
+.address-pill label,
+.address-pill .delete-pill-icon {
+  -moz-user-focus: none;
+  cursor: default;
+}
+
+.delete-pill-icon {
+  width: 1.25em;
+  height: 1.25em;
+  margin-inline-end: 2px;
+  padding: 3px;
+  border-radius: 50%;
+  background-color: rgba(0,0,0,0.1);
+  list-style-image: url("chrome://messenger/skin/icons/stop.svg");
+  -moz-context-properties: fill;
+  fill: currentColor;
+  transition: color .2s ease, background-color .2s ease;
+}
+
+.address-pill:hover:not(.editing),
+.address-pill:focus:not(.editing) {
+  box-shadow: inset 0 0 0 2px rgba(0,0,0,0.3);
+}
+
+.address-pill.editing {
+  flex: 1;
+  background-color: transparent;
+  padding-inline: 6px;
+  box-shadow: inset 0 0 0 2px Highlight;
+  min-height: calc(1.25em + 4px); /* needed to not shrink in edit mode */
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill:not(.editing) {
+  background-color: rgba(0,0,0,0.3);
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill:hover:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill:focus:not(.editing) {
+  background-color: rgba(0,0,0,0.35);
+}
+
+.address-pill.error {
+  color: #a4000f;
+  background-color: rgba(255,0,0,0.1);
+}
+
+.address-pill.error:hover:not(.editing),
+.address-pill.error:focus:not(.editing) {
+  background-color: rgba(255,0,0,0.15);
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill.error:not(.editing) {
+  color: #e10216;
+  background-color: #3e0006;
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill.error:hover:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill.error:focus:not(.editing)  {
+  background-color: #310005;
+}
+
+.address-pill.warning {
+  color: #a44900;
+  background-color: rgba(255,130,0,0.1);
+}
+
+.address-pill.warning:hover:not(.editing),
+.address-pill.warning:focus:not(.editing) {
+  background-color: rgba(255,130,0,0.15);
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill.warning:not(.editing) {
+  color: #fe7100;
+  background-color: #341700;
+}
+
+#MsgHeadersToolbar[brighttext] .address-pill.warning:hover:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill.warning:focus:not(.editing)  {
+  background-color: #301500;
+}
+
+.address-pill[selected]:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill[selected]:not(.editing),
+.address-pill.warning[selected]:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill.warning[selected]:not(.editing),
+.address-pill.error[selected]:not(.editing),
+#MsgHeadersToolbar[brighttext] .address-pill.error[selected]:not(.editing) {
+  color: HighlightText;
+  background-color: Highlight;
+}
+
+#MsgHeadersToolbar[brighttext] .delete-pill-icon {
+  background-color: rgba(255, 255, 255, 0.1);
+}
+
+.delete-pill-icon:hover,
+#MsgHeadersToolbar[brighttext] .delete-pill-icon:hover {
+  background-color: HighlightText;
+  color: Highlight;
+}
+
+.address-extra-recipients {
+  margin-inline-end: 8px;
+}
+
+.address-extra-recipients label {
+  margin-top: 0;
+  margin-bottom: 6px;
+  transition: color 0.2s;
+  margin-inline-end: 12px;
+}
+
+.new-icon {
+  -moz-appearance: none;
+  -moz-context-properties: fill, fill-opacity;
+  list-style-image: url(chrome://messenger/skin/icons/new.svg);
+  color: inherit !important;
+  fill: currentColor;
+  fill-opacity: 1;
+  width: 15px;
+  height: 15px;
+  background-color: rgba(0,0,0,0.1);
+  border-radius: 50%;
+  padding: 3px;
+  vertical-align: sub;
+  display: inline-block;
+  margin-inline-end: 4px;
+}
+
+#MsgHeadersToolbar[brighttext] .new-icon {
+  background-color: rgba(255, 255, 255, 0.1);
+}
+
+.address-extra-recipients label:hover,
+.aw-firstColBox label:hover {
+  cursor: pointer;
+  color: Highlight;
+}
+
+.aw-firstColBox label:hover .close-icon {
+  fill-opacity: 0.1;
+}
+
+.aw-firstColBox label {
+  margin-top: 4px;
+  margin-bottom: 0;
+}
+
+.address-extra-recipients label:focus:not(:hover),
+.aw-firstColBox label:focus:not(:hover) image {
+  outline: 2px dashed Highlight;
+}
+
+#msgIdentity:hover,
+#msgSubject:hover,
+.address-container:hover  {
+  border-color: rgba(0,0,0,0.5);
+}
+
+#MsgHeadersToolbar[brighttext] #msgIdentity:hover,
+#MsgHeadersToolbar[brighttext] #msgSubject:hover,
+#MsgHeadersToolbar[brighttext] .address-container:hover  {
+  border-color: rgba(255, 255, 255, 0.3);
+}
+
+#msgIdentity:focus,
+#msgIdentity:focus-within,
+#msgIdentity[focused="true"],
+#msgSubject:focus,
+.address-container[focused="true"] {
+  border-color: Highlight;
+}
--- a/mail/themes/windows/mail/compose/messengercompose.css
+++ b/mail/themes/windows/mail/compose/messengercompose.css
@@ -91,161 +91,95 @@
 #msgheaderstoolbar-box {
   padding-bottom: 2px;
 }
 
 #addresses-box {
   padding-top: 4px;
 }
 
-#msgIdentity {
-  -moz-appearance: none;
-  background-color: transparent;
-  min-height: 25px;
-  line-height: 1;
-  border: 1px solid transparent;
-  box-shadow: none;
-  margin-inline-end: 5px;
-  padding-inline-start: 3px !important;
-  background-image: none;
-  transition: border .2s, background-color .2s;
+#subjectLabel {
+  margin-bottom: 0;
 }
 
 @media (-moz-windows-default-theme: 0) {
-  #msgIdentity {
-    border-bottom-color: ThreeDShadow;
+  #msgSubject:not(:-moz-lwtheme),
+  #msgIdentity:not(:-moz-lwtheme),
+  .address-container:not(:-moz-lwtheme) {
+    --toolbarbutton-hover-bordercolor: ThreeDShadow;
   }
 
-  #msgIdentity:hover,
-  #msgIdentity:focus,
-  #msgIdentity:focus-within,
-  #msgIdentity[focused="true"] {
-    background-color: -moz-field;
-    border-color: ThreeDShadow;
+  #msgSubject:not(:-moz-lwtheme):hover,
+  #msgIdentity:not(:-moz-lwtheme):hover,
+  .address-container:not(:-moz-lwtheme):hover {
+    --toolbarbutton-hover-bordercolor: ThreeDDarkShadow;
   }
 }
 
+#identityLabel-box {
+  margin-top: 1px;
+}
+
+#msgIdentity {
+  line-height: 1;
+  box-shadow: none;
+  padding-inline-start: 3px !important;
+}
+
 #msgIdentity:-moz-focusring:not([open="true"]) > .menulist-label-box {
   outline: none;
 }
 
-#msgSubject {
-  -moz-appearance: none;
-  margin-top: 0;
-  margin-inline-end: 5px;
-  background-color: inherit;
-  border: 1px solid transparent;
-  padding: 2px;
-  padding-inline-start: 5px;
-  transition: border .2s, background-color .2s;
-}
-
-@media (-moz-windows-default-theme) {
-  #msgSubject {
-    border-bottom-color: #a9b7c9;
+@media (-moz-windows-default-theme: 0) {
+  #msgIdentity > .menulist-label-box {
+    background-color: transparent !important;
+    color: inherit !important;
   }
 
-  #msgSubject:hover,
-  #msgSubject:focus {
-    background-color: -moz-field;
-    border-color: #a9b7c9;
-  }
-}
-
-@media (-moz-windows-default-theme: 0) {
-  #msgSubject {
-    border-bottom-color: ThreeDShadow;
+  #msgIdentity > .menulist-dropmarker,
+  #msgIdentity > .menulist-dropmarker[disabled="true"] {
+    -moz-appearance: none;
+    list-style-image: url("chrome://messenger/skin/icons/toolbarbutton-arrow.svg");
+    -moz-context-properties: fill;
+    fill: currentColor;
+    margin-top: 0;
   }
 
-  #msgSubject:hover,
-  #msgSubject:focus {
-    background-color: -moz-field;
-    border-color: ThreeDShadow;
+  .menulist-dropmarker::part(icon) {
+    width: 9px;
+    height: 7px;
   }
 }
 
-/* ::::: addressing widget ::::: */
-
-#addressingWidget {
-  -moz-user-focus: none;
-  -moz-appearance: none;
-  width: 0;
-  border: none;
-  background-color: transparent;
-  margin-top: 1px;
-  margin-bottom: 0;
-  padding-inline-end: 1px;
+#msgIdentity > html|input.menulist-input {
+  background-color: inherit;
+  color: inherit;
+  margin: 2px 0;
 }
 
-#addressingWidget > .addressingWidgetItem,
-#addressingWidget > .dummy-row {
-  border-style: none;
-  background: none;
-  color: inherit;
-  box-shadow: none;
-}
-
-#addressingWidget > .addressingWidgetItem {
-  padding-top: 0;
-  padding-bottom: 2px;
-}
-
-.textbox-addressingWidget,
-.dummy-row-cell:last-child {
-  margin-bottom: 2px !important;
-  padding: 2px !important;
-  padding-inline-start: 5px !important;
-  border: 1px solid transparent !important;
-  transition: border .2s, background-color .2s;
+.address-label-container {
+  padding-top: 5px;
 }
 
-@media (-moz-windows-default-theme: 0) {
-  .textbox-addressingWidget,
-    .dummy-row-cell:last-child {
-    border-bottom-color: ThreeDShadow !important;
-    margin-inline-end: 1px !important;
-  }
-
-  .textbox-addressingWidget:hover,
-  .textbox-addressingWidget:focus {
-    background-color: -moz-field;
-    border-color: ThreeDShadow !important;
-  }
-}
-
-.addressingWidgetCell:nth-child(2),
-.dummy-row-cell:nth-child(2) {
-  border-bottom-color: transparent;
+#msgSubject {
+  margin-top: 0;
+  padding: 2px;
+  padding-inline-start: 5px;
 }
 
-.deleteAddress {
-  margin-inline-end: 0;
-  margin-bottom: 1px;
-}
+@media (-moz-windows-default-theme) {
+  #msgSubject,
+  #msgIdentity,
+  .address-container {
+    border-radius: 0;
+  }
 
-.aw-menulist {
-  -moz-appearance: none;
-  -moz-box-align: center;
-  -moz-box-pack: center;
-  color: ButtonText;
-  margin: 0 1px 2px;
-  margin-inline-end: 4px;
-  padding: 3px 0 !important;
-}
-
-.aw-menulist:-moz-focusring:not([open="true"]) > .menulist-label-box {
-  outline: none;
-}
-
-.aw-menulist > .menulist-label-box {
-  margin: 0;
-}
-
-.aw-menulist > .menulist-dropmarker {
-  display: none;
+  #msgIdentity[is="menulist-editable"][editable="true"] > .menulist-dropmarker {
+    margin-inline: 3px -3px;
+  }
 }
 
 /* ::::: format toolbar ::::: */
 
 #FormatToolbox {
   -moz-appearance: none;
 }
 
@@ -568,42 +502,16 @@ toolbar:not(:-moz-lwtheme) {
 #compose-toolbox > toolbar:not([type="menubar"]) {
   padding: 2px 1px;
 }
 
 #compose-toolbox > toolbar:not([type="menubar"]):-moz-lwtheme {
   text-shadow: none;
 }
 
-#msgIdentity > html|input.menulist-input {
-  background-color: inherit;
-  color: inherit;
-  margin: 2px 0;
-}
-
-@media (-moz-windows-default-theme) {
-  #msgIdentity {
-    border-radius: 0;
-    border-bottom-color: #a9b7c9;
-  }
-
-  #msgIdentity:hover,
-  #msgIdentity:focus,
-  #msgIdentity:focus-within,
-  #msgIdentity[focused="true"] {
-    background-color: -moz-field;
-    border-color: #a9b7c9;
-  }
-
-  #msgIdentity[is="menulist-editable"][editable="true"] > .menulist-dropmarker {
-    margin-inline-start: 3px;
-    margin-inline-end: -3px;
-  }
-}
-
 /* ::::: primary toolbar buttons ::::: */
 
 @media (-moz-windows-default-theme) {
   menulist {
     -moz-appearance: none;
     margin: 1px 4px;
     padding: 1px 5px;
   }
@@ -629,46 +537,29 @@ toolbar:not(:-moz-lwtheme) {
   min-height: 0;
   height: 5px;
   margin-top: -5px;
   border-top-width: 0;
   border-bottom-color: var(--splitter-color);
   background-color: transparent;
 }
 
-#subjectLabel {
-  margin-top: 0;
-}
-
 /* ::::: autocomplete icons ::::: */
 
 .ac-site-icon {
   display: -moz-box;
   margin: 1px 5px;
 }
 
 .autocomplete-richlistitem[type="subscribed-news"] > .ac-site-icon {
   list-style-image: url("chrome://messenger/skin/icons/folder.png");
   -moz-image-region: rect(0 160px 16px 144px);
 }
 
-/* ::::: addressing widget ::::: */
-
 @media (-moz-windows-default-theme) {
-  .textbox-addressingWidget,
-  .dummy-row-cell:last-child {
-    border-bottom-color: #a9b7c9 !important;
-  }
-
-  .textbox-addressingWidget:hover,
-  .textbox-addressingWidget:focus {
-    background-color: -moz-field;
-    border-color: #a9b7c9 !important;
-  }
-
   menulist,
   menulist[disabled="true"] {
     color: inherit;
     border: 1px solid;
     background: var(--toolbarbutton-hover-background);
     border-color: var(--toolbarbutton-hover-bordercolor);
     box-shadow: var(--toolbarbutton-hover-boxshadow);
     transition-property: background-color, border-color, box-shadow;
@@ -700,54 +591,16 @@ toolbar:not(:-moz-lwtheme) {
   }
 
   .menulist-dropmarker::part(icon) {
     width: 9px;
     height: 7px;
   }
 }
 
-.aw-menulist {
-  list-style-image: url("chrome://messenger/skin/icons/toolbarbutton-arrow.svg");
-  -moz-context-properties: fill;
-  fill: currentColor;
-}
-
-.aw-menulist > .menulist-label-box > .menulist-icon {
-  width: 9px;
-  height: 7px;
-  margin-inline-start: 2px;
-}
-
-@media (-moz-windows-default-theme: 0) {
-  .aw-menulist {
-    background-color: rgba(128, 128, 128, .3);
-    border: 1px solid ThreeDShadow;
-    transition: background-color .25s ease-in;
-  }
-
-  .aw-menulist:hover {
-    background-color: rgba(128, 128, 128, .45);
-  }
-
-  .aw-menulist:focus:not([open="true"]) > .menulist-label-box {
-    background-color: inherit;
-    color: inherit;
-  }
-}
-
-.aw-menulist > .menulist-label-box {
-  margin: -1px 0;
-}
-
-.aw-menulist > .menulist-label-box > .menulist-label {
-  margin: 0 3px !important;
-  text-align: end;
-}
-
 /* ::::: address book sidebar ::::: */
 
 #sidebar-box .sidebar-header {
   border-bottom: 1px solid ThreeDShadow;
   border-top: 1px solid ThreeDHighlight;
 }
 
 @media (-moz-windows-default-theme) {
@@ -782,20 +635,16 @@ toolbar:not(:-moz-lwtheme) {
   menulist {
     border-radius: 2px;
   }
 
   menulist[open="true"] {
     text-shadow: none;
     transition: none;
   }
-
-  .aw-menulist {
-    box-shadow: none;
-  }
 }
 
 @media (-moz-windows-glass) {
   #compose-toolbox:not(:-moz-lwtheme) {
     color: black;
     text-shadow: 0 0 .7em white, 0 0 .7em white, 0 1px 0 rgba(255, 255, 255, .4);
   }
 }
--- a/mail/themes/windows/mail/messenger.css
+++ b/mail/themes/windows/mail/messenger.css
@@ -21,17 +21,17 @@
   --toolbar-non-lwt-bgimage: linear-gradient(rgba(255,255,255,.15), rgba(255,255,255,.15));
   --toolbar-bgcolor: var(--toolbar-non-lwt-bgcolor);
   --toolbar-bgimage: var(--toolbar-non-lwt-bgimage);
   --chrome-content-separator-color: ThreeDShadow;
 
   --toolbarbutton-border-radius: 2px;
   --toolbarbutton-icon-fill-opacity: .85;
   --toolbarbutton-hover-background: rgba(0, 0, 0, .1);
-  --toolbarbutton-hover-bordercolor: rgba(0, 0, 0, .1);
+  --toolbarbutton-hover-bordercolor: rgba(0, 0, 0, .25);
   --toolbarbutton-header-bordercolor: rgba(0, 0, 0, .1);
   --toolbarbutton-hover-boxshadow: none;
 
   --toolbarbutton-active-background: rgba(0, 0, 0, .15);
   --toolbarbutton-active-bordercolor: rgba(0, 0, 0, .15);
   --toolbarbutton-active-boxshadow: 0 0 0 1px rgba(0, 0, 0, .15) inset;
 
   --toolbarbutton-checkedhover-backgroundcolor: rgba(0, 0, 0, .2);
--- a/mailnews/addrbook/content/abMailListDialog.js
+++ b/mailnews/addrbook/content/abMailListDialog.js
@@ -20,16 +20,31 @@ top.MAX_RECIPIENTS = 1;
 var inputElementType = "";
 
 var gListCard;
 var gEditList;
 var gOldListName = "";
 var gLoadListeners = [];
 var gSaveListeners = [];
 
+var gAWContentHeight = 0;
+var gAWRowHeight = 0;
+var gNumberOfCols = 0;
+
+var test_addresses_sequence = false;
+
+if (
+  Services.prefs.getPrefType("mail.debug.test_addresses_sequence") ==
+  Ci.nsIPrefBranch.PREF_BOOL
+) {
+  test_addresses_sequence = Services.prefs.getBoolPref(
+    "mail.debug.test_addresses_sequence"
+  );
+}
+
 try {
   var gDragService = Cc["@mozilla.org/widget/dragservice;1"].getService(
     Ci.nsIDragService
   );
 } catch (e) {}
 
 // Returns the load context for the current window
 function getLoadContext() {
@@ -457,20 +472,67 @@ function awAppendNewRow(setFocus) {
       awSetFocusTo(input);
     }
   }
   return input;
 }
 
 // functions for accessing the elements in the addressing widget
 
+/**
+ * Returns the recipient type popup for a row.
+ *
+ * @param {String} row - Index of the recipient row to return. Starts at 1.
+ * @return {HTMLElement} This returns the menulist (not its child menupopup),
+ *   despite the function name.
+ */
+function awGetPopupElement(row) {
+  return document.getElementById("addressCol1#" + row);
+}
+
+/**
+ * Returns the recipient inputbox for a row.
+ *
+ * @param row  Index of the recipient row to return. Starts at 1.
+ * @return     This returns the input element.
+ */
 function awGetInputElement(row) {
   return document.getElementById("addressCol1#" + row);
 }
 
+function awGetElementByCol(row, col) {
+  var colID = "addressCol" + col + "#" + row;
+  return document.getElementById(colID);
+}
+
+function awGetListItem(row) {
+  var listbox = document.getElementById("addressingWidget");
+  if (listbox && row > 0) {
+    return listbox.getItemAtIndex(row - 1);
+  }
+
+  return null;
+}
+
+/**
+ * @param inputElement  The recipient input element.
+ * @return              The row index (starting from 1) where the input element
+ *                      is found. 0 if the element is not found.
+ */
+function awGetRowByInputElement(inputElement) {
+  if (!inputElement) {
+    return 0;
+  }
+
+  var listitem = inputElement.parentNode.parentNode;
+  return (
+    document.getElementById("addressingWidget").getIndexOfItem(listitem) + 1
+  );
+}
+
 function DragOverAddressListTree(event) {
   var dragSession = gDragService.getCurrentSession();
 
   // XXX add support for other flavors here
   if (dragSession.isDataFlavorSupported("text/x-moz-address")) {
     dragSession.canDrop = true;
   }
 }
@@ -576,8 +638,356 @@ function NotifyLoadListeners(aMailingLis
 
 /* Notifies all save listeners.
  */
 function NotifySaveListeners(aMailingList) {
   for (let i = 0; i < gSaveListeners.length; i++) {
     gSaveListeners[i](aMailingList, document);
   }
 }
+
+/**
+ * Handles keypress events for the email address inputs (that auto-fill)
+ * in the Address Book Mailing List dialogs. When a comma-separated list of
+ * addresses is entered on one row, split them into one address per row. Only
+ * add a new blank row on "Enter" key. On "Tab" key focus moves to the "Cancel"
+ * button.
+ *
+ * @param {KeyboardEvent} event  The DOM keypress event.
+ * @param {Element} element      The element that triggered the keypress event.
+ */
+function awAbRecipientKeyPress(event, element) {
+  if (event.key != "Enter" && event.key != "Tab") {
+    return;
+  }
+
+  if (!element.value) {
+    if (event.key == "Enter") {
+      awReturnHit(element);
+    }
+  } else {
+    let inputElement = element;
+    let originalRow = awGetRowByInputElement(element);
+    let row;
+    let addresses = MailServices.headerParser.makeFromDisplayAddress(
+      element.value
+    );
+
+    if (addresses.length > 1) {
+      // Collect any existing addresses from the following rows so we don't
+      // simply overwrite them.
+      row = originalRow + 1;
+      inputElement = awGetInputElement(row);
+
+      while (inputElement) {
+        if (inputElement.value) {
+          addresses.push(inputElement.value);
+          inputElement.value = "";
+        }
+        row += 1;
+        inputElement = awGetInputElement(row);
+      }
+    }
+
+    // Insert the addresses, adding new rows if needed.
+    row = originalRow;
+    let needNewRows = false;
+
+    for (let address of addresses) {
+      if (needNewRows) {
+        inputElement = awAppendNewRow(false);
+      } else {
+        inputElement = awGetInputElement(row);
+        if (!inputElement) {
+          needNewRows = true;
+          inputElement = awAppendNewRow(false);
+        }
+      }
+
+      if (inputElement) {
+        inputElement.value = address;
+      }
+      row += 1;
+    }
+
+    if (event.key == "Enter") {
+      // Prevent the dialog from closing. "Enter" inserted a new row instead.
+      event.preventDefault();
+      awReturnHit(inputElement);
+    } else if (event.key == "Tab") {
+      // Focus the last row to let "Tab" move focus to the "Cancel" button.
+      let lastRow = row - 1;
+      awGetInputElement(lastRow).focus();
+    }
+  }
+}
+
+/**
+ * Handle keydown event on a recipient input.
+ * Enables recipient row deletion with DEL or BACKSPACE and
+ * recipient list navigation with cursor up/down.
+ *
+ * Note that the keydown event fires for ALL keys, so this may affect
+ * autocomplete as user enters a recipient text.
+ *
+ * @param {keydown event} event  the keydown event fired on a recipient input
+ * @param {<html:input>} inputElement  the recipient input element
+ *                                     on which the event fired (textbox-addressingWidget)
+ */
+function awRecipientKeyDown(event, inputElement) {
+  switch (event.key) {
+    // Enable deletion of empty recipient rows.
+    case "Delete":
+    case "Backspace":
+      if (inputElement.textLength == 1 && event.repeat) {
+        // User is holding down Delete or Backspace to delete recipient text
+        // inline and is now deleting the last character: Set flag to
+        // temporarily block row deletion.
+        top.awRecipientInlineDelete = true;
+      }
+      if (!inputElement.value && !event.altKey) {
+        // When user presses DEL or BACKSPACE on an empty row, and it's not an
+        // ongoing inline deletion, and not ALT+BACKSPACE for input undo,
+        // we delete the row.
+        if (top.awRecipientInlineDelete && !event.repeat) {
+          // User has released and re-pressed Delete or Backspace key
+          // after holding them down to delete recipient text inline:
+          // unblock row deletion.
+          top.awRecipientInlineDelete = false;
+        }
+        if (!top.awRecipientInlineDelete) {
+          let deleteForward = event.key == "Delete";
+          awDeleteHit(inputElement, deleteForward);
+        }
+      }
+      break;
+
+    // Enable browsing the list of recipients up and down with cursor keys.
+    case "ArrowDown":
+    case "ArrowUp":
+      // Only browse recipients if the autocomplete popup is not open.
+      if (!inputElement.popupOpen) {
+        let row = awGetRowByInputElement(inputElement);
+        let down = event.key == "ArrowDown";
+        let noEdgeRow = down ? row < top.MAX_RECIPIENTS : row > 1;
+        if (noEdgeRow) {
+          let targetRow = down ? row + 1 : row - 1;
+          awSetFocusTo(awGetInputElement(targetRow));
+        }
+      }
+      break;
+  }
+}
+
+/**
+ * Delete recipient row (addressingWidgetItem) from UI.
+ *
+ * @param {<html:input>} inputElement  the recipient input element
+ *                                     (textbox-addressingWidget) whose parent
+ *                                     row (addressingWidgetItem) will be deleted.
+ * @param {boolean} deleteForward  true: focus next row after deleting the row
+ *                                 false: focus previous row after deleting the row
+ */
+function awDeleteHit(inputElement, deleteForward = false) {
+  let row = awGetRowByInputElement(inputElement);
+
+  // Don't delete the row if it's the last one remaining; just reset it.
+  if (top.MAX_RECIPIENTS <= 1) {
+    inputElement.value = "";
+    return;
+  }
+
+  // Set the focus to the input field of the next/previous row according to
+  // the direction of deleting if possible.
+  // Note: awSetFocusTo() is asynchronous, i.e. we'll focus after row removal.
+  if (
+    (!deleteForward && row > 1) ||
+    (deleteForward && row == top.MAX_RECIPIENTS)
+  ) {
+    // We're deleting backwards, but not the first row,
+    // or forwards on the last row: Focus previous row.
+    awSetFocusTo(awGetInputElement(row - 1));
+  } else {
+    // We're deleting forwards, but not the last row,
+    // or backwards on the first row: Focus next row.
+    awSetFocusTo(awGetInputElement(row + 1));
+  }
+
+  // Delete the row.
+  awDeleteRow(row);
+}
+
+function awTestRowSequence() {
+  /*
+    This function is for debug and testing purpose only, normal user should not run it!
+
+    Every time we insert or delete a row, we must be sure we didn't break the ID sequence of
+    the addressing widget rows. This function will run a quick test to see if the sequence still ok
+
+    You need to define the pref mail.debug.test_addresses_sequence to true in order to activate it
+  */
+
+  if (!test_addresses_sequence) {
+    return true;
+  }
+
+  // Debug code to verify the sequence is still good.
+
+  let listbox = document.getElementById("addressingWidget");
+  let listitems = listbox.itemChildren;
+  if (listitems.length >= top.MAX_RECIPIENTS) {
+    for (let i = 1; i <= listitems.length; i++) {
+      let item = listitems[i - 1];
+      let inputID = item
+        .querySelector(`input[is="autocomplete-input"]`)
+        .id.split("#")[1];
+      let menulist = item.querySelector("menulist");
+      // In some places like the mailing list dialog there is no menulist,
+      // and so no popupID that needs to be kept in sequence.
+      let popupID = menulist && menulist.id.split("#")[1];
+      if (inputID != i || (popupID && popupID != i)) {
+        dump(
+          `#ERROR: sequence broken at row ${i}, ` +
+            `inputID=${inputID}, popupID=${popupID}\n`
+        );
+        return false;
+      }
+      dump("---SEQUENCE OK---\n");
+      return true;
+    }
+  } else {
+    dump(
+      `#ERROR: listitems.length(${listitems.length}) < ` +
+        `top.MAX_RECIPIENTS(${top.MAX_RECIPIENTS})\n`
+    );
+  }
+
+  return false;
+}
+
+function awRemoveRow(row) {
+  awGetListItem(row).remove();
+  awFitDummyRows();
+
+  top.MAX_RECIPIENTS--;
+}
+
+function awGetNumberOfCols() {
+  if (gNumberOfCols == 0) {
+    var listbox = document.getElementById("addressingWidget");
+    var listCols = listbox.getElementsByTagName("treecol");
+    gNumberOfCols = listCols.length;
+    if (!gNumberOfCols) {
+      // If no cols defined, that means we have only one!
+      gNumberOfCols = 1;
+    }
+  }
+
+  return gNumberOfCols;
+}
+
+function awCreateDummyItem(aParent) {
+  var listbox = document.getElementById("addressingWidget");
+  var item = listbox.getItemAtIndex(0);
+
+  var titem = document.createXULElement("richlistitem");
+  titem.setAttribute("_isDummyRow", "true");
+  titem.setAttribute("class", "dummy-row");
+  titem.style.height = item.getBoundingClientRect().height + "px";
+
+  for (let i = 0; i < awGetNumberOfCols(); i++) {
+    let cell = awCreateDummyCell(titem);
+    if (item.children[i].hasAttribute("style")) {
+      cell.setAttribute("style", item.children[i].getAttribute("style"));
+    }
+    if (item.children[i].hasAttribute("flex")) {
+      cell.setAttribute("flex", item.children[i].getAttribute("flex"));
+    }
+  }
+
+  if (aParent) {
+    aParent.appendChild(titem);
+  }
+
+  return titem;
+}
+
+function awFitDummyRows() {
+  awCalcContentHeight();
+  awCreateOrRemoveDummyRows();
+}
+
+function awCreateOrRemoveDummyRows() {
+  let listbox = document.getElementById("addressingWidget");
+  let listboxHeight = listbox.getBoundingClientRect().height;
+
+  // remove rows to remove scrollbar
+  let kids = listbox.querySelectorAll("[_isDummyRow]");
+  for (
+    let i = kids.length - 1;
+    gAWContentHeight > listboxHeight && i >= 0;
+    --i
+  ) {
+    gAWContentHeight -= gAWRowHeight;
+    kids[i].remove();
+  }
+
+  // add rows to fill space
+  if (gAWRowHeight) {
+    while (gAWContentHeight + gAWRowHeight < listboxHeight) {
+      awCreateDummyItem(listbox);
+      gAWContentHeight += gAWRowHeight;
+    }
+  }
+}
+
+function awCalcContentHeight() {
+  var listbox = document.getElementById("addressingWidget");
+  var items = listbox.itemChildren;
+
+  gAWContentHeight = 0;
+  if (items.length > 0) {
+    // all rows are forced to a uniform height in xul listboxes, so
+    // find the first listitem with a boxObject and use it as precedent
+    var i = 0;
+    do {
+      gAWRowHeight = items[i].getBoundingClientRect().height;
+      ++i;
+    } while (i < items.length && !gAWRowHeight);
+    gAWContentHeight = gAWRowHeight * items.length;
+  }
+}
+
+/* ::::::::::: addressing widget dummy rows ::::::::::::::::: */
+
+function awCreateDummyCell(aParent) {
+  var cell = document.createXULElement("hbox");
+  cell.setAttribute("class", "addressingWidgetCell dummy-row-cell");
+  if (aParent) {
+    aParent.appendChild(cell);
+  }
+
+  return cell;
+}
+
+function awGetNextDummyRow() {
+  // gets the next row from the top down
+  return document.querySelector("#addressingWidget > [_isDummyRow]");
+}
+
+/**
+ * Set focus to the specified element, typically a recipient input element.
+ * We do this asynchronously to allow other processes like adding or removing rows
+ * to complete before shifting focus.
+ *
+ * @param element  the element to receive focus asynchronously
+ */
+function awSetFocusTo(element) {
+  // Remember the (input) element to focus for asynchronous focusing, so that we
+  // play safe if this gets called again and the original element gets removed
+  // before we can focus it.
+  top.awInputToFocus = element;
+  setTimeout(_awSetFocusTo, 0);
+}
+
+function _awSetFocusTo() {
+  top.awInputToFocus.focus();
+}