Bug 674558 - Implement the HTML5 selectionDirection property for text controls (input and textarea); r=bzbarsky
authorEhsan Akhgari <ehsan@mozilla.com>
Thu, 28 Jul 2011 13:51:22 -0400
changeset 73510 e0c9fca2f500
parent 73509 f0ba9194d7ba
child 73511 5a0b3bee1f8a
push id20884
push usermak77@bonardo.net
push date2011-07-29 09:49 +0000
treeherdermozilla-central@f5f1e3822540 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbzbarsky
bugs674558
milestone8.0a1
Bug 674558 - Implement the HTML5 selectionDirection property for text controls (input and textarea); r=bzbarsky
content/base/public/nsISelectionPrivate.idl
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLTextAreaElement.cpp
content/html/content/test/Makefile.in
content/html/content/test/test_bug674558.html
dom/interfaces/html/nsIDOMHTMLInputElement.idl
dom/interfaces/html/nsIDOMHTMLTextAreaElement.idl
js/src/xpconnect/src/dom_quickstubs.qsconf
layout/forms/nsITextControlFrame.h
layout/forms/nsTextControlFrame.cpp
layout/forms/nsTextControlFrame.h
layout/generic/nsSelection.cpp
toolkit/components/satchel/nsFormFillController.cpp
--- a/content/base/public/nsISelectionPrivate.idl
+++ b/content/base/public/nsISelectionPrivate.idl
@@ -44,23 +44,25 @@ interface nsISelectionListener;
 interface nsIContent;
 
 %{C++
 class nsFrameSelection;
 class nsIFrame;
 class nsIPresShell;
 struct nsTextRangeStyle;
 struct nsPoint;
+#include "nsIFrame.h"
 %}
 
 [ptr] native nsFrameSelection(nsFrameSelection);
 [ptr] native nsIFrame(nsIFrame);
 [ptr] native nsIPresShell(nsIPresShell);
 [ref] native constTextRangeStyleRef(const nsTextRangeStyle);
 [ref] native nsPointRef(nsPoint);
+native nsDirection(nsDirection);
 
 [scriptable, uuid(98552206-ad7a-4d2d-8ce3-b6fa2389298b)]
 interface nsISelectionPrivate : nsISupports
  {
     const short ENDOFPRECEDINGLINE=0;
     const short STARTOFNEXTLINE=1;
     
     attribute boolean interlinePosition;
@@ -123,10 +125,16 @@ interface nsISelectionPrivate : nsISuppo
 
     /**
      * Set the painting style for the range. The range must be a range in
      * the selection. The textRangeStyle will be used by text frame
      * when it is painting the selection.
      */
     [noscript] void setTextRangeStyle(in nsIDOMRange range,
                       in constTextRangeStyleRef textRangeStyle);
+
+    /**
+     * Get the direction of the selection.
+     */
+    [noscript, notxpcom] nsDirection getSelectionDirection();
+    [noscript, notxpcom] void setSelectionDirection(in nsDirection aDirection);
 };
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -2718,25 +2718,34 @@ nsHTMLInputElement::GetTextLength(PRInt3
 
   *aTextLength = val.Length();
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionRange(PRInt32 aSelectionStart,
-                                      PRInt32 aSelectionEnd)
+                                      PRInt32 aSelectionEnd,
+                                      const nsAString& aDirection)
 {
   nsresult rv = NS_ERROR_FAILURE;
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
 
   if (formControlFrame) {
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame) {
-      rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd);
+      // Default to forward, even if not specified.
+      // Note that we don't currently support directionless selections, so
+      // "none" is treated like "forward".
+      nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
+      if (aDirection.EqualsLiteral("backward")) {
+        dir = nsITextControlFrame::eBackward;
+      }
+
+      rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
       if (NS_SUCCEEDED(rv)) {
         rv = textControlFrame->ScrollSelectionIntoView();
       }
     }
   }
 
   return rv;
 }
@@ -2748,59 +2757,53 @@ nsHTMLInputElement::GetSelectionStart(PR
   
   PRInt32 selEnd;
   return GetSelectionRange(aSelectionStart, &selEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionStart(PRInt32 aSelectionStart)
 {
-  nsresult rv = NS_ERROR_FAILURE;
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
-
-  if (formControlFrame) {
-    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-    if (textControlFrame) {
-      rv = textControlFrame->SetSelectionStart(aSelectionStart);
-      if (NS_SUCCEEDED(rv)) {
-        rv = textControlFrame->ScrollSelectionIntoView();
-      }
-    }
+  nsAutoString direction;
+  nsresult rv = GetSelectionDirection(direction);
+  NS_ENSURE_SUCCESS(rv, rv);
+  PRInt32 start, end;
+  rv = GetSelectionRange(&start, &end);
+  NS_ENSURE_SUCCESS(rv, rv);
+  start = aSelectionStart;
+  if (end < start) {
+    end = start;
   }
-
-  return rv;
+  return SetSelectionRange(start, end, direction);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::GetSelectionEnd(PRInt32* aSelectionEnd)
 {
   NS_ENSURE_ARG_POINTER(aSelectionEnd);
   
   PRInt32 selStart;
   return GetSelectionRange(&selStart, aSelectionEnd);
 }
 
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionEnd(PRInt32 aSelectionEnd)
 {
-  nsresult rv = NS_ERROR_FAILURE;
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
-
-  if (formControlFrame) {
-    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-    if (textControlFrame) {
-      rv = textControlFrame->SetSelectionEnd(aSelectionEnd);
-      if (NS_SUCCEEDED(rv)) {
-        rv = textControlFrame->ScrollSelectionIntoView();
-      }
-    }
+  nsAutoString direction;
+  nsresult rv = GetSelectionDirection(direction);
+  NS_ENSURE_SUCCESS(rv, rv);
+  PRInt32 start, end;
+  rv = GetSelectionRange(&start, &end);
+  NS_ENSURE_SUCCESS(rv, rv);
+  end = aSelectionEnd;
+  if (start > end) {
+    start = end;
   }
-
-  return rv;
+  return SetSelectionRange(start, end, direction);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::GetFiles(nsIDOMFileList** aFileList)
 {
   *aFileList = nsnull;
 
   if (mType != NS_FORM_INPUT_FILE) {
@@ -2831,16 +2834,55 @@ nsHTMLInputElement::GetSelectionRange(PR
     if (textControlFrame)
       rv = textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
+nsHTMLInputElement::GetSelectionDirection(nsAString& aDirection)
+{
+  nsresult rv = NS_ERROR_FAILURE;
+  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
+
+  if (formControlFrame) {
+    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+    if (textControlFrame) {
+      nsITextControlFrame::SelectionDirection dir;
+      rv = textControlFrame->GetSelectionRange(nsnull, nsnull, &dir);
+      if (NS_SUCCEEDED(rv)) {
+        if (dir == nsITextControlFrame::eNone) {
+          aDirection.AssignLiteral("none");
+        } else if (dir == nsITextControlFrame::eForward) {
+          aDirection.AssignLiteral("forward");
+        } else if (dir == nsITextControlFrame::eBackward) {
+          aDirection.AssignLiteral("backward");
+        } else {
+          NS_NOTREACHED("Invalid SelectionDirection value");
+        }
+      }
+    }
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsHTMLInputElement::SetSelectionDirection(const nsAString& aDirection) {
+  PRInt32 start, end;
+  nsresult rv = GetSelectionRange(&start, &end);
+  if (NS_SUCCEEDED(rv)) {
+    rv = SetSelectionRange(start, end, aDirection);
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
 nsHTMLInputElement::GetPhonetic(nsAString& aPhonetic)
 {
   aPhonetic.Truncate(0);
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
 
   if (formControlFrame) {
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame)
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -824,58 +824,52 @@ nsHTMLTextAreaElement::GetSelectionStart
   
   PRInt32 selEnd;
   return GetSelectionRange(aSelectionStart, &selEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionStart(PRInt32 aSelectionStart)
 {
-  nsresult rv = NS_ERROR_FAILURE;
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
-
-  if (formControlFrame){
-    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-    if (textControlFrame) {
-      rv = textControlFrame->SetSelectionStart(aSelectionStart);
-      if (NS_SUCCEEDED(rv)) {
-        rv = textControlFrame->ScrollSelectionIntoView();
-      }
-    }
+  nsAutoString direction;
+  nsresult rv = GetSelectionDirection(direction);
+  NS_ENSURE_SUCCESS(rv, rv);
+  PRInt32 start, end;
+  rv = GetSelectionRange(&start, &end);
+  NS_ENSURE_SUCCESS(rv, rv);
+  start = aSelectionStart;
+  if (end < start) {
+    end = start;
   }
-
-  return rv;
+  return SetSelectionRange(start, end, direction);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetSelectionEnd(PRInt32 *aSelectionEnd)
 {
   NS_ENSURE_ARG_POINTER(aSelectionEnd);
   
   PRInt32 selStart;
   return GetSelectionRange(&selStart, aSelectionEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionEnd(PRInt32 aSelectionEnd)
 {
-  nsresult rv = NS_ERROR_FAILURE;
-  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
-
-  if (formControlFrame) {
-    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
-    if (textControlFrame) {
-      rv = textControlFrame->SetSelectionEnd(aSelectionEnd);
-      if (NS_SUCCEEDED(rv)) {
-        rv = textControlFrame->ScrollSelectionIntoView();
-      }
-    }
+  nsAutoString direction;
+  nsresult rv = GetSelectionDirection(direction);
+  NS_ENSURE_SUCCESS(rv, rv);
+  PRInt32 start, end;
+  rv = GetSelectionRange(&start, &end);
+  NS_ENSURE_SUCCESS(rv, rv);
+  end = aSelectionEnd;
+  if (start > end) {
+    start = end;
   }
-
-  return rv;
+  return SetSelectionRange(start, end, direction);
 }
 
 nsresult
 nsHTMLTextAreaElement::GetSelectionRange(PRInt32* aSelectionStart,
                                       PRInt32* aSelectionEnd)
 {
   nsresult rv = NS_ERROR_FAILURE;
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
@@ -884,26 +878,75 @@ nsHTMLTextAreaElement::GetSelectionRange
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame)
       rv = textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
   }
 
   return rv;
 }
 
+nsresult
+nsHTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection)
+{
+  nsresult rv = NS_ERROR_FAILURE;
+  nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
+
+  if (formControlFrame) {
+    nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
+    if (textControlFrame) {
+      nsITextControlFrame::SelectionDirection dir;
+      rv = textControlFrame->GetSelectionRange(nsnull, nsnull, &dir);
+      if (NS_SUCCEEDED(rv)) {
+        if (dir == nsITextControlFrame::eNone) {
+          aDirection.AssignLiteral("none");
+        } else if (dir == nsITextControlFrame::eForward) {
+          aDirection.AssignLiteral("forward");
+        } else if (dir == nsITextControlFrame::eBackward) {
+          aDirection.AssignLiteral("backward");
+        } else {
+          NS_NOTREACHED("Invalid SelectionDirection value");
+        }
+      }
+    }
+  }
+
+  return rv;
+}
+
 NS_IMETHODIMP
-nsHTMLTextAreaElement::SetSelectionRange(PRInt32 aSelectionStart, PRInt32 aSelectionEnd)
+nsHTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection) {
+  PRInt32 start, end;
+  nsresult rv = GetSelectionRange(&start, &end);
+  if (NS_SUCCEEDED(rv)) {
+    rv = SetSelectionRange(start, end, aDirection);
+  }
+
+  return rv;
+}
+
+NS_IMETHODIMP
+nsHTMLTextAreaElement::SetSelectionRange(PRInt32 aSelectionStart,
+                                         PRInt32 aSelectionEnd,
+                                         const nsAString& aDirection)
 { 
   nsresult rv = NS_ERROR_FAILURE;
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_TRUE);
 
   if (formControlFrame) {
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame) {
-      rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd);
+      // Default to forward, even if not specified.
+      // Note that we don't currently support directionless selections, so
+      // "none" is treated like "forward".
+      nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eForward;
+      if (aDirection.EqualsLiteral("backward")) {
+        dir = nsITextControlFrame::eBackward;
+      }
+
+      rv = textControlFrame->SetSelectionRange(aSelectionStart, aSelectionEnd, dir);
       if (NS_SUCCEEDED(rv)) {
         rv = textControlFrame->ScrollSelectionIntoView();
       }
     }
   }
 
   return rv;
 } 
--- a/content/html/content/test/Makefile.in
+++ b/content/html/content/test/Makefile.in
@@ -272,13 +272,14 @@ include $(topsrcdir)/config/rules.mk
 		test_bug649134.html \
 		test_bug658746.html \
 		test_bug659596.html \
 		test_bug659743.xml \
 		test_bug660663.html \
 		test_bug664299.html \
 		test_bug666200.html \
 		test_bug666666.html \
+		test_bug674558.html \
 		test_restore_from_parser_fragment.html \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/html/content/test/test_bug674558.html
@@ -0,0 +1,151 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=674558
+-->
+<head>
+  <title>Test for Bug 674558</title>
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=674558">Mozilla Bug 674558</a>
+<p id="display"></p>
+<div id="content">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 674558 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(function() {
+  function textAreaCtor() {
+    return document.createElement("textarea");
+  }
+  var ctors = [textAreaCtor];
+  ["text", "password", "search"].forEach(function(type) {
+    ctors.push(function inputCtor() {
+      var input = document.createElement("input");
+      input.type = type;
+      return input;
+    });
+  });
+
+  for (var ctor in ctors) {
+    test(ctors[ctor]);
+  }
+
+  SimpleTest.finish();
+});
+
+function test(ctor) {
+  var elem = ctor();
+  ok(true, "Testing " + name(elem));
+
+  ok("selectionDirection" in elem, "elem should have the selectionDirection property");
+
+  var content = document.getElementById("content");
+  content.appendChild(elem);
+
+  elem.value = "foobar";
+
+  is(elem.selectionStart, 0, "Default value");
+  is(elem.selectionEnd, 0, "Default value");
+  is(elem.selectionDirection, "forward", "Default value");
+
+  elem.setSelectionRange(1, 3);
+  is(elem.selectionStart, 1, "Correct value");
+  is(elem.selectionEnd, 3, "Correct value");
+  is(elem.selectionDirection, "forward", "If not set, should default to forward");
+
+  // extend to right
+  elem.focus();
+  synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Correct value");
+  is(elem.selectionDirection, "forward", "Still forward");
+
+  // change the direction
+  elem.selectionDirection = "backward";
+
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Correct value");
+
+  // extend to right again
+  synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+  is(elem.selectionStart, 2, "Correct value");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Still backward");
+
+  elem.selectionEnd = 5;
+
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Correct value");
+  is(elem.selectionDirection, "backward", "Still backward");
+
+  elem.selectionDirection = "none";
+
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "none not supported");
+
+  elem.selectionDirection = "backward";
+
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Correct Value");
+
+  elem.selectionDirection = "invalid";
+
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Treated as none");
+
+  elem.selectionDirection = "backward";
+
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Correct Value");
+
+  elem.setSelectionRange(1, 4);
+
+  is(elem.selectionStart, 1, "Correct value");
+  is(elem.selectionEnd, 4, "Correct value");
+  is(elem.selectionDirection, "forward", "Correct value");
+
+  elem.setSelectionRange(1, 1);
+  synthesizeKey("VK_RIGHT", {shiftKey: true});
+  synthesizeKey("VK_RIGHT", {shiftKey: true});
+  synthesizeKey("VK_RIGHT", {shiftKey: true});
+
+  is(elem.selectionStart, 1, "Correct value");
+  is(elem.selectionEnd, 4, "Correct value");
+  is(elem.selectionDirection, "forward", "Correct value");
+
+  elem.setSelectionRange(5, 5);
+  synthesizeKey("VK_LEFT", {shiftKey: true});
+  synthesizeKey("VK_LEFT", {shiftKey: true});
+  synthesizeKey("VK_LEFT", {shiftKey: true});
+
+  is(elem.selectionStart, 2, "Correct value");
+  is(elem.selectionEnd, 5, "Correct value");
+  is(elem.selectionDirection, "backward", "Correct value");
+}
+
+function name(elem) {
+  var tag = elem.localName;
+  if (tag == "input") {
+    tag += "[type=" + elem.type + "]";
+  }
+  return tag;
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/html/nsIDOMHTMLInputElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLInputElement.idl
@@ -49,17 +49,17 @@ interface nsIDOMValidityState;
   *
   * This interface is trying to follow the DOM Level 2 HTML specification:
   * http://www.w3.org/TR/DOM-Level-2-HTML/
   *
   * with changes from the work-in-progress WHATWG HTML specification:
   * http://www.whatwg.org/specs/web-apps/current-work/
   */
 
-[scriptable, uuid(a59ba6b8-6f8b-4003-a8a4-184a51a05050)]
+[scriptable, uuid(66819eba-89b5-4db4-8d27-6368c70761e8)]
 interface nsIDOMHTMLInputElement : nsIDOMHTMLElement
 {
            attribute DOMString             accept;
            attribute DOMString             alt;
 
            attribute DOMString             autocomplete;
            attribute boolean               autofocus;
            attribute boolean               defaultChecked;
@@ -102,18 +102,19 @@ interface nsIDOMHTMLInputElement : nsIDO
   readonly attribute nsIDOMValidityState validity;
   readonly attribute DOMString           validationMessage;
   boolean checkValidity();
   void setCustomValidity(in DOMString error);
 
   void select();
            attribute long                  selectionStart;
            attribute long                  selectionEnd;
-  void setSelectionRange(in long selectionStart, in long selectionEnd);
-          
+  void setSelectionRange(in long selectionStart, in long selectionEnd, [optional] in DOMString direction);
+           attribute DOMString             selectionDirection;
+
 
            attribute long                  tabIndex;
            attribute DOMString             useMap;
   readonly attribute nsIControllers        controllers;	
 	readonly attribute long                  textLength;
 
   void mozGetFileNameArray([optional] out unsigned long aLength,
                            [array,size_is(aLength), retval] out wstring aFileNames);
--- a/dom/interfaces/html/nsIDOMHTMLTextAreaElement.idl
+++ b/dom/interfaces/html/nsIDOMHTMLTextAreaElement.idl
@@ -48,17 +48,17 @@ interface nsIDOMValidityState;
  *
  * This interface is trying to follow the DOM Level 2 HTML specification:
  * http://www.w3.org/TR/DOM-Level-2-HTML/
  *
  * with changes from the work-in-progress WHATWG HTML specification:
  * http://www.whatwg.org/specs/web-apps/current-work/
  */
 
-[scriptable, uuid(905edd3e-c0b3-4d54-8a2c-0eaab6ccb3cf)]
+[scriptable, uuid(43e99aee-e41f-4935-a87d-f2dbafdbfddb)]
 interface nsIDOMHTMLTextAreaElement : nsIDOMHTMLElement
 {
            attribute boolean               autofocus;
            attribute unsigned long         cols;
            attribute boolean               disabled;
   readonly attribute nsIDOMHTMLFormElement form;
            attribute long                  maxLength;
            attribute DOMString             name;
@@ -84,17 +84,18 @@ interface nsIDOMHTMLTextAreaElement : ns
   readonly attribute nsIDOMValidityState   validity;
   readonly attribute DOMString             validationMessage;
   boolean checkValidity();
   void setCustomValidity(in DOMString error);
 
   void select();
            attribute long                  selectionStart;
            attribute long                  selectionEnd;
-  void setSelectionRange(in long selectionStart, in long selectionEnd);
+  void setSelectionRange(in long selectionStart, in long selectionEnd, [optional] in DOMString direction);
+           attribute DOMString             selectionDirection;
 
   // Defined on HTMLElement in the specification.
            attribute long                  tabIndex;
 
 
   // Mozilla extensions
   readonly attribute nsIControllers   controllers;
 };
--- a/js/src/xpconnect/src/dom_quickstubs.qsconf
+++ b/js/src/xpconnect/src/dom_quickstubs.qsconf
@@ -253,16 +253,17 @@ members = [
     'nsIDOMHTMLInputElement.form',
     'nsIDOMHTMLInputElement.src',
     'nsIDOMHTMLInputElement.name',
     'nsIDOMHTMLInputElement.value',
     'nsIDOMHTMLInputElement.files',
     'nsIDOMHTMLInputElement.textLength',
     'nsIDOMHTMLInputElement.selectionStart',
     'nsIDOMHTMLInputElement.selectionEnd',
+    'nsIDOMHTMLInputElement.selectionDirection',
     'nsIDOMHTMLInputElement.setSelectionRange',
     'nsIDOMHTMLLinkElement.disabled',
     'nsIDOMHTMLOptionElement.index',
     'nsIDOMHTMLOptionElement.selected',
     'nsIDOMHTMLOptionElement.form',
     'nsIDOMHTMLOptionElement.text',
     'nsIDOMHTMLOptionElement.defaultSelected',
     'nsIDOMHTMLOptionElement.value',
@@ -312,16 +313,17 @@ members = [
     'nsIDOMHTMLTextAreaElement.defaultValue',
     'nsIDOMHTMLTextAreaElement.cols',
     'nsIDOMHTMLTextAreaElement.value',
     'nsIDOMHTMLTextAreaElement.type',
     'nsIDOMHTMLTextAreaElement.select',
     'nsIDOMHTMLTextAreaElement.setSelectionRange',
     'nsIDOMHTMLTextAreaElement.selectionStart',
     'nsIDOMHTMLTextAreaElement.selectionEnd',
+    'nsIDOMHTMLTextAreaElement.selectionDirection',
     'nsIDOMHTMLTextAreaElement.textLength',
     'nsIDOMHTMLTextAreaElement.wrap',
     'nsIDOMHTMLTitleElement.text',
     'nsIDOMHTMLCanvasElement.width',
     'nsIDOMHTMLCanvasElement.height',
     'nsIDOMHTMLCanvasElement.getContext',
     'nsIDOMHTMLCanvasElement.toDataURL',
     'nsIDOMNSHTMLElement.contentEditable',
--- a/layout/forms/nsITextControlFrame.h
+++ b/layout/forms/nsITextControlFrame.h
@@ -45,30 +45,40 @@ class nsIDocShell;
 class nsISelectionController;
 class nsFrameSelection;
 
 class nsITextControlFrame : public nsIFormControlFrame
 {
 public:
   NS_DECL_QUERYFRAME_TARGET(nsITextControlFrame)
 
+  enum SelectionDirection {
+    eNone,
+    eForward,
+    eBackward
+  };
+
   NS_IMETHOD    GetEditor(nsIEditor **aEditor) = 0;
 
   NS_IMETHOD    GetTextLength(PRInt32* aTextLength) = 0;
   
   /**
    * Fire onChange if the value has changed since it was focused or since it
    * was last fired.
    */
   NS_IMETHOD    CheckFireOnChange() = 0;
   NS_IMETHOD    SetSelectionStart(PRInt32 aSelectionStart) = 0;
   NS_IMETHOD    SetSelectionEnd(PRInt32 aSelectionEnd) = 0;
   
-  NS_IMETHOD    SetSelectionRange(PRInt32 aSelectionStart, PRInt32 aSelectionEnd) = 0;
-  NS_IMETHOD    GetSelectionRange(PRInt32* aSelectionStart, PRInt32* aSelectionEnd) = 0;
+  NS_IMETHOD    SetSelectionRange(PRInt32 aSelectionStart,
+                                  PRInt32 aSelectionEnd,
+                                  SelectionDirection aDirection = eNone) = 0;
+  NS_IMETHOD    GetSelectionRange(PRInt32* aSelectionStart,
+                                  PRInt32* aSelectionEnd,
+                                  SelectionDirection* aDirection = nsnull) = 0;
 
   NS_IMETHOD    GetOwnedSelectionController(nsISelectionController** aSelCon) = 0;
   virtual nsFrameSelection* GetOwnedFrameSelection() = 0;
 
   virtual nsresult GetPhonetic(nsAString& aPhonetic) = 0;
 
   /**
    * Ensure editor is initialized with the proper flags and the default value.
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -800,17 +800,18 @@ nsTextControlFrame::GetTextLength(PRInt3
   *aTextLength = textContents.Length();
   return NS_OK;
 }
 
 nsresult
 nsTextControlFrame::SetSelectionInternal(nsIDOMNode *aStartNode,
                                          PRInt32 aStartOffset,
                                          nsIDOMNode *aEndNode,
-                                         PRInt32 aEndOffset)
+                                         PRInt32 aEndOffset,
+                                         nsITextControlFrame::SelectionDirection aDirection)
 {
   // Create a new range to represent the new selection.
   // Note that we use a new range to avoid having to do
   // isIncreasing checks to avoid possible errors.
 
   nsCOMPtr<nsIDOMRange> range = do_CreateInstance(kRangeCID);
   NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
 
@@ -825,20 +826,34 @@ nsTextControlFrame::SetSelectionInternal
   NS_ASSERTION(txtCtrl, "Content not a text control element");
   nsISelectionController* selCon = txtCtrl->GetSelectionController();
   NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsISelection> selection;
   selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));  
   NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
 
+  nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection, &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsDirection direction;
+  if (aDirection == eNone) {
+    // Preserve the direction
+    direction = selPriv->GetSelectionDirection();
+  } else {
+    direction = (aDirection == eBackward) ? eDirPrevious : eDirNext;
+  }
+
   rv = selection->RemoveAllRanges();
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = selection->AddRange(range);  // NOTE: can destroy the world
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  selPriv->SetSelectionDirection(direction);
   return rv;
 }
 
 nsresult
 nsTextControlFrame::ScrollSelectionIntoView()
 {
   nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
   NS_ASSERTION(txtCtrl, "Content not a text control element");
@@ -901,17 +916,18 @@ nsTextControlFrame::SelectAllOrCollapseT
   rv = SetSelectionInternal(rootNode, aSelect ? 0 : numChildren,
                             rootNode, numChildren);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return ScrollSelectionIntoView();
 }
 
 nsresult
-nsTextControlFrame::SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd)
+nsTextControlFrame::SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd,
+                                          nsITextControlFrame::SelectionDirection aDirection)
 {
   NS_ASSERTION(aSelStart <= aSelEnd, "Invalid selection offsets!");
 
   if (aSelStart > aSelEnd)
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsIDOMNode> startNode, endNode;
   PRInt32 startOffset, endOffset;
@@ -931,33 +947,34 @@ nsTextControlFrame::SetSelectionEndPoint
     // Selection isn't collapsed so we have to calculate
     // the end point too.
 
     rv = OffsetToDOMPoint(aSelEnd, getter_AddRefs(endNode), &endOffset);
 
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  return SetSelectionInternal(startNode, startOffset, endNode, endOffset);
+  return SetSelectionInternal(startNode, startOffset, endNode, endOffset, aDirection);
 }
 
 NS_IMETHODIMP
-nsTextControlFrame::SetSelectionRange(PRInt32 aSelStart, PRInt32 aSelEnd)
+nsTextControlFrame::SetSelectionRange(PRInt32 aSelStart, PRInt32 aSelEnd,
+                                      nsITextControlFrame::SelectionDirection aDirection)
 {
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (aSelStart > aSelEnd) {
     // Simulate what we'd see SetSelectionStart() was called, followed
     // by a SetSelectionEnd().
 
     aSelStart   = aSelEnd;
   }
 
-  return SetSelectionEndPoints(aSelStart, aSelEnd);
+  return SetSelectionEndPoints(aSelStart, aSelEnd, aDirection);
 }
 
 
 NS_IMETHODIMP
 nsTextControlFrame::SetSelectionStart(PRInt32 aSelectionStart)
 {
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
@@ -1105,24 +1122,33 @@ nsTextControlFrame::OffsetToDOMPoint(PRI
     NS_IF_ADDREF(*aResult = rootNode);
     *aPosition = 0;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsTextControlFrame::GetSelectionRange(PRInt32* aSelectionStart, PRInt32* aSelectionEnd)
+nsTextControlFrame::GetSelectionRange(PRInt32* aSelectionStart,
+                                      PRInt32* aSelectionEnd,
+                                      SelectionDirection* aDirection)
 {
   // make sure we have an editor
   nsresult rv = EnsureEditorInitialized();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  *aSelectionStart = 0;
-  *aSelectionEnd = 0;
+  if (aSelectionStart) {
+    *aSelectionStart = 0;
+  }
+  if (aSelectionEnd) {
+    *aSelectionEnd = 0;
+  }
+  if (aDirection) {
+    *aDirection = eNone;
+  }
 
   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);
@@ -1131,16 +1157,34 @@ nsTextControlFrame::GetSelectionRange(PR
   PRInt32 numRanges = 0;
   selection->GetRangeCount(&numRanges);
 
   if (numRanges < 1)
     return NS_OK;
 
   // We only operate on the first range in the selection!
 
+  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");
+      }
+    }
+  }
+
+  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;
 
--- a/layout/forms/nsTextControlFrame.h
+++ b/layout/forms/nsTextControlFrame.h
@@ -140,18 +140,22 @@ public:
 
 //==== NSITEXTCONTROLFRAME
 
   NS_IMETHOD    GetEditor(nsIEditor **aEditor);
   NS_IMETHOD    GetTextLength(PRInt32* aTextLength);
   NS_IMETHOD    CheckFireOnChange();
   NS_IMETHOD    SetSelectionStart(PRInt32 aSelectionStart);
   NS_IMETHOD    SetSelectionEnd(PRInt32 aSelectionEnd);
-  NS_IMETHOD    SetSelectionRange(PRInt32 aSelectionStart, PRInt32 aSelectionEnd);
-  NS_IMETHOD    GetSelectionRange(PRInt32* aSelectionStart, PRInt32* aSelectionEnd);
+  NS_IMETHOD    SetSelectionRange(PRInt32 aSelectionStart,
+                                  PRInt32 aSelectionEnd,
+                                  SelectionDirection aDirection = eNone);
+  NS_IMETHOD    GetSelectionRange(PRInt32* aSelectionStart,
+                                  PRInt32* aSelectionEnd,
+                                  SelectionDirection* aDirection = nsnull);
   NS_IMETHOD    GetOwnedSelectionController(nsISelectionController** aSelCon);
   virtual nsFrameSelection* GetOwnedFrameSelection();
 
   nsresult GetPhonetic(nsAString& aPhonetic);
 
   /**
    * Ensure mEditor is initialized with the proper flags and the default value.
    * @throws NS_ERROR_NOT_INITIALIZED if mEditor has not been created
@@ -385,19 +389,21 @@ protected:
   nsresult CalcIntrinsicSize(nsRenderingContext* aRenderingContext,
                              nsSize&              aIntrinsicSize);
 
   nsresult ScrollSelectionIntoView();
 
 private:
   //helper methods
   nsresult SetSelectionInternal(nsIDOMNode *aStartNode, PRInt32 aStartOffset,
-                                nsIDOMNode *aEndNode, PRInt32 aEndOffset);
+                                nsIDOMNode *aEndNode, PRInt32 aEndOffset,
+                                SelectionDirection aDirection = eNone);
   nsresult SelectAllOrCollapseToEndOfText(PRBool aSelect);
-  nsresult SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd);
+  nsresult SetSelectionEndPoints(PRInt32 aSelStart, PRInt32 aSelEnd,
+                                 SelectionDirection aDirection = eNone);
 
   // accessors for the notify on input flag
   PRBool GetNotifyOnInput() const { return mNotifyOnInput; }
   void SetNotifyOnInput(PRBool val) { mNotifyOnInput = val; }
 
   /**
    * Return the root DOM element, and implicitly initialize the editor if needed.
    */
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -5991,16 +5991,26 @@ nsTypedSelection::SelectionLanguageChang
   
   // The caret might have moved, so invalidate the desired X position
   // for future usages of up-arrow or down-arrow
   mFrameSelection->InvalidateDesiredX();
   
   return NS_OK;
 }
 
+NS_IMETHODIMP_(nsDirection)
+nsTypedSelection::GetSelectionDirection() {
+  return mDirection;
+}
+
+NS_IMETHODIMP_(void)
+nsTypedSelection::SetSelectionDirection(nsDirection aDirection) {
+  mDirection = aDirection;
+}
+
 
 // nsAutoCopyListener
 
 nsAutoCopyListener* nsAutoCopyListener::sInstance = nsnull;
 
 NS_IMPL_ISUPPORTS1(nsAutoCopyListener, nsISelectionListener)
 
 /*
--- a/toolkit/components/satchel/nsFormFillController.cpp
+++ b/toolkit/components/satchel/nsFormFillController.cpp
@@ -486,17 +486,17 @@ nsFormFillController::GetSelectionEnd(PR
     mFocusedInput->GetSelectionEnd(aSelectionEnd);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFormFillController::SelectTextRange(PRInt32 aStartIndex, PRInt32 aEndIndex)
 {
  if (mFocusedInput)
-    mFocusedInput->SetSelectionRange(aStartIndex, aEndIndex);
+    mFocusedInput->SetSelectionRange(aStartIndex, aEndIndex, EmptyString());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFormFillController::OnSearchBegin()
 {
   return NS_OK;
 }