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
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 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;
 }