Bug 757771 part 4 - Create new nsContentUtils::GetSelectionInTextControl method; r=ehsan
authorAryeh Gregor <ayg@aryeh.name>
Thu, 07 Jun 2012 18:00:58 +0300
changeset 101364 8f5c42f0dcb33d79ec536046105be2fa5935872f
parent 101363 272fa362ebee695391eca36f7d7b4d3e2febd696
child 101365 3e12ea5cc53553f973aabf1fe13dd13786090c52
push id191
push userlsblakk@mozilla.com
push dateFri, 05 Oct 2012 17:12:53 +0000
treeherdermozilla-release@ddb22ac6c03b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan
bugs757771
milestone16.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 757771 part 4 - Create new nsContentUtils::GetSelectionInTextControl method; r=ehsan
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
editor/libeditor/html/nsHTMLEditor.h
editor/libeditor/text/nsPlaintextEditor.cpp
editor/libeditor/text/nsPlaintextEditor.h
editor/libeditor/text/nsTextEditRules.cpp
editor/libeditor/text/nsTextEditRules.h
editor/libeditor/text/tests/Makefile.in
editor/libeditor/text/tests/test_bug757771.html
layout/forms/nsTextControlFrame.cpp
layout/forms/nsTextControlFrame.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -112,16 +112,17 @@ class nsIMIMEHeaderParam;
 class nsIObserver;
 class nsPresContext;
 class nsIChannel;
 class nsAutoScriptBlockerSuppressNodeRemoved;
 struct nsIntMargin;
 class nsPIDOMWindow;
 class nsIDocumentLoaderFactory;
 class nsIDOMHTMLInputElement;
+class nsTypedSelection;
 
 namespace mozilla {
 
 namespace layers {
   class LayerManager;
 } // namespace layers
 
 namespace dom {
@@ -2022,16 +2023,32 @@ public:
    * @param aPrefURL Preference name
    * @param aAllowed [out] outparam on whether or not window is allowed
    *                       to access pref
    *
    * @return NS_OK on successful preference lookup, error code otherwise
    */
   static nsresult IsOnPrefWhitelist(nsPIDOMWindow* aWindow,
                                     const char* aPrefURL, bool *aAllowed);
+
+  /**
+   * Takes a selection, and a text control element (<input> or <textarea>), and
+   * returns the offsets in the text content corresponding to the selection.
+   * The selection's anchor and focus must both be in the root node passed or a
+   * descendant.
+   *
+   * @param aSelection      Selection to check
+   * @param aRoot           Root <input> or <textarea> element
+   * @param aOutStartOffset Output start offset
+   * @param aOutEndOffset   Output end offset
+   */
+  static void GetSelectionInTextControl(nsTypedSelection* aSelection,
+                                        Element* aRoot,
+                                        PRInt32& aOutStartOffset,
+                                        PRInt32& aOutEndOffset);
   
 private:
   static bool InitializeEventTable();
 
   static nsresult EnsureStringBundle(PropertiesFile aFile);
 
   static bool CanCallerAccess(nsIPrincipal* aSubjectPrincipal,
                                 nsIPrincipal* aPrincipal);
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -138,16 +138,17 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_
 #include "nsTextEditorState.h"
 #include "nsIPluginHost.h"
 #include "nsICategoryManager.h"
 #include "nsIViewManager.h"
 #include "nsEventStateManager.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsParserConstants.h"
 #include "nsIWebNavigation.h"
+#include "nsTypedSelection.h"
 
 #ifdef IBMBIDI
 #include "nsIBidiKeyboard.h"
 #endif
 #include "nsCycleCollectionParticipant.h"
 
 // for ReportToConsole
 #include "nsIStringBundle.h"
@@ -6858,8 +6859,62 @@ nsContentUtils::IsOnPrefWhitelist(nsPIDO
           break;
         }
       }
     }
   }
   *aAllowed = allowed;
   return NS_OK;
 }
+
+// static
+void
+nsContentUtils::GetSelectionInTextControl(nsTypedSelection* aSelection,
+                                          Element* aRoot,
+                                          PRInt32& aOutStartOffset,
+                                          PRInt32& aOutEndOffset)
+{
+  MOZ_ASSERT(aSelection && aRoot);
+
+  if (!aSelection->GetRangeCount()) {
+    // Nothing selected
+    aOutStartOffset = aOutEndOffset = 0;
+    return;
+  }
+
+  nsCOMPtr<nsINode> anchorNode = aSelection->GetAnchorNode();
+  PRInt32 anchorOffset = aSelection->GetAnchorOffset();
+  nsCOMPtr<nsINode> focusNode = aSelection->GetFocusNode();
+  PRInt32 focusOffset = aSelection->GetFocusOffset();
+
+  // We have at most two children, consisting of an optional text node followed
+  // by an optional <br>.
+  NS_ASSERTION(aRoot->GetChildCount() <= 2, "Unexpected children");
+  nsCOMPtr<nsIContent> firstChild = aRoot->GetFirstChild();
+#ifdef DEBUG
+  nsCOMPtr<nsIContent> lastChild = aRoot->GetLastChild();
+  NS_ASSERTION(anchorNode == aRoot || anchorNode == firstChild ||
+               anchorNode == lastChild, "Unexpected anchorNode");
+  NS_ASSERTION(focusNode == aRoot || focusNode == firstChild ||
+               focusNode == lastChild, "Unexpected focusNode");
+#endif
+  if (!firstChild || !firstChild->IsNodeOfType(nsINode::eTEXT)) {
+    // No text node, so everything is 0
+    anchorOffset = focusOffset = 0;
+  } else {
+    // First child is text.  If the anchor/focus is already in the text node,
+    // or the start of the root node, no change needed.  If it's in the root
+    // node but not the start, or in the trailing <br>, we need to set the
+    // offset to the end.
+    if ((anchorNode == aRoot && anchorOffset != 0) ||
+        (anchorNode != aRoot && anchorNode != firstChild)) {
+      anchorOffset = firstChild->Length();
+    }
+    if ((focusNode == aRoot && focusOffset != 0) ||
+        (focusNode != aRoot && focusNode != firstChild)) {
+      focusOffset = firstChild->Length();
+    }
+  }
+
+  // Make sure aOutStartOffset <= aOutEndOffset.
+  aOutStartOffset = NS_MIN(anchorOffset, focusOffset);
+  aOutEndOffset = NS_MAX(anchorOffset, focusOffset);
+}
--- a/editor/libeditor/html/nsHTMLEditor.h
+++ b/editor/libeditor/html/nsHTMLEditor.h
@@ -321,23 +321,16 @@ public:
   /* ------------ Utility Routines, not part of public API -------------- */
   NS_IMETHOD TypedText(const nsAString& aString, ETypingAction aAction);
   nsresult InsertNodeAtPoint( nsIDOMNode *aNode, 
                               nsCOMPtr<nsIDOMNode> *ioParent, 
                               PRInt32 *ioOffset, 
                               bool aNoEmptyNodes);
   virtual already_AddRefed<nsIDOMNode> FindUserSelectAllNode(nsIDOMNode* aNode);
 
-  /** returns the absolute position of the end points of aSelection
-    * in the document as a text stream.
-    */
-  nsresult GetTextSelectionOffsets(nsISelection *aSelection,
-                                   PRInt32 &aStartOffset, 
-                                   PRInt32 &aEndOffset);
-
   // Use this to assure that selection is set after attribute nodes when 
   //  trying to collapse selection at begining of a block node
   //  e.g., when setting at beginning of a table cell
   // This will stop at a table, however, since we don't want to
   //  "drill down" into nested tables.
   // aSelection is optional -- if null, we get current seletion
   nsresult CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode);
 
--- a/editor/libeditor/text/nsPlaintextEditor.cpp
+++ b/editor/libeditor/text/nsPlaintextEditor.cpp
@@ -529,104 +529,16 @@ nsPlaintextEditor::InsertBR(nsCOMPtr<nsI
   res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
   NS_ENSURE_SUCCESS(res, res);
   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
   selPriv->SetInterlinePosition(true);
   return selection->Collapse(selNode, selOffset+1);
 }
 
 nsresult
-nsPlaintextEditor::GetTextSelectionOffsets(nsISelection *aSelection,
-                                           PRUint32 &aOutStartOffset, 
-                                           PRUint32 &aOutEndOffset)
-{
-  NS_ASSERTION(aSelection, "null selection");
-
-  nsresult rv;
-  nsCOMPtr<nsIDOMNode> startNode, endNode;
-  PRInt32 startNodeOffset, endNodeOffset;
-  aSelection->GetAnchorNode(getter_AddRefs(startNode));
-  aSelection->GetAnchorOffset(&startNodeOffset);
-  aSelection->GetFocusNode(getter_AddRefs(endNode));
-  aSelection->GetFocusOffset(&endNodeOffset);
-
-  dom::Element *rootElement = GetRoot();
-  nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(rootElement);
-  NS_ENSURE_TRUE(rootNode, NS_ERROR_NULL_POINTER);
-
-  PRInt32 startOffset = -1;
-  PRInt32 endOffset = -1;
-
-  nsCOMPtr<nsIContentIterator> iter =
-    do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-    
-#ifdef NS_DEBUG
-  PRInt32 nodeCount = 0; // only needed for the assertions below
-#endif
-  PRUint32 totalLength = 0;
-  iter->Init(rootElement);
-  for (; !iter->IsDone() && (startOffset == -1 || endOffset == -1); iter->Next()) {
-    nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(iter->GetCurrentNode());
-    nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(currentNode);
-    if (textNode) {
-      // Note that sometimes we have an empty #text-node as start/endNode,
-      // which we regard as not editable because the frame width == 0,
-      // see nsEditor::IsEditable().
-      bool editable = IsEditable(currentNode);
-      if (currentNode == startNode) {
-        startOffset = totalLength + (editable ? startNodeOffset : 0);
-      }
-      if (currentNode == endNode) {
-        endOffset = totalLength + (editable ? endNodeOffset : 0);
-      }
-      if (editable) {
-        PRUint32 length;
-        textNode->GetLength(&length);
-        totalLength += length;
-      }
-    }
-#ifdef NS_DEBUG
-    // The post content iterator might return the parent node (which is the
-    // editor's root node) as the last item.  Don't count the root node itself
-    // as one of its children!
-    if (!SameCOMIdentity(currentNode, rootNode)) {
-      ++nodeCount;
-    }
-#endif
-  }
-
-  if (endOffset == -1) {
-    NS_ASSERTION(endNode == rootNode, "failed to find the end node");
-    NS_ASSERTION(IsPasswordEditor() ||
-                 (endNodeOffset == nodeCount-1 || endNodeOffset == 0),
-                 "invalid end node offset");
-    endOffset = endNodeOffset == 0 ? 0 : totalLength;
-  }
-  if (startOffset == -1) {
-    NS_ASSERTION(startNode == rootNode, "failed to find the start node");
-    NS_ASSERTION(startNodeOffset == nodeCount-1 || startNodeOffset == 0,
-                 "invalid start node offset");
-    startOffset = startNodeOffset == 0 ? 0 : totalLength;
-  }
-
-  // Make sure aOutStartOffset <= aOutEndOffset.
-  if (startOffset <= endOffset) {
-    aOutStartOffset = startOffset;
-    aOutEndOffset = endOffset;
-  }
-  else {
-    aOutStartOffset = endOffset;
-    aOutEndOffset = startOffset;
-  }
-
-  return NS_OK;
-}
-
-nsresult
 nsPlaintextEditor::ExtendSelectionForDelete(nsISelection *aSelection,
                                             nsIEditor::EDirection *aAction)
 {
   nsresult result = NS_OK;
 
   bool bCollapsed = aSelection->Collapsed();
 
   if (*aAction == eNextWord || *aAction == ePreviousWord
--- a/editor/libeditor/text/nsPlaintextEditor.h
+++ b/editor/libeditor/text/nsPlaintextEditor.h
@@ -116,24 +116,16 @@ public:
   virtual nsresult UpdateIMEComposition(const nsAString &aCompositionString,
                                         nsIPrivateTextRangeList *aTextRange);
 
   virtual already_AddRefed<nsIContent> GetInputEventTargetContent();
 
   /* ------------ Utility Routines, not part of public API -------------- */
   NS_IMETHOD TypedText(const nsAString& aString, ETypingAction aAction);
 
-  /** Returns the absolute position of the end points of aSelection
-   * in the document as a text stream.
-   * Invariant: aStartOffset <= aEndOffset.
-   */
-  nsresult GetTextSelectionOffsets(nsISelection *aSelection,
-                                   PRUint32 &aStartOffset, 
-                                   PRUint32 &aEndOffset);
-
   nsresult InsertTextAt(const nsAString &aStringToInsert,
                         nsIDOMNode *aDestinationNode,
                         PRInt32 aDestOffset,
                         bool aDoDeleteSelection);
 
   virtual nsresult InsertFromDataTransfer(nsIDOMDataTransfer *aDataTransfer,
                                           PRInt32 aIndex,
                                           nsIDOMDocument *aSourceDoc,
--- a/editor/libeditor/text/nsTextEditRules.cpp
+++ b/editor/libeditor/text/nsTextEditRules.cpp
@@ -24,16 +24,17 @@
 #include "nsIContentIterator.h"
 #include "nsEditorUtils.h"
 #include "EditTxn.h"
 #include "nsEditProperty.h"
 #include "nsUnicharUtils.h"
 #include "DeleteTextTxn.h"
 #include "nsNodeIterator.h"
 #include "nsIDOMNodeFilter.h"
+#include "nsContentUtils.h"
 
 // for IBMBIDI
 #include "nsFrameSelection.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/dom/Element.h"
 
@@ -573,25 +574,23 @@ nsTextEditRules::WillInsertText(nsEditor
   // If we're exceeding the maxlength when composing IME, we need to clean up
   // the composing text, so we shouldn't return early.
   if (truncated && outString->IsEmpty() &&
       aAction != nsEditor::kOpInsertIMEText) {
     *aCancel = true;
     return NS_OK;
   }
   
-  PRUint32 start = 0;
-  PRUint32 end = 0;  
+  PRInt32 start = 0;
+  PRInt32 end = 0;
 
   // handle password field docs
-  if (IsPasswordEditor())
-  {
-    res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
-    NS_ASSERTION((NS_SUCCEEDED(res)), "getTextSelectionOffsets failed!");
-    NS_ENSURE_SUCCESS(res, res);
+  if (IsPasswordEditor()) {
+    nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
+                                              start, end);
   }
 
   // if the selection isn't collapsed, delete it.
   bool bCollapsed;
   res = aSelection->GetIsCollapsed(&bCollapsed);
   NS_ENSURE_SUCCESS(res, res);
   if (!bCollapsed)
   {
@@ -775,19 +774,19 @@ nsTextEditRules::WillDeleteSelection(nsT
   nsresult res = NS_OK;
 
   if (IsPasswordEditor())
   {
     res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
     NS_ENSURE_SUCCESS(res, res);
 
     // manage the password buffer
-    PRUint32 start, end;
-    mEditor->GetTextSelectionOffsets(aSelection, start, end);
-    NS_ENSURE_SUCCESS(res, res);
+    PRInt32 start, end;
+    nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
+                                              start, end);
 
     if (LookAndFeel::GetEchoPassword()) {
       HideLastPWInput();
       mLastStart = start;
       mLastLength = 0;
       if (mTimer)
       {
         mTimer->Cancel();
@@ -1166,19 +1165,19 @@ nsTextEditRules::TruncateInsertionIfNeed
     //   which will be replaced by new one.
     // If (resultingDocLength) is at or over max, cancel the insert
     // If (resultingDocLength) + (length of input) > max, 
     //    set aOutString to subset of inString so length = max
     PRInt32 docLength;
     res = mEditor->GetTextLength(&docLength);
     if (NS_FAILED(res)) { return res; }
 
-    PRUint32 start, end;
-    res = mEditor->GetTextSelectionOffsets(aSelection, start, end);
-    if (NS_FAILED(res)) { return res; }
+    PRInt32 start, end;
+    nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
+                                              start, end);
 
     PRInt32 oldCompStrLength = mEditor->GetIMEBufferLength();
 
     const PRInt32 selectionLength = end - start;
     const PRInt32 resultingDocLength = docLength - selectionLength - oldCompStrLength;
     if (resultingDocLength >= aMaxLength)
     {
       aOutString->Truncate();
@@ -1203,17 +1202,17 @@ nsTextEditRules::TruncateInsertionIfNeed
 
 void
 nsTextEditRules::ResetIMETextPWBuf()
 {
   mPasswordIMEText.Truncate();
 }
 
 void
-nsTextEditRules::RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString)
+nsTextEditRules::RemoveIMETextFromPWBuf(PRInt32 &aStart, nsAString *aIMEString)
 {
   MOZ_ASSERT(aIMEString);
 
   // initialize PasswordIME
   if (mPasswordIMEText.IsEmpty()) {
     mPasswordIMEIndex = aStart;
   }
   else {
@@ -1236,22 +1235,21 @@ nsresult nsTextEditRules::HideLastPWInpu
   if (!mLastLength) {
     // Special case, we're trying to replace a range that no longer exists
     return NS_OK;
   }
 
   nsAutoString hiddenText;
   FillBufWithPWChars(&hiddenText, mLastLength);
 
-  nsCOMPtr<nsISelection> selection;
-  PRUint32 start, end;
-  nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
-  NS_ENSURE_SUCCESS(res, res);
-  res = mEditor->GetTextSelectionOffsets(selection, start, end);
-  NS_ENSURE_SUCCESS(res, res);
+  nsRefPtr<nsTypedSelection> selection = mEditor->GetTypedSelection();
+  NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
+  PRInt32 start, end;
+  nsContentUtils::GetSelectionInTextControl(selection, mEditor->GetRoot(),
+                                            start, end);
 
   nsCOMPtr<nsIDOMNode> selNode = GetTextNode(selection, mEditor);
   NS_ENSURE_TRUE(selNode, NS_OK);
   
   nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(selNode));
   NS_ENSURE_TRUE(nodeAsText, NS_OK);
   
   nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText);
--- a/editor/libeditor/text/nsTextEditRules.h
+++ b/editor/libeditor/text/nsTextEditRules.h
@@ -158,17 +158,17 @@ protected:
       over aMaxLength */
   nsresult TruncateInsertionIfNeeded(nsTypedSelection*         aSelection,
                                      const nsAString          *aInString,
                                      nsAString                *aOutString,
                                      PRInt32                   aMaxLength,
                                      bool                     *aTruncated);
 
   /** Remove IME composition text from password buffer */
-  void RemoveIMETextFromPWBuf(PRUint32 &aStart, nsAString *aIMEString);
+  void RemoveIMETextFromPWBuf(PRInt32 &aStart, nsAString *aIMEString);
 
   nsresult CreateMozBR(nsIDOMNode* inParent, PRInt32 inOffset,
                        nsIDOMNode** outBRNode = nsnull);
 
   nsresult CheckBidiLevelForDeletion(nsISelection         *aSelection,
                                      nsIDOMNode           *aSelNode, 
                                      PRInt32               aSelOffset, 
                                      nsIEditor::EDirection aAction,
--- a/editor/libeditor/text/tests/Makefile.in
+++ b/editor/libeditor/text/tests/Makefile.in
@@ -29,16 +29,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug625452.html \
 		test_bug629172.html \
 		test_bug638596.html \
 		test_bug641466.html \
 		test_bug645914.html \
 		test_bug681229.html \
 		test_bug692520.html \
 		test_bug740784.html \
+		test_bug757771.html \
 		test_dom_input_event_on_texteditor.html \
 		$(NULL)
 
 # disables the key handling test on gtk2 because gtk2 overrides some key events
 # on our editor, and the combinations depend on the system.
 ifneq ($(MOZ_WIDGET_TOOLKIT),gtk2)
 _TEST_FILES += \
 		test_texteditor_keyevent_handling.html \
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/text/tests/test_bug757771.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=757771
+-->
+<title>Test for Bug 757771</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=757771">Mozilla Bug 757771</a>
+<input value=foo maxlength=4>
+<input type=password value=password>
+<script>
+/** Test for Bug 757771 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  var textInput = document.querySelector("input");
+  textInput.focus();
+  textInput.select();
+  sendString("abcde");
+
+  var passwordInput = document.querySelector("input + input");
+  passwordInput.focus();
+  passwordInput.select();
+  sendString("hunter2");
+
+  ok(true, "No real tests, just crashes/asserts");
+
+  SimpleTest.finish();
+});
+</script>
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -812,16 +812,25 @@ nsTextControlFrame::ScrollSelectionIntoV
     return selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
                                            nsISelectionController::SELECTION_FOCUS_REGION,
                                            nsISelectionController::SCROLL_FIRST_ANCESTOR_ONLY);
   }
 
   return NS_ERROR_FAILURE;
 }
 
+mozilla::dom::Element*
+nsTextControlFrame::GetRootNodeAndInitializeEditor()
+{
+  nsCOMPtr<nsIDOMElement> root;
+  GetRootNodeAndInitializeEditor(getter_AddRefs(root));
+  nsCOMPtr<mozilla::dom::Element> rootElem = do_QueryInterface(root);
+  return rootElem;
+}
+
 nsresult
 nsTextControlFrame::GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement)
 {
   NS_ENSURE_ARG_POINTER(aRootElement);
 
   nsCOMPtr<nsIEditor> editor;
   GetEditor(getter_AddRefs(editor));
   if (!editor)
@@ -960,68 +969,16 @@ nsTextControlFrame::SetSelectionEnd(PRIn
   }
 
   selEnd = aSelectionEnd;
   
   return SetSelectionEndPoints(selStart, selEnd);
 }
 
 nsresult
-nsTextControlFrame::DOMPointToOffset(nsIDOMNode* aNode,
-                                     PRInt32 aNodeOffset,
-                                     PRInt32* aResult)
-{
-  NS_ENSURE_ARG_POINTER(aNode && aResult);
-
-  *aResult = 0;
-
-  nsCOMPtr<nsIDOMElement> rootElement;
-  nsresult rv = GetRootNodeAndInitializeEditor(getter_AddRefs(rootElement));
-  nsCOMPtr<nsIDOMNode> rootNode(do_QueryInterface(rootElement));
-
-  NS_ENSURE_TRUE(rootNode, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIDOMNodeList> nodeList;
-
-  rv = rootNode->GetChildNodes(getter_AddRefs(nodeList));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(nodeList, NS_ERROR_FAILURE);
-
-  PRUint32 length = 0;
-  rv = nodeList->GetLength(&length);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (!length || aNodeOffset < 0)
-    return NS_OK;
-
-  NS_ASSERTION(length <= 2, "We should have one text node and one mozBR at most");
-
-  nsCOMPtr<nsIDOMNode> firstNode;
-  rv = nodeList->Item(0, getter_AddRefs(firstNode));
-  NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(firstNode);
-
-  nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(aNode);
-  if (nodeAsText || (aNode == rootNode && aNodeOffset == 0)) {
-    // Selection is somewhere inside the text node; the offset is aNodeOffset
-    *aResult = aNodeOffset;
-  } else {
-    // Selection is on the mozBR node, so offset should be set to the length
-    // of the text node.
-    if (textNode) {
-      rv = textNode->GetLength(&length);
-      NS_ENSURE_SUCCESS(rv, rv);
-      *aResult = PRInt32(length);
-    }
-  }
-
-  return NS_OK;
-}
-
-nsresult
 nsTextControlFrame::OffsetToDOMPoint(PRInt32 aOffset,
                                      nsIDOMNode** aResult,
                                      PRInt32* aPosition)
 {
   NS_ENSURE_ARG_POINTER(aResult && aPosition);
 
   *aResult = nsnull;
   *aPosition = 0;
@@ -1097,77 +1054,45 @@ nsTextControlFrame::GetSelectionRange(PR
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
   NS_ASSERTION(txtCtrl, "Content not a text control element");
   nsISelectionController* selCon = txtCtrl->GetSelectionController();
   NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
   nsCOMPtr<nsISelection> selection;
   rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));  
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
-
-  PRInt32 numRanges = 0;
-  selection->GetRangeCount(&numRanges);
-
-  if (numRanges < 1)
-    return NS_OK;
-
-  // We only operate on the first range in the selection!
+  nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
+  NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
+  nsRefPtr<nsFrameSelection> frameSel;
+  rv = selPriv->GetFrameSelection(getter_AddRefs(frameSel));
+  NS_ENSURE_SUCCESS(rv, rv);
+  NS_ENSURE_TRUE(frameSel, NS_ERROR_FAILURE);
+  nsRefPtr<nsTypedSelection> typedSel =
+    frameSel->GetSelection(nsISelectionController::SELECTION_NORMAL);
+  NS_ENSURE_TRUE(typedSel, NS_ERROR_FAILURE);
 
   if (aDirection) {
-    nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
-    if (selPriv) {
-      nsDirection direction = selPriv->GetSelectionDirection();
-      if (direction == eDirNext) {
-        *aDirection = eForward;
-      } else if (direction == eDirPrevious) {
-        *aDirection = eBackward;
-      } else {
-        NS_NOTREACHED("Invalid nsDirection enum value");
-      }
+    nsDirection direction = typedSel->GetSelectionDirection();
+    if (direction == eDirNext) {
+      *aDirection = eForward;
+    } else if (direction == eDirPrevious) {
+      *aDirection = eBackward;
+    } else {
+      NS_NOTREACHED("Invalid nsDirection enum value");
     }
   }
 
   if (!aSelectionStart || !aSelectionEnd) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDOMRange> firstRange;
-  rv = selection->GetRangeAt(0, getter_AddRefs(firstRange));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(firstRange, NS_ERROR_FAILURE);
-
-  nsCOMPtr<nsIDOMNode> startNode, endNode;
-  PRInt32 startOffset = 0, endOffset = 0;
-
-  // Get the start point of the range.
-
-  rv = firstRange->GetStartContainer(getter_AddRefs(startNode));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
-
-  rv = firstRange->GetStartOffset(&startOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsContentUtils::GetSelectionInTextControl(typedSel,
+    GetRootNodeAndInitializeEditor(), *aSelectionStart, *aSelectionEnd);
 
-  // Get the end point of the range.
-
-  rv = firstRange->GetEndContainer(getter_AddRefs(endNode));
-  NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(endNode, NS_ERROR_FAILURE);
-
-  rv = firstRange->GetEndOffset(&endOffset);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Convert the start point to a selection offset.
-
-  rv = DOMPointToOffset(startNode, startOffset, aSelectionStart);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // Convert the end point to a selection offset.
-
-  return DOMPointToOffset(endNode, endOffset, aSelectionEnd);
+  return NS_OK;
 }
 
 /////END INTERFACE IMPLEMENTATIONS
 
 ////NSIFRAME
 NS_IMETHODIMP
 nsTextControlFrame::AttributeChanged(PRInt32         aNameSpaceID,
                                      nsIAtom*        aAttribute,
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -21,16 +21,21 @@
 
 class nsISelectionController;
 class nsIDOMCharacterData;
 #ifdef ACCESSIBILITY
 class nsIAccessible;
 #endif
 class EditorInitializerEntryTracker;
 class nsTextEditorState;
+namespace mozilla {
+namespace dom {
+class Element;
+}
+}
 
 class nsTextControlFrame : public nsStackFrame,
                            public nsIAnonymousContentCreator,
                            public nsITextControlFrame,
                            public nsIStatefulFrame
 {
 public:
   NS_DECL_FRAMEARENA_HELPERS
@@ -281,17 +286,16 @@ protected:
     void Revoke() {
       mFrame = nsnull;
     }
 
   private:
     nsTextControlFrame* mFrame;
   };
 
-  nsresult DOMPointToOffset(nsIDOMNode* aNode, PRInt32 aNodeOffset, PRInt32 *aResult);
   nsresult OffsetToDOMPoint(PRInt32 aOffset, nsIDOMNode** aResult, PRInt32* aPosition);
 
   /**
    * Find out whether this control is scrollable (i.e. if it is not a single
    * line text control)
    * @return whether this control is scrollable
    */
   bool IsScrollable() const;
@@ -342,16 +346,17 @@ private:
                                 SelectionDirection aDirection = eNone);
   nsresult SelectAllOrCollapseToEndOfText(bool aSelect);
   nsresult SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd,
                                  SelectionDirection aDirection = eNone);
 
   /**
    * Return the root DOM element, and implicitly initialize the editor if needed.
    */
+  mozilla::dom::Element* GetRootNodeAndInitializeEditor();
   nsresult GetRootNodeAndInitializeEditor(nsIDOMElement **aRootElement);
 
   void FinishedInitializer() {
     Properties().Delete(TextControlInitializer());
   }
 
 private:
   // these packed bools could instead use the high order bits on mState, saving 4 bytes