Bug 630001 - Stop usage of nsIFrame::GetRenderedText in GetTextAt/Before/After for boundary char, r=fer, a=betaN
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 03 Feb 2011 22:29:12 +0800
changeset 61843 e9fe7402dc536c0ba9386381da934b718bf19795
parent 61842 f6cbc806e87cdf852218c232bd261666a4844f88
child 61844 dfe5ab77a706b6d795d406b9a13f56c666e8cc0c
push id18518
push usersurkov.alexander@gmail.com
push dateThu, 03 Feb 2011 16:49:38 +0000
treeherdermozilla-central@e9fe7402dc53 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfer, betaN
bugs630001
milestone2.0b12pre
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 630001 - Stop usage of nsIFrame::GetRenderedText in GetTextAt/Before/After for boundary char, r=fer, a=betaN
accessible/src/html/nsHyperTextAccessible.cpp
accessible/src/html/nsHyperTextAccessible.h
accessible/tests/mochitest/text.js
accessible/tests/mochitest/text/test_singleline.html
accessible/tests/mochitest/text/test_whitespaces.html
--- a/accessible/src/html/nsHyperTextAccessible.cpp
+++ b/accessible/src/html/nsHyperTextAccessible.cpp
@@ -478,61 +478,53 @@ NS_IMETHODIMP
 nsHyperTextAccessible::GetText(PRInt32 aStartOffset, PRInt32 aEndOffset,
                                nsAString &aText)
 {
   aText.Truncate();
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
-  if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
-    aStartOffset = CharacterCount();
-  else if (aStartOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
-    GetCaretOffset(&aStartOffset);
-
-  if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
-    aEndOffset = CharacterCount();
-  else if (aEndOffset == nsIAccessibleText::TEXT_OFFSET_CARET)
-    GetCaretOffset(&aEndOffset);
-
-  PRInt32 startChildIdx = GetChildIndexAtOffset(aStartOffset);
+  PRInt32 startOffset = ConvertMagicOffset(aStartOffset);
+  PRInt32 startChildIdx = GetChildIndexAtOffset(startOffset);
   if (startChildIdx == -1)
     return NS_ERROR_INVALID_ARG;
 
-  PRInt32 endChildIdx = GetChildIndexAtOffset(aEndOffset);
+  PRInt32 endOffset = ConvertMagicOffset(aEndOffset);
+  PRInt32 endChildIdx = GetChildIndexAtOffset(endOffset);
   if (endChildIdx == -1)
     return NS_ERROR_INVALID_ARG;
 
   if (startChildIdx == endChildIdx) {
     PRInt32 childOffset =  GetChildOffset(startChildIdx);
     NS_ENSURE_STATE(childOffset != -1);
 
     nsAccessible* child = GetChildAt(startChildIdx);
-    child->AppendTextTo(aText, aStartOffset - childOffset,
-                        aEndOffset - aStartOffset);
+    child->AppendTextTo(aText, startOffset - childOffset,
+                        endOffset - startOffset);
 
     return NS_OK;
   }
 
   PRInt32 startChildOffset =  GetChildOffset(startChildIdx);
   NS_ENSURE_STATE(startChildOffset != -1);
 
   nsAccessible* startChild = GetChildAt(startChildIdx);
-  startChild->AppendTextTo(aText, aStartOffset - startChildOffset);
+  startChild->AppendTextTo(aText, startOffset - startChildOffset);
 
   for (PRInt32 childIdx = startChildIdx + 1; childIdx < endChildIdx; childIdx++) {
     nsAccessible* child = GetChildAt(childIdx);
     child->AppendTextTo(aText);
   }
 
   PRInt32 endChildOffset =  GetChildOffset(endChildIdx);
   NS_ENSURE_STATE(endChildOffset != -1);
 
   nsAccessible* endChild = GetChildAt(endChildIdx);
-  endChild->AppendTextTo(aText, 0, aEndOffset - endChildOffset);
+  endChild->AppendTextTo(aText, 0, endOffset - endChildOffset);
 
   return NS_OK;
 }
 
 /*
  * Gets the character count.
  */
 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterCount(PRInt32 *aCharacterCount)
@@ -547,30 +539,29 @@ NS_IMETHODIMP nsHyperTextAccessible::Get
   return NS_OK;
 }
 
 /*
  * Gets the specified character.
  */
 NS_IMETHODIMP nsHyperTextAccessible::GetCharacterAtOffset(PRInt32 aOffset, PRUnichar *aCharacter)
 {
+  NS_ENSURE_ARG_POINTER(aCharacter);
+  *aCharacter = nsnull;
+
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
-  nsAutoString text;
-  nsresult rv = GetText(aOffset, aOffset + 1, text);
-  if (NS_FAILED(rv)) {
-    return rv;
+  nsAutoString character;
+  if (GetCharAt(aOffset, eGetAt, character)) {
+    *aCharacter = character.First();
+    return NS_OK;
   }
 
-  if (text.IsEmpty()) {
-    return NS_ERROR_FAILURE;
-  }
-  *aCharacter = text.First();
-  return NS_OK;
+  return NS_ERROR_INVALID_ARG;
 }
 
 nsAccessible*
 nsHyperTextAccessible::DOMPointToHypertextOffset(nsINode *aNode,
                                                  PRInt32 aNodeOffset,
                                                  PRInt32 *aHyperTextOffset,
                                                  PRBool aIsEndOffset)
 {
@@ -1087,28 +1078,43 @@ nsresult nsHyperTextAccessible::GetTextH
 }
 
 /**
   * nsIAccessibleText impl.
   */
 NS_IMETHODIMP nsHyperTextAccessible::GetTextBeforeOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
                                                          PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
 {
+  if (aBoundaryType == BOUNDARY_CHAR) {
+    GetCharAt(aOffset, eGetBefore, aText, aStartOffset, aEndOffset);
+    return NS_OK;
+  }
+
   return GetTextHelper(eGetBefore, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
 }
 
 NS_IMETHODIMP nsHyperTextAccessible::GetTextAtOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
                                                      PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
 {
+  if (aBoundaryType == BOUNDARY_CHAR) {
+    GetCharAt(aOffset, eGetAt, aText, aStartOffset, aEndOffset);
+    return NS_OK;
+  }
+
   return GetTextHelper(eGetAt, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
 }
 
 NS_IMETHODIMP nsHyperTextAccessible::GetTextAfterOffset(PRInt32 aOffset, nsAccessibleTextBoundary aBoundaryType,
                                                         PRInt32 *aStartOffset, PRInt32 *aEndOffset, nsAString & aText)
 {
+  if (aBoundaryType == BOUNDARY_CHAR) {
+    GetCharAt(aOffset, eGetAfter, aText, aStartOffset, aEndOffset);
+    return NS_OK;
+  }
+
   return GetTextHelper(eGetAfter, aBoundaryType, aOffset, aStartOffset, aEndOffset, aText);
 }
 
 // nsIPersistentProperties
 // nsIAccessibleText::getTextAttributes(in boolean includeDefAttrs,
 //                                      in long offset,
 //                                      out long rangeStartOffset,
 //                                      out long rangeEndOffset);
@@ -2159,16 +2165,39 @@ nsresult nsHyperTextAccessible::Rendered
   *aContentOffset = iter.ConvertSkippedToOriginal(aRenderedOffset + ourRenderedStart) - ourContentStart;
 
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHyperTextAccessible public
 
+bool
+nsHyperTextAccessible::GetCharAt(PRInt32 aOffset, EGetTextType aShift,
+                                 nsAString& aChar, PRInt32* aStartOffset,
+                                 PRInt32* aEndOffset)
+{
+  aChar.Truncate();
+
+  PRInt32 offset = ConvertMagicOffset(aOffset) + static_cast<PRInt32>(aShift);
+  PRInt32 childIdx = GetChildIndexAtOffset(offset);
+  if (childIdx == -1)
+    return false;
+
+  nsAccessible* child = GetChildAt(childIdx);
+  child->AppendTextTo(aChar, offset - GetChildOffset(childIdx), 1);
+
+  if (aStartOffset)
+    *aStartOffset = offset;
+  if (aEndOffset)
+    *aEndOffset = aChar.IsEmpty() ? offset : offset + 1;
+
+  return true;
+}
+
 PRInt32
 nsHyperTextAccessible::GetChildOffset(PRUint32 aChildIndex,
                                       PRBool aInvalidateAfter)
 {
   if (aChildIndex == 0) {
     if (aInvalidateAfter)
       mOffsets.Clear();
 
--- a/accessible/src/html/nsHyperTextAccessible.h
+++ b/accessible/src/html/nsHyperTextAccessible.h
@@ -206,16 +206,30 @@ public:
    * Return character count within the hypertext accessible.
    */
   inline PRUint32 CharacterCount()
   {
     return GetChildOffset(GetChildCount());
   }
 
   /**
+   * Get a character before/at/after the given offset.
+   *
+   * @param aOffset       [in] the given offset
+   * @param aShift        [in] specifies whether to get a char before/at/after
+   *                        offset
+   * @param aChar         [out] the character
+   * @param aStartOffset  [out, optional] the start offset of the character
+   * @param aEndOffset    [out, optional] the end offset of the character
+   * @return               false if offset at the given shift is out of range
+   */
+  bool GetCharAt(PRInt32 aOffset, EGetTextType aShift, nsAString& aChar,
+                 PRInt32* aStartOffset = nsnull, PRInt32* aEndOffset = nsnull);
+
+  /**
    * Return text offset of the given child accessible within hypertext
    * accessible.
    *
    * @param  aChild           [in] accessible child to get text offset for
    * @param  aInvalidateAfter [in, optional] indicates whether invalidate
    *                           cached offsets for next siblings of the child
    */
   PRInt32 GetChildOffset(nsAccessible* aChild,
@@ -246,16 +260,33 @@ public:
   nsAccessible* GetChildAtOffset(PRUint32 aOffset)
   {
     return GetChildAt(GetChildIndexAtOffset(aOffset));
   }
 
 protected:
   // nsHyperTextAccessible
 
+  /**
+   * Transform magic offset into text offset.
+   */
+  inline PRInt32 ConvertMagicOffset(PRInt32 aOffset)
+  {
+    if (aOffset == nsIAccessibleText::TEXT_OFFSET_END_OF_TEXT)
+      return CharacterCount();
+
+    if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
+      PRInt32 caretOffset = -1;
+      GetCaretOffset(&caretOffset);
+      return caretOffset;
+    }
+
+    return aOffset;
+  }
+
   /*
    * This does the work for nsIAccessibleText::GetText[At|Before|After]Offset
    * @param aType, eGetBefore, eGetAt, eGetAfter
    * @param aBoundaryType, char/word-start/word-end/line-start/line-end/paragraph/attribute
    * @param aOffset, offset into the hypertext to start from
    * @param *aStartOffset, the resulting start offset for the returned substring
    * @param *aEndOffset, the resulting end offset for the returned substring
    * @param aText, the resulting substring
--- a/accessible/tests/mochitest/text.js
+++ b/accessible/tests/mochitest/text.js
@@ -39,16 +39,38 @@ function testText(aIDs, aStartOffset, aE
       ok(false,
          "getText fails between start and end offsets '" + aStartOffset +
          "', '" + aEndOffset + " for '" + prettyName(aIDs[i]) + "'");
     }
   }
 }
 
 /**
+ * Test getTextAtOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs          [in] the accessible identifier or array of accessible
+ *                        identifiers
+ * @param aOffset       [in] the offset to get a character at it
+ * @param aChar         [in] the expected character
+ * @param aStartOffset  [in] expected start offset of the character
+ * @param aEndOffset    [in] expected end offset of the character
+ */
+function testCharAtOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+  var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+  for (var i = 0; i < IDs.length; i++) {
+    var acc = getAccessible(IDs[i], nsIAccessibleText);
+    testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+                   aChar, aStartOffset, aEndOffset,
+                   kOk, kOk, kOk,
+                   acc.getTextAtOffset, "getTextAtOffset ");
+  }
+}
+
+/**
  * Test getTextAtOffset function over different elements
  *
  * @param aOffset         [in] the offset to get the text at
  * @param aBoundaryType   [in] Boundary type for text to be retrieved
  * @param aText           [in] expected return text for getTextAtOffset
  * @param aStartOffset    [in] expected return start offset for getTextAtOffset
  * @param aEndOffset      [in] expected return end offset for getTextAtOffset
  * @param ...             [in] list of tuples made of:
@@ -71,29 +93,50 @@ function testTextAtOffset(aOffset, aBoun
     testTextHelper(ID, aOffset, aBoundaryType,
                    aText, aStartOffset, aEndOffset,
                    toDoFlag1, toDoFlag2, toDoFlag3,
                    acc.getTextAtOffset, "getTextAtOffset ");
   }
 }
 
 /**
+ * Test getTextAfterOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs          [in] the accessible identifier or array of accessible
+ *                        identifiers
+ * @param aOffset       [in] the offset to get a character after it
+ * @param aChar         [in] the expected character
+ * @param aStartOffset  [in] expected start offset of the character
+ * @param aEndOffset    [in] expected end offset of the character
+ */
+function testCharAfterOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+  var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+  for (var i = 0; i < IDs.length; i++) {
+    var acc = getAccessible(IDs[i], nsIAccessibleText);
+    testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+                   aChar, aStartOffset, aEndOffset,
+                   kOk, kOk, kOk,
+                   acc.getTextAfterOffset, "getTextAfterOffset ");
+  }
+}
+
+/**
  * Test getTextAfterOffset function over different elements
  *
  * @param aOffset         [in] the offset to get the text after
  * @param aBoundaryType   [in] Boundary type for text to be retrieved
  * @param aText           [in] expected return text for getTextAfterOffset
  * @param aStartOffset    [in] expected return start offset for getTextAfterOffset
  * @param aEndOffset      [in] expected return end offset for getTextAfterOffset
  * @param ...             [in] list of tuples made of:
  *                              element identifier
  *                              kTodo or kOk for returned text
  *                              kTodo or kOk for returned start offset
  *                              kTodo or kOk for returned offset result
- *          
  */
 function testTextAfterOffset(aOffset, aBoundaryType,
                              aText, aStartOffset, aEndOffset)
 {
   for (var i = 5; i < arguments.length; i = i + 4) {
     var ID = arguments[i];
     var acc = getAccessible(ID, nsIAccessibleText);
     var toDoFlag1 = arguments[i + 1];
@@ -103,16 +146,38 @@ function testTextAfterOffset(aOffset, aB
     testTextHelper(ID, aOffset, aBoundaryType,
                    aText, aStartOffset, aEndOffset,
                    toDoFlag1, toDoFlag2, toDoFlag3, 
                    acc.getTextAfterOffset, "getTextAfterOffset ");
   }
 }
 
 /**
+ * Test getTextBeforeOffset for BOUNDARY_CHAR over different elements.
+ *
+ * @param aIDs          [in] the accessible identifier or array of accessible
+ *                        identifiers
+ * @param aOffset       [in] the offset to get a character before it
+ * @param aChar         [in] the expected character
+ * @param aStartOffset  [in] expected start offset of the character
+ * @param aEndOffset    [in] expected end offset of the character
+ */
+function testCharBeforeOffset(aIDs, aOffset, aChar, aStartOffset, aEndOffset)
+{
+  var IDs = (aIDs instanceof Array) ? aIDs : [ aIDs ];
+  for (var i = 0; i < IDs.length; i++) {
+    var acc = getAccessible(IDs[i], nsIAccessibleText);
+    testTextHelper(IDs[i], aOffset, BOUNDARY_CHAR,
+                   aChar, aStartOffset, aEndOffset,
+                   kOk, kOk, kOk,
+                   acc.getTextBeforeOffset, "getTextBeforeOffset ");
+  }
+}
+
+/**
  * Test getTextBeforeOffset function over different elements
  *
  * @param aOffset         [in] the offset to get the text before
  * @param aBoundaryType   [in] Boundary type for text to be retrieved
  * @param aText           [in] expected return text for getTextBeforeOffset
  * @param aStartOffset    [in] expected return start offset for getTextBeforeOffset
  * @param aEndOffset      [in] expected return end offset for getTextBeforeOffset
  * @param ...             [in] list of tuples made of:
--- a/accessible/tests/mochitest/text/test_singleline.html
+++ b/accessible/tests/mochitest/text/test_singleline.html
@@ -32,37 +32,33 @@
       testText(IDs, 0, 1, "h");
       testText(IDs, 1, 3, "el");
       testText(IDs, 14, 15, "d");
       testText(IDs, 0, 15, "hello my friend");
 
       ////////////////////////////////////////////////////////////////////////
       // getTextAfterOffset
 
+      var IDs = [ "input", "div", "editable", "textarea" ];
+      var regularIDs = [ "input", "div", "editable" ];
+
       // BOUNDARY_CHAR
-      testTextAfterOffset(0, BOUNDARY_CHAR, "e", 1, 2,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(1, BOUNDARY_CHAR, "l", 2, 3,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(14, BOUNDARY_CHAR, "", 15, 15,
-                          "input", kTodo, kTodo, kOk,
-                          "div", kTodo, kTodo, kOk,
-                          "editable", kTodo, kTodo, kOk,
-                          "textarea", kTodo, kTodo, kOk);
+
+      testCharAfterOffset(IDs, 0, "e", 1, 2);
+      testCharAfterOffset(IDs, 1, "l", 2, 3);
+      testCharAfterOffset(regularIDs, 14, "", 15, 15);
+      testCharAfterOffset("textarea", 14, "\n", 15, 16);
+
+      // XXX: why are 15/15 expected? there's no 16 offset we are trying to
+      // get an offset for?
       testTextAfterOffset(15, BOUNDARY_CHAR, "", 15, 15,
 			  "input", kOk, kTodo, kTodo,
 			  "div", kOk, kTodo, kTodo,
-			  "editable", kOk, kTodo, kTodo,
-			  "textarea", kTodo, kOk, kTodo);
+			  "editable", kOk, kTodo, kTodo);
+      testCharAfterOffset("textarea", 15, "", 16, 16);
 
       // BOUNDARY_WORD_START
       testTextAfterOffset(0, BOUNDARY_WORD_START, "my ", 6, 9,
                           "input", kTodo, kTodo, kTodo,
                           "div", kTodo, kTodo, kTodo,
                           "editable", kTodo, kTodo, kTodo,
                           "textarea", kTodo, kTodo, kTodo);
       testTextAfterOffset(1, BOUNDARY_WORD_START, "my ", 6, 9,
@@ -205,37 +201,23 @@
                           "input", kOk, kTodo, kTodo,
                           "div", kOk, kTodo, kTodo,
                           "editable", kOk, kTodo, kTodo,
                           "textarea", kOk, kTodo, kTodo);
 
       ////////////////////////////////////////////////////////////////////////
       // getTextBeforeOffset
 
+      var IDs = [ "input", "div", "editable", "textarea" ];
+
       // BOUNDARY_CHAR
-      testTextBeforeOffset(0, BOUNDARY_CHAR, "", 0, 0, 
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(1, BOUNDARY_CHAR, "h", 0, 1,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(14, BOUNDARY_CHAR, "n", 13, 14,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(15, BOUNDARY_CHAR, "d", 14, 15,
-                           "input", kTodo, kTodo, kTodo,
-                           "div", kTodo, kTodo, kTodo,
-                           "editable", kTodo, kTodo, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
+      testCharBeforeOffset(IDs, 0, "", 0, 0);
+      testCharBeforeOffset(IDs, 1, "h", 0, 1);
+      testCharBeforeOffset(IDs, 14, "n", 13, 14);
+      testCharBeforeOffset(IDs, 15, "d", 14, 15);
 
       // BOUNDARY_WORD_START
       testTextBeforeOffset(0, BOUNDARY_WORD_START, "", 0, 0,
                            "input", kTodo, kOk, kTodo,
                            "div", kTodo, kOk, kTodo,
                            "editable", kTodo, kOk, kTodo,
                            "textarea", kTodo, kOk, kTodo);
       testTextBeforeOffset(1, BOUNDARY_WORD_START, "", 0, 0,
@@ -378,37 +360,27 @@
                            "input", kOk, kOk, kOk,
                            "div", kOk, kOk, kOk,
                            "editable", kOk, kOk, kOk,
                            "textarea", kOk, kOk, kOk);
 
       ////////////////////////////////////////////////////////////////////////
       // getTextAtOffset
 
+      IDs = [ "input", "div", "editable", "textarea" ];
+      regularIDs = [ "input", "div", "editable" ];
+
       // BOUNDARY_CHAR
-      testTextAtOffset(0, BOUNDARY_CHAR, "h", 0, 1,
-                       "input", kOk, kOk, kOk,
-                       "div", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(1, BOUNDARY_CHAR, "e", 1, 2,
-                       "input", kOk, kOk, kOk,
-                       "div", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(14, BOUNDARY_CHAR, "d", 14, 15,
-                       "input", kOk, kOk, kOk,
-                       "div", kOk, kOk, kOk,
-                       "editable", kOk, kOk, kOk,
-                       "textarea", kOk, kOk, kOk);
-      testTextAtOffset(15, BOUNDARY_CHAR, "", 15, 15,
-                        "input", kOk, kTodo, kTodo,
-                        "div", kOk, kTodo, kTodo,
-                        "editable", kOk, kTodo, kTodo,
-                        "textarea", kTodo, kOk, kTodo);
+
+      testCharAtOffset(IDs, 0, "h", 0, 1);
+      testCharAtOffset(IDs, 1, "e", 1, 2);
+      testCharAtOffset(IDs, 14, "d", 14, 15);
+      testCharAtOffset(regularIDs, 15, "", 15, 15);
+      testCharAtOffset("textarea", 15, "\n", 15, 16);
+      testCharAtOffset("textarea", 16, "", 16, 16);
 
       // BOUNDARY_WORD_START
       testTextAtOffset(0, BOUNDARY_WORD_START, "hello ", 0, 6,
                        "input", kOk, kOk, kOk,
                        "div", kOk, kOk, kOk,
                        "editable", kOk, kOk, kOk,
                        "textarea", kOk, kOk, kOk);
       testTextAtOffset(1, BOUNDARY_WORD_START, "hello ", 0, 6,
--- a/accessible/tests/mochitest/text/test_whitespaces.html
+++ b/accessible/tests/mochitest/text/test_whitespaces.html
@@ -36,72 +36,30 @@
       testText(IDs, 5, 6, " ");
       testText(IDs, 9, 11, "  ");
       testText(IDs, 16, 19, "   ");
       testText(IDs, 0, 22, "Brave Sir  Robin   ran");
 
       ////////////////////////////////////////////////////////////////////////
       // getTextAfterOffset
 
+      var IDs = [ "input", "div", "editable", "textarea" ];
+
       // BOUNDARY_CHAR
-      testTextAfterOffset(0, BOUNDARY_CHAR, "r", 1, 2,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(1, BOUNDARY_CHAR, "a", 2, 3,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(4, BOUNDARY_CHAR, " ", 5, 6,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(5, BOUNDARY_CHAR, "S", 6, 7,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(8, BOUNDARY_CHAR, " ", 9, 10,
-                          "input", kTodo, kTodo, kTodo,
-                          "div", kTodo, kTodo, kTodo,
-                          "editable", kTodo, kTodo, kTodo,
-                          "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(9, BOUNDARY_CHAR, " ", 10, 11,
-                          "input", kOk, kTodo, kTodo,
-                          "div", kOk, kTodo, kTodo,
-                          "editable", kOk, kTodo, kTodo,
-                          "textarea", kOk, kTodo, kTodo);
-      testTextAfterOffset(10, BOUNDARY_CHAR, "R", 11, 12,
-			  "input", kTodo, kTodo, kTodo,
-			  "div", kTodo, kTodo, kTodo,
-			  "editable", kTodo, kTodo, kTodo,
-			  "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(15, BOUNDARY_CHAR, " ", 16, 17,
-			  "input", kTodo, kTodo, kTodo,
-			  "div", kTodo, kTodo, kTodo,
-			  "editable", kTodo, kTodo, kTodo,
-			  "textarea", kTodo, kTodo, kTodo);
-      testTextAfterOffset(16, BOUNDARY_CHAR, " ", 17, 18,
-			  "input", kOk, kTodo, kTodo,
-			  "div", kOk, kTodo, kTodo,
-			  "editable", kOk, kTodo, kTodo,
-			  "textarea", kOk, kTodo, kTodo);
-      testTextAfterOffset(17, BOUNDARY_CHAR, " ", 18, 19,
-			  "input", kOk, kTodo, kTodo,
-			  "div", kOk, kTodo, kTodo,
-			  "editable", kOk, kTodo, kTodo,
-			  "textarea", kOk, kTodo, kTodo);
-      testTextAfterOffset(18, BOUNDARY_CHAR, "r", 19, 20,
-			  "input", kTodo, kTodo, kTodo,
-			  "div", kTodo, kTodo, kTodo,
-			  "editable", kTodo, kTodo, kTodo,
-			  "textarea", kTodo, kTodo, kTodo);
+      testCharAfterOffset(IDs, 0, "r", 1, 2);
+      testCharAfterOffset(IDs, 1, "a", 2, 3);
+      testCharAfterOffset(IDs, 4, " ", 5, 6);
+      testCharAfterOffset(IDs, 5, "S", 6, 7);
+      testCharAfterOffset(IDs, 8, " ", 9, 10);
+      testCharAfterOffset(IDs, 9, " ", 10, 11);
+      testCharAfterOffset(IDs, 10, "R", 11, 12);
+      testCharAfterOffset(IDs, 15, " ", 16, 17);
+      testCharAfterOffset(IDs, 16, " ", 17, 18);
+      testCharAfterOffset(IDs, 17, " ", 18, 19);
+      testCharAfterOffset(IDs, 18, "r", 19, 20);
 
       // BOUNDARY_WORD_START
       testTextAfterOffset(0, BOUNDARY_WORD_START, "Sir  ", 6, 11,
                           "input", kTodo, kTodo, kTodo,
                           "div", kTodo, kTodo, kTodo,
                           "editable", kTodo, kTodo, kTodo,
                           "textarea", kTodo, kTodo, kTodo);
       testTextAfterOffset(5, BOUNDARY_WORD_START, "Sir  ", 6, 11,
@@ -220,52 +178,26 @@
                           "input", kOk, kTodo, kTodo,
                           "div", kOk, kTodo, kTodo,
                           "editable", kOk, kTodo, kTodo,
                           "textarea", kTodo, kOk, kTodo);
 
       ////////////////////////////////////////////////////////////////////////
       // getTextBeforeOffset
 
+      var IDs = [ "input", "div", "editable", "textarea" ];
+
       // BOUNDARY_CHAR
-      testTextBeforeOffset(0, BOUNDARY_CHAR, "", 0, 0, 
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(1, BOUNDARY_CHAR, "B", 0, 1,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(6, BOUNDARY_CHAR, " ", 5, 6,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(10, BOUNDARY_CHAR, " ", 9, 10,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(11, BOUNDARY_CHAR, " ", 10, 11,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(17, BOUNDARY_CHAR, " ", 16, 17,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
-      testTextBeforeOffset(19, BOUNDARY_CHAR, " ", 18, 19,
-                           "input", kTodo, kOk, kTodo,
-                           "div", kTodo, kOk, kTodo,
-                           "editable", kTodo, kOk, kTodo,
-                           "textarea", kTodo, kOk, kTodo);
+      testCharBeforeOffset(IDs, 0, "", 0, 0);
+      testCharBeforeOffset(IDs, 1, "B", 0, 1);
+      testCharBeforeOffset(IDs, 6, " ", 5, 6);
+      testCharBeforeOffset(IDs, 10, " ", 9, 10);
+      testCharBeforeOffset(IDs, 11, " ", 10, 11);
+      testCharBeforeOffset(IDs, 17, " ", 16, 17);
+      testCharBeforeOffset(IDs, 19, " ", 18, 19);
 
       // BOUNDARY_WORD_START
       testTextBeforeOffset(0, BOUNDARY_WORD_START, "", 0, 0,
                            "input", kTodo, kOk, kTodo,
                            "div", kTodo, kOk, kTodo,
                            "editable", kTodo, kOk, kTodo,
                            "textarea", kTodo, kOk, kTodo);
       testTextBeforeOffset(1, BOUNDARY_WORD_START, "", 0, 0,