function addressInputOnBeforeHandleKeyDown()

in mail/components/compose/content/addressingWidgetOverlay.js [509:794]


function addressInputOnBeforeHandleKeyDown(event) {
  const input = event.target;

  switch (event.key) {
    case "a": {
      // Break if there's text in the input, if not Ctrl/Cmd+A, or for other
      // modifiers, to not hijack our own (Ctrl/Cmd+Shift+A) or OS shortcuts.
      if (
        input.value ||
        !(AppConstants.platform == "macosx" ? event.metaKey : event.ctrlKey) ||
        event.shiftKey ||
        event.altKey
      ) {
        break;
      }

      // Ctrl/Cmd+A on empty input: Select all pills of the current row.
      // Prevent a pill keypress event when the focus moves on it.
      event.preventDefault();

      const lastPill = input
        .closest(".address-container")
        .querySelector("mail-address-pill:last-of-type");
      const mailRecipientsArea = input.closest("mail-recipients-area");
      if (lastPill) {
        // Select all pills of current address row.
        mailRecipientsArea.selectSiblingPills(lastPill);
        lastPill.focus();
        break;
      }
      // No pills in the current address row, select all pills in all rows.
      const lastPillGlobal = mailRecipientsArea.querySelector(
        "mail-address-pill:last-of-type"
      );
      if (lastPillGlobal) {
        mailRecipientsArea.selectAllPills();
        lastPillGlobal.focus();
      }
      break;
    }
    case " ":
    case ",": {
      const selection = input.value.substring(
        input.selectionStart,
        input.selectionEnd
      );

      // If keydown would normally replace all of the current trimmed input,
      // including if the current input is empty, then suppress the key and
      // clear the input instead.
      if (selection.includes(input.value.trim())) {
        event.preventDefault();
        input.value = "";
        break;
      }

      // Otherwise, comma may trigger pill creation.
      if (event.key !== ",") {
        break;
      }

      let beforeComma;
      let afterComma;
      if (input.selectionEnd == input.selectionStart) {
        // If there is no selected text, we will try to create a pill for the
        // text prior to the typed comma.
        // NOTE: This also captures auto complete suggestions that are not
        // inline. E.g. suggestion popup is shown and the user selects one with
        // the arrow keys.
        beforeComma = input.value.substring(0, input.selectionEnd);
        afterComma = input.value.substring(input.selectionEnd);
        // Only create a pill for valid addresses.
        if (!isValidAddress(beforeComma)) {
          break;
        }
      } else if (
        // There is an auto complete suggestion ...
        input.controller.searchStatus ==
          Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH &&
        input.controller.matchCount &&
        // that is also shown inline (the end of the input is selected).
        input.selectionEnd == input.value.length
        // NOTE: This should exclude cases where no suggestion is selected (user
        // presses "DownArrow" then "UpArrow" when the suggestion pops up), or
        // if the suggestions were cancelled with "Esc", or the inline
        // suggestion was cleared with "Backspace".
      ) {
        if (input.value[input.selectionStart] == ",") {
          // Don't create the pill in the special case where the auto-complete
          // suggestion starts with a comma.
          break;
        }
        // Complete the suggestion as a pill.
        beforeComma = input.value;
        afterComma = "";
      } else {
        // If any other part of the text is selected, we treat it as normal.
        break;
      }

      event.preventDefault();
      input.value = beforeComma;
      input.handleEnter(event);
      // Keep any left over text in the input.
      input.value = afterComma;
      // Keep the cursor at the same position.
      input.selectionStart = 0;
      input.selectionEnd = 0;
      break;
    }
    case "Home":
    case "ArrowLeft":
    case "Backspace": {
      if (
        event.key == "Backspace" &&
        event.repeat &&
        gPreventRowDeletionKeysRepeat
      ) {
        // Prevent repeated backspace keydown event if the flag is set.
        event.preventDefault();
        break;
      }
      // Enable repeated deletion if Home or ArrowLeft were pressed, or if it is
      // a non-repeated Backspace keydown event, or if the flag is already false.
      gPreventRowDeletionKeysRepeat = false;

      if (
        input.value.trim() ||
        input.selectionStart + input.selectionEnd ||
        event.altKey
      ) {
        // Break and allow the key's default behavior if the row has content,
        // or the cursor is not at position 0, or the Alt modifier is pressed.
        break;
      }
      // Navigate into pills if there are any, and if the input is empty or
      // whitespace-only, and the cursor is at position 0, and the Alt key was
      // not used (prevent undo via Alt+Backspace from deleting pills).
      // We'll sanitize whitespace on blur.

      // Prevent a pill keypress event when the focus moves on it, or prevent
      // deletion in previous row after removing current row via long keydown.
      event.preventDefault();

      const targetPill = input
        .closest(".address-container")
        .querySelector(
          "mail-address-pill" + (event.key == "Home" ? "" : ":last-of-type")
        );
      if (targetPill) {
        if (event.repeat) {
          // Prevent navigating into pills for repeated keydown from the middle
          // of whitespace.
          break;
        }
        input
          .closest("mail-recipients-area")
          .checkKeyboardSelected(event, targetPill);
        // Prevent removing the current row after deleting the last pill with
        // repeated deletion keydown.
        gPreventRowDeletionKeysRepeat = true;
        break;
      }

      // No pill found, so the address row is empty except whitespace.
      // Check for long Backspace keyboard shortcut to remove the row.
      if (
        event.key != "Backspace" ||
        !event.repeat ||
        input
          .closest(".address-row")
          .querySelector(".remove-field-button[hidden]")
      ) {
        break;
      }
      // Set flag to prevent further unwarranted deletion in the previous row,
      // which will receive focus while the key is still down. We have already
      // prevented the event above.
      gPreventRowDeletionKeysRepeat = true;

      // Hide the address row if it is empty except whitespace, repeated
      // Backspace keydown event occurred, and it has an [x] button for removal.
      hideAddressRowFromWithin(input, "previous");
      break;
    }
    case "Delete": {
      if (event.repeat && gPreventRowDeletionKeysRepeat) {
        // Prevent repeated Delete keydown event if the flag is set.
        event.preventDefault();
        break;
      }
      // Enable repeated deletion in case of a non-repeated Delete keydown event,
      // or if the flag is already false.
      gPreventRowDeletionKeysRepeat = false;

      if (
        !event.repeat ||
        input.value.trim() ||
        input.selectionStart + input.selectionEnd ||
        input
          .closest(".address-container")
          .querySelector("mail-address-pill") ||
        input
          .closest(".address-row")
          .querySelector(".remove-field-button[hidden]")
      ) {
        // Break and allow the key's default behaviour if the address row has
        // content, or the cursor is not at position 0, or the row is not
        // removable.
        break;
      }
      // Prevent the event and set flag to prevent further unwarranted deletion
      // in the next row, which will receive focus while the key is still down.
      event.preventDefault();
      gPreventRowDeletionKeysRepeat = true;

      // Hide the address row if it is empty except whitespace, repeated Delete
      // keydown event occurred, cursor is at position 0, and it has an
      // [x] button for removal.
      hideAddressRowFromWithin(input, "next");
      break;
    }
    case "Enter": {
      // Break if unrelated modifier keys are used. The toolkit hack for Mac
      // will consume metaKey, and we'll exclude shiftKey after that.
      if (event.ctrlKey || event.altKey) {
        break;
      }

      // MacOS-only variation necessary to send messages via Cmd+[Shift]+Enter
      // since autocomplete input fields prevent that by default (bug 1682147).
      if (event.metaKey) {
        // Cmd+[Shift]+Enter: Send message [later].
        const sendCmd = event.shiftKey ? "cmd_sendLater" : "cmd_sendWithCheck";
        goDoCommand(sendCmd);
        break;
      }

      // Break if there's text in the address input, or if Shift modifier is
      // used, to prevent hijacking shortcuts like Ctrl+Shift+Enter.
      if (input.value.trim() || event.shiftKey) {
        break;
      }

      // Enter on empty input: Focus the next available address row or subject.
      // Prevent Enter from firing again on the element we move the focus to.
      event.preventDefault();
      focusNextAddressRow(input);
      break;
    }
    case "Tab": {
      // Return if the Alt or Cmd modifiers were pressed, meaning the user is
      // switching between windows and not tabbing out of the address input.
      if (event.altKey || event.metaKey) {
        break;
      }
      // Trigger the autocomplete controller only if we have a value,
      // to prevent interfering with the natural change of focus on Tab.
      if (input.value.trim()) {
        // Prevent Tab from firing again on address input after pill creation.
        event.preventDefault();

        // Use the setTimeout only if the input field implements a forced
        // autocomplete and we don't have any match as we might need to wait for
        // the autocomplete suggestions to show up.
        if (input.forceComplete && input.mController.matchCount == 0) {
          // Prevent fast user input to become an error pill before
          // autocompletion kicks in with its default timeout.
          setTimeout(() => {
            input.handleEnter(event);
          }, input.timeout);
        } else {
          input.handleEnter(event);
        }
      }

      // Handle Shift+Tab, but not Ctrl+Shift+Tab, which is handled by
      // moveFocusToNeighbouringAreas.
      if (event.shiftKey && !event.ctrlKey) {
        event.preventDefault();
        input.closest("mail-recipients-area").moveFocusToPreviousElement(input);
      }
      break;
    }
  }
}