Bug 1233118 - implement IAccessible2_3::selectionRanges, r=yzen
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 20 Jan 2016 12:53:03 -0500
changeset 303030 c34410da6b0130bc5430d18a20ad0357114ab0ed
parent 303029 3842b1992f25049b7230930c30d8e646ceb778ae
child 303031 a4d3504abe124b5c42daeb1b5d268e2a6a5cc4c1
push id8978
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 14:05:32 +0000
treeherdermozilla-aurora@b9a803752a2c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1233118
milestone46.0a1
Bug 1233118 - implement IAccessible2_3::selectionRanges, r=yzen
accessible/base/TextRange-inl.h
accessible/base/TextRange.cpp
accessible/base/TextRange.h
accessible/generic/Accessible.cpp
accessible/generic/Accessible.h
accessible/generic/HyperTextAccessible.cpp
accessible/generic/HyperTextAccessible.h
accessible/interfaces/ia2/Makefile.in
accessible/interfaces/nsIAccessibleTextRange.idl
accessible/tests/mochitest/text.js
accessible/tests/mochitest/textrange/a11y.ini
accessible/tests/mochitest/textrange/test_selection.html
accessible/windows/ia2/ia2Accessible.cpp
accessible/windows/ia2/ia2Accessible.h
accessible/xpcom/xpcAccessibleTextRange.cpp
accessible/xpcom/xpcAccessibleTextRange.h
other-licenses/ia2/Accessible2_3.idl
other-licenses/ia2/IA2TypeLibrary.idl
new file mode 100644
--- /dev/null
+++ b/accessible/base/TextRange-inl.h
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mozilla_a11y_TextRange_inl_h__
+#define mozilla_a11y_TextRange_inl_h__
+
+#include "TextRange.h"
+#include "HyperTextAccessible.h"
+
+namespace mozilla {
+namespace a11y {
+
+inline Accessible*
+TextRange::Container() const
+{
+  uint32_t pos1 = 0, pos2 = 0;
+  nsAutoTArray<Accessible*, 30> parents1, parents2;
+  return CommonParent(mStartContainer, mEndContainer,
+                      &parents1, &pos1, &parents2, &pos2);
+}
+
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif
--- a/accessible/base/TextRange.cpp
+++ b/accessible/base/TextRange.cpp
@@ -1,18 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "TextRange-inl.h"
 
 #include "Accessible-inl.h"
-#include "HyperTextAccessible.h"
 #include "nsAccUtils.h"
 
 namespace mozilla {
 namespace a11y {
 
 ////////////////////////////////////////////////////////////////////////////////
 // TextPoint
 
@@ -54,90 +53,37 @@ TextPoint::operator <(const TextPoint& a
 TextRange::TextRange(HyperTextAccessible* aRoot,
                      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);
+
+  uint32_t pos1 = 0, pos2 = 0;
   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;
-  }
+  Accessible* container =
+    CommonParent(p1, p2, &parents1, &pos1, &parents2, &pos2);
 
   // 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);
@@ -191,16 +137,105 @@ TextRange::Bounds(nsTArray<nsIntRect> aR
 }
 
 void
 TextRange::Normalize(ETextUnit aUnit)
 {
 
 }
 
+bool
+TextRange::Crop(Accessible* aContainer)
+{
+  uint32_t boundaryPos = 0, containerPos = 0;
+  nsAutoTArray<Accessible*, 30> boundaryParents, containerParents;
+
+  // Crop the start boundary.
+  Accessible* container = nullptr;
+  Accessible* boundary = mStartContainer->GetChildAtOffset(mStartOffset);
+  if (boundary != aContainer) {
+    CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+                 &containerParents, &containerPos);
+
+    if (boundaryPos == 0) {
+      if (containerPos != 0) {
+        // The container is contained by the start boundary, reduce the range to
+        // the point starting at the container.
+        aContainer->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
+        static_cast<Accessible*>(mStartContainer)->AddRef();
+      }
+      else {
+        // The start boundary and the container are siblings.
+        container = aContainer;
+      }
+    }
+    else if (containerPos != 0) {
+      // The container does not contain the start boundary.
+      boundary = boundaryParents[boundaryPos];
+      container = containerParents[containerPos];
+    }
+
+    if (container) {
+      // If the range start is after the container, then make the range invalid.
+      if (boundary->IndexInParent() > container->IndexInParent()) {
+        return !!(mRoot = nullptr);
+      }
+
+      // If the range starts before the container, then reduce the range to
+      // the point starting at the container.
+      if (boundary->IndexInParent() < container->IndexInParent()) {
+        container->ToTextPoint(mStartContainer.StartAssignment(), &mStartOffset);
+        mStartContainer.get()->AddRef();
+      }
+    }
+
+    boundaryParents.SetLengthAndRetainStorage(0);
+    containerParents.SetLengthAndRetainStorage(0);
+  }
+
+  boundary = mEndContainer->GetChildAtOffset(mEndOffset);
+  if (boundary == aContainer) {
+    return true;
+  }
+
+  // Crop the end boundary.
+  container = nullptr;
+  CommonParent(boundary, aContainer, &boundaryParents, &boundaryPos,
+               &containerParents, &containerPos);
+
+  if (boundaryPos == 0) {
+    if (containerPos != 0) {
+      aContainer->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
+      static_cast<Accessible*>(mEndContainer)->AddRef();
+    }
+    else {
+      container = aContainer;
+    }
+  }
+  else if (containerPos != 0) {
+    boundary = boundaryParents[boundaryPos];
+    container = containerParents[containerPos];
+  }
+
+  if (!container) {
+    return true;
+  }
+
+  if (boundary->IndexInParent() < container->IndexInParent()) {
+    return !!(mRoot = nullptr);
+  }
+
+  if (boundary->IndexInParent() > container->IndexInParent()) {
+    container->ToTextPoint(mEndContainer.StartAssignment(), &mEndOffset, false);
+    static_cast<Accessible*>(mEndContainer)->AddRef();
+  }
+
+  return true;
+}
+
 void
 TextRange::FindText(const nsAString& aText, EDirection aDirection,
                     nsCaseTreatment aCaseSensitive, TextRange* aFoundRange) const
 {
 
 }
 
 void
@@ -291,10 +326,51 @@ TextRange::TextInternal(nsAString& aText
 void
 TextRange::MoveInternal(ETextUnit aUnit, int32_t aCount,
                         HyperTextAccessible& aContainer, int32_t aOffset,
                         HyperTextAccessible* aStopContainer, int32_t aStopOffset)
 {
 
 }
 
+Accessible*
+TextRange::CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+                        nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
+                        nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const
+{
+  if (aAcc1 == aAcc2) {
+    return aAcc1;
+  }
+
+  MOZ_ASSERT(aParents1->Length() == 0 || aParents2->Length() == 0,
+             "Wrong arguments");
+
+  // Build the chain of parents.
+  Accessible* p1 = aAcc1;
+  Accessible* p2 = aAcc2;
+  do {
+    aParents1->AppendElement(p1);
+    p1 = p1->Parent();
+  } while (p1);
+  do {
+    aParents2->AppendElement(p2);
+    p2 = p2->Parent();
+  } while (p2);
+
+  // Find where the parent chain differs
+  *aPos1 = aParents1->Length();
+  *aPos2 = aParents2->Length();
+  Accessible* parent = nullptr;
+  uint32_t len = 0;
+  for (len = std::min(*aPos1, *aPos2); len > 0; --len) {
+    Accessible* child1 = aParents1->ElementAt(--(*aPos1));
+    Accessible* child2 = aParents2->ElementAt(--(*aPos2));
+    if (child1 != child2)
+      break;
+
+    parent = child1;
+  }
+
+  return parent;
+}
+
 } // namespace a11y
 } // namespace mozilla
--- a/accessible/base/TextRange.h
+++ b/accessible/base/TextRange.h
@@ -126,16 +126,22 @@ public:
   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);
 
+  /**
+   * Crops the range if it overlaps the given accessible element boundaries,
+   * returns true if the range was cropped successfully.
+   */
+  bool Crop(Accessible* aContainer);
+
   enum EDirection {
     eBackward,
     eForward
   };
 
   /**
    * Return range enclosing the found text.
    */
@@ -238,16 +244,24 @@ private:
   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);
 
+  /**
+   * A helper method returning a common parent for two given accessible
+   * elements.
+   */
+  Accessible* CommonParent(Accessible* aAcc1, Accessible* aAcc2,
+                           nsTArray<Accessible*>* aParents1, uint32_t* aPos1,
+                           nsTArray<Accessible*>* aParents2, uint32_t* aPos2) const;
+
   RefPtr<HyperTextAccessible> mRoot;
   RefPtr<HyperTextAccessible> mStartContainer;
   RefPtr<HyperTextAccessible> mEndContainer;
   int32_t mStartOffset;
   int32_t mEndOffset;
 };
 
 
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -2245,16 +2245,40 @@ Accessible::AnchorAt(uint32_t aAnchorInd
 
 already_AddRefed<nsIURI>
 Accessible::AnchorURIAt(uint32_t aAnchorIndex)
 {
   NS_PRECONDITION(IsLink(), "AnchorURIAt is called on not hyper link!");
   return nullptr;
 }
 
+void
+Accessible::ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+                        bool aIsBefore) const
+{
+  if (IsHyperText()) {
+    *aContainer = const_cast<Accessible*>(this)->AsHyperText();
+    *aOffset = aIsBefore ? 0 : (*aContainer)->CharacterCount();
+    return;
+  }
+
+  const Accessible* child = nullptr;
+  const Accessible* parent = this;
+  do {
+    child = parent;
+    parent = parent->Parent();
+  } while (parent && !parent->IsHyperText());
+
+  if (parent) {
+    *aContainer = const_cast<Accessible*>(parent)->AsHyperText();
+    *aOffset = (*aContainer)->GetChildOffset(
+      child->IndexInParent() + static_cast<int32_t>(!aIsBefore));
+  }
+}
+
 
 ////////////////////////////////////////////////////////////////////////////////
 // SelectAccessible
 
 void
 Accessible::SelectedItems(nsTArray<Accessible*>* aItems)
 {
   AccIterator iter(this, filters::GetSelected);
--- a/accessible/generic/Accessible.h
+++ b/accessible/generic/Accessible.h
@@ -753,16 +753,22 @@ public:
    */
   virtual Accessible* AnchorAt(uint32_t aAnchorIndex);
 
   /**
    * Returns an anchor URI at the given index.
    */
   virtual already_AddRefed<nsIURI> AnchorURIAt(uint32_t aAnchorIndex);
 
+  /**
+   * Returns a text point for the accessible element.
+   */
+  void ToTextPoint(HyperTextAccessible** aContainer, int32_t* aOffset,
+                   bool aIsBefore = true) const;
+
   //////////////////////////////////////////////////////////////////////////////
   // SelectAccessible
 
   /**
    * Return an array of selected items.
    */
   virtual void SelectedItems(nsTArray<Accessible*>* aItems);
 
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1764,17 +1764,17 @@ HyperTextAccessible::EnclosingRange(a11y
   } else {
     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");
+  MOZ_ASSERT(aRanges->Length() == 0, "TextRange array supposed to be empty");
 
   dom::Selection* sel = DOMSelection();
   if (!sel)
     return;
 
   aRanges->SetCapacity(sel->RangeCount());
 
   for (uint32_t idx = 0; idx < sel->RangeCount(); idx++) {
--- a/accessible/generic/HyperTextAccessible.h
+++ b/accessible/generic/HyperTextAccessible.h
@@ -253,17 +253,17 @@ public:
   /**
    * 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
    */
-  int32_t GetChildOffset(Accessible* aChild,
+  int32_t GetChildOffset(const Accessible* aChild,
                          bool aInvalidateAfter = false) const
   {
     int32_t index = GetIndexOf(aChild);
     return index == -1 ? -1 : GetChildOffset(index, aInvalidateAfter);
   }
 
   /**
    * Return text offset for the child accessible index.
--- a/accessible/interfaces/ia2/Makefile.in
+++ b/accessible/interfaces/ia2/Makefile.in
@@ -6,16 +6,17 @@ IA2DIR        = $(topsrcdir)/other-licen
 
 GARBAGE       += $(MIDL_GENERATED_FILES)
 
 # Please keep this list in sync with the moz.build file until the rest of this
 # Makefile is ported over.
 MIDL_INTERFACES = \
   Accessible2.idl \
   Accessible2_2.idl \
+  Accessible2_3.idl \
   AccessibleAction.idl \
   AccessibleApplication.idl \
   AccessibleComponent.idl \
   AccessibleDocument.idl \
   AccessibleEditableText.idl \
   AccessibleHyperlink.idl \
   AccessibleHypertext.idl \
   AccessibleHypertext2.idl \
--- a/accessible/interfaces/nsIAccessibleTextRange.idl
+++ b/accessible/interfaces/nsIAccessibleTextRange.idl
@@ -8,17 +8,17 @@
 interface nsIAccessible;
 interface nsIAccessibleText;
 interface nsIArray;
 interface nsIVariant;
 
 /**
  * A range representing a piece of text in the document.
  */
-[scriptable, uuid(525b3401-8a67-4822-b35d-661065767cd8)]
+[scriptable, uuid(c4515623-55f9-4543-a3d5-c1e9afa588f4)]
 interface nsIAccessibleTextRange : nsISupports
 {
   readonly attribute nsIAccessibleText startContainer;
   readonly attribute long startOffset;
   readonly attribute nsIAccessibleText endContainer;
   readonly attribute long endOffset;
 
   /**
@@ -77,16 +77,21 @@ interface nsIAccessibleTextRange : nsISu
   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);
 
   /**
+   * Crops the range by the given accessible element.
+   */
+  boolean crop(in nsIAccessible aContainer);
+
+  /**
    * Return range enclosing the found text.
    */
   nsIAccessibleTextRange findText(in AString aText, in boolean aIsBackward,
                                   in boolean aIsIgnoreCase);
 
   /**
    * Text attributes. Used in conjunction with findAttrs().
    */
--- a/accessible/tests/mochitest/text.js
+++ b/accessible/tests/mochitest/text.js
@@ -468,16 +468,20 @@ function testTextRange(aRange, aRangeDes
            "Wrong start container of " + aRangeDescr);
   is(aRange.startOffset, aStartOffset,
      "Wrong start offset of " + aRangeDescr);
   isObject(aRange.endContainer, getAccessible(aEndContainer),
            "Wrong end container of " + aRangeDescr);
   is(aRange.endOffset, aEndOffset,
      "Wrong end offset of " + aRangeDescr);
 
+  if (aText === undefined) {
+    return;
+  }
+
   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);
--- a/accessible/tests/mochitest/textrange/a11y.ini
+++ b/accessible/tests/mochitest/textrange/a11y.ini
@@ -1,3 +1,4 @@
 [DEFAULT]
 
 [test_general.html]
+[test_selection.html]
new file mode 100644
--- /dev/null
+++ b/accessible/tests/mochitest/textrange/test_selection.html
@@ -0,0 +1,120 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Text Range selection tests</title>
+  <link rel="stylesheet" type="text/css"
+        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()
+    {
+      var sel = window.getSelection();
+      var p = getNode("p1");
+      var a = getNode("p2_a");
+
+      var range = document.createRange();
+      sel.addRange(range);
+
+      // the accessible is contained by the range
+      range.selectNode(p);
+
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #1", document, 3, document, 4);
+
+      ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #1.");
+      testTextRange(a11yrange, "cropped range #1", a, 0, a, 5);
+
+      // the range is contained by the accessible
+      range.selectNode(a);
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #2", p, 5, p, 6);
+
+      ok(a11yrange.crop(getAccessible(p)), "Range failed to crop #2.");
+      testTextRange(a11yrange, "cropped range #2", p, 5, p, 6);
+
+      // the range starts before the accessible and ends inside it
+      range.setStart(p, 0);
+      range.setEndAfter(a.firstChild, 4);
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #3", p, 0, a, 4);
+
+      ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #3.");
+      testTextRange(a11yrange, "cropped range #3", a, 0, a, 4);
+
+      // the range starts inside the accessible and ends after it
+      range.setStart(a.firstChild, 1);
+      range.setEndAfter(p);
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #4", a, 1, document, 4);
+
+      ok(a11yrange.crop(getAccessible(a)), "Range failed to crop #4.");
+      testTextRange(a11yrange, "cropped range #4", a, 1, a, 5);
+
+      // the range ends before the accessible
+      range.setStart(p.firstChild, 0);
+      range.setEnd(p.firstChild, 4);
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #5", p, 0, p, 4);
+      ok(!a11yrange.crop(getAccessible(a)), "Crop #5 succeeded while it shouldn't");
+
+      // the range starts after the accessible
+      range.setStart(p.lastChild, 0);
+      range.setEnd(p.lastChild, 4);
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #6", p, 6, p, 10);
+
+      ok(!a11yrange.crop(getAccessible(a)), "Crop #6 succeeded while it shouldn't");
+
+      // crop a range by a table
+      range.selectNode(getNode("c2"));
+      var a11yranges = getAccessible(document, [nsIAccessibleText]).selectionRanges;
+      var a11yrange = a11yranges.queryElementAt(0, nsIAccessibleTextRange);
+
+      testTextRange(a11yrange, "selection range #7", document, 4, document, 5);
+
+      ok(a11yrange.crop(getAccessible("table")), "Range failed to crop #7.");
+      testTextRange(a11yrange, "cropped range #7", "c2", 5, "c2", 6);
+
+      SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTest);
+  </script>
+</head>
+<body>
+
+  <a target="_blank"
+     title="Implement IAccessible2_3::selectionRanges"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=1233118">Bug 1233118</a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <p id="p1">text <a id="p2_a" href="www">link<img id="p2_img", src="../moz.png"></a> text</p>
+
+  <div id="c2">start<table id="table"><tr><td>cell</td></tr></table>end</div>
+</body>
+</html>
--- a/accessible/windows/ia2/ia2Accessible.cpp
+++ b/accessible/windows/ia2/ia2Accessible.cpp
@@ -3,26 +3,28 @@
 /* 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 "AccessibleWrap.h"
 
 #include "Accessible2_i.c"
 #include "Accessible2_2_i.c"
+#include "Accessible2_3_i.c"
 #include "AccessibleRole.h"
 #include "AccessibleStates.h"
 
 #include "Compatibility.h"
 #include "ia2AccessibleRelation.h"
 #include "IUnknownImpl.h"
 #include "nsCoreUtils.h"
 #include "nsIAccessibleTypes.h"
 #include "mozilla/a11y/PDocAccessible.h"
 #include "Relation.h"
+#include "TextRange-inl.h"
 #include "nsAccessibilityService.h"
 
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
 
@@ -35,17 +37,19 @@ template<typename String> static void Es
 STDMETHODIMP
 ia2Accessible::QueryInterface(REFIID iid, void** ppv)
 {
   if (!ppv)
     return E_INVALIDARG;
 
   *ppv = nullptr;
 
-  if (IID_IAccessible2_2 == iid)
+  if (IID_IAccessible2_3 == iid)
+    *ppv = static_cast<IAccessible2_3*>(this);
+  else if (IID_IAccessible2_2 == iid)
     *ppv = static_cast<IAccessible2_2*>(this);
   else if (IID_IAccessible2 == iid && !Compatibility::IsIA2Off())
     *ppv = static_cast<IAccessible2*>(this);
 
   if (*ppv) {
     (reinterpret_cast<IUnknown*>(*ppv))->AddRef();
     return S_OK;
   }
@@ -744,16 +748,68 @@ ia2Accessible::get_relationTargetsOfType
     (*aTargets)[i]->AddRef();
   }
 
   return S_OK;
 
   A11Y_TRYBLOCK_END
 }
 
+STDMETHODIMP
+ia2Accessible::get_selectionRanges(IA2Range** aRanges,
+                                   long *aNRanges)
+{
+  A11Y_TRYBLOCK_BEGIN
+
+  if (!aRanges || !aNRanges || aNRanges <= 0)
+    return E_INVALIDARG;
+
+  *aNRanges = 0;
+
+  AccessibleWrap* acc = static_cast<AccessibleWrap*>(this);
+  if (acc->IsDefunct())
+    return CO_E_OBJNOTCONNECTED;
+
+  nsAutoTArray<TextRange, 1> ranges;
+  acc->Document()->SelectionRanges(&ranges);
+  uint32_t len = ranges.Length();
+  for (uint32_t idx = 0; idx < len; idx++) {
+    if (!ranges[idx].Crop(acc)) {
+      ranges.RemoveElementAt(idx);
+    }
+  }
+
+  *aNRanges = ranges.Length();
+  *aRanges = static_cast<IA2Range*>(
+    ::CoTaskMemAlloc(sizeof(IA2Range) * *aNRanges));
+  if (!*aRanges)
+    return E_OUTOFMEMORY;
+
+  for (uint32_t idx = 0; idx < static_cast<uint32_t>(*aNRanges); idx++) {
+    AccessibleWrap* anchor =
+      static_cast<AccessibleWrap*>(ranges[idx].StartContainer());
+    (*aRanges)[idx].anchor = static_cast<IAccessible2*>(anchor);
+    anchor->AddRef();
+
+    (*aRanges)[idx].anchorOffset = ranges[idx].StartOffset();
+
+    AccessibleWrap* active =
+      static_cast<AccessibleWrap*>(ranges[idx].EndContainer());
+    (*aRanges)[idx].active = static_cast<IAccessible2*>(active);
+    active->AddRef();
+
+    (*aRanges)[idx].activeOffset = ranges[idx].EndOffset();
+  }
+
+  return S_OK;
+
+  A11Y_TRYBLOCK_END
+}
+
+
 ////////////////////////////////////////////////////////////////////////////////
 // Helpers
 
 template<typename String>
 static inline void
 EscapeAttributeChars(String& aStr)
 {
   int32_t offset = 0;
--- a/accessible/windows/ia2/ia2Accessible.h
+++ b/accessible/windows/ia2/ia2Accessible.h
@@ -4,23 +4,23 @@
  * 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_ia2Accessible_h_
 #define mozilla_a11y_ia2Accessible_h_
 
 #include "nsISupports.h"
 
-#include "Accessible2_2.h"
+#include "Accessible2_3.h"
 
 namespace mozilla {
 namespace a11y {
 class Attribute;
 
-class ia2Accessible : public IAccessible2_2
+class ia2Accessible : public IAccessible2_3
 {
 public:
 
   // IUnknown
   STDMETHODIMP QueryInterface(REFIID, void**);
 
   // IAccessible2
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_nRelations(
@@ -99,16 +99,21 @@ public:
 
   virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_relationTargetsOfType(
     /* [in] */ BSTR type,
     /* [in] */ long maxTargets,
     /* [out, size_is(,*nTargets)] */ IUnknown*** targets,
     /* [out, retval] */ long* nTargets
   );
 
+  // IAccessible2_3
+  virtual /* [propget] */ HRESULT STDMETHODCALLTYPE get_selectionRanges(
+    /* [out, size_is(,*nRanges)] */ IA2Range** ranges,
+    /* [out, retval] */ long *nRanges);
+
   // Helper method
   static HRESULT ConvertToIA2Attributes(nsIPersistentProperties* aAttributes,
                                         BSTR* aIA2Attributes);
   static HRESULT ConvertToIA2Attributes(nsTArray<Attribute>* aAttributes,
                                         BSTR* aIA2Attributes);
 };
 
 } // namespace a11y
--- a/accessible/xpcom/xpcAccessibleTextRange.cpp
+++ b/accessible/xpcom/xpcAccessibleTextRange.cpp
@@ -1,17 +1,17 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 "xpcAccessibleTextRange.h"
 
-#include "TextRange.h"
+#include "TextRange-inl.h"
 #include "xpcAccessibleDocument.h"
 
 #include "nsIMutableArray.h"
 #include "nsComponentManagerUtils.h"
 #include "nsQueryObject.h"
 
 using namespace mozilla;
 using namespace mozilla::a11y;
@@ -166,16 +166,26 @@ xpcAccessibleTextRange::MoveEnd(uint32_t
 
 NS_IMETHODIMP
 xpcAccessibleTextRange::Normalize(uint32_t aUnit)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
+xpcAccessibleTextRange::Crop(nsIAccessible* aContainer, bool* aSuccess)
+{
+  Accessible* container = aContainer->ToInternalAccessible();
+  NS_ENSURE_TRUE(container, NS_ERROR_INVALID_ARG);
+
+  *aSuccess = mRange.Crop(container);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 xpcAccessibleTextRange::FindText(const nsAString& aText, bool aIsBackward,
                                  bool aIsIgnoreCase,
                                  nsIAccessibleTextRange** aRange)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/accessible/xpcom/xpcAccessibleTextRange.h
+++ b/accessible/xpcom/xpcAccessibleTextRange.h
@@ -44,16 +44,17 @@ public:
                               uint32_t aOtherRangeEndPoint,
                               int32_t* aResult) final override;
   NS_IMETHOD GetText(nsAString& aText) final override;
   NS_IMETHOD GetBounds(nsIArray** aRectList) final override;
   NS_IMETHOD Move(uint32_t aUnit, int32_t aCount) final override;
   NS_IMETHOD MoveStart(uint32_t aUnit, int32_t aCount) final override;
   NS_IMETHOD MoveEnd(uint32_t aUnit, int32_t aCount) final override;
   NS_IMETHOD Normalize(uint32_t aUnit) final override;
+  NS_IMETHOD Crop(nsIAccessible* aContainer, bool* aSuccess) final override;
   NS_IMETHOD FindText(const nsAString& aText, bool aIsBackward, bool aIsIgnoreCase,
                       nsIAccessibleTextRange** aRange) final override;
   NS_IMETHOD FindAttr(uint32_t aAttr, nsIVariant* aVal, bool aIsBackward,
                       nsIAccessibleTextRange** aRange) final override;
   NS_IMETHOD AddToSelection() final override;
   NS_IMETHOD RemoveFromSelection() final override;
   NS_IMETHOD Select() final override;
   NS_IMETHOD ScrollIntoView(uint32_t aHow) final override;
new file mode 100644
--- /dev/null
+++ b/other-licenses/ia2/Accessible2_3.idl
@@ -0,0 +1,47 @@
+import "objidl.idl";
+import "oaidl.idl";
+import "oleacc.idl";
+import "Accessible2_2.idl";
+
+/**
+ * This structure represents a directional range of the content. It is defined
+ * by two points in the content, where each one is defined by an accessible
+ * object and an offset relative to it. A typical case of a range point is
+ * a text accessible and text offset within it.
+ *
+ * The "anchor" is one point of the range and typically remains constant.
+ * The other point is the "active" point, which typically corresponds to
+ * the user's focus or point of interest. The user moves the active point to
+ * expand or collapse the range. In most cases, anchor is the start of the range
+ * and active is the end. However, in case of selection, when selecting
+ * backwards (e.g. pressing shift+left arrow in a text field), the start of
+ * the range is the active point, as the user moves this to manipulate
+ * the selection.
+ */
+typedef struct IA2Range {
+  IUnknown* anchor;
+  long anchorOffset;
+  IUnknown* active;
+  long activeOffset;
+} IA2Range;
+
+/**
+ * @brief This interface is an extension of IAccessible2_2 and IAccessible2
+ * interfaces.
+ */
+[object, uuid(5BE18059-762E-4E73-9476-ABA294FED411)]
+interface IAccessible2_3 : IAccessible2_2
+{
+  /**
+   * @brief Returns an array of ranges for selections within the accessible.
+   * @param [out] the array of selection ranges
+   * @param [out] the array length
+   * @retval S_OK
+   * @retval S_FALSE returned if there is no selection within the accessible
+  */
+  [propget] HRESULT selectionRanges
+    (
+      [out, size_is(,*nRanges)] IA2Range **ranges,
+      [out, retval] long *nRanges
+    );
+}
--- a/other-licenses/ia2/IA2TypeLibrary.idl
+++ b/other-licenses/ia2/IA2TypeLibrary.idl
@@ -66,16 +66,17 @@ cpp_quote("")
 ]
 
 library IAccessible2Lib
 {
     importlib ("stdole2.tlb");
     importlib ("oleacc.dll");
     interface IAccessible2;
     interface IAccessible2_2;
+    interface IAccessible2_3;
     interface IAccessibleAction;
     interface IAccessibleApplication;
     interface IAccessibleComponent;
     interface IAccessibleDocument;
     interface IAccessibleEditableText;
     interface IAccessibleHyperlink;
     interface IAccessibleHypertext;
     interface IAccessibleHypertext2;