Bug 1582410 - message compose should use document.execCommand for editing commands. r=mkmelin a=wsmwk
authorKhushil Mistry <khushil324@gmail.com>
Wed, 10 Jun 2020 14:56:11 +0300
changeset 39358 ffc62563f0d7f5f39517d4a1bc2f0c70c00eb75e
parent 39357 5bec7685ccde6a7ac32aa7e9a8264bf3a262c6b3
child 39359 ff65eea9ddd548ee9737bfd1eb4f3398f659cd7d
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersmkmelin, wsmwk
bugs1582410
Bug 1582410 - message compose should use document.execCommand for editing commands. r=mkmelin a=wsmwk
mail/components/compose/content/ComposerCommands.js
mail/components/compose/content/MsgComposeCommands.js
mail/components/compose/content/editFormatButtons.inc.xhtml
mail/components/compose/content/editor.js
mail/components/compose/content/messengercompose.xhtml
--- a/mail/components/compose/content/ComposerCommands.js
+++ b/mail/components/compose/content/ComposerCommands.js
@@ -1,26 +1,52 @@
 /* -*- Mode: Java; tab-width: 4; 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/. */
 
-/* Implementations of nsIControllerCommand for composer commands */
+/**
+ * Implementations of nsIControllerCommand for composer commands. These commands
+ * are related to editing. You can fire these commands with following functions:
+ * goDoCommand and goDoCommandParams(If command requires any parameters).
+ *
+ * Sometimes, we want to reflect the changes in the UI also. We have two functions
+ * for that: pokeStyleUI and pokeMultiStateUI. The pokeStyleUI function is for those
+ * commands which are boolean in nature for example "cmd_bold" command, text can
+ * be bold or not. The pokeMultiStateUI function is for the commands which can have
+ * multiple values for example "cmd_fontFace" can have different values like
+ * arial, variable width etc.
+ *
+ * Here, some of the commands are getting executed by document.execCommand.
+ * Those are listed in the gCommandMap Map object. In that also, some commands
+ * are of type boolean and some are of multiple state. We have two functions to
+ * execute them: doStatefulCommand and doStyleUICommand.
+ *
+ * All commands are not executable through document.execCommand.
+ * In all those cases, we will use goDoCommand or goDoCommandParams.
+ * The goDoCommandParams function is implemented in this file.
+ * The goDoCOmmand function is from globalOverlay.js. For the Commands
+ * which can be executed by document.execCommand, we will use doStatefulCommand
+ * and doStyleUICommand.
+ */
 
 // Linting is disabled in chunks of this file because it contains code that never
 // runs in Thunderbird, and references things that don't exist in Thunderbird.
 
 /* import-globals-from editor.js */
 /* import-globals-from editorUtilities.js */
 /* globals CreatePublishDataFromUrl editPage FormatDirForPublishing getTopWin
    goPreferences nsIPromptService openComposeWindow openNewPrivateWith
    PrintPreviewListener SavePublishDataToPrefs SavePassword savePWObj */
 
 var gComposerJSCommandControllerID = 0;
 
+/**
+ * Used to register commands we have created manually.
+ */
 function SetupHTMLEditorCommands() {
   var commandTable = GetComposerCommandTable();
   if (!commandTable) {
     return;
   }
 
   // Include everything a text editor does
   SetupTextEditorCommands();
@@ -49,17 +75,16 @@ function SetupHTMLEditorCommands() {
   commandTable.registerCommand(
     "cmd_insertHTMLWithDialog",
     nsInsertHTMLWithDialogCommand
   );
   commandTable.registerCommand(
     "cmd_insertMathWithDialog",
     nsInsertMathWithDialogCommand
   );
-  commandTable.registerCommand("cmd_insertBreak", nsInsertBreakCommand);
   commandTable.registerCommand("cmd_insertBreakAll", nsInsertBreakAllCommand);
 
   commandTable.registerCommand("cmd_table", nsInsertOrEditTableCommand);
   commandTable.registerCommand("cmd_editTable", nsEditTableCommand);
   commandTable.registerCommand("cmd_SelectTable", nsSelectTableCommand);
   commandTable.registerCommand("cmd_SelectRow", nsSelectTableRowCommand);
   commandTable.registerCommand("cmd_SelectColumn", nsSelectTableColumnCommand);
   commandTable.registerCommand("cmd_SelectCell", nsSelectTableCellCommand);
@@ -121,79 +146,22 @@ function SetupTextEditorCommands() {
   commandTable.registerCommand("cmd_find", nsFindCommand);
   commandTable.registerCommand("cmd_findNext", nsFindAgainCommand);
   commandTable.registerCommand("cmd_findPrev", nsFindAgainCommand);
   commandTable.registerCommand("cmd_rewrap", nsRewrapCommand);
   commandTable.registerCommand("cmd_spelling", nsSpellingCommand);
   commandTable.registerCommand("cmd_insertChars", nsInsertCharsCommand);
 }
 
-function SetupComposerWindowCommands() {
-  // Don't need to do this if already done
-  if (gComposerWindowControllerID) {
-    return;
-  }
-
-  // Create a command controller and register commands
-  //   specific to Web Composer window (file-related commands, HTML Source...)
-  //   We can't use the composer controller created on the content window else
-  //     we can't process commands when in HTMLSource editor
-
-  var windowControllers = window.controllers;
-
-  if (!windowControllers) {
-    return;
-  }
-
-  var commandTable;
-  var composerController;
-  var editorController;
-  try {
-    composerController = Cc[
-      "@mozilla.org/embedcomp/base-command-controller;1"
-    ].createInstance();
-
-    editorController = composerController.QueryInterface(
-      Ci.nsIControllerContext
-    );
-
-    // Get the nsIControllerCommandTable interface we need to register commands
-    var interfaceRequestor = composerController.QueryInterface(
-      Ci.nsIInterfaceRequestor
-    );
-    commandTable = interfaceRequestor.getInterface(
-      Ci.nsIControllerCommandTable
-    );
-  } catch (e) {
-    dump("Failed to create composerController\n");
-    return;
-  }
-
-  if (!commandTable) {
-    dump("Failed to get interface for nsIControllerCommandManager\n");
-    return;
-  }
-
-  // File-related commands
-  commandTable.registerCommand("cmd_open", nsOpenCommand);
-  commandTable.registerCommand("cmd_save", nsSaveCommand);
-  commandTable.registerCommand("cmd_saveAs", nsSaveAsCommand);
-  commandTable.registerCommand("cmd_print", nsPrintCommand);
-  commandTable.registerCommand("cmd_printpreview", nsPrintPreviewCommand);
-  commandTable.registerCommand("cmd_printSetup", nsPrintSetupCommand);
-  commandTable.registerCommand("cmd_close", nsCloseCommand);
-
-  windowControllers.insertControllerAt(0, editorController);
-
-  // Store the controller ID so we can be sure to get the right one later
-  gComposerWindowControllerID = windowControllers.getControllerId(
-    editorController
-  );
-}
-
+/**
+ * Used to register the command controller in the editor document.
+ *
+ * @returns {nsIControllerCommandTable|null} - A controller used to
+ *   register the manually created commands.
+ */
 function GetComposerCommandTable() {
   var controller;
   if (gComposerJSCommandControllerID) {
     try {
       controller = window.content.controllers.getControllerById(
         gComposerJSCommandControllerID
       );
     } catch (e) {}
@@ -219,16 +187,23 @@ function GetComposerCommandTable() {
       Ci.nsIInterfaceRequestor
     );
     return interfaceRequestor.getInterface(Ci.nsIControllerCommandTable);
   }
   return null;
 }
 
 /* eslint-disable complexity */
+
+/**
+ * Get the state of the given command and call the pokeStyleUI or pokeMultiStateUI
+ * according to the type of the command to reflect the UI changes in the editor.
+ *
+ * @param {string} command - The id of the command.
+ */
 function goUpdateCommandState(command) {
   try {
     var controller = top.document.commandDispatcher.getControllerForCommand(
       command
     );
     if (!(controller instanceof Ci.nsICommandController)) {
       return;
     }
@@ -285,61 +260,78 @@ function goUpdateCommandState(command) {
         dump("no update for command: " + command + "\n");
     }
   } catch (e) {
     Cu.reportError(e);
   }
 }
 /* eslint-enable complexity */
 
+/**
+ * Used in the oncommandupdate attribute of the goUpdateComposerMenuItems.
+ * For any commandset events fired, this function will be called.
+ * Used to update the UI state of the editor buttons and menulist.
+ * Whenever you change your selection in the editor part, i.e. if you move
+ * your cursor, you will find this functions getting called and
+ * updating the editor UI of toolbarbuttons and menulists. This is mainly
+ * to update the UI according to your selection in the editor part.
+ *
+ * @param {XULElement} commandset - The <xul:commandset> element to update for.
+ */
 function goUpdateComposerMenuItems(commandset) {
   // dump("Updating commands for " + commandset.id + "\n");
   for (var i = 0; i < commandset.children.length; i++) {
     var commandNode = commandset.children[i];
     var commandID = commandNode.id;
     if (commandID) {
       goUpdateCommand(commandID); // enable or disable
       if (commandNode.hasAttribute("state")) {
         goUpdateCommandState(commandID);
       }
     }
   }
 }
 
-function goDoCommandParams(command, params) {
+/**
+ * Execute the command with the provided parameters.
+ * This is directly calling commands with multiple state attributes, which
+ * are not supported by document.execCommand()
+ *
+ * @param {string} command - The command ID.
+ * @param {string} paramValue - The parameter value.
+ */
+function goDoCommandParams(command, paramValue) {
   try {
-    var controller = top.document.commandDispatcher.getControllerForCommand(
+    let params = newCommandParams();
+    params.setStringValue("state_attribute", paramValue);
+    let controller = top.document.commandDispatcher.getControllerForCommand(
       command
     );
     if (controller && controller.isCommandEnabled(command)) {
       if (controller instanceof Ci.nsICommandController) {
         controller.doCommandWithParams(command, params);
-
-        // the following two lines should be removed when we implement observers
-        if (params) {
-          controller.getCommandStateWithParams(command, params);
-        }
       } else {
         controller.doCommand(command);
       }
     }
   } catch (e) {
     Cu.reportError(e);
   }
 }
 
 /**
- * Update the UI to reflect setting a given state for a command.
+ * Update the UI to reflect setting a given state for a command. This
+ * is used for boolean type of commands.
  *
  * @param {string} uiID - The id of the command.
  * @param {boolean} desiredState - State to set for the command.
  */
 function pokeStyleUI(uiID, desiredState) {
   let commandNode = top.document.getElementById(uiID);
-  let uiState = "true" == commandNode.getAttribute("state");
+  let uiState = commandNode.getAttribute("state") == "true";
   if (desiredState != uiState) {
     commandNode.setAttribute("state", desiredState ? "true" : "false");
     switch (uiID) {
       case "cmd_bold": {
         onButtonUpdate(document.getElementById("boldButton"), "cmd_bold");
         break;
       }
       case "cmd_italic": {
@@ -360,38 +352,65 @@ function pokeStyleUI(uiID, desiredState)
       case "cmd_ol": {
         onButtonUpdate(document.getElementById("olButton"), "cmd_ol");
         break;
       }
     }
   }
 }
 
+/**
+ * Maps internal command names to their document.execCommand() command string.
+ */
+let gCommandMap = new Map([
+  ["cmd_bold", "bold"],
+  ["cmd_italic", "italic"],
+  ["cmd_underline", "underline"],
+  ["cmd_strikethrough", "strikethrough"],
+  ["cmd_superscript", "superscript"],
+  ["cmd_subscript", "subscript"],
+  ["cmd_ul", "InsertUnorderedList"],
+  ["cmd_ol", "InsertOrderedList"],
+  ["cmd_fontFace", "fontName"],
+  // ["cmd_paragraphState", "formatBlock"],
+
+  // This are currently implemented with the help of
+  // color selection dialog box in the editor.js.
+  // ["cmd_highlight", "backColor"],
+  // ["cmd_fontColor", "foreColor"],
+]);
+
+/**
+ * Used for the boolean type commands available through
+ * document.execCommand(). We will also call pokeStyleUI to update
+ * the UI.
+ *
+ * @param {string} cmdStr - The id of the command.
+ */
 function doStyleUICommand(cmdStr) {
-  try {
-    var cmdParams = newCommandParams();
-    goDoCommandParams(cmdStr, cmdParams);
-    if (cmdParams) {
-      pokeStyleUI(cmdStr, cmdParams.getBooleanValue("state_all"));
-    }
-  } catch (e) {}
+  top.document
+    .querySelector("editor")
+    .contentDocument.execCommand(gCommandMap.get(cmdStr), false, null);
+  let commandNode = top.document.getElementById(cmdStr);
+  let newState = commandNode.getAttribute("state") != "true";
+  pokeStyleUI(cmdStr, newState);
 }
 
 // Copied from jsmime.js.
 function stringToTypedArray(buffer) {
   var typedarray = new Uint8Array(buffer.length);
   for (var i = 0; i < buffer.length; i++) {
     typedarray[i] = buffer.charCodeAt(i);
   }
   return typedarray;
 }
 
 /**
  * Update the UI to reflect setting a given state for a command. This is used
- * when the command state has a string value.
+ * when the command state has a string value i.e. multiple state type commands.
  *
  * @param {string} uiID - The id of the command.
  * @param {nsICommandParams} cmdParams - Command parameters object.
  */
 function pokeMultiStateUI(uiID, cmdParams) {
   let isMixed = cmdParams.getBooleanValue("state_mixed");
   let desiredAttrib;
   if (isMixed) {
@@ -410,56 +429,84 @@ function pokeMultiStateUI(uiID, cmdParam
   }
 
   let commandNode = document.getElementById(uiID);
   let uiState = commandNode.getAttribute("state");
   if (desiredAttrib != uiState) {
     commandNode.setAttribute("state", desiredAttrib);
     switch (uiID) {
       case "cmd_paragraphState": {
-        let menulist = document.getElementById("ParagraphSelect");
-        onParagraphFormatChange(menulist, "cmd_paragraphState");
+        onParagraphFormatChange();
         break;
       }
       case "cmd_fontFace": {
-        let menulist = document.getElementById("FontFaceSelect");
-        onFontFaceChange(menulist, "cmd_fontFace");
+        onFontFaceChange();
         break;
       }
       case "cmd_fontColor": {
         onFontColorChange();
         break;
       }
       case "cmd_backgroundColor": {
         onBackgroundColorChange();
         break;
       }
     }
   }
 }
 
-function doStatefulCommand(commandID, newState) {
-  var commandNode = document.getElementById(commandID);
-  if (commandNode) {
-    commandNode.setAttribute("state", newState);
+/**
+ * Perform the action of the multiple states type commands available through
+ * document.execCommand().
+ *
+ * @param {string} commandID - The id of the command.
+ * @param {string} newState - The parameter value.
+ * @param {boolean} updateUI - updates the UI if true. Used when
+ *   function is called in another JavaScript function.
+ */
+function doStatefulCommand(commandID, newState, updateUI) {
+  if (commandID == "cmd_align") {
+    let command;
+    switch (newState) {
+      case "left":
+        command = "justifyLeft";
+        break;
+      case "center":
+        command = "justifyCenter";
+        break;
+      case "right":
+        command = "justifyRight";
+        break;
+      case "justify":
+        command = "justifyFull";
+        break;
+    }
+    top.document
+      .querySelector("editor")
+      .contentDocument.execCommand(command, false, null);
+  } else {
+    top.document
+      .querySelector("editor")
+      .contentDocument.execCommand(gCommandMap.get(commandID), false, newState);
   }
-  gContentWindow.focus(); // needed for command dispatch to work
-
-  try {
-    var cmdParams = newCommandParams();
-    if (!cmdParams) {
-      return;
+
+  if (updateUI) {
+    let commandNode = document.getElementById(commandID);
+    commandNode.setAttribute("state", newState);
+    switch (commandID) {
+      case "cmd_fontFace": {
+        onFontFaceChange();
+        break;
+      }
     }
-
-    cmdParams.setStringValue("state_attribute", newState);
-    goDoCommandParams(commandID, cmdParams);
-
-    pokeMultiStateUI(commandID, cmdParams);
-  } catch (e) {
-    dump("error thrown in doStatefulCommand: " + e + "\n");
+  } else {
+    let commandNode = document.getElementById(commandID);
+    if (commandNode) {
+      commandNode.setAttribute("state", newState);
+    }
   }
 }
 
 function PrintObject(obj) {
   dump("-----" + obj + "------\n");
   var names = "";
   for (var i in obj) {
     if (i == "value") {
@@ -2379,31 +2426,16 @@ var nsInsertCharsCommand = {
   getCommandStateParams(aCommand, aParams, aRefCon) {},
   doCommandParams(aCommand, aParams, aRefCon) {},
 
   doCommand(aCommand) {
     EditorFindOrCreateInsertCharWindow();
   },
 };
 
-var nsInsertBreakCommand = {
-  isCommandEnabled(aCommand, dummy) {
-    return IsDocumentEditable() && IsEditingRenderedHTML();
-  },
-
-  getCommandStateParams(aCommand, aParams, aRefCon) {},
-  doCommandParams(aCommand, aParams, aRefCon) {},
-
-  doCommand(aCommand) {
-    try {
-      GetCurrentEditor().insertHTML("<br>");
-    } catch (e) {}
-  },
-};
-
 var nsInsertBreakAllCommand = {
   isCommandEnabled(aCommand, dummy) {
     return IsDocumentEditable() && IsEditingRenderedHTML();
   },
 
   getCommandStateParams(aCommand, aParams, aRefCon) {},
   doCommandParams(aCommand, aParams, aRefCon) {},
 
@@ -2642,16 +2674,19 @@ var nsIncreaseFontCommand = {
 
   doCommand(aCommand) {
     var setIndex = getFontSizeIndex();
     if (setIndex < 0 || setIndex >= 5) {
       return;
     }
     var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
     EditorSetFontSize(sizes[setIndex + 1]);
+    // Enable or Disable the toolbar buttons according to the font size.
+    goUpdateCommand("cmd_decreaseFontStep");
+    goUpdateCommand("cmd_increaseFontStep");
   },
 };
 
 var nsDecreaseFontCommand = {
   isCommandEnabled(aCommand, dummy) {
     if (!(IsDocumentEditable() && IsEditingRenderedHTML())) {
       return false;
     }
@@ -2664,16 +2699,19 @@ var nsDecreaseFontCommand = {
 
   doCommand(aCommand) {
     var setIndex = getFontSizeIndex();
     if (setIndex <= 0) {
       return;
     }
     var sizes = ["x-small", "small", "medium", "large", "x-large", "xx-large"];
     EditorSetFontSize(sizes[setIndex - 1]);
+    // Enable or Disable the toolbar buttons according to the font size.
+    goUpdateCommand("cmd_decreaseFontStep");
+    goUpdateCommand("cmd_increaseFontStep");
   },
 };
 
 var nsRemoveNamedAnchorsCommand = {
   isCommandEnabled(aCommand, dummy) {
     // We could see if there's any link in selection, but it doesn't seem worth the work!
     return IsDocumentEditable() && IsEditingRenderedHTML();
   },
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -621,16 +621,17 @@ var stateListener = {
       document.getElementById("cmd_paragraphState").setAttribute("state", "p");
 
       editor.beginningOfDocument();
       editor.enableUndo(true);
       editor.resetModificationCount();
     } else {
       document.getElementById("cmd_paragraphState").setAttribute("state", "");
     }
+    onParagraphFormatChange();
   },
 
   NotifyComposeBodyReadyReply() {
     // Control insertion of line breaks.
     let useParagraph = Services.prefs.getBoolPref(
       "mail.compose.default_to_paragraph"
     );
     if (gMsgCompose.composeHTML && useParagraph) {
@@ -668,16 +669,17 @@ var stateListener = {
 
       document.getElementById("cmd_paragraphState").setAttribute("state", "p");
 
       editor.enableUndo(true);
       editor.resetModificationCount();
     } else {
       document.getElementById("cmd_paragraphState").setAttribute("state", "");
     }
+    onParagraphFormatChange();
   },
 
   NotifyComposeBodyReadyForwardInline() {
     let mailBody = getBrowser().contentDocument.querySelector("body");
     let editor = GetCurrentEditor();
     let selection = editor.selection;
 
     editor.enableUndo(false);
@@ -696,16 +698,17 @@ var stateListener = {
     } else {
       // insertLineBreak() has been observed to insert two <br> elements
       // instead of one before a <div>, so we'll do it ourselves here.
       let brElement = editor.createElementWithDefaults("br");
       editor.insertElementAtSelection(brElement, false);
       document.getElementById("cmd_paragraphState").setAttribute("state", "");
     }
 
+    onParagraphFormatChange();
     editor.beginningOfDocument();
     editor.enableUndo(true);
     editor.resetModificationCount();
   },
 
   ComposeProcessDone(aResult) {
     ToggleWindowLock(false);
 
@@ -8039,17 +8042,17 @@ function toggleAddressPicker(aFocus = tr
       SetMsgBodyFrameFocus();
     }
   }
 }
 
 function loadHTMLMsgPrefs() {
   let fontFace = Services.prefs.getStringPref("msgcompose.font_face", "");
   if (fontFace) {
-    doStatefulCommand("cmd_fontFace", fontFace);
+    doStatefulCommand("cmd_fontFace", fontFace, true);
   }
 
   let fontSize = Services.prefs.getCharPref("msgcompose.font_size", "");
   if (fontSize) {
     EditorSetFontSize(fontSize);
   }
 
   let bodyElement = GetBodyElement();
--- a/mail/components/compose/content/editFormatButtons.inc.xhtml
+++ b/mail/components/compose/content/editFormatButtons.inc.xhtml
@@ -2,17 +2,17 @@
 # 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/.
 
         <!-- Formatting toolbar items. "value" are HTML tagnames, don't translate -->
         <toolbaritem id="paragraph-select-container"
                      class="formatting-button">
           <menulist id="ParagraphSelect"
                     class="toolbar-focustarget"
-                    oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"
+                    oncommand="goDoCommandParams('cmd_paragraphState', event.target.value)"
                     crop="right"
                     tooltiptext="&ParagraphSelect.tooltip;"
                     observes="cmd_renderedHTMLEnabler">
             <menupopup id="ParagraphPopup">
               <menuitem id="toolbarmenu_bodyText" label="&bodyTextCmd.label;" value=""/>
               <menuitem id="toolbarmenu_paragraph" label="&paragraphParagraphCmd.label;" value="p"/>
               <menuitem id="toolbarmenu_h1" label="&heading1Cmd.label;" value="h1"/>
               <menuitem id="toolbarmenu_h2" label="&heading2Cmd.label;" value="h2"/>
@@ -227,41 +227,41 @@
         <toolbarbutton id="smileButtonMenu"
                        type="menu"
                        wantdropmarker="true"
                        class="formatting-button"
                        tooltiptext="&SmileButton.tooltip;"
                        observes="cmd_smiley">
           <menupopup id="smilyPopup">
             <menuitem id="smileySmile" class="menuitem-iconic" label="&smiley1Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-)')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-)')"/>
             <menuitem id="smileyFrown" class="menuitem-iconic" label="&smiley2Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-(')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-(')"/>
             <menuitem id="smileyWink" class="menuitem-iconic" label="&smiley3Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ';-)')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ';-)')"/>
             <menuitem id="smileyTongue" class="menuitem-iconic" label="&smiley4Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-P')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-P')"/>
             <menuitem id="smileyLaughing" class="menuitem-iconic" label="&smiley5Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-D')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-D')"/>
             <menuitem id="smileyEmbarassed" class="menuitem-iconic" label="&smiley6Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-[')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-[')"/>
             <menuitem id="smileyUndecided" class="menuitem-iconic" label="&smiley7Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-\\')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-\\')"/>
             <menuitem id="smileySurprise" class="menuitem-iconic" label="&smiley8Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', '=-O')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', '=-O')"/>
             <menuitem id="smileyKiss" class="menuitem-iconic" label="&smiley9Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-*')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-*')"/>
             <menuitem id="smileyYell" class="menuitem-iconic" label="&smiley10Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', '>:o')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', '>:o')"/>
             <menuitem id="smileyCool" class="menuitem-iconic" label="&smiley11Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', '8-)')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', '8-)')"/>
             <menuitem id="smileyMoney" class="menuitem-iconic" label="&smiley12Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-$')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-$')"/>
             <menuitem id="smileyFoot" class="menuitem-iconic" label="&smiley13Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-!')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-!')"/>
             <menuitem id="smileyInnocent" class="menuitem-iconic" label="&smiley14Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', 'O:-)')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', 'O:-)')"/>
             <menuitem id="smileyCry" class="menuitem-iconic" label="&smiley15Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':\'(')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':\'(')"/>
             <menuitem id="smileySealed" class="menuitem-iconic" label="&smiley16Cmd.label;"
-                      oncommand="doStatefulCommand('cmd_smiley', ':-X')"/>
+                      oncommand="goDoCommandParams('cmd_smiley', ':-X')"/>
           </menupopup>
         </toolbarbutton>
--- a/mail/components/compose/content/editor.js
+++ b/mail/components/compose/content/editor.js
@@ -418,22 +418,23 @@ async function CheckAndSaveDocument(comm
   }
 
   // Default or result == 1 (Cancel)
   return false;
 }
 
 // --------------------------- Text style ---------------------------
 
-function onParagraphFormatChange(paraMenuList, commandID) {
+function onParagraphFormatChange() {
+  let paraMenuList = document.getElementById("ParagraphSelect");
   if (!paraMenuList) {
     return;
   }
 
-  var commandNode = document.getElementById(commandID);
+  var commandNode = document.getElementById("cmd_paragraphState");
   var state = commandNode.getAttribute("state");
 
   // force match with "normal"
   if (state == "body") {
     state = "";
   }
 
   if (state == "mixed") {
@@ -450,23 +451,20 @@ function onParagraphFormatChange(paraMen
         break;
       }
     }
   }
 }
 
 /**
  * Selects the current font face in the menulist.
- *
- * @param fontFaceMenuList  The menulist element containing the list of fonts.
- * @param commandID         The commandID which holds the current font name
- *                          in its "state" attribute.
  */
-function onFontFaceChange(fontFaceMenuList, commandID) {
-  var commandNode = document.getElementById(commandID);
+function onFontFaceChange() {
+  let fontFaceMenuList = document.getElementById("FontFaceSelect");
+  var commandNode = document.getElementById("cmd_fontFace");
   var editorFont = commandNode.getAttribute("state");
 
   // Strip quotes in font names. Experiments have shown that we only
   // ever get double quotes around the font name, never single quotes,
   // even if they were in the HTML source. Also single or double
   // quotes within the font name are never returned.
   editorFont = editorFont.replace(/"/g, "");
 
--- a/mail/components/compose/content/messengercompose.xhtml
+++ b/mail/components/compose/content/messengercompose.xhtml
@@ -144,17 +144,16 @@
     <command id="cmd_image" oncommand="goDoCommand('cmd_image')"/>
     <command id="cmd_hline" oncommand="goDoCommand('cmd_hline')"/>
     <command id="cmd_table" oncommand="goDoCommand('cmd_table')"/>
     <command id="cmd_objectProperties" oncommand="goDoCommand('cmd_objectProperties')"/>
     <command id="cmd_insertChars" oncommand="goDoCommand('cmd_insertChars')" label="&insertCharsCmd.label;"/>
     <command id="cmd_insertHTMLWithDialog" oncommand="goDoCommand('cmd_insertHTMLWithDialog')" label="&insertHTMLCmd.label;"/>
     <command id="cmd_insertMathWithDialog" oncommand="goDoCommand('cmd_insertMathWithDialog')" label="&insertMathCmd.label;"/>
 
-    <command id="cmd_insertBreak" oncommand="goDoCommand('cmd_insertBreak')"/>
     <command id="cmd_insertBreakAll" oncommand="goDoCommand('cmd_insertBreakAll')"/>
 
     <!-- dummy command used just to disable things in non-HTML modes -->
     <command id="cmd_renderedHTMLEnabler"/>
   </commandset>
 
   <!-- edit menu commands. These get updated by code in globalOverlay.js -->
   <commandset id="composerEditMenuItems"
@@ -174,41 +173,41 @@
   <!-- style related commands that update on creation, and on selection change -->
   <commandset id="composerStyleMenuItems"
               commandupdater="true"
               events="create, style, mode_switch"
               oncommandupdate="goUpdateComposerMenuItems(this)">
     <command id="cmd_bold" state="false" oncommand="doStyleUICommand('cmd_bold')"/>
     <command id="cmd_italic" state="false" oncommand="doStyleUICommand('cmd_italic')"/>
     <command id="cmd_underline" state="false" oncommand="doStyleUICommand('cmd_underline')"/>
-    <command id="cmd_tt" state="false" oncommand="doStyleUICommand('cmd_tt')"/>
+    <command id="cmd_tt" state="false" oncommand="goDoCommand('cmd_tt')"/>
     <command id="cmd_smiley"/>
 
     <command id="cmd_strikethrough" state="false" oncommand="doStyleUICommand('cmd_strikethrough');"/>
     <command id="cmd_superscript" state="false" oncommand="doStyleUICommand('cmd_superscript');"/>
     <command id="cmd_subscript" state="false" oncommand="doStyleUICommand('cmd_subscript');"/>
-    <command id="cmd_nobreak" state="false" oncommand="doStyleUICommand('cmd_nobreak');"/>
+    <command id="cmd_nobreak" state="false" oncommand="goDoCommand('cmd_nobreak');"/>
 
-    <command id="cmd_em" state="false" oncommand="doStyleUICommand('cmd_em')"/>
-    <command id="cmd_strong" state="false" oncommand="doStyleUICommand('cmd_strong')"/>
-    <command id="cmd_cite" state="false" oncommand="doStyleUICommand('cmd_cite')"/>
-    <command id="cmd_abbr" state="false" oncommand="doStyleUICommand('cmd_abbr')"/>
-    <command id="cmd_acronym" state="false" oncommand="doStyleUICommand('cmd_acronym')"/>
-    <command id="cmd_code" state="false" oncommand="doStyleUICommand('cmd_code')"/>
-    <command id="cmd_samp" state="false" oncommand="doStyleUICommand('cmd_samp')"/>
-    <command id="cmd_var" state="false" oncommand="doStyleUICommand('cmd_var')"/>
+    <command id="cmd_em" state="false" oncommand="goDoCommand('cmd_em')"/>
+    <command id="cmd_strong" state="false" oncommand="goDoCommand('cmd_strong')"/>
+    <command id="cmd_cite" state="false" oncommand="goDoCommand('cmd_cite')"/>
+    <command id="cmd_abbr" state="false" oncommand="goDoCommand('cmd_abbr')"/>
+    <command id="cmd_acronym" state="false" oncommand="goDoCommand('cmd_acronym')"/>
+    <command id="cmd_code" state="false" oncommand="goDoCommand('cmd_code')"/>
+    <command id="cmd_samp" state="false" oncommand="goDoCommand('cmd_samp')"/>
+    <command id="cmd_var" state="false" oncommand="goDoCommand('cmd_var')"/>
 
     <command id="cmd_ul" state="false" oncommand="doStyleUICommand('cmd_ul')"/>
     <command id="cmd_ol" state="false" oncommand="doStyleUICommand('cmd_ol')"/>
 
     <command id="cmd_indent" oncommand="goDoCommand('cmd_indent')"/>
     <command id="cmd_outdent" oncommand="goDoCommand('cmd_outdent')"/>
 
     <!-- the state attribute gets filled with the paragraph format before the command is executed -->
-    <command id="cmd_paragraphState" state="" oncommand="doStatefulCommand('cmd_paragraphState', event.target.value)"/>
+    <command id="cmd_paragraphState" state="" oncommand="goDoCommandParams('cmd_paragraphState', event.target.value)"/>
     <command id="cmd_fontFace" state="" oncommand="doStatefulCommand('cmd_fontFace', event.target.value)"/>
 
     <!-- No "oncommand", use EditorSelectColor() to bring up color dialog -->
     <command id="cmd_fontColor" state="" disabled="false"/>
     <command id="cmd_backgroundColor" state="" disabled="false"/>
     <command id="cmd_highlight" state="transparent" oncommand="EditorSelectColor('Highlight', event);"/>
 
     <command id="cmd_align" state=""/>
@@ -251,18 +250,18 @@
   </commandset>
 
   <!-- commands updated only when the menu gets created -->
   <commandset id="composerListMenuItems"
               commandupdater="true"
               events="create, mode_switch"
               oncommandupdate="goUpdateComposerMenuItems(this)">
     <!-- List menu  -->
-    <command id="cmd_dt" oncommand="doStyleUICommand('cmd_dt')"/>
-    <command id="cmd_dd" oncommand="doStyleUICommand('cmd_dd')"/>
+    <command id="cmd_dt" oncommand="goDoCommand('cmd_dt')"/>
+    <command id="cmd_dd" oncommand="goDoCommand('cmd_dd')"/>
     <command id="cmd_removeList" oncommand="goDoCommand('cmd_removeList')"/>
     <!-- cmd_ul and cmd_ol are shared with toolbar and are in composerStyleMenuItems commandset -->
   </commandset>
 
   <!-- File Menu -->
   <command id="cmd_new" oncommand="goDoCommand('cmd_newMessage')"/>
   <command id="cmd_attachFile" oncommand="goDoCommand('cmd_attachFile')"/>
   <command id="cmd_attachCloud" oncommand="attachToCloud(event)"/>
@@ -1383,17 +1382,17 @@
                assume that the id = 'menu_'+tagName (the 'value' label),
                except for the first ('none') item
            -->
           <!-- Paragraph Style submenu -->
           <menu id="paragraphMenu" label="&paragraphMenu.label;"
                 accesskey="&paragraphMenu.accesskey;"
                 position="10" onpopupshowing="InitParagraphMenu()">
             <menupopup id="paragraphMenuPopup"
-                       oncommand="doStatefulCommand('cmd_paragraphState', event.target.getAttribute('value'))">
+                       oncommand="goDoCommandParams('cmd_paragraphState', event.target.getAttribute('value'))">
               <menuitem id="menu_bodyText"
                         type="radio"
                         name="1"
                         label="&bodyTextCmd.label;"
                         accesskey="&bodyTextCmd.accesskey;"
                         value=""
                         observes="cmd_renderedHTMLEnabler"/>
               <menuitem id="menu_p"