Bug 626660 - cache rendered text on a11y side, r=davidb, f=marcoz, a=betaN
authorAlexander Surkov <surkov.alexander@gmail.com>
Mon, 31 Jan 2011 23:53:09 +0800
changeset 61649 7a5f8241cfa475e9f60b2bdaa06e1c1c9c2ec6e0
parent 61648 732a38102733f90cf7ad41c9c885f7262f27a288
child 61650 242d17a9bc33c22705dc80257027608c02bf898b
push idunknown
push userunknown
push dateunknown
reviewersdavidb, betaN
bugs626660
milestone2.0b11pre
Bug 626660 - cache rendered text on a11y side, r=davidb, f=marcoz, a=betaN
accessible/src/base/AccEvent.cpp
accessible/src/base/AccEvent.h
accessible/src/base/NotificationController.cpp
accessible/src/base/NotificationController.h
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsDocAccessible.cpp
accessible/src/base/nsDocAccessible.h
accessible/src/base/nsTextAccessible.h
accessible/tests/mochitest/editabletext/editabletext.js
accessible/tests/mochitest/editabletext/test_1.html
accessible/tests/mochitest/editabletext/test_2.html
accessible/tests/mochitest/hypertext/test_update.html
accessible/tests/mochitest/name/test_general.html
accessible/tests/mochitest/tree/test_combobox.xul
accessible/tests/mochitest/tree/test_dockids.html
accessible/tests/mochitest/tree/test_txtctrl.html
accessible/tests/mochitest/treeupdate/test_doc.html
--- a/accessible/src/base/AccEvent.cpp
+++ b/accessible/src/base/AccEvent.cpp
@@ -274,17 +274,17 @@ AccStateChangeEvent::CreateXPCOMObject()
 // we continue to base the event off the accessible object rather than just the
 // node. This means we won't try to create an accessible based on the node when
 // we are ready to fire the event and so we will no longer assert at that point
 // if the node was removed from the document. Either way, the AT won't work with
 // a defunct accessible so the behaviour should be equivalent.
 // XXX revisit this when coalescence is faster (eCoalesceFromSameSubtree)
 AccTextChangeEvent::
   AccTextChangeEvent(nsAccessible* aAccessible, PRInt32 aStart,
-                     nsAString& aModifiedText, PRBool aIsInserted,
+                     const nsAString& aModifiedText, PRBool aIsInserted,
                      EIsFromUserInput aIsFromUserInput)
   : AccEvent(aIsInserted ?
              static_cast<PRUint32>(nsIAccessibleEvent::EVENT_TEXT_INSERTED) :
              static_cast<PRUint32>(nsIAccessibleEvent::EVENT_TEXT_REMOVED),
              aAccessible, aIsFromUserInput, eAllowDupes)
   , mStart(aStart)
   , mIsInserted(aIsInserted)
   , mModifiedText(aModifiedText)
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -201,17 +201,17 @@ private:
 
 /**
  * Accessible text change event.
  */
 class AccTextChangeEvent: public AccEvent
 {
 public:
   AccTextChangeEvent(nsAccessible* aAccessible, PRInt32 aStart,
-                     nsAString& aModifiedText, PRBool aIsInserted,
+                     const nsAString& aModifiedText, PRBool aIsInserted,
                      EIsFromUserInput aIsFromUserInput = eAutoDetect);
 
   // AccEvent
   virtual already_AddRefed<nsAccEvent> CreateXPCOMObject();
 
   static const EventGroup kEventGroup = eTextChangeEvent;
   virtual unsigned int GetEventGroups() const
   {
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -31,26 +31,25 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
-#include "nsEventShell.h"
+#include "NotificationController.h"
 
+#include "nsAccessibilityService.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 #include "nsDocAccessible.h"
-
-#include "NotificationController.h"
+#include "nsEventShell.h"
+#include "nsTextAccessible.h"
 
-#include "nsAccessibilityService.h"
-#include "nsDocAccessible.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 // NotificationCollector
 ////////////////////////////////////////////////////////////////////////////////
 
 NotificationController::NotificationController(nsDocAccessible* aDocument,
                                                nsIPresShell* aPresShell) :
   mObservingState(eNotObservingRefresh), mDocument(aDocument),
@@ -569,16 +568,300 @@ NotificationController::CreateTextChange
   if (text.IsEmpty())
     return;
 
   aEvent->mTextChangeEvent =
     new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(),
                            aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput);
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Notification controller: text leaf accessible text update
+
+/**
+ * Used to find a difference between old and new text and fire text change
+ * events.
+ */
+class TextUpdater
+{
+public:
+  TextUpdater(nsDocAccessible* aDocument, nsTextAccessible* aTextLeaf) :
+    mDocument(aDocument), mTextLeaf(aTextLeaf) { }
+  ~TextUpdater() { mDocument = nsnull; mTextLeaf = nsnull; }
+
+  /**
+   * Update text of the text leaf accessible, fire text change events for its
+   * container hypertext accessible.
+   */
+  void Run(const nsAString& aNewText);
+
+private:
+  TextUpdater();
+  TextUpdater(const TextUpdater&);
+  TextUpdater& operator = (const TextUpdater&);
+
+  /**
+   * Fire text change events based on difference between strings.
+   */
+  void FindDiffNFireEvents(const nsDependentSubstring& aStr1,
+                           const nsDependentSubstring& aStr2,
+                           PRUint32** aMatrix,
+                           PRUint32 aStartOffset);
+
+  /**
+   * Change type used to describe the diff between strings.
+   */
+  enum ChangeType {
+    eNoChange,
+    eInsertion,
+    eRemoval,
+    eSubstitution
+  };
+
+  /**
+   * Helper to fire text change events.
+   */
+  inline void MayFireEvent(nsAString* aInsertedText, nsAString* aRemovedText,
+                           PRUint32 aOffset, ChangeType* aChange)
+  {
+    if (*aChange == eNoChange)
+      return;
+
+    if (*aChange == eRemoval || *aChange == eSubstitution) {
+      FireEvent(*aRemovedText, aOffset, PR_FALSE);
+      aRemovedText->Truncate();
+    }
+
+    if (*aChange == eInsertion || *aChange == eSubstitution) {
+      FireEvent(*aInsertedText, aOffset, PR_TRUE);
+      aInsertedText->Truncate();
+    }
+
+    *aChange = eNoChange;
+  }
+
+  /**
+   * Fire text change event.
+   */
+  void FireEvent(const nsAString& aModText, PRUint32 aOffset, PRBool aType);
+
+private:
+  nsDocAccessible* mDocument;
+  nsTextAccessible* mTextLeaf;
+};
+
+void
+TextUpdater::Run(const nsAString& aNewText)
+{
+  NS_ASSERTION(mTextLeaf, "No text leaf accessible?");
+
+  const nsString& oldText = mTextLeaf->Text();
+  PRUint32 oldLen = oldText.Length(), newLen = aNewText.Length();
+  PRUint32 minLen = oldLen < newLen ? oldLen : newLen;
+
+  // Skip coinciding begin substrings.
+  PRUint32 skipIdx = 0;
+  for (; skipIdx < minLen; skipIdx++) {
+    if (aNewText[skipIdx] != oldText[skipIdx])
+      break;
+  }
+
+  // No change, text append or removal to/from the end.
+  if (skipIdx == minLen) {
+    if (oldLen == newLen)
+      return;
+
+    // If text has been appended to the end, fire text inserted event.
+    if (oldLen < newLen) {
+      FireEvent(Substring(aNewText, oldLen), oldLen, PR_TRUE);
+      mTextLeaf->SetText(aNewText);
+      return;
+    }
+
+    // Text has been removed from the end, fire text removed event.
+    FireEvent(Substring(oldText, newLen), newLen, PR_FALSE);
+    mTextLeaf->SetText(aNewText);
+    return;
+  }
+
+  // Trim coinciding substrings from the end.
+  PRUint32 endIdx = minLen;
+  if (oldLen < newLen) {
+    PRUint32 delta = newLen - oldLen;
+    for (; endIdx > skipIdx; endIdx--) {
+      if (aNewText[endIdx + delta] != oldText[endIdx])
+        break;
+    }
+  } else {
+    PRUint32 delta = oldLen - newLen;
+    for (; endIdx > skipIdx; endIdx--) {
+      if (aNewText[endIdx] != oldText[endIdx + delta])
+        break;
+    }
+  }
+  PRUint32 oldEndIdx = oldLen - minLen + endIdx;
+  PRUint32 newEndIdx = newLen - minLen + endIdx;
+
+  // Find the difference starting from start character, we can skip initial and
+  // final coinciding characters since they don't affect on the Levenshtein
+  // distance.
+
+  const nsDependentSubstring& str1 =
+    Substring(oldText, skipIdx, oldEndIdx - skipIdx);
+  const nsDependentSubstring& str2 =
+    Substring(aNewText, skipIdx, newEndIdx - skipIdx);
+
+  // Compute the matrix.
+  PRUint32 len1 = str1.Length() + 1, len2 = str2.Length() + 1;
+
+  PRUint32** matrix = new PRUint32*[len1];
+  for (PRUint32 i = 0; i < len1; i++)
+    matrix[i] = new PRUint32[len2];
+
+  matrix[0][0] = 0;
+
+  for (PRUint32 i = 1; i < len1; i++)
+    matrix[i][0] = i;
+
+  for (PRUint32 j = 1; j < len2; j++)
+    matrix[0][j] = j;
+
+  for (PRUint32 i = 1; i < len1; i++) {
+    for (PRUint32 j = 1; j < len2; j++) {
+      if (str1[i - 1] != str2[j - 1]) {
+        PRUint32 left = matrix[i - 1][j];
+        PRUint32 up = matrix[i][j - 1];
+
+        PRUint32 upleft = matrix[i - 1][j - 1];
+        matrix[i][j] =
+            (left < up ? (upleft < left ? upleft : left) :
+                (upleft < up ? upleft : up)) + 1;
+      } else {
+        matrix[i][j] = matrix[i - 1][j - 1];
+      }
+    }
+  }
+
+  FindDiffNFireEvents(str1, str2, matrix, skipIdx);
+
+  for (PRUint32 i = 0; i < len1; i++)
+    delete[] matrix[i];
+  delete[] matrix;
+
+  mTextLeaf->SetText(aNewText);
+}
+
+void
+TextUpdater::FindDiffNFireEvents(const nsDependentSubstring& aStr1,
+                                 const nsDependentSubstring& aStr2,
+                                 PRUint32** aMatrix,
+                                 PRUint32 aStartOffset)
+{
+  // Find the difference.
+  ChangeType change = eNoChange;
+  nsAutoString insertedText;
+  nsAutoString removedText;
+  PRUint32 offset = 0;
+
+  PRInt32 i = aStr1.Length(), j = aStr2.Length();
+  while (i >= 0 && j >= 0) {
+    if (aMatrix[i][j] == 0) {
+      MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+      return;
+    }
+
+    // move up left
+    if (i >= 1 && j >= 1) {
+      // no change
+      if (aStr1[i - 1] == aStr2[j - 1]) {
+        MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+
+        i--; j--;
+        continue;
+      }
+
+      // substitution
+      if (aMatrix[i][j] == aMatrix[i - 1][j - 1] + 1) {
+        if (change != eSubstitution)
+          MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+
+        offset = j - 1;
+        insertedText.Append(aStr1[i - 1]);
+        removedText.Append(aStr2[offset]);
+        change = eSubstitution;
+
+        i--; j--;
+        continue;
+      }
+    }
+
+    // move up, insertion
+    if (j >= 1 && aMatrix[i][j] == aMatrix[i][j - 1] + 1) {
+      if (change != eInsertion)
+        MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+
+      offset = j - 1;
+      insertedText.Insert(aStr2[offset], 0);
+      change = eInsertion;
+
+      j--;
+      continue;
+    }
+
+    // move left, removal
+    if (i >= 1 && aMatrix[i][j] == aMatrix[i - 1][j] + 1) {
+      if (change != eRemoval) {
+        MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+
+        offset = j;
+      }
+
+      removedText.Insert(aStr1[i - 1], 0);
+      change = eRemoval;
+
+      i--;
+      continue;
+    }
+
+    NS_NOTREACHED("Huh?");
+    return;
+  }
+
+  MayFireEvent(&insertedText, &removedText, offset + aStartOffset, &change);
+}
+
+void
+TextUpdater::FireEvent(const nsAString& aModText, PRUint32 aOffset,
+                       PRBool aIsInserted)
+{
+  nsAccessible* parent = mTextLeaf->GetParent();
+  NS_ASSERTION(parent, "No parent for text leaf!");
+
+  nsHyperTextAccessible* hyperText = parent->AsHyperText();
+  NS_ASSERTION(hyperText, "Text leaf parnet is not hyper text!");
+
+  PRInt32 textLeafOffset = hyperText->GetChildOffset(mTextLeaf, PR_TRUE);
+  NS_ASSERTION(textLeafOffset != -1,
+               "Text leaf hasn't offset within hyper text!");
+
+  // Fire text change event.
+  nsRefPtr<AccEvent> textChangeEvent =
+    new AccTextChangeEvent(hyperText, textLeafOffset + aOffset, aModText,
+                           aIsInserted);
+  mDocument->FireDelayedAccessibleEvent(textChangeEvent);
+
+  // Fire value change event.
+  if (hyperText->Role() == nsIAccessibleRole::ROLE_ENTRY) {
+    nsRefPtr<AccEvent> valueChangeEvent =
+      new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, hyperText,
+                   eAutoDetect, AccEvent::eRemoveDupes);
+    mDocument->FireDelayedAccessibleEvent(valueChangeEvent);
+  }
+}
+
 PLDHashOperator
 NotificationController::TextEnumerator(nsCOMPtrHashKey<nsIContent>* aEntry,
                                        void* aUserArg)
 {
   nsDocAccessible* document = static_cast<nsDocAccessible*>(aUserArg);
   nsIContent* textNode = aEntry->GetKey();
   nsAccessible* textAcc = document->GetAccessible(textNode);
 
@@ -596,22 +879,22 @@ NotificationController::TextEnumerator(n
     NS_ASSERTION(!textAcc,
                  "Text node isn't rendered but accessible is kept alive!");
     return PL_DHASH_NEXT;
   }
 
   nsIContent* containerElm = containerNode->IsElement() ?
     containerNode->AsElement() : nsnull;
 
-  nsAutoString renderedText;
-  textFrame->GetRenderedText(&renderedText);
+  nsAutoString text;
+  textFrame->GetRenderedText(&text);
 
   // Remove text accessible if rendered text is empty.
   if (textAcc) {
-    if (renderedText.IsEmpty()) {
+    if (text.IsEmpty()) {
 #ifdef DEBUG_NOTIFICATIONS
       PRUint32 index = containerNode->IndexOf(textNode);
 
       nsCAutoString tag;
       nsCAutoString id;
       if (containerElm) {
         containerElm->Tag()->ToUTF8String(tag);
         nsIAtom* atomid = containerElm->GetID();
@@ -619,23 +902,46 @@ NotificationController::TextEnumerator(n
           atomid->ToUTF8String(id);
       }
 
       printf("\npending text node removal: container: %s@id='%s', index in container: %d\n\n",
              tag.get(), id.get(), index);
 #endif
 
       document->ContentRemoved(containerElm, textNode);
+      return PL_DHASH_NEXT;
     }
 
+    // Update text of the accessible and fire text change events.
+#ifdef DEBUG_TEXTCHANGE
+      PRUint32 index = containerNode->IndexOf(textNode);
+
+      nsCAutoString tag;
+      nsCAutoString id;
+      if (containerElm) {
+        containerElm->Tag()->ToUTF8String(tag);
+        nsIAtom* atomid = containerElm->GetID();
+        if (atomid)
+          atomid->ToUTF8String(id);
+      }
+
+      printf("\ntext may be changed: container: %s@id='%s', index in container: %d, old text '%s', new text: '%s'\n\n",
+             tag.get(), id.get(), index,
+             NS_ConvertUTF16toUTF8(textAcc->AsTextLeaf()->Text()).get(),
+             NS_ConvertUTF16toUTF8(text).get());
+#endif
+
+    TextUpdater updater(document, textAcc->AsTextLeaf());
+    updater.Run(text);
+
     return PL_DHASH_NEXT;
   }
 
   // Append an accessible if rendered text is not empty.
-  if (!renderedText.IsEmpty()) {
+  if (!text.IsEmpty()) {
 #ifdef DEBUG_NOTIFICATIONS
       PRUint32 index = containerNode->IndexOf(textNode);
 
       nsCAutoString tag;
       nsCAutoString id;
       if (containerElm) {
         containerElm->Tag()->ToUTF8String(tag);
         nsIAtom* atomid = containerElm->GetID();
@@ -717,8 +1023,9 @@ NotificationController::ContentInsertion
 #endif
 
   mDocument->ProcessContentInserted(mContainer, &mInsertedContent);
 
   mDocument = nsnull;
   mContainer = nsnull;
   mInsertedContent.Clear();
 }
+
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -46,16 +46,17 @@ class nsAccessible;
 class nsDocAccessible;
 class nsIContent;
 
 // Uncomment to log notifications processing.
 //#define DEBUG_NOTIFICATIONS
 
 #ifdef DEBUG_NOTIFICATIONS
 #define DEBUG_CONTENTMUTATION
+#define DEBUG_TEXTCHANGE
 #endif
 
 /**
  * Notification interface.
  */
 class Notification
 {
 public:
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -908,35 +908,32 @@ nsAccessibilityService::GetOrCreateAcces
     GetAccService()->GetDocAccessible(aNode->GetOwnerDoc());
   if (!docAcc) {
     NS_NOTREACHED("No document for accessible being created!");
     return nsnull;
   }
 
   // Attempt to create an accessible based on what we know.
   nsRefPtr<nsAccessible> newAcc;
+
+  // Create accessible for visible text frames.
   if (content->IsNodeOfType(nsINode::eTEXT)) {
-    // --- Create HTML for visible text frames ---
-    nsIFrame* f = weakFrame.GetFrame();
-    if (f && f->IsEmpty()) {
-      nsAutoString renderedWhitespace;
-      f->GetRenderedText(&renderedWhitespace, nsnull, nsnull, 0, 1);
-      if (renderedWhitespace.IsEmpty()) {
-        // Really empty -- nothing is rendered
-        if (aIsSubtreeHidden)
-          *aIsSubtreeHidden = true;
+    nsAutoString text;
+    weakFrame->GetRenderedText(&text, nsnull, nsnull, 0, PR_UINT32_MAX);
+    if (text.IsEmpty()) {
+      if (aIsSubtreeHidden)
+        *aIsSubtreeHidden = true;
 
-        return nsnull;
-      }
+      return nsnull;
     }
-    if (weakFrame.IsAlive()) {
-      newAcc = weakFrame.GetFrame()->CreateAccessible();
-      if (docAcc->BindToDocument(newAcc, nsnull))
-        return newAcc.forget();
-      return nsnull;
+
+    newAcc = weakFrame->CreateAccessible();
+    if (docAcc->BindToDocument(newAcc, nsnull)) {
+      newAcc->AsTextLeaf()->SetText(text);
+      return newAcc.forget();
     }
 
     return nsnull;
   }
 
   PRBool isHTML = content->IsHTML();
   if (isHTML && content->Tag() == nsAccessibilityAtoms::map) {
     // Create hyper text accessible for HTML map if it is used to group links
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1243,24 +1243,22 @@ void nsDocAccessible::DocumentStatesChan
                                             nsEventStates aStateMask)
 {
 }
 
 void nsDocAccessible::CharacterDataWillChange(nsIDocument *aDocument,
                                               nsIContent* aContent,
                                               CharacterDataChangeInfo* aInfo)
 {
-  FireTextChangeEventForText(aContent, aInfo, PR_FALSE);
 }
 
 void nsDocAccessible::CharacterDataChanged(nsIDocument *aDocument,
                                            nsIContent* aContent,
                                            CharacterDataChangeInfo* aInfo)
 {
-  FireTextChangeEventForText(aContent, aInfo, PR_TRUE);
 }
 
 void
 nsDocAccessible::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
                                  nsIContent* aChild, PRInt32 /* unused */)
 {
 }
 
@@ -1679,91 +1677,16 @@ nsDocAccessible::UpdateAccessibleOnAttrC
       (this, &nsDocAccessible::RecreateAccessible, aElement);
 
     return true;
   }
 
   return false;
 }
 
-void
-nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible)
-{
-  if (aAccessible->Role() != nsIAccessibleRole::ROLE_ENTRY)
-    return;
-
-  // Dependent value change event for text changes in textfields
-  nsRefPtr<AccEvent> valueChangeEvent =
-    new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible,
-                 eAutoDetect, AccEvent::eRemoveDupes);
-  FireDelayedAccessibleEvent(valueChangeEvent);
-}
-
-void
-nsDocAccessible::FireTextChangeEventForText(nsIContent *aContent,
-                                            CharacterDataChangeInfo* aInfo,
-                                            PRBool aIsInserted)
-{
-  if (!IsContentLoaded())
-    return;
-
-  PRInt32 contentOffset = aInfo->mChangeStart;
-  PRUint32 contentLength = aIsInserted ?
-    aInfo->mReplaceLength: // text has been added
-    aInfo->mChangeEnd - contentOffset; // text has been removed
-
-  if (contentLength == 0)
-    return;
-
-  nsAccessible *accessible = GetAccService()->GetAccessible(aContent);
-  if (!accessible)
-    return;
-
-  nsAccessible* parent = accessible->GetParent();
-  if (!parent)
-    return;
-
-  nsHyperTextAccessible* textAccessible = parent->AsHyperText();
-  if (!textAccessible)
-    return;
-
-  // Get offset within hypertext accessible and invalidate cached offsets after
-  // this child accessible.
-  PRInt32 offset = textAccessible->GetChildOffset(accessible, PR_TRUE);
-
-  // Get added or removed text.
-  nsIFrame* frame = aContent->GetPrimaryFrame();
-  if (!frame)
-    return;
-
-  PRUint32 textOffset = 0;
-  nsresult rv = textAccessible->ContentToRenderedOffset(frame, contentOffset,
-                                                        &textOffset);
-  if (NS_FAILED(rv))
-    return;
-
-  nsAutoString text;
-  rv = accessible->AppendTextTo(text, textOffset, contentLength);
-  if (NS_FAILED(rv))
-    return;
-
-  if (text.IsEmpty())
-    return;
-
-  // Normally we only fire delayed events created from the node, not an
-  // accessible object. See the AccTextChangeEvent constructor for details
-  // about this exceptional case.
-  nsRefPtr<AccEvent> event =
-    new AccTextChangeEvent(textAccessible, offset + textOffset, text,
-                          aIsInserted);
-  FireDelayedAccessibleEvent(event);
-
-  FireValueChangeForTextFields(textAccessible);
-}
-
 // nsDocAccessible public member
 nsresult
 nsDocAccessible::FireDelayedAccessibleEvent(PRUint32 aEventType, nsINode *aNode,
                                             AccEvent::EEventRule aAllowDupes,
                                             EIsFromUserInput aIsFromUserInput)
 {
   nsRefPtr<AccEvent> event =
     new AccEvent(aEventType, aNode, aIsFromUserInput, aAllowDupes);
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -410,35 +410,16 @@ protected:
     /**
      * Fires accessible events when ARIA attribute is changed.
      *
      * @param aContent - node that attribute is changed for
      * @param aAttribute - changed attribute
      */
     void ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute);
 
-    /**
-     * Fire text changed event for character data changed. The method is used
-     * from nsIMutationObserver methods.
-     *
-     * @param aContent     the text node holding changed data
-     * @param aInfo        info structure describing how the data was changed
-     * @param aIsInserted  the flag pointed whether removed or inserted
-     *                     characters should be cause of event
-     */
-    void FireTextChangeEventForText(nsIContent *aContent,
-                                    CharacterDataChangeInfo* aInfo,
-                                    PRBool aIsInserted);
-
-  /**
-   * Fire a value change event for the the given accessible if it is a text
-   * field (has a ROLE_ENTRY).
-   */
-  void FireValueChangeForTextFields(nsAccessible *aAccessible);
-
   /**
    * Process the event when the queue of pending events is untwisted. Fire
    * accessible events as result of the processing.
    */
   void ProcessPendingEvent(AccEvent* aEvent);
 
   /**
    * Process anchor jump notification and fire scrolling end event.
--- a/accessible/src/base/nsTextAccessible.h
+++ b/accessible/src/base/nsTextAccessible.h
@@ -49,20 +49,26 @@ class nsTextAccessible : public nsLinkab
 public:
   nsTextAccessible(nsIContent *aContent, nsIWeakReference *aShell);
 
   // nsAccessible
   virtual PRUint32 NativeRole();
   virtual nsresult AppendTextTo(nsAString& aText, PRUint32 aStartOffset,
                                 PRUint32 aLength);
 
+  // nsTextAccessible
+  void SetText(const nsAString& aText) { mText = aText; }
+  const nsString& Text() const { return mText; }
+
 protected:
-
   // nsAccessible
   virtual void CacheChildren();
+
+protected:
+  nsString mText;
 };
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsAccessible downcast method
 
 inline nsTextAccessible*
 nsAccessible::AsTextLeaf()
--- a/accessible/tests/mochitest/editabletext/editabletext.js
+++ b/accessible/tests/mochitest/editabletext/editabletext.js
@@ -31,46 +31,47 @@ function editableTextTestRun()
 /**
  * Used to test nsIEditableTextAccessible methods.
  */
 function editableTextTest(aID)
 {
   /**
    * setTextContents test.
    */
-  this.setTextContents = function setTextContents(aStr)
+  this.setTextContents = function setTextContents(aStr, aResValue)
   {
     var testID = "setTextContents '" + aStr + "' for " + prettyName(aID);
 
     function setTextContentsInvoke()
     {
       var acc = getAccessible(aID, nsIAccessibleEditableText);
       acc.setTextContents(aStr);
     }
 
-    this.sheduleTest(aID, null, [0, aStr.length, aStr],
-                     setTextContentsInvoke, getValueChecker(aID, aResValue),
-                     testID);
+    this.scheduleTest(aID, null, [0, aStr.length, aStr],
+                      setTextContentsInvoke, getValueChecker(aID, aResValue),
+                      testID);
   }
 
   /**
    * insertText test.
    */
-  this.insertText = function insertText(aStr, aPos, aResStr)
+  this.insertText = function insertText(aStr, aPos, aResStr, aResPos)
   {
     var testID = "insertText '" + aStr + "' at " + aPos + " for " +
       prettyName(aID);
 
     function insertTextInvoke()
     {
       var acc = getAccessible(aID, nsIAccessibleEditableText);
       acc.insertText(aStr, aPos);
     }
 
-    this.scheduleTest(aID, null, [aPos, aPos + aStr.length, aStr],
+    var resPos = (aResPos != undefined) ? aResPos : aPos;
+    this.scheduleTest(aID, null, [resPos, resPos + aStr.length, aStr],
                       insertTextInvoke, getValueChecker(aID, aResStr), testID);
   }
 
   /**
    * copyText test.
    */
   this.copyText = function copyText(aStartPos, aEndPos, aClipboardStr)
   {
@@ -105,28 +106,31 @@ function editableTextTest(aID)
 
     this.scheduleTest(aID, null, [aStartPos, aEndPos, getTextFromClipboard],
                       copyNPasteInvoke, getValueChecker(aID, aResStr), testID);
   }
 
   /**
    * cutText test.
    */
-  this.cutText = function cutText(aStartPos, aEndPos, aResStr)
+  this.cutText = function cutText(aStartPos, aEndPos, aResStr,
+                                  aResStartPos, aResEndPos)
   {
     var testID = "cutText from " + aStartPos + " to " + aEndPos + " for " +
       prettyName(aID);
 
     function cutTextInvoke()
     {
       var acc = getAccessible(aID, nsIAccessibleEditableText);
       acc.cutText(aStartPos, aEndPos);
     }
 
-    this.scheduleTest(aID, [aStartPos, aEndPos, getTextFromClipboard], null,
+    var resStartPos = (aResStartPos != undefined) ? aResStartPos : aStartPos;
+    var resEndPos = (aResEndPos != undefined) ? aResEndPos : aEndPos;
+    this.scheduleTest(aID, [resStartPos, resEndPos, getTextFromClipboard], null,
                       cutTextInvoke, getValueChecker(aID, aResStr), testID);
   }
 
   /**
    * cutText and pasteText test.
    */
   this.cutNPasteText = function copyNPasteText(aStartPos, aEndPos,
                                                aPos, aResStr)
--- a/accessible/tests/mochitest/editabletext/test_1.html
+++ b/accessible/tests/mochitest/editabletext/test_1.html
@@ -59,22 +59,24 @@ https://bugzilla.mozilla.org/show_bug.cg
 //      // cutNPasteText
 //      et.cutNPasteText(0, 1, 1, "ehhelloo");
 //      et.cutNPasteText(1, 2, 0, "hehelloo");
 //      et.cutNPasteText(7, 8, 8, "hehelloo");
 
       aTestRun.add(et);
     }
 
+    //gA11yEventDumpToConsole = true; // debug stuff
+
     function runTest()
     {
       var testRun = new editableTextTestRun();
 
       addTestEditable("input", testRun);
-      // addTestEditable("div"); XXX: bug 452599
+      addTestEditable("div", testRun);
       addTestEditable(getNode("frame").contentDocument, testRun);
 
       testRun.run(); // Will call SimpleTest.finish();
     }
 
     function doTest()
     {
       // Prepare tested elements.
@@ -89,20 +91,25 @@ https://bugzilla.mozilla.org/show_bug.cg
     addA11yLoadEvent(doTest);
   </script>
 </head>
 <body>
 
   <a target="_blank"
      title="nsIAccessibleEditableText chrome tests"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=452161">Mozilla Bug 452161</a>
+  <a target="_blank"
+     title="Cache rendered text on a11y side"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+    Mozilla Bug 626660
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <input id="input"/>
 
-  <div id="div" contentEditable="true"/>
+  <div id="div" contentEditable="true"></div>
 
   <iframe id="frame"/>
 </body>
 </html>
--- a/accessible/tests/mochitest/editabletext/test_2.html
+++ b/accessible/tests/mochitest/editabletext/test_2.html
@@ -17,19 +17,24 @@
   <script type="application/javascript"
           src="editabletext.js"></script>
 
   <script type="application/javascript">
     function doTest()
     {
       var et = new editableTextTest("input");
 
-      et.insertText("ee", 1, "heeello");
+      // 'ee' insertion/removal at 1 or 2 offset of 'hello'/'heeello' string
+      // reports 'ee' text was inserted/removed at 2 offset.
+      et.insertText("ee", 1, "heeello", 2);
       et.copyText(1, 3, "ee");
-      et.cutText(1, 3, "hello");
+      et.cutText(1, 3, "hello", 2, 4);
+      et.insertText("ee", 2, "heeello", 2);
+      et.cutText(2, 4, "hello", 2, 4);
+
       et.deleteText(1, 3, "hlo");
       et.pasteText(1, "heelo");
 
       var testRun = new editableTextTestRun();
       testRun.add(et);
       testRun.run(); // Will call SimpleTest.finish();
     }
 
@@ -39,16 +44,21 @@
 </head>
 <body>
 
   <a target="_blank"
      title="HyperText accessible should get focus when the caret is positioned inside of it, text is changed or copied into clipboard by ATs"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=524115">
     Mozilla Bug 524115
   </a>
+  <a target="_blank"
+     title="Cache rendered text on a11y side"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660">
+    Mozilla Bug 626660
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <input id="input" value="hello"/>
 
 </body>
--- a/accessible/tests/mochitest/hypertext/test_update.html
+++ b/accessible/tests/mochitest/hypertext/test_update.html
@@ -85,16 +85,18 @@
       }
 
       this.getID = function updateText_getID()
       {
         return "update text for '" + aContainerID + "'";
       }
     }
 
+    //gA11yEventDumpToConsole = true; // debug stuff
+
     var gQueue = null;
     function doTest()
     {
       gQueue = new eventQueue();
       gQueue.push(new addLinks("p1"));
       gQueue.push(new updateText("p2"));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
--- a/accessible/tests/mochitest/name/test_general.html
+++ b/accessible/tests/mochitest/name/test_general.html
@@ -68,18 +68,17 @@
 
       // Gets the name from image accessible.
       testName("btn_labelledby_mixed_img", "text image");
 
       // Gets the name from input accessibles
       // Note: if input have label elements then the name isn't calculated
       // from them.
       testName("btn_labelledby_mixed_input",
-               "Submit Query Reset Submit Query");
-      // XXX Bug 567203 "input button Submit Query Reset Submit Query");
+               "input button Submit Query Reset Submit Query");
 
       // Gets the name from the title of object element.
       testName("btn_labelledby_mixed_object", "object");
 
       // Gets the name from text nodes. Element br adds space between them.
       testName("btn_labelledby_mixed_br", "text text");
 
       // Gets the name from label content which allows name from subtree,
--- a/accessible/tests/mochitest/tree/test_combobox.xul
+++ b/accessible/tests/mochitest/tree/test_combobox.xul
@@ -47,21 +47,17 @@
       //////////////////////////////////////////////////////////////////////////
       // editable menulist
 
       accTree = {
         role: ROLE_COMBOBOX,
         children: [
           {
             role: ROLE_ENTRY,
-            children: [
-              {
-                role: ROLE_TEXT_LEAF // Text node for the node's value
-              }
-            ]
+            children: [ ] // no text leaf accessible for text node
           },
           {
             role: ROLE_COMBOBOX_LIST, // context menu
             children: []
           },
           {
             role: ROLE_PUSHBUTTON, // dropmarker
           },
@@ -89,17 +85,18 @@
 
       accTree = {
         role: ROLE_AUTOCOMPLETE,
         children: [
           {
             role: ROLE_ENTRY,
             children: [
               {
-                role: ROLE_TEXT_LEAF
+                role: ROLE_TEXT_LEAF,
+                name: "http://mochi.test:8888/redirect-a11y.html"
               }
             ]
           },
           {
             role: ROLE_COMBOBOX_LIST, // context menu popup
             children: [ ]
           }
         ]
@@ -117,21 +114,17 @@
             children: [
               {
                 role: ROLE_COMBOBOX_OPTION
               }
             ]
           },
           {
             role: ROLE_ENTRY,
-            children: [
-              {
-                role: ROLE_TEXT_LEAF // Text node for the node's value
-              }
-            ]
+            children: [ ] // no text leaf accessible for text node
           },
           {
             role: ROLE_COMBOBOX_LIST, // context menu popup
             children: [ ]
           }
         ]
       };
       testAccessibleTree("autocomplete2", accTree);
@@ -145,16 +138,21 @@
   </script>
 
   <hbox flex="1" style="overflow: auto;">
     <body xmlns="http://www.w3.org/1999/xhtml">
       <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=249292"
          title="Ensure accessible children for toolbarbutton types 'menu' and 'menu-button'">
         Mozilla Bug 249292
+      </a>
+      <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=626660"
+         title="Cache rendered text on a11y side">
+        Mozilla Bug 626660
       </a><br/>
       <p id="display"></p>
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>
 
--- a/accessible/tests/mochitest/tree/test_dockids.html
+++ b/accessible/tests/mochitest/tree/test_dockids.html
@@ -24,19 +24,17 @@
      { DOCUMENT: [
        { PARAGRAPH: [ // head
          { PARAGRAPH: [ // link
            { STATICTEXT: [] }, // generated content
            { STATICTEXT: [] } // generated content
          ] }
        ] },
        { TEXT_LEAF: [ ] }, // body text
-       { ENTRY: [ // input under document element
-         { TEXT_LEAF: [ ] }
-       ] },
+       { ENTRY: [ ] }, // input under document element
        { PARAGRAPH: [ // link under document element
          { TEXT_LEAF: [ ] }, // link content
          { STATICTEXT: [ ] }, // generated content
          { STATICTEXT: [ ] } // generated content
        ] },
        { LINK: [ // anchor under document element
          { TEXT_LEAF: [ ] } // anchor content
        ] },
--- a/accessible/tests/mochitest/tree/test_txtctrl.html
+++ b/accessible/tests/mochitest/tree/test_txtctrl.html
@@ -42,19 +42,17 @@
           }
         ]
       };
 
       testAccessibleTree("txc2", accTree);
 
       // input@type="text", no value
       accTree =
-        { ENTRY: [
-          { TEXT_LEAF: [ ] }
-        ] };
+        { ENTRY: [ ] };
 
       testAccessibleTree("txc3", accTree);
 
       // textarea
       accTree = {
         role: ROLE_ENTRY,
         children: [
           {
--- a/accessible/tests/mochitest/treeupdate/test_doc.html
+++ b/accessible/tests/mochitest/treeupdate/test_doc.html
@@ -318,19 +318,17 @@
       {
         this.docNode.documentElement.appendChild(this.inputNode);
       }
 
       this.finalCheck = function finalCheck()
       {
         var tree =
           { DOCUMENT: [
-            { ENTRY: [
-              { TEXT_LEAF: [ ] }
-            ] }
+            { ENTRY: [ ] }
           ] };
         testAccessibleTree(this.docNode, tree);
 
         // Remove aftermath of this test before next test starts.
         this.docNode.documentElement.removeChild(this.inputNode);
       }
 
       this.getID = function getID()