Bug 1233118 - implement IAccessible2_3::selectionRanges, r=yzen
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 20 Jan 2016 12:53:03 -0500
changeset 280815 c34410da6b0130bc5430d18a20ad0357114ab0ed
parent 280814 3842b1992f25049b7230930c30d8e646ceb778ae
child 280816 a4d3504abe124b5c42daeb1b5d268e2a6a5cc4c1
push id29922
push usercbook@mozilla.com
push dateThu, 21 Jan 2016 10:51:00 +0000
treeherdermozilla-central@977d78a8dd78 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersyzen
bugs1233118
milestone46.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 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;