Bug 688126 - nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases, r=marcoz, sr=smaug
authorAlexander Surkov <surkov.alexander@gmail.com>
Wed, 12 Oct 2011 16:39:58 +0900
changeset 78616 197cf8c60edc5baa06638fbfbb9e1646330dee0d
parent 78615 b5152a05d6160af47f1947eb232ab34fe3674071
child 78617 fbe49f0b30937bfee8be4775df8ed95512507503
push id21321
push usermak77@bonardo.net
push dateThu, 13 Oct 2011 13:50:30 +0000
treeherdermozilla-central@d6d3a1f90e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarcoz, smaug
bugs688126
milestone10.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 688126 - nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases, r=marcoz, sr=smaug
accessible/src/html/nsHyperTextAccessible.cpp
accessible/src/html/nsHyperTextAccessible.h
accessible/src/xul/nsXULFormControlAccessible.cpp
accessible/src/xul/nsXULFormControlAccessible.h
accessible/tests/mochitest/Makefile.in
accessible/tests/mochitest/events.js
accessible/tests/mochitest/textselection/Makefile.in
accessible/tests/mochitest/textselection/test_general.html
--- a/accessible/src/html/nsHyperTextAccessible.cpp
+++ b/accessible/src/html/nsHyperTextAccessible.cpp
@@ -903,27 +903,19 @@ nsresult nsHyperTextAccessible::GetTextH
   if (aOffset == nsIAccessibleText::TEXT_OFFSET_CARET) {
     GetCaretOffset(&aOffset);
     if (aOffset > 0 && (aBoundaryType == BOUNDARY_LINE_START ||
                         aBoundaryType == BOUNDARY_LINE_END)) {
       // It is the same character offset when the caret is visually at the very end of a line
       // or the start of a new line. Getting text at the line should provide the line with the visual caret,
       // otherwise screen readers will announce the wrong line as the user presses up or down arrow and land
       // at the end of a line.
-      nsCOMPtr<nsISelection> domSel;
-      nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                                  nsnull, getter_AddRefs(domSel));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
-      nsRefPtr<nsFrameSelection> frameSelection;
-      rv = privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      if (frameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
+      nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+      if (frameSelection &&
+          frameSelection->GetHint() == nsFrameSelection::HINTLEFT) {
         -- aOffset;  // We are at the start of a line
       }
     }
   }
   else if (aOffset < 0) {
     return NS_ERROR_FAILURE;
   }
 
@@ -1582,39 +1574,37 @@ nsHyperTextAccessible::SetSelectionRange
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Set the selection
   SetSelectionBounds(0, aStartPos, aEndPos);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // If range 0 was successfully set, clear any additional selection 
   // ranges remaining from previous selection
-  nsCOMPtr<nsISelection> domSel;
-  nsCOMPtr<nsISelectionController> selCon;
-  GetSelections(nsISelectionController::SELECTION_NORMAL,
-                getter_AddRefs(selCon), getter_AddRefs(domSel));
-  if (domSel) {
-    PRInt32 numRanges;
-    domSel->GetRangeCount(&numRanges);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  NS_ENSURE_STATE(frameSelection);
+
+  nsCOMPtr<nsISelection> domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_STATE(domSel);
 
-    for (PRInt32 count = 0; count < numRanges - 1; count ++) {
-      nsCOMPtr<nsIDOMRange> range;
-      domSel->GetRangeAt(1, getter_AddRefs(range));
-      domSel->RemoveRange(range);
-    }
-  }
-  
-  if (selCon) {
-    // XXX I'm not sure this can do synchronous scrolling. If the last param is
-    // set to true, this calling might flush the pending reflow. See bug 418470.
-    selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
-      nsISelectionController::SELECTION_FOCUS_REGION, 0);
+  PRInt32 numRanges = 0;
+  domSel->GetRangeCount(&numRanges);
+
+  for (PRInt32 count = 0; count < numRanges - 1; count ++) {
+    nsCOMPtr<nsIDOMRange> range;
+    domSel->GetRangeAt(1, getter_AddRefs(range));
+    domSel->RemoveRange(range);
   }
 
-  return NS_OK;
+  // XXX I'm not sure this can do synchronous scrolling. If the last param is
+  // set to true, this calling might flush the pending reflow. See bug 418470.
+  return frameSelection->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
+                                                 nsISelectionController::SELECTION_FOCUS_REGION,
+                                                 0);
 }
 
 NS_IMETHODIMP
 nsHyperTextAccessible::SetCaretOffset(PRInt32 aCaretOffset)
 {
   return SetSelectionRange(aCaretOffset, aCaretOffset);
 }
 
@@ -1630,23 +1620,25 @@ nsHyperTextAccessible::GetCaretOffset(PR
   // is not inside of focused node.
   FocusManager::FocusDisposition focusDisp =
     FocusMgr()->IsInOrContainsFocus(this);
   if (focusDisp == FocusManager::eNone)
     return NS_OK;
 
   // Turn the focus node and offset of the selection into caret hypretext
   // offset.
-  nsCOMPtr<nsISelection> domSel;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, getter_AddRefs(domSel));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  NS_ENSURE_STATE(frameSelection);
+
+  nsISelection* domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_STATE(domSel);
 
   nsCOMPtr<nsIDOMNode> focusDOMNode;
-  rv = domSel->GetFocusNode(getter_AddRefs(focusDOMNode));
+  nsresult rv = domSel->GetFocusNode(getter_AddRefs(focusDOMNode));
   NS_ENSURE_SUCCESS(rv, rv);
 
   PRInt32 focusOffset;
   rv = domSel->GetFocusOffset(&focusOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // No caret if this DOM node is inside of focused node but the selection's
   // focus point is not inside of this DOM node.
@@ -1660,28 +1652,29 @@ nsHyperTextAccessible::GetCaretOffset(PR
         !nsCoreUtils::IsAncestorOf(thisNode, resultNode))
       return NS_OK;
   }
 
   DOMPointToHypertextOffset(focusNode, focusOffset, aCaretOffset);
   return NS_OK;
 }
 
-PRInt32 nsHyperTextAccessible::GetCaretLineNumber()
+PRInt32
+nsHyperTextAccessible::GetCaretLineNumber()
 {
   // Provide the line number for the caret, relative to the
   // currently focused node. Use a 1-based index
-  nsCOMPtr<nsISelection> domSel;
-  GetSelections(nsISelectionController::SELECTION_NORMAL, nsnull,
-                getter_AddRefs(domSel));
-  nsCOMPtr<nsISelectionPrivate> privateSelection(do_QueryInterface(domSel));
-  NS_ENSURE_TRUE(privateSelection, -1);
-  nsRefPtr<nsFrameSelection> frameSelection;
-  privateSelection->GetFrameSelection(getter_AddRefs(frameSelection));
-  NS_ENSURE_TRUE(frameSelection, -1);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  if (!frameSelection)
+    return -1;
+
+  nsISelection* domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  if (!domSel)
+    return - 1;
 
   nsCOMPtr<nsIDOMNode> caretNode;
   domSel->GetFocusNode(getter_AddRefs(caretNode));
   nsCOMPtr<nsIContent> caretContent = do_QueryInterface(caretNode);
   if (!caretContent || !nsCoreUtils::IsAncestorOf(GetNode(), caretContent))
     return -1;
 
   PRInt32 caretOffset, returnOffsetUnused;
@@ -1726,154 +1719,123 @@ PRInt32 nsHyperTextAccessible::GetCaretL
 
     caretFrame = parentFrame;
   }
 
   NS_NOTREACHED("DOM ancestry had this hypertext but frame ancestry didn't");
   return lineNumber;
 }
 
-nsresult
-nsHyperTextAccessible::GetSelections(PRInt16 aType,
-                                     nsISelectionController **aSelCon,
-                                     nsISelection **aDomSel,
-                                     nsCOMArray<nsIDOMRange>* aRanges)
+already_AddRefed<nsFrameSelection>
+nsHyperTextAccessible::FrameSelection()
 {
-  if (IsDefunct())
-    return NS_ERROR_FAILURE;
+  nsIFrame* frame = GetFrame();
+  return frame->GetFrameSelection();
+}
 
-  if (aSelCon) {
-    *aSelCon = nsnull;
-  }
-  if (aDomSel) {
-    *aDomSel = nsnull;
-  }
-  if (aRanges) {
-    aRanges->Clear();
-  }
-  
-  nsCOMPtr<nsISelection> domSel;
-  nsCOMPtr<nsISelectionController> selCon;
+void
+nsHyperTextAccessible::GetSelectionDOMRanges(PRInt16 aType,
+                                             nsCOMArray<nsIDOMRange>* aRanges)
+{
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  if (!frameSelection)
+    return;
+
+  nsISelection* domSel = frameSelection->GetSelection(aType);
+  if (!domSel)
+    return;
+
+  nsCOMPtr<nsINode> startNode = GetNode();
 
   nsCOMPtr<nsIEditor> editor;
   GetAssociatedEditor(getter_AddRefs(editor));
-  nsCOMPtr<nsIPlaintextEditor> peditor(do_QueryInterface(editor));
-  if (peditor) {
-    // Case 1: plain text editor
-    // This is for form controls which have their own
-    // selection controller separate from the document, for example
-    // HTML:input, HTML:textarea, XUL:textbox, etc.
-    editor->GetSelectionController(getter_AddRefs(selCon));
-  }
-  else {
-    // Case 2: rich content subtree (can be rich editor)
-    // This uses the selection controller from the entire document
-    nsIFrame *frame = GetFrame();
-    NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
-
-    // Get the selection and selection controller
-    frame->GetSelectionController(GetPresContext(),
-                                  getter_AddRefs(selCon));
-  }
-  NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
-
-  selCon->GetSelection(aType, getter_AddRefs(domSel));
-  NS_ENSURE_TRUE(domSel, NS_ERROR_FAILURE);
-
-  if (aSelCon) {
-    NS_ADDREF(*aSelCon = selCon);
-  }
-  if (aDomSel) {
-    NS_ADDREF(*aDomSel = domSel);
+  if (editor) {
+    nsCOMPtr<nsIDOMElement> editorRoot;
+    editor->GetRootElement(getter_AddRefs(editorRoot));
+    startNode = do_QueryInterface(editorRoot);
   }
 
-  if (aRanges) {
-    nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(domSel));
-
-    nsCOMPtr<nsINode> startNode = GetNode();
-    if (peditor) {
-      nsCOMPtr<nsIDOMElement> editorRoot;
-      editor->GetRootElement(getter_AddRefs(editorRoot));
-      startNode = do_QueryInterface(editorRoot);
-    }
-    NS_ENSURE_STATE(startNode);
+  if (!startNode)
+    return;
 
-    PRUint32 childCount = startNode->GetChildCount();
-    nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(startNode));
-    nsresult rv = privSel->
-      GetRangesForIntervalCOMArray(startDOMNode, 0, startDOMNode, childCount,
-                                   PR_TRUE, aRanges);
-    NS_ENSURE_SUCCESS(rv, rv);
-    // Remove collapsed ranges
-    PRInt32 numRanges = aRanges->Count();
-    for (PRInt32 count = 0; count < numRanges; count ++) {
-      bool isCollapsed;
-      (*aRanges)[count]->GetCollapsed(&isCollapsed);
-      if (isCollapsed) {
-        aRanges->RemoveObjectAt(count);
-        -- numRanges;
-        -- count;
-      }
+  PRUint32 childCount = startNode->GetChildCount();
+  nsCOMPtr<nsIDOMNode> startDOMNode(do_QueryInterface(startNode));
+  nsCOMPtr<nsISelectionPrivate> privSel(do_QueryInterface(domSel));
+  nsresult rv = privSel->
+    GetRangesForIntervalCOMArray(startDOMNode, 0, startDOMNode, childCount,
+                                 true, aRanges);
+  NS_ENSURE_SUCCESS(rv,);
+
+  // Remove collapsed ranges
+  PRInt32 numRanges = aRanges->Count();
+  for (PRInt32 count = 0; count < numRanges; count ++) {
+    bool isCollapsed = false;
+    (*aRanges)[count]->GetCollapsed(&isCollapsed);
+    if (isCollapsed) {
+      aRanges->RemoveObjectAt(count);
+      --numRanges;
+      --count;
     }
   }
-
-  return NS_OK;
 }
 
 /*
  * Gets the number of selected regions.
  */
-NS_IMETHODIMP nsHyperTextAccessible::GetSelectionCount(PRInt32 *aSelectionCount)
+NS_IMETHODIMP
+nsHyperTextAccessible::GetSelectionCount(PRInt32* aSelectionCount)
 {
-  nsCOMPtr<nsISelection> domSel;
+  NS_ENSURE_ARG_POINTER(aSelectionCount);
+  *aSelectionCount = 0;
+
   nsCOMArray<nsIDOMRange> ranges;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, nsnull, &ranges);
-  NS_ENSURE_SUCCESS(rv, rv);
-
+  GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges);
   *aSelectionCount = ranges.Count();
 
   return NS_OK;
 }
 
 /*
  * Gets the start and end offset of the specified selection.
  */
-NS_IMETHODIMP nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum, PRInt32 *aStartOffset, PRInt32 *aEndOffset)
+NS_IMETHODIMP
+nsHyperTextAccessible::GetSelectionBounds(PRInt32 aSelectionNum,
+                                          PRInt32* aStartOffset,
+                                          PRInt32* aEndOffset)
 {
+  NS_ENSURE_ARG_POINTER(aStartOffset);
+  NS_ENSURE_ARG_POINTER(aEndOffset);
   *aStartOffset = *aEndOffset = 0;
 
-  nsCOMPtr<nsISelection> domSel;
   nsCOMArray<nsIDOMRange> ranges;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, getter_AddRefs(domSel), &ranges);
-  NS_ENSURE_SUCCESS(rv, rv);
+  GetSelectionDOMRanges(nsISelectionController::SELECTION_NORMAL, &ranges);
 
   PRInt32 rangeCount = ranges.Count();
   if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
     return NS_ERROR_INVALID_ARG;
 
   nsCOMPtr<nsIDOMRange> range = ranges[aSelectionNum];
 
   // Get start point
   nsCOMPtr<nsIDOMNode> startDOMNode;
   range->GetStartContainer(getter_AddRefs(startDOMNode));
   nsCOMPtr<nsINode> startNode(do_QueryInterface(startDOMNode));
-  PRInt32 startOffset;
+  PRInt32 startOffset = 0;
   range->GetStartOffset(&startOffset);
 
   // Get end point
   nsCOMPtr<nsIDOMNode> endDOMNode;
   range->GetEndContainer(getter_AddRefs(endDOMNode));
   nsCOMPtr<nsINode> endNode(do_QueryInterface(endDOMNode));
-  PRInt32 endOffset;
+  PRInt32 endOffset = 0;
   range->GetEndOffset(&endOffset);
 
-  PRInt16 rangeCompareResult;
-  rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range, &rangeCompareResult);
+  PRInt16 rangeCompareResult = 0;
+  nsresult rv = range->CompareBoundaryPoints(nsIDOMRange::START_TO_END, range,
+                                             &rangeCompareResult);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (rangeCompareResult < 0) {
     // Make sure start is before end, by swapping offsets
     // This occurs when the user selects backwards in the text
     startNode.swap(endNode);
     PRInt32 tempOffset = startOffset;
     startOffset = endOffset;
@@ -1893,85 +1855,97 @@ NS_IMETHODIMP nsHyperTextAccessible::Get
 /*
  * Changes the start and end offset of the specified selection.
  */
 NS_IMETHODIMP
 nsHyperTextAccessible::SetSelectionBounds(PRInt32 aSelectionNum,
                                           PRInt32 aStartOffset,
                                           PRInt32 aEndOffset)
 {
-  nsCOMPtr<nsISelection> domSel;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, getter_AddRefs(domSel));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  NS_ENSURE_STATE(frameSelection);
+
+  nsCOMPtr<nsISelection> domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_STATE(domSel);
 
   // Caret is a collapsed selection
   bool isOnlyCaret = (aStartOffset == aEndOffset);
 
-  PRInt32 rangeCount;
+  PRInt32 rangeCount = 0;
   domSel->GetRangeCount(&rangeCount);
   nsCOMPtr<nsIDOMRange> range;
   if (aSelectionNum == rangeCount) { // Add a range
     range = do_CreateInstance(kRangeCID);
     NS_ENSURE_TRUE(range, NS_ERROR_OUT_OF_MEMORY);
   }
   else if (aSelectionNum < 0 || aSelectionNum > rangeCount) {
     return NS_ERROR_INVALID_ARG;
   }
   else {
     domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
     NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
   }
 
-  PRInt32 startOffset, endOffset;
+  PRInt32 startOffset = 0, endOffset = 0;
   nsCOMPtr<nsIDOMNode> startNode, endNode;
 
-  rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset,
-                                  getter_AddRefs(startNode), &startOffset,
-                                  getter_AddRefs(endNode), &endOffset);
+  nsresult rv = HypertextOffsetsToDOMRange(aStartOffset, aEndOffset,
+                                           getter_AddRefs(startNode), &startOffset,
+                                           getter_AddRefs(endNode), &endOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = range->SetStart(startNode, startOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = isOnlyCaret ? range->Collapse(PR_TRUE) :
                      range->SetEnd(endNode, endOffset);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (aSelectionNum == rangeCount) { // Add successfully created new range
+  // If new range was created then add it, otherwise notify selection listeners
+  // that existing selection range was changed.
+  if (aSelectionNum == rangeCount)
     return domSel->AddRange(range);
-  }
+
+  domSel->RemoveRange(range);
+  domSel->AddRange(range);
   return NS_OK;
 }
 
 /*
  * Adds a selection bounded by the specified offsets.
  */
-NS_IMETHODIMP nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset)
+NS_IMETHODIMP
+nsHyperTextAccessible::AddSelection(PRInt32 aStartOffset, PRInt32 aEndOffset)
 {
-  nsCOMPtr<nsISelection> domSel;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, getter_AddRefs(domSel));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  NS_ENSURE_STATE(frameSelection);
 
-  PRInt32 rangeCount;
+  nsCOMPtr<nsISelection> domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_STATE(domSel);
+
+  PRInt32 rangeCount = 0;
   domSel->GetRangeCount(&rangeCount);
 
   return SetSelectionBounds(rangeCount, aStartOffset, aEndOffset);
 }
 
 /*
  * Removes the specified selection.
  */
-NS_IMETHODIMP nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum)
+NS_IMETHODIMP
+nsHyperTextAccessible::RemoveSelection(PRInt32 aSelectionNum)
 {
-  nsCOMPtr<nsISelection> domSel;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_NORMAL,
-                              nsnull, getter_AddRefs(domSel));
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsRefPtr<nsFrameSelection> frameSelection = FrameSelection();
+  NS_ENSURE_STATE(frameSelection);
+
+  nsCOMPtr<nsISelection> domSel =
+    frameSelection->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_STATE(domSel);
 
   PRInt32 rangeCount;
   domSel->GetRangeCount(&rangeCount);
   if (aSelectionNum < 0 || aSelectionNum >= rangeCount)
     return NS_ERROR_INVALID_ARG;
 
   nsCOMPtr<nsIDOMRange> range;
   domSel->GetRangeAt(aSelectionNum, getter_AddRefs(range));
@@ -2343,31 +2317,29 @@ nsHyperTextAccessible::DOMRangeBoundToHy
 nsresult
 nsHyperTextAccessible::GetSpellTextAttribute(nsIDOMNode *aNode,
                                              PRInt32 aNodeOffset,
                                              PRInt32 *aHTStartOffset,
                                              PRInt32 *aHTEndOffset,
                                              nsIPersistentProperties *aAttributes)
 {
   nsCOMArray<nsIDOMRange> ranges;
-  nsresult rv = GetSelections(nsISelectionController::SELECTION_SPELLCHECK,
-                              nsnull, nsnull, &ranges);
-  NS_ENSURE_SUCCESS(rv, rv);
+  GetSelectionDOMRanges(nsISelectionController::SELECTION_SPELLCHECK, &ranges);
 
   PRInt32 rangeCount = ranges.Count();
   if (!rangeCount)
     return NS_OK;
 
   for (PRInt32 index = 0; index < rangeCount; index++) {
     nsCOMPtr<nsIDOMRange> range = ranges[index];
     nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
     NS_ENSURE_STATE(nsrange);
 
     PRInt16 result;
-    rv = nsrange->ComparePoint(aNode, aNodeOffset, &result);
+    nsresult rv = nsrange->ComparePoint(aNode, aNodeOffset, &result);
     NS_ENSURE_SUCCESS(rv, rv);
     // ComparePoint checks boundary points, but we need to check that
     // text at aNodeOffset is inside the range.
     // See also bug 460690.
     if (result == 0) {
       nsCOMPtr<nsIDOMNode> end;
       rv = range->GetEndContainer(getter_AddRefs(end));
       NS_ENSURE_SUCCESS(rv, rv);
@@ -2376,36 +2348,36 @@ nsHyperTextAccessible::GetSpellTextAttri
       NS_ENSURE_SUCCESS(rv, rv);
       if (aNode == end && aNodeOffset == endOffset) {
         result = 1;
       }
     }
 
     if (result == 1) { // range is before point
       PRInt32 startHTOffset = 0;
-      rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE,
-                                          &startHTOffset);
+      nsresult rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_TRUE,
+                                                   &startHTOffset);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (startHTOffset > *aHTStartOffset)
         *aHTStartOffset = startHTOffset;
 
     } else if (result == -1) { // range is after point
       PRInt32 endHTOffset = 0;
-      rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE,
-                                          &endHTOffset);
+      nsresult rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_FALSE,
+                                                   &endHTOffset);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (endHTOffset < *aHTEndOffset)
         *aHTEndOffset = endHTOffset;
 
     } else { // point is in range
       PRInt32 startHTOffset = 0;
-      rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE,
-                                          &startHTOffset);
+      nsresult rv = DOMRangeBoundToHypertextOffset(range, PR_TRUE, PR_TRUE,
+                                                   &startHTOffset);
       NS_ENSURE_SUCCESS(rv, rv);
 
       PRInt32 endHTOffset = 0;
       rv = DOMRangeBoundToHypertextOffset(range, PR_FALSE, PR_FALSE,
                                           &endHTOffset);
       NS_ENSURE_SUCCESS(rv, rv);
 
       if (startHTOffset > *aHTStartOffset)
--- a/accessible/src/html/nsHyperTextAccessible.h
+++ b/accessible/src/html/nsHyperTextAccessible.h
@@ -346,32 +346,25 @@ protected:
                           nsIntRect *aBoundsRect = nsnull,
                           nsAccessible **aStartAcc = nsnull,
                           nsAccessible **aEndAcc = nsnull);
 
   nsIntRect GetBoundsForString(nsIFrame *aFrame, PRUint32 aStartRenderedOffset, PRUint32 aEndRenderedOffset);
 
   // Selection helpers
 
-    /**
-   * Get the relevant selection interfaces and ranges for the current hyper
-   * text.
-   *
-   * @param aType    [in] the selection type
-   * @param aSelCon  [out, optional] the selection controller for the current
-   *                 hyper text
-   * @param aDomSel  [out, optional] the selection interface for the current
-   *                 hyper text
-   * @param aRanges  [out, optional] the selected ranges within the current
-   *                 subtree
+  /**
+   * Return frame selection object for the accessible.
    */
-  nsresult GetSelections(PRInt16 aType,
-                         nsISelectionController **aSelCon,
-                         nsISelection **aDomSel = nsnull,
-                         nsCOMArray<nsIDOMRange>* aRanges = nsnull);
+  virtual already_AddRefed<nsFrameSelection> FrameSelection();
+
+  /**
+   * Return selection ranges within the accessible subtree.
+   */
+  void GetSelectionDOMRanges(PRInt16 aType, nsCOMArray<nsIDOMRange>* aRanges);
 
   nsresult SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos);
 
   /**
    * Provide the line number for the caret, relative to the
    * current DOM node.
    * @return 1-based index for the line number with the caret
    */
--- a/accessible/src/xul/nsXULFormControlAccessible.cpp
+++ b/accessible/src/xul/nsXULFormControlAccessible.cpp
@@ -878,16 +878,27 @@ nsXULTextFieldAccessible::CacheChildren(
 
   nsAccTreeWalker walker(mWeakShell, inputContent, PR_FALSE);
 
   nsAccessible* child = nsnull;
   while ((child = walker.NextChild()) && AppendChild(child));
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// nsXULTextFieldAccessible: nsHyperTextAccessible protected
+
+already_AddRefed<nsFrameSelection>
+nsXULTextFieldAccessible::FrameSelection()
+{
+  nsCOMPtr<nsIContent> inputContent(GetInputField());
+  nsIFrame* frame = inputContent->GetPrimaryFrame();
+  return frame->GetFrameSelection();
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // nsXULTextFieldAccessible protected
 
 already_AddRefed<nsIContent>
 nsXULTextFieldAccessible::GetInputField() const
 {
   nsCOMPtr<nsIDOMNode> inputFieldDOMNode;
   nsCOMPtr<nsIDOMXULTextBoxElement> textBox = do_QueryInterface(mContent);
   if (textBox) {
--- a/accessible/src/xul/nsXULFormControlAccessible.h
+++ b/accessible/src/xul/nsXULFormControlAccessible.h
@@ -271,15 +271,18 @@ public:
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
 protected:
   // nsAccessible
   virtual void CacheChildren();
 
+  // nsHyperTextAccessible
+  virtual already_AddRefed<nsFrameSelection> FrameSelection();
+
   // nsXULTextFieldAccessible
   already_AddRefed<nsIContent> GetInputField() const;
 };
 
 
 #endif  
 
--- a/accessible/tests/mochitest/Makefile.in
+++ b/accessible/tests/mochitest/Makefile.in
@@ -51,16 +51,17 @@ DIRS	= \
   hyperlink \
   hypertext \
   name \
   relations \
   selectable \
   states \
   table \
   text \
+  textselection \
   tree \
   treeupdate \
   value \
   $(null)
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
--- a/accessible/tests/mochitest/events.js
+++ b/accessible/tests/mochitest/events.js
@@ -18,16 +18,17 @@ const EVENT_SCROLLING_START = nsIAccessi
 const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
 const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
 const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
 const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;
 const EVENT_TEXT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_TEXT_ATTRIBUTE_CHANGED;
 const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED;
 const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED;
 const EVENT_TEXT_REMOVED = nsIAccessibleEvent.EVENT_TEXT_REMOVED;
+const EVENT_TEXT_SELECTION_CHANGED = nsIAccessibleEvent.EVENT_TEXT_SELECTION_CHANGED;
 const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
 
 ////////////////////////////////////////////////////////////////////////////////
 // General
 
 /**
  * Set up this variable to dump events into DOM.
  */
new file mode 100755
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/Makefile.in
@@ -0,0 +1,53 @@
+#
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is mozilla.org code.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Alexander Surkov <surkov.alexander@gmail.com> (original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either of the GNU General Public License Version 2 or later (the "GPL"),
+# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = accessible/textselection
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_TEST_FILES = \
+		test_general.html \
+		$(NULL)
+
+libs:: $(_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
new file mode 100755
--- /dev/null
+++ b/accessible/tests/mochitest/textselection/test_general.html
@@ -0,0 +1,170 @@
+<html>
+
+<head>
+  <title>Text selection testing</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="../events.js"></script>
+
+  <script type="application/javascript">
+    /**
+     * Invokers
+     */
+    function addSelection(aID, aStartOffset, aEndOffset)
+    {
+      this.hyperTextNode = getNode(aID);
+      this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+      ];
+
+      this.invoke = function addSelection_invoke()
+      {
+        this.hyperText.addSelection(aStartOffset, aEndOffset);
+      }
+
+      this.finalCheck = function addSelection_finalCheck()
+      {
+        is(this.hyperText.selectionCount, 1,
+           "addSelection: Wrong selection count for " + aID);
+        var startOffset = {}, endOffset = {};
+        this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+
+        is(startOffset.value, aStartOffset,
+           "addSelection: Wrong start offset for " + aID);
+        is(endOffset.value, aEndOffset,
+           "addSelection: Wrong end offset for " + aID);
+      }
+
+      this.getID = function addSelection_getID()
+      {
+        return "nsIAccessibleText::addSelection test for " + aID;
+      }
+    }
+
+    function changeSelection(aID, aStartOffset, aEndOffset)
+    {
+      this.hyperTextNode = getNode(aID);
+      this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+      ];
+
+      this.invoke = function changeSelection_invoke()
+      {
+        this.hyperText.setSelectionBounds(0, aStartOffset, aEndOffset);
+      }
+
+      this.finalCheck = function changeSelection_finalCheck()
+      {
+        is(this.hyperText.selectionCount, 1,
+           "setSelectionBounds: Wrong selection count for " + aID);
+        var startOffset = {}, endOffset = {};
+        this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+
+        is(startOffset.value, aStartOffset,
+           "setSelectionBounds: Wrong start offset for " + aID);
+        is(endOffset.value, aEndOffset,
+           "setSelectionBounds: Wrong end offset for " + aID);
+      }
+
+      this.getID = function changeSelection_getID()
+      {
+        return "nsIAccessibleText::setSelectionBounds test for " + aID;
+      }
+    }
+
+    function removeSelection(aID)
+    {
+      this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+
+      this.eventSeq = [
+        new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
+      ];
+
+      this.invoke = function removeSelection_invoke()
+      {
+        this.hyperText.removeSelection(0);
+      }
+
+      this.finalCheck = function removeSelection_finalCheck()
+      {
+        is(this.hyperText.selectionCount, 0,
+           "removeSelection: Wrong selection count for " + aID);
+      }
+
+      this.getID = function removeSelection_getID()
+      {
+        return "nsIAccessibleText::removeSelection test for " + aID;
+      }
+    }
+
+    function onfocusEventSeq(aID)
+    {
+      var caretMovedChecker =
+        new invokerChecker(EVENT_TEXT_CARET_MOVED, aID);
+      var selChangedChecker =
+        new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID);
+      selChangedChecker.unexpected = true;
+
+      return [ caretMovedChecker, selChangedChecker ];
+    }
+
+    /**
+     * Do tests
+     */
+
+    //gA11yEventDumpToConsole = true; // debug stuff
+
+    var gQueue = null;
+    function doTests()
+    {
+      gQueue = new eventQueue();
+
+      gQueue.push(new addSelection("paragraph", 1, 3));
+      gQueue.push(new changeSelection("paragraph", 2, 4));
+      //gQueue.push(new removeSelection("paragraph"));
+      todo(false, "removeSelection doesn't fire text selection changed events, see bug bug 688124.");
+
+      gQueue.push(new synthFocus("textbox", onfocusEventSeq("textbox")));
+      gQueue.push(new changeSelection("textbox", 1, 3));
+
+      gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
+      gQueue.push(new changeSelection("textarea", 1, 3));
+
+      gQueue.invoke(); // Will call SimpleTest.finish();
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    addA11yLoadEvent(doTests);
+  </script>
+</head>
+
+<body>
+
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=688126"
+     title="nsIAccessibleText::setSelectionBounds doesn't fire text selection changed events in some cases">
+    Mozilla Bug 688126
+  </a>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test">
+  </pre>
+
+  <p id="paragraph">hello</p>
+  <input id="textbox" value="hello"/>
+  <textarea id="textarea">hello</textarea>
+
+</body>
+</html>