Bug 675515 - Crash [@ TextUpdater::DoUpdate] with long text node, r=tbsaunde, sr=neil
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 04 Aug 2011 12:57:08 +0900
changeset 73823 75c8a2eb9f87a0a2a4e039f1311de7eba87ca95d
parent 73822 5edb33c72ec0c29ba4157ee85480458cfb1aff5e
child 73824 0e7f85aa8137ccc923824139ebde10a3c93d72b3
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewerstbsaunde, neil
bugs675515
milestone8.0a1
Bug 675515 - Crash [@ TextUpdater::DoUpdate] with long text node, r=tbsaunde, sr=neil
accessible/src/base/TextUpdater.cpp
accessible/src/base/TextUpdater.h
accessible/tests/mochitest/events/test_text_alg.html
--- a/accessible/src/base/TextUpdater.cpp
+++ b/accessible/src/base/TextUpdater.cpp
@@ -82,66 +82,68 @@ TextUpdater::DoUpdate(const nsAString& a
   // Get the text leaf accessible offset and invalidate cached offsets after it.
   mTextOffset = mHyperText->GetChildOffset(mTextLeaf, PR_TRUE);
   NS_ASSERTION(mTextOffset != -1,
                "Text leaf hasn't offset within hyper text!");
 
   PRUint32 oldLen = aOldText.Length(), newLen = aNewText.Length();
   PRUint32 minLen = NS_MIN(oldLen, newLen);
 
-  // Text was appended or removed to/from the end.
-  if (aSkipStart == minLen) {
-    // If text has been appended to the end, fire text inserted event.
-    if (oldLen < newLen) {
-      UpdateTextNFireEvent(aNewText, Substring(aNewText, oldLen),
-                           oldLen, PR_TRUE);
-      return;
-    }
-
-    // Text has been removed from the end, fire text removed event.
-    UpdateTextNFireEvent(aNewText, Substring(aOldText, newLen),
-                         newLen, PR_FALSE);
-    return;
-  }
-
   // Trim coinciding substrings from the end.
   PRUint32 skipEnd = 0;
   while (minLen - skipEnd > aSkipStart &&
          aNewText[newLen - skipEnd - 1] == aOldText[oldLen - skipEnd - 1]) {
     skipEnd++;
   }
 
-  // Text was appended or removed to/from the start.
-  if (skipEnd == minLen) {
-    // If text has been appended to the start, fire text inserted event.
-    if (oldLen < newLen) {
-      UpdateTextNFireEvent(aNewText, Substring(aNewText, 0, newLen - skipEnd),
-                           0, PR_TRUE);
-      return;
-    }
-
-    // Text has been removed from the start, fire text removed event.
-    UpdateTextNFireEvent(aNewText, Substring(aOldText, 0, oldLen - skipEnd),
-                         0, PR_FALSE);
-    return;
-  }
-
-  // Find the difference between strings and fire events.
-  // Note: we can skip initial and final coinciding characters since they don't
-  // affect the Levenshtein distance.
-
   PRInt32 strLen1 = oldLen - aSkipStart - skipEnd;
   PRInt32 strLen2 = newLen - aSkipStart - skipEnd;
 
   const nsAString& str1 = Substring(aOldText, aSkipStart, strLen1);
   const nsAString& str2 = Substring(aNewText, aSkipStart, strLen2);
 
   // Increase offset of the text leaf on skipped characters amount.
   mTextOffset += aSkipStart;
 
+  // It could be single insertion or removal or the case of long strings. Do not
+  // calculate the difference between long strings and prefer to fire pair of
+  // insert/remove events as the old string was replaced on the new one.
+  if (strLen1 == 0 || strLen2 == 0 ||
+      strLen1 > kMaxStrLen || strLen2 > kMaxStrLen) {
+    if (strLen1 > 0) {
+      // Fire text change event for removal.
+      nsRefPtr<AccEvent> textRemoveEvent =
+        new AccTextChangeEvent(mHyperText, mTextOffset, str1, PR_FALSE);
+      mDocument->FireDelayedAccessibleEvent(textRemoveEvent);
+    }
+
+    if (strLen2 > 0) {
+      // Fire text change event for insertion.
+      nsRefPtr<AccEvent> textInsertEvent =
+        new AccTextChangeEvent(mHyperText, mTextOffset, str2, PR_TRUE);
+      mDocument->FireDelayedAccessibleEvent(textInsertEvent);
+    }
+
+    // Fire value change event.
+    if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
+      nsRefPtr<AccEvent> valueChangeEvent =
+        new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
+                     eAutoDetect, AccEvent::eRemoveDupes);
+      mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
+    }
+
+    // Update the text.
+    mTextLeaf->SetText(aNewText);
+    return;
+  }
+
+  // Otherwise find the difference between strings and fire events.
+  // Note: we can skip initial and final coinciding characters since they don't
+  // affect the Levenshtein distance.
+
   // Compute the flat structured matrix need to compute the difference.
   PRUint32 len1 = strLen1 + 1, len2 = strLen2 + 1;
   PRUint32* entries = new PRUint32[len1 * len2];
 
   for (PRUint32 colIdx = 0; colIdx < len1; colIdx++)
     entries[colIdx] = colIdx;
 
   PRUint32* row = entries;
@@ -233,32 +235,8 @@ TextUpdater::ComputeTextChangeEvents(con
     return;
   }
 
   if (rowEnd)
     FireInsertEvent(Substring(aStr2, 0, rowEnd), 0, aEvents);
   if (colEnd)
     FireDeleteEvent(Substring(aStr1, 0, colEnd), 0, aEvents);
 }
-
-void
-TextUpdater::UpdateTextNFireEvent(const nsAString& aNewText,
-                                  const nsAString& aChangeText,
-                                  PRUint32 aAddlOffset,
-                                  PRBool aIsInserted)
-{
-  // Fire text change event.
-  nsRefPtr<AccEvent> textChangeEvent =
-    new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset, aChangeText,
-                           aIsInserted);
-  mDocument->FireDelayedAccessibleEvent(textChangeEvent);
-
-  // Fire value change event.
-  if (mHyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
-    nsRefPtr<AccEvent> valueChangeEvent =
-      new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, mHyperText,
-                   eAutoDetect, AccEvent::eRemoveDupes);
-    mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
-  }
-
-  // Update the text.
-  mTextLeaf->SetText(aNewText);
-}
--- a/accessible/src/base/TextUpdater.h
+++ b/accessible/src/base/TextUpdater.h
@@ -103,21 +103,20 @@ private:
   {
     nsRefPtr<AccEvent> event =
       new AccTextChangeEvent(mHyperText, mTextOffset + aAddlOffset,
                              aText, PR_FALSE);
     aEvents.AppendElement(event);
   }
 
   /**
-   * Update the text and fire text change/value change events.
+   * The constant used to skip string difference calculation in case of long
+   * strings.
    */
-  void UpdateTextNFireEvent(const nsAString& aNewText,
-                            const nsAString& aChangeText, PRUint32 aAddlOffset,
-                            PRBool aIsInserted);
+  const static PRUint32 kMaxStrLen = 1 << 6;
 
 private:
   nsDocAccessible* mDocument;
   nsTextAccessible* mTextLeaf;
   nsHyperTextAccessible* mHyperText;
   PRInt32 mTextOffset;
 };
 
--- a/accessible/tests/mochitest/events/test_text_alg.html
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -19,46 +19,62 @@
           src="../events.js"></script>
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
     const kRemoval = 0;
     const kInsertion = 1;
+    const kUnexpected = true;
 
     function changeText(aContainerID, aValue, aEventList)
     {
       this.containerNode = getNode(aContainerID);
       this.textNode = this.containerNode.firstChild;
       this.textData = this.textNode.data;
 
       this.eventSeq = [ ];
+      this.unexpectedEventSeq = [ ];
+
       for (var i = 0; i < aEventList.length; i++) {
-        var isInserted = aEventList[i][0];
-        var str = aEventList[i][1];
-        var offset = aEventList[i][2];
+        var event = aEventList[i];
+
+        var isInserted = event[0];
+        var str = event[1];
+        var offset = event[2];
         var checker = new textChangeChecker(this.containerNode, offset,
                                             offset + str.length, str,
                                             isInserted);
-        this.eventSeq.push(checker);
+
+        if (eventItem[3] == kUnexpected)
+          this.unexpectedEventSeq.push(checker);
+        else
+          this.eventSeq.push(checker);
       }
 
       this.invoke = function changeText_invoke()
       {
         this.textNode.data = aValue;
       }
 
       this.getID = function changeText_getID()
       {
         return "change text '" + this.textData + "' -> " + this.textNode.data +
           "for " + prettyName(this.containerNode);
       }
     }
 
+    function expStr(x, doublings)
+    {
+      for (var i = 0; i < doublings; ++i)
+        x = x + x;
+      return x;
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
     function doTests()
@@ -162,16 +178,44 @@
         [ kRemoval, "m", 0 ], // meilenstein -> eilenstein
         [ kInsertion, "l", 0], // eilenstein -> leilenstein
         [ kRemoval, "il", 2 ], // leilenstein -> leenstein
         [ kInsertion, "v", 2], // leenstein -> levenstein
         [ kInsertion, "h", 6 ], // levenstein -> levenshtein
       ];
       gQueue.push(new changeText("p11", "levenshtein", events));
 
+      //////////////////////////////////////////////////////////////////////////
+      // long strings, remove/insert pair as the old string was replaced on
+      // new one
+
+      var longStr1 = expStr("x", 16);
+      var longStr2 = expStr("X", 16);
+
+      var newStr = "a" + longStr1 + "b", insStr = longStr1, rmStr = "";
+      events = [
+        [ kRemoval, rmStr, 1, kUnexpected ],
+        [ kInsertion, insStr, 1 ]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
+      newStr = "a" + longStr2 + "b", insStr = longStr2, rmStr = longStr1;
+      events = [
+        [ kRemoval, rmStr, 1 ],
+        [ kInsertion, insStr, 1]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
+      newStr = "ab", insStr = "", rmStr = longStr2;
+      events = [
+        [ kRemoval, rmStr, 1 ],
+        [ kInsertion, insStr, 1, kUnexpected ]
+      ];
+      gQueue.push(new changeText("p12", newStr, events));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -196,10 +240,11 @@
   <p id="p4">abcabc</p>
   <p id="p5">abc</p>
   <p id="p6">abc</p>
   <p id="p7">defabc</p>
   <p id="p8">abcdef</p>
   <p id="p9">abcDEFabc</p>
   <p id="p10">!abcdef@</p>
   <p id="p11">meilenstein</p>
+  <p id="p12">ab</p>
 </body>
 </html>