Bug 674820 - input/textarea.selectionStart/selectionEnd/selectionDirection should not require the presence of a frame; r=bzbarsky
authorEhsan Akhgari <ehsan@mozilla.com>
Fri, 29 Jul 2011 17:31:57 -0400
changeset 73855 2112b458c8c7ebe956433681b537312da77e4738
parent 73854 70416d5c538446cd5f1109b664221bef656d625b
child 73856 968d17e71c23fc64e8963ace027ea15002766eab
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersbzbarsky
bugs674820
milestone8.0a1
Bug 674820 - input/textarea.selectionStart/selectionEnd/selectionDirection should not require the presence of a frame; r=bzbarsky
content/html/content/public/nsITextControlElement.h
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
content/html/content/src/nsHTMLTextAreaElement.cpp
content/html/content/src/nsTextEditorState.cpp
content/html/content/src/nsTextEditorState.h
content/html/content/test/test_bug674558.html
layout/forms/nsTextControlFrame.cpp
layout/reftests/editor/reftest.list
layout/reftests/editor/selection_visibility_after_reframe-3.html
layout/reftests/editor/selection_visibility_after_reframe.html
--- a/content/html/content/public/nsITextControlElement.h
+++ b/content/html/content/public/nsITextControlElement.h
@@ -44,18 +44,18 @@ class nsIContent;
 class nsAString;
 class nsIEditor;
 class nsISelectionController;
 class nsFrameSelection;
 class nsTextControlFrame;
 
 // IID for the nsITextControl interface
 #define NS_ITEXTCONTROLELEMENT_IID    \
-{ 0x66545dde, 0x3f4a, 0x49fd,    \
-  { 0x82, 0x73, 0x69, 0x7e, 0xab, 0x54, 0x06, 0x0a } }
+{ 0x2e758eee, 0xd023, 0x4fd1,    \
+  { 0x97, 0x93, 0xae, 0xeb, 0xbb, 0xf3, 0xa8, 0x3f } }
 
 /**
  * This interface is used for the text control frame to get the editor and
  * selection controller objects, and some helper properties.
  */
 class nsITextControlElement : public nsISupports {
 public:
 
@@ -213,15 +213,23 @@ public:
   typedef enum {
     eHTMLTextWrap_Off     = 1,    // "off"
     eHTMLTextWrap_Hard    = 2,    // "hard"
     eHTMLTextWrap_Soft    = 3     // the default
   } nsHTMLTextWrap;
 
   static PRBool
   GetWrapPropertyEnum(nsIContent* aContent, nsHTMLTextWrap& aWrapProp);
+
+  /**
+   * Does the editor have a selection cache?
+   *
+   * Note that this function has the side effect of making the editor for input
+   * elements be initialized eagerly.
+   */
+  NS_IMETHOD_(PRBool) HasCachedSelection() = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsITextControlElement,
                               NS_ITEXTCONTROLELEMENT_IID)
 
 #endif // nsITextControlElement_h___
 
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -2749,24 +2749,36 @@ nsHTMLInputElement::SetSelectionRange(PR
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::GetSelectionStart(PRInt32* aSelectionStart)
 {
   NS_ENSURE_ARG_POINTER(aSelectionStart);
-  
+
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    *aSelectionStart = state->GetSelectionProperties().mStart;
+    return NS_OK;
+  }
+
   PRInt32 selEnd;
   return GetSelectionRange(aSelectionStart, &selEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionStart(PRInt32 aSelectionStart)
 {
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    state->GetSelectionProperties().mStart = aSelectionStart;
+    return NS_OK;
+  }
+
   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) {
@@ -2774,25 +2786,37 @@ nsHTMLInputElement::SetSelectionStart(PR
   }
   return SetSelectionRange(start, end, direction);
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::GetSelectionEnd(PRInt32* aSelectionEnd)
 {
   NS_ENSURE_ARG_POINTER(aSelectionEnd);
-  
+
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    *aSelectionEnd = state->GetSelectionProperties().mEnd;
+    return NS_OK;
+  }
+
   PRInt32 selStart;
   return GetSelectionRange(&selStart, aSelectionEnd);
 }
 
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionEnd(PRInt32 aSelectionEnd)
 {
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    state->GetSelectionProperties().mEnd = aSelectionEnd;
+    return NS_OK;
+  }
+
   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) {
@@ -2833,46 +2857,70 @@ nsHTMLInputElement::GetSelectionRange(PR
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame)
       rv = textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
   }
 
   return rv;
 }
 
+static void
+DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
+{
+  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");
+  }
+}
+
 NS_IMETHODIMP
 nsHTMLInputElement::GetSelectionDirection(nsAString& aDirection)
 {
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    DirectionToName(state->GetSelectionProperties().mDirection, aDirection);
+    return NS_OK;
+  }
+
   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");
-        }
+        DirectionToName(dir, aDirection);
       }
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLInputElement::SetSelectionDirection(const nsAString& aDirection) {
+  nsTextEditorState *state = GetEditorState();
+  if (state && state->IsSelectionCached()) {
+    nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
+    if (aDirection.EqualsLiteral("forward")) {
+      dir = nsITextControlFrame::eForward;
+    } else if (aDirection.EqualsLiteral("backward")) {
+      dir = nsITextControlFrame::eBackward;
+    }
+    state->GetSelectionProperties().mDirection = dir;
+    return NS_OK;
+  }
+
   PRInt32 start, end;
   nsresult rv = GetSelectionRange(&start, &end);
   if (NS_SUCCEEDED(rv)) {
     rv = SetSelectionRange(start, end, aDirection);
   }
 
   return rv;
 }
@@ -4132,16 +4180,32 @@ nsHTMLInputElement::OnValueChanged(PRBoo
   // However, we don't want to waste cycles if the state doesn't apply.
   if (PlaceholderApplies()
       && HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
       && !nsContentUtils::IsFocusedContent((nsIContent*)(this))) {
     UpdateState(aNotify);
   }
 }
 
+NS_IMETHODIMP_(PRBool)
+nsHTMLInputElement::HasCachedSelection()
+{
+  PRBool isCached = PR_FALSE;
+  nsTextEditorState *state = GetEditorState();
+  if (state) {
+    isCached = state->IsSelectionCached() &&
+               state->HasNeverInitializedBefore() &&
+               !state->GetSelectionProperties().IsDefault();
+    if (isCached) {
+      state->WillInitEagerly();
+    }
+  }
+  return isCached;
+}
+
 void
 nsHTMLInputElement::FieldSetDisabledChanged(PRBool aNotify)
 {
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
   nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
 }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -209,16 +209,17 @@ public:
   NS_IMETHOD CreateEditor();
   NS_IMETHOD_(nsIContent*) GetRootEditorNode();
   NS_IMETHOD_(nsIContent*) CreatePlaceholderNode();
   NS_IMETHOD_(nsIContent*) GetPlaceholderNode();
   NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify);
   NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify);
   NS_IMETHOD_(void) InitializeKeyboardEventListeners();
   NS_IMETHOD_(void) OnValueChanged(PRBool aNotify);
+  NS_IMETHOD_(PRBool) HasCachedSelection();
 
   void GetDisplayFileName(nsAString& aFileName) const;
   const nsCOMArray<nsIDOMFile>& GetFiles() const;
   void SetFiles(const nsCOMArray<nsIDOMFile>& aFiles, bool aSetValueChanged);
   void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged);
 
   void SetCheckedChangedInternal(PRBool aCheckedChanged);
   PRBool GetCheckedChanged() const {
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -155,16 +155,17 @@ public:
   NS_IMETHOD CreateEditor();
   NS_IMETHOD_(nsIContent*) GetRootEditorNode();
   NS_IMETHOD_(nsIContent*) CreatePlaceholderNode();
   NS_IMETHOD_(nsIContent*) GetPlaceholderNode();
   NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify);
   NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify);
   NS_IMETHOD_(void) InitializeKeyboardEventListeners();
   NS_IMETHOD_(void) OnValueChanged(PRBool aNotify);
+  NS_IMETHOD_(PRBool) HasCachedSelection();
 
   // nsIContent
   virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                nsIContent* aBindingParent,
                                PRBool aCompileEventHandlers);
   virtual void UnbindFromTree(PRBool aDeep = PR_TRUE,
                               PRBool aNullParent = PR_TRUE);
   virtual PRBool ParseAttribute(PRInt32 aNamespaceID,
@@ -816,24 +817,34 @@ nsHTMLTextAreaElement::GetTextLength(PRI
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetSelectionStart(PRInt32 *aSelectionStart)
 {
   NS_ENSURE_ARG_POINTER(aSelectionStart);
-  
+
+  if (mState->IsSelectionCached()) {
+    *aSelectionStart = mState->GetSelectionProperties().mStart;
+    return NS_OK;
+  }
+
   PRInt32 selEnd;
   return GetSelectionRange(aSelectionStart, &selEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionStart(PRInt32 aSelectionStart)
 {
+  if (mState->IsSelectionCached()) {
+    mState->GetSelectionProperties().mStart = aSelectionStart;
+    return NS_OK;
+  }
+
   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) {
@@ -841,24 +852,34 @@ nsHTMLTextAreaElement::SetSelectionStart
   }
   return SetSelectionRange(start, end, direction);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetSelectionEnd(PRInt32 *aSelectionEnd)
 {
   NS_ENSURE_ARG_POINTER(aSelectionEnd);
-  
+
+  if (mState->IsSelectionCached()) {
+    *aSelectionEnd = mState->GetSelectionProperties().mEnd;
+    return NS_OK;
+  }
+
   PRInt32 selStart;
   return GetSelectionRange(&selStart, aSelectionEnd);
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionEnd(PRInt32 aSelectionEnd)
 {
+  if (mState->IsSelectionCached()) {
+    mState->GetSelectionProperties().mEnd = aSelectionEnd;
+    return NS_OK;
+  }
+
   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) {
@@ -878,46 +899,68 @@ nsHTMLTextAreaElement::GetSelectionRange
     nsITextControlFrame* textControlFrame = do_QueryFrame(formControlFrame);
     if (textControlFrame)
       rv = textControlFrame->GetSelectionRange(aSelectionStart, aSelectionEnd);
   }
 
   return rv;
 }
 
+static void
+DirectionToName(nsITextControlFrame::SelectionDirection dir, nsAString& aDirection)
+{
+  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");
+  }
+}
+
 nsresult
 nsHTMLTextAreaElement::GetSelectionDirection(nsAString& aDirection)
 {
+  if (mState->IsSelectionCached()) {
+    DirectionToName(mState->GetSelectionProperties().mDirection, aDirection);
+    return NS_OK;
+  }
+
   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");
-        }
+        DirectionToName(dir, aDirection);
       }
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection) {
+  if (mState->IsSelectionCached()) {
+    nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
+    if (aDirection.EqualsLiteral("forward")) {
+      dir = nsITextControlFrame::eForward;
+    } else if (aDirection.EqualsLiteral("backward")) {
+      dir = nsITextControlFrame::eBackward;
+    }
+    mState->GetSelectionProperties().mDirection = dir;
+    return NS_OK;
+  }
+
   PRInt32 start, end;
   nsresult rv = GetSelectionRange(&start, &end);
   if (NS_SUCCEEDED(rv)) {
     rv = SetSelectionRange(start, end, aDirection);
   }
 
   return rv;
 }
@@ -1484,16 +1527,22 @@ nsHTMLTextAreaElement::OnValueChanged(PR
 
   if (validBefore != IsValid() ||
       (HasAttr(kNameSpaceID_None, nsGkAtoms::placeholder)
        && !nsContentUtils::IsFocusedContent((nsIContent*)(this)))) {
     UpdateState(aNotify);
   }
 }
 
+NS_IMETHODIMP_(PRBool)
+nsHTMLTextAreaElement::HasCachedSelection()
+{
+  return mState->IsSelectionCached();
+}
+
 void
 nsHTMLTextAreaElement::FieldSetDisabledChanged(PRBool aNotify)
 {
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
   nsGenericHTMLFormElement::FieldSetDisabledChanged(aNotify);
 }
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -69,53 +69,50 @@
 
 using namespace mozilla::dom;
 
 static NS_DEFINE_CID(kTextEditorCID, NS_TEXTEDITOR_CID);
 
 static nsINativeKeyBindings *sNativeInputBindings = nsnull;
 static nsINativeKeyBindings *sNativeTextAreaBindings = nsnull;
 
-struct SelectionState {
-  PRInt32 mStart;
-  PRInt32 mEnd;
-};
-
 class RestoreSelectionState : public nsRunnable {
 public:
-  RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame,
-                        PRInt32 aStart, PRInt32 aEnd)
+  RestoreSelectionState(nsTextEditorState *aState, nsTextControlFrame *aFrame)
     : mFrame(aFrame),
-      mStart(aStart),
-      mEnd(aEnd),
       mTextEditorState(aState)
   {
   }
 
   NS_IMETHOD Run() {
     if (mFrame) {
       // SetSelectionRange leads to Selection::AddRange which flushes Layout -
       // need to block script to avoid nested PrepareEditor calls (bug 642800).
       nsAutoScriptBlocker scriptBlocker;
-      mFrame->SetSelectionRange(mStart, mEnd);
-      mTextEditorState->HideSelectionIfBlurred();
+       nsTextEditorState::SelectionProperties& properties =
+         mTextEditorState->GetSelectionProperties();
+       mFrame->SetSelectionRange(properties.mStart,
+                                 properties.mEnd,
+                                 properties.mDirection);
+      if (!mTextEditorState->mSelectionRestoreEagerInit) {
+        mTextEditorState->HideSelectionIfBlurred();
+      }
+      mTextEditorState->mSelectionRestoreEagerInit = PR_FALSE;
     }
     mTextEditorState->FinishedRestoringSelection();
     return NS_OK;
   }
 
   // Let the text editor tell us we're no longer relevant - avoids use of nsWeakFrame
   void Revoke() {
     mFrame = nsnull;
   }
 
 private:
   nsTextControlFrame* mFrame;
-  PRInt32 mStart;
-  PRInt32 mEnd;
   nsTextEditorState* mTextEditorState;
 };
 
 /*static*/
 PRBool
 nsITextControlElement::GetWrapPropertyEnum(nsIContent* aContent,
   nsITextControlElement::nsHTMLTextWrap& aWrapProp)
 {
@@ -928,19 +925,22 @@ nsTextInputListener::GetKeyBindings()
 
 // nsTextEditorState
 
 nsTextEditorState::nsTextEditorState(nsITextControlElement* aOwningElement)
   : mTextCtrlElement(aOwningElement),
     mRestoringSelection(nsnull),
     mBoundFrame(nsnull),
     mTextListener(nsnull),
+    mEverInited(PR_FALSE),
     mEditorInitialized(PR_FALSE),
     mInitializing(PR_FALSE),
-    mValueTransferInProgress(PR_FALSE)
+    mValueTransferInProgress(PR_FALSE),
+    mSelectionCached(PR_TRUE),
+    mSelectionRestoreEagerInit(PR_FALSE)
 {
   MOZ_COUNT_CTOR(nsTextEditorState);
 }
 
 nsTextEditorState::~nsTextEditorState()
 {
   MOZ_COUNT_DTOR(nsTextEditorState);
   Clear();
@@ -1344,33 +1344,36 @@ nsTextEditorState::PrepareEditor(const n
     // default value don't screw us up.
     // Since changing the control type does a reframe, we don't have to worry
     // about dynamic type changes here.
     newEditor->EnableUndo(PR_FALSE);
   }
 
   if (!mEditorInitialized) {
     newEditor->PostCreate();
+    mEverInited = PR_TRUE;
     mEditorInitialized = PR_TRUE;
   }
 
   if (mTextListener)
     newEditor->AddEditorObserver(mTextListener);
 
   // Restore our selection after being bound to a new frame
-  if (mSelState) {
+  if (mSelectionCached) {
     if (mRestoringSelection) // paranoia
       mRestoringSelection->Revoke();
-    mRestoringSelection = new RestoreSelectionState(this, mBoundFrame, mSelState->mStart, mSelState->mEnd);
+    mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
     if (mRestoringSelection) {
       nsContentUtils::AddScriptRunner(mRestoringSelection);
-      mSelState = nsnull;
     }
   }
 
+  // The selection cache is no longer going to be valid
+  mSelectionCached = PR_FALSE;
+
   return rv;
 }
 
 void
 nsTextEditorState::DestroyEditor()
 {
   // notify the editor that we are going away
   if (mEditorInitialized) {
@@ -1403,21 +1406,20 @@ nsTextEditorState::UnbindFromFrame(nsTex
 
   // Save our selection state if needed.
   // Note that nsTextControlFrame::GetSelectionRange attempts to initialize the
   // editor before grabbing the range, and because this is not an acceptable
   // side effect for unbinding from a text control frame, we need to call
   // GetSelectionRange before calling DestroyEditor, and only if
   // mEditorInitialized indicates that we actually have an editor available.
   if (mEditorInitialized) {
-    mSelState = new SelectionState();
-    nsresult rv = mBoundFrame->GetSelectionRange(&mSelState->mStart, &mSelState->mEnd);
-    if (NS_FAILED(rv)) {
-      mSelState = nsnull;
-    }
+    mBoundFrame->GetSelectionRange(&mSelectionProperties.mStart,
+                                   &mSelectionProperties.mEnd,
+                                   &mSelectionProperties.mDirection);
+    mSelectionCached = PR_TRUE;
   }
 
   // Destroy our editor
   DestroyEditor();
 
   // Clean up the controller
   if (!SuppressEventHandlers(mBoundFrame->PresContext()))
   {
--- a/content/html/content/src/nsTextEditorState.h
+++ b/content/html/content/src/nsTextEditorState.h
@@ -36,27 +36,27 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef nsTextEditorState_h__
 #define nsTextEditorState_h__
 
 #include "nsAutoPtr.h"
 #include "nsITextControlElement.h"
+#include "nsITextControlFrame.h"
 #include "nsCycleCollectionParticipant.h"
 
 class nsTextInputListener;
 class nsTextControlFrame;
 class nsTextInputSelectionImpl;
 class nsAnonDivObserver;
 class nsISelectionController;
 class nsFrameSelection;
 class nsIEditor;
 class nsITextControlElement;
-struct SelectionState;
 
 /**
  * nsTextEditorState is a class which is responsible for managing the state of
  * plaintext controls.  This currently includes the following HTML elements:
  *   <input type=text>
  *   <input type=password>
  *   <textarea>
  * and also XUL controls such as <textbox> which use one of these elements behind
@@ -111,16 +111,21 @@ struct SelectionState;
  *    when the frame is unbound from the text control element.
  *
  *  * The editor's cached value.  This value is stored in the mCachedValue member.
  *    It is used to improve the performance of append operations to the text
  *    control.  A mutation observer stored in the mMutationObserver has the job of
  *    invalidating this cache when the anonymous contect containing the value is
  *    changed.
  *
+ *  * The editor's cached selection properties.  These vales are stored in the
+ *    mSelectionProperties member, and include the selection's start, end and
+ *    direction. They are only used when there is no frame available for the
+ *    text field.
+ *
  *
  * As a general rule, nsTextEditorState objects own the value of the text control, and any
  * attempt to retrieve or set the value must be made through those objects.  Internally,
  * the value can be represented in several different ways, based on the state the control is
  * in.
  *
  *   * When the control is first initialized, its value is equal to the default value of
  *     the DOM node.  For <input> text controls, this default value is the value of the
@@ -209,16 +214,34 @@ public:
 
   /* called to free up native keybinding services */
   static NS_HIDDEN_(void) ShutDown();
 
   void ClearValueCache() { mCachedValue.Truncate(); }
 
   void HideSelectionIfBlurred();
 
+  struct SelectionProperties {
+    SelectionProperties() : mStart(0), mEnd(0),
+      mDirection(nsITextControlFrame::eForward) {}
+    bool IsDefault() const {
+      return mStart == 0 && mEnd == 0 &&
+             mDirection == nsITextControlFrame::eForward;
+    }
+    PRInt32 mStart, mEnd;
+    nsITextControlFrame::SelectionDirection mDirection;
+  };
+
+  PRBool IsSelectionCached() const { return mSelectionCached; }
+  SelectionProperties& GetSelectionProperties() {
+    return mSelectionProperties;
+  }
+  void WillInitEagerly() { mSelectionRestoreEagerInit = PR_TRUE; }
+  PRBool HasNeverInitializedBefore() const { return !mEverInited; }
+
 private:
   friend class RestoreSelectionState;
 
   // not copy constructible
   nsTextEditorState(const nsTextEditorState&);
   // not assignable
   void operator= (const nsTextEditorState&);
 
@@ -254,24 +277,27 @@ private:
     nsTextEditorState& mState;
     PRBool mGuardSet;
   };
   friend class InitializationGuard;
   friend class PrepareEditorEvent;
 
   nsITextControlElement* const mTextCtrlElement;
   nsRefPtr<nsTextInputSelectionImpl> mSelCon;
-  nsAutoPtr<SelectionState> mSelState;
   RestoreSelectionState* mRestoringSelection;
   nsCOMPtr<nsIEditor> mEditor;
   nsCOMPtr<nsIContent> mRootNode;
   nsCOMPtr<nsIContent> mPlaceholderDiv;
   nsTextControlFrame* mBoundFrame;
   nsTextInputListener* mTextListener;
   nsAutoPtr<nsCString> mValue;
   nsRefPtr<nsAnonDivObserver> mMutationObserver;
   mutable nsString mCachedValue; // Caches non-hard-wrapped value on a multiline control.
+  PRPackedBool mEverInited; // Have we ever been initialized?
   PRPackedBool mEditorInitialized;
   PRPackedBool mInitializing; // Whether we're in the process of initialization
   PRPackedBool mValueTransferInProgress; // Whether a value is being transferred to the frame
+  PRPackedBool mSelectionCached; // Whether mSelectionProperties is valid
+  mutable PRPackedBool mSelectionRestoreEagerInit; // Whether we're eager initing because of selection restore
+  SelectionProperties mSelectionProperties;
 };
 
 #endif
--- a/content/html/content/test/test_bug674558.html
+++ b/content/html/content/test/test_bug674558.html
@@ -41,105 +41,239 @@ SimpleTest.waitForFocus(function() {
 });
 
 function test(ctor) {
   var elem = ctor();
   ok(true, "Testing " + name(elem));
 
   ok("selectionDirection" in elem, "elem should have the selectionDirection property");
 
+  is(elem.selectionStart, 0, "Default value");
+  is(elem.selectionEnd, 0, "Default value");
+  is(elem.selectionDirection, "forward", "Default value");
+
   var content = document.getElementById("content");
   content.appendChild(elem);
 
+  function flush() { document.body.clientWidth; }
+  function hide() {
+    content.style.display = "none";
+    flush();
+  }
+  function show() {
+    content.style.display = "";
+    flush();
+  }
+
   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");
 
+  hide();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 3, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 3, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   // 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");
 
+  hide();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   // change the direction
   elem.selectionDirection = "backward";
 
   is(elem.selectionStart, 1, "Value unchanged");
   is(elem.selectionEnd, 4, "Value unchanged");
   is(elem.selectionDirection, "backward", "Correct value");
 
+  hide();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
   // 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");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
   elem.selectionEnd = 5;
 
   is(elem.selectionStart, 2, "Value unchanged");
   is(elem.selectionEnd, 5, "Correct value");
   is(elem.selectionDirection, "backward", "Still backward");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
   elem.selectionDirection = "none";
 
   is(elem.selectionStart, 2, "Value unchanged");
   is(elem.selectionEnd, 5, "Value unchanged");
   is(elem.selectionDirection, "forward", "none not supported");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   elem.selectionDirection = "backward";
 
   is(elem.selectionStart, 2, "Value unchanged");
   is(elem.selectionEnd, 5, "Value unchanged");
   is(elem.selectionDirection, "backward", "Correct Value");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
   elem.selectionDirection = "invalid";
 
   is(elem.selectionStart, 2, "Value unchanged");
   is(elem.selectionEnd, 5, "Value unchanged");
   is(elem.selectionDirection, "forward", "Treated as none");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   elem.selectionDirection = "backward";
 
   is(elem.selectionStart, 2, "Value unchanged");
   is(elem.selectionEnd, 5, "Value unchanged");
   is(elem.selectionDirection, "backward", "Correct Value");
 
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
   elem.setSelectionRange(1, 4);
 
   is(elem.selectionStart, 1, "Correct value");
   is(elem.selectionEnd, 4, "Correct value");
   is(elem.selectionDirection, "forward", "Correct value");
 
+  hide();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   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");
 
+  hide();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 1, "Value unchanged");
+  is(elem.selectionEnd, 4, "Value unchanged");
+  is(elem.selectionDirection, "forward", "Value unchanged");
+
   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");
+
+  hide();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
+
+  show();
+  is(elem.selectionStart, 2, "Value unchanged");
+  is(elem.selectionEnd, 5, "Value unchanged");
+  is(elem.selectionDirection, "backward", "Value unchanged");
 }
 
 function name(elem) {
   var tag = elem.localName;
   if (tag == "input") {
     tag += "[type=" + elem.type + "]";
   }
   return tag;
--- a/layout/forms/nsTextControlFrame.cpp
+++ b/layout/forms/nsTextControlFrame.cpp
@@ -430,16 +430,23 @@ nsTextControlFrame::CreateAnonymousConte
   }
 
   rv = UpdateValueDisplay(PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // textareas are eagerly initialized
   PRBool initEagerly = !IsSingleLineTextControl();
   if (!initEagerly) {
+    // Also, input elements which have a cached selection should get eager
+    // editor initialization.
+    nsCOMPtr<nsITextControlElement> txtCtrl = do_QueryInterface(GetContent());
+    NS_ASSERTION(txtCtrl, "Content not a text control element");
+    initEagerly = txtCtrl->HasCachedSelection();
+  }
+  if (!initEagerly) {
     nsCOMPtr<nsIDOMNSHTMLElement> element = do_QueryInterface(txtCtrl);
     if (element) {
       // so are input text controls with spellcheck=true
       element->GetSpellcheck(&initEagerly);
     }
   }
 
   if (initEagerly) {
--- a/layout/reftests/editor/reftest.list
+++ b/layout/reftests/editor/reftest.list
@@ -57,9 +57,10 @@ fails-if(Android) != spellcheck-hyphen-i
 fails-if(Android) != spellcheck-hyphen-multiple-invalid.html spellcheck-hyphen-multiple-invalid-ref.html
 == spellcheck-dotafterquote-valid.html spellcheck-dotafterquote-valid-ref.html
 == unneeded_scroll.html unneeded_scroll-ref.html
 == caret_on_presshell_reinit.html caret_on_presshell_reinit-ref.html
 == caret_on_presshell_reinit-2.html caret_on_presshell_reinit-ref.html
 == 642800.html 642800-ref.html
 == selection_visibility_after_reframe.html selection_visibility_after_reframe-ref.html
 != selection_visibility_after_reframe-2.html selection_visibility_after_reframe-ref.html
+!= selection_visibility_after_reframe-3.html selection_visibility_after_reframe-ref.html
 == 672709.html 672709-ref.html
copy from layout/reftests/editor/selection_visibility_after_reframe.html
copy to layout/reftests/editor/selection_visibility_after_reframe-3.html
--- a/layout/reftests/editor/selection_visibility_after_reframe.html
+++ b/layout/reftests/editor/selection_visibility_after_reframe.html
@@ -1,14 +1,15 @@
 <!DOCTYPE html>
 <html>
   <body>
     <input value="foo">
     <script>
       var i = document.querySelector("input");
       i.selectionStart = 1;
       i.selectionEnd = 2;
+      document.body.clientHeight;
       i.style.display = "none";
       document.body.clientHeight;
       i.style.display = "";
     </script>
   </body>
 </html>