Bug 1543966 - part 2: Make "HTML editor commands" and "document state commands" singleton classes r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 15 Apr 2019 07:29:17 +0000
changeset 469477 b742fcff4f2bd93c8bce4923ca206f1c945d1823
parent 469476 47b6254afb9a7c7c7db27f277050176baaf87e3c
child 469478 2fea4528362ef7e58d2b5fecb1a366352d7c95e4
push id35872
push userncsoregi@mozilla.com
push dateMon, 15 Apr 2019 15:24:06 +0000
treeherdermozilla-central@16d953cca414 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1543966
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1543966 - part 2: Make "HTML editor commands" and "document state commands" singleton classes r=m_kato Some "HTML editor commands" are stateful due to storing tag name to handle it with specific command. However, we can make it stateless with retrieving tag name from command name once per command only when it's necessary. The runtime cost must be really cheap since we can map it with hash table. This patch makes them stateless and singleton classes. So, we can save footprint and allocation runtime cost with this change. Differential Revision: https://phabricator.services.mozilla.com/D27407
editor/libeditor/HTMLEditorCommands.cpp
editor/libeditor/HTMLEditorCommands.h
editor/libeditor/HTMLEditorController.cpp
editor/libeditor/HTMLEditorController.h
editor/libeditor/HTMLEditorDocumentCommands.cpp
layout/build/nsLayoutStatics.cpp
--- a/editor/libeditor/HTMLEditorCommands.cpp
+++ b/editor/libeditor/HTMLEditorCommands.cpp
@@ -43,26 +43,28 @@ MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Need
 #define STATE_ALL "state_all"
 #define STATE_ANY "state_any"
 #define STATE_MIXED "state_mixed"
 #define STATE_BEGIN "state_begin"
 #define STATE_END "state_end"
 #define STATE_ATTRIBUTE "state_attribute"
 #define STATE_DATA "state_data"
 
-HTMLEditorCommandBase::HTMLEditorCommandBase() {}
+/*****************************************************************************
+ * mozilla::HTMLEditorCommandBase
+ *****************************************************************************/
 
 NS_IMPL_ISUPPORTS(HTMLEditorCommandBase, nsIControllerCommand)
 
-StateUpdatingCommandBase::StateUpdatingCommandBase(nsAtom* aTagName)
-    : HTMLEditorCommandBase(), mTagName(aTagName) {
-  MOZ_ASSERT(mTagName);
-}
+/*****************************************************************************
+ * mozilla::StateUpdatingCommandBase
+ *****************************************************************************/
 
-StateUpdatingCommandBase::~StateUpdatingCommandBase() {}
+nsRefPtrHashtable<nsCharPtrHashKey, nsAtom>
+    StateUpdatingCommandBase::sTagNameTable;
 
 NS_IMETHODIMP
 StateUpdatingCommandBase::IsCommandEnabled(const char* aCommandName,
                                            nsISupports* refCon,
                                            bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
@@ -80,17 +82,21 @@ StateUpdatingCommandBase::DoCommand(cons
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
-  return ToggleState(MOZ_KnownLive(htmlEditor));
+  RefPtr<nsAtom> tagName = TagName(aCommandName);
+  if (NS_WARN_IF(!tagName)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return ToggleState(tagName, MOZ_KnownLive(htmlEditor));
 }
 
 NS_IMETHODIMP
 StateUpdatingCommandBase::DoCommandParams(const char* aCommandName,
                                           nsICommandParams* aParams,
                                           nsISupports* refCon) {
   return DoCommand(aCommandName, refCon);
 }
@@ -102,19 +108,29 @@ StateUpdatingCommandBase::GetCommandStat
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     return NS_OK;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
-  return GetCurrentState(htmlEditor, aParams);
+  RefPtr<nsAtom> tagName = TagName(aCommandName);
+  if (NS_WARN_IF(!tagName)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+  return GetCurrentState(tagName, htmlEditor, aParams);
 }
 
+/*****************************************************************************
+ * mozilla::PasteNoFormattingCommand
+ *****************************************************************************/
+
+StaticRefPtr<PasteNoFormattingCommand> PasteNoFormattingCommand::sInstance;
+
 NS_IMETHODIMP
 PasteNoFormattingCommand::IsCommandEnabled(const char* aCommandName,
                                            nsISupports* refCon,
                                            bool* outCmdEnabled) {
   NS_ENSURE_ARG_POINTER(outCmdEnabled);
   *outCmdEnabled = false;
 
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
@@ -163,199 +179,211 @@ PasteNoFormattingCommand::GetCommandStat
 
   bool enabled = false;
   nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, enabled);
 }
 
-StyleUpdatingCommand::StyleUpdatingCommand(nsAtom* aTagName)
-    : StateUpdatingCommandBase(aTagName) {}
+/*****************************************************************************
+ * mozilla::StyleUpdatingCommand
+ *****************************************************************************/
 
-nsresult StyleUpdatingCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
+StaticRefPtr<StyleUpdatingCommand> StyleUpdatingCommand::sInstance;
+
+nsresult StyleUpdatingCommand::GetCurrentState(nsAtom* aTagName,
+                                               HTMLEditor* aHTMLEditor,
                                                nsICommandParams* aParams) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool firstOfSelectionHasProp = false;
   bool anyOfSelectionHasProp = false;
   bool allOfSelectionHasProp = false;
 
   nsresult rv = aHTMLEditor->GetInlineProperty(
-      mTagName, nullptr, EmptyString(), &firstOfSelectionHasProp,
+      aTagName, nullptr, EmptyString(), &firstOfSelectionHasProp,
       &anyOfSelectionHasProp, &allOfSelectionHasProp);
 
   nsCommandParams* params = aParams->AsCommandParams();
   params->SetBool(STATE_ENABLED, NS_SUCCEEDED(rv));
   params->SetBool(STATE_ALL, allOfSelectionHasProp);
   params->SetBool(STATE_ANY, anyOfSelectionHasProp);
   params->SetBool(STATE_MIXED, anyOfSelectionHasProp && !allOfSelectionHasProp);
   params->SetBool(STATE_BEGIN, firstOfSelectionHasProp);
   params->SetBool(STATE_END, allOfSelectionHasProp);  // not completely accurate
   return NS_OK;
 }
 
-nsresult StyleUpdatingCommand::ToggleState(HTMLEditor* aHTMLEditor) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+nsresult StyleUpdatingCommand::ToggleState(nsAtom* aTagName,
+                                           HTMLEditor* aHTMLEditor) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<nsCommandParams> params = new nsCommandParams();
 
   // tags "href" and "name" are special cases in the core editor
   // they are used to remove named anchor/link and shouldn't be used for
   // insertion
   bool doTagRemoval;
-  if (mTagName == nsGkAtoms::href || mTagName == nsGkAtoms::name) {
+  if (aTagName == nsGkAtoms::href || aTagName == nsGkAtoms::name) {
     doTagRemoval = true;
   } else {
     // check current selection; set doTagRemoval if formatting should be removed
-    nsresult rv = GetCurrentState(aHTMLEditor, params);
+    nsresult rv = GetCurrentState(aTagName, aHTMLEditor, params);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     ErrorResult error;
     doTagRemoval = params->GetBool(STATE_ALL, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.StealNSResult();
     }
   }
 
   if (doTagRemoval) {
     // Also remove equivalent properties (bug 317093)
     // XXX Why don't we make the following two transactions as an atomic
     //     transaction?  If the element is <b>, <i> or <strike>, user
     //     needs to undo twice.
-    if (mTagName == nsGkAtoms::b) {
+    if (aTagName == nsGkAtoms::b) {
       nsresult rv = aHTMLEditor->RemoveInlinePropertyAsAction(
           *nsGkAtoms::strong, nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-    } else if (mTagName == nsGkAtoms::i) {
+    } else if (aTagName == nsGkAtoms::i) {
       nsresult rv =
           aHTMLEditor->RemoveInlinePropertyAsAction(*nsGkAtoms::em, nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
-    } else if (mTagName == nsGkAtoms::strike) {
+    } else if (aTagName == nsGkAtoms::strike) {
       nsresult rv =
           aHTMLEditor->RemoveInlinePropertyAsAction(*nsGkAtoms::s, nullptr);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
-    nsresult rv = aHTMLEditor->RemoveInlinePropertyAsAction(
-        MOZ_KnownLive(*mTagName), nullptr);
+    nsresult rv = aHTMLEditor->RemoveInlinePropertyAsAction(*aTagName, nullptr);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
-  nsresult rv = aHTMLEditor->SetInlinePropertyAsAction(MOZ_KnownLive(*mTagName),
-                                                       nullptr, EmptyString());
+  nsresult rv =
+      aHTMLEditor->SetInlinePropertyAsAction(*aTagName, nullptr, EmptyString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
-ListCommand::ListCommand(nsAtom* aTagName)
-    : StateUpdatingCommandBase(aTagName) {}
+/*****************************************************************************
+ * mozilla::ListCommand
+ *****************************************************************************/
 
-nsresult ListCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
+StaticRefPtr<ListCommand> ListCommand::sInstance;
+
+nsresult ListCommand::GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
                                       nsICommandParams* aParams) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool bMixed;
   nsAutoString localName;
   nsresult rv = GetListState(aHTMLEditor, &bMixed, localName);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  bool inList = mTagName->Equals(localName);
+  bool inList = aTagName->Equals(localName);
   nsCommandParams* params = aParams->AsCommandParams();
   params->SetBool(STATE_ALL, !bMixed && inList);
   params->SetBool(STATE_MIXED, bMixed);
   params->SetBool(STATE_ENABLED, true);
   return NS_OK;
 }
 
-nsresult ListCommand::ToggleState(HTMLEditor* aHTMLEditor) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+nsresult ListCommand::ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsresult rv;
   RefPtr<nsCommandParams> params = new nsCommandParams();
-  rv = GetCurrentState(aHTMLEditor, params);
+  rv = GetCurrentState(aTagName, aHTMLEditor, params);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   ErrorResult error;
   bool inList = params->GetBool(STATE_ALL, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
-  nsDependentAtomString listType(mTagName);
+  nsDependentAtomString listType(aTagName);
   if (inList) {
     rv = aHTMLEditor->RemoveList(listType);
   } else {
     rv = aHTMLEditor->MakeOrChangeList(listType, false, EmptyString());
   }
 
   return rv;
 }
 
-ListItemCommand::ListItemCommand(nsAtom* aTagName)
-    : StateUpdatingCommandBase(aTagName) {}
+/*****************************************************************************
+ * mozilla::ListItemCommand
+ *****************************************************************************/
 
-nsresult ListItemCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
+StaticRefPtr<ListItemCommand> ListItemCommand::sInstance;
+
+nsresult ListItemCommand::GetCurrentState(nsAtom* aTagName,
+                                          HTMLEditor* aHTMLEditor,
                                           nsICommandParams* aParams) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool bMixed, bLI, bDT, bDD;
   nsresult rv = aHTMLEditor->GetListItemState(&bMixed, &bLI, &bDT, &bDD);
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool inList = false;
   if (!bMixed) {
     if (bLI) {
-      inList = mTagName == nsGkAtoms::li;
+      inList = aTagName == nsGkAtoms::li;
     } else if (bDT) {
-      inList = mTagName == nsGkAtoms::dt;
+      inList = aTagName == nsGkAtoms::dt;
     } else if (bDD) {
-      inList = mTagName == nsGkAtoms::dd;
+      inList = aTagName == nsGkAtoms::dd;
     }
   }
 
   nsCommandParams* params = aParams->AsCommandParams();
   params->SetBool(STATE_ALL, !bMixed && inList);
   params->SetBool(STATE_MIXED, bMixed);
 
   return NS_OK;
 }
 
-nsresult ListItemCommand::ToggleState(HTMLEditor* aHTMLEditor) {
-  if (NS_WARN_IF(!aHTMLEditor)) {
+nsresult ListItemCommand::ToggleState(nsAtom* aTagName,
+                                      HTMLEditor* aHTMLEditor) {
+  if (NS_WARN_IF(!aTagName) || NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  // Need to use mTagName????
+  // Need to use aTagName????
   RefPtr<nsCommandParams> params = new nsCommandParams();
-  GetCurrentState(aHTMLEditor, params);
+  GetCurrentState(aTagName, aHTMLEditor, params);
   ErrorResult error;
   bool inList = params->GetBool(STATE_ALL, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   if (inList) {
     // To remove a list, first get what kind of list we're in
@@ -370,19 +398,25 @@ nsresult ListItemCommand::ToggleState(HT
     }
     return aHTMLEditor->RemoveList(localName);
   }
 
   // Set to the requested paragraph type
   // XXX Note: This actually doesn't work for "LI",
   //    but we currently don't use this for non DL lists anyway.
   // Problem: won't this replace any current block paragraph style?
-  return aHTMLEditor->SetParagraphFormat(nsDependentAtomString(mTagName));
+  return aHTMLEditor->SetParagraphFormat(nsDependentAtomString(aTagName));
 }
 
+/*****************************************************************************
+ * mozilla::RemoveListCommand
+ *****************************************************************************/
+
+StaticRefPtr<RemoveListCommand> RemoveListCommand::sInstance;
+
 NS_IMETHODIMP
 RemoveListCommand::IsCommandEnabled(const char* aCommandName,
                                     nsISupports* refCon, bool* outCmdEnabled) {
   *outCmdEnabled = false;
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     return NS_OK;
   }
@@ -434,16 +468,22 @@ NS_IMETHODIMP
 RemoveListCommand::GetCommandStateParams(const char* aCommandName,
                                          nsICommandParams* aParams,
                                          nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
+/*****************************************************************************
+ * mozilla::IndentCommand
+ *****************************************************************************/
+
+StaticRefPtr<IndentCommand> IndentCommand::sInstance;
+
 NS_IMETHODIMP
 IndentCommand::IsCommandEnabled(const char* aCommandName, nsISupports* refCon,
                                 bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
   }
@@ -480,17 +520,21 @@ NS_IMETHODIMP
 IndentCommand::GetCommandStateParams(const char* aCommandName,
                                      nsICommandParams* aParams,
                                      nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
-// OUTDENT
+/*****************************************************************************
+ * mozilla::OutdentCommand
+ *****************************************************************************/
+
+StaticRefPtr<OutdentCommand> OutdentCommand::sInstance;
 
 NS_IMETHODIMP
 OutdentCommand::IsCommandEnabled(const char* aCommandName, nsISupports* refCon,
                                  bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
@@ -529,19 +573,19 @@ NS_IMETHODIMP
 OutdentCommand::GetCommandStateParams(const char* aCommandName,
                                       nsICommandParams* aParams,
                                       nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
-MultiStateCommandBase::MultiStateCommandBase() : HTMLEditorCommandBase() {}
-
-MultiStateCommandBase::~MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::MultiStateCommandBase
+ *****************************************************************************/
 
 NS_IMETHODIMP
 MultiStateCommandBase::IsCommandEnabled(const char* aCommandName,
                                         nsISupports* refCon,
                                         bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
@@ -604,17 +648,21 @@ MultiStateCommandBase::GetCommandStatePa
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
   return GetCurrentState(MOZ_KnownLive(htmlEditor), aParams);
 }
 
-ParagraphStateCommand::ParagraphStateCommand() : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::ParagraphStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<ParagraphStateCommand> ParagraphStateCommand::sInstance;
 
 nsresult ParagraphStateCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
                                                 nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool outMixed;
@@ -633,17 +681,21 @@ nsresult ParagraphStateCommand::GetCurre
 nsresult ParagraphStateCommand::SetState(HTMLEditor* aHTMLEditor,
                                          const nsString& newState) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
   return aHTMLEditor->SetParagraphFormat(newState);
 }
 
-FontFaceStateCommand::FontFaceStateCommand() : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::FontFaceStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<FontFaceStateCommand> FontFaceStateCommand::sInstance;
 
 nsresult FontFaceStateCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
                                                nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsAutoString outStateString;
@@ -698,17 +750,21 @@ nsresult FontFaceStateCommand::SetState(
   rv = aHTMLEditor->SetInlinePropertyAsAction(*nsGkAtoms::font, nsGkAtoms::face,
                                               newState);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
-FontSizeStateCommand::FontSizeStateCommand() : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::FontSizeStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<FontSizeStateCommand> FontSizeStateCommand::sInstance;
 
 nsresult FontSizeStateCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
                                                nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsAutoString outStateString;
@@ -767,17 +823,21 @@ nsresult FontSizeStateCommand::SetState(
 
   rv = aHTMLEditor->RemoveInlinePropertyAsAction(*nsGkAtoms::small, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
-FontColorStateCommand::FontColorStateCommand() : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::FontColorStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<FontColorStateCommand> FontColorStateCommand::sInstance;
 
 nsresult FontColorStateCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
                                                 nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool outMixed;
@@ -813,18 +873,21 @@ nsresult FontColorStateCommand::SetState
   nsresult rv = aHTMLEditor->SetInlinePropertyAsAction(
       *nsGkAtoms::font, nsGkAtoms::color, newState);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
-HighlightColorStateCommand::HighlightColorStateCommand()
-    : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::HighlightColorStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<HighlightColorStateCommand> HighlightColorStateCommand::sInstance;
 
 nsresult HighlightColorStateCommand::GetCurrentState(
     HTMLEditor* aHTMLEditor, nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool outMixed;
@@ -873,18 +936,22 @@ HighlightColorStateCommand::IsCommandEna
     return NS_OK;
   }
   mozilla::EditorBase* editorBase = editor->AsEditorBase();
   MOZ_ASSERT(editorBase);
   *outCmdEnabled = editorBase->IsSelectionEditable();
   return NS_OK;
 }
 
-BackgroundColorStateCommand::BackgroundColorStateCommand()
-    : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::BackgroundColorStateCommand
+ *****************************************************************************/
+
+StaticRefPtr<BackgroundColorStateCommand>
+    BackgroundColorStateCommand::sInstance;
 
 nsresult BackgroundColorStateCommand::GetCurrentState(
     HTMLEditor* aHTMLEditor, nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   bool outMixed;
@@ -903,17 +970,21 @@ nsresult BackgroundColorStateCommand::Ge
 nsresult BackgroundColorStateCommand::SetState(HTMLEditor* aHTMLEditor,
                                                const nsString& newState) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
   return aHTMLEditor->SetBackgroundColor(newState);
 }
 
-AlignCommand::AlignCommand() : MultiStateCommandBase() {}
+/*****************************************************************************
+ * mozilla::AlignCommand
+ *****************************************************************************/
+
+StaticRefPtr<AlignCommand> AlignCommand::sInstance;
 
 nsresult AlignCommand::GetCurrentState(HTMLEditor* aHTMLEditor,
                                        nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsIHTMLEditor::EAlignment firstAlign;
@@ -952,18 +1023,21 @@ nsresult AlignCommand::GetCurrentState(H
 nsresult AlignCommand::SetState(HTMLEditor* aHTMLEditor,
                                 const nsString& newState) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
   return aHTMLEditor->Align(newState);
 }
 
-AbsolutePositioningCommand::AbsolutePositioningCommand()
-    : StateUpdatingCommandBase(nsGkAtoms::_empty) {}
+/*****************************************************************************
+ * mozilla::AbsolutePositioningCommand
+ *****************************************************************************/
+
+StaticRefPtr<AbsolutePositioningCommand> AbsolutePositioningCommand::sInstance;
 
 NS_IMETHODIMP
 AbsolutePositioningCommand::IsCommandEnabled(const char* aCommandName,
                                              nsISupports* aCommandRefCon,
                                              bool* aOutCmdEnabled) {
   *aOutCmdEnabled = false;
 
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aCommandRefCon);
@@ -977,17 +1051,17 @@ AbsolutePositioningCommand::IsCommandEna
   if (!htmlEditor->IsSelectionEditable()) {
     return NS_OK;
   }
   *aOutCmdEnabled = htmlEditor->IsAbsolutePositionEditorEnabled();
   return NS_OK;
 }
 
 nsresult AbsolutePositioningCommand::GetCurrentState(
-    HTMLEditor* aHTMLEditor, nsICommandParams* aParams) {
+    nsAtom* aTagName, HTMLEditor* aHTMLEditor, nsICommandParams* aParams) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsCommandParams* params = aParams->AsCommandParams();
   if (!aHTMLEditor->IsAbsolutePositionEditorEnabled()) {
     params->SetBool(STATE_MIXED, false);
     params->SetCString(STATE_ATTRIBUTE, EmptyCString());
@@ -997,26 +1071,33 @@ nsresult AbsolutePositioningCommand::Get
   RefPtr<Element> container =
       aHTMLEditor->GetAbsolutelyPositionedSelectionContainer();
   params->SetBool(STATE_MIXED, false);
   params->SetCString(STATE_ATTRIBUTE, container ? NS_LITERAL_CSTRING("absolute")
                                                 : EmptyCString());
   return NS_OK;
 }
 
-nsresult AbsolutePositioningCommand::ToggleState(HTMLEditor* aHTMLEditor) {
+nsresult AbsolutePositioningCommand::ToggleState(nsAtom* aTagName,
+                                                 HTMLEditor* aHTMLEditor) {
   if (NS_WARN_IF(!aHTMLEditor)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<Element> container =
       aHTMLEditor->GetAbsolutelyPositionedSelectionContainer();
   return aHTMLEditor->SetSelectionToAbsoluteOrStatic(!container);
 }
 
+/*****************************************************************************
+ * mozilla::DecreaseZIndexCommand
+ *****************************************************************************/
+
+StaticRefPtr<DecreaseZIndexCommand> DecreaseZIndexCommand::sInstance;
+
 NS_IMETHODIMP
 DecreaseZIndexCommand::IsCommandEnabled(const char* aCommandName,
                                         nsISupports* aRefCon,
                                         bool* aOutCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
@@ -1070,16 +1151,22 @@ DecreaseZIndexCommand::GetCommandStatePa
 
   bool enabled = false;
   nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, enabled);
 }
 
+/*****************************************************************************
+ * mozilla::IncreaseZIndexCommand
+ *****************************************************************************/
+
+StaticRefPtr<IncreaseZIndexCommand> IncreaseZIndexCommand::sInstance;
+
 NS_IMETHODIMP
 IncreaseZIndexCommand::IsCommandEnabled(const char* aCommandName,
                                         nsISupports* aRefCon,
                                         bool* aOutCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(aRefCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
@@ -1126,16 +1213,22 @@ IncreaseZIndexCommand::GetCommandStatePa
 
   bool enabled = false;
   nsresult rv = IsCommandEnabled(aCommandName, refCon, &enabled);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, enabled);
 }
 
+/*****************************************************************************
+ * mozilla::RemoveStylesCommand
+ *****************************************************************************/
+
+StaticRefPtr<RemoveStylesCommand> RemoveStylesCommand::sInstance;
+
 NS_IMETHODIMP
 RemoveStylesCommand::IsCommandEnabled(const char* aCommandName,
                                       nsISupports* refCon,
                                       bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
@@ -1171,16 +1264,22 @@ NS_IMETHODIMP
 RemoveStylesCommand::GetCommandStateParams(const char* aCommandName,
                                            nsICommandParams* aParams,
                                            nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
+/*****************************************************************************
+ * mozilla::IncreaseFontSizeCommand
+ *****************************************************************************/
+
+StaticRefPtr<IncreaseFontSizeCommand> IncreaseFontSizeCommand::sInstance;
+
 NS_IMETHODIMP
 IncreaseFontSizeCommand::IsCommandEnabled(const char* aCommandName,
                                           nsISupports* refCon,
                                           bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
@@ -1217,16 +1316,22 @@ NS_IMETHODIMP
 IncreaseFontSizeCommand::GetCommandStateParams(const char* aCommandName,
                                                nsICommandParams* aParams,
                                                nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
+/*****************************************************************************
+ * mozilla::DecreaseFontSizeCommand
+ *****************************************************************************/
+
+StaticRefPtr<DecreaseFontSizeCommand> DecreaseFontSizeCommand::sInstance;
+
 NS_IMETHODIMP
 DecreaseFontSizeCommand::IsCommandEnabled(const char* aCommandName,
                                           nsISupports* refCon,
                                           bool* outCmdEnabled) {
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
@@ -1263,16 +1368,22 @@ NS_IMETHODIMP
 DecreaseFontSizeCommand::GetCommandStateParams(const char* aCommandName,
                                                nsICommandParams* aParams,
                                                nsISupports* refCon) {
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
+/*****************************************************************************
+ * mozilla::InsertHTMLCommand
+ *****************************************************************************/
+
+StaticRefPtr<InsertHTMLCommand> InsertHTMLCommand::sInstance;
+
 NS_IMETHODIMP
 InsertHTMLCommand::IsCommandEnabled(const char* aCommandName,
                                     nsISupports* refCon, bool* outCmdEnabled) {
   NS_ENSURE_ARG_POINTER(outCmdEnabled);
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
     return NS_OK;
@@ -1334,22 +1445,22 @@ InsertHTMLCommand::GetCommandStateParams
   NS_ENSURE_ARG_POINTER(aParams);
   NS_ENSURE_ARG_POINTER(refCon);
 
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
-InsertTagCommand::InsertTagCommand(nsAtom* aTagName)
-    : HTMLEditorCommandBase(), mTagName(aTagName) {
-  MOZ_ASSERT(mTagName);
-}
+/*****************************************************************************
+ * mozilla::InsertTagCommand
+ *****************************************************************************/
 
-InsertTagCommand::~InsertTagCommand() {}
+StaticRefPtr<InsertTagCommand> InsertTagCommand::sInstance;
+nsRefPtrHashtable<nsCharPtrHashKey, nsAtom> InsertTagCommand::sTagNameTable;
 
 NS_IMETHODIMP
 InsertTagCommand::IsCommandEnabled(const char* aCommandName,
                                    nsISupports* refCon, bool* outCmdEnabled) {
   NS_ENSURE_ARG_POINTER(outCmdEnabled);
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (!editor) {
     *outCmdEnabled = false;
@@ -1359,53 +1470,66 @@ InsertTagCommand::IsCommandEnabled(const
   MOZ_ASSERT(editorBase);
   *outCmdEnabled = editorBase->IsSelectionEditable();
   return NS_OK;
 }
 
 // corresponding STATE_ATTRIBUTE is: src (img) and href (a)
 NS_IMETHODIMP
 InsertTagCommand::DoCommand(const char* aCmdName, nsISupports* refCon) {
-  NS_ENSURE_TRUE(mTagName == nsGkAtoms::hr, NS_ERROR_NOT_IMPLEMENTED);
+  if (NS_WARN_IF(!aCmdName) || NS_WARN_IF(!refCon)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  RefPtr<nsAtom> tagName = TagName(aCmdName);
+  if (NS_WARN_IF(tagName != nsGkAtoms::hr)) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
 
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<Element> newElement =
-      MOZ_KnownLive(htmlEditor)
-          ->CreateElementWithDefaults(MOZ_KnownLive(*mTagName));
+      MOZ_KnownLive(htmlEditor)->CreateElementWithDefaults(*tagName);
   if (NS_WARN_IF(!newElement)) {
     return NS_ERROR_FAILURE;
   }
   nsresult rv =
       MOZ_KnownLive(htmlEditor)->InsertElementAtSelection(newElement, true);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InsertTagCommand::DoCommandParams(const char* aCommandName,
                                   nsICommandParams* aParams,
                                   nsISupports* refCon) {
-  NS_ENSURE_ARG_POINTER(refCon);
+  if (NS_WARN_IF(!aCommandName) || NS_WARN_IF(!refCon)) {
+    return NS_ERROR_INVALID_ARG;
+  }
 
   // inserting an hr shouldn't have an parameters, just call DoCommand for that
-  if (mTagName == nsGkAtoms::hr) {
+  if (!strcmp(aCommandName, "cmd_insertHR")) {
     return DoCommand(aCommandName, refCon);
   }
 
-  NS_ENSURE_ARG_POINTER(aParams);
+  if (NS_WARN_IF(!aParams)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  RefPtr<nsAtom> tagName = TagName(aCommandName);
+  if (NS_WARN_IF(!tagName)) {
+    return NS_ERROR_UNEXPECTED;
+  }
 
   nsCOMPtr<nsIEditor> editor = do_QueryInterface(refCon);
   if (NS_WARN_IF(!editor)) {
     return NS_ERROR_FAILURE;
   }
   mozilla::HTMLEditor* htmlEditor = editor->AsHTMLEditor();
   if (NS_WARN_IF(!htmlEditor)) {
     return NS_ERROR_FAILURE;
@@ -1420,39 +1544,38 @@ InsertTagCommand::DoCommandParams(const 
     return rv;
   }
   if (NS_WARN_IF(value.IsEmpty())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   // filter out tags we don't know how to insert
   nsAtom* attribute = nullptr;
-  if (mTagName == nsGkAtoms::a) {
+  if (tagName == nsGkAtoms::a) {
     attribute = nsGkAtoms::href;
-  } else if (mTagName == nsGkAtoms::img) {
+  } else if (tagName == nsGkAtoms::img) {
     attribute = nsGkAtoms::src;
   } else {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   RefPtr<Element> newElement =
-      MOZ_KnownLive(htmlEditor)
-          ->CreateElementWithDefaults(MOZ_KnownLive(*mTagName));
+      MOZ_KnownLive(htmlEditor)->CreateElementWithDefaults(*tagName);
   if (NS_WARN_IF(!newElement)) {
     return NS_ERROR_FAILURE;
   }
 
   ErrorResult err;
   newElement->SetAttr(attribute, value, err);
   if (NS_WARN_IF(err.Failed())) {
     return err.StealNSResult();
   }
 
   // do actual insertion
-  if (mTagName == nsGkAtoms::a) {
+  if (tagName == nsGkAtoms::a) {
     rv = MOZ_KnownLive(htmlEditor)->InsertLinkAroundSelection(newElement);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return NS_OK;
   }
 
   rv = MOZ_KnownLive(htmlEditor)->InsertElementAtSelection(newElement, true);
@@ -1469,19 +1592,19 @@ InsertTagCommand::GetCommandStateParams(
   NS_ENSURE_ARG_POINTER(aParams);
   NS_ENSURE_ARG_POINTER(refCon);
 
   bool outCmdEnabled = false;
   IsCommandEnabled(aCommandName, refCon, &outCmdEnabled);
   return aParams->AsCommandParams()->SetBool(STATE_ENABLED, outCmdEnabled);
 }
 
-/****************************/
-// HELPER METHODS
-/****************************/
+/*****************************************************************************
+ * Helper methods
+ *****************************************************************************/
 
 static nsresult GetListState(HTMLEditor* aHTMLEditor, bool* aMixed,
                              nsAString& aLocalName)
     MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
   MOZ_ASSERT(aHTMLEditor);
   MOZ_ASSERT(aMixed);
 
   *aMixed = false;
--- a/editor/libeditor/HTMLEditorCommands.h
+++ b/editor/libeditor/HTMLEditorCommands.h
@@ -1,281 +1,393 @@
 /* -*- Mode: C++; 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/. */
 
 #ifndef mozilla_HTMLEditorCommands_h_
 #define mozilla_HTMLEditorCommands_h_
 
+#include "mozilla/StaticPtr.h"
 #include "nsIControllerCommand.h"
 #include "nsISupportsImpl.h"  // for NS_DECL_ISUPPORTS_INHERITED, etc
+#include "nsRefPtrHashtable.h"
 #include "nsStringFwd.h"
 #include "nscore.h"  // for nsresult, NS_IMETHOD
 
 class nsAtom;
 class nsICommandParams;
 class nsISupports;
 
 namespace mozilla {
 class HTMLEditor;
 
-// This is a virtual base class for commands registered with the composer
-// controller. Note that such commands are instantiated once per composer, so
-// can store state. Also note that IsCommandEnabled can be called with an editor
-// that may not have an editor yet (because the document is loading). Most
-// commands will want to return false in this case. Don't hold on to any
-// references to the editor or document from your command. This will cause
-// leaks. Also, be aware that the document the editor is editing can change
-// under you (if the user Reverts the file, for instance).
+// This is a base class for commands registered with window which includes
+// HTMLEditor.  Like editor commands, these command classes are also designed
+// as singleton classes.  So, MUST be stateless.  Also note that
+// IsCommandEnabled can be called with an editor that may not have an editor
+// yet (because the document is loading).  Most commands will want to return
+// false in this case.  Don't hold on to any references to the editor or
+// document from your command.  This will cause leaks.  Also, be aware that
+// the document the editor is editing can change under you (if the user
+// Reverts the file, for instance).
 class HTMLEditorCommandBase : public nsIControllerCommand {
- protected:
-  virtual ~HTMLEditorCommandBase() {}
-
  public:
-  HTMLEditorCommandBase();
-
   // nsISupports
   NS_DECL_ISUPPORTS
+
+ protected:
+  HTMLEditorCommandBase() = default;
+  virtual ~HTMLEditorCommandBase() = default;
 };
 
-#define NS_DECL_COMPOSER_COMMAND(_cmd)              \
+#define NS_DECL_HTML_EDITOR_COMMAND(_cmd)           \
   class _cmd final : public HTMLEditorCommandBase { \
    public:                                          \
     NS_DECL_NSICONTROLLERCOMMAND                    \
+                                                    \
+    static _cmd* GetInstance() {                    \
+      if (!sInstance) {                             \
+        sInstance = new _cmd();                     \
+      }                                             \
+      return sInstance;                             \
+    }                                               \
+                                                    \
+    static void Shutdown() { sInstance = nullptr; } \
+                                                    \
+   private:                                         \
+    static StaticRefPtr<_cmd> sInstance;            \
   };
 
+#define NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(_cmd) \
+ public:                                            \
+  static _cmd* GetInstance() {                      \
+    if (!sInstance) {                               \
+      sInstance = new _cmd();                       \
+    }                                               \
+    return sInstance;                               \
+  }                                                 \
+                                                    \
+  static void Shutdown() { sInstance = nullptr; }   \
+                                                    \
+ private:                                           \
+  static StaticRefPtr<_cmd> sInstance;
+
 // virtual base class for commands that need to save and update Boolean state
 // (like styles etc)
 class StateUpdatingCommandBase : public HTMLEditorCommandBase {
  public:
-  explicit StateUpdatingCommandBase(nsAtom* aTagName);
-
   NS_INLINE_DECL_REFCOUNTING_INHERITED(StateUpdatingCommandBase,
                                        HTMLEditorCommandBase)
 
   NS_DECL_NSICONTROLLERCOMMAND
 
  protected:
-  virtual ~StateUpdatingCommandBase();
+  StateUpdatingCommandBase() = default;
+  virtual ~StateUpdatingCommandBase() { sTagNameTable.Clear(); }
 
   // get the current state (on or off) for this style or block format
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       virtual nsresult
-      GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) = 0;
+      GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
+                      nsICommandParams* aParams) = 0;
 
   // add/remove the style
   MOZ_CAN_RUN_SCRIPT
-  virtual nsresult ToggleState(HTMLEditor* aHTMLEditor) = 0;
+  virtual nsresult ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) = 0;
 
- protected:
-  nsAtom* mTagName;
+  static already_AddRefed<nsAtom> TagName(const char* aCommandName) {
+    MOZ_DIAGNOSTIC_ASSERT(aCommandName);
+    if (NS_WARN_IF(!aCommandName)) {
+      return nullptr;
+    }
+    if (!sTagNameTable.Count()) {
+      sTagNameTable.Put("cmd_bold", nsGkAtoms::b);
+      sTagNameTable.Put("cmd_italic", nsGkAtoms::i);
+      sTagNameTable.Put("cmd_underline", nsGkAtoms::u);
+      sTagNameTable.Put("cmd_tt", nsGkAtoms::tt);
+      sTagNameTable.Put("cmd_strikethrough", nsGkAtoms::strike);
+      sTagNameTable.Put("cmd_superscript", nsGkAtoms::sup);
+      sTagNameTable.Put("cmd_subscript", nsGkAtoms::sub);
+      sTagNameTable.Put("cmd_nobreak", nsGkAtoms::nobr);
+      sTagNameTable.Put("cmd_em", nsGkAtoms::em);
+      sTagNameTable.Put("cmd_strong", nsGkAtoms::strong);
+      sTagNameTable.Put("cmd_cite", nsGkAtoms::cite);
+      sTagNameTable.Put("cmd_abbr", nsGkAtoms::abbr);
+      sTagNameTable.Put("cmd_acronym", nsGkAtoms::acronym);
+      sTagNameTable.Put("cmd_code", nsGkAtoms::code);
+      sTagNameTable.Put("cmd_samp", nsGkAtoms::samp);
+      sTagNameTable.Put("cmd_var", nsGkAtoms::var);
+      sTagNameTable.Put("cmd_removeLinks", nsGkAtoms::href);
+      sTagNameTable.Put("cmd_ol", nsGkAtoms::ol);
+      sTagNameTable.Put("cmd_ul", nsGkAtoms::ul);
+      sTagNameTable.Put("cmd_dt", nsGkAtoms::dt);
+      sTagNameTable.Put("cmd_dd", nsGkAtoms::dd);
+      sTagNameTable.Put("cmd_absPos", nsGkAtoms::_empty);
+    }
+    RefPtr<nsAtom> tagName = sTagNameTable.Get(aCommandName);
+    MOZ_DIAGNOSTIC_ASSERT(tagName);
+    return tagName.forget();
+  }
+
+  static nsRefPtrHashtable<nsCharPtrHashKey, nsAtom> sTagNameTable;
 };
 
 // Shared class for the various style updating commands like bold, italics etc.
 // Suitable for commands whose state is either 'on' or 'off'.
 class StyleUpdatingCommand final : public StateUpdatingCommandBase {
  public:
-  explicit StyleUpdatingCommand(nsAtom* aTagName);
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(StyleUpdatingCommand)
 
  protected:
+  StyleUpdatingCommand() = default;
+  virtual ~StyleUpdatingCommand() = default;
+
   // get the current state (on or off) for this style or block format
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
-      GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
+      GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
+                      nsICommandParams* aParams) final;
 
   // add/remove the style
   MOZ_CAN_RUN_SCRIPT
-  nsresult ToggleState(HTMLEditor* aHTMLEditor) final;
+  nsresult ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) final;
 };
 
 class InsertTagCommand final : public HTMLEditorCommandBase {
  public:
-  explicit InsertTagCommand(nsAtom* aTagName);
-
   NS_INLINE_DECL_REFCOUNTING_INHERITED(InsertTagCommand, HTMLEditorCommandBase)
 
   NS_DECL_NSICONTROLLERCOMMAND
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(InsertTagCommand)
 
  protected:
-  virtual ~InsertTagCommand();
+  InsertTagCommand() = default;
+  virtual ~InsertTagCommand() { sTagNameTable.Clear(); }
 
-  nsAtom* mTagName;
+  static already_AddRefed<nsAtom> TagName(const char* aCommandName) {
+    MOZ_DIAGNOSTIC_ASSERT(aCommandName);
+    if (NS_WARN_IF(!aCommandName)) {
+      return nullptr;
+    }
+    if (!sTagNameTable.Count()) {
+      sTagNameTable.Put("cmd_insertLinkNoUI", nsGkAtoms::a);
+      sTagNameTable.Put("cmd_insertImageNoUI", nsGkAtoms::img);
+      sTagNameTable.Put("cmd_insertHR", nsGkAtoms::hr);
+    }
+    RefPtr<nsAtom> tagName = sTagNameTable.Get(aCommandName);
+    MOZ_DIAGNOSTIC_ASSERT(tagName);
+    return tagName.forget();
+  }
+
+  static nsRefPtrHashtable<nsCharPtrHashKey, nsAtom> sTagNameTable;
 };
 
 class ListCommand final : public StateUpdatingCommandBase {
  public:
-  explicit ListCommand(nsAtom* aTagName);
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(ListCommand)
 
  protected:
+  ListCommand() = default;
+  virtual ~ListCommand() = default;
+
   // get the current state (on or off) for this style or block format
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
-      GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
+      GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
+                      nsICommandParams* aParams) final;
 
   // add/remove the style
   MOZ_CAN_RUN_SCRIPT
-  nsresult ToggleState(HTMLEditor* aHTMLEditor) final;
+  nsresult ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) final;
 };
 
 class ListItemCommand final : public StateUpdatingCommandBase {
  public:
-  explicit ListItemCommand(nsAtom* aTagName);
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(ListItemCommand)
 
  protected:
+  ListItemCommand() = default;
+  virtual ~ListItemCommand() = default;
+
   // get the current state (on or off) for this style or block format
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
-      GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
+      GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
+                      nsICommandParams* aParams) final;
 
   // add/remove the style
   MOZ_CAN_RUN_SCRIPT
-  nsresult ToggleState(HTMLEditor* aHTMLEditor) final;
+  nsresult ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) final;
 };
 
 // Base class for commands whose state consists of a string (e.g. para format)
 class MultiStateCommandBase : public HTMLEditorCommandBase {
  public:
-  MultiStateCommandBase();
-
   NS_INLINE_DECL_REFCOUNTING_INHERITED(MultiStateCommandBase,
                                        HTMLEditorCommandBase)
   NS_DECL_NSICONTROLLERCOMMAND
 
  protected:
-  virtual ~MultiStateCommandBase();
+  MultiStateCommandBase() = default;
+  virtual ~MultiStateCommandBase() = default;
 
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       virtual nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) = 0;
   MOZ_CAN_RUN_SCRIPT
   virtual nsresult SetState(HTMLEditor* aHTMLEditor,
                             const nsString& newState) = 0;
 };
 
 class ParagraphStateCommand final : public MultiStateCommandBase {
  public:
-  ParagraphStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(ParagraphStateCommand)
 
  protected:
+  ParagraphStateCommand() = default;
+  virtual ~ParagraphStateCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class FontFaceStateCommand final : public MultiStateCommandBase {
  public:
-  FontFaceStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(FontFaceStateCommand)
 
  protected:
+  FontFaceStateCommand() = default;
+  virtual ~FontFaceStateCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class FontSizeStateCommand final : public MultiStateCommandBase {
  public:
-  FontSizeStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(FontSizeStateCommand)
 
  protected:
+  FontSizeStateCommand() = default;
+  virtual ~FontSizeStateCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class HighlightColorStateCommand final : public MultiStateCommandBase {
  public:
-  HighlightColorStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(HighlightColorStateCommand)
 
  protected:
+  HighlightColorStateCommand() = default;
+  virtual ~HighlightColorStateCommand() = default;
+
   NS_IMETHOD IsCommandEnabled(const char* aCommandName,
                               nsISupports* aCommandRefCon, bool* _retval) final;
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class FontColorStateCommand final : public MultiStateCommandBase {
  public:
-  FontColorStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(FontColorStateCommand)
 
  protected:
+  FontColorStateCommand() = default;
+  virtual ~FontColorStateCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class AlignCommand final : public MultiStateCommandBase {
  public:
-  AlignCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(AlignCommand)
 
  protected:
+  AlignCommand() = default;
+  virtual ~AlignCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class BackgroundColorStateCommand final : public MultiStateCommandBase {
  public:
-  BackgroundColorStateCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(BackgroundColorStateCommand)
 
  protected:
+  BackgroundColorStateCommand() = default;
+  virtual ~BackgroundColorStateCommand() = default;
+
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
       GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
   nsresult SetState(HTMLEditor* aHTMLEditor, const nsString& newState) final;
 };
 
 class AbsolutePositioningCommand final : public StateUpdatingCommandBase {
  public:
-  AbsolutePositioningCommand();
+  NS_DECL_HTML_EDITOR_COMMAND_SINGLETON(AbsolutePositioningCommand)
 
  protected:
+  AbsolutePositioningCommand() = default;
+  virtual ~AbsolutePositioningCommand() = default;
+
   NS_IMETHOD IsCommandEnabled(const char* aCommandName,
                               nsISupports* aCommandRefCon, bool* _retval) final;
   MOZ_CAN_RUN_SCRIPT_BOUNDARY  // XXX Needs to change nsIControllerCommand.idl
       nsresult
-      GetCurrentState(HTMLEditor* aHTMLEditor, nsICommandParams* aParams) final;
+      GetCurrentState(nsAtom* aTagName, HTMLEditor* aHTMLEditor,
+                      nsICommandParams* aParams) final;
   MOZ_CAN_RUN_SCRIPT
-  nsresult ToggleState(HTMLEditor* aHTMLEditor) final;
+  nsresult ToggleState(nsAtom* aTagName, HTMLEditor* aHTMLEditor) final;
 };
 
 // composer commands
 
-NS_DECL_COMPOSER_COMMAND(DocumentStateCommand)
-NS_DECL_COMPOSER_COMMAND(SetDocumentStateCommand)
+NS_DECL_HTML_EDITOR_COMMAND(DocumentStateCommand)
+NS_DECL_HTML_EDITOR_COMMAND(SetDocumentStateCommand)
 
-NS_DECL_COMPOSER_COMMAND(DecreaseZIndexCommand)
-NS_DECL_COMPOSER_COMMAND(IncreaseZIndexCommand)
+NS_DECL_HTML_EDITOR_COMMAND(DecreaseZIndexCommand)
+NS_DECL_HTML_EDITOR_COMMAND(IncreaseZIndexCommand)
 
 // Generic commands
 
 // Edit menu
-NS_DECL_COMPOSER_COMMAND(PasteNoFormattingCommand)
+NS_DECL_HTML_EDITOR_COMMAND(PasteNoFormattingCommand)
 
 // Block transformations
-NS_DECL_COMPOSER_COMMAND(IndentCommand)
-NS_DECL_COMPOSER_COMMAND(OutdentCommand)
+NS_DECL_HTML_EDITOR_COMMAND(IndentCommand)
+NS_DECL_HTML_EDITOR_COMMAND(OutdentCommand)
 
-NS_DECL_COMPOSER_COMMAND(RemoveListCommand)
-NS_DECL_COMPOSER_COMMAND(RemoveStylesCommand)
-NS_DECL_COMPOSER_COMMAND(IncreaseFontSizeCommand)
-NS_DECL_COMPOSER_COMMAND(DecreaseFontSizeCommand)
+NS_DECL_HTML_EDITOR_COMMAND(RemoveListCommand)
+NS_DECL_HTML_EDITOR_COMMAND(RemoveStylesCommand)
+NS_DECL_HTML_EDITOR_COMMAND(IncreaseFontSizeCommand)
+NS_DECL_HTML_EDITOR_COMMAND(DecreaseFontSizeCommand)
 
 // Insert content commands
-NS_DECL_COMPOSER_COMMAND(InsertHTMLCommand)
+NS_DECL_HTML_EDITOR_COMMAND(InsertHTMLCommand)
 
 }  // namespace mozilla
 
 #endif  // mozilla_HTMLEditorCommands_h_
--- a/editor/libeditor/HTMLEditorController.cpp
+++ b/editor/libeditor/HTMLEditorController.cpp
@@ -4,147 +4,137 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/HTMLEditorController.h"
 
 #include "mozilla/HTMLEditorCommands.h"  // for StyleUpdatingCommand, etc
 #include "mozilla/mozalloc.h"            // for operator new
 #include "nsControllerCommandTable.h"    // for nsControllerCommandTable
 #include "nsError.h"                     // for NS_OK
-#include "nsGkAtoms.h"                   // for nsGkAtoms, nsGkAtoms::a, etc
 
 namespace mozilla {
 
-#define NS_REGISTER_ONE_COMMAND(_cmdClass, _cmdName)            \
-  {                                                             \
-    _cmdClass *theCmd = new _cmdClass();                        \
-    aCommandTable->RegisterCommand(                             \
-        _cmdName, static_cast<nsIControllerCommand *>(theCmd)); \
-  }
-
-#define NS_REGISTER_FIRST_COMMAND(_cmdClass, _cmdName) \
-  {                                                    \
-    _cmdClass *theCmd = new _cmdClass();               \
-    aCommandTable->RegisterCommand(                    \
-        _cmdName, static_cast<nsIControllerCommand *>(theCmd));
-
-#define NS_REGISTER_NEXT_COMMAND(_cmdClass, _cmdName) \
-  aCommandTable->RegisterCommand(_cmdName,            \
-                                 static_cast<nsIControllerCommand *>(theCmd));
-
-#define NS_REGISTER_LAST_COMMAND(_cmdClass, _cmdName)                          \
-  aCommandTable->RegisterCommand(_cmdName,                                     \
-                                 static_cast<nsIControllerCommand *>(theCmd)); \
-  }
-
-#define NS_REGISTER_STYLE_COMMAND(_cmdClass, _cmdName, _styleTag) \
-  {                                                               \
-    _cmdClass *theCmd = new _cmdClass(_styleTag);                 \
-    aCommandTable->RegisterCommand(                               \
-        _cmdName, static_cast<nsIControllerCommand *>(theCmd));   \
-  }
-
-#define NS_REGISTER_TAG_COMMAND(_cmdClass, _cmdName, _tagName)  \
-  {                                                             \
-    _cmdClass *theCmd = new _cmdClass(_tagName);                \
-    aCommandTable->RegisterCommand(                             \
-        _cmdName, static_cast<nsIControllerCommand *>(theCmd)); \
+#define NS_REGISTER_COMMAND(_cmdClass, _cmdName)                        \
+  {                                                                     \
+    aCommandTable->RegisterCommand(                                     \
+        _cmdName,                                                       \
+        static_cast<nsIControllerCommand *>(_cmdClass::GetInstance())); \
   }
 
 // static
 nsresult HTMLEditorController::RegisterEditorDocStateCommands(
     nsControllerCommandTable *aCommandTable) {
   // observer commands for document state
-  NS_REGISTER_FIRST_COMMAND(DocumentStateCommand, "obs_documentCreated")
-  NS_REGISTER_NEXT_COMMAND(DocumentStateCommand, "obs_documentWillBeDestroyed")
-  NS_REGISTER_LAST_COMMAND(DocumentStateCommand, "obs_documentLocationChanged")
+  NS_REGISTER_COMMAND(DocumentStateCommand, "obs_documentCreated")
+  NS_REGISTER_COMMAND(DocumentStateCommand, "obs_documentWillBeDestroyed")
+  NS_REGISTER_COMMAND(DocumentStateCommand, "obs_documentLocationChanged")
 
   // commands that may get or change state
-  NS_REGISTER_FIRST_COMMAND(SetDocumentStateCommand, "cmd_setDocumentModified")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand, "cmd_setDocumentUseCSS")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand, "cmd_setDocumentReadOnly")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand, "cmd_insertBrOnReturn")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand,
-                           "cmd_defaultParagraphSeparator")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand, "cmd_enableObjectResizing")
-  NS_REGISTER_NEXT_COMMAND(SetDocumentStateCommand,
-                           "cmd_enableInlineTableEditing")
-  NS_REGISTER_LAST_COMMAND(SetDocumentStateCommand,
-                           "cmd_enableAbsolutePositionEditing")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_setDocumentModified")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_setDocumentUseCSS")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_setDocumentReadOnly")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_insertBrOnReturn")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_defaultParagraphSeparator")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_enableObjectResizing")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand, "cmd_enableInlineTableEditing")
+  NS_REGISTER_COMMAND(SetDocumentStateCommand,
+                      "cmd_enableAbsolutePositionEditing")
 
   return NS_OK;
 }
 
 // static
 nsresult HTMLEditorController::RegisterHTMLEditorCommands(
     nsControllerCommandTable *aCommandTable) {
   // Edit menu
-  NS_REGISTER_ONE_COMMAND(PasteNoFormattingCommand, "cmd_pasteNoFormatting");
+  NS_REGISTER_COMMAND(PasteNoFormattingCommand, "cmd_pasteNoFormatting");
 
   // indent/outdent
-  NS_REGISTER_ONE_COMMAND(IndentCommand, "cmd_indent");
-  NS_REGISTER_ONE_COMMAND(OutdentCommand, "cmd_outdent");
+  NS_REGISTER_COMMAND(IndentCommand, "cmd_indent");
+  NS_REGISTER_COMMAND(OutdentCommand, "cmd_outdent");
 
   // Styles
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_bold", nsGkAtoms::b);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_italic", nsGkAtoms::i);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_underline",
-                            nsGkAtoms::u);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_tt", nsGkAtoms::tt);
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_bold");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_italic");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_underline");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_tt");
+
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_strikethrough");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_superscript");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_subscript");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_nobreak");
 
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_strikethrough",
-                            nsGkAtoms::strike);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_superscript",
-                            nsGkAtoms::sup);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_subscript",
-                            nsGkAtoms::sub);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_nobreak",
-                            nsGkAtoms::nobr);
-
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_em", nsGkAtoms::em);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_strong",
-                            nsGkAtoms::strong);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_cite", nsGkAtoms::cite);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_abbr", nsGkAtoms::abbr);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_acronym",
-                            nsGkAtoms::acronym);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_code", nsGkAtoms::code);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_samp", nsGkAtoms::samp);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_var", nsGkAtoms::var);
-  NS_REGISTER_STYLE_COMMAND(StyleUpdatingCommand, "cmd_removeLinks",
-                            nsGkAtoms::href);
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_em");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_strong");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_cite");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_abbr");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_acronym");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_code");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_samp");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_var");
+  NS_REGISTER_COMMAND(StyleUpdatingCommand, "cmd_removeLinks");
 
   // lists
-  NS_REGISTER_STYLE_COMMAND(ListCommand, "cmd_ol", nsGkAtoms::ol);
-  NS_REGISTER_STYLE_COMMAND(ListCommand, "cmd_ul", nsGkAtoms::ul);
-  NS_REGISTER_STYLE_COMMAND(ListItemCommand, "cmd_dt", nsGkAtoms::dt);
-  NS_REGISTER_STYLE_COMMAND(ListItemCommand, "cmd_dd", nsGkAtoms::dd);
-  NS_REGISTER_ONE_COMMAND(RemoveListCommand, "cmd_removeList");
+  NS_REGISTER_COMMAND(ListCommand, "cmd_ol");
+  NS_REGISTER_COMMAND(ListCommand, "cmd_ul");
+  NS_REGISTER_COMMAND(ListItemCommand, "cmd_dt");
+  NS_REGISTER_COMMAND(ListItemCommand, "cmd_dd");
+  NS_REGISTER_COMMAND(RemoveListCommand, "cmd_removeList");
 
   // format stuff
-  NS_REGISTER_ONE_COMMAND(ParagraphStateCommand, "cmd_paragraphState");
-  NS_REGISTER_ONE_COMMAND(FontFaceStateCommand, "cmd_fontFace");
-  NS_REGISTER_ONE_COMMAND(FontSizeStateCommand, "cmd_fontSize");
-  NS_REGISTER_ONE_COMMAND(FontColorStateCommand, "cmd_fontColor");
-  NS_REGISTER_ONE_COMMAND(BackgroundColorStateCommand, "cmd_backgroundColor");
-  NS_REGISTER_ONE_COMMAND(HighlightColorStateCommand, "cmd_highlight");
+  NS_REGISTER_COMMAND(ParagraphStateCommand, "cmd_paragraphState");
+  NS_REGISTER_COMMAND(FontFaceStateCommand, "cmd_fontFace");
+  NS_REGISTER_COMMAND(FontSizeStateCommand, "cmd_fontSize");
+  NS_REGISTER_COMMAND(FontColorStateCommand, "cmd_fontColor");
+  NS_REGISTER_COMMAND(BackgroundColorStateCommand, "cmd_backgroundColor");
+  NS_REGISTER_COMMAND(HighlightColorStateCommand, "cmd_highlight");
 
-  NS_REGISTER_ONE_COMMAND(AlignCommand, "cmd_align");
-  NS_REGISTER_ONE_COMMAND(RemoveStylesCommand, "cmd_removeStyles");
+  NS_REGISTER_COMMAND(AlignCommand, "cmd_align");
+  NS_REGISTER_COMMAND(RemoveStylesCommand, "cmd_removeStyles");
 
-  NS_REGISTER_ONE_COMMAND(IncreaseFontSizeCommand, "cmd_increaseFont");
-  NS_REGISTER_ONE_COMMAND(DecreaseFontSizeCommand, "cmd_decreaseFont");
+  NS_REGISTER_COMMAND(IncreaseFontSizeCommand, "cmd_increaseFont");
+  NS_REGISTER_COMMAND(DecreaseFontSizeCommand, "cmd_decreaseFont");
 
   // Insert content
-  NS_REGISTER_ONE_COMMAND(InsertHTMLCommand, "cmd_insertHTML");
-  NS_REGISTER_TAG_COMMAND(InsertTagCommand, "cmd_insertLinkNoUI", nsGkAtoms::a);
-  NS_REGISTER_TAG_COMMAND(InsertTagCommand, "cmd_insertImageNoUI",
-                          nsGkAtoms::img);
-  NS_REGISTER_TAG_COMMAND(InsertTagCommand, "cmd_insertHR", nsGkAtoms::hr);
+  NS_REGISTER_COMMAND(InsertHTMLCommand, "cmd_insertHTML");
+  NS_REGISTER_COMMAND(InsertTagCommand, "cmd_insertLinkNoUI");
+  NS_REGISTER_COMMAND(InsertTagCommand, "cmd_insertImageNoUI");
+  NS_REGISTER_COMMAND(InsertTagCommand, "cmd_insertHR");
 
-  NS_REGISTER_ONE_COMMAND(AbsolutePositioningCommand, "cmd_absPos");
-  NS_REGISTER_ONE_COMMAND(DecreaseZIndexCommand, "cmd_decreaseZIndex");
-  NS_REGISTER_ONE_COMMAND(IncreaseZIndexCommand, "cmd_increaseZIndex");
+  NS_REGISTER_COMMAND(AbsolutePositioningCommand, "cmd_absPos");
+  NS_REGISTER_COMMAND(DecreaseZIndexCommand, "cmd_decreaseZIndex");
+  NS_REGISTER_COMMAND(IncreaseZIndexCommand, "cmd_increaseZIndex");
 
   return NS_OK;
 }
 
+// static
+void HTMLEditorController::Shutdown() {
+  // EditorDocStateCommands
+  DocumentStateCommand::Shutdown();
+  SetDocumentStateCommand::Shutdown();
+
+  // HTMLEditorCommands
+  PasteNoFormattingCommand::Shutdown();
+  IndentCommand::Shutdown();
+  OutdentCommand::Shutdown();
+  StyleUpdatingCommand::Shutdown();
+  ListCommand::Shutdown();
+  ListItemCommand::Shutdown();
+  RemoveListCommand::Shutdown();
+  ParagraphStateCommand::Shutdown();
+  FontFaceStateCommand::Shutdown();
+  FontSizeStateCommand::Shutdown();
+  FontColorStateCommand::Shutdown();
+  BackgroundColorStateCommand::Shutdown();
+  HighlightColorStateCommand::Shutdown();
+  AlignCommand::Shutdown();
+  RemoveStylesCommand::Shutdown();
+  IncreaseFontSizeCommand::Shutdown();
+  DecreaseFontSizeCommand::Shutdown();
+  InsertHTMLCommand::Shutdown();
+  InsertTagCommand::Shutdown();
+  AbsolutePositioningCommand::Shutdown();
+  DecreaseZIndexCommand::Shutdown();
+  IncreaseZIndexCommand::Shutdown();
+}
+
 }  // namespace mozilla
--- a/editor/libeditor/HTMLEditorController.h
+++ b/editor/libeditor/HTMLEditorController.h
@@ -13,13 +13,14 @@ class nsControllerCommandTable;
 namespace mozilla {
 
 class HTMLEditorController final {
  public:
   static nsresult RegisterEditorDocStateCommands(
       nsControllerCommandTable* aCommandTable);
   static nsresult RegisterHTMLEditorCommands(
       nsControllerCommandTable* aCommandTable);
+  static void Shutdown();
 };
 
 }  // namespace mozilla
 
 #endif /* mozllla_HTMLEditorController_h__ */
--- a/editor/libeditor/HTMLEditorDocumentCommands.cpp
+++ b/editor/libeditor/HTMLEditorDocumentCommands.cpp
@@ -28,23 +28,27 @@ class nsISupports;
 // defines
 #define STATE_ENABLED "state_enabled"
 #define STATE_ALL "state_all"
 #define STATE_ATTRIBUTE "state_attribute"
 #define STATE_DATA "state_data"
 
 namespace mozilla {
 
-/**
+/*****************************************************************************
+ * mozilla::SetDocumentStateCommand
+ *
  *  Commands for document state that may be changed via doCommandParams
  *  As of 11/11/02, this is just "cmd_setDocumentModified"
- *  Note that you can use the same command class, nsSetDocumentStateCommand,
+ *  Note that you can use the same command class, SetDocumentStateCommand,
  *    for more than one of this type of command
  *    We check the input command param for different behavior
- */
+ *****************************************************************************/
+
+StaticRefPtr<SetDocumentStateCommand> SetDocumentStateCommand::sInstance;
 
 NS_IMETHODIMP
 SetDocumentStateCommand::IsCommandEnabled(const char* aCommandName,
                                           nsISupports* refCon,
                                           bool* outCmdEnabled) {
   if (NS_WARN_IF(!outCmdEnabled)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -399,23 +403,25 @@ SetDocumentStateCommand::GetCommandState
     }
     return params->SetBool(STATE_ALL,
                            htmlEditor->IsAbsolutePositionEditorEnabled());
   }
 
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
-/**
+/*****************************************************************************
+ * mozilla::DocumentStateCommand
+ *
  * Commands just for state notification
  *  As of 11/21/02, possible commands are:
  *    "obs_documentCreated"
  *    "obs_documentWillBeDestroyed"
  *    "obs_documentLocationChanged"
- *  Note that you can use the same command class, nsDocumentStateCommand
+ *  Note that you can use the same command class, DocumentStateCommand
  *    for these or future observer commands.
  *    We check the input command param for different behavior
  *
  *  How to use:
  *  1. Get the nsCommandManager for the current editor
  *  2. Implement an nsIObserve object, e.g:
  *
  *    void Observe(
@@ -432,18 +438,19 @@ SetDocumentStateCommand::GetCommandState
  *
  *  RefPtr<nsCommandManager> commandManager = mDocShell->GetCommandManager();
  *  commandManager->CommandStatusChanged(obs_documentCreated);
  *
  *  5. Use GetCommandStateParams() to obtain state information
  *     e.g., any creation state codes when creating an editor are
  *     supplied for "obs_documentCreated" command in the
  *     "state_data" param's value
- *
- */
+ *****************************************************************************/
+
+StaticRefPtr<DocumentStateCommand> DocumentStateCommand::sInstance;
 
 NS_IMETHODIMP
 DocumentStateCommand::IsCommandEnabled(const char* aCommandName,
                                        nsISupports* refCon,
                                        bool* outCmdEnabled) {
   if (NS_WARN_IF(!outCmdEnabled)) {
     return NS_ERROR_INVALID_ARG;
   }
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -104,16 +104,17 @@
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/dom/HTMLVideoElement.h"
 #include "TouchManager.h"
 #include "DecoderDoctorLogger.h"
 #include "MediaDecoder.h"
 #include "mozilla/ClearSiteData.h"
 #include "mozilla/EditorController.h"
 #include "mozilla/Fuzzyfox.h"
+#include "mozilla/HTMLEditorController.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/StaticPresData.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/ipc/IPCBlobInputStreamStorage.h"
 #include "mozilla/dom/U2FTokenManager.h"
 #ifdef OS_WIN
 #  include "mozilla/dom/WinWebAuthnManager.h"
 #endif
@@ -340,16 +341,17 @@ void nsLayoutStatics::Shutdown() {
 #endif
   UIDirectionManager::Shutdown();
   StorageObserver::Shutdown();
   txMozillaXSLTProcessor::Shutdown();
   Attr::Shutdown();
   PopupBlocker::Shutdown();
   IMEStateManager::Shutdown();
   EditorController::Shutdown();
+  HTMLEditorController::Shutdown();
   nsMediaFeatures::Shutdown();
   nsHTMLDNSPrefetch::Shutdown();
   nsCSSRendering::Shutdown();
   StaticPresData::Shutdown();
 #ifdef DEBUG
   nsFrame::DisplayReflowShutdown();
 #endif
   nsCellMap::Shutdown();