Bug 1011496 - complete the TextRange interface design, r=davidb
☠☠ backed out by 3ade5772804d ☠ ☠
authorAlexander Surkov <surkov.alexander@gmail.com>
Thu, 22 May 2014 15:55:11 -0400
changeset 203784 6c37e11551d8a1c668bbe4309432d4fa81dfc5d9
parent 203783 eb24106356d9348e09f36eacac9bd8b367b4c01f
child 203785 f1068bf18e64b3aa4d16e9f9a006d3882a8b6595
push id3741
push userasasaki@mozilla.com
push dateMon, 21 Jul 2014 20:25:18 +0000
treeherdermozilla-beta@4d6f46f5af68 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb
bugs1011496
milestone32.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 1011496 - complete the TextRange interface design, r=davidb
accessible/public/nsIAccessibleTextRange.idl
accessible/src/base/TextRange.cpp
accessible/src/base/TextRange.h
accessible/src/generic/Accessible.cpp
accessible/src/generic/HyperTextAccessible.cpp
accessible/src/xpcom/xpcAccessibleTextRange.cpp
accessible/src/xpcom/xpcAccessibleTextRange.h
accessible/tests/mochitest/common.js
accessible/tests/mochitest/text.js
accessible/tests/mochitest/textrange/test_general.html
--- a/accessible/public/nsIAccessibleTextRange.idl
+++ b/accessible/public/nsIAccessibleTextRange.idl
@@ -1,25 +1,154 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
 interface nsIAccessible;
+interface nsIAccessibleText;
+interface nsIArray;
+interface nsIVariant;
 
 /**
  * A range representing a piece of text in the document.
  */
-[scriptable, uuid(6fe17c33-6709-4d7a-9ba0-3d448c4b3ef4)]
+[scriptable, uuid(525b3401-8a67-4822-b35d-661065767cd8)]
 interface nsIAccessibleTextRange : nsISupports
 {
-  readonly attribute nsIAccessible startContainer;
+  readonly attribute nsIAccessibleText startContainer;
   readonly attribute long startOffset;
-  readonly attribute nsIAccessible endContainer;
+  readonly attribute nsIAccessibleText endContainer;
   readonly attribute long endOffset;
 
   /**
+   * Return an accessible containing the whole range
+   */
+  readonly attribute nsIAccessible container;
+
+  /**
+   * Return embedded children within the range.
+   */
+  readonly attribute nsIArray embeddedChildren;
+
+  /**
+   * Return true if this range has the same end points of the given range.
+   */
+  boolean compare(in nsIAccessibleTextRange aOtherRange);
+
+  /**
+   * The two endpoints of the range (starting and ending).
+   */
+  const unsigned long EndPoint_Start = 1;
+  const unsigned long EndPoint_End = 2;
+
+  /**
+   * Compare this and given ranges end points.
+   *
+   * @return -1/0/1 if this range end point is before/equal/after the given
+   *          range end point.
+   */
+  long compareEndPoints(in unsigned long aEndPoint,
+                        in nsIAccessibleTextRange aOtherRange,
+                        in unsigned long aOtherRangeEndPoint);
+
+  /**
    * Return text within the range.
    */
   readonly attribute AString text;
+
+  /**
+   * Return list of rects of the range.
+   */
+  readonly attribute nsIArray bounds;
+
+  const unsigned long FormatUnit = 0;
+  const unsigned long WordUnit = 1;
+  const unsigned long LineUnit = 2;
+  const unsigned long ParagraphUnit = 3;
+  const unsigned long PageUnit = 4;
+  const unsigned long DocumentUnit = 5;
+
+  /**
+   *  Move the boundary(ies) by the given number of the unit.
+   */
+  void move(in unsigned long aUnit, in long aCount);
+  void moveStart(in unsigned long aUnit, in long aCount);
+  void moveEnd(in unsigned long aUnit, in long aCount);
+
+  /**
+   * Normalize the range to the closest unit of the given type.
+   */
+  void normalize(in unsigned long aUnit);
+
+  /**
+   * Return range enclosing the found text.
+   */
+  nsIAccessibleTextRange findText(in AString aText, in boolean aIsBackward,
+                                  in boolean aIsIgnoreCase);
+
+  /**
+   * Text attributes. Used in conjunction with findAttrs().
+   */
+  const unsigned long AnimationStyleAttr = 0;
+  const unsigned long AnnotationObjectsAttr = 1;
+  const unsigned long AnnotationTypesAttr = 2;
+  const unsigned long BackgroundColorAttr = 3;
+  const unsigned long BulletStyleAttr = 4;
+  const unsigned long CapStyleAttr = 5;
+  const unsigned long CaretBidiModeAttr = 6;
+  const unsigned long CaretPositionAttr = 7;
+  const unsigned long CultureAttr = 8;
+  const unsigned long FontNameAttr = 9;
+  const unsigned long FontSizeAttr = 10;
+  const unsigned long FontWeightAttr = 11;
+  const unsigned long ForegroundColorAttr = 12;
+  const unsigned long HorizontalTextAlignmentAttr = 13;
+  const unsigned long IndentationFirstLineAttr = 14;
+  const unsigned long IndentationLeadingAttr = 15;
+  const unsigned long IndentationTrailingAttr = 16;
+  const unsigned long IsActiveAttr = 17;
+  const unsigned long IsHiddenAttr = 18;
+  const unsigned long IsItalicAttr = 19;
+  const unsigned long IsReadOnlyAttr = 20;
+  const unsigned long IsSubscriptAttr = 21;
+  const unsigned long IsSuperscriptAttr = 22;
+  const unsigned long LinkAttr = 23;
+  const unsigned long MarginBottomAttr = 24;
+  const unsigned long MarginLeadingAttr = 25;
+  const unsigned long MarginTopAttr = 26;
+  const unsigned long MarginTrailingAttr = 27;
+  const unsigned long OutlineStylesAttr = 28;
+  const unsigned long OverlineColorAttr = 29;
+  const unsigned long OverlineStyleAttr = 30;
+  const unsigned long SelectionActiveEndAttr = 31;
+  const unsigned long StrikethroughColorAttr = 32;
+  const unsigned long StrikethroughStyleAttr = 33;
+  const unsigned long StyleIdAttr = 34;
+  const unsigned long StyleNameAttr = 35;
+  const unsigned long TabsAttr = 36;
+  const unsigned long TextFlowDirectionsAttr = 37;
+  const unsigned long UnderlineColorAttr = 38;
+  const unsigned long UnderlineStyleAttr = 39;
+
+  /**
+   * Return range enslosing the text having requested attribute.
+   */
+  nsIAccessibleTextRange findAttr(in unsigned long aAttr, in nsIVariant aValue,
+                                  in boolean aIsBackward);
+
+  /**
+   * Add/remove the text range from selection.
+   */
+  void addToSelection();
+  void removeFromSelection();
+  void select();
+
+  const unsigned long AlignToTop = 0;
+  const unsigned long AlignToBottom = 1;
+
+  /**
+   * Scroll the range into view.
+   */
+  void scrollIntoView(in unsigned long aHow);
 };
--- a/accessible/src/base/TextRange.cpp
+++ b/accessible/src/base/TextRange.cpp
@@ -2,36 +2,294 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "TextRange.h"
 
 #include "HyperTextAccessible.h"
+#include "nsAccUtils.h"
 
 using namespace mozilla::a11y;
 
+////////////////////////////////////////////////////////////////////////////////
+// TextPoint
+
+bool
+TextPoint::operator <(const TextPoint& aPoint) const
+{
+  if (mContainer == aPoint.mContainer)
+    return mOffset < aPoint.mOffset;
+
+  // Build the chain of parents
+  Accessible* p1 = mContainer;
+  Accessible* p2 = aPoint.mContainer;
+  nsAutoTArray<Accessible*, 30> parents1, parents2;
+  do {
+    parents1.AppendElement(p1);
+    p1 = p1->Parent();
+  } while (p1);
+  do {
+    parents2.AppendElement(p2);
+    p2 = p2->Parent();
+  } while (p2);
+
+  // Find where the parent chain differs
+  uint32_t pos1 = parents1.Length(), pos2 = parents2.Length();
+  for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+    Accessible* child1 = parents1.ElementAt(--pos1);
+    Accessible* child2 = parents2.ElementAt(--pos2);
+    if (child1 != child2)
+      return child1->IndexInParent() < child2->IndexInParent();
+  }
+
+  NS_ERROR("Broken tree?!");
+  return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// TextRange
+
 TextRange::TextRange(HyperTextAccessible* aRoot,
-                     Accessible* aStartContainer, int32_t aStartOffset,
-                     Accessible* aEndContainer, int32_t aEndOffset) :
+                     HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+                     HyperTextAccessible* aEndContainer, int32_t aEndOffset) :
   mRoot(aRoot), mStartContainer(aStartContainer), mEndContainer(aEndContainer),
   mStartOffset(aStartOffset), mEndOffset(aEndOffset)
 {
 }
 
+Accessible*
+TextRange::Container() const
+{
+  if (mStartContainer == mEndContainer)
+    return mStartContainer;
+
+  // Build the chain of parents
+  Accessible* p1 = mStartContainer;
+  Accessible* p2 = mEndContainer;
+  nsAutoTArray<Accessible*, 30> parents1, parents2;
+  do {
+    parents1.AppendElement(p1);
+    p1 = p1->Parent();
+  } while (p1);
+  do {
+    parents2.AppendElement(p2);
+    p2 = p2->Parent();
+  } while (p2);
+
+  // Find where the parent chain differs
+  uint32_t pos1 = parents1.Length();
+  uint32_t pos2 = parents2.Length();
+  Accessible* parent = nullptr;
+  uint32_t len = 0;
+  for (len = std::min(pos1, pos2); len > 0; --len) {
+    Accessible* child1 = parents1.ElementAt(--pos1);
+    Accessible* child2 = parents2.ElementAt(--pos2);
+    if (child1 != child2)
+      break;
+
+    parent = child1;
+  }
+
+  return parent;
+}
+
+void
+TextRange::EmbeddedChildren(nsTArray<Accessible*>* aChildren) const
+{
+  if (mStartContainer == mEndContainer) {
+    int32_t startIdx = mStartContainer->GetChildIndexAtOffset(mStartOffset);
+    int32_t endIdx = mStartContainer->GetChildIndexAtOffset(mEndOffset);
+    for (int32_t idx = startIdx; idx <= endIdx; idx++) {
+      Accessible* child = mStartContainer->GetChildAt(idx);
+      if (nsAccUtils::IsEmbeddedObject(child))
+        aChildren->AppendElement(child);
+    }
+    return;
+  }
+
+  Accessible* p1 = mStartContainer->GetChildAtOffset(mStartOffset);
+  Accessible* p2 = mEndContainer->GetChildAtOffset(mEndOffset);
+  nsAutoTArray<Accessible*, 30> parents1, parents2;
+  do {
+    parents1.AppendElement(p1);
+    p1 = p1->Parent();
+  } while (p1);
+  do {
+    parents2.AppendElement(p2);
+    p2 = p2->Parent();
+  } while (p2);
+
+  // Find deepest common container.
+  uint32_t pos1 = parents1.Length();
+  uint32_t pos2 = parents2.Length();
+  Accessible* container = nullptr;
+  for (uint32_t len = std::min(pos1, pos2); len > 0; --len) {
+    Accessible* child1 = parents1.ElementAt(--pos1);
+    Accessible* child2 = parents2.ElementAt(--pos2);
+    if (child1 != child2)
+      break;
+
+    container = child1;
+  }
+
+  // Traverse the tree up to the container and collect embedded objects.
+  for (uint32_t idx = 0; idx < pos1 - 1; idx++) {
+    Accessible* parent = parents1[idx + 1];
+    Accessible* child = parents1[idx];
+    uint32_t childCount = parent->ChildCount();
+    for (uint32_t childIdx = child->IndexInParent(); childIdx < childCount; childIdx++) {
+      Accessible* next = parent->GetChildAt(childIdx);
+      if (nsAccUtils::IsEmbeddedObject(next))
+        aChildren->AppendElement(next);
+    }
+  }
+
+  // Traverse through direct children in the container.
+  int32_t endIdx = parents2[pos2 - 1]->IndexInParent();
+  int32_t childIdx = parents1[pos1 - 1]->IndexInParent() + 1;
+  for (; childIdx < endIdx; childIdx++) {
+    Accessible* next = container->GetChildAt(childIdx);
+    if (nsAccUtils::IsEmbeddedObject(next))
+      aChildren->AppendElement(next);
+  }
+
+  // Traverse down from the container to end point.
+  for (int32_t idx = pos2 - 2; idx > 0; idx--) {
+    Accessible* parent = parents2[idx];
+    Accessible* child = parents2[idx - 1];
+    int32_t endIdx = child->IndexInParent();
+    for (int32_t childIdx = 0; childIdx < endIdx; childIdx++) {
+      Accessible* next = parent->GetChildAt(childIdx);
+      if (nsAccUtils::IsEmbeddedObject(next))
+        aChildren->AppendElement(next);
+    }
+  }
+}
+
 void
 TextRange::Text(nsAString& aText) const
 {
+  Accessible* current = mStartContainer->GetChildAtOffset(mStartOffset);
+  uint32_t startIntlOffset =
+    mStartOffset - mStartContainer->GetChildOffset(current);
+
+  while (current && TextInternal(aText, current, startIntlOffset)) {
+    current = current->Parent();
+    if (!current)
+      break;
+
+    current = current->NextSibling();
+  }
+}
+
+void
+TextRange::Bounds(nsTArray<nsIntRect> aRects) const
+{
 
 }
 
 void
+TextRange::Normalize(ETextUnit aUnit)
+{
+
+}
+
+void
+TextRange::FindText(const nsAString& aText, EDirection aDirection,
+                    nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const
+{
+
+}
+
+void
+TextRange::FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+                    TextRange* aFoundRange) const
+{
+
+}
+
+void
+TextRange::AddToSelection() const
+{
+
+}
+
+void
+TextRange::RemoveFromSelection() const
+{
+
+}
+
+void
+TextRange::Select() const
+{
+}
+
+void
+TextRange::ScrollIntoView(EHowToAlign aHow) const
+{
+
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// pivate
+
+void
 TextRange::Set(HyperTextAccessible* aRoot,
-               Accessible* aStartContainer, int32_t aStartOffset,
-               Accessible* aEndContainer, int32_t aEndOffset)
+               HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+               HyperTextAccessible* aEndContainer, int32_t aEndOffset)
 {
   mRoot = aRoot;
   mStartContainer = aStartContainer;
   mEndContainer = aEndContainer;
   mStartOffset = aStartOffset;
   mEndOffset = aEndOffset;
 }
+
+bool
+TextRange::TextInternal(nsAString& aText, Accessible* aCurrent,
+                        uint32_t aStartIntlOffset) const
+{
+  bool moveNext = true;
+  int32_t endIntlOffset = -1;
+  if (aCurrent->Parent() == mEndContainer &&
+      mEndContainer->GetChildAtOffset(mEndOffset) == aCurrent) {
+
+    uint32_t currentStartOffset = mEndContainer->GetChildOffset(aCurrent);
+    endIntlOffset = mEndOffset - currentStartOffset;
+    if (endIntlOffset == 0)
+      return false;
+
+    moveNext = false;
+  }
+
+  if (aCurrent->IsTextLeaf()) {
+    aCurrent->AppendTextTo(aText, aStartIntlOffset,
+                           endIntlOffset - aStartIntlOffset);
+    if (!moveNext)
+      return false;
+  }
+
+  Accessible* next = aCurrent->FirstChild();
+  if (next) {
+    if (!TextInternal(aText, next, 0))
+      return false;
+  }
+
+  next = aCurrent->NextSibling();
+  if (next) {
+    if (!TextInternal(aText, next, 0))
+      return false;
+  }
+
+  return moveNext;
+}
+
+
+void
+TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
+                        HyperTextAccessible& aContainer, int32_t aOffset,
+                        HyperTextAccessible* aStopContainer, int32_t aStopOffset)
+{
+
+}
--- a/accessible/src/base/TextRange.h
+++ b/accessible/src/base/TextRange.h
@@ -4,77 +4,253 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_TextRange_h__
 #define mozilla_a11y_TextRange_h__
 
 #include "mozilla/Move.h"
 #include "nsAutoPtr.h"
+#include "nsCaseTreatment.h"
+#include "nsRect.h"
+#include "nsTArray.h"
+
+ class nsIVariant;
 
 namespace mozilla {
 namespace a11y {
 
 class Accessible;
 class HyperTextAccessible;
 
 /**
+ * A text point (hyper text + offset), represents a boundary of text range.
+ */
+struct TextPoint MOZ_FINAL
+{
+  TextPoint(HyperTextAccessible* aContainer, int32_t aOffset) :
+    mContainer(aContainer), mOffset(aOffset) { }
+  TextPoint(const TextPoint& aPoint) :
+    mContainer(aPoint.mContainer), mOffset(aPoint.mOffset) { }
+
+  HyperTextAccessible* mContainer;
+  int32_t mOffset;
+
+  bool operator ==(const TextPoint& aPoint) const
+    { return mContainer == aPoint.mContainer && mOffset == aPoint.mOffset; }
+  bool operator <(const TextPoint& aPoint) const;
+};
+
+/**
  * Represents a text range within the text control or document.
  */
 class TextRange MOZ_FINAL
 {
 public:
   TextRange(HyperTextAccessible* aRoot,
-            Accessible* aStartContainer, int32_t aStartOffset,
-            Accessible* aEndContainer, int32_t aEndOffset);
+            HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+            HyperTextAccessible* aEndContainer, int32_t aEndOffset);
   TextRange() {}
   TextRange(TextRange&& aRange) :
-    mRoot(Move(aRange.mRoot)), mStartContainer(Move(aRange.mStartContainer)),
-    mEndContainer(Move(aRange.mEndContainer)),
+    mRoot(mozilla::Move(aRange.mRoot)),
+    mStartContainer(mozilla::Move(aRange.mStartContainer)),
+    mEndContainer(mozilla::Move(aRange.mEndContainer)),
     mStartOffset(aRange.mStartOffset), mEndOffset(aRange.mEndOffset) {}
 
   TextRange& operator= (TextRange&& aRange)
   {
-    mRoot = Move(aRange.mRoot);
-    mStartContainer = Move(aRange.mStartContainer);
-    mEndContainer = Move(aRange.mEndContainer);
+    mRoot = mozilla::Move(aRange.mRoot);
+    mStartContainer = mozilla::Move(aRange.mStartContainer);
+    mEndContainer = mozilla::Move(aRange.mEndContainer);
     mStartOffset = aRange.mStartOffset;
     mEndOffset = aRange.mEndOffset;
     return *this;
   }
 
-  Accessible* StartContainer() const { return mStartContainer; }
+  HyperTextAccessible* StartContainer() const { return mStartContainer; }
   int32_t StartOffset() const { return mStartOffset; }
-  Accessible* EndContainer() const { return mEndContainer; }
+  HyperTextAccessible* EndContainer() const { return mEndContainer; }
   int32_t EndOffset() const { return mEndOffset; }
 
+  bool operator ==(const TextRange& aRange) const
+  {
+    return mStartContainer == aRange.mStartContainer &&
+      mStartOffset == aRange.mStartOffset &&
+      mEndContainer == aRange.mEndContainer && mEndOffset == aRange.mEndOffset;
+  }
+
+  TextPoint StartPoint() const { return TextPoint(mStartContainer, mStartOffset); }
+  TextPoint EndPoint() const { return TextPoint(mEndContainer, mEndOffset); }
+
+  /**
+   * Return a container containing both start and end points.
+   */
+  Accessible* Container() const;
+
+  /**
+   * Return a list of embedded objects enclosed by the text range (includes
+   * partially overlapped objects).
+   */
+  void EmbeddedChildren(nsTArray<Accessible*>* aChildren) const;
+
   /**
    * Return text enclosed by the range.
    */
   void Text(nsAString& aText) const;
 
   /**
+   * Return list of bounding rects of the text range by lines.
+   */
+  void Bounds(nsTArray<nsIntRect> aRects) const;
+
+  enum ETextUnit {
+    eFormat,
+    eWord,
+    eLine,
+    eParagraph,
+    ePage,
+    eDocument
+  };
+
+  /**
+   * Move the range or its points on specified amount of given units.
+   */
+  void Move(ETextUnit aUnit, int32_t aCount)
+  {
+    MoveEnd(aUnit, aCount);
+    MoveStart(aUnit, aCount);
+  }
+  void MoveStart(ETextUnit aUnit, int32_t aCount)
+  {
+    MoveInternal(aUnit, aCount, *mStartContainer, mStartOffset,
+                 mEndContainer, mEndOffset);
+  }
+  void MoveEnd(ETextUnit aUnit, int32_t aCount)
+    { MoveInternal(aUnit, aCount, *mEndContainer, mEndOffset); }
+
+  /**
+   * Move the range points to the closest unit boundaries.
+   */
+  void Normalize(ETextUnit aUnit);
+
+  enum EDirection {
+    eBackward,
+    eForward
+  };
+
+  /**
+   * Return range enclosing the found text.
+   */
+  void FindText(const nsAString& aText, EDirection aDirection,
+                nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const;
+
+  enum EAttr {
+    eAnimationStyleAttr,
+    eAnnotationObjectsAttr,
+    eAnnotationTypesAttr,
+    eBackgroundColorAttr,
+    eBulletStyleAttr,
+    eCapStyleAttr,
+    eCaretBidiModeAttr,
+    eCaretPositionAttr,
+    eCultureAttr,
+    eFontNameAttr,
+    eFontSizeAttr,
+    eFontWeightAttr,
+    eForegroundColorAttr,
+    eHorizontalTextAlignmentAttr,
+    eIndentationFirstLineAttr,
+    eIndentationLeadingAttr,
+    eIndentationTrailingAttr,
+    eIsActiveAttr,
+    eIsHiddenAttr,
+    eIsItalicAttr,
+    eIsReadOnlyAttr,
+    eIsSubscriptAttr,
+    eIsSuperscriptAttr,
+    eLinkAttr,
+    eMarginBottomAttr,
+    eMarginLeadingAttr,
+    eMarginTopAttr,
+    eMarginTrailingAttr,
+    eOutlineStylesAttr,
+    eOverlineColorAttr,
+    eOverlineStyleAttr,
+    eSelectionActiveEndAttr,
+    eStrikethroughColorAttr,
+    eStrikethroughStyleAttr,
+    eStyleIdAttr,
+    eStyleNameAttr,
+    eTabsAttr,
+    eTextFlowDirectionsAttr,
+    eUnderlineColorAttr,
+    eUnderlineStyleAttr
+  };
+
+  /**
+   * Return range enclosing text having requested attribute.
+   */
+  void FindAttr(EAttr aAttr, nsIVariant* aValue, EDirection aDirection,
+                TextRange* aFoundRange) const;
+
+  /**
+   * Add/remove the text range from selection.
+   */
+  void AddToSelection() const;
+  void RemoveFromSelection() const;
+  void Select() const;
+
+  /**
+   * Scroll the text range into view.
+   */
+  enum EHowToAlign {
+    eAlignToTop,
+    eAlignToBottom
+  };
+  void ScrollIntoView(EHowToAlign aHow) const;
+
+  /**
    * Return true if this TextRange object represents an actual range of text.
    */
   bool IsValid() const { return mRoot; }
 
+  void SetStartPoint(HyperTextAccessible* aContainer, int32_t aOffset)
+    { mStartContainer = aContainer; mStartOffset = aOffset; }
+  void SetEndPoint(HyperTextAccessible* aContainer, int32_t aOffset)
+    { mStartContainer = aContainer; mStartOffset = aOffset; }
+
 private:
   TextRange(const TextRange& aRange) MOZ_DELETE;
   TextRange& operator=(const TextRange& aRange) MOZ_DELETE;
 
   friend class HyperTextAccessible;
   friend class xpcAccessibleTextRange;
 
   void Set(HyperTextAccessible* aRoot,
-           Accessible* aStartContainer, int32_t aStartOffset,
-           Accessible* aEndContainer, int32_t aEndOffset);
+           HyperTextAccessible* aStartContainer, int32_t aStartOffset,
+           HyperTextAccessible* aEndContainer, int32_t aEndOffset);
+
+  /**
+   * Text() method helper.
+   * @param  aText            [in,out] calculated text
+   * @param  aCurrent         [in] currently traversed node
+   * @param  aStartIntlOffset [in] start offset if current node is a text node
+   * @return                   true if calculation is not finished yet
+   */
+  bool TextInternal(nsAString& aText, Accessible* aCurrent,
+                    uint32_t aStartIntlOffset) const;
+
+  void MoveInternal(ETextUnit aUnit, int32_t aCount,
+                    HyperTextAccessible& aContainer, int32_t aOffset,
+                    HyperTextAccessible* aStopContainer = nullptr,
+                    int32_t aStopOffset = 0);
 
   nsRefPtr<HyperTextAccessible> mRoot;
-  nsRefPtr<Accessible> mStartContainer;
-  nsRefPtr<Accessible> mEndContainer;
+  nsRefPtr<HyperTextAccessible> mStartContainer;
+  nsRefPtr<HyperTextAccessible> mEndContainer;
   int32_t mStartOffset;
   int32_t mEndOffset;
 };
 
 
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -115,17 +115,17 @@ Accessible::Accessible(nsIContent* aCont
   mContent(aContent), mDoc(aDoc),
   mParent(nullptr), mIndexInParent(-1), mChildrenFlags(eChildrenUninitialized),
   mStateFlags(0), mContextFlags(0), mType(0), mGenericTypes(0),
   mIndexOfEmbeddedChild(-1), mRoleMapEntry(nullptr)
 {
 #ifdef NS_DEBUG_X
    {
      nsCOMPtr<nsIPresShell> shell(do_QueryReferent(aShell));
-     printf(">>> %p Created Acc - DOM: %p  PS: %p", 
+     printf(">>> %p Created Acc - DOM: %p  PS: %p",
             (void*)static_cast<nsIAccessible*>(this), (void*)aNode,
             (void*)shell.get());
     nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
     if (content) {
       printf(" Con: %s@%p",
              NS_ConvertUTF16toUTF8(content->NodeInfo()->QualifiedName()).get(),
              (void *)content.get());
       nsAutoString buf;
@@ -262,17 +262,17 @@ Accessible::GetDescription(nsAString& aD
 
 void
 Accessible::Description(nsString& aDescription)
 {
   // There are 4 conditions that make an accessible have no accDescription:
   // 1. it's a text node; or
   // 2. It has no DHTML describedby property
   // 3. it doesn't have an accName; or
-  // 4. its title attribute already equals to its accName nsAutoString name; 
+  // 4. its title attribute already equals to its accName nsAutoString name;
 
   if (!HasOwnContent() || mContent->IsNodeOfType(nsINode::eTEXT))
     return;
 
   nsTextEquivUtils::
     GetTextEquivFromIDRefs(this, nsGkAtoms::aria_describedby,
                            aDescription);
 
@@ -545,27 +545,27 @@ NS_IMETHODIMP
 Accessible::GetIndexInParent(int32_t* aIndexInParent)
 {
   NS_ENSURE_ARG_POINTER(aIndexInParent);
 
   *aIndexInParent = IndexInParent();
   return *aIndexInParent != -1 ? NS_OK : NS_ERROR_FAILURE;
 }
 
-void 
+void
 Accessible::TranslateString(const nsString& aKey, nsAString& aStringOut)
 {
   nsCOMPtr<nsIStringBundleService> stringBundleService =
     services::GetStringBundleService();
   if (!stringBundleService)
     return;
 
   nsCOMPtr<nsIStringBundle> stringBundle;
   stringBundleService->CreateBundle(
-    "chrome://global-platform/locale/accessible.properties", 
+    "chrome://global-platform/locale/accessible.properties",
     getter_AddRefs(stringBundle));
   if (!stringBundle)
     return;
 
   nsXPIDLString xsValue;
   nsresult rv = stringBundle->GetStringFromName(aKey.get(), getter_Copies(xsValue));
   if (NS_SUCCEEDED(rv))
     aStringOut.Assign(xsValue);
@@ -1099,17 +1099,17 @@ Accessible::XULElmName(DocAccessible* aD
   if (labeledEl) {
     labeledEl->GetLabel(aName);
   } else {
     nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm);
     if (itemEl) {
       itemEl->GetLabel(aName);
     } else {
       nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm);
-      // Use label if this is not a select control element which 
+      // Use label if this is not a select control element which
       // uses label attribute to indicate which option is selected
       if (!select) {
         nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aElm));
         if (xulEl)
           xulEl->GetAttribute(NS_LITERAL_STRING("label"), aName);
       }
     }
   }
@@ -1573,17 +1573,17 @@ Accessible::ApplyARIAState(uint64_t* aSt
     nsIContent *ancestorContent = mContent;
     while ((ancestorContent = ancestorContent->GetParent()) != nullptr) {
       if (ancestorContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::aria_disabled,
                                        nsGkAtoms::_true, eCaseMatters)) {
           // ancestor has aria-disabled property, this is disabled
         *aState |= states::UNAVAILABLE;
         break;
       }
-    }    
+    }
   }
 
   // special case: A native button element whose role got transformed by ARIA to a toggle button
   if (IsButton())
     aria::MapToState(aria::eARIAPressed, element, aState);
 
   if (!mRoleMapEntry)
     return;
@@ -2446,17 +2446,17 @@ Accessible::AppendTextTo(nsAString& aTex
   } else {
     aText += kEmbeddedObjectChar;
   }
 }
 
 void
 Accessible::Shutdown()
 {
-  // Mark the accessible as defunct, invalidate the child count and pointers to 
+  // Mark the accessible as defunct, invalidate the child count and pointers to
   // other accessibles, also make sure none of its children point to this parent
   mStateFlags |= eIsDefunct;
 
   InvalidateChildren();
   if (mParent)
     mParent->RemoveChild(this);
 
   mContent = nullptr;
@@ -3053,17 +3053,17 @@ Accessible::TestChildCache(Accessible* a
   Accessible* child = nullptr;
   for (int32_t childIdx = 0; childIdx < childCount; childIdx++) {
     child = mChildren[childIdx];
     if (child == aCachedChild)
       break;
   }
 
   NS_ASSERTION(child == aCachedChild,
-               "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!");  
+               "[TestChildCache] cached accessible wasn't found. Wrong accessible tree!");
 #endif
 }
 
 // Accessible public
 bool
 Accessible::EnsureChildren()
 {
   if (IsDefunct()) {
@@ -3130,17 +3130,17 @@ Accessible::GetActionRule()
     if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
       return eClickAction;
 
   // Has registered 'click' event handler.
   bool isOnclick = nsCoreUtils::HasClickListener(mContent);
 
   if (isOnclick)
     return eClickAction;
-  
+
   // Get an action based on ARIA role.
   if (mRoleMapEntry &&
       mRoleMapEntry->actionRule != eNoAction)
     return mRoleMapEntry->actionRule;
 
   // Get an action based on ARIA attribute.
   if (nsAccUtils::HasDefinedARIAToken(mContent,
                                       nsGkAtoms::aria_expanded))
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -1546,19 +1546,19 @@ HyperTextAccessible::ScrollSubstringToPo
   }
 }
 
 void
 HyperTextAccessible::EnclosingRange(a11y::TextRange& aRange) const
 {
   if (IsTextField()) {
     aRange.Set(mDoc, const_cast<HyperTextAccessible*>(this), 0,
-               const_cast<HyperTextAccessible*>(this), ChildCount());
+               const_cast<HyperTextAccessible*>(this), CharacterCount());
   } else {
-    aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->ChildCount());
+    aRange.Set(mDoc, mDoc, 0, mDoc, mDoc->CharacterCount());
   }
 }
 
 void
 HyperTextAccessible::SelectionRanges(nsTArray<a11y::TextRange>* aRanges) const
 {
   NS_ASSERTION(aRanges->Length() != 0, "TextRange array supposed to be empty");
 
@@ -1594,26 +1594,56 @@ void
 HyperTextAccessible::VisibleRanges(nsTArray<a11y::TextRange>* aRanges) const
 {
 }
 
 void
 HyperTextAccessible::RangeByChild(Accessible* aChild,
                                   a11y::TextRange& aRange) const
 {
-  aRange.Set(mDoc, aChild, 0, aChild, aChild->ChildCount());
+  HyperTextAccessible* ht = aChild->AsHyperText();
+  if (ht) {
+    aRange.Set(mDoc, ht, 0, ht, ht->CharacterCount());
+    return;
+  }
+
+  Accessible* child = aChild;
+  Accessible* parent = nullptr;
+  while ((parent = child->Parent()) && !(ht = parent->AsHyperText()))
+    child = parent;
+
+  // If no text then return collapsed text range, otherwise return a range
+  // containing the text enclosed by the given child.
+  if (ht) {
+    int32_t childIdx = child->IndexInParent();
+    int32_t startOffset = ht->GetChildOffset(childIdx);
+    int32_t endOffset = child->IsTextLeaf() ?
+      ht->GetChildOffset(childIdx + 1) : startOffset;
+    aRange.Set(mDoc, ht, startOffset, ht, endOffset);
+  }
 }
 
 void
 HyperTextAccessible::RangeAtPoint(int32_t aX, int32_t aY,
                                   a11y::TextRange& aRange) const
 {
   Accessible* child = mDoc->ChildAtPoint(aX, aY, eDeepestChild);
-  if (child)
-    aRange.Set(mDoc, child, 0, child, child->ChildCount());
+  if (!child)
+    return;
+
+  Accessible* parent = nullptr;
+  while ((parent = child->Parent()) && !parent->IsHyperText())
+    child = parent;
+
+  // Return collapsed text range for the point.
+  if (parent) {
+    HyperTextAccessible* ht = parent->AsHyperText();
+    int32_t offset = ht->GetChildOffset(child);
+    aRange.Set(mDoc, ht, offset, ht, offset);
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Accessible public
 
 // Accessible protected
 ENameValueFlag
 HyperTextAccessible::NativeName(nsString& aName)
--- a/accessible/src/xpcom/xpcAccessibleTextRange.cpp
+++ b/accessible/src/xpcom/xpcAccessibleTextRange.cpp
@@ -4,69 +4,206 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "xpcAccessibleTextRange.h"
 
 #include "HyperTextAccessible.h"
 #include "TextRange.h"
 
+ #include "nsIMutableArray.h"
+
 using namespace mozilla;
 using namespace mozilla::a11y;
 
 // nsISupports and cycle collection
 
 NS_IMPL_CYCLE_COLLECTION(xpcAccessibleTextRange,
                          mRange.mRoot,
                          mRange.mStartContainer,
                          mRange.mEndContainer)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(xpcAccessibleTextRange)
   NS_INTERFACE_MAP_ENTRY(nsIAccessibleTextRange)
+  NS_INTERFACE_MAP_ENTRY(xpcAccessibleTextRange)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAccessibleTextRange)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(xpcAccessibleTextRange)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(xpcAccessibleTextRange)
 
 // nsIAccessibleTextRange
 
 NS_IMETHODIMP
-xpcAccessibleTextRange::GetStartContainer(nsIAccessible** aAnchor)
+xpcAccessibleTextRange::GetStartContainer(nsIAccessibleText** aAnchor)
 {
   NS_ENSURE_ARG_POINTER(aAnchor);
-  NS_IF_ADDREF(*aAnchor = static_cast<nsIAccessible*>(mRange.StartContainer()));
+  NS_IF_ADDREF(*aAnchor = static_cast<nsIAccessibleText*>(mRange.StartContainer()));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibleTextRange::GetStartOffset(int32_t* aOffset)
 {
   NS_ENSURE_ARG_POINTER(aOffset);
   *aOffset = mRange.StartOffset();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-xpcAccessibleTextRange::GetEndContainer(nsIAccessible** aAnchor)
+xpcAccessibleTextRange::GetEndContainer(nsIAccessibleText** aAnchor)
 {
   NS_ENSURE_ARG_POINTER(aAnchor);
-  NS_IF_ADDREF(*aAnchor = static_cast<nsIAccessible*>(mRange.EndContainer()));
+  NS_IF_ADDREF(*aAnchor = static_cast<nsIAccessibleText*>(mRange.EndContainer()));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 xpcAccessibleTextRange::GetEndOffset(int32_t* aOffset)
 {
   NS_ENSURE_ARG_POINTER(aOffset);
   *aOffset = mRange.EndOffset();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+xpcAccessibleTextRange::GetContainer(nsIAccessible** aContainer)
+{
+  NS_ENSURE_ARG_POINTER(aContainer);
+  NS_IF_ADDREF(*aContainer = static_cast<nsIAccessible*>(mRange.Container()));
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetEmbeddedChildren(nsIArray** aList)
+{
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIMutableArray> xpcList =
+    do_CreateInstance(NS_ARRAY_CONTRACTID, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsTArray<Accessible*> objects;
+  mRange.EmbeddedChildren(&objects);
+
+  uint32_t len = objects.Length();
+  for (uint32_t idx = 0; idx < len; idx++)
+    xpcList->AppendElement(static_cast<nsIAccessible*>(objects[idx]), false);
+
+  xpcList.forget(aList);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Compare(nsIAccessibleTextRange* aOtherRange,
+                                bool* aResult)
+{
+
+  nsRefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
+  if (!xpcRange || !aResult)
+    return NS_ERROR_INVALID_ARG;
+
+  *aResult = (mRange == xpcRange->mRange);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::CompareEndPoints(uint32_t aEndPoint,
+                                         nsIAccessibleTextRange* aOtherRange,
+                                         uint32_t aOtherRangeEndPoint,
+                                         int32_t* aResult)
+{
+  nsRefPtr<xpcAccessibleTextRange> xpcRange(do_QueryObject(aOtherRange));
+  if (!xpcRange || !aResult)
+    return NS_ERROR_INVALID_ARG;
+
+  TextPoint p = (aEndPoint == EndPoint_Start) ?
+    mRange.StartPoint() : mRange.EndPoint();
+  TextPoint otherPoint = (aOtherRangeEndPoint == EndPoint_Start) ?
+    xpcRange->mRange.StartPoint() : xpcRange->mRange.EndPoint();
+
+  if (p == otherPoint)
+    *aResult = 0;
+  else
+    *aResult = p < otherPoint ? -1 : 1;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 xpcAccessibleTextRange::GetText(nsAString& aText)
 {
   nsAutoString text;
   mRange.Text(text);
   aText.Assign(text);
 
   return NS_OK;
 }
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::GetBounds(nsIArray** aRectList)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Move(uint32_t aUnit, int32_t aCount)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::MoveStart(uint32_t aUnit, int32_t aCount)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::MoveEnd(uint32_t aUnit, int32_t aCount)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Normalize(uint32_t aUnit)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::FindText(const nsAString& aText, bool aIsBackward,
+                                 bool aIsIgnoreCase,
+                                 nsIAccessibleTextRange** aRange)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::FindAttr(uint32_t aAttr, nsIVariant* aVal,
+                                 bool aIsBackward,
+                                 nsIAccessibleTextRange** aRange)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::AddToSelection()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::RemoveFromSelection()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::Select()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+xpcAccessibleTextRange::ScrollIntoView(uint32_t aHow)
+{
+  return NS_OK;
+}
--- a/accessible/src/xpcom/xpcAccessibleTextRange.h
+++ b/accessible/src/xpcom/xpcAccessibleTextRange.h
@@ -13,36 +13,69 @@
 #include "mozilla/Move.h"
 #include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace a11y {
 
 class TextRange;
 
+#define NS_ACCESSIBLETEXTRANGE_IMPL_IID                 \
+{  /* 133c8bf4-4913-4355-bd50-426bd1d6e1ad */           \
+  0xb17652d9,                                           \
+  0x4f54,                                               \
+  0x4c56,                                               \
+  { 0xbb, 0x62, 0x6d, 0x5b, 0xf1, 0xef, 0x91, 0x0c }    \
+}
+
 class xpcAccessibleTextRange MOZ_FINAL : public nsIAccessibleTextRange
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(xpcAccessibleTextRange)
 
-  NS_IMETHOD GetStartContainer(nsIAccessible** aAnchor) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD GetStartContainer(nsIAccessibleText** aAnchor) MOZ_FINAL MOZ_OVERRIDE;
   NS_IMETHOD GetStartOffset(int32_t* aOffset) MOZ_FINAL MOZ_OVERRIDE;
-  NS_IMETHOD GetEndContainer(nsIAccessible** aAnchor) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD GetEndContainer(nsIAccessibleText** aAnchor) MOZ_FINAL MOZ_OVERRIDE;
   NS_IMETHOD GetEndOffset(int32_t* aOffset) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD GetContainer(nsIAccessible** aContainer) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD GetEmbeddedChildren(nsIArray** aList) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD Compare(nsIAccessibleTextRange* aOtherRange, bool* aResult) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD CompareEndPoints(uint32_t aEndPoint,
+                              nsIAccessibleTextRange* aOtherRange,
+                              uint32_t aOtherRangeEndPoint,
+                              int32_t* aResult) MOZ_FINAL MOZ_OVERRIDE;
   NS_IMETHOD GetText(nsAString& aText) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD GetBounds(nsIArray** aRectList) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD Move(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD MoveStart(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD MoveEnd(uint32_t aUnit, int32_t aCount) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD Normalize(uint32_t aUnit) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD FindText(const nsAString& aText, bool aIsBackward, bool aIsIgnoreCase,
+                      nsIAccessibleTextRange** aRange) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD FindAttr(uint32_t aAttr, nsIVariant* aVal, bool aIsBackward,
+                      nsIAccessibleTextRange** aRange) MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD AddToSelection() MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD RemoveFromSelection() MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD Select() MOZ_FINAL MOZ_OVERRIDE;
+  NS_IMETHOD ScrollIntoView(uint32_t aHow) MOZ_FINAL MOZ_OVERRIDE;
+
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_ACCESSIBLETEXTRANGE_IMPL_IID)
 
 private:
   xpcAccessibleTextRange(TextRange&& aRange) :
     mRange(Forward<TextRange>(aRange)) {}
   xpcAccessibleTextRange() {}
   friend class xpcAccessibleHyperText;
 
   xpcAccessibleTextRange(const xpcAccessibleTextRange&) MOZ_DELETE;
   xpcAccessibleTextRange& operator =(const xpcAccessibleTextRange&) MOZ_DELETE;
 
   TextRange mRange;
 };
 
+NS_DEFINE_STATIC_IID_ACCESSOR(xpcAccessibleTextRange,
+                              NS_ACCESSIBLETEXTRANGE_IMPL_IID)
+
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -14,16 +14,17 @@ const nsIAccessibleVirtualCursorChangeEv
   Components.interfaces.nsIAccessibleVirtualCursorChangeEvent;
 
 const nsIAccessibleStates = Components.interfaces.nsIAccessibleStates;
 const nsIAccessibleRole = Components.interfaces.nsIAccessibleRole;
 const nsIAccessibleScrollType = Components.interfaces.nsIAccessibleScrollType;
 const nsIAccessibleCoordinateType = Components.interfaces.nsIAccessibleCoordinateType;
 
 const nsIAccessibleRelation = Components.interfaces.nsIAccessibleRelation;
+const nsIAccessibleTextRange = Components.interfaces.nsIAccessibleTextRange;
 
 const nsIAccessible = Components.interfaces.nsIAccessible;
 
 const nsIAccessibleDocument = Components.interfaces.nsIAccessibleDocument;
 const nsIAccessibleApplication = Components.interfaces.nsIAccessibleApplication;
 
 const nsIAccessibleText = Components.interfaces.nsIAccessibleText;
 const nsIAccessibleEditableText = Components.interfaces.nsIAccessibleEditableText;
@@ -429,17 +430,17 @@ function testAccessibleTree(aAccOrElmOrI
 
       if (prevOffset != -1) {
         var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount;
         var attrs = accTree[prop][prevOffset];
         testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, charCount, true);
       }
 
       break;
-    } 
+    }
 
     default:
       if (prop.indexOf("todo_") == 0)
         todo(false, msg);
       else if (prop != "children")
         is(acc[prop], accTree[prop], msg);
     }
   }
--- a/accessible/tests/mochitest/text.js
+++ b/accessible/tests/mochitest/text.js
@@ -5,16 +5,19 @@ const BOUNDARY_CHAR = nsIAccessibleText.
 const BOUNDARY_WORD_START = nsIAccessibleText.BOUNDARY_WORD_START;
 const BOUNDARY_WORD_END = nsIAccessibleText.BOUNDARY_WORD_END;
 const BOUNDARY_LINE_START = nsIAccessibleText.BOUNDARY_LINE_START;
 const BOUNDARY_LINE_END = nsIAccessibleText.BOUNDARY_LINE_END;
 
 const kTextEndOffset = nsIAccessibleText.TEXT_OFFSET_END_OF_TEXT;
 const kCaretOffset = nsIAccessibleText.TEXT_OFFSET_CARET;
 
+const EndPoint_Start = nsIAccessibleTextRange.EndPoint_Start;
+const EndPoint_End = nsIAccessibleTextRange.EndPoint_End;
+
 const kTodo = 1; // a test is expected to fail
 const kOk = 2; // a test doesn't fail
 
 /**
  * Test characterCount for the given array of accessibles.
  *
  * @param aCount    [in] the expected character count
  * @param aIDs      [in] array of accessible identifiers to test
@@ -388,18 +391,18 @@ function testTextAddSelection(aID, aStar
  */
 function testTextRemoveSelection(aID, aSelectionIndex, aSelectionsCount)
 {
   var acc = getAccessible(aID, [nsIAccessibleText]);
   var text = acc.getText(0, -1);
 
   acc.removeSelection(aSelectionIndex);
 
-  ok(acc.selectionCount, aSelectionsCount, 
-     text + ": failed to remove selection at index '" + 
+  ok(acc.selectionCount, aSelectionsCount,
+     text + ": failed to remove selection at index '" +
      aSelectionIndex + "': selectionCount after");
 }
 
 /**
  * Test setSelectionBounds method.
  *
  * @param aID               [in] Id, DOM node, or acc obj
  * @param aStartOffset      [in] new start offset for the selection
@@ -411,18 +414,18 @@ function testTextRemoveSelection(aID, aS
 function testTextSetSelection(aID, aStartOffset, aEndOffset,
                               aSelectionIndex, aSelectionsCount)
 {
   var acc = getAccessible(aID, [nsIAccessibleText]);
   var text = acc.getText(0, -1);
 
   acc.setSelectionBounds(aSelectionIndex, aStartOffset, aEndOffset);
 
-  is(acc.selectionCount, aSelectionsCount, 
-     text + ": failed to set selection at index '" + 
+  is(acc.selectionCount, aSelectionsCount,
+     text + ": failed to set selection at index '" +
      aSelectionIndex + "': selectionCount after");
 }
 
 /**
  * Test selectionCount method.
  *
  * @param aID        [in] Id, DOM node, or acc obj
  * @param aCount     [in] expected selection count
@@ -452,27 +455,46 @@ function testTextGetSelection(aID, aStar
   acc.getSelectionBounds(aSelectionIndex, startObj, endObj);
 
   is(startObj.value, aStartOffset, text + ": wrong start offset for index '" +
      aSelectionIndex + "'");
   is(endObj.value, aEndOffset, text + ": wrong end offset for index '" +
      aSelectionIndex + "'");
 }
 
-function testTextRange(aRange, aStartContainer, aStartOffset,
-                       aEndContainer, aEndOffset)
+function testTextRange(aRange, aRangeDescr, aStartContainer, aStartOffset,
+                       aEndContainer, aEndOffset, aText,
+                       aCommonContainer, aChildren)
 {
-  is(aRange.startContainer, getAccessible(aStartContainer),
-     "Wrong start container");
+  isObject(aRange.startContainer, getAccessible(aStartContainer),
+           "Wrong start container of " + aRangeDescr);
   is(aRange.startOffset, aStartOffset,
-     "Wrong start offset");
-  is(aRange.endContainer, getAccessible(aEndContainer),
-     "Wrong end container");
+     "Wrong start offset of " + aRangeDescr);
+  isObject(aRange.endContainer, getAccessible(aEndContainer),
+           "Wrong end container of " + aRangeDescr);
   is(aRange.endOffset, aEndOffset,
-     "Wrong end offset");
+     "Wrong end offset of " + aRangeDescr);
+
+  is(aRange.text, aText, "Wrong text of " + aRangeDescr);
+
+  var children = aRange.embeddedChildren;
+  is(children ? children.length : 0, aChildren ? aChildren.length : 0,
+     "Wrong embedded children count of " + aRangeDescr);
+
+  isObject(aRange.container, getAccessible(aCommonContainer),
+           "Wrong container of " + aRangeDescr);
+
+  if (aChildren) {
+    for (var i = 0; i < aChildren.length; i++) {
+      var expectedChild = getAccessible(aChildren[i]);
+      var actualChild = children.queryElementAt(i, nsIAccessible);
+      isObject(actualChild, expectedChild,
+               "Wrong child at index '" + i + "' of " + aRangeDescr);
+    }
+  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // Private
 
 function testTextSuperHelper(aFuncName, aArgs)
 {
   // List of tests.
--- a/accessible/tests/mochitest/textrange/test_general.html
+++ b/accessible/tests/mochitest/textrange/test_general.html
@@ -6,29 +6,85 @@
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
           src="../text.js"></script>
+  <script type="application/javascript"
+          src="../layout.js"></script>
   <script type="application/javascript">
 
     function doTest()
     {
+      // enclosingRange
       var input = getAccessible("input", [ nsIAccessibleText ]);
-      testTextRange(input.enclosingRange, input, 0, input, 1);
+      testTextRange(input.enclosingRange, "enclosing range for 'input'",
+                    input, 0, input, 5, "hello", input);
 
       var ta = getAccessible("textarea", [ nsIAccessibleText ]);
-      testTextRange(ta.enclosingRange, ta, 0, ta, 1);
+      testTextRange(ta.enclosingRange, "enclosing range for 'textarea'",
+                    ta, 0, ta, 5, "hello", textarea);
+
+      var iframeDocNode = getNode("iframe").contentDocument;
+      var iframeDoc = getAccessible(iframeDocNode, [ nsIAccessibleText ]);
+      testTextRange(iframeDoc.enclosingRange, "enclosing range for iframe doc",
+                    iframeDoc, 0, iframeDoc, 1, "hello",
+                    iframeDoc, [ getNode("p", iframeDocNode) ]);
+
+      // getRangeByChild
+      var docacc = getAccessible(document, [ nsIAccessibleText ]);
+      var p1 = getAccessible("p1");
+      var p1Range = docacc.getRangeByChild(p1);
+      testTextRange(p1Range, "range by 'p1' child",
+                    p1, 0, "p1", 11, "text  text",
+                    p1, ["p1_img"]);
+
+      testTextRange(docacc.getRangeByChild(getAccessible("p1_img")),
+                    "range by 'p1_img' child",
+                    "p1", 5, "p1", 5, "",
+                    "p1", ["p1_img"]);
+
+      var p2 = getAccessible("p2");
+      var p2Range = docacc.getRangeByChild(p2);
+      testTextRange(p2Range, "range by 'p2' child",
+                    p2, 0, "p2", 11, "text link text",
+                    p2, ["p2_a"]);
 
-      var iframeDoc = getAccessible(getNode("iframe").contentDocument,
-                                    [ nsIAccessibleText ]);
-      testTextRange(iframeDoc.enclosingRange, iframeDoc, 0, iframeDoc, 1);
+      testTextRange(docacc.getRangeByChild(getAccessible("p2_a")),
+                    "range by 'p2_a' child",
+                    "p2_a", 0, "p2_a", 5, "link",
+                    "p2_a", ["p2_img"]);
+
+      // getRangeAtPoint
+      getNode("p2_a").scrollIntoView(true);
+      var [x, y] = getPos("p2_a");
+      testTextRange(docacc.getRangeAtPoint(x + 1, y + 1),
+                    "range at 'p2_a' top-left edge",
+                    "p2_a", 0, "p2_a", 0, "",
+                    "p2_a");
+
+      // TextRange::compare
+      ok(input.enclosingRange.compare(input.enclosingRange),
+         "input enclosing ranges should be equal");
+
+      ok(!input.enclosingRange.compare(ta.enclosingRange),
+         "input and textarea enclosing ranges can't be equal");
+
+      // TextRange::compareEndPoints
+      var res = p1Range.compareEndPoints(EndPoint_End, p2Range, EndPoint_Start);
+      is(res, -1, "p1 range must be lesser with p2 range");
+
+      res = p2Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_End);
+      is(res, 1, "p2 range must be greater with p1 range");
+
+      res = p1Range.compareEndPoints(EndPoint_Start, p1Range, EndPoint_Start);
+      is(res, 0, "p1 range must be equal with p1 range");
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 </head>
@@ -39,12 +95,14 @@
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=975065">Bug 975065</a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <input id="input" value="hello">
   <textarea id="textarea">hello</textarea>
-  <iframe id="iframe" src="data:text/html,<p>hello</p>"></iframe>
+  <iframe id="iframe" src="data:text/html,<html><body><p id='p'>hello</p></body></html>"></iframe>
+  <p id="p1">text <img id="p1_img", src="../moz.png"> text</p>
+  <p id="p2">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
 
 </body>
 </html>