diff --git a/content/html/content/src/HTMLInputElement.cpp b/content/html/content/src/HTMLInputElement.cpp index a4ea3bdc2e0d..25918ec8f1ef 100644 --- a/content/html/content/src/HTMLInputElement.cpp +++ b/content/html/content/src/HTMLInputElement.cpp @@ -1128,6 +1128,7 @@ HTMLInputElement::HTMLInputElement(already_AddRefed& aNo , mNumberControlSpinnerIsSpinning(false) , mNumberControlSpinnerSpinsUp(false) , mPickerRunning(false) + , mSelectionCached(true) { // We are in a type=text so we now we currenty need a nsTextEditorState. mInputData.mState = new nsTextEditorState(this); diff --git a/content/html/content/src/HTMLInputElement.h b/content/html/content/src/HTMLInputElement.h index 4ae24b770f3c..ddc8a2ade518 100644 --- a/content/html/content/src/HTMLInputElement.h +++ b/content/html/content/src/HTMLInputElement.h @@ -22,12 +22,12 @@ #include "nsIContentPrefService2.h" #include "mozilla/Decimal.h" #include "nsContentUtils.h" +#include "nsTextEditorState.h" class nsDOMFileList; class nsIRadioGroupContainer; class nsIRadioGroupVisitor; class nsIRadioVisitor; -class nsTextEditorState; namespace mozilla { @@ -250,6 +250,28 @@ public: void MaybeLoadImage(); + void SetSelectionProperties(const nsTextEditorState::SelectionProperties& aProps) + { + MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER); + mSelectionCached = true; + mSelectionProperties = aProps; + } + bool IsSelectionCached() const + { + MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER); + return mSelectionCached; + } + void ClearSelectionCached() + { + MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER); + mSelectionCached = false; + } + nsTextEditorState::SelectionProperties& GetSelectionProperties() + { + MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER); + return mSelectionProperties; + } + // nsITimerCallback NS_DECL_NSITIMERCALLBACK @@ -1255,6 +1277,13 @@ protected: */ nsCOMPtr mProgressTimer; + /** + * The selection properties cache for number controls. This is needed because + * the number controls don't recycle their text field, so the normal cache in + * nsTextEditorState cannot do its job. + */ + nsTextEditorState::SelectionProperties mSelectionProperties; + // Step scale factor values, for input types that have one. static const Decimal kStepScaleFactorDate; static const Decimal kStepScaleFactorNumberRange; @@ -1295,6 +1324,7 @@ protected: bool mNumberControlSpinnerIsSpinning : 1; bool mNumberControlSpinnerSpinsUp : 1; bool mPickerRunning : 1; + bool mSelectionCached : 1; private: static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, diff --git a/content/html/content/src/moz.build b/content/html/content/src/moz.build index a793993d9894..1e45b0a13e87 100644 --- a/content/html/content/src/moz.build +++ b/content/html/content/src/moz.build @@ -8,6 +8,7 @@ EXPORTS += [ 'HTMLPropertiesCollection.h', 'nsGenericHTMLElement.h', 'nsHTMLDNSPrefetch.h', + 'nsTextEditorState.h', ] EXPORTS.mozilla.dom += [ diff --git a/content/html/content/src/nsTextEditorState.cpp b/content/html/content/src/nsTextEditorState.cpp index 39e8d74c0029..c161258c5090 100644 --- a/content/html/content/src/nsTextEditorState.cpp +++ b/content/html/content/src/nsTextEditorState.cpp @@ -43,6 +43,8 @@ #include "nsIController.h" #include "mozilla/TextEvents.h" #include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/HTMLInputElement.h" +#include "nsNumberControlFrame.h" using namespace mozilla; using namespace mozilla::dom; @@ -1423,7 +1425,8 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) newEditor->AddEditorObserver(mTextListener); // Restore our selection after being bound to a new frame - if (mSelectionCached) { + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number ? number->IsSelectionCached() : mSelectionCached) { if (mRestoringSelection) // paranoia mRestoringSelection->Revoke(); mRestoringSelection = new RestoreSelectionState(this, mBoundFrame); @@ -1433,11 +1436,66 @@ nsTextEditorState::PrepareEditor(const nsAString *aValue) } // The selection cache is no longer going to be valid - mSelectionCached = false; + if (number) { + number->ClearSelectionCached(); + } else { + mSelectionCached = false; + } return rv; } +bool +nsTextEditorState::IsSelectionCached() const +{ + if (mBoundFrame) { + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number) { + return number->IsSelectionCached(); + } + } + return mSelectionCached; +} + +nsTextEditorState::SelectionProperties& +nsTextEditorState::GetSelectionProperties() +{ + if (mBoundFrame) { + HTMLInputElement* number = GetParentNumberControl(mBoundFrame); + if (number) { + return number->GetSelectionProperties(); + } + } + return mSelectionProperties; +} + +HTMLInputElement* +nsTextEditorState::GetParentNumberControl(nsFrame* aFrame) const +{ + MOZ_ASSERT(aFrame); + nsIContent* content = aFrame->GetContent(); + MOZ_ASSERT(content); + nsIContent* parent = content->GetParent(); + if (!parent) { + return nullptr; + } + nsIContent* parentOfParent = parent->GetParent(); + if (!parentOfParent) { + return nullptr; + } + HTMLInputElement* input = HTMLInputElement::FromContent(parentOfParent); + if (input) { + // This function might be called during frame reconstruction as a result + // of changing the input control's type from number to something else. In + // that situation, the type of the control has changed, but its frame has + // not been reconstructed yet. So we need to check the type of the input + // control in addition to the type of the frame. + return (input->GetType() == NS_FORM_INPUT_NUMBER) ? input : nullptr; + } + + return nullptr; +} + void nsTextEditorState::DestroyEditor() { @@ -1478,10 +1536,21 @@ nsTextEditorState::UnbindFromFrame(nsTextControlFrame* aFrame) // GetSelectionRange before calling DestroyEditor, and only if // mEditorInitialized indicates that we actually have an editor available. if (mEditorInitialized) { - mBoundFrame->GetSelectionRange(&mSelectionProperties.mStart, - &mSelectionProperties.mEnd, - &mSelectionProperties.mDirection); - mSelectionCached = true; + HTMLInputElement* number = GetParentNumberControl(aFrame); + if (number) { + // If we are inside a number control, cache the selection on the + // parent control, because this text editor state will be destroyed + // together with the native anonymous text control. + SelectionProperties props; + mBoundFrame->GetSelectionRange(&props.mStart, &props.mEnd, + &props.mDirection); + number->SetSelectionProperties(props); + } else { + mBoundFrame->GetSelectionRange(&mSelectionProperties.mStart, + &mSelectionProperties.mEnd, + &mSelectionProperties.mDirection); + mSelectionCached = true; + } } // Destroy our editor diff --git a/content/html/content/src/nsTextEditorState.h b/content/html/content/src/nsTextEditorState.h index 9a2fcc58080d..3e4f99292f68 100644 --- a/content/html/content/src/nsTextEditorState.h +++ b/content/html/content/src/nsTextEditorState.h @@ -23,6 +23,13 @@ class nsISelectionController; class nsFrameSelection; class nsIEditor; class nsITextControlElement; +class nsFrame; + +namespace mozilla { +namespace dom { +class HTMLInputElement; +} +} /** * nsTextEditorState is a class which is responsible for managing the state of @@ -203,10 +210,8 @@ public: nsITextControlFrame::SelectionDirection mDirection; }; - bool IsSelectionCached() const { return mSelectionCached; } - SelectionProperties& GetSelectionProperties() { - return mSelectionProperties; - } + bool IsSelectionCached() const; + SelectionProperties& GetSelectionProperties(); void WillInitEagerly() { mSelectionRestoreEagerInit = true; } bool HasNeverInitializedBefore() const { return !mEverInited; } @@ -235,6 +240,8 @@ private: void FinishedRestoringSelection() { mRestoringSelection = nullptr; } + mozilla::dom::HTMLInputElement* GetParentNumberControl(nsFrame* aFrame) const; + class InitializationGuard { public: explicit InitializationGuard(nsTextEditorState& aState) :