Bug 1697876 - part 1: Add logging code to transaction classes for making easier to debug r=m_kato
authorMasayuki Nakano <masayuki@d-toybox.com>
Mon, 05 Apr 2021 11:35:48 +0000
changeset 574318 bc3c88ef4ea51dcb162e1820a7d282ff3a45b509
parent 574317 7341db1e3d21bd229c95c06b8ca04b40a67f2cbf
child 574319 55557d6df4831a7c38ba829ffc323f1e02570491
push id38348
push usernerli@mozilla.com
push dateMon, 05 Apr 2021 21:28:12 +0000
treeherdermozilla-central@65dc61a78542 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersm_kato
bugs1697876
milestone89.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 1697876 - part 1: Add logging code to transaction classes for making easier to debug r=m_kato Differential Revision: https://phabricator.services.mozilla.com/D110714
editor/libeditor/ChangeAttributeTransaction.cpp
editor/libeditor/ChangeAttributeTransaction.h
editor/libeditor/ChangeStyleTransaction.cpp
editor/libeditor/ChangeStyleTransaction.h
editor/libeditor/CompositionTransaction.cpp
editor/libeditor/CompositionTransaction.h
editor/libeditor/CreateElementTransaction.cpp
editor/libeditor/CreateElementTransaction.h
editor/libeditor/DeleteNodeTransaction.cpp
editor/libeditor/DeleteNodeTransaction.h
editor/libeditor/DeleteRangeTransaction.cpp
editor/libeditor/DeleteTextTransaction.cpp
editor/libeditor/DeleteTextTransaction.h
editor/libeditor/EditAggregateTransaction.cpp
editor/libeditor/EditTransactionBase.cpp
editor/libeditor/EditTransactionBase.h
editor/libeditor/EditorDOMPoint.h
editor/libeditor/InsertNodeTransaction.cpp
editor/libeditor/InsertNodeTransaction.h
editor/libeditor/InsertTextTransaction.cpp
editor/libeditor/InsertTextTransaction.h
editor/libeditor/JoinNodeTransaction.cpp
editor/libeditor/JoinNodeTransaction.h
editor/libeditor/PlaceholderTransaction.cpp
editor/libeditor/ReplaceTextTransaction.cpp
editor/libeditor/ReplaceTextTransaction.h
editor/libeditor/SplitNodeTransaction.cpp
editor/libeditor/SplitNodeTransaction.h
--- a/editor/libeditor/ChangeAttributeTransaction.cpp
+++ b/editor/libeditor/ChangeAttributeTransaction.cpp
@@ -1,15 +1,17 @@
 /* -*- 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/. */
 
 #include "ChangeAttributeTransaction.h"
 
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/Element.h"  // for Element
 
 #include "nsAString.h"
 #include "nsError.h"  // for NS_ERROR_NOT_INITIALIZED, etc.
 
 namespace mozilla {
 
 using namespace dom;
@@ -36,16 +38,33 @@ ChangeAttributeTransaction::ChangeAttrib
                                                        const nsAString* aValue)
     : EditTransactionBase(),
       mElement(&aElement),
       mAttribute(&aAttribute),
       mValue(aValue ? *aValue : u""_ns),
       mRemoveAttribute(!aValue),
       mAttributeWasSet(false) {}
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const ChangeAttributeTransaction& aTransaction) {
+  aStream << "{ mElement=" << aTransaction.mElement.get();
+  if (aTransaction.mElement) {
+    aStream << " (" << *aTransaction.mElement << ")";
+  }
+  aStream << ", mAttribute=" << nsAtomCString(aTransaction.mAttribute).get()
+          << ", mValue=\"" << NS_ConvertUTF16toUTF8(aTransaction.mValue).get()
+          << "\", mUndoValue=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mUndoValue).get()
+          << "\", mRemoveAttribute="
+          << (aTransaction.mRemoveAttribute ? "true" : "false")
+          << ", mAttributeWasSet="
+          << (aTransaction.mAttributeWasSet ? "true" : "false") << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeAttributeTransaction,
                                    EditTransactionBase, mElement)
 
 NS_IMPL_ADDREF_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(ChangeAttributeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeAttributeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
@@ -56,31 +75,39 @@ NS_IMETHODIMP ChangeAttributeTransaction
       mElement->GetAttr(kNameSpaceID_None, mAttribute, mUndoValue);
 
   // XXX: hack until attribute-was-set code is implemented
   if (!mUndoValue.IsEmpty()) {
     mAttributeWasSet = true;
   }
   // XXX: end hack
 
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeAttributeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   // Now set the attribute to the new value
   if (mRemoveAttribute) {
     OwningNonNull<Element> element = *mElement;
     nsresult rv = element->UnsetAttr(kNameSpaceID_None, mAttribute, true);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
     return rv;
   }
 
   OwningNonNull<Element> element = *mElement;
   nsresult rv = element->SetAttr(kNameSpaceID_None, mAttribute, mValue, true);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
   return rv;
 }
 
 NS_IMETHODIMP ChangeAttributeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeAttributeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mElement)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   if (mAttributeWasSet) {
     OwningNonNull<Element> element = *mElement;
     nsresult rv =
         element->SetAttr(kNameSpaceID_None, mAttribute, mUndoValue, true);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::SetAttr() failed");
@@ -88,16 +115,20 @@ NS_IMETHODIMP ChangeAttributeTransaction
   }
   OwningNonNull<Element> element = *mElement;
   nsresult rv = element->UnsetAttr(kNameSpaceID_None, mAttribute, true);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
   return rv;
 }
 
 NS_IMETHODIMP ChangeAttributeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeAttributeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mElement)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   if (mRemoveAttribute) {
     OwningNonNull<Element> element = *mElement;
     nsresult rv = element->UnsetAttr(kNameSpaceID_None, mAttribute, true);
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Element::UnsetAttr() failed");
     return rv;
--- a/editor/libeditor/ChangeAttributeTransaction.h
+++ b/editor/libeditor/ChangeAttributeTransaction.h
@@ -56,16 +56,19 @@ class ChangeAttributeTransaction final :
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ChangeAttributeTransaction,
                                            EditTransactionBase)
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(ChangeAttributeTransaction)
 
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
 
+  friend std::ostream& operator<<(
+      std::ostream& aStream, const ChangeAttributeTransaction& aTransaction);
+
  private:
   virtual ~ChangeAttributeTransaction() = default;
 
   // The element to operate upon
   nsCOMPtr<dom::Element> mElement;
 
   // The attribute to change
   RefPtr<nsAtom> mAttribute;
--- a/editor/libeditor/ChangeStyleTransaction.cpp
+++ b/editor/libeditor/ChangeStyleTransaction.cpp
@@ -1,15 +1,17 @@
 /* -*- 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/. */
 
-#include "mozilla/ChangeStyleTransaction.h"
+#include "ChangeStyleTransaction.h"
 
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/Element.h"  // for Element
 #include "nsAString.h"            // for nsAString::Append, etc.
 #include "nsCRT.h"                // for nsCRT::IsAsciiSpace
 #include "nsDebug.h"              // for NS_WARNING, etc.
 #include "nsError.h"              // for NS_ERROR_NULL_POINTER, etc.
 #include "nsGkAtoms.h"            // for nsGkAtoms, etc.
 #include "nsICSSDeclaration.h"    // for nsICSSDeclaration.
 #include "nsLiteralString.h"      // for NS_LITERAL_STRING, etc.
@@ -50,16 +52,35 @@ ChangeStyleTransaction::ChangeStyleTrans
       mUndoValue(),
       mRedoValue(),
       mRemoveProperty(aRemove),
       mUndoAttributeWasSet(false),
       mRedoAttributeWasSet(false) {
   CopyUTF16toUTF8(aValue, mValue);
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const ChangeStyleTransaction& aTransaction) {
+  aStream << "{ mStyledElement=" << aTransaction.mStyledElement.get();
+  if (aTransaction.mStyledElement) {
+    aStream << " (" << *aTransaction.mStyledElement << ")";
+  }
+  aStream << ", mProperty=" << nsAtomCString(aTransaction.mProperty).get()
+          << ", mValue=\"" << aTransaction.mValue.get() << "\", mUndoValue=\""
+          << aTransaction.mUndoValue.get()
+          << "\", mRedoValue=" << aTransaction.mRedoValue.get()
+          << ", mRemoveProperty="
+          << (aTransaction.mRemoveProperty ? "true" : "false")
+          << ", mUndoAttributeWasSet="
+          << (aTransaction.mUndoAttributeWasSet ? "true" : "false")
+          << ", mRedoAttributeWasSet="
+          << (aTransaction.mRedoAttributeWasSet ? "true" : "false") << " }";
+  return aStream;
+}
+
 #define kNullCh ('\0')
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ChangeStyleTransaction, EditTransactionBase,
                                    mStyledElement)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ChangeStyleTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
@@ -137,16 +158,20 @@ void ChangeStyleTransaction::RemoveValue
     }
 
     start = ++end;
   }
   aValues.Assign(outString);
 }
 
 NS_IMETHODIMP ChangeStyleTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mStyledElement)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<nsStyledElement> styledElement = *mStyledElement;
   nsCOMPtr<nsICSSDeclaration> cssDecl = styledElement->Style();
 
   // FIXME(bug 1606994): Using atoms forces a string copy here which is not
@@ -273,23 +298,31 @@ nsresult ChangeStyleTransaction::SetStyl
   nsresult rv =
       styledElement->UnsetAttr(kNameSpaceID_None, nsGkAtoms::style, true);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "Element::UnsetAttr(nsGkAtoms::style) failed");
   return rv;
 }
 
 NS_IMETHODIMP ChangeStyleTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   nsresult rv = SetStyle(mUndoAttributeWasSet, mUndoValue);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "ChangeStyleTransaction::SetStyle() failed");
   return rv;
 }
 
 NS_IMETHODIMP ChangeStyleTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ChangeStyleTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   nsresult rv = SetStyle(mRedoAttributeWasSet, mRedoValue);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "ChangeStyleTransaction::SetStyle() failed");
   return rv;
 }
 
 // True if the CSS property accepts more than one value
 bool ChangeStyleTransaction::AcceptsMoreThanOneValue(nsAtom& aCSSProperty) {
--- a/editor/libeditor/ChangeStyleTransaction.h
+++ b/editor/libeditor/ChangeStyleTransaction.h
@@ -67,16 +67,19 @@ class ChangeStyleTransaction final : pub
    *
    * @param aValueList      [IN] a list of white-space separated values
    * @param aValue          [IN] the value to look for in the list
    * @return                true if the value is in the list of values
    */
   static bool ValueIncludes(const nsACString& aValueList,
                             const nsACString& aValue);
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const ChangeStyleTransaction& aTransaction);
+
  private:
   virtual ~ChangeStyleTransaction() = default;
 
   /*
    * Adds the value aNewValue to list of white-space separated values aValues.
    *
    * @param aValues         [IN/OUT] a list of wite-space separated values
    * @param aNewValue       [IN] a value this code adds to aValues if it is not
--- a/editor/libeditor/CompositionTransaction.cpp
+++ b/editor/libeditor/CompositionTransaction.cpp
@@ -1,26 +1,28 @@
 /* -*- 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/. */
 
 #include "CompositionTransaction.h"
 
-#include "mozilla/EditorBase.h"       // mEditorBase
+#include "mozilla/EditorBase.h"  // mEditorBase
+#include "mozilla/Logging.h"
 #include "mozilla/SelectionState.h"   // RangeUpdater
 #include "mozilla/TextComposition.h"  // TextComposition
-#include "mozilla/dom/Selection.h"    // local var
-#include "mozilla/dom/Text.h"         // mTextNode
-#include "nsAString.h"                // params
-#include "nsDebug.h"                  // for NS_ASSERTION, etc
-#include "nsError.h"                  // for NS_SUCCEEDED, NS_FAILED, etc
-#include "nsRange.h"                  // local var
-#include "nsISelectionController.h"   // for nsISelectionController constants
-#include "nsQueryObject.h"            // for do_QueryObject
+#include "mozilla/ToString.h"
+#include "mozilla/dom/Selection.h"   // local var
+#include "mozilla/dom/Text.h"        // mTextNode
+#include "nsAString.h"               // params
+#include "nsDebug.h"                 // for NS_ASSERTION, etc
+#include "nsError.h"                 // for NS_SUCCEEDED, NS_FAILED, etc
+#include "nsRange.h"                 // local var
+#include "nsISelectionController.h"  // for nsISelectionController constants
+#include "nsQueryObject.h"           // for do_QueryObject
 
 namespace mozilla {
 
 using namespace dom;
 
 // static
 already_AddRefed<CompositionTransaction> CompositionTransaction::Create(
     EditorBase& aEditorBase, const nsAString& aStringToInsert,
@@ -63,26 +65,45 @@ CompositionTransaction::CompositionTrans
       mReplaceLength(aEditorBase.GetComposition()->XPLengthInTextNode()),
       mRanges(aEditorBase.GetComposition()->GetRanges()),
       mStringToInsert(aStringToInsert),
       mEditorBase(&aEditorBase),
       mFixed(false) {
   MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const CompositionTransaction& aTransaction) {
+  aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
+  if (aTransaction.mTextNode) {
+    aStream << " (" << *aTransaction.mTextNode << ")";
+  }
+  aStream << ", mOffset=" << aTransaction.mOffset
+          << ", mReplaceLength=" << aTransaction.mReplaceLength
+          << ", mRanges={ Length()=" << aTransaction.mRanges->Length() << " }"
+          << ", mStringToInsert=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
+          << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
                                    mEditorBase, mTextNode)
 // mRangeList can't lead to cycles
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
 
 NS_IMETHODIMP CompositionTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CompositionTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Fail before making any changes if there's no selection controller
   if (NS_WARN_IF(!mEditorBase->GetSelectionController())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
@@ -153,16 +174,20 @@ NS_IMETHODIMP CompositionTransaction::Do
   nsresult rv = SetSelectionForRanges();
   NS_WARNING_ASSERTION(
       NS_SUCCEEDED(rv),
       "CompositionTransaction::SetSelectionForRanges() failed");
   return rv;
 }
 
 NS_IMETHODIMP CompositionTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CompositionTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Get the selection first so we'll fail before making any changes if we
   // can't get it
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   if (NS_WARN_IF(!selection)) {
@@ -179,45 +204,65 @@ NS_IMETHODIMP CompositionTransaction::Un
   }
 
   // set the selection to the insertion point where the string was removed
   nsresult rv = selection->CollapseInLimiter(textNode, mOffset);
   NS_ASSERTION(NS_SUCCEEDED(rv), "Selection::CollapseInLimiter() failed");
   return rv;
 }
 
+NS_IMETHODIMP CompositionTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CompositionTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+  return DoTransaction();
+}
+
 NS_IMETHODIMP CompositionTransaction::Merge(nsITransaction* aOtherTransaction,
                                             bool* aDidMerge) {
+  MOZ_LOG(GetLogModule(), LogLevel::Debug,
+          ("%p CompositionTransaction::%s(aOtherTransaction=%p) this=%s", this,
+           __FUNCTION__, aOtherTransaction, ToString(*this).c_str()));
+
   if (NS_WARN_IF(!aOtherTransaction) || NS_WARN_IF(!aDidMerge)) {
     return NS_ERROR_INVALID_ARG;
   }
   *aDidMerge = false;
 
   // Check to make sure we aren't fixed, if we are then nothing gets merged.
   if (mFixed) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p CompositionTransaction::%s returned false due to fixed", this,
+             __FUNCTION__));
     return NS_OK;
   }
 
   RefPtr<EditTransactionBase> otherTransactionBase =
       aOtherTransaction->GetAsEditTransactionBase();
   if (!otherTransactionBase) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p CompositionTransaction::%s returned false due to not edit "
+             "transaction",
+             this, __FUNCTION__));
     return NS_OK;
   }
 
   // If aTransaction is another CompositionTransaction then merge it
   CompositionTransaction* otherCompositionTransaction =
       otherTransactionBase->GetAsCompositionTransaction();
   if (!otherCompositionTransaction) {
     return NS_OK;
   }
 
   // We merge the next IME transaction by adopting its insert string.
   mStringToInsert = otherCompositionTransaction->mStringToInsert;
   mRanges = otherCompositionTransaction->mRanges;
   *aDidMerge = true;
+  MOZ_LOG(GetLogModule(), LogLevel::Debug,
+          ("%p CompositionTransaction::%s returned true", this, __FUNCTION__));
   return NS_OK;
 }
 
 void CompositionTransaction::MarkFixed() { mFixed = true; }
 
 /* ============ private methods ================== */
 
 nsresult CompositionTransaction::SetSelectionForRanges() {
--- a/editor/libeditor/CompositionTransaction.h
+++ b/editor/libeditor/CompositionTransaction.h
@@ -56,24 +56,28 @@ class CompositionTransaction final : pub
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CompositionTransaction,
                                            EditTransactionBase)
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(CompositionTransaction)
 
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
   NS_IMETHOD Merge(nsITransaction* aOtherTransaction, bool* aDidMerge) override;
 
   void MarkFixed();
 
   MOZ_CAN_RUN_SCRIPT static nsresult SetIMESelection(
       EditorBase& aEditorBase, dom::Text* aTextNode, uint32_t aOffsetInNode,
       uint32_t aLengthOfCompositionString, const TextRangeArray* aRanges);
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const CompositionTransaction& aTransaction);
+
  private:
   virtual ~CompositionTransaction() = default;
 
   MOZ_CAN_RUN_SCRIPT nsresult SetSelectionForRanges();
 
   // The text element to operate upon.
   RefPtr<dom::Text> mTextNode;
 
--- a/editor/libeditor/CreateElementTransaction.cpp
+++ b/editor/libeditor/CreateElementTransaction.cpp
@@ -9,21 +9,25 @@
 #include <stdio.h>
 
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Selection.h"
 
 #include "mozilla/Casting.h"
 #include "mozilla/EditorBase.h"
 #include "mozilla/EditorDOMPoint.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 
 #include "nsAlgorithm.h"
 #include "nsAString.h"
+#include "nsAtom.h"
 #include "nsDebug.h"
 #include "nsError.h"
+#include "nsGkAtoms.h"
 #include "nsIContent.h"
 #include "nsINode.h"
 #include "nsISupportsUtils.h"
 #include "nsMemory.h"
 #include "nsReadableUtils.h"
 #include "nsStringFwd.h"
 #include "nsString.h"
 
@@ -55,26 +59,46 @@ CreateElementTransaction::CreateElementT
       mEditorBase(&aEditorBase),
       mTag(&aTag),
       mPointToInsert(aPointToInsert) {
   MOZ_ASSERT(!mPointToInsert.IsInDataNode());
   // We only need the child node at inserting new node.
   AutoEditorDOMPointOffsetInvalidator lockChild(mPointToInsert);
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const CreateElementTransaction& aTransaction) {
+  aStream << "{ mTag=\""
+          << nsAtomCString(aTransaction.mTag ? aTransaction.mTag.get()
+                                             : nsGkAtoms::_empty)
+                 .get()
+          << "\""
+          << ", mPointToInsert=" << aTransaction.mPointToInsert
+          << ", mNewElement=" << aTransaction.mNewElement.get();
+  if (aTransaction.mNewElement) {
+    aStream << " (" << *aTransaction.mNewElement << ")";
+  }
+  aStream << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
                                    EditTransactionBase, mEditorBase,
                                    mPointToInsert, mNewElement)
 
 NS_IMPL_ADDREF_INHERITED(CreateElementTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(CreateElementTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP CreateElementTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CreateElementTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTag) ||
       NS_WARN_IF(!mPointToInsert.IsSet())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   OwningNonNull<EditorBase> editorBase = *mEditorBase;
 
   mNewElement = editorBase->CreateHTMLContent(mTag);
@@ -168,30 +192,38 @@ void CreateElementTransaction::InsertNew
   OwningNonNull<nsINode> container = *mPointToInsert.GetContainer();
   OwningNonNull<Element> newElement = *mNewElement;
   container->AppendChild(newElement, aError);
   NS_WARNING_ASSERTION(!aError.Failed(),
                        "nsINode::AppendChild() failed, but ignored");
 }
 
 NS_IMETHODIMP CreateElementTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CreateElementTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mPointToInsert.IsSet()) ||
       NS_WARN_IF(!mNewElement)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<Element> newElement = *mNewElement;
   OwningNonNull<nsINode> containerNode = *mPointToInsert.GetContainer();
   ErrorResult error;
   containerNode->RemoveChild(newElement, error);
   NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
   return error.StealNSResult();
 }
 
 NS_IMETHODIMP CreateElementTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p CreateElementTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mPointToInsert.IsSet()) ||
       NS_WARN_IF(!mNewElement)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // First, reset mNewElement so it has no attributes or content
   // XXX We never actually did this, we only cleared mNewElement's contents if
   // it was a CharacterData node (which it's not, it's an Element)
--- a/editor/libeditor/CreateElementTransaction.h
+++ b/editor/libeditor/CreateElementTransaction.h
@@ -53,16 +53,19 @@ class CreateElementTransaction final : p
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(CreateElementTransaction)
 
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
 
   dom::Element* GetNewElement() const { return mNewElement; }
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const CreateElementTransaction& aTransaction);
+
  protected:
   virtual ~CreateElementTransaction() = default;
 
   /**
    * InsertNewNode() inserts mNewNode before the child node at mPointToInsert.
    */
   MOZ_CAN_RUN_SCRIPT void InsertNewNode(ErrorResult& aError);
 
--- a/editor/libeditor/DeleteNodeTransaction.cpp
+++ b/editor/libeditor/DeleteNodeTransaction.cpp
@@ -2,18 +2,20 @@
 /* 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/. */
 
 #include "DeleteNodeTransaction.h"
 
 #include "HTMLEditUtils.h"
 #include "mozilla/EditorBase.h"
+#include "mozilla/Logging.h"
 #include "mozilla/SelectionState.h"  // RangeUpdater
 #include "mozilla/TextEditor.h"
+#include "mozilla/ToString.h"
 #include "nsDebug.h"
 #include "nsError.h"
 #include "nsAString.h"
 
 namespace mozilla {
 
 // static
 already_AddRefed<DeleteNodeTransaction> DeleteNodeTransaction::MaybeCreate(
@@ -27,16 +29,34 @@ already_AddRefed<DeleteNodeTransaction> 
 }
 
 DeleteNodeTransaction::DeleteNodeTransaction(EditorBase& aEditorBase,
                                              nsIContent& aContentToDelete)
     : mEditorBase(&aEditorBase),
       mContentToDelete(&aContentToDelete),
       mParentNode(aContentToDelete.GetParentNode()) {}
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const DeleteNodeTransaction& aTransaction) {
+  aStream << "{ mContentToDelete=" << aTransaction.mContentToDelete.get();
+  if (aTransaction.mContentToDelete) {
+    aStream << " (" << *aTransaction.mContentToDelete << ")";
+  }
+  aStream << ", mParentNode=" << aTransaction.mParentNode.get();
+  if (aTransaction.mParentNode) {
+    aStream << " (" << *aTransaction.mParentNode << ")";
+  }
+  aStream << ", mRefContent=" << aTransaction.mRefContent.get();
+  if (aTransaction.mRefContent) {
+    aStream << " (" << *aTransaction.mRefContent << ")";
+  }
+  aStream << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction, EditTransactionBase,
                                    mEditorBase, mContentToDelete, mParentNode,
                                    mRefContent)
 
 NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
@@ -46,16 +66,20 @@ bool DeleteNodeTransaction::CanDoIt() co
       !mParentNode) {
     return false;
   }
   return mEditorBase->IsTextEditor() ||
          HTMLEditUtils::IsSimplyEditableNode(*mParentNode);
 }
 
 NS_IMETHODIMP DeleteNodeTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!CanDoIt())) {
     return NS_OK;
   }
 
   if (mEditorBase->IsTextEditor() && mContentToDelete->IsText()) {
     uint32_t length = mContentToDelete->AsText()->TextLength();
     if (length > 0) {
       mEditorBase->AsTextEditor()->WillDeleteText(length, 0, length);
@@ -74,18 +98,21 @@ NS_IMETHODIMP DeleteNodeTransaction::DoT
   OwningNonNull<nsINode> parentNode = *mParentNode;
   OwningNonNull<nsIContent> contentToDelete = *mContentToDelete;
   ErrorResult error;
   parentNode->RemoveChild(contentToDelete, error);
   NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
   return error.StealNSResult();
 }
 
-MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
-DeleteNodeTransaction::UndoTransaction() {
+NS_IMETHODIMP DeleteNodeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!CanDoIt())) {
     // This is a legal state, the transaction is a no-op.
     return NS_OK;
   }
   ErrorResult error;
   OwningNonNull<EditorBase> editorBase = *mEditorBase;
   OwningNonNull<nsINode> parentNode = *mParentNode;
   OwningNonNull<nsIContent> contentToDelete = *mContentToDelete;
@@ -110,16 +137,20 @@ DeleteNodeTransaction::UndoTransaction()
         return rv;
       }
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP DeleteNodeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!CanDoIt())) {
     // This is a legal state, the transaction is a no-op.
     return NS_OK;
   }
 
   if (mEditorBase->IsTextEditor() && mContentToDelete->IsText()) {
     uint32_t length = mContentToDelete->AsText()->TextLength();
     if (length > 0) {
--- a/editor/libeditor/DeleteNodeTransaction.h
+++ b/editor/libeditor/DeleteNodeTransaction.h
@@ -48,16 +48,19 @@ class DeleteNodeTransaction final : publ
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(DeleteNodeTransaction)
 
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
 
   nsIContent* GetContent() const { return mContentToDelete; }
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const DeleteNodeTransaction& aTransaction);
+
  protected:
   virtual ~DeleteNodeTransaction() = default;
 
   // The editor for this transaction.
   RefPtr<EditorBase> mEditorBase;
 
   // The element to delete.
   nsCOMPtr<nsIContent> mContentToDelete;
--- a/editor/libeditor/DeleteRangeTransaction.cpp
+++ b/editor/libeditor/DeleteRangeTransaction.cpp
@@ -2,25 +2,31 @@
 /* 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/. */
 
 #include "DeleteRangeTransaction.h"
 
 #include "DeleteNodeTransaction.h"
 #include "DeleteTextTransaction.h"
+
 #include "mozilla/Assertions.h"
 #include "mozilla/ContentIterator.h"
-#include "mozilla/dom/Selection.h"
 #include "mozilla/EditorBase.h"
+#include "mozilla/Logging.h"
 #include "mozilla/mozalloc.h"
 #include "mozilla/RangeBoundary.h"
+#include "mozilla/ToString.h"
+#include "mozilla/dom/Selection.h"
+
+#include "nsAtom.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsError.h"
+#include "nsGkAtoms.h"
 #include "nsIContent.h"
 #include "nsINode.h"
 #include "nsAString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
@@ -31,16 +37,22 @@ DeleteRangeTransaction::DeleteRangeTrans
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
                                    EditAggregateTransaction, mEditorBase,
                                    mRangeToDelete)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
 
 NS_IMETHODIMP DeleteRangeTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mRangeToDelete)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Swap mRangeToDelete out into a stack variable, so we make sure to null it
   // out on return from this function.  Once this function returns, we no longer
   // need mRangeToDelete, and keeping it alive in the long term slows down all
   // DOM mutations because it's observing them.
@@ -85,41 +97,71 @@ NS_IMETHODIMP DeleteRangeTransaction::Do
 
   // if we've successfully built this aggregate transaction, then do it.
   nsresult rv = EditAggregateTransaction::DoTransaction();
   if (NS_FAILED(rv)) {
     NS_WARNING("EditAggregateTransaction::DoTransaction() failed");
     return rv;
   }
 
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "End==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   if (!mEditorBase->AllowsTransactionsToChangeSelection()) {
     return NS_OK;
   }
 
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   rv = selection->CollapseInLimiter(startRef.AsRaw());
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "Selection::CollapseInLimiter() failed");
   return rv;
 }
 
 NS_IMETHODIMP DeleteRangeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   nsresult rv = EditAggregateTransaction::UndoTransaction();
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditAggregateTransaction::UndoTransaction() failed");
+
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "End==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return rv;
 }
 
 NS_IMETHODIMP DeleteRangeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   nsresult rv = EditAggregateTransaction::RedoTransaction();
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "EditAggregateTransaction::RedoTransaction() failed");
+
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteRangeTransaction::%s this={ mName=%s } "
+           "End==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return rv;
 }
 
 nsresult DeleteRangeTransaction::CreateTxnsToDeleteBetween(
     const RawRangeBoundary& aStart, const RawRangeBoundary& aEnd) {
   if (NS_WARN_IF(!aStart.IsSetAndValid()) ||
       NS_WARN_IF(!aEnd.IsSetAndValid()) ||
       NS_WARN_IF(aStart.Container() != aEnd.Container())) {
--- a/editor/libeditor/DeleteTextTransaction.cpp
+++ b/editor/libeditor/DeleteTextTransaction.cpp
@@ -80,31 +80,49 @@ DeleteTextTransaction::DeleteTextTransac
     : mEditorBase(&aEditorBase),
       mTextNode(&aTextNode),
       mOffset(aOffset),
       mLengthToDelete(aLengthToDelete) {
   NS_ASSERTION(mTextNode->Length() >= aOffset + aLengthToDelete,
                "Trying to delete more characters than in node");
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const DeleteTextTransaction& aTransaction) {
+  aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
+  if (aTransaction.mTextNode) {
+    aStream << " (" << *aTransaction.mTextNode << ")";
+  }
+  aStream << ", mOffset=" << aTransaction.mOffset
+          << ", mLengthToDelete=" << aTransaction.mLengthToDelete
+          << ", mDeletedText=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mDeletedText).get() << "\""
+          << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTransaction, EditTransactionBase,
                                    mEditorBase, mTextNode)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 bool DeleteTextTransaction::CanDoIt() const {
   if (NS_WARN_IF(!mTextNode) || NS_WARN_IF(!mEditorBase)) {
     return false;
   }
   return mEditorBase->IsTextEditor() ||
          HTMLEditUtils::IsSimplyEditableNode(*mTextNode);
 }
 
 NS_IMETHODIMP DeleteTextTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!CanDoIt())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Get the text that we're about to delete
   ErrorResult error;
   mTextNode->SubstringData(mOffset, mLengthToDelete, mDeletedText, error);
   if (error.Failed()) {
@@ -135,20 +153,31 @@ NS_IMETHODIMP DeleteTextTransaction::DoT
   NS_WARNING_ASSERTION(!error.Failed(),
                        "Selection::CollapseInLimiter() failed");
   return error.StealNSResult();
 }
 
 // XXX: We may want to store the selection state and restore it properly.  Was
 //     it an insertion point or an extended selection?
 NS_IMETHODIMP DeleteTextTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!CanDoIt())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   RefPtr<EditorBase> editorBase = mEditorBase;
   RefPtr<Text> textNode = mTextNode;
   ErrorResult error;
   editorBase->DoInsertText(*textNode, mOffset, mDeletedText, error);
   NS_WARNING_ASSERTION(!error.Failed(), "EditorBase::DoInsertText() failed");
   return error.StealNSResult();
 }
 
+NS_IMETHODIMP DeleteTextTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p DeleteTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+  return DoTransaction();
+}
+
 }  // namespace mozilla
--- a/editor/libeditor/DeleteTextTransaction.h
+++ b/editor/libeditor/DeleteTextTransaction.h
@@ -63,20 +63,25 @@ class DeleteTextTransaction final : publ
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DeleteTextTransaction,
                                            EditTransactionBase)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(DeleteTextTransaction)
 
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
+
   dom::Text* GetText() const { return mTextNode; }
   uint32_t Offset() const { return mOffset; }
   uint32_t LengthToDelete() const { return mLengthToDelete; }
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const DeleteTextTransaction& aTransaction);
+
  protected:
   // The provider of basic editing operations.
   RefPtr<EditorBase> mEditorBase;
 
   // The CharacterData node to operate upon.
   RefPtr<dom::Text> mTextNode;
 
   // The offset into mTextNode where the deletion is to take place.
--- a/editor/libeditor/EditAggregateTransaction.cpp
+++ b/editor/libeditor/EditAggregateTransaction.cpp
@@ -1,80 +1,122 @@
 /* -*- 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/. */
 
 #include "EditAggregateTransaction.h"
+
+#include "mozilla/Logging.h"
 #include "mozilla/ReverseIterator.h"  // for Reversed
 #include "nsAString.h"
-#include "nsCOMPtr.h"          // for nsCOMPtr
-#include "nsError.h"           // for NS_OK, etc.
+#include "nsAtom.h"
+#include "nsCOMPtr.h"  // for nsCOMPtr
+#include "nsError.h"   // for NS_OK, etc.
+#include "nsGkAtoms.h"
 #include "nsISupportsUtils.h"  // for NS_ADDREF
 #include "nsString.h"          // for nsAutoString
 
 namespace mozilla {
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(EditAggregateTransaction,
                                    EditTransactionBase, mChildren)
 
 NS_IMPL_ADDREF_INHERITED(EditAggregateTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(EditAggregateTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditAggregateTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP EditAggregateTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s, mChildren=%zu } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get(),
+           mChildren.Length()));
   // FYI: It's legal (but not very useful) to have an empty child list.
   for (const OwningNonNull<EditTransactionBase>& childTransaction :
        CopyableAutoTArray<OwningNonNull<EditTransactionBase>, 10>(mChildren)) {
     nsresult rv = MOZ_KnownLive(childTransaction)->DoTransaction();
     if (NS_FAILED(rv)) {
       NS_WARNING("EditTransactionBase::DoTransaction() failed");
       return rv;
     }
   }
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s } "
+           "End================================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return NS_OK;
 }
 
 NS_IMETHODIMP EditAggregateTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s, mChildren=%zu } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get(),
+           mChildren.Length()));
   // FYI: It's legal (but not very useful) to have an empty child list.
   // Undo goes through children backwards.
   const CopyableAutoTArray<OwningNonNull<EditTransactionBase>, 10> children(
       mChildren);
   for (const OwningNonNull<EditTransactionBase>& childTransaction :
        Reversed(children)) {
     nsresult rv = MOZ_KnownLive(childTransaction)->UndoTransaction();
     if (NS_FAILED(rv)) {
       NS_WARNING("EditTransactionBase::UndoTransaction() failed");
       return rv;
     }
   }
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s } "
+           "End================================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return NS_OK;
 }
 
 NS_IMETHODIMP EditAggregateTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s, mChildren=%zu } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get(),
+           mChildren.Length()));
   // It's legal (but not very useful) to have an empty child list.
   const CopyableAutoTArray<OwningNonNull<EditTransactionBase>, 10> children(
       mChildren);
   for (const OwningNonNull<EditTransactionBase>& childTransaction : children) {
     nsresult rv = MOZ_KnownLive(childTransaction)->RedoTransaction();
     if (NS_FAILED(rv)) {
       NS_WARNING("EditTransactionBase::RedoTransaction() failed");
       return rv;
     }
   }
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p EditAggregateTransaction::%s this={ mName=%s } "
+           "End================================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return NS_OK;
 }
 
 NS_IMETHODIMP EditAggregateTransaction::Merge(nsITransaction* aOtherTransaction,
                                               bool* aDidMerge) {
   if (aDidMerge) {
     *aDidMerge = false;
   }
   if (mChildren.IsEmpty()) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p EditAggregateTransaction::%s this={ mName=%s } returned false "
+             "due to no children",
+             this, __FUNCTION__,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
   // FIXME: Is this really intended not to loop?  It looks like the code
   // that used to be here sort of intended to loop, but didn't.
   return mChildren[0]->Merge(aOtherTransaction, aDidMerge);
 }
 
 NS_IMETHODIMP EditAggregateTransaction::AppendChild(
--- a/editor/libeditor/EditTransactionBase.cpp
+++ b/editor/libeditor/EditTransactionBase.cpp
@@ -1,15 +1,17 @@
 /* -*- 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/. */
 
 #include "mozilla/EditTransactionBase.h"
 
+#include "mozilla/Logging.h"
+
 #include "ChangeAttributeTransaction.h"
 #include "ChangeStyleTransaction.h"
 #include "CompositionTransaction.h"
 #include "CreateElementTransaction.h"
 #include "DeleteNodeTransaction.h"
 #include "DeleteRangeTransaction.h"
 #include "DeleteTextTransaction.h"
 #include "EditAggregateTransaction.h"
@@ -35,30 +37,43 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(EditTransactionBase)
   NS_INTERFACE_MAP_ENTRY(nsITransaction)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITransaction)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(EditTransactionBase)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(EditTransactionBase)
 
-NS_IMETHODIMP EditTransactionBase::RedoTransaction() { return DoTransaction(); }
+NS_IMETHODIMP EditTransactionBase::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info, ("%p %s", this, __FUNCTION__));
+  return DoTransaction();
+}
 
 NS_IMETHODIMP EditTransactionBase::GetIsTransient(bool* aIsTransient) {
+  MOZ_LOG(GetLogModule(), LogLevel::Verbose,
+          ("%p %s returned false", this, __FUNCTION__));
   *aIsTransient = false;
-
   return NS_OK;
 }
 
 NS_IMETHODIMP EditTransactionBase::Merge(nsITransaction* aOtherTransaction,
                                          bool* aDidMerge) {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p %s(aOtherTransaction=%p) returned false", this, __FUNCTION__,
+           aOtherTransaction));
   *aDidMerge = false;
   return NS_OK;
 }
 
+// static
+LogModule* EditTransactionBase::GetLogModule() {
+  static LazyLogModule sLog("EditorTransaction");
+  return static_cast<LogModule*>(sLog);
+}
+
 #define NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(aClass)           \
   aClass* EditTransactionBase::GetAs##aClass() { return nullptr; } \
   const aClass* EditTransactionBase::GetAs##aClass() const { return nullptr; }
 
 NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(ChangeAttributeTransaction)
 NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(ChangeStyleTransaction)
 NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(CompositionTransaction)
 NS_IMPL_EDITTRANSACTIONBASE_GETASMETHODS(CreateElementTransaction)
--- a/editor/libeditor/EditTransactionBase.h
+++ b/editor/libeditor/EditTransactionBase.h
@@ -28,16 +28,17 @@ class CompositionTransaction;
 class CreateElementTransaction;
 class DeleteNodeTransaction;
 class DeleteRangeTransaction;
 class DeleteTextTransaction;
 class EditAggregateTransaction;
 class InsertNodeTransaction;
 class InsertTextTransaction;
 class JoinNodeTransaction;
+class LogModule;
 class PlaceholderTransaction;
 class ReplaceTextTransaction;
 class SplitNodeTransaction;
 
 #define NS_DECL_GETASTRANSACTION_BASE(aClass) \
   virtual aClass* GetAs##aClass();            \
   virtual const aClass* GetAs##aClass() const;
 
@@ -72,16 +73,18 @@ class EditTransactionBase : public nsITr
   NS_DECL_GETASTRANSACTION_BASE(InsertTextTransaction)
   NS_DECL_GETASTRANSACTION_BASE(JoinNodeTransaction)
   NS_DECL_GETASTRANSACTION_BASE(PlaceholderTransaction)
   NS_DECL_GETASTRANSACTION_BASE(ReplaceTextTransaction)
   NS_DECL_GETASTRANSACTION_BASE(SplitNodeTransaction)
 
  protected:
   virtual ~EditTransactionBase() = default;
+
+  static LogModule* GetLogModule();
 };
 
 #undef NS_DECL_GETASTRANSACTION_BASE
 
 }  // namespace mozilla
 
 #define NS_DECL_EDITTRANSACTIONBASE                       \
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD DoTransaction() override; \
--- a/editor/libeditor/EditorDOMPoint.h
+++ b/editor/libeditor/EditorDOMPoint.h
@@ -5,16 +5,17 @@
 
 #ifndef mozilla_EditorDOMPoint_h
 #define mozilla_EditorDOMPoint_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RangeBoundary.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/AbstractRange.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Text.h"
 #include "nsAtom.h"
 #include "nsCOMPtr.h"
 #include "nsContentUtils.h"
 #include "nsCRT.h"
 #include "nsGkAtoms.h"
@@ -954,16 +955,31 @@ class EditorDOMPointBase final {
     if (!IsSetAndValid() || !aOther.IsSetAndValid()) {
       return false;
     }
     Maybe<int32_t> comp = nsContentUtils::ComparePoints(
         ToRawRangeBoundary(), aOther.ToRawRangeBoundary());
     return comp.isSome() && comp.value() <= 0;
   }
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const SelfType& aDOMPoint) {
+    aStream << "{ mParent=" << aDOMPoint.mParent.get();
+    if (aDOMPoint.mParent) {
+      aStream << " (" << *aDOMPoint.mParent << ")";
+    }
+    aStream << ", mChild=" << aDOMPoint.mChild.get();
+    if (aDOMPoint.mChild) {
+      aStream << " (" << *aDOMPoint.mChild << ")";
+    }
+    aStream << ", mOffset=" << aDOMPoint.mOffset << ", mIsChildInitialized="
+            << (aDOMPoint.mIsChildInitialized ? "true" : "false") << " }";
+    return aStream;
+  }
+
  private:
   void EnsureChild() {
     if (mIsChildInitialized) {
       return;
     }
     if (!mParent) {
       MOZ_ASSERT(!mOffset.isSome());
       return;
--- a/editor/libeditor/InsertNodeTransaction.cpp
+++ b/editor/libeditor/InsertNodeTransaction.cpp
@@ -3,17 +3,19 @@
  * 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/. */
 
 #include "InsertNodeTransaction.h"
 
 #include "mozilla/EditorBase.h"      // for EditorBase
 #include "mozilla/EditorDOMPoint.h"  // for EditorDOMPoint
 #include "mozilla/HTMLEditor.h"      // for HTMLEditor
-#include "mozilla/TextEditor.h"      // for TextEditor
+#include "mozilla/Logging.h"
+#include "mozilla/TextEditor.h"  // for TextEditor
+#include "mozilla/ToString.h"
 
 #include "mozilla/dom/Selection.h"  // for Selection
 
 #include "nsAString.h"
 #include "nsDebug.h"          // for NS_WARNING, etc.
 #include "nsError.h"          // for NS_ERROR_NULL_POINTER, etc.
 #include "nsIContent.h"       // for nsIContent
 #include "nsMemory.h"         // for nsMemory
@@ -48,26 +50,47 @@ InsertNodeTransaction::InsertNodeTransac
     : mContentToInsert(&aContentToInsert),
       mPointToInsert(aPointToInsert),
       mEditorBase(&aEditorBase) {
   MOZ_ASSERT(mPointToInsert.IsSetAndValid());
   // Ensure mPointToInsert stores child at offset.
   Unused << mPointToInsert.GetChild();
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const InsertNodeTransaction& aTransaction) {
+  aStream << "{ mContentToInsert=" << aTransaction.mContentToInsert.get();
+  if (aTransaction.mContentToInsert) {
+    if (aTransaction.mContentToInsert->IsText()) {
+      nsAutoString data;
+      aTransaction.mContentToInsert->AsText()->GetData(data);
+      aStream << " (#text \"" << NS_ConvertUTF16toUTF8(data).get() << "\")";
+    } else {
+      aStream << " (" << *aTransaction.mContentToInsert << ")";
+    }
+  }
+  aStream << ", mPointToInsert=" << aTransaction.mPointToInsert
+          << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
                                    mEditorBase, mContentToInsert,
                                    mPointToInsert)
 
 NS_IMPL_ADDREF_INHERITED(InsertNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(InsertNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP InsertNodeTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mContentToInsert) ||
       NS_WARN_IF(!mPointToInsert.IsSet())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   if (!mPointToInsert.IsSetAndValid()) {
     // It seems that DOM tree has been changed after first DoTransaction()
     // and current RedoTranaction() call.
@@ -143,16 +166,20 @@ NS_IMETHODIMP InsertNodeTransaction::DoT
   IgnoredErrorResult ignoredError;
   selection->CollapseInLimiter(afterInsertedNode, ignoredError);
   NS_WARNING_ASSERTION(!ignoredError.Failed(),
                        "Selection::CollapseInLimiter() failed, but ignored");
   return NS_OK;
 }
 
 NS_IMETHODIMP InsertNodeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mContentToInsert) ||
       NS_WARN_IF(!mPointToInsert.IsSet())) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   if (!mEditorBase->AsHTMLEditor() && mContentToInsert->IsText()) {
     uint32_t length = mContentToInsert->TextLength();
     if (length > 0) {
       mEditorBase->AsTextEditor()->WillDeleteText(length, 0, length);
@@ -163,9 +190,16 @@ NS_IMETHODIMP InsertNodeTransaction::Und
   OwningNonNull<nsINode> container = *mPointToInsert.GetContainer();
   OwningNonNull<nsIContent> contentToInsert = *mContentToInsert;
   ErrorResult error;
   container->RemoveChild(contentToInsert, error);
   NS_WARNING_ASSERTION(!error.Failed(), "nsINode::RemoveChild() failed");
   return error.StealNSResult();
 }
 
+NS_IMETHODIMP InsertNodeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+  return DoTransaction();
+}
+
 }  // namespace mozilla
--- a/editor/libeditor/InsertNodeTransaction.h
+++ b/editor/libeditor/InsertNodeTransaction.h
@@ -48,16 +48,21 @@ class InsertNodeTransaction final : publ
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertNodeTransaction,
                                            EditTransactionBase)
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(InsertNodeTransaction)
 
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
+
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const InsertNodeTransaction& aTransaction);
+
  protected:
   virtual ~InsertNodeTransaction() = default;
 
   // The element to insert.
   nsCOMPtr<nsIContent> mContentToInsert;
 
   // The DOM point we will insert mContentToInsert.
   EditorDOMPoint mPointToInsert;
--- a/editor/libeditor/InsertTextTransaction.cpp
+++ b/editor/libeditor/InsertTextTransaction.cpp
@@ -1,23 +1,25 @@
 /* -*- 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/. */
 
 #include "InsertTextTransaction.h"
 
-#include "mozilla/EditorBase.h"      // mEditorBase
+#include "mozilla/EditorBase.h"  // mEditorBase
+#include "mozilla/Logging.h"
 #include "mozilla/SelectionState.h"  // RangeUpdater
-#include "mozilla/dom/Selection.h"   // Selection local var
-#include "mozilla/dom/Text.h"        // mTextNode
-#include "nsAString.h"               // nsAString parameter
-#include "nsDebug.h"                 // for NS_ASSERTION, etc.
-#include "nsError.h"                 // for NS_OK, etc.
-#include "nsQueryObject.h"           // for do_QueryObject
+#include "mozilla/ToString.h"
+#include "mozilla/dom/Selection.h"  // Selection local var
+#include "mozilla/dom/Text.h"       // mTextNode
+#include "nsAString.h"              // nsAString parameter
+#include "nsDebug.h"                // for NS_ASSERTION, etc.
+#include "nsError.h"                // for NS_OK, etc.
+#include "nsQueryObject.h"          // for do_QueryObject
 
 namespace mozilla {
 
 using namespace dom;
 
 // static
 already_AddRefed<InsertTextTransaction> InsertTextTransaction::Create(
     EditorBase& aEditorBase, const nsAString& aStringToInsert,
@@ -31,25 +33,41 @@ already_AddRefed<InsertTextTransaction> 
 InsertTextTransaction::InsertTextTransaction(
     EditorBase& aEditorBase, const nsAString& aStringToInsert,
     const EditorDOMPointInText& aPointToInsert)
     : mTextNode(aPointToInsert.ContainerAsText()),
       mOffset(aPointToInsert.Offset()),
       mStringToInsert(aStringToInsert),
       mEditorBase(&aEditorBase) {}
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const InsertTextTransaction& aTransaction) {
+  aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
+  if (aTransaction.mTextNode) {
+    aStream << " (" << *aTransaction.mTextNode << ")";
+  }
+  aStream << ", mOffset=" << aTransaction.mOffset << ", mStringToInsert=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
+          << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase,
                                    mEditorBase, mTextNode)
 
 NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(InsertTextTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP InsertTextTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<EditorBase> editorBase = *mEditorBase;
   OwningNonNull<Text> textNode = *mTextNode;
 
   ErrorResult error;
@@ -76,54 +94,80 @@ NS_IMETHODIMP InsertTextTransaction::DoT
   //     Why do this transaction do this by itself?
   editorBase->RangeUpdaterRef().SelAdjInsertText(textNode, mOffset,
                                                  mStringToInsert.Length());
 
   return NS_OK;
 }
 
 NS_IMETHODIMP InsertTextTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
   OwningNonNull<EditorBase> editorBase = *mEditorBase;
   OwningNonNull<Text> textNode = *mTextNode;
   ErrorResult error;
   editorBase->DoDeleteText(textNode, mOffset, mStringToInsert.Length(), error);
   NS_WARNING_ASSERTION(!error.Failed(), "EditorBase::DoDeleteText() failed");
   return error.StealNSResult();
 }
 
+NS_IMETHODIMP InsertTextTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p InsertTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+  return DoTransaction();
+}
+
 NS_IMETHODIMP InsertTextTransaction::Merge(nsITransaction* aOtherTransaction,
                                            bool* aDidMerge) {
+  MOZ_LOG(GetLogModule(), LogLevel::Debug,
+          ("%p InsertTextTransaction::%s(aOtherTransaction=%p) this=%s", this,
+           __FUNCTION__, aOtherTransaction, ToString(*this).c_str()));
+
   if (NS_WARN_IF(!aOtherTransaction) || NS_WARN_IF(!aDidMerge)) {
     return NS_ERROR_INVALID_ARG;
   }
   // Set out param default value
   *aDidMerge = false;
 
   RefPtr<EditTransactionBase> otherTransactionBase =
       aOtherTransaction->GetAsEditTransactionBase();
   if (!otherTransactionBase) {
+    MOZ_LOG(
+        GetLogModule(), LogLevel::Debug,
+        ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false",
+         this, __FUNCTION__, aOtherTransaction));
     return NS_OK;
   }
 
   // If aTransaction is a InsertTextTransaction, and if the selection hasn't
   // changed, then absorb it.
   InsertTextTransaction* otherInsertTextTransaction =
       otherTransactionBase->GetAsInsertTextTransaction();
   if (!otherInsertTextTransaction ||
       !IsSequentialInsert(*otherInsertTextTransaction)) {
+    MOZ_LOG(
+        GetLogModule(), LogLevel::Debug,
+        ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned false",
+         this, __FUNCTION__, aOtherTransaction));
     return NS_OK;
   }
 
   nsAutoString otherData;
   otherInsertTextTransaction->GetData(otherData);
   mStringToInsert += otherData;
   *aDidMerge = true;
+  MOZ_LOG(GetLogModule(), LogLevel::Debug,
+          ("%p InsertTextTransaction::%s(aOtherTransaction=%p) returned true",
+           this, __FUNCTION__, aOtherTransaction));
   return NS_OK;
 }
 
 /* ============ private methods ================== */
 
 void InsertTextTransaction::GetData(nsString& aResult) {
   aResult = mStringToInsert;
 }
--- a/editor/libeditor/InsertTextTransaction.h
+++ b/editor/libeditor/InsertTextTransaction.h
@@ -46,23 +46,27 @@ class InsertTextTransaction final : publ
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(InsertTextTransaction,
                                            EditTransactionBase)
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(InsertTextTransaction)
 
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
   NS_IMETHOD Merge(nsITransaction* aOtherTransaction, bool* aDidMerge) override;
 
   /**
    * Return the string data associated with this transaction.
    */
   void GetData(nsString& aResult);
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const InsertTextTransaction& aTransaction);
+
  private:
   virtual ~InsertTextTransaction() = default;
 
   // Return true if aOtherTransaction immediately follows this transaction.
   bool IsSequentialInsert(InsertTextTransaction& aOtherTrasaction);
 
   // The Text node to operate upon.
   RefPtr<dom::Text> mTextNode;
--- a/editor/libeditor/JoinNodeTransaction.cpp
+++ b/editor/libeditor/JoinNodeTransaction.cpp
@@ -2,16 +2,18 @@
 /* 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/. */
 
 #include "JoinNodeTransaction.h"
 
 #include "HTMLEditUtils.h"
 #include "mozilla/HTMLEditor.h"  // for HTMLEditor
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/Text.h"
 #include "nsAString.h"
 #include "nsDebug.h"          // for NS_ASSERTION, etc.
 #include "nsError.h"          // for NS_ERROR_NULL_POINTER, etc.
 #include "nsIContent.h"       // for nsIContent
 #include "nsISupportsImpl.h"  // for QueryInterface, etc.
 
 namespace mozilla {
@@ -33,16 +35,35 @@ already_AddRefed<JoinNodeTransaction> Jo
 JoinNodeTransaction::JoinNodeTransaction(HTMLEditor& aHTMLEditor,
                                          nsIContent& aLeftContent,
                                          nsIContent& aRightContent)
     : mHTMLEditor(&aHTMLEditor),
       mLeftContent(&aLeftContent),
       mRightContent(&aRightContent),
       mOffset(0) {}
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const JoinNodeTransaction& aTransaction) {
+  aStream << "{ mLeftContent=" << aTransaction.mLeftContent.get();
+  if (aTransaction.mLeftContent) {
+    aStream << " (" << *aTransaction.mLeftContent << ")";
+  }
+  aStream << ", mRightContent=" << aTransaction.mRightContent.get();
+  if (aTransaction.mRightContent) {
+    aStream << " (" << *aTransaction.mRightContent << ")";
+  }
+  aStream << ", mParentNode=" << aTransaction.mParentNode.get();
+  if (aTransaction.mParentNode) {
+    aStream << " (" << *aTransaction.mParentNode << ")";
+  }
+  aStream << ", mOffset=" << aTransaction.mOffset
+          << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodeTransaction, EditTransactionBase,
                                    mHTMLEditor, mLeftContent, mRightContent,
                                    mParentNode)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 bool JoinNodeTransaction::CanDoIt() const {
@@ -51,16 +72,20 @@ bool JoinNodeTransaction::CanDoIt() cons
     return false;
   }
   return HTMLEditUtils::IsRemovableFromParentNode(*mLeftContent);
 }
 
 // After DoTransaction() and RedoTransaction(), the left node is removed from
 // the content tree and right node remains.
 NS_IMETHODIMP JoinNodeTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p JoinNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mLeftContent) ||
       NS_WARN_IF(!mRightContent)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // Get the parent node
   nsINode* leftContentParent = mLeftContent->GetParentNode();
   if (NS_WARN_IF(!leftContentParent)) {
@@ -84,16 +109,20 @@ NS_IMETHODIMP JoinNodeTransaction::DoTra
   nsresult rv = htmlEditor->DoJoinNodes(rightContent, leftContent);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "HTMLEditor::DoJoinNodes() failed");
   return rv;
 }
 
 // XXX: What if instead of split, we just deleted the unneeded children of
 //     mRight and re-inserted mLeft?
 NS_IMETHODIMP JoinNodeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p JoinNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mParentNode) || NS_WARN_IF(!mLeftContent) ||
       NS_WARN_IF(!mRightContent) || NS_WARN_IF(!mHTMLEditor)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<nsIContent> leftContent = *mLeftContent;
   OwningNonNull<nsIContent> rightContent = *mRightContent;
   OwningNonNull<nsINode> parentNode = *mParentNode;
@@ -134,9 +163,16 @@ NS_IMETHODIMP JoinNodeTransaction::UndoT
   parentNode->InsertBefore(leftContent, rightContent, error);
   // InsertBefore() may call MightThrowJSException() even if there is no
   // error. We don't need the flag here.
   error.WouldReportJSException();
   NS_WARNING_ASSERTION(!error.Failed(), "nsINode::InsertBefore() failed");
   return error.StealNSResult();
 }
 
+NS_IMETHODIMP JoinNodeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p JoinNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+  return DoTransaction();
+}
+
 }  // namespace mozilla
--- a/editor/libeditor/JoinNodeTransaction.h
+++ b/editor/libeditor/JoinNodeTransaction.h
@@ -51,16 +51,21 @@ class JoinNodeTransaction final : public
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JoinNodeTransaction,
                                            EditTransactionBase)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(JoinNodeTransaction)
 
+  MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
+
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const JoinNodeTransaction& aTransaction);
+
  protected:
   RefPtr<HTMLEditor> mHTMLEditor;
 
   // The nodes to operate upon.  After the merge, mRightContent remains and
   // mLeftContent is removed from the content tree.
   nsCOMPtr<nsIContent> mLeftContent;
   nsCOMPtr<nsIContent> mRightContent;
 
--- a/editor/libeditor/PlaceholderTransaction.cpp
+++ b/editor/libeditor/PlaceholderTransaction.cpp
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "PlaceholderTransaction.h"
 
 #include <utility>
 
 #include "CompositionTransaction.h"
 #include "mozilla/EditorBase.h"
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/Selection.h"
 #include "nsGkAtoms.h"
 #include "nsQueryObject.h"
 
 namespace mozilla {
 
 using namespace dom;
 
@@ -45,19 +47,31 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
 
 NS_IMPL_ADDREF_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
 NS_IMPL_RELEASE_INHERITED(PlaceholderTransaction, EditAggregateTransaction)
 
-NS_IMETHODIMP PlaceholderTransaction::DoTransaction() { return NS_OK; }
+NS_IMETHODIMP PlaceholderTransaction::DoTransaction() {
+  MOZ_LOG(
+      GetLogModule(), LogLevel::Info,
+      ("%p PlaceholderTransaction::%s this={ mName=%s }", this, __FUNCTION__,
+       nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+  return NS_OK;
+}
 
 NS_IMETHODIMP PlaceholderTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p PlaceholderTransaction::%s this={ mName=%s } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   if (NS_WARN_IF(!mEditorBase)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Undo transactions.
   nsresult rv = EditAggregateTransaction::UndoTransaction();
   if (NS_FAILED(rv)) {
     NS_WARNING("EditAggregateTransaction::UndoTransaction() failed");
@@ -67,20 +81,32 @@ NS_IMETHODIMP PlaceholderTransaction::Un
   // now restore selection
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
   rv = mStartSel.RestoreSelection(*selection);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "SelectionState::RestoreSelection() failed");
+
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p PlaceholderTransaction::%s this={ mName=%s } "
+           "End==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return rv;
 }
 
 NS_IMETHODIMP PlaceholderTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p PlaceholderTransaction::%s this={ mName=%s } "
+           "Start==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
+
   if (NS_WARN_IF(!mEditorBase)) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   // Redo transactions.
   nsresult rv = EditAggregateTransaction::RedoTransaction();
   if (NS_FAILED(rv)) {
     NS_WARNING("EditAggregateTransaction::RedoTransaction() failed");
@@ -90,16 +116,21 @@ NS_IMETHODIMP PlaceholderTransaction::Re
   // now restore selection
   RefPtr<Selection> selection = mEditorBase->GetSelection();
   if (NS_WARN_IF(!selection)) {
     return NS_ERROR_FAILURE;
   }
   rv = mEndSel.RestoreSelection(*selection);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
                        "SelectionState::RestoreSelection() failed");
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p PlaceholderTransaction::%s this={ mName=%s } "
+           "End==============================",
+           this, __FUNCTION__,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return rv;
 }
 
 NS_IMETHODIMP PlaceholderTransaction::Merge(nsITransaction* aOtherTransaction,
                                             bool* aDidMerge) {
   if (NS_WARN_IF(!aDidMerge) || NS_WARN_IF(!aOtherTransaction)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -112,16 +143,21 @@ NS_IMETHODIMP PlaceholderTransaction::Me
         "tried to merge into a placeholder that was in "
         "forwarding mode!");
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<EditTransactionBase> otherTransactionBase =
       aOtherTransaction->GetAsEditTransactionBase();
   if (!otherTransactionBase) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+             "mName=%s } returned false due to non edit transaction",
+             this, __FUNCTION__, aOtherTransaction,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
 
   // We are absorbing all transactions if mAbsorb is lit.
   if (mAbsorb) {
     if (CompositionTransaction* otherCompositionTransaction =
             otherTransactionBase->GetAsCompositionTransaction()) {
       // special handling for CompositionTransaction's: they need to merge with
@@ -168,53 +204,79 @@ NS_IMETHODIMP PlaceholderTransaction::Me
     //  ends. we can remeber the selection then.
     return NS_OK;
   }
 
   // merge typing or IME or deletion transactions if the selection matches
   if (mCommitted ||
       (mName != nsGkAtoms::TypingTxnName && mName != nsGkAtoms::IMETxnName &&
        mName != nsGkAtoms::DeleteTxnName)) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+             "mName=%s } returned false due to non mergable transaction",
+             this, __FUNCTION__, aOtherTransaction,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
 
   PlaceholderTransaction* otherPlaceholderTransaction =
       otherTransactionBase->GetAsPlaceholderTransaction();
   if (!otherPlaceholderTransaction) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+             "mName=%s } returned false due to non placeholder transaction",
+             this, __FUNCTION__, aOtherTransaction,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
 
   RefPtr<nsAtom> otherTransactionName;
   DebugOnly<nsresult> rvIgnored = otherPlaceholderTransaction->GetName(
       getter_AddRefs(otherTransactionName));
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                        "PlaceholderTransaction::GetName() failed, but ignored");
   if (!otherTransactionName || otherTransactionName == nsGkAtoms::_empty ||
       otherTransactionName != mName) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+             "mName=%s } returned false due to non mergable placeholder "
+             "transaction",
+             this, __FUNCTION__, aOtherTransaction,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
   // check if start selection of next placeholder matches
   // end selection of this placeholder
   if (!otherPlaceholderTransaction->StartSelectionEquals(mEndSel)) {
+    MOZ_LOG(GetLogModule(), LogLevel::Debug,
+            ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+             "mName=%s } returned false due to selection difference",
+             this, __FUNCTION__, aOtherTransaction,
+             nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
     return NS_OK;
   }
   mAbsorb = true;  // we need to start absorbing again
   otherPlaceholderTransaction->ForwardEndBatchTo(*this);
   // AppendChild(editTransactionBase);
   // see bug 171243: we don't need to merge placeholders
   // into placeholders.  We just reactivate merging in the
   // pre-existing placeholder and drop the new one on the floor.  The
   // EndPlaceHolderBatch() call on the new placeholder will be
   // forwarded to this older one.
   rvIgnored = RememberEndingSelection();
   NS_WARNING_ASSERTION(
       NS_SUCCEEDED(rvIgnored),
       "PlaceholderTransaction::RememberEndingSelection() failed, but "
       "ignored");
   *aDidMerge = true;
+  MOZ_LOG(GetLogModule(), LogLevel::Debug,
+          ("%p PlaceholderTransaction::%s(aOtherTransaction=%p) this={ "
+           "mName=%s } returned true",
+           this, __FUNCTION__, aOtherTransaction,
+           nsAtomCString(mName ? mName.get() : nsGkAtoms::_empty).get()));
   return NS_OK;
 }
 
 bool PlaceholderTransaction::StartSelectionEquals(
     SelectionState& aSelectionState) {
   // determine if starting selection matches the given selection state.
   // note that we only care about collapsed selections.
   return mStartSel.IsCollapsed() && aSelectionState.IsCollapsed() &&
--- a/editor/libeditor/ReplaceTextTransaction.cpp
+++ b/editor/libeditor/ReplaceTextTransaction.cpp
@@ -2,31 +2,52 @@
 /* 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/. */
 
 #include "ReplaceTextTransaction.h"
 
 #include "HTMLEditUtils.h"
 
+#include "mozilla/Logging.h"
 #include "mozilla/OwningNonNull.h"
+#include "mozilla/ToString.h"
 
 namespace mozilla {
 
 using namespace dom;
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const ReplaceTextTransaction& aTransaction) {
+  aStream << "{ mTextNode=" << aTransaction.mTextNode.get();
+  if (aTransaction.mTextNode) {
+    aStream << " (" << *aTransaction.mTextNode << ")";
+  }
+  aStream << ", mStringToInsert=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mStringToInsert).get() << "\""
+          << ", mStringToBeReplaced=\""
+          << NS_ConvertUTF16toUTF8(aTransaction.mStringToBeReplaced).get()
+          << "\", mOffset=" << aTransaction.mOffset
+          << ", mEditorBase=" << aTransaction.mEditorBase.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(ReplaceTextTransaction, EditTransactionBase,
                                    mEditorBase, mTextNode)
 
 NS_IMPL_ADDREF_INHERITED(ReplaceTextTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(ReplaceTextTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ReplaceTextTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP ReplaceTextTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode) ||
       NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*mTextNode))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<EditorBase> editorBase = *mEditorBase;
   OwningNonNull<Text> textNode = *mTextNode;
 
@@ -58,16 +79,20 @@ NS_IMETHODIMP ReplaceTextTransaction::Do
     return NS_ERROR_EDITOR_DESTROYED;
   }
   NS_ASSERTION(NS_SUCCEEDED(rvIgnored),
                "Selection::CollapseInLimiter() failed, but ignored");
   return NS_OK;
 }
 
 NS_IMETHODIMP ReplaceTextTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode) ||
       NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*mTextNode))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   ErrorResult error;
   nsAutoString insertedString;
   mTextNode->SubstringData(mOffset, mStringToInsert.Length(), insertedString,
@@ -113,16 +138,20 @@ NS_IMETHODIMP ReplaceTextTransaction::Un
     return NS_ERROR_EDITOR_DESTROYED;
   }
   NS_ASSERTION(NS_SUCCEEDED(rvIgnored),
                "Selection::CollapseInLimiter() failed, but ignored");
   return NS_OK;
 }
 
 NS_IMETHODIMP ReplaceTextTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p ReplaceTextTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode) ||
       NS_WARN_IF(!HTMLEditUtils::IsSimplyEditableNode(*mTextNode))) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   ErrorResult error;
   nsAutoString undoneString;
   mTextNode->SubstringData(mOffset, mStringToBeReplaced.Length(), undoneString,
--- a/editor/libeditor/ReplaceTextTransaction.h
+++ b/editor/libeditor/ReplaceTextTransaction.h
@@ -64,16 +64,19 @@ class ReplaceTextTransaction final : pub
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ReplaceTextTransaction,
                                            EditTransactionBase)
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(ReplaceTextTransaction)
 
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() final;
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const ReplaceTextTransaction& aTransaction);
+
  private:
   RefPtr<EditorBase> mEditorBase;
   RefPtr<dom::Text> mTextNode;
 
   nsString mStringToInsert;
   nsString mStringToBeReplaced;
 
   uint32_t mOffset;
--- a/editor/libeditor/SplitNodeTransaction.cpp
+++ b/editor/libeditor/SplitNodeTransaction.cpp
@@ -2,16 +2,18 @@
 /* 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/. */
 
 #include "SplitNodeTransaction.h"
 
 #include "mozilla/EditorDOMPoint.h"  // for RangeBoundary, EditorRawDOMPoint
 #include "mozilla/HTMLEditor.h"      // for HTMLEditor
+#include "mozilla/Logging.h"
+#include "mozilla/ToString.h"
 #include "mozilla/dom/Selection.h"
 #include "nsAString.h"
 #include "nsDebug.h"     // for NS_ASSERTION, etc.
 #include "nsError.h"     // for NS_ERROR_NOT_INITIALIZED, etc.
 #include "nsIContent.h"  // for nsIContent
 
 namespace mozilla {
 
@@ -35,26 +37,46 @@ already_AddRefed<SplitNodeTransaction> S
 template <typename PT, typename CT>
 SplitNodeTransaction::SplitNodeTransaction(
     HTMLEditor& aHTMLEditor,
     const EditorDOMPointBase<PT, CT>& aStartOfRightContent)
     : mHTMLEditor(&aHTMLEditor), mStartOfRightContent(aStartOfRightContent) {
   MOZ_DIAGNOSTIC_ASSERT(aStartOfRightContent.IsInContentNode());
 }
 
+std::ostream& operator<<(std::ostream& aStream,
+                         const SplitNodeTransaction& aTransaction) {
+  aStream << "{ mStartOfRightContent=" << aTransaction.mStartOfRightContent;
+  aStream << ", mNewLeftContent=" << aTransaction.mNewLeftContent.get();
+  if (aTransaction.mNewLeftContent) {
+    aStream << " (" << *aTransaction.mNewLeftContent << ")";
+  }
+  aStream << ", mContainerParentNode="
+          << aTransaction.mContainerParentNode.get();
+  if (aTransaction.mContainerParentNode) {
+    aStream << " (" << *aTransaction.mContainerParentNode << ")";
+  }
+  aStream << ", mHTMLEditor=" << aTransaction.mHTMLEditor.get() << " }";
+  return aStream;
+}
+
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase,
                                    mHTMLEditor, mStartOfRightContent,
                                    mContainerParentNode, mNewLeftContent)
 
 NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP SplitNodeTransaction::DoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mHTMLEditor) ||
       NS_WARN_IF(!mStartOfRightContent.IsInContentNode())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
   MOZ_ASSERT(mStartOfRightContent.IsSetAndValid());
 
   // Create a new node
   ErrorResult error;
@@ -114,16 +136,20 @@ NS_IMETHODIMP SplitNodeTransaction::DoTr
   EditorRawDOMPoint atEndOfLeftNode(EditorRawDOMPoint::AtEndOf(newLeftContent));
   selection->CollapseInLimiter(atEndOfLeftNode, error);
   NS_WARNING_ASSERTION(!error.Failed(),
                        "Selection::CollapseInLimiter() failed");
   return error.StealNSResult();
 }
 
 NS_IMETHODIMP SplitNodeTransaction::UndoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mHTMLEditor) || NS_WARN_IF(!mNewLeftContent) ||
       NS_WARN_IF(!mContainerParentNode) ||
       NS_WARN_IF(!mStartOfRightContent.IsInContentNode())) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   // This assumes Do inserted the new node in front of the prior existing node
   // XXX Perhaps, we should reset mStartOfRightNode with current first child
@@ -137,16 +163,20 @@ NS_IMETHODIMP SplitNodeTransaction::Undo
   return rv;
 }
 
 /* Redo cannot simply resplit the right node, because subsequent transactions
  * on the redo stack may depend on the left node existing in its previous
  * state.
  */
 NS_IMETHODIMP SplitNodeTransaction::RedoTransaction() {
+  MOZ_LOG(GetLogModule(), LogLevel::Info,
+          ("%p SplitNodeTransaction::%s this=%s", this, __FUNCTION__,
+           ToString(*this).c_str()));
+
   if (NS_WARN_IF(!mNewLeftContent) || NS_WARN_IF(!mContainerParentNode) ||
       NS_WARN_IF(!mStartOfRightContent.IsInContentNode()) ||
       NS_WARN_IF(!mHTMLEditor)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   OwningNonNull<HTMLEditor> htmlEditor = *mHTMLEditor;
   OwningNonNull<nsINode> newLeftContent = *mNewLeftContent;
--- a/editor/libeditor/SplitNodeTransaction.h
+++ b/editor/libeditor/SplitNodeTransaction.h
@@ -51,16 +51,19 @@ class SplitNodeTransaction final : publi
 
   NS_DECL_EDITTRANSACTIONBASE
   NS_DECL_EDITTRANSACTIONBASE_GETASMETHODS_OVERRIDE(SplitNodeTransaction)
 
   MOZ_CAN_RUN_SCRIPT NS_IMETHOD RedoTransaction() override;
 
   nsIContent* GetNewLeftContent() const { return mNewLeftContent; }
 
+  friend std::ostream& operator<<(std::ostream& aStream,
+                                  const SplitNodeTransaction& aTransaction);
+
  protected:
   virtual ~SplitNodeTransaction() = default;
 
   RefPtr<HTMLEditor> mHTMLEditor;
 
   // The container is existing right node (will be split).
   // The point referring this is start of the right node after it's split.
   EditorDOMPoint mStartOfRightContent;