Bug 961737 - getTextAtOffset is broken w.r.t. embedded object characters, part3 (list items), r=tbsaunde
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 05 Feb 2014 10:21:32 -0500
changeset 166960 826d951bd6fa1e2b1159d68bc490ad14653ed2dc
parent 166959 993ddadafaea669d70c009b4ae3ed69a171f7793
child 166961 62a1d33b9fe0a3b9d566613333b09e717d69c83f
push id39341
push usersurkov.alexander@gmail.com
push dateWed, 05 Feb 2014 15:21:49 +0000
treeherdermozilla-inbound@826d951bd6fa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstbsaunde
bugs961737
milestone30.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 961737 - getTextAtOffset is broken w.r.t. embedded object characters, part3 (list items), r=tbsaunde
accessible/src/generic/HyperTextAccessible.cpp
accessible/src/generic/HyperTextAccessible.h
accessible/src/html/HTMLListAccessible.cpp
accessible/src/html/HTMLListAccessible.h
accessible/tests/mochitest/text/test_lineboundary.html
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "HyperTextAccessible-inl.h"
 
 #include "Accessible-inl.h"
 #include "nsAccessibilityService.h"
 #include "nsIAccessibleTypes.h"
 #include "DocAccessible.h"
+#include "HTMLListAccessible.h"
 #include "Role.h"
 #include "States.h"
 #include "TextAttrs.h"
 #include "TreeWalker.h"
 
 #include "nsCaret.h"
 #include "nsContentUtils.h"
 #include "nsFocusManager.h"
@@ -233,30 +234,30 @@ HyperTextAccessible::TextSubstring(int32
 
 int32_t
 HyperTextAccessible::DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
                                       bool aIsEndOffset) const
 {
   if (!aNode)
     return 0;
 
-  uint32_t addTextOffset = 0;
+  uint32_t offset = 0;
   nsINode* findNode = nullptr;
 
   if (aNodeOffset == -1) {
     findNode = aNode;
 
   } else if (aNode->IsNodeOfType(nsINode::eTEXT)) {
     // For text nodes, aNodeOffset comes in as a character offset
     // Text offset will be added at the end, if we find the offset in this hypertext
     // We want the "skipped" offset into the text (rendered text without the extra whitespace)
     nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
     NS_ENSURE_TRUE(frame, 0);
 
-    nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &addTextOffset);
+    nsresult rv = ContentToRenderedOffset(frame, aNodeOffset, &offset);
     NS_ENSURE_SUCCESS(rv, 0);
     // Get the child node and 
     findNode = aNode;
 
   } else {
     // findNode could be null if aNodeOffset == # of child nodes, which means
     // one of two things:
     // 1) there are no children, and the passed-in node is not mContent -- use
@@ -299,44 +300,49 @@ HyperTextAccessible::DOMPointToOffset(ns
                                  nsGkAtoms::_true,
                                  eIgnoreCase)) {
       // This <br> is the hacky "bogus node" used when there is no text in a control
       return 0;
     }
     descendant = GetFirstAvailableAccessible(findNode);
   }
 
-  // From the descendant, go up and get the immediate child of this hypertext
-  Accessible* childAtOffset = nullptr;
+  return TransformOffset(descendant, offset, aIsEndOffset);
+}
+
+int32_t
+HyperTextAccessible::TransformOffset(Accessible* aDescendant,
+                                     int32_t aOffset, bool aIsEndOffset) const
+{
+  // From the descendant, go up and get the immediate child of this hypertext.
+  int32_t offset = aOffset;
+  Accessible* descendant = aDescendant;
   while (descendant) {
     Accessible* parent = descendant->Parent();
-    if (parent == this) {
-      childAtOffset = descendant;
-      break;
-    }
+    if (parent == this)
+      return GetChildOffset(descendant) + offset;
 
     // This offset no longer applies because the passed-in text object is not
     // a child of the hypertext. This happens when there are nested hypertexts,
     // e.g. <div>abc<h1>def</h1>ghi</div>. Thus we need to adjust the offset
     // to make it relative the hypertext.
     // If the end offset is not supposed to be inclusive and the original point
     // is not at 0 offset then the returned offset should be after an embedded
     // character the original point belongs to.
     if (aIsEndOffset)
-      addTextOffset = (addTextOffset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
+      offset = (offset > 0 || descendant->IndexInParent() > 0) ? 1 : 0;
     else
-      addTextOffset = 0;
+      offset = 0;
 
     descendant = parent;
   }
 
-  // If the given DOM point cannot be mapped into offset relative this hypertext
+  // If the given a11y point cannot be mapped into offset relative this hypertext
   // offset then return length as fallback value.
-  return childAtOffset ?
-    GetChildOffset(childAtOffset) + addTextOffset : CharacterCount();
+  return CharacterCount();
 }
 
 bool
 HyperTextAccessible::OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
                                        nsRange* aRange)
 {
   DOMPoint startPoint = OffsetToDOMPoint(aStartOffset);
   if (!startPoint.node)
@@ -415,16 +421,41 @@ HyperTextAccessible::FindOffset(int32_t 
 
   do {
     int32_t childIdx = text->GetChildIndexAtOffset(innerOffset);
     NS_ASSERTION(childIdx != -1, "Bad in offset!");
     if (childIdx == -1)
       return -1;
 
     child = text->GetChildAt(childIdx);
+
+    // HTML list items may need special processing because PeekOffset doesn't
+    // work with list bullets.
+    if (text->IsHTMLListItem()) {
+      HTMLLIAccessible* li = text->AsHTMLListItem();
+      if (child == li->Bullet()) {
+        // It works only when the bullet is one single char.
+        if (aDirection == eDirPrevious)
+          return text != this ? TransformOffset(text, 0, false) : 0;
+
+        if (aAmount == eSelectEndLine || aAmount == eSelectLine) {
+          if (text != this)
+            return TransformOffset(text, 1, true);
+
+          // Ask a text leaf next (if not empty) to the bullet for an offset
+          // since list item may be multiline.
+          return aOffset + 1 < CharacterCount() ?
+            FindOffset(aOffset + 1, aDirection, aAmount, aWordMovementType) : 1;
+        }
+
+        // Case of word and char boundaries.
+        return text != this ? TransformOffset(text, 1, true) : 1;
+      }
+    }
+
     innerOffset -= text->GetChildOffset(childIdx);
 
     text = child->AsHyperText();
   } while (text);
 
   nsIFrame* childFrame = child->GetFrame();
   NS_ENSURE_TRUE(childFrame, -1);
 
@@ -458,20 +489,26 @@ HyperTextAccessible::FindOffset(int32_t 
   if (!pos.mResultContent)
     return -1;
 
   // Turn the resulting DOM point into an offset.
   int32_t hyperTextOffset = DOMPointToOffset(pos.mResultContent,
                                              pos.mContentOffset,
                                              aDirection == eDirNext);
 
-  // If we reached the end during search, this means we didn't find the DOM point
-  // and we're actually at the start of the paragraph
-  if (hyperTextOffset == CharacterCount() && aDirection == eDirPrevious)
-    return 0;
+  if (aDirection == eDirPrevious) {
+    // If we reached the end during search, this means we didn't find the DOM point
+    // and we're actually at the start of the paragraph
+    if (hyperTextOffset == CharacterCount())
+      return 0;
+
+    // PeekOffset stops right before bullet so return 0 to workaround it.
+    if (IsHTMLListItem() && aAmount == eSelectBeginLine && hyperTextOffset == 1)
+      return 0;
+  }
 
   return hyperTextOffset;
 }
 
 int32_t
 HyperTextAccessible::FindLineBoundary(int32_t aOffset,
                                       EWhichLineBoundary aWhichLineBoundary)
 {
--- a/accessible/src/generic/HyperTextAccessible.h
+++ b/accessible/src/generic/HyperTextAccessible.h
@@ -116,16 +116,22 @@ public:
    *                       If false, then the offset is inclusive. The character indicated
    *                       by the offset returned is at [offset]. If the passed-in offset in inside a
    *                       descendant, then the returned offset will be on the relevant embedded object char.
    */
   int32_t DOMPointToOffset(nsINode* aNode, int32_t aNodeOffset,
                            bool aIsEndOffset = false) const;
 
   /**
+   * Transform the given a11y point into the offset relative this hypertext.
+   */
+  int32_t TransformOffset(Accessible* aDescendant, int32_t aOffset,
+                          bool aIsEndOffset) const;
+
+  /**
    * Convert start and end hypertext offsets into DOM range.
    *
    * @param  aStartOffset  [in] the given start hypertext offset
    * @param  aEndOffset    [in] the given end hypertext offset
    * @param  aRange        [in, out] the range whose bounds to set
    * @return true   if conversion was successful
    */
   bool OffsetsToDOMRange(int32_t aStartOffset, int32_t aEndOffset,
@@ -430,19 +436,19 @@ protected:
    */
   int32_t FindLineBoundary(int32_t aOffset,
                            EWhichLineBoundary aWhichLineBoundary);
 
   /**
    * Return an offset corresponding to the given direction and selection amount
    * relative the given offset. A helper used to find word or line boundaries.
    */
-  virtual int32_t FindOffset(int32_t aOffset, nsDirection aDirection,
-                             nsSelectionAmount aAmount,
-                             EWordMovementType aWordMovementType = eDefaultBehavior);
+  int32_t FindOffset(int32_t aOffset, nsDirection aDirection,
+                     nsSelectionAmount aAmount,
+                     EWordMovementType aWordMovementType = eDefaultBehavior);
 
   /**
    * Return the boundaries of the substring in case of textual frame or
    * frame boundaries in case of non textual frame, offsets are ignored.
    */
   nsIntRect GetBoundsInFrame(nsIFrame* aFrame,
                              uint32_t aStartRenderedOffset,
                              uint32_t aEndRenderedOffset);
--- a/accessible/src/html/HTMLListAccessible.cpp
+++ b/accessible/src/html/HTMLListAccessible.cpp
@@ -93,43 +93,16 @@ HTMLLIAccessible::GetBounds(int32_t* aX,
   rv = mBullet->GetBounds(&bulletX, &bulletY, &bulletWidth, &bulletHeight);
   NS_ENSURE_SUCCESS(rv, rv);
 
   *aWidth += *aX - bulletX;
   *aX = bulletX; // Move x coordinate of list item over to cover bullet as well
   return NS_OK;
 }
 
-int32_t
-HTMLLIAccessible::FindOffset(int32_t aOffset, nsDirection aDirection,
-                             nsSelectionAmount aAmount,
-                             EWordMovementType aWordMovementType)
-{
-  Accessible* child = GetChildAtOffset(aOffset);
-  if (!child)
-    return -1;
-
-  if (child != mBullet) {
-    if (aDirection == eDirPrevious &&
-        (aAmount == eSelectBeginLine || aAmount == eSelectLine))
-      return 0;
-
-    return HyperTextAccessible::FindOffset(aOffset, aDirection,
-                                           aAmount, aWordMovementType);
-  }
-
-  if (aDirection == eDirPrevious)
-    return 0;
-
-  if (aAmount == eSelectEndLine || aAmount == eSelectLine)
-    return CharacterCount();
-
-  return nsAccUtils::TextLength(child);
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // HTMLLIAccessible: public
 
 void
 HTMLLIAccessible::UpdateBullet(bool aHasBullet)
 {
   if (aHasBullet == !!mBullet) {
     NS_NOTREACHED("Bullet and accessible are in sync already!");
--- a/accessible/src/html/HTMLListAccessible.h
+++ b/accessible/src/html/HTMLListAccessible.h
@@ -50,22 +50,18 @@ public:
   NS_IMETHOD GetBounds(int32_t* aX, int32_t* aY,
                        int32_t* aWidth, int32_t* aHeight);
 
   // Accessible
   virtual void Shutdown();
   virtual a11y::role NativeRole();
   virtual uint64_t NativeState();
 
-  // HyperTextAccessible
-  virtual int32_t FindOffset(int32_t aOffset, nsDirection aDirection,
-                             nsSelectionAmount aAmount,
-                             EWordMovementType aWordMovementType) MOZ_OVERRIDE;
-
   // HTMLLIAccessible
+  HTMLListBulletAccessible* Bullet() const { return mBullet; }
   void UpdateBullet(bool aHasBullet);
 
 protected:
   // Accessible
   virtual void CacheChildren();
 
 private:
   nsRefPtr<HTMLListBulletAccessible> mBullet;
--- a/accessible/tests/mochitest/text/test_lineboundary.html
+++ b/accessible/tests/mochitest/text/test_lineboundary.html
@@ -114,16 +114,28 @@
       testTextAtOffset([ "ht_4" ], BOUNDARY_LINE_START,
                        [ [ 0, 12, "Hello world ", 0, 12 ] ]);
 
       //////////////////////////////////////////////////////////////////////////
       // list items
 
       testTextAtOffset([ "li1" ], BOUNDARY_LINE_START,
                        [ [ 0, 5, kDiscBulletChar + "Item", 0, 5 ] ]);
+      testTextAtOffset([ "li2" ], BOUNDARY_LINE_START,
+                       [ [ 0, 1, kDiscBulletChar, 0, 1 ] ]);
+      testTextAtOffset([ "li3" ], BOUNDARY_LINE_START,
+                       [ [ 0, 7, kDiscBulletChar + "a long ", 0, 8 ],
+                         [ 8, 11, "and ", 8, 12 ] ]);
+      testTextAtOffset([ "li4" ], BOUNDARY_LINE_START,
+                       [ [ 0, 6, kDiscBulletChar + "a " + kEmbedChar + " c", 0, 6 ] ]);
+      testTextAtOffset([ "ul1" ], BOUNDARY_LINE_START,
+                       [ [ 0, 0, kEmbedChar, 0, 1 ],
+                         [ 1, 1, kEmbedChar, 1, 2 ],
+                         [ 2, 2, kEmbedChar, 2, 3 ],
+                         [ 3, 4, kEmbedChar, 3, 4 ] ]);
 
       //////////////////////////////////////////////////////////////////////////
       // Nested hypertexts
 
       testTextAtOffset(["ht_5" ], BOUNDARY_LINE_START,
                        [ [ 0, 0, kEmbedChar, 0, 1 ] ]);
 
       SimpleTest.finish();
@@ -188,20 +200,24 @@ two words
   <iframe id="ht_1" src="data:text/html,<html><body>a <a href=''>b</a> c</body></html>"></iframe>
 
   <iframe id="ht_2" src="data:text/html,<div contentEditable='true'>foo<br/></div>"></iframe>
   <iframe id="ht_3" src="data:text/html,<div contentEditable='true'>foo<br/><br/></div>"></iframe>
 
   <p id="ht_4">Hello world
 </p>
 
-  <ul>
+  <ul id="ul1">
     <li id="li1">Item</li>
+    <li id="li2"></li>
+    <li id="li3" style="width:10ex; font-family:monospace; font-size:10pt;">a long and winding road that lead me to your door</li>
+    <li id="li4">a <a href=''>b</a> c</li>
   </ul>
 
   <div id="ht_5">
     <div>
       <p>sectiounus</p>
       <p>seciofarus</p>
     </div>
   </div>
+
 </body>
 </html>