Bug 1591417: part 4) Adapt `InsertTextIntoTextNodeWithTransaction` to pass adjusted range to `TopLevelEditSubActionDataRef.DidInsertText`. r=masayuki
authorMirko Brodesser <mbrodesser@mozilla.com>
Wed, 13 Nov 2019 09:16:06 +0000
changeset 501719 e711f304a2cd05c11e7c8e0231b75be4a70e7493
parent 501718 0dd20dae7caac58c51562d1ab9fd0b51792a8d2d
child 501720 4424f41acdb04b53e7efcf8d64fc9dd1a2c53275
push id114172
push userdluca@mozilla.com
push dateTue, 19 Nov 2019 11:31:10 +0000
treeherdermozilla-inbound@b5c5ba07d3db [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmasayuki
bugs1591417
milestone72.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 1591417: part 4) Adapt `InsertTextIntoTextNodeWithTransaction` to pass adjusted range to `TopLevelEditSubActionDataRef.DidInsertText`. r=masayuki Otherwise, an invalid `EditorRawDOMPoint` was constructed by a test. Differential Revision: https://phabricator.services.mozilla.com/D52344
editor/libeditor/EditorBase.cpp
editor/libeditor/EditorBase.h
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -47,16 +47,17 @@
 #include "mozilla/TextComposition.h"    // for TextComposition
 #include "mozilla/TextInputListener.h"  // for TextInputListener
 #include "mozilla/TextServicesDocument.h"  // for TextServicesDocument
 #include "mozilla/TextEvents.h"
 #include "mozilla/TransactionManager.h"  // for TransactionManager
 #include "mozilla/dom/AbstractRange.h"   // for AbstractRange
 #include "mozilla/dom/CharacterData.h"   // for CharacterData
 #include "mozilla/dom/DataTransfer.h"    // for DataTransfer
+#include "mozilla/InternalMutationEvent.h"  // for NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED
 #include "mozilla/dom/Element.h"         // for Element, nsINode::AsElement
 #include "mozilla/dom/EventTarget.h"     // for EventTarget
 #include "mozilla/dom/HTMLBodyElement.h"
 #include "mozilla/dom/HTMLBRElement.h"
 #include "mozilla/dom/Selection.h"  // for Selection, etc.
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/Event.h"
 #include "nsAString.h"                // for nsAString::Length, etc.
@@ -2827,16 +2828,56 @@ nsresult EditorBase::InsertTextWithTrans
     return rv;
   }
   if (aPointAfterInsertedString) {
     aPointAfterInsertedString->Set(newNode, lengthToInsert.value());
   }
   return NS_OK;
 }
 
+static bool TextFragmentBeginsWithStringAtOffset(
+    const nsTextFragment& aTextFragment, const int32_t aOffset,
+    const nsAString& aString) {
+  const uint32_t stringLength = aString.Length();
+
+  if (aOffset + stringLength > aTextFragment.GetLength()) {
+    return false;
+  }
+
+  if (aTextFragment.Is2b()) {
+    return aString.Equals(aTextFragment.Get2b() + aOffset);
+  }
+
+  return aString.EqualsLatin1(aTextFragment.Get1b() + aOffset, stringLength);
+}
+
+namespace {
+struct AdjustedInsertionRange {
+  EditorRawDOMPoint mBegin;
+  EditorRawDOMPoint mEnd;
+};
+}  // anonymous namespace
+
+static AdjustedInsertionRange AdjustTextInsertionRange(
+    Text& aTextNode, const int32_t aInsertionOffset,
+    const nsAString& aInsertedString) {
+  if (TextFragmentBeginsWithStringAtOffset(aTextNode.TextFragment(),
+                                           aInsertionOffset, aInsertedString)) {
+    EditorRawDOMPoint begin{&aTextNode, aInsertionOffset};
+    EditorRawDOMPoint end{
+        &aTextNode,
+        static_cast<int32_t>(aInsertionOffset + aInsertedString.Length())};
+    return {begin, end};
+  }
+
+  const EditorRawDOMPoint begin{&aTextNode, 0};
+  const EditorRawDOMPoint end{&aTextNode,
+                              static_cast<int32_t>(aTextNode.TextLength())};
+  return {begin, end};
+}
 nsresult EditorBase::InsertTextIntoTextNodeWithTransaction(
     const nsAString& aStringToInsert, Text& aTextNode, int32_t aOffset,
     bool aSuppressIME) {
   MOZ_ASSERT(IsEditActionDataAvailable());
 
   RefPtr<EditTransactionBase> transaction;
   bool isIMETransaction = false;
   RefPtr<Text> insertedTextNode = &aTextNode;
@@ -2862,25 +2903,40 @@ nsresult EditorBase::InsertTextIntoTextN
 
   // XXX We may not need these view batches anymore.  This is handled at a
   // higher level now I believe.
   BeginUpdateViewBatch();
   nsresult rv = DoTransactionInternal(transaction);
   EndUpdateViewBatch();
 
   if (AsHTMLEditor() && insertedTextNode) {
-    TopLevelEditSubActionDataRef().DidInsertText(
-        *this, EditorRawDOMPoint(insertedTextNode, insertedOffset),
-        aStringToInsert);
+    // The DOM was potentially modified during the transaction. This is possible
+    // through mutation event listeners. That is, the node could've been removed
+    // from the doc or otherwise modified.
+    if (!MaybeHasMutationEventListeners(
+            NS_EVENT_BITS_MUTATION_CHARACTERDATAMODIFIED)) {
+      const EditorRawDOMPoint begin{insertedTextNode, insertedOffset};
+      const EditorRawDOMPoint end{
+          insertedTextNode,
+          static_cast<int32_t>(insertedOffset + aStringToInsert.Length())};
+      TopLevelEditSubActionDataRef().DidInsertText(*this, begin, end);
+    } else if (insertedTextNode->IsInComposedDoc()) {
+      AdjustedInsertionRange adjustedRange = AdjustTextInsertionRange(
+          *insertedTextNode, insertedOffset, aStringToInsert);
+      TopLevelEditSubActionDataRef().DidInsertText(*this, adjustedRange.mBegin,
+                                                   adjustedRange.mEnd);
+    }
   }
 
   // let listeners know what happened
   if (!mActionListeners.IsEmpty()) {
     AutoActionListenerArray listeners(mActionListeners);
     for (auto& listener : listeners) {
+      // TODO: might need adaptation because of mutation event listeners called
+      // during `DoTransactionInternal`.
       listener->DidInsertText(insertedTextNode, insertedOffset, aStringToInsert,
                               rv);
     }
   }
 
   // Added some cruft here for bug 43366.  Layout was crashing because we left
   // an empty text node lying around in the document.  So I delete empty text
   // nodes caused by IME.  I have to mark the IME transaction as "fixed", which
@@ -5724,32 +5780,30 @@ void EditorBase::TopLevelEditSubActionDa
           &aRightContent,
           aEditorBase.EditSubActionDataRef().mJoinedLeftNodeLength));
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                        "TopLevelEditSubActionData::AddPointToChangedRange() "
                        "failed, but ignored");
 }
 
 void EditorBase::TopLevelEditSubActionData::DidInsertText(
-    EditorBase& aEditorBase, const EditorRawDOMPoint& aInsertionPoint,
-    const nsAString& aString) {
+    EditorBase& aEditorBase, const EditorRawDOMPoint& aInsertionBegin,
+    const EditorRawDOMPoint& aInsertionEnd) {
   MOZ_ASSERT(aEditorBase.AsHTMLEditor());
 
   if (!aEditorBase.mInitSucceeded || aEditorBase.Destroyed()) {
     return;  // We have not been initialized yet or already been destroyed.
   }
 
   if (!aEditorBase.EditSubActionDataRef().mAdjustChangedRangeFromListener) {
     return;  // Temporarily disabled by edit sub-action handler.
   }
 
   DebugOnly<nsresult> rvIgnored = AddRangeToChangedRange(
-      *aEditorBase.AsHTMLEditor(), aInsertionPoint,
-      EditorRawDOMPoint(aInsertionPoint.GetContainer(),
-                        aInsertionPoint.Offset() + aString.Length()));
+      *aEditorBase.AsHTMLEditor(), aInsertionBegin, aInsertionEnd);
   NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
                        "TopLevelEditSubActionData::AddRangeToChangedRange() "
                        "failed, but ignored");
 }
 
 void EditorBase::TopLevelEditSubActionData::DidDeleteText(
     EditorBase& aEditorBase, const EditorRawDOMPoint& aStartInTextNode) {
   MOZ_ASSERT(aEditorBase.AsHTMLEditor());
--- a/editor/libeditor/EditorBase.h
+++ b/editor/libeditor/EditorBase.h
@@ -673,18 +673,18 @@ class EditorBase : public nsIEditor,
     void DidSplitContent(EditorBase& aEditorBase,
                          nsIContent& aExistingRightContent,
                          nsIContent& aNewLeftContent);
     void WillJoinContents(EditorBase& aEditorBase, nsIContent& aLeftContent,
                           nsIContent& aRightContent);
     void DidJoinContents(EditorBase& aEditorBase, nsIContent& aLeftContent,
                          nsIContent& aRightContent);
     void DidInsertText(EditorBase& aEditorBase,
-                       const EditorRawDOMPoint& aInsertionPoint,
-                       const nsAString& aString);
+                       const EditorRawDOMPoint& aInsertionBegin,
+                       const EditorRawDOMPoint& aInsertionEnd);
     void DidDeleteText(EditorBase& aEditorBase,
                        const EditorRawDOMPoint& aStartInTextNode);
     void WillDeleteRange(EditorBase& aEditorBase,
                          const EditorRawDOMPoint& aStart,
                          const EditorRawDOMPoint& aEnd);
 
    private:
     void Clear() {